From 433670a75439804f1d6aedde3fbc60a7dc5ca3c3 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 19 Jul 2026 22:13:50 +0800 Subject: [PATCH] feat(invariants): require package-owned companions --- docs/config-catalog.md | 2 +- docs/cordis-catalog/services.md | 2 +- docs/core-data-structures/session.md | 2 +- docs/module-graph.md | 251 ++++++++++------ ...06-11-dev-invariants-over-deep-readonly.md | 2 +- .../2026-06-15-turn-enclosure-invariant.md | 2 +- .../2026-07-05-reconstructable-requests.md | 2 +- .../2026-07-12-agent-scope-runtime-design.md | 2 +- ...-package-owned-invariant-service.i18n.yaml | 4 +- ...6-07-19-package-owned-invariant-service.md | 18 +- ...7-19-package-owned-invariant-service.zh.md | 18 +- .../2026-06-16-typed-event-schemas.md | 6 +- docs/testing.md | 2 + knip.json | 21 +- package.json | 4 +- packages/AGENTS.md | 1 + packages/bash/bash-local/package.json | 7 + packages/bash/bash-local/src/invariant.ts | 30 ++ packages/bash/bash-local/tsconfig.json | 3 + packages/bash/bash-sandbox/package.json | 11 +- packages/bash/bash-sandbox/src/invariant.ts | 30 ++ packages/bash/bash-sandbox/tsconfig.json | 3 + packages/bash/bash/package.json | 7 + packages/bash/bash/src/invariant.ts | 30 ++ packages/bash/bash/tsconfig.json | 3 + packages/bash/tool-bash/package.json | 13 +- packages/bash/tool-bash/src/invariant.ts | 30 ++ packages/bash/tool-bash/tsconfig.json | 3 + .../code-runtime-worker/package.json | 7 + .../code-runtime-worker/src/invariant.ts | 30 ++ .../code-runtime-worker/tsconfig.json | 3 + .../code-runtime-worker/tsdown.config.ts | 2 +- .../code-runtime/code-runtime/package.json | 7 + .../code-runtime/src/invariant.ts | 30 ++ .../code-runtime/code-runtime/tsconfig.json | 3 + packages/compact/compact-basic/package.json | 6 + .../compact/compact-basic/src/invariant.ts | 30 ++ .../compact-basic/tests/compact-basic.spec.ts | 8 +- packages/compact/compact-basic/tsconfig.json | 35 ++- packages/compact/compact/package.json | 7 + packages/compact/compact/src/invariant.ts | 30 ++ packages/compact/compact/tsconfig.json | 3 + packages/context/time-context/package.json | 7 + .../context/time-context/src/invariant.ts | 30 ++ .../time-context/tests/time-context.spec.ts | 5 +- packages/context/time-context/tsconfig.json | 35 ++- .../context/workspace-context/package.json | 7 + .../workspace-context/src/invariant.ts | 30 ++ .../tests/workspace-context.spec.ts | 60 ++-- .../context/workspace-context/tsconfig.json | 3 + packages/cordis/tool-cordis/package.json | 15 +- packages/cordis/tool-cordis/src/invariant.ts | 30 ++ packages/cordis/tool-cordis/tsconfig.json | 3 + packages/core/agent-loop/src/invariant.ts | 6 +- packages/core/agent-loop/tests/resume.spec.ts | 4 +- packages/core/session/src/invariant.ts | 6 +- packages/core/session/tests/session.spec.ts | 23 +- packages/core/system-prompt/package.json | 7 + packages/core/system-prompt/src/invariant.ts | 30 ++ packages/core/system-prompt/tsconfig.json | 3 + packages/core/tools/package.json | 11 +- packages/core/tools/src/invariant.ts | 30 ++ packages/core/tools/tsconfig.json | 3 + packages/examples/acp-demo/package.json | 19 +- packages/examples/acp-demo/src/invariant.ts | 30 ++ packages/examples/acp-demo/tsconfig.json | 3 + packages/examples/acp-demo/tsdown.config.ts | 2 +- .../examples/agent-spine-demo/package.json | 17 +- .../agent-spine-demo/src/invariant.ts | 30 ++ packages/examples/cli-demo/package.json | 7 + packages/examples/cli-demo/src/invariant.ts | 30 ++ packages/examples/cli-demo/tsconfig.json | 43 ++- packages/examples/cli-demo/tsdown.config.ts | 2 +- packages/examples/jsonrpc-demo/package.json | 7 + .../examples/jsonrpc-demo/src/invariant.ts | 30 ++ packages/examples/jsonrpc-demo/tsconfig.json | 3 + .../examples/jsonrpc-demo/tsdown.config.ts | 2 +- packages/examples/stdio-demo/package.json | 25 +- packages/examples/stdio-demo/src/invariant.ts | 30 ++ packages/examples/stdio-demo/tsconfig.json | 3 + packages/examples/stdio-demo/tsdown.config.ts | 2 +- packages/fs/fs-local/package.json | 7 + packages/fs/fs-local/src/invariant.ts | 30 ++ packages/fs/fs-local/tsconfig.json | 23 +- packages/fs/fs-policy/package.json | 7 + packages/fs/fs-policy/src/invariant.ts | 30 ++ packages/fs/fs-policy/tsconfig.json | 19 +- packages/fs/fs/package.json | 7 + packages/fs/fs/src/invariant.ts | 30 ++ packages/fs/fs/tsconfig.json | 19 +- packages/fs/tool-fs-search/package.json | 7 + packages/fs/tool-fs-search/src/invariant.ts | 30 ++ packages/fs/tool-fs-search/tsconfig.json | 43 ++- packages/fs/tool-fs/package.json | 9 +- packages/fs/tool-fs/src/invariant.ts | 30 ++ packages/fs/tool-fs/tsconfig.json | 35 ++- packages/guard/repeat-tool-guard/package.json | 7 + .../guard/repeat-tool-guard/src/invariant.ts | 30 ++ .../guard/repeat-tool-guard/tsconfig.json | 3 + packages/hooks/hook-protocol/package.json | 7 + packages/hooks/hook-protocol/src/invariant.ts | 30 ++ packages/hooks/hook-protocol/tsconfig.json | 3 + packages/hooks/hooks-claude/package.json | 7 + packages/hooks/hooks-claude/src/invariant.ts | 30 ++ .../hooks/hooks-claude/tests/bridge.spec.ts | 13 +- .../hooks-claude/tests/coverage-cases.ts | 15 +- packages/hooks/hooks-claude/tsconfig.json | 3 + packages/hooks/hooks-codex/package.json | 7 + packages/hooks/hooks-codex/src/invariant.ts | 30 ++ packages/hooks/hooks-codex/tsconfig.json | 3 + packages/llm/llm-deepseek/package.json | 7 + packages/llm/llm-deepseek/src/invariant.ts | 30 ++ packages/llm/llm-deepseek/tsconfig.json | 3 + packages/llm/llm-pi-ai/package.json | 7 + packages/llm/llm-pi-ai/src/invariant.ts | 30 ++ packages/llm/llm-pi-ai/tsconfig.json | 3 + packages/llm/llm/package.json | 7 + packages/llm/llm/src/invariant.ts | 30 ++ packages/llm/llm/tsconfig.json | 3 + packages/llm/token-meter/package.json | 7 + packages/llm/token-meter/src/invariant.ts | 30 ++ .../llm/token-meter/tests/token-meter.spec.ts | 13 +- packages/llm/token-meter/tsconfig.json | 3 + packages/mcp/mcp-client/package.json | 11 +- packages/mcp/mcp-client/src/invariant.ts | 30 ++ packages/mcp/mcp-client/tsconfig.json | 23 +- packages/sandbox/sandbox-local/package.json | 7 + .../sandbox/sandbox-local/src/invariant.ts | 30 ++ packages/sandbox/sandbox-local/tsconfig.json | 3 + packages/sandbox/sandbox/package.json | 7 + packages/sandbox/sandbox/src/invariant.ts | 30 ++ packages/sandbox/sandbox/tsconfig.json | 3 + packages/sdk/create-sdk/package.json | 7 + packages/sdk/create-sdk/src/invariant.ts | 30 ++ packages/sdk/create-sdk/tsconfig.json | 11 +- packages/sdk/create-sdk/tsdown.config.ts | 2 +- packages/sdk/helper/package.json | 7 + packages/sdk/helper/src/invariant.ts | 30 ++ packages/sdk/helper/tsconfig.json | 39 ++- packages/sdk/helper/tsdown.config.ts | 2 +- packages/sdk/scripts/package.json | 15 +- packages/sdk/scripts/src/invariant.ts | 30 ++ packages/sdk/scripts/tsconfig.json | 15 +- packages/sdk/scripts/tsdown.config.ts | 4 + .../session-persistence-jsonl/package.json | 7 + .../src/invariant.ts | 30 ++ .../tests/jsonl.spec.ts | 29 +- .../session-persistence-jsonl/tsconfig.json | 3 + .../session-persistence-sqlite/package.json | 7 + .../src/invariant.ts | 30 ++ .../tests/sqlite.spec.ts | 32 +- .../session-persistence-sqlite/tsconfig.json | 3 + .../session-persistence/package.json | 8 + .../session-persistence/src/invariant.ts | 30 ++ .../tests/coordinator-contract.ts | 53 ++-- .../session-persistence/tsconfig.json | 3 + .../session-query/session-query/package.json | 7 + .../session-query/src/invariant.ts | 30 ++ .../session-query/tests/session-query.spec.ts | 32 +- .../session-query/tests/tracing.spec.ts | 59 ++-- .../session-query/session-query/tsconfig.json | 3 + packages/skill/skill-local/package.json | 7 + packages/skill/skill-local/src/invariant.ts | 30 ++ packages/skill/skill-local/tsconfig.json | 27 +- packages/skill/skill/package.json | 7 + packages/skill/skill/src/invariant.ts | 30 ++ packages/skill/skill/tsconfig.json | 15 +- packages/skill/tool-skill/package.json | 7 + packages/skill/tool-skill/src/invariant.ts | 30 ++ packages/skill/tool-skill/tsconfig.json | 35 ++- packages/spill/spill-local/package.json | 7 + packages/spill/spill-local/src/invariant.ts | 30 ++ packages/spill/spill-local/tsconfig.json | 19 +- packages/spill/spill-policy/package.json | 7 + packages/spill/spill-policy/src/invariant.ts | 30 ++ packages/spill/spill-policy/tsconfig.json | 35 ++- packages/spill/spill/package.json | 7 + packages/spill/spill/src/invariant.ts | 30 ++ packages/spill/spill/tsconfig.json | 23 +- packages/subagent/subagent-acp/package.json | 9 +- .../subagent/subagent-acp/src/invariant.ts | 30 ++ packages/subagent/subagent-acp/tsconfig.json | 3 + packages/subagent/subagent-fork/package.json | 8 +- .../subagent/subagent-fork/src/invariant.ts | 30 ++ packages/subagent/subagent-fork/tsconfig.json | 3 + .../subagent/subagent-inprocess/package.json | 6 + .../subagent-inprocess/src/invariant.ts | 30 ++ .../subagent/subagent-inprocess/tsconfig.json | 3 + packages/subagent/subagent-spawn/package.json | 8 +- .../subagent/subagent-spawn/src/invariant.ts | 30 ++ .../subagent/subagent-spawn/tsconfig.json | 3 + .../subagent/subagent-subprocess/package.json | 7 + .../subagent-subprocess/src/invariant.ts | 30 ++ .../subagent-subprocess/tsconfig.json | 6 +- packages/subagent/subagent/package.json | 7 + packages/subagent/subagent/src/invariant.ts | 30 ++ packages/subagent/subagent/tsconfig.json | 3 + packages/subagent/tool-subagent/package.json | 7 + .../subagent/tool-subagent/src/invariant.ts | 30 ++ packages/subagent/tool-subagent/tsconfig.json | 3 + packages/support/acp-snapshot/package.json | 7 + .../support/acp-snapshot/src/invariant.ts | 30 ++ packages/support/acp-snapshot/tsconfig.json | 7 +- .../support/agent-loop-testkit/package.json | 7 + .../agent-loop-testkit/src/invariant.ts | 30 ++ .../support/agent-loop-testkit/tsconfig.json | 3 + packages/support/invariants/README.md | 10 +- packages/support/invariants/package.json | 5 + packages/support/invariants/src/index.ts | 5 +- packages/support/invariants/src/invariant.ts | 30 ++ packages/support/llm-replay/package.json | 7 + packages/support/llm-replay/src/invariant.ts | 30 ++ packages/support/llm-replay/tsconfig.json | 3 + packages/support/loader-smoke/package.json | 7 + .../support/loader-smoke/src/invariant.ts | 30 ++ packages/support/loader-smoke/tsconfig.json | 6 +- packages/tasks/tasks/package.json | 9 +- packages/tasks/tasks/src/invariant.ts | 30 ++ packages/tasks/tasks/tsconfig.json | 3 + packages/tasks/tool-tasks/package.json | 7 + packages/tasks/tool-tasks/src/invariant.ts | 30 ++ packages/tasks/tool-tasks/tsconfig.json | 3 + packages/timeout/timeout-policy/package.json | 7 + .../timeout/timeout-policy/src/invariant.ts | 30 ++ packages/timeout/timeout-policy/tsconfig.json | 27 +- packages/todo/tool-todo/package.json | 7 + packages/todo/tool-todo/src/invariant.ts | 30 ++ packages/todo/tool-todo/tsconfig.json | 3 + packages/ui/acp/package.json | 6 + packages/ui/acp/src/invariant.ts | 30 ++ packages/ui/acp/tsconfig.json | 3 + packages/ui/app-boot/package.json | 7 + packages/ui/app-boot/src/invariant.ts | 30 ++ packages/ui/app-boot/tsconfig.json | 3 + packages/ui/jsonrpc/package.json | 7 + packages/ui/jsonrpc/src/invariant.ts | 30 ++ packages/ui/jsonrpc/tsconfig.json | 3 + packages/ui/permission/package.json | 7 + packages/ui/permission/src/invariant.ts | 30 ++ packages/ui/permission/tsconfig.json | 3 + packages/ui/stdio/package.json | 7 + packages/ui/stdio/src/invariant.ts | 30 ++ packages/ui/stdio/tests/stdio.spec.ts | 112 ++++--- packages/ui/stdio/tsconfig.json | 3 + packages/ui/tool-ask-user/package.json | 7 + packages/ui/tool-ask-user/src/invariant.ts | 30 ++ packages/ui/tool-ask-user/tsconfig.json | 3 + packages/ui/tui/package.json | 7 + packages/ui/tui/src/invariant.ts | 30 ++ packages/ui/tui/tests/harness.ts | 9 +- packages/ui/tui/tests/tui.snapshot.ts | 42 +-- packages/ui/tui/tests/tui.spec.ts | 138 +++++---- packages/ui/tui/tsconfig.json | 3 + packages/ui/user-approval/package.json | 7 + packages/ui/user-approval/src/invariant.ts | 30 ++ .../ui/user-approval/tests/approval.spec.ts | 4 +- packages/ui/user-approval/tsconfig.json | 3 + packages/ui/user-interaction/package.json | 7 + packages/ui/user-interaction/src/invariant.ts | 30 ++ packages/ui/user-interaction/tsconfig.json | 3 + packages/util/brand/package.json | 7 + packages/util/brand/src/invariant.ts | 30 ++ packages/util/brand/tsconfig.json | 6 +- packages/util/home/package.json | 7 + packages/util/home/src/invariant.ts | 30 ++ packages/util/home/tsconfig.json | 6 +- packages/util/paths/package.json | 7 + packages/util/paths/src/invariant.ts | 30 ++ packages/util/paths/tsconfig.json | 6 +- packages/util/retention/package.json | 7 + packages/util/retention/src/invariant.ts | 30 ++ packages/util/retention/tsconfig.json | 6 +- packages/util/timeout/package.json | 7 + packages/util/timeout/src/invariant.ts | 30 ++ packages/util/timeout/tsconfig.json | 6 +- packages/web/tool-web/package.json | 9 +- packages/web/tool-web/src/invariant.ts | 30 ++ packages/web/tool-web/tsconfig.json | 35 ++- packages/web/web-fetch-local/package.json | 7 + packages/web/web-fetch-local/src/invariant.ts | 30 ++ packages/web/web-fetch-local/tsconfig.json | 3 + packages/web/web-search-deepseek/package.json | 7 + .../web/web-search-deepseek/src/invariant.ts | 30 ++ .../web/web-search-deepseek/tsconfig.json | 3 + packages/web/web-search-exa/package.json | 7 + packages/web/web-search-exa/src/invariant.ts | 30 ++ packages/web/web-search-exa/tsconfig.json | 3 + .../web/web-search-perplexity/package.json | 7 + .../web-search-perplexity/src/invariant.ts | 30 ++ .../web/web-search-perplexity/tsconfig.json | 3 + packages/web/web/package.json | 7 + packages/web/web/src/invariant.ts | 30 ++ packages/web/web/tsconfig.json | 3 + packages/workflow/tool-workflow/package.json | 7 + .../workflow/tool-workflow/src/invariant.ts | 30 ++ packages/workflow/tool-workflow/tsconfig.json | 3 + .../workflow-workerthread/package.json | 6 + .../workflow-workerthread/src/invariant.ts | 30 ++ .../workflow-workerthread/tsconfig.json | 3 + .../workflow-workerthread/tsdown.config.ts | 2 +- packages/workflow/workflow/package.json | 7 + packages/workflow/workflow/src/invariant.ts | 30 ++ packages/workflow/workflow/tsconfig.json | 3 + pnpm-lock.yaml | 237 +++++++++++++++ scripts/check-workspace-constraints.ts | 62 +--- scripts/gen-package-invariants.ts | 44 +++ scripts/package-invariants.spec.ts | 104 +++++++ scripts/package-invariants.ts | 283 ++++++++++++++++++ scripts/run-gates.ts | 3 + scripts/test-invariants.spec.ts | 70 +++++ scripts/test-invariants.ts | 104 +++++++ tsdown.config.ts | 9 +- vitest.config.ts | 1 + vitest.e2e.config.ts | 1 + vitest.snapshot.config.ts | 1 + website/zh-CN/api/harness/invariants.md | 4 +- 316 files changed, 5348 insertions(+), 646 deletions(-) create mode 100644 packages/bash/bash-local/src/invariant.ts create mode 100644 packages/bash/bash-sandbox/src/invariant.ts create mode 100644 packages/bash/bash/src/invariant.ts create mode 100644 packages/bash/tool-bash/src/invariant.ts create mode 100644 packages/code-runtime/code-runtime-worker/src/invariant.ts create mode 100644 packages/code-runtime/code-runtime/src/invariant.ts create mode 100644 packages/compact/compact-basic/src/invariant.ts create mode 100644 packages/compact/compact/src/invariant.ts create mode 100644 packages/context/time-context/src/invariant.ts create mode 100644 packages/context/workspace-context/src/invariant.ts create mode 100644 packages/cordis/tool-cordis/src/invariant.ts create mode 100644 packages/core/system-prompt/src/invariant.ts create mode 100644 packages/core/tools/src/invariant.ts create mode 100644 packages/examples/acp-demo/src/invariant.ts create mode 100644 packages/examples/agent-spine-demo/src/invariant.ts create mode 100644 packages/examples/cli-demo/src/invariant.ts create mode 100644 packages/examples/jsonrpc-demo/src/invariant.ts create mode 100644 packages/examples/stdio-demo/src/invariant.ts create mode 100644 packages/fs/fs-local/src/invariant.ts create mode 100644 packages/fs/fs-policy/src/invariant.ts create mode 100644 packages/fs/fs/src/invariant.ts create mode 100644 packages/fs/tool-fs-search/src/invariant.ts create mode 100644 packages/fs/tool-fs/src/invariant.ts create mode 100644 packages/guard/repeat-tool-guard/src/invariant.ts create mode 100644 packages/hooks/hook-protocol/src/invariant.ts create mode 100644 packages/hooks/hooks-claude/src/invariant.ts create mode 100644 packages/hooks/hooks-codex/src/invariant.ts create mode 100644 packages/llm/llm-deepseek/src/invariant.ts create mode 100644 packages/llm/llm-pi-ai/src/invariant.ts create mode 100644 packages/llm/llm/src/invariant.ts create mode 100644 packages/llm/token-meter/src/invariant.ts create mode 100644 packages/mcp/mcp-client/src/invariant.ts create mode 100644 packages/sandbox/sandbox-local/src/invariant.ts create mode 100644 packages/sandbox/sandbox/src/invariant.ts create mode 100644 packages/sdk/create-sdk/src/invariant.ts create mode 100644 packages/sdk/helper/src/invariant.ts create mode 100644 packages/sdk/scripts/src/invariant.ts create mode 100644 packages/session-persistence/session-persistence-jsonl/src/invariant.ts create mode 100644 packages/session-persistence/session-persistence-sqlite/src/invariant.ts create mode 100644 packages/session-persistence/session-persistence/src/invariant.ts create mode 100644 packages/session-query/session-query/src/invariant.ts create mode 100644 packages/skill/skill-local/src/invariant.ts create mode 100644 packages/skill/skill/src/invariant.ts create mode 100644 packages/skill/tool-skill/src/invariant.ts create mode 100644 packages/spill/spill-local/src/invariant.ts create mode 100644 packages/spill/spill-policy/src/invariant.ts create mode 100644 packages/spill/spill/src/invariant.ts create mode 100644 packages/subagent/subagent-acp/src/invariant.ts create mode 100644 packages/subagent/subagent-fork/src/invariant.ts create mode 100644 packages/subagent/subagent-inprocess/src/invariant.ts create mode 100644 packages/subagent/subagent-spawn/src/invariant.ts create mode 100644 packages/subagent/subagent-subprocess/src/invariant.ts create mode 100644 packages/subagent/subagent/src/invariant.ts create mode 100644 packages/subagent/tool-subagent/src/invariant.ts create mode 100644 packages/support/acp-snapshot/src/invariant.ts create mode 100644 packages/support/agent-loop-testkit/src/invariant.ts create mode 100644 packages/support/invariants/src/invariant.ts create mode 100644 packages/support/llm-replay/src/invariant.ts create mode 100644 packages/support/loader-smoke/src/invariant.ts create mode 100644 packages/tasks/tasks/src/invariant.ts create mode 100644 packages/tasks/tool-tasks/src/invariant.ts create mode 100644 packages/timeout/timeout-policy/src/invariant.ts create mode 100644 packages/todo/tool-todo/src/invariant.ts create mode 100644 packages/ui/acp/src/invariant.ts create mode 100644 packages/ui/app-boot/src/invariant.ts create mode 100644 packages/ui/jsonrpc/src/invariant.ts create mode 100644 packages/ui/permission/src/invariant.ts create mode 100644 packages/ui/stdio/src/invariant.ts create mode 100644 packages/ui/tool-ask-user/src/invariant.ts create mode 100644 packages/ui/tui/src/invariant.ts create mode 100644 packages/ui/user-approval/src/invariant.ts create mode 100644 packages/ui/user-interaction/src/invariant.ts create mode 100644 packages/util/brand/src/invariant.ts create mode 100644 packages/util/home/src/invariant.ts create mode 100644 packages/util/paths/src/invariant.ts create mode 100644 packages/util/retention/src/invariant.ts create mode 100644 packages/util/timeout/src/invariant.ts create mode 100644 packages/web/tool-web/src/invariant.ts create mode 100644 packages/web/web-fetch-local/src/invariant.ts create mode 100644 packages/web/web-search-deepseek/src/invariant.ts create mode 100644 packages/web/web-search-exa/src/invariant.ts create mode 100644 packages/web/web-search-perplexity/src/invariant.ts create mode 100644 packages/web/web/src/invariant.ts create mode 100644 packages/workflow/tool-workflow/src/invariant.ts create mode 100644 packages/workflow/workflow-workerthread/src/invariant.ts create mode 100644 packages/workflow/workflow/src/invariant.ts create mode 100644 scripts/gen-package-invariants.ts create mode 100644 scripts/package-invariants.spec.ts create mode 100644 scripts/package-invariants.ts create mode 100644 scripts/test-invariants.spec.ts create mode 100644 scripts/test-invariants.ts diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 76663117b8..4ea9c7a300 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -396,7 +396,7 @@ export interface Config { } ``` -Source: [`packages/support/invariants/src/index.ts:15`](../packages/support/invariants/src/index.ts) +Source: [`packages/support/invariants/src/index.ts:16`](../packages/support/invariants/src/index.ts) ## `@deepseek-ai/dsh-jsonrpc` diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md index 5a868e1eff..ca5eb4232b 100644 --- a/docs/cordis-catalog/services.md +++ b/docs/cordis-catalog/services.md @@ -495,7 +495,7 @@ Package-owned invariant registry with global and regex-based selection. register(packageName: string, installer: InvariantInstaller): () => void ``` -Source: [`packages/support/invariants/src/index.ts:94`](../../packages/support/invariants/src/index.ts) +Source: [`packages/support/invariants/src/index.ts:95`](../../packages/support/invariants/src/index.ts) ## `ctx.llm` — `LlmService` diff --git a/docs/core-data-structures/session.md b/docs/core-data-structures/session.md index 2dc5a1c34e..fb99f5c15e 100644 --- a/docs/core-data-structures/session.md +++ b/docs/core-data-structures/session.md @@ -502,7 +502,7 @@ interface TurnEndReasonMap { ## The turn-enclosure invariant -Every session event lives **inside** a turn (between a `turn/start` and its `turn/end`). The loop appends queued `user/message` events *after* `turn/start`, and an idle `agent.inject()` wraps its `context/message` in a one-shot `injection` turn. This makes the turn the single durability/replay boundary: a backend can treat anything after the last `turn/end` as an interrupted-crash tail without risking the loss of legitimately-recorded between-turn context. The optional `dsh-session/invariant` companion enforces it in dev through `ctx.invariants` (a message event outside an open turn throws). See [the turn-enclosure invariant RFC](../rfc/implemented/architecture/2026-06-15-turn-enclosure-invariant.md). +Every session event lives **inside** a turn (between a `turn/start` and its `turn/end`). The loop appends queued `user/message` events *after* `turn/start`, and an idle `agent.inject()` wraps its `context/message` in a one-shot `injection` turn. This makes the turn the single durability/replay boundary: a backend can treat anything after the last `turn/end` as an interrupted-crash tail without risking the loss of legitimately-recorded between-turn context. The `dsh-session/invariant` companion enforces it when selected through `ctx.invariants` (a message event outside an open turn throws). See [the turn-enclosure invariant RFC](../rfc/implemented/architecture/2026-06-15-turn-enclosure-invariant.md). ## Plugin-contributed log-only events diff --git a/docs/module-graph.md b/docs/module-graph.md index af8504be18..07ef0f4da9 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -150,23 +150,46 @@ flowchart TD pkg_workflow["workflow"] pkg_workflow_workerthread["workflow-workerthread"] end - pkg_llm --> pkg_brand + pkg_brand --> pkg_invariants + pkg_home --> pkg_invariants + pkg_paths --> pkg_invariants + pkg_retention --> pkg_invariants + pkg_timeout --> pkg_invariants pkg_scope --> pkg_invariants + pkg_skill --> pkg_invariants + pkg_subagent_subprocess --> pkg_invariants + pkg_acp_snapshot --> pkg_invariants + pkg_loader_smoke --> pkg_invariants + pkg_app_boot --> pkg_invariants + pkg_code_runtime --> pkg_invariants + pkg_jsonrpc_demo --> pkg_invariants + pkg_llm --> pkg_brand + pkg_llm --> pkg_invariants pkg_code_runtime_worker --> pkg_code_runtime + pkg_code_runtime_worker --> pkg_invariants pkg_helper --> pkg_brand + pkg_helper --> pkg_invariants pkg_scripts --> pkg_app_boot + pkg_scripts --> pkg_invariants + pkg_llm_deepseek --> pkg_invariants pkg_llm_deepseek --> pkg_llm + pkg_llm_pi_ai --> pkg_invariants pkg_llm_pi_ai --> pkg_llm pkg_session --> pkg_brand pkg_session --> pkg_invariants pkg_session --> pkg_llm pkg_session --> pkg_scope + pkg_system_prompt --> pkg_invariants pkg_system_prompt --> pkg_llm pkg_system_prompt --> pkg_scope pkg_fs --> pkg_brand + pkg_fs --> pkg_invariants pkg_fs --> pkg_llm + pkg_web --> pkg_invariants pkg_web --> pkg_llm + pkg_sandbox --> pkg_invariants pkg_sandbox --> pkg_llm + pkg_token_meter --> pkg_invariants pkg_token_meter --> pkg_llm pkg_token_meter --> pkg_session pkg_agent --> pkg_brand @@ -175,64 +198,90 @@ flowchart TD pkg_agent --> pkg_scope pkg_agent --> pkg_session pkg_agent --> pkg_system_prompt + pkg_bash --> pkg_invariants pkg_bash --> pkg_sandbox pkg_bash --> pkg_session pkg_fs_local --> pkg_fs + pkg_fs_local --> pkg_invariants pkg_fs_policy --> pkg_fs + pkg_fs_policy --> pkg_invariants pkg_skill_local --> pkg_fs pkg_skill_local --> pkg_home + pkg_skill_local --> pkg_invariants pkg_skill_local --> pkg_skill + pkg_compact --> pkg_invariants pkg_compact --> pkg_llm pkg_compact --> pkg_session + pkg_web_fetch_local --> pkg_invariants pkg_web_fetch_local --> pkg_timeout pkg_web_fetch_local --> pkg_web + pkg_web_search_deepseek --> pkg_invariants pkg_web_search_deepseek --> pkg_web + pkg_web_search_exa --> pkg_invariants pkg_web_search_exa --> pkg_web + pkg_web_search_perplexity --> pkg_invariants pkg_web_search_perplexity --> pkg_web pkg_spill --> pkg_brand + pkg_spill --> pkg_invariants pkg_spill --> pkg_llm pkg_spill --> pkg_session + pkg_session_persistence --> pkg_invariants pkg_session_persistence --> pkg_session + pkg_llm_replay --> pkg_invariants pkg_llm_replay --> pkg_llm pkg_llm_replay --> pkg_session + pkg_sandbox_local --> pkg_invariants pkg_sandbox_local --> pkg_llm pkg_sandbox_local --> pkg_sandbox pkg_bash_local --> pkg_bash + pkg_bash_local --> pkg_invariants pkg_bash_local --> pkg_timeout pkg_compact_basic --> pkg_agent pkg_compact_basic --> pkg_compact + pkg_compact_basic --> pkg_invariants pkg_compact_basic --> pkg_llm pkg_compact_basic --> pkg_session pkg_compact_basic --> pkg_token_meter + pkg_spill_local --> pkg_invariants pkg_spill_local --> pkg_spill pkg_hook_protocol --> pkg_bash + pkg_hook_protocol --> pkg_invariants pkg_hook_protocol --> pkg_session + pkg_session_persistence_jsonl --> pkg_invariants pkg_session_persistence_jsonl --> pkg_session pkg_session_persistence_jsonl --> pkg_session_persistence + pkg_session_persistence_sqlite --> pkg_invariants pkg_session_persistence_sqlite --> pkg_session pkg_session_persistence_sqlite --> pkg_session_persistence + pkg_session_query --> pkg_invariants pkg_session_query --> pkg_llm pkg_session_query --> pkg_session pkg_session_query --> pkg_session_persistence pkg_user_approval --> pkg_agent pkg_user_approval --> pkg_brand + pkg_user_approval --> pkg_invariants pkg_user_approval --> pkg_llm pkg_user_approval --> pkg_scope pkg_user_approval --> pkg_session pkg_user_approval --> pkg_system_prompt pkg_user_interaction --> pkg_agent + pkg_user_interaction --> pkg_invariants pkg_user_interaction --> pkg_llm pkg_time_context --> pkg_agent + pkg_time_context --> pkg_invariants pkg_tasks --> pkg_agent pkg_tasks --> pkg_brand + pkg_tasks --> pkg_invariants pkg_tasks --> pkg_session pkg_tasks --> pkg_timeout pkg_workflow --> pkg_agent pkg_workflow --> pkg_brand + pkg_workflow --> pkg_invariants pkg_workflow --> pkg_llm pkg_workflow --> pkg_session pkg_tools --> pkg_agent pkg_tools --> pkg_code_runtime + pkg_tools --> pkg_invariants pkg_tools --> pkg_llm pkg_tools --> pkg_scope pkg_tools --> pkg_session @@ -240,8 +289,10 @@ flowchart TD pkg_tools --> pkg_user_approval pkg_bash_sandbox --> pkg_bash pkg_bash_sandbox --> pkg_bash_local + pkg_bash_sandbox --> pkg_invariants pkg_bash_sandbox --> pkg_sandbox pkg_permission --> pkg_bash + pkg_permission --> pkg_invariants pkg_permission --> pkg_sandbox pkg_permission --> pkg_session pkg_permission --> pkg_user_approval @@ -256,6 +307,7 @@ flowchart TD pkg_tool_bash --> pkg_agent pkg_tool_bash --> pkg_bash pkg_tool_bash --> pkg_home + pkg_tool_bash --> pkg_invariants pkg_tool_bash --> pkg_llm pkg_tool_bash --> pkg_sandbox pkg_tool_bash --> pkg_session_persistence @@ -264,11 +316,13 @@ flowchart TD pkg_tool_bash --> pkg_tools pkg_tool_bash --> pkg_user_approval pkg_tool_fs --> pkg_fs + pkg_tool_fs --> pkg_invariants pkg_tool_fs --> pkg_llm pkg_tool_fs --> pkg_session pkg_tool_fs --> pkg_system_prompt pkg_tool_fs --> pkg_tools pkg_tool_fs_search --> pkg_bash + pkg_tool_fs_search --> pkg_invariants pkg_tool_fs_search --> pkg_llm pkg_tool_fs_search --> pkg_retention pkg_tool_fs_search --> pkg_session @@ -276,45 +330,55 @@ flowchart TD pkg_tool_fs_search --> pkg_system_prompt pkg_tool_fs_search --> pkg_tools pkg_tool_skill --> pkg_agent + pkg_tool_skill --> pkg_invariants pkg_tool_skill --> pkg_llm pkg_tool_skill --> pkg_skill pkg_tool_skill --> pkg_tools pkg_subagent --> pkg_agent pkg_subagent --> pkg_brand + pkg_subagent --> pkg_invariants pkg_subagent --> pkg_llm pkg_subagent --> pkg_scope pkg_subagent --> pkg_session pkg_subagent --> pkg_tools + pkg_tool_web --> pkg_invariants pkg_tool_web --> pkg_llm pkg_tool_web --> pkg_system_prompt pkg_tool_web --> pkg_tools pkg_tool_web --> pkg_web + pkg_spill_policy --> pkg_invariants pkg_spill_policy --> pkg_llm pkg_spill_policy --> pkg_retention pkg_spill_policy --> pkg_session pkg_spill_policy --> pkg_spill pkg_spill_policy --> pkg_tools + pkg_timeout_policy --> pkg_invariants pkg_timeout_policy --> pkg_llm pkg_timeout_policy --> pkg_timeout pkg_timeout_policy --> pkg_tools pkg_tool_todo --> pkg_agent + pkg_tool_todo --> pkg_invariants pkg_tool_todo --> pkg_session pkg_tool_todo --> pkg_tools + pkg_tool_cordis --> pkg_invariants pkg_tool_cordis --> pkg_scope pkg_tool_cordis --> pkg_tools pkg_hooks_codex --> pkg_agent pkg_hooks_codex --> pkg_hook_protocol + pkg_hooks_codex --> pkg_invariants pkg_hooks_codex --> pkg_llm pkg_hooks_codex --> pkg_session pkg_hooks_codex --> pkg_session_persistence pkg_hooks_codex --> pkg_tools pkg_agent_loop_testkit --> pkg_agent + pkg_agent_loop_testkit --> pkg_invariants pkg_agent_loop_testkit --> pkg_llm pkg_agent_loop_testkit --> pkg_session pkg_agent_loop_testkit --> pkg_system_prompt pkg_agent_loop_testkit --> pkg_tools pkg_acp --> pkg_agent pkg_acp --> pkg_bash + pkg_acp --> pkg_invariants pkg_acp --> pkg_llm pkg_acp --> pkg_permission pkg_acp --> pkg_sandbox @@ -325,51 +389,62 @@ flowchart TD pkg_acp --> pkg_user_approval pkg_acp --> pkg_user_interaction pkg_tool_ask_user --> pkg_agent + pkg_tool_ask_user --> pkg_invariants pkg_tool_ask_user --> pkg_tools pkg_tool_ask_user --> pkg_user_interaction pkg_workspace_context --> pkg_agent pkg_workspace_context --> pkg_fs + pkg_workspace_context --> pkg_invariants pkg_workspace_context --> pkg_llm pkg_workspace_context --> pkg_paths pkg_workspace_context --> pkg_session pkg_workspace_context --> pkg_tools pkg_repeat_tool_guard --> pkg_agent + pkg_repeat_tool_guard --> pkg_invariants pkg_repeat_tool_guard --> pkg_tools + pkg_mcp_client --> pkg_invariants pkg_mcp_client --> pkg_llm pkg_mcp_client --> pkg_tools pkg_tool_tasks --> pkg_agent + pkg_tool_tasks --> pkg_invariants pkg_tool_tasks --> pkg_system_prompt pkg_tool_tasks --> pkg_tasks pkg_tool_tasks --> pkg_tools pkg_tool_workflow --> pkg_agent + pkg_tool_workflow --> pkg_invariants pkg_tool_workflow --> pkg_llm pkg_tool_workflow --> pkg_system_prompt pkg_tool_workflow --> pkg_tools pkg_tool_workflow --> pkg_workflow pkg_subagent_acp --> pkg_agent + pkg_subagent_acp --> pkg_invariants pkg_subagent_acp --> pkg_llm pkg_subagent_acp --> pkg_session pkg_subagent_acp --> pkg_subagent pkg_subagent_acp --> pkg_subagent_subprocess pkg_subagent_inprocess --> pkg_agent + pkg_subagent_inprocess --> pkg_invariants pkg_subagent_inprocess --> pkg_llm pkg_subagent_inprocess --> pkg_session pkg_subagent_inprocess --> pkg_subagent pkg_subagent_inprocess --> pkg_system_prompt pkg_subagent_inprocess --> pkg_tools pkg_tool_subagent --> pkg_agent + pkg_tool_subagent --> pkg_invariants pkg_tool_subagent --> pkg_llm pkg_tool_subagent --> pkg_subagent pkg_tool_subagent --> pkg_tasks pkg_tool_subagent --> pkg_tools pkg_hooks_claude --> pkg_agent pkg_hooks_claude --> pkg_hook_protocol + pkg_hooks_claude --> pkg_invariants pkg_hooks_claude --> pkg_llm pkg_hooks_claude --> pkg_session pkg_hooks_claude --> pkg_session_persistence pkg_hooks_claude --> pkg_subagent pkg_hooks_claude --> pkg_tools pkg_jsonrpc --> pkg_agent + pkg_jsonrpc --> pkg_invariants pkg_jsonrpc --> pkg_llm pkg_jsonrpc --> pkg_llm_deepseek pkg_jsonrpc --> pkg_scope @@ -377,11 +452,13 @@ flowchart TD pkg_jsonrpc --> pkg_subagent pkg_stdio --> pkg_agent pkg_stdio --> pkg_agent_loop + pkg_stdio --> pkg_invariants pkg_stdio --> pkg_llm pkg_stdio --> pkg_session pkg_stdio --> pkg_user_interaction pkg_tui --> pkg_agent pkg_tui --> pkg_agent_loop + pkg_tui --> pkg_invariants pkg_tui --> pkg_llm pkg_tui --> pkg_session pkg_tui --> pkg_tools @@ -404,20 +481,24 @@ flowchart TD pkg_agent_spine_demo --> pkg_workspace_context pkg_workflow_workerthread --> pkg_agent pkg_workflow_workerthread --> pkg_brand + pkg_workflow_workerthread --> pkg_invariants pkg_workflow_workerthread --> pkg_llm pkg_workflow_workerthread --> pkg_session pkg_workflow_workerthread --> pkg_subagent pkg_workflow_workerthread --> pkg_tools pkg_workflow_workerthread --> pkg_workflow pkg_subagent_fork --> pkg_agent + pkg_subagent_fork --> pkg_invariants pkg_subagent_fork --> pkg_session pkg_subagent_fork --> pkg_subagent pkg_subagent_fork --> pkg_subagent_inprocess + pkg_subagent_spawn --> pkg_invariants pkg_subagent_spawn --> pkg_subagent pkg_subagent_spawn --> pkg_subagent_inprocess pkg_acp_demo --> pkg_acp pkg_acp_demo --> pkg_agent_spine_demo pkg_acp_demo --> pkg_app_boot + pkg_acp_demo --> pkg_invariants pkg_acp_demo --> pkg_session_persistence_jsonl pkg_acp_demo --> pkg_tools pkg_acp_demo --> pkg_user_interaction @@ -425,6 +506,7 @@ flowchart TD pkg_cli_demo --> pkg_agent pkg_cli_demo --> pkg_agent_spine_demo pkg_cli_demo --> pkg_app_boot + pkg_cli_demo --> pkg_invariants pkg_cli_demo --> pkg_llm pkg_cli_demo --> pkg_session pkg_cli_demo --> pkg_session_persistence_jsonl @@ -434,6 +516,7 @@ flowchart TD pkg_stdio_demo --> pkg_agent_loop pkg_stdio_demo --> pkg_agent_spine_demo pkg_stdio_demo --> pkg_app_boot + pkg_stdio_demo --> pkg_invariants pkg_stdio_demo --> pkg_llm pkg_stdio_demo --> pkg_session pkg_stdio_demo --> pkg_session_persistence_jsonl @@ -447,92 +530,92 @@ flowchart TD | Package | Group | Depends on | | --- | --- | --- | -| [`brand`](../packages/util/brand) | `util` | — | -| [`home`](../packages/util/home) | `util` | — | -| [`paths`](../packages/util/paths) | `util` | — | -| [`retention`](../packages/util/retention) | `util` | — | -| [`timeout`](../packages/util/timeout) | `util` | — | -| [`skill`](../packages/skill/skill) | `skill` | — | -| [`subagent-subprocess`](../packages/subagent/subagent-subprocess) | `subagent` | — | -| [`acp-snapshot`](../packages/support/acp-snapshot) | `support` | — | | [`invariants`](../packages/support/invariants) | `support` | — | -| [`loader-smoke`](../packages/support/loader-smoke) | `support` | — | -| [`app-boot`](../packages/ui/app-boot) | `ui` | — | -| [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime` | — | -| [`jsonrpc-demo`](../packages/examples/jsonrpc-demo) | `examples` | — | -| [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand) | +| [`brand`](../packages/util/brand) | `util` | [`invariants`](../packages/support/invariants) | +| [`home`](../packages/util/home) | `util` | [`invariants`](../packages/support/invariants) | +| [`paths`](../packages/util/paths) | `util` | [`invariants`](../packages/support/invariants) | +| [`retention`](../packages/util/retention) | `util` | [`invariants`](../packages/support/invariants) | +| [`timeout`](../packages/util/timeout) | `util` | [`invariants`](../packages/support/invariants) | | [`scope`](../packages/core/scope) | `core` | [`invariants`](../packages/support/invariants) | -| [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime) | -| [`helper`](../packages/sdk/helper) | `sdk` | [`brand`](../packages/util/brand) | -| [`scripts`](../packages/sdk/scripts) | `sdk` | [`app-boot`](../packages/ui/app-boot) | -| [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`llm`](../packages/llm/llm) | -| [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`llm`](../packages/llm/llm) | +| [`skill`](../packages/skill/skill) | `skill` | [`invariants`](../packages/support/invariants) | +| [`subagent-subprocess`](../packages/subagent/subagent-subprocess) | `subagent` | [`invariants`](../packages/support/invariants) | +| [`acp-snapshot`](../packages/support/acp-snapshot) | `support` | [`invariants`](../packages/support/invariants) | +| [`loader-smoke`](../packages/support/loader-smoke) | `support` | [`invariants`](../packages/support/invariants) | +| [`app-boot`](../packages/ui/app-boot) | `ui` | [`invariants`](../packages/support/invariants) | +| [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime` | [`invariants`](../packages/support/invariants) | +| [`jsonrpc-demo`](../packages/examples/jsonrpc-demo) | `examples` | [`invariants`](../packages/support/invariants) | +| [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) | +| [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/support/invariants) | +| [`helper`](../packages/sdk/helper) | `sdk` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) | +| [`scripts`](../packages/sdk/scripts) | `sdk` | [`app-boot`](../packages/ui/app-boot), [`invariants`](../packages/support/invariants) | +| [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) | +| [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) | | [`session`](../packages/core/session) | `core` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | -| [`system-prompt`](../packages/core/system-prompt) | `core` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | -| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | -| [`web`](../packages/web/web) | `web` | [`llm`](../packages/llm/llm) | -| [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`llm`](../packages/llm/llm) | -| [`token-meter`](../packages/llm/token-meter) | `llm` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`system-prompt`](../packages/core/system-prompt) | `core` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | +| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) | +| [`web`](../packages/web/web) | `web` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) | +| [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) | +| [`token-meter`](../packages/llm/token-meter) | `llm` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`agent`](../packages/core/agent) | `core` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | -| [`bash`](../packages/bash/bash) | `bash` | [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) | -| [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs) | -| [`fs-policy`](../packages/fs/fs-policy) | `fs` | [`fs`](../packages/fs/fs) | -| [`skill-local`](../packages/skill/skill-local) | `skill` | [`fs`](../packages/fs/fs), [`home`](../packages/util/home), [`skill`](../packages/skill/skill) | -| [`compact`](../packages/compact/compact) | `compact` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`web-fetch-local`](../packages/web/web-fetch-local) | `web` | [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) | -| [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`web`](../packages/web/web) | -| [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`web`](../packages/web/web) | -| [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`web`](../packages/web/web) | -| [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`session-persistence`](../packages/session-persistence/session-persistence) | `session-persistence` | [`session`](../packages/core/session) | -| [`llm-replay`](../packages/support/llm-replay) | `support` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | -| [`bash-local`](../packages/bash/bash-local) | `bash` | [`bash`](../packages/bash/bash), [`timeout`](../packages/util/timeout) | -| [`compact-basic`](../packages/compact/compact-basic) | `compact` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | -| [`spill-local`](../packages/spill/spill-local) | `spill` | [`spill`](../packages/spill/spill) | -| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`bash`](../packages/bash/bash), [`session`](../packages/core/session) | -| [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl) | `session-persistence` | [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) | -| [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | `session-persistence` | [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) | -| [`session-query`](../packages/session-query/session-query) | `session-query` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) | -| [`user-approval`](../packages/ui/user-approval) | `ui` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | -| [`user-interaction`](../packages/ui/user-interaction) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm) | -| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent) | -| [`tasks`](../packages/tasks/tasks) | `tasks` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | -| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/ui/user-approval) | -| [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`sandbox`](../packages/sandbox/sandbox) | -| [`permission`](../packages/ui/permission) | `ui` | [`bash`](../packages/bash/bash), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`user-approval`](../packages/ui/user-approval) | +| [`bash`](../packages/bash/bash) | `bash` | [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) | +| [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants) | +| [`fs-policy`](../packages/fs/fs-policy) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants) | +| [`skill-local`](../packages/skill/skill-local) | `skill` | [`fs`](../packages/fs/fs), [`home`](../packages/util/home), [`invariants`](../packages/support/invariants), [`skill`](../packages/skill/skill) | +| [`compact`](../packages/compact/compact) | `compact` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`web-fetch-local`](../packages/web/web-fetch-local) | `web` | [`invariants`](../packages/support/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) | +| [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) | +| [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) | +| [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) | +| [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`session-persistence`](../packages/session-persistence/session-persistence) | `session-persistence` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | +| [`llm-replay`](../packages/support/llm-replay) | `support` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | +| [`bash-local`](../packages/bash/bash-local) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`timeout`](../packages/util/timeout) | +| [`compact-basic`](../packages/compact/compact-basic) | `compact` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | +| [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/support/invariants), [`spill`](../packages/spill/spill) | +| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | +| [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl) | `session-persistence` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) | +| [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | `session-persistence` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) | +| [`session-query`](../packages/session-query/session-query) | `session-query` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) | +| [`user-approval`](../packages/ui/user-approval) | `ui` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | +| [`user-interaction`](../packages/ui/user-interaction) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) | +| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants) | +| [`tasks`](../packages/tasks/tasks) | `tasks` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | +| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/ui/user-approval) | +| [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox) | +| [`permission`](../packages/ui/permission) | `ui` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`user-approval`](../packages/ui/user-approval) | | [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`home`](../packages/util/home), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) | -| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) | -| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | -| [`tool-web`](../packages/web/tool-web) | `web` | [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) | -| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`tools`](../packages/core/tools) | -| [`timeout-policy`](../packages/timeout/timeout-policy) | `timeout` | [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | -| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | -| [`tool-cordis`](../packages/cordis/tool-cordis) | `cordis` | [`scope`](../packages/core/scope), [`tools`](../packages/core/tools) | -| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools) | -| [`agent-loop-testkit`](../packages/support/agent-loop-testkit) | `support` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`acp`](../packages/ui/acp) | `ui` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`permission`](../packages/ui/permission), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval), [`user-interaction`](../packages/ui/user-interaction) | -| [`tool-ask-user`](../packages/ui/tool-ask-user) | `ui` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) | -| [`workspace-context`](../packages/context/workspace-context) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | -| [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools) | -| [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`llm`](../packages/llm/llm), [`tools`](../packages/core/tools) | -| [`tool-tasks`](../packages/tasks/tool-tasks) | `tasks` | [`agent`](../packages/core/agent), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | -| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | -| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-subprocess`](../packages/subagent/subagent-subprocess) | -| [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | -| [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | -| [`jsonrpc`](../packages/ui/jsonrpc) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | -| [`stdio`](../packages/ui/stdio) | `ui` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`user-interaction`](../packages/ui/user-interaction) | -| [`tui`](../packages/ui/tui) | `ui` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) | +| [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`home`](../packages/util/home), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) | +| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) | +| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | +| [`tool-web`](../packages/web/tool-web) | `web` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) | +| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`tools`](../packages/core/tools) | +| [`timeout-policy`](../packages/timeout/timeout-policy) | `timeout` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | +| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | +| [`tool-cordis`](../packages/cordis/tool-cordis) | `cordis` | [`invariants`](../packages/support/invariants), [`scope`](../packages/core/scope), [`tools`](../packages/core/tools) | +| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools) | +| [`agent-loop-testkit`](../packages/support/agent-loop-testkit) | `support` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`acp`](../packages/ui/acp) | `ui` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`permission`](../packages/ui/permission), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval), [`user-interaction`](../packages/ui/user-interaction) | +| [`tool-ask-user`](../packages/ui/tool-ask-user) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) | +| [`workspace-context`](../packages/context/workspace-context) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | +| [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools) | +| [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`tools`](../packages/core/tools) | +| [`tool-tasks`](../packages/tasks/tool-tasks) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | +| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | +| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-subprocess`](../packages/subagent/subagent-subprocess) | +| [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | +| [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | +| [`jsonrpc`](../packages/ui/jsonrpc) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | +| [`stdio`](../packages/ui/stdio) | `ui` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`user-interaction`](../packages/ui/user-interaction) | +| [`tui`](../packages/ui/tui) | `ui` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) | | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`home`](../packages/util/home), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`skill`](../packages/skill/skill), [`skill-local`](../packages/skill/skill-local), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tool-bash`](../packages/bash/tool-bash), [`tool-skill`](../packages/skill/tool-skill), [`tool-tasks`](../packages/tasks/tool-tasks), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) | -| [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | -| [`subagent-fork`](../packages/subagent/subagent-fork) | `subagent` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | -| [`subagent-spawn`](../packages/subagent/subagent-spawn) | `subagent` | [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | -| [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/ui/acp), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction), [`workspace-context`](../packages/context/workspace-context) | -| [`cli-demo`](../packages/examples/cli-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) | -| [`stdio-demo`](../packages/examples/stdio-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`stdio`](../packages/ui/stdio), [`tool-ask-user`](../packages/ui/tool-ask-user), [`tools`](../packages/core/tools), [`tui`](../packages/ui/tui), [`user-interaction`](../packages/ui/user-interaction), [`workspace-context`](../packages/context/workspace-context) | +| [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | +| [`subagent-fork`](../packages/subagent/subagent-fork) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | +| [`subagent-spawn`](../packages/subagent/subagent-spawn) | `subagent` | [`invariants`](../packages/support/invariants), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | +| [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/ui/acp), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`invariants`](../packages/support/invariants), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction), [`workspace-context`](../packages/context/workspace-context) | +| [`cli-demo`](../packages/examples/cli-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) | +| [`stdio-demo`](../packages/examples/stdio-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`stdio`](../packages/ui/stdio), [`tool-ask-user`](../packages/ui/tool-ask-user), [`tools`](../packages/core/tools), [`tui`](../packages/ui/tui), [`user-interaction`](../packages/ui/user-interaction), [`workspace-context`](../packages/context/workspace-context) | diff --git a/docs/rfc/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md b/docs/rfc/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md index 9e272456fc..4b77930280 100644 --- a/docs/rfc/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md +++ b/docs/rfc/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md @@ -30,7 +30,7 @@ This guarantee belongs in `Session`, not in an optional listener, because every ### Package-owned invariant companions check relationships -`dsh-invariants` registers the configurable `ctx.invariants` service and contains no product checks. Optional `./invariant` companions from `dsh-session`, `dsh-agent`, `dsh-scope`, and `dsh-agent-loop` own the rules that require trace state or observation of another seam: monotonic sequence numbers, turn and step nesting, tool-call/result pairing, legal agent-status transitions, subject-correct scoped dispatch, and equality between a loop-built request and the request reconstructed from its session-log prefix. Global enablement and package-name regex filters belong to the service ([package-owned invariant service](2026-07-19-package-owned-invariant-service.md)). +`dsh-invariants` registers the configurable `ctx.invariants` service and contains no product checks. Every package publishes a `./invariant` ownership companion; `dsh-session`, `dsh-agent`, `dsh-scope`, and `dsh-agent-loop` currently add the rules that require trace state or observation of another seam: monotonic sequence numbers, turn and step nesting, tool-call/result pairing, legal agent-status transitions, subject-correct scoped dispatch, and equality between a loop-built request and the request reconstructed from its session-log prefix. Global enablement and package-name regex filters belong to the service ([package-owned invariant service](2026-07-19-package-owned-invariant-service.md)). When the session companion attaches to an existing or seeded session, it replays the immutable log to rebuild trace state. The service gives each contribution a disposable child fiber, so hot reload is safe in the middle of a turn without giving diagnostics ownership of session storage. diff --git a/docs/rfc/implemented/architecture/2026-06-15-turn-enclosure-invariant.md b/docs/rfc/implemented/architecture/2026-06-15-turn-enclosure-invariant.md index 81b1bd25df..4ee29e8fb8 100644 --- a/docs/rfc/implemented/architecture/2026-06-15-turn-enclosure-invariant.md +++ b/docs/rfc/implemented/architecture/2026-06-15-turn-enclosure-invariant.md @@ -21,7 +21,7 @@ In case 2, if the injected `context/message` is the last event before a flush/di - An `agent.inject()` made while the agent is **running** joins the already-open turn. While the current step executes assistant tool calls, accepted context waits in arrival order until that batch settles, then appends after every recorded result and before the turn closes even when execution is interrupted. - An `agent.inject()` made while **idle** wraps its `context/message` in a one-shot turn: `turn/start{trigger:{kind:'injection'}}` → `context/message` → `turn/end{completed}`. A new `injection` variant joins the merge-extensible `TurnTriggerMap`. - The loop derives the next turn number from the log each iteration (`lastTurnNumber(session) + 1`) instead of keeping a private counter, so an idle injection's one-shot turn cannot collide with the next real turn's number. -- The optional `dsh-session/invariant` companion registers the dev check with `ctx.invariants`: a `user/message` / `context/message` / `steering/message` appended while no turn is open throws an `InvariantError` attributed to `@deepseek-ai/dsh-session`. +- The `dsh-session/invariant` companion registers the check with `ctx.invariants`: when selected, a `user/message` / `context/message` / `steering/message` appended while no turn is open throws an `InvariantError` attributed to `@deepseek-ai/dsh-session`. The serializability invariant is enforced at the same source boundary (`Session.append` throws on non-JSON-serializable data), so "what may enter the log" is now governed in one place rather than discovered downstream by whichever backend happens to be watching. diff --git a/docs/rfc/implemented/architecture/2026-07-05-reconstructable-requests.md b/docs/rfc/implemented/architecture/2026-07-05-reconstructable-requests.md index e86d6b9610..62630152c9 100644 --- a/docs/rfc/implemented/architecture/2026-07-05-reconstructable-requests.md +++ b/docs/rfc/implemented/architecture/2026-07-05-reconstructable-requests.md @@ -26,7 +26,7 @@ Each step rebuilds prompt assembly. On the instance's first step, `agent/session **`step/start` is the reconstruction boundary.** A step derives messages from events before that sequence. Injection after the snapshot joins the next request, and reentrant appends are rejected during event publication. `agent/pre-step(agent, turn, step, signal)` remains the generic seam for content needed by the current request. Header reconstruction selects the step's `request/header`, or carries the prior snapshot when no new header is written. -**Enforcement.** In development, the optional `dsh-agent-loop/invariant` companion registers with `ctx.invariants` and independently rebuilds each loop request through a fresh `Session`, so the live cache cannot vouch for itself, then compares messages and folded header fields at `llm/stream`. Loop requests are identified by their frozen shape and session id; direct one-shots are excluded. Correctness depends on sequence-bounded reconstruction rather than listener order. A with-key e2e requires positive cache-read tokens after the first request; per-step usage is the production signal, and a header change or compaction appears as a cache-read drop on the next step. +**Enforcement.** The `dsh-agent-loop/invariant` companion registers with `ctx.invariants` and, when selected, independently rebuilds each loop request through a fresh `Session`, so the live cache cannot vouch for itself, then compares messages and folded header fields at `llm/stream`. Loop requests are identified by their frozen shape and session id; direct one-shots are excluded. Correctness depends on sequence-bounded reconstruction rather than listener order. A with-key e2e requires positive cache-read tokens after the first request; per-step usage is the production signal, and a header change or compaction appears as a cache-read drop on the next step. ### The MiniCode shape: adopted, with the provenance arrow inverted diff --git a/docs/rfc/implemented/architecture/2026-07-12-agent-scope-runtime-design.md b/docs/rfc/implemented/architecture/2026-07-12-agent-scope-runtime-design.md index e7e43cc1eb..2be6be1c53 100644 --- a/docs/rfc/implemented/architecture/2026-07-12-agent-scope-runtime-design.md +++ b/docs/rfc/implemented/architecture/2026-07-12-agent-scope-runtime-design.md @@ -322,7 +322,7 @@ TypeScript cannot govern JavaScript casts, direct Cordis dispatch, process messa ### Runtime invariants cover cross-service facts -The optional `dsh-scope/invariant` companion verifies that every declared scoped event uses a marked carrier and that event families exposing a subject use the matching key. The separate `dsh-session/invariant` contribution stages trace validation before append commit and advances after the same event commits; both register through `ctx.invariants`. +The `dsh-scope/invariant` companion verifies, when selected, that every declared scoped event uses a marked carrier and that event families exposing a subject use the matching key. The separate `dsh-session/invariant` contribution stages trace validation before append commit and advances after the same event commits; both register through `ctx.invariants`. The plugin does not police trusted setup by scanning registries or reject prompt assembly objects fabricated through casts. Those checks would turn composition contracts into speculative runtime machinery without protecting a real external boundary. diff --git a/docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.i18n.yaml b/docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.i18n.yaml index ce8fabdc40..dfe64b7110 100644 --- a/docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.i18n.yaml +++ b/docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-19-package-owned-invariant-service.md: f147cd91134509e17df39d1acbb28ae8b521c2f8 -2026-07-19-package-owned-invariant-service.zh.md: a106b57137c36009d0d4b4d39f109b7abf857f42 +2026-07-19-package-owned-invariant-service.md: bf27bb4e951988bc124509b49dea9b5509f8e6f3 +2026-07-19-package-owned-invariant-service.zh.md: c9dfbb65bf4fd497f2f592f7741923dcef3e51fb diff --git a/docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.md b/docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.md index f147cd9113..bf27bb4e95 100644 --- a/docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.md +++ b/docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.md @@ -10,13 +10,15 @@ Runtime invariant checks span session traces, agent state, scoped dispatch, and Deployments also need more than presence or absence of one plugin. A standard composition should carry the known invariant contributions while permitting a global off switch and package-selective diagnostics. Selection must remain stable when a package loads later or reloads under HMR, and disabled contributions must not allow two plugins to claim the same package name silently. +Package ownership must also be exhaustive. Without a mechanical repository rule, a new package can omit the companion, dependency, or publication wiring and remain invisible to diagnostics until a maintainer notices the gap. + ## Decision ### One registry service, package-owned contributions `@deepseek-ai/dsh-invariants` is a product-independent Cordis service plugin that registers `ctx.invariants`. It owns configuration, registration uniqueness, child-fiber lifecycle, and package-attributed failures. It imports no session, agent, scope, or agent-loop package and contains none of their checks. -Packages expose diagnostics through optional `./invariant` companion plugins. Their root entrypoints do not import or register diagnostics implicitly, so loading a product package does not change runtime checking or require the invariant service. +Every workspace package publishes a `./invariant` companion plugin that registers its exact full npm name. A package with no relational check uses a generated ownership-only installer: it reserves the name through the real service boundary but installs no listeners. Package root entrypoints do not import or register diagnostics implicitly, so loading a root package does not change runtime checking or require the invariant service. ### Configuration and selection @@ -53,7 +55,7 @@ Registration setup is transactional. If an installer fails after registering lis The former functional-plugin entrypoint and one-argument `InvariantError` constructor are not retained as compatibility surfaces. The repository is pre-release and all call sites move to the service and package-attributed error together. -### Shipped companions +### Stateful companions and exhaustive ownership | Companion entry | Registration name | Owned checks | |---|---|---| @@ -62,7 +64,9 @@ The former functional-plugin entrypoint and one-argument `InvariantError` constr | `@deepseek-ai/dsh-scope/invariant` | `@deepseek-ai/dsh-scope` | scoped-event carrier presence and subject consistency | | `@deepseek-ai/dsh-agent-loop/invariant` | `@deepseek-ai/dsh-agent-loop` | model-request reconstruction | -Each owner contains its source and focused tests. Every companion is a separately bundled `./invariant` export with its own declarations and Loader-safe namespace plugin shape. The service package retains only registry tests. +These four owners contain stateful checks and focused tests. Every other package carries a generated baseline companion until it gains a relational assertion. Every companion is a separately bundled `./invariant` export with its own declarations and Loader-safe namespace plugin shape; the service package's own companion imports its local service type to avoid a self-dependency. + +`verify-package-invariants` discovers every workspace package and rejects missing or stale companion source, foreign or unresolved registration names, missing `./invariant` exports or published files, missing invariant peer/development dependencies and project references, and bundle overrides that omit the companion entry. The generator writes only missing or marked ownership baselines, so a package-owned implementation is never replaced. ### Scoped-event semantic map @@ -70,7 +74,7 @@ The generated scoped-event subject resolver lives in `dsh-scope`, beside the con ### Standard composition and SDK output -The standard agent spine mounts the service and all four companion subpaths, forwarding `enabled`, `package_allowlist`, and `package_blocklist` to the service. Generated SDK Cordis composition emits the same entries. A subpath entry adds its installable root npm package rather than treating the subpath as a package name. +The standard agent spine mounts the service and all four stateful companion subpaths, forwarding `enabled`, `package_allowlist`, and `package_blocklist` to the service. Generated SDK Cordis composition emits the same entries. A subpath entry adds its installable root npm package rather than treating the subpath as a package name. Workspace constraints recognize the separate invariant bundle, and package exports, project references, build configuration, dependency declarations, and the lockfile describe the same publication surface. Generated config catalogs, module graphs, and API documentation derive from those sources. @@ -80,18 +84,22 @@ Service tests cover defaults, global disablement, allow/block selection, blockli Composition tests cover standard-spine forwarding and generated SDK entries. Loader tests preserve each companion namespace, while built plain-Node smokes exercise the compiled subpath exports. The scoped-event freshness gate reruns its semantic Program analysis. +Every Vitest configuration loads a test host that mounts an explicitly enabled service and all package companions before an ordinary Cordis root's first plugin. Focused service and owner tests construct their own invariant topology so they can exercise disablement, filtering, rollback, and reload without duplicate ownership. Gate tests also execute every companion's `apply` function and verify that it calls `register` with its manifest name, rather than accepting source text alone. + ## Alternatives considered - **Keep all checks in `dsh-invariants`.** Rejected because the registry would continue importing every checked product domain, owner changes would require central edits, and package tests would remain detached from the contracts they protect. - **Let root package entrypoints register checks implicitly when `ctx.invariants` happens to exist.** Rejected because root behavior would depend on composition order and optional service presence, diagnostics could not be selected independently, and package loading would hide a registration effect outside an explicit companion. -- **Discover every `invariant.ts` file automatically.** Rejected because filesystem/package discovery is not a runtime ownership contract, makes bundled publication ambiguous, and cannot express explicit Cordis load order or dependency installation. +- **Discover every `invariant.ts` file automatically at runtime.** Rejected because filesystem/package discovery is not a runtime ownership contract, makes bundled publication ambiguous, and cannot express explicit Cordis load order or dependency installation. Build-time generation, verification, and the test host may enumerate the source tree because they validate repository completeness rather than composing a shipped deployment. - **Validate allow/block entries against the currently loaded package set.** Rejected because a zero-match pattern can intentionally target a later or HMR-loaded contribution; current load order must not determine config validity. ## Consequences - Product packages own and test their relational assertions while the service stays product-independent. +- Every package pays the small publication and dependency cost of an invariant companion, including packages whose generated baseline currently installs no listeners. - Standard compositions can disable all checks or select package names without changing their plugin tree. - Explicit companion entries make diagnostic cost and ownership visible in Cordis config and package exports. - One selected contribution adds one child fiber and its listener/state cost; filtered registrations retain only name ownership. - Regex sources are deployment configuration and remain fixed until the service reloads. +- Ordinary Vitest roots install every selected companion, trading extra child fibers during tests for repository-wide invariant coverage and immediate fixture failures. - Session storage validation, snapshotting, freezing, provenance, and surface acceptance remain always on and are not affected by invariant selection. diff --git a/docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.zh.md b/docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.zh.md index a106b57137..c9dfbb65bf 100644 --- a/docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.zh.md +++ b/docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.zh.md @@ -10,13 +10,15 @@ Status: implemented 部署还需要比“是否加载一个插件”更细的控制。标准组合应携带已知的不变式贡献,同时允许全局关闭或按包选择诊断。包稍后加载或在 HMR 下重载时,选择结果必须保持稳定;被过滤的贡献也不能让两个插件静默占用同一个包名。 +包所有权还必须覆盖完整。若没有机械化的仓库规则,新包可能遗漏伴随插件、依赖或发布配置,并一直不会进入诊断范围,直到维护者发现这一缺口。 + ## 决策 ### 一个注册服务,贡献归包所有 `@deepseek-ai/dsh-invariants` 是与产品无关的 Cordis 服务插件,注册 `ctx.invariants`。它只负责配置、注册唯一性、子 fiber 生命周期和带包归属的失败;不导入 session、agent、scope 或 agent-loop 包,也不包含这些包的检查。 -各包通过可选的 `./invariant` 伴随插件公开诊断。根入口不会隐式导入或注册诊断,因此加载产品包本身不会改变运行时检查,也不要求不变式服务存在。 +工作区内的每个包都发布 `./invariant` 伴随插件,并注册自己完整且准确的 npm 包名。没有关系检查的包使用生成的仅声明所有权 installer:它通过真实服务边界占用包名,但不安装监听器。包的根入口不会隐式导入或注册诊断,因此加载根包不会改变运行时检查,也不要求不变式服务存在。 ### 配置与选择 @@ -53,7 +55,7 @@ blocklist 匹配优先于 allowlist 匹配。每个条目都是区分大小写 原有函数式插件入口与单参数 `InvariantError` 构造函数不作为兼容表面保留。仓库尚未发布,所有调用方会一起迁移到服务和带包归属的错误。 -### 已提供的伴随插件 +### 有状态伴随插件与完整所有权 | 伴随入口 | 注册名 | 所属检查 | |---|---|---| @@ -62,7 +64,9 @@ blocklist 匹配优先于 allowlist 匹配。每个条目都是区分大小写 | `@deepseek-ai/dsh-scope/invariant` | `@deepseek-ai/dsh-scope` | scoped event carrier 存在性与主体一致性 | | `@deepseek-ai/dsh-agent-loop/invariant` | `@deepseek-ai/dsh-agent-loop` | 模型请求重建 | -每个所有者都保存自己的源码与聚焦测试。每个伴随入口都是单独打包的 `./invariant` export,具有独立声明和对 Loader 安全的命名空间插件形态。服务包只保留注册服务测试。 +这四个所有者保存有状态检查与聚焦测试。其他每个包在获得关系断言之前,都带有生成的基线伴随插件。每个伴随入口都是单独打包的 `./invariant` export,具有独立声明和对 Loader 安全的命名空间插件形态;服务包自身的伴随插件导入本地服务类型,避免形成自依赖。 + +`verify-package-invariants` 会发现每个工作区包,并拒绝缺失或陈旧的伴随插件源码、外部或无法解析的注册名、缺失的 `./invariant` export 或发布文件、缺失的不变式对等依赖(peer dependency)、开发依赖及项目引用,以及遗漏伴随入口的自定义构建配置。生成器只写入缺失或带生成标记的所有权基线,因此绝不会替换包自行维护的实现。 ### Scoped event 语义映射 @@ -70,7 +74,7 @@ blocklist 匹配优先于 allowlist 匹配。每个条目都是区分大小写 ### 标准组合与 SDK 输出 -标准 agent spine 会挂载服务和四个伴随子路径,并把 `enabled`、`package_allowlist` 与 `package_blocklist` 转发给服务。生成的 SDK Cordis 组合输出相同条目。子路径条目添加可安装的根 npm 包,而不会把子路径误当成包名。 +标准 agent spine 会挂载服务和四个有状态伴随子路径,并把 `enabled`、`package_allowlist` 与 `package_blocklist` 转发给服务。生成的 SDK Cordis 组合输出相同条目。子路径条目添加可安装的根 npm 包,而不会把子路径误当成包名。 Workspace 约束识别独立的不变式 bundle;包 exports、项目引用、构建配置、依赖声明和 lockfile 描述同一发布表面。生成的配置目录、模块图和 API 文档都从这些源派生。 @@ -80,18 +84,22 @@ Workspace 约束识别独立的不变式 bundle;包 exports、项目引用、 组合测试覆盖标准 spine 转发和生成的 SDK 条目。Loader 测试固定每个伴随命名空间,构建后的纯 Node smoke 覆盖编译子路径 export。scoped event 新鲜度门禁会重新执行语义 Program 分析。 +每个 Vitest 配置都会加载测试宿主;在普通 Cordis 根上下文启动第一个插件之前,宿主会挂载显式启用的服务以及所有包的伴随插件。服务与所有者的聚焦测试自行构建不变式拓扑,从而在不发生重复所有权冲突的前提下覆盖关闭、过滤、回滚与重载。门禁测试还会执行每个伴随插件的 `apply` 函数,并验证它调用 `register` 时使用包清单中的包名,而不是只检查源码文本。 + ## 考虑过的替代方案 - **把所有检查保留在 `dsh-invariants`。** 不予采纳,因为注册包仍要导入所有被检查的产品领域,所有者变更仍需中央编辑,测试也继续远离被保护的契约。 - **当 `ctx.invariants` 恰好存在时,让根包入口隐式注册检查。** 不予采纳,因为根入口行为会依赖组合顺序与可选服务是否存在,诊断无法独立选择,而且包加载会隐藏一个不在显式伴随插件中的注册 effect。 -- **自动发现所有 `invariant.ts` 文件。** 不予采纳,因为文件系统或包发现不是运行时所有权契约,会让 bundle 发布含义不清,也无法表达显式 Cordis 加载顺序或依赖安装。 +- **在运行时自动发现所有 `invariant.ts` 文件。** 不予采纳,因为文件系统或包发现不是运行时所有权契约,会让 bundle 发布含义不清,也无法表达显式 Cordis 加载顺序或依赖安装。构建期生成与校验以及测试 host 可以枚举源码树,因为它们验证的是仓库完整性,而不是组合已发布的部署。 - **根据当前已加载包集合验证 allow/block 条目。** 不予采纳,因为零匹配模式可能有意指向稍后加载或 HMR 加载的贡献;当前加载顺序不能决定配置有效性。 ## 后果 - 产品包拥有并测试自己的关系断言,服务保持与产品无关。 +- 每个包都要承担不变式伴随插件带来的少量发布与依赖成本,包括目前只安装生成基线、不添加监听器的包。 - 标准组合无需改变插件树即可关闭全部检查或按包名选择。 - 显式伴随条目让诊断成本和所有权在 Cordis 配置与包 export 中可见。 - 每个选中贡献增加一个子 fiber 及其监听器和状态成本;被过滤注册只保留包名占用。 - 正则表达式源属于部署配置,在服务重载前保持固定。 +- 普通 Vitest 根上下文会安装每个被选中的伴随插件,以增加测试期间的子 fiber 为代价,换取覆盖整个仓库的不变式检查和对 fixture(测试前置数据)错误的即时反馈。 - 会话存储验证、快照、冻结、provenance 与 surface 接受规则始终启用,不受不变式选择影响。 diff --git a/docs/rfc/proposed/architecture/2026-06-16-typed-event-schemas.md b/docs/rfc/proposed/architecture/2026-06-16-typed-event-schemas.md index 5db00851ae..026273c088 100644 --- a/docs/rfc/proposed/architecture/2026-06-16-typed-event-schemas.md +++ b/docs/rfc/proposed/architecture/2026-06-16-typed-event-schemas.md @@ -28,7 +28,7 @@ A migration of the event/vocabulary surface to runtime schemas touches, at minim - **Six merge-extensible maps** (~370 LOC of core types): `ContentBlockMap`, `MessageSourceMap`, `FinishReasonMap` (in `dsh-llm`); `TurnTriggerMap`, `TurnEndReasonMap`, `SessionEventMap` (in `dsh-session`). - **~10 `declare module` augmentation sites** across `dsh-agent`, `dsh-agent-loop`, `dsh-bash`, `dsh-llm`, `dsh-session`, `dsh-session-persistence`, `dsh-system-prompt`, `dsh-tools` — each would move from declaration merging to a runtime `register()` call. - **The event producers** — 16 `session.append(...)` call sites in the loop — unchanged in shape but now validated at the boundary. -- **~7 switch-consumers** that branch on these unions: `deriveMessages` and the optional invariant companion (`dsh-session`), `BlockAssembler` (`dsh-llm`), both LLM adapters (`dsh-llm-deepseek`, `dsh-llm-pi-ai`), and the tool schema layer (`dsh-tools`). The `assertNever`-on-closed-unions vs fall-through-on-extensible-unions convention (a documented lint rule) would need rethinking — runtime variants are not statically exhaustive. +- **~7 switch-consumers** that branch on these unions: `deriveMessages` and the package-owned invariant companion (`dsh-session`), `BlockAssembler` (`dsh-llm`), both LLM adapters (`dsh-llm-deepseek`, `dsh-llm-pi-ai`), and the tool schema layer (`dsh-tools`). The `assertNever`-on-closed-unions vs fall-through-on-extensible-unions convention (a documented lint rule) would need rethinking — runtime variants are not statically exhaustive. - **The `defineTool` `InferArgs` DSL** (`dsh-tools`), which derives zero-cast `execute` arg types from a compile-time schema spec — the showcase of the current approach. - **Docs**: architecture.md (the pattern is described as foundational), [dev-mode invariants](../../implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md), and any RFC that references the pattern. @@ -37,7 +37,7 @@ This is a repository-wide vocabulary redesign, not a persistence implementation ## Alternatives considered ### A. Status quo — merge-extensible types + `isJsonValue` at the durable boundary -Keep the compile-time pattern. Persistence stays opaque-JSON + serializability guard. Plugins extend via declaration merging; correctness of event *shape* is the producer's responsibility and is enforced by TypeScript at compile time. Package-owned invariant companions check selected cross-record relationships in dev but do not provide general runtime shape schemas. +Keep the compile-time pattern. Persistence stays opaque-JSON + serializability guard. Plugins extend via declaration merging; correctness of event *shape* is the producer's responsibility and is enforced by TypeScript at compile time. Package-owned invariant companions check selected cross-record relationships when enabled but do not provide general runtime shape schemas. - **Pros**: zero churn; plugin extension is a one-line `interface` augmentation with full type inference and no runtime registration ceremony; no new runtime dependency; the `defineTool` DSL and `assertNever` exhaustiveness keep working. - **Cons**: no runtime structural validation at the persistence boundary or at plugin seams; a malformed-but-JSON datum is caught late. @@ -72,4 +72,4 @@ Defer. If runtime validation is wanted at the durable boundary, **Option B** (sc - If a registry is adopted, is the library **schemastery** (already in the tree, already the config schema lib) or **Zod** (richer ecosystem, currently only transitive)? Adopting two schema libraries is a cost in itself. - Can a hybrid keep compile-time inference (so `defineTool` and plugin DX survive) while adding an *optional* runtime schema per variant, validated only at the persistence/wire boundary rather than on every in-process append? -- Does the `dsh-invariants` plugin already cover enough of the runtime-shape gap in dev that boundary validation is only needed for genuinely untrusted input (reload of an externally-modified log)? +- Does the `ctx.invariants` service already cover enough of the runtime-shape gap when enabled that boundary validation is only needed for genuinely untrusted input (reload of an externally-modified log)? diff --git a/docs/testing.md b/docs/testing.md index cd3b1aa9e8..97ec20e99f 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -9,6 +9,8 @@ How this repo tests, tier by tier, and the rules that keep a green suite meaning - **Real-API e2e** (`pnpm run test:e2e`): with-key tests against live provider APIs — the DeepSeek model plus provider-specific smokes that gate on their own keys (`EXA_API_KEY`, `PERPLEXITY_API_KEY`, …); each suite self-skips without its key so keyless CI stays green ([real-API e2e RFC](rfc/implemented/testing/2026-06-19-real-api-e2e-ci.md)). - **Snapshot** (`pnpm run test:snapshot`): transport-specific keyless goldens cover external presentation. ACP suites boot the real example subprocess, replay a recorded session, and diff normalized JSON-RPC plus the re-persisted log ([ACP snapshot RFC](rfc/implemented/testing/2026-06-19-acp-snapshot-tests.md)); the headless suite independently pins `stream-json` through its real one-shot subprocess. TUI completed journeys replay recorded primary/child JSONL through the real agent loop and tools before projecting ANSI into semantic terminal-state goldens; package-local snapshots retain transient renderer states, and a real PTY conversation covers the process boundary ([TUI snapshot RFC](rfc/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md)). Use `pnpm run test:snapshot:record` when a model transcript must change and `pnpm run test:snapshot:refresh` when committed replay input remains correct; review every JSONL and golden diff. System-prompt/tool-schema content is pinned by one ACP scenario (`text-turn`) and tokenized in every other fixture, so a prompt or schema edit churns one committed line ([pinned-header RFC](rfc/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.md)). +All Vitest configurations mount enabled `ctx.invariants` and every package companion before ordinary Cordis roots start. Focused invariant topology tests compose enabled or deliberately disabled services explicitly, without competing global registrations. + ## The with-key policy: inference is cheap here We are DeepSeek — do not ration real-API tests. A no-key test proves plumbing; only a with-key run proves the agent works against a real model. Write many: file-writing prompts, multi-turn conversations, tool use, cancellation mid-stream. Highest-value are **smoke tests** that boot the real example, send one real prompt, and check the world — they catch the "green unit tests, broken product" class that mocks structurally cannot ([postmortem 0001](postmortem/0001-acp-default-export-drops-inject.md)). The self-skip exists only so secretless CI and keyless contributors aren't blocked; it is not a cost signal. Every example ships a keyless smoke and — unless keyless-by-nature — a with-key smoke ([examples/AGENTS.md](../examples/AGENTS.md)). diff --git a/knip.json b/knip.json index 2c3465ab38..3735303d8f 100644 --- a/knip.json +++ b/knip.json @@ -35,23 +35,19 @@ "project": ["src/**/*.ts", "tests/**/*.ts"] }, "packages/util/brand": { - "project": ["src/**/*.ts"], - "ignoreDependencies": ["cordis"] + "project": ["src/**/*.ts"] }, "packages/util/home": { "entry": ["tests/**/*.spec.ts"], - "project": ["src/**/*.ts", "tests/**/*.ts"], - "ignoreDependencies": ["cordis"] + "project": ["src/**/*.ts", "tests/**/*.ts"] }, "packages/util/timeout": { "entry": ["tests/**/*.spec.ts"], - "project": ["src/**/*.ts", "tests/**/*.ts"], - "ignoreDependencies": ["cordis"] + "project": ["src/**/*.ts", "tests/**/*.ts"] }, "packages/util/retention": { "entry": ["tests/**/*.spec.ts"], - "project": ["src/**/*.ts", "tests/**/*.ts"], - "ignoreDependencies": ["cordis"] + "project": ["src/**/*.ts", "tests/**/*.ts"] }, "packages/support/acp-snapshot": { "entry": ["tests/**/*.spec.ts", "tests/fixtures/fake-acp-agent.ts"], @@ -59,8 +55,7 @@ }, "packages/support/loader-smoke": { "entry": ["tests/**/*.spec.ts", "tests/fixtures/*.ts"], - "project": ["src/**/*.ts", "tests/**/*.ts"], - "ignoreDependencies": ["cordis"] + "project": ["src/**/*.ts", "tests/**/*.ts"] }, "packages/core/agent-loop": { "entry": ["tests/**/*.spec.ts", "tests/**/*.e2e.ts"], @@ -84,8 +79,7 @@ }, "packages/util/paths": { "entry": ["tests/**/*.spec.ts"], - "project": ["src/**/*.ts", "tests/**/*.ts"], - "ignoreDependencies": ["cordis"] + "project": ["src/**/*.ts", "tests/**/*.ts"] }, "packages/web/web-search-exa": { "entry": ["tests/**/*.spec.ts", "tests/**/*.e2e.ts"], @@ -149,8 +143,7 @@ }, "packages/subagent/subagent-subprocess": { "entry": ["tests/**/*.spec.ts"], - "project": ["src/**/*.ts", "tests/**/*.ts"], - "ignoreDependencies": ["cordis"] + "project": ["src/**/*.ts", "tests/**/*.ts"] }, "packages/fs/tool-fs": { "entry": ["tests/**/*.spec.ts", "tests/**/*.e2e.ts"], diff --git a/package.json b/package.json index e736e3f8d1..abf1e59178 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ "verify-md-links": "tsx scripts/verify-md-links.ts", "verify-doc-refs": "tsx scripts/verify-doc-refs.ts", "verify-package-paths": "tsx scripts/verify-package-paths.ts", + "gen-package-invariants": "tsx scripts/gen-package-invariants.ts", + "verify-package-invariants": "tsx scripts/gen-package-invariants.ts --check", "verify-package-readme-model-experience": "tsx scripts/verify-package-readme-model-experience.ts", "verify-mermaid": "tsx scripts/verify-mermaid.ts", "verify-rfc-classification": "tsx scripts/verify-rfc-classification.ts", @@ -77,7 +79,7 @@ "website:build": "pnpm --filter @deepseek-ai/website run build", "constraints": "tsx scripts/check-workspace-constraints.ts", "doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-cordis-api && pnpm run verify-export-jsdoc && pnpm run verify-tool-catalog && pnpm run verify-config-catalog && pnpm run verify-persistence-catalog && pnpm run verify-doc-graphs && pnpm run verify-scoped-events && pnpm run verify-website-api && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-package-readme-model-experience && pnpm run verify-mermaid && pnpm run verify-rfc-classification && pnpm run verify-rfc-format && pnpm run verify-type-equiv && pnpm run verify-translation-prompt && pnpm run verify-translation-pairing && pnpm run verify-doc-budgets && pnpm run verify-package-readme-limitations && pnpm run verify-website-yaml", - "hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-cordis-config && pnpm run verify-node-next-types && pnpm run verify-runtime-closure", + "hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-package-invariants && pnpm run verify-cordis-config && pnpm run verify-node-next-types && pnpm run verify-runtime-closure", "demo:echo": "node --expose-internals --import tsx packages/examples/stdio-demo/src/bin.ts examples/echo-agent/cordis.yml", "demo:repl": "node --expose-internals --import tsx packages/examples/stdio-demo/src/bin.ts examples/repl-agent/cordis.yml", "demo:headless": "node --expose-internals --import tsx packages/examples/cli-demo/src/bin.ts --config examples/headless-agent/cordis.yml", diff --git a/packages/AGENTS.md b/packages/AGENTS.md index e4b5ded6a3..1735c44978 100644 --- a/packages/AGENTS.md +++ b/packages/AGENTS.md @@ -16,6 +16,7 @@ These package-specific rules supplement the repo-wide [conventions](../AGENTS.md - **Publish state only at its commit point.** Emit each notification and update derived state only after the success boundary that makes it true; derive caches, prompts, UI echoes, replay, and query views from one authoritative source. - **Apply bounds to the complete result.** Enforce byte, token, item, and time limits where the complete emitted or retained value, including wrappers and metadata, is known; test tiny and exact limits, oversized single chunks, and multibyte byte limits. - **Registry contributions prove disposal.** Add the HMR-safety test required by the [testing policy](../docs/testing.md): dispose the contributing fiber and observe removal. +- **Every package owns an invariant companion.** Publish `./invariant`, register the manifest's exact npm name, and keep the generated ownership baseline until relational checks exist. `verify-package-invariants` gates source and publication wiring ([rationale](../docs/rfc/implemented/architecture/2026-07-19-package-owned-invariant-service.md)). Naming notes: diff --git a/packages/bash/bash-local/package.json b/packages/bash/bash-local/package.json index 381855b465..152c2db28b 100644 --- a/packages/bash/bash-local/package.json +++ b/packages/bash/bash-local/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,6 +28,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-bash": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-timeout": "^0.0.1", "cordis": "^4.0.0-rc.7" }, @@ -31,6 +37,7 @@ }, "devDependencies": { "@deepseek-ai/dsh-bash": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-timeout": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/bash/bash-local/src/invariant.ts b/packages/bash/bash-local/src/invariant.ts new file mode 100644 index 0000000000..8af5f91b22 --- /dev/null +++ b/packages/bash/bash-local/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-bash-local`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-bash-local/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-bash-local' + +/** Cordis companion plugin name. */ +export const name = 'bash-local-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/bash/bash-local/tsconfig.json b/packages/bash/bash-local/tsconfig.json index 02448770f4..a55c76f00a 100644 --- a/packages/bash/bash-local/tsconfig.json +++ b/packages/bash/bash-local/tsconfig.json @@ -25,6 +25,9 @@ }, { "path": "../../bash/bash" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/bash/bash-sandbox/package.json b/packages/bash/bash-sandbox/package.json index b4f61abcbe..42ba34c6a2 100644 --- a/packages/bash/bash-sandbox/package.json +++ b/packages/bash/bash-sandbox/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -24,6 +29,7 @@ "peerDependencies": { "@deepseek-ai/dsh-bash": "^0.0.1", "@deepseek-ai/dsh-bash-local": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-sandbox": "^0.0.1", "cordis": "^4.0.0-rc.7" }, @@ -33,9 +39,10 @@ "devDependencies": { "@deepseek-ai/dsh-bash": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-sandbox": "workspace:^", "@deepseek-ai/dsh-sandbox-local": "workspace:^", - "node-addon-landlock-run": "0.0.0-test.0", - "cordis": "^4.0.0-rc.7" + "cordis": "^4.0.0-rc.7", + "node-addon-landlock-run": "0.0.0-test.0" } } diff --git a/packages/bash/bash-sandbox/src/invariant.ts b/packages/bash/bash-sandbox/src/invariant.ts new file mode 100644 index 0000000000..e74190fa82 --- /dev/null +++ b/packages/bash/bash-sandbox/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-bash-sandbox`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-bash-sandbox/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-bash-sandbox' + +/** Cordis companion plugin name. */ +export const name = 'bash-sandbox-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/bash/bash-sandbox/tsconfig.json b/packages/bash/bash-sandbox/tsconfig.json index 6dad98d54f..0b0e350ce9 100644 --- a/packages/bash/bash-sandbox/tsconfig.json +++ b/packages/bash/bash-sandbox/tsconfig.json @@ -31,6 +31,9 @@ }, { "path": "../../bash/bash-local" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/bash/bash/package.json b/packages/bash/bash/package.json index 0783995161..8bee74f632 100644 --- a/packages/bash/bash/package.json +++ b/packages/bash/bash/package.json @@ -11,22 +11,29 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-sandbox": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-sandbox": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "cordis": "^4.0.0-rc.7" diff --git a/packages/bash/bash/src/invariant.ts b/packages/bash/bash/src/invariant.ts new file mode 100644 index 0000000000..350f68bfc7 --- /dev/null +++ b/packages/bash/bash/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-bash`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-bash/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-bash' + +/** Cordis companion plugin name. */ +export const name = 'bash-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/bash/bash/tsconfig.json b/packages/bash/bash/tsconfig.json index b9ab094edb..0e228ada67 100644 --- a/packages/bash/bash/tsconfig.json +++ b/packages/bash/bash/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../../core/session" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/bash/tool-bash/package.json b/packages/bash/tool-bash/package.json index 816be1ef88..ec17dbacdb 100644 --- a/packages/bash/tool-bash/package.json +++ b/packages/bash/tool-bash/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,15 +28,16 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", - "@deepseek-ai/dsh-user-approval": "^0.0.1", "@deepseek-ai/dsh-bash": "^0.0.1", "@deepseek-ai/dsh-home": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", - "@deepseek-ai/dsh-session-persistence": "^0.0.1", "@deepseek-ai/dsh-sandbox": "^0.0.1", + "@deepseek-ai/dsh-session-persistence": "^0.0.1", "@deepseek-ai/dsh-system-prompt": "^0.0.1", "@deepseek-ai/dsh-tasks": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", + "@deepseek-ai/dsh-user-approval": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "dependencies": { @@ -41,10 +47,10 @@ "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", - "@deepseek-ai/dsh-user-approval": "workspace:^", "@deepseek-ai/dsh-bash": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", "@deepseek-ai/dsh-home": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-sandbox": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", @@ -54,6 +60,7 @@ "@deepseek-ai/dsh-tasks": "workspace:^", "@deepseek-ai/dsh-tool-tasks": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", + "@deepseek-ai/dsh-user-approval": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/bash/tool-bash/src/invariant.ts b/packages/bash/tool-bash/src/invariant.ts new file mode 100644 index 0000000000..286089c7ff --- /dev/null +++ b/packages/bash/tool-bash/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tool-bash`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tool-bash/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tool-bash' + +/** Cordis companion plugin name. */ +export const name = 'tool-bash-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/bash/tool-bash/tsconfig.json b/packages/bash/tool-bash/tsconfig.json index 407e78ebd8..6854bb9f7e 100644 --- a/packages/bash/tool-bash/tsconfig.json +++ b/packages/bash/tool-bash/tsconfig.json @@ -46,6 +46,9 @@ }, { "path": "../../sandbox/sandbox" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/code-runtime/code-runtime-worker/package.json b/packages/code-runtime/code-runtime-worker/package.json index 77169f8eab..f9c0f6be4c 100644 --- a/packages/code-runtime/code-runtime-worker/package.json +++ b/packages/code-runtime/code-runtime-worker/package.json @@ -11,6 +11,10 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./worker": { "types": "./lib/types/worker.d.ts", "default": "./lib/worker.cjs" @@ -19,6 +23,7 @@ }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/worker.cjs", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", @@ -27,6 +32,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-code-runtime": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "dependencies": { @@ -34,6 +40,7 @@ }, "devDependencies": { "@deepseek-ai/dsh-code-runtime": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/code-runtime/code-runtime-worker/src/invariant.ts b/packages/code-runtime/code-runtime-worker/src/invariant.ts new file mode 100644 index 0000000000..41b3eab511 --- /dev/null +++ b/packages/code-runtime/code-runtime-worker/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-code-runtime-worker`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-code-runtime-worker/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-code-runtime-worker' + +/** Cordis companion plugin name. */ +export const name = 'code-runtime-worker-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/code-runtime/code-runtime-worker/tsconfig.json b/packages/code-runtime/code-runtime-worker/tsconfig.json index af962eda4f..4a201c70e1 100644 --- a/packages/code-runtime/code-runtime-worker/tsconfig.json +++ b/packages/code-runtime/code-runtime-worker/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../code-runtime" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/code-runtime/code-runtime-worker/tsdown.config.ts b/packages/code-runtime/code-runtime-worker/tsdown.config.ts index 6fee724195..1c40637722 100644 --- a/packages/code-runtime/code-runtime-worker/tsdown.config.ts +++ b/packages/code-runtime/code-runtime-worker/tsdown.config.ts @@ -7,7 +7,7 @@ import { defineConfig } from 'tsdown' */ export default defineConfig([ { - entry: ['lib/types/index.js'], + entry: ['lib/types/index.js', 'lib/types/invariant.js'], outDir: 'lib', format: ['esm'], platform: 'node', diff --git a/packages/code-runtime/code-runtime/package.json b/packages/code-runtime/code-runtime/package.json index 5380d26ace..2d59302ec9 100644 --- a/packages/code-runtime/code-runtime/package.json +++ b/packages/code-runtime/code-runtime/package.json @@ -11,20 +11,27 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/code-runtime/code-runtime/src/invariant.ts b/packages/code-runtime/code-runtime/src/invariant.ts new file mode 100644 index 0000000000..6102927d77 --- /dev/null +++ b/packages/code-runtime/code-runtime/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-code-runtime`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-code-runtime/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-code-runtime' + +/** Cordis companion plugin name. */ +export const name = 'code-runtime-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/code-runtime/code-runtime/tsconfig.json b/packages/code-runtime/code-runtime/tsconfig.json index 754725418e..9966c8ca8a 100644 --- a/packages/code-runtime/code-runtime/tsconfig.json +++ b/packages/code-runtime/code-runtime/tsconfig.json @@ -13,6 +13,9 @@ }, { "path": "../../../vendor/cordis" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/compact/compact-basic/package.json b/packages/compact/compact-basic/package.json index a0ee2b4036..44274238de 100644 --- a/packages/compact/compact-basic/package.json +++ b/packages/compact/compact-basic/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -24,6 +29,7 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-compact": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-token-meter": "^0.0.1", diff --git a/packages/compact/compact-basic/src/invariant.ts b/packages/compact/compact-basic/src/invariant.ts new file mode 100644 index 0000000000..f72c0e5898 --- /dev/null +++ b/packages/compact/compact-basic/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-compact-basic`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-compact-basic/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-compact-basic' + +/** Cordis companion plugin name. */ +export const name = 'compact-basic-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/compact/compact-basic/tests/compact-basic.spec.ts b/packages/compact/compact-basic/tests/compact-basic.spec.ts index 0a411440b3..31992793f0 100644 --- a/packages/compact/compact-basic/tests/compact-basic.spec.ts +++ b/packages/compact/compact-basic/tests/compact-basic.spec.ts @@ -10,7 +10,7 @@ import LlmService, { CallId, CONTEXT_WINDOW_EXCEEDED_CODE, LlmAdapter } from '@d import type { ContentBlock, GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' import { Session, SessionId } from '@deepseek-ai/dsh-session' import TokenMeterService from '@deepseek-ai/dsh-token-meter' -import type { Agent } from '@deepseek-ai/dsh-agent' +import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent' const SIGNAL = new AbortController().signal const MODEL = 'test-model' @@ -791,7 +791,7 @@ describe('default one-shot summarizer', () => { describe('automatic listener and loader composition', () => { function postStep(ctx: Context, owner: Agent, signal = SIGNAL): Promise { - return ctx.serial('agent/post-step', owner, 1, 1, signal) + return agentEvents(ctx, owner).serial('agent/post-step', 1, 1, signal) } function recover( @@ -802,7 +802,9 @@ describe('automatic listener and loader composition', () => { signal = SIGNAL, next: () => Promise<{ action: 'fail' | 'retry' }> = () => Promise.resolve({ action: 'fail' }), ): Promise<{ action: 'fail' | 'retry' }> { - return ctx.waterfall('agent/request-error', owner, 1, 1, error, retryAttempt, signal, next) + return agentEvents(ctx, owner).waterfall( + 'agent/request-error', 1, 1, error, retryAttempt, signal, next, + ) } function overflow(message = 'provider overflow'): Error & { code: string } { diff --git a/packages/compact/compact-basic/tsconfig.json b/packages/compact/compact-basic/tsconfig.json index 0103ad82a8..a009b9ecb8 100644 --- a/packages/compact/compact-basic/tsconfig.json +++ b/packages/compact/compact-basic/tsconfig.json @@ -6,13 +6,32 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../../llm/llm" }, - { "path": "../../llm/token-meter" }, - { "path": "../../core/session" }, - { "path": "../../core/agent" }, - { "path": "../compact" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../llm/token-meter" + }, + { + "path": "../../core/session" + }, + { + "path": "../../core/agent" + }, + { + "path": "../compact" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/compact/compact/package.json b/packages/compact/compact/package.json index 985c42d3b2..135c688507 100644 --- a/packages/compact/compact/package.json +++ b/packages/compact/compact/package.json @@ -11,22 +11,29 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "cordis": "^4.0.0-rc.7" diff --git a/packages/compact/compact/src/invariant.ts b/packages/compact/compact/src/invariant.ts new file mode 100644 index 0000000000..f7a2cbffd1 --- /dev/null +++ b/packages/compact/compact/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-compact`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-compact/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-compact' + +/** Cordis companion plugin name. */ +export const name = 'compact-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/compact/compact/tsconfig.json b/packages/compact/compact/tsconfig.json index 95245937ec..673ee51547 100644 --- a/packages/compact/compact/tsconfig.json +++ b/packages/compact/compact/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../../core/session" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/context/time-context/package.json b/packages/context/time-context/package.json index c0d69a75cb..ffbc3a87ee 100644 --- a/packages/context/time-context/package.json +++ b/packages/context/time-context/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -26,12 +31,14 @@ }, "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-loader-smoke": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", diff --git a/packages/context/time-context/src/invariant.ts b/packages/context/time-context/src/invariant.ts new file mode 100644 index 0000000000..64fb98ac81 --- /dev/null +++ b/packages/context/time-context/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-time-context`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-time-context/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-time-context' + +/** Cordis companion plugin name. */ +export const name = 'time-context-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/context/time-context/tests/time-context.spec.ts b/packages/context/time-context/tests/time-context.spec.ts index 06ae13818d..07c2fe54ae 100644 --- a/packages/context/time-context/tests/time-context.spec.ts +++ b/packages/context/time-context/tests/time-context.spec.ts @@ -4,8 +4,7 @@ import Loader from '@cordisjs/plugin-loader' import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm' import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' import { Session, SessionId } from '@deepseek-ai/dsh-session' -import AgentRegistry from '@deepseek-ai/dsh-agent' -import type { Agent } from '@deepseek-ai/dsh-agent' +import AgentRegistry, { agentEvents, type Agent } from '@deepseek-ai/dsh-agent' import { defineTool } from '@deepseek-ai/dsh-tools' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' @@ -83,7 +82,7 @@ async function fire( step: number, signal: AbortSignal = SIGNAL, ): Promise { - await ctx.serial('agent/pre-step', agent, turn, step, signal) + await agentEvents(ctx, agent).serial('agent/pre-step', turn, step, signal) } function textResponse(text: string): StreamChunk[] { diff --git a/packages/context/time-context/tsconfig.json b/packages/context/time-context/tsconfig.json index 7815242b55..491552def0 100644 --- a/packages/context/time-context/tsconfig.json +++ b/packages/context/time-context/tsconfig.json @@ -6,13 +6,32 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../../llm/llm" }, - { "path": "../../core/agent" }, - { "path": "../../core/system-prompt" }, - { "path": "../../core/agent" }, - { "path": "../../support/loader-smoke" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../core/agent" + }, + { + "path": "../../core/system-prompt" + }, + { + "path": "../../core/agent" + }, + { + "path": "../../support/loader-smoke" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/context/workspace-context/package.json b/packages/context/workspace-context/package.json index 7f704c838a..0c50b8cc17 100644 --- a/packages/context/workspace-context/package.json +++ b/packages/context/workspace-context/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -24,6 +29,7 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-fs": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-paths": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", @@ -39,6 +45,7 @@ "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-fs": "workspace:^", "@deepseek-ai/dsh-fs-local": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-llm-deepseek": "workspace:^", "@deepseek-ai/dsh-paths": "workspace:^", diff --git a/packages/context/workspace-context/src/invariant.ts b/packages/context/workspace-context/src/invariant.ts new file mode 100644 index 0000000000..f3e76225c0 --- /dev/null +++ b/packages/context/workspace-context/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-workspace-context`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-workspace-context/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-workspace-context' + +/** Cordis companion plugin name. */ +export const name = 'workspace-context-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/context/workspace-context/tests/workspace-context.spec.ts b/packages/context/workspace-context/tests/workspace-context.spec.ts index 2ff2067cf5..bdeeabe810 100644 --- a/packages/context/workspace-context/tests/workspace-context.spec.ts +++ b/packages/context/workspace-context/tests/workspace-context.spec.ts @@ -7,8 +7,9 @@ import Loader from '@cordisjs/plugin-loader' import * as workspaceContext from '@deepseek-ai/dsh-workspace-context' import LlmService, { CallId, type Message, type StreamChunk } from '@deepseek-ai/dsh-llm' import SessionStore, { Session, SessionId, SESSION_FORMAT_VERSION, type SessionEvent } from '@deepseek-ai/dsh-session' -import AgentRegistry, { type Agent, type HookContext } from '@deepseek-ai/dsh-agent' +import AgentRegistry, { agentEvents, type Agent, type HookContext } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { scopeTarget } from '@deepseek-ai/dsh-scope' import { FileSystem, FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs' import type { FsDirEntry, @@ -23,7 +24,12 @@ import type { import LocalFileSystem from '@deepseek-ai/dsh-fs-local' import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools' -import type { ToolExecution, ToolExecutionToken } from '@deepseek-ai/dsh-tools' +import type { + PostToolDecision, + ToolExecution, + ToolExecutionResult, + ToolExecutionToken, +} from '@deepseek-ai/dsh-tools' import * as ToolFs from '@deepseek-ai/dsh-tool-fs' import { discoverBaselineInstructionFiles, @@ -228,14 +234,31 @@ const composedPrefixes = new WeakMap() async function composeBaselinePrefix(ctx: Context, agent: Agent): Promise { const empty: Message[] = [] - const prefix = await ctx.waterfall( - 'agent/session-prefix', agent, empty, AbortSignal.timeout(1000), + const prefix = await agentEvents(ctx, agent).waterfall( + 'agent/session-prefix', empty, AbortSignal.timeout(1000), () => Promise.resolve(empty), ) composedPrefixes.set(agent, prefix) return prefix } +function toolEventCarrier(ctx: Context, exec: ToolExecution) { + return scopeTarget(ctx.get('tools') ?? ctx as unknown as ToolRegistry, exec.agent) +} + +function postExecute( + ctx: Context, + exec: ToolExecution, + result: Readonly, + next: () => Promise, +): Promise { + return ctx.waterfall(toolEventCarrier(ctx, exec), 'tools/post-execute', exec, result, next) +} + +function emitToolResult(ctx: Context, exec: ToolExecution, result: Readonly): void { + ctx.emit(toolEventCarrier(ctx, exec), 'tools/result', exec, result) +} + function derivedText(agent: Agent): string { return blocksText(composedPrefixes.get(agent)?.[0]?.content) } @@ -796,7 +819,7 @@ describe('workspace context request injection', () => { try { await ctx.plugin(workspaceContext, { maxBytes: 65536 }) - const decision = await ctx.waterfall('tools/post-execute', stubToolExecution({ + const decision = await postExecute(ctx, stubToolExecution({ callId: CallId('no-fs-post-execute'), name: 'read', arguments: { file_path: 'pkg/file.txt' }, @@ -843,7 +866,7 @@ describe('workspace context request injection', () => { } // A later PostToolUse-style policy blocks this otherwise-successful read. - const blocked = await ctx.waterfall('tools/post-execute', exec, result, async () => ({ + const blocked = await postExecute(ctx, exec, result, async () => ({ kind: 'block' as const, feedback: [{ type: 'text' as const, text: 'blocked by policy' }], })) @@ -857,7 +880,7 @@ describe('workspace context request injection', () => { // The same read, when the downstream accepts, DOES surface the nested // instructions — proving the block branch above is what suppressed them, // and that the block did not consume the pending nested change. - const accepted = await ctx.waterfall('tools/post-execute', exec, result, async () => ({ + const accepted = await postExecute(ctx, exec, result, async () => ({ kind: 'accept' as const, })) expect(accepted.kind).toBe('accept') @@ -1169,8 +1192,9 @@ describe('workspace context request injection', () => { const controller = new AbortController() const reason = new Error('cancel prefix') const empty: Message[] = [] - const pending = ctx.waterfall( - 'agent/session-prefix', stubAgent(root), empty, controller.signal, + const agent = stubAgent(root) + const pending = agentEvents(ctx, agent).waterfall( + 'agent/session-prefix', empty, controller.signal, () => Promise.resolve(empty), ) @@ -1660,7 +1684,7 @@ describe('dynamic nested workspace context injection', () => { signal: controller.signal, }) - const pending = ctx.waterfall('tools/post-execute', exec, { + const pending = postExecute(ctx, exec, { content: [{ type: 'text', text: 'ok' }], isError: false, }, () => Promise.resolve({ kind: 'accept' as const })) @@ -2387,12 +2411,12 @@ describe('dynamic nested workspace context injection', () => { isError: false, } - const failedStat = await ctx.waterfall('tools/post-execute', stubToolExecution({ + const failedStat = await postExecute(ctx, stubToolExecution({ callId: CallId('provider-stat-failure'), name: 'read', arguments: { file_path: 'pkg/file.txt' }, agent, }), result, async () => ({ kind: 'accept' as const })) fs.throwOnStat.clear() fs.entries.set(join(root, 'pkg/AGENTS.md'), { type: 'directory' }) - const mismatchedStat = await ctx.waterfall('tools/post-execute', stubToolExecution({ + const mismatchedStat = await postExecute(ctx, stubToolExecution({ callId: CallId('provider-stat-mismatch'), name: 'read', arguments: { file_path: 'pkg/file.txt' }, agent, }), result, async () => ({ kind: 'accept' as const })) @@ -2626,19 +2650,19 @@ describe('dynamic nested workspace context injection', () => { const parent = Symbol('parent') as ToolExecutionToken const plainResult = { callId: CallId('plain'), content: [], isError: false } - ctx.emit('tools/result', stubToolExecution({ + emitToolResult(ctx, stubToolExecution({ callId: CallId('agentless-child'), name: 'read', arguments: {}, parent, }), plainResult) - ctx.emit('tools/result', stubToolExecution({ + emitToolResult(ctx, stubToolExecution({ callId: CallId('contextless-child'), name: 'read', arguments: {}, agent, parent, }), { ...plainResult, additionalContexts: [{ content: [], source: { kind: 'plugin', plugin: 'workspace-context' } }] }) - ctx.emit('tools/result', stubToolExecution({ + emitToolResult(ctx, stubToolExecution({ callId: CallId('first-child'), name: 'read', arguments: {}, agent, parent, }), { ...plainResult, additionalContexts: [workspaceChangeContext('first', 'one')] }) - ctx.emit('tools/result', stubToolExecution({ + emitToolResult(ctx, stubToolExecution({ callId: CallId('second-child'), name: 'read', arguments: {}, agent, parent, }), { ...plainResult, additionalContexts: [workspaceChangeContext('second', 'two')] }) - ctx.emit('tools/result', { + emitToolResult(ctx, { ...stubToolExecution({ callId: CallId('agentless-parent'), name: 'composite', arguments: {} }), token: parent, }, plainResult) @@ -2674,7 +2698,7 @@ describe('dynamic nested workspace context injection', () => { ] for (const item of cases) { - const decision = await ctx.waterfall('tools/post-execute', stubToolExecution({ + const decision = await postExecute(ctx, stubToolExecution({ callId: CallId(`manual-${item.name}-${cases.indexOf(item)}`), name: item.name, arguments: item.arguments, diff --git a/packages/context/workspace-context/tsconfig.json b/packages/context/workspace-context/tsconfig.json index b4807ded65..b5aca1dfc8 100644 --- a/packages/context/workspace-context/tsconfig.json +++ b/packages/context/workspace-context/tsconfig.json @@ -31,6 +31,9 @@ }, { "path": "../../util/paths" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/cordis/tool-cordis/package.json b/packages/cordis/tool-cordis/package.json index e13de3e58b..7e7b75cab3 100644 --- a/packages/cordis/tool-cordis/package.json +++ b/packages/cordis/tool-cordis/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-scope": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", "cordis": "^4.0.0-rc.7" @@ -30,16 +36,17 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@cordisjs/plugin-loader": "^1.0.0-rc.5", + "@cordisjs/plugin-timer": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-scope": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", + "@deepseek-ai/dsh-scope": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", - "@cordisjs/plugin-loader": "^1.0.0-rc.5", - "cordis": "^4.0.0-rc.7", - "@cordisjs/plugin-timer": "workspace:^" + "cordis": "^4.0.0-rc.7" } } diff --git a/packages/cordis/tool-cordis/src/invariant.ts b/packages/cordis/tool-cordis/src/invariant.ts new file mode 100644 index 0000000000..a3f375b69f --- /dev/null +++ b/packages/cordis/tool-cordis/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tool-cordis`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tool-cordis/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tool-cordis' + +/** Cordis companion plugin name. */ +export const name = 'tool-cordis-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/cordis/tool-cordis/tsconfig.json b/packages/cordis/tool-cordis/tsconfig.json index cc9928d81f..4f10b49622 100644 --- a/packages/cordis/tool-cordis/tsconfig.json +++ b/packages/cordis/tool-cordis/tsconfig.json @@ -25,6 +25,9 @@ }, { "path": "../../core/tools" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/core/agent-loop/src/invariant.ts b/packages/core/agent-loop/src/invariant.ts index 41b9112a4e..ab6a885dc0 100644 --- a/packages/core/agent-loop/src/invariant.ts +++ b/packages/core/agent-loop/src/invariant.ts @@ -12,8 +12,8 @@ const PACKAGE_NAME = '@deepseek-ai/dsh-agent-loop' /** Cordis companion plugin name. */ export const name = 'agent-loop-invariant' -/** Services required before the companion can register. */ -export const inject = ['invariants', 'sessions'] +/** Service required before the companion can reserve package ownership. */ +export const inject = ['invariants'] /** Install the request-reconstruction contribution into its child registration fiber. */ const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { @@ -66,7 +66,7 @@ const install: InvariantInstaller = Object.assign((ctx: Context, fail: Invariant /** * Register the agent-loop invariant companion. - * @param ctx - Cordis context carrying the invariant and session services. + * @param ctx - Cordis context carrying the invariant service. * @returns the installed registration's disposer after setup succeeds. */ export const apply = (ctx: Context): Promise<() => void> => diff --git a/packages/core/agent-loop/tests/resume.spec.ts b/packages/core/agent-loop/tests/resume.spec.ts index 74192dde90..be98eb8447 100644 --- a/packages/core/agent-loop/tests/resume.spec.ts +++ b/packages/core/agent-loop/tests/resume.spec.ts @@ -425,7 +425,7 @@ describe('the session-persistence RFC: AgentLoop factory create/resume', () => { seed, meta: { cwd: '/w', parentSession: SessionId('parent-sess'), seedLength: seed.length }, }) - await ctx1.parallel('session/flush', forked) + await ctx1.sessions.flush(forked) await ctx1.fiber.dispose() // Lifecycle 2: resume it; the parentSession + seedLength header survives the @@ -482,7 +482,7 @@ describe('the session-persistence RFC: AgentLoop factory create/resume', () => { a1.send([{ type: 'text', text: 'q' }], { source: { kind: 'user' } }) await waitForIdle(ctx1, a1) a1.inject([{ type: 'text', text: 'background task 42 finished' }], { source: { kind: 'plugin', plugin: 'tool-bash' } }) - await ctx1.parallel('session/flush', a1.session) + await ctx1.sessions.flush(a1.session) await ctx1.fiber.dispose() // Lifecycle 2: resume; the injected context is still in the derived history. diff --git a/packages/core/session/src/invariant.ts b/packages/core/session/src/invariant.ts index 2887d6f9f0..22b567183e 100644 --- a/packages/core/session/src/invariant.ts +++ b/packages/core/session/src/invariant.ts @@ -15,8 +15,8 @@ const PACKAGE_NAME = '@deepseek-ai/dsh-session' /** Cordis companion plugin name. */ export const name = 'session-invariant' -/** Services required before the companion can register. */ -export const inject = ['invariants', 'sessions'] +/** Service required before the companion can reserve package ownership. */ +export const inject = ['invariants'] /** Per-session bookkeeping for relational log checks. */ interface SessionTrace { @@ -223,7 +223,7 @@ const install: InvariantInstaller = Object.assign((ctx: Context, fail: Invariant /** * Register the session invariant companion. - * @param ctx - Cordis context carrying the invariant and session services. + * @param ctx - Cordis context carrying the invariant service. * @returns the installed registration's disposer after setup succeeds. */ export const apply = (ctx: Context): Promise<() => void> => diff --git a/packages/core/session/tests/session.spec.ts b/packages/core/session/tests/session.spec.ts index 3b15157bde..a9addbf1cb 100644 --- a/packages/core/session/tests/session.spec.ts +++ b/packages/core/session/tests/session.spec.ts @@ -718,10 +718,11 @@ describe('SessionStore', () => { // may create an unrelated property with the old implementation's name, // but cannot suppress the durable event feed. expect(Reflect.set(session, 'onAppend', undefined)).toBe(true) + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('user/message', { content: [{ type: 'text', text: 'x' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) - expect(events).toHaveLength(1) - expect(events[0]![0]).toBe(session) - expect(events[0]![1].type).toBe('user/message') + expect(events).toHaveLength(2) + expect(events[1]![0]).toBe(session) + expect(events[1]![1].type).toBe('user/message') expect(ctx.sessions.get(session.id)).toBe(session) expect(ctx.sessions.list()).toEqual([session]) @@ -733,6 +734,7 @@ describe('SessionStore', () => { const a = ctx.sessions.create(SessionId('fixed')) expect(() => ctx.sessions.create(SessionId('fixed'))).toThrow('already exists') + a.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) a.append('user/message', { content: [{ type: 'text', text: 'q' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) const forked = ctx.sessions.create(SessionId('fork'), { seed: [...a.events] }) expect(forked.deriveMessages()).toEqual(a.deriveMessages()) @@ -958,8 +960,9 @@ describe('SessionStore', () => { ctx.on('session/event', (_session, event) => void events.push(event)) const session = ctx.sessions.create(SessionId('fixed')) expect(ctx.sessions.get(SessionId('fixed'))).toBe(session) + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('user/message', { content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) - expect(events).toHaveLength(1) + expect(events.at(-1)?.type).toBe('user/message') }) it('contains session/event observer failures after the append commit point', async () => { @@ -1043,6 +1046,8 @@ describe('SessionStore', () => { const ctx = new Context() await ctx.plugin(SessionStore) const session = ctx.sessions.create(SessionId('surface-dispatch-veto')) + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) + session.append('step/start', { turn: 1, step: 1 }) session.append('user/message', { content: [{ type: 'text', text: 'source' }], source: { kind: 'user' }, @@ -1062,19 +1067,19 @@ describe('SessionStore', () => { step: 1, content: [{ type: 'text', text: 'replacement' }], }, { - surfaceOp: { op: 'replace', start: 0, end: 0 }, - sourceEventSeqs: [0], + surfaceOp: { op: 'replace', start: 2, end: 2 }, + sourceEventSeqs: [2], })).toThrow('reject surface candidate') - expect(session.events).toHaveLength(1) - expect(surface.nodes).toEqual([0]) + expect(session.events).toHaveLength(3) + expect(surface.nodes).toEqual([2]) expect(surface.replaceGeneration).toBe(0) session.append('user/message', { content: [{ type: 'text', text: 'next' }], source: { kind: 'user' }, }, { surfaceOp: 'append' }) - expect(surface.nodes).toEqual([0, 1]) + expect(surface.nodes).toEqual([2, 3]) expect(surface.replaceGeneration).toBe(0) }) diff --git a/packages/core/system-prompt/package.json b/packages/core/system-prompt/package.json index 67161b79b4..6cf94f477e 100644 --- a/packages/core/system-prompt/package.json +++ b/packages/core/system-prompt/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-scope": "^0.0.1", "cordis": "^4.0.0-rc.7" @@ -30,6 +36,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-scope": "workspace:^", "cordis": "^4.0.0-rc.7" diff --git a/packages/core/system-prompt/src/invariant.ts b/packages/core/system-prompt/src/invariant.ts new file mode 100644 index 0000000000..5117f93ce0 --- /dev/null +++ b/packages/core/system-prompt/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-system-prompt`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-system-prompt/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-system-prompt' + +/** Cordis companion plugin name. */ +export const name = 'system-prompt-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/core/system-prompt/tsconfig.json b/packages/core/system-prompt/tsconfig.json index 91e7bf1ba4..c7de9a1b69 100644 --- a/packages/core/system-prompt/tsconfig.json +++ b/packages/core/system-prompt/tsconfig.json @@ -22,6 +22,9 @@ }, { "path": "../../core/scope" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/core/tools/package.json b/packages/core/tools/package.json index 2fe3cbd448..535e60f60b 100644 --- a/packages/core/tools/package.json +++ b/packages/core/tools/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,12 +28,13 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", - "@deepseek-ai/dsh-user-approval": "^0.0.1", "@deepseek-ai/dsh-code-runtime": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-scope": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-system-prompt": "^0.0.1", + "@deepseek-ai/dsh-user-approval": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "dependencies": { @@ -36,12 +42,13 @@ }, "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", - "@deepseek-ai/dsh-user-approval": "workspace:^", "@deepseek-ai/dsh-code-runtime": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-scope": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", + "@deepseek-ai/dsh-user-approval": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/core/tools/src/invariant.ts b/packages/core/tools/src/invariant.ts new file mode 100644 index 0000000000..7c5743e1d4 --- /dev/null +++ b/packages/core/tools/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tools`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tools/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tools' + +/** Cordis companion plugin name. */ +export const name = 'tools-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/core/tools/tsconfig.json b/packages/core/tools/tsconfig.json index c94b270c8c..918112d7d0 100644 --- a/packages/core/tools/tsconfig.json +++ b/packages/core/tools/tsconfig.json @@ -34,6 +34,9 @@ }, { "path": "../../ui/user-approval" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/examples/acp-demo/package.json b/packages/examples/acp-demo/package.json index 28d057c0a0..6743c55a3e 100644 --- a/packages/examples/acp-demo/package.json +++ b/packages/examples/acp-demo/package.json @@ -14,6 +14,10 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./bin": { "types": "./lib/types/bin.d.ts", "default": "./lib/bin.js" @@ -23,6 +27,7 @@ }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/bin.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", @@ -32,28 +37,30 @@ "peerDependencies": { "@cordisjs/plugin-include": "^1.0.4", "@cordisjs/plugin-loader": "^1.0.0-rc.5", - "@deepseek-ai/dsh-app-boot": "^0.0.1", "@deepseek-ai/dsh-acp": "^0.0.1", "@deepseek-ai/dsh-agent-spine-demo": "^0.0.1", - "@deepseek-ai/dsh-workspace-context": "^0.0.1", + "@deepseek-ai/dsh-app-boot": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-session-persistence-jsonl": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", "@deepseek-ai/dsh-user-interaction": "^0.0.1", + "@deepseek-ai/dsh-workspace-context": "^0.0.1", "cordis": "^4.0.0-rc.7", "schemastery": "^3.17.0" }, "devDependencies": { "@cordisjs/plugin-include": "workspace:^", "@cordisjs/plugin-loader": "workspace:^", - "@deepseek-ai/dsh-app-boot": "workspace:^", "@deepseek-ai/dsh-acp": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", - "@deepseek-ai/dsh-tools": "workspace:^", - "@deepseek-ai/dsh-system-prompt": "workspace:^", - "@deepseek-ai/dsh-workspace-context": "workspace:^", + "@deepseek-ai/dsh-app-boot": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^", + "@deepseek-ai/dsh-system-prompt": "workspace:^", + "@deepseek-ai/dsh-tools": "workspace:^", "@deepseek-ai/dsh-user-interaction": "workspace:^", + "@deepseek-ai/dsh-workspace-context": "workspace:^", "cordis": "^4.0.0-rc.7", "schemastery": "^3.17.0" } diff --git a/packages/examples/acp-demo/src/invariant.ts b/packages/examples/acp-demo/src/invariant.ts new file mode 100644 index 0000000000..d8f2ff17dc --- /dev/null +++ b/packages/examples/acp-demo/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-acp-demo`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-acp-demo/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-acp-demo' + +/** Cordis companion plugin name. */ +export const name = 'acp-demo-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/examples/acp-demo/tsconfig.json b/packages/examples/acp-demo/tsconfig.json index b0e537574a..2f6acd4a17 100644 --- a/packages/examples/acp-demo/tsconfig.json +++ b/packages/examples/acp-demo/tsconfig.json @@ -40,6 +40,9 @@ }, { "path": "../../session-persistence/session-persistence-jsonl" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/examples/acp-demo/tsdown.config.ts b/packages/examples/acp-demo/tsdown.config.ts index 9dd130b30d..2fa93780be 100644 --- a/packages/examples/acp-demo/tsdown.config.ts +++ b/packages/examples/acp-demo/tsdown.config.ts @@ -8,7 +8,7 @@ import { defineConfig } from 'tsdown' * matching every package. */ export default defineConfig({ - entry: ['lib/types/index.js', 'lib/types/bin.js'], + entry: ['lib/types/index.js', 'lib/types/invariant.js', 'lib/types/bin.js'], outDir: 'lib', format: ['esm'], platform: 'node', diff --git a/packages/examples/agent-spine-demo/package.json b/packages/examples/agent-spine-demo/package.json index cae0aab6d4..1cc6214599 100644 --- a/packages/examples/agent-spine-demo/package.json +++ b/packages/examples/agent-spine-demo/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -25,12 +30,11 @@ "@cordisjs/plugin-timer": "^1.1.2", "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-agent-loop": "^0.0.1", - "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-home": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", - "@deepseek-ai/dsh-workspace-context": "^0.0.1", - "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-scope": "^0.0.1", + "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-skill": "^0.0.1", "@deepseek-ai/dsh-skill-local": "^0.0.1", "@deepseek-ai/dsh-system-prompt": "^0.0.1", @@ -39,6 +43,7 @@ "@deepseek-ai/dsh-tool-skill": "^0.0.1", "@deepseek-ai/dsh-tool-tasks": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", + "@deepseek-ai/dsh-workspace-context": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { @@ -46,12 +51,11 @@ "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-fs-local": "workspace:^", - "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-home": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", - "@deepseek-ai/dsh-workspace-context": "workspace:^", - "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-scope": "workspace:^", + "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-skill": "workspace:^", "@deepseek-ai/dsh-skill-local": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", @@ -60,6 +64,7 @@ "@deepseek-ai/dsh-tool-skill": "workspace:^", "@deepseek-ai/dsh-tool-tasks": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", + "@deepseek-ai/dsh-workspace-context": "workspace:^", "cordis": "^4.0.0-rc.7" }, "dependencies": { diff --git a/packages/examples/agent-spine-demo/src/invariant.ts b/packages/examples/agent-spine-demo/src/invariant.ts new file mode 100644 index 0000000000..913b5ca2ab --- /dev/null +++ b/packages/examples/agent-spine-demo/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-agent-spine-demo`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-agent-spine-demo/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-agent-spine-demo' + +/** Cordis companion plugin name. */ +export const name = 'agent-spine-demo-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/examples/cli-demo/package.json b/packages/examples/cli-demo/package.json index 7b035e2524..1c00a32891 100644 --- a/packages/examples/cli-demo/package.json +++ b/packages/examples/cli-demo/package.json @@ -14,6 +14,10 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./bin": { "types": "./lib/types/bin.d.ts", "default": "./lib/bin.js" @@ -23,6 +27,7 @@ }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/bin.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", @@ -35,6 +40,7 @@ "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-agent-spine-demo": "^0.0.1", "@deepseek-ai/dsh-app-boot": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-session-persistence-jsonl": "^0.0.1", @@ -49,6 +55,7 @@ "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", "@deepseek-ai/dsh-app-boot": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^", diff --git a/packages/examples/cli-demo/src/invariant.ts b/packages/examples/cli-demo/src/invariant.ts new file mode 100644 index 0000000000..5681362dcc --- /dev/null +++ b/packages/examples/cli-demo/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-cli-demo`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-cli-demo/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-cli-demo' + +/** Cordis companion plugin name. */ +export const name = 'cli-demo-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/examples/cli-demo/tsconfig.json b/packages/examples/cli-demo/tsconfig.json index f25b1592ca..c7e3aed914 100644 --- a/packages/examples/cli-demo/tsconfig.json +++ b/packages/examples/cli-demo/tsconfig.json @@ -8,15 +8,38 @@ }, "include": ["src/**/*.ts"], "references": [ - { "path": "../../../vendor/schemastery" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../llm/llm" }, - { "path": "../../core/session" }, - { "path": "../../core/agent" }, - { "path": "../../core/system-prompt" }, - { "path": "../../core/tools" }, - { "path": "../agent-spine-demo" }, - { "path": "../../session-persistence/session-persistence-jsonl" }, - { "path": "../../ui/app-boot" } + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../core/session" + }, + { + "path": "../../core/agent" + }, + { + "path": "../../core/system-prompt" + }, + { + "path": "../../core/tools" + }, + { + "path": "../agent-spine-demo" + }, + { + "path": "../../session-persistence/session-persistence-jsonl" + }, + { + "path": "../../ui/app-boot" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/examples/cli-demo/tsdown.config.ts b/packages/examples/cli-demo/tsdown.config.ts index e5b164d46f..646855bea9 100644 --- a/packages/examples/cli-demo/tsdown.config.ts +++ b/packages/examples/cli-demo/tsdown.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from 'tsdown' /** Builds the plugin and executable entries from declarations emitted by `tsc -b`. */ export default defineConfig({ - entry: ['lib/types/index.js', 'lib/types/bin.js'], + entry: ['lib/types/index.js', 'lib/types/invariant.js', 'lib/types/bin.js'], outDir: 'lib', format: ['esm'], platform: 'node', diff --git a/packages/examples/jsonrpc-demo/package.json b/packages/examples/jsonrpc-demo/package.json index 3b04fcc977..d103e3bd00 100644 --- a/packages/examples/jsonrpc-demo/package.json +++ b/packages/examples/jsonrpc-demo/package.json @@ -14,6 +14,10 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./bin": { "types": "./lib/types/bin.d.ts", "default": "./lib/bin.js" @@ -23,6 +27,7 @@ }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/bin.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", @@ -33,9 +38,11 @@ "@deepseek-ai/dsh-app-boot": "workspace:^" }, "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/examples/jsonrpc-demo/src/invariant.ts b/packages/examples/jsonrpc-demo/src/invariant.ts new file mode 100644 index 0000000000..21f19faf0f --- /dev/null +++ b/packages/examples/jsonrpc-demo/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-jsonrpc-demo`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-jsonrpc-demo/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-jsonrpc-demo' + +/** Cordis companion plugin name. */ +export const name = 'jsonrpc-demo-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/examples/jsonrpc-demo/tsconfig.json b/packages/examples/jsonrpc-demo/tsconfig.json index 83d6cf5aa2..aba279d405 100644 --- a/packages/examples/jsonrpc-demo/tsconfig.json +++ b/packages/examples/jsonrpc-demo/tsconfig.json @@ -16,6 +16,9 @@ }, { "path": "../../ui/app-boot" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/examples/jsonrpc-demo/tsdown.config.ts b/packages/examples/jsonrpc-demo/tsdown.config.ts index aaa860edd0..a8864a84a9 100644 --- a/packages/examples/jsonrpc-demo/tsdown.config.ts +++ b/packages/examples/jsonrpc-demo/tsdown.config.ts @@ -4,7 +4,7 @@ import { defineConfig } from 'tsdown' * Build the doc-only module and CLI entry; `tsc -b` supplies declarations. */ export default defineConfig({ - entry: ['lib/types/index.js', 'lib/types/bin.js'], + entry: ['lib/types/index.js', 'lib/types/invariant.js', 'lib/types/bin.js'], outDir: 'lib', format: ['esm'], platform: 'node', diff --git a/packages/examples/stdio-demo/package.json b/packages/examples/stdio-demo/package.json index 94554e3f8e..69c45bb362 100644 --- a/packages/examples/stdio-demo/package.json +++ b/packages/examples/stdio-demo/package.json @@ -14,6 +14,10 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./bin": { "types": "./lib/types/bin.d.ts", "default": "./lib/bin.js" @@ -23,6 +27,7 @@ }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/bin.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", @@ -33,19 +38,20 @@ "@cordisjs/plugin-include": "^1.0.4", "@cordisjs/plugin-loader": "^1.0.0-rc.5", "@cordisjs/plugin-logger-console": "^1.0.0", - "@deepseek-ai/dsh-app-boot": "^0.0.1", "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-agent-loop": "^0.0.1", - "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-agent-spine-demo": "^0.0.1", - "@deepseek-ai/dsh-workspace-context": "^0.0.1", + "@deepseek-ai/dsh-app-boot": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", + "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-session-persistence-jsonl": "^0.0.1", "@deepseek-ai/dsh-stdio": "^0.0.1", - "@deepseek-ai/dsh-tui": "^0.0.1", "@deepseek-ai/dsh-tool-ask-user": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", + "@deepseek-ai/dsh-tui": "^0.0.1", "@deepseek-ai/dsh-user-interaction": "^0.0.1", + "@deepseek-ai/dsh-workspace-context": "^0.0.1", "cordis": "^4.0.0-rc.7", "schemastery": "^3.17.0" }, @@ -53,20 +59,21 @@ "@cordisjs/plugin-include": "workspace:^", "@cordisjs/plugin-loader": "workspace:^", "@cordisjs/plugin-logger-console": "workspace:^", - "@deepseek-ai/dsh-app-boot": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", - "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", - "@deepseek-ai/dsh-system-prompt": "workspace:^", - "@deepseek-ai/dsh-workspace-context": "workspace:^", + "@deepseek-ai/dsh-app-boot": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", + "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^", "@deepseek-ai/dsh-stdio": "workspace:^", - "@deepseek-ai/dsh-tui": "workspace:^", + "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/dsh-tool-ask-user": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", + "@deepseek-ai/dsh-tui": "workspace:^", "@deepseek-ai/dsh-user-interaction": "workspace:^", + "@deepseek-ai/dsh-workspace-context": "workspace:^", "cordis": "^4.0.0-rc.7", "schemastery": "^3.17.0" } diff --git a/packages/examples/stdio-demo/src/invariant.ts b/packages/examples/stdio-demo/src/invariant.ts new file mode 100644 index 0000000000..5d51c3cd9b --- /dev/null +++ b/packages/examples/stdio-demo/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-stdio-demo`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-stdio-demo/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-stdio-demo' + +/** Cordis companion plugin name. */ +export const name = 'stdio-demo-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/examples/stdio-demo/tsconfig.json b/packages/examples/stdio-demo/tsconfig.json index fc6711ffb9..265ee5f35b 100644 --- a/packages/examples/stdio-demo/tsconfig.json +++ b/packages/examples/stdio-demo/tsconfig.json @@ -49,6 +49,9 @@ }, { "path": "../../session-persistence/session-persistence-jsonl" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/examples/stdio-demo/tsdown.config.ts b/packages/examples/stdio-demo/tsdown.config.ts index 53797cdd79..7e907e8a18 100644 --- a/packages/examples/stdio-demo/tsdown.config.ts +++ b/packages/examples/stdio-demo/tsdown.config.ts @@ -8,7 +8,7 @@ import { defineConfig } from 'tsdown' * matching every package. */ export default defineConfig({ - entry: ['lib/types/index.js', 'lib/types/bin.js'], + entry: ['lib/types/index.js', 'lib/types/invariant.js', 'lib/types/bin.js'], outDir: 'lib', format: ['esm'], platform: 'node', diff --git a/packages/fs/fs-local/package.json b/packages/fs/fs-local/package.json index dd80cb4d9c..75a0400afb 100644 --- a/packages/fs/fs-local/package.json +++ b/packages/fs/fs-local/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,6 +28,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-fs": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "dependencies": { @@ -30,6 +36,7 @@ }, "devDependencies": { "@deepseek-ai/dsh-fs": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/fs/fs-local/src/invariant.ts b/packages/fs/fs-local/src/invariant.ts new file mode 100644 index 0000000000..0c89f2299b --- /dev/null +++ b/packages/fs/fs-local/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-fs-local`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-fs-local/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-fs-local' + +/** Cordis companion plugin name. */ +export const name = 'fs-local-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/fs/fs-local/tsconfig.json b/packages/fs/fs-local/tsconfig.json index 0808fd29ca..b249913d43 100644 --- a/packages/fs/fs-local/tsconfig.json +++ b/packages/fs/fs-local/tsconfig.json @@ -6,10 +6,23 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../../llm/llm" }, - { "path": "../fs" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../fs" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/fs/fs-policy/package.json b/packages/fs/fs-policy/package.json index e27302e4a6..e74852ef7d 100644 --- a/packages/fs/fs-policy/package.json +++ b/packages/fs/fs-policy/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,10 +28,12 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-fs": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { "@deepseek-ai/dsh-fs": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/fs/fs-policy/src/invariant.ts b/packages/fs/fs-policy/src/invariant.ts new file mode 100644 index 0000000000..11f155d2ec --- /dev/null +++ b/packages/fs/fs-policy/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-fs-policy`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-fs-policy/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-fs-policy' + +/** Cordis companion plugin name. */ +export const name = 'fs-policy-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/fs/fs-policy/tsconfig.json b/packages/fs/fs-policy/tsconfig.json index fcc1307a36..3f22545107 100644 --- a/packages/fs/fs-policy/tsconfig.json +++ b/packages/fs/fs-policy/tsconfig.json @@ -6,9 +6,20 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../llm/llm" }, - { "path": "../fs" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../fs" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/fs/fs/package.json b/packages/fs/fs/package.json index f1efde152a..3f15e0762a 100644 --- a/packages/fs/fs/package.json +++ b/packages/fs/fs/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,11 +28,13 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-brand": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { "@deepseek-ai/dsh-brand": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/fs/fs/src/invariant.ts b/packages/fs/fs/src/invariant.ts new file mode 100644 index 0000000000..55895b4b24 --- /dev/null +++ b/packages/fs/fs/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-fs`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-fs/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-fs' + +/** Cordis companion plugin name. */ +export const name = 'fs-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/fs/fs/tsconfig.json b/packages/fs/fs/tsconfig.json index a352aea65a..5fb6fec5a3 100644 --- a/packages/fs/fs/tsconfig.json +++ b/packages/fs/fs/tsconfig.json @@ -6,9 +6,20 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../util/brand" }, - { "path": "../../llm/llm" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../util/brand" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/fs/tool-fs-search/package.json b/packages/fs/tool-fs-search/package.json index 002d54569a..45de8f5e4a 100644 --- a/packages/fs/tool-fs-search/package.json +++ b/packages/fs/tool-fs-search/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -26,6 +31,7 @@ }, "peerDependencies": { "@deepseek-ai/dsh-bash": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-retention": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", @@ -38,6 +44,7 @@ "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-bash": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-retention": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", diff --git a/packages/fs/tool-fs-search/src/invariant.ts b/packages/fs/tool-fs-search/src/invariant.ts new file mode 100644 index 0000000000..f0055b611a --- /dev/null +++ b/packages/fs/tool-fs-search/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tool-fs-search`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tool-fs-search/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tool-fs-search' + +/** Cordis companion plugin name. */ +export const name = 'tool-fs-search-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/fs/tool-fs-search/tsconfig.json b/packages/fs/tool-fs-search/tsconfig.json index 9241aca15b..ad0c703117 100644 --- a/packages/fs/tool-fs-search/tsconfig.json +++ b/packages/fs/tool-fs-search/tsconfig.json @@ -6,15 +6,38 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../../util/retention" }, - { "path": "../../llm/llm" }, - { "path": "../../core/session" }, - { "path": "../../core/tools" }, - { "path": "../../core/system-prompt" }, - { "path": "../../bash/bash" }, - { "path": "../../spill/spill" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../util/retention" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../core/session" + }, + { + "path": "../../core/tools" + }, + { + "path": "../../core/system-prompt" + }, + { + "path": "../../bash/bash" + }, + { + "path": "../../spill/spill" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/fs/tool-fs/package.json b/packages/fs/tool-fs/package.json index f9ef3136bf..51586836c9 100644 --- a/packages/fs/tool-fs/package.json +++ b/packages/fs/tool-fs/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -27,6 +32,7 @@ }, "peerDependencies": { "@deepseek-ai/dsh-fs": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-system-prompt": "^0.0.1", @@ -37,9 +43,10 @@ "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", - "@deepseek-ai/dsh-fs-policy": "workspace:^", "@deepseek-ai/dsh-fs": "workspace:^", "@deepseek-ai/dsh-fs-local": "workspace:^", + "@deepseek-ai/dsh-fs-policy": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-llm-deepseek": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", diff --git a/packages/fs/tool-fs/src/invariant.ts b/packages/fs/tool-fs/src/invariant.ts new file mode 100644 index 0000000000..7a683d978f --- /dev/null +++ b/packages/fs/tool-fs/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tool-fs`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tool-fs/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tool-fs' + +/** Cordis companion plugin name. */ +export const name = 'tool-fs-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/fs/tool-fs/tsconfig.json b/packages/fs/tool-fs/tsconfig.json index f0133b1d2b..43c91b2dd6 100644 --- a/packages/fs/tool-fs/tsconfig.json +++ b/packages/fs/tool-fs/tsconfig.json @@ -6,13 +6,32 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../../llm/llm" }, - { "path": "../../core/tools" }, - { "path": "../../core/system-prompt" }, - { "path": "../fs" }, - { "path": "../fs-policy" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../core/tools" + }, + { + "path": "../../core/system-prompt" + }, + { + "path": "../fs" + }, + { + "path": "../fs-policy" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/guard/repeat-tool-guard/package.json b/packages/guard/repeat-tool-guard/package.json index 92d49c548f..c892ca99e4 100644 --- a/packages/guard/repeat-tool-guard/package.json +++ b/packages/guard/repeat-tool-guard/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -26,6 +31,7 @@ }, "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", "cordis": "^4.0.0-rc.7" }, @@ -33,6 +39,7 @@ "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", diff --git a/packages/guard/repeat-tool-guard/src/invariant.ts b/packages/guard/repeat-tool-guard/src/invariant.ts new file mode 100644 index 0000000000..05c4df1a66 --- /dev/null +++ b/packages/guard/repeat-tool-guard/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-repeat-tool-guard`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-repeat-tool-guard/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-repeat-tool-guard' + +/** Cordis companion plugin name. */ +export const name = 'repeat-tool-guard-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/guard/repeat-tool-guard/tsconfig.json b/packages/guard/repeat-tool-guard/tsconfig.json index 66439bcd5f..9ca11b7119 100644 --- a/packages/guard/repeat-tool-guard/tsconfig.json +++ b/packages/guard/repeat-tool-guard/tsconfig.json @@ -25,6 +25,9 @@ }, { "path": "../../llm/llm" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/hooks/hook-protocol/package.json b/packages/hooks/hook-protocol/package.json index 201c744219..f357278db3 100644 --- a/packages/hooks/hook-protocol/package.json +++ b/packages/hooks/hook-protocol/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,11 +28,13 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-bash": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { "@deepseek-ai/dsh-bash": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/hooks/hook-protocol/src/invariant.ts b/packages/hooks/hook-protocol/src/invariant.ts new file mode 100644 index 0000000000..9893493644 --- /dev/null +++ b/packages/hooks/hook-protocol/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-hook-protocol`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-hook-protocol/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-hook-protocol' + +/** Cordis companion plugin name. */ +export const name = 'hook-protocol-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/hooks/hook-protocol/tsconfig.json b/packages/hooks/hook-protocol/tsconfig.json index dc4f8d9e16..220748cb0f 100644 --- a/packages/hooks/hook-protocol/tsconfig.json +++ b/packages/hooks/hook-protocol/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../../core/session" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/hooks/hooks-claude/package.json b/packages/hooks/hooks-claude/package.json index c6870a6471..34696ab625 100644 --- a/packages/hooks/hooks-claude/package.json +++ b/packages/hooks/hooks-claude/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -27,6 +32,7 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-hook-protocol": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-session-persistence": "^0.0.1", @@ -41,6 +47,7 @@ "@deepseek-ai/dsh-bash": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", "@deepseek-ai/dsh-hook-protocol": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-session-persistence": "workspace:^", diff --git a/packages/hooks/hooks-claude/src/invariant.ts b/packages/hooks/hooks-claude/src/invariant.ts new file mode 100644 index 0000000000..5c6002f7f5 --- /dev/null +++ b/packages/hooks/hooks-claude/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-hooks-claude`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-hooks-claude/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-hooks-claude' + +/** Cordis companion plugin name. */ +export const name = 'hooks-claude-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/hooks/hooks-claude/tests/bridge.spec.ts b/packages/hooks/hooks-claude/tests/bridge.spec.ts index 65dd29d146..953b4befd0 100644 --- a/packages/hooks/hooks-claude/tests/bridge.spec.ts +++ b/packages/hooks/hooks-claude/tests/bridge.spec.ts @@ -10,7 +10,8 @@ import type { Agent } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' -import { SubagentRunId } from '@deepseek-ai/dsh-subagent' +import { scopeTarget } from '@deepseek-ai/dsh-scope' +import SubagentService, { SubagentRunId } from '@deepseek-ai/dsh-subagent' import * as HooksClaude from '@deepseek-ai/dsh-hooks-claude' import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -25,6 +26,10 @@ import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent const dirs: string[] = [] afterEach(() => { for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true }) }) +function subagentCarrier(ctx: Context) { + return scopeTarget(ctx as unknown as SubagentService, undefined) +} + /** Write a hooks.json + named executable scripts into a fresh temp dir. */ function writeConfig(hooks: unknown, scripts: Record = {}): string { const dir = mkdtempSync(join(tmpdir(), 'dsh-hooks-claude-')) @@ -290,8 +295,8 @@ describe('hooks-claude bridge — SubagentStart / SubagentStop (observe)', () => // Drive the observe-only lifecycle events directly (no real child needed — the // bridge just listens). No child agent is registered, so SubagentStart's // child lookup yields undefined and it simply runs the hook. - ctx.emit('subagent/start', { runId: SubagentRunId('run-1'), provider: 'inproc', id: SessionId('child-1'), local: false }) - ctx.emit('subagent/end', { runId: SubagentRunId('run-1'), provider: 'inproc', id: SessionId('child-1'), local: false, stopReason: 'completed', lastAssistantMessage: [{ type: 'text', text: 'done' }] }) + ctx.emit(subagentCarrier(ctx), 'subagent/start', { runId: SubagentRunId('run-1'), provider: 'inproc', id: SessionId('child-1'), local: false }) + ctx.emit(subagentCarrier(ctx), 'subagent/end', { runId: SubagentRunId('run-1'), provider: 'inproc', id: SessionId('child-1'), local: false, stopReason: 'completed', lastAssistantMessage: [{ type: 'text', text: 'done' }] }) // Both hooks run async (detached .then); poll for their marker files rather // than a fixed sleep that flakes under load. @@ -326,7 +331,7 @@ describe('hooks-claude bridge — SubagentStart / SubagentStop (observe)', () => const { ctx, hooks } = await harnessWithFiber(dir, new MockAdapter([])) const warn = vi.fn() ctx.logger.warn = warn as never - ctx.emit('subagent/start', { runId: SubagentRunId('run-1'), provider: 'inproc', id: SessionId('child-1'), local: false }) + ctx.emit(subagentCarrier(ctx), 'subagent/start', { runId: SubagentRunId('run-1'), provider: 'inproc', id: SessionId('child-1'), local: false }) await waitFor(() => existsSync(marker)) const pid = Number(readFileSync(pidFile, 'utf8').trim()) await hooks.dispose() diff --git a/packages/hooks/hooks-claude/tests/coverage-cases.ts b/packages/hooks/hooks-claude/tests/coverage-cases.ts index ec7d18b4c4..4b8036f3a5 100644 --- a/packages/hooks/hooks-claude/tests/coverage-cases.ts +++ b/packages/hooks/hooks-claude/tests/coverage-cases.ts @@ -10,7 +10,8 @@ import type { Agent } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' -import { SubagentRunId } from '@deepseek-ai/dsh-subagent' +import { scopeTarget } from '@deepseek-ai/dsh-scope' +import SubagentService, { SubagentRunId } from '@deepseek-ai/dsh-subagent' import * as HooksClaude from '@deepseek-ai/dsh-hooks-claude' import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -20,6 +21,10 @@ import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent const dirs: string[] = [] afterEach(() => { for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true }) }) +function subagentCarrier(ctx: Context) { + return scopeTarget(ctx as unknown as SubagentService, undefined) +} + function dir(): string { const d = mkdtempSync(join(tmpdir(), 'dsh-hc-cov-')); dirs.push(d); return d } function sh(d: string, name: string, body: string): string { const p = join(d, name); writeFileSync(p, body); chmodSync(p, 0o755); return p @@ -233,7 +238,7 @@ export function defineCoverageCases(group: CoverageGroup): void { const injected: string[] = [] const child = { id: SessionId('child-x'), inject: (content: { type: string; text?: string }[]) => { injected.push(content.map(b => b.text ?? '').join('')) }, session: { id: SessionId('child-x'), header: { id: 'child-x' } } } as unknown as Parameters[0] ctx.agents.register(child) - ctx.emit('subagent/start', { runId: SubagentRunId('run-x'), provider: 'p', id: SessionId('child-x'), local: true }) + ctx.emit(subagentCarrier(ctx), 'subagent/start', { runId: SubagentRunId('run-x'), provider: 'p', id: SessionId('child-x'), local: true }) await waitFor(() => injected.includes('child guidance')) expect(injected).toContain('child guidance') }) @@ -249,7 +254,7 @@ export function defineCoverageCases(group: CoverageGroup): void { const warn = vi.fn(); ctx.logger.warn = warn as never const child = { id: SessionId('child-y'), inject: () => { throw new Error('inject boom') }, session: { id: SessionId('child-y'), header: { id: 'child-y' } } } as unknown as Parameters[0] ctx.agents.register(child) - ctx.emit('subagent/start', { runId: SubagentRunId('run-y'), provider: 'p', id: SessionId('child-y'), local: true }) + ctx.emit(subagentCarrier(ctx), 'subagent/start', { runId: SubagentRunId('run-y'), provider: 'p', id: SessionId('child-y'), local: true }) await waitFor(() => warn.mock.calls.some(c => String(c[0]).includes('SubagentStart hook failed'))) expect(warn).toHaveBeenCalledWith(expect.stringContaining('SubagentStart hook failed')) }) @@ -293,7 +298,7 @@ export function defineCoverageCases(group: CoverageGroup): void { const s = sh(d, 'stop.sh', `#!/usr/bin/env bash\ntouch "${marker}"\n`) const path = hooks(d, { SubagentStop: [{ hooks: [{ type: 'command', command: s }] }] }) const ctx = await harness(path, new MockAdapter([])) - ctx.emit('subagent/end', { runId: SubagentRunId('run-z'), provider: 'p', id: SessionId('child-z'), local: false, stopReason: 'completed' }) + ctx.emit(subagentCarrier(ctx), 'subagent/end', { runId: SubagentRunId('run-z'), provider: 'p', id: SessionId('child-z'), local: false, stopReason: 'completed' }) await waitFor(() => existsSync(marker)) expect(existsSync(marker)).toBe(true) }) @@ -693,7 +698,7 @@ export function defineCoverageCases(group: CoverageGroup): void { // Register a live child on its own session cwd; emit subagent/end with its id. const { SessionId } = await import('@deepseek-ai/dsh-session') const childHandle = await ctx.agents.create({ sessionId: SessionId('child-stop-session'), meta: { cwd: childDir }, agentOptions: { provider: 'mock', model: 'mock' } }) - ctx.emit('subagent/end', { runId: SubagentRunId('run-stop'), provider: 'inproc', id: childHandle.agent.id, local: true, stopReason: 'completed' }) + ctx.emit(subagentCarrier(ctx), 'subagent/end', { runId: SubagentRunId('run-stop'), provider: 'inproc', id: childHandle.agent.id, local: true, stopReason: 'completed' }) await waitFor(() => existsSync(marker)) expect(existsSync(marker)).toBe(true) // the marker landed in the CHILD dir diff --git a/packages/hooks/hooks-claude/tsconfig.json b/packages/hooks/hooks-claude/tsconfig.json index 07c88610f9..445d0f68b1 100644 --- a/packages/hooks/hooks-claude/tsconfig.json +++ b/packages/hooks/hooks-claude/tsconfig.json @@ -40,6 +40,9 @@ }, { "path": "../../bash/bash" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/hooks/hooks-codex/package.json b/packages/hooks/hooks-codex/package.json index 8c8686c539..5d583baafe 100644 --- a/packages/hooks/hooks-codex/package.json +++ b/packages/hooks/hooks-codex/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -27,6 +32,7 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-hook-protocol": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-session-persistence": "^0.0.1", @@ -40,6 +46,7 @@ "@deepseek-ai/dsh-bash": "workspace:^", "@deepseek-ai/dsh-bash-local": "workspace:^", "@deepseek-ai/dsh-hook-protocol": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-session-persistence": "workspace:^", diff --git a/packages/hooks/hooks-codex/src/invariant.ts b/packages/hooks/hooks-codex/src/invariant.ts new file mode 100644 index 0000000000..1b8f03a057 --- /dev/null +++ b/packages/hooks/hooks-codex/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-hooks-codex`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-hooks-codex/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-hooks-codex' + +/** Cordis companion plugin name. */ +export const name = 'hooks-codex-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/hooks/hooks-codex/tsconfig.json b/packages/hooks/hooks-codex/tsconfig.json index ae3c91e9dd..3bd9bd91e5 100644 --- a/packages/hooks/hooks-codex/tsconfig.json +++ b/packages/hooks/hooks-codex/tsconfig.json @@ -37,6 +37,9 @@ }, { "path": "../../bash/bash" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/llm/llm-deepseek/package.json b/packages/llm/llm-deepseek/package.json index 1461ad0f44..af8ca45085 100644 --- a/packages/llm/llm-deepseek/package.json +++ b/packages/llm/llm-deepseek/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "cordis": "^4.0.0-rc.7" }, @@ -29,6 +35,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/llm/llm-deepseek/src/invariant.ts b/packages/llm/llm-deepseek/src/invariant.ts new file mode 100644 index 0000000000..c3e6b5e153 --- /dev/null +++ b/packages/llm/llm-deepseek/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-llm-deepseek`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-llm-deepseek/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-llm-deepseek' + +/** Cordis companion plugin name. */ +export const name = 'llm-deepseek-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/llm/llm-deepseek/tsconfig.json b/packages/llm/llm-deepseek/tsconfig.json index e9de391ba1..d145ddb6ee 100644 --- a/packages/llm/llm-deepseek/tsconfig.json +++ b/packages/llm/llm-deepseek/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../../llm/llm" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/llm/llm-pi-ai/package.json b/packages/llm/llm-pi-ai/package.json index c922467deb..6901441490 100644 --- a/packages/llm/llm-pi-ai/package.json +++ b/packages/llm/llm-pi-ai/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "cordis": "^4.0.0-rc.7" }, @@ -30,6 +36,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-llm-deepseek": "workspace:^", "cordis": "^4.0.0-rc.7" diff --git a/packages/llm/llm-pi-ai/src/invariant.ts b/packages/llm/llm-pi-ai/src/invariant.ts new file mode 100644 index 0000000000..4ada7a1a51 --- /dev/null +++ b/packages/llm/llm-pi-ai/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-llm-pi-ai`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-llm-pi-ai/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-llm-pi-ai' + +/** Cordis companion plugin name. */ +export const name = 'llm-pi-ai-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/llm/llm-pi-ai/tsconfig.json b/packages/llm/llm-pi-ai/tsconfig.json index e9de391ba1..d145ddb6ee 100644 --- a/packages/llm/llm-pi-ai/tsconfig.json +++ b/packages/llm/llm-pi-ai/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../../llm/llm" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/llm/llm/package.json b/packages/llm/llm/package.json index ab11c8574f..9ef3e3323e 100644 --- a/packages/llm/llm/package.json +++ b/packages/llm/llm/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,10 +28,12 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-brand": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { "@deepseek-ai/dsh-brand": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/llm/llm/src/invariant.ts b/packages/llm/llm/src/invariant.ts new file mode 100644 index 0000000000..a8a56ce245 --- /dev/null +++ b/packages/llm/llm/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-llm`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-llm/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-llm' + +/** Cordis companion plugin name. */ +export const name = 'llm-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/llm/llm/tsconfig.json b/packages/llm/llm/tsconfig.json index 342f636170..5bc7a9fcf5 100644 --- a/packages/llm/llm/tsconfig.json +++ b/packages/llm/llm/tsconfig.json @@ -16,6 +16,9 @@ }, { "path": "../../util/brand" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/llm/token-meter/package.json b/packages/llm/token-meter/package.json index 13fa4e3cdc..dadd5e8f8d 100644 --- a/packages/llm/token-meter/package.json +++ b/packages/llm/token-meter/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "cordis": "^4.0.0-rc.7" @@ -30,6 +36,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "cordis": "^4.0.0-rc.7" diff --git a/packages/llm/token-meter/src/invariant.ts b/packages/llm/token-meter/src/invariant.ts new file mode 100644 index 0000000000..00ceb567be --- /dev/null +++ b/packages/llm/token-meter/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-token-meter`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-token-meter/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-token-meter' + +/** Cordis companion plugin name. */ +export const name = 'token-meter-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/llm/token-meter/tests/token-meter.spec.ts b/packages/llm/token-meter/tests/token-meter.spec.ts index e7ddca6696..d49c96fdb0 100644 --- a/packages/llm/token-meter/tests/token-meter.spec.ts +++ b/packages/llm/token-meter/tests/token-meter.spec.ts @@ -631,19 +631,24 @@ describe('malformed replay and listener lifecycle', () => { }) const firstFiber = await ctx.plugin(TokenMeterService) activeMeter = ctx.tokenMeter - const session = ctx.sessions.create(SessionId('listener-order')) + const session = ctx.sessions.create(SessionId('listener-order'), { seed: [{ + type: 'turn/start', + seq: 0, + time: 1, + data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }, + }] }) activeMeter.measure(session) session.append('user/message', { content: [{ type: 'text', text: 'one' }], source: { kind: 'user' }, }, { surfaceOp: 'append' }) - expect(revisions).toEqual([1]) - expect(activeMeter.measure(session).logRevision).toBe(1) + expect(revisions).toEqual([2]) + expect(activeMeter.measure(session).logRevision).toBe(2) await firstFiber.dispose() const secondFiber = await ctx.plugin(TokenMeterService) activeMeter = ctx.tokenMeter - expect(activeMeter.measure(session).logRevision).toBe(1) + expect(activeMeter.measure(session).logRevision).toBe(2) await secondFiber.dispose() }) }) diff --git a/packages/llm/token-meter/tsconfig.json b/packages/llm/token-meter/tsconfig.json index 5e1604e02f..481fad6e15 100644 --- a/packages/llm/token-meter/tsconfig.json +++ b/packages/llm/token-meter/tsconfig.json @@ -22,6 +22,9 @@ }, { "path": "../../core/session" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/mcp/mcp-client/package.json b/packages/mcp/mcp-client/package.json index 6b8f145108..cb3b51e1aa 100644 --- a/packages/mcp/mcp-client/package.json +++ b/packages/mcp/mcp-client/package.json @@ -11,19 +11,25 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { - "@deepseek-ai/dsh-tools": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", + "@deepseek-ai/dsh-tools": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "dependencies": { @@ -31,8 +37,9 @@ "schemastery": "^3.18.0" }, "devDependencies": { - "@deepseek-ai/dsh-tools": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", + "@deepseek-ai/dsh-tools": "workspace:^", "@modelcontextprotocol/server-everything": "^2026.7.4", "@modelcontextprotocol/server-filesystem": "^2026.7.4", "cordis": "^4.0.0-rc.7", diff --git a/packages/mcp/mcp-client/src/invariant.ts b/packages/mcp/mcp-client/src/invariant.ts new file mode 100644 index 0000000000..d9e75e9955 --- /dev/null +++ b/packages/mcp/mcp-client/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-mcp-client`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-mcp-client/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-mcp-client' + +/** Cordis companion plugin name. */ +export const name = 'mcp-client-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/mcp/mcp-client/tsconfig.json b/packages/mcp/mcp-client/tsconfig.json index e9c9266415..668ee2c3cb 100644 --- a/packages/mcp/mcp-client/tsconfig.json +++ b/packages/mcp/mcp-client/tsconfig.json @@ -6,10 +6,23 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../../llm/llm" }, - { "path": "../../core/tools" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../core/tools" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/sandbox/sandbox-local/package.json b/packages/sandbox/sandbox-local/package.json index 9dd90a3e9a..6750b92577 100644 --- a/packages/sandbox/sandbox-local/package.json +++ b/packages/sandbox/sandbox-local/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-sandbox": "^0.0.1", "cordis": "^4.0.0-rc.7" @@ -31,6 +37,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-sandbox": "workspace:^", "cordis": "^4.0.0-rc.7" diff --git a/packages/sandbox/sandbox-local/src/invariant.ts b/packages/sandbox/sandbox-local/src/invariant.ts new file mode 100644 index 0000000000..3582962f94 --- /dev/null +++ b/packages/sandbox/sandbox-local/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-sandbox-local`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-sandbox-local/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-sandbox-local' + +/** Cordis companion plugin name. */ +export const name = 'sandbox-local-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/sandbox/sandbox-local/tsconfig.json b/packages/sandbox/sandbox-local/tsconfig.json index c756b6af69..608f0e9568 100644 --- a/packages/sandbox/sandbox-local/tsconfig.json +++ b/packages/sandbox/sandbox-local/tsconfig.json @@ -22,6 +22,9 @@ }, { "path": "../sandbox" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/sandbox/sandbox/package.json b/packages/sandbox/sandbox/package.json index 50c5b443ba..84266499b3 100644 --- a/packages/sandbox/sandbox/package.json +++ b/packages/sandbox/sandbox/package.json @@ -11,21 +11,28 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/sandbox/sandbox/src/invariant.ts b/packages/sandbox/sandbox/src/invariant.ts new file mode 100644 index 0000000000..b220f11717 --- /dev/null +++ b/packages/sandbox/sandbox/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-sandbox`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-sandbox/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-sandbox' + +/** Cordis companion plugin name. */ +export const name = 'sandbox-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/sandbox/sandbox/tsconfig.json b/packages/sandbox/sandbox/tsconfig.json index 9f687793d7..af4de1c016 100644 --- a/packages/sandbox/sandbox/tsconfig.json +++ b/packages/sandbox/sandbox/tsconfig.json @@ -16,6 +16,9 @@ }, { "path": "../../llm/llm" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/sdk/create-sdk/package.json b/packages/sdk/create-sdk/package.json index e23415f8dc..27102154fc 100644 --- a/packages/sdk/create-sdk/package.json +++ b/packages/sdk/create-sdk/package.json @@ -13,10 +13,15 @@ ".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" + }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" } }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/bin.js", "lib/assets", "lib/types/**/*.d.ts", @@ -29,9 +34,11 @@ "commander": "^15.0.0" }, "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/sdk/create-sdk/src/invariant.ts b/packages/sdk/create-sdk/src/invariant.ts new file mode 100644 index 0000000000..87a697e438 --- /dev/null +++ b/packages/sdk/create-sdk/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/create-sdk`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/create-sdk/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/create-sdk' + +/** Cordis companion plugin name. */ +export const name = 'create-sdk-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/sdk/create-sdk/tsconfig.json b/packages/sdk/create-sdk/tsconfig.json index f77f711880..e2ed951fa4 100644 --- a/packages/sdk/create-sdk/tsconfig.json +++ b/packages/sdk/create-sdk/tsconfig.json @@ -6,7 +6,14 @@ }, "include": ["src"], "references": [ - { "path": "../helper" }, - { "path": "../../../vendor/cordis" } + { + "path": "../helper" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/sdk/create-sdk/tsdown.config.ts b/packages/sdk/create-sdk/tsdown.config.ts index cdf9ef6d4f..08d522590e 100644 --- a/packages/sdk/create-sdk/tsdown.config.ts +++ b/packages/sdk/create-sdk/tsdown.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from 'tsdown' /** Bundle the library and create bin, then mirror package-owned terminal templates. */ export default defineConfig({ - entry: ['lib/types/index.js', 'lib/types/bin.js'], + entry: ['lib/types/index.js', 'lib/types/invariant.js', 'lib/types/bin.js'], outDir: 'lib', format: ['esm'], platform: 'node', diff --git a/packages/sdk/helper/package.json b/packages/sdk/helper/package.json index 8d95dafe66..1d9c6a2be2 100644 --- a/packages/sdk/helper/package.json +++ b/packages/sdk/helper/package.json @@ -10,10 +10,15 @@ ".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" + }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" } }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/assets", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", @@ -29,12 +34,14 @@ }, "peerDependencies": { "@deepseek-ai/dsh-brand": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { "@deepseek-ai/dsh-brand": "workspace:^", "@deepseek-ai/dsh-hooks-claude": "workspace:^", "@deepseek-ai/dsh-hooks-codex": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^", "@deepseek-ai/dsh-session-persistence-sqlite": "workspace:^", "@deepseek-ai/dsh-tool-subagent": "workspace:^", diff --git a/packages/sdk/helper/src/invariant.ts b/packages/sdk/helper/src/invariant.ts new file mode 100644 index 0000000000..63a6fc2055 --- /dev/null +++ b/packages/sdk/helper/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-helper`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-helper/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-helper' + +/** Cordis companion plugin name. */ +export const name = 'helper-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/sdk/helper/tsconfig.json b/packages/sdk/helper/tsconfig.json index e4a8604575..18e79898c7 100644 --- a/packages/sdk/helper/tsconfig.json +++ b/packages/sdk/helper/tsconfig.json @@ -6,14 +6,35 @@ }, "include": ["src"], "references": [ - { "path": "../../util/brand" }, - { "path": "../../compact/compact-basic" }, - { "path": "../../hooks/hooks-claude" }, - { "path": "../../hooks/hooks-codex" }, - { "path": "../../session-persistence/session-persistence-jsonl" }, - { "path": "../../session-persistence/session-persistence-sqlite" }, - { "path": "../../subagent/tool-subagent" }, - { "path": "../../web/tool-web" }, - { "path": "../../../vendor/cordis" } + { + "path": "../../util/brand" + }, + { + "path": "../../compact/compact-basic" + }, + { + "path": "../../hooks/hooks-claude" + }, + { + "path": "../../hooks/hooks-codex" + }, + { + "path": "../../session-persistence/session-persistence-jsonl" + }, + { + "path": "../../session-persistence/session-persistence-sqlite" + }, + { + "path": "../../subagent/tool-subagent" + }, + { + "path": "../../web/tool-web" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/sdk/helper/tsdown.config.ts b/packages/sdk/helper/tsdown.config.ts index cb8b9fafef..b8ba9cb652 100644 --- a/packages/sdk/helper/tsdown.config.ts +++ b/packages/sdk/helper/tsdown.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from 'tsdown' /** Bundle helper runtime and mirror template assets beside the bundle. */ export default defineConfig({ - entry: ['lib/types/index.js'], + entry: ['lib/types/index.js', 'lib/types/invariant.js'], outDir: 'lib', format: ['esm'], platform: 'node', diff --git a/packages/sdk/scripts/package.json b/packages/sdk/scripts/package.json index ba441a528c..3f607f7849 100644 --- a/packages/sdk/scripts/package.json +++ b/packages/sdk/scripts/package.json @@ -14,6 +14,10 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./dev/tsdown-config": { "types": "./lib/types/dev/tsdown-config.d.ts", "default": "./lib/dev/tsdown-config.js" @@ -21,6 +25,7 @@ }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/bin.js", "lib/dev/tsdown-config.js", "lib/local-plugin-loader-hooks.js", @@ -37,16 +42,22 @@ }, "peerDependencies": { "@deepseek-ai/dsh-app-boot": "workspace:^", + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7", "tsdown": "^0.22.2", "tsx": "^4.22.4" }, "peerDependenciesMeta": { - "tsdown": { "optional": true }, - "tsx": { "optional": true } + "tsdown": { + "optional": true + }, + "tsx": { + "optional": true + } }, "devDependencies": { "@deepseek-ai/dsh-app-boot": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.7", "tsdown": "^0.22.2", "tsx": "^4.22.4" diff --git a/packages/sdk/scripts/src/invariant.ts b/packages/sdk/scripts/src/invariant.ts new file mode 100644 index 0000000000..fd0a0ef55b --- /dev/null +++ b/packages/sdk/scripts/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-scripts`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-scripts/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-scripts' + +/** Cordis companion plugin name. */ +export const name = 'scripts-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/sdk/scripts/tsconfig.json b/packages/sdk/scripts/tsconfig.json index 848de9a314..6ada45e1ac 100644 --- a/packages/sdk/scripts/tsconfig.json +++ b/packages/sdk/scripts/tsconfig.json @@ -6,8 +6,17 @@ }, "include": ["src"], "references": [ - { "path": "../helper" }, - { "path": "../../ui/app-boot" }, - { "path": "../../../vendor/cordis" } + { + "path": "../helper" + }, + { + "path": "../../ui/app-boot" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/sdk/scripts/tsdown.config.ts b/packages/sdk/scripts/tsdown.config.ts index 2218faa7be..14bb59b8bc 100644 --- a/packages/sdk/scripts/tsdown.config.ts +++ b/packages/sdk/scripts/tsdown.config.ts @@ -7,6 +7,10 @@ export default defineConfig([ fixedExtension: false, outputOptions: { codeSplitting: false }, dts: false, clean: false, copy: [{ from: 'src/templates/assets/*', to: 'lib/assets' }], }, + { + entry: ['lib/types/invariant.js'], outDir: 'lib', format: ['esm'], platform: 'node', target: 'es2024', + fixedExtension: false, outputOptions: { codeSplitting: false }, dts: false, clean: false, + }, { entry: ['lib/types/bin.js'], outDir: 'lib', format: ['esm'], platform: 'node', target: 'es2024', fixedExtension: false, outputOptions: { codeSplitting: false }, dts: false, clean: false, diff --git a/packages/session-persistence/session-persistence-jsonl/package.json b/packages/session-persistence/session-persistence-jsonl/package.json index ddb9f2af4d..bc5c6d2c4c 100644 --- a/packages/session-persistence/session-persistence-jsonl/package.json +++ b/packages/session-persistence/session-persistence-jsonl/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-session-persistence": "^0.0.1", "cordis": "^4.0.0-rc.7" @@ -30,6 +36,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-session-persistence": "workspace:^", "cordis": "^4.0.0-rc.7" diff --git a/packages/session-persistence/session-persistence-jsonl/src/invariant.ts b/packages/session-persistence/session-persistence-jsonl/src/invariant.ts new file mode 100644 index 0000000000..12c65db1c4 --- /dev/null +++ b/packages/session-persistence/session-persistence-jsonl/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-session-persistence-jsonl`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-session-persistence-jsonl/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-session-persistence-jsonl' + +/** Cordis companion plugin name. */ +export const name = 'session-persistence-jsonl-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/session-persistence/session-persistence-jsonl/tests/jsonl.spec.ts b/packages/session-persistence/session-persistence-jsonl/tests/jsonl.spec.ts index e7dc469132..710a0ec9f1 100644 --- a/packages/session-persistence/session-persistence-jsonl/tests/jsonl.spec.ts +++ b/packages/session-persistence/session-persistence-jsonl/tests/jsonl.spec.ts @@ -21,17 +21,15 @@ function mutableHeader(header: SessionHeader): MutableSessionHeader { return header } -async function expectParallelFlushError(promise: Promise, message: RegExp): Promise { +async function expectFlushError(promise: Promise, message: RegExp): Promise { try { await promise } catch (error) { - expect(error).toBeInstanceOf(AggregateError) - const [cause] = (error as AggregateError).errors as unknown[] - expect(cause).toBeInstanceOf(Error) - expect((cause as Error).message).toMatch(message) + expect(error).toBeInstanceOf(Error) + expect((error as Error).message).toMatch(message) return } - throw new Error('expected parallel flush to reject') + throw new Error('expected flush to reject') } async function freshRoot(): Promise { @@ -250,7 +248,7 @@ describe('SessionPersistenceJsonl: durability and crash semantics', () => { appendClosedTurn(source) const child = ctx.sessions.fork(source, undefined, SessionId('persist-child')) - await ctx.parallel('session/flush', child) + await ctx.sessions.flush(child) const loaded = await ctx.sessionPersistence.load(child.id) expect(loaded.events).toEqual(source.events) @@ -429,12 +427,14 @@ describe('SessionPersistenceJsonl: write path (session/event → flush)', () => const a = ctx.sessions.create(SessionId('sa')) const b = ctx.sessions.create(SessionId('sb')) + a.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) + b.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) a.append('user/message', { content: [{ type: 'text', text: 'A' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) b.append('user/message', { content: [{ type: 'text', text: 'B' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) a.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) b.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.parallel('session/flush', a) - await ctx.parallel('session/flush', b) + await ctx.sessions.flush(a) + await ctx.sessions.flush(b) const la = await ctx.sessionPersistence.load(SessionId('sa')) const lb = await ctx.sessionPersistence.load(SessionId('sb')) @@ -607,7 +607,7 @@ describe('SessionPersistenceJsonl: edge cases', () => { }, { inject: ['sessions'] })) // Drain A, then dispose ITS fiber (the live session A is gone) while the // backend stays loaded. - for (const s of ctx.sessions.list()) await ctx.parallel('session/flush', s) + for (const s of ctx.sessions.list()) await ctx.sessions.flush(s) await sessFiberA.dispose() // A new Session object reuses the id. Object-keyed initialization must run independently, @@ -675,7 +675,7 @@ describe('SessionPersistenceJsonl: edge cases', () => { a.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) a.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) }, { inject: ['sessions'] })) - for (const s of ctx.sessions.list()) await ctx.parallel('session/flush', s) + for (const s of ctx.sessions.list()) await ctx.sessions.flush(s) await firstFiber.dispose() let second!: Session @@ -783,18 +783,19 @@ describe('SessionPersistenceJsonl: edge cases', () => { await ctx2.plugin(SessionPersistenceJsonl, { root }) const session = ctx2.sessions.create(SessionId('flush-fail')) // A full turn lands in the write-behind buffer. + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('user/message', { content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) // Make the durable materialize fail on the next flush. const backend = ctx2.sessionPersistence as unknown as { materialize: (...args: unknown[]) => Promise } const origMat = backend.materialize.bind(backend) backend.materialize = () => Promise.reject(new Error('disk full')) - await expectParallelFlushError(ctx2.parallel('session/flush', session), /disk full/) + await expectFlushError(ctx2.sessions.flush(session), /disk full/) // The events are STILL buffered (not silently dropped): a retry persists them. backend.materialize = origMat - await ctx2.parallel('session/flush', session) + await ctx2.sessions.flush(session) const loaded = await ctx2.sessionPersistence.load(SessionId('flush-fail')) - expect(loaded.events.map(e => e.seq)).toEqual([0, 1]) + expect(loaded.events.map(e => e.seq)).toEqual([0, 1, 2]) await ctx2.fiber.dispose() }) diff --git a/packages/session-persistence/session-persistence-jsonl/tsconfig.json b/packages/session-persistence/session-persistence-jsonl/tsconfig.json index 23970f5a57..044156938b 100644 --- a/packages/session-persistence/session-persistence-jsonl/tsconfig.json +++ b/packages/session-persistence/session-persistence-jsonl/tsconfig.json @@ -22,6 +22,9 @@ }, { "path": "../../session-persistence/session-persistence" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/session-persistence/session-persistence-sqlite/package.json b/packages/session-persistence/session-persistence-sqlite/package.json index f367b737b3..f65bdebd16 100644 --- a/packages/session-persistence/session-persistence-sqlite/package.json +++ b/packages/session-persistence/session-persistence-sqlite/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-session-persistence": "^0.0.1", "cordis": "^4.0.0-rc.7" @@ -30,6 +36,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-session-persistence": "workspace:^", "cordis": "^4.0.0-rc.7" diff --git a/packages/session-persistence/session-persistence-sqlite/src/invariant.ts b/packages/session-persistence/session-persistence-sqlite/src/invariant.ts new file mode 100644 index 0000000000..a9b04cf5f5 --- /dev/null +++ b/packages/session-persistence/session-persistence-sqlite/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-session-persistence-sqlite`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-session-persistence-sqlite/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-session-persistence-sqlite' + +/** Cordis companion plugin name. */ +export const name = 'session-persistence-sqlite-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts b/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts index f26edfa54d..ee599987bb 100644 --- a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts +++ b/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts @@ -14,17 +14,15 @@ import { runCoordinatorContract, type CoordinatorFixture } from '../../session-p const dirs: string[] = [] afterEach(async () => { for (const d of dirs.splice(0)) await rm(d, { recursive: true, force: true }) }) -async function expectParallelFlushError(promise: Promise, message: RegExp): Promise { +async function expectFlushError(promise: Promise, message: RegExp): Promise { try { await promise } catch (error) { - expect(error).toBeInstanceOf(AggregateError) - const [cause] = (error as AggregateError).errors as unknown[] - expect(cause).toBeInstanceOf(Error) - expect((cause as Error).message).toMatch(message) + expect(error).toBeInstanceOf(Error) + expect((error as Error).message).toMatch(message) return } - throw new Error('expected parallel flush to reject') + throw new Error('expected flush to reject') } async function freshDbPath(): Promise { @@ -502,7 +500,7 @@ describe('SessionPersistenceSqlite: edge cases', () => { const b1 = await backend(path) const s1 = b1.ctx.sessions.create(SessionId('hmr-collide')) appendLog(s1, oneTurnLog()) - await b1.ctx.parallel('session/flush', s1) + await b1.ctx.sessions.flush(s1) await b1.dispose() // A fresh context with an UNRELATED live session reusing the id meets a @@ -513,9 +511,9 @@ describe('SessionPersistenceSqlite: edge cases', () => { await ctx.plugin(Object.assign((inner: Context) => { session = inner.sessions.create(SessionId('hmr-collide')) }, { inject: ['sessions'] })) - session.append('turn/start', { turn: 9, trigger: { kind: 'message', source: { kind: 'user' } } }) + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) await ctx.plugin(SessionPersistenceSqlite, { path }) - await expectParallelFlushError(ctx.parallel('session/flush', session), /id collision/) + await expectFlushError(ctx.sessions.flush(session), /id collision/) await ctx.fiber.dispose() }) }) @@ -567,18 +565,20 @@ describe('surface field round-trip', () => { const fiber = await ctx.plugin(SessionPersistenceSqlite, { path: ':memory:' }) const session = ctx.sessions.create(SessionId('roundtrip-surface')) session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) + session.append('step/start', { turn: 1, step: 1 }) session.append('user/message', { content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) - session.append('assistant/message', { provenance: { provider: 'mock', model: 'mock' }, turn: 1, step: 1, content: [] }, { surfaceOp: 'append', sourceEventSeqs: [0] }) + session.append('assistant/message', { provenance: { provider: 'mock', model: 'mock' }, turn: 1, step: 1, content: [] }, { surfaceOp: 'append', sourceEventSeqs: [2] }) + session.append('step/end', { turn: 1, step: 1 }) session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('roundtrip-surface')) - expect(loaded.events).toHaveLength(4) - const um = loaded.events[1]! + expect(loaded.events).toHaveLength(6) + const um = loaded.events[2]! expect((um as SurfaceEvent).surfaceOp).toBe('append') expect((um as SurfaceEvent).sourceEventSeqs).toBeUndefined() - const am = loaded.events[2]! + const am = loaded.events[3]! expect((am as SurfaceEvent).surfaceOp).toBe('append') - expect((am as SurfaceEvent).sourceEventSeqs).toEqual([0]) + expect((am as SurfaceEvent).sourceEventSeqs).toEqual([2]) await fiber.dispose() }) @@ -590,7 +590,7 @@ describe('surface field round-trip', () => { session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('steering/message', { turn: 1, content: [], source: { kind: 'user' } }, { surfaceOp: 'append' }) session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('surface-noseq')) expect((loaded.events[1]! as SurfaceEvent).surfaceOp).toBe('append') expect((loaded.events[1]! as SurfaceEvent).sourceEventSeqs).toBeUndefined() diff --git a/packages/session-persistence/session-persistence-sqlite/tsconfig.json b/packages/session-persistence/session-persistence-sqlite/tsconfig.json index 23970f5a57..044156938b 100644 --- a/packages/session-persistence/session-persistence-sqlite/tsconfig.json +++ b/packages/session-persistence/session-persistence-sqlite/tsconfig.json @@ -22,6 +22,9 @@ }, { "path": "../../session-persistence/session-persistence" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/session-persistence/session-persistence/package.json b/packages/session-persistence/session-persistence/package.json index 91eef09007..a503b146ae 100644 --- a/packages/session-persistence/session-persistence/package.json +++ b/packages/session-persistence/session-persistence/package.json @@ -11,21 +11,29 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", + "@deepseek-ai/dsh-scope": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/session-persistence/session-persistence/src/invariant.ts b/packages/session-persistence/session-persistence/src/invariant.ts new file mode 100644 index 0000000000..cc7cc8fa2c --- /dev/null +++ b/packages/session-persistence/session-persistence/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-session-persistence`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-session-persistence/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-session-persistence' + +/** Cordis companion plugin name. */ +export const name = 'session-persistence-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/session-persistence/session-persistence/tests/coordinator-contract.ts b/packages/session-persistence/session-persistence/tests/coordinator-contract.ts index defcaefb6d..47867c2f56 100644 --- a/packages/session-persistence/session-persistence/tests/coordinator-contract.ts +++ b/packages/session-persistence/session-persistence/tests/coordinator-contract.ts @@ -11,6 +11,7 @@ import { describe, expect, it, vi } from 'vitest' import { Context, type Fiber } from 'cordis' +import { scopeTarget } from '@deepseek-ai/dsh-scope' import SessionStore, { SESSION_FORMAT_VERSION, SessionId } from '@deepseek-ai/dsh-session' import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' import { meta, oneTurnLog, appendLog } from './contract.ts' @@ -76,7 +77,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< try { const session = ctx.sessions.create(SessionId('live'), { meta: { cwd: WORK } }) send(session, oneTurnLog()) - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('live')) expect(loaded.events).toHaveLength(6) @@ -96,7 +97,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< try { const session = ctx.sessions.create(SessionId('forked-child'), { meta: { cwd: WORK, seedLength: 3 } }) send(session, oneTurnLog()) - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('forked-child')) expect(loaded.meta.seedLength).toBe(3) @@ -111,17 +112,17 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< const { ctx, fiber } = await freshCtx(fix) try { const session = ctx.sessions.create(SessionId('mutate'), { meta: { cwd: WORK } }) + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) const ev = session.append('user/message', { content: [{ type: 'text', text: 'original' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) expect(() => { ;(ev.data as { content: { type: 'text'; text: string }[] }).content[0]!.text = 'HACKED' }).toThrow(TypeError) - session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('mutate')) - const first = loaded.events[0] - expect(first?.type === 'user/message' && (first.data.content[0] as { text: string }).text).toBe('original') + const message = loaded.events.find(event => event.type === 'user/message') + expect(message?.type === 'user/message' && (message.data.content[0] as { text: string }).text).toBe('original') } finally { await fiber.dispose() await fix.cleanup() @@ -166,7 +167,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< const loaded = await ctx.sessionPersistence.load(SessionId('forked')) expect(loaded.events).toEqual(seed) // A flush with no NEW events must not double-write. - await ctx.parallel('session/flush', forked) + await ctx.sessions.flush(forked) const reloaded = await ctx.sessionPersistence.load(SessionId('forked')) expect(reloaded.events).toEqual(seed) } finally { @@ -182,7 +183,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< try { const s1 = first.ctx.sessions.create(SessionId('resumed'), { meta: { cwd: WORK } }) send(s1, oneTurnLog()) - await first.ctx.parallel('session/flush', s1) + await first.ctx.sessions.flush(s1) } finally { await first.fiber.dispose() } @@ -194,7 +195,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< await second.ctx.sessions.flush(s2) // let onCreated adopt s2.append('turn/start', { turn: 2, trigger: { kind: 'message', source: { kind: 'user' } } }) s2.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) - await second.ctx.parallel('session/flush', s2) + await second.ctx.sessions.flush(s2) const reloaded = await second.ctx.sessionPersistence.load(SessionId('resumed')) expect(reloaded.events.map(e => e.seq)).toEqual([0, 1, 2, 3, 4, 5, 6, 7]) @@ -212,13 +213,14 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< await ctx.plugin(SessionStore) // A session exists BEFORE the persistence plugin is applied. const session = ctx.sessions.create(SessionId('pre-existing'), { meta: { cwd: WORK } }) + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('user/message', { content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) const fiber = await fix.mount(ctx) try { // The plugin seeded it on apply; a subsequent flush persists its events. - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('pre-existing')) expect(loaded.events.length).toBeGreaterThanOrEqual(2) } finally { @@ -233,6 +235,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< await ctx.plugin(SessionStore) const fiber = await fix.mount(ctx) const session = await liveSessionInFiber(ctx, 'drain', WORK) + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('user/message', { content: [{ type: 'text', text: 'buffered' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) // No explicit flush — dispose must drain. @@ -261,7 +264,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('user/message', { content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) // Hot-reload: dispose instance 1, mount instance 2 over the same storage while the // session stays live. The new instance has no coordinator state but must adopt the @@ -271,7 +274,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< session.append('turn/start', { turn: 2, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('user/message', { content: [{ type: 'text', text: 'again' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) session.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) - await expect(ctx.parallel('session/flush', session)).resolves.not.toThrow() + await expect(ctx.sessions.flush(session)).resolves.not.toThrow() const loaded = await ctx.sessionPersistence.load(SessionId('hmr-adopt')) expect(loaded.events.filter(e => e.type === 'turn/start')).toHaveLength(2) @@ -291,7 +294,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< const backend1 = await fix.mount(ctx) session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) // Append turn 2 to the LIVE session, then dispose instance 1 WITHOUT // flushing turn 2: it is now ONLY in the live session's events; the new @@ -303,7 +306,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< // Instance 2 adopts the stored prefix (turn 1) and MUST also persist the // live suffix (turn 2) carried in the session's events. await fix.mount(ctx) - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('hmr-suffix')) expect(loaded.events.map(e => e.seq)).toEqual([0, 1, 2, 3]) expect(loaded.events.filter(e => e.type === 'turn/start')).toHaveLength(2) @@ -322,7 +325,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< const first = await fix.mount(ctx) session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('step/start', { turn: 1, step: 1 }) - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) // Crash-tail a torn fragment past the (open) committed turn, then reload. await first.dispose() @@ -332,7 +335,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< // end. Adoption must truncate the torn tail but NOT synthesize closers. session.append('step/end', { turn: 1, step: 1 }) session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('hmr-open')) expect(loaded.events.map(e => e.type)).toEqual(['turn/start', 'step/start', 'step/end', 'turn/end']) @@ -352,7 +355,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< try { const s1 = first.ctx.sessions.create(SessionId('collide'), { meta: { cwd: WORK } }) send(s1, oneTurnLog()) - await first.ctx.parallel('session/flush', s1) + await first.ctx.sessions.flush(s1) } finally { await first.fiber.dispose() } @@ -392,7 +395,7 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< await expect(ctx.sessions.flush(reuse)).resolves.toBeUndefined() reuse.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) reuse.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.parallel('session/flush', reuse) + await ctx.sessions.flush(reuse) const loaded = await ctx.sessionPersistence.load(SessionId('abandoned')) expect(loaded.events.map(e => e.seq)).toEqual([0, 1]) } finally { @@ -438,14 +441,15 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< const { ctx, fiber } = await freshCtx(fix) try { const session = ctx.sessions.create(SessionId('idem'), { meta: { cwd: WORK } }) + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('user/message', { content: [{ type: 'text', text: 'x' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) // Re-emit session/created for the SAME live session (idempotent initFor). - ctx.emit('session/created', session) - await ctx.parallel('session/flush', session) + ctx.emit(scopeTarget(session, undefined), 'session/created', session) + await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('idem')) - expect(loaded.events).toHaveLength(2) // not doubled + expect(loaded.events).toHaveLength(3) // not doubled } finally { await fiber.dispose() await fix.cleanup() @@ -690,11 +694,12 @@ export function runCoordinatorContract(name: string, makeFixture: () => Promise< // async onCreated init has necessarily set state (exercises the // state-undefined cursor path). const session = ctx.sessions.create(SessionId('flush-nostate'), { meta: { cwd: WORK } }) + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) session.append('user/message', { content: [{ type: 'text', text: 'q' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.parallel('session/flush', session) + await ctx.sessions.flush(session) const loaded = await ctx.sessionPersistence.load(SessionId('flush-nostate')) - expect(loaded.events).toHaveLength(2) + expect(loaded.events).toHaveLength(3) } finally { await fiber.dispose() await fix.cleanup() diff --git a/packages/session-persistence/session-persistence/tsconfig.json b/packages/session-persistence/session-persistence/tsconfig.json index e817086a6a..cbd74a19e7 100644 --- a/packages/session-persistence/session-persistence/tsconfig.json +++ b/packages/session-persistence/session-persistence/tsconfig.json @@ -16,6 +16,9 @@ }, { "path": "../../core/session" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/session-query/session-query/package.json b/packages/session-query/session-query/package.json index ae2767598a..040697391f 100644 --- a/packages/session-query/session-query/package.json +++ b/packages/session-query/session-query/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-session-persistence": "^0.0.1", @@ -36,6 +42,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-session-persistence": "workspace:^", diff --git a/packages/session-query/session-query/src/invariant.ts b/packages/session-query/session-query/src/invariant.ts new file mode 100644 index 0000000000..91bcee721e --- /dev/null +++ b/packages/session-query/session-query/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-session-query`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-session-query/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-session-query' + +/** Cordis companion plugin name. */ +export const name = 'session-query-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/session-query/session-query/tests/session-query.spec.ts b/packages/session-query/session-query/tests/session-query.spec.ts index f532edf168..658eb709d9 100644 --- a/packages/session-query/session-query/tests/session-query.spec.ts +++ b/packages/session-query/session-query/tests/session-query.spec.ts @@ -101,6 +101,8 @@ describe('session-query exact reads', () => { it('classifies current, shadowed, and raw-log-only events through foldSurface', async () => { const ctx = await liveContext() const session = ctx.sessions.create(SessionId('surface')) + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) + session.append('step/start', { turn: 1, step: 1 }) const first = session.append( 'user/message', { content: [{ type: 'text', text: 'first' }], source: { kind: 'user' } }, @@ -117,13 +119,14 @@ describe('session-query exact reads', () => { { surfaceOp: { op: 'replace', start: first.seq, end: first.seq }, sourceEventSeqs: [first.seq] }, ) - expect((await ctx.sessionQuery.listEvents(session.id)).map(record => record.surface)) + expect((await ctx.sessionQuery.listEvents(session.id)).slice(2).map(record => record.surface)) .toEqual(['shadowed', 'log-only', 'current']) }) it('returns a bounded detached raw-event window and validates the request', async () => { const ctx = await liveContext({ readWindowMax: 1 }) const session = ctx.sessions.create(SessionId('window'), { meta: { cwd: '/work' } }) + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) for (const text of ['one', 'two', 'three']) { session.append( 'user/message', @@ -132,14 +135,14 @@ describe('session-query exact reads', () => { ) } - const result = await ctx.sessionQuery.readEvent({ sessionId: session.id, seq: 1, before: 1, after: 1 }) - expect([result.startSeq, result.endSeq, result.target.seq]).toEqual([0, 2, 1]) + const result = await ctx.sessionQuery.readEvent({ sessionId: session.id, seq: 2, before: 1, after: 1 }) + expect([result.startSeq, result.endSeq, result.target.seq]).toEqual([1, 3, 2]) expect(result.session).toEqual(session.header) Object.assign(result.session, { createdAt: -1 }) if (result.events[0]?.type !== 'user/message') throw new Error('expected user message') result.events[0].data.content = [] expect(session.header.createdAt).not.toBe(-1) - expect(session.events[0]?.type === 'user/message' && session.events[0].data.content).toHaveLength(1) + expect(session.events[1]?.type === 'user/message' && session.events[1].data.content).toHaveLength(1) await expect(ctx.sessionQuery.readEvent({ sessionId: session.id, seq: 9 })) .rejects.toThrow(expectCode('SESSION_QUERY_EVENT_NOT_FOUND')) @@ -161,6 +164,7 @@ describe('session-query exact reads', () => { ]) const ctx = await liveContext() const live = ctx.sessions.create(shared.id, { meta: { createdAt: 3, cwd: '/same' } }) + live.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) live.append( 'user/message', { content: [{ type: 'text', text: 'live' }], source: { kind: 'user' } }, @@ -170,7 +174,7 @@ describe('session-query exact reads', () => { expect((await ctx.sessionQuery.listSessions()).map(record => [record.header.id, record.live, record.persisted])) .toEqual([[shared.id, true, true], [durable.id, false, true]]) - const liveRead = await ctx.sessionQuery.readEvent({ sessionId: shared.id, seq: 0 }) + const liveRead = await ctx.sessionQuery.readEvent({ sessionId: shared.id, seq: 1 }) expect(liveRead.target.type === 'user/message' && liveRead.target.data.content[0]) .toMatchObject({ text: 'live' }) await expect(ctx.sessionQuery.readEvent({ sessionId: durable.id, seq: 0 })) @@ -189,6 +193,7 @@ describe('session-query exact reads', () => { TestPersistence.reset() const ctx = await liveContext() const live = ctx.sessions.create(SessionId('live')) + live.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) live.append( 'user/message', { content: [{ type: 'text', text: 'available' }], source: { kind: 'user' } }, @@ -198,8 +203,8 @@ describe('session-query exact reads', () => { TestPersistence.listFailure = new Error('list unavailable') TestPersistence.loadFailure = new Error('load unavailable') - await expect(ctx.sessionQuery.listEvents(live.id)).resolves.toHaveLength(1) - await expect(ctx.sessionQuery.readEvent({ sessionId: live.id, seq: 0 })).resolves.toMatchObject({ target: { seq: 0 } }) + await expect(ctx.sessionQuery.listEvents(live.id)).resolves.toHaveLength(2) + await expect(ctx.sessionQuery.readEvent({ sessionId: live.id, seq: 1 })).resolves.toMatchObject({ target: { seq: 1 } }) await expect(ctx.sessionQuery.listSessions()).rejects.toThrow(expectCode('SESSION_QUERY_PERSISTENCE_FAILED')) await expect(ctx.sessionQuery.listEvents(SessionId('durable'))).rejects.toThrow(expectCode('SESSION_QUERY_PERSISTENCE_FAILED')) }) @@ -228,19 +233,8 @@ describe('session-query exact reads', () => { .rejects.toThrow(expectCode('SESSION_QUERY_SOURCE_CONFLICT')) }) - it('turns malformed surfaces and direct invalid config into typed errors', async () => { + it('turns persisted malformed surfaces and direct invalid config into typed errors', async () => { const ctx = await liveContext() - const session = ctx.sessions.create(SessionId('bad-surface')) - ;(session as unknown as { log: SessionEvent[] }).log.push({ - type: 'assistant/message', - seq: 0, - time: 1, - data: { turn: 1, step: 1, content: [], provenance: { provider: 'mock', model: 'mock' } }, - surfaceOp: { op: 'replace', start: 9, end: 9 }, - }) - await expect(ctx.sessionQuery.listEvents(session.id)) - .rejects.toThrow(expectCode('SESSION_QUERY_INVALID_SURFACE')) - const persisted = header('bad-persisted-surface') TestPersistence.reset([{ meta: persisted, diff --git a/packages/session-query/session-query/tests/tracing.spec.ts b/packages/session-query/session-query/tests/tracing.spec.ts index 03e1adad89..ee8b1d833f 100644 --- a/packages/session-query/session-query/tests/tracing.spec.ts +++ b/packages/session-query/session-query/tests/tracing.spec.ts @@ -89,6 +89,8 @@ function expectCode(code: SessionQueryErrorCode): Error { } function appendTraceEvents(session: Session): void { + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) + session.append('step/start', { turn: 1, step: 1 }) session.append('assistant/chunk', { turn: 1, step: 1, @@ -97,22 +99,24 @@ function appendTraceEvents(session: Session): void { session.append( 'user/message', { content: [{ type: 'text', text: 'original' }], source: { kind: 'user' } }, - { surfaceOp: 'append', sourceEventSeqs: [0] }, + { surfaceOp: 'append', sourceEventSeqs: [2] }, ) session.append( 'assistant/message', { provenance: { provider: 'mock', model: 'mock' }, turn: 1, step: 1, content: [{ type: 'text', text: 'summary one' }] }, - { surfaceOp: { op: 'replace', start: 1, end: 1 }, sourceEventSeqs: [1, 0] }, + { surfaceOp: { op: 'replace', start: 3, end: 3 }, sourceEventSeqs: [3, 2] }, ) session.append( 'context/message', { content: [{ type: 'text', text: 'context' }], source: { kind: 'plugin', plugin: 'test' } }, { surfaceOp: 'append' }, ) + session.append('step/end', { turn: 1, step: 1 }) + session.append('step/start', { turn: 1, step: 2 }) session.append( 'assistant/message', { provenance: { provider: 'mock', model: 'mock' }, turn: 1, step: 2, content: [{ type: 'text', text: 'summary two' }] }, - { surfaceOp: { op: 'replace', start: 2, end: 2 }, sourceEventSeqs: [0, 2] }, + { surfaceOp: { op: 'replace', start: 4, end: 4 }, sourceEventSeqs: [2, 4] }, ) } @@ -235,40 +239,40 @@ describe('session event tracing', () => { const session = ctx.sessions.create(SessionId('trace')) appendTraceEvents(session) - const original = await ctx.sessionQuery.traceEvent({ sessionId: session.id, seq: 1 }) + const original = await ctx.sessionQuery.traceEvent({ sessionId: session.id, seq: 3 }) expect(original.target).toMatchObject({ sessionId: session.id, - seq: 1, + seq: 3, type: 'user/message', surface: 'shadowed', }) expect(original).toMatchObject({ - replacedBy: 2, - replacementChain: [2, 4], + replacedBy: 4, + replacementChain: [4, 8], replacedEventSeqs: [], - sourceEventSeqs: [0], - derivedEventSeqs: [2], + sourceEventSeqs: [2], + derivedEventSeqs: [4], }) - await expect(ctx.sessionQuery.traceEvent({ sessionId: session.id, seq: 2 })) + await expect(ctx.sessionQuery.traceEvent({ sessionId: session.id, seq: 4 })) .resolves.toMatchObject({ - replacedBy: 4, - replacementChain: [4], - replacedEventSeqs: [1], - sourceEventSeqs: [1, 0], - derivedEventSeqs: [4], + replacedBy: 8, + replacementChain: [8], + replacedEventSeqs: [3], + sourceEventSeqs: [3, 2], + derivedEventSeqs: [8], }) - await expect(ctx.sessionQuery.traceEvent({ sessionId: session.id, seq: 0 })) + await expect(ctx.sessionQuery.traceEvent({ sessionId: session.id, seq: 2 })) .resolves.toMatchObject({ target: { surface: 'log-only' }, replacementChain: [], sourceEventSeqs: [], - derivedEventSeqs: [1, 2, 4], + derivedEventSeqs: [3, 4, 8], }) - await expect(ctx.sessionQuery.traceEvent({ sessionId: session.id, seq: 4 })) + await expect(ctx.sessionQuery.traceEvent({ sessionId: session.id, seq: 8 })) .resolves.toMatchObject({ replacementChain: [], - replacedEventSeqs: [2], - sourceEventSeqs: [0, 2], + replacedEventSeqs: [4], + sourceEventSeqs: [2, 4], derivedEventSeqs: [], }) }) @@ -278,18 +282,18 @@ describe('session event tracing', () => { const session = ctx.sessions.create(SessionId('detached')) appendTraceEvents(session) - const first = await ctx.sessionQuery.traceEvent({ sessionId: session.id, seq: 2 }) + const first = await ctx.sessionQuery.traceEvent({ sessionId: session.id, seq: 4 }) first.target.time = -1 first.replacementChain.push(99) first.replacedEventSeqs.push(99) first.sourceEventSeqs.push(99) first.derivedEventSeqs.push(99) - const repeated = await ctx.sessionQuery.traceEvent({ sessionId: session.id, seq: 2 }) + const repeated = await ctx.sessionQuery.traceEvent({ sessionId: session.id, seq: 4 }) expect(repeated.target.time).not.toBe(-1) - expect(repeated.replacementChain).toEqual([4]) - expect(repeated.replacedEventSeqs).toEqual([1]) - expect(repeated.sourceEventSeqs).toEqual([1, 0]) - expect(repeated.derivedEventSeqs).toEqual([4]) + expect(repeated.replacementChain).toEqual([8]) + expect(repeated.replacedEventSeqs).toEqual([3]) + expect(repeated.sourceEventSeqs).toEqual([3, 2]) + expect(repeated.derivedEventSeqs).toEqual([8]) }) it('loads persisted logs once, prefers live logs, and preserves failures and conflicts', async () => { @@ -303,6 +307,7 @@ describe('session event tracing', () => { expect([TracePersistence.listCalls, TracePersistence.loadCalls]).toEqual([1, 1]) const live = ctx.sessions.create(durable.id, { meta: { createdAt: 1, cwd: '/same' } }) + live.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) live.append( 'context/message', { content: [{ type: 'text', text: 'live' }], source: { kind: 'plugin', plugin: 'test' } }, @@ -310,7 +315,7 @@ describe('session event tracing', () => { ) TracePersistence.listFailure = new Error('list unavailable') TracePersistence.loadFailure = new Error('load unavailable') - await expect(ctx.sessionQuery.traceEvent({ sessionId: durable.id, seq: 0 })) + await expect(ctx.sessionQuery.traceEvent({ sessionId: durable.id, seq: 1 })) .resolves.toMatchObject({ target: { type: 'context/message' } }) expect([TracePersistence.listCalls, TracePersistence.loadCalls]).toEqual([1, 1]) diff --git a/packages/session-query/session-query/tsconfig.json b/packages/session-query/session-query/tsconfig.json index 7153dae8bb..d7205907e1 100644 --- a/packages/session-query/session-query/tsconfig.json +++ b/packages/session-query/session-query/tsconfig.json @@ -25,6 +25,9 @@ }, { "path": "../../session-persistence/session-persistence" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/skill/skill-local/package.json b/packages/skill/skill-local/package.json index d490438c51..be7c7baae2 100644 --- a/packages/skill/skill-local/package.json +++ b/packages/skill/skill-local/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -24,6 +29,7 @@ "peerDependencies": { "@deepseek-ai/dsh-fs": "^0.0.1", "@deepseek-ai/dsh-home": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-skill": "^0.0.1", "cordis": "^4.0.0-rc.7" }, @@ -34,6 +40,7 @@ "devDependencies": { "@deepseek-ai/dsh-fs": "workspace:^", "@deepseek-ai/dsh-home": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-skill": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/skill/skill-local/src/invariant.ts b/packages/skill/skill-local/src/invariant.ts new file mode 100644 index 0000000000..475d02bb8f --- /dev/null +++ b/packages/skill/skill-local/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-skill-local`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-skill-local/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-skill-local' + +/** Cordis companion plugin name. */ +export const name = 'skill-local-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/skill/skill-local/tsconfig.json b/packages/skill/skill-local/tsconfig.json index f51147abce..d471e269d0 100644 --- a/packages/skill/skill-local/tsconfig.json +++ b/packages/skill/skill-local/tsconfig.json @@ -6,11 +6,26 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../../util/home" }, - { "path": "../../fs/fs" }, - { "path": "../skill" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../util/home" + }, + { + "path": "../../fs/fs" + }, + { + "path": "../skill" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/skill/skill/package.json b/packages/skill/skill/package.json index c025de6ee9..3148a3f577 100644 --- a/packages/skill/skill/package.json +++ b/packages/skill/skill/package.json @@ -11,23 +11,30 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "dependencies": { "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/skill/skill/src/invariant.ts b/packages/skill/skill/src/invariant.ts new file mode 100644 index 0000000000..c1d4091bb2 --- /dev/null +++ b/packages/skill/skill/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-skill`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-skill/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-skill' + +/** Cordis companion plugin name. */ +export const name = 'skill-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/skill/skill/tsconfig.json b/packages/skill/skill/tsconfig.json index 1b1855dcc4..e882ed2d72 100644 --- a/packages/skill/skill/tsconfig.json +++ b/packages/skill/skill/tsconfig.json @@ -6,8 +6,17 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/skill/tool-skill/package.json b/packages/skill/tool-skill/package.json index 3d6ddc6b7e..47421bf19c 100644 --- a/packages/skill/tool-skill/package.json +++ b/packages/skill/tool-skill/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,6 +28,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-skill": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", @@ -33,6 +39,7 @@ }, "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-scope": "workspace:^", "@deepseek-ai/dsh-skill": "workspace:^", diff --git a/packages/skill/tool-skill/src/invariant.ts b/packages/skill/tool-skill/src/invariant.ts new file mode 100644 index 0000000000..abf4ba3961 --- /dev/null +++ b/packages/skill/tool-skill/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tool-skill`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tool-skill/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tool-skill' + +/** Cordis companion plugin name. */ +export const name = 'tool-skill-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/skill/tool-skill/tsconfig.json b/packages/skill/tool-skill/tsconfig.json index 52ebb8bf9d..fed1ffa5f5 100644 --- a/packages/skill/tool-skill/tsconfig.json +++ b/packages/skill/tool-skill/tsconfig.json @@ -6,13 +6,32 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../../core/scope" }, - { "path": "../../llm/llm" }, - { "path": "../../core/agent" }, - { "path": "../skill" }, - { "path": "../../core/tools" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../core/scope" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../core/agent" + }, + { + "path": "../skill" + }, + { + "path": "../../core/tools" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/spill/spill-local/package.json b/packages/spill/spill-local/package.json index a75c4bfc0b..25a20db1d5 100644 --- a/packages/spill/spill-local/package.json +++ b/packages/spill/spill-local/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-spill": "^0.0.1", "cordis": "^4.0.0-rc.6" }, @@ -30,6 +36,7 @@ }, "devDependencies": { "@deepseek-ai/dsh-brand": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-spill": "workspace:^", diff --git a/packages/spill/spill-local/src/invariant.ts b/packages/spill/spill-local/src/invariant.ts new file mode 100644 index 0000000000..d638ffa2a9 --- /dev/null +++ b/packages/spill/spill-local/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-spill-local`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-spill-local/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-spill-local' + +/** Cordis companion plugin name. */ +export const name = 'spill-local-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/spill/spill-local/tsconfig.json b/packages/spill/spill-local/tsconfig.json index 8e818212f5..0cb209d8d0 100644 --- a/packages/spill/spill-local/tsconfig.json +++ b/packages/spill/spill-local/tsconfig.json @@ -6,9 +6,20 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../spill" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../spill" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/spill/spill-policy/package.json b/packages/spill/spill-policy/package.json index 9c28ea5382..dac7d4311d 100644 --- a/packages/spill/spill-policy/package.json +++ b/packages/spill/spill-policy/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-retention": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", @@ -34,6 +40,7 @@ }, "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-retention": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", diff --git a/packages/spill/spill-policy/src/invariant.ts b/packages/spill/spill-policy/src/invariant.ts new file mode 100644 index 0000000000..d4aa544ecd --- /dev/null +++ b/packages/spill/spill-policy/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-spill-policy`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-spill-policy/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-spill-policy' + +/** Cordis companion plugin name. */ +export const name = 'spill-policy-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/spill/spill-policy/tsconfig.json b/packages/spill/spill-policy/tsconfig.json index 6a81ab2f3c..71f19d381c 100644 --- a/packages/spill/spill-policy/tsconfig.json +++ b/packages/spill/spill-policy/tsconfig.json @@ -6,13 +6,32 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../../util/retention" }, - { "path": "../../llm/llm" }, - { "path": "../../core/session" }, - { "path": "../spill" }, - { "path": "../../core/tools" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../util/retention" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../core/session" + }, + { + "path": "../spill" + }, + { + "path": "../../core/tools" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/spill/spill/package.json b/packages/spill/spill/package.json index 3103c9cd11..c66306ff0a 100644 --- a/packages/spill/spill/package.json +++ b/packages/spill/spill/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,12 +28,14 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-brand": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "cordis": "^4.0.0-rc.6" }, "devDependencies": { "@deepseek-ai/dsh-brand": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "cordis": "^4.0.0-rc.6" diff --git a/packages/spill/spill/src/invariant.ts b/packages/spill/spill/src/invariant.ts new file mode 100644 index 0000000000..714e43f3a6 --- /dev/null +++ b/packages/spill/spill/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-spill`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-spill/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-spill' + +/** Cordis companion plugin name. */ +export const name = 'spill-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/spill/spill/tsconfig.json b/packages/spill/spill/tsconfig.json index 0c2fd5c57f..30d0d29f0f 100644 --- a/packages/spill/spill/tsconfig.json +++ b/packages/spill/spill/tsconfig.json @@ -6,10 +6,23 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../util/brand" }, - { "path": "../../llm/llm" }, - { "path": "../../core/session" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../util/brand" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../core/session" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/subagent/subagent-acp/package.json b/packages/subagent/subagent-acp/package.json index fa16edcf60..2564afa8da 100644 --- a/packages/subagent/subagent-acp/package.json +++ b/packages/subagent/subagent-acp/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,6 +28,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-subagent": "^0.0.1", @@ -34,13 +40,14 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@cordisjs/plugin-loader": "^1.0.0-rc.5", "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-loader-smoke": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-subagent": "workspace:^", "@deepseek-ai/dsh-subagent-subprocess": "workspace:^", - "@cordisjs/plugin-loader": "^1.0.0-rc.5", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/subagent/subagent-acp/src/invariant.ts b/packages/subagent/subagent-acp/src/invariant.ts new file mode 100644 index 0000000000..a5828fd4c1 --- /dev/null +++ b/packages/subagent/subagent-acp/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-subagent-acp`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-subagent-acp/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-subagent-acp' + +/** Cordis companion plugin name. */ +export const name = 'subagent-acp-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/subagent/subagent-acp/tsconfig.json b/packages/subagent/subagent-acp/tsconfig.json index 5aa28528ac..175eb78e2f 100644 --- a/packages/subagent/subagent-acp/tsconfig.json +++ b/packages/subagent/subagent-acp/tsconfig.json @@ -31,6 +31,9 @@ }, { "path": "../../support/loader-smoke" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/subagent/subagent-fork/package.json b/packages/subagent/subagent-fork/package.json index 63f548d217..aa93b83e03 100644 --- a/packages/subagent/subagent-fork/package.json +++ b/packages/subagent/subagent-fork/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,6 +28,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-subagent": "^0.0.1", "@deepseek-ai/dsh-subagent-inprocess": "^0.0.1", @@ -32,6 +38,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@cordisjs/plugin-loader": "^1.0.0-rc.5", "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", @@ -41,7 +48,6 @@ "@deepseek-ai/dsh-subagent": "workspace:^", "@deepseek-ai/dsh-subagent-inprocess": "workspace:^", "@deepseek-ai/dsh-subagent-spawn": "workspace:^", - "@cordisjs/plugin-loader": "^1.0.0-rc.5", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/subagent/subagent-fork/src/invariant.ts b/packages/subagent/subagent-fork/src/invariant.ts new file mode 100644 index 0000000000..c6903fd82d --- /dev/null +++ b/packages/subagent/subagent-fork/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-subagent-fork`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-subagent-fork/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-subagent-fork' + +/** Cordis companion plugin name. */ +export const name = 'subagent-fork-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/subagent/subagent-fork/tsconfig.json b/packages/subagent/subagent-fork/tsconfig.json index bac12550af..a07a2319ef 100644 --- a/packages/subagent/subagent-fork/tsconfig.json +++ b/packages/subagent/subagent-fork/tsconfig.json @@ -28,6 +28,9 @@ }, { "path": "../subagent-inprocess" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/subagent/subagent-inprocess/package.json b/packages/subagent/subagent-inprocess/package.json index b9397e300c..69b573ecdd 100644 --- a/packages/subagent/subagent-inprocess/package.json +++ b/packages/subagent/subagent-inprocess/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,6 +28,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-subagent": "^0.0.1", diff --git a/packages/subagent/subagent-inprocess/src/invariant.ts b/packages/subagent/subagent-inprocess/src/invariant.ts new file mode 100644 index 0000000000..0eac204104 --- /dev/null +++ b/packages/subagent/subagent-inprocess/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-subagent-inprocess`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-subagent-inprocess/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-subagent-inprocess' + +/** Cordis companion plugin name. */ +export const name = 'subagent-inprocess-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/subagent/subagent-inprocess/tsconfig.json b/packages/subagent/subagent-inprocess/tsconfig.json index 7b7a015cc9..02fd8e53d0 100644 --- a/packages/subagent/subagent-inprocess/tsconfig.json +++ b/packages/subagent/subagent-inprocess/tsconfig.json @@ -31,6 +31,9 @@ }, { "path": "../../core/tools" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/subagent/subagent-spawn/package.json b/packages/subagent/subagent-spawn/package.json index 1a986e69aa..f429025a5d 100644 --- a/packages/subagent/subagent-spawn/package.json +++ b/packages/subagent/subagent-spawn/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-subagent": "^0.0.1", "@deepseek-ai/dsh-subagent-inprocess": "^0.0.1", "cordis": "^4.0.0-rc.7" @@ -30,6 +36,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@cordisjs/plugin-loader": "^1.0.0-rc.5", "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", @@ -42,7 +49,6 @@ "@deepseek-ai/dsh-subagent-inprocess": "workspace:^", "@deepseek-ai/dsh-tool-bash": "workspace:^", "@deepseek-ai/dsh-tool-subagent": "workspace:^", - "@cordisjs/plugin-loader": "^1.0.0-rc.5", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/subagent/subagent-spawn/src/invariant.ts b/packages/subagent/subagent-spawn/src/invariant.ts new file mode 100644 index 0000000000..d179a2b72a --- /dev/null +++ b/packages/subagent/subagent-spawn/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-subagent-spawn`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-subagent-spawn/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-subagent-spawn' + +/** Cordis companion plugin name. */ +export const name = 'subagent-spawn-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/subagent/subagent-spawn/tsconfig.json b/packages/subagent/subagent-spawn/tsconfig.json index 219bf2a0c9..ee9ab096c2 100644 --- a/packages/subagent/subagent-spawn/tsconfig.json +++ b/packages/subagent/subagent-spawn/tsconfig.json @@ -22,6 +22,9 @@ }, { "path": "../subagent-inprocess" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/subagent/subagent-subprocess/package.json b/packages/subagent/subagent-subprocess/package.json index 5f17459276..bd573b3c0c 100644 --- a/packages/subagent/subagent-subprocess/package.json +++ b/packages/subagent/subagent-subprocess/package.json @@ -11,20 +11,27 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/subagent/subagent-subprocess/src/invariant.ts b/packages/subagent/subagent-subprocess/src/invariant.ts new file mode 100644 index 0000000000..22dac32814 --- /dev/null +++ b/packages/subagent/subagent-subprocess/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-subagent-subprocess`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-subagent-subprocess/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-subagent-subprocess' + +/** Cordis companion plugin name. */ +export const name = 'subagent-subprocess-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/subagent/subagent-subprocess/tsconfig.json b/packages/subagent/subagent-subprocess/tsconfig.json index 749cb0208e..d970a00263 100644 --- a/packages/subagent/subagent-subprocess/tsconfig.json +++ b/packages/subagent/subagent-subprocess/tsconfig.json @@ -7,5 +7,9 @@ "include": [ "src" ], - "references": [] + "references": [ + { + "path": "../../support/invariants" + } + ] } diff --git a/packages/subagent/subagent/package.json b/packages/subagent/subagent/package.json index aea05553e4..58b51d1888 100644 --- a/packages/subagent/subagent/package.json +++ b/packages/subagent/subagent/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -24,6 +29,7 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-brand": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-scope": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", @@ -33,6 +39,7 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-brand": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-scope": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", diff --git a/packages/subagent/subagent/src/invariant.ts b/packages/subagent/subagent/src/invariant.ts new file mode 100644 index 0000000000..3a79592ee1 --- /dev/null +++ b/packages/subagent/subagent/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-subagent`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-subagent/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-subagent' + +/** Cordis companion plugin name. */ +export const name = 'subagent-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/subagent/subagent/tsconfig.json b/packages/subagent/subagent/tsconfig.json index f93f929241..713e214f04 100644 --- a/packages/subagent/subagent/tsconfig.json +++ b/packages/subagent/subagent/tsconfig.json @@ -25,6 +25,9 @@ }, { "path": "../../core/scope" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/subagent/tool-subagent/package.json b/packages/subagent/tool-subagent/package.json index e2d4378743..6ed447dd56 100644 --- a/packages/subagent/tool-subagent/package.json +++ b/packages/subagent/tool-subagent/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,6 +28,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-subagent": "^0.0.1", "@deepseek-ai/dsh-tasks": "^0.0.1", @@ -35,6 +41,7 @@ "devDependencies": { "@cordisjs/plugin-loader": "^1.0.0-rc.5", "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-subagent": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", diff --git a/packages/subagent/tool-subagent/src/invariant.ts b/packages/subagent/tool-subagent/src/invariant.ts new file mode 100644 index 0000000000..08881e4b2b --- /dev/null +++ b/packages/subagent/tool-subagent/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tool-subagent`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tool-subagent/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tool-subagent' + +/** Cordis companion plugin name. */ +export const name = 'tool-subagent-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/subagent/tool-subagent/tsconfig.json b/packages/subagent/tool-subagent/tsconfig.json index 2aa9d4f14e..25780c367f 100644 --- a/packages/subagent/tool-subagent/tsconfig.json +++ b/packages/subagent/tool-subagent/tsconfig.json @@ -31,6 +31,9 @@ }, { "path": "../../tasks/tasks" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/support/acp-snapshot/package.json b/packages/support/acp-snapshot/package.json index 64e1b0cbd7..abffcc2708 100644 --- a/packages/support/acp-snapshot/package.json +++ b/packages/support/acp-snapshot/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -27,9 +32,11 @@ "vitest": "^4.1.8" }, "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/support/acp-snapshot/src/invariant.ts b/packages/support/acp-snapshot/src/invariant.ts new file mode 100644 index 0000000000..3579b96cf5 --- /dev/null +++ b/packages/support/acp-snapshot/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-acp-snapshot`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-acp-snapshot/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-acp-snapshot' + +/** Cordis companion plugin name. */ +export const name = 'acp-snapshot-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/support/acp-snapshot/tsconfig.json b/packages/support/acp-snapshot/tsconfig.json index 9120df0ad1..893282ce51 100644 --- a/packages/support/acp-snapshot/tsconfig.json +++ b/packages/support/acp-snapshot/tsconfig.json @@ -8,6 +8,11 @@ "src" ], "references": [ - { "path": "../loader-smoke" } + { + "path": "../loader-smoke" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/support/agent-loop-testkit/package.json b/packages/support/agent-loop-testkit/package.json index 423bd3e80d..aa04d85832 100644 --- a/packages/support/agent-loop-testkit/package.json +++ b/packages/support/agent-loop-testkit/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,6 +28,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-system-prompt": "^0.0.1", @@ -32,6 +38,7 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", diff --git a/packages/support/agent-loop-testkit/src/invariant.ts b/packages/support/agent-loop-testkit/src/invariant.ts new file mode 100644 index 0000000000..fc2554aa77 --- /dev/null +++ b/packages/support/agent-loop-testkit/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-agent-loop-testkit`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-agent-loop-testkit/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-agent-loop-testkit' + +/** Cordis companion plugin name. */ +export const name = 'agent-loop-testkit-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/support/agent-loop-testkit/tsconfig.json b/packages/support/agent-loop-testkit/tsconfig.json index 5e5b3c47f2..d24b4dd988 100644 --- a/packages/support/agent-loop-testkit/tsconfig.json +++ b/packages/support/agent-loop-testkit/tsconfig.json @@ -28,6 +28,9 @@ }, { "path": "../../core/tools" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/support/invariants/README.md b/packages/support/invariants/README.md index 1cb0e57838..c49771ded9 100644 --- a/packages/support/invariants/README.md +++ b/packages/support/invariants/README.md @@ -1,6 +1,6 @@ # dsh-invariants -Configurable registry service for package-owned runtime invariant checks. The root plugin registers `ctx.invariants`; it contains no product checks or product-package imports. Packages publish optional `./invariant` companion plugins that contribute their own assertions. +Configurable registry service for package-owned runtime invariant checks. The root plugin registers `ctx.invariants`; it contains no product checks or product-package imports. Every workspace package publishes a `./invariant` companion that registers its exact npm package name. ## Service: `InvariantService` (`ctx.invariants`) @@ -24,6 +24,10 @@ The service owns every registration fiber, while the returned disposer also belo ## Package companions +An ownership-only generated baseline installs no listeners but still reserves its package name through the real service boundary. A package replaces that marked file when it gains a relational check, retaining the same registration. `pnpm run verify-package-invariants` checks every package's source registration, export, published files, dependencies, TypeScript reference, and bundle entry. + +Four companions currently install stateful checks: + | Companion | Registration | Checks | |---|---|---| | `@deepseek-ai/dsh-session/invariant` | `@deepseek-ai/dsh-session` | sequence, turn/step enclosure, and same-step tool call/result trace | @@ -50,7 +54,7 @@ ctx.plugin(InvariantService, { ctx.plugin(SessionInvariant) ``` -The standard agent spine mounts the service and all four companions. Custom compositions choose the companions they want and may disable or filter them without changing package entrypoints. +The standard agent spine mounts the service and the four stateful companions. Custom compositions choose the companions they want and may disable or filter them without changing package entrypoints. Vitest mounts every package companion against an explicitly enabled service for ordinary Cordis roots, so baseline ownership and stateful checks execute across unit, snapshot, and e2e suites; focused invariant-service tests construct their own topology to exercise filtering and lifecycle behavior. ## Model Experience @@ -58,6 +62,6 @@ None, as the service and companions observe runtime events and requests but neve ## Known Limitations and Deferred Work -- The shipped checks cover only the four listed package contracts; a merge-extended event family has no family-specific assertion until its owner publishes one. +- Stateful checks cover only the four listed package contracts; other companions reserve ownership but add no listeners until their packages gain relational assertions. - Request reconstruction covers frozen loop-built requests with a live session id; direct one-shot calls remain outside that companion's marker contract. - Regular-expression filters are fixed for the service lifetime; changing them requires ordinary Cordis plugin reload. diff --git a/packages/support/invariants/package.json b/packages/support/invariants/package.json index e06e9a3db7..d52dd0a14d 100644 --- a/packages/support/invariants/package.json +++ b/packages/support/invariants/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" diff --git a/packages/support/invariants/src/index.ts b/packages/support/invariants/src/index.ts index 754cb46ab9..4bbf218e97 100644 --- a/packages/support/invariants/src/index.ts +++ b/packages/support/invariants/src/index.ts @@ -1,7 +1,8 @@ /** * Configurable registry for package-owned runtime invariant contributions. - * Packages register checks from optional `./invariant` companion plugins; - * ordinary package entrypoints stay independent of diagnostics. + * Every workspace package registers its name from a `./invariant` companion; + * ordinary package entrypoints stay independent of diagnostics, and packages + * without relational checks use an ownership-only installer. * * @module @deepseek-ai/dsh-invariants */ diff --git a/packages/support/invariants/src/invariant.ts b/packages/support/invariants/src/invariant.ts new file mode 100644 index 0000000000..b6b4d8ae48 --- /dev/null +++ b/packages/support/invariants/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-invariants`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-invariants/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from './index.ts' + +const PACKAGE_NAME = '@deepseek-ai/dsh-invariants' + +/** Cordis companion plugin name. */ +export const name = 'invariants-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/support/llm-replay/package.json b/packages/support/llm-replay/package.json index 403f3bda92..4a7e7dd8d8 100644 --- a/packages/support/llm-replay/package.json +++ b/packages/support/llm-replay/package.json @@ -11,22 +11,29 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "cordis": "^4.0.0-rc.7" diff --git a/packages/support/llm-replay/src/invariant.ts b/packages/support/llm-replay/src/invariant.ts new file mode 100644 index 0000000000..50295cbedf --- /dev/null +++ b/packages/support/llm-replay/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-llm-replay`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-llm-replay/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-llm-replay' + +/** Cordis companion plugin name. */ +export const name = 'llm-replay-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/support/llm-replay/tsconfig.json b/packages/support/llm-replay/tsconfig.json index 95245937ec..673ee51547 100644 --- a/packages/support/llm-replay/tsconfig.json +++ b/packages/support/llm-replay/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../../core/session" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/support/loader-smoke/package.json b/packages/support/loader-smoke/package.json index ddba421b41..570ee2c5ca 100644 --- a/packages/support/loader-smoke/package.json +++ b/packages/support/loader-smoke/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -25,9 +30,11 @@ "tsx": "^4.22.4" }, "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.6" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.6" } } diff --git a/packages/support/loader-smoke/src/invariant.ts b/packages/support/loader-smoke/src/invariant.ts new file mode 100644 index 0000000000..9265a5f8b4 --- /dev/null +++ b/packages/support/loader-smoke/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-loader-smoke`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-loader-smoke/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-loader-smoke' + +/** Cordis companion plugin name. */ +export const name = 'loader-smoke-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/support/loader-smoke/tsconfig.json b/packages/support/loader-smoke/tsconfig.json index 749cb0208e..d970a00263 100644 --- a/packages/support/loader-smoke/tsconfig.json +++ b/packages/support/loader-smoke/tsconfig.json @@ -7,5 +7,9 @@ "include": [ "src" ], - "references": [] + "references": [ + { + "path": "../../support/invariants" + } + ] } diff --git a/packages/tasks/tasks/package.json b/packages/tasks/tasks/package.json index 9e5f824418..128a8d2c4e 100644 --- a/packages/tasks/tasks/package.json +++ b/packages/tasks/tasks/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-tasks", - "description": "Background task registry (ctx.tasks) for the DeepSeek Harness \u2014 shared ids, owner isolation, polling, cancellation, and completion listeners for long-running tool work", + "description": "Background task registry (ctx.tasks) for the DeepSeek Harness — shared ids, owner isolation, polling, cancellation, and completion listeners for long-running tool work", "version": "0.0.1", "private": true, "type": "module", @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -24,6 +29,7 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-brand": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-timeout": "^0.0.1", "cordis": "^4.0.0-rc.6" @@ -31,6 +37,7 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-brand": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-timeout": "workspace:^", "cordis": "^4.0.0-rc.6" diff --git a/packages/tasks/tasks/src/invariant.ts b/packages/tasks/tasks/src/invariant.ts new file mode 100644 index 0000000000..468fe664b9 --- /dev/null +++ b/packages/tasks/tasks/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tasks`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tasks/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tasks' + +/** Cordis companion plugin name. */ +export const name = 'tasks-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/tasks/tasks/tsconfig.json b/packages/tasks/tasks/tsconfig.json index 392d3f8d8a..e29262ca74 100644 --- a/packages/tasks/tasks/tsconfig.json +++ b/packages/tasks/tasks/tsconfig.json @@ -25,6 +25,9 @@ }, { "path": "../../util/timeout" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/tasks/tool-tasks/package.json b/packages/tasks/tool-tasks/package.json index 9e0b19d307..fc1f96417d 100644 --- a/packages/tasks/tool-tasks/package.json +++ b/packages/tasks/tool-tasks/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,6 +28,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-system-prompt": "^0.0.1", "@deepseek-ai/dsh-tasks": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", @@ -33,6 +39,7 @@ }, "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", diff --git a/packages/tasks/tool-tasks/src/invariant.ts b/packages/tasks/tool-tasks/src/invariant.ts new file mode 100644 index 0000000000..fded38c895 --- /dev/null +++ b/packages/tasks/tool-tasks/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tool-tasks`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tool-tasks/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tool-tasks' + +/** Cordis companion plugin name. */ +export const name = 'tool-tasks-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/tasks/tool-tasks/tsconfig.json b/packages/tasks/tool-tasks/tsconfig.json index 9e5411df25..feab4f3be8 100644 --- a/packages/tasks/tool-tasks/tsconfig.json +++ b/packages/tasks/tool-tasks/tsconfig.json @@ -28,6 +28,9 @@ }, { "path": "../tasks" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/timeout/timeout-policy/package.json b/packages/timeout/timeout-policy/package.json index aa351cb7a6..43dc9dfa28 100644 --- a/packages/timeout/timeout-policy/package.json +++ b/packages/timeout/timeout-policy/package.json @@ -11,23 +11,30 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-timeout": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-timeout": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", diff --git a/packages/timeout/timeout-policy/src/invariant.ts b/packages/timeout/timeout-policy/src/invariant.ts new file mode 100644 index 0000000000..9e7b5b7d4b --- /dev/null +++ b/packages/timeout/timeout-policy/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-timeout-policy`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-timeout-policy/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-timeout-policy' + +/** Cordis companion plugin name. */ +export const name = 'timeout-policy-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/timeout/timeout-policy/tsconfig.json b/packages/timeout/timeout-policy/tsconfig.json index 8c0b47716e..55e50befde 100644 --- a/packages/timeout/timeout-policy/tsconfig.json +++ b/packages/timeout/timeout-policy/tsconfig.json @@ -6,11 +6,26 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../../llm/llm" }, - { "path": "../../util/timeout" }, - { "path": "../../core/tools" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../util/timeout" + }, + { + "path": "../../core/tools" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/todo/tool-todo/package.json b/packages/todo/tool-todo/package.json index bab0f1230c..88d5e9b9c9 100644 --- a/packages/todo/tool-todo/package.json +++ b/packages/todo/tool-todo/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,6 +28,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", "cordis": "^4.0.0-rc.7" @@ -31,6 +37,7 @@ "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", diff --git a/packages/todo/tool-todo/src/invariant.ts b/packages/todo/tool-todo/src/invariant.ts new file mode 100644 index 0000000000..a5980342f3 --- /dev/null +++ b/packages/todo/tool-todo/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tool-todo`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tool-todo/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tool-todo' + +/** Cordis companion plugin name. */ +export const name = 'tool-todo-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/todo/tool-todo/tsconfig.json b/packages/todo/tool-todo/tsconfig.json index adf2f25dec..f980e5ead1 100644 --- a/packages/todo/tool-todo/tsconfig.json +++ b/packages/todo/tool-todo/tsconfig.json @@ -22,6 +22,9 @@ }, { "path": "../../core/session" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/ui/acp/package.json b/packages/ui/acp/package.json index 84e4dcbda9..7d324d2e6f 100644 --- a/packages/ui/acp/package.json +++ b/packages/ui/acp/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -29,6 +34,7 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-bash": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-permission": "^0.0.1", "@deepseek-ai/dsh-sandbox": "^0.0.1", diff --git a/packages/ui/acp/src/invariant.ts b/packages/ui/acp/src/invariant.ts new file mode 100644 index 0000000000..2c081fc48c --- /dev/null +++ b/packages/ui/acp/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-acp`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-acp/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-acp' + +/** Cordis companion plugin name. */ +export const name = 'acp-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/ui/acp/tsconfig.json b/packages/ui/acp/tsconfig.json index 387e0d0c53..5f93e1589d 100644 --- a/packages/ui/acp/tsconfig.json +++ b/packages/ui/acp/tsconfig.json @@ -46,6 +46,9 @@ }, { "path": "../../bash/bash" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/ui/app-boot/package.json b/packages/ui/app-boot/package.json index 1eef56ae93..e2f0631e8b 100644 --- a/packages/ui/app-boot/package.json +++ b/packages/ui/app-boot/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -24,11 +29,13 @@ "peerDependencies": { "@cordisjs/plugin-include": "^1.0.4", "@cordisjs/plugin-loader": "^1.0.0-rc.5", + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { "@cordisjs/plugin-include": "workspace:^", "@cordisjs/plugin-loader": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/ui/app-boot/src/invariant.ts b/packages/ui/app-boot/src/invariant.ts new file mode 100644 index 0000000000..498d967799 --- /dev/null +++ b/packages/ui/app-boot/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-app-boot`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-app-boot/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-app-boot' + +/** Cordis companion plugin name. */ +export const name = 'app-boot-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/ui/app-boot/tsconfig.json b/packages/ui/app-boot/tsconfig.json index 3171312de4..b85dc7f6a2 100644 --- a/packages/ui/app-boot/tsconfig.json +++ b/packages/ui/app-boot/tsconfig.json @@ -16,6 +16,9 @@ }, { "path": "../../../vendor/include" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/ui/jsonrpc/package.json b/packages/ui/jsonrpc/package.json index e59fc4aaea..72dd9fe8c3 100644 --- a/packages/ui/jsonrpc/package.json +++ b/packages/ui/jsonrpc/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -26,6 +31,7 @@ }, "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-llm-deepseek": "^0.0.1", "@deepseek-ai/dsh-scope": "^0.0.1", @@ -37,6 +43,7 @@ "@cordisjs/plugin-loader": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-spine-demo": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-llm-deepseek": "workspace:^", "@deepseek-ai/dsh-scope": "workspace:^", diff --git a/packages/ui/jsonrpc/src/invariant.ts b/packages/ui/jsonrpc/src/invariant.ts new file mode 100644 index 0000000000..552c312481 --- /dev/null +++ b/packages/ui/jsonrpc/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-jsonrpc`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-jsonrpc/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-jsonrpc' + +/** Cordis companion plugin name. */ +export const name = 'jsonrpc-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/ui/jsonrpc/tsconfig.json b/packages/ui/jsonrpc/tsconfig.json index dcd57ef9af..14a70d8eaa 100644 --- a/packages/ui/jsonrpc/tsconfig.json +++ b/packages/ui/jsonrpc/tsconfig.json @@ -28,6 +28,9 @@ }, { "path": "../../subagent/subagent" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/ui/permission/package.json b/packages/ui/permission/package.json index e38e5a7bf1..bd8164f427 100644 --- a/packages/ui/permission/package.json +++ b/packages/ui/permission/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,6 +28,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-bash": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-sandbox": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-user-approval": "^0.0.1", @@ -33,6 +39,7 @@ }, "devDependencies": { "@deepseek-ai/dsh-bash": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-sandbox": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-user-approval": "workspace:^", diff --git a/packages/ui/permission/src/invariant.ts b/packages/ui/permission/src/invariant.ts new file mode 100644 index 0000000000..1a774e2e4c --- /dev/null +++ b/packages/ui/permission/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-permission`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-permission/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-permission' + +/** Cordis companion plugin name. */ +export const name = 'permission-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/ui/permission/tsconfig.json b/packages/ui/permission/tsconfig.json index 8b9cff62b4..3e7b747b2c 100644 --- a/packages/ui/permission/tsconfig.json +++ b/packages/ui/permission/tsconfig.json @@ -28,6 +28,9 @@ }, { "path": "../user-approval" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/ui/stdio/package.json b/packages/ui/stdio/package.json index e1bffdf171..f8473af7a3 100644 --- a/packages/ui/stdio/package.json +++ b/packages/ui/stdio/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -24,6 +29,7 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-agent-loop": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-user-interaction": "^0.0.1", @@ -41,6 +47,7 @@ "@cordisjs/plugin-loader": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-user-interaction": "workspace:^", diff --git a/packages/ui/stdio/src/invariant.ts b/packages/ui/stdio/src/invariant.ts new file mode 100644 index 0000000000..443440a215 --- /dev/null +++ b/packages/ui/stdio/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-stdio`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-stdio/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-stdio' + +/** Cordis companion plugin name. */ +export const name = 'stdio-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/ui/stdio/tests/stdio.spec.ts b/packages/ui/stdio/tests/stdio.spec.ts index a3069462ff..4f8cf49e71 100644 --- a/packages/ui/stdio/tests/stdio.spec.ts +++ b/packages/ui/stdio/tests/stdio.spec.ts @@ -1,9 +1,9 @@ import { Readable, Writable } from 'node:stream' import { describe, expect, it, vi } from 'vitest' import { Context } from 'cordis' -import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent' -import AgentRegistry from '@deepseek-ai/dsh-agent' +import AgentRegistry, { agentEvents, type Agent, type AgentStatus } from '@deepseek-ai/dsh-agent' import type { ContentBlock, StreamChunk } from '@deepseek-ai/dsh-llm' +import { scopeTarget } from '@deepseek-ai/dsh-scope' import { SessionId, type Session, type SessionEvent } from '@deepseek-ai/dsh-session' import UserInteractionService from '@deepseek-ai/dsh-user-interaction' import { createStdioChat, mountStdio, type Config, type StdioRuntime } from '../src/index.ts' @@ -67,10 +67,26 @@ function makeAgent(id: string, status: AgentStatus = 'idle'): Agent & { /** Register a fake configured agent and cross the supported startup-work boundary. */ function registerReady(ctx: Context, agent: Agent, source: 'startup' | 'resume' = 'startup'): () => void { const dispose = ctx.agents.register(agent) - ctx.emit('agent/session-start', agent, source) + agentEvents(ctx, agent).emit('agent/session-start', source) return dispose } +function emitAgentSessionStart(ctx: Context, agent: Agent, source: 'startup' | 'resume'): void { + agentEvents(ctx, agent).emit('agent/session-start', source) +} + +function emitAgentStatus(ctx: Context, agent: Agent, status: AgentStatus): void { + agentEvents(ctx, agent).emit('agent/status', status) +} + +function emitAgentDisposed(ctx: Context, agent: Agent): void { + agentEvents(ctx, agent).emit('agent/disposed') +} + +function emitSessionEvent(ctx: Context, session: Session, event: SessionEvent): void { + ctx.emit(scopeTarget(session, undefined), 'session/event', session, event) +} + /** A session stub whose `header.id` matches an agent's, for `session/event` emits. */ function makeSession(id: string): Session { return { id, header: { id } } as Session @@ -192,23 +208,23 @@ describe('createStdioChat rendering', () => { it('renders text-delta chunks verbatim', async () => { const { ctx, out } = await setup() - ctx.emit('session/event', makeSession('main'), chunkEvent({ type: 'text-delta', index: 0, text: 'hello' })) + emitSessionEvent(ctx, makeSession('main'), chunkEvent({ type: 'text-delta', index: 0, text: 'hello' })) expect(out.text()).toContain('hello') }) it('wraps reasoning-delta in the dim SGR and resets on the following text-delta', async () => { const { ctx, out } = await setup() const session = makeSession('main') - ctx.emit('session/event', session, chunkEvent({ type: 'reasoning-delta', index: 0, text: 'think' })) - ctx.emit('session/event', session, chunkEvent({ type: 'reasoning-delta', index: 0, text: 'more' })) - ctx.emit('session/event', session, chunkEvent({ type: 'text-delta', index: 0, text: 'answer' })) + emitSessionEvent(ctx, session, chunkEvent({ type: 'reasoning-delta', index: 0, text: 'think' })) + emitSessionEvent(ctx, session, chunkEvent({ type: 'reasoning-delta', index: 0, text: 'more' })) + emitSessionEvent(ctx, session, chunkEvent({ type: 'text-delta', index: 0, text: 'answer' })) expect(out.text()).toContain('\x1B[2mthinkmore\x1B[0m\nanswer') }) it('ignores stream-chunk types it does not render', async () => { const { ctx, out } = await setup() const before = out.text() - ctx.emit('session/event', makeSession('main'), chunkEvent({ type: 'block-start', index: 0, blockType: 'text' })) + emitSessionEvent(ctx, makeSession('main'), chunkEvent({ type: 'block-start', index: 0, blockType: 'text' })) expect(out.text()).toBe(before) }) @@ -217,20 +233,20 @@ describe('createStdioChat rendering', () => { const agent = makeAgent('main') ctx.agents.register(agent) const session = agent.session - ctx.emit('session/event', session, { + emitSessionEvent(ctx, session, { type: 'turn/start', seq: 1, time: 0, data: { turn: 3, trigger: { kind: 'message' } }, } as SessionEvent) expect(out.text()).toContain('[main turn 3] ') - ctx.emit('session/event', session, { + emitSessionEvent(ctx, session, { type: 'turn/end', seq: 2, time: 0, data: { turn: 3, reason: { kind: 'completed' } }, - } as SessionEvent) + }) expect(out.text()).toContain('\n> ') }) it('uses the session id as the label for a non-target session', async () => { const { ctx, out } = await setup() // No target exists, so the event's durable identity is the label. - ctx.emit('session/event', makeSession('orphan'), { + emitSessionEvent(ctx, makeSession('orphan'), { type: 'turn/start', seq: 1, time: 0, data: { turn: 1, trigger: { kind: 'message' } }, } as SessionEvent) expect(out.text()).toContain('[orphan turn 1] ') @@ -253,7 +269,7 @@ describe('createStdioChat rendering', () => { await ctx.plugin(Object.assign((inner: Context) => { createStdioChat(inner, CONFIG, runtime) }, { inject: ['agents', 'userInteraction'] })) - ctx.emit('session/event', agent.session, { + emitSessionEvent(ctx, agent.session, { type: 'turn/start', seq: 1, time: 0, data: { turn: 5, trigger: { kind: 'message' } }, } as SessionEvent) expect(out.text()).toContain('[main turn 5] ') @@ -266,14 +282,14 @@ describe('createStdioChat rendering', () => { const unrelated = makeAgent('unrelated') ctx.agents.register(unrelated) - ctx.emit('agent/session-start', unrelated, 'startup') + emitAgentSessionStart(ctx, unrelated, 'startup') const resumed = makeAgent('resumed') ;(resumed.session.header as { parentSession?: string }).parentSession = 'persisted-parent' ctx.agents.register(resumed) await new Promise(resolve => setImmediate(resolve)) expect(resumed.sent).toEqual([]) - ctx.emit('agent/session-start', resumed, 'resume') + emitAgentSessionStart(ctx, resumed, 'resume') await new Promise(resolve => setImmediate(resolve)) expect(unrelated.sent).toEqual([]) @@ -283,10 +299,10 @@ describe('createStdioChat rendering', () => { it('resets dim styling at turn/end if a turn ends mid-reasoning', async () => { const { ctx, out } = await setup() const session = makeSession('main') - ctx.emit('session/event', session, chunkEvent({ type: 'reasoning-delta', index: 0, text: 'mid' })) - ctx.emit('session/event', session, { + emitSessionEvent(ctx, session, chunkEvent({ type: 'reasoning-delta', index: 0, text: 'mid' })) + emitSessionEvent(ctx, session, { type: 'turn/end', seq: 1, time: 0, data: { turn: 1, reason: { kind: 'completed' } }, - } as SessionEvent) + }) expect(out.text()).toContain('\x1B[2mmid\x1B[0m') }) @@ -297,7 +313,7 @@ describe('createStdioChat rendering', () => { dispose() // After disposal the event belongs to a non-target session, so its durable // identity is rendered directly. - ctx.emit('session/event', agent.session, { + emitSessionEvent(ctx, agent.session, { type: 'turn/start', seq: 1, time: 0, data: { turn: 1, trigger: { kind: 'message' } }, } as SessionEvent) expect(out.text()).toContain('[main turn 1] ') @@ -307,8 +323,8 @@ describe('createStdioChat rendering', () => { const { ctx, out } = await setup() const target = makeAgent('main') ctx.agents.register(target) - ctx.emit('agent/disposed', makeAgent('other')) - ctx.emit('session/event', target.session, { + emitAgentDisposed(ctx, makeAgent('other')) + emitSessionEvent(ctx, target.session, { type: 'turn/start', seq: 1, time: 0, data: { turn: 1, trigger: { kind: 'message' } }, } as SessionEvent) expect(out.text()).toContain('[main turn 1] ') @@ -326,7 +342,7 @@ describe('createStdioChat rendering', () => { input.feed('after hmr') await new Promise(resolve => setImmediate(resolve)) expect(replacement.sent).toEqual([]) - ctx.emit('agent/session-start', replacement, 'resume') + emitAgentSessionStart(ctx, replacement, 'resume') await new Promise(resolve => setImmediate(resolve)) expect(prefixCollision.sent).toEqual([]) @@ -356,28 +372,28 @@ describe('createStdioChat rendering', () => { type: 'tool/call', seq: 1, time: 0, data: { turn: 1, step: 0, callId: 'c1', name: 'bash', arguments: '{"command":"ls"}' }, } as SessionEvent - ctx.emit('session/event', session, callEvent) + emitSessionEvent(ctx, session, callEvent) expect(out.text()).toContain('[tool call] bash({"command":"ls"})') const resultEvent = { type: 'tool/result', seq: 2, time: 0, data: { turn: 1, step: 0, callId: 'c1', content: [{ type: 'text', text: 'file.txt' }], isError: false }, } as SessionEvent - ctx.emit('session/event', session, resultEvent) + emitSessionEvent(ctx, session, resultEvent) expect(out.text()).toContain('[tool result] file.txt') }) it('renders a todo/write session event as a glyphed checklist', async () => { const { ctx, out } = await setup() const session = {} as Session - ctx.emit('session/event', session, { + emitSessionEvent(ctx, session, { type: 'todo/write', seq: 1, time: 0, data: { todos: [ { content: 'read the code', status: 'completed' }, { content: 'write the fix', status: 'in_progress' }, { content: 'run the tests', status: 'pending' }, ] }, - } as SessionEvent) + }) const text = out.text() expect(text).toContain('[todos]') expect(text).toContain('[x] read the code') @@ -387,19 +403,19 @@ describe('createStdioChat rendering', () => { it('resets dim styling when a todo/write interrupts reasoning', async () => { const { ctx, out } = await setup() - ctx.emit('session/event', {} as Session, chunkEvent({ type: 'reasoning-delta', index: 0, text: 'r' })) - ctx.emit('session/event', {} as Session, { + emitSessionEvent(ctx, {} as Session, chunkEvent({ type: 'reasoning-delta', index: 0, text: 'r' })) + emitSessionEvent(ctx, {} as Session, { type: 'todo/write', seq: 1, time: 0, data: { todos: [{ content: 'a task', status: 'pending' }] }, - } as SessionEvent) + }) expect(out.text()).toContain('\x1B[2mr\x1B[0m') }) it('resets dim styling when a tool/call interrupts reasoning', async () => { const { ctx, out } = await setup() const session = {} as Session - ctx.emit('session/event', session, chunkEvent({ type: 'reasoning-delta', index: 0, text: 'r' })) - ctx.emit('session/event', session, { + emitSessionEvent(ctx, session, chunkEvent({ type: 'reasoning-delta', index: 0, text: 'r' })) + emitSessionEvent(ctx, session, { type: 'tool/call', seq: 1, time: 0, data: { turn: 1, step: 0, callId: 'c1', name: 'bash', arguments: '{}' }, } as SessionEvent) @@ -409,10 +425,10 @@ describe('createStdioChat rendering', () => { it('ignores session events it does not render', async () => { const { ctx, out } = await setup() const before = out.text() - ctx.emit('session/event', {} as Session, { + emitSessionEvent(ctx, {} as Session, { type: 'user/message', seq: 1, time: 0, data: { content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } }, - } as SessionEvent) + }) expect(out.text()).toBe(before) }) }) @@ -799,7 +815,7 @@ describe('createStdioChat input', () => { ctx.agents.register(agent) await new Promise(r => setImmediate(r)) expect(agent.sent).toEqual([]) - ctx.emit('agent/session-start', agent, 'startup') + emitAgentSessionStart(ctx, agent, 'startup') await new Promise(r => setImmediate(r)) expect(agent.sent).toEqual([[{ type: 'text', text: 'nobody home' }]]) }) @@ -860,9 +876,9 @@ describe('createStdioChat EOF exit', () => { // Work submitted but no 'running' observed yet — must NOT exit. expect(exit).not.toHaveBeenCalled() // The turn starts, then settles. - ctx.emit('agent/status', agent, 'running') + emitAgentStatus(ctx, agent, 'running') ;(agent as { status: AgentStatus }).status = 'idle' - ctx.emit('agent/status', agent, 'idle') + emitAgentStatus(ctx, agent, 'idle') await flushExit() expect(exit).toHaveBeenCalledWith(0) }) @@ -878,12 +894,12 @@ describe('createStdioChat EOF exit', () => { ctx.agents.register(agent) await new Promise(r => setImmediate(r)) expect(agent.sent).toEqual([]) - ctx.emit('agent/session-start', agent, 'startup') + emitAgentSessionStart(ctx, agent, 'startup') await new Promise(r => setImmediate(r)) expect(agent.sent).toEqual([[{ type: 'text', text: 'work' }]]) - ctx.emit('agent/status', agent, 'running') + emitAgentStatus(ctx, agent, 'running') ;(agent as { status: AgentStatus }).status = 'idle' - ctx.emit('agent/status', agent, 'idle') + emitAgentStatus(ctx, agent, 'idle') await flushExit() expect(exit).toHaveBeenCalledWith(0) }) @@ -913,14 +929,14 @@ describe('createStdioChat EOF exit', () => { registerReady(ctx, agent) input.feed('work') await new Promise(r => setImmediate(r)) - ctx.emit('agent/status', agent, 'running') // sawRunning = true + emitAgentStatus(ctx, agent, 'running') // sawRunning = true input.finish() await new Promise(r => setImmediate(r)) // let readline 'close' set stdinClosed ;(agent as { status: AgentStatus }).status = 'idle' // Two idle signals while stdin is already closed: the first arms the timer, // the second must hit the already-scheduled guard, not arm a second. - ctx.emit('agent/status', agent, 'idle') - ctx.emit('agent/status', agent, 'idle') + emitAgentStatus(ctx, agent, 'idle') + emitAgentStatus(ctx, agent, 'idle') await flushExit() expect(exit).toHaveBeenCalledTimes(1) }) @@ -933,8 +949,8 @@ describe('createStdioChat EOF exit', () => { await new Promise(r => setImmediate(r)) input.finish() const other = makeAgent('other') - ctx.emit('agent/status', other, 'running') - ctx.emit('agent/status', other, 'idle') + emitAgentStatus(ctx, other, 'running') + emitAgentStatus(ctx, other, 'idle') await flushExit() expect(exit).not.toHaveBeenCalled() }) @@ -945,11 +961,11 @@ describe('createStdioChat EOF exit', () => { registerReady(ctx, agent) input.feed('work') await new Promise(r => setImmediate(r)) - ctx.emit('agent/status', agent, 'running') + emitAgentStatus(ctx, agent, 'running') ;(agent as { status: AgentStatus }).status = 'running' input.finish() // sawRunning is true, but the agent is still running — the idle gate holds. - ctx.emit('agent/status', agent, 'idle') // a stale/duplicate signal while status stays 'running' + emitAgentStatus(ctx, agent, 'idle') // a stale/duplicate signal while status stays 'running' await flushExit() expect(exit).not.toHaveBeenCalled() }) @@ -998,8 +1014,8 @@ describe('createStdioChat disposal (HMR safety)', () => { // After dispose, status transitions must neither throw nor schedule an exit // (the listener and the EOF-exit path are both torn down). expect(() => { - ctx.emit('agent/status', agent, 'running') - ctx.emit('agent/status', agent, 'idle') + emitAgentStatus(ctx, agent, 'running') + emitAgentStatus(ctx, agent, 'idle') }).not.toThrow() await flushExit() expect(exit).not.toHaveBeenCalled() diff --git a/packages/ui/stdio/tsconfig.json b/packages/ui/stdio/tsconfig.json index e0c578ed32..c37d7ed522 100644 --- a/packages/ui/stdio/tsconfig.json +++ b/packages/ui/stdio/tsconfig.json @@ -28,6 +28,9 @@ }, { "path": "../user-interaction" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/ui/tool-ask-user/package.json b/packages/ui/tool-ask-user/package.json index 5ee90f0818..d2523ce220 100644 --- a/packages/ui/tool-ask-user/package.json +++ b/packages/ui/tool-ask-user/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,12 +28,14 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", "@deepseek-ai/dsh-user-interaction": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", diff --git a/packages/ui/tool-ask-user/src/invariant.ts b/packages/ui/tool-ask-user/src/invariant.ts new file mode 100644 index 0000000000..eebb1ced42 --- /dev/null +++ b/packages/ui/tool-ask-user/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tool-ask-user`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tool-ask-user/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tool-ask-user' + +/** Cordis companion plugin name. */ +export const name = 'tool-ask-user-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/ui/tool-ask-user/tsconfig.json b/packages/ui/tool-ask-user/tsconfig.json index c779bad37f..6a55e89abe 100644 --- a/packages/ui/tool-ask-user/tsconfig.json +++ b/packages/ui/tool-ask-user/tsconfig.json @@ -31,6 +31,9 @@ }, { "path": "../user-interaction" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/ui/tui/package.json b/packages/ui/tui/package.json index fd4f187e35..35d51fbd2f 100644 --- a/packages/ui/tui/package.json +++ b/packages/ui/tui/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -24,6 +29,7 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-agent-loop": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", @@ -38,6 +44,7 @@ "@cordisjs/plugin-loader": "workspace:^", "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", diff --git a/packages/ui/tui/src/invariant.ts b/packages/ui/tui/src/invariant.ts new file mode 100644 index 0000000000..cba5fd9d2e --- /dev/null +++ b/packages/ui/tui/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tui`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tui/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tui' + +/** Cordis companion plugin name. */ +export const name = 'tui-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/ui/tui/tests/harness.ts b/packages/ui/tui/tests/harness.ts index 9994833308..fcf8105350 100644 --- a/packages/ui/tui/tests/harness.ts +++ b/packages/ui/tui/tests/harness.ts @@ -63,6 +63,11 @@ export async function createTuiTestHarness { await renderAfter(harness, () => { appendUser(harness.session, 'Show the live update.') harness.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 1, + step: 1, chunk: { type: 'block-start', index: 0, blockType: 'reasoning' }, }) harness.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 1, + step: 1, chunk: { type: 'reasoning-delta', index: 0, text: 'Inspecting width and styles.' }, }) harness.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 1, + step: 1, chunk: { type: 'block-start', index: 1, blockType: 'text' }, }) harness.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 1, + step: 1, chunk: { type: 'text-delta', index: 1, text: 'Streaming **visible state**…' }, }) }) @@ -345,9 +346,10 @@ describe('TUI terminal-state snapshots', () => { source: { kind: 'user' }, reason: `Unsafe policy ${CONTROL_PROBE}`, }) + session.append('step/end', { turn: 1, step: 1 }) session.append('turn/end', { - turn: 7, - reason: { kind: 'error', step: 2, message: `Unsafe turn error ${CONTROL_PROBE}` }, + turn: 1, + reason: { kind: 'error', step: 1, message: `Unsafe turn error ${CONTROL_PROBE}` }, }) }, }, { columns: 100, rows: 34 }) @@ -369,7 +371,7 @@ describe('TUI terminal-state snapshots', () => { const rejected = expect(answer).rejects.toMatchObject({ code: 'ASK_ABORTED' }) await harness.terminal.waitForFrame(beforeQuestion) await renderAfter(harness, () => { - harness.ctx.emit('agent/error', harness.agent, 8, 3, new Error(`Unsafe live error ${CONTROL_PROBE}`)) + agentEvents(harness.ctx, harness.agent).emit('agent/error', 8, 3, new Error(`Unsafe live error ${CONTROL_PROBE}`)) }) await checkpoint('untrusted-controls', harness.terminal, { includeScrollback: true }) @@ -427,14 +429,14 @@ describe('TUI terminal-state snapshots', () => { }, { surfaceOp: 'append' }) const assistant = session.append('assistant/message', { turn: 1, - step: 0, + step: 1, provenance: { provider: 'mock', model: 'deepseek-v4-flash' }, content: [{ type: 'tool-call', id: CallId('old-tool'), name: 'bash', arguments: '{}' }], }, { surfaceOp: 'append' }) - session.append('tool/call', { turn: 1, step: 0, callId: CallId('old-tool'), name: 'bash', arguments: '{}' }) + session.append('tool/call', { turn: 1, step: 1, callId: CallId('old-tool'), name: 'bash', arguments: '{}' }) const result = session.append('tool/result', { turn: 1, - step: 0, + step: 1, callId: CallId('old-tool'), content: [{ type: 'text', text: 'obsolete output that must disappear' }], isError: false, @@ -470,13 +472,15 @@ describe('TUI terminal-state snapshots', () => { harness.terminal.send('\r') harness.terminal.send('/unknown-advanced-command') harness.terminal.send('\r') - harness.ctx.emit('agent/error', harness.agent, 3, 1, new Error('provider stream failed after partial output')) + agentEvents(harness.ctx, harness.agent).emit('agent/error', 1, 1, new Error('provider stream failed after partial output')) + harness.session.append('step/end', { turn: 1, step: 1 }) harness.session.append('turn/end', { - turn: 3, + turn: 1, reason: { kind: 'error', step: 1, message: 'provider stream failed after partial output' }, }) + harness.session.append('turn/start', { turn: 2, trigger: { kind: 'message', source: { kind: 'user' } } }) harness.session.append('turn/end', { - turn: 4, + turn: 2, reason: { kind: 'interrupted' }, }) }) diff --git a/packages/ui/tui/tests/tui.spec.ts b/packages/ui/tui/tests/tui.spec.ts index 27200e0fa9..c4520090d7 100644 --- a/packages/ui/tui/tests/tui.spec.ts +++ b/packages/ui/tui/tests/tui.spec.ts @@ -3,7 +3,7 @@ import { join } from 'node:path' import { describe, expect, it, vi } from 'vitest' import { Context } from 'cordis' import type { Terminal } from '@earendil-works/pi-tui' -import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent' +import AgentRegistry, { agentEvents, type Agent } from '@deepseek-ai/dsh-agent' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import type { ToolDefinition } from '@deepseek-ai/dsh-tools' import UserInteractionService from '@deepseek-ai/dsh-user-interaction' @@ -174,7 +174,7 @@ describe('pi-tui chat lifecycle and transcript', () => { expect(result.terminal.output).toContain('↑1.3k ↓42') result.agent.status = 'running' - result.ctx.emit('agent/status', result.agent, 'running') + agentEvents(result.ctx, result.agent).emit('agent/status', 'running') result.session.append('user/message', { content: [{ type: 'text', text: ' ' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) result.session.append('steering/message', { turn: 2, content: [{ type: 'text', text: 'steering note' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) result.session.append('steering/message', { turn: 2, content: [{ type: 'text', text: '' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) @@ -182,69 +182,77 @@ describe('pi-tui chat lifecycle and transcript', () => { result.session.append('context/message', { content: [{ type: 'text', text: '' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) result.session.append('prompt/blocked', { content: [{ type: 'text', text: 'blocked' }], source: { kind: 'user' }, reason: 'test policy' }) appendAssistant(result.session, []) - result.session.append('turn/end', { turn: 9, reason: { kind: 'aborted' } }) - result.session.append('turn/end', { turn: 10, reason: { kind: 'completed' } }) - result.session.append('step/start', { turn: 11, step: 0 }) + result.session.append('step/end', { turn: 1, step: 1 }) + result.session.append('turn/end', { turn: 1, reason: { kind: 'aborted' } }) + result.session.append('turn/start', { turn: 2, trigger: { kind: 'message', source: { kind: 'user' } } }) + result.session.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) + result.session.append('turn/start', { turn: 3, trigger: { kind: 'message', source: { kind: 'user' } } }) + result.session.append('step/start', { turn: 3, step: 1 }) result.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 3, + step: 1, chunk: { type: 'block-start', index: 0, blockType: 'reasoning' }, }) result.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 3, + step: 1, chunk: { type: 'reasoning-delta', index: 0, text: 'live thought' }, }) result.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 3, + step: 1, chunk: { type: 'reasoning-delta', index: 9, text: 'unannounced thought' }, }) result.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 3, + step: 1, chunk: { type: 'block-end', index: 0, block: { type: 'reasoning', text: 'live thought complete' } }, }) result.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 3, + step: 1, chunk: { type: 'block-start', index: 1, blockType: 'text' }, }) result.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 3, + step: 1, chunk: { type: 'text-delta', index: 1, text: 'live answer' }, }) result.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 3, + step: 1, chunk: { type: 'block-end', index: 1, block: { type: 'text', text: 'live answer done' } }, }) result.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 3, + step: 1, chunk: { type: 'block-start', index: 2, blockType: 'tool-call' }, }) result.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 3, + step: 1, chunk: { type: 'block-end', index: 2, block: { type: 'tool-call', id: 'stream-tool' as never, name: 'tool', arguments: '{}' } }, }) result.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 3, + step: 1, chunk: { type: 'tool-call-delta', index: 2, id: 'stream-tool' as never, argumentsDelta: '{}' }, }) result.session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 3, + step: 1, chunk: { type: 'usage', usage: { inputTokens: 1, outputTokens: 2 } }, }) await tick() expect(result.terminal.output).toContain('live thought') result.terminal.send('\x12') await tick() - appendAssistant(result.session, [{ type: 'text', text: 'final live answer' }], { inputTokens: 500, outputTokens: 8 }) + appendAssistant( + result.session, + [{ type: 'text', text: 'final live answer' }], + { inputTokens: 500, outputTokens: 8 }, + { turn: 3, step: 1 }, + ) await tick() expect(result.terminal.output).toContain('Working') @@ -258,17 +266,17 @@ describe('pi-tui chat lifecycle and transcript', () => { result.session.append('assistant/chunk', { turn: 3, - step: 0, + step: 1, chunk: { type: 'text-delta', index: 0, text: 'cleared stream' }, }) result.terminal.send('/clear') result.terminal.send('\r') - appendAssistant(result.session, [{ type: 'text', text: 'answer after clear' }]) + appendAssistant(result.session, [{ type: 'text', text: 'answer after clear' }], undefined, { turn: 3, step: 1 }) await tick() expect(result.terminal.output).toContain('answer after clear') result.agent.status = 'idle' - result.ctx.emit('agent/status', result.agent, 'idle') + agentEvents(result.ctx, result.agent).emit('agent/status', 'idle') await tick() expect(result.terminal.progress.at(-1)).toBe(false) await dispose(result) @@ -324,8 +332,8 @@ describe('pi-tui chat lifecycle and transcript', () => { appendUser(session, 'first prompt') appendUser(session, 'second prompt') session.append('assistant/chunk', { - turn: 2, - step: 0, + turn: 1, + step: 1, chunk: { type: 'text-delta', index: 0, text: 'stale partial response' }, }) }, @@ -442,18 +450,25 @@ describe('pi-tui chat lifecycle and transcript', () => { const events = await setup() const unrelatedSession = events.ctx.sessions.create(SessionId('unrelated-session')) const unrelatedAgent = { ...events.agent, id: unrelatedSession.id, session: unrelatedSession } + unrelatedSession.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) unrelatedSession.append('todo/write', { todos: [{ content: 'hidden', status: 'pending' }] }) - events.ctx.emit('agent/status', unrelatedAgent, 'running') - events.ctx.emit('agent/error', unrelatedAgent, 1, 1, new Error('hidden error')) - events.ctx.emit('agent/disposed', unrelatedAgent) - events.ctx.emit('agent/error', events.agent, 3, 2, new Error('live failure')) - events.session.append('turn/end', { turn: 3, reason: { kind: 'error', step: 2, message: 'live failure' } }) - events.session.append('turn/end', { turn: 4, reason: { kind: 'error', step: 1, message: 'durable failure' } }) - events.session.append('turn/end', { turn: 5, reason: { kind: 'aborted', reason: 'stopped' } }) - events.session.append('turn/end', { turn: 6, reason: { kind: 'max-tokens' } }) - events.session.append('turn/end', { turn: 7, reason: { kind: 'rejected', reason: 'policy' } }) - events.session.append('turn/end', { turn: 8, reason: { kind: 'interrupted' } }) - events.ctx.emit('agent/disposed', events.agent) + agentEvents(events.ctx, unrelatedAgent).emit('agent/status', 'running') + agentEvents(events.ctx, unrelatedAgent).emit('agent/error', 1, 1, new Error('hidden error')) + agentEvents(events.ctx, unrelatedAgent).emit('agent/disposed') + agentEvents(events.ctx, events.agent).emit('agent/error', 1, 1, new Error('live failure')) + events.session.append('step/end', { turn: 1, step: 1 }) + events.session.append('turn/end', { turn: 1, reason: { kind: 'error', step: 1, message: 'live failure' } }) + events.session.append('turn/start', { turn: 2, trigger: { kind: 'message', source: { kind: 'user' } } }) + events.session.append('turn/end', { turn: 2, reason: { kind: 'error', step: 1, message: 'durable failure' } }) + events.session.append('turn/start', { turn: 3, trigger: { kind: 'message', source: { kind: 'user' } } }) + events.session.append('turn/end', { turn: 3, reason: { kind: 'aborted', reason: 'stopped' } }) + events.session.append('turn/start', { turn: 4, trigger: { kind: 'message', source: { kind: 'user' } } }) + events.session.append('turn/end', { turn: 4, reason: { kind: 'max-tokens' } }) + events.session.append('turn/start', { turn: 5, trigger: { kind: 'message', source: { kind: 'user' } } }) + events.session.append('turn/end', { turn: 5, reason: { kind: 'rejected', reason: 'policy' } }) + events.session.append('turn/start', { turn: 6, trigger: { kind: 'message', source: { kind: 'user' } } }) + events.session.append('turn/end', { turn: 6, reason: { kind: 'interrupted' } }) + agentEvents(events.ctx, events.agent).emit('agent/disposed') await tick() expect(events.terminal.output).toContain('live failure') expect(events.terminal.output).toContain('durable failure') @@ -546,7 +561,7 @@ describe('tool cards and surface replay', () => { })), ]) for (const [id, name, args] of calls) { - result.session.append('tool/call', { turn: 1, step: 0, callId: id as never, name, arguments: args }) + result.session.append('tool/call', { turn: 1, step: 1, callId: id as never, name, arguments: args }) } await tick() expect(result.terminal.output).toContain('$ raw command') @@ -556,23 +571,23 @@ describe('tool cards and surface replay', () => { expect(result.terminal.output).toContain('call presenter boom') expect(result.terminal.output).toContain('Symbol(input)') result.session.append('tool/result', { - turn: 1, step: 0, callId: 'c1' as never, content: [{ type: 'text', text: 'raw bash' }], isError: false, + turn: 1, step: 1, callId: 'c1' as never, content: [{ type: 'text', text: 'raw bash' }], isError: false, }, { surfaceOp: 'append' }) result.session.append('tool/result', { - turn: 1, step: 0, callId: 'c2' as never, content: [{ type: 'text', text: 'stopped' }], isError: true, + turn: 1, step: 1, callId: 'c2' as never, content: [{ type: 'text', text: 'stopped' }], isError: true, }, { surfaceOp: 'append' }) result.session.append('tool/result', { - turn: 1, step: 0, callId: 'c3' as never, content: [{ type: 'text', text: 'done' }], isError: false, + turn: 1, step: 1, callId: 'c3' as never, content: [{ type: 'text', text: 'done' }], isError: false, }, { surfaceOp: 'append' }) result.session.append('tool/result', { - turn: 1, step: 0, callId: 'c4' as never, content: [{ type: 'text', text: 'raw generic' }], isError: false, + turn: 1, step: 1, callId: 'c4' as never, content: [{ type: 'text', text: 'raw generic' }], isError: false, }, { surfaceOp: 'append' }) result.session.append('tool/result', { - turn: 1, step: 0, callId: 'c5' as never, content: [{ type: 'text', text: 'raw throwing' }], isError: false, + turn: 1, step: 1, callId: 'c5' as never, content: [{ type: 'text', text: 'raw throwing' }], isError: false, meta: { value: 1 }, }, { surfaceOp: 'append' }) result.session.append('tool/result', { - turn: 1, step: 0, callId: 'c7' as never, + turn: 1, step: 1, callId: 'c7' as never, content: [ { type: 'tool-call', id: 'inner' as never, name: 'inner', arguments: '{}' }, { type: 'tool-result', toolCallId: 'inner' as never, content: [{ type: 'text', text: 'nested output' }] }, @@ -581,13 +596,18 @@ describe('tool cards and surface replay', () => { isError: false, }, { surfaceOp: 'append' }) result.session.append('tool/result', { - turn: 1, step: 0, callId: 'c8' as never, content: [{ type: 'text', text: '\nundefined presenter output\n\nkept tail\n' }], isError: false, + turn: 1, step: 1, callId: 'c8' as never, content: [{ type: 'text', text: '\nundefined presenter output\n\nkept tail\n' }], isError: false, }, { surfaceOp: 'append' }) result.session.append('tool/result', { - turn: 1, step: 0, callId: 'c11' as never, content: [{ type: 'text', text: '\nconverted terminal\n\nfinished\n' }], isError: false, + turn: 1, step: 1, callId: 'c11' as never, content: [{ type: 'text', text: '\nconverted terminal\n\nfinished\n' }], isError: false, }, { surfaceOp: 'append' }) result.session.append('tool/result', { - turn: 1, step: 0, callId: 'orphan' as never, content: [{ type: 'text', text: 'orphan result' }], isError: false, + turn: 1, + step: 1, + callId: 'orphan' as never, + content: [{ type: 'text', text: 'orphan result' }], + isError: true, + error: { name: 'InterruptedError', code: 'interrupted' }, }, { surfaceOp: 'append' }) await tick() @@ -623,15 +643,15 @@ describe('tool cards and surface replay', () => { appendUser(result.session, 'old prompt') const assistant = result.session.append('assistant/message', { turn: 1, - step: 0, + step: 1, provenance: { provider: 'mock', model: 'deepseek-v4-flash' }, content: [{ type: 'tool-call', id: 'old-call' as never, name: 'bash', arguments: '{}' }], }, { surfaceOp: 'append' }) result.session.append('tool/call', { - turn: 1, step: 0, callId: 'old-call' as never, name: 'bash', arguments: '{}', + turn: 1, step: 1, callId: 'old-call' as never, name: 'bash', arguments: '{}', }) const toolResult = result.session.append('tool/result', { - turn: 1, step: 0, callId: 'old-call' as never, content: [{ type: 'text', text: 'old output' }], isError: false, + turn: 1, step: 1, callId: 'old-call' as never, content: [{ type: 'text', text: 'old output' }], isError: false, }, { surfaceOp: 'append' }) const start = result.session.surface.nodes[0] as number result.session.append('context/message', { @@ -904,6 +924,8 @@ describe('terminal mounting', () => { await ctx.plugin(UserInteractionService) ctx.provide('tools', { get: () => undefined } as never) const session = ctx.sessions.create(SessionId('failed-start-session')) + session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } }) + session.append('step/start', { turn: 1, step: 1 }) ctx.agents.register({ id: session.id, options: {}, session, status: 'running', ctx, send() {}, steer() {}, inject() {}, cancel() {}, whenIdle: () => Promise.resolve(), @@ -919,7 +941,7 @@ describe('terminal mounting', () => { .rejects.toMatchObject({ code: 'NO_PROVIDER' }) session.append('assistant/chunk', { turn: 1, - step: 0, + step: 1, chunk: { type: 'text-delta', index: 0, text: 'must not render' }, }) await tick() diff --git a/packages/ui/tui/tsconfig.json b/packages/ui/tui/tsconfig.json index 3a09f80ad8..134b87248b 100644 --- a/packages/ui/tui/tsconfig.json +++ b/packages/ui/tui/tsconfig.json @@ -31,6 +31,9 @@ }, { "path": "../user-interaction" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/ui/user-approval/package.json b/packages/ui/user-approval/package.json index 1696a7b603..098a0cc44b 100644 --- a/packages/ui/user-approval/package.json +++ b/packages/ui/user-approval/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -24,6 +29,7 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-brand": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-scope": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", @@ -36,6 +42,7 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-brand": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-scope": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", diff --git a/packages/ui/user-approval/src/invariant.ts b/packages/ui/user-approval/src/invariant.ts new file mode 100644 index 0000000000..73dee4f9f1 --- /dev/null +++ b/packages/ui/user-approval/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-user-approval`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-user-approval/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-user-approval' + +/** Cordis companion plugin name. */ +export const name = 'user-approval-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/ui/user-approval/tests/approval.spec.ts b/packages/ui/user-approval/tests/approval.spec.ts index 5592063e45..fe2643cb3f 100644 --- a/packages/ui/user-approval/tests/approval.spec.ts +++ b/packages/ui/user-approval/tests/approval.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' import { Context } from 'cordis' -import type { Agent } from '@deepseek-ai/dsh-agent' +import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent' import { CallId } from '@deepseek-ai/dsh-llm' import { carrierKeyOf, createScope } from '@deepseek-ai/dsh-scope' import type { Scope } from '@deepseek-ai/dsh-scope' @@ -371,7 +371,7 @@ describe('approval policy (the approval/policy fold)', () => { } const preStep = (ctx: Context, agent: Agent): Promise => - ctx.serial('agent/pre-step', agent, 1, 1, new AbortController().signal) + agentEvents(ctx, agent).serial('agent/pre-step', 1, 1, new AbortController().signal) /** Append a `request/header` snapshot whose system text is exactly `system`. */ function appendHeader(session: Session, system: string): void { diff --git a/packages/ui/user-approval/tsconfig.json b/packages/ui/user-approval/tsconfig.json index fb9a9e6e2d..2fe19338ca 100644 --- a/packages/ui/user-approval/tsconfig.json +++ b/packages/ui/user-approval/tsconfig.json @@ -34,6 +34,9 @@ }, { "path": "../../core/system-prompt" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/ui/user-interaction/package.json b/packages/ui/user-interaction/package.json index f4c9c411fd..bcabc951df 100644 --- a/packages/ui/user-interaction/package.json +++ b/packages/ui/user-interaction/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,11 +28,13 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/ui/user-interaction/src/invariant.ts b/packages/ui/user-interaction/src/invariant.ts new file mode 100644 index 0000000000..262681fc06 --- /dev/null +++ b/packages/ui/user-interaction/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-user-interaction`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-user-interaction/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-user-interaction' + +/** Cordis companion plugin name. */ +export const name = 'user-interaction-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/ui/user-interaction/tsconfig.json b/packages/ui/user-interaction/tsconfig.json index 178ff39f3f..1361d87c20 100644 --- a/packages/ui/user-interaction/tsconfig.json +++ b/packages/ui/user-interaction/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../../llm/llm" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/util/brand/package.json b/packages/util/brand/package.json index 7074aaa621..51ce4d795d 100644 --- a/packages/util/brand/package.json +++ b/packages/util/brand/package.json @@ -11,20 +11,27 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/util/brand/src/invariant.ts b/packages/util/brand/src/invariant.ts new file mode 100644 index 0000000000..e932e7e35e --- /dev/null +++ b/packages/util/brand/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-brand`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-brand/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-brand' + +/** Cordis companion plugin name. */ +export const name = 'brand-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/util/brand/tsconfig.json b/packages/util/brand/tsconfig.json index 749cb0208e..d970a00263 100644 --- a/packages/util/brand/tsconfig.json +++ b/packages/util/brand/tsconfig.json @@ -7,5 +7,9 @@ "include": [ "src" ], - "references": [] + "references": [ + { + "path": "../../support/invariants" + } + ] } diff --git a/packages/util/home/package.json b/packages/util/home/package.json index efeaf4832c..07795b1a07 100644 --- a/packages/util/home/package.json +++ b/packages/util/home/package.json @@ -11,20 +11,27 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.6" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.6" } } diff --git a/packages/util/home/src/invariant.ts b/packages/util/home/src/invariant.ts new file mode 100644 index 0000000000..5874a57c1c --- /dev/null +++ b/packages/util/home/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-home`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-home/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-home' + +/** Cordis companion plugin name. */ +export const name = 'home-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/util/home/tsconfig.json b/packages/util/home/tsconfig.json index 9770ef25d6..67d4461281 100644 --- a/packages/util/home/tsconfig.json +++ b/packages/util/home/tsconfig.json @@ -5,5 +5,9 @@ "outDir": "lib/types" }, "include": ["src"], - "references": [] + "references": [ + { + "path": "../../support/invariants" + } + ] } diff --git a/packages/util/paths/package.json b/packages/util/paths/package.json index b4f760afe9..601a2941b2 100644 --- a/packages/util/paths/package.json +++ b/packages/util/paths/package.json @@ -11,20 +11,27 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.6" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.6" } } diff --git a/packages/util/paths/src/invariant.ts b/packages/util/paths/src/invariant.ts new file mode 100644 index 0000000000..c2cedfbb0d --- /dev/null +++ b/packages/util/paths/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-paths`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-paths/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-paths' + +/** Cordis companion plugin name. */ +export const name = 'paths-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/util/paths/tsconfig.json b/packages/util/paths/tsconfig.json index 749cb0208e..d970a00263 100644 --- a/packages/util/paths/tsconfig.json +++ b/packages/util/paths/tsconfig.json @@ -7,5 +7,9 @@ "include": [ "src" ], - "references": [] + "references": [ + { + "path": "../../support/invariants" + } + ] } diff --git a/packages/util/retention/package.json b/packages/util/retention/package.json index db8bab3342..312bebd624 100644 --- a/packages/util/retention/package.json +++ b/packages/util/retention/package.json @@ -11,20 +11,27 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.6" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.6" } } diff --git a/packages/util/retention/src/invariant.ts b/packages/util/retention/src/invariant.ts new file mode 100644 index 0000000000..7516f9e5ed --- /dev/null +++ b/packages/util/retention/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-retention`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-retention/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-retention' + +/** Cordis companion plugin name. */ +export const name = 'retention-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/util/retention/tsconfig.json b/packages/util/retention/tsconfig.json index 749cb0208e..d970a00263 100644 --- a/packages/util/retention/tsconfig.json +++ b/packages/util/retention/tsconfig.json @@ -7,5 +7,9 @@ "include": [ "src" ], - "references": [] + "references": [ + { + "path": "../../support/invariants" + } + ] } diff --git a/packages/util/timeout/package.json b/packages/util/timeout/package.json index 381b9d269e..615d21759e 100644 --- a/packages/util/timeout/package.json +++ b/packages/util/timeout/package.json @@ -11,20 +11,27 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/util/timeout/src/invariant.ts b/packages/util/timeout/src/invariant.ts new file mode 100644 index 0000000000..15140bb880 --- /dev/null +++ b/packages/util/timeout/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-timeout`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-timeout/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-timeout' + +/** Cordis companion plugin name. */ +export const name = 'timeout-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/util/timeout/tsconfig.json b/packages/util/timeout/tsconfig.json index 749cb0208e..d970a00263 100644 --- a/packages/util/timeout/tsconfig.json +++ b/packages/util/timeout/tsconfig.json @@ -7,5 +7,9 @@ "include": [ "src" ], - "references": [] + "references": [ + { + "path": "../../support/invariants" + } + ] } diff --git a/packages/web/tool-web/package.json b/packages/web/tool-web/package.json index 23ed7157a2..ec1d33f4d4 100644 --- a/packages/web/tool-web/package.json +++ b/packages/web/tool-web/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-system-prompt": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", @@ -33,13 +39,14 @@ }, "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-spill-local": "workspace:^", "@deepseek-ai/dsh-spill-policy": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", - "@deepseek-ai/dsh-tools": "workspace:^", "@deepseek-ai/dsh-timeout-policy": "workspace:^", + "@deepseek-ai/dsh-tools": "workspace:^", "@deepseek-ai/dsh-web": "workspace:^", "@deepseek-ai/dsh-web-fetch-local": "workspace:^", "@deepseek-ai/dsh-web-search-exa": "workspace:^", diff --git a/packages/web/tool-web/src/invariant.ts b/packages/web/tool-web/src/invariant.ts new file mode 100644 index 0000000000..008fe2f5e1 --- /dev/null +++ b/packages/web/tool-web/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tool-web`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tool-web/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tool-web' + +/** Cordis companion plugin name. */ +export const name = 'tool-web-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/web/tool-web/tsconfig.json b/packages/web/tool-web/tsconfig.json index 5226425ec6..f6684272ce 100644 --- a/packages/web/tool-web/tsconfig.json +++ b/packages/web/tool-web/tsconfig.json @@ -6,13 +6,32 @@ }, "include": ["src"], "references": [ - { "path": "../../../vendor/cosmokit" }, - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../../llm/llm" }, - { "path": "../../core/tools" }, - { "path": "../../core/system-prompt" }, - { "path": "../../timeout/timeout-policy" }, - { "path": "../web" } + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../core/tools" + }, + { + "path": "../../core/system-prompt" + }, + { + "path": "../../timeout/timeout-policy" + }, + { + "path": "../web" + }, + { + "path": "../../support/invariants" + } ] } diff --git a/packages/web/web-fetch-local/package.json b/packages/web/web-fetch-local/package.json index 1e1d7ea71b..5c42bfa09c 100644 --- a/packages/web/web-fetch-local/package.json +++ b/packages/web/web-fetch-local/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-timeout": "^0.0.1", "@deepseek-ai/dsh-web": "^0.0.1", "cordis": "^4.0.0-rc.7" @@ -30,6 +36,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-timeout": "workspace:^", "@deepseek-ai/dsh-web": "workspace:^", "cordis": "^4.0.0-rc.7" diff --git a/packages/web/web-fetch-local/src/invariant.ts b/packages/web/web-fetch-local/src/invariant.ts new file mode 100644 index 0000000000..ef61e2a611 --- /dev/null +++ b/packages/web/web-fetch-local/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-web-fetch-local`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-web-fetch-local/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-web-fetch-local' + +/** Cordis companion plugin name. */ +export const name = 'web-fetch-local-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/web/web-fetch-local/tsconfig.json b/packages/web/web-fetch-local/tsconfig.json index c6fb75a5c1..29e7c5078c 100644 --- a/packages/web/web-fetch-local/tsconfig.json +++ b/packages/web/web-fetch-local/tsconfig.json @@ -22,6 +22,9 @@ }, { "path": "../web" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/web/web-search-deepseek/package.json b/packages/web/web-search-deepseek/package.json index 42471006c0..b38f552957 100644 --- a/packages/web/web-search-deepseek/package.json +++ b/packages/web/web-search-deepseek/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-web": "^0.0.1", "cordis": "^4.0.0-rc.7" }, @@ -29,6 +35,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-web": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/web/web-search-deepseek/src/invariant.ts b/packages/web/web-search-deepseek/src/invariant.ts new file mode 100644 index 0000000000..8781949be9 --- /dev/null +++ b/packages/web/web-search-deepseek/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-web-search-deepseek`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-web-search-deepseek/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-web-search-deepseek' + +/** Cordis companion plugin name. */ +export const name = 'web-search-deepseek-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/web/web-search-deepseek/tsconfig.json b/packages/web/web-search-deepseek/tsconfig.json index aa7c949fec..e9610ea5c9 100644 --- a/packages/web/web-search-deepseek/tsconfig.json +++ b/packages/web/web-search-deepseek/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../web" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/web/web-search-exa/package.json b/packages/web/web-search-exa/package.json index 240909e24a..7d6b802d2e 100644 --- a/packages/web/web-search-exa/package.json +++ b/packages/web/web-search-exa/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-web": "^0.0.1", "cordis": "^4.0.0-rc.7" }, @@ -29,6 +35,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-web": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/web/web-search-exa/src/invariant.ts b/packages/web/web-search-exa/src/invariant.ts new file mode 100644 index 0000000000..a2dd956625 --- /dev/null +++ b/packages/web/web-search-exa/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-web-search-exa`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-web-search-exa/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-web-search-exa' + +/** Cordis companion plugin name. */ +export const name = 'web-search-exa-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/web/web-search-exa/tsconfig.json b/packages/web/web-search-exa/tsconfig.json index aa7c949fec..e9610ea5c9 100644 --- a/packages/web/web-search-exa/tsconfig.json +++ b/packages/web/web-search-exa/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../web" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/web/web-search-perplexity/package.json b/packages/web/web-search-perplexity/package.json index 26d077fada..9aa7080431 100644 --- a/packages/web/web-search-perplexity/package.json +++ b/packages/web/web-search-perplexity/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-web": "^0.0.1", "cordis": "^4.0.0-rc.7" }, @@ -29,6 +35,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-web": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/web/web-search-perplexity/src/invariant.ts b/packages/web/web-search-perplexity/src/invariant.ts new file mode 100644 index 0000000000..fe82c79dae --- /dev/null +++ b/packages/web/web-search-perplexity/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-web-search-perplexity`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-web-search-perplexity/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-web-search-perplexity' + +/** Cordis companion plugin name. */ +export const name = 'web-search-perplexity-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/web/web-search-perplexity/tsconfig.json b/packages/web/web-search-perplexity/tsconfig.json index aa7c949fec..e9610ea5c9 100644 --- a/packages/web/web-search-perplexity/tsconfig.json +++ b/packages/web/web-search-perplexity/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../web" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/web/web/package.json b/packages/web/web/package.json index b94f7ba685..7b732ec819 100644 --- a/packages/web/web/package.json +++ b/packages/web/web/package.json @@ -11,17 +11,23 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" ], "license": "BSD-3-Clause", "peerDependencies": { + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "cordis": "^4.0.0-rc.7" }, @@ -29,6 +35,7 @@ "schemastery": "^3.18.0" }, "devDependencies": { + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "cordis": "^4.0.0-rc.7" } diff --git a/packages/web/web/src/invariant.ts b/packages/web/web/src/invariant.ts new file mode 100644 index 0000000000..b9b1b0d45d --- /dev/null +++ b/packages/web/web/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-web`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-web/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-web' + +/** Cordis companion plugin name. */ +export const name = 'web-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/web/web/tsconfig.json b/packages/web/web/tsconfig.json index e9de391ba1..d145ddb6ee 100644 --- a/packages/web/web/tsconfig.json +++ b/packages/web/web/tsconfig.json @@ -19,6 +19,9 @@ }, { "path": "../../llm/llm" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/workflow/tool-workflow/package.json b/packages/workflow/tool-workflow/package.json index 327e6ec3a9..dd055ad9ec 100644 --- a/packages/workflow/tool-workflow/package.json +++ b/packages/workflow/tool-workflow/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -23,6 +28,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-system-prompt": "^0.0.1", "@deepseek-ai/dsh-tools": "^0.0.1", @@ -34,6 +40,7 @@ }, "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-subagent": "workspace:^", diff --git a/packages/workflow/tool-workflow/src/invariant.ts b/packages/workflow/tool-workflow/src/invariant.ts new file mode 100644 index 0000000000..cd1f0e475b --- /dev/null +++ b/packages/workflow/tool-workflow/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-tool-workflow`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-tool-workflow/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-tool-workflow' + +/** Cordis companion plugin name. */ +export const name = 'tool-workflow-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/workflow/tool-workflow/tsconfig.json b/packages/workflow/tool-workflow/tsconfig.json index f66eda75a7..c08ae597f2 100644 --- a/packages/workflow/tool-workflow/tsconfig.json +++ b/packages/workflow/tool-workflow/tsconfig.json @@ -31,6 +31,9 @@ }, { "path": "../workflow" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/workflow/workflow-workerthread/package.json b/packages/workflow/workflow-workerthread/package.json index 1cd6e07e17..a915229449 100644 --- a/packages/workflow/workflow-workerthread/package.json +++ b/packages/workflow/workflow-workerthread/package.json @@ -11,6 +11,10 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./worker": { "types": "./lib/types/worker.d.ts", "default": "./lib/worker.cjs" @@ -20,6 +24,7 @@ }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/worker.cjs", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", @@ -29,6 +34,7 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-brand": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-subagent": "^0.0.1", diff --git a/packages/workflow/workflow-workerthread/src/invariant.ts b/packages/workflow/workflow-workerthread/src/invariant.ts new file mode 100644 index 0000000000..6bcc40862c --- /dev/null +++ b/packages/workflow/workflow-workerthread/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-workflow-workerthread`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-workflow-workerthread/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-workflow-workerthread' + +/** Cordis companion plugin name. */ +export const name = 'workflow-workerthread-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/workflow/workflow-workerthread/tsconfig.json b/packages/workflow/workflow-workerthread/tsconfig.json index 730a3e61d9..37d90aaea1 100644 --- a/packages/workflow/workflow-workerthread/tsconfig.json +++ b/packages/workflow/workflow-workerthread/tsconfig.json @@ -37,6 +37,9 @@ }, { "path": "../workflow" + }, + { + "path": "../../support/invariants" } ] } diff --git a/packages/workflow/workflow-workerthread/tsdown.config.ts b/packages/workflow/workflow-workerthread/tsdown.config.ts index 8ebd93d89f..962a3d9078 100644 --- a/packages/workflow/workflow-workerthread/tsdown.config.ts +++ b/packages/workflow/workflow-workerthread/tsdown.config.ts @@ -7,7 +7,7 @@ import { defineConfig } from 'tsdown' */ export default defineConfig([ { - entry: ['lib/types/index.js'], + entry: ['lib/types/index.js', 'lib/types/invariant.js'], outDir: 'lib', format: ['esm'], platform: 'node', diff --git a/packages/workflow/workflow/package.json b/packages/workflow/workflow/package.json index 476866382a..b8ce1ebc7f 100644 --- a/packages/workflow/workflow/package.json +++ b/packages/workflow/workflow/package.json @@ -11,11 +11,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, + "./invariant": { + "types": "./lib/types/invariant.d.ts", + "default": "./lib/invariant.js" + }, "./src/*": "./src/*", "./package.json": "./package.json" }, "files": [ "lib/index.js", + "lib/invariant.js", "lib/types/**/*.d.ts", "lib/types/**/*.d.ts.map", "src" @@ -24,6 +29,7 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-brand": "^0.0.1", + "@deepseek-ai/dsh-invariants": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "cordis": "^4.0.0-rc.7" @@ -31,6 +37,7 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-brand": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "cordis": "^4.0.0-rc.7" diff --git a/packages/workflow/workflow/src/invariant.ts b/packages/workflow/workflow/src/invariant.ts new file mode 100644 index 0000000000..b552021ca4 --- /dev/null +++ b/packages/workflow/workflow/src/invariant.ts @@ -0,0 +1,30 @@ +/** + * Generated invariant ownership companion for `@deepseek-ai/dsh-workflow`. + * Replace this file with package-owned checks while preserving its registration. + * + * @generated scripts/gen-package-invariants.ts + * @module @deepseek-ai/dsh-workflow/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' + +const PACKAGE_NAME = '@deepseek-ai/dsh-workflow' + +/** Cordis companion plugin name. */ +export const name = 'workflow-invariant' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ diff --git a/packages/workflow/workflow/tsconfig.json b/packages/workflow/workflow/tsconfig.json index 6ec42e0bfe..76ad9f725a 100644 --- a/packages/workflow/workflow/tsconfig.json +++ b/packages/workflow/workflow/tsconfig.json @@ -22,6 +22,9 @@ }, { "path": "../../llm/llm" + }, + { + "path": "../../support/invariants" } ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43aae285c7..b13f346cd5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -209,6 +209,9 @@ importers: packages/bash/bash: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-sandbox': specifier: workspace:^ version: link:../../sandbox/sandbox @@ -228,6 +231,9 @@ importers: '@deepseek-ai/dsh-bash': specifier: workspace:^ version: link:../bash + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-timeout': specifier: workspace:^ version: link:../../util/timeout @@ -247,6 +253,9 @@ importers: '@deepseek-ai/dsh-bash-local': specifier: workspace:^ version: link:../bash-local + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-sandbox': specifier: workspace:^ version: link:../../sandbox/sandbox @@ -284,6 +293,9 @@ importers: '@deepseek-ai/dsh-home': specifier: workspace:^ version: link:../../util/home + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -320,6 +332,9 @@ importers: packages/code-runtime/code-runtime: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) @@ -333,12 +348,18 @@ importers: '@deepseek-ai/dsh-code-runtime': specifier: workspace:^ version: link:../code-runtime + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) packages/compact/compact: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -407,6 +428,9 @@ importers: '@deepseek-ai/dsh-agent-loop-testkit': specifier: workspace:^ version: link:../../support/agent-loop-testkit + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -447,6 +471,9 @@ importers: '@deepseek-ai/dsh-fs-local': specifier: workspace:^ version: link:../../fs/fs-local + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -493,6 +520,9 @@ importers: '@deepseek-ai/dsh-agent-loop-testkit': specifier: workspace:^ version: link:../../support/agent-loop-testkit + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -606,6 +636,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -628,6 +661,9 @@ importers: '@deepseek-ai/dsh-code-runtime': specifier: workspace:^ version: link:../../code-runtime/code-runtime + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -667,6 +703,9 @@ importers: '@deepseek-ai/dsh-app-boot': specifier: workspace:^ version: link:../../ui/app-boot + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ version: link:../../session-persistence/session-persistence-jsonl @@ -770,6 +809,9 @@ importers: '@deepseek-ai/dsh-app-boot': specifier: workspace:^ version: link:../../ui/app-boot + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -801,6 +843,9 @@ importers: specifier: workspace:^ version: link:../../ui/app-boot devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) @@ -828,6 +873,9 @@ importers: '@deepseek-ai/dsh-app-boot': specifier: workspace:^ version: link:../../ui/app-boot + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -870,6 +918,9 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:^ version: link:../../util/brand + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -886,6 +937,9 @@ importers: '@deepseek-ai/dsh-fs': specifier: workspace:^ version: link:../fs + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -898,6 +952,9 @@ importers: '@deepseek-ai/dsh-fs': specifier: workspace:^ version: link:../fs + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -932,6 +989,9 @@ importers: '@deepseek-ai/dsh-fs-policy': specifier: workspace:^ version: link:../fs-policy + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -966,6 +1026,9 @@ importers: '@deepseek-ai/dsh-bash-local': specifier: workspace:^ version: link:../../bash/bash-local + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1003,6 +1066,9 @@ importers: '@deepseek-ai/dsh-agent-loop-testkit': specifier: workspace:^ version: link:../../support/agent-loop-testkit + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1021,6 +1087,9 @@ importers: '@deepseek-ai/dsh-bash': specifier: workspace:^ version: link:../../bash/bash + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session @@ -1052,6 +1121,9 @@ importers: '@deepseek-ai/dsh-hook-protocol': specifier: workspace:^ version: link:../hook-protocol + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1098,6 +1170,9 @@ importers: '@deepseek-ai/dsh-hook-protocol': specifier: workspace:^ version: link:../hook-protocol + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1122,6 +1197,9 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:^ version: link:../../util/brand + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) @@ -1132,6 +1210,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../llm @@ -1148,6 +1229,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../llm @@ -1164,6 +1248,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../llm @@ -1183,6 +1270,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1204,6 +1294,9 @@ importers: packages/sandbox/sandbox: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1220,6 +1313,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1239,6 +1335,9 @@ importers: specifier: ^15.0.0 version: 15.0.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) @@ -1270,6 +1369,9 @@ importers: '@deepseek-ai/dsh-hooks-codex': specifier: workspace:^ version: link:../../hooks/hooks-codex + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ version: link:../../session-persistence/session-persistence-jsonl @@ -1301,6 +1403,9 @@ importers: '@deepseek-ai/dsh-app-boot': specifier: workspace:^ version: link:../../ui/app-boot + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) @@ -1313,6 +1418,12 @@ importers: packages/session-persistence/session-persistence: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-scope': + specifier: workspace:^ + version: link:../../core/scope '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session @@ -1326,6 +1437,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session @@ -1342,6 +1456,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session @@ -1358,6 +1475,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1377,6 +1497,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) @@ -1396,6 +1519,9 @@ importers: '@deepseek-ai/dsh-home': specifier: workspace:^ version: link:../../util/home + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-skill': specifier: workspace:^ version: link:../skill @@ -1412,6 +1538,9 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1436,6 +1565,9 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:^ version: link:../../util/brand + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1455,6 +1587,9 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:^ version: link:../../util/brand + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1477,6 +1612,9 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1504,6 +1642,9 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:^ version: link:../../util/brand + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1535,6 +1676,9 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1678,6 +1822,9 @@ importers: packages/subagent/subagent-subprocess: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) @@ -1694,6 +1841,9 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1728,6 +1878,9 @@ importers: specifier: ^4.1.8 version: 4.1.8(@types/node@25.9.3)(@vitest/coverage-v8@4.1.8)(jsdom@29.1.1)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.22.4)(yaml@2.9.0)) devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../invariants cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) @@ -1740,6 +1893,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1768,6 +1924,9 @@ importers: packages/support/llm-replay: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1784,6 +1943,9 @@ importers: specifier: ^4.22.4 version: 4.22.4 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../invariants cordis: specifier: ^4.0.0-rc.6 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) @@ -1796,6 +1958,9 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:^ version: link:../../util/brand + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session @@ -1815,6 +1980,9 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1836,6 +2004,9 @@ importers: packages/timeout/timeout-policy: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1860,6 +2031,9 @@ importers: '@deepseek-ai/dsh-agent-loop-testkit': specifier: workspace:^ version: link:../../support/agent-loop-testkit + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1966,6 +2140,9 @@ importers: '@cordisjs/plugin-loader': specifier: workspace:^ version: link:../../../vendor/loader + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@vendor+include)(@cordisjs/plugin-loader@vendor+loader) @@ -1985,6 +2162,9 @@ importers: '@deepseek-ai/dsh-agent-spine-demo': specifier: workspace:^ version: link:../../examples/agent-spine-demo + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -2016,6 +2196,9 @@ importers: '@deepseek-ai/dsh-bash': specifier: workspace:^ version: link:../../bash/bash + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-sandbox': specifier: workspace:^ version: link:../../sandbox/sandbox @@ -2044,6 +2227,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -2062,6 +2248,9 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -2096,6 +2285,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -2139,6 +2331,9 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:^ version: link:../../util/brand + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -2160,6 +2355,9 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -2169,30 +2367,45 @@ importers: packages/util/brand: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) packages/util/home: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.6 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) packages/util/paths: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.6 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) packages/util/retention: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.6 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) packages/util/timeout: devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants cordis: specifier: ^4.0.0-rc.7 version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5) @@ -2206,6 +2419,9 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -2246,6 +2462,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -2259,6 +2478,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-timeout': specifier: workspace:^ version: link:../../util/timeout @@ -2275,6 +2497,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-web': specifier: workspace:^ version: link:../web @@ -2288,6 +2513,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-web': specifier: workspace:^ version: link:../web @@ -2301,6 +2529,9 @@ importers: specifier: ^3.18.0 version: 3.18.0 devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-web': specifier: workspace:^ version: link:../web @@ -2317,6 +2548,9 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -2350,6 +2584,9 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:^ version: link:../../util/brand + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm diff --git a/scripts/check-workspace-constraints.ts b/scripts/check-workspace-constraints.ts index 224f8540b8..2a9cf01bdb 100644 --- a/scripts/check-workspace-constraints.ts +++ b/scripts/check-workspace-constraints.ts @@ -93,37 +93,6 @@ function workspaceManifests(): WorkspaceManifest[] { return manifests } -const dshPackageFiles = [ - 'lib/index.js', - 'lib/types/**/*.d.ts', - 'lib/types/**/*.d.ts.map', - 'src', -] as const - -const dshBinPackageFiles = [ - 'lib/index.js', - 'lib/bin.js', - 'lib/types/**/*.d.ts', - 'lib/types/**/*.d.ts.map', - 'src', -] as const - -const dshWorkerPackageFiles = [ - 'lib/index.js', - 'lib/worker.cjs', - 'lib/types/**/*.d.ts', - 'lib/types/**/*.d.ts.map', - 'src', -] as const - -const dshInvariantPackageFiles = [ - 'lib/index.js', - 'lib/invariant.js', - 'lib/types/**/*.d.ts', - 'lib/types/**/*.d.ts.map', - 'src', -] as const - const packageFileExtras: Readonly> = { '@deepseek-ai/dsh-helper': ['lib/assets'], '@deepseek-ai/dsh-scripts': [ @@ -139,25 +108,18 @@ function sameStringList(actual: readonly string[] | undefined, expected: readonl function expectedDshPackageFiles(manifest: PackageManifest): readonly string[] { const extras = manifest.name ? packageFileExtras[manifest.name] ?? [] : [] - if (extras.length > 0) { - return [ - 'lib/index.js', - ...manifest.bin ? ['lib/bin.js'] : [], - ...extras, - 'lib/types/**/*.d.ts', - 'lib/types/**/*.d.ts.map', - 'src', - ] - } - if (manifest.bin) return dshBinPackageFiles - // Package-owned diagnostic companions are separately bundled optional - // entries; their source stays in the owner package without bloating root. - if (manifest.exports?.['./invariant']) return dshInvariantPackageFiles - // A declared "./worker" subpath export sanctions the one extra runtime - // bundle a worker-thread entry needs (and NodeNext/publint then validate - // that subpath's targets like any other export). - if (manifest.exports?.['./worker']) return dshWorkerPackageFiles - return dshPackageFiles + return [ + 'lib/index.js', + // Every package publishes its invariant ownership companion as a separate + // bundle; the package-invariant gate validates the companion itself. + 'lib/invariant.js', + ...manifest.bin ? ['lib/bin.js'] : [], + ...manifest.exports?.['./worker'] ? ['lib/worker.cjs'] : [], + ...extras, + 'lib/types/**/*.d.ts', + 'lib/types/**/*.d.ts.map', + 'src', + ] } function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] { diff --git a/scripts/gen-package-invariants.ts b/scripts/gen-package-invariants.ts new file mode 100644 index 0000000000..d30ce5a3eb --- /dev/null +++ b/scripts/gen-package-invariants.ts @@ -0,0 +1,44 @@ +/** Generate or verify package-owned invariant companion baselines. */ + +import { readFileSync, writeFileSync } from 'node:fs' +import { resolve } from 'node:path' +import { + GENERATED_INVARIANT_MARKER, + collectPackageInvariantViolations, + formatPackageInvariantViolation, + packageInvariantOwners, + renderBaselineInvariant, +} from './package-invariants.ts' + +const root = resolve(import.meta.dirname, '..') +const check = process.argv.includes('--check') + +if (!check) { + let generated = 0 + for (const owner of packageInvariantOwners(root)) { + const path = resolve(root, owner.sourcePath) + let current: string | undefined + try { + current = readFileSync(path, 'utf8') + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error + } + if (current !== undefined && !current.includes(GENERATED_INVARIANT_MARKER)) continue + const expected = renderBaselineInvariant(owner) + if (current === expected) continue + writeFileSync(path, expected) + generated += 1 + } + console.log(`gen-package-invariants: wrote ${generated} generated baseline companion(s).`) +} + +const violations = collectPackageInvariantViolations(root) +if (violations.length > 0) { + console.error('verify-package-invariants: violations found:') + for (const violation of violations) { + console.error(` ${formatPackageInvariantViolation(root, violation)}`) + } + process.exit(1) +} + +console.log(`verify-package-invariants: ${packageInvariantOwners(root).length} package companion(s) conform.`) diff --git a/scripts/package-invariants.spec.ts b/scripts/package-invariants.spec.ts new file mode 100644 index 0000000000..698969b3d7 --- /dev/null +++ b/scripts/package-invariants.spec.ts @@ -0,0 +1,104 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { afterEach, describe, expect, it } from 'vitest' +import { + collectPackageInvariantViolations, + packageInvariantOwners, + renderBaselineInvariant, +} from './package-invariants.ts' + +const roots: string[] = [] + +afterEach(() => { + for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }) +}) + +function fixture(options: { + packageName?: string + source?: string + invariantExport?: boolean + invariantDependency?: boolean + invariantReference?: boolean + buildEntry?: boolean +} = {}): string { + const root = mkdtempSync(join(tmpdir(), 'dsh-package-invariants-')) + roots.push(root) + const dir = join(root, 'packages/core/probe') + mkdirSync(join(dir, 'src'), { recursive: true }) + const packageName = options.packageName ?? '@deepseek-ai/dsh-probe' + const manifest = { + name: packageName, + exports: options.invariantExport === false ? {} : { + './invariant': { + types: './lib/types/invariant.d.ts', + default: './lib/invariant.js', + }, + }, + files: ['lib/index.js', 'lib/invariant.js', 'src'], + peerDependencies: options.invariantDependency === false ? {} : { + '@deepseek-ai/dsh-invariants': '^0.0.1', + }, + devDependencies: options.invariantDependency === false ? {} : { + '@deepseek-ai/dsh-invariants': 'workspace:^', + }, + } + writeFileSync(join(dir, 'package.json'), `${JSON.stringify(manifest, null, 2)}\n`) + writeFileSync(join(dir, 'tsconfig.json'), `${JSON.stringify({ + references: options.invariantReference === false ? [] : [{ path: '../../support/invariants' }], + }, null, 2)}\n`) + const owner = packageInvariantOwners(root)[0]! + writeFileSync(join(dir, 'src/invariant.ts'), options.source ?? renderBaselineInvariant(owner)) + writeFileSync( + join(dir, 'tsdown.config.ts'), + options.buildEntry === false ? "export default { entry: ['lib/types/index.js'] }\n" : "export default { entry: ['lib/types/index.js', 'lib/types/invariant.js'] }\n", + ) + return root +} + +describe('package invariant gate', () => { + it('accepts a generated owner companion with publication metadata', () => { + expect(collectPackageInvariantViolations(fixture())).toEqual([]) + }) + + it('rejects missing publication metadata and build output', () => { + const violations = collectPackageInvariantViolations(fixture({ + invariantExport: false, + invariantDependency: false, + invariantReference: false, + buildEntry: false, + })) + expect(violations.map(violation => violation.message)).toEqual(expect.arrayContaining([ + expect.stringContaining('exports["./invariant"]'), + expect.stringContaining('peerDependency'), + expect.stringContaining('devDependency'), + expect.stringContaining('TypeScript project references'), + expect.stringContaining('must bundle lib/types/invariant.js'), + ])) + }) + + it('rejects foreign, duplicate, and unresolved registrations', () => { + const source = ` +export const name = 'probe-invariant' +export const inject = ['invariants'] +const selected = process.env.PACKAGE_NAME +export const apply = (ctx: { invariants: { register(name: string, install: () => void): () => void } }) => { + ctx.invariants.register('@deepseek-ai/dsh-foreign', () => {}) + return ctx.invariants.register(selected!, () => {}) +} +` + const violations = collectPackageInvariantViolations(fixture({ source })) + expect(violations.map(violation => violation.message)).toEqual(expect.arrayContaining([ + expect.stringContaining('must resolve to a local string constant'), + expect.stringContaining('must register exactly its own package name'), + ])) + }) + + it('rejects edits to a generated baseline', () => { + const root = fixture() + const path = join(root, 'packages/core/probe/src/invariant.ts') + writeFileSync(path, `${renderBaselineInvariant(packageInvariantOwners(root)[0]!)}// stale\n`) + expect(collectPackageInvariantViolations(root).map(violation => violation.message)) + .toContain('generated baseline is stale; run pnpm run gen-package-invariants') + }) +}) diff --git a/scripts/package-invariants.ts b/scripts/package-invariants.ts new file mode 100644 index 0000000000..45fb64829f --- /dev/null +++ b/scripts/package-invariants.ts @@ -0,0 +1,283 @@ +/** + * Package-invariant companion discovery, generation, and structural checks. + * The runtime registry stays product-independent; this gate makes ownership + * exhaustive across packages without centralizing package checks. + */ + +import { existsSync, globSync, readFileSync } from 'node:fs' +import { basename, dirname, relative, resolve, sep } from 'node:path' +import ts from 'typescript' + +/** Marker identifying baseline companions owned by this generator. */ +export const GENERATED_INVARIANT_MARKER = '@generated scripts/gen-package-invariants.ts' + +interface PackageManifest { + name?: string + exports?: Record + files?: string[] + peerDependencies?: Record + devDependencies?: Record +} + +/** One package and the files participating in its invariant publication contract. */ +export interface PackageInvariantOwner { + readonly dir: string + readonly manifestPath: string + readonly sourcePath: string + readonly packageName: string +} + +/** One gate violation with a repo-relative owner path. */ +export interface PackageInvariantViolation { + readonly path: string + readonly message: string +} + +/** Discover every package under the repository package tree. */ +export function packageInvariantOwners(root: string): PackageInvariantOwner[] { + return globSync('packages/*/*/package.json', { cwd: root }) + .map(path => path.split(sep).join('/')) + .sort() + .map((manifestPath) => { + const manifest = readManifest(resolve(root, manifestPath)) + if (manifest.name === undefined || manifest.name === '') { + throw new Error(`${manifestPath}: package invariant owner must declare a package name`) + } + const dir = dirname(manifestPath) + return { + dir, + manifestPath, + sourcePath: `${dir}/src/invariant.ts`, + packageName: manifest.name, + } + }) +} + +/** Render the generated ownership-only companion for a package without custom checks. */ +export function renderBaselineInvariant(owner: PackageInvariantOwner): string { + const serviceImport = owner.packageName === '@deepseek-ai/dsh-invariants' + ? './index.ts' + : '@deepseek-ai/dsh-invariants' + const pluginName = `${basename(owner.dir)}-invariant` + return `/** + * Generated invariant ownership companion for \`${owner.packageName}\`. + * Replace this file with package-owned checks while preserving its registration. + * + * ${GENERATED_INVARIANT_MARKER} + * @module ${owner.packageName}/invariant + */ + +/* jscpd:ignore-start */ +import type { Context } from 'cordis' +import type { InvariantInstaller } from '${serviceImport}' + +const PACKAGE_NAME = '${owner.packageName}' + +/** Cordis companion plugin name. */ +export const name = '${pluginName}' +/** Services required before the companion can register. */ +export const inject = ['invariants'] + +/** Reserve this package's invariant ownership until it adds relational checks. */ +const install: InvariantInstaller = () => {} + +/** + * Register this package's invariant companion. + * @param ctx - Cordis context carrying the invariant service. + * @returns the installed registration's disposer after setup succeeds. + */ +export const apply = (ctx: Context): Promise<() => void> => + Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) +/* jscpd:ignore-end */ +` +} + +/** Return all violations of the package-invariant companion contract. */ +export function collectPackageInvariantViolations(root: string): PackageInvariantViolation[] { + const violations: PackageInvariantViolation[] = [] + for (const owner of packageInvariantOwners(root)) { + const manifest = readManifest(resolve(root, owner.manifestPath)) + checkManifest(owner, manifest, violations) + checkBuild(owner, root, violations) + checkSource(owner, root, violations) + } + return violations +} + +function readManifest(path: string): PackageManifest { + return JSON.parse(readFileSync(path, 'utf8')) as PackageManifest +} + +function addViolation( + violations: PackageInvariantViolation[], + path: string, + message: string, +): void { + violations.push({ path, message }) +} + +function checkManifest( + owner: PackageInvariantOwner, + manifest: PackageManifest, + violations: PackageInvariantViolation[], +): void { + const invariantExport = manifest.exports?.['./invariant'] + if (typeof invariantExport !== 'object' + || invariantExport.types !== './lib/types/invariant.d.ts' + || invariantExport.default !== './lib/invariant.js') { + addViolation( + violations, + owner.manifestPath, + 'exports["./invariant"] must target ./lib/types/invariant.d.ts and ./lib/invariant.js', + ) + } + if (!manifest.files?.includes('lib/invariant.js')) { + addViolation(violations, owner.manifestPath, 'files must publish lib/invariant.js') + } + if (owner.packageName === '@deepseek-ai/dsh-invariants') return + if (manifest.peerDependencies?.['@deepseek-ai/dsh-invariants'] !== '^0.0.1') { + addViolation( + violations, + owner.manifestPath, + '@deepseek-ai/dsh-invariants must be a ^0.0.1 peerDependency', + ) + } + if (manifest.devDependencies?.['@deepseek-ai/dsh-invariants'] !== 'workspace:^') { + addViolation( + violations, + owner.manifestPath, + '@deepseek-ai/dsh-invariants must also be a workspace:^ devDependency', + ) + } +} + +function checkBuild( + owner: PackageInvariantOwner, + root: string, + violations: PackageInvariantViolation[], +): void { + const tsconfigPath = `${owner.dir}/tsconfig.json` + const tsconfig = JSON.parse(readFileSync(resolve(root, tsconfigPath), 'utf8')) as { + references?: Array<{ path?: string }> + } + if (owner.packageName !== '@deepseek-ai/dsh-invariants' + && !tsconfig.references?.some(reference => reference.path === '../../support/invariants')) { + addViolation( + violations, + tsconfigPath, + 'TypeScript project references must include ../../support/invariants', + ) + } + + const configPath = `${owner.dir}/tsdown.config.ts` + if (!existsSync(resolve(root, configPath))) return + const source = readFileSync(resolve(root, configPath), 'utf8') + if (!source.includes('lib/types/invariant.js')) { + addViolation(violations, configPath, 'package build override must bundle lib/types/invariant.js') + } +} + +function checkSource( + owner: PackageInvariantOwner, + root: string, + violations: PackageInvariantViolation[], +): void { + const absolutePath = resolve(root, owner.sourcePath) + if (!existsSync(absolutePath)) { + addViolation(violations, owner.sourcePath, 'missing package-owned invariant companion') + return + } + const sourceText = readFileSync(absolutePath, 'utf8') + if (sourceText.includes(GENERATED_INVARIANT_MARKER) + && sourceText !== renderBaselineInvariant(owner)) { + addViolation( + violations, + owner.sourcePath, + 'generated baseline is stale; run pnpm run gen-package-invariants', + ) + } + + const sourceFile = ts.createSourceFile( + absolutePath, + sourceText, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.TS, + ) + const constants = topLevelStringConstants(sourceFile) + const registrations: string[] = [] + const unresolved: number[] = [] + const visit = (node: ts.Node): void => { + if (ts.isCallExpression(node) && isInvariantRegistration(node.expression)) { + const argument = node.arguments[0] + const packageName = argument === undefined ? undefined : stringValue(argument, constants) + if (packageName === undefined) unresolved.push(sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1) + else registrations.push(packageName) + } + ts.forEachChild(node, visit) + } + visit(sourceFile) + + for (const line of unresolved) { + addViolation( + violations, + owner.sourcePath, + `line ${line}: ctx.invariants.register package name must resolve to a local string constant`, + ) + } + if (registrations.length !== 1 || registrations[0] !== owner.packageName) { + addViolation( + violations, + owner.sourcePath, + `must register exactly its own package name ${JSON.stringify(owner.packageName)}; saw ${JSON.stringify(registrations)}`, + ) + } + for (const exportedName of ['name', 'inject', 'apply']) { + if (!hasNamedExport(sourceFile, exportedName)) { + addViolation(violations, owner.sourcePath, `must named-export ${exportedName}`) + } + } +} + +function topLevelStringConstants(sourceFile: ts.SourceFile): ReadonlyMap { + const constants = new Map() + for (const statement of sourceFile.statements) { + if (!ts.isVariableStatement(statement)) continue + for (const declaration of statement.declarationList.declarations) { + if (!ts.isIdentifier(declaration.name) || declaration.initializer === undefined) continue + const value = stringValue(declaration.initializer, constants) + if (value !== undefined) constants.set(declaration.name.text, value) + } + } + return constants +} + +function stringValue(node: ts.Expression, constants: ReadonlyMap): string | undefined { + if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text + if (ts.isIdentifier(node)) return constants.get(node.text) + return undefined +} + +function isInvariantRegistration(expression: ts.LeftHandSideExpression): boolean { + return ts.isPropertyAccessExpression(expression) + && expression.name.text === 'register' + && ts.isPropertyAccessExpression(expression.expression) + && expression.expression.name.text === 'invariants' +} + +function hasNamedExport(sourceFile: ts.SourceFile, name: string): boolean { + return sourceFile.statements.some((statement) => { + if (!ts.isVariableStatement(statement) + || !statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) return false + return statement.declarationList.declarations.some(declaration => ts.isIdentifier(declaration.name) && declaration.name.text === name) + }) +} + +/** Format violations for the command-line gate. */ +export function formatPackageInvariantViolation( + root: string, + violation: PackageInvariantViolation, +): string { + const path = resolve(root, violation.path) + return `${relative(root, path)}: ${violation.message}` +} diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index f46de4475a..8e2a7f961c 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -204,6 +204,7 @@ function ciPrimaryGates(): Gate[] { return [ pnpmScript('runtime-closure', 'verify-runtime-closure', { label: 'runtime closure' }), pnpmScript('constraints', 'constraints'), + pnpmScript('package-invariants', 'verify-package-invariants', { label: 'package invariants' }), pnpmScript('cordis-config', 'verify-cordis-config', { label: 'Cordis config' }), pnpmScript('typecheck', 'typecheck'), lintGate(), @@ -229,6 +230,7 @@ function ciStaticGates(): Gate[] { return [ pnpmScript('runtime-closure', 'verify-runtime-closure', { label: 'runtime closure' }), pnpmScript('constraints', 'constraints'), + pnpmScript('package-invariants', 'verify-package-invariants', { label: 'package invariants' }), pnpmScript('cordis-config', 'verify-cordis-config', { label: 'Cordis config' }), ...staticDemoSmokeGates(), ...docSyncLeafGates(), @@ -314,6 +316,7 @@ function hygieneLeafGates(options: { artifactNeeds?: string[] } = {}): Gate[] { pnpmScript('knip', 'knip'), pnpmScript('publint', 'publint', artifactOptions), pnpmScript('constraints', 'constraints'), + pnpmScript('package-invariants', 'verify-package-invariants', { label: 'package invariants' }), pnpmScript('node-next-types', 'verify-node-next-types', { label: 'node-next types', ...artifactOptions, diff --git a/scripts/test-invariants.spec.ts b/scripts/test-invariants.spec.ts new file mode 100644 index 0000000000..77150abb74 --- /dev/null +++ b/scripts/test-invariants.spec.ts @@ -0,0 +1,70 @@ +import { describe, expect, it, vi } from 'vitest' +import { Context, Service } from 'cordis' +import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' +import { packageInvariantOwners } from './package-invariants.ts' +import { MANUAL_INVARIANT_TESTS, testInvariantCompanions } from './test-invariants.ts' + +declare module 'cordis' { + interface Context { + testInvariantProbe: TestInvariantProbe + } +} + +class TestInvariantProbe extends Service { + constructor(ctx: Context) { + super(ctx, 'testInvariantProbe') + } +} + +describe('global test invariant host', () => { + it('loads every companion and reserves every package name with enabled checks', async () => { + const ctx = new Context() + await ctx.plugin(TestInvariantProbe) + + const owners = packageInvariantOwners(process.cwd()) + expect(Object.keys(testInvariantCompanions)).toHaveLength(owners.length) + const unreserved: string[] = [] + for (const owner of owners) { + try { + const dispose = ctx.invariants.register(owner.packageName, () => {}) + unreserved.push(owner.packageName) + dispose() + } catch (error) { + expect(error).toHaveProperty( + 'message', + `invariants: package "${owner.packageName}" is already registered`, + ) + } + } + expect(unreserved).toEqual([]) + }) + + it('executes each companion registration with its owning package name', async () => { + const owners = new Map(packageInvariantOwners(process.cwd()).map(owner => [owner.sourcePath, owner.packageName])) + const registrations = new Map() + const register = vi.fn((_packageName: string, installer: InvariantInstaller) => { + expect(typeof installer).toBe('function') + return () => {} + }) + const fakeContext = { invariants: { register } } as unknown as Context + for (const [rawPath, companion] of Object.entries(testInvariantCompanions)) { + const path = rawPath.replace(/^\.\.\//, '') + await companion.apply(fakeContext) + const call = register.mock.calls.at(-1) + if (call === undefined) throw new Error(`${path}: companion did not register`) + registrations.set(path, call[0]) + } + expect(registrations).toEqual(owners) + }) + + it('limits manual composition to focused invariant topology tests', () => { + expect(MANUAL_INVARIANT_TESTS).toEqual([ + '/packages/support/invariants/tests/service.spec.ts', + '/packages/core/session/tests/invariant.spec.ts', + '/packages/core/agent/tests/invariant.spec.ts', + '/packages/core/scope/tests/invariant.spec.ts', + '/packages/core/agent-loop/tests/invariant.spec.ts', + '/packages/examples/agent-spine-demo/tests/agent-core.spec.ts', + ]) + }) +}) diff --git a/scripts/test-invariants.ts b/scripts/test-invariants.ts new file mode 100644 index 0000000000..584db3d5d7 --- /dev/null +++ b/scripts/test-invariants.ts @@ -0,0 +1,104 @@ +/** + * Vitest-wide invariant host. Ordinary Cordis roots receive the invariant + * service with global enablement and every package companion before their first + * plugin starts. Focused invariant tests own their service topology explicitly. + */ + +import { expect } from 'vitest' +import { RegistryService } from 'cordis' +import type { Context, Plugin } from 'cordis' +import InvariantService from '@deepseek-ai/dsh-invariants' + +declare global { + interface ImportMeta { + /** Eager Vite module-glob expansion used by the Vitest setup file. */ + glob(pattern: string, options: { eager: true }): Record + } +} + +/** Loader-safe shape shared by every package invariant companion. */ +export interface TestInvariantCompanion { + readonly name: string + readonly inject: readonly string[] + apply(ctx: Context): Promise<() => void> +} + +/** Every package companion, discovered eagerly so coverage observes each registration. */ +export const testInvariantCompanions: Readonly> = + import.meta.glob('../packages/*/*/src/invariant.ts', { eager: true }) + +/** Tests that exercise selection or companion lifecycle with a deliberately hand-built service tree. */ +export const MANUAL_INVARIANT_TESTS = [ + '/packages/support/invariants/tests/service.spec.ts', + '/packages/core/session/tests/invariant.spec.ts', + '/packages/core/agent/tests/invariant.spec.ts', + '/packages/core/scope/tests/invariant.spec.ts', + '/packages/core/agent-loop/tests/invariant.spec.ts', + '/packages/examples/agent-spine-demo/tests/agent-core.spec.ts', +] as const + +interface InvariantHost { + readonly fibers: readonly PluginFiber[] + readonly byCallback: ReadonlyMap +} + +type PluginFiber = ReturnType + +const hosts = new WeakMap() +// eslint-disable-next-line @typescript-eslint/unbound-method -- every call below supplies its RegistryService receiver explicitly. +const originalPlugin = RegistryService.prototype.plugin + +RegistryService.prototype.plugin = function(plugin: Plugin, config?: unknown, getOuterStack?: () => string[]) { + if (usesManualInvariantTree()) return originalPlugin.call(this, plugin, config, getOuterStack) + + const root = this.ctx.root + const host = hosts.get(root) ?? startInvariantHost(root) + const callback = this.resolve(plugin) + const existing = callback === undefined ? undefined : host.byCallback.get(callback) + if (existing !== undefined) return existing + + const fiber = originalPlugin.call(this, plugin, config, getOuterStack) + // A root-level await is the test's composition boundary. Nested plugin + // fibers must not await their own companion parent through the global host. + if (this.ctx !== root) return fiber + return joinInvariantStartup(fiber, host.fibers) +} + +function usesManualInvariantTree(): boolean { + const testPath = expect.getState().testPath?.replaceAll('\\', '/') ?? '' + return MANUAL_INVARIANT_TESTS.some(path => testPath.endsWith(path)) +} + +function startInvariantHost(root: Context): InvariantHost { + const fibers: PluginFiber[] = [] + const byCallback = new Map() + const mount = (plugin: Plugin, config?: unknown): void => { + const fiber = originalPlugin.call(root.registry, plugin, config) + const callback = root.registry.resolve(plugin) + if (callback === undefined) throw new Error('test invariants: companion is not a valid Cordis plugin') + fibers.push(fiber) + byCallback.set(callback, fiber) + } + + mount(InvariantService, { enabled: true }) + for (const [path, companion] of Object.entries(testInvariantCompanions).sort(([left], [right]) => left.localeCompare(right))) { + if (!companion.inject.includes('invariants')) { + throw new Error(`test invariants: ${path} must inject the invariant service`) + } + mount(companion) + } + + const host = { fibers, byCallback } + hosts.set(root, host) + return host +} + +function joinInvariantStartup(fiber: PluginFiber, invariantFibers: readonly PluginFiber[]): PluginFiber { + const readiness = fiber.await().then(async (loaded) => { + await Promise.all(invariantFibers.map(invariant => invariant.await())) + return loaded + }) + const joined = Object.create(fiber) as PluginFiber + joined.then = readiness.then.bind(readiness) + return joined +} diff --git a/tsdown.config.ts b/tsdown.config.ts index 5efebe5b0b..31fa53c675 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -3,8 +3,9 @@ import { defineConfig } from 'tsdown' /** * JS bundling for vendored Cordis and Harness TypeScript packages. * TypeScript source is compiled first by `tsc -b tsconfig.build.json`; tsdown - * reads only the emitted JS under lib/types and writes lib/index.* runtime - * bundles. Declarations are NOT produced here, hence `dts: false`. + * reads only the emitted JS under lib/types and writes the package root and + * invariant companion runtime bundles. Declarations are NOT produced here, + * hence `dts: false`. * * Per-package shape overrides live in `/tsdown.config.ts` * (schemastery: dual ESM+CJS; logger-console: extra browser entry). @@ -14,7 +15,9 @@ export default defineConfig({ // `workspace: true` would discover package manifests outside that bundle set. Landlock // platform packages contain only a prebuilt native binary, so they have no JS entry. workspace: ['vendor/*', 'packages/*/*'], - entry: ['lib/types/index.js'], + // The brace glob admits the package companion when present while retaining the + // index-only build for vendored Cordis packages outside the Harness package tree. + entry: ['lib/types/{index,invariant}.js'], outDir: 'lib', format: ['esm'], platform: 'node', diff --git a/vitest.config.ts b/vitest.config.ts index c0946e2e10..4fdcf012f0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -7,6 +7,7 @@ export default defineConfig({ // to source; native resolution would fall through to absent `lib/` outputs. plugins: [tsconfigPaths({ projects: ['./tsconfig.json'] })], test: { + setupFiles: ['./scripts/test-invariants.ts'], include: ['packages/*/*/tests/**/*.spec.ts', 'examples/*/tests/**/*.spec.ts', 'scripts/**/*.spec.ts'], coverage: { provider: 'v8', diff --git a/vitest.e2e.config.ts b/vitest.e2e.config.ts index adc8b3f3b2..ab22640c79 100644 --- a/vitest.e2e.config.ts +++ b/vitest.e2e.config.ts @@ -31,6 +31,7 @@ export default defineConfig({ // through the root tsconfig paths map; the native option cannot do this. plugins: [tsconfigPaths({ projects: ['./tsconfig.json'] })], test: { + setupFiles: ['./scripts/test-invariants.ts'], include: ['packages/*/*/tests/**/*.e2e.ts', 'examples/*/tests/**/*.e2e.ts'], // Real model calls: generous timeouts, and retries for transient flakes // (the shared internal key hits concurrency quotas). No coverage — the diff --git a/vitest.snapshot.config.ts b/vitest.snapshot.config.ts index fb2e890eb7..368f04f423 100644 --- a/vitest.snapshot.config.ts +++ b/vitest.snapshot.config.ts @@ -39,6 +39,7 @@ export default defineConfig({ // through the root tsconfig paths map; the native option cannot do this. plugins: [tsconfigPaths({ projects: ['./tsconfig.json'] })], test: { + setupFiles: ['./scripts/test-invariants.ts'], include: [ 'examples/*/tests/**/*.snapshot.ts', 'packages/sdk/*/tests/**/*.snapshot.ts', diff --git a/website/zh-CN/api/harness/invariants.md b/website/zh-CN/api/harness/invariants.md index b87ea93aa7..e55dd664c7 100644 --- a/website/zh-CN/api/harness/invariants.md +++ b/website/zh-CN/api/harness/invariants.md @@ -6,7 +6,7 @@ Package-owned invariant registry with global and regex-based selection. -[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/support/invariants/src/index.ts#L94) +[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/support/invariants/src/index.ts#L95) ### ctx.invariants.register(packageName, installer) @@ -29,4 +29,4 @@ Register one package's invariant installer. The package name is reserved even wh **Returns** an effect-scoped disposer for the registration. -[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/support/invariants/src/index.ts#L136) +[Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/support/invariants/src/index.ts#L137)