mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-10 10:01:43 -10:00
feat(plugins): uplift the Native DSH host to 0.2.2-rc.1 / Cordis 4.1.0 (CW-7051)
Decision 91 makes DSH 0.2.2-rc.1 the compatibility target and requires the host uplift before any plugin receipt. Upstream tag dsh-v0.2.2-rc.1 resolves to 66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db locally and on GitHub. Cordis 4.1.0, cosmokit 1.9.0, schemastery 3.19.0 and dsh-util-values 0.2.2-rc.1 are pinned exactly; lockfile integrities equal the npm registry and the fetched tarballs. Loader, include and group 1.1.0 are rebased with a three-way merge from the recorded rc.5 sources; every prior source/candidate hash verified before the merge. Local adaptations kept: reviewed-closure imports, the disabled native-internals probe, the erased FiberState values (still a const enum in 4.1.0) and strict-mode casts. Two adaptations follow upstream API changes: tree persistence moved from write() to commit(change), so the reviewed tree's no-op moved with it, and service providers moved from impl.fiber to impl.ctx.fiber in the leaked-service audit. One new delta: bare specifiers keep the native import fallback instead of upstream createResolve(), which writes a .cordis/resolve.mjs helper into the package scope and imports it. Include's new journal.ts and patch.ts are vendored with type-only casts; resolve.ts is not vendored. The dsh-tools definition excerpt is re-taken from rc.1's split build: the JSON-schema region and error tail are byte-identical (now in the shared chunk) and the schema region differs by one doc line and an added pure projectToolSchema helper, which is not exported. The six pure message excerpts and deepFreeze are byte-identical at their new line numbers. Hooks and persona sources are byte-identical; dsh-system-prompt's prompt presentation split is recorded but not imported. Injectable and refused services are unchanged; no new service is bridged. The compatibility digest changes, so earlier Native review and trust must be renewed; no live trust state was edited. The host stays experimental and off by default. Checks: typecheck and build pass. Node 413 pass, 7 skip; Bun 1.4.0 414 pass, 6 skip (420 each, matching the rc.5 receipt). Four deferLoading/when definition probes match rc.5 on both runtimes. Rust crates are unchanged here; plugin and extension-host Rust tests are recorded separately. No hosted CI, packaged install, live plugin or provider call is claimed. Receipts: artifacts/dsh-plugins-20261109/uplift/. Co-Authored-By: Claude Fable 5.2 <[email protected]>
This commit is contained in:
1 parent
b3e874b8fb
commit
7d6779d346
38 files changed
+8154
-5356
No files matched your search
+11
-7
@@ -224,7 +224,7 @@ tag. Items moved to 0.10.4 by name are marked below.
|
||||
- **Change mode and permissions while the agent works:** Tab, Shift+Tab, `/mode`, the hotbar and `/config mode` no longer refuse while a turn is running. A response that is already streaming is not restarted; the change applies at the agent's next step in the same turn (before its next tool batch, at every approval gate, and before its next model request, which carries the new mode notice). The status line says so until it applies. A call still waiting for approval goes back to the model under the new posture, and a call you already approved runs only if the new posture is equal or broader. Model, thinking level and provider stay locked until the turn ends.
|
||||
- **Steering interrupts thinking:** a steer (Ctrl+Enter, or Enter twice after queueing) now closes the in-flight model request at once, whether the model is waiting on headers, reasoning, writing text or emitting a tool call, and continues the same turn with your message. Text already shown is kept as interrupted output; unfinished reasoning and partial tool calls are discarded, never run.
|
||||
- **Modern MCP client:** MCP servers now connect through the official MCP SDK (2.4) behind Codewhale's Rust broker by default (`mcp_backend = "host"`; `"rust"` stays available for one release). Servers that speak the 2026-07-28 revision are discovered with `server/discover`; other stdio and HTTP servers fall back to the legacy `initialize` handshake, and configured SSE servers keep theirs. Rust keeps process, network, credential, catalog, approval and cancellation authority.
|
||||
- **DeepSeek Harness plugins:** reviewed DSH plugins (definition API pinned to DSH 0.2.1-rc.5 in a shared compatibility table) run in the Native extension host. Tool output is validated against its declared schema, unsupported options are refused instead of ignored, and plugin commands can submit a prompt with visible plugin attribution. Terminal command receipts are kept in thread snapshots without entering model history. Changing the compatibility table invalidates earlier trust. A newly enabled plugin's tools are offered on the first turn: the turn waits up to 10 seconds for the plugin host to finish loading instead of adding them only from the next turn. The importer reports that it does not convert `dsh.client` UI; Engine routes and a desktop slot exist, but isolated end-to-end plugin UI remains unqualified, including network containment. A plugin tool can call `ctx.sessions.flush(exec.agent.session)` to have the Engine save the conversation so far before it continues (Runtime, terminal and `exec` sessions); it reports failure instead of claiming a save. Installing a plugin whose bytes changed after review now returns a 409 conflict instead of a server error. A plugin tool can ask you a question with `ctx.userQuestions.ask` and gets back the options you picked, your own text, or a skip; the question shows like any other Codewhale question in the terminal and through the Runtime API. A plugin tool can start a child agent with `ctx.subagents.startActivation` (the `spawn` provider): the child runs through Codewhale's own sub-agent manager, needs the same approval as the model's `agent` tool, can carry an extra persona section and a narrower tool list set in its `agent/created` handler, and returns its result to the plugin.
|
||||
- **DeepSeek Harness plugins:** reviewed DSH plugins (definition API pinned to DSH 0.2.2-rc.1 in a shared compatibility table) run in the Native extension host. Tool output is validated against its declared schema, unsupported options are refused instead of ignored, and plugin commands can submit a prompt with visible plugin attribution. Terminal command receipts are kept in thread snapshots without entering model history. Changing the compatibility table invalidates earlier trust. A newly enabled plugin's tools are offered on the first turn: the turn waits up to 10 seconds for the plugin host to finish loading instead of adding them only from the next turn. The importer reports that it does not convert `dsh.client` UI; Engine routes and a desktop slot exist, but isolated end-to-end plugin UI remains unqualified, including network containment. A plugin tool can call `ctx.sessions.flush(exec.agent.session)` to have the Engine save the conversation so far before it continues (Runtime, terminal and `exec` sessions); it reports failure instead of claiming a save. Installing a plugin whose bytes changed after review now returns a 409 conflict instead of a server error. A plugin tool can ask you a question with `ctx.userQuestions.ask` and gets back the options you picked, your own text, or a skip; the question shows like any other Codewhale question in the terminal and through the Runtime API. A plugin tool can start a child agent with `ctx.subagents.startActivation` (the `spawn` provider): the child runs through Codewhale's own sub-agent manager, needs the same approval as the model's `agent` tool, can carry an extra persona section and a narrower tool list set in its `agent/created` handler, and returns its result to the plugin.
|
||||
- **MCP catalogs stay fresh:** a server's tools, resources, prompts and skills are rediscovered on their own connection when an advertised `ttlMs` expires or the server sends a list-changed notice. A tool resolved against the old catalog is refused with "retry the call", a removed tool cannot run, and a failed refresh reconnects instead of serving stale entries. Catalogs are never shared across servers, credentials or reconnects.
|
||||
- **MCP Skills extension:** servers that advertise the published Skills extension have their skills listed in `list_mcp_resources`. Listing never activates a skill, and reading a skill file is refused unless its size and sha256 digest match the listing. MCP skills do not yet join `/skills`.
|
||||
- **Updates install themselves:** release-binary installs check for a new release every 6 hours (was 1), download and checksum-verify it in the background, and apply it the next time `codewhale` starts, never during a session. One line says `Updated to v… · What's new: … · Undo: codewhale update --rollback`. Until releases are signed, the download waits for your OK (one `[y/N]` question at the next start, or `/update install`); a release with a valid Ed25519 manifest signature applies on its own. `codewhale update --rollback` puts back the version the last update replaced and keeps the rolled-back version from returning automatically. Turn it down with `codewhale config set update.mode notify` (or `off`, or `CODEWHALE_UPDATE_MODE`); an organization's root-owned `policy.toml` overrides both. Offline and rate-limited checks retry about an hour later instead of on every launch. Package-manager and desktop-app copies only get their manager's command. Builds with the release key refuse a missing or invalid signature, and re-check the staged binary against the signed manifest at start. The checksum manifest now always comes from GitHub Releases; on Linux x64 the CNB mirror can still serve the binary, but only bytes that GitHub's manifest lists.
|
||||
@@ -363,12 +363,16 @@ tag. Items moved to 0.10.4 by name are marked below.
|
||||
- **Cleaner, safer Markdown export:** headings no longer carry `Content N:` numbering, a text block that opens a message is bare, tool results show their tool name and appear under a `tool` role instead of `user`, and `Error:` appears only on an error. Tool output is now fenced, so a tool result can no longer forge a heading or a message in the transcript. A trailing `--force` is a flag rather than part of the path. `/share` now publishes the same HTML page as `/export file x.html`, still as a redacted secret gist after confirmation.
|
||||
- Internal coarse split: Runtime owns bounded owner work, Paths owns the protected socket layout, and Protocol owns shared avatar data. App Server now owns the Runtime host and control transports previously embedded in the TUI. Engine and Sandbox do not depend on App Server; the remaining Stage B boundaries stay open (#5586).
|
||||
- **Newer DeepSeek Harness plugin compatibility:** the Native host targets
|
||||
DSH 0.2.1-rc.5 with Cordis 4.0.5 and refreshed reviewed composition
|
||||
packages. Boolean `deferLoading` hints are accepted while the Engine keeps
|
||||
tool-loading policy; `when` remains refused. Service metadata distinguishes
|
||||
DSH, Cordis and Codewhale services. The changed compatibility digest requires
|
||||
fresh plugin review and trust. The legacy preset adapter keeps its recorded
|
||||
source pin; this is local compatibility evidence, not release qualification.
|
||||
DSH 0.2.2-rc.1 with Cordis 4.1.0 and refreshed reviewed composition
|
||||
packages (loader, include and group 1.1.0). Reviewed compositions are still
|
||||
never rewritten, and plugin module loading still goes only through the
|
||||
reviewed closure. Boolean `deferLoading` hints are accepted while the Engine
|
||||
keeps tool-loading policy; `when` remains refused. The services a plugin may
|
||||
inject are unchanged, so plugins needing `llm`, `fs`, `subprocess`,
|
||||
`configEditor` or `dsh.client` still do not run. The changed compatibility
|
||||
digest requires fresh plugin review and trust. The legacy preset adapter keeps
|
||||
its recorded source pin; this is local compatibility evidence, not release
|
||||
qualification.
|
||||
- **Goal tool guidance while awaiting an answer (#7177):** the tool schema now
|
||||
tells the model to yield when it needs the user’s answer and reserves blocked
|
||||
for stalled progress. Existing runtime semantics are unchanged; no live-model
|
||||
|
||||
+24
-23
@@ -89,9 +89,9 @@ The embedded extension host bundle
|
||||
(`crates/engine/extension-host/dist/codewhale-extension-host.mjs`) contains these
|
||||
packages, as published by DeepSeek Harness:
|
||||
|
||||
- `@deepseek-ai/cordis` 4.0.5
|
||||
- `@deepseek-ai/schemastery` 3.18.5
|
||||
- `@deepseek-ai/cosmokit` 1.8.6
|
||||
- `@deepseek-ai/cordis` 4.1.0
|
||||
- `@deepseek-ai/schemastery` 3.19.0
|
||||
- `@deepseek-ai/cosmokit` 1.9.0
|
||||
|
||||
Upstream: <https://github.com/deepseek-ai/deepseek-harness> (`vendor/cordis`,
|
||||
`vendor/schemastery`, `vendor/cosmokit`)
|
||||
@@ -122,15 +122,16 @@ SOFTWARE.
|
||||
|
||||
## DeepSeek Harness utility packages (`dsh-util-values`, `dsh-tools`, message compatibility) — MIT
|
||||
|
||||
The same bundle contains `@deepseek-ai/dsh-util-values` 0.2.1-rc.5, and
|
||||
The same bundle contains `@deepseek-ai/dsh-util-values` 0.2.2-rc.1, and
|
||||
`crates/engine/extension-host/src/dsh/dsh-tools-compat.js` contains verbatim
|
||||
excerpts of `@deepseek-ai/dsh-tools` 0.2.1-rc.5 (`lib/index.js`), under the
|
||||
same licence text. `crates/engine/extension-host/src/dsh/dsh-message-compat.js`
|
||||
contains verbatim excerpts of `@deepseek-ai/dsh-brand` 0.2.1-rc.5,
|
||||
`@deepseek-ai/dsh-llm` 0.2.1-rc.5, `@deepseek-ai/dsh-session` 0.2.1-rc.5,
|
||||
`@deepseek-ai/dsh-system-prompt` 0.2.1-rc.5,
|
||||
`@deepseek-ai/dsh-user-questions` 0.2.1-rc.5 and
|
||||
`@deepseek-ai/dsh-util-crypto` 0.2.1-rc.5, under the same licence text.
|
||||
excerpts of `@deepseek-ai/dsh-tools` 0.2.2-rc.1 (`lib/index.js` and its shared
|
||||
`lib/shared-DTdn5dXO.js` chunk), under the same licence text.
|
||||
`crates/engine/extension-host/src/dsh/dsh-message-compat.js` contains verbatim
|
||||
excerpts of `@deepseek-ai/dsh-brand` 0.2.2-rc.1, `@deepseek-ai/dsh-llm`
|
||||
0.2.2-rc.1, `@deepseek-ai/dsh-session` 0.2.2-rc.1,
|
||||
`@deepseek-ai/dsh-system-prompt` 0.2.2-rc.1,
|
||||
`@deepseek-ai/dsh-user-questions` 0.2.2-rc.1 and
|
||||
`@deepseek-ai/dsh-util-crypto` 0.2.2-rc.1, under the same licence text.
|
||||
|
||||
Upstream: <https://github.com/deepseek-ai/deepseek-harness>
|
||||
(`dsh-util-values` is published from `packages/util/values`)
|
||||
@@ -750,7 +751,7 @@ The host and its offline composition reviewer also contain these packages.
|
||||
Vendored composition adaptations are recorded in
|
||||
`crates/engine/extension-host/src/dsh/upstream/UPSTREAM.json`.
|
||||
|
||||
### `@deepseek-ai/cordis-plugin-group` 1.0.5
|
||||
### `@deepseek-ai/cordis-plugin-group` 1.1.0
|
||||
|
||||
```text
|
||||
MIT License
|
||||
@@ -776,7 +777,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
### `@deepseek-ai/cordis-plugin-include` 1.0.10
|
||||
### `@deepseek-ai/cordis-plugin-include` 1.1.0
|
||||
|
||||
```text
|
||||
MIT License
|
||||
@@ -802,7 +803,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
### `@deepseek-ai/cordis-plugin-loader` 1.0.6
|
||||
### `@deepseek-ai/cordis-plugin-loader` 1.1.0
|
||||
|
||||
```text
|
||||
MIT License
|
||||
@@ -888,19 +889,19 @@ THE SOFTWARE.
|
||||
The embedded extension host includes these pinned DeepSeek Harness packages:
|
||||
|
||||
- `@deepseek-ai/dsh-agent-presets` 0.1.6-alpha.1
|
||||
- `@deepseek-ai/dsh-hook-protocol` 0.2.1-rc.5
|
||||
- `@deepseek-ai/dsh-hooks-claude-code` 0.2.1-rc.5
|
||||
- `@deepseek-ai/dsh-hooks-codex` 0.2.1-rc.5
|
||||
- `@deepseek-ai/dsh-persona` 0.2.1-rc.5
|
||||
- `@deepseek-ai/dsh-system-prompt` 0.2.1-rc.5
|
||||
- `@deepseek-ai/dsh-hook-protocol` 0.2.2-rc.1
|
||||
- `@deepseek-ai/dsh-hooks-claude-code` 0.2.2-rc.1
|
||||
- `@deepseek-ai/dsh-hooks-codex` 0.2.2-rc.1
|
||||
- `@deepseek-ai/dsh-persona` 0.2.2-rc.1
|
||||
- `@deepseek-ai/dsh-system-prompt` 0.2.2-rc.1
|
||||
|
||||
Upstream: <https://github.com/deepseek-ai/deepseek-harness>, tag
|
||||
`dsh-v0.2.1-rc.5`, commit `b830ec950faba0a46dda564be3e2d854afed94f1`.
|
||||
`dsh-v0.2.2-rc.1`, commit `66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db`.
|
||||
The legacy `dsh-agent-presets` adapter alone retains commit
|
||||
`0d1f50007f9bca3f52b06e1c3074fa14d5fb0720`; the newer preset runtime packages
|
||||
are not bundled. The reviewed composition loader is refreshed to the rc.5
|
||||
source: `cordis-plugin-loader` 1.0.6, `cordis-plugin-include` 1.0.10 and
|
||||
`cordis-plugin-group` 1.0.5 (MIT, Shigma).
|
||||
are not bundled. The reviewed composition loader is refreshed to the rc.1
|
||||
source: `cordis-plugin-loader` 1.1.0, `cordis-plugin-include` 1.1.0 and
|
||||
`cordis-plugin-group` 1.1.0 (MIT, Shigma).
|
||||
|
||||
Preset discovery and metadata, hook matching and dialect codecs, and additive
|
||||
persona templates run through receipt-backed inputs. Core retains selection,
|
||||
|
||||
+46
-46
@@ -2,26 +2,26 @@ Third-party software bundled into codewhale-extension-host.mjs, dsh-composition-
|
||||
Generated by crates/engine/extension-host/build.mjs from the bundler metafile; do not edit.
|
||||
|
||||
Packages:
|
||||
@deepseek-ai/cordis@4.0.5 (MIT)
|
||||
@deepseek-ai/cordis-plugin-group@1.0.5 (MIT)
|
||||
@deepseek-ai/cordis-plugin-include@1.0.10 (MIT)
|
||||
@deepseek-ai/cordis-plugin-loader@1.0.6 (MIT)
|
||||
@deepseek-ai/cosmokit@1.8.6 (MIT)
|
||||
@deepseek-ai/cordis@4.1.0 (MIT)
|
||||
@deepseek-ai/cordis-plugin-group@1.1.0 (MIT)
|
||||
@deepseek-ai/[email protected].0 (MIT)
|
||||
@deepseek-ai/cordis-plugin-loader@1.1.0 (MIT)
|
||||
@deepseek-ai/cosmokit@1.9.0 (MIT)
|
||||
@deepseek-ai/[email protected] (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected]8.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
@deepseek-ai/[email protected]9.0 (MIT)
|
||||
@modelcontextprotocol/[email protected] (Apache-2.0)
|
||||
@modelcontextprotocol/[email protected] (Apache-2.0)
|
||||
[email protected] (MIT)
|
||||
@@ -34,7 +34,7 @@ Packages:
|
||||
[email protected] (MIT)
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/cordis@4.0.5 (MIT)
|
||||
@deepseek-ai/cordis@4.1.0 (MIT)
|
||||
========================================================================
|
||||
|
||||
MIT License
|
||||
@@ -60,10 +60,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/cordis-plugin-group@1.0.5 (MIT)
|
||||
@deepseek-ai/cordis-plugin-group@1.1.0 (MIT)
|
||||
========================================================================
|
||||
|
||||
Pinned deepseek-harness b830ec950faba0a46dda564be3e2d854afed94f1. Loader native-internals probe disabled; reviewed imports and non-writing composition retain Codewhale's authority boundary.
|
||||
Pinned deepseek-harness 66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db. Loader native-internals probe disabled; reviewed imports and non-writing composition retain Codewhale's authority boundary.
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -88,10 +88,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/cordis-plugin-include@1.0.10 (MIT)
|
||||
@deepseek-ai/[email protected].0 (MIT)
|
||||
========================================================================
|
||||
|
||||
Pinned deepseek-harness b830ec950faba0a46dda564be3e2d854afed94f1. Loader native-internals probe disabled; reviewed imports and non-writing composition retain Codewhale's authority boundary.
|
||||
Pinned deepseek-harness 66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db. Loader native-internals probe disabled; reviewed imports and non-writing composition retain Codewhale's authority boundary.
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -116,10 +116,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/cordis-plugin-loader@1.0.6 (MIT)
|
||||
@deepseek-ai/cordis-plugin-loader@1.1.0 (MIT)
|
||||
========================================================================
|
||||
|
||||
Pinned deepseek-harness b830ec950faba0a46dda564be3e2d854afed94f1. Loader native-internals probe disabled; reviewed imports and non-writing composition retain Codewhale's authority boundary.
|
||||
Pinned deepseek-harness 66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db. Loader native-internals probe disabled; reviewed imports and non-writing composition retain Codewhale's authority boundary.
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -144,7 +144,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/cosmokit@1.8.6 (MIT)
|
||||
@deepseek-ai/cosmokit@1.9.0 (MIT)
|
||||
========================================================================
|
||||
|
||||
MIT License
|
||||
@@ -198,7 +198,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
Verbatim excerpts of this package are in src/dsh/dsh-message-compat.js, under the same licence text as @deepseek-ai/dsh-util-values above.
|
||||
@@ -226,10 +226,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
Pinned deepseek-harness b830ec950faba0a46dda564be3e2d854afed94f1. Pure matcher/codec/merge and configuration parsers reused; local imports and session-writer type declarations adapted. Core retains scheduling and steering.
|
||||
Pinned deepseek-harness 66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db. Pure matcher/codec/merge and configuration parsers reused; local imports and session-writer type declarations adapted. Core retains scheduling and steering.
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -254,10 +254,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
Pinned deepseek-harness b830ec950faba0a46dda564be3e2d854afed94f1. Pure matcher/codec/merge and configuration parsers reused; local imports and session-writer type declarations adapted. Core retains scheduling and steering.
|
||||
Pinned deepseek-harness 66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db. Pure matcher/codec/merge and configuration parsers reused; local imports and session-writer type declarations adapted. Core retains scheduling and steering.
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -282,10 +282,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
Pinned deepseek-harness b830ec950faba0a46dda564be3e2d854afed94f1. Pure matcher/codec/merge and configuration parsers reused; local imports and session-writer type declarations adapted. Core retains scheduling and steering.
|
||||
Pinned deepseek-harness 66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db. Pure matcher/codec/merge and configuration parsers reused; local imports and session-writer type declarations adapted. Core retains scheduling and steering.
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -310,7 +310,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
Verbatim excerpts of this package are in src/dsh/dsh-message-compat.js, under the same licence text as @deepseek-ai/dsh-util-values above.
|
||||
@@ -338,10 +338,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
Pinned deepseek-harness b830ec950faba0a46dda564be3e2d854afed94f1. Additive persona configuration and strict simple template mapping adapted in src/dsh/persona.ts and the Rust prompt renderer; source and license receipts: src/dsh/persona.UPSTREAM.json. Core retains prompt assembly and accepted model/workspace values.
|
||||
Pinned deepseek-harness 66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db. Additive persona configuration and strict simple template mapping adapted in src/dsh/persona.ts and the Rust prompt renderer; source and license receipts: src/dsh/persona.UPSTREAM.json. Core retains prompt assembly and accepted model/workspace values.
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -366,7 +366,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
Verbatim excerpts of this package are in src/dsh/dsh-message-compat.js, under the same licence text as @deepseek-ai/dsh-util-values above.
|
||||
@@ -394,7 +394,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
Verbatim excerpts of this package are in src/dsh/dsh-message-compat.js, under the same licence text as @deepseek-ai/dsh-util-values above.
|
||||
@@ -422,10 +422,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
Pinned deepseek-harness b830ec950faba0a46dda564be3e2d854afed94f1. Additive persona configuration and strict simple template mapping adapted in src/dsh/persona.ts and the Rust prompt renderer; source and license receipts: src/dsh/persona.UPSTREAM.json. Core retains prompt assembly and accepted model/workspace values.
|
||||
Pinned deepseek-harness 66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db. Additive persona configuration and strict simple template mapping adapted in src/dsh/persona.ts and the Rust prompt renderer; source and license receipts: src/dsh/persona.UPSTREAM.json. Core retains prompt assembly and accepted model/workspace values.
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -450,7 +450,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
Verbatim excerpts of this package are in src/dsh/dsh-tools-compat.js, under the same licence text as @deepseek-ai/dsh-util-values above.
|
||||
@@ -478,7 +478,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
Verbatim excerpts of this package are in src/dsh/dsh-message-compat.js, under the same licence text as @deepseek-ai/dsh-util-values above.
|
||||
@@ -506,7 +506,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
Verbatim excerpts of this package are in src/dsh/dsh-message-compat.js, under the same licence text as @deepseek-ai/dsh-util-values above.
|
||||
@@ -534,7 +534,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected].1-rc.5 (MIT)
|
||||
@deepseek-ai/[email protected].2-rc.1 (MIT)
|
||||
========================================================================
|
||||
|
||||
MIT License
|
||||
@@ -560,7 +560,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
========================================================================
|
||||
@deepseek-ai/[email protected]8.5 (MIT)
|
||||
@deepseek-ai/[email protected]9.0 (MIT)
|
||||
========================================================================
|
||||
|
||||
MIT License
|
||||
|
||||
+1660
-1039
File diff suppressed because it is too large.
Load diff
+824
-751
File diff suppressed because it is too large.
Load diff
+1752
-1150
File diff suppressed because it is too large.
Load diff
+1660
-1039
File diff suppressed because it is too large.
Load diff
+824
-751
File diff suppressed because it is too large.
Load diff
+21
-21
@@ -9,10 +9,10 @@
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/cordis": "4.0.5",
|
||||
"@deepseek-ai/cosmokit": "1.8.6",
|
||||
"@deepseek-ai/dsh-util-values": "0.2.1-rc.5",
|
||||
"@deepseek-ai/schemastery": "3.18.5",
|
||||
"@deepseek-ai/cordis": "4.1.0",
|
||||
"@deepseek-ai/cosmokit": "1.9.0",
|
||||
"@deepseek-ai/dsh-util-values": "0.2.2-rc.1",
|
||||
"@deepseek-ai/schemastery": "3.19.0",
|
||||
"@modelcontextprotocol/client": "2.4.0",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "22.20.4",
|
||||
@@ -29,21 +29,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@deepseek-ai/cordis": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@deepseek-ai/cordis/-/cordis-4.0.5.tgz",
|
||||
"integrity": "sha512-Sktq5axy0owk9PdnivBRNOJjHEVEibCt0+TDDZcv0+UH2KS1M910fX4kn5y45YFsrpZIi1mJf/1s5VzywMUnyg==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@deepseek-ai/cordis/-/cordis-4.1.0.tgz",
|
||||
"integrity": "sha512-CJrjNyBkKn1SfvBQUcgFYKFnLjS+uKrhdMsbHU60xabvi12d+xQ+IQSNlME5Z+B+4Zi6n4BDknKY94tEPeo47w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@deepseek-ai/cosmokit": "~1.8.6",
|
||||
"@deepseek-ai/cosmokit": "~1.9.0",
|
||||
"@standard-schema/spec": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"cordis": "bin.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/cordis-plugin-include": "~1.0.10",
|
||||
"@deepseek-ai/cordis-plugin-loader": "~1.0.6"
|
||||
"@deepseek-ai/cordis-plugin-include": "~1.1.0",
|
||||
"@deepseek-ai/cordis-plugin-loader": "~1.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@deepseek-ai/cordis-plugin-include": {
|
||||
@@ -55,30 +55,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@deepseek-ai/cosmokit": {
|
||||
"version": "1.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@deepseek-ai/cosmokit/-/cosmokit-1.8.6.tgz",
|
||||
"integrity": "sha512-ZSD/fN52L498LBIOYwjIOlaLv5veY6ZEwI2yzvZUKQtctn0QAPqu8tKdJ+OF5+Tv6NBq1U+wJWH+QI6OGy5EvQ==",
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@deepseek-ai/cosmokit/-/cosmokit-1.9.0.tgz",
|
||||
"integrity": "sha512-5O1S53mZiJyZYcvxxD0Qv1EY7qFPSxbGyD8CbVK6gw7W9FvcXZj2YIX1ZkOqyQQ3Xt2rFkZDHJv0+V3Zmmp8CA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@deepseek-ai/dsh-util-values": {
|
||||
"version": "0.2.1-rc.5",
|
||||
"resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-util-values/-/dsh-util-values-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-PL8TerJM/4jpZ2ArwgTpFbeDiAVnqkSh8Xinw2iEu8B6a+axmshzL4dCpwpf+uB8GHdXAhGz1cT+SBQwX/2TLQ==",
|
||||
"version": "0.2.2-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@deepseek-ai/dsh-util-values/-/dsh-util-values-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-UN4B/Fa9zAunYN3UuXDQ3QknxF/lMKNwTu2WxXKKEC+Ejz745po7I/JmsVFVa7+socfuwwTmVrfSsddRVU+lew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/cordis": "~4.0.5"
|
||||
"@deepseek-ai/cordis": "~4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@deepseek-ai/schemastery": {
|
||||
"version": "3.18.5",
|
||||
"resolved": "https://registry.npmjs.org/@deepseek-ai/schemastery/-/schemastery-3.18.5.tgz",
|
||||
"integrity": "sha512-Mnobvqf9rHsZYv4g5edj5ddxS1oF1kIdSlz0DunUgnY3LWG5F26MqvH3YOOsSaodCgyzJ16OMN9O99/s5Crtdw==",
|
||||
"version": "3.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@deepseek-ai/schemastery/-/schemastery-3.19.0.tgz",
|
||||
"integrity": "sha512-bDXoYQa7ghAhmdYD5VPlzKxEzJV/aKkz5l4V5h1u06z8GAPN1ROewSjvR9WiXhpa52E9fng8lpH5QdCRIQgnyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@deepseek-ai/cosmokit": "~1.8.6",
|
||||
"@deepseek-ai/cosmokit": "~1.9.0",
|
||||
"@standard-schema/spec": "^1.1.0"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
"compile": "node compile-host.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/cordis": "4.0.5",
|
||||
"@deepseek-ai/cosmokit": "1.8.6",
|
||||
"@deepseek-ai/dsh-util-values": "0.2.1-rc.5",
|
||||
"@deepseek-ai/schemastery": "3.18.5",
|
||||
"@deepseek-ai/cordis": "4.1.0",
|
||||
"@deepseek-ai/cosmokit": "1.9.0",
|
||||
"@deepseek-ai/dsh-util-values": "0.2.2-rc.1",
|
||||
"@deepseek-ai/schemastery": "3.19.0",
|
||||
"@modelcontextprotocol/client": "2.4.0",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "22.20.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 3,
|
||||
"target_dsh_version": "0.2.1-rc.5",
|
||||
"target_dsh_version": "0.2.2-rc.1",
|
||||
"provided_services": {
|
||||
"dsh": [
|
||||
"tools",
|
||||
@@ -76,24 +76,44 @@
|
||||
},
|
||||
"definition_api": {
|
||||
"package": "@deepseek-ai/dsh-tools",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-tools/-/dsh-tools-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-tOEMsbG1jH4EV80xcGucfeWsKHLjnETjRLtSnyFrVl2oma95tzTHBjti5MK60IvT2ijcCWhx8CrUKcqRksfDPA==",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "1e53932e7c4213ea24a7d87f06960b7a7702cb21f8a5741c97242b4a470a442a",
|
||||
"source_lines": [
|
||||
[
|
||||
8,
|
||||
888
|
||||
],
|
||||
[
|
||||
2625,
|
||||
2655
|
||||
]
|
||||
],
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-tools/-/dsh-tools-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-svB49dLPCHkKHgGwik3dTvEH0sL1vKwIzlQgqjE4qzEqFbSEROW8fByk3g2y2lxDv8TqgEUa0Vp2qFrVbm2lfw==",
|
||||
"license_sha256": "ebb4f09972aee8608be255debaf78451a68e95c290f55c240dec2ecfa16ea6be",
|
||||
"tarball_sha256": "9614366d61214a304f937d0f3b8481d5827d2aef4529f3bff9eb49ef8ac65283"
|
||||
"tarball_sha256": "bb94ee037bd7d3efe2d0654c6a1fa7084b268b895fa0ca6ceeb7e65d4fd6da72",
|
||||
"sources": [
|
||||
{
|
||||
"file": "lib/shared-DTdn5dXO.js",
|
||||
"sha256": "d02f0d8f36878313b0dae3a1bba5b77f7db4cffc77a0b72772048a36bacf2cf0",
|
||||
"lines": [
|
||||
[
|
||||
22,
|
||||
548
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "lib/index.js",
|
||||
"sha256": "1889cbf718d64518dd83245d4dba1e1f080b2c5326259c3ed5b1be7a7666a2bd",
|
||||
"lines": [
|
||||
[
|
||||
7,
|
||||
377
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "lib/shared-DTdn5dXO.js",
|
||||
"sha256": "d02f0d8f36878313b0dae3a1bba5b77f7db4cffc77a0b72772048a36bacf2cf0",
|
||||
"lines": [
|
||||
[
|
||||
566,
|
||||
596
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"command_followup": "one-text-message-submit-on-success",
|
||||
"pure_api": {
|
||||
@@ -117,11 +137,11 @@
|
||||
"packages": [
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-brand",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-brand/-/dsh-brand-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-7lUfWjWWAkrb/CFtMbXgjXLYQnL3KJwv+GLMTgY5EZ/4sOH0ufZUX8xlBWyMpdV2LqdvJ1yPKQ+mopH71/wbMQ==",
|
||||
"tarball_sha256": "9a0d30b3466783f7b0e52a636602630ab62f39598df9568600ddfaad97b389ca",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-brand/-/dsh-brand-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-ShGNEmNuEQA+bfkspWC2kGrdbrM4dFNhqlrNHIWsHO+PgVH1KoTKTLSraT1pt0A8YYk/moKE0gL2gY9dNBU+rw==",
|
||||
"tarball_sha256": "d953cb5e8dcd69be0b0347ce9a3b36421eefa751820cbe909f4a185348ad5530",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "14feb59256b891982b2017d73ed03ee65e2a7365f48e4902a856acb0e52d5eb0",
|
||||
"excerpts": [
|
||||
@@ -140,11 +160,11 @@
|
||||
},
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-util-crypto",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-util-crypto/-/dsh-util-crypto-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-7mGcnCH0BzIUzbnnC2Vx17Q/O3/+EfMHNt9iC2VaGr9SK010HYUpei/+6wrZsuGy77rqAUNhU6VB35wQTLM/VA==",
|
||||
"tarball_sha256": "3d99b1120718249a52fe854b84f4bacae21464345073d560019974268e19a089",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-util-crypto/-/dsh-util-crypto-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-I9Nhh3/64HPzjdR/mUt0J534FYHgodMUe0coXtA91+kxGcJQcSHOYouewv4hf0NWw0BuLh5zihTdtldZtobOWA==",
|
||||
"tarball_sha256": "c9ef0dfed280c83202f1d7bd5d32b1139a92a2fc375da62962ef709c3d1e4a6a",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "640b26c8e5a1156c6c8e8749a22f805c9c0e34444778eb4ee9529be3e88ec862",
|
||||
"excerpts": [
|
||||
@@ -163,13 +183,13 @@
|
||||
},
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-llm",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-llm/-/dsh-llm-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-g9mSILkqY7wByl0tpLBhXxWxQ3MoNzXokuJq86R/snPL3v1OonXv1b46wtvldEgyr88aK1gBBlifMsRnXjMWTA==",
|
||||
"tarball_sha256": "6b422d70aee363dcce81be0481dda9d801c2ae03a4f314497e407d184df2313c",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-llm/-/dsh-llm-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-XeEvH+6bpkjx0E2P1B5ZE+WWa+VHp4DimQaJDKNUZEFouqHMuN1B61TIz6IzUquRxa/6X4yJy+qXQ9WwAeMw5w==",
|
||||
"tarball_sha256": "cbf45039d3ef63e26987de514928a847a8c2d0b049df8fb24c11684e203b05d4",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "d18d8ae4c8d961e628708c1e4698e09716a0ebb80e55a672e0bde1029bec1155",
|
||||
"source_sha256": "8e541fc98a11897706a122fde1db42f8b2a41b3b354ba3fcb932e9a16929ac6a",
|
||||
"excerpts": [
|
||||
{
|
||||
"name": "createMessage",
|
||||
@@ -210,13 +230,13 @@
|
||||
},
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-session",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-session/-/dsh-session-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-A5NdQFgeY4qi29+lBzgZiOloiFe+E1iBbIRjJTplee79rswYGUUmUZYmOvAcdmk4tERkY2S9htuc+V+jNifqRw==",
|
||||
"tarball_sha256": "8571710817dbf5172079168861bfaef581efc26a4b83a4c003f2d1ef632ae7b4",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-session/-/dsh-session-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-e0zx9HFNn8EyqrymGQS85SjKOyr61//ChTgIVDlCBSZvBB723fuXyi9LiHIIuPTlkgmWJl3gm5J51ebEXew7PQ==",
|
||||
"tarball_sha256": "b8b1b4a7b9c8efbdbe4c134ed3b18cddf11842b62ad7675c21c05b3afef1cfb5",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "2f5dd07cb5c744a29abbb8003a8a4cefd9e582c6e2538074ca299a0ddd02a3bf",
|
||||
"source_sha256": "6fad466aa1f7aa64bfa038681ead5721f66c88e9891e25d357d2b8edbccd5494",
|
||||
"excerpts": [
|
||||
{
|
||||
"name": "SessionId",
|
||||
@@ -233,19 +253,19 @@
|
||||
},
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-system-prompt",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-system-prompt/-/dsh-system-prompt-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-HWW0k4Ug4XjRbCghLZYFMpLh8Ywcuh/bS7DAnjWfq4HbwwB+hV0XXJRF3iCf5600uYArfbuv4Ttz+4PHKu7k9w==",
|
||||
"tarball_sha256": "f223700954221de8cce2feaf6d6fbf526aa2ea5fd261fc0791edf1f858c31a02",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-system-prompt/-/dsh-system-prompt-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-1p9imdrenPnJTkLb6Coq+ELBDjngblM/YGP1Pa/xf1oisjLHcCoK2n9kkOUTIfgvoFtqWfxZJNZeKef71ZmlrA==",
|
||||
"tarball_sha256": "5ecb3f2ff2f0c037287c22c1ef917f1ce5cdd4c8055d51da6646ed5a39fe02a9",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "7be9f7aafbf345d01ddcc12fd297b16e9a1d41283a6a04073b867becca0bf020",
|
||||
"source_sha256": "18b692b03bbb717589085f63b126cbcf22963763c5a0565bb3a7a5d254e8e4a7",
|
||||
"excerpts": [
|
||||
{
|
||||
"name": "PERSONA_PREFIX_SECTION",
|
||||
"lines": [
|
||||
59,
|
||||
59
|
||||
61,
|
||||
61
|
||||
],
|
||||
"sha256": "11338254c278626eea6887b9cb5dc94d16f2ab4dae4bd7e555ea608c4b15648f"
|
||||
}
|
||||
@@ -256,19 +276,19 @@
|
||||
},
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-util-values",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-util-values/-/dsh-util-values-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-PL8TerJM/4jpZ2ArwgTpFbeDiAVnqkSh8Xinw2iEu8B6a+axmshzL4dCpwpf+uB8GHdXAhGz1cT+SBQwX/2TLQ==",
|
||||
"tarball_sha256": "2472d81f0dc4597bc5c4eeff493686a2b59a6b27cb6eb6654394192066d6ec05",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-util-values/-/dsh-util-values-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-UN4B/Fa9zAunYN3UuXDQ3QknxF/lMKNwTu2WxXKKEC+Ejz745po7I/JmsVFVa7+socfuwwTmVrfSsddRVU+lew==",
|
||||
"tarball_sha256": "e5de9a3c2600c70d72dba702cd0dfc8097264b7b6a7f393f4cfa799c81ecf759",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "4322a2aacdfe38476d2bbc62338120beea1e1660346d7db5b6c1e1f927a26b7c",
|
||||
"source_sha256": "8b4ffdf40126d48bd22017b4671f7e90612d01833fc865f7a0b60d3de4183e8b",
|
||||
"excerpts": [
|
||||
{
|
||||
"name": "deepFreeze",
|
||||
"lines": [
|
||||
824,
|
||||
860
|
||||
809,
|
||||
845
|
||||
],
|
||||
"sha256": "66ee7f28877afa912496ba944a581ac74e7b87390e6a47c4c640d8f477bdc6e2"
|
||||
}
|
||||
@@ -279,19 +299,19 @@
|
||||
},
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-user-questions",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-user-questions/-/dsh-user-questions-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-Yz4c6YO804nvJ6DVJGPZSAF3DCJ7HJgpylBL6KfbI7Au3a6MV49mLLEv5cWs67qL/tyQMsJO9DI2N/xPlczEXA==",
|
||||
"tarball_sha256": "5a2cb8c9451b4288c26a3c132b60aac15229a33bae34ce3c7bb0050906d6a8bc",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-user-questions/-/dsh-user-questions-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-COY40boWZ7XEszSvMGZ4CY5ze5ujCdA+Trtqx5C6ioqWzH9e8YbkY7AxyCOadxVO++5zAsqvsAfq65cZUPIMGA==",
|
||||
"tarball_sha256": "e7c1c38234fea9c0634ec95272bd1005c48ce890d2b935c8442b4ae7fee1b51e",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "e1c51afac2e31be8593c6426976e5f31dbe2a709b85ae1ea43ed406146f54265",
|
||||
"source_sha256": "69fee77aa57cc43de583d53a4877394c4cf3b5d58b8d7579fd8095c0a015bfbd",
|
||||
"excerpts": [
|
||||
{
|
||||
"name": "UserQuestionError",
|
||||
"lines": [
|
||||
429,
|
||||
434
|
||||
430,
|
||||
435
|
||||
],
|
||||
"sha256": "5d49a4ecdf40039b26824f3575a79820b48ed011f90641870f44fe5a41bf5ced"
|
||||
}
|
||||
@@ -303,12 +323,12 @@
|
||||
],
|
||||
"reuse": {
|
||||
"package": "@deepseek-ai/dsh-util-values",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"file": "lib/index.js",
|
||||
"symbol": "deepFreeze",
|
||||
"lines": [
|
||||
824,
|
||||
860
|
||||
809,
|
||||
845
|
||||
],
|
||||
"sha256": "66ee7f28877afa912496ba944a581ac74e7b87390e6a47c4c640d8f477bdc6e2"
|
||||
},
|
||||
|
||||
@@ -223,7 +223,7 @@ class ReviewedTree extends EntryTree {
|
||||
}
|
||||
|
||||
/** A reviewed composition is input; teardown and self-disposal never rewrite it. */
|
||||
write(): void {}
|
||||
commit(): void {}
|
||||
|
||||
override import(name: string): unknown {
|
||||
if (name === PERSONA) return reviewedPersonaModule
|
||||
@@ -375,7 +375,7 @@ function leakedServices(ctx: Context, mount: Fiber): string[] {
|
||||
for (const key of Object.getOwnPropertySymbols(ctx.reflect.store)) {
|
||||
const impl = ctx.reflect.store[key]
|
||||
if (impl === undefined) continue
|
||||
let current = impl.fiber
|
||||
let current = impl.ctx.fiber
|
||||
while (current !== mount && current.parent.fiber !== current) current = current.parent.fiber
|
||||
if (current === mount && rootIsolate[impl.name] === key) leaked.push(impl.name)
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
* *definition-side* subset only.
|
||||
*
|
||||
* Lines between the VERBATIM markers are copied unchanged from
|
||||
* `@deepseek-ai/[email protected].1-rc.5` `lib/index.js` (lines 8-888 and
|
||||
* 2625-2655), MIT License, Copyright (c) 2026 DeepSeek. See
|
||||
* `@deepseek-ai/[email protected].2-rc.1` `lib/shared-DTdn5dXO.js` (lines 22-548 and
|
||||
* 566-596) and `lib/index.js` (lines 7-377), MIT License, Copyright (c) 2026 DeepSeek. See
|
||||
* ../../LICENSES.txt. Everything outside the markers is Codewhale's.
|
||||
*
|
||||
* `ToolRuntime` is deliberately NOT exported: a plugin that tries to provide
|
||||
* or construct the DSH tool runtime fails to link, loudly.
|
||||
*/
|
||||
import { assertNever, isJsonValue } from '@deepseek-ai/dsh-util-values'
|
||||
import { assertNever, isJsonValue, snapshotJsonValue } from '@deepseek-ai/dsh-util-values'
|
||||
|
||||
/** Local stand-in for `@deepseek-ai/dsh-llm` `HarnessError` (same shape). */
|
||||
export class HarnessError extends Error {
|
||||
@@ -28,7 +28,7 @@ export class HarnessError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- VERBATIM @deepseek-ai/[email protected].1-rc.5 lib/index.js:8-888 ----
|
||||
// ---- VERBATIM @deepseek-ai/[email protected].2-rc.1 lib/shared-DTdn5dXO.js:22-548 ----
|
||||
//#region lib/types/json-schema.js
|
||||
/**
|
||||
* Enforced JSON Schema subset shared by tool outputs, generated PTC mode
|
||||
@@ -556,8 +556,11 @@ function validateJsonSchemaValue(schema, value, path = "value") {
|
||||
return checkValue(schema, value, path);
|
||||
}
|
||||
//#endregion
|
||||
// ---- END VERBATIM ----
|
||||
|
||||
// ---- VERBATIM @deepseek-ai/[email protected] lib/index.js:7-377 ----
|
||||
//#region lib/types/schema.js
|
||||
/** Unified JSON-value schema DSL, inference, compilation, and typed tool helper. @module dsh-tools/schema */
|
||||
/** Unified JSON-value schema DSL, inference, compilation, and tool definition helpers. @module dsh-tools/schema */
|
||||
const ANNOTATION_KEYS = [
|
||||
"description",
|
||||
"title",
|
||||
@@ -909,10 +912,27 @@ function defineTool(options) {
|
||||
};
|
||||
return tool;
|
||||
}
|
||||
/**
|
||||
* Omit execution and presentation callbacks from a tool definition.
|
||||
* @param definition - visible capability or executor-owned transport.
|
||||
* @param detachParameters - copy parameters for a caller-owned inspection result.
|
||||
* @returns the model-facing schema fields.
|
||||
*/
|
||||
function projectToolSchema(definition, detachParameters) {
|
||||
const { name, description, parameters, deferLoading } = definition;
|
||||
const detached = detachParameters ? snapshotJsonValue(parameters) : parameters;
|
||||
if (detached === void 0) throw new Error(`tool "${name}" parameters must be lossless JSON before schema projection`);
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
parameters: detached,
|
||||
...deferLoading === true ? { deferLoading } : {}
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
// ---- END VERBATIM ----
|
||||
|
||||
// ---- VERBATIM @deepseek-ai/[email protected].1-rc.5 lib/index.js:2625-2655 ----
|
||||
// ---- VERBATIM @deepseek-ai/[email protected].2-rc.1 lib/shared-DTdn5dXO.js:566-596 ----
|
||||
const TOOL_ABORTED = "ABORTED";
|
||||
/** Canonical error code for cancellation before a tool body was invoked. */
|
||||
const TOOL_ABORTED_BEFORE_DISPATCH = "ABORTED_BEFORE_DISPATCH";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FiberState as CordisFiberState } from '@deepseek-ai/cordis'
|
||||
|
||||
// @deepseek-ai/cordis 4.0.5 publishes FiberState as an erased const enum,
|
||||
// @deepseek-ai/cordis 4.1.0 publishes FiberState as an erased const enum,
|
||||
// with no runtime FiberState/FiberStatus export. These exact declared values
|
||||
// keep esbuild imports valid; update this receipt when the exact pin changes.
|
||||
export const FiberState = {
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{
|
||||
"commit": "b830ec950faba0a46dda564be3e2d854afed94f1",
|
||||
"commit": "66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db",
|
||||
"sources": [
|
||||
{
|
||||
"upstream_path": "packages/preset/persona/src/index.ts",
|
||||
"sha256": "16dc065551de44f581868b029ef1883aca8b990aa724283466f26edfb736db76",
|
||||
"url": "https://github.com/deepseek-ai/deepseek-harness/blob/b830ec950faba0a46dda564be3e2d854afed94f1/packages/preset/persona/src/index.ts"
|
||||
"url": "https://github.com/deepseek-ai/deepseek-harness/blob/66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db/packages/preset/persona/src/index.ts"
|
||||
},
|
||||
{
|
||||
"upstream_path": "packages/core/system-prompt/src/index.ts",
|
||||
"sha256": "9e254cddc9ef00072f9205950405ba8650bc43c8809fba6162da93253efc7f71",
|
||||
"url": "https://github.com/deepseek-ai/deepseek-harness/blob/b830ec950faba0a46dda564be3e2d854afed94f1/packages/core/system-prompt/src/index.ts"
|
||||
"sha256": "1c12dce4d2781be211695b199560602fa2fc7cc86f68672e656ea61d1ceda1a9",
|
||||
"url": "https://github.com/deepseek-ai/deepseek-harness/blob/66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db/packages/core/system-prompt/src/index.ts"
|
||||
},
|
||||
{
|
||||
"upstream_path": "LICENSE",
|
||||
"sha256": "ebb4f09972aee8608be255debaf78451a68e95c290f55c240dec2ecfa16ea6be",
|
||||
"url": "https://github.com/deepseek-ai/deepseek-harness/blob/b830ec950faba0a46dda564be3e2d854afed94f1/LICENSE"
|
||||
"url": "https://github.com/deepseek-ai/deepseek-harness/blob/66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db/LICENSE"
|
||||
}
|
||||
],
|
||||
"adapter": "src/dsh/persona.ts",
|
||||
"license_file": "src/dsh/upstream/hooks/LICENSE",
|
||||
"mode": "Adapted additive persona configuration and strict simple-template semantics; Engine retains prompt authority",
|
||||
"repository": "https://github.com/deepseek-ai/deepseek-harness",
|
||||
"version": "0.2.1-rc.5",
|
||||
"review": "Persona source and strict interpolation semantics are unchanged. New system-prompt context refresh and optional-context behavior remain Engine-owned and are not imported."
|
||||
"version": "0.2.2-rc.1",
|
||||
"review": "Persona source is byte-identical in rc.1. dsh-system-prompt rc.1 splits PromptPresentation (sections, contexts, variables) from provider-owned tool declarations and adds two tool section orders; prompt assembly, refresh and optional-context behavior remain Engine-owned and are not imported."
|
||||
}
|
||||
@@ -21,8 +21,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
/** Bounded additive projection of dsh-persona 0.2.1-rc.5 (MIT), pinned
|
||||
* b830ec950faba0a46dda564be3e2d854afed94f1. Core retains its prompt and
|
||||
/** Bounded additive projection of dsh-persona 0.2.2-rc.1 (MIT), pinned
|
||||
* 66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db. Core retains its prompt and
|
||||
* runtime context. Prefix/suffix become exact entry-scoped contributions. */
|
||||
import { normalizePromptSection, validatePromptTemplate, MAX_PROMPT_SECTION_BYTES } from '../shims/prompt.ts'
|
||||
import { ownerStorage } from '../root.ts'
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
# DeepSeek Harness composition sources
|
||||
|
||||
Pinned to `deepseek-ai/deepseek-harness` commit
|
||||
`b830ec950faba0a46dda564be3e2d854afed94f1`. `UPSTREAM.json` records each
|
||||
`66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db`. `UPSTREAM.json` records each
|
||||
original source hash, current source hash, and explicit adaptation.
|
||||
|
||||
Loader, include, and group retain the upstream composition and lifecycle
|
||||
algorithms. The loader disables the optional native addon probe. Strict host
|
||||
builds add type annotations and context casts, and two upstream configs extend
|
||||
the host configuration. `../fiber-state.ts` carries the exact six values of
|
||||
Cordis 4.0.5's erased const enum. These changed files are marked as adaptations.
|
||||
Cordis 4.1.0's erased const enum. These changed files are marked as adaptations.
|
||||
|
||||
Each package's complete MIT license is adjacent to its source and included in
|
||||
`dist/LICENSES.txt`. The package loader runs only reviewed source closures.
|
||||
Rust retains session identity, permission decisions, secrets, invocation
|
||||
admission, and the Engine turn loop.
|
||||
|
||||
The pin is tag `dsh-v0.2.1-rc.5`. Loader 1.0.6, include 1.0.10 and group
|
||||
1.0.5 share Cordis 4.0.5. Reviewed composition imports remain confined to the
|
||||
Rust-approved module closure, and composition writes remain no-ops. Volatile
|
||||
updates retain the same reviewed Native owner; they do not mint Engine grants.
|
||||
The separately recorded preset mount audit retains its original commit.
|
||||
The pin is tag `dsh-v0.2.2-rc.1`. Loader 1.1.0, include 1.1.0 and group
|
||||
1.1.0 share Cordis 4.1.0. Include now carries upstream `journal.ts` and
|
||||
`patch.ts`; the host imports only the pure patch application and YAML schema.
|
||||
Tree mutations report through `commit(change)`, which reviewed compositions
|
||||
implement as a no-op, so a reviewed composition is never rewritten. Bare
|
||||
specifiers keep the native import fallback rather than upstream's
|
||||
`.cordis/resolve.mjs` helper, and reviewed composition imports remain confined
|
||||
to the Rust-approved module closure. Volatile updates retain the same reviewed
|
||||
Native owner; they do not mint Engine grants. The separately recorded preset
|
||||
mount audit retains its original commit.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"repository": "https://github.com/deepseek-ai/deepseek-harness",
|
||||
"commit": "b830ec950faba0a46dda564be3e2d854afed94f1",
|
||||
"commit": "66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db",
|
||||
"license": "MIT; Shigma 2021-present",
|
||||
"files": [
|
||||
{
|
||||
@@ -13,34 +13,34 @@
|
||||
{
|
||||
"path": "loader/package.json",
|
||||
"upstream_path": "vendor/loader/package.json",
|
||||
"upstream_sha256": "e5a2092daa45b264072465788c5b0a750b5d88c4801333070ba90063af65491c",
|
||||
"sha256": "e5a2092daa45b264072465788c5b0a750b5d88c4801333070ba90063af65491c",
|
||||
"upstream_sha256": "ab6b44d86747ab7f09756f7d2c0a93c60d1b913f565d2e5d00449edadf3c2b1b",
|
||||
"sha256": "ab6b44d86747ab7f09756f7d2c0a93c60d1b913f565d2e5d00449edadf3c2b1b",
|
||||
"verbatim": true
|
||||
},
|
||||
{
|
||||
"path": "loader/src/config/entry.ts",
|
||||
"upstream_path": "vendor/loader/src/config/entry.ts",
|
||||
"upstream_sha256": "6a061f1d3c2cdaac19409bf8ec6956ec5a0a457c4f1689be658d50af7c9ee294",
|
||||
"sha256": "28829a932e7e57aba6f363601137fad91b411603ed91477f8b5f6d8f7c31efc8",
|
||||
"upstream_sha256": "650e4e1f3015bcca63c5a3474310f87a418add6249d9939d173a2b79f2ee7d9b",
|
||||
"sha256": "8f0e7c06b4a3059bd890a75be96261111534ea5031781e592d522949ff7d32f8",
|
||||
"verbatim": false,
|
||||
"deltas": [
|
||||
"Explicit types and context casts for the host strict TypeScript configuration; no suppression or algorithm replacement.",
|
||||
"Use the six exact Cordis 4.0.5 const-enum values from ../../../../fiber-state.ts because published JavaScript erases the enum.",
|
||||
"Use the six exact Cordis 4.1.0 const-enum values from ../../../../fiber-state.ts because published JavaScript erases the enum.",
|
||||
"Set the framework filter symbol via Reflect.set because the published Context declarations do not expose its symbol index; runtime assignment is unchanged."
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "loader/src/config/group.ts",
|
||||
"upstream_path": "vendor/loader/src/config/group.ts",
|
||||
"upstream_sha256": "5e138e8a8fad22e21d031859345797c445efbc380559fc32d35b03c835e60ccc",
|
||||
"sha256": "5e138e8a8fad22e21d031859345797c445efbc380559fc32d35b03c835e60ccc",
|
||||
"upstream_sha256": "9f63daea792e38a106c841e2ca062ded8fb67177665e8dcee821992b2071c017",
|
||||
"sha256": "9f63daea792e38a106c841e2ca062ded8fb67177665e8dcee821992b2071c017",
|
||||
"verbatim": true
|
||||
},
|
||||
{
|
||||
"path": "loader/src/config/isolate.ts",
|
||||
"upstream_path": "vendor/loader/src/config/isolate.ts",
|
||||
"upstream_sha256": "dfc9e961f61fe5f7701b5593f98902aa971884fb242e208fc0d5207794117094",
|
||||
"sha256": "c3d73d417e800fc8d04dd887d981a7e8db801ff3371708246e3f76d19c069fa2",
|
||||
"upstream_sha256": "70c7f8050a4fdc65031e956c5dae0a751d793ebdb51e5c955312666603c977a3",
|
||||
"sha256": "988006c99c2bc5edfddf009c62bcd5f942d5707249aef79de5dced0780e92c10",
|
||||
"verbatim": false,
|
||||
"deltas": [
|
||||
"Explicit types and context casts for the host strict TypeScript configuration; no suppression or algorithm replacement."
|
||||
@@ -49,9 +49,12 @@
|
||||
{
|
||||
"path": "loader/src/config/tree.ts",
|
||||
"upstream_path": "vendor/loader/src/config/tree.ts",
|
||||
"upstream_sha256": "761889adfb6b8a164fb12a0cec49a6ef439598b9267e85073000cfa9b7d682c8",
|
||||
"sha256": "761889adfb6b8a164fb12a0cec49a6ef439598b9267e85073000cfa9b7d682c8",
|
||||
"verbatim": true
|
||||
"upstream_sha256": "dd856ead27ac9b81e88dd51b13753f138d7951cc6d6bf3e5cfcbe206281aa503",
|
||||
"sha256": "299635e1c9a09b5ef567d6c60a1d72f5b1ff78ae210beccfabf076e0f2b26abc",
|
||||
"verbatim": false,
|
||||
"deltas": [
|
||||
"Bare specifiers keep the native import() fallback instead of upstream createResolve(), which writes a .cordis/resolve.mjs helper into the package scope and imports it; reviewed compositions resolve only through the Rust-approved module closure."
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "loader/src/config/utils.ts",
|
||||
@@ -66,12 +69,12 @@
|
||||
{
|
||||
"path": "loader/src/index.ts",
|
||||
"upstream_path": "vendor/loader/src/index.ts",
|
||||
"upstream_sha256": "a135e2189a0feb0af08ff79de761698fb8ce7b41c96fefda1d4a299e5982fef3",
|
||||
"sha256": "14e1a6382bb0f7eb7f46391d8bd419f8804fe752568f988a76cdd3ea7d892d67",
|
||||
"upstream_sha256": "e2d04b34ef703e08d2e40f0ba41daa31ff564343cfeb018f102133fc62317050",
|
||||
"sha256": "13e56d5065be15e0f5b0fe1be54c446b29e5323e4acb94796a6344eedcb1d00b",
|
||||
"verbatim": false,
|
||||
"deltas": [
|
||||
"Disable optional native-internals/addon probe; retain the upstream ESM fallback.",
|
||||
"Use the six exact Cordis 4.0.5 const-enum values from ../../../fiber-state.ts because published JavaScript erases the enum.",
|
||||
"Use the six exact Cordis 4.1.0 const-enum values from ../../../fiber-state.ts because published JavaScript erases the enum.",
|
||||
"Explicit types and context casts for the host strict TypeScript configuration; no suppression or algorithm replacement."
|
||||
]
|
||||
},
|
||||
@@ -109,15 +112,15 @@
|
||||
{
|
||||
"path": "include/package.json",
|
||||
"upstream_path": "vendor/include/package.json",
|
||||
"upstream_sha256": "d6d6047472127599a6f7cf59a4a569530e8a1f518d151e69e7f6d5c67599ab75",
|
||||
"sha256": "d6d6047472127599a6f7cf59a4a569530e8a1f518d151e69e7f6d5c67599ab75",
|
||||
"upstream_sha256": "3b5cba7e54cf99b8e60f1b489abd14be2e0953f8145ae0d11051d2d1d239189e",
|
||||
"sha256": "3b5cba7e54cf99b8e60f1b489abd14be2e0953f8145ae0d11051d2d1d239189e",
|
||||
"verbatim": true
|
||||
},
|
||||
{
|
||||
"path": "include/src/index.ts",
|
||||
"upstream_path": "vendor/include/src/index.ts",
|
||||
"upstream_sha256": "50005dcc3b35cb8e18e33ee157567aa14210b932fb25e22506d31aee25e59428",
|
||||
"sha256": "a5de160d0c629233cc7aa06aee11b666c6a3b31cac71183ad72d73d5cebd619f",
|
||||
"upstream_sha256": "43ddf01220a45fbbe8c0fd64d5d4944fe42dedcd0862127d6ede1d6086b9d053",
|
||||
"sha256": "d103ea246ebdbd2859d8b926729ad489e3571f172ed90a0bc7d8121078b5250e",
|
||||
"verbatim": false,
|
||||
"deltas": [
|
||||
"Explicit types and context casts for the host strict TypeScript configuration; no suppression or algorithm replacement."
|
||||
@@ -143,8 +146,8 @@
|
||||
{
|
||||
"path": "group/package.json",
|
||||
"upstream_path": "vendor/group/package.json",
|
||||
"upstream_sha256": "5fd58be1a7e3705815cbcba9fd573ff367edb8508ace1b09ea5e98a5649f8066",
|
||||
"sha256": "5fd58be1a7e3705815cbcba9fd573ff367edb8508ace1b09ea5e98a5649f8066",
|
||||
"upstream_sha256": "30f68079e1da306deec57272552301a3ca170079ecfcf600cc683f92071a36a0",
|
||||
"sha256": "30f68079e1da306deec57272552301a3ca170079ecfcf600cc683f92071a36a0",
|
||||
"verbatim": true
|
||||
},
|
||||
{
|
||||
@@ -160,6 +163,26 @@
|
||||
"upstream_sha256": "6025a6b9798a0a16af017bb7d8d6394bbe345685b2ace56b15975add5843edb3",
|
||||
"sha256": "6025a6b9798a0a16af017bb7d8d6394bbe345685b2ace56b15975add5843edb3",
|
||||
"verbatim": true
|
||||
},
|
||||
{
|
||||
"path": "include/src/journal.ts",
|
||||
"upstream_path": "vendor/include/src/journal.ts",
|
||||
"upstream_sha256": "219b874ab6a7045fb5065fdbd114fe307eae89e68ab5a59d25aff9b53710187d",
|
||||
"sha256": "918c0ce1a3f773f289f774e9945b2d0892c8aaa1c4edb40adb87d5096af72e79",
|
||||
"verbatim": false,
|
||||
"deltas": [
|
||||
"Explicit types and context casts for the host strict TypeScript configuration; no suppression or algorithm replacement."
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "include/src/patch.ts",
|
||||
"upstream_path": "vendor/include/src/patch.ts",
|
||||
"upstream_sha256": "38281b6365faaca6d99027716644d58ee85cba133e1fe2c36afc017cf011ae57",
|
||||
"sha256": "4579467d8b678880f566c23cfad78d6531cb0de1d445a8f45431e63916e8fc25",
|
||||
"verbatim": false,
|
||||
"deltas": [
|
||||
"Explicit types and context casts for the host strict TypeScript configuration; no suppression or algorithm replacement."
|
||||
]
|
||||
}
|
||||
],
|
||||
"adapted_audit": {
|
||||
@@ -169,5 +192,5 @@
|
||||
"commit": "0d1f50007f9bca3f52b06e1c3074fa14d5fb0720"
|
||||
},
|
||||
"integration": "The existing host uses this loader for reviewed native composition; the offline reviewer shares its parser without executing plugin code.",
|
||||
"tag": "dsh-v0.2.1-rc.5"
|
||||
"tag": "dsh-v0.2.2-rc.1"
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
Borrowed @deepseek-ai/dsh-agent-presets 0.1.6-alpha.1, MIT.
|
||||
Source: deepseek-ai/deepseek-harness 0d1f50007f9bca3f52b06e1c3074fa14d5fb0720.
|
||||
|
||||
This legacy preset adapter retains that exact source pin during the rc.5
|
||||
This legacy preset adapter retains that exact source pin during the rc.5 and rc.1
|
||||
upgrade. Upstream later split preset discovery and mounting into other
|
||||
packages; those runtime packages are not imported by this upgrade. The
|
||||
loader/include/group composition underneath it now uses the rc.5 pin in
|
||||
loader/include/group composition underneath it now uses the rc.1 pin in
|
||||
`../UPSTREAM.json`.
|
||||
Codewhale delta: discovery and metadata require receipt-backed IO; no ambient
|
||||
filesystem, home root, Node package walk, Agent, Session, settings store or
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/cordis-plugin-group",
|
||||
"description": "Nested plugin group for cordis",
|
||||
"version": "1.0.5",
|
||||
"version": "1.1.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Pinned hook protocol and configuration parsers
|
||||
|
||||
Source: DeepSeek Harness rc.5 commit `b830ec950faba0a46dda564be3e2d854afed94f1`.
|
||||
Source: DeepSeek Harness rc.1 commit `66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db`.
|
||||
Packages: `@deepseek-ai/dsh-hook-protocol`, `@deepseek-ai/dsh-hooks-claude-code`, and
|
||||
`@deepseek-ai/dsh-hooks-codex`, each version `0.2.1-rc.5`, MIT.
|
||||
`@deepseek-ai/dsh-hooks-codex`, each version `0.2.2-rc.1`, MIT.
|
||||
|
||||
`UPSTREAM.json` records every original and adapted hash. Matcher, codec and merge
|
||||
are verbatim. Types remove session-writer declaration merging; the two parsers
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"ref": "b830ec950faba0a46dda564be3e2d854afed94f1",
|
||||
"ref": "66491b05e3f05ad35bcdd4f6cd7ed26041d9e3db",
|
||||
"files": {
|
||||
"hook-protocol/src/matcher.ts": {
|
||||
"source_sha256": "f2d9cd9640b902ba40be97746ba9c20365aea9de184456976ae64b41dfdbfe0b",
|
||||
@@ -46,5 +46,5 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"not_full_index": "Full upstream lifecycle index expects agent/session writers; only pure configuration/matcher/codec/merge are adopted. Core firepoints and scheduling are reused.",
|
||||
"version": "0.2.1-rc.5"
|
||||
"version": "0.2.2-rc.1"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/cordis-plugin-include",
|
||||
"description": "Include files in cordis configurations",
|
||||
"version": "1.0.10",
|
||||
"version": "1.1.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { EntryGroup, EntryTree, isJsExpr, type EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
|
||||
import { type EntryChange, type EntryOptions, EntryTree, EntryGroup, isJsExpr } from '@deepseek-ai/cordis-plugin-loader'
|
||||
import { Context, Service } from '@deepseek-ai/cordis'
|
||||
import { extname } from 'node:path'
|
||||
import { access, constants, readFile, rename, writeFile } from 'node:fs/promises'
|
||||
import { setTimeout as delay } from 'node:timers/promises'
|
||||
import { deepEqual } from '@deepseek-ai/cosmokit'
|
||||
import { basename, dirname, extname, join } from 'node:path'
|
||||
import { access, constants, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
||||
import { setTimeout as sleep } from 'node:timers/promises'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
import * as yaml from 'js-yaml'
|
||||
import { applyJournal, flatten, type Journal, merge, reconcile, record } from './journal.ts'
|
||||
import { applyPatches, ensureIds, ensureInsertIds, PatchIndex, type PatchOptions, routeJournal } from './patch.ts'
|
||||
/** Shared patch semantics used by config dumps and mounting. */
|
||||
export { applyPatches as applyEntryPatches } from './patch.ts'
|
||||
/** Serialized entry patch options. */
|
||||
export type { PatchOptions } from './patch.ts'
|
||||
|
||||
const JsExpr = new yaml.Type('tag:yaml.org,2002:js', {
|
||||
kind: 'scalar',
|
||||
@@ -14,329 +21,444 @@ const JsExpr = new yaml.Type('tag:yaml.org,2002:js', {
|
||||
represent: (data) => (data as { __jsExpr: string }).__jsExpr,
|
||||
})
|
||||
|
||||
/**
|
||||
* The entry-list YAML dialect: `!!js` scalars round-trip as expression nodes
|
||||
* the Loader evaluates at entry activation. Exported so config tooling
|
||||
* (`dsh --dump-config`) parses and prints exactly the dialect this include
|
||||
* mounts.
|
||||
*/
|
||||
/** YAML dialect preserving child-owned JavaScript expression nodes. */
|
||||
export const entryListSchema = yaml.JSON_SCHEMA.extend(JsExpr)
|
||||
|
||||
const schema = entryListSchema
|
||||
|
||||
const writable: Record<string, string> = {
|
||||
const types: Record<string, string> = {
|
||||
'.json': 'application/json',
|
||||
'.yaml': 'application/yaml',
|
||||
'.yml': 'application/yaml',
|
||||
}
|
||||
|
||||
const supported = new Set(Object.keys(writable))
|
||||
type ConfigStage = 'read' | 'parse' | 'validate'
|
||||
|
||||
const WRITE_RETRY_LIMIT = 10
|
||||
const WRITE_RETRY_DELAY_MS = 50
|
||||
|
||||
function retryableWriteError(error: unknown): boolean {
|
||||
const code = (error as NodeJS.ErrnoException | null)?.code
|
||||
return code === 'EACCES' || code === 'EBUSY' || code === 'EPERM'
|
||||
interface Snapshot {
|
||||
content: string
|
||||
data: EntryOptions[]
|
||||
}
|
||||
|
||||
export class ConfigFileError extends Error {
|
||||
name = 'ConfigFileError'
|
||||
|
||||
constructor(public stage: ConfigStage, path: string, cause: unknown) {
|
||||
super(`failed to ${stage} config file ${path}`, { cause })
|
||||
}
|
||||
}
|
||||
|
||||
/** The file changed under us between the pre-check and the rename. */
|
||||
class StaleWriteError extends Error {
|
||||
name = 'StaleWriteError'
|
||||
}
|
||||
|
||||
interface Anonymous {
|
||||
parent: string | null
|
||||
options: EntryOptions
|
||||
}
|
||||
|
||||
function isENOENT(error: unknown) {
|
||||
return (error as NodeJS.ErrnoException | undefined)?.code === 'ENOENT'
|
||||
}
|
||||
|
||||
const RENAME_RETRIES = 10
|
||||
const RENAME_BACKOFF = 20
|
||||
|
||||
/**
|
||||
* Apply patch lists to an entry list — THE patch semantics of this include,
|
||||
* shared by mounting (`applyPatches`) and offline config tooling
|
||||
* (`dsh --dump-config`) so a dump can never drift from what boots. The input
|
||||
* is never mutated: patching shared entry objects would bake earlier patch
|
||||
* values into the cached parse, so repeated application (config hot-reloads)
|
||||
* could never revert a removed or changed patch. Inserted entries are indexed
|
||||
* as they are added, so a later patch in the same list can target a row an
|
||||
* earlier patch inserted. A patch that matches nothing warns and is skipped.
|
||||
* @param data - the parsed entry list (JSON-safe plain data).
|
||||
* @param patches - the patch list to apply, in order.
|
||||
* @param warn - sink for skipped-patch diagnostics (printf-style, `%C` = code).
|
||||
* @returns a detached entry list with every applicable patch applied.
|
||||
* On Windows a rename over a file that another process holds open (typically
|
||||
* an antivirus scanning what we just wrote) fails with one of these; the lock
|
||||
* is normally gone within milliseconds. A bounded retry is cheap enough to
|
||||
* apply on every platform.
|
||||
*/
|
||||
export function applyEntryPatches(
|
||||
data: EntryOptions[],
|
||||
patches: PatchOptions[] | undefined,
|
||||
warn: (message: string, ...args: any[]) => void,
|
||||
): EntryOptions[] {
|
||||
if (!patches?.length) return [...data]
|
||||
data = structuredClone(data)
|
||||
|
||||
const entryMap = new Map<string, EntryOptions>()
|
||||
const buildMap = (entries: EntryOptions[]) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.id) entryMap.set(entry.id, entry)
|
||||
if (entry.group && Array.isArray(entry.config)) {
|
||||
buildMap(entry.config)
|
||||
}
|
||||
}
|
||||
}
|
||||
buildMap(data)
|
||||
|
||||
for (const patch of patches) {
|
||||
const { id, insert, name, ...overrides } = patch
|
||||
|
||||
if (insert) {
|
||||
if (id) {
|
||||
const target = entryMap.get(id)
|
||||
if (!target) {
|
||||
warn('patch insert: entry %C not found', id)
|
||||
continue
|
||||
}
|
||||
if (!target.group) {
|
||||
warn('patch insert: entry %C is not a group', id)
|
||||
continue
|
||||
}
|
||||
if (!Array.isArray(target.config)) target.config = []
|
||||
target.config.push(...insert)
|
||||
} else {
|
||||
data.push(...insert)
|
||||
}
|
||||
// Index what this patch added so a LATER patch in the same list can
|
||||
// target it. Patch lists compose one layer per source (each bundle
|
||||
// layer, then the user's, then `--patch` overlays), and a layer must be
|
||||
// able to configure or disable a row an earlier layer inserted; without
|
||||
// this, inserted rows were silently unpatchable.
|
||||
buildMap(insert)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
warn('patch: id is required for non-insert patches')
|
||||
continue
|
||||
}
|
||||
|
||||
const target = entryMap.get(id)
|
||||
if (!target) {
|
||||
warn('patch: entry %C not found', id)
|
||||
continue
|
||||
}
|
||||
|
||||
if (name && name !== target.name) {
|
||||
warn('patch: name mismatch for %C (expected %C, got %C), skipping', id, target.name, name)
|
||||
continue
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(overrides)) {
|
||||
if (key === 'id') continue
|
||||
(target as unknown as Record<string, unknown>)[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
function isTransientRenameError(error: unknown) {
|
||||
const code = (error as NodeJS.ErrnoException | undefined)?.code
|
||||
return code === 'EACCES' || code === 'EPERM' || code === 'EBUSY'
|
||||
}
|
||||
|
||||
/** Runtime patch applied to entries loaded from an included config file. */
|
||||
export interface PatchOptions {
|
||||
id?: string
|
||||
insert?: EntryOptions[]
|
||||
name?: string
|
||||
config?: any
|
||||
group?: boolean | null
|
||||
disabled?: boolean | null
|
||||
inject?: any
|
||||
intercept?: any
|
||||
isolate?: any
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/** Config namespace for the file-backed include loader. */
|
||||
export namespace Include {
|
||||
/** Config for a file-backed loader subtree. */
|
||||
export interface Config {
|
||||
/** YAML or JSON path resolved from `ctx.baseUrl`. */
|
||||
path: string
|
||||
/** Entry list written when the file does not already exist. */
|
||||
initial?: any[]
|
||||
/** Runtime patches applied after reading the file. */
|
||||
patches?: PatchOptions[]
|
||||
/** Enables loader apply/reload/unload logs for this subtree. */
|
||||
enableLogs?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/** Loader entry tree backed by a YAML or JSON file. */
|
||||
export class Include extends EntryTree {
|
||||
static inject = ['loader']
|
||||
|
||||
// Tree-carrier marker (the Group plugin declares the same): this config is
|
||||
// entry and patch lists, so the Loader's `internal/config` interpolation
|
||||
// keeps it literal — a `!!js` expression inside a nested row's config
|
||||
// belongs to that row's fiber, resolving lazily in the row's own context.
|
||||
// Include's own fields (`path`, `enableLogs`) therefore stay literal too.
|
||||
// Child entries evaluate their expressions against their own injections.
|
||||
static readonly [EntryGroup.key] = true
|
||||
|
||||
public filename: string
|
||||
private type?: string
|
||||
private readonly: boolean
|
||||
private content?: string
|
||||
private data?: EntryOptions[]
|
||||
private writeTask?: NodeJS.Timeout | undefined
|
||||
private pendingWrite?: EntryOptions[]
|
||||
private writeQueue: Promise<void> = Promise.resolve()
|
||||
private type: string
|
||||
|
||||
/** The last file state we read or wrote. */
|
||||
private cache?: Snapshot
|
||||
/** Tree-side changes that have not reached the file yet. */
|
||||
private journal: Journal = new Map()
|
||||
/** Ids we assigned to entries the file leaves anonymous. */
|
||||
private anonymous = new Map<string, Anonymous>()
|
||||
|
||||
private dirtyRead = false
|
||||
private dirtyWrite = false
|
||||
private _draining?: Promise<void>
|
||||
|
||||
/** Only the latest intended tree is ever applied; older ones are skipped. */
|
||||
private _pendingTree?: EntryOptions[]
|
||||
private _applyTask?: Promise<void>
|
||||
|
||||
private _disposed = false
|
||||
private _seq = 0
|
||||
private persistenceError: unknown
|
||||
|
||||
constructor(ctx: Context, public config: Include.Config) {
|
||||
super(ctx)
|
||||
this.enableLogs = config.enableLogs ?? ctx.fiber.entry?.parent.tree.enableLogs ?? false
|
||||
this.filename = fileURLToPath(new URL(this.config.path, this.ctx.baseUrl))
|
||||
const ext = extname(this.filename)
|
||||
if (!supported.has(ext)) {
|
||||
if (!types[ext]) {
|
||||
throw new Error(`extension "${ext}" not supported`)
|
||||
}
|
||||
this.type = writable[ext]
|
||||
this.readonly = !this.type
|
||||
this.type = types[ext]
|
||||
this.ctx.baseUrl = new URL('.', pathToFileURL(this.filename)).href
|
||||
ensureInsertIds(config.patches, id => !!this.store[id])
|
||||
|
||||
ctx.on('internal/update', (config, _, next) => {
|
||||
ctx.on('internal/update', (config, next) => {
|
||||
if (config.path !== this.config.path) return next()
|
||||
// Veto the fiber restart (children update in place), but persist the new
|
||||
// config ourselves — `Fiber.update` only assigns `this.config` behind
|
||||
// `next()`, and a stale `this.config.patches` would make the next
|
||||
// `refresh()` re-apply the old overlay.
|
||||
this.config = config
|
||||
this.root.update(this.applyPatches(this.data!, config.patches)).catch((error) => {
|
||||
this.ctx.logger.warn('config update at %C failed', this.filename)
|
||||
this.ctx.logger.warn(error)
|
||||
})
|
||||
ensureInsertIds(config.patches, id => !!this.store[id])
|
||||
this._scheduleApply()
|
||||
return this._applyTask
|
||||
})
|
||||
}
|
||||
|
||||
private async checkAccess() {
|
||||
if (!this.type) return
|
||||
try {
|
||||
await access(this.filename, constants.W_OK)
|
||||
} catch {
|
||||
this.readonly = true
|
||||
}
|
||||
}
|
||||
|
||||
private async read(forced = false) {
|
||||
const content = await readFile(this.filename, 'utf8')
|
||||
if (!forced && this.content === content) return false
|
||||
let data: any
|
||||
if (this.type === 'application/yaml') {
|
||||
data = yaml.load(content, { schema: entryListSchema })
|
||||
} else if (this.type === 'application/json') {
|
||||
data = JSON.parse(content)
|
||||
} else {
|
||||
const module = await import(/* @vite-ignore */ this.filename)
|
||||
data = module.default || module
|
||||
}
|
||||
// An empty or truncated file (common mid-edit: editors and `sed -i` write
|
||||
// through temp states) parses to `undefined`, not an error; reject every
|
||||
// non-array shape here so callers see one "invalid file" signal. Content
|
||||
// and data commit only on success, so an edit that is later reverted to
|
||||
// the exact last good content correctly reads as "unchanged".
|
||||
if (!Array.isArray(data)) {
|
||||
throw new TypeError(`config file must be a top-level array of entries: ${this.filename}`)
|
||||
}
|
||||
this.content = content
|
||||
this.data = data
|
||||
await this.checkAccess()
|
||||
return true
|
||||
}
|
||||
|
||||
private applyPatches(data: EntryOptions[], patches = this.config.patches): EntryOptions[] {
|
||||
return applyEntryPatches(data, patches, (message, ...args) => {
|
||||
this.ctx.root.logger?.('loader').warn(message, ...args)
|
||||
})
|
||||
private warn = (format: string, ...args: any[]) => {
|
||||
this.ctx.root.logger?.('loader').warn(format, ...args)
|
||||
}
|
||||
|
||||
async* [Service.init]() {
|
||||
try {
|
||||
await this.read()
|
||||
await this._read()
|
||||
} catch (error) {
|
||||
// Only a missing file falls back to `initial` (or the not-found error):
|
||||
// an existing-but-invalid file must fail loud with its real parse error,
|
||||
// never be mislabelled as absent or silently overwritten.
|
||||
if ((error as NodeJS.ErrnoException | null)?.code !== 'ENOENT') throw error
|
||||
if (this.config.initial) {
|
||||
await this._writeFile(this.config.initial as any)
|
||||
await this.read(true)
|
||||
} else {
|
||||
// only a missing file falls back to `initial`
|
||||
const cause = error instanceof ConfigFileError && error.stage === 'read' ? error.cause : undefined
|
||||
if (!isENOENT(cause)) throw error
|
||||
if (!this.config.initial) {
|
||||
throw new Error(`config file not found: ${this.filename}`)
|
||||
}
|
||||
const initial = structuredClone(this.config.initial) as EntryOptions[]
|
||||
ensureIds(initial, id => !!this.store[id])
|
||||
await this._writeText(this.dump(initial))
|
||||
await this._read()
|
||||
}
|
||||
|
||||
yield () => this.stop()
|
||||
await this.root.update(this.applyPatches(this.data!))
|
||||
await this._applyTask
|
||||
}
|
||||
|
||||
async stop() {
|
||||
this._disposed = true
|
||||
this._pendingTree = undefined
|
||||
// Flush what the user last did before the tree goes away; this is also the
|
||||
// last attempt for changes a failed write left in the journal.
|
||||
if (this.journal.size) {
|
||||
this.dirtyWrite = true
|
||||
this._drain()
|
||||
}
|
||||
try {
|
||||
await this.flushWrite()
|
||||
await this.flush()
|
||||
} finally {
|
||||
this.root.stop()
|
||||
await this.flushWrite()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-read the file and refresh child entries when content changed. An
|
||||
* unreadable or unparsable file logs a warning and keeps the last good
|
||||
* tree: a hot-reload of a live app must never take the process down.
|
||||
* Resolves once every pending read and write has been processed and the
|
||||
* tree reflects the outcome. Retries pending writes and rejects while changes remain unsaved.
|
||||
*/
|
||||
async refresh() {
|
||||
try {
|
||||
if (!await this.read()) return
|
||||
await this.root.update(this.applyPatches(this.data!))
|
||||
} catch (error) {
|
||||
this.ctx.logger.warn('config reload at %C failed; keeping the running tree', this.filename)
|
||||
this.ctx.logger.warn(error)
|
||||
async flush() {
|
||||
if (this.journal.size && !this._draining) {
|
||||
this.dirtyWrite = true
|
||||
this._drain()
|
||||
}
|
||||
while (this._draining || this._applyTask) {
|
||||
await this._draining
|
||||
await this._applyTask
|
||||
}
|
||||
if (this.journal.size) {
|
||||
throw this.persistenceError ?? new Error(`config file ${this.filename} has ${this.journal.size} unsaved change(s)`)
|
||||
}
|
||||
this.persistenceError = undefined
|
||||
}
|
||||
|
||||
private async _writeFile(config: EntryOptions[]) {
|
||||
if (this.readonly) {
|
||||
throw new Error(`cannot overwrite readonly config`)
|
||||
}
|
||||
if (this.type === 'application/yaml') {
|
||||
this.content = yaml.dump(config, { schema })
|
||||
} else if (this.type === 'application/json') {
|
||||
this.content = JSON.stringify(config, null, 2)
|
||||
}
|
||||
await writeFile(this.filename + '.tmp', this.content!)
|
||||
for (let retry = 0; ; retry++) {
|
||||
try {
|
||||
await rename(this.filename + '.tmp', this.filename)
|
||||
return
|
||||
} catch (error) {
|
||||
if (!retryableWriteError(error) || retry >= WRITE_RETRY_LIMIT) throw error
|
||||
await delay((retry + 1) * WRITE_RETRY_DELAY_MS)
|
||||
/** Re-read the file and reconcile the tree with it. */
|
||||
refresh() {
|
||||
this.dirtyRead = true
|
||||
this._drain()
|
||||
return this.flush()
|
||||
}
|
||||
|
||||
/** Called synchronously by the loader once a tree-side change is in place. */
|
||||
commit(change: EntryChange) {
|
||||
this.context.emit('loader/config-update')
|
||||
const parent = change.group === this.root ? null : change.group.ctx.fiber.entry!.options.id
|
||||
record(this.journal, change, parent)
|
||||
this.dirtyWrite = true
|
||||
this._drain()
|
||||
}
|
||||
|
||||
private _drain() {
|
||||
if (this._draining) return
|
||||
this._draining = this._loop().finally(() => {
|
||||
this._draining = undefined
|
||||
})
|
||||
}
|
||||
|
||||
private async _loop() {
|
||||
while (this.dirtyRead || this.dirtyWrite) {
|
||||
if (this.dirtyRead) {
|
||||
this.dirtyRead = false
|
||||
try {
|
||||
await this._read()
|
||||
} catch (error) {
|
||||
// Half-written files are routine while the user is editing, hence
|
||||
// only a warning. With the file in an unknown state there is nothing
|
||||
// safe to write: unless another read is due, stop here and leave
|
||||
// `dirtyWrite` set for the next trigger.
|
||||
this.persistenceError = error
|
||||
this.ctx.logger.warn(error)
|
||||
if (this.dirtyRead) continue
|
||||
return
|
||||
}
|
||||
}
|
||||
if (this.dirtyWrite) {
|
||||
this.dirtyWrite = false
|
||||
try {
|
||||
await this._write()
|
||||
} catch (error) {
|
||||
this.persistenceError = error
|
||||
this.ctx.logger.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private writeFile(config: EntryOptions[]) {
|
||||
clearTimeout(this.writeTask)
|
||||
this.pendingWrite = config
|
||||
this.writeTask = setTimeout(() => {
|
||||
void this.flushWrite()
|
||||
}, 0)
|
||||
private async _read() {
|
||||
let content: string
|
||||
try {
|
||||
content = await readFile(this.filename, 'utf8')
|
||||
} catch (error) {
|
||||
throw new ConfigFileError('read', this.filename, error)
|
||||
}
|
||||
if (content === this.cache?.content) return
|
||||
|
||||
let data: any
|
||||
try {
|
||||
data = this.type === 'application/yaml'
|
||||
? yaml.load(content, { schema })
|
||||
: JSON.parse(content)
|
||||
} catch (error) {
|
||||
throw new ConfigFileError('parse', this.filename, error)
|
||||
}
|
||||
// An empty or truncated file (routine mid-edit) parses to `undefined`, so
|
||||
// every non-array shape is rejected here as one "invalid file" signal.
|
||||
if (!Array.isArray(data)) {
|
||||
throw new ConfigFileError('validate', this.filename, new TypeError('config file must be a top-level array'))
|
||||
}
|
||||
|
||||
this._assignIds(data)
|
||||
|
||||
if (this.cache) {
|
||||
const index = new PatchIndex(this.config.patches ?? [])
|
||||
const conflicts = reconcile(this.journal, flatten(this.cache.data), flatten(data), (id, key) => index.fileOwned(id, key))
|
||||
for (const { id, reason } of conflicts) {
|
||||
this.ctx.logger.error('config conflict in %C: entry %C %s; file wins', this.filename, id, reason)
|
||||
}
|
||||
}
|
||||
|
||||
this.cache = { content, data }
|
||||
this._scheduleApply()
|
||||
// the file changed while tree-side changes were pending: write the merge
|
||||
if (this.journal.size) this.dirtyWrite = true
|
||||
}
|
||||
|
||||
private flushWrite(): Promise<void> {
|
||||
clearTimeout(this.writeTask)
|
||||
this.writeTask = undefined
|
||||
const config = this.pendingWrite
|
||||
this.pendingWrite = undefined
|
||||
if (config === undefined) return this.writeQueue
|
||||
const run = this.writeQueue.then(
|
||||
() => this._writeFile(config),
|
||||
() => this._writeFile(config),
|
||||
)
|
||||
this.writeQueue = run
|
||||
void run.catch((error) => {
|
||||
this.ctx.root.logger?.('loader').warn('failed to write config file %C', this.filename)
|
||||
this.ctx.root.logger?.('loader').warn(error)
|
||||
private async _write() {
|
||||
if (!this.cache || !this.journal.size) return
|
||||
const current = await readFile(this.filename, 'utf8').catch((error) => {
|
||||
if (isENOENT(error)) return undefined
|
||||
throw error
|
||||
})
|
||||
return run
|
||||
if (current === undefined) {
|
||||
this.ctx.logger.warn('config file %C is missing, %d pending change(s) kept in memory', this.filename, this.journal.size)
|
||||
return
|
||||
}
|
||||
if (current !== this.cache.content) {
|
||||
// someone else wrote the file: merge first, then come back
|
||||
this.dirtyRead = true
|
||||
this.dirtyWrite = true
|
||||
return
|
||||
}
|
||||
|
||||
// Snapshot and swap: changes reported while we are writing belong to the
|
||||
// next round.
|
||||
const batch = this.journal
|
||||
this.journal = new Map()
|
||||
const restore = () => {
|
||||
for (const [id, record] of this.journal) merge(batch, id, record)
|
||||
this.journal = batch
|
||||
}
|
||||
|
||||
const data = structuredClone(this.cache.data)
|
||||
const patches = structuredClone(this.config.patches ?? [])
|
||||
if (routeJournal(batch, data, patches, this.warn)) {
|
||||
// Take the new patches now so that the tree scheduled below already
|
||||
// reflects them; the round trip through the parent only persists them.
|
||||
this.config = { ...this.config, patches }
|
||||
if (this.ctx.fiber.entry) {
|
||||
// Not awaited: the returned promise is the tree reconciliation, and
|
||||
// the drain loop must not block on fibers.
|
||||
try {
|
||||
Promise.resolve(this.ctx.fiber.update(this.config)).catch((error) => {
|
||||
this.ctx.logger.error(error)
|
||||
})
|
||||
} catch (error) {
|
||||
this.ctx.logger.error(error)
|
||||
}
|
||||
} else {
|
||||
this.ctx.logger.warn('patches of %C changed at runtime but cannot be persisted', this.filename)
|
||||
}
|
||||
}
|
||||
|
||||
// nothing for the file itself (patch-only changes, or ids we assigned to
|
||||
// anonymous entries that are already part of the cache)
|
||||
if (deepEqual(data, this.cache.data)) {
|
||||
this._scheduleApply()
|
||||
return
|
||||
}
|
||||
const text = this.dump(data)
|
||||
|
||||
try {
|
||||
await access(this.filename, constants.W_OK)
|
||||
} catch {
|
||||
restore()
|
||||
this.ctx.logger.warn('config file %C is read-only, %d pending change(s) kept in memory', this.filename, this.journal.size)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this._writeText(text, this.cache.content)
|
||||
} catch (error) {
|
||||
restore()
|
||||
if (error instanceof StaleWriteError) {
|
||||
this.dirtyRead = true
|
||||
this.dirtyWrite = true
|
||||
return
|
||||
}
|
||||
this.ctx.logger.warn('failed to write config file %C, %d pending change(s) kept in memory', this.filename, this.journal.size)
|
||||
throw error
|
||||
}
|
||||
this.cache = { content: text, data }
|
||||
this._scheduleApply()
|
||||
}
|
||||
|
||||
/** Schedule a write of the current root entry data. */
|
||||
write() {
|
||||
this.context.emit('loader/config-update')
|
||||
return this.writeFile(this.root.data)
|
||||
/**
|
||||
* Write through a temp file and a rename, so that no reader ever sees a
|
||||
* truncated file. When `expected` is given, the rename (and every retry of
|
||||
* it) only goes ahead while the file still holds that content.
|
||||
*/
|
||||
private async _writeText(text: string, expected?: string) {
|
||||
const tmp = join(dirname(this.filename), `.${basename(this.filename)}.${process.pid}.${this._seq++}.tmp`)
|
||||
try {
|
||||
await writeFile(tmp, text)
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
if (expected !== undefined) {
|
||||
const current = await readFile(this.filename, 'utf8').catch((error) => {
|
||||
if (isENOENT(error)) return undefined
|
||||
throw error
|
||||
})
|
||||
if (current !== expected) throw new StaleWriteError()
|
||||
}
|
||||
try {
|
||||
await rename(tmp, this.filename)
|
||||
return
|
||||
} catch (error) {
|
||||
if (attempt >= RENAME_RETRIES || !isTransientRenameError(error)) throw error
|
||||
}
|
||||
await sleep(RENAME_BACKOFF * (attempt + 1))
|
||||
}
|
||||
} catch (error) {
|
||||
await rm(tmp, { force: true }).catch(() => {})
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private dump(data: EntryOptions[]) {
|
||||
return this.type === 'application/yaml'
|
||||
? yaml.dump(data, { schema })
|
||||
: JSON.stringify(data, null, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Entries the file leaves anonymous get an id here, before the loader sees
|
||||
* them. One that looks exactly like an entry we named on the previous read
|
||||
* keeps that id, so editing a neighbor does not restart it; anything else
|
||||
* gets a fresh id. Assigned ids stay in memory until the next write.
|
||||
*/
|
||||
private _assignIds(data: EntryOptions[]) {
|
||||
const used = new Set<string>()
|
||||
const pending: { options: EntryOptions; parent: string | null }[] = []
|
||||
const walk = (entries: EntryOptions[], parent: string | null) => {
|
||||
for (const options of entries) {
|
||||
if (options.id) {
|
||||
used.add(options.id)
|
||||
} else {
|
||||
pending.push({ options, parent })
|
||||
}
|
||||
if (options.group && Array.isArray(options.config)) {
|
||||
walk(options.config, options.id ?? null)
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(data, null)
|
||||
|
||||
const previous = this.anonymous
|
||||
this.anonymous = new Map()
|
||||
for (const { options, parent } of pending) {
|
||||
let id: string | undefined
|
||||
for (const [candidate, info] of previous) {
|
||||
if (info.parent !== parent || !deepEqual(info.options, options)) continue
|
||||
id = candidate
|
||||
previous.delete(candidate)
|
||||
break
|
||||
}
|
||||
if (!id) {
|
||||
do {
|
||||
id = Math.random().toString(16).slice(2, 10)
|
||||
} while (used.has(id) || this.store[id])
|
||||
}
|
||||
used.add(id)
|
||||
this.anonymous.set(id, { parent, options: structuredClone(options) })
|
||||
const rest = { ...options }
|
||||
for (const key of Object.keys(options)) delete (options as unknown as Record<string, unknown>)[key]
|
||||
Object.assign(options, { id }, rest)
|
||||
}
|
||||
}
|
||||
|
||||
private _scheduleApply() {
|
||||
if (this._disposed || !this.cache) return
|
||||
const tree = applyPatches(this.cache.data, this.config.patches, this.warn)
|
||||
this._pendingTree = applyJournal(tree, this.journal, undefined, this.warn)
|
||||
this._applyTask ??= (async () => {
|
||||
try {
|
||||
while (this._pendingTree) {
|
||||
const tree = this._pendingTree
|
||||
this._pendingTree = undefined
|
||||
await this.root.update(tree)
|
||||
}
|
||||
} finally {
|
||||
this._applyTask = undefined
|
||||
}
|
||||
})()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
import type { EntryChange, EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
|
||||
import { deepEqual, type Dict, omit } from '@deepseek-ai/cosmokit'
|
||||
|
||||
/** One tree-side mutation that has not reached the file yet, keyed by entry id. */
|
||||
export type JournalRecord = JournalRecord.Remove | JournalRecord.Upsert
|
||||
|
||||
export namespace JournalRecord {
|
||||
export interface Remove {
|
||||
kind: 'remove'
|
||||
}
|
||||
|
||||
export interface Upsert {
|
||||
kind: 'upsert'
|
||||
/** The entry did not exist when the tree first reported it. */
|
||||
created: boolean
|
||||
/**
|
||||
* The group the tree placed the entry in (`null` for the root), or
|
||||
* `undefined` when the tree did not relocate it.
|
||||
*/
|
||||
parent: string | null | undefined
|
||||
/** Index within `parent`; only meaningful alongside a `parent`. */
|
||||
position: number
|
||||
/**
|
||||
* Per-key delta relative to the options the entry had when the change
|
||||
* was reported; `undefined` deletes a key. Collected at report time: the
|
||||
* live tree is reconciled asynchronously and may lag behind the file.
|
||||
*/
|
||||
changes: Dict
|
||||
}
|
||||
}
|
||||
|
||||
export type Journal = Map<string, JournalRecord>
|
||||
|
||||
export function diffOptions(legacy: EntryOptions, options: EntryOptions) {
|
||||
const changes: Dict = {}
|
||||
for (const key of new Set([...Object.keys(legacy), ...Object.keys(options)])) {
|
||||
if (key === 'id') continue
|
||||
if (deepEqual((legacy as unknown as Record<string, unknown>)[key], (options as unknown as Record<string, unknown>)[key])) continue
|
||||
changes[key] = (options as unknown as Record<string, unknown>)[key]
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
/** Fold a later record into an earlier one for the same entry. */
|
||||
export function mergeRecords(older: JournalRecord | undefined, newer: JournalRecord): JournalRecord | undefined {
|
||||
if (!older) return newer
|
||||
if (newer.kind === 'remove') {
|
||||
// an entry created and removed before ever being written leaves no trace
|
||||
return older.kind === 'upsert' && older.created ? undefined : newer
|
||||
}
|
||||
if (older.kind === 'remove') {
|
||||
return { ...newer, created: false }
|
||||
}
|
||||
const relocated = newer.parent !== undefined
|
||||
return {
|
||||
kind: 'upsert',
|
||||
created: older.created,
|
||||
parent: relocated ? newer.parent : older.parent,
|
||||
position: relocated ? newer.position : older.position,
|
||||
changes: { ...older.changes, ...newer.changes },
|
||||
}
|
||||
}
|
||||
|
||||
export function merge(journal: Journal, id: string, record: JournalRecord) {
|
||||
const merged = mergeRecords(journal.get(id), record)
|
||||
if (merged) {
|
||||
journal.set(id, merged)
|
||||
} else {
|
||||
journal.delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
export function record(journal: Journal, change: EntryChange, parent: string | null) {
|
||||
const { id, options, legacy } = change
|
||||
if (!options) {
|
||||
return merge(journal, id, { kind: 'remove' })
|
||||
}
|
||||
const position = change.group.data.indexOf(options)
|
||||
if (!legacy) {
|
||||
return merge(journal, id, { kind: 'upsert', created: true, parent, position, changes: omit(options, ['id']) })
|
||||
}
|
||||
merge(journal, id, {
|
||||
kind: 'upsert',
|
||||
created: false,
|
||||
parent: change.from ? parent : undefined,
|
||||
position,
|
||||
changes: diffOptions(legacy, options),
|
||||
})
|
||||
}
|
||||
|
||||
export interface FlatEntry {
|
||||
parent: string | null
|
||||
position: number
|
||||
options: EntryOptions
|
||||
list: EntryOptions[]
|
||||
}
|
||||
|
||||
/** Index every entry (including nested group members) by id. */
|
||||
export function flatten(data: EntryOptions[], parent: string | null = null, result = new Map<string, FlatEntry>()) {
|
||||
data.forEach((options, position) => {
|
||||
if (options.id) result.set(options.id, { parent, position, options, list: data })
|
||||
if (options.group && Array.isArray(options.config)) {
|
||||
flatten(options.config, options.id ?? null, result)
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
export function applyChanges(options: EntryOptions, changes: Dict, filter?: (key: string) => boolean) {
|
||||
for (const [key, value] of Object.entries(changes)) {
|
||||
if (filter && !filter(key)) continue
|
||||
if (value === undefined) {
|
||||
delete (options as unknown as Record<string, unknown>)[key]
|
||||
} else {
|
||||
(options as unknown as Record<string, unknown>)[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function detach(data: EntryOptions[], id: string) {
|
||||
const flat = flatten(data).get(id)
|
||||
if (!flat) return
|
||||
flat.list.splice(flat.list.indexOf(flat.options), 1)
|
||||
return flat.options
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert `options` under `parent` (a group id, or `null` for the root) at
|
||||
* `position`, clamped to the group's length. Returns false when the parent
|
||||
* group does not exist.
|
||||
*/
|
||||
export function place(data: EntryOptions[], options: EntryOptions, parent: string | null, position: number) {
|
||||
let list = data
|
||||
if (parent !== null) {
|
||||
const group = flatten(data).get(parent)
|
||||
if (!group?.options.group) return false
|
||||
list = group.options.config ??= []
|
||||
}
|
||||
list.splice(Math.min(position, list.length), 0, options)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Overlay a journal onto `data` in place. `filter(id, key)` restricts which
|
||||
* changes apply; `key` is `null` when asking about the entry as a whole
|
||||
* (creation, removal, relocation).
|
||||
*/
|
||||
export function applyJournal(
|
||||
data: EntryOptions[],
|
||||
journal: Journal,
|
||||
filter: (id: string, key: string | null) => boolean = () => true,
|
||||
warn: (format: string, ...args: any[]) => void = () => {},
|
||||
) {
|
||||
for (const [id, record] of journal) {
|
||||
if (!filter(id, null)) continue
|
||||
if (record.kind === 'remove') {
|
||||
detach(data, id)
|
||||
continue
|
||||
}
|
||||
const flat = flatten(data).get(id)
|
||||
if (!flat) {
|
||||
const options = { id } as EntryOptions
|
||||
applyChanges(options, record.changes, key => filter(id, key))
|
||||
const parent = record.parent ?? null
|
||||
if (!place(data, options, parent, record.position)) {
|
||||
warn('cannot place entry %C: group %C not found', id, parent)
|
||||
}
|
||||
continue
|
||||
}
|
||||
applyChanges(flat.options, record.changes, key => filter(id, key))
|
||||
if (record.parent !== undefined && flat.parent !== record.parent) {
|
||||
detach(data, id)
|
||||
if (!place(data, flat.options, record.parent, record.position)) {
|
||||
warn('cannot move entry %C: group %C not found', id, record.parent)
|
||||
place(data, flat.options, flat.parent, flat.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
export interface Conflict {
|
||||
id: string
|
||||
reason: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Three-way reconciliation of a journal against a new file state. The file
|
||||
* wins every conflict: conflicting keys are dropped from the journal so that
|
||||
* the tree follows the file, and each drop is reported.
|
||||
*
|
||||
* `fileOwned(id, key)` scopes the check to what the file can actually express
|
||||
* (patch-owned keys and patch-inserted entries live elsewhere).
|
||||
*/
|
||||
export function reconcile(
|
||||
journal: Journal,
|
||||
base: Map<string, FlatEntry>,
|
||||
theirs: Map<string, FlatEntry>,
|
||||
fileOwned: (id: string, key: string | null) => boolean,
|
||||
) {
|
||||
const conflicts: Conflict[] = []
|
||||
for (const [id, record] of journal) {
|
||||
if (!fileOwned(id, null)) continue
|
||||
const b = base.get(id)
|
||||
const t = theirs.get(id)
|
||||
if (record.kind === 'remove') {
|
||||
if (!t) {
|
||||
journal.delete(id)
|
||||
} else if (!b || !deepEqual(t.options, b.options)) {
|
||||
conflicts.push({ id, reason: b ? 'modified in file, removed at runtime' : 'added in file, removed at runtime' })
|
||||
journal.delete(id)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (!t) {
|
||||
if (b) {
|
||||
conflicts.push({ id, reason: 'removed in file, modified at runtime' })
|
||||
journal.delete(id)
|
||||
}
|
||||
// else: created at runtime, nothing in the file to conflict with
|
||||
continue
|
||||
}
|
||||
if (record.created) record.created = false
|
||||
for (const key of Object.keys(record.changes)) {
|
||||
if (!fileOwned(id, key)) continue
|
||||
const bv = (b?.options as unknown as Record<string, unknown> | undefined)?.[key]
|
||||
const tv = (t.options as unknown as Record<string, unknown>)[key]
|
||||
if (deepEqual(bv, tv) || deepEqual(tv, record.changes[key])) continue
|
||||
conflicts.push({ id, reason: `key "${key}" modified both in file and at runtime` })
|
||||
delete record.changes[key]
|
||||
}
|
||||
if (record.parent !== undefined && b && t.parent !== b.parent && t.parent !== record.parent) {
|
||||
conflicts.push({ id, reason: 'moved both in file and at runtime' })
|
||||
record.parent = undefined
|
||||
}
|
||||
const moved = record.parent !== undefined && record.parent !== t.parent
|
||||
if (!Object.keys(record.changes).length && !moved) {
|
||||
journal.delete(id)
|
||||
}
|
||||
}
|
||||
return conflicts
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
|
||||
import { type Dict, omit } from '@deepseek-ai/cosmokit'
|
||||
import { applyChanges, detach, flatten, type Journal, place } from './journal.ts'
|
||||
|
||||
export interface PatchOptions {
|
||||
id?: string
|
||||
insert?: EntryOptions[]
|
||||
name?: string
|
||||
config?: any
|
||||
group?: boolean | null
|
||||
disabled?: boolean | null
|
||||
inject?: any
|
||||
intercept?: any
|
||||
isolate?: any
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export type Warn = (format: string, ...args: any[]) => void
|
||||
|
||||
function randomId(used: (id: string) => boolean) {
|
||||
let id: string
|
||||
do {
|
||||
id = Math.random().toString(16).slice(2, 10)
|
||||
} while (used(id))
|
||||
return id
|
||||
}
|
||||
|
||||
/** Give every entry (nested groups included) an id, in place. */
|
||||
export function ensureIds(entries: EntryOptions[], used: (id: string) => boolean) {
|
||||
for (const entry of entries) {
|
||||
if (!entry.id) entry.id = randomId(used)
|
||||
if (entry.group && Array.isArray(entry.config)) ensureIds(entry.config, used)
|
||||
}
|
||||
}
|
||||
|
||||
/** Runtime changes are routed back to an inserted entry by its id, so every insert needs one. */
|
||||
export function ensureInsertIds(patches: PatchOptions[] | undefined, used: (id: string) => boolean) {
|
||||
for (const patch of patches ?? []) {
|
||||
if (patch.insert) ensureIds(patch.insert, used)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overlay `patches` onto a fresh clone of `data`. Inserted entries are cloned
|
||||
* too, so the tree owns every object it mounts.
|
||||
*/
|
||||
export function applyPatches(data: EntryOptions[], patches: PatchOptions[] | undefined, warn: Warn): EntryOptions[] {
|
||||
data = structuredClone(data)
|
||||
if (!patches?.length) return data
|
||||
|
||||
const entryMap = new Map<string, EntryOptions>()
|
||||
const buildMap = (entries: EntryOptions[]) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.id) entryMap.set(entry.id, entry)
|
||||
if (entry.group && Array.isArray(entry.config)) {
|
||||
buildMap(entry.config)
|
||||
}
|
||||
}
|
||||
}
|
||||
buildMap(data)
|
||||
|
||||
for (const patch of patches) {
|
||||
const { id, insert, name, ...overrides } = patch
|
||||
|
||||
if (insert) {
|
||||
const clone = structuredClone(insert)
|
||||
if (id) {
|
||||
const target = entryMap.get(id)
|
||||
if (!target) {
|
||||
warn('patch insert: entry %C not found', id)
|
||||
continue
|
||||
}
|
||||
if (!target.group) {
|
||||
warn('patch insert: entry %C is not a group', id)
|
||||
continue
|
||||
}
|
||||
if (!Array.isArray(target.config)) target.config = []
|
||||
target.config.push(...clone)
|
||||
} else {
|
||||
data.push(...clone)
|
||||
}
|
||||
buildMap(clone)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
warn('patch: id is required for non-insert patches')
|
||||
continue
|
||||
}
|
||||
|
||||
const target = entryMap.get(id)
|
||||
if (!target) {
|
||||
warn('patch: entry %C not found', id)
|
||||
continue
|
||||
}
|
||||
|
||||
if (name && name !== target.name) {
|
||||
warn('patch: name mismatch for %C (expected %C, got %C), skipping', id, target.name, name)
|
||||
continue
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(overrides)) {
|
||||
if (key === 'id') continue
|
||||
(target as unknown as Record<string, unknown>)[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Who owns an entry, or one key of it, in the patched tree: the file, a patch
|
||||
* that overrides that key (the last one wins), or the patch that inserted the
|
||||
* whole entry.
|
||||
*/
|
||||
export type EntryOwner = EntryOwner.File | EntryOwner.Patch | EntryOwner.Insert
|
||||
|
||||
export namespace EntryOwner {
|
||||
export interface File {
|
||||
type: 'file'
|
||||
}
|
||||
|
||||
export interface Patch {
|
||||
type: 'patch'
|
||||
/** Index into the patch list. */
|
||||
index: number
|
||||
}
|
||||
|
||||
export interface Insert {
|
||||
type: 'insert'
|
||||
/** Index into the patch list. */
|
||||
index: number
|
||||
/** The insert list (or nested group config) holding the entry. */
|
||||
list: EntryOptions[]
|
||||
options: EntryOptions
|
||||
/** The group the entry is inserted into (`null` for the root). */
|
||||
parent: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export class PatchIndex {
|
||||
private keys = new Map<string, Map<string, number>>()
|
||||
private inserts = new Map<string, EntryOwner.Insert>()
|
||||
|
||||
constructor(public patches: PatchOptions[]) {
|
||||
patches.forEach((patch, index) => {
|
||||
const { id, insert } = patch
|
||||
if (insert) {
|
||||
const walk = (list: EntryOptions[], parent: string | null) => {
|
||||
for (const options of list) {
|
||||
this.inserts.set(options.id, { type: 'insert', index, list, options, parent })
|
||||
if (options.group && Array.isArray(options.config)) walk(options.config, options.id)
|
||||
}
|
||||
}
|
||||
walk(insert, id ?? null)
|
||||
return
|
||||
}
|
||||
if (!id) return
|
||||
const keys = this.keys.get(id) ?? new Map<string, number>()
|
||||
for (const key of Object.keys(omit(patch, ['id', 'insert', 'name']))) {
|
||||
keys.set(key, index)
|
||||
}
|
||||
this.keys.set(id, keys)
|
||||
})
|
||||
}
|
||||
|
||||
/** Owner of the entry as a whole. */
|
||||
entry(id: string | null): EntryOwner {
|
||||
if (id === null) return { type: 'file' }
|
||||
return this.inserts.get(id) ?? { type: 'file' }
|
||||
}
|
||||
|
||||
/** Owner of one key of an entry. */
|
||||
key(id: string, key: string): EntryOwner {
|
||||
const insert = this.inserts.get(id)
|
||||
if (insert) return insert
|
||||
const index = this.keys.get(id)?.get(key)
|
||||
return index === undefined ? { type: 'file' } : { type: 'patch', index }
|
||||
}
|
||||
|
||||
fileOwned(id: string, key: string | null) {
|
||||
return (key === null ? this.entry(id) : this.key(id, key)).type === 'file'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Route a journal to its owners: file-owned changes are applied to `data`,
|
||||
* patch-owned ones to `patches`. Both are mutated in place, so pass clones.
|
||||
* Returns whether any patch changed.
|
||||
*/
|
||||
export function routeJournal(journal: Journal, data: EntryOptions[], patches: PatchOptions[], warn: Warn) {
|
||||
const index = new PatchIndex(patches)
|
||||
let patched = false
|
||||
|
||||
const insertList = (owner: EntryOwner.Insert) => owner.options.config ??= []
|
||||
|
||||
const placeAt = (options: EntryOptions, parent: string | null, position: number) => {
|
||||
const owner = index.entry(parent)
|
||||
if (owner.type === 'insert') {
|
||||
const list = insertList(owner)
|
||||
list.splice(Math.min(position, list.length), 0, options)
|
||||
patched = true
|
||||
} else if (!place(data, options, parent, position)) {
|
||||
warn('cannot place entry %C: group %C not found', options.id, parent)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [id, record] of journal) {
|
||||
const owner = index.entry(id)
|
||||
if (record.kind === 'remove') {
|
||||
if (owner.type === 'insert') {
|
||||
owner.list.splice(owner.list.indexOf(owner.options), 1)
|
||||
patched = true
|
||||
} else {
|
||||
detach(data, id)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
const current = owner.type === 'insert' ? owner : flatten(data).get(id)
|
||||
if (!current) {
|
||||
const options = { id } as EntryOptions
|
||||
applyChanges(options, record.changes)
|
||||
placeAt(options, record.parent ?? null, record.position)
|
||||
continue
|
||||
}
|
||||
|
||||
if (record.parent !== undefined && current.parent !== record.parent) {
|
||||
// moving across owners: carry the full options over
|
||||
if (owner.type === 'insert') {
|
||||
owner.list.splice(owner.list.indexOf(owner.options), 1)
|
||||
patched = true
|
||||
} else {
|
||||
detach(data, id)
|
||||
}
|
||||
applyChanges(current.options, record.changes)
|
||||
placeAt(current.options, record.parent, record.position)
|
||||
continue
|
||||
}
|
||||
|
||||
if (owner.type === 'insert') {
|
||||
applyChanges(current.options, record.changes)
|
||||
patched = true
|
||||
continue
|
||||
}
|
||||
|
||||
const patchChanges: Dict<Dict> = {}
|
||||
applyChanges(current.options, record.changes, (key) => {
|
||||
const keyOwner = index.key(id, key)
|
||||
if (keyOwner.type !== 'patch') return true
|
||||
;(patchChanges[keyOwner.index] ??= {})[key] = record.changes[key]
|
||||
return false
|
||||
})
|
||||
for (const [i, changes] of Object.entries(patchChanges)) {
|
||||
applyChanges((patches as unknown as Record<string, PatchOptions>)[i] as any, changes)
|
||||
patched = true
|
||||
}
|
||||
}
|
||||
|
||||
return patched
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/cordis-plugin-loader",
|
||||
"description": "Plugin loader for cordis",
|
||||
"version": "1.0.6",
|
||||
"version": "1.1.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Context, Fiber, Inject, resolveConfig } from '@deepseek-ai/cordis'
|
||||
import { Context, Fiber, Inject } from '@deepseek-ai/cordis'
|
||||
import { deepEqual, isNullable, updateVolatile, volatileEntries, type Volatile } from '@deepseek-ai/cosmokit'
|
||||
import { Loader } from '../index.ts'
|
||||
import { EntryGroup } from './group.ts'
|
||||
@@ -80,7 +80,7 @@ export class Entry {
|
||||
if (this.options.group) return false
|
||||
let entry: Entry | undefined = this
|
||||
do {
|
||||
if (this.disabledOf(entry.options)) return true
|
||||
if (this._resolveDisabled(entry.options)) return true
|
||||
entry = entry.parent.ctx.fiber.entry
|
||||
} while (entry)
|
||||
return false
|
||||
@@ -90,7 +90,7 @@ export class Entry {
|
||||
* Effective disabled state: a `!!js` expression evaluates against the loader
|
||||
* context. The raw node stays in the options, so write-back keeps the form.
|
||||
*/
|
||||
private disabledOf(options: EntryOptions): boolean {
|
||||
private _resolveDisabled(options: EntryOptions): boolean {
|
||||
return isJsExpr(options.disabled)
|
||||
? Boolean(this.evaluate(options.disabled.__jsExpr))
|
||||
: Boolean(options.disabled)
|
||||
@@ -100,12 +100,15 @@ export class Entry {
|
||||
return evaluate(this.ctx, expr)
|
||||
}
|
||||
|
||||
private _patchContext(diff: string[]) {
|
||||
this.context.waterfall('loader/patch-context', this, () => {
|
||||
private async _patchContext(diff: string[]) {
|
||||
await this.context.waterfall('loader/patch-context', this, async () => {
|
||||
Object.setPrototypeOf(this.ctx, this.parent.ctx)
|
||||
|
||||
if (this.fiber?.uid && (diff.includes('config') || this.options.group)) {
|
||||
this.fiber.update(this.options.config, true)
|
||||
// A simultaneous context/config edit must restart against the new dependencies.
|
||||
const fiber = this.fiber.ctx.fiber
|
||||
for (const name of Object.keys(fiber.inject)) fiber._checkImpl(name)
|
||||
await fiber.update(this.options.config, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -136,7 +139,7 @@ export class Entry {
|
||||
|
||||
// step 2: execute
|
||||
if (this.disabled) {
|
||||
this.fiber?.dispose()
|
||||
await this.fiber?.dispose()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -152,11 +155,11 @@ export class Entry {
|
||||
const volatileOnly = changes.length === 1 && changes[0] === 'config'
|
||||
&& this.fiber.state === FiberState.ACTIVE && Object.getPrototypeOf(this.ctx) === this.parent.ctx
|
||||
&& equalExceptVolatile(legacy.config, this.options.config, this.fiber.runtime?.Config)
|
||||
if (volatileOnly) this.fiber._config = this.options.config
|
||||
if (volatileOnly) this.fiber.rawConfig = this.options.config
|
||||
const pending = volatileOnly && this._commitVolatile() ? [] : changes
|
||||
if (!pending.length && !force) return
|
||||
this.context.emit('loader/partial-dispose', this, legacy, true)
|
||||
this._patchContext(pending)
|
||||
await this._patchContext(pending)
|
||||
} else {
|
||||
await this.init()
|
||||
}
|
||||
@@ -168,19 +171,19 @@ export class Entry {
|
||||
* @returns `false` when an ordinary effective value changed, so the caller applies the ordinary update lifecycle.
|
||||
*/
|
||||
private _commitVolatile(): boolean {
|
||||
const fiber = this.fiber!
|
||||
const refs = volatileEntries(fiber.config)
|
||||
const fiber = this.fiber!.ctx.fiber
|
||||
const refs = volatileEntries(fiber.effectiveConfig)
|
||||
if (!refs.length) return true
|
||||
const raw = this.options.config
|
||||
let candidate: unknown
|
||||
try {
|
||||
candidate = resolveConfig(fiber.runtime!, fiber.ctx.waterfall(fiber, 'internal/config', raw, () => raw))
|
||||
candidate = fiber.resolveConfig(raw)
|
||||
} catch (error) {
|
||||
this.ctx.logger.warn('volatile config update failed for %C', this.options.id)
|
||||
this.ctx.logger.warn(error)
|
||||
return true
|
||||
}
|
||||
if (!deepEqual(fiber.config, candidate, true)) {
|
||||
if (!deepEqual(fiber.effectiveConfig, candidate, true)) {
|
||||
this.ctx.logger.debug('ordinary config values of %C changed with its volatile values; applying the ordinary update', this.options.id)
|
||||
return false
|
||||
}
|
||||
@@ -219,11 +222,10 @@ export class Entry {
|
||||
} finally {
|
||||
this._initTask = undefined
|
||||
}
|
||||
const notify = () => {
|
||||
this.fiber?.await().catch(() => {}).finally(() => {
|
||||
if (this.loader.getTasks().length) return
|
||||
this.ctx.reflect.notify(['loader'])
|
||||
}
|
||||
this.fiber?.await().then(notify, notify)
|
||||
})
|
||||
}
|
||||
|
||||
private async _init() {
|
||||
@@ -233,13 +235,11 @@ export class Entry {
|
||||
} catch (error) {
|
||||
this.ctx.logger.error(error)
|
||||
return
|
||||
} finally {
|
||||
this._initTask = undefined
|
||||
}
|
||||
const plugin = this.loader.unwrapExports(moduleNamespace)
|
||||
this._patchContext([])
|
||||
await this._patchContext([])
|
||||
this.loader.showLog(this, 'apply')
|
||||
this.fiber = this.ctx.registry.plugin(plugin, this.options.config, this.getOuterStack).ctx.fiber
|
||||
this.fiber = this.ctx.registry.plugin(plugin, this.options.config, this.getOuterStack)
|
||||
this.moduleNamespace = moduleNamespace
|
||||
}
|
||||
}
|
||||
@@ -36,12 +36,13 @@ export class EntryGroup {
|
||||
|
||||
remove(id: string, isDispose = false) {
|
||||
const entry = this.tree.store[id]
|
||||
if (!entry) return
|
||||
if (!entry || entry.parent !== this) return
|
||||
// Unregister before disposal so Loader can distinguish removal from self-disposal.
|
||||
delete this.tree.store[id]
|
||||
entry.fiber?.dispose()
|
||||
if (!isDispose) {
|
||||
this.unlink(entry.options)
|
||||
}
|
||||
delete this.tree.store[id]
|
||||
this.context.emit('loader/partial-dispose', entry, entry.options, false)
|
||||
}
|
||||
|
||||
@@ -78,9 +79,7 @@ export class Group extends EntryGroup {
|
||||
|
||||
constructor(public ctx: Context, public config: EntryOptions[]) {
|
||||
super(ctx, ctx.fiber.entry!.parent.tree)
|
||||
ctx.on('internal/update', (config) => {
|
||||
this.update(config)
|
||||
})
|
||||
ctx.on('internal/update', config => this.update(config))
|
||||
}
|
||||
|
||||
async* [Service.init]() {
|
||||
|
||||
@@ -96,7 +96,7 @@ export default function isolate(ctx: Context) {
|
||||
entry.ctx[Context.isolate] = Object.create(entry.ctx[Context.isolate])
|
||||
})
|
||||
|
||||
ctx.on('loader/patch-context', (entry, next) => {
|
||||
ctx.on('loader/patch-context', async (entry, next) => {
|
||||
// step 1: generate new isolate map
|
||||
const newMap: Dict<symbol> = Object.create(entry.parent.ctx[Context.isolate])
|
||||
for (const name of Object.keys(entry.options.isolate ?? {})) {
|
||||
@@ -113,12 +113,13 @@ export default function isolate(ctx: Context) {
|
||||
for (const symbol of [oldMap[name], newMap[name]]) {
|
||||
const impl = symbol && entry.ctx.reflect.store[symbol]
|
||||
if (!impl) continue
|
||||
if (!impl.fiber) {
|
||||
const fiber = impl.ctx.fiber
|
||||
if (!fiber) {
|
||||
entry.ctx.logger.warn(new Error(`expected service ${name} to be implemented`))
|
||||
continue
|
||||
}
|
||||
diff[name] = [oldMap[name], newMap[name], (entry.ctx as DelimitedContext)[delim], (impl.fiber.ctx as DelimitedContext)[delim]]
|
||||
if ((entry.ctx as DelimitedContext)[delim] !== (impl.fiber.ctx as DelimitedContext)[delim]) break
|
||||
diff[name] = [oldMap[name], newMap[name], (entry.ctx as DelimitedContext)[delim], (fiber.ctx as DelimitedContext)[delim]]
|
||||
if ((entry.ctx as DelimitedContext)[delim] !== (fiber.ctx as DelimitedContext)[delim]) break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,13 +130,15 @@ export default function isolate(ctx: Context) {
|
||||
swap(entry.ctx[Context.intercept], entry.options.intercept)
|
||||
|
||||
// step 4: reload fiber
|
||||
next()
|
||||
await next()
|
||||
|
||||
// step 5: replace service impl
|
||||
for (const [symbol1, symbol2, flag1, flag2] of Object.values(diff)) {
|
||||
if (flag1 === flag2 && entry.ctx.reflect.store[symbol1] && !entry.ctx.reflect.store[symbol2]) {
|
||||
entry.ctx.reflect.store[symbol2] = entry.ctx.reflect.store[symbol1]
|
||||
const impl = entry.ctx.reflect.store[symbol1]
|
||||
if (flag1 === flag2 && impl && !entry.ctx.reflect.store[symbol2]) {
|
||||
entry.ctx.reflect.store[symbol2] = impl
|
||||
delete entry.ctx.reflect.store[symbol1]
|
||||
impl.isolate = symbol2
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,42 @@ import { isNonNullable, type Dict } from '@deepseek-ai/cosmokit'
|
||||
import { Entry, type EntryOptions } from './entry.ts'
|
||||
import { EntryGroup } from './group.ts'
|
||||
|
||||
/**
|
||||
* One tree-side mutation, reported to `EntryTree.commit()` synchronously after
|
||||
* it has been applied to `entry.options` / `group.data`.
|
||||
*
|
||||
* - `options` absent: the entry was removed from `group`.
|
||||
* - `legacy` absent: the entry was created in `group`.
|
||||
* - both present: the entry was updated and now lives in `group`; `from` is
|
||||
* the group it left if the update moved it within the same tree. A move to
|
||||
* another tree reports a removal to the source tree and a creation to the
|
||||
* destination tree.
|
||||
*
|
||||
* `id` is local to the receiving tree; `Entry.id` prefixes it with the parent
|
||||
* tree's path. Only `EntryTree` mutation methods and explicit Fiber saves
|
||||
* report changes; file-driven `EntryGroup` reconciliation does not.
|
||||
*/
|
||||
export interface EntryChange {
|
||||
id: string
|
||||
group: EntryGroup
|
||||
from?: EntryGroup
|
||||
options?: EntryOptions
|
||||
legacy?: EntryOptions
|
||||
}
|
||||
|
||||
/** The entry and the same-tree members of its group, recursively. */
|
||||
function members(entry: Entry): Entry[] {
|
||||
const { tree } = entry.parent
|
||||
const result = [entry]
|
||||
const group = entry.subgroup
|
||||
if (group?.tree !== tree) return result
|
||||
for (const options of group.data) {
|
||||
const member = tree.store[options.id]
|
||||
if (member?.parent === group) result.push(...members(member))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** Mutable tree of loader entries. Persistence is supplied by subclasses. */
|
||||
export abstract class EntryTree {
|
||||
static readonly sep = ':'
|
||||
@@ -48,6 +84,13 @@ export abstract class EntryTree {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a random tree-local id when `options.id` is absent. Explicit ids
|
||||
* may contain `EntryTree.sep`; `resolve()` and `resolveGroup()` cannot
|
||||
* address such entries by nested path.
|
||||
* @param options - Entry options; a generated id is written to `options.id`.
|
||||
* @returns The existing or generated id.
|
||||
*/
|
||||
ensureId(options: Partial<EntryOptions>) {
|
||||
if (!options.id) {
|
||||
do {
|
||||
@@ -81,31 +124,57 @@ export abstract class EntryTree {
|
||||
/** Create an entry in the root group or a nested group. */
|
||||
async create(options: Omit<EntryOptions, 'id'>, parent: string | null = null, position = Infinity) {
|
||||
const group = this.resolveGroup(parent)
|
||||
const id = group.tree.ensureId(options)
|
||||
group.data.splice(position, 0, options as EntryOptions)
|
||||
group.tree.write()
|
||||
group.tree.commit({ id, group, options: options as EntryOptions })
|
||||
return group.create(options)
|
||||
}
|
||||
|
||||
/** Stop and remove an entry from its parent group. */
|
||||
remove(id: string) {
|
||||
const entry = this.resolve(id)
|
||||
entry.parent.remove(id)
|
||||
entry.parent.tree.write()
|
||||
const group = entry.parent
|
||||
const legacy = entry.options
|
||||
group.remove(legacy.id)
|
||||
group.tree.commit({ id: legacy.id, group, legacy })
|
||||
}
|
||||
|
||||
/** Update an entry and optionally move it to another group. */
|
||||
async update(id: string, options: Omit<EntryOptions, 'id' | 'name'>, parent?: string | null, position?: number) {
|
||||
const entry = this.resolve(id)
|
||||
const source = entry.parent
|
||||
const legacy = { ...entry.options }
|
||||
if (parent !== undefined) {
|
||||
const target = this.resolveGroup(parent)
|
||||
const moved = target.tree === source.tree ? [] : members(entry)
|
||||
for (const member of moved) {
|
||||
const { id } = member.options
|
||||
if (target.tree.store[id]) throw new Error(`entry ${id} already exists in the target tree`)
|
||||
}
|
||||
source.unlink(entry.options)
|
||||
target.data.splice(position ?? Infinity, 0, entry.options)
|
||||
target.tree.write()
|
||||
entry.parent = target
|
||||
// The target tree owns the moved entries from now on, so that it adopts
|
||||
// them instead of creating new ones when it reconciles its source.
|
||||
for (const member of moved) {
|
||||
delete source.tree.store[member.options.id]
|
||||
target.tree.store[member.options.id] = member
|
||||
if (member.subgroup?.tree === source.tree) member.subgroup.tree = target.tree
|
||||
}
|
||||
}
|
||||
source.tree.write()
|
||||
return entry.update(options, false, true)
|
||||
// `Entry.update` assigns the new options before its first `await`, so the
|
||||
// change is fully visible to `commit()` once the call returns.
|
||||
const task = entry.update(options, false, true)
|
||||
if (entry.parent.tree !== source.tree) {
|
||||
// a move across trees removes the entry from the source tree and
|
||||
// creates it in the target tree
|
||||
source.tree.commit({ id: legacy.id, group: source, legacy })
|
||||
entry.parent.tree.commit({ id: legacy.id, group: entry.parent, options: entry.options })
|
||||
} else {
|
||||
const from = entry.parent === source ? undefined : source
|
||||
entry.parent.tree.commit({ id: legacy.id, group: entry.parent, from, options: entry.options, legacy })
|
||||
}
|
||||
return task
|
||||
}
|
||||
|
||||
/** Import a plugin module from a specifier or `cordis:` builtin. */
|
||||
@@ -128,6 +197,6 @@ export abstract class EntryTree {
|
||||
}, getOuterStack)
|
||||
}
|
||||
|
||||
/** Persist current tree state. In-memory trees may implement this as a no-op. */
|
||||
abstract write(): void
|
||||
/** Report an applied mutation synchronously. In-memory trees may use a no-op. */
|
||||
abstract commit(change: EntryChange): void
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Context, Inject, Service, type Fiber } from '@deepseek-ai/cordis'
|
||||
import { defineProperty, isNullable, type Dict } from '@deepseek-ai/cosmokit'
|
||||
import { Context, Inject, Service } from '@deepseek-ai/cordis'
|
||||
import { defineProperty, isNullable, type Awaitable, type Dict } from '@deepseek-ai/cosmokit'
|
||||
import { ModuleLoader } from './internal.ts'
|
||||
import { Entry, type EntryOptions } from './config/entry.ts'
|
||||
import { EntryGroup } from './config/group.ts'
|
||||
import isolate from './config/isolate.ts'
|
||||
import { EntryTree } from './config/tree.ts'
|
||||
import { EntryTree, type EntryChange } from './config/tree.ts'
|
||||
import { interpolate } from './config/utils.ts'
|
||||
import { FiberState } from '../../../fiber-state.ts'
|
||||
|
||||
@@ -39,7 +39,7 @@ declare module '@deepseek-ai/cordis' {
|
||||
* @param next - continue context refresh and Loader's config update.
|
||||
* @mode waterfall
|
||||
*/
|
||||
'loader/patch-context'(entry: Entry, next: () => void): void
|
||||
'loader/patch-context'(entry: Entry, next: () => Awaitable<void>): Awaitable<void>
|
||||
}
|
||||
|
||||
interface Context {
|
||||
@@ -74,7 +74,7 @@ export namespace Loader {
|
||||
/**
|
||||
* Service that owns a loader entry tree and imports configured plugins.
|
||||
*
|
||||
* Subclasses provide persistence by implementing `write()` on `EntryTree`.
|
||||
* Subclasses provide persistence by implementing `commit(change)` on `EntryTree`.
|
||||
*/
|
||||
export class Loader extends EntryTree {
|
||||
declare [Service.config]: Loader.Intercept
|
||||
@@ -105,26 +105,25 @@ export class Loader extends EntryTree {
|
||||
|
||||
ctx.reflect.provide('loader', this, this[Service.check])
|
||||
|
||||
ctx.on('internal/config', function (this: Fiber, _config, next) {
|
||||
ctx.on('internal/config', (fiber, next) => {
|
||||
const config = next()
|
||||
if (!this.entry || this.parent.fiber?.entry === this.entry) return config
|
||||
// Tree carriers (Group, Include) keep their configs literal: their
|
||||
// entry and patch lists hold other rows' configs, whose `!!js`
|
||||
// expressions belong to those rows' own fibers.
|
||||
const plugin = this.runtime?.callback as Record<PropertyKey, unknown> | undefined
|
||||
if (plugin?.[EntryGroup.key]) return config
|
||||
return interpolate(this.ctx, config)
|
||||
}, { global: true })
|
||||
if (!fiber.entry || fiber.parent.fiber?.entry === fiber.entry) return config
|
||||
// Tree carriers preserve the expressions owned by their child entries.
|
||||
if ((fiber.runtime?.callback as Record<PropertyKey, unknown> | undefined)?.[EntryGroup.key]) return config
|
||||
return interpolate(fiber.ctx, config)
|
||||
})
|
||||
|
||||
ctx.on('internal/update', function (config, noSave, next) {
|
||||
if (!this.entry || noSave || this.parent.fiber?.entry === this.entry) return next()
|
||||
const unparse = (this.runtime?.Config as { simplify?: (value: any) => any } | undefined)?.simplify
|
||||
this.entry.options.config = unparse ? unparse.call(this.runtime!.Config, config) : config
|
||||
this.entry.parent.tree.write()
|
||||
return next()
|
||||
}, { global: true, prepend: true })
|
||||
ctx.on('internal/commit', (fiber, config) => {
|
||||
if (!fiber.entry || fiber.parent.fiber?.entry === fiber.entry) return
|
||||
const schema = fiber.runtime?.Config
|
||||
const unparse = (schema as { simplify?: (value: any) => any } | undefined)?.simplify
|
||||
const { entry } = fiber
|
||||
const legacy = { ...entry.options }
|
||||
entry.options.config = unparse ? unparse.call(schema, config) : config
|
||||
entry.parent.tree.commit({ id: entry.options.id, group: entry.parent, options: entry.options, legacy })
|
||||
})
|
||||
|
||||
ctx.on('internal/update', function (config, _, next) {
|
||||
ctx.on('internal/update', function (config, next) {
|
||||
if (!this.entry || this.parent.fiber?.entry === this.entry) return next()
|
||||
self.showLog(this.entry, 'reload')
|
||||
return next()
|
||||
@@ -156,24 +155,33 @@ export class Loader extends EntryTree {
|
||||
if (!ctx.registry.has(fiber.runtime!.callback)) return
|
||||
|
||||
// case 5: the entry's tree is being disposed
|
||||
const treeOwner = fiber.entry.parent.tree.ctx.fiber
|
||||
if (!treeOwner.uid || treeOwner.state === FiberState.UNLOADING) return
|
||||
if (!fiber.entry.parent.tree.ctx.fiber.uid) return
|
||||
|
||||
this.showLog(fiber.entry, 'unload')
|
||||
const { entry } = fiber
|
||||
this.showLog(entry, 'unload')
|
||||
|
||||
// case 6: fiber is disposed by loader behavior
|
||||
// case 6: the entry's parent is being unloaded, which takes the entry with it
|
||||
// (the parent may still be registered, e.g. when it has lost a dependency)
|
||||
if (fiber.parent.fiber.state === FiberState.UNLOADING) return
|
||||
|
||||
// case 7: the entry is being removed by the loader (`EntryGroup.remove`
|
||||
// unregisters it before disposing the fiber)
|
||||
if (entry.parent.tree.store[entry.options.id] !== entry) return
|
||||
|
||||
// case 8: fiber is disposed by loader behavior
|
||||
// such as inject checker, config file update, ancestor group disable
|
||||
if (fiber.entry.disabled) return
|
||||
if (entry.disabled) return
|
||||
|
||||
fiber.entry.options.disabled = true
|
||||
fiber.entry.parent.tree.write()
|
||||
const legacy = { ...entry.options }
|
||||
entry.options.disabled = true
|
||||
entry.parent.tree.commit({ id: entry.options.id, group: entry.parent, options: entry.options, legacy })
|
||||
})
|
||||
|
||||
ctx.plugin(isolate)
|
||||
}
|
||||
|
||||
write() {
|
||||
// Loader's root tree is in-memory; writes are no-ops.
|
||||
commit(_change: EntryChange) {
|
||||
// Loader's root tree is in-memory; commits are no-ops.
|
||||
}
|
||||
|
||||
[Service.check]() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 3,
|
||||
"target_dsh_version": "0.2.1-rc.5",
|
||||
"target_dsh_version": "0.2.2-rc.1",
|
||||
"provided_services": {
|
||||
"dsh": [
|
||||
"tools",
|
||||
@@ -76,24 +76,44 @@
|
||||
},
|
||||
"definition_api": {
|
||||
"package": "@deepseek-ai/dsh-tools",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-tools/-/dsh-tools-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-tOEMsbG1jH4EV80xcGucfeWsKHLjnETjRLtSnyFrVl2oma95tzTHBjti5MK60IvT2ijcCWhx8CrUKcqRksfDPA==",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "1e53932e7c4213ea24a7d87f06960b7a7702cb21f8a5741c97242b4a470a442a",
|
||||
"source_lines": [
|
||||
[
|
||||
8,
|
||||
888
|
||||
],
|
||||
[
|
||||
2625,
|
||||
2655
|
||||
]
|
||||
],
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-tools/-/dsh-tools-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-svB49dLPCHkKHgGwik3dTvEH0sL1vKwIzlQgqjE4qzEqFbSEROW8fByk3g2y2lxDv8TqgEUa0Vp2qFrVbm2lfw==",
|
||||
"license_sha256": "ebb4f09972aee8608be255debaf78451a68e95c290f55c240dec2ecfa16ea6be",
|
||||
"tarball_sha256": "9614366d61214a304f937d0f3b8481d5827d2aef4529f3bff9eb49ef8ac65283"
|
||||
"tarball_sha256": "bb94ee037bd7d3efe2d0654c6a1fa7084b268b895fa0ca6ceeb7e65d4fd6da72",
|
||||
"sources": [
|
||||
{
|
||||
"file": "lib/shared-DTdn5dXO.js",
|
||||
"sha256": "d02f0d8f36878313b0dae3a1bba5b77f7db4cffc77a0b72772048a36bacf2cf0",
|
||||
"lines": [
|
||||
[
|
||||
22,
|
||||
548
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "lib/index.js",
|
||||
"sha256": "1889cbf718d64518dd83245d4dba1e1f080b2c5326259c3ed5b1be7a7666a2bd",
|
||||
"lines": [
|
||||
[
|
||||
7,
|
||||
377
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "lib/shared-DTdn5dXO.js",
|
||||
"sha256": "d02f0d8f36878313b0dae3a1bba5b77f7db4cffc77a0b72772048a36bacf2cf0",
|
||||
"lines": [
|
||||
[
|
||||
566,
|
||||
596
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"command_followup": "one-text-message-submit-on-success",
|
||||
"pure_api": {
|
||||
@@ -117,11 +137,11 @@
|
||||
"packages": [
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-brand",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-brand/-/dsh-brand-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-7lUfWjWWAkrb/CFtMbXgjXLYQnL3KJwv+GLMTgY5EZ/4sOH0ufZUX8xlBWyMpdV2LqdvJ1yPKQ+mopH71/wbMQ==",
|
||||
"tarball_sha256": "9a0d30b3466783f7b0e52a636602630ab62f39598df9568600ddfaad97b389ca",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-brand/-/dsh-brand-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-ShGNEmNuEQA+bfkspWC2kGrdbrM4dFNhqlrNHIWsHO+PgVH1KoTKTLSraT1pt0A8YYk/moKE0gL2gY9dNBU+rw==",
|
||||
"tarball_sha256": "d953cb5e8dcd69be0b0347ce9a3b36421eefa751820cbe909f4a185348ad5530",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "14feb59256b891982b2017d73ed03ee65e2a7365f48e4902a856acb0e52d5eb0",
|
||||
"excerpts": [
|
||||
@@ -140,11 +160,11 @@
|
||||
},
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-util-crypto",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-util-crypto/-/dsh-util-crypto-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-7mGcnCH0BzIUzbnnC2Vx17Q/O3/+EfMHNt9iC2VaGr9SK010HYUpei/+6wrZsuGy77rqAUNhU6VB35wQTLM/VA==",
|
||||
"tarball_sha256": "3d99b1120718249a52fe854b84f4bacae21464345073d560019974268e19a089",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-util-crypto/-/dsh-util-crypto-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-I9Nhh3/64HPzjdR/mUt0J534FYHgodMUe0coXtA91+kxGcJQcSHOYouewv4hf0NWw0BuLh5zihTdtldZtobOWA==",
|
||||
"tarball_sha256": "c9ef0dfed280c83202f1d7bd5d32b1139a92a2fc375da62962ef709c3d1e4a6a",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "640b26c8e5a1156c6c8e8749a22f805c9c0e34444778eb4ee9529be3e88ec862",
|
||||
"excerpts": [
|
||||
@@ -163,13 +183,13 @@
|
||||
},
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-llm",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-llm/-/dsh-llm-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-g9mSILkqY7wByl0tpLBhXxWxQ3MoNzXokuJq86R/snPL3v1OonXv1b46wtvldEgyr88aK1gBBlifMsRnXjMWTA==",
|
||||
"tarball_sha256": "6b422d70aee363dcce81be0481dda9d801c2ae03a4f314497e407d184df2313c",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-llm/-/dsh-llm-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-XeEvH+6bpkjx0E2P1B5ZE+WWa+VHp4DimQaJDKNUZEFouqHMuN1B61TIz6IzUquRxa/6X4yJy+qXQ9WwAeMw5w==",
|
||||
"tarball_sha256": "cbf45039d3ef63e26987de514928a847a8c2d0b049df8fb24c11684e203b05d4",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "d18d8ae4c8d961e628708c1e4698e09716a0ebb80e55a672e0bde1029bec1155",
|
||||
"source_sha256": "8e541fc98a11897706a122fde1db42f8b2a41b3b354ba3fcb932e9a16929ac6a",
|
||||
"excerpts": [
|
||||
{
|
||||
"name": "createMessage",
|
||||
@@ -210,13 +230,13 @@
|
||||
},
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-session",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-session/-/dsh-session-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-A5NdQFgeY4qi29+lBzgZiOloiFe+E1iBbIRjJTplee79rswYGUUmUZYmOvAcdmk4tERkY2S9htuc+V+jNifqRw==",
|
||||
"tarball_sha256": "8571710817dbf5172079168861bfaef581efc26a4b83a4c003f2d1ef632ae7b4",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-session/-/dsh-session-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-e0zx9HFNn8EyqrymGQS85SjKOyr61//ChTgIVDlCBSZvBB723fuXyi9LiHIIuPTlkgmWJl3gm5J51ebEXew7PQ==",
|
||||
"tarball_sha256": "b8b1b4a7b9c8efbdbe4c134ed3b18cddf11842b62ad7675c21c05b3afef1cfb5",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "2f5dd07cb5c744a29abbb8003a8a4cefd9e582c6e2538074ca299a0ddd02a3bf",
|
||||
"source_sha256": "6fad466aa1f7aa64bfa038681ead5721f66c88e9891e25d357d2b8edbccd5494",
|
||||
"excerpts": [
|
||||
{
|
||||
"name": "SessionId",
|
||||
@@ -233,19 +253,19 @@
|
||||
},
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-system-prompt",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-system-prompt/-/dsh-system-prompt-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-HWW0k4Ug4XjRbCghLZYFMpLh8Ywcuh/bS7DAnjWfq4HbwwB+hV0XXJRF3iCf5600uYArfbuv4Ttz+4PHKu7k9w==",
|
||||
"tarball_sha256": "f223700954221de8cce2feaf6d6fbf526aa2ea5fd261fc0791edf1f858c31a02",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-system-prompt/-/dsh-system-prompt-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-1p9imdrenPnJTkLb6Coq+ELBDjngblM/YGP1Pa/xf1oisjLHcCoK2n9kkOUTIfgvoFtqWfxZJNZeKef71ZmlrA==",
|
||||
"tarball_sha256": "5ecb3f2ff2f0c037287c22c1ef917f1ce5cdd4c8055d51da6646ed5a39fe02a9",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "7be9f7aafbf345d01ddcc12fd297b16e9a1d41283a6a04073b867becca0bf020",
|
||||
"source_sha256": "18b692b03bbb717589085f63b126cbcf22963763c5a0565bb3a7a5d254e8e4a7",
|
||||
"excerpts": [
|
||||
{
|
||||
"name": "PERSONA_PREFIX_SECTION",
|
||||
"lines": [
|
||||
59,
|
||||
59
|
||||
61,
|
||||
61
|
||||
],
|
||||
"sha256": "11338254c278626eea6887b9cb5dc94d16f2ab4dae4bd7e555ea608c4b15648f"
|
||||
}
|
||||
@@ -256,19 +276,19 @@
|
||||
},
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-util-values",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-util-values/-/dsh-util-values-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-PL8TerJM/4jpZ2ArwgTpFbeDiAVnqkSh8Xinw2iEu8B6a+axmshzL4dCpwpf+uB8GHdXAhGz1cT+SBQwX/2TLQ==",
|
||||
"tarball_sha256": "2472d81f0dc4597bc5c4eeff493686a2b59a6b27cb6eb6654394192066d6ec05",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-util-values/-/dsh-util-values-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-UN4B/Fa9zAunYN3UuXDQ3QknxF/lMKNwTu2WxXKKEC+Ejz745po7I/JmsVFVa7+socfuwwTmVrfSsddRVU+lew==",
|
||||
"tarball_sha256": "e5de9a3c2600c70d72dba702cd0dfc8097264b7b6a7f393f4cfa799c81ecf759",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "4322a2aacdfe38476d2bbc62338120beea1e1660346d7db5b6c1e1f927a26b7c",
|
||||
"source_sha256": "8b4ffdf40126d48bd22017b4671f7e90612d01833fc865f7a0b60d3de4183e8b",
|
||||
"excerpts": [
|
||||
{
|
||||
"name": "deepFreeze",
|
||||
"lines": [
|
||||
824,
|
||||
860
|
||||
809,
|
||||
845
|
||||
],
|
||||
"sha256": "66ee7f28877afa912496ba944a581ac74e7b87390e6a47c4c640d8f477bdc6e2"
|
||||
}
|
||||
@@ -279,19 +299,19 @@
|
||||
},
|
||||
{
|
||||
"package": "@deepseek-ai/dsh-user-questions",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"license": "MIT",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-user-questions/-/dsh-user-questions-0.2.1-rc.5.tgz",
|
||||
"integrity": "sha512-Yz4c6YO804nvJ6DVJGPZSAF3DCJ7HJgpylBL6KfbI7Au3a6MV49mLLEv5cWs67qL/tyQMsJO9DI2N/xPlczEXA==",
|
||||
"tarball_sha256": "5a2cb8c9451b4288c26a3c132b60aac15229a33bae34ce3c7bb0050906d6a8bc",
|
||||
"tarball": "https://registry.npmjs.org/@deepseek-ai/dsh-user-questions/-/dsh-user-questions-0.2.2-rc.1.tgz",
|
||||
"integrity": "sha512-COY40boWZ7XEszSvMGZ4CY5ze5ujCdA+Trtqx5C6ioqWzH9e8YbkY7AxyCOadxVO++5zAsqvsAfq65cZUPIMGA==",
|
||||
"tarball_sha256": "e7c1c38234fea9c0634ec95272bd1005c48ce890d2b935c8442b4ae7fee1b51e",
|
||||
"source_file": "lib/index.js",
|
||||
"source_sha256": "e1c51afac2e31be8593c6426976e5f31dbe2a709b85ae1ea43ed406146f54265",
|
||||
"source_sha256": "69fee77aa57cc43de583d53a4877394c4cf3b5d58b8d7579fd8095c0a015bfbd",
|
||||
"excerpts": [
|
||||
{
|
||||
"name": "UserQuestionError",
|
||||
"lines": [
|
||||
429,
|
||||
434
|
||||
430,
|
||||
435
|
||||
],
|
||||
"sha256": "5d49a4ecdf40039b26824f3575a79820b48ed011f90641870f44fe5a41bf5ced"
|
||||
}
|
||||
@@ -303,12 +323,12 @@
|
||||
],
|
||||
"reuse": {
|
||||
"package": "@deepseek-ai/dsh-util-values",
|
||||
"version": "0.2.1-rc.5",
|
||||
"version": "0.2.2-rc.1",
|
||||
"file": "lib/index.js",
|
||||
"symbol": "deepFreeze",
|
||||
"lines": [
|
||||
824,
|
||||
860
|
||||
809,
|
||||
845
|
||||
],
|
||||
"sha256": "66ee7f28877afa912496ba944a581ac74e7b87390e6a47c4c640d8f477bdc6e2"
|
||||
},
|
||||
|
||||
+11
-7
@@ -224,7 +224,7 @@ tag. Items moved to 0.10.4 by name are marked below.
|
||||
- **Change mode and permissions while the agent works:** Tab, Shift+Tab, `/mode`, the hotbar and `/config mode` no longer refuse while a turn is running. A response that is already streaming is not restarted; the change applies at the agent's next step in the same turn (before its next tool batch, at every approval gate, and before its next model request, which carries the new mode notice). The status line says so until it applies. A call still waiting for approval goes back to the model under the new posture, and a call you already approved runs only if the new posture is equal or broader. Model, thinking level and provider stay locked until the turn ends.
|
||||
- **Steering interrupts thinking:** a steer (Ctrl+Enter, or Enter twice after queueing) now closes the in-flight model request at once, whether the model is waiting on headers, reasoning, writing text or emitting a tool call, and continues the same turn with your message. Text already shown is kept as interrupted output; unfinished reasoning and partial tool calls are discarded, never run.
|
||||
- **Modern MCP client:** MCP servers now connect through the official MCP SDK (2.4) behind Codewhale's Rust broker by default (`mcp_backend = "host"`; `"rust"` stays available for one release). Servers that speak the 2026-07-28 revision are discovered with `server/discover`; other stdio and HTTP servers fall back to the legacy `initialize` handshake, and configured SSE servers keep theirs. Rust keeps process, network, credential, catalog, approval and cancellation authority.
|
||||
- **DeepSeek Harness plugins:** reviewed DSH plugins (definition API pinned to DSH 0.2.1-rc.5 in a shared compatibility table) run in the Native extension host. Tool output is validated against its declared schema, unsupported options are refused instead of ignored, and plugin commands can submit a prompt with visible plugin attribution. Terminal command receipts are kept in thread snapshots without entering model history. Changing the compatibility table invalidates earlier trust. A newly enabled plugin's tools are offered on the first turn: the turn waits up to 10 seconds for the plugin host to finish loading instead of adding them only from the next turn. The importer reports that it does not convert `dsh.client` UI; Engine routes and a desktop slot exist, but isolated end-to-end plugin UI remains unqualified, including network containment. A plugin tool can call `ctx.sessions.flush(exec.agent.session)` to have the Engine save the conversation so far before it continues (Runtime, terminal and `exec` sessions); it reports failure instead of claiming a save. Installing a plugin whose bytes changed after review now returns a 409 conflict instead of a server error. A plugin tool can ask you a question with `ctx.userQuestions.ask` and gets back the options you picked, your own text, or a skip; the question shows like any other Codewhale question in the terminal and through the Runtime API. A plugin tool can start a child agent with `ctx.subagents.startActivation` (the `spawn` provider): the child runs through Codewhale's own sub-agent manager, needs the same approval as the model's `agent` tool, can carry an extra persona section and a narrower tool list set in its `agent/created` handler, and returns its result to the plugin.
|
||||
- **DeepSeek Harness plugins:** reviewed DSH plugins (definition API pinned to DSH 0.2.2-rc.1 in a shared compatibility table) run in the Native extension host. Tool output is validated against its declared schema, unsupported options are refused instead of ignored, and plugin commands can submit a prompt with visible plugin attribution. Terminal command receipts are kept in thread snapshots without entering model history. Changing the compatibility table invalidates earlier trust. A newly enabled plugin's tools are offered on the first turn: the turn waits up to 10 seconds for the plugin host to finish loading instead of adding them only from the next turn. The importer reports that it does not convert `dsh.client` UI; Engine routes and a desktop slot exist, but isolated end-to-end plugin UI remains unqualified, including network containment. A plugin tool can call `ctx.sessions.flush(exec.agent.session)` to have the Engine save the conversation so far before it continues (Runtime, terminal and `exec` sessions); it reports failure instead of claiming a save. Installing a plugin whose bytes changed after review now returns a 409 conflict instead of a server error. A plugin tool can ask you a question with `ctx.userQuestions.ask` and gets back the options you picked, your own text, or a skip; the question shows like any other Codewhale question in the terminal and through the Runtime API. A plugin tool can start a child agent with `ctx.subagents.startActivation` (the `spawn` provider): the child runs through Codewhale's own sub-agent manager, needs the same approval as the model's `agent` tool, can carry an extra persona section and a narrower tool list set in its `agent/created` handler, and returns its result to the plugin.
|
||||
- **MCP catalogs stay fresh:** a server's tools, resources, prompts and skills are rediscovered on their own connection when an advertised `ttlMs` expires or the server sends a list-changed notice. A tool resolved against the old catalog is refused with "retry the call", a removed tool cannot run, and a failed refresh reconnects instead of serving stale entries. Catalogs are never shared across servers, credentials or reconnects.
|
||||
- **MCP Skills extension:** servers that advertise the published Skills extension have their skills listed in `list_mcp_resources`. Listing never activates a skill, and reading a skill file is refused unless its size and sha256 digest match the listing. MCP skills do not yet join `/skills`.
|
||||
- **Updates install themselves:** release-binary installs check for a new release every 6 hours (was 1), download and checksum-verify it in the background, and apply it the next time `codewhale` starts, never during a session. One line says `Updated to v… · What's new: … · Undo: codewhale update --rollback`. Until releases are signed, the download waits for your OK (one `[y/N]` question at the next start, or `/update install`); a release with a valid Ed25519 manifest signature applies on its own. `codewhale update --rollback` puts back the version the last update replaced and keeps the rolled-back version from returning automatically. Turn it down with `codewhale config set update.mode notify` (or `off`, or `CODEWHALE_UPDATE_MODE`); an organization's root-owned `policy.toml` overrides both. Offline and rate-limited checks retry about an hour later instead of on every launch. Package-manager and desktop-app copies only get their manager's command. Builds with the release key refuse a missing or invalid signature, and re-check the staged binary against the signed manifest at start. The checksum manifest now always comes from GitHub Releases; on Linux x64 the CNB mirror can still serve the binary, but only bytes that GitHub's manifest lists.
|
||||
@@ -363,12 +363,16 @@ tag. Items moved to 0.10.4 by name are marked below.
|
||||
- **Cleaner, safer Markdown export:** headings no longer carry `Content N:` numbering, a text block that opens a message is bare, tool results show their tool name and appear under a `tool` role instead of `user`, and `Error:` appears only on an error. Tool output is now fenced, so a tool result can no longer forge a heading or a message in the transcript. A trailing `--force` is a flag rather than part of the path. `/share` now publishes the same HTML page as `/export file x.html`, still as a redacted secret gist after confirmation.
|
||||
- Internal coarse split: Runtime owns bounded owner work, Paths owns the protected socket layout, and Protocol owns shared avatar data. App Server now owns the Runtime host and control transports previously embedded in the TUI. Engine and Sandbox do not depend on App Server; the remaining Stage B boundaries stay open (#5586).
|
||||
- **Newer DeepSeek Harness plugin compatibility:** the Native host targets
|
||||
DSH 0.2.1-rc.5 with Cordis 4.0.5 and refreshed reviewed composition
|
||||
packages. Boolean `deferLoading` hints are accepted while the Engine keeps
|
||||
tool-loading policy; `when` remains refused. Service metadata distinguishes
|
||||
DSH, Cordis and Codewhale services. The changed compatibility digest requires
|
||||
fresh plugin review and trust. The legacy preset adapter keeps its recorded
|
||||
source pin; this is local compatibility evidence, not release qualification.
|
||||
DSH 0.2.2-rc.1 with Cordis 4.1.0 and refreshed reviewed composition
|
||||
packages (loader, include and group 1.1.0). Reviewed compositions are still
|
||||
never rewritten, and plugin module loading still goes only through the
|
||||
reviewed closure. Boolean `deferLoading` hints are accepted while the Engine
|
||||
keeps tool-loading policy; `when` remains refused. The services a plugin may
|
||||
inject are unchanged, so plugins needing `llm`, `fs`, `subprocess`,
|
||||
`configEditor` or `dsh.client` still do not run. The changed compatibility
|
||||
digest requires fresh plugin review and trust. The legacy preset adapter keeps
|
||||
its recorded source pin; this is local compatibility evidence, not release
|
||||
qualification.
|
||||
- **Goal tool guidance while awaiting an answer (#7177):** the tool schema now
|
||||
tells the model to yield when it needs the user’s answer and reserves blocked
|
||||
for stalled progress. Existing runtime semantics are unchanged; no live-model
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Codewhale runs reviewed DeepSeek Harness (DSH) plugins in its experimental
|
||||
Native extension host ([EXTENSIONS.md](EXTENSIONS.md)). The target is DSH
|
||||
**0.2.1-rc.5** with Cordis **4.0.5**, pinned in `crates/engine/extension-host/src/dsh/compatibility.json`.
|
||||
**0.2.2-rc.1** with Cordis **4.1.0**, pinned in `crates/engine/extension-host/src/dsh/compatibility.json`.
|
||||
The Rust Engine keeps turns, sessions, approvals, credentials and receipts; a
|
||||
DSH service is offered only where it maps onto one of those existing owners.
|
||||
Everything else is refused by name when the plugin activates, never faked.
|
||||
@@ -49,12 +49,19 @@ compatibility services, not services declared by DSH. This reclassification
|
||||
keeps the same injectable names. Its changed table digest requires fresh
|
||||
Native review and trust for previously reviewed plugins.
|
||||
|
||||
The rc.5 update reuses loader 1.0.6, include 1.0.10 and group 1.0.5 while
|
||||
keeping reviewed module imports and non-writing composition. The legacy
|
||||
preset adapter remains explicitly pinned to `0d1f5000`; its replacement
|
||||
upstream runtime packages are not adopted. Node support remains
|
||||
`^22.19.0 || >=24.0.0`, and Bun remains `>=1.4.0`. rc.5 is a prerelease.
|
||||
Local checks of this upgrade do not repeat the live walkthroughs below.
|
||||
The rc.1 update reuses loader 1.1.0, include 1.1.0 and group 1.1.0 while
|
||||
keeping reviewed module imports and non-writing composition: tree mutations
|
||||
now report through `commit(change)`, which a reviewed composition ignores, and
|
||||
bare specifiers keep the native import fallback instead of upstream's
|
||||
writable `.cordis/resolve.mjs` helper. The `dsh-tools` definition excerpt is
|
||||
taken from rc.1's split build with unchanged definition semantics. The
|
||||
injectable and refused service lists are unchanged; the changed table digest
|
||||
requires fresh Native review and trust. The legacy preset adapter remains
|
||||
explicitly pinned to `0d1f5000`; its replacement upstream runtime packages
|
||||
are not adopted. Node support remains `^22.19.0 || >=24.0.0`, and Bun remains
|
||||
`>=1.4.0`. rc.1 is a prerelease. Earlier rc.5 receipts are not evidence for
|
||||
this target, and local checks of this upgrade do not repeat the live
|
||||
walkthroughs below.
|
||||
|
||||
## Headless services
|
||||
|
||||
|
||||
+1
-1
@@ -359,7 +359,7 @@ keeps its existing tool-loading policy. Non-boolean values are refused.
|
||||
Nonempty registration options, `when`, `projectContent`, `finalizeContent`
|
||||
and `timeoutMs` remain unsupported and are refused. The shared
|
||||
[`compatibility.json`](../crates/engine/extension-host/src/dsh/compatibility.json)
|
||||
pins the definition API to DSH 0.2.1-rc.5 and records service, row-bridge and
|
||||
pins the definition API to DSH 0.2.2-rc.1 and records service, row-bridge and
|
||||
module mappings. Its bytes participate in Native content and capability review,
|
||||
so changing the compatibility contract invalidates prior trust.
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ DSH 定义若声明 `output`,必须同时提供 `output.schema` 和 `output.re
|
||||
`timeoutMs` 和 `when` 仍不支持。布尔值 `deferLoading` 仅作为展示提示接受,
|
||||
不会改变 Engine 的工具加载策略;非布尔值会被拒绝。共享
|
||||
[`compatibility.json`](../../crates/engine/extension-host/src/dsh/compatibility.json)
|
||||
记录 DSH 0.2.1-rc.5 定义 API 与服务、模块映射,其内容参与 Native 内容和能力摘要;
|
||||
记录 DSH 0.2.2-rc.1 定义 API 与服务、模块映射,其内容参与 Native 内容和能力摘要;
|
||||
契约改变后必须重新审查。成功及软失败结果中的 `extension` 元数据由 Rust 写入,
|
||||
记录插件、已审查内容、入口、代际、调用方选择、句柄与进程启动身份。
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"sources": [
|
||||
{
|
||||
"path": "docs/CONFIGURATION.md",
|
||||
"sha256": "39a310e4e06e8bfa649e4bb659c8264dcd574c5673e0227d8f52595289700ab1",
|
||||
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Usage and execution statistics\n\n`codewhale stats` is an alias of `codewhale metrics`; both use the same local,\nread-only backend. Use `--json` for the structured `stats` report. For example:\n\n```sh\ncodewhale stats --days 7 --models 10 --tools 20 --roles 10\ncodewhale metrics --project /path/to/workspace --json\n```\n\n`--days N` accepts a positive number of days and conflicts with `--since`.\n`--since 24h` and the existing duration syntax remain available. `--models N`,\n`--tools N` and `--roles N` select the top positive N rows by known tokens,\nidentified tool executions and recorded Fleet tasks respectively. These limits\nonly shorten the display; they do not remove stored receipts.\n\nThe report separates saved-document and Runtime views, frozen native-source\nmoney and token subtotals by actual model, retained tool-result history\nestimates and outcomes, completed compaction observations, and recorded Fleet\nroles. USD and CNY stay separate. A cumulative correction replaces its proven\npredecessor; copied Session/Runtime receipts and inherited Runtime fork rows\nare not added as new provider work. Conflicting source versions are shown\nseparately and excluded from the subtotal. Saved legacy display totals remain\nvisible as document projections without being charged again globally.\n\nCoverage is explicit. A missing quote, usage receipt, tool outcome, output\nshape or recovery link is unknown, not zero. Empty scalar usage from older\ntransport records cannot prove a reported zero. Token counts describe known\nreported data; tool history tokens are estimates of retained text, not bytes\nproven to have been replayed on a provider request. Latest prepared-request\ncomponents and source-linked compaction/role economics are unavailable where\nthey were not captured. The report does not infer provider invoices or prove\ncomplete historical coverage.\n\n`--project WORKSPACE` filters current saved/thread/Fleet views. Original source\nworkspace is not yet captured, so native money and model subtotals are\nunavailable for that filter; mutable current metadata is not used to assign a\ncharge. Older receipt projections without reliable workspace scope are also\nunavailable under this filter. Without it, those legacy receipt views remain\nseparate fromLine truncated
|
||||
"sha256": "05652bcf4681f34e7369800d57d13a8d03277b9b4f759b069d850f0d3ab6f2e7",
|
||||
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Usage and execution statistics\n\n`codewhale stats` is an alias of `codewhale metrics`; both use the same local,\nread-only backend. Use `--json` for the structured `stats` report. For example:\n\n```sh\ncodewhale stats --days 7 --models 10 --tools 20 --roles 10\ncodewhale metrics --project /path/to/workspace --json\n```\n\n`--days N` accepts a positive number of days and conflicts with `--since`.\n`--since 24h` and the existing duration syntax remain available. `--models N`,\n`--tools N` and `--roles N` select the top positive N rows by known tokens,\nidentified tool executions and recorded Fleet tasks respectively. These limits\nonly shorten the display; they do not remove stored receipts.\n\nThe report separates saved-document and Runtime views, frozen native-source\nmoney and token subtotals by actual model, retained tool-result history\nestimates and outcomes, completed compaction observations, and recorded Fleet\nroles. USD and CNY stay separate. A cumulative correction replaces its proven\npredecessor; copied Session/Runtime receipts and inherited Runtime fork rows\nare not added as new provider work. Conflicting source versions are shown\nseparately and excluded from the subtotal. Saved legacy display totals remain\nvisible as document projections without being charged again globally.\n\nCoverage is explicit. A missing quote, usage receipt, tool outcome, output\nshape or recovery link is unknown, not zero. Empty scalar usage from older\ntransport records cannot prove a reported zero. Token counts describe known\nreported data; tool history tokens are estimates of retained text, not bytes\nproven to have been replayed on a provider request. Latest prepared-request\ncomponents and source-linked compaction/role economics are unavailable where\nthey were not captured. The report does not infer provider invoices or prove\ncomplete historical coverage.\n\n`--project WORKSPACE` filters current saved/thread/Fleet views. Original source\nworkspace is not yet captured, so native money and model subtotals are\nunavailable for that filter; mutable current metadata is not used to assign a\ncharge. Older receipt projections without reliable workspace scope are also\nunavailable under this filter. Without it, those legacy receipt views remain\nseparate fromLine truncated
|
||||
},
|
||||
{
|
||||
"path": "docs/CODEWHALE_AGENT.md",
|
||||
@@ -64,8 +64,8 @@
|
||||
"sources": [
|
||||
{
|
||||
"path": "docs/CONFIGURATION.md",
|
||||
"sha256": "39a310e4e06e8bfa649e4bb659c8264dcd574c5673e0227d8f52595289700ab1",
|
||||
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Usage and execution statistics\n\n`codewhale stats` is an alias of `codewhale metrics`; both use the same local,\nread-only backend. Use `--json` for the structured `stats` report. For example:\n\n```sh\ncodewhale stats --days 7 --models 10 --tools 20 --roles 10\ncodewhale metrics --project /path/to/workspace --json\n```\n\n`--days N` accepts a positive number of days and conflicts with `--since`.\n`--since 24h` and the existing duration syntax remain available. `--models N`,\n`--tools N` and `--roles N` select the top positive N rows by known tokens,\nidentified tool executions and recorded Fleet tasks respectively. These limits\nonly shorten the display; they do not remove stored receipts.\n\nThe report separates saved-document and Runtime views, frozen native-source\nmoney and token subtotals by actual model, retained tool-result history\nestimates and outcomes, completed compaction observations, and recorded Fleet\nroles. USD and CNY stay separate. A cumulative correction replaces its proven\npredecessor; copied Session/Runtime receipts and inherited Runtime fork rows\nare not added as new provider work. Conflicting source versions are shown\nseparately and excluded from the subtotal. Saved legacy display totals remain\nvisible as document projections without being charged again globally.\n\nCoverage is explicit. A missing quote, usage receipt, tool outcome, output\nshape or recovery link is unknown, not zero. Empty scalar usage from older\ntransport records cannot prove a reported zero. Token counts describe known\nreported data; tool history tokens are estimates of retained text, not bytes\nproven to have been replayed on a provider request. Latest prepared-request\ncomponents and source-linked compaction/role economics are unavailable where\nthey were not captured. The report does not infer provider invoices or prove\ncomplete historical coverage.\n\n`--project WORKSPACE` filters current saved/thread/Fleet views. Original source\nworkspace is not yet captured, so native money and model subtotals are\nunavailable for that filter; mutable current metadata is not used to assign a\ncharge. Older receipt projections without reliable workspace scope are also\nunavailable under this filter. Without it, those legacy receipt views remain\nseparate fromLine truncated
|
||||
"sha256": "05652bcf4681f34e7369800d57d13a8d03277b9b4f759b069d850f0d3ab6f2e7",
|
||||
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Usage and execution statistics\n\n`codewhale stats` is an alias of `codewhale metrics`; both use the same local,\nread-only backend. Use `--json` for the structured `stats` report. For example:\n\n```sh\ncodewhale stats --days 7 --models 10 --tools 20 --roles 10\ncodewhale metrics --project /path/to/workspace --json\n```\n\n`--days N` accepts a positive number of days and conflicts with `--since`.\n`--since 24h` and the existing duration syntax remain available. `--models N`,\n`--tools N` and `--roles N` select the top positive N rows by known tokens,\nidentified tool executions and recorded Fleet tasks respectively. These limits\nonly shorten the display; they do not remove stored receipts.\n\nThe report separates saved-document and Runtime views, frozen native-source\nmoney and token subtotals by actual model, retained tool-result history\nestimates and outcomes, completed compaction observations, and recorded Fleet\nroles. USD and CNY stay separate. A cumulative correction replaces its proven\npredecessor; copied Session/Runtime receipts and inherited Runtime fork rows\nare not added as new provider work. Conflicting source versions are shown\nseparately and excluded from the subtotal. Saved legacy display totals remain\nvisible as document projections without being charged again globally.\n\nCoverage is explicit. A missing quote, usage receipt, tool outcome, output\nshape or recovery link is unknown, not zero. Empty scalar usage from older\ntransport records cannot prove a reported zero. Token counts describe known\nreported data; tool history tokens are estimates of retained text, not bytes\nproven to have been replayed on a provider request. Latest prepared-request\ncomponents and source-linked compaction/role economics are unavailable where\nthey were not captured. The report does not infer provider invoices or prove\ncomplete historical coverage.\n\n`--project WORKSPACE` filters current saved/thread/Fleet views. Original source\nworkspace is not yet captured, so native money and model subtotals are\nunavailable for that filter; mutable current metadata is not used to assign a\ncharge. Older receipt projections without reliable workspace scope are also\nunavailable under this filter. Without it, those legacy receipt views remain\nseparate fromLine truncated
|
||||
},
|
||||
{
|
||||
"path": "docs/LEGACY_PATHS.md",
|
||||
@@ -96,8 +96,8 @@
|
||||
},
|
||||
{
|
||||
"path": "docs/CONFIGURATION.md",
|
||||
"sha256": "39a310e4e06e8bfa649e4bb659c8264dcd574c5673e0227d8f52595289700ab1",
|
||||
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Usage and execution statistics\n\n`codewhale stats` is an alias of `codewhale metrics`; both use the same local,\nread-only backend. Use `--json` for the structured `stats` report. For example:\n\n```sh\ncodewhale stats --days 7 --models 10 --tools 20 --roles 10\ncodewhale metrics --project /path/to/workspace --json\n```\n\n`--days N` accepts a positive number of days and conflicts with `--since`.\n`--since 24h` and the existing duration syntax remain available. `--models N`,\n`--tools N` and `--roles N` select the top positive N rows by known tokens,\nidentified tool executions and recorded Fleet tasks respectively. These limits\nonly shorten the display; they do not remove stored receipts.\n\nThe report separates saved-document and Runtime views, frozen native-source\nmoney and token subtotals by actual model, retained tool-result history\nestimates and outcomes, completed compaction observations, and recorded Fleet\nroles. USD and CNY stay separate. A cumulative correction replaces its proven\npredecessor; copied Session/Runtime receipts and inherited Runtime fork rows\nare not added as new provider work. Conflicting source versions are shown\nseparately and excluded from the subtotal. Saved legacy display totals remain\nvisible as document projections without being charged again globally.\n\nCoverage is explicit. A missing quote, usage receipt, tool outcome, output\nshape or recovery link is unknown, not zero. Empty scalar usage from older\ntransport records cannot prove a reported zero. Token counts describe known\nreported data; tool history tokens are estimates of retained text, not bytes\nproven to have been replayed on a provider request. Latest prepared-request\ncomponents and source-linked compaction/role economics are unavailable where\nthey were not captured. The report does not infer provider invoices or prove\ncomplete historical coverage.\n\n`--project WORKSPACE` filters current saved/thread/Fleet views. Original source\nworkspace is not yet captured, so native money and model subtotals are\nunavailable for that filter; mutable current metadata is not used to assign a\ncharge. Older receipt projections without reliable workspace scope are also\nunavailable under this filter. Without it, those legacy receipt views remain\nseparate fromLine truncated
|
||||
"sha256": "05652bcf4681f34e7369800d57d13a8d03277b9b4f759b069d850f0d3ab6f2e7",
|
||||
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Usage and execution statistics\n\n`codewhale stats` is an alias of `codewhale metrics`; both use the same local,\nread-only backend. Use `--json` for the structured `stats` report. For example:\n\n```sh\ncodewhale stats --days 7 --models 10 --tools 20 --roles 10\ncodewhale metrics --project /path/to/workspace --json\n```\n\n`--days N` accepts a positive number of days and conflicts with `--since`.\n`--since 24h` and the existing duration syntax remain available. `--models N`,\n`--tools N` and `--roles N` select the top positive N rows by known tokens,\nidentified tool executions and recorded Fleet tasks respectively. These limits\nonly shorten the display; they do not remove stored receipts.\n\nThe report separates saved-document and Runtime views, frozen native-source\nmoney and token subtotals by actual model, retained tool-result history\nestimates and outcomes, completed compaction observations, and recorded Fleet\nroles. USD and CNY stay separate. A cumulative correction replaces its proven\npredecessor; copied Session/Runtime receipts and inherited Runtime fork rows\nare not added as new provider work. Conflicting source versions are shown\nseparately and excluded from the subtotal. Saved legacy display totals remain\nvisible as document projections without being charged again globally.\n\nCoverage is explicit. A missing quote, usage receipt, tool outcome, output\nshape or recovery link is unknown, not zero. Empty scalar usage from older\ntransport records cannot prove a reported zero. Token counts describe known\nreported data; tool history tokens are estimates of retained text, not bytes\nproven to have been replayed on a provider request. Latest prepared-request\ncomponents and source-linked compaction/role economics are unavailable where\nthey were not captured. The report does not infer provider invoices or prove\ncomplete historical coverage.\n\n`--project WORKSPACE` filters current saved/thread/Fleet views. Original source\nworkspace is not yet captured, so native money and model subtotals are\nunavailable for that filter; mutable current metadata is not used to assign a\ncharge. Older receipt projections without reliable workspace scope are also\nunavailable under this filter. Without it, those legacy receipt views remain\nseparate fromLine truncated
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -224,8 +224,8 @@
|
||||
},
|
||||
{
|
||||
"path": "docs/CONFIGURATION.md",
|
||||
"sha256": "39a310e4e06e8bfa649e4bb659c8264dcd574c5673e0227d8f52595289700ab1",
|
||||
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Usage and execution statistics\n\n`codewhale stats` is an alias of `codewhale metrics`; both use the same local,\nread-only backend. Use `--json` for the structured `stats` report. For example:\n\n```sh\ncodewhale stats --days 7 --models 10 --tools 20 --roles 10\ncodewhale metrics --project /path/to/workspace --json\n```\n\n`--days N` accepts a positive number of days and conflicts with `--since`.\n`--since 24h` and the existing duration syntax remain available. `--models N`,\n`--tools N` and `--roles N` select the top positive N rows by known tokens,\nidentified tool executions and recorded Fleet tasks respectively. These limits\nonly shorten the display; they do not remove stored receipts.\n\nThe report separates saved-document and Runtime views, frozen native-source\nmoney and token subtotals by actual model, retained tool-result history\nestimates and outcomes, completed compaction observations, and recorded Fleet\nroles. USD and CNY stay separate. A cumulative correction replaces its proven\npredecessor; copied Session/Runtime receipts and inherited Runtime fork rows\nare not added as new provider work. Conflicting source versions are shown\nseparately and excluded from the subtotal. Saved legacy display totals remain\nvisible as document projections without being charged again globally.\n\nCoverage is explicit. A missing quote, usage receipt, tool outcome, output\nshape or recovery link is unknown, not zero. Empty scalar usage from older\ntransport records cannot prove a reported zero. Token counts describe known\nreported data; tool history tokens are estimates of retained text, not bytes\nproven to have been replayed on a provider request. Latest prepared-request\ncomponents and source-linked compaction/role economics are unavailable where\nthey were not captured. The report does not infer provider invoices or prove\ncomplete historical coverage.\n\n`--project WORKSPACE` filters current saved/thread/Fleet views. Original source\nworkspace is not yet captured, so native money and model subtotals are\nunavailable for that filter; mutable current metadata is not used to assign a\ncharge. Older receipt projections without reliable workspace scope are also\nunavailable under this filter. Without it, those legacy receipt views remain\nseparate fromLine truncated
|
||||
"sha256": "05652bcf4681f34e7369800d57d13a8d03277b9b4f759b069d850f0d3ab6f2e7",
|
||||
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Usage and execution statistics\n\n`codewhale stats` is an alias of `codewhale metrics`; both use the same local,\nread-only backend. Use `--json` for the structured `stats` report. For example:\n\n```sh\ncodewhale stats --days 7 --models 10 --tools 20 --roles 10\ncodewhale metrics --project /path/to/workspace --json\n```\n\n`--days N` accepts a positive number of days and conflicts with `--since`.\n`--since 24h` and the existing duration syntax remain available. `--models N`,\n`--tools N` and `--roles N` select the top positive N rows by known tokens,\nidentified tool executions and recorded Fleet tasks respectively. These limits\nonly shorten the display; they do not remove stored receipts.\n\nThe report separates saved-document and Runtime views, frozen native-source\nmoney and token subtotals by actual model, retained tool-result history\nestimates and outcomes, completed compaction observations, and recorded Fleet\nroles. USD and CNY stay separate. A cumulative correction replaces its proven\npredecessor; copied Session/Runtime receipts and inherited Runtime fork rows\nare not added as new provider work. Conflicting source versions are shown\nseparately and excluded from the subtotal. Saved legacy display totals remain\nvisible as document projections without being charged again globally.\n\nCoverage is explicit. A missing quote, usage receipt, tool outcome, output\nshape or recovery link is unknown, not zero. Empty scalar usage from older\ntransport records cannot prove a reported zero. Token counts describe known\nreported data; tool history tokens are estimates of retained text, not bytes\nproven to have been replayed on a provider request. Latest prepared-request\ncomponents and source-linked compaction/role economics are unavailable where\nthey were not captured. The report does not infer provider invoices or prove\ncomplete historical coverage.\n\n`--project WORKSPACE` filters current saved/thread/Fleet views. Original source\nworkspace is not yet captured, so native money and model subtotals are\nunavailable for that filter; mutable current metadata is not used to assign a\ncharge. Older receipt projections without reliable workspace scope are also\nunavailable under this filter. Without it, those legacy receipt views remain\nseparate fromLine truncated
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -256,8 +256,8 @@
|
||||
},
|
||||
{
|
||||
"path": "docs/CONFIGURATION.md",
|
||||
"sha256": "39a310e4e06e8bfa649e4bb659c8264dcd574c5673e0227d8f52595289700ab1",
|
||||
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Usage and execution statistics\n\n`codewhale stats` is an alias of `codewhale metrics`; both use the same local,\nread-only backend. Use `--json` for the structured `stats` report. For example:\n\n```sh\ncodewhale stats --days 7 --models 10 --tools 20 --roles 10\ncodewhale metrics --project /path/to/workspace --json\n```\n\n`--days N` accepts a positive number of days and conflicts with `--since`.\n`--since 24h` and the existing duration syntax remain available. `--models N`,\n`--tools N` and `--roles N` select the top positive N rows by known tokens,\nidentified tool executions and recorded Fleet tasks respectively. These limits\nonly shorten the display; they do not remove stored receipts.\n\nThe report separates saved-document and Runtime views, frozen native-source\nmoney and token subtotals by actual model, retained tool-result history\nestimates and outcomes, completed compaction observations, and recorded Fleet\nroles. USD and CNY stay separate. A cumulative correction replaces its proven\npredecessor; copied Session/Runtime receipts and inherited Runtime fork rows\nare not added as new provider work. Conflicting source versions are shown\nseparately and excluded from the subtotal. Saved legacy display totals remain\nvisible as document projections without being charged again globally.\n\nCoverage is explicit. A missing quote, usage receipt, tool outcome, output\nshape or recovery link is unknown, not zero. Empty scalar usage from older\ntransport records cannot prove a reported zero. Token counts describe known\nreported data; tool history tokens are estimates of retained text, not bytes\nproven to have been replayed on a provider request. Latest prepared-request\ncomponents and source-linked compaction/role economics are unavailable where\nthey were not captured. The report does not infer provider invoices or prove\ncomplete historical coverage.\n\n`--project WORKSPACE` filters current saved/thread/Fleet views. Original source\nworkspace is not yet captured, so native money and model subtotals are\nunavailable for that filter; mutable current metadata is not used to assign a\ncharge. Older receipt projections without reliable workspace scope are also\nunavailable under this filter. Without it, those legacy receipt views remain\nseparate fromLine truncated
|
||||
"sha256": "05652bcf4681f34e7369800d57d13a8d03277b9b4f759b069d850f0d3ab6f2e7",
|
||||
"content": "# Configuration\n\ncodewhale reads configuration from a TOML file plus environment variables.\nAt process startup it may also load literal built-in-provider credentials from\na workspace-local `.env` file. Use the tracked `.env.example` as the template;\ncopy it to `.env`, then add only credential values.\n\nA workspace is not configuration authority. Codewhale therefore ignores\nconfig/profile/home paths, provider/model/base-URL routing, MCP/plugin state,\napproval/sandbox/shell posture, executable paths, runtime settings, and every\nother non-credential `.env` entry. Variable expansion is rejected so a\nrepository cannot substitute an ambient secret into a credential value. Use\n`config.toml`, CLI flags, or values exported by the launching shell for those\nexplicit control-plane settings. `.env` is read from a stable regular-file\nhandle, is capped at 1 MiB, and symbolic links, reparse points, and multiply\nlinked files are rejected.\n\n## Reading and checking configuration from the CLI\n\n`codewhale config get <key>` reads scalar keys, whole tables such as `tools`,\nand nested paths such as `tools.user_input_timeout_seconds`. Displayed tables\nand nested values apply the same recursive credential redaction as `config dump`.\n\n`config set` supports its named scalar keys and the provider, route, and\nnotification commands. It refuses a key that nothing reads and suggests the\nnearest real key (`config set calm_mod on` names `calm_mode`). A settings.toml\nkey such as `calm_mode` or `tool_collapse` is validated and written to the\nuser-global settings.toml, not config.toml; `config get` reads it back from\nthere even when an old config.toml copy (which nothing reads) is still present,\nand names that copy so you can `config unset` it. Settings keys have no project\nscope, so `--project` refuses them. A config.toml key is checked against the\ntype its reader expects and stored as a TOML boolean or number where the\nreader needs one (`yolo = true`, `max_subagents = 4`); `reasoning_effort`\naccepts the same aliases as `/effort` (#6563). Other dotted writes fail before\nmodifying the file and name the TOML table to edit. For example, set a tools\ntimeout in the file as:\n\n```toml\n[tools]\nuser_input_timeout_seconds = 0\n```\n\n`codewhale config doctor` checks credential presence and endpoint shape, and\nwarns about each config.toml root key that nothing reads: a settings.toml key\nleft in config.toml is named as misplaced, and any other unread key gets a\ndid-you-mean. Keys read by any runtime reader are not reported. The warnings do\nnot fail the check, and a clean result does not validate every runtime setting's\nvalue (#6083, #6563).\n\n## Usage and execution statistics\n\n`codewhale stats` is an alias of `codewhale metrics`; both use the same local,\nread-only backend. Use `--json` for the structured `stats` report. For example:\n\n```sh\ncodewhale stats --days 7 --models 10 --tools 20 --roles 10\ncodewhale metrics --project /path/to/workspace --json\n```\n\n`--days N` accepts a positive number of days and conflicts with `--since`.\n`--since 24h` and the existing duration syntax remain available. `--models N`,\n`--tools N` and `--roles N` select the top positive N rows by known tokens,\nidentified tool executions and recorded Fleet tasks respectively. These limits\nonly shorten the display; they do not remove stored receipts.\n\nThe report separates saved-document and Runtime views, frozen native-source\nmoney and token subtotals by actual model, retained tool-result history\nestimates and outcomes, completed compaction observations, and recorded Fleet\nroles. USD and CNY stay separate. A cumulative correction replaces its proven\npredecessor; copied Session/Runtime receipts and inherited Runtime fork rows\nare not added as new provider work. Conflicting source versions are shown\nseparately and excluded from the subtotal. Saved legacy display totals remain\nvisible as document projections without being charged again globally.\n\nCoverage is explicit. A missing quote, usage receipt, tool outcome, output\nshape or recovery link is unknown, not zero. Empty scalar usage from older\ntransport records cannot prove a reported zero. Token counts describe known\nreported data; tool history tokens are estimates of retained text, not bytes\nproven to have been replayed on a provider request. Latest prepared-request\ncomponents and source-linked compaction/role economics are unavailable where\nthey were not captured. The report does not infer provider invoices or prove\ncomplete historical coverage.\n\n`--project WORKSPACE` filters current saved/thread/Fleet views. Original source\nworkspace is not yet captured, so native money and model subtotals are\nunavailable for that filter; mutable current metadata is not used to assign a\ncharge. Older receipt projections without reliable workspace scope are also\nunavailable under this filter. Without it, those legacy receipt views remain\nseparate fromLine truncated
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -282,8 +282,8 @@
|
||||
},
|
||||
{
|
||||
"path": "docs/EXTENSIONS.md",
|
||||
"sha256": "46d2443e6068f17c6c904a4d2fe1ffcec393d7c86b708bb467ec68423ecba1ee",
|
||||
"content": "# Writing an extension tool or command\n\nThe experimental TypeScript extension host runs reviewed plugin code in a\nshared Node process (or, as an opt-in, Bun). Rust still owns sessions, tool admission, approval and\nexecution. Extensions currently contribute tools and slash commands; they\ncannot provide an approval service, run a second agent loop or replace\nbuilt-in tools or commands.\n\n## Quick start\n\nStart with [hello-extension](examples/plugins/hello-extension/hello.mts) and its\n[plugin.json](examples/plugins/hello-extension/plugin.json). The example is\nexecuted unchanged by the host tests. It returns a greeting and does not read\nfiles or use the network.\n\n1. Explicitly enable `[features] extension_host = true` in your configuration,\n or start Codewhale with `codewhale --enable extension_host` (the flag goes\n before any subcommand).\n2. Install the example's directory using `/plugin install <local-directory>`.\n3. Run `/plugin validate hello-extension`, then `/plugin show hello-extension`.\n4. Run `/plugin enable hello-extension` to open its content/capability review.\n Read the review and personally run its exact `/plugin trust <token>` command,\n then enable the plugin. Trust alone does not execute the code.\n5. Ask Codewhale to use `hello_greet`. The tool follows the normal approval gate.\n6. Run `/hello-greet Codewhale`. The command runs when you type it, and shows\n its answer in the transcript with its origin, `extension:hello-extension`.\n\nAn authoring agent should stop after installation and validation and present\nthe review to the person. Do not trust or enable a bundle automatically.\n\n## Locations, installation and trust\n\nUser bundles live under `~/.codewhale/plugins/`; workspace bundles live under\n`<workspace>/.codewhale/plugins/`. New bundles use Agent Plugins v1.0.0\n`plugin.json`. Declare `extensions[\"net.codewhale\"].native.path` as one regular\n`.mjs`, `.js` or `.mts` file inside the bundle. Directories and other extensions\nfail validation when the host is enabled. With the flag off, native entries\nremain inventory-only.\n\nA plugin may split its code across several entries with `native.paths` (up to\n64, alone or beside `path`). They are activated in the order declared, as the\nfibers of one plugin: they share one owner, so one disable, review change or\ncrash tears all of them down together. Each entry is a separate module with its\nown `apply`, and tool and command names must be unique across them. If an entry\nfails to activate (throws, requires a service the host does not provide, or has\na registration refused), only that entry is withdrawn: entries that already\nactivated stay registered, later entries still activate, and the plugin fails\nonly when no entry activates. Listing the same file twice is one entry.\n\n## Runtime\n\n`[extension_host] runtime` selects what runs the host. Node is the default.\nBun is an opt-in: it is qualified on macOS only, and becomes the default only\nafter an explicit, recorded cutover.\n\n```toml\n[extension_host]\nruntime = \"node\" # default: Node only\n# runtime = \"bun\" # Bun only; fails rather than falling back to Node\n# runtime = \"auto\" # Bun >= 1.4.0 if one is found and starts, otherwise Node\n# bun = \"~/.bun/bin/bun\" # the only Bun tried when set\n# node = \"/opt/homebrew/bin/node\" # the only Node tried when set\n# mcp_backend = \"rust\" # temporary opt-out; default is \"host\"\n```\n\nThe default `mcp_backend = \"host\"` uses the pinned MCP SDK independently of\n`[features] extension_host`, which controls optional Native extensions. Existing\nstdio and HTTP servers use modern discovery when supported; explicitly configured\nlegacy SSE servers use their legacy handshake. Rust retains process and network\nauthority, credential resolution, catalog admission, tool approval and cancellation.\nThe SDK receives opaque session selectors and exact operation grants. Modern\nrequests carry Rust-approved protocol metadata and headers without a session id.\n\nA typed discovery refusal can admit a legacy handshake. Only an explicit HTTP\ntransport refusal during that handshake can admit legacy SSE with a fresh exact\nRust grant. Legacy HTTP retains its GET preflight and bounded OAuth refresh;\nmodern HTTP does not inherit those session or resend behaviors. Incomplete result\nflows are reported without automatically repeating the operation. Credentials,\nconfigured URLs, OAuth browser/login operations and Computer Use decision keys\nstay in Rust.\n\nBoth backends share recorded semantic MCP goldens and retain separate transport\ngoldens, including discovery, authentication and session evidence. The Host default\nrequires those fixtures and the actual broker acceptance checks, plus the platform\nisolation and measured gates in Ops CURRENT_DECISIONS §26. An explicit\n`mcp_backend = \"rust\"` remains available for one release after the qualified default\nflip, before the native protocoLine truncated
|
||||
"sha256": "18cabae8aa269665302b6443087b385bcf1f1de89a2be317213a67a82bcad6de",
|
||||
"content": "# Writing an extension tool or command\n\nThe experimental TypeScript extension host runs reviewed plugin code in a\nshared Node process (or, as an opt-in, Bun). Rust still owns sessions, tool admission, approval and\nexecution. Extensions currently contribute tools and slash commands; they\ncannot provide an approval service, run a second agent loop or replace\nbuilt-in tools or commands.\n\n## Quick start\n\nStart with [hello-extension](examples/plugins/hello-extension/hello.mts) and its\n[plugin.json](examples/plugins/hello-extension/plugin.json). The example is\nexecuted unchanged by the host tests. It returns a greeting and does not read\nfiles or use the network.\n\n1. Explicitly enable `[features] extension_host = true` in your configuration,\n or start Codewhale with `codewhale --enable extension_host` (the flag goes\n before any subcommand).\n2. Install the example's directory using `/plugin install <local-directory>`.\n3. Run `/plugin validate hello-extension`, then `/plugin show hello-extension`.\n4. Run `/plugin enable hello-extension` to open its content/capability review.\n Read the review and personally run its exact `/plugin trust <token>` command,\n then enable the plugin. Trust alone does not execute the code.\n5. Ask Codewhale to use `hello_greet`. The tool follows the normal approval gate.\n6. Run `/hello-greet Codewhale`. The command runs when you type it, and shows\n its answer in the transcript with its origin, `extension:hello-extension`.\n\nAn authoring agent should stop after installation and validation and present\nthe review to the person. Do not trust or enable a bundle automatically.\n\n## Locations, installation and trust\n\nUser bundles live under `~/.codewhale/plugins/`; workspace bundles live under\n`<workspace>/.codewhale/plugins/`. New bundles use Agent Plugins v1.0.0\n`plugin.json`. Declare `extensions[\"net.codewhale\"].native.path` as one regular\n`.mjs`, `.js` or `.mts` file inside the bundle. Directories and other extensions\nfail validation when the host is enabled. With the flag off, native entries\nremain inventory-only.\n\nA plugin may split its code across several entries with `native.paths` (up to\n64, alone or beside `path`). They are activated in the order declared, as the\nfibers of one plugin: they share one owner, so one disable, review change or\ncrash tears all of them down together. Each entry is a separate module with its\nown `apply`, and tool and command names must be unique across them. If an entry\nfails to activate (throws, requires a service the host does not provide, or has\na registration refused), only that entry is withdrawn: entries that already\nactivated stay registered, later entries still activate, and the plugin fails\nonly when no entry activates. Listing the same file twice is one entry.\n\n## Runtime\n\n`[extension_host] runtime` selects what runs the host. Node is the default.\nBun is an opt-in: it is qualified on macOS only, and becomes the default only\nafter an explicit, recorded cutover.\n\n```toml\n[extension_host]\nruntime = \"node\" # default: Node only\n# runtime = \"bun\" # Bun only; fails rather than falling back to Node\n# runtime = \"auto\" # Bun >= 1.4.0 if one is found and starts, otherwise Node\n# bun = \"~/.bun/bin/bun\" # the only Bun tried when set\n# node = \"/opt/homebrew/bin/node\" # the only Node tried when set\n# mcp_backend = \"rust\" # temporary opt-out; default is \"host\"\n```\n\nThe default `mcp_backend = \"host\"` uses the pinned MCP SDK independently of\n`[features] extension_host`, which controls optional Native extensions. Existing\nstdio and HTTP servers use modern discovery when supported; explicitly configured\nlegacy SSE servers use their legacy handshake. Rust retains process and network\nauthority, credential resolution, catalog admission, tool approval and cancellation.\nThe SDK receives opaque session selectors and exact operation grants. Modern\nrequests carry Rust-approved protocol metadata and headers without a session id.\n\nA typed discovery refusal can admit a legacy handshake. Only an explicit HTTP\ntransport refusal during that handshake can admit legacy SSE with a fresh exact\nRust grant. Legacy HTTP retains its GET preflight and bounded OAuth refresh;\nmodern HTTP does not inherit those session or resend behaviors. Incomplete result\nflows are reported without automatically repeating the operation. Credentials,\nconfigured URLs, OAuth browser/login operations and Computer Use decision keys\nstay in Rust.\n\nBoth backends share recorded semantic MCP goldens and retain separate transport\ngoldens, including discovery, authentication and session evidence. The Host default\nrequires those fixtures and the actual broker acceptance checks, plus the platform\nisolation and measured gates in Ops CURRENT_DECISIONS §26. An explicit\n`mcp_backend = \"rust\"` remains available for one release after the qualified default\nflip, before the native protocoLine truncated
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -346,8 +346,8 @@
|
||||
},
|
||||
{
|
||||
"path": "docs/RELEASE_CHECKLIST.md",
|
||||
"sha256": "07893b226a5605e524fdb5389b9e2c56746aff80562c3de2be4c092990020ed1",
|
||||
"content": "# Release Checklist\n\nFollow this sequence from the canonical `/Volumes/VIXinSSD/CW/codewhale`\ncheckout on `main`. Agent-owned preparation lands as verified slices on `main`;\nit requires no release branch, worktree, nested clone or preparation PR.\nContributor PRs still land as PRs. Treat any unchecked required gate as a release\nblocker.\n\nFor deeper context on the underlying tools (preflight scripts, npm smoke,\npublish-crates), see [`RELEASE_RUNBOOK.md`](RELEASE_RUNBOOK.md).\nFor larger milestone releases, add any version-specific acceptance matrix to\nthe existing release packet before tagging; use it for provider routes, feature\ngates, GUI/runtime smoke, remote-workbench decisions, and credit hygiene that the\ngeneric checklist does not enumerate. Record its candidate SHA, owners, local\ncheck results, hosted run URLs, artifact inventory, install/provider receipts,\nknown limitations and publication approval. These proofs are separate.\n\n## 0. Reconcile the canonical checkout\n\n- [ ] Inspect the branch, status, upstream and alternate checkouts before editing:\n ```bash\n git branch --show-current\n git status --short --branch\n git log -5 --oneline\n git worktree list\n git fetch origin main\n git log --left-right --oneline main...origin/main\n ```\n- [ ] The canonical checkout is on `main`. Recover stranded work only after\n preserving dirty/untracked files and comparing commits with current\n `origin/main`. Keep a recoverable backup and unresolved inventory; do not\n reset away work, stash it indefinitely or create another development lane.\n- [ ] Uncommitted work found in the checkout is yours (founder, 2026-10-22):\n read it, prove it, and commit it by explicit path. A file changed in the\n last 15 minutes or held by a live session is mid-edit: ask that writer\n to commit first. Check status before staging; never `git add -A`.\n Historical branches/checkouts are inventoried;\n deleting them requires explicit Hunter approval even if a helper calls\n their tips safe to delete.\n\n## 1. CHANGELOG entry exists for the version\n\n- [ ] `CHANGELOG.md` has a `## [X.Y.Z] - YYYY-MM-DD` heading at the top\n- [ ] The entry credits every external contributor, harvested PR author,\n linked issue reporter, reproduction/log provider, reviewer, and\n verification helper whose work materially shaped this version. Get the\n commit list with:\n ```\n git log vPREV..HEAD --no-merges --format=\"%h %an <%ae> %s\" \\\n | grep -v '<your-email@…>'\n ```\n For each contributor, link both their display name and (when known)\n `@github-handle`. Then inspect linked issues and harvested PRs so\n reporters/helpers are not lost just because they did not author commits.\n Keep `docs/CONTRIBUTORS.md` and `web/lib/release-credits.ts` consistent.\n- [ ] The entry uses the Keep a Changelog headers — `Added`, `Changed`,\n `Fixed`, `Security`, `Removed`, `Deprecated`. Add `Known issues` only\n if there is something material the user must work around.\n- [ ] The entry mentions all referenced issue/PR numbers as `#NNNN` so the\n auto-linker on GitHub picks them up.\n- [ ] Run `scripts/sync-changelog.sh` to regenerate `crates/tui/CHANGELOG.md`\n (the recent-releases slice embedded in the binary for `/change`). Do\n not edit that file by hand, and do not copy the full root changelog\n into it — older entries live in `docs/CHANGELOG_ARCHIVE.md`.\n- [ ] Run `scripts/release/check-feature-release-notes.sh vPREV HEAD`. Every\n issue-linked `feat` commit must leave a receipt in `CHANGELOG.md` or the\n archive; the Version drift CI gate runs the same check with full history.\n\n## 2. Version pins are in sync\n\n- [ ] Use `./scripts/release/prepare-release.sh X.Y.Z` when version pins or\n generated sources need preparation. Reuse a valid preparation receipt\n when those sources are already current. The helper bumps the\n workspace version, every per-crate dependency pin, the npm wrapper\n (`version` + `codewhaleBinaryVersion`), Runtime SDK, VS Code extension\n and lock, remote-smoke default, public source-candidate facts, and README\n install-tag examples; it refreshes the Cargo/npm locks, regenerates\n `crates/tui/CHANGELOG.md` and `web/lib/facts.generated.ts`, and ends\n by running the version and OHOS gates. Write the CHANGELOG entry\n **before** running it. The helper is safe to rerun at the requested\n workspace version; it skips replacements but refreshes both generated\n files and reruns the gates.\n- [ ] `npm/deepseek-tui/package.json` remains private/compatibility-only and\n is **not** bumped or published.\n- [ ] `./scripts/release/check-versions.sh` reports\n `Version state OK: workspace=X.Y.Z, npm=X.Y.Z, npm-binary=X.Y.Z, lockfile in sync.`\n- [ ] `./scripts/reLine truncated
|
||||
"sha256": "31de19e7ea014bb21f431322667619e5e5f88c4cdfb5907451c63a95b6c92f4e",
|
||||
"content": "# Release Checklist\n\nFollow this sequence from the canonical `/Volumes/VIXinSSD/CW/codewhale`\ncheckout on `main`. Agent-owned preparation lands as verified slices on `main`;\nit requires no release branch, worktree, nested clone or preparation PR.\nContributor PRs still land as PRs. Treat any unchecked required gate as a release\nblocker.\n\nFor deeper context on the underlying tools (preflight scripts, npm smoke,\npublish-crates), see [`RELEASE_RUNBOOK.md`](RELEASE_RUNBOOK.md).\nFor larger milestone releases, add any version-specific acceptance matrix to\nthe existing release packet before tagging; use it for provider routes, feature\ngates, GUI/runtime smoke, remote-workbench decisions, and credit hygiene that the\ngeneric checklist does not enumerate. Record its candidate SHA, owners, local\ncheck results, hosted run URLs, artifact inventory, install/provider receipts,\nknown limitations and publication approval. These proofs are separate.\n\n## 0. Reconcile the canonical checkout\n\n- [ ] Inspect the branch, status, upstream and alternate checkouts before editing:\n ```bash\n git branch --show-current\n git status --short --branch\n git log -5 --oneline\n git worktree list\n git fetch origin main\n git log --left-right --oneline main...origin/main\n ```\n- [ ] The canonical checkout is on `main`. Recover stranded work only after\n preserving dirty/untracked files and comparing commits with current\n `origin/main`. Keep a recoverable backup and unresolved inventory; do not\n reset away work, stash it indefinitely or create another development lane.\n- [ ] Uncommitted work found in the checkout is yours (founder, 2026-10-22):\n read it, prove it, and commit it by explicit path. A file changed in the\n last 15 minutes or held by a live session is mid-edit: ask that writer\n to commit first. Check status before staging; never `git add -A`.\n Historical branches/checkouts are inventoried;\n deleting them requires explicit Hunter approval even if a helper calls\n their tips safe to delete.\n\n## 1. CHANGELOG entry exists for the version\n\n- [ ] `CHANGELOG.md` has a `## [X.Y.Z] - YYYY-MM-DD` heading at the top\n- [ ] The entry credits every external contributor, harvested PR author,\n linked issue reporter, reproduction/log provider, reviewer, and\n verification helper whose work materially shaped this version. Get the\n commit list with:\n ```\n git log vPREV..HEAD --no-merges --format=\"%h %an <%ae> %s\" \\\n | grep -v '<your-email@…>'\n ```\n For each contributor, link both their display name and (when known)\n `@github-handle`. Then inspect linked issues and harvested PRs so\n reporters/helpers are not lost just because they did not author commits.\n Keep `docs/CONTRIBUTORS.md` and `web/lib/release-credits.ts` consistent.\n- [ ] The entry uses the Keep a Changelog headers — `Added`, `Changed`,\n `Fixed`, `Security`, `Removed`, `Deprecated`. Add `Known issues` only\n if there is something material the user must work around.\n- [ ] The entry mentions all referenced issue/PR numbers as `#NNNN` so the\n auto-linker on GitHub picks them up.\n- [ ] Run `scripts/sync-changelog.sh` to regenerate `crates/tui/CHANGELOG.md`\n (the recent-releases slice embedded in the binary for `/change`). Do\n not edit that file by hand, and do not copy the full root changelog\n into it — older entries live in `docs/CHANGELOG_ARCHIVE.md`.\n- [ ] Run `scripts/release/check-feature-release-notes.sh vPREV HEAD`. Every\n issue-linked `feat` commit must leave a receipt in `CHANGELOG.md` or the\n archive; the Version drift CI gate runs the same check with full history.\n\n## 2. Version pins are in sync\n\n- [ ] Use `./scripts/release/prepare-release.sh X.Y.Z` when version pins or\n generated sources need preparation. Reuse a valid preparation receipt\n when those sources are already current. The helper bumps the\n workspace version, every per-crate dependency pin, the npm wrapper\n (`version` + `codewhaleBinaryVersion`), Runtime SDK, VS Code extension\n and lock, remote-smoke default, public source-candidate facts, and README\n install-tag examples; it refreshes the Cargo/npm locks, regenerates\n `crates/tui/CHANGELOG.md` and `web/lib/facts.generated.ts`, and ends\n by running the version and OHOS gates. Write the CHANGELOG entry\n **before** running it. The helper is safe to rerun at the requested\n workspace version; it skips replacements but refreshes both generated\n files and reruns the gates.\n- [ ] `npm/deepseek-tui/package.json` remains private/compatibility-only and\n is **not** bumped or published.\n- [ ] `./scripts/release/check-versions.sh` reports\n `Version state OK: workspace=X.Y.Z, npm=X.Y.Z, npm-binary=X.Y.Z, lockfile in sync.`\n- [ ] `./scripts/reLine truncated
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user