diff --git a/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml b/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml new file mode 100644 index 0000000000..93ae94beb7 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# 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 .agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.md +2026-07-19-package-invariant-runtime-contracts.md: d0559b0379ce05aa9a5a0c30df4808e3088c840b +2026-07-19-package-invariant-runtime-contracts.zh.md: 0cfe61ee6344a4f6da73ffff22bb8a1b30f9f3a9 diff --git a/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md b/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md rename to .agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.md index a1b635cc40..d0559b0379 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md +++ b/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.md @@ -1,6 +1,7 @@ # Agent Note: Meaningful package invariant contracts Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-19-package-invariant-runtime-contracts.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md b/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md rename to .agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md index d315c440f1..0cfe61ee63 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md +++ b/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md @@ -1,6 +1,7 @@ # Agent Note: 有意义的包不变量约定 Status: implemented +Archived: 2026-09-30 [English](2026-07-19-package-invariant-runtime-contracts.md) | 中文 diff --git a/.agents/notes/archived/manifest.json b/.agents/notes/archived/manifest.json index 44178cc0ec..f1d922eecb 100644 --- a/.agents/notes/archived/manifest.json +++ b/.agents/notes/archived/manifest.json @@ -73,6 +73,9 @@ "architecture/2026-07-19-gui-layering-and-rpc-protocol.i18n.yaml": "sha256:855477999c84236430dc9308e16797eb21658a67a72b8537315c6964ff0c0c0a", "architecture/2026-07-19-gui-layering-and-rpc-protocol.md": "sha256:3517f37e98e74865dced37d5e1559d443e8fa827031c8335e99a1e910586e9ac", "architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md": "sha256:8181386d957fa6d6b3eb9b05d29adb10804b5a926853425415d368cc7fceaefa", + "architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml": "sha256:a232db6670ea51521112ad54bcc0cc4ad43c5b11dfa0df2963a6f04e79434c7f", + "architecture/2026-07-19-package-invariant-runtime-contracts.md": "sha256:8f4a84b310d5ad8398cc272201c8c0ed259496050fad8cb4d3634ee5bbb1dc1e", + "architecture/2026-07-19-package-invariant-runtime-contracts.zh.md": "sha256:6b43c6437523e2715cdb8d75fd286a1eebd0d17f5a35f6c9afddb3f15cdfb945", "architecture/2026-07-19-package-owned-invariant-service.i18n.yaml": "sha256:9ea696b84c3af215eb3640e8acfe75a73b0f16109cbdffd4ce18cbf616221231", "architecture/2026-07-19-package-owned-invariant-service.md": "sha256:514f93f2261fecab9eb7efa8613340b1f01b729b411ac4adce903c50935fa95e", "architecture/2026-07-19-package-owned-invariant-service.zh.md": "sha256:cbcc5550b68bc6699ac7411c46c5da67db3fc4fe8330873f1d190ef8141f1280", @@ -1849,6 +1852,9 @@ "simplification/2026-08-27-persistence-export-and-pre-release-trims.i18n.yaml": "sha256:1c6779c70d8d327844d69390a449b0c4f0603364666b7d30c93cbeb01899d3ec", "simplification/2026-08-27-persistence-export-and-pre-release-trims.md": "sha256:715ecdf9be8eda840c8ad37e56569d91aeac8128a171cbc783aba898cea240fc", "simplification/2026-08-27-persistence-export-and-pre-release-trims.zh.md": "sha256:244a2225316ad54edbeaeef558cba8a01c68f6f366e22abd5688553c77b71629", + "simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml": "sha256:49a8f569a6b8b04596c846e6ef27b19d5a9a0e6f04bf0559580927966b0747a1", + "simplification/2026-08-28-omit-unneeded-invariant-companions.md": "sha256:d981d9b813f7b317c41cc636e44d6bf0cd79dd71971285db3843ef2f27be47a3", + "simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md": "sha256:c6a18d0be3bf03fcd4febc33d863d3c993dea24e5e2e2b3a93fda4ba646eb5e7", "simplification/2026-08-28-remove-hero-input-glow.i18n.yaml": "sha256:68af9690c3463de8e658dbc14194edb438ebf75c8788bba740332d11b0420819", "simplification/2026-08-28-remove-hero-input-glow.md": "sha256:1d492977342fae8542c2781b0b3df8a95388ce97bedf9dc69db74868167b782c", "simplification/2026-08-28-remove-hero-input-glow.zh.md": "sha256:b5e52a51adddee66a7f738e0345203cc339f0aaf2dbdfd9ad7524592a300837c", diff --git a/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml b/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml new file mode 100644 index 0000000000..221aedc107 --- /dev/null +++ b/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# 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 .agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.md +2026-08-28-omit-unneeded-invariant-companions.md: db6e74752add7f7f78384987a55ea77f8a47c4ac +2026-08-28-omit-unneeded-invariant-companions.zh.md: ecd3eec023ee18ee0ef3b638230b0d64ceed1d94 diff --git a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md b/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md rename to .agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.md index 82d421677c..db6e74752a 100644 --- a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md +++ b/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.md @@ -1,6 +1,7 @@ # Agent Note: Omit invariant companions without independent observations Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-28-omit-unneeded-invariant-companions.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md b/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md rename to .agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md index d0fda91546..ecd3eec023 100644 --- a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md +++ b/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md @@ -1,6 +1,7 @@ # Agent Note: 没有独立观察时省略不变量伴生入口 Status: implemented +Archived: 2026-09-30 [English](2026-08-28-omit-unneeded-invariant-companions.md) | 中文 diff --git a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.i18n.yaml index d01dd83fdb..cc4e1e124c 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.i18n.yaml @@ -2,36 +2,33 @@ # section, a hash of its English and Chinese blocks outside code blocks and generated regions. # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md -/agent-note-source-owned-session-immutability-and-dev-mode-invariants: - en: 052aa271e87074c0 - zh: 3ccffc50fc1a28d4 -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/problem: - en: 6ea85a4531d5e59f - zh: b4b568eb347f016a -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/decision: - en: 6e701ec64c3bbfae - zh: b8ed0f1cb7a0477a -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/decision/session-owns-immutable-history: - en: ff47e58c377a8dc0 - zh: 5edbe4f3642a3fe6 -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/decision/derived-requests-remain-detached: +/agent-note-source-owned-session-immutability: + en: cf33ffdd07df7257 + zh: 3d6895c9c80b0b1f +/agent-note-source-owned-session-immutability/problem: + en: e5d87f6c5aef2dad + zh: 9f7f1459d99c523c +/agent-note-source-owned-session-immutability/decision: + en: 821a886d365a2271 + zh: 4ba5d74a7772150d +/agent-note-source-owned-session-immutability/decision/session-owns-immutable-history: + en: 76fc0d8548d192c1 + zh: 34c610fff5beb455 +/agent-note-source-owned-session-immutability/decision/derived-requests-remain-detached: en: 975df574ee6e6df8 zh: 96d765058482835a -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/decision/package-owned-invariant-companions-check-relationships: - en: a88e3a896eedca91 - zh: 03e96c7870cba932 -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/alternatives-considered: +/agent-note-source-owned-session-immutability/alternatives-considered: en: 6119e2aff3b6be5a zh: 76136ef5249c23eb -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/alternatives-considered/pervasive-deep-readonly-types: +/agent-note-source-owned-session-immutability/alternatives-considered/pervasive-deep-readonly-types: en: ec54af2931616616 zh: 5fd9bc469a6fd3a5 -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/alternatives-considered/development-only-freezing: - en: 85591197bb62eabf - zh: ba69b9ccf596a6d7 -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/alternatives-considered/clone-only-when-deriving-messages: +/agent-note-source-owned-session-immutability/alternatives-considered/development-only-freezing: + en: 1857fb245ec3f086 + zh: 6d3324c872627bf6 +/agent-note-source-owned-session-immutability/alternatives-considered/clone-only-when-deriving-messages: en: a9848dec50664b92 zh: 7c558550b054627b -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/consequences: - en: bcccf7d464a070db - zh: eccd442a21e2c99e +/agent-note-source-owned-session-immutability/consequences: + en: d26609b76b9eea01 + zh: cf046d5c7173b738 diff --git a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md index 0784960397..8265c7980a 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md +++ b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md @@ -1,4 +1,4 @@ -# Agent Note: Source-owned session immutability and dev-mode invariants +# Agent Note: Source-owned session immutability Status: implemented @@ -6,17 +6,17 @@ English | [中文](2026-06-11-dev-invariants-over-deep-readonly.zh.md) ## Problem -The session log needs two different protections: immutable ownership of each stored fact, and checks for relationships among facts across time and service contracts. Conflating them in an optional development plugin would leave production history vulnerable; trying to express both through TypeScript readonly types would not create a runtime boundary or describe relational rules. +The session log needs immutable ownership of each stored fact. Making that protection an optional development plugin would leave production history vulnerable; expressing it through TypeScript readonly types would not create a runtime boundary. The session log is the durable source of truth for replay, request reconstruction, persistence, and user-visible history. Code outside the session package must be able to inspect that history without retaining a reference that can rewrite it later, and inputs accepted from callers must not remain connected to caller-owned mutable objects. -Immutability of individual values is only half of the contract. A log can contain perfectly immutable records whose sequence, turn/step nesting, tool-call pairing, scoped delivery, or reconstructed model request is wrong. Those rules relate multiple records or services and cannot be established by freezing one object. +Immutability of individual values does not establish relationships among records. A log can contain perfectly immutable records whose sequence, turn/step nesting, tool-call pairing, scoped delivery, or reconstructed model request is wrong. Those rules relate multiple records or services, cannot be established by freezing one object, and are outside this decision. TypeScript readonly types are not a sufficient runtime boundary. They disappear when the program runs, a cast can bypass them, and a recursive `DeepReadonly` would spread through every log and message consumer even though some downstream request-processing APIs intentionally work with mutable values. ## Decision -Responsibility is split between an always-on storage boundary and optional development assertions. +`Session` owns an always-on storage boundary. ### Session owns immutable history @@ -24,18 +24,12 @@ Responsibility is split between an always-on storage boundary and optional devel The accepted event and all of its descendants are deep-frozen before publication. `append()` returns that owned frozen event, and `session/event` observers and `eventAt(seq)` receive the same record. `snapshotEvents(fromSeq?, toSeqExclusive?)` returns a frozen array snapshot; a previously returned array does not grow after a later append. `seq` reads the current length without materializing an array. Synchronous historical readers are deprecated under the [event-read policy](2026-09-09-deprecate-synchronous-session-event-reads.md). Seed records pass through the same validation, snapshot, and freeze boundary before construction succeeds. -This guarantee belongs in `Session`, not in an optional listener, because every composition relies on trustworthy history. A production deployment, a focused test, or a custom embedding receives the same storage semantics whether or not development support plugins are registered. +This guarantee belongs in `Session`, not in an optional listener, because every composition relies on trustworthy history. A production deployment, a focused test, or a custom embedding receives the same storage semantics regardless of which plugins are registered. ### Derived requests remain detached `deriveMessages()` projects logged surface events into detached, deep-frozen `Message` objects and returns a fresh array snapshot. Request assembly can therefore combine derived history with other inputs without exposing a path back into the log. The cache reuses safe immutable projections rather than recloning the complete history for each model call. -### Package-owned invariant companions check relationships - -`dsh-invariants` registers the configurable `ctx.invariants` service and contains no product checks. A package publishes a `./invariant` ownership companion only for an independently observable runtime relationship; packages without one omit the companion and record the reason in their README. `dsh-session`, `dsh-agent`, `dsh-scope`, and `dsh-agent-loop` provide the initial 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](../../archived/architecture/2026-07-19-package-owned-invariant-service.md); [omission decision](../simplification/2026-08-28-omit-unneeded-invariant-companions.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. - ## Alternatives considered ### Pervasive deep-readonly types @@ -44,7 +38,7 @@ A rejected companion proposal would apply a recursive `DeepReadonly` type acr ### Development-only freezing -Freezing history only when an invariants plugin is installed would make the core guarantee composition-dependent. Code could pass development tests and still corrupt history in production or in a focused composition that omits the plugin. Storage immutability is therefore always on, while the more expensive relational checks remain opt-in development support. +Freezing history only when a development plugin is installed would make the core guarantee composition-dependent. Code could pass development tests and still corrupt history in production or in a focused composition that omits the plugin. Storage immutability is therefore always on. ### Clone only when deriving messages @@ -55,6 +49,4 @@ Detaching `deriveMessages()` would protect the most common request path but leav - Every accepted live or seeded session event is detached from caller-owned inputs and deeply immutable before any observer can receive it. - Existing `snapshotEvents()` and `eventAt()` callers retain immutable read results while their migration is deferred; `seq` reads the log length without copying the array. - Request-side mutation cannot reach stored history through derived messages. -- Development builds can enable relational assertions without changing storage behavior, and disposing or filtering a companion does not weaken log immutability. -- `dsh-invariants` configures global enablement plus package allow/block regex lists; each check remains owned and tested by its product package. - The runtime boundary carries a recursive snapshot-and-freeze cost once per accepted event; later readers and cached projections reuse the owned immutable records. diff --git a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md index 7183813130..10077cc24b 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md @@ -1,4 +1,4 @@ -# Agent Note: 源端拥有的会话不可变性与开发模式不变式 +# Agent Note: 源端拥有的会话不可变性 Status: implemented @@ -6,17 +6,17 @@ Status: implemented ## 问题 -会话日志需要两种不同的保护:对每条已存储事实的不可变所有权,以及对跨时间和服务约定的事实之间关系的检查。如果将二者混为一个可选的开发插件,生产环境的历史记录将失去保护;如果试图通过 TypeScript readonly 类型同时表达两者,既无法建立运行时边界,也无法描述关系规则。 +会话日志需要对每条已存储事实的不可变所有权。如果将这项保护做成可选的开发插件,生产环境的历史记录将失去保护;如果通过 TypeScript readonly 类型表达它,则无法建立运行时边界。 会话日志是回放、请求重建、持久化与用户可见历史的持久真源。会话包外部的代码必须能检视历史,但不能保留一个可在之后改写历史的引用;从调用方接受的输入也不能继续连接到调用方拥有的可变对象。 -单个值的不可变性只是约定的一半。一份日志可以包含完全不可变的记录,但其序列、轮次/步骤嵌套、工具调用配对、作用域分发或重建的模型请求是错误的。这些规则涉及多条记录或多个服务,无法通过冻结单个对象来建立。 +单个值的不可变性不能建立记录之间的关系。一份日志可以包含完全不可变的记录,但其序列、轮次/步骤嵌套、工具调用配对、作用域分发或重建的模型请求是错误的。这些规则涉及多条记录或多个服务,无法通过冻结单个对象来建立,也不属于本决策。 TypeScript readonly 类型不是充分的运行时边界。它们在程序运行时消失,类型转换可以绕过它们,而递归的 `DeepReadonly` 会扩散到每个日志和消息消费方,尽管某些下游请求处理 API 有意使用可变值。 ## 决策 -职责划分给始终启用的存储边界和可选的开发断言。 +`Session` 拥有始终启用的存储边界。 ### Session 拥有不可变历史 @@ -24,18 +24,12 @@ TypeScript readonly 类型不是充分的运行时边界。它们在程序运行 被接受的事件及其所有后代在发布前被深度冻结。`append()` 返回由 Session 拥有的冻结事件,`session/event` 观察者和 `eventAt(seq)` 接收同一记录。`snapshotEvents(fromSeq?, toSeqExclusive?)` 返回冻结的数组快照;先前返回的数组不会因后续 append 而增长。`seq` 无需物化数组即可读取当前长度。同步历史读取方法按[事件读取策略](2026-09-09-deprecate-synchronous-session-event-reads.zh.md)弃用。种子记录在构造成功前经过相同的验证、快照与冻结边界。 -此保证属于 `Session` 而非可选监听器,因为每种组合都依赖可信的历史。无论是否注册了开发支持插件,生产部署、聚焦测试或自定义嵌入都获得相同的存储语义。 +此保证属于 `Session` 而非可选监听器,因为每种组合都依赖可信的历史。无论注册了哪些插件,生产部署、聚焦测试或自定义嵌入都获得相同的存储语义。 ### 派生请求保持分离 `deriveMessages()` 将已记录的表面事件投影为分离的、深度冻结的 `Message` 对象,并返回一份新的数组快照。因此请求组装可以将派生历史与其他输入组合,而不会暴露一条回到日志的路径。缓存复用安全的不可变投影,而非为每次模型调用重新克隆完整历史。 -### 包拥有的不变式配套插件检查关系 - -`dsh-invariants` 注册可配置的 `ctx.invariants` 服务,本身不包含产品检查。只有拥有可独立观察的运行时关系时,包才发布 `./invariant` 所有权配套插件;没有该关系的包会省略 companion 并在 README 中记录原因。`dsh-session`、`dsh-agent`、`dsh-scope` 和 `dsh-agent-loop` 提供首批需要跟踪状态或观察另一个 seam 的规则:单调递增的序列号、轮次与步骤嵌套、工具调用/结果配对、合法的 agent(智能体)状态转换、主体正确的作用域分发,以及循环构建的请求与从其会话日志前缀重建的请求之间的相等性。全局启用和包名 regex 过滤器归该服务所有(见[包拥有的不变式服务](../../archived/architecture/2026-07-19-package-owned-invariant-service.md)与[省略决策](../simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md))。 - -当会话配套插件附加到已有会话或以种子记录初始化的会话时,它回放不可变日志以重建跟踪状态。服务为每项贡献提供一个可 dispose(资源释放)的子 fiber,因此轮次中途热重载是安全的,同时不赋予诊断逻辑对会话存储的所有权。 - ## 曾考虑的替代方案 ### 全面的 deep-readonly 类型 @@ -44,7 +38,7 @@ TypeScript readonly 类型不是充分的运行时边界。它们在程序运行 ### 仅在开发模式冻结 -仅当不变式插件安装时才冻结历史,会使核心保证依赖于组合方式。代码可能通过开发测试,却在生产环境或省略了该插件的聚焦组合中破坏历史。因此存储不可变性始终启用,而开销更大的关系检查则保持为可选的开发支持。 +仅当开发插件安装时才冻结历史,会使核心保证依赖于组合方式。代码可能通过开发测试,却在生产环境或省略了该插件的聚焦组合中破坏历史。因此存储不可变性始终启用。 ### 仅在派生消息时克隆 @@ -55,6 +49,4 @@ TypeScript readonly 类型不是充分的运行时边界。它们在程序运行 - 每个被接受的实时或种子会话事件在任何观察者接收之前,都已从调用方拥有的输入中分离并深度不可变。 - 现有 `snapshotEvents()` 和 `eventAt()` 调用方在暂缓迁移期间仍获得不可变的读取结果;`seq` 无需复制数组即可读取日志长度。 - 请求侧的修改无法通过派生消息触及已存储的历史。 -- 开发构建可以启用关系断言而不改变存储行为;dispose 或过滤一个配套插件不会削弱日志不可变性。 -- `dsh-invariants` 配置全局启用状态以及包名允许/阻止 regex 列表;每项检查仍由其产品包拥有并测试。 - 运行时边界对每个被接受的事件产生一次递归快照与冻结的开销;后续读取者和缓存投影复用已拥有的不可变记录。 diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml index db7edf6873..c4bc3be352 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml @@ -27,8 +27,8 @@ en: ee71f9c0f12b6a0e zh: 403b1723f808391d /agent-note-session-surface-an-ordered-projection-over-the-event-log/decision/invariants: - en: 24b1342fb3b5be29 - zh: a62692b0250c072a + en: 98f39e77083d67b7 + zh: 634a9227fc2a7ec9 /agent-note-session-surface-an-ordered-projection-over-the-event-log/alternatives-considered: en: 193157129e65e487 zh: a0a73b8a9cbd78e6 diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.md b/.agents/notes/implemented/architecture/2026-06-18-session-surface.md index 26780d54a1..3ac1707390 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.md +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.md @@ -45,7 +45,7 @@ The `repair.ts` module synthesizes `tool/result` closers for orphaned tool calls ### Invariants -`Session` validates `sourceEventSeqs` and `surfaceOp` at the always-on seed/append boundary: source lists are non-empty, unique, earlier, and known; `assistant/message` carries no source list; replacement endpoints exist in surface order; and `sourceEventSeqs` covers every shadowed node. These are single-record acceptance and storage-projection rules, not optional invariant-service contributions. +`Session` validates `sourceEventSeqs` and `surfaceOp` at the always-on seed/append boundary: source lists are non-empty, unique, earlier, and known; `assistant/message` carries no source list; replacement endpoints exist in surface order; and `sourceEventSeqs` covers every shadowed node. These are single-record acceptance and storage-projection rules. Every surface-eligible event must carry `surfaceOp` or it would disappear from derived history. Typed `append` overloads enforce this for literal event types; runtime checks in `append` and the seed constructor cover widened unions and current loaded logs. Released validation and conversion belong to their versioned migration edges rather than generic Session code; see the [V2-to-V3 placement rules](../../../../packages/session/session-format-v2-to-v3/README.md#canonical-envelopes). diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md b/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md index 328a9ee9f1..d556142fde 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md @@ -45,7 +45,7 @@ surface 元数据仅属于四种 surface 事件类型(`system/message`、`user ### 不变式 -`Session` 在始终启用的 seed/append 边界校验 `sourceEventSeqs` 与 `surfaceOp`:source list 必须非空、唯一、更早且已知;`assistant/message` 不携带 source list;replacement endpoint 必须存在于 surface 顺序中;`sourceEventSeqs` 必须覆盖每个被遮蔽的节点。这些是单记录接纳与存储投影规则,不是由可选 invariant service 提供的规则。 +`Session` 在始终启用的 seed/append 边界校验 `sourceEventSeqs` 与 `surfaceOp`:source list 必须非空、唯一、更早且已知;`assistant/message` 不携带 source list;replacement endpoint 必须存在于 surface 顺序中;`sourceEventSeqs` 必须覆盖每个被遮蔽的节点。这些是单记录接纳与存储投影规则。 每个可进入 surface 的事件都必须携带 `surfaceOp`,否则它将从派生历史中消失。类型化的 `append` 重载对字面事件类型强制执行此规则;`append` 和种子构造函数中的运行时检查覆盖宽化联合类型和当前已加载日志。已发布格式的校验与转换属于各自版本化迁移边,而不属于通用 Session 代码;参见 [V2 到 V3 位置规则](../../../../packages/session/session-format-v2-to-v3/README.zh.md#canonical-envelopes)。 diff --git a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml index 987abc5f0e..615c62a1a1 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml @@ -15,8 +15,8 @@ en: 6da15cf986d59a91 zh: 40282908b54295f9 /agent-note-every-llm-request-is-reconstructable-from-the-session-log/decision/the-mechanism: - en: 7d86e5058dbab2fb - zh: 9a97481325c8eded + en: ab22e8949f7adaf1 + zh: 55d4d85a711f8371 /agent-note-every-llm-request-is-reconstructable-from-the-session-log/decision/the-minicode-shape-adopted-with-the-event-log-as-the-source: en: e5f2ffaa3d7de0cc zh: 5ba6b36787a0e189 diff --git a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md index f47d0938b9..725d510495 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md +++ b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md @@ -28,7 +28,7 @@ Each proposed step first claims its inbox batch, assembles the system prompt and **The open step is the reconstruction boundary.** Its entered `user/message` batch and any newly written `request/header` precede request dispatch. Injection after the atomic claim joins a later request, while a listener that must affect this request returns messages through `agent/pre-step`. Header reconstruction selects the step's `request/header`, or carries the prior snapshot when no new header is written. -**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`. The loop records the exact frozen request through `markAgentLoopRequest()` in `dsh-llm`; the process-local identity lets the companion and other request observers recognize conversation work, while direct one-shots remain excluded regardless of their frozen shape or session id. 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 loop records the exact frozen request through `markAgentLoopRequest()` in `dsh-llm`; the process-local identity lets request observers recognize conversation work, while direct one-shots remain excluded regardless of their frozen shape or session id. 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 event log as the source diff --git a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md index d2499a6264..85fbd1003e 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md @@ -28,7 +28,7 @@ Status: implemented **已打开步骤是重建边界。** 进入步骤的 `user/message` 批次与任何新写入的 `request/header` 都位于请求分派之前。原子领取后发生的注入加入后续请求;必须影响本次请求的监听器则通过 `agent/pre-step` 返回消息。header 重建选择该步骤的 `request/header`,或在无新 header 写入时沿用前一个快照。 -**强制执行。** `dsh-agent-loop/invariant` 配套插件向 `ctx.invariants` 注册,并在被选用时通过一个全新的 `Session` 独立重建每个循环请求,使活跃缓存无法为自身背书,然后在 `llm/stream` 处比较消息和折叠后的 header 字段。循环通过 `dsh-llm` 的 `markAgentLoopRequest()` 记录精确的冻结请求;这一进程内标识让配套插件和其他请求观察者识别对话工作,而直接的一次性调用无论其冻结形状或会话 id 如何都保持排除。正确性依赖于序列有界的重建,而非监听器顺序。带密钥的 e2e 要求首次请求之后有正值的 cache-read token;逐步骤用量是生产信号,header 变更或压缩表现为下一步骤的 cache-read 下降。 +**强制执行。** 循环通过 `dsh-llm` 的 `markAgentLoopRequest()` 记录精确的冻结请求;这一进程内标识让请求观察者识别对话工作,而直接的一次性调用无论其冻结形状或会话 id 如何都保持排除。带密钥的 e2e 要求首次请求之后有正值的 cache-read token;逐步骤用量是生产信号,header 变更或压缩表现为下一步骤的 cache-read 下降。 ### MiniCode 形态:采纳,以事件日志为真源 diff --git a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml index 6d5a779689..ef02a125e1 100644 --- a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml @@ -33,8 +33,8 @@ en: 28636ae6d55935d7 zh: 33a989f049de2f3e /agent-note-agent-scope-runtime-design-and-correctness/scope-routing-one-opaque-key-selects-one-layer/fused-dispatch-helpers-prevent-subject-drift: - en: 638e3711206e3c41 - zh: 61d370123d30c1ed + en: 79a8edc4bc5e52b4 + zh: 45d76f37d1f2fe70 /agent-note-agent-scope-runtime-design-and-correctness/agent-creation-one-transaction-owns-the-complete-operation: en: ef58215c8554180b zh: 44571074148d2467 @@ -60,8 +60,8 @@ en: d0a57b0ac227d84c zh: e9ae6ced0e65fe49 /agent-note-agent-scope-runtime-design-and-correctness/session-append-materialize-validate-commit-notify/pre-commit-listeners-can-veto-post-commit-observers-cannot: - en: 15255d3d0b62fac0 - zh: c1995d5aa3c78f0a + en: 35cb9a69d0c80a5a + zh: d99d93c1d407dc81 /agent-note-agent-scope-runtime-design-and-correctness/trust-boundaries-copy-only-when-ownership-actually-changes: en: 22b381902fe903f3 zh: 8a2efe9a8b458bba @@ -114,14 +114,11 @@ en: 840564e766d2f05c zh: 059243afea7eb9c0 /agent-note-agent-scope-runtime-design-and-correctness/correctness-enforcement/types-make-the-ordinary-path-hard-to-misuse: - en: 9183eb29dddfd97f - zh: e6ce13295e6c3e65 -/agent-note-agent-scope-runtime-design-and-correctness/correctness-enforcement/runtime-invariants-cover-cross-service-facts: - en: fabafbebc0ab4f95 - zh: 06d94a98ed5aa40c + en: fb4d5abf86a2f6a4 + zh: 190b710a27ae0d9c /agent-note-agent-scope-runtime-design-and-correctness/correctness-enforcement/generated-artifacts-keep-public-contracts-aligned: - en: d0ddc5f38289225d - zh: 781d910228d5e334 + en: 77d470221115af97 + zh: 1924afe62df16430 /agent-note-agent-scope-runtime-design-and-correctness/alternatives-considered: en: 5eef7873a795c8f0 zh: 3e8ba109a6e29ba5 diff --git a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md index b9b2c837f5..f8a7e0a3b7 100644 --- a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md +++ b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md @@ -84,7 +84,7 @@ Each service retains its domain rule. Named command and prompt views use the sha `agentEvents(context, agent)` constructs the agent's carrier and injects the same agent as the event subject. Session, tool, approval, prompt, and subagent services likewise derive routing from the object they already own instead of accepting an unrelated key. -The type marker rejects ordinary bare-receiver mistakes, and development invariants cover direct JavaScript or casted dispatch. The subject remains explicit because routing correctness and useful event data are different concerns. +The type marker rejects ordinary bare-receiver mistakes; direct JavaScript or casted dispatch is not checked at runtime. The subject remains explicit because routing correctness and useful event data are different concerns. ## Agent creation: one transaction owns the complete operation @@ -183,7 +183,7 @@ Append follows one sequence: 5. Notify each observer independently, containing synchronous and asynchronous failures. 6. Release append state and honor a detach requested during publication. -No observer error makes a committed event look uncommitted, and one bad listener cannot starve later listeners. Session invariants stage their transition before commit and apply it only when the same event reaches the contained post-commit observer. +No observer error makes a committed event look uncommitted, and one bad listener cannot starve later listeners. `flush()` starts every persistence listener and awaits every result before reporting failure. This deliberate all-settled behavior prevents a synchronous failure from starving another backend or final flush. @@ -332,15 +332,11 @@ Readonly contracts describe borrowed same-process values. `Scoped` marks even TypeScript cannot govern JavaScript casts, direct Cordis dispatch, process messages, or durable files, so runtime enforcement remains at those escape points. -### Runtime invariants cover cross-service facts - -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. +No runtime check polices trusted setup by scanning registries or rejects prompt assembly objects fabricated through casts. Those checks would turn composition contracts into speculative runtime machinery without protecting a real external boundary. ### Generated artifacts keep public contracts aligned -The event catalog, service catalog, producer/consumer matrix, configuration catalog, module graph, tool catalog, type-equivalence blocks, and scoped-event resolver map are generated or freshness-gated from source. The [TypeScript semantic-gates Agent Note](../../archived/process/2026-07-14-typescript-program-backed-semantic-gates.md) owns Program construction, semantic event discovery, and resolver-generation rules. +The event catalog, service catalog, producer/consumer matrix, configuration catalog, module graph, tool catalog, and type-equivalence blocks are generated or freshness-gated from source. The [TypeScript semantic-gates Agent Note](../../archived/process/2026-07-14-typescript-program-backed-semantic-gates.md) owns Program construction and semantic event discovery rules. Behavioral tests pin scoped routing and disposal, final-entry collision cleanup, publication rollback, ordered quiescence, durable pre/post-commit behavior, live tool filtering across presentation and execution, cooperative prompt assembly, structured-output commit in native and PTC mode, async subagent startup and signal cancellation, workflow terminal arbitration, ACP settlement, and process teardown. diff --git a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md index 4b94d1b844..d9e9ccf676 100644 --- a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md @@ -84,7 +84,7 @@ Receiver 是一个小型载体而非领域对象的透明代理。需要 agent `agentEvents(context, agent)` 构造 agent 的载体并注入同一个 agent 作为事件主体。会话、工具、approval、提示词和 subagent 服务同样从它们已拥有的对象派生路由,而非接受一个无关的键。 -类型标记拒绝普通的裸 receiver 误用,开发环境不变式覆盖直接 JavaScript 或强制转换的 dispatch。主体保持显式,因为路由正确性和有用的事件数据是不同的关注点。 +类型标记拒绝普通的裸 receiver 误用;直接 JavaScript 或强制转换的 dispatch 不做运行时检查。主体保持显式,因为路由正确性和有用的事件数据是不同的关注点。 ## Agent 创建:一个事务拥有完整操作 @@ -183,7 +183,7 @@ Session 头部、种子和追加的事件是无损 JSON 数据。Session 构造 5. 逐个通知每个观察者,隔离同步和异步失败。 6. 释放追加状态并兑现发布期间请求的 detach。 -没有观察者错误能让已提交的事件看起来未提交,一个坏的监听器也无法饿死后续监听器。Session 不变式在提交前暂存其转换,仅当同一事件到达被隔离的提交后观察者时才应用。 +没有观察者错误能让已提交的事件看起来未提交,一个坏的监听器也无法饿死后续监听器。 `flush()` 启动每个持久化监听器并等待所有结果后再报告失败。这种有意的 all-settled 行为防止同步失败饿死另一个后端或最终刷新。 @@ -332,15 +332,11 @@ Readonly 约定描述借用的同进程值。`Scoped` 标记事件接收器 TypeScript 无法管控 JavaScript 强制转换、直接 Cordis dispatch、进程消息或持久化文件,因此运行时强制保留在这些逃逸点。 -### 运行时不变式覆盖跨服务事实 - -`dsh-scope/invariant` 配套插件在被选用时验证每个声明的作用域事件使用带标记的载体,以及暴露主体的事件族使用匹配的键。独立的 `dsh-session/invariant` 贡献在追加提交前暂存 trace 验证,并在同一事件提交后推进;二者都通过 `ctx.invariants` 注册。 - -该插件不通过扫描注册表来管控可信 setup,也不拒绝通过强制转换构造的提示词 assembly 对象。这些检查会将组合约定变成推测性的运行时机制,却不保护真实的外部边界。 +没有运行时检查通过扫描注册表来管控可信 setup,也不拒绝通过强制转换构造的提示词 assembly 对象。这些检查会将组合约定变成推测性的运行时机制,却不保护真实的外部边界。 ### 生成的产物使公开约定保持对齐 -事件目录、服务目录、生产者/消费方矩阵、配置目录、模块图、工具目录、type-equiv 块和作用域事件解析器映射都是从源码生成或受新鲜度门禁约束的。[TypeScript 语义门禁 Agent Note](../../archived/process/2026-07-14-typescript-program-backed-semantic-gates.md) 拥有 Program 构造、语义事件发现和解析器生成规则。 +事件目录、服务目录、生产者/消费方矩阵、配置目录、模块图、工具目录和 type-equiv 块都是从源码生成或受新鲜度门禁约束的。[TypeScript 语义门禁 Agent Note](../../archived/process/2026-07-14-typescript-program-backed-semantic-gates.md) 拥有 Program 构造和语义事件发现规则。 行为测试固定了作用域路由和 dispose、最终写入注册表时的碰撞清理、发布回滚、有序完全停稳、持久化前/后提交行为、跨展示和执行的活跃工具过滤、协作式提示词组装、原生和 PTC mode 中的结构化输出提交、异步 subagent 启动和信号取消、工作流终态仲裁、ACP 结算和进程拆除。 diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml index b48fcf3403..d32bae6f90 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml @@ -27,8 +27,8 @@ en: 0971d6196b5358fd zh: 26175d648feccfd9 /agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/directory-shape: - en: 74c45680c8aab51c - zh: fd431ae5d3656038 + en: b08293ef60b790a9 + zh: a4f87449d44d48a2 /agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/how-to-develop: en: cbc6abcd9f291868 zh: 0078f15f2d08a02e diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md index 4d62d7ddbe..6c7fa469ab 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md @@ -85,7 +85,7 @@ The dynamic ui-renderer plugin owns the ctx↔React adapter, application-level i ## Directory shape -Client packages live under `packages/client/*`, with `apps/web` as the thin Vite application over the shell's boot export. Plugin packages keep their browser half under `src/client/`; **every build artifact lands in `lib/`** — the node half as `lib/index.js`/`lib/invariant.js`, the browser bundle as `lib/client.js` (the shared tsdown client preset emits both; there is no `dist/` directory, and `exports["./client"]` points at `./lib/client.js`). `ui-slots`, runtime, and ui-renderer form the infrastructure direction; feature plugins cooperate through services and slots rather than importing presentation implementations. +Client packages live under `packages/client/*`, with `apps/web` as the thin Vite application over the shell's boot export. Plugin packages keep their browser half under `src/client/`; **every build artifact lands in `lib/`** — the node half as `lib/index.js`, the browser bundle as `lib/client.js` (the shared tsdown client preset emits both; there is no `dist/` directory, and `exports["./client"]` points at `./lib/client.js`). `ui-slots`, runtime, and ui-renderer form the infrastructure direction; feature plugins cooperate through services and slots rather than importing presentation implementations. A multi-domain plugin package additionally splits its client half by future package boundaries — ui-conversation is the exemplar: diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md index aeb6c89e67..89878d7228 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md @@ -85,7 +85,7 @@ Notifier 微任务合批 ──► ConversationSnapshot 缓存 ──uSES── ## 目录形态 -Client 包位于 `packages/client/*`,`apps/web` 是壳 boot 导出之上的薄 Vite 应用。插件包的浏览器半边在 `src/client/` 下;**一切构建产物落 `lib/`**——node 半边为 `lib/index.js`/`lib/invariant.js`,浏览器 bundle 为 `lib/client.js`(共享 tsdown client 预设两者皆出;无 `dist/` 目录,`exports["./client"]` 指向 `./lib/client.js`)。`ui-slots`、runtime 与 ui-renderer 构成基础设施方向;功能插件通过服务与 slot 协作,不导入展示实现。 +Client 包位于 `packages/client/*`,`apps/web` 是壳 boot 导出之上的薄 Vite 应用。插件包的浏览器半边在 `src/client/` 下;**一切构建产物落 `lib/`**——node 半边为 `lib/index.js`,浏览器 bundle 为 `lib/client.js`(共享 tsdown client 预设两者皆出;无 `dist/` 目录,`exports["./client"]` 指向 `./lib/client.js`)。`ui-slots`、runtime 与 ui-renderer 构成基础设施方向;功能插件通过服务与 slot 协作,不导入展示实现。 多域插件包的 client 半边还按未来包边界再拆——ui-conversation 即样板: diff --git a/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml deleted file mode 100644 index f8fe485684..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-19-package-invariant-runtime-contracts.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md -/agent-note-meaningful-package-invariant-contracts: - en: e5db3b3373f72761 - zh: 312b83d320d9aaab -/agent-note-meaningful-package-invariant-contracts/problem: - en: 220a4ceb3cd9b9c9 - zh: 9eac93e1762b2914 -/agent-note-meaningful-package-invariant-contracts/decision: - en: a15c7c13f167e5ac - zh: 63bcc2ef60c2f344 -/agent-note-meaningful-package-invariant-contracts/decision/published-assertions-must-be-meaningful: - en: 11a1c080592def10 - zh: d1189de665f9e327 -/agent-note-meaningful-package-invariant-contracts/decision/representative-implemented-checks: - en: 2f7ae4ed588d6de8 - zh: ed7ea9ccc6c54e87 -/agent-note-meaningful-package-invariant-contracts/decision/repository-gate-and-tests: - en: 37e646754f13dd65 - zh: 355ebf3ce1403117 -/agent-note-meaningful-package-invariant-contracts/alternatives-considered: - en: 74c6a5a0cfadbf88 - zh: e41356bb1a8c1fdf -/agent-note-meaningful-package-invariant-contracts/consequences: - en: 1f8d53063fb5903e - zh: 5edd23739749b065 diff --git a/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.i18n.yaml index 7e61625e6d..a009725012 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.i18n.yaml @@ -21,8 +21,8 @@ en: 0efad389c38dfe95 zh: 13dd73de20226540 /agent-note-separate-context-injection-from-turn-execution/verification: - en: 3f3db450a3fc5848 - zh: b2027393d570339d + en: 56fe8195ed669f6d + zh: 503ca0ce723c69b1 /agent-note-separate-context-injection-from-turn-execution/consequences: en: 71079e4dede5e12f zh: deb46b5a4f151678 diff --git a/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.md b/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.md index d22d396676..141fffa1e4 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.md +++ b/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.md @@ -64,7 +64,7 @@ This decision preserves the caller-owned framing decision from [unwrapped inject - Idle `inject()` immediately appends one durable inbox insertion but no model-visible `user/message`; a later waking delivery may start pre-step processing. - Active-turn injection is claimed at the nearest later pre-step boundary, after complete tool-result batches and before the request that consumes it. - Rejected or failed pre-step drops its claimed batch; input inserted after the claim remains pending. -- Unit, persistence/resume, invariant, and TUI coverage pin event order, claim ownership, and durable replay. +- Unit, persistence/resume, and TUI coverage pin event order, claim ownership, and durable replay. ## Consequences diff --git a/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md b/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md index 2c567511b2..c70d7db8a1 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md @@ -64,7 +64,7 @@ enter 分支的 `PreStepDecision.messages` 是拟议步骤的完整批次。wate - idle 状态下的 `inject()` 会立即追加一条持久 inbox 插入记录,但不会追加模型可见的 `user/message`;后续可唤醒投递可能开始 pre-step 处理。 - 活跃轮次中的注入会在最近的后续 pre-step 边界领取,并位于完整工具结果批次之后、消费它的请求之前。 - pre-step reject 或失败会丢弃其已领取批次;领取后插入的输入继续保持待处理。 -- 单元测试、持久化与 resume 测试、不变量测试和 TUI 覆盖会固定事件顺序、领取归属和持久回放。 +- 单元测试、持久化与 resume 测试和 TUI 覆盖会固定事件顺序、领取归属和持久回放。 ## 后果 diff --git a/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.i18n.yaml index ddc9c2bc46..82a4675afc 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.i18n.yaml @@ -9,8 +9,8 @@ en: 41363fca57ae20e6 zh: 1d05eeeecaafa7f3 /agent-note-terminal-llm-stream-failures/decision: - en: c3bfde5f736f8b27 - zh: f51ebb4089df6406 + en: dd1dd01dda70bfb7 + zh: 8715075a5be2f7b7 /agent-note-terminal-llm-stream-failures/alternatives-considered: en: 46153a92b2135aae zh: 0b8fdaf1f7edeb77 diff --git a/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.md b/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.md index c5d160c5df..d867cda610 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.md +++ b/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.md @@ -16,7 +16,7 @@ Retry policy had the same indirect ownership. It was discovered through the stre `LlmRuntime` is the normalization boundary for one adapter attempt. It catches only final-adapter selection, synchronous dispatch, iterator construction, and `next()` failures, converts the thrown value to immutable `LlmFailure`, and emits one terminal `finish`. Caller cancellation or an `ABORTED` failure selects the aborted reason; every other adapter failure selects error. An adapter may also emit either terminal reason directly. -The adapter-owned catch ends before each yielded chunk. Errors from `llm/stream` middleware, nested calls, adapter cleanup, chunk consumers, logging, signal checks, and assembly remain thrown as defects or lifecycle failures; they never enter model-request recovery. A transport failure after partial deltas may leave blocks open, so the stream invariant permits open blocks only for terminal error or aborted finishes. No assistant message or tool call is assembled from that incomplete output. +The adapter-owned catch ends before each yielded chunk. Errors from `llm/stream` middleware, nested calls, adapter cleanup, chunk consumers, logging, signal checks, and assembly remain thrown as defects or lifecycle failures; they never enter model-request recovery. A transport failure after partial deltas may leave blocks open before a terminal error or aborted finish. No assistant message or tool call is assembled from that incomplete output. `PreparedLlmCall` exposes the immutable retry policy captured with its config and registration. One-shot reuse and config mismatch remain synchronous `INVALID_PREPARED_CALL` misuse errors. A route served entirely by `llm/stream` middleware has no prepared registration and therefore no serving policy. diff --git a/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.zh.md b/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.zh.md index 297898d8f9..1fa31abffa 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.zh.md @@ -16,7 +16,7 @@ Status: implemented `LlmRuntime` 是一次适配器尝试的规范化边界。它只捕获最终适配器选择、同步分发、iterator 构造与 `next()` 失败,将抛出值转换为不可变 `LlmFailure`,并发出一个终止 `finish`。调用方取消或 `ABORTED` 失败选择 aborted 结束原因;其他适配器失败选择 error 结束原因。适配器也可以直接发出任一终止原因。 -适配器所属的 catch 会在每个分片被 yield 前结束。来自 `llm/stream` middleware、嵌套调用、适配器清理、分片消费方、日志记录、signal 检查与组装的错误仍作为缺陷或生命周期失败抛出;它们绝不进入模型请求恢复。部分 delta 之后的传输失败可能留下未关闭块,因此流 invariant 只允许在终止 finish 的结束原因为 error 或 aborted 时存在未关闭块。不会从这些不完整输出组装 assistant 消息或工具调用。 +适配器所属的 catch 会在每个分片被 yield 前结束。来自 `llm/stream` middleware、嵌套调用、适配器清理、分片消费方、日志记录、signal 检查与组装的错误仍作为缺陷或生命周期失败抛出;它们绝不进入模型请求恢复。部分 delta 之后的传输失败可能在结束原因为 error 或 aborted 的终止 finish 之前留下未关闭块。不会从这些不完整输出组装 assistant 消息或工具调用。 `PreparedLlmCall` 公开随其配置和注册捕获的不可变重试策略。一次性句柄复用与配置不匹配仍是同步的 `INVALID_PREPARED_CALL` 误用错误。完全由 `llm/stream` middleware 提供服务的路由没有准备完成的注册,因此也没有服务策略。 diff --git a/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.i18n.yaml index a4c259b654..bbbf53a526 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.i18n.yaml @@ -9,8 +9,8 @@ en: 6228c81e4965b688 zh: 04b702be0cfbf195 /agent-note-credential-boundaries-whole-snapshot-requests-and-atomic-route-registration/decision: - en: d2aa88cf1e9ec97b - zh: da9422723fc9ac93 + en: 052787311a4e6edc + zh: 963eed632ea198de /agent-note-credential-boundaries-whole-snapshot-requests-and-atomic-route-registration/alternatives-considered: en: 90c5883b247f7301 zh: 6d2a660d8653f805 diff --git a/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.md b/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.md index 0e65bb1093..03ea573307 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.md +++ b/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.md @@ -22,7 +22,7 @@ Two request-path defects sat beside them. DeepSeek resolved connection and crede **Route replacement is a registry operation, not a caller sequence.** `registerAdapter` returns a handle carrying `replace(providers)`: the candidate set is validated in full first (conflicts, names, provider metadata), then swapped in one synchronous section. A refused replacement leaves the previous routes registered and serving, and the caller's facts cache only advances after the registry actually holds the new set, so reverting to a working configuration re-applies. pi-ai's registration facts are sorted by provider, so a settings document that merely reorders its keys is no longer a route change. -**Contained publication for committed credential writes.** `CredentialProvider.notifyUpdated` fans `credentials/reference-updated` out one listener at a time; sync throws and async rejections are logged without changing the committed operation's outcome, and `INVARIANT`-coded failures rethrow after every listener ran — the same shape the settings seam uses for `settings/updated`. `SettingsProvider.installSection()` cleanup distinguishes its two triggers: a provider detaching still falls back to the composition entry and re-derives, while the consumer's own unload returns immediately instead of re-registering routes during teardown. +**Contained publication for committed credential writes.** `CredentialProvider.notifyUpdated` fans `credentials/reference-updated` out one listener at a time; sync throws and async rejections are logged without changing the committed operation's outcome. `SettingsProvider.installSection()` cleanup distinguishes its two triggers: a provider detaching still falls back to the composition entry and re-derives, while the consumer's own unload returns immediately instead of re-registering routes during teardown. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.zh.md b/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.zh.md index cbc66efa26..9f822b8bee 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.zh.md @@ -26,7 +26,7 @@ Status: implemented **路由替换是注册表的操作,不是调用方的一串步骤。**`registerAdapter` 返回一个携带 `replace(providers)` 的句柄:候选集合先被完整校验(冲突、名称、提供方元数据),再在一个同步区段内完成替换。被拒绝的替换会让先前的路由保持注册并继续服务,而调用方的事实缓存只有在注册表确实持有新集合之后才会推进,因此改回可用配置时会重新生效。pi-ai 的注册事实按提供方排序,因此仅仅调换键顺序的设置文档不再算作路由变更。 -**已提交的凭据写入采用收容式发布。**`CredentialProvider.notifyUpdated` 逐个监听器扇出 `credentials/reference-updated`;同步抛错与异步 rejection 都只记日志,不改变已提交操作的结果,而带 `INVARIANT` 代码的失败会在每个监听器都运行完之后重抛——与 settings seam 处理 `settings/updated` 的形状相同。`SettingsProvider.installSection()` 的清理会区分它的两个触发来源:提供方脱离时仍回退到组合的 entry 配置并重新推导,而消费方自身卸载时立即返回,不在拆卸过程中重新注册路由。 +**已提交的凭据写入采用收容式发布。**`CredentialProvider.notifyUpdated` 逐个监听器扇出 `credentials/reference-updated`;同步抛错与异步 rejection 都只记日志,不改变已提交操作的结果。`SettingsProvider.installSection()` 的清理会区分它的两个触发来源:提供方脱离时仍回退到组合的 entry 配置并重新推导,而消费方自身卸载时立即返回,不在拆卸过程中重新注册路由。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.i18n.yaml index 18ab603d7b..c64d879d90 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.i18n.yaml @@ -21,5 +21,5 @@ en: cd1ed7dfa77722ae zh: 4ad834f3cf3b5443 /agent-note-the-end-seed-log-boundary/consequences: - en: 69eb86480b191135 - zh: 51ef88fb78701b1f + en: 75dd52b899053769 + zh: a5d887987c2360cd diff --git a/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.md b/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.md index 9cf7c7f207..53a3c89db0 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.md +++ b/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.md @@ -52,4 +52,4 @@ Cost: a seeded session's log is one event longer, including an empty resumed log `session/end-seed` joins the on-disk vocabulary. The current format requires the validated marker semantics owned by Session; the frozen v0 codec and migration edge own which historical v0 seed layouts remain admissible. The exact inherited cut stays separate from the logical header and is available after a body read. -The [queued manual compaction decision](../feature/2026-07-30-queued-manual-compaction.md) now supplies the first consumer. Its tail scan independently finds the unmatched `compaction/start` and newest end-seed, treats only a start after that boundary as live, and clears the invariant trace on the same replay transition. The predicate remains in the compaction package rather than becoming a generic core helper. +The [queued manual compaction decision](../feature/2026-07-30-queued-manual-compaction.md) now supplies the first consumer. Its tail scan independently finds the unmatched `compaction/start` and newest end-seed, and treats only a start after that boundary as live. The predicate remains in the compaction package rather than becoming a generic core helper. diff --git a/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.zh.md b/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.zh.md index fdf4ec9de6..aadafe1d4c 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.zh.md @@ -52,4 +52,4 @@ Status: implemented `session/end-seed` 加入了落盘词汇表。当前格式要求由 Session 拥有的已校验 marker 语义;冻结的 v0 codec 与迁移边负责哪些历史 v0 seed 布局仍可接受。精确继承 cut 与逻辑 header 分离,并在读取正文后可用。 -[排队手动压缩决策](../feature/2026-07-30-queued-manual-compaction.zh.md)如今提供了第一个消费方。其尾部扫描会分别查找未匹配的 `compaction/start` 与最新 end-seed,只把位于该边界之后的 start 视为存活,并在同一个回放转换上清除不变量追踪状态。该谓词仍位于压缩功能所在的包中,不会成为通用核心辅助函数。 +[排队手动压缩决策](../feature/2026-07-30-queued-manual-compaction.zh.md)如今提供了第一个消费方。其尾部扫描会分别查找未匹配的 `compaction/start` 与最新 end-seed,并只把位于该边界之后的 start 视为存活。该谓词仍位于压缩功能所在的包中,不会成为通用核心辅助函数。 diff --git a/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.i18n.yaml index 18c1983fdb..f7b30f24e6 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.i18n.yaml @@ -15,5 +15,5 @@ en: 7d79dfa834d9168f zh: 0b3e82a7d0ef71b9 /agent-note-goal-owned-durable-events/consequences: - en: 0e4513ea5e3e1e2e - zh: 69d81ba52ee2f4b6 + en: b2a2f902f126f318 + zh: 8a38fe8acb0a2241 diff --git a/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.md b/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.md index 0d3e632ba2..ad02b4d989 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.md +++ b/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.md @@ -28,6 +28,6 @@ The domain does not automatically project each mutation into model input. Goal t ## Consequences -Goal state is independent of inbox placement and admission. Replay has one mutation path, projections advance directly on `goal/change`, and continuation messages carry only round attribution. The model does not receive a mutation-only `` message; model-visible state appears through goal tools and scheduled continuation prompts. Direct session writers remain trusted and can append malformed changes, which the strict fold and invariant companion reject. +Goal state is independent of inbox placement and admission. Replay has one mutation path, projections advance directly on `goal/change`, and continuation messages carry only round attribution. The model does not receive a mutation-only `` message; model-visible state appears through goal tools and scheduled continuation prompts. Direct session writers remain trusted and can append malformed changes, which the strict fold rejects. Focused goal, goal-round-driver, command, TUI, and Client RemoteMock tests pin durable replay, positive-round accounting, inbox independence, projection updates, and restored-session behavior. The keyless process test inspects the persisted `goal/change` event and verifies that creation alone starts no continuation round. diff --git a/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.zh.md b/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.zh.md index fd29a6f36e..a9730425dc 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.zh.md @@ -28,6 +28,6 @@ Goal 领域需要持久状态,但不需要拥有待处理的模型输入。继 ## 后果 -Goal 状态不依赖 inbox 放置与准入。回放只有一条变更路径,投影直接由 `goal/change` 推进,继续执行消息只携带 Round 归属。模型不会收到仅用于变更的 `` 消息;模型可见状态来自 goal 工具与已调度的继续执行提示词。直接写入会话的写入方仍受信任,并且可以追加畸形变更;严格折叠与 invariant 配套模块会拒绝这些变更。 +Goal 状态不依赖 inbox 放置与准入。回放只有一条变更路径,投影直接由 `goal/change` 推进,继续执行消息只携带 Round 归属。模型不会收到仅用于变更的 `` 消息;模型可见状态来自 goal 工具与已调度的继续执行提示词。直接写入会话的写入方仍受信任,并且可以追加畸形变更;严格折叠会拒绝这些变更。 聚焦的 goal、goal-round-driver、command、TUI 与 Client RemoteMock 测试固定持久回放、正数 Round 计数、inbox 独立性、投影更新和恢复会话行为。无密钥进程测试检查持久的 `goal/change` 事件,并验证仅创建 goal 不会启动继续执行 Round。 diff --git a/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.i18n.yaml index 85084f80cb..0572db7e04 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.i18n.yaml @@ -9,8 +9,8 @@ en: 2c36946bbff637b7 zh: f290de0866ba7caa /agent-note-the-ptc-runtime-python-fd-3-frame-protocol/decision: - en: 1f93159850fc7f48 - zh: dcc2a4788e3254f7 + en: f0d0d5ba5a9066ad + zh: fbdab3731c33eb26 /agent-note-the-ptc-runtime-python-fd-3-frame-protocol/wire-contract: en: 913f9465320dc191 zh: f8e0338645d6c204 @@ -18,8 +18,8 @@ en: 26128b65f2763920 zh: df24359faebcbfed /agent-note-the-ptc-runtime-python-fd-3-frame-protocol/alternatives-considered: - en: cc42636bd7793305 - zh: e196f980daec3037 + en: 2f52b91980d013ed + zh: 12dc9a24c03c3c68 /agent-note-the-ptc-runtime-python-fd-3-frame-protocol/consequences: en: 16df6efd46d71e85 zh: 4062475bec04ab5f diff --git a/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.md b/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.md index 097bcfd55f..16c125340b 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.md +++ b/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.md @@ -22,7 +22,7 @@ The experimental package contains both the protocol and runtime implementation: `py/protocol.py` mirrors the message shapes as `TypedDict`s and re-declares the two surfaces both sides EXECUTE against — `PROTOCOL_FD = 3` and `log_truncation_marker` — with byte-identical text. -The package ships the runtime alongside the protocol; it remains independently buildable. `check-workspace-constraints` reads every `packages///package.json` unconditionally, while the coverage and invariant-topology checks exercise the package as soon as its directory exists. +The package ships the runtime alongside the protocol; it remains independently buildable. `check-workspace-constraints` reads every `packages///package.json` unconditionally, while the coverage check exercises the package as soon as its directory exists. ## Wire contract @@ -36,7 +36,7 @@ Frames are JSON-lines on fd 3, one object per line, leaving stdout/stderr free f **Require a future Python JSON codec (`_encode_json_plain` / `_decode_json_plain`) to live in `py/protocol.py` for cross-side symmetry with `protocol.ts`.** Rejected. The repository's "prefer symmetry for parallel values" rule points at genuinely parallel values; these are not. The host-side codec in `protocol.ts` validates hostile input and is self-contained. A child-side codec would produce trusted output and belong with bootstrap-owned emission and cost accounting; forcing only its entry points into `protocol.py` would couple the vocabulary mirror to runtime internals or create an import cycle. `protocol.py` remains a pure wire-vocabulary mirror; the codec (`_encode_json_plain` / `_decode_json_plain`) lives in `bootstrap.py` with the runtime it serves. -**Keep the protocol files outside a buildable package until a runtime ships.** Rejected: the workspace-constraint, coverage, and invariant-topology checks require every directory under `packages//` to be a buildable package, and the protocol has independent tests and a public wire vocabulary. +**Keep the protocol files outside a buildable package until a runtime ships.** Rejected: the workspace-constraint and coverage checks require every directory under `packages//` to be a buildable package, and the protocol has independent tests and a public wire vocabulary. ## Consequences diff --git a/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.zh.md b/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.zh.md index ac656e421b..b5c7539844 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.zh.md @@ -22,7 +22,7 @@ CPython PTC 运行时位于 `packages/experimental/ptc-runtime-python`,以 `@d `py/protocol.py` 用 `TypedDict` 镜像消息形状,并重新声明两侧都会 EXECUTE 的两个面——`PROTOCOL_FD = 3` 与 `log_truncation_marker`——文本逐字节一致。 -该包随协议一起交付 runtime,同时保持独立可构建。`check-workspace-constraints` 会无条件读取每个 `packages///package.json`,coverage 与 invariant-topology 检查则会在包目录存在时立即覆盖该包。 +该包随协议一起交付 runtime,同时保持独立可构建。`check-workspace-constraints` 会无条件读取每个 `packages///package.json`,coverage 检查则会在包目录存在时立即覆盖该包。 ## Wire contract @@ -36,7 +36,7 @@ CPython PTC 运行时位于 `packages/experimental/ptc-runtime-python`,以 `@d **要求未来的 Python JSON codec(`_encode_json_plain` / `_decode_json_plain`)放进 `py/protocol.py`,以便与 `protocol.ts` 跨侧对称。**拒绝。仓库的 “prefer symmetry for parallel values” 规则指向真正平行的值;这两者不是。`protocol.ts` 中的 host 侧 codec 校验敌意输入且自包含。Child 侧 codec 会产出受信任输出,应与 bootstrap 拥有的发出逻辑和成本核算放在一起;只把入口强塞进 `protocol.py` 会让 vocabulary 镜像耦合 runtime 内部实现,或制造 import 环。`protocol.py` 保持纯 wire-vocabulary 镜像;codec(`_encode_json_plain`/`_decode_json_plain`)与它所服务的 runtime 一起位于 `bootstrap.py`。 -**在 runtime 交付前把协议文件放在不可构建的包外。**拒绝:workspace-constraint、coverage 与 invariant-topology 检查要求 `packages//` 下的每个目录都是可构建包,而协议本身拥有独立测试与公开 wire vocabulary。 +**在 runtime 交付前把协议文件放在不可构建的包外。**拒绝:workspace-constraint 与 coverage 检查要求 `packages//` 下的每个目录都是可构建包,而协议本身拥有独立测试与公开 wire vocabulary。 ## Consequences diff --git a/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.i18n.yaml index 93f0597f19..d68a83e2a0 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.i18n.yaml @@ -9,14 +9,14 @@ en: 5a5d4de7d40ed86b zh: abfae08f9c291c54 /agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/decision: - en: 60be2f515ff5f46d - zh: ee8514deadb08260 + en: be767ee6f00ac6c3 + zh: 7742a77e3ea12a5e /agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/testing: - en: a7b7ca354607dd21 - zh: f88356bfc7dc770e + en: 960fc4b18dff9f38 + zh: d549e33100351124 /agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/alternatives-considered: - en: eac46307265e647d - zh: acfad02f8ca1dc3f + en: ca7c9088d0793e1e + zh: ea648bac9e726c64 /agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/consequences: - en: d8576d7db9dea547 - zh: 428bb5a7e609d170 + en: de26cde94adda770 + zh: 7f39760b77958966 diff --git a/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.md b/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.md index daf48f27c2..9fe96d042c 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.md +++ b/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.md @@ -18,7 +18,7 @@ Nothing named an agent that joined no preset. The join is a scope-parent link; w **The meter is host-plane.** `dsh-token-meter` returns to the host composition and leaves the presets' `isolate` map, so `compaction-basic` and `tool-result-pruner` resolve the one host instance from inside their realm. The presets keep the realm and the backend — what a preset chooses is whether its agent compacts, not whether its tokens are counted. This is the criterion `tasks` and `goals` are already read by, applied to a Service whose *projection* reach is what made preset ownership wrong: a unit whose empty value is indistinguishable from a real one cannot be per-composition while the table it registers into is per-process. -**An unjoined agent fails at model use.** The invariant companion checks `system-prompt/assemble`, because a bare Agent can legally be bound later through `recompose`. Host-only and cold-scope prompt reads have no Agent and remain outside this check. +**Entry points join Agents explicitly.** No runtime check rejects an unjoined Agent, because a bare Agent can legally be bound later through `recompose`. Projection key presence is not a per-session capability signal ([session projection](../../../../packages/session/session-projection/README.md)). Temporary plugins mounted through `cordis_mount` belong to the composition rather than the invoking session ([tool Cordis](../../../../packages/extensions/tool-cordis/README.md)). [Declarative presets](2026-09-18-declarative-agent-presets.md) own revision retention and reclaim retired trees after their final reference releases. @@ -26,15 +26,13 @@ Projection key presence is not a per-session capability signal ([session project `apps/cli/tests/web-agent-presets.e2e.ts` reads `ctx.get('tokenMeter')` on the booted Web composition before any Agent is created — a preset-side meter sits behind an `isolate` realm and is invisible to `ctx.get`, so the read is an ownership assertion rather than a mount-order coincidence — then asserts a `minimal` session's snapshot carries all three units. -`packages/preset/agent-preset-registry/tests/invariant.spec.ts` rejects an unjoined Agent's assembly and accepts a joined Agent, a Host read and a cold-scope read. - ## Alternatives considered **Keep the meter in the preset and scope-layer the projection registry.** The precise fix, and much larger: `snapshot`, `checkpoint`, and the eager drive would each need a session→scope resolution that a cold read does not have without the api-proxy's `presenterScopeFor`. Rejected as disproportionate to one Service with no per-preset state at all; the general rule is documented on the registry instead. **Veto publication for an unjoined agent.** Loud beats silent, and the registry supports it — a synchronous `agent/created` listener that throws rolls the creation back. Rejected because composing an agent outside the roster is legal: `recompose` documents the bare agent it then binds, and the ACP bridge, the SDK server, and the headless bundle all create one. A veto would convert a capability gap into an outage. -**Check the join at `agent/created` in the companion too.** Rejected: publication cannot distinguish a missed join from an agent that will be bound later, so the check would reject a documented path. Prompt assembly can distinguish them. +**Check the join at `agent/created`.** Rejected: publication cannot distinguish a missed join from an agent that will be bound later, so the check would reject a documented path. **Move `plan-mode` and `tool-todo` off the agent plane for the same projection reason.** Rejected: both are genuinely per-preset capabilities, and their units compute an empty value for a session that never uses them, which clients already read by value (`plan.active`, an empty list). Only a unit whose empty value is indistinguishable from a real one — the meter — forces host ownership. @@ -42,4 +40,4 @@ Projection key presence is not a per-session capability signal ([session project The context meter becomes a per-session fact instead of a function of mount history. A preset can no longer opt out of token accounting; no shipped preset did, and `minimal` now says it drops auto-compaction rather than the accounting. -The invariant reaches compositions loading `dsh-invariants`. Production entry points must compose Agents explicitly; installing the registry alone does not bind them. +Production entry points must compose Agents explicitly; installing the registry alone does not bind them. An unjoined Agent receives no tools, prompt sections, or skills and reports no error. diff --git a/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.zh.md b/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.zh.md index 56babf5e71..d51a3bc715 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.zh.md @@ -18,7 +18,7 @@ Status: implemented **meter 属于宿主平面。** `dsh-token-meter` 回到宿主组装,并离开各 preset 的 `isolate` 映射,于是 `compaction-basic` 与 `tool-result-pruner` 在自己的 realm 内部解析到那一份宿主实例。preset 保留 realm 与压缩后端——preset 选择的是它的 agent 是否压缩,而不是它的 token 是否被计。这正是 `tasks` 与 `goals` 已经采用的判据,只是这次适用于一个因**投影**触达面而不该归 preset 所有的 Service:当一个单元的空值与真实值无法区分时,只要它注册进的那张表是进程级的,它就不能是逐组装的。 -**未加入的 Agent 在使用模型时失败。** 不变量 companion 检查 `system-prompt/assemble`,因为未绑定的 Agent 可以合法地稍后通过 `recompose` 绑定。纯 Host 和历史 scope 提示词读取没有 Agent,不属于此检查范围。 +**入口显式让 Agent 加入。** 没有运行时检查会拒绝未加入的 Agent,因为未绑定的 Agent 可以合法地稍后通过 `recompose` 绑定。 投影 key 是否存在不能代表逐会话能力([会话投影](../../../../packages/session/session-projection/README.zh.md))。通过 `cordis_mount` 挂载的临时插件属于组装,而非调用会话([Cordis 工具](../../../../packages/extensions/tool-cordis/README.zh.md))。[声明式 preset](2026-09-18-declarative-agent-presets.zh.md) 负责代际保留,在最后一个引用释放后回收退役树。 @@ -26,15 +26,13 @@ Status: implemented `apps/cli/tests/web-agent-presets.e2e.ts` 在创建任何 Agent **之前**,于已启动的 Web 组装上读取 `ctx.get('tokenMeter')`——preset 侧的 meter 会待在 `isolate` realm 里,对 `ctx.get` 不可见,因此这次读取是一次所有权断言而不是挂载顺序的巧合——随后断言一个 `minimal` 会话的快照带齐三个单元。 -`packages/preset/agent-preset-registry/tests/invariant.spec.ts` 拒绝未加入的 Agent 组装,并接受已加入的 Agent、Host 读取和历史 scope 读取。 - ## 考虑过的替代方案 **把 meter 留在 preset,改为给投影注册表分层。** 这是更精确的修法,代价也大得多:`snapshot`、`checkpoint` 与主动驱动都需要一次「会话 → 作用域」的解析,而冷读在没有 api-proxy 的 `presenterScopeFor` 时并不具备。相对于一个完全没有 per-preset 状态的 Service,这不成比例,因此改为把通则写在注册表上。 **对未加入的 agent 否决发布。** 大声胜过静默,注册表也支持这么做——同步的 `agent/created` 监听器抛出会把创建整体回滚。否决的理由是:在名单之外组装 agent 是合法的——`recompose` 写明了它随后绑定的那个裸 agent,而 ACP 桥、SDK server 与 headless bundle 都会创建一个。否决会把能力缺口变成一次故障。 -**让配套也在 `agent/created` 处检查加入情况。** 否决:发布时分不清漏掉的加入与之后才会被绑定的 agent,因此该检查会拒绝一条已写明的路径。提示词组装分得清。 +**在 `agent/created` 处检查加入情况。** 否决:发布时分不清漏掉的加入与之后才会被绑定的 agent,因此该检查会拒绝一条已写明的路径。 **基于同样的投影理由,把 `plan-mode` 与 `tool-todo` 也搬离 agent 平面。** 否决:两者确实是逐 preset 的能力,且对从不使用它们的会话,其单元算出的就是空值,而客户端本来就按值读取(`plan.active`、空列表)。只有空值与真实值无法区分的单元——meter——才被迫归宿主所有。 @@ -42,4 +40,4 @@ Status: implemented context meter 成为逐会话的事实,而不再是挂载历史的函数。代价是 preset 不能再选择不做 token 记账;随附的 preset 没有一个这么做,`minimal` 现在也写明它放弃的是自动压缩而非记账。 -不变量覆盖加载了 `dsh-invariants` 的组装。生产入口必须显式组装 Agent;仅安装注册表不会自动绑定。 +生产入口必须显式组装 Agent;仅安装注册表不会自动绑定。未加入的 Agent 不会获得工具、提示词段落或 skill,也不会报告错误。 diff --git a/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.i18n.yaml index b0f473168f..2de69324c0 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.i18n.yaml @@ -33,8 +33,8 @@ en: 4ec0e1c76da8d3b6 zh: 23de66f8fc659c15 /agent-note-remote-event-delivery-ctx-remote-on/consequences: - en: c50a0968e3f65df7 - zh: 786f33cf1e95dae4 + en: b5e5bdd536743b30 + zh: 78948abf22f16bdb /agent-note-remote-event-delivery-ctx-remote-on/related: en: b64e771dafa6c05b zh: 4c2f47884f5a2d19 diff --git a/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.md b/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.md index 3eaa26fb92..e2cc1dc6bc 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.md +++ b/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.md @@ -194,7 +194,7 @@ The few required Client symbols are mirrored on the test side: `scaffold.ts` exp - **Test-side mirrors can drift.** No mechanism compares mirrored Client constants under `apps/web/tests` with their source. Drift instead produces a selector mismatch. `apps/web/tests/README.md` records the review rule; a grep-level gate is deliberately omitted. - **Capabilities deliberately omitted.** Payload projection and redaction are unsupported, scopes other than Agent are unsupported, and ordinary notifications are not replayed. Recoverable state needs a query, cursor, or opening baseline; a waterfall is replayed only while its original Host invocation remains pending. - **Some Client packages remain in the Host graph.** Twelve projects, including `connection`, `runtime`, and `ui-slots`, remain reachable through unsplit `directory-picker-browse`/`-native` and `api/gateway → client/connection`. They compile and no longer pull in API Remotes' Client face, so this change does not split them. Direct `dsh-client-runtime/client` imports in two chat e2e files rely on Runtime's current presence in that graph rather than a general guarantee. -- **The package intentionally publishes no invariant companion.** A prior revision asserted delivery form on the live event bus, coupling diagnostics to the allowlist and causing Rolldown to emit a third bundle chunk omitted by the mechanically derived publication list. The Host-face `TypertForwardableEventEntry` assertion already rejects those mismatches at compile time, and the package README records why no independent runtime relation remains. +- **Delivery form is checked at compile time only.** The Host-face `TypertForwardableEventEntry` assertion rejects delivery-form mismatches; no runtime check inspects delivery form on the live event bus. ## Related diff --git a/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.zh.md b/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.zh.md index d41589f1b8..982bbec798 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.zh.md @@ -196,7 +196,7 @@ Client 要求首项是带非空 `clientId` 与 `host.home` 的 `ready`;后续 - **测试侧镜像值可能漂移**:没有任何机制核对 `apps/web/tests` 中镜像的 client 常量与其源;安全网只是漂移会让选择器失配。规则写在 `apps/web/tests/README.md`,由 review 守;grep 级门禁经评估后刻意不做。 - **放弃的能力**:不支持投影或脱敏载荷,不支持 Agent 以外的 Scope,也不为普通通知提供重放。需要可靠恢复的状态必须拥有查询、cursor 或 opening baseline;waterfall 只重放仍处于同一次 Host 调用生命周期内的 pending request。 - **仍有 client 包留在 host 图里**:12 个工程(`connection`、`runtime`、`ui-slots` 等)经未拆分的 `directory-picker-browse`/`-native` 与 `api/gateway → client/connection` 仍可达 host 图。它们都能编译且不再牵连 api/remotes 的 client face,因此没有阻塞本次改动;拆分那些包能减少几个,但经评估后不做。两个 chat e2e 直接引 `dsh-client-runtime/client` 依赖 `runtime` 本来就在图里——属偶然而非保证。 -- **本包不发布 invariant companion**:早先的修订曾在活事件总线上断言投递形状(`thisArg === null`、`mode === 'emit'`),这让诊断逻辑与名单值耦合,并使 rolldown 把它提成第三个 bundle chunk——而机械推导的发布文件清单并不携带它。Host 面的 `TypertForwardableEventEntry` 断言已在编译期拒绝这些偏离,包 README 也记录了不再存在独立运行时关系的原因。 +- **投递形式只在编译期检查**:Host 面的 `TypertForwardableEventEntry` 断言拒绝投递形式偏离;没有运行时检查会在活事件总线上检查投递形式。 ## 相关 diff --git a/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.i18n.yaml index 94fd1f56d0..bc46999689 100644 --- a/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.i18n.yaml @@ -21,5 +21,5 @@ en: ef7c5f6fa346c1d8 zh: 439b0d8394015f1e /agent-note-credential-records-and-authorization-flows/testing: - en: 6c8c01f75e6f916b - zh: 7c5c7491f64f2936 + en: 89f5714501a92b19 + zh: c92fac2c35532db1 diff --git a/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.md b/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.md index 6bf65761c9..ad7c402aed 100644 --- a/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.md +++ b/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.md @@ -61,7 +61,7 @@ Two limits are recorded in the package READMEs rather than fixed. An attempt is ## Testing -The seam's suite pins the lifecycle it owns: single-flight refusal and release, withdrawal before the flow starts and during it, a flow that ignores its signal, the commit confirmation, and the settlement event including the `failed` case a caller sees as a thrown error. The invariant companion pins that a settled key is a free key, because a wedged one is otherwise invisible. +The seam's suite pins the lifecycle it owns: single-flight refusal and release, withdrawal before the flow starts and during it, a flow that ignores its signal, the commit confirmation, and the settlement event including the `failed` case a caller sees as a thrown error. `llm-pi-ai` covers the three translations against a real `$DSH_HOME` document — an api-key credential field by field, an OAuth credential verbatim including its refresh half, a foreign plugin's record skipped by scope, and the write refusal without a credentials service — plus every `AuthEvent` and `AuthPrompt` member restated, with `Models.login()` mocked at the collection boundary since a real one opens a browser. Two real-composition tests boot the plugin with and without the authorization seam. diff --git a/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.zh.md b/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.zh.md index 72ec310c2f..8600104f36 100644 --- a/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.zh.md @@ -61,7 +61,7 @@ seam 的边缘与写入路径同一纪律。prompt 被拒是结果而非故障 ## Testing -seam 自己的套件钉住它拥有的生命周期:单飞的拒绝与释放、flow 启动前与进行中的撤销、一个忽略自身信号的 flow、提交核实,以及包含"调用方看到的是抛出错误"那种 `failed` 情形的结算事件。invariant companion 钉住"已结算的键就是空闲的键",因为被卡住的键否则不可见。 +seam 自己的套件钉住它拥有的生命周期:单飞的拒绝与释放、flow 启动前与进行中的撤销、一个忽略自身信号的 flow、提交核实,以及包含"调用方看到的是抛出错误"那种 `failed` 情形的结算事件。 `llm-pi-ai` 针对一份真实的 `$DSH_HOME` 文档覆盖三处翻译——逐字段的 api-key 凭据、连 refresh 半边一起原样保存的 OAuth 凭据、按 scope 跳过的他插件记录,以及没有凭据服务时的写入拒绝——外加每一个 `AuthEvent` 与 `AuthPrompt` 成员的重述;`Models.login()` 在集合边界处被 mock,因为真实登录会打开浏览器。两个真实组合测试分别在挂载与不挂载授权 seam 的情况下启动插件。 diff --git a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml index c0aa66e83f..84b42ea748 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml @@ -9,8 +9,8 @@ en: e45938d278a6c12d zh: 93c790d466f7bd12 /agent-note-publish-agent-teams-under-experimental-package-names/decision: - en: a2555b0c5a6f6f53 - zh: f7082ceb8542b0fb + en: 2195965b69c27c77 + zh: 0ce002b6fb25aab4 /agent-note-publish-agent-teams-under-experimental-package-names/alternatives-considered: en: 869decdfbc7b3466 zh: b51f5ab731d1a84a diff --git a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md index 92eba148b3..e1b40e0afb 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md +++ b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md @@ -22,7 +22,7 @@ The [single-bundle decision](2026-09-18-agent-teams-single-bundle.md) supersedes Profile startup resolves selected bundles before computing the [immutable runtime resolution](2026-09-09-profile-resolution-generations.md). The runtime resolution retains installation-first precedence, traverses each explicit bundle root completely in profile order, and keeps pnpm-managed profile packages authoritative. The runtime interception enforces the result in memory without materializing fallback links. A private profile layer can therefore carry experimental plugin rows without adding those plugins to a release app, requiring profile users to install transitive packages directly, weakening packaged-runtime module identity, or changing another profile's resolution. -Experimental status changes compatibility and support expectations, not publication for these four packages. They retain the repository's ordinary documentation, invariant, lifecycle, security, unit, real-composition, and snapshot requirements. Promotion still requires review of the public contracts, limitations, test evidence, runtime dependents, and a named owner accepting stable-package obligations. +Experimental status changes compatibility and support expectations, not publication for these four packages. They retain the repository's ordinary documentation, lifecycle, security, unit, real-composition, and snapshot requirements. Promotion still requires review of the public contracts, limitations, test evidence, runtime dependents, and a named owner accepting stable-package obligations. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.zh.md b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.zh.md index 6f4d004a78..f9a1abc76d 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.zh.md @@ -22,7 +22,7 @@ dsh 打包与发布集合以及本地基线发布器包含这四个 Agent Teams profile 启动会先解析所选 bundle,再计算[不可变 runtime resolution](2026-09-09-profile-resolution-generations.zh.md)。runtime resolution 保留安装优先顺序,按 profile 顺序完整遍历每个显式 bundle 根,并让 pnpm 管理的 profile 包保持优先。运行时拦截在内存中强制该结果,不物化 fallback 链接。因此,私有 profile 层可以携带实验性 plugin 配置行,而无需把这些 plugin 加入发布 app、要求 profile 用户直接安装传递依赖、破坏 packaged-runtime 的模块身份,或改变其他 profile 的解析结果。 -对这四个包而言,实验性状态改变兼容性与支持预期,而不阻止发布。这些包仍须满足仓库的一般文档、不变式、生命周期、安全、单元测试、真实组合测试和快照要求。promotion 前仍须评审公开约定、限制、测试证据、运行时依赖方,并由一名具名 owner 接受稳定包义务。 +对这四个包而言,实验性状态改变兼容性与支持预期,而不阻止发布。这些包仍须满足仓库的一般文档、生命周期、安全、单元测试、真实组合测试和快照要求。promotion 前仍须评审公开约定、限制、测试证据、运行时依赖方,并由一名具名 owner 接受稳定包义务。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml index 9de76f79b1..06fdbd1ffa 100644 --- a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml @@ -21,8 +21,8 @@ en: eb69576e756f4aa1 zh: 5c1923159da3f7a2 /agent-note-deepseek-llm-api-request-extensions-for-session-logs-and-plugin-packages/verification: - en: 0bda0074b5e11b44 - zh: 4989c1bb840c78ce + en: 9935338a0970d09d + zh: a9a36702bc72a9f7 /agent-note-deepseek-llm-api-request-extensions-for-session-logs-and-plugin-packages/alternatives-considered: en: 5faf7699272f99b4 zh: 7a402e24c80131cf diff --git a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md index 15dc88a3b6..c43b73d0eb 100644 --- a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md +++ b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md @@ -50,7 +50,7 @@ The process-lifetime manifest-identity cache remains separate because in-process ## Verification -Registry tests pin duplicate ownership, effect-scoped disposal, detached field values, concurrent and abortable preparation, receiver-preserving acceptance, one acceptance settlement, and failure aggregation. Session tests pin default-on and explicit-off policies, full-first/suffix-later delivery, direct complete event envelopes independent of base-body messages, incremental watermark folding, persisted restart recovery, fork identity fencing, out-of-order acceptance, and late invariant loading. Package-inventory tests pin default-on and explicit-off policies, host and standing-preset discovery, conflicting Loader resolution bases, manifest resolution, lifecycle filtering, and exact name/version ordering. The direct adapter mock proves pre-HTTP preparation failure, cancellation, non-2xx non-acceptance, 2xx acceptance before a later stream failure, and field collision. Keyless replay pins post-2xx extension acceptance, and the TypeScript JSON-RPC plus Python packaged-runtime snapshots project the acceptance event through both SDKs. Real Loader composition pins default package metadata and Session upload plus explicit upload disablement, one real-API request mounts both shipped extensions and proves the official endpoint accepts them, and pi-ai tests retain their unchanged wire requests. +Registry tests pin duplicate ownership, effect-scoped disposal, detached field values, concurrent and abortable preparation, receiver-preserving acceptance, one acceptance settlement, and failure aggregation. Session tests pin default-on and explicit-off policies, full-first/suffix-later delivery, direct complete event envelopes independent of base-body messages, incremental watermark folding, persisted restart recovery, fork identity fencing, and out-of-order acceptance. Package-inventory tests pin default-on and explicit-off policies, host and standing-preset discovery, conflicting Loader resolution bases, manifest resolution, lifecycle filtering, and exact name/version ordering. The direct adapter mock proves pre-HTTP preparation failure, cancellation, non-2xx non-acceptance, 2xx acceptance before a later stream failure, and field collision. Keyless replay pins post-2xx extension acceptance, and the TypeScript JSON-RPC plus Python packaged-runtime snapshots project the acceptance event through both SDKs. Real Loader composition pins default package metadata and Session upload plus explicit upload disablement, one real-API request mounts both shipped extensions and proves the official endpoint accepts them, and pi-ai tests retain their unchanged wire requests. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md index 4358e0f86e..56814925ce 100644 --- a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md @@ -50,7 +50,7 @@ Status: implemented ## 验证 -注册表测试固定重复所有权、effect 作用域 dispose(资源释放)、分离字段值、并发且可取消的准备、保留接收者的接受操作、单次接受结算与失败聚合。会话测试固定默认开启与显式关闭策略、首次完整/后续后缀交付、与基础正文消息无关的直接完整事件信封、增量水位 fold、持久化重启恢复、fork 身份围栏、乱序接受与 invariant 延迟加载。插件包清单测试固定默认开启与显式关闭策略、宿主与 standing preset 发现、冲突的 Loader 解析基址、manifest 解析、生命周期过滤及确切名称/版本排序。直接适配器 mock 测试证明 HTTP 前准备失败、取消、非 2xx 不接受、2xx 在后续流失败前接受,以及字段冲突。无密钥回放会固定 2xx 后扩展接受,TypeScript JSON-RPC 与 Python 打包运行时快照则通过两套 SDK 投影接受事件。真实 Loader 组合会固定默认包元数据、默认会话上传与显式关闭上传,一个真实 API 请求会挂载两个随附扩展并证明官方端点接受它们;pi-ai 测试保持其协议请求不变。 +注册表测试固定重复所有权、effect 作用域 dispose(资源释放)、分离字段值、并发且可取消的准备、保留接收者的接受操作、单次接受结算与失败聚合。会话测试固定默认开启与显式关闭策略、首次完整/后续后缀交付、与基础正文消息无关的直接完整事件信封、增量水位 fold、持久化重启恢复、fork 身份围栏与乱序接受。插件包清单测试固定默认开启与显式关闭策略、宿主与 standing preset 发现、冲突的 Loader 解析基址、manifest 解析、生命周期过滤及确切名称/版本排序。直接适配器 mock 测试证明 HTTP 前准备失败、取消、非 2xx 不接受、2xx 在后续流失败前接受,以及字段冲突。无密钥回放会固定 2xx 后扩展接受,TypeScript JSON-RPC 与 Python 打包运行时快照则通过两套 SDK 投影接受事件。真实 Loader 组合会固定默认包元数据、默认会话上传与显式关闭上传,一个真实 API 请求会挂载两个随附扩展并证明官方端点接受它们;pi-ai 测试保持其协议请求不变。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.i18n.yaml index 6a5de04c23..6077e29553 100644 --- a/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.i18n.yaml @@ -18,8 +18,8 @@ en: a217a08932938062 zh: 4924d99d55d9c973 /agent-note-the-system-prompt-is-surface-node-0/decision/ownership-in-the-loop: - en: 88945024c2efe271 - zh: 05dca84a4ce5e614 + en: a7d0d3a61d2fef04 + zh: 29a3d5dbed2f987a /agent-note-the-system-prompt-is-surface-node-0/decision/consumers: en: 39f9f4acffcccdc6 zh: 36b23a57e7d28950 @@ -33,5 +33,5 @@ en: bccea8236b4c32b3 zh: a1150784718a6639 /agent-note-the-system-prompt-is-surface-node-0/testing: - en: f5b21c4863cd92bd - zh: 7d1efed827d55161 + en: 9a5a241ae3d0c676 + zh: 8f3e83cc54604bcc diff --git a/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md b/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md index 1500fd2350..2796b9dd52 100644 --- a/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md +++ b/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md @@ -36,7 +36,7 @@ When the initial rendered prompt is empty, the loop reserves an empty system hea `dsh-agent-loop` owns `SystemPromptProjection` beside `RuntimeContextProjection` in `packages/core/agent-loop/src/runtime-context.ts`. It reads the surviving `system/message` nodes from the current surface on every projection, so a compaction or replacement that ran earlier in the same step is already reflected. `project(rendered, { inHistory, startsSeries })` returns `{ message, intent }` — `intent` is `{ surfaceOp: 'append' }` when no system node survives or when the [in-history rule](../feature/2026-09-02-in-history-system-prompt-replacement.md) applies, otherwise a replacement of exactly the latest surviving system node — or `undefined` when the latest node already holds the rendered text. -In `packages/core/agent-loop/src/agent.ts`, `preStep` renders the prompt with `renderPrompt(assembly)` and projects it after the `agent/pre-step` waterfall, so a compaction provider's replacement inside that waterfall is visible to the decision; `turn()` commits the `system/message` immediately after `step/start` and before the step's `user/message` events, so log order is wire order. `buildRequest` sets no `system` on the request: the request is `header.config`, `session.deriveMessages()` (system message first), and `header.tools`. The loop step order is: claim inbox → `systemPrompt.assemble()` → project runtime context → `agent/pre-step` waterfall → project system prompt → `step/start` → commit `system/message` (when changed) → commit `user/message`s → `agent/request` waterfall → `request/header` → `request/context` → stream. The `dsh-agent-loop/invariant` companion (`packages/core/agent-loop/src/invariant.ts`) asserts that a loop-built request has `system === undefined` and `messages` equal to `deriveMessages()`. +In `packages/core/agent-loop/src/agent.ts`, `preStep` renders the prompt with `renderPrompt(assembly)` and projects it after the `agent/pre-step` waterfall, so a compaction provider's replacement inside that waterfall is visible to the decision; `turn()` commits the `system/message` immediately after `step/start` and before the step's `user/message` events, so log order is wire order. `buildRequest` sets no `system` on the request: the request is `header.config`, `session.deriveMessages()` (system message first), and `header.tools`. The loop step order is: claim inbox → `systemPrompt.assemble()` → project runtime context → `agent/pre-step` waterfall → project system prompt → `step/start` → commit `system/message` (when changed) → commit `user/message`s → `agent/request` waterfall → `request/header` → `request/context` → stream. `dsh-token-meter` anchors usage to the priced surface immediately before the successful `assistant/message`, not to `step/start`. The loop admits the system prompt and user messages after step start, and retry recovery can replace nodes before rebuilding the request. Capturing that current surface includes every admitted input once; the embedded provider output remains separately priced so durable assistant rewrites retain their signed delta. The open step stores only turn and step for lifecycle validation, not a second node snapshot. @@ -90,6 +90,5 @@ The [canonical-envelope specification](../../../../packages/session/session-form - `packages/core/session/tests/surface.spec.ts` (`system/message surface node` block) pins the leading system-role projection, the empty-content `null` projection, `assertSystemHeadRewrite`'s acceptance and rejection paths, the unprotected later system nodes, and the rejection of a seeded `system/message` with a non-system role or non-plugin source. - `packages/core/agent-loop/tests/system-prompt-projection.spec.ts` pins the append on first render (including empty), the later non-empty prompt at the derived head in replacement mode, the no-op on an unchanged prompt, the replacement of the latest surviving node on change, the tail append after a replacement shadowed a non-head system node, and the in-history append and re-baseline rules. - `packages/core/agent-loop/tests/request-reconstruction.spec.ts` (`a system-prompt change replaces surface node 0 and starts a new series under the same header`) pins the `series` header that follows a prompt replacement. -- `packages/core/agent-loop/tests/invariant.spec.ts` pins the companion's rejection of a loop request carrying a `system` field and its `messages` equality check against the boundary derivation. - `packages/llm/llm-deepseek/tests/serialize.spec.ts` (`serializes a leading system message byte-for-byte like the same prompt passed as options.system`) pins wire identity. `packages/llm/llm-pi-ai/tests/context.spec.ts` compares both system sources on text and image paths. `packages/compaction/compaction-basic/tests/compaction-basic.spec.ts` pins the derived prefix, routed tools, absent separate `system` option, and protected non-empty or empty head through the region transaction and default summarizer. - The recorded snapshots under `snapshots/` pin the model-visible wire request of every shipped profile; a recorded session that renders a prompt carries the `system/message` event at surface node 0 in its `session.jsonl`, and a session with a mid-session prompt change carries the replacement of node 0 or, on an in-history route, the appended node. diff --git a/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.zh.md b/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.zh.md index 306f347092..417bf5fb3e 100644 --- a/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.zh.md @@ -36,7 +36,7 @@ Status: implemented `dsh-agent-loop` 在 `packages/core/agent-loop/src/runtime-context.ts` 中与 `RuntimeContextProjection` 并列拥有 `SystemPromptProjection`。它在每次投影时从当前 surface 读取存活的 `system/message` 节点,因此同一步骤中更早运行的压缩或替换已经反映在内。`project(rendered, { inHistory, startsSeries })` 返回 `{ message, intent }`——没有系统节点存活或[历史内规则](../feature/2026-09-02-in-history-system-prompt-replacement.zh.md)适用时 `intent` 为 `{ surfaceOp: 'append' }`,否则是对最新存活系统节点的精确替换——最新节点已持有渲染文本时返回 `undefined`。 -在 `packages/core/agent-loop/src/agent.ts` 中,`preStep` 用 `renderPrompt(assembly)` 渲染提示词,并在 `agent/pre-step` waterfall 之后投影它,因此压缩提供者在该 waterfall 内做出的替换对决定可见;`turn()` 紧接在 `step/start` 之后、该步骤的 `user/message` 事件之前提交 `system/message`,因此日志顺序即协议顺序。`buildRequest` 不在请求上设置 `system`:请求由 `header.config`、`session.deriveMessages()`(系统消息在先)和 `header.tools` 构成。循环步骤顺序为:领取收件箱 → `systemPrompt.assemble()` → 投影运行时上下文 → `agent/pre-step` waterfall → 投影系统提示词 → `step/start` → 提交 `system/message`(有变化时) → 提交各条 `user/message` → `agent/request` waterfall → `request/header` → `request/context` → 流式请求。`dsh-agent-loop/invariant` 伴随组件(`packages/core/agent-loop/src/invariant.ts`)断言循环构建的请求满足 `system === undefined` 且 `messages` 等于 `deriveMessages()`。 +在 `packages/core/agent-loop/src/agent.ts` 中,`preStep` 用 `renderPrompt(assembly)` 渲染提示词,并在 `agent/pre-step` waterfall 之后投影它,因此压缩提供者在该 waterfall 内做出的替换对决定可见;`turn()` 紧接在 `step/start` 之后、该步骤的 `user/message` 事件之前提交 `system/message`,因此日志顺序即协议顺序。`buildRequest` 不在请求上设置 `system`:请求由 `header.config`、`session.deriveMessages()`(系统消息在先)和 `header.tools` 构成。循环步骤顺序为:领取收件箱 → `systemPrompt.assemble()` → 投影运行时上下文 → `agent/pre-step` waterfall → 投影系统提示词 → `step/start` → 提交 `system/message`(有变化时) → 提交各条 `user/message` → `agent/request` waterfall → `request/header` → `request/context` → 流式请求。 `dsh-token-meter` 把用量锚定到成功的 `assistant/message` 之前的已计价 surface,而不是 `step/start`。循环在步骤开始之后接纳系统提示词与用户消息,重试恢复还可能在重建请求之前替换节点。捕获当前 surface 会让每个已接纳输入恰好计入一次;内嵌的提供方输出仍单独计价,因此持久 assistant 改写保留其带符号增量。开放步骤只保存 turn 与 step 以验证生命周期,不保存第二份节点快照。 @@ -90,6 +90,5 @@ Status: implemented - `packages/core/session/tests/surface.spec.ts`(`system/message surface node` 块)钉住开头 system 角色的投影、空内容的 `null` 投影、`assertSystemHeadRewrite` 的接受与拒绝路径、更后位置系统节点不受保护,以及对 seed 中非 system 角色或非插件 source 的 `system/message` 的拒绝。 - `packages/core/agent-loop/tests/system-prompt-projection.spec.ts` 钉住首次渲染时的追加(包括空提示词)、替换模式下后来非空提示词位于派生历史头部、提示词未变时的无操作、变更时对最新存活节点的替换、替换遮蔽了非头部系统节点之后的尾部追加,以及历史内追加与重新基线规则。 - `packages/core/agent-loop/tests/request-reconstruction.spec.ts`(`a system-prompt change replaces surface node 0 and starts a new series under the same header`)钉住提示词替换之后跟随的 `series` header。 -- `packages/core/agent-loop/tests/invariant.spec.ts` 钉住伴随组件对携带 `system` 字段的循环请求的拒绝,以及其 `messages` 与边界派生结果的相等性检查。 - `packages/llm/llm-deepseek/tests/serialize.spec.ts`(`serializes a leading system message byte-for-byte like the same prompt passed as options.system`)钉住协议一致性。 `packages/llm/llm-pi-ai/tests/context.spec.ts` 在文本与图片路径上比较两种系统提示词来源。`packages/compaction/compaction-basic/tests/compaction-basic.spec.ts` 通过区域事务与默认摘要器钉住派生前缀、已路由工具、不携带单独 `system` 选项,以及非空或空头节点的保护。 - `snapshots/` 下的录制快照钉住每个随发 profile 的模型可见协议请求;渲染了提示词的录制会话在其 `session.jsonl` 中于 surface 第 0 号节点携带 `system/message` 事件,会话中途发生提示词变更的会话则携带对第 0 号节点的替换,或在历史内路由上携带追加的节点。 diff --git a/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.i18n.yaml index 69dfebb312..b69ae4ba27 100644 --- a/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.i18n.yaml @@ -15,5 +15,5 @@ en: d5d9d6c2c05e563b zh: 66b2489d947365fe /agent-note-one-output-ring-one-projection-one-event-stream-for-background-jobs/consequences: - en: 23b0112bba743396 - zh: 28f38ff246b4525f + en: 464eb843a71eb04e + zh: 783062f5b288b1b4 diff --git a/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.md b/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.md index 2742ff1a80..6b7e847dac 100644 --- a/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.md +++ b/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.md @@ -32,4 +32,4 @@ The job seam had grown by accretion. Producers declared their output twice — a The model-visible text is unchanged: status lines, tool descriptions and schemas, and notice wording are byte-identical, and the only new model-visible fact is the kill reason a `killed` status line now carries. A producer that needs to stream output picks pull sources or `append`; it never formats a consuming delta. The Web client renders every live job as expandable, so a job that never writes shows an empty running panel instead of a static row. `JobKindMap` merges are host-side, so a browser program types `JobView.kind` as the kinds it knows and treats the rest as opaque strings, which is all the roster does with it. -Review follow-ups after the master merge tightened four of these edges. A job's first event is always `registered`: the announcement precedes the pump, whose first drain runs synchronously and may announce output. A settled ring keeps every byte the model cursor has not consumed until the first terminal model read, so a job that finishes before its first `job_output` loses nothing the live cap retained. `dsh-tool-jobs` holds one claim per wait call with a `live` flag that the caller's abort clears synchronously from the abort event, so a settlement announced before the registry's rejection reaches the tool still delivers its notice, and a timed-out wait drops only its own claim while a concurrent wait keeps the settlement covered. The browser records an observation failure that precedes the anchor as an errored view, so the panel shows the notice instead of staying blank. The `@deepseek-ai/dsh-jobs/invariant` companion checks the announced event protocol per job (registered first, one settlement, removal last) and each announced projection against the registry's own read, which is the cross-observation the [companion rule](../simplification/2026-08-28-omit-unneeded-invariant-companions.md) requires; the earlier single-view field checks are gone. The spill file a pull source keeps is job metadata (`JobView.output.spillPaths`, reported by every pump read and withdrawn once a read stops naming it), not per-chunk metadata, and the model's read renders the shell tools' dropped-output notice for a producer-side gap as well as for ring eviction, naming those files even after the gap chunk itself was evicted — the `before this read` variant this change had introduced is gone. An observation generation that sees its job removed mid-stream (the owner's teardown) closes with the removed job's terminal projection instead of a failed read. A rejected background spawn produces no process output, so the shell providers now serve the `spawn failed: …` note as the whole observed stderr stream (the consuming read still folds it in once), and `ShellProcess.observed` with both readers is required: the model's first `job_output` after such a failure reads exactly what master's consuming read rendered. +Review follow-ups after the master merge tightened four of these edges. A job's first event is always `registered`: the announcement precedes the pump, whose first drain runs synchronously and may announce output. A settled ring keeps every byte the model cursor has not consumed until the first terminal model read, so a job that finishes before its first `job_output` loses nothing the live cap retained. `dsh-tool-jobs` holds one claim per wait call with a `live` flag that the caller's abort clears synchronously from the abort event, so a settlement announced before the registry's rejection reaches the tool still delivers its notice, and a timed-out wait drops only its own claim while a concurrent wait keeps the settlement covered. The browser records an observation failure that precedes the anchor as an errored view, so the panel shows the notice instead of staying blank. The spill file a pull source keeps is job metadata (`JobView.output.spillPaths`, reported by every pump read and withdrawn once a read stops naming it), not per-chunk metadata, and the model's read renders the shell tools' dropped-output notice for a producer-side gap as well as for ring eviction, naming those files even after the gap chunk itself was evicted — the `before this read` variant this change had introduced is gone. An observation generation that sees its job removed mid-stream (the owner's teardown) closes with the removed job's terminal projection instead of a failed read. A rejected background spawn produces no process output, so the shell providers now serve the `spawn failed: …` note as the whole observed stderr stream (the consuming read still folds it in once), and `ShellProcess.observed` with both readers is required: the model's first `job_output` after such a failure reads exactly what master's consuming read rendered. diff --git a/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.zh.md b/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.zh.md index 1fa61919a2..2b3db53701 100644 --- a/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.zh.md @@ -32,4 +32,4 @@ job seam 是逐步堆出来的。生产者要把输出声明两次——模型 模型可见文本保持不变:状态行、工具描述与 schema、播报措辞逐字节相同,唯一新增的模型可见事实是 `killed` 状态行现在携带的 kill 原因。需要流式输出的生产者选择拉取源或 `append`,从不格式化消耗式增量。Web 客户端把每个进行中的 job 渲染为可展开,因此从不写输出的 job 显示为空的运行中面板而非静态行。`JobKindMap` 的合并发生在宿主侧,浏览器程序把 `JobView.kind` 视为它认识的 kind 集合,其余当作不透明字符串——名册对它也只做这些。 -合并 master 之后的评审跟进收紧了其中四条边。job 的第一个事件永远是 `registered`:通告先于泵,而泵的首轮 drain 同步执行且可能通告输出。已结算的环在模型的首次终态读取之前保留模型游标尚未消费的全部字节,因此在首次 `job_output` 之前结束的 job 不会丢失运行期上限保留的任何内容。`dsh-tool-jobs` 为每次等待调用保存一条带 `live` 标记的认领,调用方 abort 时在 abort 事件里同步清掉该标记,因此注册表的拒绝尚未传回工具之前宣布的结算仍会投递通知;超时的等待只丢弃自己的认领,并发的另一个等待仍覆盖该结算。浏览器把先于锚点发生的观察失败记录为带错误的视图,面板显示提示而不是留白。`@deepseek-ai/dsh-jobs/invariant` 伴生插件校验每个 job 的事件协议(先 `registered`、恰一次结算、最后 `removed`)以及每个通告的投影与注册表自身读取的一致性,这正是[伴生插件规则](../simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md)要求的交叉观察;先前只针对单个视图的字段校验已删除。拉取源保留的 spill 文件是 job 元数据(`JobView.output.spillPaths`,每次泵读取都会上报,读取不再点名时即撤回),不是逐块元数据;模型读取对生产者侧缺口与环淘汰都渲染 shell 工具的丢失输出提示并列出这些文件,即使缺口块本身已被淘汰——本次变更曾引入的 `before this read` 变体已删除。观察生成过程若在流中看到其 job 被移除(拥有者 teardown),以被移除 job 的终态投影收尾,而不是一次失败的读取。被拒绝的后台 spawn 不产生任何进程输出,因此 shell provider 现在把 `spawn failed: …` 提示作为 observed stderr 的整条流提供(消耗式读取仍只折入一次),且 `ShellProcess.observed` 及其两个读取器改为必填:这类失败之后模型的首次 `job_output` 读到的正是 master 的消耗式读取所渲染的内容。 +合并 master 之后的评审跟进收紧了其中四条边。job 的第一个事件永远是 `registered`:通告先于泵,而泵的首轮 drain 同步执行且可能通告输出。已结算的环在模型的首次终态读取之前保留模型游标尚未消费的全部字节,因此在首次 `job_output` 之前结束的 job 不会丢失运行期上限保留的任何内容。`dsh-tool-jobs` 为每次等待调用保存一条带 `live` 标记的认领,调用方 abort 时在 abort 事件里同步清掉该标记,因此注册表的拒绝尚未传回工具之前宣布的结算仍会投递通知;超时的等待只丢弃自己的认领,并发的另一个等待仍覆盖该结算。浏览器把先于锚点发生的观察失败记录为带错误的视图,面板显示提示而不是留白。拉取源保留的 spill 文件是 job 元数据(`JobView.output.spillPaths`,每次泵读取都会上报,读取不再点名时即撤回),不是逐块元数据;模型读取对生产者侧缺口与环淘汰都渲染 shell 工具的丢失输出提示并列出这些文件,即使缺口块本身已被淘汰——本次变更曾引入的 `before this read` 变体已删除。观察生成过程若在流中看到其 job 被移除(拥有者 teardown),以被移除 job 的终态投影收尾,而不是一次失败的读取。被拒绝的后台 spawn 不产生任何进程输出,因此 shell provider 现在把 `spawn failed: …` 提示作为 observed stderr 的整条流提供(消耗式读取仍只折入一次),且 `ShellProcess.observed` 及其两个读取器改为必填:这类失败之后模型的首次 `job_output` 读到的正是 master 的消耗式读取所渲染的内容。 diff --git a/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.i18n.yaml index 552e27d0a6..90c7dfd954 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.i18n.yaml @@ -9,11 +9,11 @@ en: bd3ad5505204ec0b zh: 770b6089c760d99f /agent-note-plugin-owned-message-projections/decision: - en: 2b33d4a1cfba0f53 - zh: 55347f3ecf234862 + en: 50c02960b1f087c6 + zh: fa3f5c18b897eab9 /agent-note-plugin-owned-message-projections/alternatives-considered: - en: c6e4c770739dca68 - zh: 7bfcb3dda06a22a1 + en: b0da1fbf24bc506a + zh: c4e35896599f824c /agent-note-plugin-owned-message-projections/consequences: en: c2f3ff0b9639bec5 zh: 357f90d9a3423751 diff --git a/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.md b/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.md index 9e1eca9256..739d27644b 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.md +++ b/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.md @@ -16,7 +16,7 @@ The `@messageProjection` tag on the owning `SessionEventMap` member generates th The compaction-image-offload plugin registers its definition through a fiber-owned `ctx.sessions.registerMessageProjection()` effect. Each event has one registered owner. Removing a definition invalidates pending committed decisions and cached reads that used it; a replacement definition requires restoring the session. Rejected, uncommitted candidates retain no interpreter dependency. -Detached readers pass definitions explicitly. The current Session format catalog assembles the same browser-safe plugin exports for persistence validation and offline queries. This static assembly does not mount recovery listeners. Live sessions use the registered composition, while compaction invariants borrow that composition's definitions. No process-global registry or import-time registration is involved. +Detached readers pass definitions explicitly. The current Session format catalog assembles the same browser-safe plugin exports for persistence validation and offline queries. This static assembly does not mount recovery listeners. Live sessions use the registered composition. No process-global registry or import-time registration is involved. The image event payload and required-on-read semantics remain owned by the image-offload note. Its indexes, selection policy, retry behavior, and SDK recordings are unchanged; this decision partially supersedes that note's core-owned interpretation. @@ -26,7 +26,7 @@ The image event payload and required-on-read semantics remain owned by the image **Use the ordinary session-projection registry.** Its folds expose domain state after commit. They cannot veto an invalid append or supply canonical `deriveMessages()` content without another integration point. -**Transform only the outgoing request.** Compaction, fork, offline replay, and request invariants also derive messages. A send-time transformation does not cover these consumers. +**Transform only the outgoing request.** Compaction, fork, and offline replay also derive messages. A send-time transformation does not cover these consumers. ## Consequences diff --git a/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.zh.md b/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.zh.md index b26b5d4549..74c85825bf 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.zh.md @@ -16,7 +16,7 @@ Status: implemented compaction-image-offload 插件通过 fiber 拥有的 `ctx.sessions.registerMessageProjection()` effect 注册处理器。每种事件只有一个注册者。移除处理器后,尚未应用的已提交决策和使用过它的缓存读取都会失效。替换处理器需要重新恢复会话。被拒绝且未提交的候选事件不会保留对处理器的依赖。 -独立读取器显式传入处理器。当前 Session 格式目录装配相同的浏览器安全插件导出,供持久数据校验和离线查询使用。这项静态装配不挂载恢复监听器。实时会话使用已注册的组合,压缩 invariant 借用该组合的处理器。不使用进程全局注册表,也不在导入模块时注册。 +独立读取器显式传入处理器。当前 Session 格式目录装配相同的浏览器安全插件导出,供持久数据校验和离线查询使用。这项静态装配不挂载恢复监听器。实时会话使用已注册的组合。不使用进程全局注册表,也不在导入模块时注册。 图片事件载荷和读取时必须识别的语义仍由图片省略记录负责。索引、选图策略、重试行为和 SDK 录制保持不变。本决策部分取代该记录中由核心负责解释事件的职责划分。 @@ -26,7 +26,7 @@ compaction-image-offload 插件通过 fiber 拥有的 `ctx.sessions.registerMess **使用普通的 session-projection 注册表。** 它的折叠在提交后公开领域状态。没有额外集成点,它无法否决无效追加,也无法提供标准 `deriveMessages()` 内容。 -**只转换发出的请求。** 压缩、分叉、离线回放和请求 invariant 也会派生消息。发送时转换无法覆盖这些消费方。 +**只转换发出的请求。** 压缩、分叉和离线回放也会派生消息。发送时转换无法覆盖这些消费方。 ## 影响 diff --git a/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.i18n.yaml index 10a92839c7..4f3fda83b9 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.i18n.yaml @@ -9,8 +9,8 @@ en: 2c774d05ae75784f zh: 619ba7f281b13233 /agent-note-host-owned-scheduled-messages/decision: - en: de81925284f13c12 - zh: d0ef35c8b58813f2 + en: 89dde12a97dce767 + zh: b5bdab5f8008ad45 /agent-note-host-owned-scheduled-messages/alternatives-considered: en: 21d7ec9a9ff3cbf2 zh: 93d03b8b3012aa7e diff --git a/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.md b/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.md index 04ea387c3a..790b58ee0f 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.md +++ b/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.md @@ -14,7 +14,7 @@ The existing Schedule package owns one Host storage domain containing each task' The task domain declares the whole-unit layout. Tasks are authoritative, so the JSON backend rejects an unreadable file, malformed document, unsupported version, or invalid task as a complete load failure instead of interpreting it as deletion. Failed recovery leaves `schedule.json` unchanged, while an initially absent file opens as an empty domain; repairing the file preserves task identity. `Service.init` waits for both storage validation and runtime setup, management methods wait for the same initialization result, and an unloading owner releases late-acquired storage without starting dispatch. Dispatch admission is separate from initialization: an ancestor can close Agent admission after a task mutation commits but before runtime cleanup starts. That refusal is logged without automatic retry and does not turn the persisted mutation into a failed management result. Invalid storage and failed cleanup registration still reject startup. -This supersedes the Session-log authority, live-only activation, and read-only catalog decisions in the [Session-local reminders note](../feature/2026-08-05-durable-web-schedule.md). Its explicit absolute-time validation, fixed-rate arithmetic, and distinction between queue admission and model completion remain applicable. Historical `schedule/change` creation, fold, and invariant types remain restricted to After, At, and Every through `LegacyScheduleRecord`; a separate Host decoder accepts the current `ScheduleRecord`, including Daily, Weekly, and Cron. The Host record decoder requires a stored `title` that is non-empty after trimming, already trimmed, and at most 120 characters, and the strict task schema rejects the whole domain open instead of dropping such a task. The historical change decoder tolerates an absent `title` so an already-written Session log stays readable, and it applies the same three checks when the member is present. The storage domain remains version 1. Historical events do not populate that domain, and existing `at` tasks are not converted into daily or weekly rules. Loading a Session with active legacy reminders warns that they must be recreated. +This supersedes the Session-log authority, live-only activation, and read-only catalog decisions in the [Session-local reminders note](../feature/2026-08-05-durable-web-schedule.md). Its explicit absolute-time validation, fixed-rate arithmetic, and distinction between queue admission and model completion remain applicable. Historical `schedule/change` creation and fold types remain restricted to After, At, and Every through `LegacyScheduleRecord`; a separate Host decoder accepts the current `ScheduleRecord`, including Daily, Weekly, and Cron. The Host record decoder requires a stored `title` that is non-empty after trimming, already trimmed, and at most 120 characters, and the strict task schema rejects the whole domain open instead of dropping such a task. The historical change decoder tolerates an absent `title` so an already-written Session log stays readable, and it applies the same three checks when the member is present. The storage domain remains version 1. Historical events do not populate that domain, and existing `at` tasks are not converted into daily or weekly rules. Loading a Session with active legacy reminders warns that they must be recreated. A plugin-sourced `followup()` synchronously appends the message to the Session inbox. Successful Session flush acknowledges durable delivery. One task-record put stores `lastDelivery`, appends a delivery record with the occurrence target, acknowledgment time, actual message id, and instruction snapshot, and updates the one-shot's `inactive` status or the recurring task's next target and status. Task state and its saved history therefore share one commit. Inactive tasks remain stored and never schedule another delivery; explicit deletion stops future delivery and removes the row together with its saved records. These records acknowledge inbox delivery, not user receipt or model execution completion. diff --git a/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.zh.md b/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.zh.md index bb14527e3e..193b3eeea2 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.zh.md @@ -14,7 +14,7 @@ Status: implemented 任务 domain 声明整 unit 布局。任务是权威数据,因此 JSON 后端会把文件不可读、文档损坏、版本不受支持或任务非法作为整批加载失败,而不是解释为删除。恢复失败不会改写 `schedule.json`,初始不存在的文件则作为空 domain 打开;修复该文件后会保留任务身份。`Service.init` 等待存储校验和运行时初始化,管理方法等待同一初始化结果;拥有方卸载时释放迟到的存储资源,而不开始投递。投递调度准入与初始化分开:任务变更提交后、运行时清理开始前,祖先可能关闭 Agent 准入。此类拒绝会记录日志而不自动重试,不会将已持久化的变更变成失败的管理结果。存储非法和清理注册失败仍拒绝启动。 -此决策替代[会话内提醒记录](../feature/2026-08-05-durable-web-schedule.zh.md)中的 Session 日志权威来源、仅 live 时激活和只读目录决策。其中显式绝对时间校验、固定速率计算,以及区分排队接纳与模型完成的决策仍然适用。历史 `schedule/change` 的创建、fold 和 invariant 类型通过 `LegacyScheduleRecord` 保持为 After、At 和 Every;独立的宿主解码器接受包含 Daily、Weekly 与 Cron 的当前 `ScheduleRecord`。宿主记录解码器要求已存储的 `title` 去除首尾空白后非空、本身已去除首尾空白且不超过 120 个字符,严格的 task schema 会拒绝整个 domain 的打开,而不是丢弃这样的任务。历史变更解码器容忍缺失的 `title`,以便已写入的 Session 日志仍可读取;该成员存在时,同样按这三项检查校验。storage domain 保持版本 1。历史事件不会填充该 domain,已有 `at` 任务也不会转换为每日或每周规则。加载含有活动旧提醒的 Session 时,会警告需要重新创建提醒。 +此决策替代[会话内提醒记录](../feature/2026-08-05-durable-web-schedule.zh.md)中的 Session 日志权威来源、仅 live 时激活和只读目录决策。其中显式绝对时间校验、固定速率计算,以及区分排队接纳与模型完成的决策仍然适用。历史 `schedule/change` 的创建与 fold 类型通过 `LegacyScheduleRecord` 保持为 After、At 和 Every;独立的宿主解码器接受包含 Daily、Weekly 与 Cron 的当前 `ScheduleRecord`。宿主记录解码器要求已存储的 `title` 去除首尾空白后非空、本身已去除首尾空白且不超过 120 个字符,严格的 task schema 会拒绝整个 domain 的打开,而不是丢弃这样的任务。历史变更解码器容忍缺失的 `title`,以便已写入的 Session 日志仍可读取;该成员存在时,同样按这三项检查校验。storage domain 保持版本 1。历史事件不会填充该 domain,已有 `at` 任务也不会转换为每日或每周规则。加载含有活动旧提醒的 Session 时,会警告需要重新创建提醒。 插件来源的 `followup()` 同步将消息追加到 Session 收件箱。Session flush 成功后确认持久投递。一次任务记录 put 存储 `lastDelivery`,追加包含发生目标、确认时间、真实消息 id 和指令快照的发送记录,并更新一次性任务的 `inactive` 状态或重复任务的下一目标和状态。因此,任务状态与其保存的历史共用一次提交。未运行任务仍保留在存储中,且不会再次安排投递;显式删除会阻止后续投递,并把该行连同其已保存记录一并移除。这些记录确认收件箱投递,不表示用户收到消息或模型执行完成。 diff --git a/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.i18n.yaml index cfe428cf55..ecb3c391ff 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.i18n.yaml @@ -9,11 +9,11 @@ en: cfb527c1c2ca661a zh: b61a906330cdaf97 /agent-note-mandatory-native-v4-relationship-validation/decision: - en: 14feaf74a58d5609 - zh: 8aea80c89110e638 + en: 534efaae5eb9beb0 + zh: ccb3734458248383 /agent-note-mandatory-native-v4-relationship-validation/alternatives-considered: - en: d6296e00a6b30d48 - zh: 9f7074c69fb42e5f + en: 96a37d9ba0252630 + zh: fdf2d085805f0010 /agent-note-mandatory-native-v4-relationship-validation/consequences: en: b76aaca1c35dcc6c zh: b933c2518c64c60c diff --git a/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.md b/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.md index b111e9f1dd..385377db23 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.md +++ b/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.md @@ -16,7 +16,7 @@ The V4 format restorer and current JSONL scanner share one mandatory cross-event An unfinished tail may retain an open turn, step, tool call, or compaction. A closing event must settle the relationships it closes. An inherited unfinished compaction ends at its `session/end-seed` marker; it does not constrain the child's lifecycle. Repair message identities retain their historical numeric suffix after earlier migrations change sequence coordinates. Fork repairs retain their separately validated native identities. -Installed Session adoption continues to own event envelopes, message roles and metadata, canonical request headers, and surface replacement reference coverage. Optional runtime invariants provide diagnostics; their installation is not required for durable admission. Native system-message admission also validates coordinates, identity, content blocks, and known image/tool-call fields before physical recovery can discard rows. Additional JSON fields and unknown nonempty content tags remain intact; retired tool-result wrappers are refused. Retired `request/header.header.system` and required `tool/code-dispatch*` tags remain hard refusals before recovery; obsolete ignorable PTC records remain opaque. Physical framing alone does not establish semantic validity. +Installed Session adoption continues to own event envelopes, message roles and metadata, canonical request headers, and surface replacement reference coverage. Native system-message admission also validates coordinates, identity, content blocks, and known image/tool-call fields before physical recovery can discard rows. Additional JSON fields and unknown nonempty content tags remain intact; retired tool-result wrappers are refused. Retired `request/header.header.system` and required `tool/code-dispatch*` tags remain hard refusals before recovery; obsolete ignorable PTC records remain opaque. Physical framing alone does not establish semantic validity. A retired tool-result wrapper outside its owning `tool/result` row would require changing the event, role, or source to represent it in V4. The adjacent migration lifts only the canonical owned wrapper; target restoration refuses wrappers remaining in interpreted content. Native row admission applies the same refusal before recoverable tail suppression. The check follows declared message, content, and Assistant-stream slots instead of recursively inspecting JSON, so tool arguments, replay state, schema parameters, nested extensions, and unknown ignorable payloads retain their recorded values. @@ -26,8 +26,6 @@ A retired tool-result wrapper outside its owning `tool/result` row would require **Project V4 back into a frozen V3 validation view.** This couples current acceptance to retired representations and invites new fields to disappear in the projection. Frozen generations remain independently readable, while the current restorer interprets its own fields. -**Rely on optional runtime invariants.** Detached readers and restoration paths may not install diagnostic companions. A required relationship must be checked before publication or consumption of the restored Session. - ## Consequences The native restorer maintains one bounded state machine per read and rejects contradictions before successor publication. Frozen V0–V3 validators and committed Session generations remain unchanged. Public catalog regressions cover invalid relationships alongside partial tails, inherited cuts, immutable unknown attribution, and complete tool exchanges; these checks enforce the declared V4 representation without changing its schemas. Breaking changes require a successor; compatible additions use new acknowledgements, and implementation fixes preserve the declared meaning. diff --git a/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.zh.md b/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.zh.md index 20f1dc53b6..169a80ecf7 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.zh.md @@ -16,7 +16,7 @@ V4 格式恢复器与当前 JSONL 扫描器共用同一强制跨事件关系校 未完成的尾部可以保留打开的 turn、step、工具调用或压缩事务。关闭事件必须结清它关闭的关系。继承的未完成压缩事务在其 `session/end-seed` 标记处结束,不约束子 Session 的生命周期。较早迁移改变序号坐标后,修复消息身份仍保留历史数字后缀。fork 修复保留经过独立校验的原生身份。 -已安装的 Session 接纳器继续负责事件封装、消息角色与元数据、规范请求头,以及 surface 替换的引用覆盖。可选运行时不变式提供诊断;持久化准入不要求安装它们。原生 system 消息准入还会在物理恢复可能丢弃行之前,校验坐标、身份、内容块及已知 image/tool-call 字段。额外 JSON 字段和非空的未知内容标签保持不变,退役的 tool-result 包装会被拒绝。退役的 `request/header.header.system` 和必需的 `tool/code-dispatch*` 标签仍在恢复前强制拒绝;标记为可忽略的过时 PTC 记录保持不透明。仅通过物理分帧不能证明语义有效。 +已安装的 Session 接纳器继续负责事件封装、消息角色与元数据、规范请求头,以及 surface 替换的引用覆盖。原生 system 消息准入还会在物理恢复可能丢弃行之前,校验坐标、身份、内容块及已知 image/tool-call 字段。额外 JSON 字段和非空的未知内容标签保持不变,退役的 tool-result 包装会被拒绝。退役的 `request/header.header.system` 和必需的 `tool/code-dispatch*` 标签仍在恢复前强制拒绝;标记为可忽略的过时 PTC 记录保持不透明。仅通过物理分帧不能证明语义有效。 退役的 tool-result 包装若不属于对应的 `tool/result` 行,就需要改变事件、角色或来源才能在 V4 中表示。相邻迁移只提升规范的自有包装;目标恢复器拒绝解释内容中剩余的包装。原生行准入在可恢复尾部被忽略之前执行同样的拒绝。检查沿声明的消息、内容和 Assistant stream 字段进行,不递归扫描 JSON,因此工具参数、replay state、schema 参数、嵌套扩展和未知可忽略载荷保留其记录值。 @@ -26,8 +26,6 @@ V4 格式恢复器与当前 JSONL 扫描器共用同一强制跨事件关系校 **将 V4 投影回冻结的 V3 校验视图。** 这会将当前准入与退役表示绑定,并容易让新增字段在投影中消失。冻结代际仍可独立读取,当前恢复器解释自己的字段。 -**依赖可选运行时不变式。** 分离读取器和恢复路径可能不安装诊断组件。必需关系必须在发布或消费恢复后的 Session 之前完成校验。 - ## Consequences 原生恢复器为每次读取维护独立状态机,在发布后继文件之前拒绝矛盾。冻结的 V0–V3 校验器及已提交的 Session 代际保持不变。公开目录回归覆盖无效关系、部分尾部、继承切分点、未知归属的不可变保存,以及完整工具交互;这些检查落实已声明的 V4 表示,不改变其 schema。破坏性变更要求后继版本;兼容新增使用新的确认记录,实现修复须保留声明的含义。 diff --git a/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.i18n.yaml index ca6497cb81..f8ca59acd6 100644 --- a/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.i18n.yaml @@ -15,5 +15,5 @@ en: 8e5f4d8fa8c2e78d zh: 3353c7042c6a1d6b /agent-note-schedule-as-an-opt-in-optional-bundle/consequences: - en: 36cd0eb71abeedbe - zh: 7c134095cc19fcfd + en: 095751360f0b7585 + zh: 69f59f6d1d2b5361 diff --git a/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.md b/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.md index 865f97848d..47aa97a90a 100644 --- a/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.md +++ b/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.md @@ -32,4 +32,4 @@ Enabling the bundle inserts `time-context` (a per-step clock reading with the sa - An enabled installation adds four tool schemas to every live root Agent and one durable clock message per eligible step; the reading is model-visible and durable, so it replays, compacts, and appears in exported Session logs like any other user message. - The Plugins page lists the three rows under the bundle with the titles their packages' `locale/*.json` declare, their state, and, while the bundle is on, a switch per row; switching one of them off leaves Schedule without that part. - A profile patch or `--patch` overlay that targets one of the three ids matches no row while the bundle is not selected, and the loader warns `patch: entry not found` for it; selecting the bundle replaces switching the rows on by id. -- The switch is configuration-only: `src/index.ts` is an empty module, the patch carries the runtime content, and the package owns no mutable runtime state, so it publishes no invariant companion. +- The switch is configuration-only: `src/index.ts` is an empty module, the patch carries the runtime content, and the package owns no mutable runtime state. diff --git a/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.zh.md b/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.zh.md index a90516606a..819b9339b2 100644 --- a/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-24-schedule-opt-in-optional-bundle.zh.md @@ -32,4 +32,4 @@ Schedule 会给每个活跃根 Agent 的请求增加四个工具 schema,并在 - 启用后的安装会给每个活跃根 Agent 增加四个工具 schema,并在每个符合条件的步骤追加一条持久时钟消息;该读数对模型可见且持久,因此它与其他 user 消息一样参与回放、压缩,并出现在导出的 Session 日志中。 - 插件管理页在该 bundle 下列出这三行,显示各自包的 `locale/*.json` 声明的标题、它们的状态,并在 bundle 打开时为每行提供开关;关掉其中一行会让 Schedule 缺少这一部分。 - 未选中该 bundle 时,按 id 定位这三行之一的 profile 补丁或 `--patch` overlay 匹配不到任何条目,加载器会为它报告 `patch: entry not found` 警告;选中该 bundle 即可取代按 id 打开这些行。 -- 该开关只涉及配置:`src/index.ts` 是空模块,运行时内容由 patch 承载,包本身不拥有可变的运行时状态,因此不发布不变量伴随模块。 +- 该开关只涉及配置:`src/index.ts` 是空模块,运行时内容由 patch 承载,包本身不拥有可变的运行时状态。 diff --git a/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.i18n.yaml b/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.i18n.yaml index 4b48c194fe..be51c9d067 100644 --- a/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.i18n.yaml @@ -39,14 +39,14 @@ en: 1c71cf67450e78d5 zh: fb44b3cf5cee070a /agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision/blocking-via-a-log-recorded-lock-plus-a-crash-recoverable-failure-taxonomy: - en: 5aed57a49e031b1e - zh: 1424f332197ef541 + en: 6c3878b3ec1aebb8 + zh: 10175d34ddf1c543 /agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/alternatives-considered: en: 24a7bb1949445136 zh: 7914ea99f498ed67 /agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/consequences: - en: 400bf1cadb974d04 - zh: 7612a4634ae98b32 + en: a14d5d63b35bf945 + zh: 87678ddf2ce5e7d7 /agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/testing: - en: 843476bd607fe832 - zh: 50be0b06553a54d0 + en: 5919e8aee57c9f06 + zh: e17f19bd1856a356 diff --git a/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md b/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md index 7d8b4f5011..4407e26fc1 100644 --- a/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md +++ b/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md @@ -106,7 +106,7 @@ The lifecycle boundary makes crash state unambiguous: `compaction/end` keeps its `error?` field (mirroring `tool/result`'s self-contained error — one event tells success from failure without correlating a sibling). There is no separate `compaction/error` event. -**Core session repair stays compaction-agnostic — deliberately.** `interruptedTurnClosers` is never taught about `compaction/*`. The general `session/end-seed` lifecycle boundary supplies the evidence the compaction owner needs; the compaction invariant and backend interpret it without adding plugin-specific repair to core. +**Core session repair stays compaction-agnostic — deliberately.** `interruptedTurnClosers` is never taught about `compaction/*`. The general `session/end-seed` lifecycle boundary supplies the evidence the compaction owner needs; the compaction backend interprets it without adding plugin-specific repair to core. ## Alternatives considered @@ -122,12 +122,12 @@ The lifecycle boundary makes crash state unambiguous: - **Automatic extension points**: `agent/pre-step` (`@mode waterfall`) handles pressure before request derivation and `agent/request-error` (`@mode waterfall`) handles final request failures after the failed step closes. The pre-step payload carries the claimed batch, turn, step, and signal (see the [payload-object events decision](../../archived/architecture/2026-08-06-agent-event-payload-objects.md)), with no compaction-only prompt/prefix payload. - **`SessionEventMap`** gains `compaction/start` / `compaction/summary` / `compaction/end` by declaration merging (merge-extensible); `SurfaceEventType` is **not** touched. These are session events, not cordis `Events`, so the event-taxonomy gate needs no entry. - **`dsh-compaction`** owns `COMPACT_CHECKPOINT_SOURCE`, `isCompactCheckpointSource(source)`, `toolPairingBalancedBefore(session, seq)`, and `toolPairingBalancedAfter(session, seq)`. The marker identifies replacement summaries across backend implementations. The cached surface-edge checks prevent `compactRegion` and `compactIfNeeded` from splitting a tool-call/result pair, validate current membership by seq, answer both edges from one per-cut balance sequence, and reject stale or missing seqs and orphan results. -- **`dsh-session`** validates positional replacement, complete cited source-event coverage, and content-only single-node `tool/result` rewrites through its one surface manager. Its invariant companion treats fresh appended tool results as executions that require an open step and pending call, while the compaction companion owns numeric-turn versus standalone-null bracket relations. +- **`dsh-session`** validates positional replacement, complete cited source-event coverage, and content-only single-node `tool/result` rewrites through its one surface manager. - **Wiring**: `examples/tui-agent/cordis.yml` loads zero-config `dsh-token-meter`, `dsh-compaction-tool-result-pruner`, `dsh-compaction-basic`, then `dsh-command-compact`; service-wide defaults make the composition usable without repeated numeric policy. ## Testing -- **Unit:** Real Loader and invariant plugins cover whole-unit retention, pruning configuration and replay, rich-block ordering, metadata preservation, convergence, both `compaction/end` outcomes, open-tail refusal, pruning-only and summarized overflow recovery, generation proof, caps, and original-error preservation. +- **Unit:** Real Loader compositions cover whole-unit retention, pruning configuration and replay, rich-block ordering, metadata preservation, convergence, both `compaction/end` outcomes, open-tail refusal, pruning-only and summarized overflow recovery, generation proof, caps, and original-error preservation. - **Loop:** Tests pin pre-step after the preceding `step/end` and before the next `step/start`, actual `agent/request` routing, closed failed steps, fresh retry numbering, and complete thrown/in-band overflow → compaction → reconstructed retry composition. - **Manual:** Maintenance serialization, marker ordering, injection retention, live/stale orphan classification, cancellation, close/flush failures, command mapping, and the queued TUI journey are pinned without a model key. - **With-key e2e:** A real model and bash session with lowered limits triggers compaction, records a complete `compaction/start…end` pair, shrinks the surface, and finishes the task. diff --git a/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md b/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md index 6a255bb05e..4a273e1f34 100644 --- a/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md +++ b/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md @@ -106,7 +106,7 @@ compaction/end → log-only. Releases the lock (carries `error` on a recove `compaction/end` 保留其 `error?` 字段(与 `tool/result` 的自包含错误一致——一个事件即可区分成功与失败,无需关联兄弟事件)。没有单独的 `compaction/error` 事件。 -**核心会话修复保持对压缩无感知——这是有意为之。** `interruptedTurnClosers` 从不被教导 `compaction/*`。通用 `session/end-seed` 生命周期边界提供压缩所有者所需的证据;压缩不变量与后端负责解释它,无需向核心添加插件专属修复。 +**核心会话修复保持对压缩无感知——这是有意为之。** `interruptedTurnClosers` 从不被教导 `compaction/*`。通用 `session/end-seed` 生命周期边界提供压缩所有者所需的证据;压缩后端负责解释它,无需向核心添加插件专属修复。 ## 曾考虑的替代方案 @@ -122,12 +122,12 @@ compaction/end → log-only. Releases the lock (carries `error` on a recove - **自动扩展点**:`agent/pre-step`(`@mode waterfall`)在请求派生前处理压力,`agent/request-error`(`@mode waterfall`)处理失败步骤关闭后的最终请求失败。pre-step 的 payload 携带已领取批次、轮次、步骤与 signal(参见 [payload-object 事件决策](../../archived/architecture/2026-08-06-agent-event-payload-objects.md)),不携带压缩专属的提示词/前缀 payload。 - **`SessionEventMap`** 通过可合并扩展的声明合并获得 `compaction/start` / `compaction/summary` / `compaction/end`;`SurfaceEventType` **未被**触及。这些是会话事件,不是 cordis `Events`,因此事件分类门禁无需新增条目。 - **`dsh-compaction`** 拥有 `COMPACT_CHECKPOINT_SOURCE`、`isCompactCheckpointSource(source)`、`toolPairingBalancedBefore(session, seq)` 与 `toolPairingBalancedAfter(session, seq)`。该标记用于跨后端实现识别替换摘要。带缓存的 surface 边缘检查会防止 `compactRegion` 和 `compactIfNeeded` 拆分工具调用/结果对,按 seq 校验当前成员关系,从每个切割点的一条平衡序列回答两侧边缘,并拒绝陈旧或缺失的 seq 与孤立结果。 -- **`dsh-session`** 通过唯一的 surface 管理器校验位置替换、引用的来源事件是否覆盖完整,以及仅内容的单节点 `tool/result` 重写。其不变式配套插件将新追加的工具结果视为执行,要求存在已打开的步骤与待处理调用,而压缩配套组件负责维护数字轮次 owner 与独立 `null` owner 事件对之间的关系。 +- **`dsh-session`** 通过唯一的 surface 管理器校验位置替换、引用的来源事件是否覆盖完整,以及仅内容的单节点 `tool/result` 重写。 - **接线**:`examples/tui-agent/cordis.yml` 依次加载零配置的 `dsh-token-meter`、`dsh-compaction-tool-result-pruner`、`dsh-compaction-basic`,然后加载 `dsh-command-compact`;服务级默认值使组合无需重复数值策略即可使用。 ## 测试 -- **单元测试:** 使用真实 Loader 和 invariant 插件覆盖完整单元保留、修剪配置与回放、富块顺序、元数据保留、收敛、`compaction/end` 的两种结果、开放尾部拒绝、仅修剪与带摘要的溢出恢复、generation 证明、上限和原始错误保留。 +- **单元测试:** 使用真实 Loader 组合覆盖完整单元保留、修剪配置与回放、富块顺序、元数据保留、收敛、`compaction/end` 的两种结果、开放尾部拒绝、仅修剪与带摘要的溢出恢复、generation 证明、上限和原始错误保留。 - **循环测试:** 测试固定 pre-step 发生在前一个 `step/end` 之后、下一个 `step/start` 之前,使用实际 `agent/request` 路由,关闭失败步骤,分配新的重试编号,并覆盖完整的抛出/带内溢出 → 压缩 → 重建重试组合。 - **手动测试:** 无需模型密钥即可固定 maintenance 串行化、标记顺序、注入保留、活动/陈旧未匹配标记分类、取消、闭合/flush 失败、命令映射以及排队 TUI 流程。 - **带密钥 e2e:** 真实模型和 bash 会话在降低的限制下触发压缩,记录完整的 `compaction/start…end` 对,缩小 surface,并完成任务。 diff --git a/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.i18n.yaml b/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.i18n.yaml index ff719e7b97..dd08ba1ded 100644 --- a/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.i18n.yaml @@ -12,8 +12,8 @@ en: 70182cd2979bdfd6 zh: a6580a23cfbe6c07 /agent-note-workspace-registration-deletion/durable-commit-and-publication: - en: f97d526274c3608b - zh: b0ae161f4e9febb8 + en: 930257532b3892c8 + zh: c510faf75f06a3f9 /agent-note-workspace-registration-deletion/client-convergence: en: 84e56775e22ecf23 zh: ab146e3179868efb @@ -24,8 +24,8 @@ en: d7b866f763db5364 zh: 5e0865d42522d189 /agent-note-workspace-registration-deletion/verification: - en: a7e98d39e3ffdba7 - zh: 9c4f8168e3fff83e + en: d4d131da609fff89 + zh: c026d6cf53cbd42d /agent-note-workspace-registration-deletion/consequences: en: 3a50bfb5d93f6ef7 zh: 80b8dd19b2d50b16 diff --git a/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md b/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md index 1d9804cfc4..6db4ab2533 100644 --- a/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md +++ b/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md @@ -18,7 +18,7 @@ Unknown ids return `false` at the domain contract. `workspace.delete({ workspace ## Durable commit and publication -Registry operations serialize create and delete. Deletion first writes the Workspace order without the id, then removes the entity from the cache, then deletes the table row. The table deletion is the notification commit point: the package invariant accepts it only after the cache stopped publishing the entity, and the Host emits `host/workspace-removed` only from that committed deletion. A table-write failure restores the cache and prior durable order; no removal frame is published. +Registry operations serialize create and delete. Deletion first writes the Workspace order without the id, then removes the entity from the cache, then deletes the table row. The table deletion is the notification commit point: the cache has already stopped publishing the entity, and the Host emits `host/workspace-removed` only from that committed deletion. A table-write failure restores the cache and prior durable order; no removal frame is published. The Host stream keeps its committed-id set through the preceding global-order write and removes the id only on the table deletion. Create rollback therefore emits no false removal, while every connected tab receives exactly the id needed to delete its projection. @@ -50,7 +50,7 @@ The menu, Modal, and buttons retain their existing structure and design tokens. ## Verification -Workspace package tests pin successful metadata-only deletion, same-path re-registration, unknown-id idempotence, table-failure rollback, explicit-marker restart recovery, unexplained-corruption rejection, and cache/table invariant behavior. Apiproxy and carrier tests pin the schema, handler, `workspace-not-found`, retained Session/folder, fresh-id re-registration, and committed `host/workspace-removed` frame. Client tests pin unary direct echo, duplicate removal, late changed frames, and deletion racing an in-flight baseline. Component tests pin confirmation, projection-settled closing, success-frame-before-unary ordering, failure, Cancel, Escape, and Close. The browser scenario observes every transient alert, slot error, console error, and page error while reusing a deleted title for a different directory. +Workspace package tests pin successful metadata-only deletion, same-path re-registration, unknown-id idempotence, table-failure rollback, explicit-marker restart recovery, and unexplained-corruption rejection. Apiproxy and carrier tests pin the schema, handler, `workspace-not-found`, retained Session/folder, fresh-id re-registration, and committed `host/workspace-removed` frame. Client tests pin unary direct echo, duplicate removal, late changed frames, and deletion racing an in-flight baseline. Component tests pin confirmation, projection-settled closing, success-frame-before-unary ordering, failure, Cancel, Escape, and Close. The browser scenario observes every transient alert, slot error, console error, and page error while reusing a deleted title for a different directory. The assembled keyless Web scenario registers an existing temporary project directory, accounts a persisted Session, makes that Session current, confirms deletion in Chromium, and verifies the Workspace group disappears while Ungrouped retains the current Session. It checks the user file and JSONL log before and after deletion and repeats the UI, directory, and log assertions after reload. The scenario holds the seeded attachment frame until the browser selects the adoption-created New Session, proves that the seed is absent from the group, then delivers the frame and selects the only non-blank Session. Host attachment completion and a two-row count cannot prove browser membership: the group header plus New Session already satisfy that count, and the arriving seed can replace the positional locator between click and assertion. diff --git a/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.zh.md b/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.zh.md index 54e10b67a7..157e049de0 100644 --- a/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.zh.md +++ b/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.zh.md @@ -18,7 +18,7 @@ Workspace 注册已有代码目录,使 GUI 能够为目录命名,并对其 ## 持久化提交与发布 -注册表操作会串行执行创建与删除。删除时先写入移除该 id 后的 Workspace 顺序,再从缓存中移除实体,最后删除表行。表删除是通知提交点:只有缓存停止发布该实体后,包不变量才接受该删除;Host 也只根据这次已提交的删除发出 `host/workspace-removed`。表写入失败时,系统会恢复缓存和此前的持久化顺序,且不会发布移除帧。 +注册表操作会串行执行创建与删除。删除时先写入移除该 id 后的 Workspace 顺序,再从缓存中移除实体,最后删除表行。表删除是通知提交点:此时缓存已停止发布该实体;Host 只根据这次已提交的删除发出 `host/workspace-removed`。表写入失败时,系统会恢复缓存和此前的持久化顺序,且不会发布移除帧。 Host 流在前一笔全局顺序写入期间继续保留其已提交 id 集合,只在删除表行时移除该 id。因此,创建回滚不会发出错误的移除帧,而每个已连接标签页都能收到从自身投影中删除该记录所需的准确 id。 @@ -50,7 +50,7 @@ Host 流在前一笔全局顺序写入期间继续保留其已提交 id 集合 ## 验证 -Workspace 包测试固定了仅删除元数据的成功路径、同路径重新注册、未知 id 的幂等行为、表操作失败回滚、明确标记的重启恢复、来源不明损坏的拒绝,以及缓存/表不变量行为。Apiproxy 与载体测试固定了 schema、处理器、`workspace-not-found`、保留会话/文件夹、使用新 id 重新注册,以及已提交的 `host/workspace-removed` 帧。客户端测试固定了一元直接回显、重复移除、延迟到达的 changed 帧,以及删除与进行中基线并发的行为。组件测试固定了确认交互、投影稳定后关闭、成功帧先于一元响应、失败、Cancel、Escape 与 Close。浏览器场景会在为不同目录复用已删除名称时,观测每一次瞬时 alert、slot error、console error 与 page error。 +Workspace 包测试固定了仅删除元数据的成功路径、同路径重新注册、未知 id 的幂等行为、表操作失败回滚、明确标记的重启恢复,以及来源不明损坏的拒绝。Apiproxy 与载体测试固定了 schema、处理器、`workspace-not-found`、保留会话/文件夹、使用新 id 重新注册,以及已提交的 `host/workspace-removed` 帧。客户端测试固定了一元直接回显、重复移除、延迟到达的 changed 帧,以及删除与进行中基线并发的行为。组件测试固定了确认交互、投影稳定后关闭、成功帧先于一元响应、失败、Cancel、Escape 与 Close。浏览器场景会在为不同目录复用已删除名称时,观测每一次瞬时 alert、slot error、console error 与 page error。 组装后的无密钥 Web 场景会注册一个已有临时项目目录,将持久化会话计入账本,把该会话设为当前会话,在 Chromium 中确认删除,并验证 Workspace 分组消失,而 Ungrouped 保留当前会话。该场景在删除前后检查用户文件和 JSONL 日志,并在刷新后重复验证 UI、目录与日志。场景会扣住种子会话的归属帧,直到浏览器选中接纳目录时创建的 New Session,确认分组中尚无种子会话,再交付该帧并选中唯一的非空白会话。Host 完成归属写入和行数达到二都不能证明浏览器已收到成员关系:分组标题加 New Session 已满足该行数,而到达的种子会话会使位置定位器在点击与断言之间指向另一行。 diff --git a/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.i18n.yaml b/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.i18n.yaml index 9da56d634b..11a2ea20d9 100644 --- a/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.i18n.yaml @@ -27,8 +27,8 @@ en: e9d8043178dc946f zh: 56808150ba2ab275 /agent-note-queued-manual-compaction-with-one-durable-lock/decision/end-seed-distinguishes-live-and-stale-orphans: - en: cc767e216225e117 - zh: 4ec7dbc179f96c37 + en: b4c949af9f56c0a3 + zh: 8481c8baf95a6ab5 /agent-note-queued-manual-compaction-with-one-durable-lock/decision/reference-implementation-boundaries: en: 97b121c53b8f3b68 zh: 13d86db92c30bef1 @@ -36,8 +36,8 @@ en: f1c5bae090425598 zh: 60a30f851d85dfb0 /agent-note-queued-manual-compaction-with-one-durable-lock/verification: - en: 37587419460fcf36 - zh: e45c752f93d5a87e + en: acd99ed77d2c36d4 + zh: f7f93c4ac2b7dc2c /agent-note-queued-manual-compaction-with-one-durable-lock/consequences: en: b8c71ea1d1db7b8e zh: 0d276bb8317ada91 diff --git a/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.md b/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.md index 3efd1a4901..371950abb1 100644 --- a/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.md +++ b/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.md @@ -67,8 +67,6 @@ Failed `changed` or `summary` attempts leave the conversation surface unchanged, Tail scanning finds the current turn, unmatched compaction start, and newest `session/end-seed` independently. An unmatched start after the newest end-seed is live and blocks every compaction entry point. An unmatched start before a later end-seed belongs to an earlier session lifecycle and is stale, so it does not wedge the resumed or forked session. -The compaction invariant uses the same transition logic during seed replay: `session/end-seed` clears an open historical trace. The boundary need not publish live from the constructor for this case; replay is the load-bearing path. - The client request projection closes an unmatched compaction request as interrupted at the `session/end-seed` time and clears its active index. A later `compaction/start` therefore creates an independent request instead of leaving or overwriting a permanently running orphan. Once a transaction has appended its start, every later failure makes one closing attempt. A failed close leaves the unmatched start deliberately visible and blocking, and no flush is attempted. A closed manual attempt is flushed even when it reports an expected failure. Cancellation retains exact-reason precedence after required close and flush cleanup. @@ -97,7 +95,7 @@ That reference also carried client-side replacement-anchor machinery to preserve ## Verification -Agent-loop tests cover same-tick right of way, preserved IDs and FIFO lifecycle, waking and quiet queued work, idempotent release, `whenIdle()`, cancellation, and teardown. Compact tests cover standalone and numbered invariant ownership, end-seed replay, live versus stale orphans, re-entrant listeners, selected-span drift, commit and close failures, flush ordering, exact cancellation causes, raw output and usage preservation, and automatic/manual mutual exclusion. +Agent-loop tests cover same-tick right of way, preserved IDs and FIFO lifecycle, waking and quiet queued work, idempotent release, `whenIdle()`, cancellation, and teardown. Compact tests cover end-seed replay, live versus stale orphans, re-entrant listeners, selected-span drift, commit and close failures, flush ordering, exact cancellation causes, raw output and usage preservation, and automatic/manual mutual exclusion. The command package pins registration, Loader composition, argument rejection, exact success/failure text, cancellation, absence from model history, and disposal waiting across separate close and flush boundaries after an abort stops the executor from awaiting the handler. The client runtime projection test pins end-seed interruption followed by an independent completed attempt. The `queued-manual-compact` terminal snapshot drives real keystrokes through the assembled TUI: `/help` discovers the command, a held summary admits a queued prompt and immediate injection, `turn: null` markers and the flush precede the queued prompt turn, command lifecycle stays log-only, and the derived order is checkpoint → injection → queued prompt. diff --git a/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.zh.md b/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.zh.md index 2da3f494af..643baef5db 100644 --- a/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.zh.md +++ b/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.zh.md @@ -67,8 +67,6 @@ DSH 有意在调用摘要器前记录 `compaction/start`。缓慢或崩溃的尝 尾部扫描会分别查找当前轮次、未匹配的 compaction start 与最新 `session/end-seed`。位于最新 end-seed 之后的未匹配 start 是活动锁,会阻塞每个压缩入口点。位于较新 end-seed 之前的未匹配 start 属于更早的会话生命周期,已经陈旧,因此不会卡住恢复或 fork 后的会话。 -压缩不变量在 seed 回放期间使用同一项转换逻辑:`session/end-seed` 会清除未闭合的历史追踪状态。此场景不要求构造函数实时发布该边界;回放才是承重路径。 - 客户端请求投影会在 `session/end-seed` 时刻将未匹配的压缩请求以中断状态结束,并清除其活动索引。因此,后续 `compaction/start` 会创建一个独立请求,而不是让该遗留的未匹配请求永久保持运行状态或将其覆盖。 事务追加 start 后,每次后续失败都会进行一次闭合尝试。闭合失败会有意留下可见且具有阻塞作用的未匹配 start,并且不尝试 flush。已闭合的手动尝试即使报告预期失败也会 flush。完成必需的闭合与 flush 清理后,取消仍保留原始原因优先级。 @@ -97,7 +95,7 @@ DSH 有意在调用摘要器前记录 `compaction/start`。缓慢或崩溃的尝 ## 验证 -agent loop(智能体循环)测试覆盖同一 tick 内的优先权、保留 ID 与 FIFO 生命周期、会唤醒和静默的排队工作、幂等释放、`whenIdle()`、取消与 teardown。压缩测试覆盖独立与数字形式的不变量 owner、end-seed 回放、活动与陈旧未匹配标记、listener 重入、所选区段漂移、commit 与闭合失败、flush 顺序、原始取消原因、raw output 与 usage 保留,以及自动/手动互斥。 +agent loop(智能体循环)测试覆盖同一 tick 内的优先权、保留 ID 与 FIFO 生命周期、会唤醒和静默的排队工作、幂等释放、`whenIdle()`、取消与 teardown。压缩测试覆盖 end-seed 回放、活动与陈旧未匹配标记、listener 重入、所选区段漂移、commit 与闭合失败、flush 顺序、原始取消原因、raw output 与 usage 保留,以及自动/手动互斥。 命令包固定注册行为、Loader 组合、参数拒绝、精确的成功/失败文本、取消、不进入模型历史的保证,以及 dispose 在中止使执行器停止等待处理器后,仍会跨越相互独立的闭合与 flush 边界等待该处理器结算。客户端运行时投影测试固定 end-seed 中断,以及随后一次独立尝试的完成。`queued-manual-compact` 终端快照通过已组装 TUI 驱动真实按键:`/help` 可发现该命令;被暂停的摘要会接纳一个排队提示词和即时注入;`turn: null` 标记与 flush 先于排队提示词轮次;命令生命周期保持纯日志;派生顺序固定为检查点 → 注入 → 排队提示词。 diff --git a/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.i18n.yaml b/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.i18n.yaml index 562d31b711..4b188a2fef 100644 --- a/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.i18n.yaml @@ -18,14 +18,14 @@ en: fa9f572f763bc6b0 zh: f3924fa49d2be22d /agent-note-background-job-completion-wakes-an-idle-owner/decision/teardown-claims-the-report: - en: f79257596c755a32 - zh: 856bf42492425f9c + en: 89efaa83188b28d7 + zh: 7e78f3b616602402 /agent-note-background-job-completion-wakes-an-idle-owner/decision/completion-is-announced-last: en: 9cab013b3513f1e0 zh: 78ea1b05df94d8a8 /agent-note-background-job-completion-wakes-an-idle-owner/alternatives-considered: - en: debb2c9b924474c3 - zh: 90e2cefc230e3ec8 + en: 0cebce77d37e74b2 + zh: 1339714c0885f8af /agent-note-background-job-completion-wakes-an-idle-owner/consequences: en: a2a6f76c8cc92d57 zh: 6eed3e6a5c75dfb9 diff --git a/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md b/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md index f13f09d326..2ce338ff0b 100644 --- a/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md +++ b/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md @@ -40,7 +40,7 @@ The cap exists because this chain is self-exciting in a way subagent settlement `cancelForTeardown` now marks the record `reported`, exactly as `kill()` does after cancelling. The asymmetry was invisible while the notice was a harmless inject; a waking reporter turns it into one model request per teardown layer, on agents the host is destroying. -`reported` was already the right bit — "a kill, read, or wait has reported or committed to report the terminal state" — and teardown is a kill without a caller. Using it keeps every observer of the settlement intact: `onJobDone` still fires, so runtime invariants and the force-fail path stay covered, and only notice reporters go quiet. +`reported` was already the right bit — "a kill, read, or wait has reported or committed to report the terminal state" — and teardown is a kill without a caller. Using it keeps every observer of the settlement intact: `onJobDone` still fires, so the force-fail path stays covered, and only notice reporters go quiet. ### Completion is announced last @@ -56,7 +56,7 @@ The cap exists because this chain is self-exciting in a way subagent settlement **A wall-clock window** on top of the counter. For an interactive agent the slow case is the wanted one — an hour-long build finishing and the agent resuming is the feature — and `dsh run` exits once its task turn goes idle regardless. Worth revisiting only if an unattended long-lived deployment appears. -**Suppressing `onJobDone` entirely during owner drain,** symmetric with the service-wide `listenersClosed`. It reads cleaner and removes a signal that is not only for notices: the force-fail record and the runtime invariant both observe teardown settlements. The `reported` bit denies exactly the reporters and nothing else. +**Suppressing `onJobDone` entirely during owner drain,** symmetric with the service-wide `listenersClosed`. It reads cleaner and removes a signal that is not only for notices: the force-fail record observes teardown settlements. The `reported` bit denies exactly the reporters and nothing else. ## Consequences diff --git a/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md b/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md index f9b563ebbf..eab8e25885 100644 --- a/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md +++ b/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md @@ -40,7 +40,7 @@ Update:本文依赖的 `reported` 位与 `onJobDone` 已随 [jobs seam 收敛] `cancelForTeardown` 现在会把记录标记为 `reported`,与 `kill()` 在取消之后所做的完全一致。当通知只是一次无害的注入时,这处不对称看不出来;而会唤醒的报告方会把它变成每个 teardown 层级一次模型请求,作用在宿主正要销毁的 agent 上。 -`reported` 本来就是正确的那个 bit——「kill、read 或 wait 已报告或承诺报告终止状态」——而 teardown 是一次没有调用方的 kill。用它可以让该结算的每一个观察者都保持完整:`onJobDone` 仍会触发,因此运行时不变量与强制失败路径依旧被覆盖,只有通知报告方会安静下来。 +`reported` 本来就是正确的那个 bit——「kill、read 或 wait 已报告或承诺报告终止状态」——而 teardown 是一次没有调用方的 kill。用它可以让该结算的每一个观察者都保持完整:`onJobDone` 仍会触发,因此强制失败路径依旧被覆盖,只有通知报告方会安静下来。 ### 完成是最后才宣布的 @@ -56,7 +56,7 @@ Update:本文依赖的 `reported` 位与 `onJobDone` 已随 [jobs seam 收敛] **在计数之上再加墙钟窗口**。对交互式 agent 而言,慢的那种情形恰恰是想要的——一小时的构建结束、agent 接着干下去,这就是特性本身——而 `dsh run` 无论如何都在任务轮次空闲后退出。只有当出现无人值守的长生命周期部署时才值得重新考虑。 -**在 owner 排空期间整体压制 `onJobDone`**,与服务级的 `listenersClosed` 对称。它读起来更干净,但会移走一个不只服务于通知的信号:强制失败记录与运行时不变量都会观察 teardown 结算。`reported` 位恰好只否决报告方,别的什么也不否决。 +**在 owner 排空期间整体压制 `onJobDone`**,与服务级的 `listenersClosed` 对称。它读起来更干净,但会移走一个不只服务于通知的信号:强制失败记录会观察 teardown 结算。`reported` 位恰好只否决报告方,别的什么也不否决。 ## 影响 diff --git a/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.i18n.yaml b/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.i18n.yaml index 9c2732ed18..089f75444a 100644 --- a/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.i18n.yaml @@ -12,8 +12,8 @@ en: 26a46d3b6f21288a zh: 94c32f789b3e28ac /agent-note-promote-open-anywhere-from-community-plugin-to-first-party-package/alternatives-considered: - en: cc7c2ed172e4de3f - zh: b8fed28d9ee835c5 + en: 7e2043244ea608cf + zh: 2b2f63d82803138a /agent-note-promote-open-anywhere-from-community-plugin-to-first-party-package/consequences: en: 5be6b2891e3429c9 zh: 68ffcff52676824f diff --git a/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.md b/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.md index e5a435d24c..7395b13164 100644 --- a/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.md +++ b/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.md @@ -32,7 +32,7 @@ The pair lives in `packages/host/` and `packages/client/` because that is what t **Offer VS Code's remote CLI during SSH sessions.** Its installed executable does not prove a usable editor connection: the inherited IPC socket belongs to a live VS Code connection and can disappear while Harness keeps running. Browser-side SSH-target configuration and local editor handoff remain outside this host-application feature. -**Vendor the plugin's `lib/` as-is under `packages/`.** Fastest, but the hand-authored JavaScript fails typecheck, coverage, i18n, JSDoc, and invariant gates wholesale; keeping it exempt would create a package class the repository deliberately does not have. +**Vendor the plugin's `lib/` as-is under `packages/`.** Fastest, but the hand-authored JavaScript fails typecheck, coverage, i18n, and JSDoc gates wholesale; keeping it exempt would create a package class the repository deliberately does not have. **A Typert Remote instead of raw webServer routes.** The apps/open calls fit the Remote RPC shape, but the icon route serves binary PNGs, which the JSON RPC vocabulary does not carry; splitting icons onto a raw route while apps/open ride Remote gives two transports for one feature. Raw routes also match the original's client, and `webhook-github` establishes the validated-raw-route pattern. diff --git a/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.zh.md b/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.zh.md index 108091f3e7..229e6c135f 100644 --- a/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.zh.md +++ b/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.zh.md @@ -32,7 +32,7 @@ Status: implemented **在 SSH 会话中提供 VS Code 的远端 CLI。** 已安装的可执行文件不能证明编辑器连接可用:继承的 IPC socket 属于一个仍在运行的 VS Code 连接,Harness 继续运行时它也可能消失。浏览器侧的 SSH 目标配置与本地编辑器唤起不属于这个主机应用功能。 -**将插件的 `lib/` 原样 vendor 进 `packages/`。** 最快,但手写 JavaScript 会整体不过 typecheck、覆盖率、i18n、JSDoc 和 invariant 门禁;为其保留豁免会造出仓库刻意不设的包类别。 +**将插件的 `lib/` 原样 vendor 进 `packages/`。** 最快,但手写 JavaScript 会整体不过 typecheck、覆盖率、i18n 和 JSDoc 门禁;为其保留豁免会造出仓库刻意不设的包类别。 **用 Typert Remote 而非裸 webServer 路由。** apps/open 调用符合 Remote RPC 形态,但 icon 路由提供二进制 PNG,JSON RPC 词汇承载不了;把 icon 拆去裸路由而 apps/open 走 Remote 会让一个功能有两种传输。裸路由也匹配原插件的客户端,且 `webhook-github` 已确立带校验裸路由的先例。 diff --git a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml index 60dff04b5b..626784a5ea 100644 --- a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml @@ -9,8 +9,8 @@ en: 896ffdae7d51cc19 zh: eed0d7e80456bd8e /agent-note-parallel-pre-push-gates/decision: - en: c95d213689164ad6 - zh: da85165d13b26d58 + en: 381e7d9b5281ace7 + zh: 08542da75620602c /agent-note-parallel-pre-push-gates/verification: en: c7124c68afb12a8e zh: 0a0269476ae353f3 diff --git a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md index 9773a86428..50537ee668 100644 --- a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md +++ b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md @@ -16,7 +16,7 @@ Aggregate jobs such as documentation synchronization hide long sequential chains Long coordinator gates whose own subprocesses preserve useful attribution may opt into `streamOutput`. Their stdout and stderr reach the parent immediately without being buffered or printed again at completion. Partitioned coverage and parallel Web snapshots use this mode so a mid-run failure is visible without waiting for sibling work. -The Node 24 consumer job is one ten-gate mode rather than a shell-owned process pool. Its default worker count equals its gate count, while pull-request CI caps active gates at eight and dependencies control readiness. Build and source compatibility start immediately; after build, `publint` and built-package invariant validation run in parallel. Lint, both snapshot suites, documentation typechecking, NodeNext type checks, and built-bin smokes wait for the invariant validator to remove its temporary package views. +The Node 24 consumer job is one ten-gate mode rather than a shell-owned process pool. Its default worker count equals its gate count, while pull-request CI caps active gates at eight and dependencies control readiness. Build and source compatibility start immediately; after build, `publint`, lint, both snapshot suites, documentation typechecking, NodeNext type checks, and built-bin smokes become ready together. [scripts/publint-all.ts](../../../../scripts/publint-all.ts) discovers packages from `packages//` and runs `publint` with a worker pool sized from `availableParallelism()`. `DSH_PUBLINT_CONCURRENCY` can cap or raise the worker count for local machines and CI runners with different resource profiles. Results are buffered per package and printed in deterministic package order, so parallel execution does not scramble each package's log block. diff --git a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md index 4d0e15a338..bb266af48d 100644 --- a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md +++ b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md @@ -16,7 +16,7 @@ Status: implemented 自身子进程能够保留有效归因的长时间协调门禁可以选择 `streamOutput`。其 stdout 与 stderr 会立即到达父进程,不会被缓冲,也不会在结束时重复打印。分区覆盖率与并行 Web 快照使用该模式,使运行中途的失败无需等待兄弟工作结束就能显示。 -Node 24 消费方任务采用单个包含 10 道门禁的模式,而非由 shell 管理的进程池。其默认 worker 数等于门禁数,拉取请求 CI 则把活动门禁限制为 8 道,并由依赖关系控制就绪状态。构建与源码兼容性立即启动;构建完成后,`publint` 与已构建包不变式验证并行运行。lint、两套快照、文档类型检查、NodeNext 类型检查和 built-bin 冒烟测试等待不变式验证器清除临时包视图。 +Node 24 消费方任务采用单个包含 10 道门禁的模式,而非由 shell 管理的进程池。其默认 worker 数等于门禁数,拉取请求 CI 则把活动门禁限制为 8 道,并由依赖关系控制就绪状态。构建与源码兼容性立即启动;构建完成后,`publint`、lint、两套快照、文档类型检查、NodeNext 类型检查和 built-bin 冒烟测试同时就绪。 [scripts/publint-all.ts](../../../../scripts/publint-all.ts) 从 `packages//` 发现包,并以根据 `availableParallelism()` 确定大小的 worker 池运行 `publint`。`DSH_PUBLINT_CONCURRENCY` 可以针对资源配置不同的本地机器和 CI runner 限制或提高 worker 数量。结果按包缓冲,并按确定性的包顺序打印,因此并行执行不会打乱各包的日志块。 diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml index 40e0e18b71..dd0445904e 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml @@ -24,8 +24,8 @@ en: 4f399390f58d7a34 zh: 9df67248acfc61c8 /agent-note-private-npm-publication-as-three-independent-sequences/decision/workspace-internal-references-use-the-workspace-protocol: - en: 6cff087e7e2d0ac7 - zh: 5d45a3abdd960d9c + en: 79c003c8838bb88c + zh: d23efb6ecccda3cc /agent-note-private-npm-publication-as-three-independent-sequences/decision/published-dependency-faces-use-an-explicit-policy: en: 30a64e0e919a27c0 zh: 19729d7ebee35439 diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md index 6a4f9d1ec2..501eebaaa6 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md @@ -80,7 +80,7 @@ Two registry behaviours shape how a publish is attempted. Writes are spaced by a Every reference to a workspace member uses the `workspace:` protocol. The [release-range policy](2026-09-22-workspace-release-ranges.md) requires exact `workspace:*` DSH references and `workspace:~` vendor/native references in every dependency section and consumer, including the native entry's optional platform packages. Local workspace linking is unchanged. -`scripts/check-workspace-constraints.ts` reads every member declared in `pnpm-workspace.yaml` plus the root manifest and enforces ranges by dependency target, not consumer directory. The invariant-companion rule requires `workspace:*` for `@deepseek-ai/dsh-invariants`; the dependency repairer preserves vendor/native tilde ranges. Published DSH peers therefore require the matching release instead of admitting later compatible versions. +`scripts/check-workspace-constraints.ts` reads every member declared in `pnpm-workspace.yaml` plus the root manifest and enforces ranges by dependency target, not consumer directory. The dependency repairer preserves vendor/native tilde ranges. Published DSH peers therefore require the matching release instead of admitting later compatible versions. ### Published dependency faces use an explicit policy diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md index d33d48e445..e923f94880 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md @@ -80,7 +80,7 @@ registry 的两个行为决定了「怎么尝试一次发布」。写入之间 所有指向 workspace 成员的引用都用 `workspace:` 协议。[发布范围策略](2026-09-22-workspace-release-ranges.zh.md)要求每个消费者的所有依赖区段对 DSH 使用精确的 `workspace:*` 引用,对 vendor/native 使用 `workspace:~` 引用,包括原生入口的可选平台包。本地 workspace 链接方式不变。 -`scripts/check-workspace-constraints.ts` 读取 `pnpm-workspace.yaml` 声明的全部成员及根清单,按依赖目标而非消费者目录校验范围。Invariant companion 规则要求 `@deepseek-ai/dsh-invariants` 使用 `workspace:*`;依赖修复器保留 vendor/native tilde 范围。因此,发布的 DSH peer 要求匹配的发布版本,而不接纳后续兼容版本。 +`scripts/check-workspace-constraints.ts` 读取 `pnpm-workspace.yaml` 声明的全部成员及根清单,按依赖目标而非消费者目录校验范围。依赖修复器保留 vendor/native tilde 范围。因此,发布的 DSH peer 要求匹配的发布版本,而不接纳后续兼容版本。 ### 发布依赖门面使用显式策略 diff --git a/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.i18n.yaml b/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.i18n.yaml index e36a2aa1c1..c78ea819d3 100644 --- a/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.i18n.yaml +++ b/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.i18n.yaml @@ -9,8 +9,8 @@ en: 528a0c462d456493 zh: a2af4901a8f498a7 /agent-note-evidence-led-simplification-surveys/decision: - en: 865cfb210f13b188 - zh: 777d8155216d98c6 + en: c292a9b7fa087874 + zh: 4a3e09282b085889 /agent-note-evidence-led-simplification-surveys/historical-evidence: en: 53eea2f1d7560461 zh: 3c55432d9cd3ebe3 diff --git a/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.md b/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.md index c217f80239..dd2cd97592 100644 --- a/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.md +++ b/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.md @@ -12,7 +12,7 @@ Unused-symbol searches can miss working behavior whose implementation costs more The [simplification skill](../../../skills/dsh-find-simplifications/SKILL.md) traces a complete producer-to-outcome path, asks which distinctions change consumer actions, considers explicit reductions in supported behavior, and searches for maintained copies that an authoritative read can replace. It separates composition availability from deployment policy and counts removed configuration, lifecycle, tests, and documentation together with source code. -Production use changes the required trade-off evidence; it does not automatically veto a proposal or authorize implementation. Dynamic plugin discovery, installed consumers, generated runtime assets, profiles, applications, and Python participate in consumer tracing. Protected adapters and persistence designs, released data, trust boundaries, independent invariant observations, synchronous publication, cancellation, and quiescent disposal remain constraints. +Production use changes the required trade-off evidence; it does not automatically veto a proposal or authorize implementation. Dynamic plugin discovery, installed consumers, generated runtime assets, profiles, applications, and Python participate in consumer tracing. Protected adapters and persistence designs, released data, trust boundaries, synchronous publication, cancellation, and quiescent disposal remain constraints. The entry point separates decision criteria from an [optional historical reference](../../../skills/dsh-find-simplifications/references/historical-patterns.md) pairing mechanisms with counterexamples and residual obligations. Archive mechanics remain at their existing owner, and the proposal skeleton uses the canonical headings. diff --git a/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.zh.md b/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.zh.md index 1b1a117319..783c3583b3 100644 --- a/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.zh.md +++ b/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.zh.md @@ -12,7 +12,7 @@ Status: implemented [简化 skill](../../../skills/dsh-find-simplifications/SKILL.md) 追踪从生产方到结果的完整路径,判断哪些区分会改变消费方动作,考虑明确收缩受支持行为,并寻找可以用权威读取替代的持续维护副本。它区分组合可用性与部署策略,并在源码之外合计删除的配置、生命周期、测试和文档成本。 -生产使用会改变所需的取舍证据,但不会自动否决提案,也不构成实施授权。动态插件发现、已安装消费方、生成的运行时产物、profile、应用和 Python 都纳入消费方追踪。受保护的适配器与持久化设计、已发布数据、信任边界、独立 invariant 观察、同步发布、取消及达到静止状态的释放仍是约束。 +生产使用会改变所需的取舍证据,但不会自动否决提案,也不构成实施授权。动态插件发现、已安装消费方、生成的运行时产物、profile、应用和 Python 都纳入消费方追踪。受保护的适配器与持久化设计、已发布数据、信任边界、同步发布、取消及达到静止状态的释放仍是约束。 入口将判断标准与[可选历史参考页](../../../skills/dsh-find-simplifications/references/historical-patterns.md) 分开,后者将机制与反例、残留义务配对。归档操作仍由原有归属处负责,提案结构使用规范标题。 diff --git a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml index ad1c43c6dd..a1d2efaf56 100644 --- a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml @@ -24,8 +24,8 @@ en: 4098e049cd6d28d8 zh: 4ab332ad6af53ccf /agent-note-plan-specific-collaboration-state/verification: - en: 41179b6ab02b3f90 - zh: 47f5a602496281cd + en: 25717419b3123d8e + zh: c45f2abbc7cb902f /agent-note-plan-specific-collaboration-state/consequences: en: a71537da7d3921e2 zh: a2ab69676bfe985d diff --git a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md index 20648b8d6c..d07e9e16fb 100644 --- a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md +++ b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md @@ -59,7 +59,7 @@ The tool renders the submitted plan as a generic card titled by its first headin ## Verification -- Package tests retain boundary ordering, retry, append-failure, HMR disposal, prompt assembly, stable native and PTC mode schemas, review outcomes, and invariant coverage through the boolean service. +- Package tests retain boundary ordering, retry, append-failure, HMR disposal, prompt assembly, stable native and PTC mode schemas, and review outcomes through the boolean service. - Command tests cover bare `/plan`, `/plan `, active `/plan off`, pending-entry cancellation, inactive idempotence, absence of `/mode` and `/review`, and effect-scoped removal. - The keyless TUI scenarios enter through `/plan `, leave through `/plan off`, and prove that each committed `plan/mode` precedes the request header it changes, the entry message is logged under plan guidance, and the post-exit request omits that guidance. - The complete `exit_plan_mode` review arc is package-tested but has no assembled-application snapshot after the interactive ACP scenarios were retired; current keyless TUI scenarios cover command entry and direct exit only. diff --git a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md index 2142839d5a..f9a02ae623 100644 --- a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md @@ -59,7 +59,7 @@ Plan mode 拥有一个 plan 专用产品包:位于 `packages/plan/plan-mode/` ## 验证 -- 包测试通过布尔服务继续覆盖边界顺序、重试、追加失败、HMR(热模块替换)资源释放、提示词组装、稳定的原生 schema 与 PTC mode schema、评审结果和不变式。 +- 包测试通过布尔服务继续覆盖边界顺序、重试、追加失败、HMR(热模块替换)资源释放、提示词组装、稳定的原生 schema 与 PTC mode schema 和评审结果。 - 命令测试覆盖不带参数的 `/plan`、`/plan `、激活状态下的 `/plan off`、取消待生效的进入选择、未激活状态下的幂等性、不存在 `/mode` 和 `/review`,以及随 effect 作用域移除。 - 无密钥 TUI 场景通过 `/plan ` 进入、通过 `/plan off` 退出,并证明每个已提交的 `plan/mode` 都先于其所改变的请求头,进入消息在 plan 引导下记录到日志,且退出后的请求不含该引导。 - 完整的 `exit_plan_mode` 评审流程有包测试,但交互式 ACP 场景退役后没有组装应用快照;当前无密钥 TUI 场景只覆盖命令进入和直接退出。 diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml index 036542d074..fbd548f59c 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml @@ -9,14 +9,14 @@ en: 33413b092d535f4f zh: 02f79f372227c104 /agent-note-remove-synthetic-turns-for-log-only-events/decision: - en: a49da9273f224a87 - zh: 870ffea9aacca6c7 + en: 6cb6b279574134a3 + zh: 6ea50ef14992d029 /agent-note-remove-synthetic-turns-for-log-only-events/alternatives-considered: - en: fbee860bf43530a7 - zh: 276647ba5402d6a8 + en: f9849e72fa2c3e9f + zh: c5dec6de58d9e6bf /agent-note-remove-synthetic-turns-for-log-only-events/verification: - en: bb7b19f80c4fbed6 - zh: ef06defe5b745e38 + en: 4290c31853d42929 + zh: 7fd3dea33c8e8f8d /agent-note-remove-synthetic-turns-for-log-only-events/consequences: en: 0c636b744dfa4ca9 zh: 05f409a4fd962015 diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md index 073ac08880..04bbfc9b38 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md @@ -16,7 +16,7 @@ The generic helper also duplicated domain policy. Its marker map said which plug `SessionStore.appendOutOfBand()`, `OutOfBandSessionEventMap`, and `OutOfBandSessionEventType` do not exist. A plugin that owns a log-only event appends it through `Session`; when the operation promises durability, it explicitly awaits `ctx.sessions.flush(session)`. No turn is opened solely to obtain that checkpoint. -Core session invariants continue to enforce core-owned execution relations: turn and step numbering, enclosure of steering, assistant, tool, todo, and request-header events, and same-step tool call/result pairing. Core permits merge-extensible events between turns because only their declaring plugin knows whether they are execution-scoped or standalone. Plugin invariant companions remain responsible for their own event relations. +Core permits merge-extensible events between turns because only their declaring plugin knows whether they are execution-scoped or standalone. The title service appends `session/title` directly after its existing service, revision, cancellation, and live-session checks. The bundled model helper appends its literal `session/title-llm-request` record before dispatch. Persistence admits both through the bounded `session/event` path and drains them at ordinary checkpoints and lifecycle teardown; neither append forces a flush merely because it is between turns. A fallback, auxiliary request record, or accepted provider title may therefore appear after `turn/end` and before the next `turn/start`. Manual compaction uses the same between-turn capability for a `compaction/* { turn: null }` bracket, but explicitly flushes the closed attempt because `/compact` promises durability before releasing queued prompt admission. @@ -32,11 +32,11 @@ The historical [universal turn-enclosure decision](../../archived/architecture/2 **Store titles as mutable session metadata.** This avoids between-turn events but creates a second mutation, replay, persistence, and fork protocol beside the append-only log. Titles remain replayable latest-wins events instead. -**Require every plugin event to declare standalone eligibility to core.** This keeps a central allowlist but makes absence mean an execution relation that core cannot verify. Merge-extensible unions already assign semantic ownership to the declaring plugin; its invariant companion is the correct enforcement point. +**Require every plugin event to declare standalone eligibility to core.** This keeps a central allowlist but makes absence mean an execution relation that core cannot verify. Merge-extensible unions already assign semantic ownership to the declaring plugin. ## Verification -Core invariant tests accept an unknown plugin event between turns while continuing to reject built-in execution events there. Hook, plan-mode, PTC mode dispatch, and approval invariant companions reject their execution-scoped events when no turn is open; the compaction companion separately accepts a balanced `turn: null` manual bracket between turns and requires numeric owners to match an open turn. Session-title service tests pin one direct fallback event under concurrent refresh, detached-session rejection, and newest-revision acceptance. A JSONL round trip preserves a title appended after `turn/end` through the persistence lifecycle drain, and fork tests retain a standalone log-only tail. A keyless assembled ACP snapshot delays the model-backed title until after `turn/end` and pins one standalone provider title with no synthetic turn. Generated API and type-equivalence catalogs contain no removed symbol. +Session-title service tests pin one direct fallback event under concurrent refresh, detached-session rejection, and newest-revision acceptance. A JSONL round trip preserves a title appended after `turn/end` through the persistence lifecycle drain, and fork tests retain a standalone log-only tail. A keyless assembled ACP snapshot delays the model-backed title until after `turn/end` and pins one standalone provider title with no synthetic turn. Generated API and type-equivalence catalogs contain no removed symbol. ## Consequences diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md index 67b24c7423..a4f0db4ba4 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md @@ -16,7 +16,7 @@ Status: implemented `SessionStore.appendOutOfBand()`、`OutOfBandSessionEventMap` 和 `OutOfBandSessionEventType` 均不再存在。拥有纯日志事件的插件通过 `Session` 追加该事件;当操作承诺持久性时,插件会显式等待 `ctx.sessions.flush(session)`。系统不会仅为获得该检查点而打开轮次。 -核心会话不变量继续强制核心所属的执行关系:轮次与步骤编号、steering(中途引导)、助手、工具、待办和请求头事件的封闭,以及同一步骤内的工具调用/结果配对。核心允许可合并扩展事件位于轮次之间,因为只有声明它们的插件知道这些事件受执行作用域约束,还是可以独立存在。插件的不变量配套组件仍负责其自身的事件关系。 +核心允许可合并扩展事件位于轮次之间,因为只有声明它们的插件知道这些事件受执行作用域约束,还是可以独立存在。 标题服务会在完成既有的服务状态、修订、取消和活跃会话检查后,直接追加 `session/title`。随附模型辅助函数会在发起调用前追加其字面量 `session/title-llm-request` 记录。持久化通过有界 `session/event` 路径接纳两者,并在常规检查点与生命周期结束时排空;二者都不会仅因为位于轮次之间就强制刷写。因此,回退标题、辅助请求记录或已接受的提供方标题可以出现在 `turn/end` 之后、下一个 `turn/start` 之前。手动压缩(compaction)利用同一项轮次间能力记录 `compaction/* { turn: null }` 标记对,但会显式刷写已闭合的尝试,因为 `/compact` 承诺在放行排队中的提示词前完成持久化。 @@ -32,11 +32,11 @@ Status: implemented **将标题存储为可变会话元数据。** 这可以避免轮次间事件,却会在仅追加日志之外建立第二套变更、回放、持久化和 fork 协议。标题仍采用可回放、后写覆盖的事件。 -**要求每个插件事件向核心声明可独立存在的资格。** 这可以保留中心准入列表,但会让缺少声明意味着一种核心无法验证的执行关系。可合并扩展联合类型已经将语义所有权赋予声明事件的插件;该插件的不变量配套组件才是正确的强制位置。 +**要求每个插件事件向核心声明可独立存在的资格。** 这可以保留中心准入列表,但会让缺少声明意味着一种核心无法验证的执行关系。可合并扩展联合类型已经将语义所有权赋予声明事件的插件。 ## 验证 -核心不变量测试会接受轮次之间的未知插件事件,同时继续拒绝位于该处的内置执行事件。钩子、plan-mode、PTC mode 分发和审批的不变量配套组件会在没有开放轮次时拒绝其执行作用域事件;压缩配套组件则另外接受轮次之间平衡的 `turn: null` 手动标记对,并要求数字 owner匹配一个开放轮次。会话标题服务测试会在并发刷新、拒绝已脱离会话和接受最新修订的场景下,固定一个直接追加的回退事件。JSONL 往返测试会通过持久化生命周期排空保留追加在 `turn/end` 之后的标题;fork 测试会保留独立纯日志尾部。一个无密钥、经完整组装的 ACP(Agent Client Protocol)快照会将模型生成的标题延迟到 `turn/end` 之后,并固定一个不含合成轮次的独立提供方标题。生成的 API 和类型等价性目录不含任何已移除符号。 +会话标题服务测试会在并发刷新、拒绝已脱离会话和接受最新修订的场景下,固定一个直接追加的回退事件。JSONL 往返测试会通过持久化生命周期排空保留追加在 `turn/end` 之后的标题;fork 测试会保留独立纯日志尾部。一个无密钥、经完整组装的 ACP(Agent Client Protocol)快照会将模型生成的标题延迟到 `turn/end` 之后,并固定一个不含合成轮次的独立提供方标题。生成的 API 和类型等价性目录不含任何已移除符号。 ## 后果 diff --git a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml deleted file mode 100644 index 8ccac77c7e..0000000000 --- a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-28-omit-unneeded-invariant-companions.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md -/agent-note-omit-invariant-companions-without-independent-observations: - en: 1aaf29f8d757fcc8 - zh: 76d99dd71bfabbaa -/agent-note-omit-invariant-companions-without-independent-observations/problem: - en: 53d9687bdef70d91 - zh: 61d225b1cd374c28 -/agent-note-omit-invariant-companions-without-independent-observations/decision: - en: a15c7c13f167e5ac - zh: 63bcc2ef60c2f344 -/agent-note-omit-invariant-companions-without-independent-observations/decision/independent-observations-justify-publication: - en: ee3e2ff2f0565d94 - zh: 232ddf075bf3b353 -/agent-note-omit-invariant-companions-without-independent-observations/decision/omission-is-explicit-in-the-package-readme: - en: 465c523c29fd7836 - zh: 9967d7e5b17f1adf -/agent-note-omit-invariant-companions-without-independent-observations/decision/audit-result: - en: b19c08cd1f7df630 - zh: 30e872df9ee3ff0a -/agent-note-omit-invariant-companions-without-independent-observations/alternatives-considered: - en: 5908a05a2aeb3f82 - zh: 9a01748e8d8b86f4 -/agent-note-omit-invariant-companions-without-independent-observations/consequences: - en: c648b67008e676f1 - zh: c89400eae3fef89c diff --git a/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml b/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml index 432ef80b37..553fe59c73 100644 --- a/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml @@ -27,8 +27,8 @@ en: c529437d4fbaeeb3 zh: fe13551e56cba7df /agent-note-keyless-browser-e2e-lane-for-the-web-gui/decision/ci-stance: - en: 23b625d86ba7f5e2 - zh: 88f33b233fca0a82 + en: de7d5a3c976bede9 + zh: aaec61e4058b3268 /agent-note-keyless-browser-e2e-lane-for-the-web-gui/prior-art: en: 7472e212043721dd zh: 61dd9f5a8e12730b diff --git a/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md b/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md index eb79591e7b..7b06c2ab52 100644 --- a/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md +++ b/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md @@ -50,7 +50,7 @@ The lane covers three behavior families. Live-turn scenarios pin ordinary tool e ### CI stance -The lane is a required compare-only gate for Linux pull requests under the [browser snapshot CI decision](2026-07-30-web-browser-snapshot-ci-gate.md). The `node 24 / snapshots and artifacts` consumer job owns the [single Linux build](../../archived/process/2026-07-30-independent-ci-consumer-build.md), installs the lockfile-selected Chromium and WebKit, restores their OS-and-lockfile-keyed cache, and runs the lane with `DSH_SNAPSHOT=replay`. This is an intentional plane split: the host and specs use the [tsx source-launch contract](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md), while the browser consumes `apps/web/dist` and package `lib/client.js` artifacts, so the gate depends on `built-package-invariants` for those client artifacts. The self-hosted default-branch Linux serial standby runs the same gate; there is no hosted Linux serial producer of the browser cache consumed by pull requests, and the persistent self-hosted pool needs no hosted cache. CI never records or refreshes goldens. Scenarios remain POSIX-oriented and stay outside the Windows and macOS matrices. +The lane is a required compare-only gate for Linux pull requests under the [browser snapshot CI decision](2026-07-30-web-browser-snapshot-ci-gate.md). The `node 24 / snapshots and artifacts` consumer job owns the [single Linux build](../../archived/process/2026-07-30-independent-ci-consumer-build.md), installs the lockfile-selected Chromium and WebKit, restores their OS-and-lockfile-keyed cache, and runs the lane with `DSH_SNAPSHOT=replay`. This is an intentional plane split: the host and specs use the [tsx source-launch contract](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md), while the browser consumes `apps/web/dist` and package `lib/client.js` artifacts, so the gate depends on `build` for those client artifacts. The self-hosted default-branch Linux serial standby runs the same gate; there is no hosted Linux serial producer of the browser cache consumed by pull requests, and the persistent self-hosted pool needs no hosted cache. CI never records or refreshes goldens. Scenarios remain POSIX-oriented and stay outside the Windows and macOS matrices. High-cardinality performance diagnostics use the separate opt-in `apps/web/tests/**/*.perf.ts` inventory selected only by `vitest.web.perf.config.ts`. The isolated `complex-history.perf.ts` cases reuse the real scaffold: the workspace case seeds 1,000 compact sessions plus one 500-turn history containing 500 tool calls, exhausts and remounts that history in Chat, and reports Chromium main-thread, DOM, listener, heap, paging, search, and Trajectory measurements. Two continuation cases seed the same long history but compare the default 24-turn Chat window with all 500 turns expanded before each continues eight identical turns through the real composer, agent loop, SSE wire, tools, and persistence; two turns execute a real `bash` call and assert its durable result, while the final turn fills an 8,232-character mixed-language prompt and replays 120 paced text deltas. A separate soak case starts from a blank session, drives 100 consecutive real composer turns with a `bash` call and result every tenth turn, forces GC every ten turns, and reports ten-turn latency windows plus retained browser state. It then submits a 101st text-only turn with a trusted browser click and measures browser-clock send-to-transcript-DOM and send-to-post-paint latency, excluding the composer's draft mirrors, separately from full-turn completion. Per-turn diagnostics cover composer fill, click-to-user-echo, click-to-first-chunk, completion, browser mutations, persisted chunks, and tool events; the synthetic replay model has enough context capacity to keep fixture cardinality stable instead of consuming scripted calls through compaction. Structural assertions pin the intended load, stream, and tool shapes, but timing remains threshold-free because machine speed is not a correctness contract. The required `vitest.web.config.ts` inventory remains limited to `*.e2e.ts` and `*.snapshot.ts`, so neither `test:web:built` nor its CI gate collects performance cases. diff --git a/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md b/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md index f715f4ca93..3975884785 100644 --- a/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md +++ b/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md @@ -50,7 +50,7 @@ Web GUI 以一条真实组装链交付——chromium 页面 → client 插件 bu ### CI 立场 -根据[浏览器快照 CI 决策](2026-07-30-web-browser-snapshot-ci-gate.zh.md),该车道是 Linux 拉取请求必需的只比较门禁。`node 24 / snapshots and artifacts` 消费方任务在[消费方独立构建](../../archived/process/2026-07-30-independent-ci-consumer-build.md)中负责唯一一次 Linux 构建,安装锁文件选定的 Chromium 和 WebKit,恢复以操作系统和锁文件为键的缓存,并用 `DSH_SNAPSHOT=replay` 运行该车道。这是有意的平面切分:host 与 spec 使用 [tsx 源码启动约定](../architecture/2026-07-29-dsh-source-launch-tsx-esm.zh.md),浏览器则消费 `apps/web/dist` 和包的 `lib/client.js` 产物,因此门禁依赖 `built-package-invariants` 提供这些客户端产物。自托管的默认分支 Linux 串行热备运行同一门禁;不存在托管 Linux 串行生产者生成供 PR 消费的浏览器缓存,持久化自托管池则不需要托管侧缓存。CI 从不录制或刷新预期输出。场景仍面向 POSIX,并继续置于 Windows 和 macOS 矩阵之外。 +根据[浏览器快照 CI 决策](2026-07-30-web-browser-snapshot-ci-gate.zh.md),该车道是 Linux 拉取请求必需的只比较门禁。`node 24 / snapshots and artifacts` 消费方任务在[消费方独立构建](../../archived/process/2026-07-30-independent-ci-consumer-build.md)中负责唯一一次 Linux 构建,安装锁文件选定的 Chromium 和 WebKit,恢复以操作系统和锁文件为键的缓存,并用 `DSH_SNAPSHOT=replay` 运行该车道。这是有意的平面切分:host 与 spec 使用 [tsx 源码启动约定](../architecture/2026-07-29-dsh-source-launch-tsx-esm.zh.md),浏览器则消费 `apps/web/dist` 和包的 `lib/client.js` 产物,因此门禁依赖 `build` 提供这些客户端产物。自托管的默认分支 Linux 串行热备运行同一门禁;不存在托管 Linux 串行生产者生成供 PR 消费的浏览器缓存,持久化自托管池则不需要托管侧缓存。CI 从不录制或刷新预期输出。场景仍面向 POSIX,并继续置于 Windows 和 macOS 矩阵之外。 高基数性能诊断使用单独按需启用的 `apps/web/tests/**/*.perf.ts` 清单,并且只由 `vitest.web.perf.config.ts` 选中。`complex-history.perf.ts` 的隔离用例复用真实 scaffold:工作区用例播种 1,000 个紧凑会话以及一份包含 500 次工具调用的 500 轮次历史,在 Chat 中穷尽并重新挂载该历史,并报告 Chromium 主线程、DOM、监听器、堆内存、分页、搜索和 Trajectory 测量结果。两个续聊用例播种同一份长历史,但比较默认的 24 轮次 Chat 窗口与展开全部 500 轮次的状态,然后各自通过真实输入框、agent loop、SSE wire、工具和持久化继续进行 8 个相同轮次;其中两轮执行真实 `bash` 调用并断言其持久化结果,最后一轮则填入一条包含 8,232 个字符的混合语言提示词,并回放 120 个带节奏的文本增量。一个单独的 soak 用例从空白会话开始,通过真实输入框连续驱动 100 轮,每第 10 轮执行一次 `bash` 调用并产生结果,每 10 轮强制执行一次 GC,并报告每 10 轮的延迟窗口及保留的浏览器状态。随后它通过受信任的浏览器点击提交第 101 个纯文本轮次,并使用浏览器时钟分别测量发送到 transcript DOM 和发送到绘制后的延迟,排除输入框的草稿镜像,并与完整轮次完成时间分开。逐轮诊断涵盖输入框填入、点击到用户消息回显、点击到首个分片、完成、浏览器变更、持久化分片和工具事件;合成回放模型拥有足够的上下文容量,可使 fixture 基数保持稳定,而不会因压缩(compaction)消耗脚本化调用。结构性断言钉住预期的负载、流和工具形状,但时间仍不设阈值,因为机器速度不属于正确性约定。必需的 `vitest.web.config.ts` 清单仍仅限 `*.e2e.ts` 和 `*.snapshot.ts`,因此 `test:web:built` 及其 CI 门禁都不会收集性能用例。 diff --git a/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml b/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml index 8a40db5638..084a6b28c6 100644 --- a/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml @@ -9,8 +9,8 @@ en: e04a5ecbd6d6a39e zh: 049ce77a1a679daa /agent-note-required-ci-gate-for-web-browser-expected-outputs/decision: - en: 915efba11d7ece34 - zh: 540495b04ef9a305 + en: 2d281656114999c6 + zh: df81089782749849 /agent-note-required-ci-gate-for-web-browser-expected-outputs/alternatives-considered: en: 145e763735dd33b1 zh: 4ea34838629cd3bf diff --git a/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md b/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md index 352072bca6..207c35c7ef 100644 --- a/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md +++ b/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md @@ -20,7 +20,7 @@ CI’s `scripts/run-web-snapshots.ts` runs `hmr-live.e2e.ts` separately because For pull requests, the gate runs only in the Linux consumer job: these scenarios target POSIX, and the other PR jobs do not provision both engines. The self-hosted default-branch Linux serial standby also includes the comparison, while the macOS and Windows serial jobs remain browser-free (there is no hosted Linux serial aggregate). A PR's `all checks passed` verdict already depends on the consumer job, so a browser compare failure blocks the merge without requiring a new branch-protection check name. -Completed local replays measured the six-worker browser command at about 65–71 seconds. A twelve-worker comparison completed in about 50 seconds, so halving the browser worker budget adds about 15–20 seconds rather than doubling wall time. The gate scheduler starts browser snapshots as soon as `built-package-invariants` succeeds and runs independent gates concurrently, so it needs neither a dedicated job timeout nor a manual YAML ordering rule. +Completed local replays measured the six-worker browser command at about 65–71 seconds. A twelve-worker comparison completed in about 50 seconds, so halving the browser worker budget adds about 15–20 seconds rather than doubling wall time. The gate scheduler starts browser snapshots as soon as `build` succeeds and runs independent gates concurrently, so it needs neither a dedicated job timeout nor a manual YAML ordering rule. ## Alternatives considered diff --git a/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md b/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md index cd04f33a5d..04a90a201a 100644 --- a/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md +++ b/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md @@ -20,7 +20,7 @@ CI 的 `scripts/run-web-snapshots.ts` 单独运行会修改构建产物的 `hmr- 对 PR 而言,门禁仅在 Linux 消费方 job 中运行:这些场景面向 POSIX,其他 PR job 不会同时安装这两种引擎。自托管的默认分支 Linux 串行热备也包含该比较,而 macOS 和 Windows 串行 job 仍不使用浏览器(不存在托管的 Linux 串行聚合)。PR 的 `all checks passed` 已依赖消费方 job,因此浏览器比较失败会阻止合并,无需新增 branch-protection check 名称。 -完整本地 replay 中,6-worker 浏览器命令耗时约 65–71 秒。12-worker 对比约为 50 秒,因此把浏览器 worker 预算减半只增加约 15–20 秒,而不是让墙钟时间翻倍。门禁调度器会在 `built-package-invariants` 成功后立即启动浏览器快照,并发运行彼此独立的门禁,因此既不需要专用 job 超时,也不需要手动制定 YAML 顺序规则。 +完整本地 replay 中,6-worker 浏览器命令耗时约 65–71 秒。12-worker 对比约为 50 秒,因此把浏览器 worker 预算减半只增加约 15–20 秒,而不是让墙钟时间翻倍。门禁调度器会在 `build` 成功后立即启动浏览器快照,并发运行彼此独立的门禁,因此既不需要专用 job 超时,也不需要手动制定 YAML 顺序规则。 ## 曾考虑的替代方案 diff --git a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml index 714d3ca25a..ee2ca829f6 100644 --- a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml @@ -21,8 +21,8 @@ en: 6a26dd7ef357db74 zh: 951b31136e2415f9 /agent-note-domain-kv-storage-capability-seam-and-the-workspace-entity/proposal/dsh-domain-the-domain-data-form: - en: d51c71c793c26834 - zh: 3aab9f9695a0e225 + en: 2ee24f36cd93f7dd + zh: 45c83bd745de41f9 /agent-note-domain-kv-storage-capability-seam-and-the-workspace-entity/proposal/future-work-session-side-deletion-design-settled-not-implemented-this-phase: en: a3824326587b6539 zh: 9736e3060bd57651 diff --git a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md index dc14648c70..c05913baa3 100644 --- a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md +++ b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md @@ -106,7 +106,7 @@ export function apply(ctx: Context, config: Config) { } ``` -(Facility unmount order: dispose each domain first (drain its write chain), then remove the name from the hub — in-flight writes still emit `domain/changed` during the drain, and the event-consistency invariant resolves domains back through the facility, so the name must stay resolvable at that point.) +(Facility unmount order: dispose each domain first (drain its write chain), then remove the name from the hub — in-flight writes still emit `domain/changed` during the drain, so the name must stay resolvable at that point.) Domain declarations (the spec object is defined and exported by the package that owns the domain — the single source of type and runtime truth; schemas use zod with `z.infer` deriving the types without re-declaration — the record model projects into RPC wire schemas next phase and the wire boundary is all zod; schemastery still owns plugin Config only): diff --git a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md index 1cf7280919..a25c18f2ce 100644 --- a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md +++ b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md @@ -106,7 +106,7 @@ export function apply(ctx: Context, config: Config) { } ``` -(facility 卸载顺序:先 dispose 各域(排空写链)再从枢纽摘名——排空期间在途写仍发 `domain/changed`,事件一致性 invariant 经 facility 反查域,要求此时域名仍可解析。) +(facility 卸载顺序:先 dispose 各域(排空写链)再从枢纽摘名——排空期间在途写仍发 `domain/changed`,因此此时域名仍须可解析。) 域声明(spec 对象由拥有该域的包定义导出,是类型与运行时的唯一真源;schema 用 zod,`z.infer` 推导类型不重复声明——记录模型下期要投影成 RPC wire schema,wire 边界全是 zod;schemastery 仍只管插件 Config): diff --git a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml index 7207445070..59a262d59e 100644 --- a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml @@ -15,8 +15,8 @@ en: e6c93d89a25f0d03 zh: 65e1f5842bc74008 /agent-note-session-projections-and-command-lifecycle-logging/proposal/host-projection-registry-dsh-session-projection-new-package: - en: 6068eb6aba54b102 - zh: 20e91466baf6ad11 + en: a0f86a588c9dff31 + zh: 421803bc8b0dddb5 /agent-note-session-projections-and-command-lifecycle-logging/proposal/shipped-consumer-the-subagent-identity-unit: en: 10093535660c3cae zh: a7f4ff9e71ee6751 @@ -33,8 +33,8 @@ en: 7b536bd8a51cc5dc zh: 1ead63f6358b3799 /agent-note-session-projections-and-command-lifecycle-logging/proposal/command-lifecycle-in-the-log: - en: a7e04451f3ed417c - zh: fb60c8729200b825 + en: 41c7f98634d4762f + zh: 0f3b85f4c6fa06a3 /agent-note-session-projections-and-command-lifecycle-logging/delivery-plan: en: 64ad8d08a8ab44e0 zh: fb0a0f7369d0bd23 @@ -45,5 +45,5 @@ en: 9dfcca30fffda225 zh: 0beddb81d32e0ca7 /agent-note-session-projections-and-command-lifecycle-logging/risks: - en: f0eb219b4119ca7d - zh: de870016c1623895 + en: c3740b6e12eb7295 + zh: b0b427b83c73dbba diff --git a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md index fb329a4117..4e2e02482e 100644 --- a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md +++ b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md @@ -59,7 +59,6 @@ declare module 'cordis' { - **State is always computed, never logged.** The log holds events only; the unit's state lives in the framework's per-session watermark cache (`{state, observedSeq}` per unit) and, in a later phase, in a **persisted projection cache** on the domain-KV storage seam: rows of `(sessionId, key, ver, seq, val)` (`ver` = the unit's `stateVersion`, `seq` = the watermark, `val` = the state JSON). A row is never wrong, only possibly stale — its `seq` says exactly how stale. The one read recipe, cold and live alike: take the cached state (or `init()`), forward-apply only the events past its watermark, `view` the result. Cold listings (every session's title across all workspaces) become an index read plus, at worst, a short tail replay; the session-persistence seam grows a read-from-seq primitive for that tail in the same later phase. Write policy: throttled (count/interval, configurable) plus two mandatory points — `turn/end` and detach (the live-to-cold moment). A crash between writes costs a longer tail replay, never a wrong value. - A domain's input event set is its own choice: todos folds `todo/write` alone; plan folds `plan/mode` plus its own `/plan` `command/run` records (see the plan section); goal folds `goal/change` metadata; session title folds its title events (retiring the bespoke `session/title` frame and the client's title-snapshot map — the fourth hand-rolled projection this seam absorbs). - Registration is an effect (disposer with the fiber): an unloaded plugin's key disappears from subsequent responses and the client reads it as capability absence — HMR semantics for free. Duplicate keys throw. Domain plugins register under `ctx.inject(['sessionProjections'], …)` so headless assemblies without the registry stay unaffected. -- The package owns `./invariant` (every served key has a live registration). ### Shipped consumer: the subagent identity unit @@ -128,7 +127,7 @@ Two log-only (non-surface, model-invisible) events, mirroring the `tool/call`/`t 'command/done': { commandId: string; kind: 'success' | 'error'; text?: string } ``` -The host command executor (`packages/interaction/commands`) appends `command/run` before invoking the handler and `command/done` at settlement — direct standalone appends on the receiving agent's session, in the same shape as every other plugin-owned log-only event after the [synthetic-turn removal](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md): no turn wraps them (turns describe model-loop executions only), persistence drains them at ordinary checkpoints, and the commands package's own invariant companion enforces the run/done pairing. The payload is structured — `name` and, by default, `args` are the parser's own split (`parseCommand`'s name and rawInput), so a consumer (a projection unit folding its own command records, a rich command card) never re-parses a line. A definition sets `recordInput: false` when its authoritative domain event owns the payload; `command/run` then omits `args` rather than duplicating it. `text` is the handler's verbatim outcome — factual data of the same nature as `tool/result.content`, not presentation (how it is laid out remains client-computed at render time, satisfying the "presentation never enters the log" red line). Domains that want the model to know the outcome keep their existing behavior (plan's narration, goal's inject) — that is a domain decision, unchanged. +The host command executor (`packages/interaction/commands`) appends `command/run` before invoking the handler and `command/done` at settlement — direct standalone appends on the receiving agent's session, in the same shape as every other plugin-owned log-only event after the [synthetic-turn removal](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md): no turn wraps them (turns describe model-loop executions only), and persistence drains them at ordinary checkpoints. The payload is structured — `name` and, by default, `args` are the parser's own split (`parseCommand`'s name and rawInput), so a consumer (a projection unit folding its own command records, a rich command card) never re-parses a line. A definition sets `recordInput: false` when its authoritative domain event owns the payload; `command/run` then omits `args` rather than duplicating it. `text` is the handler's verbatim outcome — factual data of the same nature as `tool/result.content`, not presentation (how it is laid out remains client-computed at render time, satisfying the "presentation never enters the log" red line). Domains that want the model to know the outcome keep their existing behavior (plan's narration, goal's inject) — that is a domain decision, unchanged. Because committed events broadcast on the mux stream, refresh persistence, multi-tab sync, and fork/resume recovery all come for free. The `command.execute` RPC degrades to admission — `{ matched, commandId? }`: whether the line resolved, and the minted pairing id when it did, so the issuing client can correlate its request with the flow node the lifecycle events produce. The one-shot notice channel (`runDetached` → `noticeFor`) is retired. @@ -184,7 +183,7 @@ Infrastructure first; the three in-flight PRs are left untouched and re-target a ## Risks - **Whole-value rule is load-bearing**: a future domain logging bare deltas cannot serve consumers from its latest event and complicates its own unit. Mitigation: the rule is stated here and in the projection package README; the unit contract makes the full state explicit at every transition. -- **Synchronous unit discipline**: `init`/`apply`/`view` that await would tear the consistency cut. The registry documents and the invariant companion asserts synchronicity as far as practical; review owns the rest. +- **Synchronous unit discipline**: `init`/`apply`/`view` that await would tear the consistency cut. The registry documents it; review enforces it. - **Live registry churn is not pushed**: loading or unloading a domain plugin mid-session changes the key set, but no session event fires and no frame is pushed; open clients hold the stale key until the next tail pull (reconnect, gap repair, open). Accepted as a dev-only (HMR) staleness window — a registry-change push can be added to the change feed later without contract impact. - **Eager drive costs on busy sessions**: every committed event passes every registered unit's `apply`. Units are cheap per-event by construction (whole-value rule), non-matching events return the same reference, and the count of registered domains is small; if a hot path ever shows, per-unit event-type prefilters can be added without contract change. - **Projection payload growth**: every tail page carries every registered key. Payloads are whole values of UI-scale state (a todo list, a goal snapshot); if a future domain's value is large, per-key opt-out or lazy keys can be added to the request without changing the model. diff --git a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md index eb359058a7..f226acfc83 100644 --- a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md +++ b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md @@ -59,7 +59,6 @@ declare module 'cordis' { - **状态永远靠计算得出,绝不入日志。** 日志只存事件;单元的状态住在框架的按会话水位线缓存里(每单元一份 `{state, observedSeq}`),并在后续阶段进入 domain-KV 存储 seam 上的**持久投影缓存(persisted projection cache)**:形如 `(sessionId, key, ver, seq, val)` 的行(`ver` = 单元的 `stateVersion`,`seq` = 水位线,`val` = 状态 JSON)。一行永远不会是错的,至多是陈旧的——其 `seq` 精确说明陈旧到哪。冷读与活读共用同一套读取配方:取缓存状态(或 `init()`),只对超出其水位线的事件做正向 `apply`,再对结果做 `view`。冷列表(跨全部 workspace 列出每个会话的标题)变成一次索引读,至多外加一小段尾部回放;session-persistence seam 在同一后续阶段为这段尾部补一个按 seq 起读的原语。写入策略:节流(次数/间隔,可配置)外加两个强制点——`turn/end` 与 detach(由活转冷的时刻)。两次写入之间崩溃的代价是尾部回放更长一些,绝不会是值出错。 - 领域的输入事件集由领域自己选择:todos 只折叠 `todo/write`;plan 折叠 `plan/mode` 外加它自己的 `/plan` `command/run` 记录(见 plan 一节);goal 折叠 `goal/change` 元数据;会话标题折叠其标题事件(顺带下线专设的 `session/title` 帧与客户端的标题快照表——这是该 seam 收编的第四个手工投影)。 - 注册是 effect(disposer 随 fiber 走):插件卸载后其 key 从后续响应中消失,客户端将其读作能力缺失——HMR(热模块替换)语义随之自动成立。key 重复直接 throw。领域插件在 `ctx.inject(['sessionProjections'], …)` 下注册,因此不带注册表的 headless 组装完全不受影响。 -- 该包拥有 `./invariant`(每个被服务的 key 都有一条存活的注册)。 ### 已交付的消费方:subagent 身份单元 @@ -128,7 +127,7 @@ type UseProjection = { 'command/done': { commandId: string; kind: 'success' | 'error'; text?: string } ``` -host 侧命令执行器(`packages/interaction/commands`)在调用处理器前追加 `command/run`,在结算时追加 `command/done`——在接收 agent(智能体)的会话上直接独立追加,与[合成轮次移除](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)之后所有插件自有 log-only 事件同一形状:没有轮次包裹它们(轮次只描述模型循环执行),持久化在常规检查点排空它们,run/done 配对由 commands 包自己的 invariant 伴生插件把守。载荷是结构化的——`name` 以及默认携带的 `args` 来自解析器自己的切分(`parseCommand` 的 name 与 rawInput),因此消费方(折叠自己命令记录的投影单元、富命令卡片)永远无需重新解析行文本。当载荷由权威领域事件持有时,命令定义会设置 `recordInput: false`;此时 `command/run` 省略 `args`,而不是重复该载荷。`text` 是处理器的原样结果——与 `tool/result.content` 同一性质的事实数据,不是呈现(版式如何编排仍由客户端在渲染时计算,满足「呈现永不入日志」这条红线)。想让模型知道结果的领域保持既有行为(plan 的旁白、goal 的注入)——那是领域自己的决定,保持不变。 +host 侧命令执行器(`packages/interaction/commands`)在调用处理器前追加 `command/run`,在结算时追加 `command/done`——在接收 agent(智能体)的会话上直接独立追加,与[合成轮次移除](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)之后所有插件自有 log-only 事件同一形状:没有轮次包裹它们(轮次只描述模型循环执行),持久化在常规检查点排空它们。载荷是结构化的——`name` 以及默认携带的 `args` 来自解析器自己的切分(`parseCommand` 的 name 与 rawInput),因此消费方(折叠自己命令记录的投影单元、富命令卡片)永远无需重新解析行文本。当载荷由权威领域事件持有时,命令定义会设置 `recordInput: false`;此时 `command/run` 省略 `args`,而不是重复该载荷。`text` 是处理器的原样结果——与 `tool/result.content` 同一性质的事实数据,不是呈现(版式如何编排仍由客户端在渲染时计算,满足「呈现永不入日志」这条红线)。想让模型知道结果的领域保持既有行为(plan 的旁白、goal 的注入)——那是领域自己的决定,保持不变。 由于已提交事件会在 mux 流上广播,刷新后仍在、多标签页同步、fork/恢复后可还原这三件事随之全部自动获得。`command.execute` RPC 退化为准入判定——`{ matched, commandId? }`:该行是否匹配命中,以及命中时新铸的配对 id,发起命令的客户端据此把自己的请求与生命周期事件产出的 flow 节点关联起来。一次性通知通道(`runDetached` → `noticeFor`)就此下线。 @@ -184,7 +183,7 @@ host 侧命令执行器(`packages/interaction/commands`)在调用处理器 ## 风险 - **全量值规则是承重结构**:未来某个领域若只记裸增量,就无法凭其最新事件服务消费方,还会让自己的单元复杂化。缓解:该规则写明在本 Note 与投影包的 README 里;单元约定让完整状态在每次转移处都是显式的。 -- **单元的同步纪律**:`init`/`apply`/`view` 一旦 await 就会撕裂一致性切面。注册表在文档中申明这条纪律,invariant 配套在可行范围内断言同步性;其余由评审把关。 +- **单元的同步纪律**:`init`/`apply`/`view` 一旦 await 就会撕裂一致性切面。注册表在文档中申明这条纪律,由评审把关。 - **注册表的实时增删不做推送**:会话中途加载或卸载领域插件会改变键集,但不会触发任何会话事件、也不会推任何帧;开着的客户端持有陈旧的 key 直到下次尾页拉取(重连、缺口修补、打开)。接受为仅开发期(HMR)的陈旧时窗——日后可以在变更流上加一个注册表变更推送,约定不受影响。 - **忙碌会话上的主动驱动开销**:每个已提交事件都要过每个已注册单元的 `apply`。按构造,单元的逐事件开销很低(全量值规则),不匹配的事件返回同一引用,且已注册领域的数量很小;若真出现热点路径,可以加按单元的事件类型预过滤,约定不变。 - **投影载荷膨胀**:每个尾页携带每个已注册的 key。载荷是 UI 量级状态的全量值(一张 todo 清单、一份 goal 快照);将来若某领域的值很大,可以在请求上加逐 key 的 opt-out 或惰性 key,模型本身不用改。 diff --git a/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.i18n.yaml b/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.i18n.yaml deleted file mode 100644 index 30d37e8252..0000000000 --- a/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-19-omit-fs-payload-invariant.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.md -/agent-note-omit-the-filesystem-payload-only-invariant-companion: - en: 6385aa079529c4f4 - zh: 602b99cea351125c -/agent-note-omit-the-filesystem-payload-only-invariant-companion/problem: - en: 4568d53b3ef69254 - zh: 66c9aea2ae3645e7 -/agent-note-omit-the-filesystem-payload-only-invariant-companion/proposal: - en: 2bfceaa480b6645d - zh: 3ad1060bf91ded74 -/agent-note-omit-the-filesystem-payload-only-invariant-companion/alternatives-considered: - en: d12fd1577a308af3 - zh: 56271794646f5a87 -/agent-note-omit-the-filesystem-payload-only-invariant-companion/acceptance-criteria: - en: 253009b93ce34c5b - zh: 2268e0303bd025d6 -/agent-note-omit-the-filesystem-payload-only-invariant-companion/risks: - en: 70c52f193d72d02d - zh: f30844e865ac4c27 diff --git a/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.md b/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.md deleted file mode 100644 index e91fa1784a..0000000000 --- a/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.md +++ /dev/null @@ -1,34 +0,0 @@ -# Agent Note: Omit the filesystem payload-only invariant companion - -Status: proposed - -English | [中文](2026-09-19-omit-fs-payload-invariant.zh.md) - -## Problem - -The [filesystem invariant](../../../../packages/fs/fs/src/invariant.ts) checks non-empty target strings, a non-empty present version, and an observation discriminant. It reads one dispatch payload, retains no history, and compares no provider, policy, filesystem, or independently mutable state. Its [tests](../../../../packages/fs/fs/tests/invariant.spec.ts) construct malformed payloads directly. Publishing this check costs a companion module, export/build wiring, invariant dependencies, compiler references, and dedicated tests. - -Current guidance needs an explicit decision: the [independent-observation rule](../../implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md) requires observations that can independently diverge, while the [runtime-contract examples](../../implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md) specifically endorse filesystem target/version validity. The later rule retains the earlier note's semantic authority, so chronology alone does not resolve this conflict. - -## Proposal - -Apply the independent-observation criterion to this concrete companion and remove it. The event producers are independent plugins, but checking the format of each single payload does not compare their observations. Keep typed filesystem events and the real [observation-policy consumer](../../../../packages/fs/fs-observation-policy/src/index.ts), along with parser, wire, read, edit, and write validation at their existing owners. - -Remove the 48-line companion, its 55-line dedicated test, `./invariant` export/publication, invariant-only dependencies and compiler references. Add the package-specific omission reason to both fs READMEs. Amend the earlier representative-check row when implementing this decision, retaining its other semantic examples and the shared invariant registration machinery. This proposal leaves those current owners unchanged until acceptance. - -## Alternatives considered - -**Keep payload validation as an explicit policy exception.** It can detect malformed third-party emissions early, but this would deliberately broaden the stated independent-observation criterion. The proposal prefers input validation at its owner and reserves companions for relational checks. - -**Add a second state source to justify the companion.** Rejected without a concrete missing invariant. Duplicating policy state or exposing new API solely to retain this diagnostic increases the maintenance burden. - -## Acceptance criteria - -- Resolve the named policy conflict in the active owning notes; do not claim this is already an uncontested mechanical omission. -- No companion source, test, export, artifact entry, invariant-only dependency, or compiler reference remains; package topology checks accept the explained omission. -- Real file-tool and observation-policy tests retain stale-version, absent-target, alias, actor-routing, and world-state assertions. The events and consumers remain. -- Run focused fs and observation-policy tests, relevant file-tool integration checks, source/built invariant-topology checks, build/hygiene, doc-sync, and lint. Do not replace the module with equivalent generic payload-validation machinery. - -## Risks - -Malformed trusted-plugin emissions lose this opt-in early diagnostic. The check's removal does not justify weakening validation of external data, nor removing other companions that compare lifecycle, durable history, or independently assembled values. If payload-format diagnostics are intentionally part of the companion policy, retain this module and clarify that exception instead. diff --git a/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.zh.md b/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.zh.md deleted file mode 100644 index ba81baed5f..0000000000 --- a/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.zh.md +++ /dev/null @@ -1,34 +0,0 @@ -# Agent Note: 省略仅校验载荷的文件系统 invariant 配套模块 - -Status: proposed - -[English](2026-09-19-omit-fs-payload-invariant.md) | 中文 - -## 问题 - -[文件系统 invariant](../../../../packages/fs/fs/src/invariant.ts) 检查目标字符串非空、present 版本非空及观察值的判别标签。它只读取一次分发的载荷,不保留历史,也不与提供方、策略、文件系统或独立可变状态比较。其[测试](../../../../packages/fs/fs/tests/invariant.spec.ts) 直接构造格式错误的载荷。发布该检查需要配套模块、导出/构建配置、invariant 依赖、编译器引用和专用测试。 - -当前指导需要显式决策:[独立观察规则](../../implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md) 要求能够独立分歧的观察值,而[运行时约定示例](../../implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md) 明确认可文件系统目标/版本有效性检查。后者的语义权威仍被较新的规则保留,因此不能仅凭时间先后解决冲突。 - -## 提案 - -将独立观察标准应用于这个具体配套模块,并删除它。事件生产方来自独立插件,但逐一检查单个载荷的格式,并没有比较它们的观察值。保留类型化文件系统事件和实际的[观察策略消费方](../../../../packages/fs/fs-observation-policy/src/index.ts),以及现有归属处的解析器、wire、读取、编辑和写入校验。 - -删除 48 行配套模块、55 行专用测试、`./invariant` 导出/发布项、仅供 invariant 使用的依赖和编译器引用。在 fs 的两个 README 中补充包特有的省略理由。实现本决策时修改较早的代表性检查行,保留其他语义示例及共享 invariant 注册机制。本提案获接受前不修改这些当前归属文档。 - -## 考虑过的替代方案 - -**将载荷校验保留为显式策略例外。** 它能提前发现第三方格式错误的事件,但这会有意扩大已声明的独立观察标准。提案倾向于在输入归属处校验,并将配套模块留给关系检查。 - -**添加第二个状态源以证明配套模块有必要。** 在没有具体缺失 invariant 的情况下否决。仅为保留此诊断而复制策略状态或暴露新 API,会增加维护负担。 - -## 验收标准 - -- 在当前归属 Agent Notes 中解决已指出的策略冲突;不将其宣称为已有共识的机械省略。 -- 不再保留配套源码、测试、导出、产物条目、仅供 invariant 使用的依赖或编译器引用;包拓扑检查接受有说明的省略。 -- 真实文件工具和观察策略测试保留过期版本、目标缺失、别名、执行方路由及外部状态断言。事件及其消费方保留。 -- 运行聚焦的 fs 与观察策略测试、相关文件工具集成检查、源码/构建产物 invariant 拓扑检查、build/hygiene、doc-sync(文档同步门禁)和 lint。不用等价的通用载荷校验机制替换该模块。 - -## 风险 - -来自受信任插件的格式错误事件会失去这项可选的早期诊断。删除该检查不构成削弱外部数据校验的理由,也不意味着可以删除比较生命周期、持久化历史或独立组装值的其他配套模块。如果载荷格式诊断本来就是配套模块策略的一部分,应保留模块并澄清该例外。 diff --git a/.agents/notes/rejected/process/2026-06-11-architectural-conformance.i18n.yaml b/.agents/notes/rejected/process/2026-06-11-architectural-conformance.i18n.yaml index 0e5a8660a4..d14592ce5d 100644 --- a/.agents/notes/rejected/process/2026-06-11-architectural-conformance.i18n.yaml +++ b/.agents/notes/rejected/process/2026-06-11-architectural-conformance.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/rejected/process/2026-06-11-architectural-conformance.md /agent-note-architectural-conformance-dependency-rules-and-the-adapter-kit: - en: 708fe0c94db3e25a - zh: 07437d3cb67a9240 + en: ef7ac2975a42d676 + zh: 03158d5f19d86f96 /agent-note-architectural-conformance-dependency-rules-and-the-adapter-kit/problem: en: 2d5d19405f22920b zh: 8ca48379706cbe35 diff --git a/.agents/notes/rejected/process/2026-06-11-architectural-conformance.md b/.agents/notes/rejected/process/2026-06-11-architectural-conformance.md index 631b032235..0d845f4961 100644 --- a/.agents/notes/rejected/process/2026-06-11-architectural-conformance.md +++ b/.agents/notes/rejected/process/2026-06-11-architectural-conformance.md @@ -1,6 +1,6 @@ # Agent Note: Architectural conformance — dependency rules and the adapter kit -Status: rejected — premise gone: plugins depend on dsh-agent-loop by design; dsh-llm/invariant and the hygiene dependency gates own conformance +Status: rejected — premise gone: plugins depend on dsh-agent-loop by design; the hygiene dependency gates own dependency conformance English | [中文](2026-06-11-architectural-conformance.zh.md) diff --git a/.agents/notes/rejected/process/2026-06-11-architectural-conformance.zh.md b/.agents/notes/rejected/process/2026-06-11-architectural-conformance.zh.md index 74af5a377c..a23ad25802 100644 --- a/.agents/notes/rejected/process/2026-06-11-architectural-conformance.zh.md +++ b/.agents/notes/rejected/process/2026-06-11-architectural-conformance.zh.md @@ -1,6 +1,6 @@ # Agent Note: 架构一致性——依赖规则与适配器套件 -Status: rejected — premise gone: plugins depend on dsh-agent-loop by design; dsh-llm/invariant and the hygiene dependency gates own conformance +Status: rejected — premise gone: plugins depend on dsh-agent-loop by design; the hygiene dependency gates own dependency conformance [English](2026-06-11-architectural-conformance.md) | 中文 diff --git a/.agents/skills/dsh-code-review/SKILL.md b/.agents/skills/dsh-code-review/SKILL.md index bc145e212a..7810ada660 100644 --- a/.agents/skills/dsh-code-review/SKILL.md +++ b/.agents/skills/dsh-code-review/SKILL.md @@ -25,9 +25,8 @@ description: Use when reviewing a pull request in the deepseek-harness repo — 2. **Docs match the code.** Config, defaults, errors, wire fields, events, and public behavior update the package README and JSDoc in the same diff. Comments state non-obvious contracts; flag implementation narration, test walkthroughs, review history, and duplicated rationale for deletion or a link to their one home. 3. **Core type docs match.** Changes to spine or seam vocabulary update the appropriate [subsystems](../../../docs/subsystems/README.md) page and any `type-equiv` entry. Internal types need no catalog entry. 4. **Registrations clean up.** Verify each new registry contribution passes the disposal tests required by [packages/AGENTS.md](../../../packages/AGENTS.md). -5. **Invariant companions are semantic.** For every touched `./invariant`, require an owner event-stream or mutable-data relationship with independent observations at the point where that package can observe it; service or method presence, plugin metadata or effects, fixed pure examples, and probes that call the same operation they claim to verify belong in load, behavior, or unit tests. When no plausible relationship exists, require the package to omit the companion and publication wiring and record its package-specific reason in the README. Reject empty installers and invented checks ([repository rule](../../../AGENTS.md#conventions); [package invariant rules](../../../packages/AGENTS.md)). -6. **Required evidence exists.** Verify the author ran the [relevant local checks](../../../AGENTS.md#run-relevant-checks-locally) for the diff and that CI covers the exhaustive matrix; review the semantic gaps neither can detect. -7. **Client UI copy is locale-owned.** Reject product text embedded in JSX, templates, helper returns, accessibility attributes, or primitive defaults. Require typed dictionary keys, the standard `t` seat or explicit localized props, `verify-client-ui-i18n`, and behavior evidence in each affected locale; preserve user/model/wire data and code tokens verbatim. +5. **Required evidence exists.** Verify the author ran the [relevant local checks](../../../AGENTS.md#run-relevant-checks-locally) for the diff and that CI covers the exhaustive matrix; review the semantic gaps neither can detect. +6. **Client UI copy is locale-owned.** Reject product text embedded in JSX, templates, helper returns, accessibility attributes, or primitive defaults. Require typed dictionary keys, the standard `t` seat or explicit localized props, `verify-client-ui-i18n`, and behavior evidence in each affected locale; preserve user/model/wire data and code tokens verbatim. ## Manual checks @@ -43,7 +42,7 @@ description: Use when reviewing a pull request in the deepseek-harness repo — - **Real entry path:** tests exercise the shipped Loader, bin, worker, ACP bridge, or subprocess where relevant. A hand-mounted plugin does not catch invalid Loader exports; a function plugin must named-export its namespace and have no default export. - **Test strength:** assertions fail on the intended regression and verify external state, logs, events, or disposal rather than restating the implementation or trusting an agent's report. Coverage is necessary but not evidence that the scenario is correct. - **Test reliability:** for a resource-owning, asynchronous, platform-sensitive, or flaky test, apply [dsh-ci-test-reliability](../dsh-ci-test-reliability/SKILL.md) to the real worker/job topology, resource allocation, global-state restoration, synchronization, timeout budget, and quiescent teardown. -- **Invariant lifecycle and negative controls:** verify candidate observations are rejected before publication where possible, session-backed checks reconstruct durable history after late loading or HMR, and a deliberately invalid case fails through the real runner for the intended rule. +- **Negative controls:** verify a deliberately invalid case fails through the real runner for each changed acceptance rule. - **Implemented Agent Notes match shipped reality:** when a PR implements a proposed Agent Note, move and rewrite it as present-tense shipped state in the same diff, then verify paths, names, and mechanisms against the implementation. - **Transcript changes:** editor-visible or model-visible changes update snapshots or explain why no snapshot applies. Review expected-output diffs as behavior changes, not formatting noise. - **Bilingual changes:** compare meaning and terminology on both sides; a green pairing hash does not prove translation quality. diff --git a/.agents/skills/dsh-find-simplifications/SKILL.md b/.agents/skills/dsh-find-simplifications/SKILL.md index 53444db411..12692cc82c 100644 --- a/.agents/skills/dsh-find-simplifications/SKILL.md +++ b/.agents/skills/dsh-find-simplifications/SKILL.md @@ -46,8 +46,6 @@ For copies, freezes, validators, and callback captures, name the value's origin, For asynchronous machinery, map each promise, flag, cancellation path, disposer, and reservation to its owner and transition. Collapse mechanisms only when they express the same fact. Preserve synchronous publication and rollback, callback containment, first-terminal-outcome arbitration, worker/process ownership, and dispose-to-quiescence where required. Durable start/end markers can record real facts even when transcript renderers ignore them; dropping interrupted work is a capability loss, not redundant-log cleanup. -An invariant companion is justified by comparing independently produced observations that can diverge. Checks of service presence, plugin metadata, fixed examples, or the same mutation's return value do not qualify. Removing a companion includes its export, build entry, invariant-only compiler references, dependencies, and tests; record the package-specific omission reason in both READMEs. Keep checks comparing independent event producers, durable history, or mutable data. - ## Replace infrastructure only for a net reduction Follow the [dependency policy](../../notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.md). Search existing dependencies and Node builtins at the engine floor before adding a package. Identify the exact implementation and dedicated tests the replacement deletes, its maintenance and transitive cost, and every residual behavior the glue must preserve. diff --git a/AGENTS.md b/AGENTS.md index fc94452a0f..4cf6d3ccc3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -68,7 +68,6 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// mcp/ external tools experimental/ pre-stable prototypes; public by default with explicit private exceptions test-support/ test infrastructure - runtime-diagnostics/ runtime invariants util/ zero-dependency utilities python/ Python SDK/runtime (python/README.md) native/ @deepseek-ai/node-addon-system source (native/README.md) @@ -131,8 +130,7 @@ Real-API tests/demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, and - Packages use `@deepseek-ai/dsh-`; vendor is [rescoped](docs/rescope.md) and `private: true`. Harness packages declare `@deepseek-ai/cordis` in `peerDependencies`/`devDependencies`. Workspace dependency sections use DSH `workspace:*`, vendor/native `workspace:~` ([rules](.agents/notes/implemented/process/2026-09-22-workspace-release-ranges.md)). - ESM everywhere (`"type": "module"`). Use package names across packages and `.ts` in local relative imports. Config subprocesses run built `lib/` under plain Node; source regressions use their declared launcher ([testing policy](docs/testing.md#test-subprocess-launch-modes)). The `dsh` CLI source launch runs through tsx's ESM-only hook (`node --import tsx/esm`); modules it reaches must stay ESM (no CJS-only exports) — Node's native TypeScript modes are unavailable across the engines range ([source-launch contract](.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md)). Raw/Web `cordis.yml` bare plugins must appear in their resolver manifest's `dependencies`; `verify-cordis-config` enforces it. - **Registrations are effects**: every contribution goes through `ctx.effect()` / `ctx.on()`; a registry's `register()` returns the disposer. -- **Runtime invariants assert owned relationships.** Publish `./invariant` only when independent observations can diverge. Otherwise omit its source and wiring and record why in its README; empty installers and checks of service presence, plugin metadata, effects, or fixed examples are invalid ([package invariant rules](packages/AGENTS.md)). -- **Typed events use declaration merging** and merge-extensible maps. Event JSDoc needs `@mode` and payload `@param`; scoped keys absent from payloads need `@dshScopeScan unsupported`. Public service methods document parameters and non-void returns. `SessionEventMap` members are required-on-read by default — builds that do not know a type refuse the log unless the event carries the envelope's `ignorable: true`; only structural format changes bump `SESSION_FORMAT_VERSION` ([mechanism](.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md)). +- **Typed events use declaration merging** and merge-extensible maps. Event JSDoc needs `@mode` and payload `@param`. Public service methods document parameters and non-void returns. `SessionEventMap` members are required-on-read by default — builds that do not know a type refuse the log unless the event carries the envelope's `ignorable: true`; only structural format changes bump `SESSION_FORMAT_VERSION` ([mechanism](.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md)). - **Switch on discriminant tags.** Closed unions end in `assertNever`; merge-extensible unions fall through a documented default. - **Waterfall listeners MUST call `next()`** to delegate; returning without it short-circuits the chain ([semantics](docs/cordis-primer.md#cordis-waterfall-semantics)). - **Model-visible ⟺ logged**: anything that reaches a model request must be reconstructable from the session log; a new model-visible input requires a session event. diff --git a/apps/cli/tests/built-bin.e2e.ts b/apps/cli/tests/built-bin.e2e.ts index b26b3e538c..483637fe90 100644 --- a/apps/cli/tests/built-bin.e2e.ts +++ b/apps/cli/tests/built-bin.e2e.ts @@ -1264,11 +1264,6 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)', ['agent', '@deepseek-ai/dsh-agent'], ['llm-retry', '@deepseek-ai/dsh-llm-retry'], ['jobs', '@deepseek-ai/dsh-jobs-local'], - ['invariants', '@deepseek-ai/dsh-invariants'], - ['session-invariant', '@deepseek-ai/dsh-session/invariant'], - ['agent-invariant', '@deepseek-ai/dsh-agent/invariant'], - ['scope-invariant', '@deepseek-ai/dsh-scope/invariant'], - ['agent-loop-invariant', '@deepseek-ai/dsh-agent-loop/invariant'], ['agent-loop', '@deepseek-ai/dsh-agent-loop'], ['persistent-bash', '@deepseek-ai/dsh-tool-bash-persistent'], ['persistent-pwsh', '@deepseek-ai/dsh-tool-pwsh-persistent'], diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index 5e0e5f9848..6e8253ffe6 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -27,8 +27,8 @@ en: badc0ffea030cdce zh: 3241f0b37c3ec067 /deepseek-harness-architecture/session-log: - en: e1e3a44140036f25 - zh: 84c70c969e5ad216 + en: 82e67cf5b66b8add + zh: 5d0ea37270bdd0d5 /deepseek-harness-architecture/capability-seams: en: 93d1dc2eeda4aec2 zh: ee7a9da89151ea17 diff --git a/docs/architecture.md b/docs/architecture.md index c63a75cb23..d23d0759f0 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -124,7 +124,7 @@ The session log is the source of the context the model sees. `deriveMessages()` Session consumers know only the current logical format. Header-only `stat` and `list` rescan each Session directory, select its numerically highest canonical generation, and translate a supported historical header without loading events or publishing a successor. A stored-session `open` selects that same generation, refuses a future version, or decodes and composes the static adjacent migration chain once before returning validated current logical events. A read open uses that in-memory result without publishing a successor; a write open first encodes, verifies, and exclusively publishes the final version-named successor beside the unchanged source. Ordinary repair of an unsealed interrupted tail remains a handle consumer responsibility; migration inserts a missing interrupted `turn/end` only for the bounded released restart already sealed by a later `turn/start`. JSONL v0 uses `session.jsonl[.zstd]`, v1 and later use lowercase `session.vN.jsonl[.zstd]`, and committed generation paths are never renamed, replaced, or deleted. The JSONL provider owns physical framing, compression, generation selection, and exclusive publication, while each adjacent migration package owns exactly one `vN -> vN+1` step ([decision](../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md)). -**Model-visible means logged.** A runtime invariant checks model requests are reconstructable from the log. New model-visible inputs require session events. Plugins changing existing message content register [pure message projections](subsystems/session.md#plugin-owned-message-projections); detached readers supply the same definitions explicitly. Tool changes are capability-independent; [Session tool history](../packages/core/session/README.md) supplies provider declarations. +**Model-visible means logged.** Every model request must be reconstructable from the log. New model-visible inputs require session events. Plugins changing existing message content register [pure message projections](subsystems/session.md#plugin-owned-message-projections); detached readers supply the same definitions explicitly. Tool changes are capability-independent; [Session tool history](../packages/core/session/README.md) supplies provider declarations. **Projection seam.** `dsh-session-projection` owns `ctx.sessionProjections`: registered units fold committed events incrementally, host consumers read one typed state with `stateOf()`, and carriers batch cropped client views with `snapshot()`. A host reader either requires this service during activation or fails explicitly when the registry or required key is absent. Contributors may retain `ctx.inject(['sessionProjections'], ...)` registration without silently defaulting a missing host value. The agent loop registers shared `turnBoundary` state for its readers ([decision](../.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md)). diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index 1ae1356a74..bbd8eac74f 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -128,7 +128,7 @@ turn/end Session 消费方只了解当前逻辑格式。仅 header 的 `stat` 与 `list` 会重新扫描每个 Session 目录,选择数值最高的规范 generation,并在不加载事件或发布后继的情况下转换受支持的历史 header。已存储 Session 的 `open` 选择同一 generation,拒绝未来版本,或只 Decode 并组合一次构建时静态确定的相邻迁移链,再返回经过校验的当前逻辑事件。只读 open 直接使用这份内存结果,不发布后继;写 open 则先编码、校验并在未改变源的旁边排他发布最终版本命名的后继。未被后续事件封住的普通中断尾部仍由句柄消费方修复;只有在后续 `turn/start` 已经封住一种有限的已发布 restart 时,migration 才会插入缺失的 interrupted `turn/end`。JSONL v0 使用 `session.jsonl[.zstd]`,v1 及后续版本使用小写 `session.vN.jsonl[.zstd]`;已提交 generation 路径绝不重命名、替换或删除。JSONL provider 负责物理 framing、压缩、generation 选择与排他发布,每个相邻迁移包只负责一个 `vN -> vN+1` 步骤([决策](../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md))。 -**模型可见即已记录。** 运行时不变量检查模型请求是否可从日志重建。新增模型可见输入需要会话事件。修改现有消息内容的插件注册[纯消息投影](subsystems/session.zh.md#plugin-owned-message-projections),独立读取器显式传入相同的处理器。 工具变更不依赖能力;[Session 工具历史](../packages/core/session/README.zh.md)提供提供方声明。 +**模型可见即已记录。** 每个模型请求都必须能从日志重建。新增模型可见输入需要会话事件。修改现有消息内容的插件注册[纯消息投影](subsystems/session.zh.md#plugin-owned-message-projections),独立读取器显式传入相同的处理器。 工具变更不依赖能力;[Session 工具历史](../packages/core/session/README.zh.md)提供提供方声明。 **投影 seam。** `dsh-session-projection` 提供 `ctx.sessionProjections`:已注册单元增量折叠已提交事件,host 消费方通过 `stateOf()` 读取单个类型化状态,载体通过 `snapshot()` 批量取得裁剪后的客户端视图。host 读取方要么在激活时要求该服务,要么在注册表或必需 key 缺席时明确失败。贡献方可以保留 `ctx.inject(['sessionProjections'], ...)` 注册,但不能为缺失的 host 值静默提供默认值。agent loop 为读取方注册共享的 `turnBoundary` 状态([决策](../.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.zh.md))。 diff --git a/docs/capability-seams.i18n.yaml b/docs/capability-seams.i18n.yaml index bdc6ab959f..764dbaf986 100644 --- a/docs/capability-seams.i18n.yaml +++ b/docs/capability-seams.i18n.yaml @@ -6,5 +6,5 @@ en: 41d772192075e133 zh: 5ef3da69b4de1d8a /capability-seams-and-core-services: - en: 890a2217279b7c70 - zh: a5c64adca1fb5a40 + en: 65f0b4104daac871 + zh: 48bbc8eb309f27d4 diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 5cc25f88f2..fd8ac9a534 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -64,7 +64,6 @@ flowchart LR pkg_session_query["session-query"] pkg_session_query_sqlite["session-query-sqlite"] pkg_subagent_in_process_driver["subagent-in-process-driver"] - pkg_invariants["invariants"] pkg_message_feedback["message-feedback"] pkg_experimental_api_speech_to_text["experimental-api-speech-to-text"] svc_speechController["ctx.speechController
Experimental transcription Remote"] @@ -85,8 +84,6 @@ flowchart LR pkg_api_workspace_controller["api-workspace-controller"] svc_workspaceController["ctx.workspaceController
Host Workspace Remote controller"] svc_directoryPickerController["ctx.directoryPickerController
Host directory-picking Remote controller"] - svc_invariants["ctx.invariants
Package-owned invariant registry"] - pkg_scope["scope"] pkg_typert_registry["typert-registry"] svc_typert["ctx.typert
Runtime type registry"] pkg_typert_loader["typert-loader"] @@ -341,7 +338,6 @@ flowchart LR pkg_host_product_telemetry_otel --> svc_productTelemetry pkg_host_webserver --> svc_webServer pkg_inspector --> svc_inspector - pkg_invariants --> svc_invariants pkg_jobs --> svc_jobs pkg_jobs_local --> svc_jobs pkg_llm --> svc_llm @@ -465,10 +461,6 @@ flowchart LR svc_fileUploads --> pkg_api_session_controller svc_fs --> pkg_tool_fs svc_hmr --> pkg_app_boot - svc_invariants --> pkg_agent - svc_invariants --> pkg_agent_loop - svc_invariants --> pkg_scope - svc_invariants --> pkg_session svc_jobs --> pkg_api_job_controller svc_jobs --> pkg_tool_bash svc_jobs --> pkg_tool_jobs @@ -510,7 +502,6 @@ flowchart LR svc_sessionQuery --> pkg_tool_session_query svc_sessions --> pkg_agent svc_sessions --> pkg_agent_loop - svc_sessions --> pkg_invariants svc_sessions --> pkg_message_feedback svc_sessions --> pkg_session_persistence svc_sessions --> pkg_session_query @@ -592,7 +583,7 @@ flowchart LR | `ctx.deepseekLlmApiExtensions` | `seam` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | [`session-log-deepseek`](../packages/session/session-log-deepseek), [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | [`llm-deepseek`](../packages/llm/llm-deepseek) | - | Plugins prepare independent top-level fields; the official adapter merges them and commits their delivery state after HTTP acceptance. | | `ctx.tokenMeter` | `core` | [`token-meter`](../packages/llm/token-meter) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | Owns isolated per-session replay folds; pressure consumers share immutable revisioned measurements. | | `ctx.toolResultPruner` | `core` | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | Rewrites oversized current tool results through replayable single-node surface replacements before summary compaction. | -| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`invariants`](../packages/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback) | - | Owns append-only Session instances and emits the durable session event feed. | +| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`message-feedback`](../packages/feedback/message-feedback) | - | Owns append-only Session instances and emits the durable session event feed. | | `ctx.speechController` | `core` | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | - | - | Validates bounded browser audio before provider dispatch. | | `ctx.sessionController` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | Owns Session commands, cold reads, durable-event following, live control state, model catalogs, workspace opening, and Agent activation policy. | | `ctx.sessionFileReferences` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | Delegates file-reference discovery through the Session Controller's established Agent lookup policy. | @@ -605,7 +596,6 @@ flowchart LR | `ctx.terminalController` | `core` | [`api-terminal-controller`](../packages/api/terminal-controller) | - | - | - | Owns user terminal processes, default shell resolution and bounded screen recovery through the subprocess provider and typed Remote transport. | | `ctx.workspaceController` | `core` | [`api-workspace-controller`](../packages/api/workspace-controller) | - | - | - | Owns Workspace commands and reconnect-safe Workspace state delivery through the generated Remote namespace. | | `ctx.directoryPickerController` | `core` | [`api-workspace-controller`](../packages/api/workspace-controller) | - | - | - | Carries the picking seam onto the wire: capability gating, cancellation, and the seam-coded failures a browser directory flow discriminates on. | -| `ctx.invariants` | `core` | [`invariants`](../packages/runtime-diagnostics/invariants) | - | [`session`](../packages/core/session), [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`agent-loop`](../packages/core/agent-loop) | - | Companion subpaths register owner-local checks; the service owns selection, uniqueness, child fibers, and package-attributed failures. | | `ctx.typert` | `core` | [`typert-registry`](../packages/typert/registry) | - | [`typert-loader`](../packages/typert/loader), [`api-gateway`](../packages/api/gateway) | - | Plugins register live zod contributions directly or through dsh-typert-loader; the API gateway consumes invocation descriptors and providers, while other runtime consumers query schemas and reflection metadata at their own edges. | | `ctx.typertGateway` | `core` | [`api-gateway`](../packages/api/gateway) | - | - | - | Associates generated Remote descriptors with live Cordis services, resolves registered identities, and exposes unary calls through the shared Connection RPC carrier. | | `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session/session-persistence) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/shell/tool-bash), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`message-feedback`](../packages/feedback/message-feedback) | - | The JSONL backend persists the SessionEvent vocabulary as one artifact per Session. | diff --git a/docs/capability-seams.zh.md b/docs/capability-seams.zh.md index 03c62ceeca..9d96d5dc60 100644 --- a/docs/capability-seams.zh.md +++ b/docs/capability-seams.zh.md @@ -66,7 +66,6 @@ flowchart LR pkg_session_query["session-query"] pkg_session_query_sqlite["session-query-sqlite"] pkg_subagent_in_process_driver["subagent-in-process-driver"] - pkg_invariants["invariants"] pkg_message_feedback["message-feedback"] pkg_experimental_api_speech_to_text["experimental-api-speech-to-text"] svc_speechController["ctx.speechController
Experimental transcription Remote"] @@ -87,8 +86,6 @@ flowchart LR pkg_api_workspace_controller["api-workspace-controller"] svc_workspaceController["ctx.workspaceController
Host Workspace Remote controller"] svc_directoryPickerController["ctx.directoryPickerController
Host directory-picking Remote controller"] - svc_invariants["ctx.invariants
Package-owned invariant registry"] - pkg_scope["scope"] pkg_typert_registry["typert-registry"] svc_typert["ctx.typert
Runtime type registry"] pkg_typert_loader["typert-loader"] @@ -343,7 +340,6 @@ flowchart LR pkg_host_product_telemetry_otel --> svc_productTelemetry pkg_host_webserver --> svc_webServer pkg_inspector --> svc_inspector - pkg_invariants --> svc_invariants pkg_jobs --> svc_jobs pkg_jobs_local --> svc_jobs pkg_llm --> svc_llm @@ -467,10 +463,6 @@ flowchart LR svc_fileUploads --> pkg_api_session_controller svc_fs --> pkg_tool_fs svc_hmr --> pkg_app_boot - svc_invariants --> pkg_agent - svc_invariants --> pkg_agent_loop - svc_invariants --> pkg_scope - svc_invariants --> pkg_session svc_jobs --> pkg_api_job_controller svc_jobs --> pkg_tool_bash svc_jobs --> pkg_tool_jobs @@ -512,7 +504,6 @@ flowchart LR svc_sessionQuery --> pkg_tool_session_query svc_sessions --> pkg_agent svc_sessions --> pkg_agent_loop - svc_sessions --> pkg_invariants svc_sessions --> pkg_message_feedback svc_sessions --> pkg_session_persistence svc_sessions --> pkg_session_query @@ -594,7 +585,7 @@ flowchart LR | `ctx.deepseekLlmApiExtensions` | `seam` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | [`session-log-deepseek`](../packages/session/session-log-deepseek), [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | [`llm-deepseek`](../packages/llm/llm-deepseek) | - | 插件准备彼此独立的顶层字段;官方适配器会合并这些字段,并在 HTTP 接受后提交其交付状态。 | | `ctx.tokenMeter` | `core` | [`token-meter`](../packages/llm/token-meter) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 拥有按会话隔离的回放折叠区;压力消费方共享不可变且带修订版本的测量结果。 | | `ctx.toolResultPruner` | `core` | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 在摘要压缩前,通过可回放的单节点表层替换来改写过大的当前工具结果。 | -| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`invariants`](../packages/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback) | - | 拥有仅追加的 Session 实例,并发出持久的会话事件流。 | +| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`message-feedback`](../packages/feedback/message-feedback) | - | 拥有仅追加的 Session 实例,并发出持久的会话事件流。 | | `ctx.speechController` | `core` | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | - | - | 在 Provider 调用前校验受限的浏览器音频。 | | `ctx.sessionController` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | 负责 Session 命令、冷读取、持久事件跟随、实时控制状态、模型目录、workspace 打开与 Agent 激活策略。 | | `ctx.sessionFileReferences` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | 通过 Session Controller 的既有 Agent lookup 策略委托文件引用发现。 | @@ -607,7 +598,6 @@ flowchart LR | `ctx.terminalController` | `core` | [`api-terminal-controller`](../packages/api/terminal-controller) | - | - | - | 通过子进程提供方与类型化 Remote 传输管理用户终端进程、解析默认 shell,并恢复有界终端屏幕。 | | `ctx.workspaceController` | `core` | [`api-workspace-controller`](../packages/api/workspace-controller) | - | - | - | 通过生成的 Remote namespace 负责 Workspace 命令和可在重连后收敛的 Workspace 状态投递。 | | `ctx.directoryPickerController` | `core` | [`api-workspace-controller`](../packages/api/workspace-controller) | - | - | - | 把选目录 seam 送上线:能力门禁、取消传播,以及浏览器目录流程用于分支判断的 seam 错误码。 | -| `ctx.invariants` | `core` | [`invariants`](../packages/runtime-diagnostics/invariants) | - | [`session`](../packages/core/session), [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`agent-loop`](../packages/core/agent-loop) | - | 配套子路径注册所属包本地的检查;该服务负责选择、唯一性、子 fiber,以及标明所属包的失败。 | | `ctx.typert` | `core` | [`typert-registry`](../packages/typert/registry) | - | [`typert-loader`](../packages/typert/loader), [`api-gateway`](../packages/api/gateway) | - | 插件直接或通过 dsh-typert-loader 注册实时 zod 贡献;API 网关消费调用描述符和提供方,其他运行时消费方则在各自边界查询 schema 与反射元数据。 | | `ctx.typertGateway` | `core` | [`api-gateway`](../packages/api/gateway) | - | - | - | 将生成的 Remote 描述符与实时 Cordis 服务关联,解析已注册的身份,并通过共享的 Connection RPC 载体提供一元调用。 | | `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session/session-persistence) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/shell/tool-bash), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`message-feedback`](../packages/feedback/message-feedback) | - | JSONL backend 把 SessionEvent 词汇持久化为每个 Session 一份产物。 | diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 7c40fad913..f3d4f8f8f0 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -1513,26 +1513,6 @@ export interface Config { ``` - - - -## `@deepseek-ai/dsh-invariants` - -- `source`: [`packages/runtime-diagnostics/invariants/src/index.ts:15`](../packages/runtime-diagnostics/invariants/src/index.ts) - -```ts config-catalog -/** Runtime invariant selection configured on the service plugin. */ -export interface Config { - /** Global switch; defaults to `true`. */ - readonly enabled?: boolean - /** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */ - readonly package_allowlist?: string[] - /** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */ - readonly package_blocklist?: string[] -} -``` - - @@ -3085,7 +3065,7 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist' ## `@deepseek-ai/dsh-subagent` - `refs`: `Volatile` (`@deepseek-ai/cordis`) -- `source`: [`packages/subagent/subagent/src/index.ts:192`](../packages/subagent/subagent/src/index.ts) +- `source`: [`packages/subagent/subagent/src/index.ts:190`](../packages/subagent/subagent/src/index.ts) ```ts config-catalog /** Host configuration for continuable subagent capacity. */ diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 44b5a73455..b3b9cd6ed1 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -1515,26 +1515,6 @@ export interface Config { ``` - - - -## `@deepseek-ai/dsh-invariants` - -- `source`: [`packages/runtime-diagnostics/invariants/src/index.ts:15`](../packages/runtime-diagnostics/invariants/src/index.ts) - -```ts config-catalog -/** Runtime invariant selection configured on the service plugin. */ -export interface Config { - /** Global switch; defaults to `true`. */ - readonly enabled?: boolean - /** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */ - readonly package_allowlist?: string[] - /** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */ - readonly package_blocklist?: string[] -} -``` - - @@ -3087,7 +3067,7 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist' ## `@deepseek-ai/dsh-subagent` - `refs`: `Volatile` (`@deepseek-ai/cordis`) -- `source`: [`packages/subagent/subagent/src/index.ts:192`](../packages/subagent/subagent/src/index.ts) +- `source`: [`packages/subagent/subagent/src/index.ts:190`](../packages/subagent/subagent/src/index.ts) ```ts config-catalog /** Host configuration for continuable subagent capacity. */ diff --git a/docs/cookbook/adding-a-package.i18n.yaml b/docs/cookbook/adding-a-package.i18n.yaml index a5f3e9fba3..bbc2fc29d0 100644 --- a/docs/cookbook/adding-a-package.i18n.yaml +++ b/docs/cookbook/adding-a-package.i18n.yaml @@ -6,8 +6,8 @@ en: b94bcdbebcd6355e zh: 9fd61c2b986da759 /cookbook-adding-a-workspace-package/1-create-the-package: - en: 3499082415478cc5 - zh: 65d3120abd646b64 + en: 062249fc249b2fec + zh: f6b9fe7f63c4cbad /cookbook-adding-a-workspace-package/2-register-it-in-the-root-configs: en: 12b985dee93a47a7 zh: 1027db3d251cd860 diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index 60b5d6e047..5cd3dc916a 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -24,7 +24,7 @@ packages/// Choose an existing group when one matches the package's role (`core`, `llm`, `shell`, `compaction`, `subagent`, `todo`, `session`, `client`/`host`, `util`, or `test-support`). A new group is allowed, but it is a pure container: no `package.json`, no source files, and packages still sit exactly one level below it. -package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, a `version` matching the root `package.json`, `type: module`, `main: "lib/index.js"`, `types: "lib/types/index.d.ts"`, `exports["."].types: "./lib/types/index.d.ts"`, `exports["."].default: "./lib/index.js"`, `@deepseek-ai/cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `@deepseek-ai/schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop. The `files` list contains exactly `lib/index.js`, `lib/types/**/*.d.ts`, and package-specific runtime artifacts recognized by the gate; a package that publishes `./invariant` also includes `lib/invariant.js`. A package whose runtime export points into the emitted tree also includes `lib/types/**/*.js`. Do not publish `src`, declaration maps, JS maps, or stale root declaration files. CLI app packages with a package `bin` include `lib/bin.js` immediately after `lib/index.js` in `files`. +package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, a `version` matching the root `package.json`, `type: module`, `main: "lib/index.js"`, `types: "lib/types/index.d.ts"`, `exports["."].types: "./lib/types/index.d.ts"`, `exports["."].default: "./lib/index.js"`, `@deepseek-ai/cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `@deepseek-ai/schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop. The `files` list contains exactly `lib/index.js`, `lib/types/**/*.d.ts`, and package-specific runtime artifacts recognized by the gate. A package whose runtime export points into the emitted tree also includes `lib/types/**/*.js`. Do not publish `src`, declaration maps, JS maps, or stale root declaration files. CLI app packages with a package `bin` include `lib/bin.js` immediately after `lib/index.js` in `files`. In-package relative imports use explicit `.ts` specifiers in source (for example, `export * from './types.ts'`). The compiler rewrites those to `.js` in emitted JS and leaves explicit `.ts` specifiers in declarations, which standard NodeNext/Node16 TypeScript consumers resolve to the sibling `.d.ts` files. diff --git a/docs/cookbook/adding-a-package.zh.md b/docs/cookbook/adding-a-package.zh.md index 44a613bcf0..edc57648a0 100644 --- a/docs/cookbook/adding-a-package.zh.md +++ b/docs/cookbook/adding-a-package.zh.md @@ -24,7 +24,7 @@ packages/// 当已有分组与包的角色匹配时,选择该分组(`core`、`llm`、`shell`、`compaction`、`subagent`、`todo`、`session`、`client`/`host`、`util` 或 `test-support`)。允许新建分组,但分组只是纯容器:没有 `package.json`,没有源文件,包仍然恰好位于其下一层。 -package.json 不变式(由 `pnpm run constraints` / `scripts/check-workspace-constraints.ts` 强制执行):`private: true`,`version` 与根 `package.json` 一致,`type: module`,`main: "lib/index.js"`,`types: "lib/types/index.d.ts"`,`exports["."].types: "./lib/types/index.d.ts"`,`exports["."].default: "./lib/index.js"`,`@deepseek-ai/cordis` 同时出现在 peerDependencies 和 devDependencies 中(相同范围)。每个 dsh 对等依赖(peer dependency)都要在 devDependencies 中镜像。`@deepseek-ai/schemastery` 放在 `dependencies` 中(它是运行时校验器),与 agent-loop 保持一致。`files` 列表精确包含 `lib/index.js`、`lib/types/**/*.d.ts` 以及门禁认可的包专用运行时产物;发布 `./invariant` 的包还要包含 `lib/invariant.js`。如果包的运行时 export 指向输出树,还要包含 `lib/types/**/*.js`。不要发布 `src`、声明映射、JS map 或陈旧的根声明文件。带有 `bin` 的 CLI 应用包在 `files` 中将 `lib/bin.js` 紧跟在 `lib/index.js` 之后。 +package.json 不变式(由 `pnpm run constraints` / `scripts/check-workspace-constraints.ts` 强制执行):`private: true`,`version` 与根 `package.json` 一致,`type: module`,`main: "lib/index.js"`,`types: "lib/types/index.d.ts"`,`exports["."].types: "./lib/types/index.d.ts"`,`exports["."].default: "./lib/index.js"`,`@deepseek-ai/cordis` 同时出现在 peerDependencies 和 devDependencies 中(相同范围)。每个 dsh 对等依赖(peer dependency)都要在 devDependencies 中镜像。`@deepseek-ai/schemastery` 放在 `dependencies` 中(它是运行时校验器),与 agent-loop 保持一致。`files` 列表精确包含 `lib/index.js`、`lib/types/**/*.d.ts` 以及门禁认可的包专用运行时产物。如果包的运行时 export 指向输出树,还要包含 `lib/types/**/*.js`。不要发布 `src`、声明映射、JS map 或陈旧的根声明文件。带有 `bin` 的 CLI 应用包在 `files` 中将 `lib/bin.js` 紧跟在 `lib/index.js` 之后。 包内的相对导入在源码中使用显式 `.ts` 后缀(例如 `export * from './types.ts'`)。编译器在输出的 JS 中将其重写为 `.js`,在声明文件中保留显式 `.ts` 后缀;标准的 NodeNext/Node16 TypeScript 消费方会将其解析到同目录的 `.d.ts` 文件。 diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 3e2d548aee..4d28e03940 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -17,10 +17,10 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | | `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:307`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | | `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:288`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:320`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:320`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | | `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:337`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`webhook`](../packages/webhook/webhook) | | `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:353`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | | `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:381`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:611`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:591`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | @@ -29,7 +29,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:604`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:52`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) | | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` | -| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization) | +| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - | | `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:89`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` | | `compaction/summary-error` | `waterfall` | [`packages/compaction/compaction/src/index.ts:106`](../packages/compaction/compaction/src/index.ts) | [`compaction-basic`](../packages/compaction/compaction-basic) (`waterfall`) | [`compaction-image-offload`](../packages/compaction/compaction-image-offload) | | `connection/request` | `waterfall` | [`packages/client/connection/src/index.ts:67`](../packages/client/connection/src/index.ts) | `connection` (`waterfall`) | - | @@ -39,12 +39,12 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `cordis/inspect-query-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:401`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | | `cordis/request-run` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:371`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | | `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:377`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | -| `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:102`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization), [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform), `remotes` | -| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:90`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`credentials`](../packages/credentials/credentials), `remotes` | +| `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:99`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization), [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform), `remotes` | +| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:87`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `remotes` | | `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:90`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` | | `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:86`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` | | `deepseek-account/signed-out` | `emit` | [`packages/credentials/deepseek-account/src/index.ts:12`](../packages/credentials/deepseek-account/src/index.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | [`deepseek-account`](../packages/credentials/deepseek-account) | -| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace), `workspace-controller` | +| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `workspace-controller` | | `feedback/committed` | `parallel` | [`packages/feedback/message-feedback/src/index.ts:58`](../packages/feedback/message-feedback/src/index.ts) | [`message-feedback`](../packages/feedback/message-feedback) (`parallel`) | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | | `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:67`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | | `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:77`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`emit`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`skill-filesystem`](../packages/skill/skill-filesystem), `workspace-files` | @@ -53,25 +53,25 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `goal/changed` | `emit` | [`packages/goal/goal/src/domain.ts:114`](../packages/goal/goal/src/domain.ts) | [`goal`](../packages/goal/goal) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) | | `hmr/change` | `emit` | [`packages/boot/hmr/src/index.ts:30`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - | | `hmr/reload` | `emit` | [`packages/boot/hmr/src/index.ts:35`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - | -| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`llm`](../packages/llm/llm), `remotes` | -| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:75`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) | +| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), `remotes` | +| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:75`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) | | `permission-presets/catalog-changed` | `emit` | [`packages/interaction/permission-presets/src/types.ts:48`](../packages/interaction/permission-presets/src/types.ts) | [`permission-presets`](../packages/interaction/permission-presets) (`events.dispatch`) | `remotes` | | `plugin-manager/changed` | `emit` | [`packages/boot/plugin-manager/src/types.ts:248`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | | `plugin-manager/install-log` | `emit` | [`packages/boot/plugin-manager/src/types.ts:254`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | | `plugin-manager/install-state` | `emit` | [`packages/boot/plugin-manager/src/types.ts:261`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | | `schedule/changed` | `emit` | [`packages/schedule/schedule/src/types.ts:471`](../packages/schedule/schedule/src/types.ts) | [`schedule`](../packages/schedule/schedule) (`emit`) | `remotes` | | `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:44`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - | -| `session/created` | `emit` | [`packages/core/session/src/index.ts:55`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), `session-controller`, [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | -| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:65`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `file-upload`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:77`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `product-analytics`, `server`, [`session`](../packages/core/session), `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`user-questions`](../packages/interaction/user-questions), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:86`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-telemetry`](../packages/session/session-telemetry) | +| `session/created` | `emit` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`permission-presets`](../packages/interaction/permission-presets), [`schedule`](../packages/schedule/schedule), `server`, `session-controller`, [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry) | +| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:63`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `file-upload`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:74`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`loader-smoke`](../packages/test-support/loader-smoke), `product-analytics`, `server`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`user-questions`](../packages/interaction/user-questions), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:82`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-telemetry`](../packages/session/session-telemetry) | | `settings/document-updated` | `emit` | [`packages/settings/settings/src/types.ts:75`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`emit`) | `remotes` | | `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:296`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | - | -| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:174`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), `server`, [`subagent`](../packages/subagent/subagent) | -| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:148`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) | -| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:154`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) | -| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:165`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`subagent`](../packages/subagent/subagent) | -| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference), [`system-prompt`](../packages/core/system-prompt) | +| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:172`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), `server` | +| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:148`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) | +| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:154`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`tool-subagent`](../packages/subagent/tool-subagent) | +| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:164`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | +| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference) | | `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:37`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - | | `tools/change` | `emit` | [`packages/core/tools/src/index.ts:208`](../packages/core/tools/src/index.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`), [`tools`](../packages/core/tools) (`emit`) | `browser-use-runtime`, [`tool-subagent`](../packages/subagent/tool-subagent) | | `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:164`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `browser-use-runtime`, `browser-use-stagehand-native`, `computer-use-cua-driver-native`, [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `timeout-policy` | @@ -81,12 +81,12 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `tools/result` | `emit` | [`packages/core/tools/src/index.ts:198`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`tool-present`](../packages/deliverables/tool-present) | | `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:157`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes` | | `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:34`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` | -| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) | -| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) | -| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:89`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) | +| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | +| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | +| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:89`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - | | `workflow/log` | `emit` | [`packages/workflow/workflow/src/index.ts:58`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | | `workflow/phase` | `emit` | [`packages/workflow/workflow/src/index.ts:51`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | -| `workflow/start` | `emit` | [`packages/workflow/workflow/src/index.ts:43`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) | +| `workflow/start` | `emit` | [`packages/workflow/workflow/src/index.ts:43`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - | | `workspace/session-activity` | `waterfall` | [`packages/workspace/workspace/src/index.ts:129`](../packages/workspace/workspace/src/index.ts) | [`workspace`](../packages/workspace/workspace) (`waterfall`) | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`schedule`](../packages/schedule/schedule), [`subagent`](../packages/subagent/subagent) | | `workspace/session-stop` | `parallel` | [`packages/workspace/workspace/src/index.ts:147`](../packages/workspace/workspace/src/index.ts) | [`workspace`](../packages/workspace/workspace) (`parallel`) | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`schedule`](../packages/schedule/schedule), [`subagent`](../packages/subagent/subagent) | @@ -97,9 +97,9 @@ This matrix shows which packages dispatch each harness-owned event and which pac | Event string | Dispatchers | Listeners | | --- | --- | --- | | `internal/config` | [`config-editor`](../packages/boot/config-editor) (`waterfall`) | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | -| `internal/dispatch` | - | `agent-team`, [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), `ui-renderer`, [`user-approval`](../packages/interaction/user-approval), [`webhook`](../packages/webhook/webhook), [`workflow`](../packages/workflow/workflow) | +| `internal/dispatch` | - | [`terminal-bash`](../packages/terminal/terminal-bash) | | `internal/plugin` | - | `computer-use-cua-driver-native`, `inspector`, `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), [`mcp-client`](../packages/mcp/mcp-client), `modules` | -| `internal/service` | - | [`agent-preset-registry`](../packages/preset/agent-preset-registry), `gateway` | +| `internal/service` | - | `gateway` | | `internal/status` | - | [`agent`](../packages/core/agent), `inspector`, [`web`](../packages/web/web) | | `internal/update` | - | [`app-boot`](../packages/boot/app-boot) | | `loader/volatile-update` | - | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `product-analytics`, `speech-to-text` | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index 9cd0d380ea..01037b336c 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -19,10 +19,10 @@ | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | | `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:307`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | | `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:288`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:320`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:320`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | | `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:337`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`webhook`](../packages/webhook/webhook) | | `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:353`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | | `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:381`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:611`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:591`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | @@ -31,7 +31,7 @@ | `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:604`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:52`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) | | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` | -| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization) | +| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - | | `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:89`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` | | `compaction/summary-error` | `waterfall` | [`packages/compaction/compaction/src/index.ts:106`](../packages/compaction/compaction/src/index.ts) | [`compaction-basic`](../packages/compaction/compaction-basic) (`waterfall`) | [`compaction-image-offload`](../packages/compaction/compaction-image-offload) | | `connection/request` | `waterfall` | [`packages/client/connection/src/index.ts:67`](../packages/client/connection/src/index.ts) | `connection` (`waterfall`) | - | @@ -41,12 +41,12 @@ | `cordis/inspect-query-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:401`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | | `cordis/request-run` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:371`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | | `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:377`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | -| `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:102`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization), [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform), `remotes` | -| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:90`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`credentials`](../packages/credentials/credentials), `remotes` | +| `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:99`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization), [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform), `remotes` | +| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:87`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `remotes` | | `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:90`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` | | `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:86`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` | | `deepseek-account/signed-out` | `emit` | [`packages/credentials/deepseek-account/src/index.ts:12`](../packages/credentials/deepseek-account/src/index.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | [`deepseek-account`](../packages/credentials/deepseek-account) | -| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace), `workspace-controller` | +| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `workspace-controller` | | `feedback/committed` | `parallel` | [`packages/feedback/message-feedback/src/index.ts:58`](../packages/feedback/message-feedback/src/index.ts) | [`message-feedback`](../packages/feedback/message-feedback) (`parallel`) | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | | `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:67`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | | `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:77`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`emit`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`skill-filesystem`](../packages/skill/skill-filesystem), `workspace-files` | @@ -55,25 +55,25 @@ | `goal/changed` | `emit` | [`packages/goal/goal/src/domain.ts:114`](../packages/goal/goal/src/domain.ts) | [`goal`](../packages/goal/goal) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) | | `hmr/change` | `emit` | [`packages/boot/hmr/src/index.ts:30`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - | | `hmr/reload` | `emit` | [`packages/boot/hmr/src/index.ts:35`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - | -| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`llm`](../packages/llm/llm), `remotes` | -| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:75`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) | +| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), `remotes` | +| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:75`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) | | `permission-presets/catalog-changed` | `emit` | [`packages/interaction/permission-presets/src/types.ts:48`](../packages/interaction/permission-presets/src/types.ts) | [`permission-presets`](../packages/interaction/permission-presets) (`events.dispatch`) | `remotes` | | `plugin-manager/changed` | `emit` | [`packages/boot/plugin-manager/src/types.ts:248`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | | `plugin-manager/install-log` | `emit` | [`packages/boot/plugin-manager/src/types.ts:254`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | | `plugin-manager/install-state` | `emit` | [`packages/boot/plugin-manager/src/types.ts:261`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | | `schedule/changed` | `emit` | [`packages/schedule/schedule/src/types.ts:471`](../packages/schedule/schedule/src/types.ts) | [`schedule`](../packages/schedule/schedule) (`emit`) | `remotes` | | `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:44`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - | -| `session/created` | `emit` | [`packages/core/session/src/index.ts:55`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), `session-controller`, [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | -| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:65`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `file-upload`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:77`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `product-analytics`, `server`, [`session`](../packages/core/session), `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`user-questions`](../packages/interaction/user-questions), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:86`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-telemetry`](../packages/session/session-telemetry) | +| `session/created` | `emit` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`permission-presets`](../packages/interaction/permission-presets), [`schedule`](../packages/schedule/schedule), `server`, `session-controller`, [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry) | +| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:63`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `file-upload`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:74`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`loader-smoke`](../packages/test-support/loader-smoke), `product-analytics`, `server`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`user-questions`](../packages/interaction/user-questions), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:82`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-telemetry`](../packages/session/session-telemetry) | | `settings/document-updated` | `emit` | [`packages/settings/settings/src/types.ts:75`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`emit`) | `remotes` | | `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:296`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | - | -| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:174`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), `server`, [`subagent`](../packages/subagent/subagent) | -| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:148`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) | -| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:154`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) | -| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:165`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`subagent`](../packages/subagent/subagent) | -| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference), [`system-prompt`](../packages/core/system-prompt) | +| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:172`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), `server` | +| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:148`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) | +| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:154`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`tool-subagent`](../packages/subagent/tool-subagent) | +| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:164`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | +| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference) | | `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:37`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - | | `tools/change` | `emit` | [`packages/core/tools/src/index.ts:208`](../packages/core/tools/src/index.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`), [`tools`](../packages/core/tools) (`emit`) | `browser-use-runtime`, [`tool-subagent`](../packages/subagent/tool-subagent) | | `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:164`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `browser-use-runtime`, `browser-use-stagehand-native`, `computer-use-cua-driver-native`, [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `timeout-policy` | @@ -83,12 +83,12 @@ | `tools/result` | `emit` | [`packages/core/tools/src/index.ts:198`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`tool-present`](../packages/deliverables/tool-present) | | `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:157`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes` | | `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:34`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` | -| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) | -| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) | -| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:89`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) | +| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | +| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | +| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:89`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - | | `workflow/log` | `emit` | [`packages/workflow/workflow/src/index.ts:58`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | | `workflow/phase` | `emit` | [`packages/workflow/workflow/src/index.ts:51`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | -| `workflow/start` | `emit` | [`packages/workflow/workflow/src/index.ts:43`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) | +| `workflow/start` | `emit` | [`packages/workflow/workflow/src/index.ts:43`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - | | `workspace/session-activity` | `waterfall` | [`packages/workspace/workspace/src/index.ts:129`](../packages/workspace/workspace/src/index.ts) | [`workspace`](../packages/workspace/workspace) (`waterfall`) | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`schedule`](../packages/schedule/schedule), [`subagent`](../packages/subagent/subagent) | | `workspace/session-stop` | `parallel` | [`packages/workspace/workspace/src/index.ts:147`](../packages/workspace/workspace/src/index.ts) | [`workspace`](../packages/workspace/workspace) (`parallel`) | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`schedule`](../packages/schedule/schedule), [`subagent`](../packages/subagent/subagent) | @@ -99,9 +99,9 @@ | Event string | Dispatchers | Listeners | | --- | --- | --- | | `internal/config` | [`config-editor`](../packages/boot/config-editor) (`waterfall`) | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | -| `internal/dispatch` | - | `agent-team`, [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), `ui-renderer`, [`user-approval`](../packages/interaction/user-approval), [`webhook`](../packages/webhook/webhook), [`workflow`](../packages/workflow/workflow) | +| `internal/dispatch` | - | [`terminal-bash`](../packages/terminal/terminal-bash) | | `internal/plugin` | - | `computer-use-cua-driver-native`, `inspector`, `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), [`mcp-client`](../packages/mcp/mcp-client), `modules` | -| `internal/service` | - | [`agent-preset-registry`](../packages/preset/agent-preset-registry), `gateway` | +| `internal/service` | - | `gateway` | | `internal/status` | - | [`agent`](../packages/core/agent), `inspector`, [`web`](../packages/web/web) | | `internal/update` | - | [`app-boot`](../packages/boot/app-boot) | | `loader/volatile-update` | - | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `product-analytics`, `speech-to-text` | diff --git a/docs/module-graph.md b/docs/module-graph.md index 5147fbcba0..ae7daeea8e 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -326,9 +326,6 @@ flowchart TD pkg_ptc_runtime["ptc-runtime"] pkg_ptc_runtime_node["ptc-runtime-node"] end - subgraph group_runtime_diagnostics["packages/runtime-diagnostics"] - pkg_invariants["invariants"] - end subgraph group_sandbox["packages/sandbox"] pkg_sandbox["sandbox"] pkg_sandbox_local["sandbox-local"] @@ -433,12 +430,24 @@ flowchart TD subgraph group_workspace["packages/workspace"] pkg_workspace["workspace"] end - pkg_scope --> pkg_invariants + pkg_session --> pkg_scope + pkg_system_prompt --> pkg_llm + pkg_system_prompt --> pkg_scope + pkg_skill --> pkg_llm + pkg_skill --> pkg_scope pkg_web --> pkg_llm + pkg_api_remotes --> pkg_scope pkg_attachment --> pkg_brand pkg_browser_use --> pkg_brand + pkg_client_connection --> pkg_scope + pkg_client_file_upload --> pkg_scope pkg_computer_use --> pkg_brand - pkg_credentials --> pkg_invariants + pkg_authorization --> pkg_credentials + pkg_authorization --> pkg_llm + pkg_credentials_local --> pkg_atomic_write + pkg_credentials_local --> pkg_credentials + pkg_credentials_local --> pkg_home_paths + pkg_credentials_local --> pkg_launch_environment pkg_experimental_inspector --> pkg_client_modules pkg_experimental_inspector --> pkg_host_webserver pkg_host_directory_picker_auto --> pkg_client_ui_directory_picker_browse @@ -450,19 +459,14 @@ flowchart TD pkg_anonymous_user_id --> pkg_home_paths pkg_lsp --> pkg_brand pkg_lsp --> pkg_llm - pkg_storage_domain --> pkg_invariants pkg_storage_domain --> pkg_storage pkg_storage_json --> pkg_storage pkg_storage_sqlite --> pkg_storage pkg_subprocess --> pkg_http_proxy pkg_remote_mock --> pkg_typert_protocol pkg_typert_loader --> pkg_typert_registry - pkg_session --> pkg_scope - pkg_system_prompt --> pkg_invariants - pkg_system_prompt --> pkg_llm - pkg_system_prompt --> pkg_scope - pkg_skill --> pkg_llm - pkg_skill --> pkg_scope + pkg_skill_badge --> pkg_skill + pkg_skill_office --> pkg_skill pkg_web_fetch_http --> pkg_http_proxy pkg_web_fetch_http --> pkg_timeout pkg_web_fetch_http --> pkg_web @@ -470,31 +474,17 @@ flowchart TD pkg_web_search_exa --> pkg_web pkg_web_search_perplexity --> pkg_launch_environment pkg_web_search_perplexity --> pkg_web - pkg_api_remotes --> pkg_scope - pkg_api_terminal_controller --> pkg_subprocess - pkg_attachment_local --> pkg_attachment - pkg_attachment_local --> pkg_home_paths - pkg_client_connection --> pkg_scope - pkg_client_file_upload --> pkg_scope - pkg_authorization --> pkg_credentials - pkg_authorization --> pkg_invariants - pkg_authorization --> pkg_llm - pkg_credentials_local --> pkg_atomic_write - pkg_credentials_local --> pkg_credentials - pkg_credentials_local --> pkg_home_paths - pkg_credentials_local --> pkg_launch_environment - pkg_experimental_computer_use_cua_driver_mcp --> pkg_computer_use - pkg_subprocess_local --> pkg_subprocess - pkg_subprocess_local --> pkg_timeout - pkg_skill_badge --> pkg_skill - pkg_skill_office --> pkg_skill pkg_spill --> pkg_brand pkg_spill --> pkg_llm pkg_spill --> pkg_session pkg_api_gateway --> pkg_client_connection + pkg_api_terminal_controller --> pkg_subprocess + pkg_attachment_local --> pkg_attachment + pkg_attachment_local --> pkg_home_paths pkg_app_boot --> pkg_home_paths pkg_app_boot --> pkg_launch_environment pkg_app_boot --> pkg_system_prompt + pkg_experimental_computer_use_cua_driver_mcp --> pkg_computer_use pkg_experimental_webworker_runtime --> pkg_client_connection pkg_experimental_webworker_runtime --> pkg_client_modules pkg_experimental_webworker_runtime --> pkg_host_webserver @@ -507,20 +497,19 @@ flowchart TD pkg_sandbox_windows_acl --> pkg_subprocess pkg_session_format_catalog --> pkg_session pkg_session_log_deepseek --> pkg_deepseek_llm_api_extensions - pkg_session_log_deepseek --> pkg_invariants pkg_session_log_deepseek --> pkg_session pkg_session_persistence --> pkg_brand pkg_session_persistence --> pkg_session pkg_session_persistence --> pkg_timeout pkg_session_projection --> pkg_session pkg_settings --> pkg_brand - pkg_settings --> pkg_invariants pkg_settings --> pkg_session + pkg_subprocess_local --> pkg_subprocess + pkg_subprocess_local --> pkg_timeout pkg_otel --> pkg_session pkg_session_snapshot --> pkg_http_proxy pkg_session_snapshot --> pkg_session pkg_fs --> pkg_brand - pkg_fs --> pkg_invariants pkg_fs --> pkg_llm pkg_fs --> pkg_sandbox pkg_spill_local --> pkg_spill @@ -547,7 +536,6 @@ flowchart TD pkg_session_turn_outline --> pkg_session_projection pkg_shell --> pkg_sandbox pkg_shell --> pkg_subprocess - pkg_workspace --> pkg_invariants pkg_workspace --> pkg_session pkg_workspace --> pkg_session_persistence pkg_workspace --> pkg_storage @@ -569,7 +557,6 @@ flowchart TD pkg_llm_pi_ai --> pkg_launch_environment pkg_llm_pi_ai --> pkg_llm pkg_llm_pi_ai --> pkg_timeout - pkg_agent --> pkg_invariants pkg_agent --> pkg_llm pkg_agent --> pkg_scope pkg_agent --> pkg_session @@ -583,7 +570,6 @@ flowchart TD pkg_skill_filesystem --> pkg_fs pkg_skill_filesystem --> pkg_home_paths pkg_skill_filesystem --> pkg_skill - pkg_hook_protocol --> pkg_invariants pkg_hook_protocol --> pkg_session pkg_hook_protocol --> pkg_shell pkg_api_workspace_controller --> pkg_api_gateway @@ -620,7 +606,6 @@ flowchart TD pkg_llm_deepseek_api_key --> pkg_llm_deepseek pkg_llm_retry --> pkg_agent pkg_llm_retry --> pkg_brand - pkg_llm_retry --> pkg_invariants pkg_llm_retry --> pkg_llm pkg_llm_retry --> pkg_session pkg_llm_retry --> pkg_session_projection @@ -629,7 +614,6 @@ flowchart TD pkg_agent_default_model --> pkg_llm pkg_goal --> pkg_agent pkg_goal --> pkg_brand - pkg_goal --> pkg_invariants pkg_goal --> pkg_llm pkg_goal --> pkg_scope pkg_goal --> pkg_session @@ -637,7 +621,6 @@ flowchart TD pkg_goal --> pkg_typert_protocol pkg_file_reference --> pkg_agent pkg_time_context --> pkg_agent - pkg_time_context --> pkg_invariants pkg_time_context --> pkg_llm pkg_time_context --> pkg_session pkg_time_context --> pkg_session_projection @@ -651,14 +634,12 @@ flowchart TD pkg_commands --> pkg_agent pkg_commands --> pkg_attachment pkg_commands --> pkg_brand - pkg_commands --> pkg_invariants pkg_commands --> pkg_llm pkg_commands --> pkg_scope pkg_commands --> pkg_session pkg_commands --> pkg_typert_protocol 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 @@ -671,11 +652,9 @@ flowchart TD pkg_user_questions --> pkg_typert_protocol pkg_jobs --> pkg_agent pkg_jobs --> pkg_brand - pkg_jobs --> pkg_invariants pkg_jobs --> pkg_session pkg_jobs --> pkg_workspace pkg_sandbox_policy --> pkg_agent - pkg_sandbox_policy --> pkg_invariants pkg_sandbox_policy --> pkg_sandbox pkg_sandbox_policy --> pkg_session pkg_sandbox_policy --> pkg_session_projection @@ -684,7 +663,6 @@ flowchart TD pkg_session_telemetry --> pkg_session pkg_session_title --> pkg_agent pkg_session_title --> pkg_brand - pkg_session_title --> pkg_invariants pkg_session_title --> pkg_llm pkg_session_title --> pkg_session pkg_session_title --> pkg_session_projection @@ -696,7 +674,6 @@ flowchart TD pkg_loader_smoke --> pkg_session pkg_workflow --> pkg_agent pkg_workflow --> pkg_brand - pkg_workflow --> pkg_invariants pkg_workflow --> pkg_llm pkg_workflow --> pkg_session pkg_llm_deepseek_account --> pkg_deepseek_account @@ -704,7 +681,6 @@ flowchart TD pkg_llm_deepseek_account --> pkg_llm pkg_llm_deepseek_account --> pkg_llm_deepseek pkg_tools --> pkg_agent - pkg_tools --> pkg_invariants pkg_tools --> pkg_llm pkg_tools --> pkg_ptc_runtime pkg_tools --> pkg_sandbox @@ -718,7 +694,6 @@ flowchart TD pkg_command_goal --> pkg_llm pkg_goal_round_driver --> pkg_agent pkg_goal_round_driver --> pkg_goal - pkg_goal_round_driver --> pkg_invariants pkg_goal_round_driver --> pkg_llm pkg_goal_round_driver --> pkg_session pkg_fs_sandbox --> pkg_fs @@ -736,7 +711,6 @@ flowchart TD pkg_api_account_controller --> pkg_typert_protocol pkg_compaction --> pkg_brand pkg_compaction --> pkg_commands - pkg_compaction --> pkg_invariants pkg_compaction --> pkg_llm pkg_compaction --> pkg_session pkg_deepseek_account_platform --> pkg_authorization @@ -748,7 +722,6 @@ flowchart TD pkg_command_feedback --> pkg_session pkg_command_feedback --> pkg_typert_protocol pkg_permission_presets --> pkg_commands - pkg_permission_presets --> pkg_invariants pkg_permission_presets --> pkg_sandbox pkg_permission_presets --> pkg_sandbox_policy pkg_permission_presets --> pkg_session @@ -787,7 +760,6 @@ flowchart TD pkg_terminal_bash --> pkg_subprocess pkg_terminal_bash --> pkg_terminal pkg_agent_loop --> pkg_agent - pkg_agent_loop --> pkg_invariants pkg_agent_loop --> pkg_llm pkg_agent_loop --> pkg_scope pkg_agent_loop --> pkg_session @@ -835,13 +807,11 @@ flowchart TD pkg_tool_web --> pkg_tools pkg_tool_web --> pkg_web pkg_tool_todo --> pkg_agent - pkg_tool_todo --> pkg_invariants pkg_tool_todo --> pkg_session pkg_tool_todo --> pkg_session_projection pkg_tool_todo --> pkg_tools pkg_plan_mode --> pkg_agent pkg_plan_mode --> pkg_commands - pkg_plan_mode --> pkg_invariants pkg_plan_mode --> pkg_llm pkg_plan_mode --> pkg_session pkg_plan_mode --> pkg_session_projection @@ -933,7 +903,6 @@ flowchart TD pkg_mcp_resources --> pkg_system_prompt pkg_mcp_resources --> pkg_tools pkg_agent_preset_registry --> pkg_agent - pkg_agent_preset_registry --> pkg_invariants pkg_agent_preset_registry --> pkg_scope pkg_agent_preset_registry --> pkg_session pkg_agent_preset_registry --> pkg_session_projection @@ -990,7 +959,6 @@ flowchart TD pkg_llm_replay --> pkg_llm pkg_llm_replay --> pkg_session pkg_tool_workflow --> pkg_agent - pkg_tool_workflow --> pkg_invariants pkg_tool_workflow --> pkg_jobs pkg_tool_workflow --> pkg_llm pkg_tool_workflow --> pkg_session @@ -1091,7 +1059,6 @@ flowchart TD pkg_webhook --> pkg_agent pkg_webhook --> pkg_agent_default_model pkg_webhook --> pkg_agent_preset_registry - pkg_webhook --> pkg_invariants pkg_webhook --> pkg_llm pkg_webhook --> pkg_permission_presets pkg_webhook --> pkg_session @@ -1100,7 +1067,6 @@ flowchart TD pkg_subagent --> pkg_agent pkg_subagent --> pkg_agent_preset_registry pkg_subagent --> pkg_attachment - pkg_subagent --> pkg_invariants pkg_subagent --> pkg_jobs pkg_subagent --> pkg_llm pkg_subagent --> pkg_permission_presets @@ -1196,7 +1162,6 @@ flowchart TD pkg_subagent_in_process_driver --> pkg_system_prompt pkg_subagent_in_process_driver --> pkg_tools pkg_tool_subagent --> pkg_agent - pkg_tool_subagent --> pkg_invariants pkg_tool_subagent --> pkg_jobs pkg_tool_subagent --> pkg_llm pkg_tool_subagent --> pkg_scope @@ -1266,7 +1231,6 @@ flowchart TD pkg_session_reference --> pkg_system_prompt pkg_session_reference --> pkg_typert_protocol pkg_experimental_agent_team --> pkg_agent - pkg_experimental_agent_team --> pkg_invariants pkg_experimental_agent_team --> pkg_llm pkg_experimental_agent_team --> pkg_session pkg_experimental_agent_team --> pkg_session_persistence @@ -1329,7 +1293,6 @@ flowchart TD pkg_schedule --> pkg_agent pkg_schedule --> pkg_api_session_controller pkg_schedule --> pkg_brand - pkg_schedule --> pkg_invariants pkg_schedule --> pkg_llm pkg_schedule --> pkg_session pkg_schedule --> pkg_session_persistence @@ -1398,6 +1361,7 @@ flowchart TD | [`util-workspace-path`](../packages/util/workspace-path) | `util` | — | | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | `llm` | — | | [`llm`](../packages/llm/llm) | `llm` | — | +| [`scope`](../packages/core/scope) | `core` | — | | [`api-job-controller`](../packages/api/job-controller) | `api` | — | | [`api-workspace-files`](../packages/api/workspace-files) | `api` | — | | [`cmdline`](../packages/boot/cmdline) | `boot` | — | @@ -1466,6 +1430,7 @@ flowchart TD | [`client-ui-workflow-run`](../packages/client/ui-workflow-run) | `client` | — | | [`client-ui-workspace`](../packages/client/ui-workspace) | `client` | — | | [`client-web`](../packages/client/web) | `client` | — | +| [`credentials`](../packages/credentials/credentials) | `credentials` | — | | [`office-to-pdf`](../packages/document/office-to-pdf) | `document` | — | | [`experimental-agent-team-profile`](../packages/experimental/agent-team-profile) | `experimental` | — | | [`experimental-schedule-bundle`](../packages/experimental/schedule-bundle) | `experimental` | — | @@ -1478,7 +1443,6 @@ flowchart TD | [`host-directory-picker-native`](../packages/host/directory-picker-native) | `host` | — | | [`host-open-in-app`](../packages/host/open-in-app) | `host` | — | | [`host-webserver`](../packages/host/webserver) | `host` | — | -| [`invariants`](../packages/runtime-diagnostics/invariants) | `runtime-diagnostics` | — | | [`session-format`](../packages/session/session-format) | `session` | — | | [`session-format-v0-to-v1`](../packages/session/session-format-v0-to-v1) | `session` | — | | [`session-format-v1-to-v2`](../packages/session/session-format-v1-to-v2) | `session` | — | @@ -1490,55 +1454,53 @@ flowchart TD | [`typert-generator`](../packages/typert/generator) | `typert` | — | | [`typert-protocol`](../packages/typert/protocol) | `typert` | — | | [`typert-registry`](../packages/typert/registry) | `typert` | — | -| [`scope`](../packages/core/scope) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants) | +| [`session`](../packages/core/session) | `core` | [`scope`](../packages/core/scope) | +| [`system-prompt`](../packages/core/system-prompt) | `core` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | +| [`skill`](../packages/skill/skill) | `skill` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | | [`web`](../packages/web/web) | `web` | [`llm`](../packages/llm/llm) | +| [`api-remotes`](../packages/api/remotes) | `api` | [`scope`](../packages/core/scope) | | [`attachment`](../packages/attachment/attachment) | `attachment` | [`brand`](../packages/util/brand) | | [`browser-use`](../packages/browser-use/browser-use) | `browser-use` | [`brand`](../packages/util/brand) | +| [`client-connection`](../packages/client/connection) | `client` | [`scope`](../packages/core/scope) | +| [`client-file-upload`](../packages/client/file-upload) | `client` | [`scope`](../packages/core/scope) | | [`computer-use`](../packages/computer-use/computer-use) | `computer-use` | [`brand`](../packages/util/brand) | -| [`credentials`](../packages/credentials/credentials) | `credentials` | [`invariants`](../packages/runtime-diagnostics/invariants) | +| [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`llm`](../packages/llm/llm) | +| [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment) | | [`experimental-inspector`](../packages/experimental/inspector) | `experimental` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver) | | [`anonymous-user-id`](../packages/identity/anonymous-user-id) | `identity` | [`brand`](../packages/util/brand), [`home-paths`](../packages/util/home-paths) | | [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | -| [`storage-domain`](../packages/storage/storage-domain) | `storage` | [`invariants`](../packages/runtime-diagnostics/invariants), [`storage`](../packages/storage/storage) | +| [`storage-domain`](../packages/storage/storage-domain) | `storage` | [`storage`](../packages/storage/storage) | | [`storage-json`](../packages/storage/storage-json) | `storage` | [`storage`](../packages/storage/storage) | | [`storage-sqlite`](../packages/storage/storage-sqlite) | `storage` | [`storage`](../packages/storage/storage) | | [`subprocess`](../packages/subprocess/subprocess) | `subprocess` | [`http-proxy`](../packages/util/http-proxy) | | [`remote-mock`](../packages/test-support/remote-mock) | `test-support` | [`typert-protocol`](../packages/typert/protocol) | | [`typert-loader`](../packages/typert/loader) | `typert` | [`typert-registry`](../packages/typert/registry) | -| [`session`](../packages/core/session) | `core` | [`scope`](../packages/core/scope) | -| [`system-prompt`](../packages/core/system-prompt) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | -| [`skill`](../packages/skill/skill) | `skill` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | +| [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`skill`](../packages/skill/skill) | +| [`skill-office`](../packages/skill/skill-office) | `skill` | [`skill`](../packages/skill/skill) | | [`web-fetch-http`](../packages/web/web-fetch-http) | `web` | [`http-proxy`](../packages/util/http-proxy), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) | | [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) | | [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) | -| [`api-remotes`](../packages/api/remotes) | `api` | [`scope`](../packages/core/scope) | -| [`api-terminal-controller`](../packages/api/terminal-controller) | `api` | [`subprocess`](../packages/subprocess/subprocess) | -| [`attachment-local`](../packages/attachment/attachment-local) | `attachment` | [`attachment`](../packages/attachment/attachment), [`home-paths`](../packages/util/home-paths) | -| [`client-connection`](../packages/client/connection) | `client` | [`scope`](../packages/core/scope) | -| [`client-file-upload`](../packages/client/file-upload) | `client` | [`scope`](../packages/core/scope) | -| [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) | -| [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment) | -| [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp) | `experimental` | [`computer-use`](../packages/computer-use/computer-use) | -| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | -| [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`skill`](../packages/skill/skill) | -| [`skill-office`](../packages/skill/skill-office) | `skill` | [`skill`](../packages/skill/skill) | | [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`api-gateway`](../packages/api/gateway) | `api` | [`client-connection`](../packages/client/connection) | +| [`api-terminal-controller`](../packages/api/terminal-controller) | `api` | [`subprocess`](../packages/subprocess/subprocess) | +| [`attachment-local`](../packages/attachment/attachment-local) | `attachment` | [`attachment`](../packages/attachment/attachment), [`home-paths`](../packages/util/home-paths) | | [`app-boot`](../packages/boot/app-boot) | `boot` | [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment), [`system-prompt`](../packages/core/system-prompt) | +| [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp) | `experimental` | [`computer-use`](../packages/computer-use/computer-use) | | [`experimental-webworker-runtime`](../packages/experimental/webworker-runtime) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | | [`host-frontend-static`](../packages/host/frontend-static) | `host` | [`client-connection`](../packages/client/connection), [`host-webserver`](../packages/host/webserver) | | [`persona`](../packages/preset/persona) | `preset` | [`system-prompt`](../packages/core/system-prompt) | | [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`sandbox-windows-acl`](../packages/sandbox/sandbox-windows-acl) | `sandbox` | [`skill`](../packages/skill/skill), [`subprocess`](../packages/subprocess/subprocess) | | [`session-format-catalog`](../packages/session/session-format-catalog) | `session` | [`session`](../packages/core/session) | -| [`session-log-deepseek`](../packages/session/session-log-deepseek) | `session` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) | +| [`session-log-deepseek`](../packages/session/session-log-deepseek) | `session` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) | | [`session-persistence`](../packages/session/session-persistence) | `session` | [`brand`](../packages/util/brand), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | | [`session-projection`](../packages/session/session-projection) | `session` | [`session`](../packages/core/session) | -| [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) | +| [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`session`](../packages/core/session) | +| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`otel`](../packages/telemetry/otel) | `telemetry` | [`session`](../packages/core/session) | | [`session-snapshot`](../packages/test-support/session-snapshot) | `test-support` | [`http-proxy`](../packages/util/http-proxy), [`session`](../packages/core/session) | -| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | +| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | | [`spill-local`](../packages/spill/spill-local) | `spill` | [`spill`](../packages/spill/spill) | | [`session-log-export`](../packages/session-query/session-log-export) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) | | [`hmr`](../packages/boot/hmr) | `boot` | [`app-boot`](../packages/boot/app-boot), [`cmdline`](../packages/boot/cmdline) | @@ -1551,14 +1513,14 @@ flowchart TD | [`session-stats`](../packages/session/session-stats) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`session-turn-outline`](../packages/session/session-turn-outline) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`shell`](../packages/shell/shell) | `shell` | [`sandbox`](../packages/sandbox/sandbox), [`subprocess`](../packages/subprocess/subprocess) | -| [`workspace`](../packages/workspace/workspace) | `workspace` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) | +| [`workspace`](../packages/workspace/workspace) | `workspace` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) | | [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`atomic-write`](../packages/util/atomic-write), [`attachment`](../packages/attachment/attachment), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) | | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`attachment`](../packages/attachment/attachment), [`authorization`](../packages/credentials/authorization), [`credentials`](../packages/credentials/credentials), [`fs`](../packages/fs/fs), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) | -| [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol), [`util-values`](../packages/util/values), [`workspace`](../packages/workspace/workspace) | +| [`agent`](../packages/core/agent) | `core` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol), [`util-values`](../packages/util/values), [`workspace`](../packages/workspace/workspace) | | [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs) | | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | `fs` | [`fs`](../packages/fs/fs) | | [`skill-filesystem`](../packages/skill/skill-filesystem) | `skill` | [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`skill`](../packages/skill/skill) | -| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) | +| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`session`](../packages/core/session), [`shell`](../packages/shell/shell) | | [`api-workspace-controller`](../packages/api/workspace-controller) | `api` | [`api-gateway`](../packages/api/gateway), [`client-connection`](../packages/client/connection), [`host-directory-picker`](../packages/host/directory-picker), [`session`](../packages/core/session), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) | | [`config-editor`](../packages/boot/config-editor) | `boot` | [`app-boot`](../packages/boot/app-boot), [`hmr`](../packages/boot/hmr) | | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | `experimental` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text), [`typert-protocol`](../packages/typert/protocol) | @@ -1568,41 +1530,41 @@ flowchart TD | [`bash-local`](../packages/shell/bash-local) | `shell` | [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`pwsh-local`](../packages/shell/pwsh-local) | `shell` | [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`llm-deepseek-api-key`](../packages/llm/llm-deepseek-api-key) | `llm` | [`credentials`](../packages/credentials/credentials), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek) | -| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`timeout`](../packages/util/timeout) | +| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`timeout`](../packages/util/timeout) | | [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm) | -| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | +| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | | [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent) | -| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | +| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell) | | [`deepseek-account`](../packages/credentials/deepseek-account) | `credentials` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | -| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | -| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | +| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | +| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`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-questions`](../packages/interaction/user-questions) | `interaction` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | -| [`jobs`](../packages/jobs/jobs) | `jobs` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`workspace`](../packages/workspace/workspace) | -| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) | +| [`jobs`](../packages/jobs/jobs) | `jobs` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`session`](../packages/core/session), [`workspace`](../packages/workspace/workspace) | +| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) | | [`session-telemetry`](../packages/session/session-telemetry) | `session` | [`agent`](../packages/core/agent), [`session`](../packages/core/session) | -| [`session-title`](../packages/session/session-title) | `session` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | +| [`session-title`](../packages/session/session-title) | `session` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`terminal`](../packages/terminal/terminal) | `terminal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand) | | [`loader-smoke`](../packages/test-support/loader-smoke) | `test-support` | [`agent`](../packages/core/agent), [`http-proxy`](../packages/util/http-proxy), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`llm-deepseek-account`](../packages/llm/llm-deepseek-account) | `llm` | [`deepseek-account`](../packages/credentials/deepseek-account), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek) | -| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) | +| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) | | [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm) | -| [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) | | [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`deepseek-account`](../packages/credentials/deepseek-account), [`launch-environment`](../packages/util/launch-environment), [`session`](../packages/core/session), [`web`](../packages/web/web) | | [`api-account-controller`](../packages/api/account-controller) | `api` | [`agent`](../packages/core/agent), [`deepseek-account`](../packages/credentials/deepseek-account), [`typert-protocol`](../packages/typert/protocol) | -| [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) | `credentials` | [`authorization`](../packages/credentials/authorization), [`credentials`](../packages/credentials/credentials), [`deepseek-account`](../packages/credentials/deepseek-account), [`host-webserver`](../packages/host/webserver) | | [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`commands`](../packages/interaction/commands), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | -| [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | +| [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | | [`jobs-local`](../packages/jobs/jobs-local) | `jobs` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`scope`](../packages/core/scope), [`timeout`](../packages/util/timeout) | | [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node) | `ptc-runtime` | [`fs`](../packages/fs/fs), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`session-title-llm`](../packages/session/session-title-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`timeout`](../packages/util/timeout) | | [`bash-sandbox`](../packages/shell/bash-sandbox) | `shell` | [`bash-local`](../packages/shell/bash-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) | | [`pwsh-sandbox`](../packages/shell/pwsh-sandbox) | `shell` | [`pwsh-local`](../packages/shell/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) | | [`terminal-bash`](../packages/terminal/terminal-bash) | `terminal` | [`agent`](../packages/core/agent), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`terminal`](../packages/terminal/terminal) | -| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`agent-tool-presentation`](../packages/core/agent-tool-presentation) | `core` | [`tools`](../packages/core/tools) | | [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | @@ -1611,8 +1573,8 @@ flowchart TD | [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) | | [`tool-workspace-dependencies`](../packages/skill/tool-workspace-dependencies) | `skill` | [`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) | -| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | -| [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) | +| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | +| [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) | | [`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-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | | [`command-compact`](../packages/compaction/command-compact) | `compaction` | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction) | | [`compaction-image-offload`](../packages/compaction/compaction-image-offload) | `compaction` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | @@ -1632,7 +1594,7 @@ flowchart TD | [`tool-jobs`](../packages/jobs/tool-jobs) | `jobs` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`mcp-resources`](../packages/mcp/mcp-resources) | `mcp` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`agent-preset-registry`](../packages/preset/agent-preset-registry) | `preset` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | +| [`agent-preset-registry`](../packages/preset/agent-preset-registry) | `preset` | [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) | | [`session-title-all-prompts-llm`](../packages/session/session-title-all-prompts-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | | [`session-title-first-prompt-llm`](../packages/session/session-title-first-prompt-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | @@ -1642,7 +1604,7 @@ flowchart TD | [`ssh`](../packages/ssh/ssh) | `ssh` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-local`](../packages/sandbox/sandbox-local), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) | | [`tool-terminal`](../packages/terminal/tool-terminal) | `terminal` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`terminal`](../packages/terminal/terminal), [`tools`](../packages/core/tools) | | [`llm-replay`](../packages/test-support/llm-replay) | `test-support` | [`compaction`](../packages/compaction/compaction), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | +| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | `llm` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) | | [`token-meter`](../packages/llm/token-meter) | `llm` | [`compaction`](../packages/compaction/compaction), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-title`](../packages/session/session-title), [`tool-todo`](../packages/todo/tool-todo) | @@ -1660,8 +1622,8 @@ flowchart TD | [`sandbox-ssh`](../packages/ssh/sandbox-ssh) | `ssh` | [`sandbox`](../packages/sandbox/sandbox), [`ssh`](../packages/ssh/ssh) | | [`subprocess-ssh`](../packages/ssh/subprocess-ssh) | `ssh` | [`ssh`](../packages/ssh/ssh), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) | | [`agent-loop-testkit`](../packages/test-support/agent-loop-testkit) | `test-support` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`webhook`](../packages/webhook/webhook) | `webhook` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) | -| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time), [`workspace`](../packages/workspace/workspace) | +| [`webhook`](../packages/webhook/webhook) | `webhook` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) | +| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time), [`workspace`](../packages/workspace/workspace) | | [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) | | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) | | [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | @@ -1675,13 +1637,13 @@ flowchart TD | [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-codex`](../packages/subagent/subagent-codex) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | `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), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`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), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-file-upload`](../packages/client/file-upload), [`commands`](../packages/interaction/commands), [`credentials`](../packages/credentials/credentials), [`file-reference`](../packages/context/file-reference), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`settings`](../packages/settings/settings), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-time`](../packages/util/time), [`util-values`](../packages/util/values), [`util-workspace-path`](../packages/util/workspace-path), [`workspace`](../packages/workspace/workspace) | | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`spill`](../packages/spill/spill), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) | -| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent) | +| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent) | | [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | | [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`workflow-ptc`](../packages/workflow/workflow-ptc) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | @@ -1690,7 +1652,7 @@ flowchart TD | [`experimental-client-ui-agent-team`](../packages/experimental/client-ui-agent-team) | `experimental` | [`api-session-controller`](../packages/api/session-controller), [`client-locale`](../packages/client/locale), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`experimental-agent-team`](../packages/experimental/agent-team), [`session`](../packages/core/session) | | [`experimental-client-ui-voice-input`](../packages/experimental/client-ui-voice-input) | `experimental` | [`api-gateway`](../packages/api/gateway), [`api-remotes`](../packages/api/remotes), [`api-session-controller`](../packages/api/session-controller), [`client-locale`](../packages/client/locale), [`client-store`](../packages/client/store), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-plugin-manager`](../packages/client/ui-plugin-manager), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-slots`](../packages/client/ui-slots), [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text), [`experimental-speech-to-text`](../packages/experimental/speech-to-text), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | `experimental` | [`agent`](../packages/core/agent), [`experimental-agent-team`](../packages/experimental/agent-team), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`api-session-controller`](../packages/api/session-controller), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) | +| [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`api-session-controller`](../packages/api/session-controller), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) | | [`sdk-client`](../packages/sdk/client) | `sdk` | [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session) | | [`sdk-jsonrpc-server`](../packages/sdk/server) | `sdk` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`llm-deepseek-api-key`](../packages/llm/llm-deepseek-api-key), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | | [`client-test-runtime`](../packages/test-support/client-runtime) | `test-support` | [`api-gateway`](../packages/api/gateway), [`api-session-controller`](../packages/api/session-controller), [`api-workspace-controller`](../packages/api/workspace-controller), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-hmr`](../packages/client/hmr), [`client-modules`](../packages/client/modules), [`client-store`](../packages/client/store), [`client-ui-chat`](../packages/client/ui-chat), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web`](../packages/client/web), [`remote-mock`](../packages/test-support/remote-mock), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index a1a4c081d1..9035d2ba96 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -328,9 +328,6 @@ flowchart TD pkg_ptc_runtime["ptc-runtime"] pkg_ptc_runtime_node["ptc-runtime-node"] end - subgraph group_runtime_diagnostics["packages/runtime-diagnostics"] - pkg_invariants["invariants"] - end subgraph group_sandbox["packages/sandbox"] pkg_sandbox["sandbox"] pkg_sandbox_local["sandbox-local"] @@ -435,12 +432,24 @@ flowchart TD subgraph group_workspace["packages/workspace"] pkg_workspace["workspace"] end - pkg_scope --> pkg_invariants + pkg_session --> pkg_scope + pkg_system_prompt --> pkg_llm + pkg_system_prompt --> pkg_scope + pkg_skill --> pkg_llm + pkg_skill --> pkg_scope pkg_web --> pkg_llm + pkg_api_remotes --> pkg_scope pkg_attachment --> pkg_brand pkg_browser_use --> pkg_brand + pkg_client_connection --> pkg_scope + pkg_client_file_upload --> pkg_scope pkg_computer_use --> pkg_brand - pkg_credentials --> pkg_invariants + pkg_authorization --> pkg_credentials + pkg_authorization --> pkg_llm + pkg_credentials_local --> pkg_atomic_write + pkg_credentials_local --> pkg_credentials + pkg_credentials_local --> pkg_home_paths + pkg_credentials_local --> pkg_launch_environment pkg_experimental_inspector --> pkg_client_modules pkg_experimental_inspector --> pkg_host_webserver pkg_host_directory_picker_auto --> pkg_client_ui_directory_picker_browse @@ -452,19 +461,14 @@ flowchart TD pkg_anonymous_user_id --> pkg_home_paths pkg_lsp --> pkg_brand pkg_lsp --> pkg_llm - pkg_storage_domain --> pkg_invariants pkg_storage_domain --> pkg_storage pkg_storage_json --> pkg_storage pkg_storage_sqlite --> pkg_storage pkg_subprocess --> pkg_http_proxy pkg_remote_mock --> pkg_typert_protocol pkg_typert_loader --> pkg_typert_registry - pkg_session --> pkg_scope - pkg_system_prompt --> pkg_invariants - pkg_system_prompt --> pkg_llm - pkg_system_prompt --> pkg_scope - pkg_skill --> pkg_llm - pkg_skill --> pkg_scope + pkg_skill_badge --> pkg_skill + pkg_skill_office --> pkg_skill pkg_web_fetch_http --> pkg_http_proxy pkg_web_fetch_http --> pkg_timeout pkg_web_fetch_http --> pkg_web @@ -472,31 +476,17 @@ flowchart TD pkg_web_search_exa --> pkg_web pkg_web_search_perplexity --> pkg_launch_environment pkg_web_search_perplexity --> pkg_web - pkg_api_remotes --> pkg_scope - pkg_api_terminal_controller --> pkg_subprocess - pkg_attachment_local --> pkg_attachment - pkg_attachment_local --> pkg_home_paths - pkg_client_connection --> pkg_scope - pkg_client_file_upload --> pkg_scope - pkg_authorization --> pkg_credentials - pkg_authorization --> pkg_invariants - pkg_authorization --> pkg_llm - pkg_credentials_local --> pkg_atomic_write - pkg_credentials_local --> pkg_credentials - pkg_credentials_local --> pkg_home_paths - pkg_credentials_local --> pkg_launch_environment - pkg_experimental_computer_use_cua_driver_mcp --> pkg_computer_use - pkg_subprocess_local --> pkg_subprocess - pkg_subprocess_local --> pkg_timeout - pkg_skill_badge --> pkg_skill - pkg_skill_office --> pkg_skill pkg_spill --> pkg_brand pkg_spill --> pkg_llm pkg_spill --> pkg_session pkg_api_gateway --> pkg_client_connection + pkg_api_terminal_controller --> pkg_subprocess + pkg_attachment_local --> pkg_attachment + pkg_attachment_local --> pkg_home_paths pkg_app_boot --> pkg_home_paths pkg_app_boot --> pkg_launch_environment pkg_app_boot --> pkg_system_prompt + pkg_experimental_computer_use_cua_driver_mcp --> pkg_computer_use pkg_experimental_webworker_runtime --> pkg_client_connection pkg_experimental_webworker_runtime --> pkg_client_modules pkg_experimental_webworker_runtime --> pkg_host_webserver @@ -509,20 +499,19 @@ flowchart TD pkg_sandbox_windows_acl --> pkg_subprocess pkg_session_format_catalog --> pkg_session pkg_session_log_deepseek --> pkg_deepseek_llm_api_extensions - pkg_session_log_deepseek --> pkg_invariants pkg_session_log_deepseek --> pkg_session pkg_session_persistence --> pkg_brand pkg_session_persistence --> pkg_session pkg_session_persistence --> pkg_timeout pkg_session_projection --> pkg_session pkg_settings --> pkg_brand - pkg_settings --> pkg_invariants pkg_settings --> pkg_session + pkg_subprocess_local --> pkg_subprocess + pkg_subprocess_local --> pkg_timeout pkg_otel --> pkg_session pkg_session_snapshot --> pkg_http_proxy pkg_session_snapshot --> pkg_session pkg_fs --> pkg_brand - pkg_fs --> pkg_invariants pkg_fs --> pkg_llm pkg_fs --> pkg_sandbox pkg_spill_local --> pkg_spill @@ -549,7 +538,6 @@ flowchart TD pkg_session_turn_outline --> pkg_session_projection pkg_shell --> pkg_sandbox pkg_shell --> pkg_subprocess - pkg_workspace --> pkg_invariants pkg_workspace --> pkg_session pkg_workspace --> pkg_session_persistence pkg_workspace --> pkg_storage @@ -571,7 +559,6 @@ flowchart TD pkg_llm_pi_ai --> pkg_launch_environment pkg_llm_pi_ai --> pkg_llm pkg_llm_pi_ai --> pkg_timeout - pkg_agent --> pkg_invariants pkg_agent --> pkg_llm pkg_agent --> pkg_scope pkg_agent --> pkg_session @@ -585,7 +572,6 @@ flowchart TD pkg_skill_filesystem --> pkg_fs pkg_skill_filesystem --> pkg_home_paths pkg_skill_filesystem --> pkg_skill - pkg_hook_protocol --> pkg_invariants pkg_hook_protocol --> pkg_session pkg_hook_protocol --> pkg_shell pkg_api_workspace_controller --> pkg_api_gateway @@ -622,7 +608,6 @@ flowchart TD pkg_llm_deepseek_api_key --> pkg_llm_deepseek pkg_llm_retry --> pkg_agent pkg_llm_retry --> pkg_brand - pkg_llm_retry --> pkg_invariants pkg_llm_retry --> pkg_llm pkg_llm_retry --> pkg_session pkg_llm_retry --> pkg_session_projection @@ -631,7 +616,6 @@ flowchart TD pkg_agent_default_model --> pkg_llm pkg_goal --> pkg_agent pkg_goal --> pkg_brand - pkg_goal --> pkg_invariants pkg_goal --> pkg_llm pkg_goal --> pkg_scope pkg_goal --> pkg_session @@ -639,7 +623,6 @@ flowchart TD pkg_goal --> pkg_typert_protocol pkg_file_reference --> pkg_agent pkg_time_context --> pkg_agent - pkg_time_context --> pkg_invariants pkg_time_context --> pkg_llm pkg_time_context --> pkg_session pkg_time_context --> pkg_session_projection @@ -653,14 +636,12 @@ flowchart TD pkg_commands --> pkg_agent pkg_commands --> pkg_attachment pkg_commands --> pkg_brand - pkg_commands --> pkg_invariants pkg_commands --> pkg_llm pkg_commands --> pkg_scope pkg_commands --> pkg_session pkg_commands --> pkg_typert_protocol 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 @@ -673,11 +654,9 @@ flowchart TD pkg_user_questions --> pkg_typert_protocol pkg_jobs --> pkg_agent pkg_jobs --> pkg_brand - pkg_jobs --> pkg_invariants pkg_jobs --> pkg_session pkg_jobs --> pkg_workspace pkg_sandbox_policy --> pkg_agent - pkg_sandbox_policy --> pkg_invariants pkg_sandbox_policy --> pkg_sandbox pkg_sandbox_policy --> pkg_session pkg_sandbox_policy --> pkg_session_projection @@ -686,7 +665,6 @@ flowchart TD pkg_session_telemetry --> pkg_session pkg_session_title --> pkg_agent pkg_session_title --> pkg_brand - pkg_session_title --> pkg_invariants pkg_session_title --> pkg_llm pkg_session_title --> pkg_session pkg_session_title --> pkg_session_projection @@ -698,7 +676,6 @@ flowchart TD pkg_loader_smoke --> pkg_session pkg_workflow --> pkg_agent pkg_workflow --> pkg_brand - pkg_workflow --> pkg_invariants pkg_workflow --> pkg_llm pkg_workflow --> pkg_session pkg_llm_deepseek_account --> pkg_deepseek_account @@ -706,7 +683,6 @@ flowchart TD pkg_llm_deepseek_account --> pkg_llm pkg_llm_deepseek_account --> pkg_llm_deepseek pkg_tools --> pkg_agent - pkg_tools --> pkg_invariants pkg_tools --> pkg_llm pkg_tools --> pkg_ptc_runtime pkg_tools --> pkg_sandbox @@ -720,7 +696,6 @@ flowchart TD pkg_command_goal --> pkg_llm pkg_goal_round_driver --> pkg_agent pkg_goal_round_driver --> pkg_goal - pkg_goal_round_driver --> pkg_invariants pkg_goal_round_driver --> pkg_llm pkg_goal_round_driver --> pkg_session pkg_fs_sandbox --> pkg_fs @@ -738,7 +713,6 @@ flowchart TD pkg_api_account_controller --> pkg_typert_protocol pkg_compaction --> pkg_brand pkg_compaction --> pkg_commands - pkg_compaction --> pkg_invariants pkg_compaction --> pkg_llm pkg_compaction --> pkg_session pkg_deepseek_account_platform --> pkg_authorization @@ -750,7 +724,6 @@ flowchart TD pkg_command_feedback --> pkg_session pkg_command_feedback --> pkg_typert_protocol pkg_permission_presets --> pkg_commands - pkg_permission_presets --> pkg_invariants pkg_permission_presets --> pkg_sandbox pkg_permission_presets --> pkg_sandbox_policy pkg_permission_presets --> pkg_session @@ -789,7 +762,6 @@ flowchart TD pkg_terminal_bash --> pkg_subprocess pkg_terminal_bash --> pkg_terminal pkg_agent_loop --> pkg_agent - pkg_agent_loop --> pkg_invariants pkg_agent_loop --> pkg_llm pkg_agent_loop --> pkg_scope pkg_agent_loop --> pkg_session @@ -837,13 +809,11 @@ flowchart TD pkg_tool_web --> pkg_tools pkg_tool_web --> pkg_web pkg_tool_todo --> pkg_agent - pkg_tool_todo --> pkg_invariants pkg_tool_todo --> pkg_session pkg_tool_todo --> pkg_session_projection pkg_tool_todo --> pkg_tools pkg_plan_mode --> pkg_agent pkg_plan_mode --> pkg_commands - pkg_plan_mode --> pkg_invariants pkg_plan_mode --> pkg_llm pkg_plan_mode --> pkg_session pkg_plan_mode --> pkg_session_projection @@ -935,7 +905,6 @@ flowchart TD pkg_mcp_resources --> pkg_system_prompt pkg_mcp_resources --> pkg_tools pkg_agent_preset_registry --> pkg_agent - pkg_agent_preset_registry --> pkg_invariants pkg_agent_preset_registry --> pkg_scope pkg_agent_preset_registry --> pkg_session pkg_agent_preset_registry --> pkg_session_projection @@ -992,7 +961,6 @@ flowchart TD pkg_llm_replay --> pkg_llm pkg_llm_replay --> pkg_session pkg_tool_workflow --> pkg_agent - pkg_tool_workflow --> pkg_invariants pkg_tool_workflow --> pkg_jobs pkg_tool_workflow --> pkg_llm pkg_tool_workflow --> pkg_session @@ -1093,7 +1061,6 @@ flowchart TD pkg_webhook --> pkg_agent pkg_webhook --> pkg_agent_default_model pkg_webhook --> pkg_agent_preset_registry - pkg_webhook --> pkg_invariants pkg_webhook --> pkg_llm pkg_webhook --> pkg_permission_presets pkg_webhook --> pkg_session @@ -1102,7 +1069,6 @@ flowchart TD pkg_subagent --> pkg_agent pkg_subagent --> pkg_agent_preset_registry pkg_subagent --> pkg_attachment - pkg_subagent --> pkg_invariants pkg_subagent --> pkg_jobs pkg_subagent --> pkg_llm pkg_subagent --> pkg_permission_presets @@ -1198,7 +1164,6 @@ flowchart TD pkg_subagent_in_process_driver --> pkg_system_prompt pkg_subagent_in_process_driver --> pkg_tools pkg_tool_subagent --> pkg_agent - pkg_tool_subagent --> pkg_invariants pkg_tool_subagent --> pkg_jobs pkg_tool_subagent --> pkg_llm pkg_tool_subagent --> pkg_scope @@ -1268,7 +1233,6 @@ flowchart TD pkg_session_reference --> pkg_system_prompt pkg_session_reference --> pkg_typert_protocol pkg_experimental_agent_team --> pkg_agent - pkg_experimental_agent_team --> pkg_invariants pkg_experimental_agent_team --> pkg_llm pkg_experimental_agent_team --> pkg_session pkg_experimental_agent_team --> pkg_session_persistence @@ -1331,7 +1295,6 @@ flowchart TD pkg_schedule --> pkg_agent pkg_schedule --> pkg_api_session_controller pkg_schedule --> pkg_brand - pkg_schedule --> pkg_invariants pkg_schedule --> pkg_llm pkg_schedule --> pkg_session pkg_schedule --> pkg_session_persistence @@ -1400,6 +1363,7 @@ flowchart TD | [`util-workspace-path`](../packages/util/workspace-path) | `util` | — | | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | `llm` | — | | [`llm`](../packages/llm/llm) | `llm` | — | +| [`scope`](../packages/core/scope) | `core` | — | | [`api-job-controller`](../packages/api/job-controller) | `api` | — | | [`api-workspace-files`](../packages/api/workspace-files) | `api` | — | | [`cmdline`](../packages/boot/cmdline) | `boot` | — | @@ -1468,6 +1432,7 @@ flowchart TD | [`client-ui-workflow-run`](../packages/client/ui-workflow-run) | `client` | — | | [`client-ui-workspace`](../packages/client/ui-workspace) | `client` | — | | [`client-web`](../packages/client/web) | `client` | — | +| [`credentials`](../packages/credentials/credentials) | `credentials` | — | | [`office-to-pdf`](../packages/document/office-to-pdf) | `document` | — | | [`experimental-agent-team-profile`](../packages/experimental/agent-team-profile) | `experimental` | — | | [`experimental-schedule-bundle`](../packages/experimental/schedule-bundle) | `experimental` | — | @@ -1480,7 +1445,6 @@ flowchart TD | [`host-directory-picker-native`](../packages/host/directory-picker-native) | `host` | — | | [`host-open-in-app`](../packages/host/open-in-app) | `host` | — | | [`host-webserver`](../packages/host/webserver) | `host` | — | -| [`invariants`](../packages/runtime-diagnostics/invariants) | `runtime-diagnostics` | — | | [`session-format`](../packages/session/session-format) | `session` | — | | [`session-format-v0-to-v1`](../packages/session/session-format-v0-to-v1) | `session` | — | | [`session-format-v1-to-v2`](../packages/session/session-format-v1-to-v2) | `session` | — | @@ -1492,55 +1456,53 @@ flowchart TD | [`typert-generator`](../packages/typert/generator) | `typert` | — | | [`typert-protocol`](../packages/typert/protocol) | `typert` | — | | [`typert-registry`](../packages/typert/registry) | `typert` | — | -| [`scope`](../packages/core/scope) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants) | +| [`session`](../packages/core/session) | `core` | [`scope`](../packages/core/scope) | +| [`system-prompt`](../packages/core/system-prompt) | `core` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | +| [`skill`](../packages/skill/skill) | `skill` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | | [`web`](../packages/web/web) | `web` | [`llm`](../packages/llm/llm) | +| [`api-remotes`](../packages/api/remotes) | `api` | [`scope`](../packages/core/scope) | | [`attachment`](../packages/attachment/attachment) | `attachment` | [`brand`](../packages/util/brand) | | [`browser-use`](../packages/browser-use/browser-use) | `browser-use` | [`brand`](../packages/util/brand) | +| [`client-connection`](../packages/client/connection) | `client` | [`scope`](../packages/core/scope) | +| [`client-file-upload`](../packages/client/file-upload) | `client` | [`scope`](../packages/core/scope) | | [`computer-use`](../packages/computer-use/computer-use) | `computer-use` | [`brand`](../packages/util/brand) | -| [`credentials`](../packages/credentials/credentials) | `credentials` | [`invariants`](../packages/runtime-diagnostics/invariants) | +| [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`llm`](../packages/llm/llm) | +| [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment) | | [`experimental-inspector`](../packages/experimental/inspector) | `experimental` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver) | | [`anonymous-user-id`](../packages/identity/anonymous-user-id) | `identity` | [`brand`](../packages/util/brand), [`home-paths`](../packages/util/home-paths) | | [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | -| [`storage-domain`](../packages/storage/storage-domain) | `storage` | [`invariants`](../packages/runtime-diagnostics/invariants), [`storage`](../packages/storage/storage) | +| [`storage-domain`](../packages/storage/storage-domain) | `storage` | [`storage`](../packages/storage/storage) | | [`storage-json`](../packages/storage/storage-json) | `storage` | [`storage`](../packages/storage/storage) | | [`storage-sqlite`](../packages/storage/storage-sqlite) | `storage` | [`storage`](../packages/storage/storage) | | [`subprocess`](../packages/subprocess/subprocess) | `subprocess` | [`http-proxy`](../packages/util/http-proxy) | | [`remote-mock`](../packages/test-support/remote-mock) | `test-support` | [`typert-protocol`](../packages/typert/protocol) | | [`typert-loader`](../packages/typert/loader) | `typert` | [`typert-registry`](../packages/typert/registry) | -| [`session`](../packages/core/session) | `core` | [`scope`](../packages/core/scope) | -| [`system-prompt`](../packages/core/system-prompt) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | -| [`skill`](../packages/skill/skill) | `skill` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | +| [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`skill`](../packages/skill/skill) | +| [`skill-office`](../packages/skill/skill-office) | `skill` | [`skill`](../packages/skill/skill) | | [`web-fetch-http`](../packages/web/web-fetch-http) | `web` | [`http-proxy`](../packages/util/http-proxy), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) | | [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) | | [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) | -| [`api-remotes`](../packages/api/remotes) | `api` | [`scope`](../packages/core/scope) | -| [`api-terminal-controller`](../packages/api/terminal-controller) | `api` | [`subprocess`](../packages/subprocess/subprocess) | -| [`attachment-local`](../packages/attachment/attachment-local) | `attachment` | [`attachment`](../packages/attachment/attachment), [`home-paths`](../packages/util/home-paths) | -| [`client-connection`](../packages/client/connection) | `client` | [`scope`](../packages/core/scope) | -| [`client-file-upload`](../packages/client/file-upload) | `client` | [`scope`](../packages/core/scope) | -| [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) | -| [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment) | -| [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp) | `experimental` | [`computer-use`](../packages/computer-use/computer-use) | -| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | -| [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`skill`](../packages/skill/skill) | -| [`skill-office`](../packages/skill/skill-office) | `skill` | [`skill`](../packages/skill/skill) | | [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`api-gateway`](../packages/api/gateway) | `api` | [`client-connection`](../packages/client/connection) | +| [`api-terminal-controller`](../packages/api/terminal-controller) | `api` | [`subprocess`](../packages/subprocess/subprocess) | +| [`attachment-local`](../packages/attachment/attachment-local) | `attachment` | [`attachment`](../packages/attachment/attachment), [`home-paths`](../packages/util/home-paths) | | [`app-boot`](../packages/boot/app-boot) | `boot` | [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment), [`system-prompt`](../packages/core/system-prompt) | +| [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp) | `experimental` | [`computer-use`](../packages/computer-use/computer-use) | | [`experimental-webworker-runtime`](../packages/experimental/webworker-runtime) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | | [`host-frontend-static`](../packages/host/frontend-static) | `host` | [`client-connection`](../packages/client/connection), [`host-webserver`](../packages/host/webserver) | | [`persona`](../packages/preset/persona) | `preset` | [`system-prompt`](../packages/core/system-prompt) | | [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`sandbox-windows-acl`](../packages/sandbox/sandbox-windows-acl) | `sandbox` | [`skill`](../packages/skill/skill), [`subprocess`](../packages/subprocess/subprocess) | | [`session-format-catalog`](../packages/session/session-format-catalog) | `session` | [`session`](../packages/core/session) | -| [`session-log-deepseek`](../packages/session/session-log-deepseek) | `session` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) | +| [`session-log-deepseek`](../packages/session/session-log-deepseek) | `session` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) | | [`session-persistence`](../packages/session/session-persistence) | `session` | [`brand`](../packages/util/brand), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | | [`session-projection`](../packages/session/session-projection) | `session` | [`session`](../packages/core/session) | -| [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) | +| [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`session`](../packages/core/session) | +| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`otel`](../packages/telemetry/otel) | `telemetry` | [`session`](../packages/core/session) | | [`session-snapshot`](../packages/test-support/session-snapshot) | `test-support` | [`http-proxy`](../packages/util/http-proxy), [`session`](../packages/core/session) | -| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | +| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | | [`spill-local`](../packages/spill/spill-local) | `spill` | [`spill`](../packages/spill/spill) | | [`session-log-export`](../packages/session-query/session-log-export) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) | | [`hmr`](../packages/boot/hmr) | `boot` | [`app-boot`](../packages/boot/app-boot), [`cmdline`](../packages/boot/cmdline) | @@ -1553,14 +1515,14 @@ flowchart TD | [`session-stats`](../packages/session/session-stats) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`session-turn-outline`](../packages/session/session-turn-outline) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`shell`](../packages/shell/shell) | `shell` | [`sandbox`](../packages/sandbox/sandbox), [`subprocess`](../packages/subprocess/subprocess) | -| [`workspace`](../packages/workspace/workspace) | `workspace` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) | +| [`workspace`](../packages/workspace/workspace) | `workspace` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) | | [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`atomic-write`](../packages/util/atomic-write), [`attachment`](../packages/attachment/attachment), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) | | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`attachment`](../packages/attachment/attachment), [`authorization`](../packages/credentials/authorization), [`credentials`](../packages/credentials/credentials), [`fs`](../packages/fs/fs), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) | -| [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol), [`util-values`](../packages/util/values), [`workspace`](../packages/workspace/workspace) | +| [`agent`](../packages/core/agent) | `core` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol), [`util-values`](../packages/util/values), [`workspace`](../packages/workspace/workspace) | | [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs) | | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | `fs` | [`fs`](../packages/fs/fs) | | [`skill-filesystem`](../packages/skill/skill-filesystem) | `skill` | [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`skill`](../packages/skill/skill) | -| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) | +| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`session`](../packages/core/session), [`shell`](../packages/shell/shell) | | [`api-workspace-controller`](../packages/api/workspace-controller) | `api` | [`api-gateway`](../packages/api/gateway), [`client-connection`](../packages/client/connection), [`host-directory-picker`](../packages/host/directory-picker), [`session`](../packages/core/session), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) | | [`config-editor`](../packages/boot/config-editor) | `boot` | [`app-boot`](../packages/boot/app-boot), [`hmr`](../packages/boot/hmr) | | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | `experimental` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text), [`typert-protocol`](../packages/typert/protocol) | @@ -1570,41 +1532,41 @@ flowchart TD | [`bash-local`](../packages/shell/bash-local) | `shell` | [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`pwsh-local`](../packages/shell/pwsh-local) | `shell` | [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`llm-deepseek-api-key`](../packages/llm/llm-deepseek-api-key) | `llm` | [`credentials`](../packages/credentials/credentials), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek) | -| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`timeout`](../packages/util/timeout) | +| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`timeout`](../packages/util/timeout) | | [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm) | -| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | +| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | | [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent) | -| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | +| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell) | | [`deepseek-account`](../packages/credentials/deepseek-account) | `credentials` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | -| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | -| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | +| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | +| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`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-questions`](../packages/interaction/user-questions) | `interaction` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | -| [`jobs`](../packages/jobs/jobs) | `jobs` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`workspace`](../packages/workspace/workspace) | -| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) | +| [`jobs`](../packages/jobs/jobs) | `jobs` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`session`](../packages/core/session), [`workspace`](../packages/workspace/workspace) | +| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) | | [`session-telemetry`](../packages/session/session-telemetry) | `session` | [`agent`](../packages/core/agent), [`session`](../packages/core/session) | -| [`session-title`](../packages/session/session-title) | `session` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | +| [`session-title`](../packages/session/session-title) | `session` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`terminal`](../packages/terminal/terminal) | `terminal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand) | | [`loader-smoke`](../packages/test-support/loader-smoke) | `test-support` | [`agent`](../packages/core/agent), [`http-proxy`](../packages/util/http-proxy), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`llm-deepseek-account`](../packages/llm/llm-deepseek-account) | `llm` | [`deepseek-account`](../packages/credentials/deepseek-account), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek) | -| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) | +| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) | | [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm) | -| [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) | | [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`deepseek-account`](../packages/credentials/deepseek-account), [`launch-environment`](../packages/util/launch-environment), [`session`](../packages/core/session), [`web`](../packages/web/web) | | [`api-account-controller`](../packages/api/account-controller) | `api` | [`agent`](../packages/core/agent), [`deepseek-account`](../packages/credentials/deepseek-account), [`typert-protocol`](../packages/typert/protocol) | -| [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) | `credentials` | [`authorization`](../packages/credentials/authorization), [`credentials`](../packages/credentials/credentials), [`deepseek-account`](../packages/credentials/deepseek-account), [`host-webserver`](../packages/host/webserver) | | [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`commands`](../packages/interaction/commands), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | -| [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | +| [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | | [`jobs-local`](../packages/jobs/jobs-local) | `jobs` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`scope`](../packages/core/scope), [`timeout`](../packages/util/timeout) | | [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node) | `ptc-runtime` | [`fs`](../packages/fs/fs), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`session-title-llm`](../packages/session/session-title-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`timeout`](../packages/util/timeout) | | [`bash-sandbox`](../packages/shell/bash-sandbox) | `shell` | [`bash-local`](../packages/shell/bash-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) | | [`pwsh-sandbox`](../packages/shell/pwsh-sandbox) | `shell` | [`pwsh-local`](../packages/shell/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) | | [`terminal-bash`](../packages/terminal/terminal-bash) | `terminal` | [`agent`](../packages/core/agent), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`terminal`](../packages/terminal/terminal) | -| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`agent-tool-presentation`](../packages/core/agent-tool-presentation) | `core` | [`tools`](../packages/core/tools) | | [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | @@ -1613,8 +1575,8 @@ flowchart TD | [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) | | [`tool-workspace-dependencies`](../packages/skill/tool-workspace-dependencies) | `skill` | [`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) | -| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | -| [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) | +| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | +| [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) | | [`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-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | | [`command-compact`](../packages/compaction/command-compact) | `compaction` | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction) | | [`compaction-image-offload`](../packages/compaction/compaction-image-offload) | `compaction` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | @@ -1634,7 +1596,7 @@ flowchart TD | [`tool-jobs`](../packages/jobs/tool-jobs) | `jobs` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`mcp-resources`](../packages/mcp/mcp-resources) | `mcp` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`agent-preset-registry`](../packages/preset/agent-preset-registry) | `preset` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | +| [`agent-preset-registry`](../packages/preset/agent-preset-registry) | `preset` | [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) | | [`session-title-all-prompts-llm`](../packages/session/session-title-all-prompts-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | | [`session-title-first-prompt-llm`](../packages/session/session-title-first-prompt-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | @@ -1644,7 +1606,7 @@ flowchart TD | [`ssh`](../packages/ssh/ssh) | `ssh` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-local`](../packages/sandbox/sandbox-local), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) | | [`tool-terminal`](../packages/terminal/tool-terminal) | `terminal` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`terminal`](../packages/terminal/terminal), [`tools`](../packages/core/tools) | | [`llm-replay`](../packages/test-support/llm-replay) | `test-support` | [`compaction`](../packages/compaction/compaction), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | +| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | `llm` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) | | [`token-meter`](../packages/llm/token-meter) | `llm` | [`compaction`](../packages/compaction/compaction), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-title`](../packages/session/session-title), [`tool-todo`](../packages/todo/tool-todo) | @@ -1662,8 +1624,8 @@ flowchart TD | [`sandbox-ssh`](../packages/ssh/sandbox-ssh) | `ssh` | [`sandbox`](../packages/sandbox/sandbox), [`ssh`](../packages/ssh/ssh) | | [`subprocess-ssh`](../packages/ssh/subprocess-ssh) | `ssh` | [`ssh`](../packages/ssh/ssh), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) | | [`agent-loop-testkit`](../packages/test-support/agent-loop-testkit) | `test-support` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`webhook`](../packages/webhook/webhook) | `webhook` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) | -| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time), [`workspace`](../packages/workspace/workspace) | +| [`webhook`](../packages/webhook/webhook) | `webhook` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) | +| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time), [`workspace`](../packages/workspace/workspace) | | [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) | | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) | | [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | @@ -1677,13 +1639,13 @@ flowchart TD | [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-codex`](../packages/subagent/subagent-codex) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | `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), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`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), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-file-upload`](../packages/client/file-upload), [`commands`](../packages/interaction/commands), [`credentials`](../packages/credentials/credentials), [`file-reference`](../packages/context/file-reference), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`settings`](../packages/settings/settings), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-time`](../packages/util/time), [`util-values`](../packages/util/values), [`util-workspace-path`](../packages/util/workspace-path), [`workspace`](../packages/workspace/workspace) | | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`spill`](../packages/spill/spill), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) | -| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent) | +| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent) | | [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | | [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`workflow-ptc`](../packages/workflow/workflow-ptc) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | @@ -1692,7 +1654,7 @@ flowchart TD | [`experimental-client-ui-agent-team`](../packages/experimental/client-ui-agent-team) | `experimental` | [`api-session-controller`](../packages/api/session-controller), [`client-locale`](../packages/client/locale), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`experimental-agent-team`](../packages/experimental/agent-team), [`session`](../packages/core/session) | | [`experimental-client-ui-voice-input`](../packages/experimental/client-ui-voice-input) | `experimental` | [`api-gateway`](../packages/api/gateway), [`api-remotes`](../packages/api/remotes), [`api-session-controller`](../packages/api/session-controller), [`client-locale`](../packages/client/locale), [`client-store`](../packages/client/store), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-plugin-manager`](../packages/client/ui-plugin-manager), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-slots`](../packages/client/ui-slots), [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text), [`experimental-speech-to-text`](../packages/experimental/speech-to-text), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | `experimental` | [`agent`](../packages/core/agent), [`experimental-agent-team`](../packages/experimental/agent-team), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`api-session-controller`](../packages/api/session-controller), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) | +| [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`api-session-controller`](../packages/api/session-controller), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) | | [`sdk-client`](../packages/sdk/client) | `sdk` | [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session) | | [`sdk-jsonrpc-server`](../packages/sdk/server) | `sdk` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`llm-deepseek-api-key`](../packages/llm/llm-deepseek-api-key), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | | [`client-test-runtime`](../packages/test-support/client-runtime) | `test-support` | [`api-gateway`](../packages/api/gateway), [`api-session-controller`](../packages/api/session-controller), [`api-workspace-controller`](../packages/api/workspace-controller), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-hmr`](../packages/client/hmr), [`client-modules`](../packages/client/modules), [`client-store`](../packages/client/store), [`client-ui-chat`](../packages/client/ui-chat), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web`](../packages/client/web), [`remote-mock`](../packages/test-support/remote-mock), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) | diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 1575e2104d..e20157e22f 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -798,8 +798,8 @@ Source: [`packages/schedule/schedule/src/types.ts:358`](../packages/schedule/sch * keep ordinary restore and replay lifecycle boundaries. * * Only the `Session` constructor and `buildForkSeed` may create this marker. - * The invariant companion deliberately constrains nothing here, so a plugin - * appending one would silently classify every live bracket before it as seed history. + * Session append does not reject other writers, so a plugin appending one + * would silently classify every live bracket before it as seed history. * * An owner of a standalone open/close bracket (`compaction/start` … * `compaction/end`) reads it because seed history and live work are otherwise diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index 6bdbb92982..e3503bab9b 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -800,8 +800,8 @@ export type SessionEvent = { * keep ordinary restore and replay lifecycle boundaries. * * Only the `Session` constructor and `buildForkSeed` may create this marker. - * The invariant companion deliberately constrains nothing here, so a plugin - * appending one would silently classify every live bracket before it as seed history. + * Session append does not reject other writers, so a plugin appending one + * would silently classify every live bracket before it as seed history. * * An owner of a standalone open/close bracket (`compaction/start` … * `compaction/end`) reads it because seed history and live work are otherwise diff --git a/docs/subsystems/README.i18n.yaml b/docs/subsystems/README.i18n.yaml index 7051478b51..e23bf8b735 100644 --- a/docs/subsystems/README.i18n.yaml +++ b/docs/subsystems/README.i18n.yaml @@ -3,5 +3,5 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/README.md /subsystems: - en: ab6cd11804f6b644 - zh: 7a82dc5b22c40703 + en: 3716b19db3b2e1b2 + zh: a9611e4745f0ac89 diff --git a/docs/subsystems/README.md b/docs/subsystems/README.md index e2f8dc51e2..f7382c49ff 100644 --- a/docs/subsystems/README.md +++ b/docs/subsystems/README.md @@ -14,7 +14,7 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures | [typert.md](typert.md) | Remote invocation descriptors, lookup/Context declarations, Typert registries, and the Host Gateway/Client API boundaries | | [goal.md](goal.md) | persisted goal identity, lifecycle snapshots, activation, change records, and round attribution | | [schedule.md](schedule.md) | Host-owned reminder records, durable transitions, active and ended views, and ordinary-conversation delivery | -| [todo.md](todo.md) | the todo package's whole-list item type, durable event ownership, projection, and open-turn invariant | +| [todo.md](todo.md) | the todo package's whole-list item type, durable event ownership, and projection | | [deliverables.md](deliverables.md) | what a turn hands to the user: `PresentedFile` deliveries from `present` and the Host-served `WorkspaceChangesSummary` of changed files from git snapshots | | [commands.md](commands.md) | the human-command registry service: definitions, adapter discovery, direct invocation, results, and parsing views | | [session.md](session.md) | the full `SessionEventMap` variant catalog, `TurnEndReason`, `deriveMessages()`, execution enclosure, and standalone events | @@ -54,7 +54,6 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures | [jobs.md](jobs.md) | the background-job runtime: branded `JobId`s, the producer contract, consumer views, and `ctx.jobs` service behavior | | [permission-presets.md](permission-presets.md) | the permission-preset layer: `PresetSpec`/`PresetOption`, the derived `custom` state, the log-only `permission/preset` event | | [plan.md](plan.md) | plan mode: the log-only `plan/mode` state, pending-selection flush, `PlanModeConfig`, the `exit_plan_mode` review arc | -| [invariants.md](invariants.md) | the runtime-invariant registry: selection `Config`, `InvariantInstaller`/`InvariantFailure`, the empty-companion contract | | [web-server.md](web-server.md) | the HTTP carrier: `WebRouteKind`/`WebRoute`, match order, the claimable fallback seat, index taps | | [webhook.md](webhook.md) | authenticated provider deliveries, arbitrary programmatic rules, and fire-and-forget Workspace Session creation | | [storage.md](storage.md) | the storage subsystem: the backend contract (`StorageBackend`), `StorageForms`, `DomainSpec`/`Domain`, `domain/changed` | diff --git a/docs/subsystems/README.zh.md b/docs/subsystems/README.zh.md index 31f4489d43..5228af1a60 100644 --- a/docs/subsystems/README.zh.md +++ b/docs/subsystems/README.zh.md @@ -14,7 +14,7 @@ | [typert.md](typert.zh.md) | 远程调用描述符、lookup/Context 声明、Typert 注册表,以及 Host Gateway/Client API 边界 | | [goal.md](goal.zh.md) | 持久 goal 标识、生命周期快照、激活、变更记录与 Round 归属 | | [schedule.md](schedule.zh.md) | Host 拥有的提醒记录、持久转换、活动和已结束视图与普通对话交付 | -| [todo.md](todo.zh.md) | todo 包的整列表条目类型、持久事件所有权、投影和未结束轮次不变式 | +| [todo.md](todo.zh.md) | todo 包的整列表条目类型、持久事件所有权和投影 | | [deliverables.md](deliverables.zh.md) | 一轮交给用户的东西:`present` 声明的 `PresentedFile` 交付,以及由 git 快照得出、由 Host 提供的 `WorkspaceChangesSummary` 改动摘要 | | [commands.md](commands.zh.md) | 人类命令注册表服务:定义、适配器发现、直接调用、结果与解析视图 | | [session.md](session.zh.md) | 完整的 `SessionEventMap` 变体目录、`TurnEndReason`、`deriveMessages()`、执行封闭与独立事件 | @@ -54,7 +54,6 @@ | [jobs.md](jobs.zh.md) | 后台任务运行时:品牌化 `JobId`、producer 约定、消费方视图和 `ctx.jobs` 服务行为 | | [permission-presets.md](permission-presets.zh.md) | 权限预设层:`PresetSpec`/`PresetOption`、派生的 `custom` 状态、仅记日志的 `permission/preset` 事件 | | [plan.md](plan.zh.md) | 计划模式:仅记日志的 `plan/mode` 状态、待定选择的冲刷、`PlanModeConfig`、`exit_plan_mode` 审阅流程 | -| [invariants.md](invariants.zh.md) | 运行时不变式注册表:选择配置 `Config`、`InvariantInstaller`/`InvariantFailure`、空配套插件约定 | | [web-server.md](web-server.zh.md) | HTTP 载体:`WebRouteKind`/`WebRoute`、匹配顺序、可认领的回退席位、index 渲染挂接点 | | [webhook.md](webhook.zh.md) | 通过身份验证的提供方交付、任意程序化规则,以及发起 Workspace 会话创建后不等待结果 | | [storage.md](storage.zh.md) | 存储子系统:后端约定(`StorageBackend`)、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed` | diff --git a/docs/subsystems/credentials.md b/docs/subsystems/credentials.md index eb708f95f9..ef0b5b04c3 100644 --- a/docs/subsystems/credentials.md +++ b/docs/subsystems/credentials.md @@ -420,7 +420,7 @@ Source: [`packages/credentials/credentials/src/types.ts`](../../packages/credent #### `credentials/reference-updated` — emit -Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome, except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions. +Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome. ```ts cordis-catalog /** @@ -428,10 +428,7 @@ Committed change to a provider-managed credential source: a `set`, an `unset`, o * `unset`, or an external edit observed in storage. Ambient * process-environment changes are not observable and never emit. Listener * failures are contained and logged — a sync throw and an async rejection - * alike — without changing the committed operation's outcome, except - * `INVARIANT`-coded failures, which rethrow after every listener ran; - * that rethrow reaches the emitter only from synchronous listeners, so - * invariant checks on this event must not be async functions. + * alike — without changing the committed operation's outcome. * @param ref - the reference whose stored value changed. * @mode emit */ diff --git a/docs/subsystems/credentials.zh.md b/docs/subsystems/credentials.zh.md index 9a6ce9f3fa..d6a8d73938 100644 --- a/docs/subsystems/credentials.zh.md +++ b/docs/subsystems/credentials.zh.md @@ -420,7 +420,7 @@ Source: [`packages/credentials/credentials/src/types.ts`](../../packages/credent #### `credentials/reference-updated` — emit -Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome, except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions. +Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome. ```ts cordis-catalog /** @@ -428,10 +428,7 @@ Committed change to a provider-managed credential source: a `set`, an `unset`, o * `unset`, or an external edit observed in storage. Ambient * process-environment changes are not observable and never emit. Listener * failures are contained and logged — a sync throw and an async rejection - * alike — without changing the committed operation's outcome, except - * `INVARIANT`-coded failures, which rethrow after every listener ran; - * that rethrow reaches the emitter only from synchronous listeners, so - * invariant checks on this event must not be async functions. + * alike — without changing the committed operation's outcome. * @param ref - the reference whose stored value changed. * @mode emit */ diff --git a/docs/subsystems/invariants.i18n.yaml b/docs/subsystems/invariants.i18n.yaml deleted file mode 100644 index 8cbba766c6..0000000000 --- a/docs/subsystems/invariants.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for invariants.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/subsystems/invariants.md -/runtime-invariants: - en: b48053808b8bb042 - zh: 7be7763ded5f3a6e -/runtime-invariants/selection: - en: 2d3bb3a2a0212e2d - zh: 8c8f1e1ba5790ba4 -/runtime-invariants/the-installer: - en: ba1607612e5ed640 - zh: 33459bdb7c42443c -/runtime-invariants/the-service: - en: 6368b54b2cbdb36c - zh: 4859ac095e536d42 -/runtime-invariants/the-companion-contract: - en: 414b986025768325 - zh: b4071f8b39f8f755 diff --git a/docs/subsystems/invariants.md b/docs/subsystems/invariants.md deleted file mode 100644 index 2d1856dfd0..0000000000 --- a/docs/subsystems/invariants.md +++ /dev/null @@ -1,88 +0,0 @@ -# Runtime Invariants - -English | [中文](invariants.zh.md) - -[dsh-invariants](../../packages/runtime-diagnostics/invariants) is the configurable registry service (`ctx.invariants`) for package-owned runtime invariant checks. It is one support-group package, not a three-package capability seam, and not part of the agent-loop spine: the registry owns selection, name reservation, child-fiber lifecycle, and package-attributed failure, while every workspace package publishes a `./invariant` companion plugin that registers checks under its exact npm package name. What a check may assert — authoritative event streams or mutable data, never service or method presence — is the runtime-invariants convention in [AGENTS.md](../../AGENTS.md#conventions). - -Source: [`packages/runtime-diagnostics/invariants/src/index.ts`](../../packages/runtime-diagnostics/invariants/src/index.ts) - -## Selection - -```ts type-equiv -/** Runtime invariant selection configured on the service plugin. */ -interface Config { - /** Global switch; defaults to `true`. */ - readonly enabled?: boolean - /** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */ - readonly package_allowlist?: string[] - /** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */ - readonly package_blocklist?: string[] -} -``` - -A package is selected when the service is enabled, the allowlist is empty or at least one pattern matches its full npm name, and no blocklist pattern matches — a blocklist match overrides an allowlist match. Entries compile with `new RegExp(source)`: matching is unanchored unless the source supplies `^` and `$`, and `/pattern/flags` syntax is not parsed. Validation fails loud at service startup: a blank, whitespace-padded, duplicate, or invalid entry throws instead of being skipped. A valid pattern may match no currently loaded package, so later loading and HMR stay deterministic; filters are fixed for the service lifetime ([README](../../packages/runtime-diagnostics/invariants/README.md)). - -## The installer - -```ts type-equiv -/** - * Throw a package-attributed invariant failure. - * @param message - violated package contract without the standard prefix. - * @returns never because reporting a violation throws. - */ -type InvariantFailure = (message: string) => never -``` - -```ts type-equiv -/** Install one package's checks into the registration's child context. */ -interface InvariantInstaller { - /** - * Install the package contribution. - * @param ctx - child context owned by this invariant registration. - * @param fail - reporter bound to the registering package name. - * @returns nothing, or a promise settling after asynchronous checks finish. - */ - (ctx: Context, fail: InvariantFailure): void | Promise - /** Services the child installer fiber may access. */ - readonly inject?: Inject -} -``` - -An enabled installer runs in a dedicated child Cordis fiber; `installer.inject` declares the services that fiber may access, and synchronous or asynchronous installer completion is joined before the registration succeeds. `fail(message)` throws `InvariantError` — `extends Error` with stable `code: 'INVARIANT'`, the owning `packageName`, and a message prefixed `invariant violated by "": …` — so a violation is attributable without the registry importing any product package. - -## The service - -`ctx.invariants.register(packageName, installer)` reserves one active registration for the full npm package name and returns its effect-scoped disposer. The reservation holds even when filters keep the installer inactive, so two plugins can never silently claim the same package name; a duplicate, blank, or whitespace-containing name throws. An installer failure disposes the child fiber and releases the reservation atomically. The service owns every registration fiber while the returned disposer also belongs to the companion fiber: unloading either side removes listeners, trace state, and the reservation, so a companion can reload and register the same name again without retained state. - -## The companion contract - -Every workspace package owns a `./invariant` companion ([package contract](../../packages/AGENTS.md)); publication and registration are exhaustive, but assertions are deliberately not synthetic. A companion installs a check only when its package owns an observable event or mutable-data relationship; otherwise it exports an empty installer whose leading comment starts `No runtime invariant:` and explains, package-specifically, why nothing is checkable. `pnpm run verify-package-invariants` mechanically rejects generated markers, unexplained empty installers, non-empty installers that omit or ignore the reporter, incorrect registration names, and incomplete export, publication, dependency, or bundle wiring ([mechanical-rule Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md)). The catalog of executable companions and the standard composition live in the [package README](../../packages/runtime-diagnostics/invariants/README.md). - - - - - -## Cordis API - -Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). - - - -### `ctx.invariants` — `InvariantRegistry` - -Package-owned invariant registry with global and regex-based selection. - -```ts cordis-catalog -/** - * Register one package's invariant installer. The package name is reserved - * even when filtering disables its checks. Enabled installers run in a child - * fiber; failure disposes that fiber and releases the reservation. - * @param packageName - full npm package name that owns the contribution. - * @param installer - listener or startup-check installer for the child context. - * @returns an effect-scoped disposer for the registration. - */ -register(packageName: string, installer: InvariantInstaller): () => void -``` - -Source: [`packages/runtime-diagnostics/invariants/src/index.ts`](../../packages/runtime-diagnostics/invariants/src/index.ts) - diff --git a/docs/subsystems/invariants.zh.md b/docs/subsystems/invariants.zh.md deleted file mode 100644 index 15760a9b11..0000000000 --- a/docs/subsystems/invariants.zh.md +++ /dev/null @@ -1,88 +0,0 @@ -# 运行时不变式 - -[English](invariants.md) | 中文 - -[dsh-invariants](../../packages/runtime-diagnostics/invariants) 是面向包自有运行时不变式检查的可配置注册表服务(`ctx.invariants`)。它是一个 support 组的包,不是三包能力 seam,也不属于 agent loop(智能体循环)主干:注册表拥有选择逻辑、名称保留、子 fiber 生命周期和归因到包的失败,而每个工作区包发布一个 `./invariant` 配套插件,以自己确切的 npm 包名注册检查。检查可以断言什么(权威事件流或可变数据,绝不是服务或方法是否存在)是 [AGENTS.md](../../AGENTS.md#conventions) 中的运行时不变式约定。 - -源码:[`packages/runtime-diagnostics/invariants/src/index.ts`](../../packages/runtime-diagnostics/invariants/src/index.ts) - -## 选择 - -```ts type-equiv -/** Runtime invariant selection configured on the service plugin. */ -interface Config { - /** Global switch; defaults to `true`. */ - readonly enabled?: boolean - /** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */ - readonly package_allowlist?: string[] - /** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */ - readonly package_blocklist?: string[] -} -``` - -一个包被选中的条件是:服务已启用,允许列表为空或至少一个模式匹配其完整 npm 名称,且没有任何阻止列表模式匹配;阻止列表匹配优先于允许列表匹配。条目用 `new RegExp(source)` 编译:除非模式自带 `^` 和 `$`,匹配不锚定;`/pattern/flags` 语法不被解析。校验在服务启动时明确报错:空白、首尾带空白、重复或无效的条目会抛出异常,而不是被跳过。有效模式可以不匹配任何当前已加载的包,因此后续加载与 HMR(热模块替换)保持确定性;过滤器在服务生命周期内固定不变([README](../../packages/runtime-diagnostics/invariants/README.zh.md))。 - -## 安装器 - -```ts type-equiv -/** - * Throw a package-attributed invariant failure. - * @param message - violated package contract without the standard prefix. - * @returns never because reporting a violation throws. - */ -type InvariantFailure = (message: string) => never -``` - -```ts type-equiv -/** Install one package's checks into the registration's child context. */ -interface InvariantInstaller { - /** - * Install the package contribution. - * @param ctx - child context owned by this invariant registration. - * @param fail - reporter bound to the registering package name. - * @returns nothing, or a promise settling after asynchronous checks finish. - */ - (ctx: Context, fail: InvariantFailure): void | Promise - /** Services the child installer fiber may access. */ - readonly inject?: Inject -} -``` - -被启用的安装器在专属的子 Cordis fiber 中运行;`installer.inject` 声明该 fiber 可以访问的服务,注册成功之前会先等待安装器同步或异步地执行完毕。`fail(message)` 抛出 `InvariantError`(`extends Error`,带稳定的 `code: 'INVARIANT'`、所属 `packageName`,以及前缀为 `invariant violated by "": …` 的消息),因此违规可归因,而注册表无需导入任何产品包。 - -## 服务 - -`ctx.invariants.register(packageName, installer)` 为完整 npm 包名保留唯一一个活跃注册,并返回其绑定到 effect 的 disposer。即使过滤器使安装器保持不活跃,保留依然成立,因此两个插件绝不可能静默地认领同一个包名;重复、空白或含空白字符的名称会抛出异常。安装器失败会原子地 dispose(资源释放)子 fiber 并释放保留。服务拥有每个注册 fiber,而返回的 disposer 同时属于配套插件的 fiber:卸载任一侧都会移除监听器、trace 状态和保留项,因此配套插件可以重载并再次注册同一名称,不留残余状态。 - -## 配套插件约定 - -每个工作区包都拥有一个 `./invariant` 配套插件([包约定](../../packages/AGENTS.md));发布与注册是穷尽式的,但刻意不合成断言。只有当包拥有某个可观察事件或某种可变数据关系时,配套插件才安装检查;否则它导出一个空安装器,其起始注释以 `No runtime invariant:` 开头,针对该包具体解释为什么没有可检查项。`pnpm run verify-package-invariants` 机械地拒绝「生成文件」标记、无解释的空安装器、遗漏或忽略报告器的非空安装器、错误的注册名称,以及不完整的导出、发布、依赖或打包接线([机械规则 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md))。可执行配套插件的目录与标准组合方式见[包 README](../../packages/runtime-diagnostics/invariants/README.zh.md)。 - - - - - -## Cordis API - -Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.zh.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). - - - -### `ctx.invariants` — `InvariantRegistry` - -Package-owned invariant registry with global and regex-based selection. - -```ts cordis-catalog -/** - * Register one package's invariant installer. The package name is reserved - * even when filtering disables its checks. Enabled installers run in a child - * fiber; failure disposes that fiber and releases the reservation. - * @param packageName - full npm package name that owns the contribution. - * @param installer - listener or startup-check installer for the child context. - * @returns an effect-scoped disposer for the registration. - */ -register(packageName: string, installer: InvariantInstaller): () => void -``` - -Source: [`packages/runtime-diagnostics/invariants/src/index.ts`](../../packages/runtime-diagnostics/invariants/src/index.ts) - diff --git a/docs/subsystems/session.i18n.yaml b/docs/subsystems/session.i18n.yaml index e4269bb273..238bec2cc3 100644 --- a/docs/subsystems/session.i18n.yaml +++ b/docs/subsystems/session.i18n.yaml @@ -45,20 +45,20 @@ en: 6e48ebd63b85f473 zh: c8ab986e975bbce2 /sessions/live-session-fork-api: - en: e779481af02174c6 - zh: 848a6a0a797c7d29 + en: c72c73ce4b4c697c + zh: 8a34785a92df0ad5 /sessions/why-a-turn-ended-turnendreasonmap: en: 966cc60273fb3911 zh: de5123ea9dbb0df2 /sessions/execution-enclosure-and-standalone-events: - en: 5d33a4901131123a - zh: c774d7d32cb2dcfe + en: 9091c5446e8cb9a2 + zh: 04c8c4fd5d0eaefc /sessions/the-end-seed-boundary-session-end-seed: en: 63b4059793255ca3 zh: 6bc7891c320249f4 /sessions/plugin-contributed-log-only-events: - en: 6b895a381f28a41c - zh: b2fe46ba9b5304c4 + en: 0a014a755b75449f + zh: 9d59d74490a1bcb2 /sessions/durability-contract: en: 62e3e4fbcba292a3 zh: cb6dcaf531621e85 diff --git a/docs/subsystems/session.md b/docs/subsystems/session.md index 415201d159..4d6e7fcd91 100644 --- a/docs/subsystems/session.md +++ b/docs/subsystems/session.md @@ -160,8 +160,8 @@ interface SessionEventMap { * keep ordinary restore and replay lifecycle boundaries. * * Only the `Session` constructor and `buildForkSeed` may create this marker. - * The invariant companion deliberately constrains nothing here, so a plugin - * appending one would silently classify every live bracket before it as seed history. + * Session append does not reject other writers, so a plugin appending one + * would silently classify every live bracket before it as seed history. * * An owner of a standalone open/close bracket (`compaction/start` … * `compaction/end`) reads it because seed history and live work are otherwise @@ -680,7 +680,7 @@ Everything else (`turn/*`, `step/*`, `assistant/attempt`, plugin-owned `llm/retr - `fork(source, boundary?, childSessionId?)` accepts a live `Session` object or live `SessionId`, selects source events through the inclusive `SessionSeq` boundary (default: current last event), requires the selected prefix to end outside an open turn, then creates a live child session with deep-cloned seed events, `parentSession`, `isSeeded: true`, the exact `inheritedEventCount`, and inherited `cwd`. -An explicit `boundary` lets callers fork from any stable between-turn position, including a previous `turn/end` or a later standalone log-only event, even if the source has newer events or an open current turn. The API rejects a prefix that ends inside an open turn instead of clipping silently. Broader execution-relation sanity stays in the existing `dsh-invariants` plugin and persistence repair path rather than being duplicated in `fork()`. `dsh-subagent-fork-in-process` keeps its completed-prefix clipping because tool-time delegation usually starts while the parent turn is open; ordinary session branching should make the requested boundary explicit. +An explicit `boundary` lets callers fork from any stable between-turn position, including a previous `turn/end` or a later standalone log-only event, even if the source has newer events or an open current turn. The API rejects a prefix that ends inside an open turn instead of clipping silently. `dsh-subagent-fork-in-process` keeps its completed-prefix clipping because tool-time delegation usually starts while the parent turn is open; ordinary session branching should make the requested boundary explicit. ## Why a turn ended: `TurnEndReasonMap` @@ -732,7 +732,7 @@ interface TurnEndReasonMap { A turn encloses one model-loop execution, not the whole session log. AgentLoop records injected `user/message` events only from entering pre-step batches inside a turn; plugin-owned log-only events may still appear between `turn/end` and the next `turn/start`, consuming event seqs without incrementing turn numbers. Persistence admits every contiguous accepted event into a bounded durable batch, while crash repair closes only a genuinely open trailing turn. A producer that needs an immediate durability barrier explicitly awaits `ctx.sessions.flush(session)`. -The optional `dsh-session/invariant` companion enforces the relations owned by core: turn and step numbering, execution-event enclosure, and same-step tool call/result pairing. Merge-extensible event relations belong to the plugin that declares them, so core does not reject an unknown event merely because no turn is open. See [the standalone-event decision](../../.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md). +Merge-extensible event relations belong to the plugin that declares them, so core does not reject an unknown event merely because no turn is open. See [the standalone-event decision](../../.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md). ## The end-seed boundary: `session/end-seed` @@ -746,7 +746,7 @@ Consumers that order Sessions by human activity exclude this boundary: picking a ## Plugin-contributed log-only events -A plugin may declaration-merge extra `SessionEventMap` types. These are **log-only**: NOT `SurfaceEventType`s (they carry no `surfaceOp` and contribute nothing to derived history). Their owner decides whether they belong to an open execution turn or may stand between turns, and enforces any relation in its own invariant companion. The generated [persistence log event catalog](../persistence-catalog.md) enumerates every core and plugin-contributed event; the compaction seam's `compaction/*` semantics are discussed on [compaction.md](compaction.md). +A plugin may declaration-merge extra `SessionEventMap` types. These are **log-only**: NOT `SurfaceEventType`s (they carry no `surfaceOp` and contribute nothing to derived history). Their owner decides whether they belong to an open execution turn or may stand between turns. The generated [persistence log event catalog](../persistence-catalog.md) enumerates every core and plugin-contributed event; the compaction seam's `compaction/*` semantics are discussed on [compaction.md](compaction.md). When several events in one plugin-owned family assemble into one Web Client Conversation Node, every start, update, result, resource, or interruption event in that family carries or independently derives the same stable business id. This requirement applies to correlated Node families, not to every Session event; it lets the client group each event without guessing from adjacency or scanning history. See the [Conversation subsystem](conversation.md). @@ -1051,8 +1051,8 @@ announce(session: Session): void * store owns the carrier, so callers (the checkpoint policy's per-request * barrier, goal-round-driver's idle checkpoint, teardown drains, and consumers * that flush themselves before reading storage) must come through here - * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner, - * one spelling, and the scoped-dispatch invariant can pin it. + * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner + * and one spelling. * @param session - the session whose buffered events must reach durable storage. * @returns whether at least one durability listener participated, after every * listener has settled successfully. @@ -1216,7 +1216,6 @@ Creation announcement during session publication. A synchronous throw vetoes and * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners * receive only sessions entered through that agent's context. * @param session - the session just entered and announced. - * @dshScopeScan unsupported * @mode emit */ 'session/created'(this: Scoped, session: Session): void @@ -1239,7 +1238,6 @@ Emitted once when an announced session leaves the store, including publication r * did not begin. Listener failures are logged and contained. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope. * @param session - the session that is no longer live in the store. - * @dshScopeScan unsupported * @mode emit */ 'session/disposed'(this: Scoped, session: Session): void @@ -1264,7 +1262,6 @@ Post-commit, fire-and-forget append feed. The listener snapshot resolves before * receive only events from sessions entered through that agent's context. * @param session - the session whose log grew. * @param event - the appended event, exactly as recorded. - * @dshScopeScan unsupported * @mode emit */ 'session/event'(this: Scoped, session: Session, event: SessionEvent): void @@ -1286,7 +1283,6 @@ Awaited parallel durability checkpoint: every listener runs and the caller await * caller awaits all of them, with no waterfall veto. Scope-filtered dispatch * (`@deepseek-ai/dsh-scope`) reuses the session's owner scope. * @param session - the session whose buffered events must reach durable storage. - * @dshScopeScan unsupported * @mode parallel */ 'session/flush'(this: Scoped, session: Session): Promise | void diff --git a/docs/subsystems/session.zh.md b/docs/subsystems/session.zh.md index e52c08e819..79b07f3bbb 100644 --- a/docs/subsystems/session.zh.md +++ b/docs/subsystems/session.zh.md @@ -160,8 +160,8 @@ interface SessionEventMap { * keep ordinary restore and replay lifecycle boundaries. * * Only the `Session` constructor and `buildForkSeed` may create this marker. - * The invariant companion deliberately constrains nothing here, so a plugin - * appending one would silently classify every live bracket before it as seed history. + * Session append does not reject other writers, so a plugin appending one + * would silently classify every live bracket before it as seed history. * * An owner of a standalone open/close bracket (`compaction/start` … * `compaction/end`) reads it because seed history and live work are otherwise @@ -682,7 +682,7 @@ declare class Session { - `fork(source, boundary?, childSessionId?)` 接受一个活跃的 `Session` 对象或活跃的 `SessionId`,选取到 `SessionSeq` boundary(含)为止的源事件(默认为当前最后一个事件),要求所选前缀结束时没有开放轮次,然后创建一个活跃的子会话,包含深克隆的 seed event、`parentSession`、`isSeeded: true`、精确 `inheritedEventCount` 及继承的 `cwd`。 -显式 `boundary` 允许调用者从任意稳定的轮次间位置 fork,包括之前的 `turn/end` 或更晚的独立纯日志事件,即使源会话有更新的事件或正在进行的轮次。API 拒绝结束于开放轮次内的前缀,而不是静默截断。更广泛的执行关系健全性检查留在既有的 `dsh-invariants` 插件和持久化修复路径中,不在 `fork()` 中重复。`dsh-subagent-fork-in-process` 保留其已完成前缀截断逻辑,因为工具调用时的委托通常在父轮次仍然打开时启动;普通的会话分支应显式指定请求的 boundary。 +显式 `boundary` 允许调用者从任意稳定的轮次间位置 fork,包括之前的 `turn/end` 或更晚的独立纯日志事件,即使源会话有更新的事件或正在进行的轮次。API 拒绝结束于开放轮次内的前缀,而不是静默截断。`dsh-subagent-fork-in-process` 保留其已完成前缀截断逻辑,因为工具调用时的委托通常在父轮次仍然打开时启动;普通的会话分支应显式指定请求的 boundary。 @@ -736,7 +736,7 @@ interface TurnEndReasonMap { 一个轮次包围一次模型循环执行,而不是整个会话日志。AgentLoop 只会在轮次内进入 pre-step 批次时记录注入的 `user/message` 事件;插件所属的纯日志事件仍可出现在 `turn/end` 与下一个 `turn/start` 之间,占用事件 seq 但不递增轮次编号。持久化会将每个连续且已接受的事件纳入有界持久化批次,而崩溃修复只关闭确实仍处于开放状态的尾部轮次。需要即时持久性屏障的生产方会显式等待 `ctx.sessions.flush(session)`。 -可选的 `dsh-session/invariant` 配套插件会强制核心拥有的关系:轮次与步骤编号、执行事件封闭,以及同一步骤内的工具调用/结果配对。可合并扩展事件的关系由声明它的插件拥有,因此核心不会仅因没有开放轮次就拒绝未知事件。见[独立事件决策](../../.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)。 +可合并扩展事件的关系由声明它的插件拥有,因此核心不会仅因没有开放轮次就拒绝未知事件。见[独立事件决策](../../.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)。 ## 种子结束边界:`session/end-seed` @@ -750,7 +750,7 @@ interface TurnEndReasonMap { ## 插件贡献的仅日志事件 -插件可以通过 declaration merging 添加额外的 `SessionEventMap` 类型。这些是**仅日志**事件:不是 `SurfaceEventType`(不携带 `surfaceOp`,不参与派生历史)。事件所有方决定它们属于一个开放的执行轮次,还是可以独立位于轮次之间,并在自己的不变量配套插件中强制所需关系。生成的[持久化日志事件目录](../persistence-catalog.zh.md)会列出每个核心或插件贡献的事件;压缩 seam 的 `compaction/*` 语义在 [compaction.md](compaction.zh.md) 中讨论。 +插件可以通过 declaration merging 添加额外的 `SessionEventMap` 类型。这些是**仅日志**事件:不是 `SurfaceEventType`(不携带 `surfaceOp`,不参与派生历史)。事件所有方决定它们属于一个开放的执行轮次,还是可以独立位于轮次之间。生成的[持久化日志事件目录](../persistence-catalog.zh.md)会列出每个核心或插件贡献的事件;压缩 seam 的 `compaction/*` 语义在 [compaction.md](compaction.zh.md) 中讨论。 如果同一个插件事件族中的多条事件要组装成一个 Web Client Conversation Node,该事件族中的每条 start、update、result、resource 或 interruption 事件都必须携带或独立推导出同一个稳定业务 id。此要求只约束需要关联的 Node 事件族,并不要求每条 Session 事件都有业务 id;Client 因此无须根据相邻关系猜测归属,也无须扫描历史。参见 [Conversation 子系统](conversation.zh.md)。 @@ -1055,8 +1055,8 @@ announce(session: Session): void * store owns the carrier, so callers (the checkpoint policy's per-request * barrier, goal-round-driver's idle checkpoint, teardown drains, and consumers * that flush themselves before reading storage) must come through here - * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner, - * one spelling, and the scoped-dispatch invariant can pin it. + * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner + * and one spelling. * @param session - the session whose buffered events must reach durable storage. * @returns whether at least one durability listener participated, after every * listener has settled successfully. @@ -1220,7 +1220,6 @@ Creation announcement during session publication. A synchronous throw vetoes and * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners * receive only sessions entered through that agent's context. * @param session - the session just entered and announced. - * @dshScopeScan unsupported * @mode emit */ 'session/created'(this: Scoped, session: Session): void @@ -1243,7 +1242,6 @@ Emitted once when an announced session leaves the store, including publication r * did not begin. Listener failures are logged and contained. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope. * @param session - the session that is no longer live in the store. - * @dshScopeScan unsupported * @mode emit */ 'session/disposed'(this: Scoped, session: Session): void @@ -1268,7 +1266,6 @@ Post-commit, fire-and-forget append feed. The listener snapshot resolves before * receive only events from sessions entered through that agent's context. * @param session - the session whose log grew. * @param event - the appended event, exactly as recorded. - * @dshScopeScan unsupported * @mode emit */ 'session/event'(this: Scoped, session: Session, event: SessionEvent): void @@ -1290,7 +1287,6 @@ Awaited parallel durability checkpoint: every listener runs and the caller await * caller awaits all of them, with no waterfall veto. Scope-filtered dispatch * (`@deepseek-ai/dsh-scope`) reuses the session's owner scope. * @param session - the session whose buffered events must reach durable storage. - * @dshScopeScan unsupported * @mode parallel */ 'session/flush'(this: Scoped, session: Session): Promise | void diff --git a/docs/subsystems/storage.md b/docs/subsystems/storage.md index 03b0fa1c67..674221a263 100644 --- a/docs/subsystems/storage.md +++ b/docs/subsystems/storage.md @@ -215,8 +215,7 @@ The mounted domain facility. Opens declared domains over routed backends; one fa async open(spec: S): Promise> /** - * Look up an open domain by name, untyped. Diagnostic surface (the package - * invariant cross-checks change events against live domain state); typed + * Look up an open domain by name, untyped. Diagnostic surface; typed * consumers hold the handle returned by {@link open}. * @param name - Domain name. * @returns the open domain runtime, or `undefined` when not open. diff --git a/docs/subsystems/storage.zh.md b/docs/subsystems/storage.zh.md index ded8783fbf..2a4366c84e 100644 --- a/docs/subsystems/storage.zh.md +++ b/docs/subsystems/storage.zh.md @@ -215,8 +215,7 @@ The mounted domain facility. Opens declared domains over routed backends; one fa async open(spec: S): Promise> /** - * Look up an open domain by name, untyped. Diagnostic surface (the package - * invariant cross-checks change events against live domain state); typed + * Look up an open domain by name, untyped. Diagnostic surface; typed * consumers hold the handle returned by {@link open}. * @param name - Domain name. * @returns the open domain runtime, or `undefined` when not open. diff --git a/docs/subsystems/subagent.i18n.yaml b/docs/subsystems/subagent.i18n.yaml index 5b6c01d39a..e8f17fc341 100644 --- a/docs/subsystems/subagent.i18n.yaml +++ b/docs/subsystems/subagent.i18n.yaml @@ -27,5 +27,5 @@ en: cb05cffdb837b7bc zh: a53133845d673f0a /subagent/in-process-backends-permission-depth-and-seed: - en: 3290c070c97c284d - zh: ebced35b081ea1c2 + en: 4f009d383e782b2d + zh: 0c11e25423968f24 diff --git a/docs/subsystems/subagent.md b/docs/subsystems/subagent.md index de96d75509..621ee4662e 100644 --- a/docs/subsystems/subagent.md +++ b/docs/subsystems/subagent.md @@ -459,7 +459,7 @@ The spawn and fork backends create an ordinary one-shot agent through `parent.ct - **Delegated permission** is captured before the first await. Auto and Full access parents append their captured `permission/preset` identity to the fresh child after fork seeding and sandbox/approval overrides. One-shot and continuable children share this path; cold resume reads only the child log. Read Only and Workspace Write retain the inherited sandbox override plus `approval: never`, so unmatched bundles remain `custom`. Each Auto child call is reviewed independently using existing `parentSession`, creation prompt, and authenticated human/direct-parent messages. The [Auto review decision](../../.agents/notes/implemented/feature/2026-08-28-auto-review.md) defines low/medium/high semantics; no delegation records, receipt, Header field, descriptor field, or Session format is added. - **Delegation depth** is durable `SessionHeader.delegationDepth` plus the merge-extensible runtime field `AgentOptions.subagentDepth`; absence means top-level depth zero, and the greater present value is authoritative. The seam owns both fields — the loop neither sets nor reads them — so an in-process child persists parent depth + 1, cold resume cannot lower it, and every start rejects a derived depth outside the safe-integer domain or above a defined absolute `request.maxDepth` cap. -- **Fork seeding** uses [`CreateAgentOptions.seed`](core.md#creation-and-ownership) (a `SessionEvent[]` prefix threaded through `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })`, the same primitive `ctx.agents.resume()` uses). The fork backend passes a *balanced completed-turn prefix* of the parent's log — the parent's events up to and including its last `turn/end` — so the seed is contiguous-from-0 and the [invariants](../../packages/runtime-diagnostics/invariants) replay accepts it (the in-flight, unbalanced turn is excluded). +- **Fork seeding** uses [`CreateAgentOptions.seed`](core.md#creation-and-ownership) (a `SessionEvent[]` prefix threaded through `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })`, the same primitive `ctx.agents.resume()` uses). The fork backend passes a *balanced completed-turn prefix* of the parent's log — the parent's events up to and including its last `turn/end` — so the seed is contiguous-from-0 and contains only balanced turns (the in-flight, unbalanced turn is excluded). `SubagentCatalogEntry` describes a direct child with complete or unknown-mode discovery information; `SubagentCatalogState` is the host-only projection state. `listChildren()` owns a live-preferred parent observation without opening child logs. Browser consumers read `subagentCatalog` through the shared Session projection store and combine membership with Session-list activity. `SubagentCatalogRow` belongs to recursive catalog listing. [The parent-catalog decision](../../.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.md) owns the persistent facts and read semantics. @@ -690,7 +690,6 @@ A published child settled. Scope-filtered dispatch uses the same delegating pare * parent carrier as `subagent/start`, so the lifecycle pair reaches the * same scoped audience. * @param info - the run identity and terminal outcome. - * @dshScopeScan unsupported * @mode emit */ 'subagent/end'(this: Scoped, info: SubagentRunEndInfo): void @@ -748,7 +747,6 @@ A provider established a published child. For in-process providers, `ctx.agents. * parent-scoped listener observes only its own delegations. Paired with * `subagent/end`. * @param info - the provider and published child identity. - * @dshScopeScan unsupported * @mode emit */ 'subagent/start'(this: Scoped, info: SubagentRunInfo): void diff --git a/docs/subsystems/subagent.zh.md b/docs/subsystems/subagent.zh.md index 843f0e0cd7..2ea0fe5cd8 100644 --- a/docs/subsystems/subagent.zh.md +++ b/docs/subsystems/subagent.zh.md @@ -463,7 +463,7 @@ spawn 和 fork 后端通过 `parent.ctx` 创建一个普通的单次 agent,将 - **委派权限**在首次 await 前捕获。Auto 与 Full access 父级在 fresh child 完成 fork seed 和 sandbox/approval override 后,追加捕获的 `permission/preset` 身份。单次与可继续 child 共用此路径;cold resume 只读取 child 日志。Read Only 与 Workspace Write 保留继承的 sandbox override 加 `approval: never`,不匹配预设的组合仍为 `custom`。每个 Auto child 调用都使用既有 `parentSession`、创建 prompt 和经过核验的 human/直接父级消息独立审查。[Auto review 决策](../../.agents/notes/implemented/feature/2026-08-28-auto-review.zh.md)定义 low/medium/high 语义;不增加 委派记录、receipt、Header 字段、descriptor 字段或 Session format。 - **委派深度**由持久 `SessionHeader.delegationDepth` 与可合并扩展的运行时字段 `AgentOptions.subagentDepth` 共同表示;缺失表示顶层深度为零,存在的较大值具有权威性。两个字段都归该 seam 所有——循环既不设置也不读取它们——因此进程内子 agent 会持久保存 parent 深度 + 1,冷恢复无法降低深度,而且每次 start 都会拒绝超出安全整数域、或高于已定义绝对 `request.maxDepth` 上限的派生深度。 -- **Fork 种子注入**使用 [`CreateAgentOptions.seed`](core.zh.md#creation-and-ownership)(一个 `SessionEvent[]` 前缀,经由 `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })` 传递,与 `ctx.agents.resume()` 使用的原语相同)。fork 后端传入父级日志的一段*平衡的已完成轮次前缀*——父级事件直到并包括其最后一个 `turn/end`——因此种子从 0 连续,[invariants](../../packages/runtime-diagnostics/invariants) 回放可以接受它(进行中的、未平衡的轮次被排除在外)。 +- **Fork 种子注入**使用 [`CreateAgentOptions.seed`](core.zh.md#creation-and-ownership)(一个 `SessionEvent[]` 前缀,经由 `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })` 传递,与 `ctx.agents.resume()` 使用的原语相同)。fork 后端传入父级日志的一段*平衡的已完成轮次前缀*——父级事件直到并包括其最后一个 `turn/end`——因此种子从 0 连续,且只包含平衡的轮次(进行中的、未平衡的轮次被排除在外)。 `SubagentCatalogEntry` 描述一条完整或未知模式的直接子级目录记录;`SubagentCatalogState` 是仅 host 使用的 projection state。`listChildren()` 拥有一次 live-preferred 父 Session observation,不打开子级日志。浏览器消费者通过共享 Session projection store 读取 `subagentCatalog`,并将成员关系与 Session 列表活动状态组合。`SubagentCatalogRow` 属于递归目录列表。[父目录决策](../../.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.zh.md) 规定持久事实与读取语义。 @@ -694,7 +694,6 @@ A published child settled. Scope-filtered dispatch uses the same delegating pare * parent carrier as `subagent/start`, so the lifecycle pair reaches the * same scoped audience. * @param info - the run identity and terminal outcome. - * @dshScopeScan unsupported * @mode emit */ 'subagent/end'(this: Scoped, info: SubagentRunEndInfo): void @@ -752,7 +751,6 @@ A provider established a published child. For in-process providers, `ctx.agents. * parent-scoped listener observes only its own delegations. Paired with * `subagent/end`. * @param info - the provider and published child identity. - * @dshScopeScan unsupported * @mode emit */ 'subagent/start'(this: Scoped, info: SubagentRunInfo): void diff --git a/docs/subsystems/todo.i18n.yaml b/docs/subsystems/todo.i18n.yaml index ff23d182e0..eff1b60972 100644 --- a/docs/subsystems/todo.i18n.yaml +++ b/docs/subsystems/todo.i18n.yaml @@ -3,11 +3,11 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/todo.md /todo: - en: 157e8c91aeaf9f64 - zh: c568d90eecee7b12 + en: 92e62f0694b67bea + zh: 1759d71cfa980af9 /todo/todoitem-one-list-entry: en: 3580467f183af02a zh: e3fc7208b0c2ee59 -/todo/durable-event-and-invariant: - en: ae4046c1260671ae - zh: 4246b14e1965487f +/todo/durable-event: + en: 5e00744b8bf1d72f + zh: c85327d075675630 diff --git a/docs/subsystems/todo.md b/docs/subsystems/todo.md index 70eca60ff4..8a5306793b 100644 --- a/docs/subsystems/todo.md +++ b/docs/subsystems/todo.md @@ -2,7 +2,7 @@ English | [中文](todo.zh.md) -The durable todo vocabulary owned by [`@deepseek-ai/dsh-tool-todo`](../../packages/todo/tool-todo/README.md). The model-facing tool replaces one agent session's whole list; the package also owns the event declaration, replay projection, and invariant companion. Tool behavior and configuration are on the [package README](../../packages/todo/tool-todo/README.md). +The durable todo vocabulary owned by [`@deepseek-ai/dsh-tool-todo`](../../packages/todo/tool-todo/README.md). The model-facing tool replaces one agent session's whole list; the package also owns the event declaration and replay projection. Tool behavior and configuration are on the [package README](../../packages/todo/tool-todo/README.md). Source: [`packages/todo/tool-todo/src/types.ts`](../../packages/todo/tool-todo/src/types.ts) @@ -27,6 +27,6 @@ interface TodoItem { } ``` -## Durable event and invariant +## Durable event -The package declaration-merges `todo/write: { todos: TodoItem[] }` into `SessionEventMap`. The event is log-only and carries the complete replacement list; the generated [persistence catalog](../persistence-catalog.md#todowrite--log-only) records its declaration site. The package's invariant companion validates existing and newly announced sessions in one pass, then tracks committed turn boundaries incrementally so every live `todo/write` is checked before append without rescanning the log. +The package declaration-merges `todo/write: { todos: TodoItem[] }` into `SessionEventMap`. The event is log-only and carries the complete replacement list; the generated [persistence catalog](../persistence-catalog.md#todowrite--log-only) records its declaration site. diff --git a/docs/subsystems/todo.zh.md b/docs/subsystems/todo.zh.md index 74f76f59b6..09015a16de 100644 --- a/docs/subsystems/todo.zh.md +++ b/docs/subsystems/todo.zh.md @@ -2,7 +2,7 @@ [English](todo.md) | 中文 -本页记录 [`@deepseek-ai/dsh-tool-todo`](../../packages/todo/tool-todo/README.zh.md) 拥有的持久 todo 词汇。面向模型的工具会整体替换一个 agent(智能体)会话的列表;该包还拥有事件声明、回放投影和不变量配套插件。工具行为与配置见[包 README](../../packages/todo/tool-todo/README.zh.md)。 +本页记录 [`@deepseek-ai/dsh-tool-todo`](../../packages/todo/tool-todo/README.zh.md) 拥有的持久 todo 词汇。面向模型的工具会整体替换一个 agent(智能体)会话的列表;该包还拥有事件声明和回放投影。工具行为与配置见[包 README](../../packages/todo/tool-todo/README.zh.md)。 源码:[`packages/todo/tool-todo/src/types.ts`](../../packages/todo/tool-todo/src/types.ts) @@ -27,6 +27,6 @@ interface TodoItem { } ``` -## 持久事件与不变量 +## 持久事件 -该包通过声明合并把 `todo/write: { todos: TodoItem[] }` 加入 `SessionEventMap`。此事件仅写入日志,并携带完整替换列表;生成的[持久化目录](../persistence-catalog.zh.md#todowrite--log-only)会记录其声明位置。该包的不变量配套插件会单次遍历校验现有会话和新发布的会话,随后增量追踪已提交的轮次边界,使每个实时 `todo/write` 都能在追加前得到校验,而无需重新扫描日志。 +该包通过声明合并把 `todo/write: { todos: TodoItem[] }` 加入 `SessionEventMap`。此事件仅写入日志,并携带完整替换列表;生成的[持久化目录](../persistence-catalog.zh.md#todowrite--log-only)会记录其声明位置。 diff --git a/docs/subsystems/typert.md b/docs/subsystems/typert.md index 2444ee2fc4..f0ef75f223 100644 --- a/docs/subsystems/typert.md +++ b/docs/subsystems/typert.md @@ -442,8 +442,6 @@ listPackages(filter: TypertPackageFilter = {}): TypertPackageRecord[] toJSONSchema(key: string, params?: z.core.ToJSONSchemaParams): z.core.JSONSchema.BaseSchema ``` -Types: [TypertContribution](invariants.md) · [TypertFace](invariants.md) · [TypertPackageFilter](invariants.md) · [TypertPackageRecord](invariants.md) · [TypertSchemaFilter](invariants.md) · [TypertSchemaRecord](invariants.md) - Source: [`packages/typert/registry/src/service.ts`](../../packages/typert/registry/src/service.ts) diff --git a/docs/subsystems/typert.zh.md b/docs/subsystems/typert.zh.md index 60932a3b77..e2414dfc5e 100644 --- a/docs/subsystems/typert.zh.md +++ b/docs/subsystems/typert.zh.md @@ -442,8 +442,6 @@ listPackages(filter: TypertPackageFilter = {}): TypertPackageRecord[] toJSONSchema(key: string, params?: z.core.ToJSONSchemaParams): z.core.JSONSchema.BaseSchema ``` -Types: [TypertContribution](invariants.zh.md) · [TypertFace](invariants.zh.md) · [TypertPackageFilter](invariants.zh.md) · [TypertPackageRecord](invariants.zh.md) · [TypertSchemaFilter](invariants.zh.md) · [TypertSchemaRecord](invariants.zh.md) - Source: [`packages/typert/registry/src/service.ts`](../../packages/typert/registry/src/service.ts) diff --git a/docs/subsystems/workflow.i18n.yaml b/docs/subsystems/workflow.i18n.yaml index fdd5f9c496..1a92e83eaf 100644 --- a/docs/subsystems/workflow.i18n.yaml +++ b/docs/subsystems/workflow.i18n.yaml @@ -24,5 +24,5 @@ en: de123821a128c1d1 zh: e50bcaea2812575a /workflow/durable-chat-records: - en: 59896f51d9696ffa - zh: a12b33117f60d1fe + en: eefba4d75e7bcfe9 + zh: feeeaf660f97f96d diff --git a/docs/subsystems/workflow.md b/docs/subsystems/workflow.md index 8f56774699..ab401f806f 100644 --- a/docs/subsystems/workflow.md +++ b/docs/subsystems/workflow.md @@ -123,7 +123,7 @@ The `workflow/*` events (`workflow/start`, `workflow/phase`, `workflow/log`, `wo The top-level `dsh-tool-workflow` consumer projects display facts into its calling parent Session without changing execution ownership. It writes `tool-workflow/run-start` after a run is accepted, pairs member start and end by `runId + seq`, and writes `tool-workflow/run-end` only after the result is known and disposal reaches quiescence. Nested transport calls write no record. The first append failure disables later writes for that run, so the log remains empty or a legal continuous prefix and the tool result is unchanged. -`dsh-tool-workflow/invariant` validates the same protocol before live commit and when a Session is loaded: one start per run, positive unique member sequences, paired member endings, no run ending with open members, and no updates after the run ending. A missing member ending or run ending at the log tail is valid interruption evidence rather than corruption. +The record protocol allows one start per run, positive unique member sequences, paired member endings, no run ending with open members, and no updates after the run ending. A missing member ending or run ending at the log tail is valid interruption evidence rather than corruption. `dsh-client-ui-workflow-run` folds the four events through the Conversation Node engine into one `workflow-run` Chat node anchored at the run-start sequence, after the original workflow tool node. Phase groups come only from actual member starts and preserve exact strings, including the distinction between an omitted phase and `''`. Closed Locations turn missing terminal facts into interrupted presentation. The [UI package README](../../packages/client/ui-workflow-run/README.md) owns disclosure, status, and same-parent local navigation behavior. diff --git a/docs/subsystems/workflow.zh.md b/docs/subsystems/workflow.zh.md index dd24f920f6..4608015dae 100644 --- a/docs/subsystems/workflow.zh.md +++ b/docs/subsystems/workflow.zh.md @@ -123,7 +123,7 @@ interface WorkflowRun { 顶层 `dsh-tool-workflow` 消费方把展示事实投影到调用它的父 Session,同时不改变执行所有权。运行接受后写 `tool-workflow/run-start`,以 `runId + seq` 配对成员开始与结束,并且只在结果已取得且 dispose 完全停稳后写 `tool-workflow/run-end`。嵌套 transport 调用不写记录。第一次 append 失败会禁用本运行后续写入,因此日志保持为空或合法连续前缀,工具结果不变。 -`dsh-tool-workflow/invariant` 会在实时提交前和 Session 加载时校验同一协议:每个运行只有一个 start,成员序号为正且唯一,成员 end 必须配对,仍有开放成员时不能结束运行,运行结束后不能继续更新。日志尾部缺少成员 end 或 run end 是有效的中断证据,不是损坏。 +该记录协议规定:每个运行只有一个 start,成员序号为正且唯一,成员 end 必须配对,仍有开放成员时不能结束运行,运行结束后不能继续更新。日志尾部缺少成员 end 或 run end 是有效的中断证据,不是损坏。 `dsh-client-ui-workflow-run` 通过 Conversation Node 引擎把四类事件折叠为一个 `workflow-run` Chat 节点,以 run-start 序号锚定在原工作流工具节点之后。阶段组只来自真正开始过的成员,并保留精确字符串,包括字段缺省与 `''` 的区别。Location 关闭时,缺失终点会显示为已中断。[界面包 README](../../packages/client/ui-workflow-run/README.zh.md)负责定义 disclosure、状态与同父本地导航行为。 diff --git a/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.i18n.yaml b/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.i18n.yaml new file mode 100644 index 0000000000..18689b57d4 --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.md +/: + en: 3cb412fe69354a6b + zh: fbd2dd7abcd08ed7 +/runtime-invariant-plugins-are-removed: + en: 573fd5239a856cab + zh: edb2683d049a81b7 +/runtime-invariant-plugins-are-removed/change: + en: 58c246cfc02ab19d + zh: 4826b8cbb34e1363 +/runtime-invariant-plugins-are-removed/migration: + en: b42a35a30b792f84 + zh: 0ddbccc4b93410a3 diff --git a/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.md b/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.md new file mode 100644 index 0000000000..e42dc95ac6 --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.md @@ -0,0 +1,23 @@ +--- +kind: upgrade-guide +description: "The runtime invariant registry package and every package's `./invariant` subpath export are removed." +--- + +# Runtime invariant plugins are removed + +English | [中文](guide.zh.md) + +## Change + +In v0.2.0-rc.2, `@deepseek-ai/dsh-invariants` provided the `ctx.invariants` service, and workspace packages such as `@deepseek-ai/dsh-session`, `@deepseek-ai/dsh-agent`, `@deepseek-ai/dsh-scope`, and `@deepseek-ai/dsh-agent-loop` published `./invariant` companion plugins that registered checks with it. The `sdk-minimal` profile mounted five of these rows with ids `invariants`, `session-invariant`, `agent-invariant`, `scope-invariant`, and `agent-loop-invariant`. + +The next release no longer publishes `@deepseek-ai/dsh-invariants`, `InvariantRegistry`, `InvariantInstaller`, `InvariantFailure`, `InvariantError`, or any `/invariant` subpath. The `sdk-minimal` profile no longer contains the five rows. The `credentials/reference-updated`, `credentials/record-updated`, `authorization/settled`, and `llm/adapters-updated` emitters contain and log every listener failure, including errors with `code: 'INVARIANT'`, instead of rethrowing them. + +A `cordis.yml`, patch, or overlay that names a removed module fails to load it. A patch that targets one of the five `sdk-minimal` ids logs `patch: entry not found`. TypeScript code that imports a removed module or symbol no longer compiles. + +## Migration + +1. Delete rows whose `name` is `@deepseek-ai/dsh-invariants` or ends in `/invariant` from `cordis.yml`, `$DSH_HOME/profiles//cordis.patch.yml`, and `--patch` overlays. +2. Delete patch entries that target the ids `invariants`, `session-invariant`, `agent-invariant`, `scope-invariant`, or `agent-loop-invariant`. +3. Remove `@deepseek-ai/dsh-invariants` from `package.json` dependencies and delete imports of it and of `/invariant` subpaths. A plugin that relied on a rethrown `INVARIANT` failure from one of the four emitters must report the failure through its own channel. +4. Confirm: `dsh` starts the profile without a module-not-found error or `patch: entry ... not found` warning, and `tsc` reports no missing module or export. diff --git a/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.zh.md b/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.zh.md new file mode 100644 index 0000000000..39e076d1bf --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.zh.md @@ -0,0 +1,23 @@ +--- +kind: upgrade-guide +description: "运行时不变式注册表包和每个包的 `./invariant` 子路径导出均被移除。" +--- + +# 运行时不变式插件被移除 + +[English](guide.md) | 中文 + +## 变更 + +在 v0.2.0-rc.2 中,`@deepseek-ai/dsh-invariants` 提供 `ctx.invariants` 服务,`@deepseek-ai/dsh-session`、`@deepseek-ai/dsh-agent`、`@deepseek-ai/dsh-scope`、`@deepseek-ai/dsh-agent-loop` 等 workspace 包发布向该服务注册检查的 `./invariant` 配套插件。`sdk-minimal` profile 挂载其中五行,id 为 `invariants`、`session-invariant`、`agent-invariant`、`scope-invariant` 和 `agent-loop-invariant`。 + +下一版本不再发布 `@deepseek-ai/dsh-invariants`、`InvariantRegistry`、`InvariantInstaller`、`InvariantFailure`、`InvariantError`,也不再发布任何 `/invariant` 子路径。`sdk-minimal` profile 不再包含这五行。`credentials/reference-updated`、`credentials/record-updated`、`authorization/settled` 和 `llm/adapters-updated` 的发出方会隔离并记录每个监听器失败,包括 `code: 'INVARIANT'` 的错误,不再重新抛出。 + +引用已移除模块的 `cordis.yml`、patch 或 overlay 无法加载该模块。指向上述五个 `sdk-minimal` id 之一的 patch 会记录 `patch: entry not found`。导入已移除模块或符号的 TypeScript 代码无法编译。 + +## 迁移 + +1. 从 `cordis.yml`、`$DSH_HOME/profiles//cordis.patch.yml` 和 `--patch` overlay 中删除 `name` 为 `@deepseek-ai/dsh-invariants` 或以 `/invariant` 结尾的行。 +2. 删除指向 id `invariants`、`session-invariant`、`agent-invariant`、`scope-invariant` 或 `agent-loop-invariant` 的 patch 条目。 +3. 从 `package.json` 依赖中移除 `@deepseek-ai/dsh-invariants`,并删除对它和 `/invariant` 子路径的导入。依赖上述四个发出方重新抛出 `INVARIANT` 失败的插件,必须通过自己的通道报告该失败。 +4. 确认:`dsh` 启动该 profile 时没有模块未找到错误或 `patch: entry ... not found` 警告,`tsc` 不再报告缺失的模块或导出。 diff --git a/package.json b/package.json index f5ce66dc9e..989e7b51d8 100644 --- a/package.json +++ b/package.json @@ -110,9 +110,7 @@ "verify-package-paths": "tsx scripts/verify-package-paths.ts", "verify-dsh-package-licenses": "tsx scripts/verify-dsh-package-licenses.ts", "verify-config-source-ownership": "tsx scripts/verify-config-source-ownership.ts", - "verify-package-invariants": "tsx scripts/verify-package-invariants.ts", "verify-package-meta": "tsx scripts/verify-package-meta.ts", - "verify-built-package-invariants": "node scripts/verify-built-package-invariants.mjs", "verify-package-readme-model-experience": "tsx scripts/verify-package-readme-model-experience.ts", "verify-package-readme-summaries": "tsx scripts/verify-package-readme-summaries.ts", "verify-mermaid": "tsx scripts/verify-mermaid.ts", @@ -186,8 +184,6 @@ "gen-third-party-notices": "tsx scripts/gen-third-party-notices.ts", "verify-third-party-notices": "tsx scripts/gen-third-party-notices.ts --check", "gen-module-graph": "tsx scripts/gen-module-graph.ts", - "gen-scoped-events": "node --max-old-space-size=4096 --import tsx/esm scripts/gen-scoped-events.ts", - "verify-scoped-events": "node --max-old-space-size=4096 --import tsx/esm scripts/gen-scoped-events.ts --check", "verify-module-graph": "tsx scripts/gen-module-graph.ts --check", "constraints": "tsx scripts/check-workspace-constraints.ts", "doc-sync": "tsx scripts/run-gates.ts doc-sync", diff --git a/packages/AGENTS.md b/packages/AGENTS.md index f0c49d55e5..8c071dac2e 100644 --- a/packages/AGENTS.md +++ b/packages/AGENTS.md @@ -16,11 +16,10 @@ These package-specific rules supplement the repo-wide [conventions](../AGENTS.md - **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** through the HMR-safety test required by [testing policy](../docs/testing.md): dispose the fiber and observe removal. - **Specs run concurrently** in forked workers beside other gate processes. Own each acquired port, path, and child process through teardown; a spec that passes only when run alone is a defect in the spec ([execution model](../docs/testing.md#how-specs-execute)). -- **Publish `./invariant` only for diverging observations.** Check an owned relation under the manifest name. Otherwise omit wiring and give the package-specific README reason. Empty companions and ignored reporters fail [`verify-package-invariants`](../.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md). [Naming rules](../docs/cookbook/adding-a-package.md#name-the-role-that-exists): -- **Package tsconfig:** extends `tsconfig.base.json` (Client: `tsconfig.base.client.json`), sets `rootDir: src` and `outDir: lib/types`, references workspace dependencies, references `runtime-diagnostics/invariants` only when the package publishes `./invariant`, and registers in one aggregate. Packages with distinct Host and Client compiler faces use `tsconfig.host.json` and `tsconfig.client.json` leaves plus a solution-only root; ordinary two-entry Client plugins do not split ([layout](../docs/development.md#typescript-project-layout)). +- **Package tsconfig:** extends `tsconfig.base.json` (Client: `tsconfig.base.client.json`), sets `rootDir: src` and `outDir: lib/types`, references workspace dependencies, and registers in one aggregate. Packages with distinct Host and Client compiler faces use `tsconfig.host.json` and `tsconfig.client.json` leaves plus a solution-only root; ordinary two-entry Client plugins do not split ([layout](../docs/development.md#typescript-project-layout)). - `src/types.ts` contains only types — no runtime code. - Tests live at package level under `tests/`, not `src/__tests__/`. - Update package README and JSDoc contracts in the same commit as behavior, and verify them against code with [dsh-prose-standard](../.agents/skills/dsh-prose-standard/SKILL.md). Group READMEs declare subsystem ownership through a canonical English page link or justified [exemption](../scripts/verify-subsystem-pages.ts). diff --git a/packages/README.i18n.yaml b/packages/README.i18n.yaml index f9879d6dcd..331665509e 100644 --- a/packages/README.i18n.yaml +++ b/packages/README.i18n.yaml @@ -15,17 +15,17 @@ en: ffc55792d6e922e1 zh: 83bd759638857e2d /packages/package-groups: - en: d865abbfa65d1d4f - zh: ea079b795b51dd55 + en: 989c5a7131853dc6 + zh: 259d15e2bceb0bb5 /packages/release-expectations: - en: 1ab880da3642bde1 - zh: 6e168322f026a9cf + en: 7a152a53e2b1f2e5 + zh: 3c154c20db49713d /packages/dependencies: en: 9576e7c486bf123f zh: fedd18da3e27ad00 /packages/package-readme-contracts: - en: b36505dacbfe2ea1 - zh: 51e25517fae092ba + en: 9f35ef77d1b4c388 + zh: 39cdc988e87a96c2 /packages/dev-note: en: 5de1d6304e00c58f zh: 8d8225361cfa29e5 diff --git a/packages/README.md b/packages/README.md index cfb5ea6c74..3322d3cb7d 100644 --- a/packages/README.md +++ b/packages/README.md @@ -81,7 +81,6 @@ Every package lives in exactly one group; new packages join existing groups, and | [`host/`](host/README.md) | Web GUI host services, directory picking, application launch, plugin inventory, and product telemetry | | [`client/`](client/README.md) | Web-GUI browser half: shell, wire, object services, slots, `ui-*` plugins | | [`test-support/`](test-support/README.md) | Test infrastructure (testkits, replay, Loader smokes) | -| [`runtime-diagnostics/`](runtime-diagnostics/README.md) | Runtime diagnostics: package-owned invariant checks and reports | | [`util/`](util/README.md) | Low-level zero-dependency utilities shared across groups (`Branded`, home/path helpers, timeout, retention) | ----- @@ -89,7 +88,7 @@ Every package lives in exactly one group; new packages join existing groups, and ## Release expectations -Most groups are product — stable API. The exceptions: `experimental/` publishes without stability or support promises, and `test-support/`, `runtime-diagnostics/`, and `util/` are support with lower compatibility expectations. +Most groups are product — stable API. The exceptions: `experimental/` publishes without stability or support promises, and `test-support/` and `util/` are support with lower compatibility expectations. ----- @@ -105,7 +104,7 @@ The dependency graph is generated: [docs/module-graph.md](../docs/module-graph.m ## Package README contracts -Every package README covers purpose, configuration, extension points, and [Model Experience](../docs/cookbook/adding-a-package.md#4-write-the-package-readme) unless the model-agnostic [omission allowlist](../scripts/verify-package-readme-model-experience.ts) exempts it. It also carries `## Known Limitations and Deferred Work` or uses its [allowlist](../scripts/verify-package-readme-limitations.ts). Package conventions — exports, service access, invariants, tests — live in [packages/AGENTS.md](AGENTS.md). +Every package README covers purpose, configuration, extension points, and [Model Experience](../docs/cookbook/adding-a-package.md#4-write-the-package-readme) unless the model-agnostic [omission allowlist](../scripts/verify-package-readme-model-experience.ts) exempts it. It also carries `## Known Limitations and Deferred Work` or uses its [allowlist](../scripts/verify-package-readme-limitations.ts). Package conventions — exports, service access, tests — live in [packages/AGENTS.md](AGENTS.md). ----- diff --git a/packages/README.zh.md b/packages/README.zh.md index 2edb62d8cd..c187e74f52 100644 --- a/packages/README.zh.md +++ b/packages/README.zh.md @@ -81,7 +81,6 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 | [`host/`](host/README.zh.md) | Web GUI 宿主服务、目录选择、应用启动、插件清单和产品遥测 | | [`client/`](client/README.zh.md) | web GUI 浏览器半侧:shell、协议层、对象服务、slot、`ui-*` 插件 | | [`test-support/`](test-support/README.zh.md) | 测试基础设施(testkit、回放、Loader 冒烟测试) | -| [`runtime-diagnostics/`](runtime-diagnostics/README.zh.md) | 运行时诊断:按包归属的运行时不变式检查与报告 | | [`util/`](util/README.zh.md) | 组间共享的低层零依赖工具(`Branded`、home/路径辅助函数、超时、留存) | ----- @@ -89,7 +88,7 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 ## 发布预期 -大多数组属于产品组,提供稳定 API。例外:`experimental/` 发布时不提供稳定性或支持承诺,`test-support/`、`runtime-diagnostics/` 与 `util/` 是兼容性预期较低的支持组。 +大多数组属于产品组,提供稳定 API。例外:`experimental/` 发布时不提供稳定性或支持承诺,`test-support/` 与 `util/` 是兼容性预期较低的支持组。 ----- @@ -105,7 +104,7 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 ## 包 README 约定 -每个包 README 都覆盖用途、配置、扩展点与[模型体验](../docs/cookbook/adding-a-package.zh.md#4-write-the-package-readme),列入模型无关[省略允许清单](../scripts/verify-package-readme-model-experience.ts)的包除外。它还要包含 `## Known Limitations and Deferred Work`,或列入其[允许清单](../scripts/verify-package-readme-limitations.ts)。包约定——导出、服务访问、不变式、测试——见 [packages/AGENTS.md](AGENTS.md)。 +每个包 README 都覆盖用途、配置、扩展点与[模型体验](../docs/cookbook/adding-a-package.zh.md#4-write-the-package-readme),列入模型无关[省略允许清单](../scripts/verify-package-readme-model-experience.ts)的包除外。它还要包含 `## Known Limitations and Deferred Work`,或列入其[允许清单](../scripts/verify-package-readme-limitations.ts)。包约定——导出、服务访问、测试——见 [packages/AGENTS.md](AGENTS.md)。 ----- diff --git a/packages/acp/acp/README.i18n.yaml b/packages/acp/acp/README.i18n.yaml index 1b3bf620a1..540d77f43a 100644 --- a/packages/acp/acp/README.i18n.yaml +++ b/packages/acp/acp/README.i18n.yaml @@ -36,8 +36,8 @@ en: ad90a93d87b6caa4 zh: d7e90af3ea7a678c /deepseek-ai-dsh-acp/understand-the-implementation/source-map: - en: 837330574e01d196 - zh: 46f29717d175f7bb + en: 9f85ae4ecdb5d022 + zh: dc2b911360768b9e /deepseek-ai-dsh-acp/understand-the-implementation/admission-and-prompt-settlement: en: 6485d449b5945b4a zh: 90e56a9b410c9c42 diff --git a/packages/acp/acp/README.md b/packages/acp/acp/README.md index a303967330..3fe8c04ff2 100644 --- a/packages/acp/acp/README.md +++ b/packages/acp/acp/README.md @@ -102,7 +102,6 @@ The decision history lives in the [ACP as an automation-only protocol note](../. | [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, `AgentSideConnection` wiring, per-session records, admission and settlement, teardown | | [`src/content.ts`](src/content.ts) | Wire-content admission and projection: image validation, route recheck, prompt reconstruction, assistant block conversion | | [`src/codec.ts`](src/codec.ts) | Pure turn-ending to ACP `stopReason` mapping | -| — | No runtime invariant companion is published; this transport owns no durable package-local event stream; protocol and lifecycle tests cover its mapping. | ### Admission and prompt settlement diff --git a/packages/acp/acp/README.zh.md b/packages/acp/acp/README.zh.md index 7f7f2f43ac..2b7404b56b 100644 --- a/packages/acp/acp/README.zh.md +++ b/packages/acp/acp/README.zh.md @@ -102,7 +102,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、`AgentSideConnection` 接线、按会话记录、准入与结算、清理 | | [`src/content.ts`](src/content.ts) | 协议内容准入与投影:图片校验、路由重查、提示词重建、assistant 块转换 | | [`src/codec.ts`](src/codec.ts) | 轮次结束到 ACP `stopReason` 的纯映射 | -| — | 不发布运行时不变式伴生入口;本传输不拥有持久包内事件流;协议与生命周期测试覆盖其映射。 | ### 准入与提示词结算 diff --git a/packages/api/account-controller/README.i18n.yaml b/packages/api/account-controller/README.i18n.yaml index 352b915310..f678c0c9f0 100644 --- a/packages/api/account-controller/README.i18n.yaml +++ b/packages/api/account-controller/README.i18n.yaml @@ -18,8 +18,8 @@ en: 5ad12300bbc151ef zh: 3ccfb6046ab423ef /deepseek-ai-dsh-api-account-controller/understand-the-implementation: - en: 3d899b3480391175 - zh: 803808ca9691366e + en: d76d0fa4dc48033d + zh: e193caafd78250e9 /deepseek-ai-dsh-api-account-controller/further-exploration: en: e5d7405e19fb6d00 zh: f92bdfe5247d758b diff --git a/packages/api/account-controller/README.md b/packages/api/account-controller/README.md index dabbb13b52..3e7ff7b0bc 100644 --- a/packages/api/account-controller/README.md +++ b/packages/api/account-controller/README.md @@ -31,7 +31,7 @@ Every operation that reaches Platform takes the calling UI's `AccountClientMetad ## Understand the implementation -The controller forwards operations to the account service and maintains no independent account state; no invariant companion is published. +The controller forwards operations to the account service and maintains no independent account state. ## Further Exploration diff --git a/packages/api/account-controller/README.zh.md b/packages/api/account-controller/README.zh.md index 63c05a43ff..7007cfdd6f 100644 --- a/packages/api/account-controller/README.zh.md +++ b/packages/api/account-controller/README.zh.md @@ -31,7 +31,7 @@ account 命名空间提供 getState、getProfile / getBalance、getUnnotifiedBon ## 理解实现 -控制器向账号服务转发操作,不维护独立的账号状态,因此不发布 invariant。 +控制器向账号服务转发操作,不维护独立的账号状态。 ## 深入探索 diff --git a/packages/api/gateway/README.i18n.yaml b/packages/api/gateway/README.i18n.yaml index 7305ddc267..e8cf3b7f4b 100644 --- a/packages/api/gateway/README.i18n.yaml +++ b/packages/api/gateway/README.i18n.yaml @@ -30,5 +30,5 @@ en: 3ade2903f40a0a98 zh: 9af1d557fefdbd89 /deepseek-ai-dsh-api-gateway/known-limitations-and-deferred-work/dev-note: - en: 365f5c32056ff39a - zh: 9556fd36702f5ecd + en: 10727471030aee01 + zh: e6bee82abd12127d diff --git a/packages/api/gateway/README.md b/packages/api/gateway/README.md index 6bb4f02cf9..41cfc3a324 100644 --- a/packages/api/gateway/README.md +++ b/packages/api/gateway/README.md @@ -100,6 +100,4 @@ None. -**Runtime invariant:** No companion is published. Host calls re-read authoritative Cordis and Typert state, while Client methods, descriptors, and `$on` subscriptions mutate in one owned effect. - The ./stream-protocol export supplies the shared Remote stream framing and parser to native Desktop callers. These callers use the same authenticated WebSocket endpoint as the browser client. diff --git a/packages/api/gateway/README.zh.md b/packages/api/gateway/README.zh.md index 97fe9897c9..c73b0f45b6 100644 --- a/packages/api/gateway/README.zh.md +++ b/packages/api/gateway/README.zh.md @@ -100,6 +100,4 @@ Client waterfall 的 Context 解析保持同步。解析器可以返回借用的 -**运行时不变式:** 不发布伴生入口。Host 调用会重新读取权威的 Cordis 与 Typert 状态,Client 方法、描述符与 `$on` 订阅的变更则统一归属同一个 effect。 - ./stream-protocol 导出为原生桌面调用方提供共享的 Remote 流帧格式和解析器。它们与浏览器客户端使用同一个经过认证的 WebSocket 端点。 diff --git a/packages/api/job-controller/README.i18n.yaml b/packages/api/job-controller/README.i18n.yaml index b89d3a11c2..44db98b258 100644 --- a/packages/api/job-controller/README.i18n.yaml +++ b/packages/api/job-controller/README.i18n.yaml @@ -30,5 +30,5 @@ en: c0f98753f7ba6b9b zh: 4022148512051bfe /job-controller/known-limitations-and-deferred-work/dev-note: - en: a1f297405fd3bf23 - zh: 7323c5216ec30800 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/api/job-controller/README.md b/packages/api/job-controller/README.md index ea343a8a20..6c7f5c9af3 100644 --- a/packages/api/job-controller/README.md +++ b/packages/api/job-controller/README.md @@ -65,5 +65,3 @@ No direct effect; observation reads never touch model requests. None. - -**Runtime invariant:** No companion is published. The controller is a stateless projection of `ctx.jobs` reads; the registry's own `@deepseek-ai/dsh-jobs/invariant` owns the event protocol and event-versus-read relations these streams forward. diff --git a/packages/api/job-controller/README.zh.md b/packages/api/job-controller/README.zh.md index ce8f166cec..7366dec51b 100644 --- a/packages/api/job-controller/README.zh.md +++ b/packages/api/job-controller/README.zh.md @@ -65,5 +65,3 @@ Client 入口安装 `ctx.jobs`(`IJobs`),由包内部的 `ClientJobsModel` 无。 - -**运行时不变式:** 不发布伴生入口。控制器是 `ctx.jobs` 读取的无状态投影;这些流转发的事件协议与事件对读取的关系由注册表自己的 `@deepseek-ai/dsh-jobs/invariant` 拥有。 diff --git a/packages/api/remotes/README.i18n.yaml b/packages/api/remotes/README.i18n.yaml index 93d585f2c4..6aeac5e27e 100644 --- a/packages/api/remotes/README.i18n.yaml +++ b/packages/api/remotes/README.i18n.yaml @@ -33,5 +33,5 @@ en: 00a837c9e7b5a800 zh: 1b86e789327b7798 /deepseek-ai-dsh-api-remotes/known-limitations-and-deferred-work/dev-note: - en: 947ba019d2a6a7e7 - zh: ad4d687f3bc77dbb + en: d60df1790fac729c + zh: cfc4e027025639bf diff --git a/packages/api/remotes/README.md b/packages/api/remotes/README.md index 20d818b659..abd5b605ed 100644 --- a/packages/api/remotes/README.md +++ b/packages/api/remotes/README.md @@ -87,6 +87,4 @@ None. -**Runtime invariant:** No companion is published. Typert and the Agent/Session registries own the observed relationships. - The Client assembly includes the account namespace for login commands and reconnectable account snapshots. diff --git a/packages/api/remotes/README.zh.md b/packages/api/remotes/README.zh.md index b3bda48bac..df5e47d07d 100644 --- a/packages/api/remotes/README.zh.md +++ b/packages/api/remotes/README.zh.md @@ -87,6 +87,4 @@ Host entry 为每条 Client 流独立注册一组 allowlist listener 和一个 -**运行时不变式:** 不发布伴生入口。被观察的关系由 Typert、agent 注册表和会话注册表负责。 - 客户端组装包含 account 命名空间,用于登录操作和可重连的账号快照。 diff --git a/packages/api/session-controller/README.i18n.yaml b/packages/api/session-controller/README.i18n.yaml index 5878d2f71b..192398573b 100644 --- a/packages/api/session-controller/README.i18n.yaml +++ b/packages/api/session-controller/README.i18n.yaml @@ -36,5 +36,5 @@ en: e896bfcaf38b4f6c zh: 55a4eba838f166de /session-controller/known-limitations-and-deferred-work/dev-note: - en: 68e0dfe4588c5b88 - zh: db51b435611bded1 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/api/session-controller/README.md b/packages/api/session-controller/README.md index be3f9a0bf5..2e1e0b885d 100644 --- a/packages/api/session-controller/README.md +++ b/packages/api/session-controller/README.md @@ -127,5 +127,3 @@ No direct effect; model requests remain owned by the Agent and LLM packages. None. - -**Runtime invariant:** No companion is published. Every page and frame is checked against the addressed durable Session. diff --git a/packages/api/session-controller/README.zh.md b/packages/api/session-controller/README.zh.md index de40067e39..087cc32f77 100644 --- a/packages/api/session-controller/README.zh.md +++ b/packages/api/session-controller/README.zh.md @@ -127,5 +127,3 @@ GUI 模型选择要求确切提供方/模型对出现在可用目录中;不 无。 - -**运行时不变式:** 不发布伴生入口。每个分页与帧都会对照其指向的持久 Session 校验。 diff --git a/packages/api/settings-controller/README.i18n.yaml b/packages/api/settings-controller/README.i18n.yaml index 8b39231902..4b3f0d24d3 100644 --- a/packages/api/settings-controller/README.i18n.yaml +++ b/packages/api/settings-controller/README.i18n.yaml @@ -30,5 +30,5 @@ en: a7475fd8aedf0830 zh: add1adce9d320baa /settings-controller/known-limitations-and-deferred-work/dev-note: - en: b684658b106c5d16 - zh: 1ba652cf16d8418b + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/api/settings-controller/README.md b/packages/api/settings-controller/README.md index b7472f6b0f..b38072546d 100644 --- a/packages/api/settings-controller/README.md +++ b/packages/api/settings-controller/README.md @@ -67,5 +67,3 @@ No direct effect; reading or writing these configuration values does not alter m None. - -**Runtime invariant:** No companion is published. The settings and credential seams own storage and update events, while this package only projects their methods onto the wire. diff --git a/packages/api/settings-controller/README.zh.md b/packages/api/settings-controller/README.zh.md index 9159e1f167..3c9e876740 100644 --- a/packages/api/settings-controller/README.zh.md +++ b/packages/api/settings-controller/README.zh.md @@ -67,5 +67,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。settings 与凭据 seam 负责存储和更新事件,本包只把它们的方法投影到 wire。 diff --git a/packages/api/terminal-controller/README.i18n.yaml b/packages/api/terminal-controller/README.i18n.yaml index 545139127a..d0ed5a8927 100644 --- a/packages/api/terminal-controller/README.i18n.yaml +++ b/packages/api/terminal-controller/README.i18n.yaml @@ -33,5 +33,5 @@ en: 6af6663b852441db zh: 244ed7729cae8d81 /deepseek-ai-dsh-api-terminal-controller/known-limitations-and-deferred-work/dev-note: - en: 2acda84e5b9a3ce2 - zh: f86e92da1c66ffff + en: e67f633dc6fe7773 + zh: 7aeea62eda52b83b diff --git a/packages/api/terminal-controller/README.md b/packages/api/terminal-controller/README.md index f33ce680c0..6fcdef9c4c 100644 --- a/packages/api/terminal-controller/README.md +++ b/packages/api/terminal-controller/README.md @@ -97,6 +97,6 @@ None; terminal output travels only between the browser and Host.
Working context for maintainers — click to expand -No runtime invariant companion is published. One owner orders terminal metadata and screen updates; the provider exposes no independently observed dimensions to compare. +None.
diff --git a/packages/api/terminal-controller/README.zh.md b/packages/api/terminal-controller/README.zh.md index d4d92f0d3e..e8d1b9b172 100644 --- a/packages/api/terminal-controller/README.zh.md +++ b/packages/api/terminal-controller/README.zh.md @@ -97,6 +97,6 @@ Client 在分配前将每条 Session/内容与终端身份的关联保存到独
维护说明 -不发布运行时 invariant companion。终端元数据与屏幕更新由同一对象按序写入,没有独立的进程尺寸观测可供比较。 +无。
diff --git a/packages/api/workspace-controller/README.i18n.yaml b/packages/api/workspace-controller/README.i18n.yaml index 6df6cd44a3..d804399b83 100644 --- a/packages/api/workspace-controller/README.i18n.yaml +++ b/packages/api/workspace-controller/README.i18n.yaml @@ -30,5 +30,5 @@ en: 43b497c19779532e zh: c6d9d3afff774219 /workspace-controller/known-limitations-and-deferred-work/dev-note: - en: 0f74591fc1097e46 - zh: f4df9e5617a50a9f + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/api/workspace-controller/README.md b/packages/api/workspace-controller/README.md index ab96bb0114..b24053384f 100644 --- a/packages/api/workspace-controller/README.md +++ b/packages/api/workspace-controller/README.md @@ -70,5 +70,3 @@ No direct effect; Workspace mutations do not alter model requests. None. - -**Runtime invariant:** No companion is published. Workspace Registry owns persistence; every stream generation is a full projection. diff --git a/packages/api/workspace-controller/README.zh.md b/packages/api/workspace-controller/README.zh.md index bcf713306e..dc240950cc 100644 --- a/packages/api/workspace-controller/README.zh.md +++ b/packages/api/workspace-controller/README.zh.md @@ -70,5 +70,3 @@ Documents 查询占用注册表变更队列,因此其他 Workspace 变更( 无。 - -**运行时不变式:** 不发布伴生入口。Workspace 注册表负责持久化,每次流生成都是完整投影。 diff --git a/packages/api/workspace-files/README.i18n.yaml b/packages/api/workspace-files/README.i18n.yaml index e76b5b1e0f..0dbb9b3f71 100644 --- a/packages/api/workspace-files/README.i18n.yaml +++ b/packages/api/workspace-files/README.i18n.yaml @@ -48,8 +48,8 @@ en: c3a8852c33dcc0e9 zh: d24fd47da7faaa29 /deepseek-ai-dsh-api-workspace-files/understand-the-implementation/source-map: - en: 2394243d582bb452 - zh: 763e56ca827efa0d + en: 596a75f43bfafda3 + zh: 3cb267a0946cd75e /deepseek-ai-dsh-api-workspace-files/further-exploration: en: 71f00fb919d12181 zh: 1e3819cd90dfca31 diff --git a/packages/api/workspace-files/README.md b/packages/api/workspace-files/README.md index 3511616eea..9ceb0105fa 100644 --- a/packages/api/workspace-files/README.md +++ b/packages/api/workspace-files/README.md @@ -105,7 +105,6 @@ Complete-file reads delegate size enforcement to `fs.readBytes` and return raw b | [`src/types.ts`](src/types.ts) | Wire types and the `RemoteErrorDetailsMap` codes, published as `./types` for Client packages | | [`src/client/index.ts`](src/client/index.ts), [`provider.ts`](src/client/provider.ts), [`change-feed.ts`](src/client/change-feed.ts) | Browser plugin, file metadata, and per-target change feeds | | [`src/client/types.ts`](src/client/types.ts), [`remote.ts`](src/client/remote.ts) | Resource values, parameters, Client error codes, and generated Remote types | -| — | No runtime invariant companion is published; every Host answer is derived from `ctx.fs` and the sandbox policy at call time. | Typert generates the Host and Client Remote artifacts exposed by `./typert` and `./remote`. diff --git a/packages/api/workspace-files/README.zh.md b/packages/api/workspace-files/README.zh.md index d11740b8e7..e1f0e0000d 100644 --- a/packages/api/workspace-files/README.zh.md +++ b/packages/api/workspace-files/README.zh.md @@ -105,7 +105,6 @@ kind: "package-reference" | [`src/types.ts`](src/types.ts) | 线路类型与 `RemoteErrorDetailsMap` 错误码,以 `./types` 发布给 Client 包 | | [`src/client/index.ts`](src/client/index.ts)、[`provider.ts`](src/client/provider.ts)、[`change-feed.ts`](src/client/change-feed.ts) | 浏览器插件、文件元数据与按目标建立的变更流 | | [`src/client/types.ts`](src/client/types.ts)、[`remote.ts`](src/client/remote.ts) | 资源值、参数、Client 错误码与生成的 Remote 类型 | -| — | 不发布运行时 invariant 伴生件;每个 Host 答案都在调用时由 `ctx.fs` 与沙箱策略推导。 | Typert 生成 `./typert` 与 `./remote` 暴露的 Host 与 Client Remote 产物。 diff --git a/packages/attachment/attachment-local/README.i18n.yaml b/packages/attachment/attachment-local/README.i18n.yaml index 4a8f7cd11f..520e5af483 100644 --- a/packages/attachment/attachment-local/README.i18n.yaml +++ b/packages/attachment/attachment-local/README.i18n.yaml @@ -39,8 +39,8 @@ en: 609ccc784a87b770 zh: bc702582cf2b3316 /deepseek-ai-dsh-attachment-local/understand-the-implementation/source-map: - en: b092fd38c68a06bd - zh: 4f3f4f1ec98401f4 + en: 05af85aa85934d01 + zh: 1c89e80d496dc0a3 /deepseek-ai-dsh-attachment-local/further-exploration: en: f8b09b2542211379 zh: 57a8fa44aa76b737 diff --git a/packages/attachment/attachment-local/README.md b/packages/attachment/attachment-local/README.md index ea15a8700b..4303813905 100644 --- a/packages/attachment/attachment-local/README.md +++ b/packages/attachment/attachment-local/README.md @@ -99,7 +99,6 @@ Generic-file bytes have one canonical object at `/attachments/v1/file- | [`src/normalization.ts`](src/normalization.ts) + [`src/encoding.ts`](src/encoding.ts) | Provider-independent normalization and bounded format/quality candidates | | [`src/request-image.ts`](src/request-image.ts) | Route-specific request transforms, cache identity, and singleflight | | [`src/image.ts`](src/image.ts) | Full raster decode and metadata verification | -| — | No runtime invariant companion is published; immutable writes and verified reads are enforced directly at the backend boundary. | diff --git a/packages/attachment/attachment-local/README.zh.md b/packages/attachment/attachment-local/README.zh.md index f9b24dfd8e..4c52dab3ac 100644 --- a/packages/attachment/attachment-local/README.zh.md +++ b/packages/attachment/attachment-local/README.zh.md @@ -99,7 +99,6 @@ kind: "package-reference" | [`src/normalization.ts`](src/normalization.ts) + [`src/encoding.ts`](src/encoding.ts) | 提供方无关的规范化与有界格式/质量候选 | | [`src/request-image.ts`](src/request-image.ts) | 路由专用请求变换、缓存身份与 singleflight | | [`src/image.ts`](src/image.ts) | 完整光栅解码与元数据校验 | -| — | 不发布运行时不变式伴生入口;不可变写入与校验读取在后端边界直接强制。 | diff --git a/packages/attachment/attachment/README.i18n.yaml b/packages/attachment/attachment/README.i18n.yaml index 0c7491db3a..4f941409fe 100644 --- a/packages/attachment/attachment/README.i18n.yaml +++ b/packages/attachment/attachment/README.i18n.yaml @@ -42,8 +42,8 @@ en: 4a35cdba790c3b8b zh: 08d4a18bbad4b182 /deepseek-ai-dsh-attachment/understand-the-implementation/source-map: - en: a196e1f4cf9895ae - zh: 39b430b53d30297e + en: 4fb4c96184c38169 + zh: 015b06e4bfff4fcd /deepseek-ai-dsh-attachment/further-exploration: en: 0f179d9ab84a962a zh: 4a1d0722a3309f4a diff --git a/packages/attachment/attachment/README.md b/packages/attachment/attachment/README.md index d2a667e2d1..43eb4d8ae8 100644 --- a/packages/attachment/attachment/README.md +++ b/packages/attachment/attachment/README.md @@ -83,7 +83,6 @@ The service family runs one admission-and-storage flow: every entry point enforc | [`src/admission.ts`](src/admission.ts) | Canonical-base64 enforcement and store delegation for encoded image and file uploads | | [`src/error.ts`](src/error.ts) | `AttachmentError` class and the `isImageAdmissionError` runtime subset | | [`src/brand.ts`](src/brand.ts) | `AttachmentId` branded opaque identifier | -| — | No runtime invariant companion is published; this stateless seam owns types while implementations enforce immutable-store checks. | diff --git a/packages/attachment/attachment/README.zh.md b/packages/attachment/attachment/README.zh.md index b5598eb8cc..4fa95d20f3 100644 --- a/packages/attachment/attachment/README.zh.md +++ b/packages/attachment/attachment/README.zh.md @@ -83,7 +83,6 @@ kind: "package-reference" | [`src/admission.ts`](src/admission.ts) | 对编码图片和文件上传强制执行规范 base64 并委托存储 | | [`src/error.ts`](src/error.ts) | `AttachmentError` 类与 `isImageAdmissionError` 运行时子集 | | [`src/brand.ts`](src/brand.ts) | `AttachmentId` 带类型标记的不透明标识符 | -| — | 不发布运行时不变式伴生入口;这个无状态 seam 承载类型,实现则负责强制执行不可变存储检查。 | diff --git a/packages/boot/app-boot/README.i18n.yaml b/packages/boot/app-boot/README.i18n.yaml index bd29896a32..7dfd0a8c32 100644 --- a/packages/boot/app-boot/README.i18n.yaml +++ b/packages/boot/app-boot/README.i18n.yaml @@ -51,8 +51,8 @@ en: f7bc3a8dd4b00bdd zh: 30098428aa769ad0 /deepseek-ai-dsh-app-boot/understand-the-implementation/source-map: - en: 25602fd8eb9b2907 - zh: 870a33dd34a52c32 + en: 3da01827e3354655 + zh: 06ccfaed83f65a76 /deepseek-ai-dsh-app-boot/further-exploration: en: ffd10e94443a10ca zh: cd11495bae751bdd diff --git a/packages/boot/app-boot/README.md b/packages/boot/app-boot/README.md index aae8f44cc6..c3e5bde1f5 100644 --- a/packages/boot/app-boot/README.md +++ b/packages/boot/app-boot/README.md @@ -159,7 +159,6 @@ The exports each own one stage of the boot: config resolution and snapshot repla | [`src/profile-sanitize.ts`](src/profile-sanitize.ts) | Profile patch backup and recovery bundle activation | | [`src/config-schema/`](src/config-schema/) | Profile schema generation, discovery, native projection, and result types | | [`src/profile-resolution/`](src/profile-resolution/) | Runtime resolver, package-metadata service, and built Worker bootstrap | -| — | No runtime invariant companion is published; one interception owns each runtime resolution. | diff --git a/packages/boot/app-boot/README.zh.md b/packages/boot/app-boot/README.zh.md index 9cd18a99fa..1d0588d4a7 100644 --- a/packages/boot/app-boot/README.zh.md +++ b/packages/boot/app-boot/README.zh.md @@ -159,7 +159,6 @@ Loader 结算后,app-boot 在仅 optional 条目未激活时输出警告。如 | [`src/profile-sanitize.ts`](src/profile-sanitize.ts) | profile patch 备份与恢复 bundle 启用状态 | | [`src/config-schema/`](src/config-schema/) | Profile schema 生成、发现、原生投影与结果类型 | | [`src/profile-resolution/`](src/profile-resolution/) | 运行时 resolver、package metadata 服务与构建后 Worker bootstrap | -| — | 不发布运行时不变式伴生入口;每个 runtime resolution 只有一个拦截所有。 | diff --git a/packages/boot/cmdline/README.i18n.yaml b/packages/boot/cmdline/README.i18n.yaml index 18fa26d696..6af53897e6 100644 --- a/packages/boot/cmdline/README.i18n.yaml +++ b/packages/boot/cmdline/README.i18n.yaml @@ -39,8 +39,8 @@ en: 720b38c4c9afeb93 zh: b43160d7176e5799 /deepseek-ai-dsh-cmdline/understand-the-implementation/source-map: - en: 9867efd4c939077c - zh: 00247b7fe19bc2eb + en: a5f51481ca8e8751 + zh: c512261b9a3bca74 /deepseek-ai-dsh-cmdline/further-exploration: en: 6ea3c827c59aff4c zh: 2977b4cfa7f73942 diff --git a/packages/boot/cmdline/README.md b/packages/boot/cmdline/README.md index f0c6636b34..eb093bb5bf 100644 --- a/packages/boot/cmdline/README.md +++ b/packages/boot/cmdline/README.md @@ -97,7 +97,6 @@ The parse path is one small family with two owners: `provideCmdline` freezes the | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | `CmdlineArgs`/`AppExit` types, `provideCmdline`, `parseCmdline`, commander exit/output routing | -| — | No runtime invariant companion is published; `cmdlineArgs` is an immutable launcher fact that any number of ordinary plugins may read. App-owned providers and consumers use normal Cordis service injection, whose missing dependencies are already reported by Loader settlement. | diff --git a/packages/boot/cmdline/README.zh.md b/packages/boot/cmdline/README.zh.md index b852c59ee4..c2ec9b4a61 100644 --- a/packages/boot/cmdline/README.zh.md +++ b/packages/boot/cmdline/README.zh.md @@ -97,7 +97,6 @@ kind: "package-library" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | `CmdlineArgs`/`AppExit` 类型、`provideCmdline`、`parseCmdline`、commander 退出/输出路由 | -| — | 不发布运行时不变式配套条目;`cmdlineArgs` 是不可变的启动器事实,任意数量的普通插件都可以读取它。应用自有提供方与消费方使用普通 Cordis 服务注入;Loader 结算已会报告缺失的依赖。 | diff --git a/packages/boot/config-editor/README.i18n.yaml b/packages/boot/config-editor/README.i18n.yaml index 5e26f64cec..5727bdff08 100644 --- a/packages/boot/config-editor/README.i18n.yaml +++ b/packages/boot/config-editor/README.i18n.yaml @@ -18,8 +18,8 @@ en: 1eedaa5d72b48177 zh: 39abc1d927a692d2 /deepseek-ai-dsh-config-editor/understand-the-implementation: - en: b6ecd942daccc797 - zh: 2f4637d725034f96 + en: baa887b5ecfa7001 + zh: d3e03c2b1d10c67e /deepseek-ai-dsh-config-editor/further-exploration: en: 0c9e1d1401a865cc zh: 6130d52636e84676 diff --git a/packages/boot/config-editor/README.md b/packages/boot/config-editor/README.md index a7ae05067a..17da6551f9 100644 --- a/packages/boot/config-editor/README.md +++ b/packages/boot/config-editor/README.md @@ -42,8 +42,6 @@ The [editor](src/index.ts) reconciles external changes before deriving a candida Configuration reads share one composition for entries without profile config overrides. Overridden entries are composed separately with only their own override removed; profile inserts and other entries' overrides remain effective. Returned configs are detached, and compositions are not cached between reads. -No invariant companion is published: the editor keeps no independent configuration projection. Loader and the persisted profile patch own configuration state. - diff --git a/packages/boot/config-editor/README.zh.md b/packages/boot/config-editor/README.zh.md index a5bfe0faed..d1ff18344f 100644 --- a/packages/boot/config-editor/README.zh.md +++ b/packages/boot/config-editor/README.zh.md @@ -42,8 +42,6 @@ kind: "package-reference" 读取配置时,没有 profile config 覆盖项的条目共用一次组合结果。有覆盖项的条目分别组合,仅移除自身的覆盖项;profile 插入的条目和其他条目的覆盖项仍然生效。返回的配置为独立副本,组合结果不跨读取缓存。 -此包不发布 invariant companion:编辑器不维护独立配置投影。Loader 和持久化的 profile patch 拥有配置状态。 - diff --git a/packages/boot/hmr/README.i18n.yaml b/packages/boot/hmr/README.i18n.yaml index 7e4bce6b3b..e66a76d589 100644 --- a/packages/boot/hmr/README.i18n.yaml +++ b/packages/boot/hmr/README.i18n.yaml @@ -21,8 +21,8 @@ en: 45ee194ad781d603 zh: b3d7fcd8ed73717c /deepseek-ai-dsh-hmr/understand-the-implementation: - en: 5c7ff789d5d6b653 - zh: 46ef96a031ac87bd + en: 247499831fdee81f + zh: 690f1859c1f73bbe /deepseek-ai-dsh-hmr/model-experience: en: 215e7ba838619b7b zh: a311da3843709f90 diff --git a/packages/boot/hmr/README.md b/packages/boot/hmr/README.md index c4405697c6..6c7d9bb87f 100644 --- a/packages/boot/hmr/README.md +++ b/packages/boot/hmr/README.md @@ -56,7 +56,7 @@ Chokidar options, including polling, retain their existing meaning. Exact config `watchConfig()` registers an awaited configuration handler. `runExclusive()` serializes configuration changes and Loader updates with automatic reloads and rejects nested transactions. Package installation and removal run outside this queue. HMR does not acquire the package writer lock; manifest notifications reload only when the ordered `dsh.profile.bundles` list changes. Profile and home patch changes also trigger recomposition. File events received during a configuration transaction are processed afterward. Include refreshes and profile reconciliation reach plugins through ordinary Loader entry updates; Loader commits volatile-only changes in place. -App-boot owns profile parsing and patch precedence. HMR reads the launcher’s data-only `profileContext`, registers the profile manifest and both user patch watches during initialization, and waits for application readiness before processing changes. Its disposal closes the watchers and cancels reloads waiting for startup. HMR also owns module-cache replacement and reload scheduling. Configuration watchers start outside the active transaction context so later notifications can enter the queue. No invariant companion is published because the queue and watcher registrations have no independent persisted projection. +App-boot owns profile parsing and patch precedence. HMR reads the launcher’s data-only `profileContext`, registers the profile manifest and both user patch watches during initialization, and waits for application readiness before processing changes. Its disposal closes the watchers and cancels reloads waiting for startup. HMR also owns module-cache replacement and reload scheduling. Configuration watchers start outside the active transaction context so later notifications can enter the queue. Watched module paths use Node ESM resolution's `realpathSync()` spelling, including Windows short directory names, so file events match the module cache. diff --git a/packages/boot/hmr/README.zh.md b/packages/boot/hmr/README.zh.md index 6c82c3db8e..b6976cf255 100644 --- a/packages/boot/hmr/README.zh.md +++ b/packages/boot/hmr/README.zh.md @@ -56,7 +56,7 @@ Chokidar 选项(包括轮询)保持原有含义。精确配置监听同时 `watchConfig()` 注册会被等待的配置处理器。`runExclusive()` 将配置变更、Loader 更新与自动重载串行化,并拒绝嵌套事务。包安装和删除在该队列之外执行。HMR 不获取包操作写锁;manifest 通知仅在有序的 `dsh.profile.bundles` 列表变化时触发重载。profile 与 home patch 变化也会触发重新组合。配置事务期间收到的文件事件在事务结束后处理。 Include 刷新和 profile 重载都通过普通的 Loader 条目更新到达插件;仅 volatile 变化由 Loader 就地提交。 -App-boot 负责 profile 解析和 patch 优先级规则。HMR 读取启动器提供的纯数据 `profileContext`,在初始化时注册 profile manifest 和两份用户 patch 的监听,并等待应用就绪后处理更改。销毁 HMR 时会关闭监听器并取消等待启动的重载。HMR 也负责模块缓存替换和重载调度。配置监听器在当前事务上下文之外启动,使后续通知可以进入队列。不发布 invariant 伴生入口,因为队列和监听注册没有独立的持久投影。 +App-boot 负责 profile 解析和 patch 优先级规则。HMR 读取启动器提供的纯数据 `profileContext`,在初始化时注册 profile manifest 和两份用户 patch 的监听,并等待应用就绪后处理更改。销毁 HMR 时会关闭监听器并取消等待启动的重载。HMR 也负责模块缓存替换和重载调度。配置监听器在当前事务上下文之外启动,使后续通知可以进入队列。 被监听模块的路径沿用 Node ESM 解析所用的 `realpathSync()` 表示,包括 Windows 短目录名,使文件事件与模块缓存匹配。 diff --git a/packages/boot/plugin-manager/README.i18n.yaml b/packages/boot/plugin-manager/README.i18n.yaml index 7b503ceb6a..6450b0cc71 100644 --- a/packages/boot/plugin-manager/README.i18n.yaml +++ b/packages/boot/plugin-manager/README.i18n.yaml @@ -24,8 +24,8 @@ en: d5643a06f300c30a zh: 9348b6a7676a2849 /deepseek-ai-dsh-plugin-manager/understand-the-implementation: - en: 1c737c375aeac163 - zh: 009d7f139a749c79 + en: 0f3d0c6c622577c1 + zh: 5a85f66e33977da3 /deepseek-ai-dsh-plugin-manager/further-exploration: en: c6538243d8702a0d zh: 04a82d923353c75f diff --git a/packages/boot/plugin-manager/README.md b/packages/boot/plugin-manager/README.md index eabc124c92..505cbad686 100644 --- a/packages/boot/plugin-manager/README.md +++ b/packages/boot/plugin-manager/README.md @@ -91,7 +91,7 @@ The CLI exposes `dsh plugin --profile version-exemptions`, `allow-vers The service and `dsh plugin` share the package operations in [operations.ts](src/operations.ts). The launcher supplies the current profile; [DSH HMR](../hmr/README.md) serializes module reloads, file watching and management writes. Each refresh re-reads bundle selection and patch layers, updates the original root Include, and awaits removed plugin resources as well as the remaining Loader tree. CLI and service operations share the profile manifest writer lock to prevent concurrent package and manifest writes. HMR does not acquire that lock. Pnpm runs outside the HMR queue; installation selects the bundle after pnpm succeeds, while removal deselects and unloads the bundle before pnpm runs. A service run whose captured output stays silent for `idleTimeoutMs` is terminated, reports `timedOut` alongside its exit status, is classified `timeout` whatever status the signal left behind, and is not retried on the next registry, which bounds how long one operation can hold the profile lock; the CLI inherits the terminal, captures no output, and stays unbounded for its operator to interrupt. A run completes when its process exits, and the pipes then drain under a bounded grace period, so a descendant that inherited them cannot hold the operation open. A terminated run stops its whole process tree and waits for it, because a lifecycle script outlives the pnpm process that started it (issue #4981). Each operation records the pnpm run it starts in `.plugin-manager/run.json` and removes the record when the run ends. A lock whose holder process exited is taken over by the next writer, but that process's pnpm tree can still be running, so an operation that finds a record waits up to five seconds for the recorded run to stop and otherwise fails with a diagnostic that names the process and the record, without running pnpm. Dependency-only changes do not trigger configuration reloads. -Results contain the last attempted stage, target, saved-state change, application status and error codes. Web dictionaries render management text; pnpm and Loader diagnostics remain unmodified. Unrelated pre-existing inactive entries return warnings; new or changed failures and inactive explicit enablement targets fail the operation. A failed or cancelled installation restores the manifest and lockfile it snapshotted before pnpm ran ([rationale](../../../.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.md)); a failed removal retains its partial changes and diagnostics. Installations are tracked by request id until their call settles, so a cancellation names one run and joins its settlement without taking the profile lock. The CLI inherits authentication variables and terminal descriptors; service operations use a scrubbed environment and captured output. No invariant companion is published because the manager reads files and Loader state directly and owns no independent state projection. +Results contain the last attempted stage, target, saved-state change, application status and error codes. Web dictionaries render management text; pnpm and Loader diagnostics remain unmodified. Unrelated pre-existing inactive entries return warnings; new or changed failures and inactive explicit enablement targets fail the operation. A failed or cancelled installation restores the manifest and lockfile it snapshotted before pnpm ran ([rationale](../../../.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.md)); a failed removal retains its partial changes and diagnostics. Installations are tracked by request id until their call settles, so a cancellation names one run and joins its settlement without taking the profile lock. The CLI inherits authentication variables and terminal descriptors; service operations use a scrubbed environment and captured output. diff --git a/packages/boot/plugin-manager/README.zh.md b/packages/boot/plugin-manager/README.zh.md index c715052d8f..b157c2d64a 100644 --- a/packages/boot/plugin-manager/README.zh.md +++ b/packages/boot/plugin-manager/README.zh.md @@ -91,7 +91,7 @@ CLI 提供 `dsh plugin --profile version-exemptions`、`allow-version 服务与 `dsh plugin` 共用 [operations.ts](src/operations.ts) 中的包管理操作。启动器提供当前 profile;[DSH HMR](../hmr/README.zh.md) 串行执行模块重载、文件监听和管理写入。每次刷新重新读取组合包选择与 patch 层,更新原有根 Include,并等待已移除插件释放资源及剩余 Loader 树稳定。CLI 与 service 操作共用 profile manifest 写锁,防止并发包操作和 manifest 写入。HMR 不获取该锁。pnpm 在 HMR 队列之外执行;安装在 pnpm 成功后选入组合包,删除则在执行 pnpm 前取消选入并完成卸载。service 运行若在 `idleTimeoutMs` 内没有任何捕获输出即被终止,与退出状态一并报告 `timedOut`,不论信号留下什么退出状态都归类为 `timeout`,且不再转问下一个注册表,因此单次操作占用 profile 锁的时长有上界;CLI 继承终端、不捕获输出,因此不受此上界约束,由操作者中断。运行以进程退出为完成点,随后只在一个有界的宽限窗口内排空管道,因此继承管道的孙进程无法让操作挂起。被终止的运行会停止整棵进程树并等待其消失,因为生命周期脚本的存活时间超过启动它的 pnpm 进程(issue #4981)。每个操作把它启动的 pnpm 运行记录在 `.plugin-manager/run.json` 中,并在运行结束时删除该记录。持有者进程已退出的锁会被下一个写入方接管,但该进程的 pnpm 进程树可能仍在运行,因此发现记录的操作最多等待五秒让记录中的运行停止,否则不运行 pnpm,并以指明该进程与记录文件的诊断失败。仅依赖字段变化不会触发配置重载。 -结果包含最后尝试的阶段、目标、磁盘变化、应用状态和错误码。Web 词典呈现管理文案;pnpm 与 Loader 的诊断保持原样。无关的已有故障作为警告返回;新出现、配置变化后的故障,以及显式启用目标未激活,都会使操作失败。失败或被取消的安装会恢复 pnpm 运行前快照的 manifest 与 lockfile([理由](../../../.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.zh.md));失败的删除保留部分改动和诊断。安装按 request id 跟踪到调用结束,因此取消只针对一次运行,并且不取 profile 锁就能等待它结束。CLI 继承认证环境和终端描述符;service 使用清理后的环境并捕获输出。管理器直接读取文件和 Loader 状态,不维护第二份目标状态注册表,因此不发布单独的运行时不变式伴生入口。 +结果包含最后尝试的阶段、目标、磁盘变化、应用状态和错误码。Web 词典呈现管理文案;pnpm 与 Loader 的诊断保持原样。无关的已有故障作为警告返回;新出现、配置变化后的故障,以及显式启用目标未激活,都会使操作失败。失败或被取消的安装会恢复 pnpm 运行前快照的 manifest 与 lockfile([理由](../../../.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.zh.md));失败的删除保留部分改动和诊断。安装按 request id 跟踪到调用结束,因此取消只针对一次运行,并且不取 profile 锁就能等待它结束。CLI 继承认证环境和终端描述符;service 使用清理后的环境并捕获输出。 diff --git a/packages/browser-use/browser-use/README.i18n.yaml b/packages/browser-use/browser-use/README.i18n.yaml index ef8440b211..0b7404b051 100644 --- a/packages/browser-use/browser-use/README.i18n.yaml +++ b/packages/browser-use/browser-use/README.i18n.yaml @@ -18,8 +18,8 @@ en: d0c5ace058c0572a zh: 2bbfa786c1f9ba53 /deepseek-ai-dsh-browser-use/understand-the-implementation: - en: e8421af75ba37792 - zh: 3dbc5e6ff5291972 + en: c4d9ec0df22a30ea + zh: 06057e2a56cebbb9 /deepseek-ai-dsh-browser-use/further-exploration: en: 5ae59b1b38304607 zh: 429171c1a3fb914c diff --git a/packages/browser-use/browser-use/README.md b/packages/browser-use/browser-use/README.md index d721f4f22e..859345994d 100644 --- a/packages/browser-use/browser-use/README.md +++ b/packages/browser-use/browser-use/README.md @@ -45,8 +45,6 @@ Providers stop admitting tool calls, close their resources, and await owned work One private name owns the slot. Cordis effects remove contributions when their plugin unloads; a repeated disposer cannot remove a later registration. The [source](src/index.ts) contains no browser object, operation interface, resource lifecycle, or provider selector. -No runtime invariant companion is published: the registry has one authoritative field and exposes no independently maintained observation that can diverge. Duplicate rejection and plugin disposal are covered by the owning tests. - ----- diff --git a/packages/browser-use/browser-use/README.zh.md b/packages/browser-use/browser-use/README.zh.md index fd1b2dc832..17d1590eb2 100644 --- a/packages/browser-use/browser-use/README.zh.md +++ b/packages/browser-use/browser-use/README.zh.md @@ -45,8 +45,6 @@ kind: "package-reference" 一个私有名称拥有该注册位。Cordis effect 在插件卸载时移除贡献;重复调用清理器不能移除后续注册。[源码](src/index.ts) 不包含浏览器对象、操作接口、资源生命周期或 提供方 选择器。 -不发布运行时不变量伴随入口:注册表只有一个权威字段,不暴露可能与之偏离的独立维护观测值。所属测试覆盖重复注册拒绝与插件清理。 - ----- diff --git a/packages/bundle/acp-app/README.i18n.yaml b/packages/bundle/acp-app/README.i18n.yaml index 39606fc9d9..d2e5b8d55b 100644 --- a/packages/bundle/acp-app/README.i18n.yaml +++ b/packages/bundle/acp-app/README.i18n.yaml @@ -39,5 +39,5 @@ en: e5e9b2ef3b75e6f6 zh: f34d43d5dd68ab60 /deepseek-ai-dsh-acp-app/known-limitations-and-deferred-work/dev-note: - en: 8bc6c774f783517d - zh: d1643c271de867ae + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/bundle/acp-app/README.md b/packages/bundle/acp-app/README.md index c770e25bbc..240c2d0ce7 100644 --- a/packages/bundle/acp-app/README.md +++ b/packages/bundle/acp-app/README.md @@ -72,5 +72,3 @@ Stable for a fixed profile, provider, model, and tool roster. Profile changes ta None. - -**Runtime invariant:** No companion is published. The bundle adds a process transport and startup latch; source/built stdio tests own frame purity, help exclusion, and shutdown. diff --git a/packages/bundle/acp-app/README.zh.md b/packages/bundle/acp-app/README.zh.md index 3f87f92b93..bd9e90d5ae 100644 --- a/packages/bundle/acp-app/README.zh.md +++ b/packages/bundle/acp-app/README.zh.md @@ -72,5 +72,3 @@ profile 在第一方指导之前提供 `You are a coding agent powered by the {{ 无。 - -**运行时不变式:** 不发布伴生入口。该 bundle 只增加进程传输与启动 latch;帧纯度、help 排除和关闭行为由源码及构建产物的 stdio 测试负责。 diff --git a/packages/bundle/base/README.i18n.yaml b/packages/bundle/base/README.i18n.yaml index 4aff8b80e2..397428513a 100644 --- a/packages/bundle/base/README.i18n.yaml +++ b/packages/bundle/base/README.i18n.yaml @@ -39,11 +39,8 @@ en: 8c2444fa7b237783 zh: 82002a8b9ddf5942 /deepseek-ai-dsh-base/understand-the-implementation/source-map: - en: 3aaf655b9c93534b - zh: 5a1b627cc6385a17 -/deepseek-ai-dsh-base/understand-the-implementation/invariant-ownership: - en: 533299d7bd555e61 - zh: 0aed404f790ea447 + en: a8efda71d444155e + zh: c8221593709fbb99 /deepseek-ai-dsh-base/further-exploration: en: 39701b25b209fb67 zh: 2b7ce98365f1ecfc diff --git a/packages/bundle/base/README.md b/packages/bundle/base/README.md index 98f4b148bf..c3d0e6fd39 100644 --- a/packages/bundle/base/README.md +++ b/packages/bundle/base/README.md @@ -93,13 +93,8 @@ The patch gates the two shell stacks by platform on its own rows: `bash-sandbox` |---|---| | [`cordis.patch.yml`](cordis.patch.yml) | The bundle substance: the base plugin rows, with per-row rationale as inline comments | | [`src/index.ts`](src/index.ts) | Package entry; carries no runtime API | -| — | No runtime invariant companion is published; the package is a static patch-list carrier (a YAML document of loader rows owned by other packages); it mounts no service, emits no events, and owns no mutable relation to check. Each inserted row's own package carries that row's invariants. | | [`tests/base.spec.ts`](tests/base.spec.ts) | Manifest declaration and platform-gating checks | -### Invariant ownership - -No invariant companion is published because the package is a static patch-list carrier: each inserted row's package owns that row's invariants, and the bundle owns no mutable relation to check. - ----- diff --git a/packages/bundle/base/README.zh.md b/packages/bundle/base/README.zh.md index 2ed43b780b..cf2bf9bd34 100644 --- a/packages/bundle/base/README.zh.md +++ b/packages/bundle/base/README.zh.md @@ -93,13 +93,8 @@ patch 在自身上按平台门控两个 shell 栈:`bash-sandbox` 与 `tool-bas |---|---| | [`cordis.patch.yml`](cordis.patch.yml) | 组合包的实体:基础插件行,附以行内注释说明各行依据 | | [`src/index.ts`](src/index.ts) | 包入口;不携带任何运行时 API | -| — | 不发布运行时不变式伴生入口;本包是静态 patch 列表载体(由其他包拥有的 loader 行构成的 YAML 文档);它不挂载任何服务、不发出任何事件,也没有任何可检查的可变关系。每条插入行所属的包负责该行的不变式。 | | [`tests/base.spec.ts`](tests/base.spec.ts) | manifest(元数据清单)声明与平台门控检查 | -### 不变式归属 - -不发布不变式伴生入口,因为本包是静态 patch 列表载体:每条插入行由所属的包负责其不变式,组合包自身没有任何可审计的可变关系。 - ----- diff --git a/packages/bundle/headless/README.i18n.yaml b/packages/bundle/headless/README.i18n.yaml index 5e6cce5acb..c697c53bd5 100644 --- a/packages/bundle/headless/README.i18n.yaml +++ b/packages/bundle/headless/README.i18n.yaml @@ -45,11 +45,8 @@ en: cc6c485a42d40d28 zh: c57775d9319d8dfd /deepseek-ai-dsh-headless/understand-the-implementation/source-map: - en: 20a37f62e58b3ddb - zh: ede1fb8cfbae7a0e -/deepseek-ai-dsh-headless/understand-the-implementation/invariant-ownership: - en: 329f170dec4b2279 - zh: bb40f8beda5b3a1c + en: d82278600e0efdbd + zh: 0f546bdba164ef25 /deepseek-ai-dsh-headless/further-exploration: en: 65aebf0f8ab3047c zh: 85616d3859073c4b diff --git a/packages/bundle/headless/README.md b/packages/bundle/headless/README.md index d3eb8054e5..98ecb77f5d 100644 --- a/packages/bundle/headless/README.md +++ b/packages/bundle/headless/README.md @@ -95,15 +95,10 @@ A completed final `turn/end` exits 0; any other outcome — aborted, error, or n | [`src/startup.ts`](src/startup.ts) | The `headless-startup` provider: task positional, `--session-id`, `--json`, and `--help` | | [`src/json-stream.ts`](src/json-stream.ts) | The `--json` projection: event vocabulary, commit-point emission, string bounding | | [`cordis.patch.yml`](cordis.patch.yml) | The one-shot patch over `dsh-base` | -| — | No runtime invariant companion is published; the runner's observable contract (provider reasoning on stderr, final text on stdout, exit code by turn-end reason) is process-level and owned by the launcher e2e; it registers nothing and holds no mutable relation to audit inside the tree. | | [`tests/headless.spec.ts`](tests/headless.spec.ts) | Run flow, aggregation, flush, session adoption, and exit mapping | | [`tests/json-stream.spec.ts`](tests/json-stream.spec.ts) | Projection ordering, commit-point emission, bounding, and disposal | | [`tests/startup.spec.ts`](tests/startup.spec.ts) | Command-line parsing over a real Loader tree | -### Invariant ownership - -No invariant companion is published because the runner's observable contract (final text on stdout, exit code by turn-end reason) is process-level and owned by the launcher e2e; the plugin registers nothing and holds no mutable relation to audit inside the tree. - ----- diff --git a/packages/bundle/headless/README.zh.md b/packages/bundle/headless/README.zh.md index b3f4d44513..e039880df7 100644 --- a/packages/bundle/headless/README.zh.md +++ b/packages/bundle/headless/README.zh.md @@ -95,15 +95,10 @@ patch 叠加在 `dsh-base` 之上:继承投影缓存与共享 PTC 运行时, | [`src/startup.ts`](src/startup.ts) | `headless-startup` 提供方:任务位置参数、`--session-id`、`--json` 与 `--help` | | [`src/json-stream.ts`](src/json-stream.ts) | `--json` 投影:事件词汇、提交点发射、字符串限长 | | [`cordis.patch.yml`](cordis.patch.yml) | 叠加在 `dsh-base` 之上的一次性 patch | -| — | 不发布运行时不变式伴生入口;runner 的可观察约定(stderr 中的提供方推理、stdout 中的最终文本、按轮次结束原因决定的退出码)属于进程级,并由启动器 e2e 负责;runner 不注册任何内容,树内也没有任何可变关系可审计。 | | [`tests/headless.spec.ts`](tests/headless.spec.ts) | 运行流程、汇总、flush、Session 沿用与退出映射 | | [`tests/json-stream.spec.ts`](tests/json-stream.spec.ts) | 投影顺序、提交点发射、限长与释放 | | [`tests/startup.spec.ts`](tests/startup.spec.ts) | 在真实 Loader 树上的命令行解析 | -### 不变式归属 - -不发布不变式伴生入口,因为 runner 的可观察约定(stdout 的最终文本、按轮次结束原因决定的退出码)是进程级的、由启动器 e2e 负责;插件不注册任何内容,树内也没有任何可变关系可审计。 - ----- diff --git a/packages/bundle/sdk-app/README.i18n.yaml b/packages/bundle/sdk-app/README.i18n.yaml index 14acc275d8..0211211c9e 100644 --- a/packages/bundle/sdk-app/README.i18n.yaml +++ b/packages/bundle/sdk-app/README.i18n.yaml @@ -36,5 +36,5 @@ en: 416e2793f9786480 zh: 05cc4fd289d0185d /deepseek-ai-dsh-sdk-app/known-limitations-and-deferred-work/dev-note: - en: 8bc6c774f783517d - zh: b1ae7ae9832b2444 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/bundle/sdk-app/README.md b/packages/bundle/sdk-app/README.md index 6d6106e6dd..03163050c5 100644 --- a/packages/bundle/sdk-app/README.md +++ b/packages/bundle/sdk-app/README.md @@ -72,5 +72,3 @@ Stable for a fixed profile, provider, model, and tool roster. Profile changes ta None. - -**Runtime invariant:** No companion is published. The bundle adds a process transport and startup latch; source/built stdio tests own frame purity, help exclusion, and shutdown. diff --git a/packages/bundle/sdk-app/README.zh.md b/packages/bundle/sdk-app/README.zh.md index 9de1fb6e17..d61f586a0f 100644 --- a/packages/bundle/sdk-app/README.zh.md +++ b/packages/bundle/sdk-app/README.zh.md @@ -72,5 +72,3 @@ profile 在第一方指导之前提供 `You are a coding agent powered by the {{ 无。 - -**运行时不变式:** 不发布伴生入口。该 bundle 增加进程传输与启动 latch;帧纯度、help 排除和关闭行为由源码及构建产物的 stdio 测试负责。 diff --git a/packages/bundle/sdk-minimal/README.i18n.yaml b/packages/bundle/sdk-minimal/README.i18n.yaml index 0aa6dc1565..cc7d5e843e 100644 --- a/packages/bundle/sdk-minimal/README.i18n.yaml +++ b/packages/bundle/sdk-minimal/README.i18n.yaml @@ -21,8 +21,8 @@ en: 281b8d6f1aa0e526 zh: 070c2a66b1b2feee /deepseek-ai-dsh-sdk-minimal/understand-the-implementation/source-map: - en: d992e946f4928fa3 - zh: 46d6b3d1aabdc3f4 + en: 3784c554e96ab004 + zh: 0a05d4fe1e26b3b1 /deepseek-ai-dsh-sdk-minimal/further-exploration: en: 6909fc128c321153 zh: 7d2a0866efdc5a69 diff --git a/packages/bundle/sdk-minimal/README.md b/packages/bundle/sdk-minimal/README.md index 2632bf9bd9..9ca6d68810 100644 --- a/packages/bundle/sdk-minimal/README.md +++ b/packages/bundle/sdk-minimal/README.md @@ -56,7 +56,6 @@ The bundle's single insert is the complete application tree: SDK stdio startup a |---|---| | [`cordis.patch.yml`](cordis.patch.yml) | Complete standalone profile tree and its environment-backed defaults | | [`src/index.ts`](src/index.ts) | Bundle package entry | -| — | No runtime invariant companion is published; the package is a static patch-list carrier whose inserted rows own their runtime relationships and invariant companions. | | [`tests/sdk-minimal.spec.ts`](tests/sdk-minimal.spec.ts) | Exact composition, profile-name, and platform-selection checks | diff --git a/packages/bundle/sdk-minimal/README.zh.md b/packages/bundle/sdk-minimal/README.zh.md index 7aa26c9e89..24b023c284 100644 --- a/packages/bundle/sdk-minimal/README.zh.md +++ b/packages/bundle/sdk-minimal/README.zh.md @@ -56,7 +56,6 @@ dsh --profile sdk-minimal |---|---| | [`cordis.patch.yml`](cordis.patch.yml) | 完整独立 profile 配置树及其环境默认值 | | [`src/index.ts`](src/index.ts) | Bundle 包入口 | -| — | 不发布运行时不变式伴生项;本包只是静态 patch 列表载体,插入的各行分别拥有自己的运行时关系和不变式伴生项。 | | [`tests/sdk-minimal.spec.ts`](tests/sdk-minimal.spec.ts) | 精确组合、profile 名称与平台选择检查 | diff --git a/packages/bundle/sdk-minimal/cordis.patch.yml b/packages/bundle/sdk-minimal/cordis.patch.yml index ac6c7c4bca..0ff0ae9a6c 100644 --- a/packages/bundle/sdk-minimal/cordis.patch.yml +++ b/packages/bundle/sdk-minimal/cordis.patch.yml @@ -103,21 +103,6 @@ - id: jobs name: '@deepseek-ai/dsh-jobs-local' - - id: invariants - name: '@deepseek-ai/dsh-invariants' - - - id: session-invariant - name: '@deepseek-ai/dsh-session/invariant' - - - id: agent-invariant - name: '@deepseek-ai/dsh-agent/invariant' - - - id: scope-invariant - name: '@deepseek-ai/dsh-scope/invariant' - - - id: agent-loop-invariant - name: '@deepseek-ai/dsh-agent-loop/invariant' - - id: agent-loop name: '@deepseek-ai/dsh-agent-loop' config: diff --git a/packages/bundle/sdk-minimal/package.json b/packages/bundle/sdk-minimal/package.json index 27acba13d2..43f5ea6f68 100644 --- a/packages/bundle/sdk-minimal/package.json +++ b/packages/bundle/sdk-minimal/package.json @@ -38,7 +38,6 @@ "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-agent-loop": "workspace:*", "@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-jobs-local": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-llm-deepseek-api-key": "workspace:*", @@ -47,7 +46,6 @@ "@deepseek-ai/dsh-plugin-package-inventory-deepseek": "workspace:*", "@deepseek-ai/dsh-sandbox-local": "workspace:*", "@deepseek-ai/dsh-sandbox-policy": "workspace:*", - "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/dsh-sdk-app": "workspace:*", "@deepseek-ai/dsh-sdk-jsonrpc-server": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", diff --git a/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts b/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts index f23bc62084..dda3b50ed6 100644 --- a/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts +++ b/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts @@ -49,11 +49,6 @@ describe('dsh-sdk-minimal bundle', () => { ['agent', '@deepseek-ai/dsh-agent'], ['llm-retry', '@deepseek-ai/dsh-llm-retry'], ['jobs', '@deepseek-ai/dsh-jobs-local'], - ['invariants', '@deepseek-ai/dsh-invariants'], - ['session-invariant', '@deepseek-ai/dsh-session/invariant'], - ['agent-invariant', '@deepseek-ai/dsh-agent/invariant'], - ['scope-invariant', '@deepseek-ai/dsh-scope/invariant'], - ['agent-loop-invariant', '@deepseek-ai/dsh-agent-loop/invariant'], ['agent-loop', '@deepseek-ai/dsh-agent-loop'], ['persistent-bash', '@deepseek-ai/dsh-tool-bash-persistent'], ['persistent-pwsh', '@deepseek-ai/dsh-tool-pwsh-persistent'], diff --git a/packages/bundle/web-app/README.i18n.yaml b/packages/bundle/web-app/README.i18n.yaml index dcb212a0eb..0c7cc88fc6 100644 --- a/packages/bundle/web-app/README.i18n.yaml +++ b/packages/bundle/web-app/README.i18n.yaml @@ -45,11 +45,8 @@ en: b5ed8ff0056ca9da zh: fdc1fa4f14cb00b3 /deepseek-ai-dsh-web-app/understand-the-implementation/source-map: - en: 0d08e95f0a5543cb - zh: 5a3e8e4102678cb7 -/deepseek-ai-dsh-web-app/understand-the-implementation/invariant-ownership: - en: 34767bb4c0471622 - zh: e6d76e7c7a0b9ce8 + en: 9257edc233a36488 + zh: 39f8393f14ef4473 /deepseek-ai-dsh-web-app/further-exploration: en: f81694f7424e2988 zh: 6643f9227039e228 diff --git a/packages/bundle/web-app/README.md b/packages/bundle/web-app/README.md index 79d7d7ad9e..59609eed99 100644 --- a/packages/bundle/web-app/README.md +++ b/packages/bundle/web-app/README.md @@ -99,16 +99,11 @@ The URL line and browser handoff are readiness signals: supervisors RPC as soon | [`src/startup.ts`](src/startup.ts) | The `web-startup` provider: `--host`, `--port`, `--trusted-host`, `--no-open`, `--help` | | [`cordis.patch.yml`](cordis.patch.yml) | The web patch: restated base values, web host rows, browser roster, preset registry | | [`presets/`](presets) | One `@deepseek-ai/dsh-agent-preset` declaration per shipped preset (`standard`, `ptc`, `minimal`, `cordis`), each its own patch file | -| — | No runtime invariant companion is published; every contribution (frontend-static child plugin, prompt section, bashEnv registration) is registry-disposed with the fiber, and each owning registry's package carries that relation's invariant; the package holds no mutable state of its own to audit. | | [`tests/web-app.spec.ts`](tests/web-app.spec.ts) | Dist resolution, fallback seat, prompt sections, readiness | | [`tests/startup.spec.ts`](tests/startup.spec.ts) | Command-line parsing over a real Loader tree | | [`tests/trusted-hosts.spec.ts`](tests/trusted-hosts.spec.ts) | LAN-trust sampling | | [`tests/browser-open.spec.ts`](tests/browser-open.spec.ts) | Default-browser handoff after the page is reachable | -### Invariant ownership - -No invariant companion is published because every contribution — the frontend-static child plugin, the prompt sections, and the bash variable registration — is registry-disposed with the fiber, and each owning registry package carries that relation's invariant. - ----- diff --git a/packages/bundle/web-app/README.zh.md b/packages/bundle/web-app/README.zh.md index c7fca1838a..c90d95714c 100644 --- a/packages/bundle/web-app/README.zh.md +++ b/packages/bundle/web-app/README.zh.md @@ -99,16 +99,11 @@ URL 行与浏览器交接都是就绪信号:监督方一观察到该行就发 | [`src/startup.ts`](src/startup.ts) | `web-startup` 提供方:`--host`、`--port`、`--trusted-host`、`--no-open`、`--help` | | [`cordis.patch.yml`](cordis.patch.yml) | Web patch:重述的基础值、Web 宿主行、浏览器名录、preset 注册表 | | [`presets/`](presets) | 每个随发行版交付的 preset(`standard`、`ptc`、`minimal`、`cordis`)各一条 `@deepseek-ai/dsh-agent-preset` 声明,各自一个补丁文件 | -| — | 不发布运行时不变式伴生入口;每项贡献(frontend-static 子插件、提示词段落、bashEnv 注册)都会随 fiber 由注册表释放,且每个所属注册表的包负责该关系的不变式;本包不持有需要审计的可变状态。 | | [`tests/web-app.spec.ts`](tests/web-app.spec.ts) | dist 解析、回退席位、提示词段落、就绪宣告 | | [`tests/startup.spec.ts`](tests/startup.spec.ts) | 在真实 Loader 树上的命令行解析 | | [`tests/trusted-hosts.spec.ts`](tests/trusted-hosts.spec.ts) | LAN 信任采样 | | [`tests/browser-open.spec.ts`](tests/browser-open.spec.ts) | 页面可达后的默认浏览器交接 | -### 不变式归属 - -不发布不变式伴生入口,因为每项贡献——frontend-static 子插件、提示词段落与 bash 变量注册——都会随 fiber 由注册表释放,且每个所属注册表的包负责该关系的不变式。 - ----- diff --git a/packages/client/AGENTS.md b/packages/client/AGENTS.md index 605a9fcde3..1368b22dc2 100644 --- a/packages/client/AGENTS.md +++ b/packages/client/AGENTS.md @@ -139,7 +139,7 @@ If `test:gui` is red on code you did not touch, neither silently fix nor ignore Bringing up a new `packages/client/` plugin package (ui-workspace is a complete example; ui-sidebar/ui-user-questions are minimal skeletons): -1. **Package skeleton**: `package.json` (`@deepseek-ai/dsh-client-`, exports `.`/`./client`/`./src/*`/`./package.json`, optional `./invariant` only for an independent runtime relationship, `dsh.client` manifest, `files` list), `tsconfig.json` (extends `tsconfig.base.client.json`, one `references` entry per workspace dependency), `tsdown.config.ts` (`clientBundle(id, ['lib/types/index.js'])`, plus `lib/types/invariant.js` only when published), `src/index.ts` (empty node-half apply), optional `src/invariant.ts`, `src/css-modules.d.ts` when using CSS Modules, and `README.md` with the Model Experience section and the reason when no invariant is published. +1. **Package skeleton**: `package.json` (`@deepseek-ai/dsh-client-`, exports `.`/`./client`/`./src/*`/`./package.json`, `dsh.client` manifest, `files` list), `tsconfig.json` (extends `tsconfig.base.client.json`, one `references` entry per workspace dependency), `tsdown.config.ts` (`clientBundle(id, ['lib/types/index.js'])`), `src/index.ts` (empty node-half apply), `src/css-modules.d.ts` when using CSS Modules, and `README.md` with the Model Experience section. 2. **Three registration surfaces, all required** (missing any one fails at a different, later point): the `tsconfig.client.json` aggregate `references` entry; a `dsh.client` row in `packages/bundle/web-app/cordis.patch.yml`; a `packages/bundle/web-app/package.json` dependency (profile boots resolve bare row names through the computed runtime resolution, which is installed as an interception on Node's resolvers — a row whose package no manifest declares fails to import). `pnpm-workspace.yaml` already globs `packages/*/*`. 3. **dsh.client manifest semantics**: `platform: 'web'` always, and the declaration requires a `./client` export (the scan throws without one); `immediately: true` only for stage-one-prefetch infrastructure rows. `inject` lists package-name dependency edges — they are **informational only** (preflight display, HMR diffing); they do not sequence entry activation or apply order. Activation order is Cordis fiber inject waiting on *services*, nothing else. A non-baseline `external` request sequences its dynamic supplier ahead of the consumer — see [shared modules](#shared-modules-and-the-module-graph). 4. **Registering into another package's slot**: apply order is unconstrained, and a business service is not a declaration barrier. Use `ctx.slots.inject(name, () => ctx.slots.register(...))`; it waits on the actual declaration, removes the contribution when that declaration collapses, reruns after redeclaration, and leaves with the caller's plugin fiber. Return a generator yielding each registration when several contributions must install and roll back atomically. A bare `slots.register` into an undeclared slot remains an error; keep service edges only for services the contribution actually reads. diff --git a/packages/client/connection/README.i18n.yaml b/packages/client/connection/README.i18n.yaml index 2b327002f9..2877410f20 100644 --- a/packages/client/connection/README.i18n.yaml +++ b/packages/client/connection/README.i18n.yaml @@ -33,5 +33,5 @@ en: 2c964a7546604347 zh: c30b59fc92a01c36 /deepseek-ai-dsh-client-connection/known-limitations-and-deferred-work/dev-note: - en: c51db1312f6a43ef - zh: 7193ff08f53e3230 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/connection/README.md b/packages/client/connection/README.md index e50f4fb683..9dd14cea8c 100644 --- a/packages/client/connection/README.md +++ b/packages/client/connection/README.md @@ -85,5 +85,3 @@ None; this package neither assembles nor sends a provider request. None. - -**Runtime invariant:** No companion is published. Browser-session verification reads the credential record asynchronously at the request that authorizes work, while the credentials companion owns record commit-event lifetime. Stream/reconnect sequencing and rpcId round-trip discipline are exercised directly by behavior specs, and route register/dispose symmetry is audited by the webserver companion. diff --git a/packages/client/connection/README.zh.md b/packages/client/connection/README.zh.md index 1bf0733bd6..9925c4126c 100644 --- a/packages/client/connection/README.zh.md +++ b/packages/client/connection/README.zh.md @@ -85,5 +85,3 @@ API Gateway Client 把内部 `$events` 逻辑流注册为唯一 generation sourc 无。 - -**运行时不变式:** 不发布伴生入口。浏览器会话验证会在请求授权工作时异步读取凭据记录,而记录的 commit-event 生命周期由 credentials 伴生入口负责;流与重连的时序及 rpcId 往返约束由行为规范直接验证,路由注册与 dispose(资源释放)的对称性由 webserver 伴生入口审计。 diff --git a/packages/client/file-upload/README.i18n.yaml b/packages/client/file-upload/README.i18n.yaml index 2867fd1467..d8a9ea6b7a 100644 --- a/packages/client/file-upload/README.i18n.yaml +++ b/packages/client/file-upload/README.i18n.yaml @@ -18,8 +18,8 @@ en: 9d5bbf52599b0d00 zh: 7907542ce1e34549 /deepseek-ai-dsh-client-file-upload/understand-the-implementation: - en: da896ccc5ecbe207 - zh: f434a7572085ee1e + en: d2b9d2fe9a44b772 + zh: 7d3365dae6b3dbde /deepseek-ai-dsh-client-file-upload/further-exploration: en: ac94eb9da9ecc296 zh: 25cc3362e2bea330 diff --git a/packages/client/file-upload/README.md b/packages/client/file-upload/README.md index e0c0d7637d..aef0307ebe 100644 --- a/packages/client/file-upload/README.md +++ b/packages/client/file-upload/README.md @@ -56,7 +56,7 @@ The Host plugin provides `ctx.fileUploads`. It owns the authenticated streaming -**Runtime invariant:** No companion is published. Each upload receipt belongs to one exact Session, and each request uses one selected carrier. Unsupported stream carriers fail before the body is sent. +Each upload receipt belongs to one exact Session, and each request uses one selected carrier. Unsupported stream carriers fail before the body is sent. ----- diff --git a/packages/client/file-upload/README.zh.md b/packages/client/file-upload/README.zh.md index abdf965694..451211a808 100644 --- a/packages/client/file-upload/README.zh.md +++ b/packages/client/file-upload/README.zh.md @@ -56,7 +56,7 @@ Host 插件提供 `ctx.fileUploads`。它拥有经过认证的流式路由、编 -**运行时不变式:** 不发布伴生入口。每个上传凭证只属于一个准确的 Session,每个请求只使用一个已选定载体。载体不支持的 stream 会在发送请求体前失败。 +每个上传凭证只属于一个准确的 Session,每个请求只使用一个已选定载体。载体不支持的 stream 会在发送请求体前失败。 ----- diff --git a/packages/client/hmr/package.json b/packages/client/hmr/package.json index 3b1df23377..4273a95ef0 100644 --- a/packages/client/hmr/package.json +++ b/packages/client/hmr/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./client": { "types": "./lib/types/client/index.d.ts", "default": "./lib/client.js" @@ -49,12 +45,10 @@ "@deepseek-ai/cordis-plugin-loader": "workspace:~", "@deepseek-ai/dsh-client-modules": "workspace:*", "@deepseek-ai/dsh-host-webserver": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/cordis": "workspace:~" }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/client.js", "lib/types/**/*.d.ts" ] diff --git a/packages/client/hmr/src/invariant.ts b/packages/client/hmr/src/invariant.ts deleted file mode 100644 index 1054d91430..0000000000 --- a/packages/client/hmr/src/invariant.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-client-hmr`. - * @module @deepseek-ai/dsh-client-hmr/invariant - */ - -import type { Context, Fiber } from '@deepseek-ai/cordis' -import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' - -const PACKAGE_NAME = '@deepseek-ai/dsh-client-hmr' - -/** Cordis companion plugin name. */ -export const name = 'client-hmr-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Live fs.watchFile pollers (this package is the composition's only stat-poll user). */ -function statWatchers(): number { - return process.getActiveResourcesInfo().filter(kind => kind === 'StatWatcher').length -} - -/** - * Owned relation: every bundle stat watcher the node half starts must die - * with its fiber — a surviving poller would keep re-hashing bundles for a - * torn-down dev chain forever. Checked as a baseline delta: the StatWatcher - * count observed at fiber creation must be restored once disposal has drained - * the fiber's effects (`internal/plugin` fires at dispose start; the microtask - * hop lets the disposer queue its unload before `fiber.await()` joins it). - * SSE-connection and listener teardown live inside the same ctx.effect - * disposers, so the watcher count is the relation's observable proxy. - */ -const install: InvariantInstaller = (ctx, fail) => { - const baselines = new WeakMap() - // Async listener by design: emitPluginDisposed awaits-and-logs returned - // promises, so a violation surfaces loudly instead of unhandled. - // oxlint-disable-next-line typescript/no-misused-promises - ctx.on('internal/plugin', async (fiber) => { - if (fiber.name !== 'client-hmr') return - if (fiber.uid !== null) { - baselines.set(fiber, statWatchers()) - return - } - const baseline = baselines.get(fiber) - if (baseline === undefined) return - await Promise.resolve() - await fiber.await() - const remaining = statWatchers() - if (remaining > baseline) { - fail(`client-hmr fiber disposed but ${remaining - baseline} bundle stat watcher(s) survived teardown`) - } - }, { global: true }) -} - -/** - * 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)) diff --git a/packages/client/hmr/tsconfig.json b/packages/client/hmr/tsconfig.json index 29e15882e6..00226929f0 100644 --- a/packages/client/hmr/tsconfig.json +++ b/packages/client/hmr/tsconfig.json @@ -30,9 +30,6 @@ }, { "path": "../../../vendor/schemastery" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/client/hmr/tsdown.config.ts b/packages/client/hmr/tsdown.config.ts index 9a28baba7c..69b96b4229 100644 --- a/packages/client/hmr/tsdown.config.ts +++ b/packages/client/hmr/tsdown.config.ts @@ -1,3 +1,3 @@ import { clientBundle } from '../tsdown.client.ts' -export default clientBundle('@deepseek-ai/dsh-client-hmr', ['lib/types/index.js', 'lib/types/invariant.js']) +export default clientBundle('@deepseek-ai/dsh-client-hmr', ['lib/types/index.js']) diff --git a/packages/client/locale/README.i18n.yaml b/packages/client/locale/README.i18n.yaml index 61fd606960..6f71f84b69 100644 --- a/packages/client/locale/README.i18n.yaml +++ b/packages/client/locale/README.i18n.yaml @@ -60,5 +60,5 @@ en: 500ede048ef44c6a zh: f741fae55b29a627 /deepseek-ai-dsh-client-locale/known-limitations-and-deferred-work/dev-note: - en: 82fb34d8a128a0ac - zh: fa3f40c900032aae + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/locale/README.md b/packages/client/locale/README.md index a21f61dd93..cd5a313afd 100644 --- a/packages/client/locale/README.md +++ b/packages/client/locale/README.md @@ -146,5 +146,3 @@ These limits define where localization is incomplete or frozen at registration t None. - -**Runtime invariant:** No companion is published. The locale catalog and dictionaries have no independent runtime source to compare against; registration disposal, preference resolution, and fallback lookup are asserted by behavior specs. diff --git a/packages/client/locale/README.zh.md b/packages/client/locale/README.zh.md index c93b555f96..dfd42fde8a 100644 --- a/packages/client/locale/README.zh.md +++ b/packages/client/locale/README.zh.md @@ -146,5 +146,3 @@ Host 通过 settings 服务为 loopback 页面持久化偏好。Client 会刻意 无。 - -**运行时不变式:** 不发布伴生入口。locale catalog 与字典没有可供交叉核对的独立运行时来源;注册释放、偏好解析和 fallback 查找由行为测试覆盖。 diff --git a/packages/client/modules/package.json b/packages/client/modules/package.json index da38169d13..316a37c0c2 100644 --- a/packages/client/modules/package.json +++ b/packages/client/modules/package.json @@ -22,10 +22,6 @@ "types": "./lib/types/client/index.d.ts", "default": "./lib/client.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./src/*": "./src/*", "./package.json": "./package.json" }, @@ -44,14 +40,12 @@ "devDependencies": { "@deepseek-ai/cordis-plugin-loader": "workspace:~", "@deepseek-ai/dsh-host-webserver": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-package-manifest": "workspace:*", "@deepseek-ai/dsh-client-store": "workspace:*" }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/client.js", "lib/types/**/*.d.ts" ], diff --git a/packages/client/modules/src/invariant.ts b/packages/client/modules/src/invariant.ts deleted file mode 100644 index c7c9b6d7c4..0000000000 --- a/packages/client/modules/src/invariant.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-client-modules`. - * @module @deepseek-ai/dsh-client-modules/invariant - */ - -/* jscpd:ignore-start */ -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' - -const PACKAGE_NAME = '@deepseek-ai/dsh-client-modules' - -/** Cordis companion plugin name. */ -export const name = 'client-modules-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** - * Owned relation: the node half's boot entry graph must stay self-consistent - * — every row must resolve a clientPath under the same id (the - * `plugins//client.js` reference it advertises would otherwise 404 in the - * browser that just received the graph). Checked on every scan trigger (cordis - * 'internal/plugin'): graph() and clientPath() read the same table object, - * so the relation holds at any instant — no need to wait out the node half's - * own microtask-debounced flush. - */ -const install: InvariantInstaller = (ctx, fail) => { - ctx.on('internal/plugin', () => { - const host = ctx.get('clientModules') - if (host === undefined) return // browser side / host without the node half: nothing to audit - for (const row of host.graph().entries) { - if (host.clientPath(row.id) === undefined) { - fail(`web plugin graph row "${row.id}" advertises ${row.url} but resolves no client bundle path — the served __DSH_BOOT__ would 404 on fetch`) - } - } - }, { global: true }) -} - -/** - * 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/client/modules/tsconfig.json b/packages/client/modules/tsconfig.json index cb5c506f4f..cdcd1282f4 100644 --- a/packages/client/modules/tsconfig.json +++ b/packages/client/modules/tsconfig.json @@ -12,7 +12,6 @@ {"path": "../../../vendor/loader"}, {"path": "../../host/webserver"}, {"path": "../../util/package-manifest"}, - {"path": "../../runtime-diagnostics/invariants"}, {"path": "../store"} ] } diff --git a/packages/client/modules/tsdown.config.ts b/packages/client/modules/tsdown.config.ts index b6be2bc737..06db107f32 100644 --- a/packages/client/modules/tsdown.config.ts +++ b/packages/client/modules/tsdown.config.ts @@ -2,5 +2,5 @@ import { clientBundle } from '../tsdown.client.ts' export default clientBundle( '@deepseek-ai/dsh-client-modules', - ['lib/types/index.js', 'lib/types/invariant.js'], + ['lib/types/index.js'], ) diff --git a/packages/client/product-analytics/README.i18n.yaml b/packages/client/product-analytics/README.i18n.yaml index d55501f808..9b47a9f66e 100644 --- a/packages/client/product-analytics/README.i18n.yaml +++ b/packages/client/product-analytics/README.i18n.yaml @@ -18,8 +18,8 @@ en: 3932aceab765ab96 zh: 8b5174fff1208053 /deepseek-ai-dsh-client-product-analytics/understand-the-implementation: - en: 416b7e925f0852c9 - zh: c0b83bacf18e91b1 + en: aa37aaf70a94e620 + zh: 23b9a01ed88cb772 /deepseek-ai-dsh-client-product-analytics/model-experience: en: 291751b16b8a37e7 zh: 3b38008f4364ec41 diff --git a/packages/client/product-analytics/README.md b/packages/client/product-analytics/README.md index 57d3cae72c..941025b2de 100644 --- a/packages/client/product-analytics/README.md +++ b/packages/client/product-analytics/README.md @@ -39,7 +39,7 @@ Only `plugin_toggle` carries `plugin_type`: plugin rows use `plugin`, and packag
Implementation internals — click to expand -Presentational components receive callbacks. Shared Client services resolve the optional Desktop analytics sender, which requires both the native preload marker and the synchronized Host policy. The sender retains its own RPC context when called by other plugins. Native actions use the authenticated Host API; its generated Typert validator accepts only the typed event fields. The Host enriches identity immediately before queueing. It observes live compaction events without replaying Session history. No runtime invariant companion is published because collection has no independent delivery acknowledgement to compare. +Presentational components receive callbacks. Shared Client services resolve the optional Desktop analytics sender, which requires both the native preload marker and the synchronized Host policy. The sender retains its own RPC context when called by other plugins. Native actions use the authenticated Host API; its generated Typert validator accepts only the typed event fields. The Host enriches identity immediately before queueing. It observes live compaction events without replaying Session history.
diff --git a/packages/client/product-analytics/README.zh.md b/packages/client/product-analytics/README.zh.md index a3b0fb22e6..be44c8e742 100644 --- a/packages/client/product-analytics/README.zh.md +++ b/packages/client/product-analytics/README.zh.md @@ -39,7 +39,7 @@ Electron 将构建内联的 `DSH_CLIENT_VERSION` 传给 Host;埋点与导出
实现细节——点击展开 -展示组件接收回调。共享 Client 服务读取可选的桌面埋点发送器,后者要求同时存在原生 preload 标识和 同步的 Host 采集策略。发送器在被其他插件调用时保留自身的 RPC 上下文。原生操作使用已认证的 Host API,其生成的 Typert 校验器只接受类型中声明的事件字段。Host 在入队前补充身份信息,并监听实时压缩事件,不重放会话历史。本包不发布运行时不变量伴随模块,因为采集没有可用于比较的独立交付确认。 +展示组件接收回调。共享 Client 服务读取可选的桌面埋点发送器,后者要求同时存在原生 preload 标识和 同步的 Host 采集策略。发送器在被其他插件调用时保留自身的 RPC 上下文。原生操作使用已认证的 Host API,其生成的 Typert 校验器只接受类型中声明的事件字段。Host 在入队前补充身份信息,并监听实时压缩事件,不重放会话历史。
diff --git a/packages/client/resources/README.i18n.yaml b/packages/client/resources/README.i18n.yaml index 1133f89a1a..23b8157bc8 100644 --- a/packages/client/resources/README.i18n.yaml +++ b/packages/client/resources/README.i18n.yaml @@ -45,5 +45,5 @@ en: c79b85b72a55cdd4 zh: 612c401b31c3a136 /deepseek-ai-dsh-client-resources/known-limitations-and-deferred-work/dev-note: - en: 9bbb90f2302b50e0 - zh: f16c794374fc1079 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/resources/README.md b/packages/client/resources/README.md index 6b5afc8223..57a50209a2 100644 --- a/packages/client/resources/README.md +++ b/packages/client/resources/README.md @@ -103,5 +103,3 @@ None; resource streams do not assemble model requests. None. - -**Runtime invariant:** No companion is published. Provider ownership and holder counts have one owner, the registry, with no independent runtime source to compare against; registration disposal and the open/close lifecycle are asserted by behavior specs. diff --git a/packages/client/resources/README.zh.md b/packages/client/resources/README.zh.md index b56ee6bcde..db76a5d41b 100644 --- a/packages/client/resources/README.zh.md +++ b/packages/client/resources/README.zh.md @@ -103,5 +103,3 @@ export function apply(ctx) { 无。 - -**运行时不变式:** 不发布伴生入口。提供方归属与持有者计数只有注册表这一个拥有者,没有可供比对的独立运行时来源;注册的 dispose 与打开/关闭生命周期由行为测试断言。 diff --git a/packages/client/shortcuts/README.i18n.yaml b/packages/client/shortcuts/README.i18n.yaml index 21adffeaa1..572dc15172 100644 --- a/packages/client/shortcuts/README.i18n.yaml +++ b/packages/client/shortcuts/README.i18n.yaml @@ -33,5 +33,5 @@ en: 323e99bf933b4c6c zh: 0a7ecdb2dcdb1288 /deepseek-ai-dsh-client-shortcuts/known-limitations-and-deferred-work/dev-note: - en: 09894c06f9d76ac0 - zh: d166e1338ae660e4 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/shortcuts/README.md b/packages/client/shortcuts/README.md index 61d0ac7000..97b1e1bb75 100644 --- a/packages/client/shortcuts/README.md +++ b/packages/client/shortcuts/README.md @@ -90,5 +90,3 @@ None; this package neither assembles nor sends a provider request. None. - -**Runtime invariant:** No companion is published. The catalog and binding index are derived together from the private registry; there is no independently maintained runtime relationship to inspect. diff --git a/packages/client/shortcuts/README.zh.md b/packages/client/shortcuts/README.zh.md index 30110aed87..31b2c692d3 100644 --- a/packages/client/shortcuts/README.zh.md +++ b/packages/client/shortcuts/README.zh.md @@ -90,5 +90,3 @@ Desktop 原生菜单和内嵌 frame 使用与 DOM 输入相同的命令注册表 无。 - -**Runtime invariant:** 不发布配套检查模块。目录和键位索引由私有注册表同步派生,没有独立维护、需要检查的运行时关系。 diff --git a/packages/client/store/README.i18n.yaml b/packages/client/store/README.i18n.yaml index 27ca8bddc8..5da729b805 100644 --- a/packages/client/store/README.i18n.yaml +++ b/packages/client/store/README.i18n.yaml @@ -24,5 +24,5 @@ en: 3190cd5b744025fe zh: f559c3316b6a7da4 /deepseek-ai-dsh-client-store/known-limitations-and-deferred-work/dev-note: - en: dd767840e33344a1 - zh: 0e06a491e39acf38 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/store/README.md b/packages/client/store/README.md index 0d4525ab6e..a83f5aa420 100644 --- a/packages/client/store/README.md +++ b/packages/client/store/README.md @@ -44,5 +44,3 @@ None; the stores neither assemble nor send model requests. None. - -**Runtime invariant:** No companion is published. The package exports a library engine and creates no process-global state; each store instance is covered by its owning tests. diff --git a/packages/client/store/README.zh.md b/packages/client/store/README.zh.md index 1d5548ab38..15a62b4bae 100644 --- a/packages/client/store/README.zh.md +++ b/packages/client/store/README.zh.md @@ -44,5 +44,3 @@ kind: "package-library" 无。 - -**运行时不变式:** 不发布伴生入口。本包只导出库引擎,不创建进程全局状态;每个存储实例由其所属测试覆盖。 diff --git a/packages/client/tsdown.client.ts b/packages/client/tsdown.client.ts index 215001dbb9..386beef230 100644 --- a/packages/client/tsdown.client.ts +++ b/packages/client/tsdown.client.ts @@ -101,9 +101,7 @@ function browserSourcePath(source: string, sourcemapPath: string): string { * original source content, into the standalone plugin map. * @param id - plugin id (package name), stamped into the __ModuleLoader__.load * handoff and onto the injected style tags. - * @param libEntry - node-half entries, spelled at the call site so the - * package-invariants gate can see `lib/types/invariant.js` in each package's - * own tsdown.config.ts (a preset-side glob hides it from the mechanical check). + * @param libEntry - node-half entries. * @param options - phase placement, lib overrides, companion Node configs, and optional per-file Client banner. * @returns ENV-selected tsdown config for the current build face. */ diff --git a/packages/client/ui-agent-preset/README.i18n.yaml b/packages/client/ui-agent-preset/README.i18n.yaml index a1086951fe..e1ab2a5ab1 100644 --- a/packages/client/ui-agent-preset/README.i18n.yaml +++ b/packages/client/ui-agent-preset/README.i18n.yaml @@ -33,5 +33,5 @@ en: 2c5e994e86b1ae4a zh: 8bc5a90d3a2133ba /deepseek-ai-dsh-client-ui-agent-preset/known-limitations-and-deferred-work/dev-note: - en: 6ce953763f1dc0f0 - zh: 2d8cabb28ccd151b + en: e67f633dc6fe7773 + zh: 8ec3e73528ddb1a6 diff --git a/packages/client/ui-agent-preset/README.md b/packages/client/ui-agent-preset/README.md index b66072911d..a896c050d5 100644 --- a/packages/client/ui-agent-preset/README.md +++ b/packages/client/ui-agent-preset/README.md @@ -80,5 +80,3 @@ Selection changes affect only later tasks; existing plugins and prompts remain u None. - -**Runtime invariant:** No companion is published; the Host registry owns state, and component tests cover client presentation and selection. diff --git a/packages/client/ui-agent-preset/README.zh.md b/packages/client/ui-agent-preset/README.zh.md index 434b74e026..1002bdd374 100644 --- a/packages/client/ui-agent-preset/README.zh.md +++ b/packages/client/ui-agent-preset/README.zh.md @@ -80,5 +80,3 @@ kind: "package-reference" 无。 - -**运行时不变量:** 不发布 companion;状态由 Host 注册表拥有,客户端展示与选择行为由组件测试验证。 diff --git a/packages/client/ui-approval/README.i18n.yaml b/packages/client/ui-approval/README.i18n.yaml index 7fef3ae2d8..1b646cd6a8 100644 --- a/packages/client/ui-approval/README.i18n.yaml +++ b/packages/client/ui-approval/README.i18n.yaml @@ -24,5 +24,5 @@ en: ae644102ed52f7ee zh: e109de1d517a3a94 /deepseek-ai-dsh-client-ui-approval/known-limitations-and-deferred-work/dev-note: - en: 240c92ce5e544116 - zh: 5bfae598cc20a3af + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/ui-approval/README.md b/packages/client/ui-approval/README.md index 4986683615..ca21af33fb 100644 --- a/packages/client/ui-approval/README.md +++ b/packages/client/ui-approval/README.md @@ -45,5 +45,3 @@ None; approval request and response rendering does not alter a model request. None. - -**Runtime invariant:** No companion is published. Registries own and observe the Remote listener and temporary Slot entry. diff --git a/packages/client/ui-approval/README.zh.md b/packages/client/ui-approval/README.zh.md index 7015ba5f44..a9a714ef68 100644 --- a/packages/client/ui-approval/README.zh.md +++ b/packages/client/ui-approval/README.zh.md @@ -45,5 +45,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。Remote listener 与临时 Slot entry 由各自注册表持有并观察。 diff --git a/packages/client/ui-attachment/README.i18n.yaml b/packages/client/ui-attachment/README.i18n.yaml index b4d276c42f..ff89654059 100644 --- a/packages/client/ui-attachment/README.i18n.yaml +++ b/packages/client/ui-attachment/README.i18n.yaml @@ -42,5 +42,5 @@ en: 9a16cb4f174464cf zh: 3de4b8288a74c594 /deepseek-ai-dsh-client-ui-attachment/known-limitations-and-deferred-work/dev-note: - en: a06a37a39a68c325 - zh: d465458aa4406397 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-attachment/README.md b/packages/client/ui-attachment/README.md index af2ed79cc2..473429094b 100644 --- a/packages/client/ui-attachment/README.md +++ b/packages/client/ui-attachment/README.md @@ -104,5 +104,3 @@ These limits define the current attachment surface. They are package constraints None. - -**Runtime invariant:** No companion is published. The package contributes only effect-owned slot entries; the slot registry owns their lifecycle and validates their declarations. diff --git a/packages/client/ui-attachment/README.zh.md b/packages/client/ui-attachment/README.zh.md index 64d12869c5..7487ae47a7 100644 --- a/packages/client/ui-attachment/README.zh.md +++ b/packages/client/ui-attachment/README.zh.md @@ -104,5 +104,3 @@ Trajectory 附件行使用 48px 方形缩略图,完整缩放图片而不裁剪 无。 - -**运行时不变式:** 不发布伴生入口。本包只贡献由 effect 持有的 slot entry;slot 注册表负责其生命周期并校验声明。 diff --git a/packages/client/ui-brand-official/README.i18n.yaml b/packages/client/ui-brand-official/README.i18n.yaml index 565a544c51..980d6969d3 100644 --- a/packages/client/ui-brand-official/README.i18n.yaml +++ b/packages/client/ui-brand-official/README.i18n.yaml @@ -39,5 +39,5 @@ en: 89e2b4bd095a0265 zh: a002f0c8a60267ad /deepseek-ai-dsh-client-ui-brand-official/known-limitations-and-deferred-work/dev-note: - en: e368635c2e69b577 - zh: 6b0fcfc1e089e5d6 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-brand-official/README.md b/packages/client/ui-brand-official/README.md index f8687047ca..458d4be020 100644 --- a/packages/client/ui-brand-official/README.md +++ b/packages/client/ui-brand-official/README.md @@ -88,5 +88,3 @@ These limits define how brand presentation is supplied. They are current package None. - -**Runtime invariant:** No companion is published. The package retains no mutable state, and its three slot occupants install and leave through one transactional effect. diff --git a/packages/client/ui-brand-official/README.zh.md b/packages/client/ui-brand-official/README.zh.md index e23b0c5972..8055844e52 100644 --- a/packages/client/ui-brand-official/README.zh.md +++ b/packages/client/ui-brand-official/README.zh.md @@ -88,5 +88,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。本包不保留可变状态,三个 slot occupant 通过同一个事务性 effect 安装和释放。 diff --git a/packages/client/ui-chat/README.i18n.yaml b/packages/client/ui-chat/README.i18n.yaml index 548db3905d..297ad067e8 100644 --- a/packages/client/ui-chat/README.i18n.yaml +++ b/packages/client/ui-chat/README.i18n.yaml @@ -48,5 +48,5 @@ en: 811787a5d2feaa90 zh: 3f1697bbc1fc7691 /deepseek-ai-dsh-client-ui-chat/known-limitations-and-deferred-work/dev-note: - en: b8b7eeb4ae675d2a - zh: fbe7022074ba81b9 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/ui-chat/README.md b/packages/client/ui-chat/README.md index f09b12c461..426b34e3bb 100644 --- a/packages/client/ui-chat/README.md +++ b/packages/client/ui-chat/README.md @@ -180,5 +180,3 @@ None; Chat presentation does not assemble or mutate provider requests. None. - -**Runtime invariant:** No companion is published. Conversation and Slot registration enforce Chat target consistency. diff --git a/packages/client/ui-chat/README.zh.md b/packages/client/ui-chat/README.zh.md index 599c4cb0cf..e953a285b5 100644 --- a/packages/client/ui-chat/README.zh.md +++ b/packages/client/ui-chat/README.zh.md @@ -180,5 +180,3 @@ Chat 会在历史前插与 renderer 重新挂载时恢复语义锚点,并且 无。 - -**运行时不变式:** 不发布伴生入口。Conversation 与 slot 注册已经强制 Chat target 一致。 diff --git a/packages/client/ui-commands/README.i18n.yaml b/packages/client/ui-commands/README.i18n.yaml index ba51e078fb..6177afb95c 100644 --- a/packages/client/ui-commands/README.i18n.yaml +++ b/packages/client/ui-commands/README.i18n.yaml @@ -42,5 +42,5 @@ en: cfce26d6a603cb7d zh: 8332a8d1ad44abcf /deepseek-ai-dsh-client-ui-commands/known-limitations-and-deferred-work/dev-note: - en: 2f5e19244ac6c305 - zh: e43b52ae5b4af798 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-commands/README.md b/packages/client/ui-commands/README.md index eb354816d2..8bf2cdc958 100644 --- a/packages/client/ui-commands/README.md +++ b/packages/client/ui-commands/README.md @@ -101,5 +101,3 @@ These limits define the current command surface. They are current package constr None. - -**Runtime invariant:** No companion is published. This browser-side source uses the wire command directory; it emits no Cordis events and owns no cross-plugin mutable state. Its dispatch and cache behavior are asserted by this package's specs. diff --git a/packages/client/ui-commands/README.zh.md b/packages/client/ui-commands/README.zh.md index de78e36758..792611b54e 100644 --- a/packages/client/ui-commands/README.zh.md +++ b/packages/client/ui-commands/README.zh.md @@ -101,5 +101,3 @@ composer 携带图片或通用文件提交时,只有声明了 `input.attachmen 无。 - -**运行时不变式:** 不发布伴生入口。这是基于 wire 命令目录的浏览器侧 source,不发出 Cordis 事件,也不持有跨插件可变状态;dispatch 与 cache 行为由包测试覆盖。 diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index 95bf8db2bf..caa05e8046 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -33,5 +33,5 @@ en: 38d243630d0e4c38 zh: a58cdc35b664020b /deepseek-ai-dsh-client-ui-conversation/known-limitations-and-deferred-work/dev-note: - en: 48c7f1797012293f - zh: ec56b609a1b5fb9e + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index b53f2c15eb..c4ba072938 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -168,5 +168,3 @@ None; Conversation assembly and browser input state do not alter provider-side p None. - -**Runtime invariant:** No companion is published. Conversation Definitions, target builders, and Views are already validated by their owning registries and the Slot ledger. diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index dc18970069..79fead9b8e 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -168,5 +168,3 @@ selector 必须是 owner currency 的纯函数。非 null 返回值作为 `match 无。 - -**运行时不变式:** 不发布伴生入口。Conversation Definition、target builder 与 View 已由其所属注册表和 Slot ledger 校验。 diff --git a/packages/client/ui-deliverables/README.i18n.yaml b/packages/client/ui-deliverables/README.i18n.yaml index e9516416e7..dce70c3a3a 100644 --- a/packages/client/ui-deliverables/README.i18n.yaml +++ b/packages/client/ui-deliverables/README.i18n.yaml @@ -54,5 +54,5 @@ en: 3b59e3b776c52ba4 zh: ceee2728a0e7779b /deepseek-ai-dsh-client-ui-deliverables/known-limitations-and-deferred-work/dev-note: - en: a55f817f597aa646 - zh: aba9a91ecde566ab + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-deliverables/README.md b/packages/client/ui-deliverables/README.md index 8805bbf43e..ebd9dfdfec 100644 --- a/packages/client/ui-deliverables/README.md +++ b/packages/client/ui-deliverables/README.md @@ -128,5 +128,3 @@ These limits define the current deliverables vocabulary. They are current packag None. - -**Runtime invariant:** No companion is published. Prompt, slot, dictionary, file-action route, and optional service registrations are effect-owned; the Session log owns declarations and the filesystem owns file contents. diff --git a/packages/client/ui-deliverables/README.zh.md b/packages/client/ui-deliverables/README.zh.md index 493082ef65..1c322f266b 100644 --- a/packages/client/ui-deliverables/README.zh.md +++ b/packages/client/ui-deliverables/README.zh.md @@ -128,5 +128,3 @@ Node 半部注册[模型体验](#model-experience)所述的静态 `ui:deliverabl 无。 - -**运行时不变式:** 不发布伴生入口。提示词、slot、dictionary、文件操作路由与可选 service 注册归 effect 所有;Session 日志拥有声明,文件系统拥有文件内容。 diff --git a/packages/client/ui-directory-picker-browse/README.i18n.yaml b/packages/client/ui-directory-picker-browse/README.i18n.yaml index e89c02eac8..2ba253016b 100644 --- a/packages/client/ui-directory-picker-browse/README.i18n.yaml +++ b/packages/client/ui-directory-picker-browse/README.i18n.yaml @@ -36,5 +36,5 @@ en: 20e381071e1644e2 zh: 9260bcb09a52dd8c /deepseek-ai-dsh-client-ui-directory-picker-browse/known-limitations-and-deferred-work/dev-note: - en: d31303387d07ac33 - zh: 5613857edfa8b0b4 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-directory-picker-browse/README.md b/packages/client/ui-directory-picker-browse/README.md index 0e760fa51c..7b62b297b6 100644 --- a/packages/client/ui-directory-picker-browse/README.md +++ b/packages/client/ui-directory-picker-browse/README.md @@ -85,5 +85,3 @@ These limits define the current browse surface. They are current package constra None. - -**Runtime invariant:** No companion is published. The plugin registers one workspace directory-flow owner whose disposal the HMR-safety spec proves, and every listing it shows is re-read from the Host on demand rather than held here. diff --git a/packages/client/ui-directory-picker-browse/README.zh.md b/packages/client/ui-directory-picker-browse/README.zh.md index 74b0e782db..acb82edc4a 100644 --- a/packages/client/ui-directory-picker-browse/README.zh.md +++ b/packages/client/ui-directory-picker-browse/README.zh.md @@ -85,5 +85,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。插件只注册一个工作区目录流程持有方,其资源释放由 HMR(热模块替换)安全规范验证;它显示的每个目录列表都会按需从 Host 重新读取,而不会保存在本包中。 diff --git a/packages/client/ui-directory-picker-native/README.i18n.yaml b/packages/client/ui-directory-picker-native/README.i18n.yaml index ed8f471b40..8d0a7b2fe7 100644 --- a/packages/client/ui-directory-picker-native/README.i18n.yaml +++ b/packages/client/ui-directory-picker-native/README.i18n.yaml @@ -36,5 +36,5 @@ en: d041273e43ca0541 zh: 8b7a718ed22e7f18 /deepseek-ai-dsh-client-ui-directory-picker-native/known-limitations-and-deferred-work/dev-note: - en: b2aec04118566ad8 - zh: 15639689899fc5a1 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-directory-picker-native/README.md b/packages/client/ui-directory-picker-native/README.md index 73115c4413..e03a867d9d 100644 --- a/packages/client/ui-directory-picker-native/README.md +++ b/packages/client/ui-directory-picker-native/README.md @@ -88,5 +88,3 @@ These limits define when the native chooser fits. They are current package const None. - -**Runtime invariant:** No companion is published. The plugin registers a renderless flow occupant into two workspace holes as one transactional effect, whose disposal the HMR-safety spec proves, and it retains no state between picks. diff --git a/packages/client/ui-directory-picker-native/README.zh.md b/packages/client/ui-directory-picker-native/README.zh.md index 5cf92adfb4..70202db1f8 100644 --- a/packages/client/ui-directory-picker-native/README.zh.md +++ b/packages/client/ui-directory-picker-native/README.zh.md @@ -88,5 +88,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。插件将一个无渲染 flow occupant 作为一个事务性 effect 注册到两个 workspace hole;HMR 安全性规范证明该 effect 的释放行为,并且插件在各次 pick 之间不保留状态。 diff --git a/packages/client/ui-dockkit/README.i18n.yaml b/packages/client/ui-dockkit/README.i18n.yaml index c92fb30883..23d3c8e84a 100644 --- a/packages/client/ui-dockkit/README.i18n.yaml +++ b/packages/client/ui-dockkit/README.i18n.yaml @@ -36,5 +36,5 @@ en: 4323810ca5a87e6c zh: 3a24af2ea4467ab7 /deepseek-ai-dsh-client-ui-dockkit/known-limitations-and-deferred-work/dev-note: - en: 9eda9e1ea3785ca7 - zh: b734eb644b27e474 + en: 45680a19e42f401a + zh: 5500de88331c8a59 diff --git a/packages/client/ui-dockkit/README.md b/packages/client/ui-dockkit/README.md index df84a12abf..9ca8b6c447 100644 --- a/packages/client/ui-dockkit/README.md +++ b/packages/client/ui-dockkit/README.md @@ -113,5 +113,3 @@ None. The embedder supplies effective close keycaps and ARIA combinations for tab and floating close controls. Close controls expose the same tooltip on hover and keyboard focus. Tab close tooltips stay hidden while their pane's context menu is open. - -**Runtime invariant:** No companion is published. The engine is pure functions over plain data and the components report intents only; the operation sequence's invertibility and the settle rule are asserted directly by this package's engine specs, and no cordis service is provided or observed. diff --git a/packages/client/ui-dockkit/README.zh.md b/packages/client/ui-dockkit/README.zh.md index 64df0ad36b..b0104aa678 100644 --- a/packages/client/ui-dockkit/README.zh.md +++ b/packages/client/ui-dockkit/README.zh.md @@ -113,5 +113,3 @@ Sidebar 使用的 `DockLayout` 在选中项变化、跨格移动与浮动期间 嵌入方为标签和浮动面板的关闭控件提供有效关闭键帽及 ARIA 组合。关闭控件在悬停和键盘聚焦时显示相同 Tooltip。分栏的上下文菜单打开时,该分栏的标签关闭 Tooltip 保持隐藏。 - -**运行时不变量:** 不发布 companion。引擎是作用于纯数据的纯函数,组件只上报意图;操作序列的可逆性与 settle 规则由本包的引擎 spec 直接断言,不提供也不观察任何 Cordis 服务。 diff --git a/packages/client/ui-goal/README.i18n.yaml b/packages/client/ui-goal/README.i18n.yaml index 180da222a0..628c67f401 100644 --- a/packages/client/ui-goal/README.i18n.yaml +++ b/packages/client/ui-goal/README.i18n.yaml @@ -39,5 +39,5 @@ en: bbb97ea1b94eae0f zh: 8e6b0e0516746dcf /deepseek-ai-dsh-client-ui-goal/known-limitations-and-deferred-work/dev-note: - en: 4548b53e34e6f005 - zh: ff186c76070b52bd + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-goal/README.md b/packages/client/ui-goal/README.md index 5867bcfd90..c8fe5c0491 100644 --- a/packages/client/ui-goal/README.md +++ b/packages/client/ui-goal/README.md @@ -89,5 +89,3 @@ These limits define the current goal surface. They are current package constrain None. - -**Runtime invariant:** No companion is published. There is a single GoalBar dock registration whose disposal is proven by the HMR-safety spec — durable state arrives on the goal projection, process-local activation arrives through the entry's private hook source, and that source subscribes only while the framework hook observes it. diff --git a/packages/client/ui-goal/README.zh.md b/packages/client/ui-goal/README.zh.md index 908bba2d02..83d33fe35a 100644 --- a/packages/client/ui-goal/README.zh.md +++ b/packages/client/ui-goal/README.zh.md @@ -89,5 +89,3 @@ Web GUI 的 goal 界面同时显示持久 goal 状态及当前的进程本地激 无。 - -**运行时不变式:** 不发布伴生入口。插件只注册一个 GoalBar dock,其释放已由 HMR(热模块替换)安全性用例证明;持久状态来自 goal projection,进程本地 activation 来自入口私有钩子源,且该源只在框架钩子观察期间订阅。 diff --git a/packages/client/ui-input-trigger/README.i18n.yaml b/packages/client/ui-input-trigger/README.i18n.yaml index efe8c0a791..775f5d4172 100644 --- a/packages/client/ui-input-trigger/README.i18n.yaml +++ b/packages/client/ui-input-trigger/README.i18n.yaml @@ -36,5 +36,5 @@ en: 560e5df72c0fb698 zh: ed6fa149bc90258a /deepseek-ai-dsh-client-ui-input-trigger/known-limitations-and-deferred-work/dev-note: - en: 749345762f3fe660 - zh: 2e58212ba4db347f + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-input-trigger/README.md b/packages/client/ui-input-trigger/README.md index 32e8fe3083..62088481ce 100644 --- a/packages/client/ui-input-trigger/README.md +++ b/packages/client/ui-input-trigger/README.md @@ -89,5 +89,3 @@ These limits define the current trigger pipeline. They are current package const None. - -**Runtime invariant:** No companion is published. The trigger pipeline is a browser-side pure core (detect/reduce/match) plus a registry whose disposal is proven by the HMR-safety spec; it emits no cordis events and owns no cross-plugin mutable state. diff --git a/packages/client/ui-input-trigger/README.zh.md b/packages/client/ui-input-trigger/README.zh.md index f24447225a..2751dff3e0 100644 --- a/packages/client/ui-input-trigger/README.zh.md +++ b/packages/client/ui-input-trigger/README.zh.md @@ -89,5 +89,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。触发流水线是浏览器侧纯内核(检测/归约/匹配)加一个注册表,其资源释放已由 HMR(热模块替换)安全性测试证明;它不发出 Cordis 事件,也不持有跨插件可变状态。 diff --git a/packages/client/ui-jobs/README.i18n.yaml b/packages/client/ui-jobs/README.i18n.yaml index de3a7df885..2fa6a0f252 100644 --- a/packages/client/ui-jobs/README.i18n.yaml +++ b/packages/client/ui-jobs/README.i18n.yaml @@ -39,5 +39,5 @@ en: 8be824a877852373 zh: bf4ae00ee70273b5 /deepseek-ai-dsh-client-ui-jobs/known-limitations-and-deferred-work/dev-note: - en: 2e50937c500fbfd6 - zh: 8b86762471d56dcc + en: e67f633dc6fe7773 + zh: b532016223ceceef diff --git a/packages/client/ui-jobs/README.md b/packages/client/ui-jobs/README.md index 0c530b802b..35441bd0fb 100644 --- a/packages/client/ui-jobs/README.md +++ b/packages/client/ui-jobs/README.md @@ -92,5 +92,3 @@ These limits define current package constraints, not a task backlog. None. - -**Runtime invariant:** No companion is published. This package is a read-only projection of the `ctx.jobs` rosters and views onto one header slot entry. It emits no Cordis events, owns no cross-plugin mutable state, and its single slot registration proves disposal through the HMR-safety spec. diff --git a/packages/client/ui-jobs/README.zh.md b/packages/client/ui-jobs/README.zh.md index 86ac289011..ced4523e9a 100644 --- a/packages/client/ui-jobs/README.zh.md +++ b/packages/client/ui-jobs/README.zh.md @@ -92,5 +92,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。本包只把 `ctx.jobs` 的名册与视图只读投影到一个 header slot,不发出 Cordis 事件,也不持有跨插件可变状态;其唯一的 slot 注册通过 HMR(热模块替换)安全性规范验证了资源释放行为。 diff --git a/packages/client/ui-layout/README.i18n.yaml b/packages/client/ui-layout/README.i18n.yaml index aab49e22e5..0a83e3530e 100644 --- a/packages/client/ui-layout/README.i18n.yaml +++ b/packages/client/ui-layout/README.i18n.yaml @@ -39,5 +39,5 @@ en: da92b97ac60d989f zh: 5816b861728f1a04 /deepseek-ai-dsh-client-ui-layout/known-limitations-and-deferred-work/dev-note: - en: 4671d1561021e834 - zh: cf6dba523a442cb5 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-layout/README.md b/packages/client/ui-layout/README.md index e32e9261d4..ceea5ddacd 100644 --- a/packages/client/ui-layout/README.md +++ b/packages/client/ui-layout/README.md @@ -103,5 +103,3 @@ These limits define the current layout behavior. They are current package constr None. - -**Runtime invariant:** No companion is published. The shell viewing-state store behind `ctx.layout` emits no Cordis events; clamp and track sequencing is asserted directly by this package's columns and service specs. diff --git a/packages/client/ui-layout/README.zh.md b/packages/client/ui-layout/README.zh.md index 83b29e59a9..e2dccc461e 100644 --- a/packages/client/ui-layout/README.zh.md +++ b/packages/client/ui-layout/README.zh.md @@ -103,5 +103,3 @@ Windows Electron 的 `data-windows-titlebar` 标记在所有列上方预留顶 无。 - -**运行时不变式:** 不发布伴生入口。外壳中 `ctx.layout` 背后的浏览状态存储不发出 Cordis 事件;clamp 与轨道的时序由本包各栏与服务规格直接断言。 diff --git a/packages/client/ui-message-feedback/README.i18n.yaml b/packages/client/ui-message-feedback/README.i18n.yaml index c596a64c51..e171909bad 100644 --- a/packages/client/ui-message-feedback/README.i18n.yaml +++ b/packages/client/ui-message-feedback/README.i18n.yaml @@ -36,5 +36,5 @@ en: 17939bf926bf384b zh: d4cb01a03076f761 /deepseek-ai-dsh-client-ui-message-feedback/known-limitations-and-deferred-work/dev-note: - en: 9b670f2a6e0fc0b4 - zh: 3eb9ed838ad61571 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-message-feedback/README.md b/packages/client/ui-message-feedback/README.md index b6c6f227eb..ef293870b6 100644 --- a/packages/client/ui-message-feedback/README.md +++ b/packages/client/ui-message-feedback/README.md @@ -89,5 +89,3 @@ These limits define the current feedback surface. They are current package const None. - -**Runtime invariant:** No companion is published. The plugin owns two slot registrations, one command decoration, the `feedbackUi` service, and one per-session controller-pair map, all released with the owning plugin fiber. The lifecycle spec verifies that the registrations and service are withdrawn and every controller pair is dropped when the fiber is disposed, so no second authority exists to check at runtime. diff --git a/packages/client/ui-message-feedback/README.zh.md b/packages/client/ui-message-feedback/README.zh.md index a294c73c87..fce7d27049 100644 --- a/packages/client/ui-message-feedback/README.zh.md +++ b/packages/client/ui-message-feedback/README.zh.md @@ -89,5 +89,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。插件持有两个 slot 注册、一个命令装饰、`feedbackUi` 服务,以及一个按 Session 划分的控制器对 map;它们都随所属插件 fiber 释放。生命周期规格测试验证,fiber 释放时会撤销所有注册和服务并丢弃所有控制器,因此不存在需要在运行时检查的第二权威来源。 diff --git a/packages/client/ui-model-selection/README.i18n.yaml b/packages/client/ui-model-selection/README.i18n.yaml index 9c22fdfbdf..4d2f30c8c5 100644 --- a/packages/client/ui-model-selection/README.i18n.yaml +++ b/packages/client/ui-model-selection/README.i18n.yaml @@ -42,5 +42,5 @@ en: 75b37954ceacd92a zh: a49d0361486e35c4 /deepseek-ai-dsh-client-ui-model-selection/known-limitations-and-deferred-work/dev-note: - en: 83bfc3dae3365b49 - zh: f7dc4d0654ec83e1 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-model-selection/README.md b/packages/client/ui-model-selection/README.md index 09b016ef3a..a073d9ec27 100644 --- a/packages/client/ui-model-selection/README.md +++ b/packages/client/ui-model-selection/README.md @@ -111,5 +111,3 @@ These limits define the current model surface. They are current package constrai None. - -**Runtime invariant:** No companion is published. The plugin registers a single command contribution, and the HMR-safety spec proves that the registration is disposed correctly. The plugin emits no Cordis events and owns no cross-plugin mutable state. diff --git a/packages/client/ui-model-selection/README.zh.md b/packages/client/ui-model-selection/README.zh.md index 12df7a98ed..bf719d839d 100644 --- a/packages/client/ui-model-selection/README.zh.md +++ b/packages/client/ui-model-selection/README.zh.md @@ -111,5 +111,3 @@ composer 的 `ModelSelect` 与 `/model` 选项构建器共用[提供方排序](s 无。 - -**运行时不变式:** 不发布伴生入口。插件只注册一个 command contribution,HMR(热模块替换)安全性测试证明该注册的 dispose 能正确完成;它不发出 Cordis 事件,也不持有跨插件可变状态。 diff --git a/packages/client/ui-open-in-app/README.i18n.yaml b/packages/client/ui-open-in-app/README.i18n.yaml index 1489618994..8fd2399184 100644 --- a/packages/client/ui-open-in-app/README.i18n.yaml +++ b/packages/client/ui-open-in-app/README.i18n.yaml @@ -36,5 +36,5 @@ en: 3d77fca62d3c8e86 zh: 3125d32a158e690a /deepseek-ai-dsh-client-ui-open-in-app/known-limitations-and-deferred-work/dev-note: - en: c79e15f681adeacd - zh: 6a30399297bde201 + en: 1c3373210deb62af + zh: c0226e5c231bd44b diff --git a/packages/client/ui-open-in-app/README.md b/packages/client/ui-open-in-app/README.md index 9b2e7e3d6b..d47256a783 100644 --- a/packages/client/ui-open-in-app/README.md +++ b/packages/client/ui-open-in-app/README.md @@ -93,5 +93,3 @@ The Host verifies the path through the composed filesystem before opening or rev The feature-level decisions, including the split into the host package and this surface, are recorded in the [promotion Agent Note](../../../.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.md); the document preview's file controls are recorded in the [default-application Agent Note](../../../.agents/notes/implemented/feature/2026-09-16-open-in-default-app-for-sidebar-files.md). - -**Runtime invariant:** No companion is published. The plugin registers one dictionary effect and six slot entries whose disposal the HMR-safety spec proves; application availability, the choice, and the desktop answer live in the controllers' snapshot stores with no second copy to diverge. diff --git a/packages/client/ui-open-in-app/README.zh.md b/packages/client/ui-open-in-app/README.zh.md index 8259f0695f..3d1983c2a4 100644 --- a/packages/client/ui-open-in-app/README.zh.md +++ b/packages/client/ui-open-in-app/README.zh.md @@ -93,5 +93,3 @@ Host 在打开或定位前通过当前文件系统验证路径。没有对应 Ho 功能层面的各项决定,包括拆分为主机包与本表面包,记录在[转正 Agent Note](../../../.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.zh.md);文档预览的文件控件记录在[默认应用 Agent Note](../../../.agents/notes/implemented/feature/2026-09-16-open-in-default-app-for-sidebar-files.zh.md)。 - -**运行时不变式:** 不发布伴生入口。插件注册一个词典 effect 和六个 slot 条目,HMR 安全性 spec 证明它们都会在资源释放时撤销;应用可用性、选择与桌面回答存储在控制器的快照存储中,不存在可能与之分歧的第二份副本。 diff --git a/packages/client/ui-permission-presets/README.i18n.yaml b/packages/client/ui-permission-presets/README.i18n.yaml index a9d029b666..3f24ef0979 100644 --- a/packages/client/ui-permission-presets/README.i18n.yaml +++ b/packages/client/ui-permission-presets/README.i18n.yaml @@ -39,5 +39,5 @@ en: 08cdc0ad196d12c1 zh: 1b65f5d30e219197 /deepseek-ai-dsh-client-ui-permission-presets/known-limitations-and-deferred-work/dev-note: - en: febc49a55a454923 - zh: b72c74e467fd6407 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-permission-presets/README.md b/packages/client/ui-permission-presets/README.md index b6e1d843a4..6798cc7f47 100644 --- a/packages/client/ui-permission-presets/README.md +++ b/packages/client/ui-permission-presets/README.md @@ -92,5 +92,3 @@ These limits define the current permission surfaces. They are current package co None. - -**Runtime invariant:** No companion is published. The command and slot contribution lifecycles are proven by the HMR-safety spec, while the browser-only Settings controller owns no host events or cross-plugin mutable state. diff --git a/packages/client/ui-permission-presets/README.zh.md b/packages/client/ui-permission-presets/README.zh.md index d95f037b1d..1c603b5eb5 100644 --- a/packages/client/ui-permission-presets/README.zh.md +++ b/packages/client/ui-permission-presets/README.zh.md @@ -92,5 +92,3 @@ General Settings 行经 `ctx.configForms` 读取显式暴露的 `permission` Set 无。 - -**运行时不变式:** 不发布伴生入口。命令与 slot 贡献的生命周期由 HMR(热模块替换)安全性测试验证;浏览器侧设置控制器不持有宿主事件或跨插件可变状态。 diff --git a/packages/client/ui-plan/README.i18n.yaml b/packages/client/ui-plan/README.i18n.yaml index 29501d3d4e..e84a27c641 100644 --- a/packages/client/ui-plan/README.i18n.yaml +++ b/packages/client/ui-plan/README.i18n.yaml @@ -42,5 +42,5 @@ en: f74784ddad90a7e1 zh: 638ce5a243e6b5cc /deepseek-ai-dsh-client-ui-plan/known-limitations-and-deferred-work/dev-note: - en: c95ae111420d6513 - zh: 9d58639022d2ab36 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-plan/README.md b/packages/client/ui-plan/README.md index e02091c34b..3a32768c05 100644 --- a/packages/client/ui-plan/README.md +++ b/packages/client/ui-plan/README.md @@ -100,5 +100,3 @@ These limits define the current plan chip. They are current package constraints, None. - -**Runtime invariant:** No companion is published. Plan state and boundary ownership are audited by dsh-plan-mode, while the control is a slot effect whose declaration, registration, and teardown are exercised by this package. diff --git a/packages/client/ui-plan/README.zh.md b/packages/client/ui-plan/README.zh.md index 5bb898b582..da06e2f514 100644 --- a/packages/client/ui-plan/README.zh.md +++ b/packages/client/ui-plan/README.zh.md @@ -100,5 +100,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。plan state 与 boundary 的所有权由 dsh-plan-mode 审计;本包的 control 是一种 slot effect,其声明、注册与清理由本包执行。 diff --git a/packages/client/ui-plugin-manager/README.i18n.yaml b/packages/client/ui-plugin-manager/README.i18n.yaml index 42c48c46ed..b3106785bf 100644 --- a/packages/client/ui-plugin-manager/README.i18n.yaml +++ b/packages/client/ui-plugin-manager/README.i18n.yaml @@ -57,5 +57,5 @@ en: e9e56a6f084e7ebb zh: bebb7086ed0237bf /deepseek-ai-dsh-client-ui-plugin-manager/known-limitations-and-deferred-work/dev-note: - en: 3856b8f32b9d2655 - zh: 8eae0dd37f198555 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-plugin-manager/README.md b/packages/client/ui-plugin-manager/README.md index 8e70c66e45..4749db12a3 100644 --- a/packages/client/ui-plugin-manager/README.md +++ b/packages/client/ui-plugin-manager/README.md @@ -173,5 +173,3 @@ These limits define the reach of the management view; they are current package c None. - -**Runtime invariant:** No companion is published. The panel reads Host-owned facts, and the registry-probe cache stores one comparison result without an independently maintained projection. diff --git a/packages/client/ui-plugin-manager/README.zh.md b/packages/client/ui-plugin-manager/README.zh.md index 75288b869d..47d7a72545 100644 --- a/packages/client/ui-plugin-manager/README.zh.md +++ b/packages/client/ui-plugin-manager/README.zh.md @@ -173,5 +173,3 @@ Host 入口通过生成的 Remote 接口暴露 `pluginRegistryProbe.fastest()` 无。 - -**运行时不变式:** 不发布伴生检查。面板读取 Host 事实,安装源探测缓存只保存一次比较结果,没有独立维护的投影。 diff --git a/packages/client/ui-primitives/README.i18n.yaml b/packages/client/ui-primitives/README.i18n.yaml index b9fe84a4aa..324ba3d7ea 100644 --- a/packages/client/ui-primitives/README.i18n.yaml +++ b/packages/client/ui-primitives/README.i18n.yaml @@ -57,5 +57,5 @@ en: 2f14f1e9d3239316 zh: c3fdaafc4c591dff /deepseek-ai-dsh-client-ui-primitives/known-limitations-and-deferred-work/dev-note: - en: ae1d5d1bcc80c932 - zh: 15709ccdbfcdf26c + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-primitives/README.md b/packages/client/ui-primitives/README.md index ddaf37bb6a..0df737df09 100644 --- a/packages/client/ui-primitives/README.md +++ b/packages/client/ui-primitives/README.md @@ -212,5 +212,3 @@ These limits define how the atoms behave at the edges; they are current package None. - -**Runtime invariant:** No companion is published. Pure props-in React atoms with no Cordis API — no events, no services, no mutable cross-plugin state; rendering contracts are asserted directly by this package's component specs. diff --git a/packages/client/ui-primitives/README.zh.md b/packages/client/ui-primitives/README.zh.md index be75be479d..6d1370a90d 100644 --- a/packages/client/ui-primitives/README.zh.md +++ b/packages/client/ui-primitives/README.zh.md @@ -212,5 +212,3 @@ kind: "package-library" 无。 - -**运行时不变式:** 不发布伴生入口。这些是纯 props-in React atom,没有 Cordis API、事件、service 或跨插件可变状态;渲染约定由组件测试覆盖。 diff --git a/packages/client/ui-reference/README.i18n.yaml b/packages/client/ui-reference/README.i18n.yaml index bc7b7c1ebc..e96368df0e 100644 --- a/packages/client/ui-reference/README.i18n.yaml +++ b/packages/client/ui-reference/README.i18n.yaml @@ -45,5 +45,5 @@ en: e79991a25b11e342 zh: 15064e8374c8e63d /deepseek-ai-dsh-client-ui-reference/known-limitations-and-deferred-work/dev-note: - en: 80768071dca1786f - zh: a82f1ec9dcb368bd + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-reference/README.md b/packages/client/ui-reference/README.md index 57b5ae2440..b39bf52141 100644 --- a/packages/client/ui-reference/README.md +++ b/packages/client/ui-reference/README.md @@ -104,5 +104,3 @@ These limits define when the reference source cannot help; they are current pack None. - -**Runtime invariant:** No companion is published. A single slash-source registration whose disposal is proven by the HMR-safety spec — it emits no cordis events and owns no cross-plugin mutable state. diff --git a/packages/client/ui-reference/README.zh.md b/packages/client/ui-reference/README.zh.md index fa5af63c93..b291958195 100644 --- a/packages/client/ui-reference/README.zh.md +++ b/packages/client/ui-reference/README.zh.md @@ -104,5 +104,3 @@ Web 用户需要从同一个 `@` 补全菜单提及文件、文件夹或会话 无。 - -**运行时不变式:** 不发布伴生入口。插件只注册一个 slash source,HMR 测试覆盖释放;它不发出 Cordis 事件,也不持有跨插件可变状态。 diff --git a/packages/client/ui-renderer/package.json b/packages/client/ui-renderer/package.json index e1df5f1393..7b2ca233c4 100644 --- a/packages/client/ui-renderer/package.json +++ b/packages/client/ui-renderer/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./client": { "types": "./lib/types/client/index.d.ts", "default": "./lib/client.js" @@ -47,7 +43,6 @@ "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-client-test-runtime": "workspace:*", "@deepseek-ai/dsh-client-ui-slots": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@types/react": "~18.3.1", "@types/react-dom": "~18.3.0", "@types/use-sync-external-store": "^1.5.0", @@ -57,7 +52,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/client.js", "lib/types/**/*.d.ts" ] diff --git a/packages/client/ui-renderer/src/invariant.ts b/packages/client/ui-renderer/src/invariant.ts deleted file mode 100644 index e3ec2db324..0000000000 --- a/packages/client/ui-renderer/src/invariant.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-client-ui-renderer`. - * @module @deepseek-ai/dsh-client-ui-renderer/invariant - */ - -/* jscpd:ignore-start */ -/* oxlint-disable typescript/no-redundant-type-constituents -- - * `keyof SlotMap & string` is the declaration-merge key pattern: SlotMap is - * empty in this compilation unit but consumers merge concrete keys into it. */ -import type { Context } from '@deepseek-ai/cordis' -import type { SlotMap } from '@deepseek-ai/dsh-client-ui-slots' -import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' - -const PACKAGE_NAME = '@deepseek-ai/dsh-client-ui-renderer' - -/** Cordis companion plugin name. */ -export const name = 'client-ui-renderer-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** - * Verify that each `slots/changed` dispatch observes its mutation already - * applied to the renderer-owned slot registry. - */ -const install: InvariantInstaller = (ctx, fail) => { - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'slots/changed') return - const key: unknown = args[0] - if (typeof key !== 'string' || key === '') { - fail("'slots/changed' dispatched without a slot key argument") - return - } - const slots = ctx.get('slots') - if (slots !== undefined && slots.getVersion(key as keyof SlotMap & string) === 0) { - fail(`'slots/changed' fired for "${key}" before any mutation bumped its version — emission must follow the applied mutation`) - } - }, { global: true }) -} - -/** - * 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/client/ui-renderer/tests/invariant.client.spec.ts b/packages/client/ui-renderer/tests/invariant.client.spec.ts deleted file mode 100644 index 5030a33001..0000000000 --- a/packages/client/ui-renderer/tests/invariant.client.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Renderer invariant companion: the 'slots/changed' emission-order audit — - * a fired key must already carry a bumped version (emission follows the - * applied mutation), bogus payloads fail loud, foreign events pass. - */ -import { Context } from '@deepseek-ai/cordis' -import { describe, expect, it } from 'vitest' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import * as RendererInvariant from '../src/invariant.ts' -import { SlotRegistry } from '../src/client/registry.ts' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(RendererInvariant).await() - return ctx -} - -const emit = (ctx: Context, event: string, ...args: unknown[]): void => { - Reflect.apply(ctx.emit.bind(ctx), undefined, [event, ...args]) -} - -describe('renderer slots/changed invariant', () => { - it('passes foreign events and a legitimate mutation-then-emission sequence', async () => { - const ctx = await setup() - expect(() => { emit(ctx, 'unrelated/event', 'x') }).not.toThrow() - await ctx.plugin(SlotRegistry).await() // fiber must reach ACTIVE — the audit reads strict ctx.get - // A real registration bumps the version first and re-emits through - // onMutate — the audit sees version > 0 and stays quiet. (Erased call: - // the typed register face rides the wave-1 ui-slots types.) - const slots = ctx.slots as { register(options: object, component: unknown): () => void } - expect(() => slots.register({ name: 'root' }, () => null)).not.toThrow() - }) - - it('fails loud on a missing key and on an emission with no applied mutation', async () => { - const ctx = await setup() - expect(() => { emit(ctx, 'slots/changed', '') }).toThrow(/without a slot key/) - expect(() => { emit(ctx, 'slots/changed', 42) }).toThrow(/without a slot key/) - await ctx.plugin(SlotRegistry).await() - // Hand-emitted key that never saw a mutation: version 0 → violation. - expect(() => { emit(ctx, 'slots/changed', 'never-mutated') }) - .toThrow(/before any mutation bumped its version/) - }) - - it('stays quiet when no slots service is mounted (nothing to audit against)', async () => { - const ctx = await setup() - expect(() => { emit(ctx, 'slots/changed', 'any-key') }).not.toThrow() - }) -}) diff --git a/packages/client/ui-renderer/tsconfig.json b/packages/client/ui-renderer/tsconfig.json index e36243012c..e4e089863b 100644 --- a/packages/client/ui-renderer/tsconfig.json +++ b/packages/client/ui-renderer/tsconfig.json @@ -13,9 +13,6 @@ }, { "path": "../ui-slots" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/client/ui-renderer/tsdown.config.ts b/packages/client/ui-renderer/tsdown.config.ts index 3a35cdcdd6..2aa78ec480 100644 --- a/packages/client/ui-renderer/tsdown.config.ts +++ b/packages/client/ui-renderer/tsdown.config.ts @@ -1,3 +1,3 @@ import { clientBundle } from '../tsdown.client.ts' -export default clientBundle('@deepseek-ai/dsh-client-ui-renderer', ['lib/types/index.js', 'lib/types/invariant.js']) +export default clientBundle('@deepseek-ai/dsh-client-ui-renderer', ['lib/types/index.js']) diff --git a/packages/client/ui-schedule/README.i18n.yaml b/packages/client/ui-schedule/README.i18n.yaml index b6c11dfaab..f89d8a74e6 100644 --- a/packages/client/ui-schedule/README.i18n.yaml +++ b/packages/client/ui-schedule/README.i18n.yaml @@ -21,8 +21,8 @@ en: 6bd6882dbbbefcb0 zh: 03aa15f1a65d13dc /deepseek-ai-dsh-client-ui-schedule/understand-the-implementation: - en: 5bbf2bc1458455c3 - zh: 2bf04cdd1f17bb18 + en: a03a551e0ed96c6b + zh: a85efe7db56b7df3 /deepseek-ai-dsh-client-ui-schedule/further-exploration: en: 06e265120b05dbf5 zh: 08ab1ef979b8ea40 diff --git a/packages/client/ui-schedule/README.md b/packages/client/ui-schedule/README.md index 5b68802156..67192d6cfa 100644 --- a/packages/client/ui-schedule/README.md +++ b/packages/client/ui-schedule/README.md @@ -67,8 +67,6 @@ A restored task tab carries no navigation parameters, because the Sidebar persis Subscriptions exist only while the framework observes a source. The final unsubscribe releases event listeners and invalidates pending responses. Components receive framework-bound `useCatalog` hooks and explicit action callbacks. The mounted Records view reads `schedule.history` with the selected task's original Session binding; catalog and list responses omit full history. Components own disposable search, filtering, selection, confirmation, and history-page state. Reminder records remain Host-owned and do not come from historical Session projections or browser-local demo data. -No runtime invariant companion is published because the catalogs render Host-owned task state and own only disposable query and interaction state. - diff --git a/packages/client/ui-schedule/README.zh.md b/packages/client/ui-schedule/README.zh.md index 63c93ba5ef..e14a8a523f 100644 --- a/packages/client/ui-schedule/README.zh.md +++ b/packages/client/ui-schedule/README.zh.md @@ -67,8 +67,6 @@ kind: "package-reference" 仅在框架观察数据源期间订阅。最后一个订阅取消时释放事件监听,并使尚未完成的响应失效。组件接收框架绑定的 `useCatalog` hook 和明确的操作回调。已挂载的任务运行记录视图使用所选任务的原会话绑定读取 `schedule.history`;目录和列表响应不包含完整历史。组件拥有可丢弃的搜索、筛选、选择、确认和历史分页状态。提醒记录由 Host 拥有,不来自历史会话投影或浏览器本地演示数据。 -本包不发布运行时 invariant companion,因为目录呈现 Host 拥有的任务状态,自身只拥有可丢弃的查询和交互状态。 - diff --git a/packages/client/ui-session/README.i18n.yaml b/packages/client/ui-session/README.i18n.yaml index 9235141f7d..02db9d6ecb 100644 --- a/packages/client/ui-session/README.i18n.yaml +++ b/packages/client/ui-session/README.i18n.yaml @@ -24,5 +24,5 @@ en: 61ef65087e3d4ac8 zh: 01b38db2957f9096 /deepseek-ai-dsh-client-ui-session/known-limitations-and-deferred-work/dev-note: - en: 48d675ab370a4967 - zh: 0e22dedcf99801ad + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/ui-session/README.md b/packages/client/ui-session/README.md index df85bf42b0..d10b62ffbc 100644 --- a/packages/client/ui-session/README.md +++ b/packages/client/ui-session/README.md @@ -45,5 +45,3 @@ None; Session selectors and Slot scopes do not assemble model requests. None. - -**Runtime invariant:** No companion is published. The adapter materialization path enforces Session binding consistency. diff --git a/packages/client/ui-session/README.zh.md b/packages/client/ui-session/README.zh.md index 5f54c1db7b..95944f55f3 100644 --- a/packages/client/ui-session/README.zh.md +++ b/packages/client/ui-session/README.zh.md @@ -45,5 +45,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。适配器 materialization 路径已经强制 Session 绑定一致。 diff --git a/packages/client/ui-settings-account/README.i18n.yaml b/packages/client/ui-settings-account/README.i18n.yaml index 1b597f4d27..16f6b304e9 100644 --- a/packages/client/ui-settings-account/README.i18n.yaml +++ b/packages/client/ui-settings-account/README.i18n.yaml @@ -21,8 +21,8 @@ en: c1c4db71fd09ca13 zh: a40c0a01e9951945 /deepseek-ai-dsh-client-ui-settings-account/understand-the-implementation: - en: 6b3ad3b6860100cf - zh: aeced4fb37642f15 + en: 97952c58e674ad9f + zh: 625cbc2a43d75c21 /deepseek-ai-dsh-client-ui-settings-account/further-exploration: en: a28acdef21b1643a zh: 5e2e357a6b716274 diff --git a/packages/client/ui-settings-account/README.md b/packages/client/ui-settings-account/README.md index 18604436f7..b8980e24c8 100644 --- a/packages/client/ui-settings-account/README.md +++ b/packages/client/ui-settings-account/README.md @@ -64,7 +64,7 @@ The welcome page requires Get started. The credit page always appears. A positiv ## Understand the implementation -The Account section appears first in Settings only while signed in; it is hidden before account state loads and after sign-out. The plugin owns one Host snapshot stream shared through framework hooks by settings.section and settings.launcher. The launcher opens Settings and offers Sign out only while an account credential is stored. Platform failures leave the menu available for retry. It maintains no independent credential state, so no invariant companion is published. +The Account section appears first in Settings only while signed in; it is hidden before account state loads and after sign-out. The plugin owns one Host snapshot stream shared through framework hooks by settings.section and settings.launcher. The launcher opens Settings and offers Sign out only while an account credential is stored. Platform failures leave the menu available for retry. It maintains no independent credential state. The desktop controller writes progress to `ui-settings-account` through Host settings. Completion first applies `ui-chat.transcriptView` and `ui-chat.performanceUsage` together, then calls the shared Coding Tools preference to persist `ui-settings.enabled`, and finally saves the done marker; a rejected write keeps the flow available for retry. Successful completion reveals the workspace through a 180ms fade; choices and ordinary navigation preview immediately while writes run in order. A failed final queued write restores saved progress and allows retry. Completion waits for queued choices and retains the page until preferences persist; reduced motion skips the fade. Onboarding choices compact, standard, and detailed apply directly to the corresponding Chat work-detail modes. API-key entry and explicit skip select standard; onboarding does not select verbose. Complete Figma illustration layers are bundled as transparent, palette-compressed 3× PNG assets; welcome layers are merged with local sidebar blur; recharge uses a 70% opaque foreground window with local blur of the covered illustration, and headings use the bundled Montserrat brand font; English headings and descriptions use Light (300), except the DeepSeek Harness brand within headings uses Medium (500); card titles retain Regular (400) when selected. English footer navigation uses Light (300). Confirmed onboarding sets a 960px minimum window width and enlarges narrower windows; completion releases the minimum without restoring the previous size. Loading or already-completed onboarding does not resize the window. Empty footer navigation space passes pointer events through to the primary action. Cards exclude native window dragging and own the keyboard focus outline for their checkbox. The account package owns its onboarding overlay and control geometry. Each step and the confirmation dialog has a separate component; the flow coordinates navigation, transitions, and recharge. The overlay hides the workspace while initial account and progress state loads. Question headings and cards follow the Figma positions at 1440 × 920 and 960 × 600, independently of their action buttons. On macOS, content clears the body portal’s inherited no-drag region and a first-child drag band leaves an 8px native resize margin; interactive controls and later dialogs exclude their regions, and a native Platform page disables the underlying onboarding band. The upper workspace popup illustration has an opaque background in both languages and themes; the underlying sidebar omits its selected-row highlight. Welcome keeps its illustration at its original size, repositions and clips the artwork in narrow windows, and reveals the complete illustration on wide windows. The [onboarding decision](../../../.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.md) owns persistence and presentation trade-offs. diff --git a/packages/client/ui-settings-account/README.zh.md b/packages/client/ui-settings-account/README.zh.md index ccc6daaeae..763314403c 100644 --- a/packages/client/ui-settings-account/README.zh.md +++ b/packages/client/ui-settings-account/README.zh.md @@ -64,7 +64,7 @@ Desktop preload 标记在账号凭证已存储后启用引导。进度属于本 ## 理解实现 -仅登录后在设置导航首位显示账号页;账号状态加载完成前以及退出登录后隐藏该页。 插件持有一条 Host 快照流,通过框架 hook 供 settings.section 和 settings.launcher 共享。入口菜单可打开设置,并仅在已存储账号凭证时提供退出登录。Platform 请求失败时保留菜单,以供重试。插件不维护独立凭证状态,因此不发布 invariant。 +仅登录后在设置导航首位显示账号页;账号状态加载完成前以及退出登录后隐藏该页。 插件持有一条 Host 快照流,通过框架 hook 供 settings.section 和 settings.launcher 共享。入口菜单可打开设置,并仅在已存储账号凭证时提供退出登录。Platform 请求失败时保留菜单,以供重试。插件不维护独立凭证状态。 桌面控制器通过 Host 设置将进度写入 `ui-settings-account`。完成时先一并应用 `ui-chat.transcriptView` 与 `ui-chat.performanceUsage`,再通过共享代码工作工具偏好保存 `ui-settings.enabled`,最后保存完成标记;写入被拒绝时保留引导以供重试。成功完成后通过 180ms 淡出显露工作区;选择和普通翻页立即预览,写入在后台按顺序执行。队列最后一次写入失败时恢复已保存进度,并允许重试。完成操作会等待选择队列及偏好保存成功,再关闭页面;减少动态效果偏好会跳过淡出。onboarding 选项 compact、standard、detailed 直接应用到对应的 Chat 工作过程模式。API Key 进入与显式跳过均采用 standard;onboarding 不选择 verbose。完整 Figma 示意图层以透明、调色板压缩的 3× PNG 资源打包;欢迎页图层合并并烘焙侧栏局部模糊;充值页前景窗口不透明度为 70%,仅烘焙其覆盖插图区域的模糊,标题使用已打包的 Montserrat 品牌字体,英文标题和描述使用 Light(300),标题中的 DeepSeek Harness 使用 Medium(500);卡片标题选中前后均使用 Regular(400)。英文底部导航使用 Light(300)。确认进入引导后,窗口最小宽度设为 960px,较窄窗口自动放大;完成后解除宽度限制,但不恢复原尺寸。加载中或已完成引导的启动不改变窗口尺寸。底部导航空白区域透过鼠标事件,避免遮挡主操作。卡片区域不参与原生窗口拖动,复选框的键盘焦点仅在卡片外框展示。账号包负责引导覆盖层和控件尺寸样式。每个步骤和确认弹窗各有独立组件,流程组件协调导航、切换动画和充值。初始账号与进度加载期间,覆盖层隐藏工作区。答题标题和卡片独立于操作按钮,位置对齐 Figma 的 1440 × 920 和 960 × 600 断点。macOS 的内容层清除 body 浮层继承的 no-drag,位于内容之前的拖拽层保留 8px 原生缩放边缘;交互控件和后续弹窗排除自身区域;原生平台页面打开时禁用底层引导拖拽层。上方工作区弹窗插画在两种语言和主题下均使用不透明背景,下层侧边栏不绘制选中行高亮。欢迎页保持示意图的原始尺寸,在窄窗口内调整插图位置并裁切,在宽窗口内展示完整插图。[引导决策](../../../.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.zh.md)记录持久化与展示方面的取舍。 diff --git a/packages/client/ui-settings-agent-loop/README.i18n.yaml b/packages/client/ui-settings-agent-loop/README.i18n.yaml index 937134f596..4c8d714840 100644 --- a/packages/client/ui-settings-agent-loop/README.i18n.yaml +++ b/packages/client/ui-settings-agent-loop/README.i18n.yaml @@ -30,8 +30,8 @@ en: ca75c51c89c2c9b0 zh: 6e1c9a8fdde89577 /deepseek-ai-dsh-client-ui-settings-agent-loop/known-limitations-and-deferred-work: - en: 2696c3c3a1328b84 - zh: 10274e668ec7ea02 + en: 4618f99cab06ba2d + zh: b77328e55203863d /deepseek-ai-dsh-client-ui-settings-agent-loop/known-limitations-and-deferred-work/dev-note: en: e67f633dc6fe7773 zh: e88aeb617ffb74ea diff --git a/packages/client/ui-settings-agent-loop/README.md b/packages/client/ui-settings-agent-loop/README.md index 46e74fdf28..f1f7ec5f78 100644 --- a/packages/client/ui-settings-agent-loop/README.md +++ b/packages/client/ui-settings-agent-loop/README.md @@ -65,7 +65,6 @@ None; this package neither assembles nor sends a provider request. - **One field of the namespace** — the Host section carries only the parallel cap; the composed `agents` array is boot composition, not a user setting, and has no field here. -- **Runtime invariant:** No companion is published. The page holds no owned relationship of its own: what it shows derives from the settings mirror, and what it writes the Host validates. ### Dev Note diff --git a/packages/client/ui-settings-agent-loop/README.zh.md b/packages/client/ui-settings-agent-loop/README.zh.md index 5381d3f40c..f399524998 100644 --- a/packages/client/ui-settings-agent-loop/README.zh.md +++ b/packages/client/ui-settings-agent-loop/README.zh.md @@ -65,7 +65,6 @@ kind: "package-reference" - **只编辑命名空间的一个字段**——Host 的这一节只承载并行上限;组合进来的 `agents` 数组属于启动组合而非用户设置,这里没有对应字段。 -- **运行时不变量:**不发布伴生。本页没有自己拥有的关系:它显示的内容派生自设置镜像,它写入的内容由 Host 校验。 ### 开发备注 diff --git a/packages/client/ui-settings-general/README.i18n.yaml b/packages/client/ui-settings-general/README.i18n.yaml index f8ba0625be..85937c4fe6 100644 --- a/packages/client/ui-settings-general/README.i18n.yaml +++ b/packages/client/ui-settings-general/README.i18n.yaml @@ -54,5 +54,5 @@ en: 97dd637a320ac334 zh: faa5c512c56a128e /deepseek-ai-dsh-client-ui-settings-general/known-limitations-and-deferred-work/dev-note: - en: b36039e49a66b8e0 - zh: 306682cd90cce1db + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-settings-general/README.md b/packages/client/ui-settings-general/README.md index 5a746969fd..6e246e990e 100644 --- a/packages/client/ui-settings-general/README.md +++ b/packages/client/ui-settings-general/README.md @@ -126,5 +126,3 @@ These limits define what the shell itself provides versus what features must sup None. - -**Runtime invariant:** No companion is published. The settings seam validates and publishes the durable onboarding section, while slot conflicts fail loud in the slot core. The local document action is browser state over typed RPC responses and is covered by store/component tests rather than a Cordis runtime relationship. diff --git a/packages/client/ui-settings-general/README.zh.md b/packages/client/ui-settings-general/README.zh.md index acca362cdc..8bf6cc82b6 100644 --- a/packages/client/ui-settings-general/README.zh.md +++ b/packages/client/ui-settings-general/README.zh.md @@ -126,5 +126,3 @@ Web 与桌面端的通用设置底部显示当前发布版本,使用构建注 无。 - -**运行时不变式:** 不发布伴生入口。settings seam 校验并发布持久 onboarding section,slot core 会拒绝冲突;本地 document action 由 store 与组件测试覆盖。 diff --git a/packages/client/ui-settings-models/README.i18n.yaml b/packages/client/ui-settings-models/README.i18n.yaml index d7af2df2ea..c299cf433c 100644 --- a/packages/client/ui-settings-models/README.i18n.yaml +++ b/packages/client/ui-settings-models/README.i18n.yaml @@ -57,5 +57,5 @@ en: cc69c9934bf22302 zh: 4ea896f4b54aa203 /deepseek-ai-dsh-client-ui-settings-models/known-limitations-and-deferred-work/dev-note: - en: 4f6749870e661a48 - zh: 4953f3ea814546a4 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-settings-models/README.md b/packages/client/ui-settings-models/README.md index ff2a5bd48f..cc1074dbc0 100644 --- a/packages/client/ui-settings-models/README.md +++ b/packages/client/ui-settings-models/README.md @@ -138,5 +138,3 @@ These limits define the editor's field coverage and the page's reach; they are c None. - -**Runtime invariant:** No companion is published. A nav-entry-only section plugin rendering a fixed empty content column — it emits no cordis events and owns no cross-plugin mutable relation. diff --git a/packages/client/ui-settings-models/README.zh.md b/packages/client/ui-settings-models/README.zh.md index e0f2e017ff..0c25bfc85a 100644 --- a/packages/client/ui-settings-models/README.zh.md +++ b/packages/client/ui-settings-models/README.zh.md @@ -138,5 +138,3 @@ Models 页面包含 **DeepSeek 账号**(`deepseek-account`,英文为 **DeepS 无。 - -**运行时不变式:** 不发布伴生入口。这是只贡献 nav entry 的 section 插件,渲染固定空 content column,不发出 Cordis 事件,也不持有跨插件可变关系。 diff --git a/packages/client/ui-settings-plugin-inventory/README.i18n.yaml b/packages/client/ui-settings-plugin-inventory/README.i18n.yaml index b30b1c29f8..d4b459bb3e 100644 --- a/packages/client/ui-settings-plugin-inventory/README.i18n.yaml +++ b/packages/client/ui-settings-plugin-inventory/README.i18n.yaml @@ -48,5 +48,5 @@ en: 3d3343d97eb73762 zh: 5fc9d1c1f0c4055d /deepseek-ai-dsh-client-ui-settings-plugin-inventory/known-limitations-and-deferred-work/dev-note: - en: 7516b7e39304e5b3 - zh: 8961c0cefb89ee12 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-settings-plugin-inventory/README.md b/packages/client/ui-settings-plugin-inventory/README.md index f6aa2312b8..2107090327 100644 --- a/packages/client/ui-settings-plugin-inventory/README.md +++ b/packages/client/ui-settings-plugin-inventory/README.md @@ -107,5 +107,3 @@ These limits define the freshness and reach of the inventory view; they are curr None. - -**Runtime invariant:** No companion is published. This package owns a read-only Settings contribution. diff --git a/packages/client/ui-settings-plugin-inventory/README.zh.md b/packages/client/ui-settings-plugin-inventory/README.zh.md index cb0c2ce723..737ff343de 100644 --- a/packages/client/ui-settings-plugin-inventory/README.zh.md +++ b/packages/client/ui-settings-plugin-inventory/README.zh.md @@ -107,5 +107,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。本包只持有一个只读 Settings contribution。 diff --git a/packages/client/ui-settings-plugins/README.i18n.yaml b/packages/client/ui-settings-plugins/README.i18n.yaml index 77a7c3d9ce..a783ce1ee9 100644 --- a/packages/client/ui-settings-plugins/README.i18n.yaml +++ b/packages/client/ui-settings-plugins/README.i18n.yaml @@ -30,8 +30,8 @@ en: ca75c51c89c2c9b0 zh: 6e1c9a8fdde89577 /deepseek-ai-dsh-client-ui-settings-plugins/known-limitations-and-deferred-work: - en: 262d3dac53b5aff1 - zh: 3ac7a4d5c0d0f70f + en: 4950e959be39bc0c + zh: 8f0703f3304d9336 /deepseek-ai-dsh-client-ui-settings-plugins/known-limitations-and-deferred-work/dev-note: en: e67f633dc6fe7773 zh: e88aeb617ffb74ea diff --git a/packages/client/ui-settings-plugins/README.md b/packages/client/ui-settings-plugins/README.md index b4b3d37bd9..ac0d69210f 100644 --- a/packages/client/ui-settings-plugins/README.md +++ b/packages/client/ui-settings-plugins/README.md @@ -67,7 +67,6 @@ None; this package neither assembles nor sends a provider request. - **The section has no tab of its own** — it renders its empty line until a feature plugin registers one; the shell cannot fill the section alone. -- **Runtime invariant:** No companion is published. The section owns no relationship beyond the slot ledger it projects. ### Dev Note diff --git a/packages/client/ui-settings-plugins/README.zh.md b/packages/client/ui-settings-plugins/README.zh.md index d22a9ec3f1..44a16ec015 100644 --- a/packages/client/ui-settings-plugins/README.zh.md +++ b/packages/client/ui-settings-plugins/README.zh.md @@ -67,7 +67,6 @@ kind: "package-reference" - **分区没有自己的标签页**——在功能插件注册标签页之前它只显示空提示;壳自己填不满分区。 -- **运行时不变量:**不发布伴生。分区除了投影 slot 账本之外不拥有任何关系。 ### 开发备注 diff --git a/packages/client/ui-settings-session-log/README.i18n.yaml b/packages/client/ui-settings-session-log/README.i18n.yaml index a247ab248b..1ae4954c16 100644 --- a/packages/client/ui-settings-session-log/README.i18n.yaml +++ b/packages/client/ui-settings-session-log/README.i18n.yaml @@ -30,8 +30,8 @@ en: c8b5628ff5370218 zh: 5ebb7ff0959a3a69 /deepseek-ai-dsh-client-ui-settings-session-log/known-limitations-and-deferred-work: - en: 637707c46284b048 - zh: 5d3feddb58cb9b50 + en: a77d8093293518fa + zh: 0098c6b150a6d382 /deepseek-ai-dsh-client-ui-settings-session-log/known-limitations-and-deferred-work/dev-note: en: 0a1253c6f7b5e8b9 zh: 9f56528562c7c914 diff --git a/packages/client/ui-settings-session-log/README.md b/packages/client/ui-settings-session-log/README.md index e2d59b5e6d..8da5994678 100644 --- a/packages/client/ui-settings-session-log/README.md +++ b/packages/client/ui-settings-session-log/README.md @@ -62,7 +62,6 @@ None; the setting controls request metadata outside model input. - The row is unavailable when the Host does not expose the `session-log-deepseek` namespace. -- No runtime invariant companion is published: accepted enablement comes directly from the shared configuration form, without an independent copy. ### Dev Note diff --git a/packages/client/ui-settings-session-log/README.zh.md b/packages/client/ui-settings-session-log/README.zh.md index ffa6d06b13..55e6edb624 100644 --- a/packages/client/ui-settings-session-log/README.zh.md +++ b/packages/client/ui-settings-session-log/README.zh.md @@ -62,7 +62,6 @@ Host 提供 `session-log-deepseek` 时,配套插件贡献一个 `settings.gene - Host 不提供 `session-log-deepseek` 命名空间时,该行不可用。 -- 不发布运行时不变量配套插件:已接受的启用状态直接来自共享配置表单,没有独立副本。 ### 开发备注 diff --git a/packages/client/ui-settings-shell/README.i18n.yaml b/packages/client/ui-settings-shell/README.i18n.yaml index c7173c8d55..cebf7b1e04 100644 --- a/packages/client/ui-settings-shell/README.i18n.yaml +++ b/packages/client/ui-settings-shell/README.i18n.yaml @@ -30,8 +30,8 @@ en: ca75c51c89c2c9b0 zh: 6e1c9a8fdde89577 /deepseek-ai-dsh-client-ui-settings-shell/known-limitations-and-deferred-work: - en: 51c2778fb17f8d1d - zh: 6425a598ba93833e + en: 410d2fb3320329b5 + zh: 0ba9eebfdc6c9b10 /deepseek-ai-dsh-client-ui-settings-shell/known-limitations-and-deferred-work/dev-note: en: e67f633dc6fe7773 zh: e88aeb617ffb74ea diff --git a/packages/client/ui-settings-shell/README.md b/packages/client/ui-settings-shell/README.md index 057cc6d268..2d87a62c23 100644 --- a/packages/client/ui-settings-shell/README.md +++ b/packages/client/ui-settings-shell/README.md @@ -65,7 +65,6 @@ None; this package neither assembles nor sends a provider request. - **The page follows the composed executor** — the POSIX and PowerShell executor families share the `shell` namespace because a host composes exactly one of them, so the served schema differs by platform (PowerShell adds `pwshPath`) while the page edits the same two fields on both. -- **Runtime invariant:** No companion is published. The page holds no owned relationship of its own: what it shows derives from the settings mirror, and what it writes the Host validates. ### Dev Note diff --git a/packages/client/ui-settings-shell/README.zh.md b/packages/client/ui-settings-shell/README.zh.md index 04016f9dd4..64a981f588 100644 --- a/packages/client/ui-settings-shell/README.zh.md +++ b/packages/client/ui-settings-shell/README.zh.md @@ -65,7 +65,6 @@ kind: "package-reference" - **页面跟随组合进来的执行器**——POSIX 与 PowerShell 两个执行器家族共用 `shell` 命名空间,因为一台主机只组合其中一个,所以服务出来的 schema 因平台而异(PowerShell 多一个 `pwshPath`),而页面在两个平台上编辑的都是同样两个字段。 -- **运行时不变量:**不发布伴生。本页没有自己拥有的关系:它显示的内容派生自设置镜像,它写入的内容由 Host 校验。 ### 开发备注 diff --git a/packages/client/ui-settings-subagent/README.i18n.yaml b/packages/client/ui-settings-subagent/README.i18n.yaml index edffbd8f34..3c19146553 100644 --- a/packages/client/ui-settings-subagent/README.i18n.yaml +++ b/packages/client/ui-settings-subagent/README.i18n.yaml @@ -30,8 +30,8 @@ en: ca75c51c89c2c9b0 zh: 6e1c9a8fdde89577 /deepseek-ai-dsh-client-ui-settings-subagent/known-limitations-and-deferred-work: - en: ea92269b6c36cbcc - zh: ec43fdf5c47a4906 + en: 23012dfa92ce3e01 + zh: 66fb0e3d8ef9cb7c /deepseek-ai-dsh-client-ui-settings-subagent/known-limitations-and-deferred-work/dev-note: en: e67f633dc6fe7773 zh: e88aeb617ffb74ea diff --git a/packages/client/ui-settings-subagent/README.md b/packages/client/ui-settings-subagent/README.md index 66761cc1ca..3c1d9dbf1f 100644 --- a/packages/client/ui-settings-subagent/README.md +++ b/packages/client/ui-settings-subagent/README.md @@ -69,7 +69,6 @@ None; this package neither assembles nor sends a provider request. - **The catalogue re-reads on three signals only** — adapter changes, document commits, and reconnects; a provider that starts advertising without any of them shows up on the next of those or on **Retry**. -- **Runtime invariant:** No companion is published. The page holds no owned relationship of its own: what it shows derives from the settings mirror and the model catalogue, and what it writes the Host validates. ### Dev Note diff --git a/packages/client/ui-settings-subagent/README.zh.md b/packages/client/ui-settings-subagent/README.zh.md index 58a5a1b923..123cb1a665 100644 --- a/packages/client/ui-settings-subagent/README.zh.md +++ b/packages/client/ui-settings-subagent/README.zh.md @@ -69,7 +69,6 @@ kind: "package-reference" - **目录只在三种信号下重读**——适配器变化、文档提交和重连;在这三者之外开始公布模型的提供方,要等下一次信号或点击**重试**才会出现。 -- **运行时不变量:**不发布伴生。本页没有自己拥有的关系:它显示的内容派生自设置镜像与模型目录,它写入的内容由 Host 校验。 ### 开发备注 diff --git a/packages/client/ui-settings-web-search/README.i18n.yaml b/packages/client/ui-settings-web-search/README.i18n.yaml index e63cb11abd..0f690d2b00 100644 --- a/packages/client/ui-settings-web-search/README.i18n.yaml +++ b/packages/client/ui-settings-web-search/README.i18n.yaml @@ -30,8 +30,8 @@ en: ca75c51c89c2c9b0 zh: 6e1c9a8fdde89577 /deepseek-ai-dsh-client-ui-settings-web-search/known-limitations-and-deferred-work: - en: 3fb1d37ebc9bf37a - zh: d6ac7929cb19ff55 + en: 2f7f093f506401cd + zh: a9b748ae7d4c64e4 /deepseek-ai-dsh-client-ui-settings-web-search/known-limitations-and-deferred-work/dev-note: en: e67f633dc6fe7773 zh: e88aeb617ffb74ea diff --git a/packages/client/ui-settings-web-search/README.md b/packages/client/ui-settings-web-search/README.md index 01fa9c24c4..a716ab53c2 100644 --- a/packages/client/ui-settings-web-search/README.md +++ b/packages/client/ui-settings-web-search/README.md @@ -66,7 +66,6 @@ None; this package neither assembles nor sends a provider request. - **Three fields of the namespace** — the provider's model, API version, and token budget stay at their composed values; the page edits the key, the endpoint, and the search budget only. -- **Runtime invariant:** No companion is published. The page holds no owned relationship of its own: what it shows derives from the settings mirror and the credentials domain, and what it writes the Host validates. ### Dev Note diff --git a/packages/client/ui-settings-web-search/README.zh.md b/packages/client/ui-settings-web-search/README.zh.md index fd46f2f687..71dbcbab8e 100644 --- a/packages/client/ui-settings-web-search/README.zh.md +++ b/packages/client/ui-settings-web-search/README.zh.md @@ -66,7 +66,6 @@ kind: "package-reference" - **只编辑命名空间的三个字段**——提供方的模型、API 版本和 token 预算保持组合值不变;本页只编辑密钥、接口地址和搜索次数。 -- **运行时不变量:**不发布伴生。本页没有自己拥有的关系:它显示的内容派生自设置镜像与凭据域,它写入的内容由 Host 校验。 ### 开发备注 diff --git a/packages/client/ui-settings/README.i18n.yaml b/packages/client/ui-settings/README.i18n.yaml index 9729978636..b5488bea3e 100644 --- a/packages/client/ui-settings/README.i18n.yaml +++ b/packages/client/ui-settings/README.i18n.yaml @@ -54,5 +54,5 @@ en: d8ef5c4f3a48e05f zh: 7c335dc8543249c6 /deepseek-ai-dsh-client-ui-settings/known-limitations-and-deferred-work/dev-note: - en: 26f0b742de49074e - zh: ee7321bad1101bf1 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-settings/README.md b/packages/client/ui-settings/README.md index 376e2da9a9..0a7113c921 100644 --- a/packages/client/ui-settings/README.md +++ b/packages/client/ui-settings/README.md @@ -114,5 +114,3 @@ These limits define where the settings transport cannot reach; they are current None. - -**Runtime invariant:** No companion is published. A presentation shell projecting the settings.section ledger into navigation — it emits no cordis events and owns no cross-plugin mutable relation; slot declaration/registration conflicts already fail loud in the slot core at load time. diff --git a/packages/client/ui-settings/README.zh.md b/packages/client/ui-settings/README.zh.md index c6ee2f2178..193d764637 100644 --- a/packages/client/ui-settings/README.zh.md +++ b/packages/client/ui-settings/README.zh.md @@ -114,5 +114,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。本包只把 `settings.section` ledger 投影为导航,不发出 Cordis 事件,也不持有跨插件可变关系;slot core 会在加载时拒绝冲突。 diff --git a/packages/client/ui-shortcuts/README.i18n.yaml b/packages/client/ui-shortcuts/README.i18n.yaml index aec20e18ca..c19532d5f5 100644 --- a/packages/client/ui-shortcuts/README.i18n.yaml +++ b/packages/client/ui-shortcuts/README.i18n.yaml @@ -33,5 +33,5 @@ en: 4c1ee388c3bbb5b0 zh: f3e5d73036a449b5 /deepseek-ai-dsh-client-ui-shortcuts/known-limitations-and-deferred-work/dev-note: - en: 47c5f082ab7e39eb - zh: 7405a26672e600e3 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/ui-shortcuts/README.md b/packages/client/ui-shortcuts/README.md index aa2e32e7a7..8c874fb2bc 100644 --- a/packages/client/ui-shortcuts/README.md +++ b/packages/client/ui-shortcuts/README.md @@ -85,5 +85,3 @@ None; this package neither assembles nor sends a provider request. None. - -**Runtime invariant:** No companion is published. Slot registration validates ownership; the view derives its rows from the shortcut catalog without a second mutable registry. diff --git a/packages/client/ui-shortcuts/README.zh.md b/packages/client/ui-shortcuts/README.zh.md index 5226b14e9f..4ba2688c9f 100644 --- a/packages/client/ui-shortcuts/README.zh.md +++ b/packages/client/ui-shortcuts/README.zh.md @@ -85,5 +85,3 @@ Web 应用 bundle 将此包与 `shortcuts` 一同挂载。速查仅展示已注 无。 - -**Runtime invariant:** 不发布配套检查模块。slot 注册验证所有权,界面从快捷键目录派生列表,不维护第二份可变注册表。 diff --git a/packages/client/ui-sidebar-browser/README.i18n.yaml b/packages/client/ui-sidebar-browser/README.i18n.yaml index 63cc55dfdf..d17a9852a0 100644 --- a/packages/client/ui-sidebar-browser/README.i18n.yaml +++ b/packages/client/ui-sidebar-browser/README.i18n.yaml @@ -48,5 +48,5 @@ en: 307604babbe74f38 zh: 7d7a5aa4b99964d0 /deepseek-ai-dsh-client-ui-sidebar-browser/known-limitations-and-deferred-work/dev-note: - en: 945685f7066eda81 - zh: da888e0d3f785bd5 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/ui-sidebar-browser/README.md b/packages/client/ui-sidebar-browser/README.md index b15844e70d..e3664ef582 100644 --- a/packages/client/ui-sidebar-browser/README.md +++ b/packages/client/ui-sidebar-browser/README.md @@ -118,5 +118,3 @@ The isolation policy deliberately gives up some browser compatibility: None. - -**Runtime invariant:** No companion is published. Each navigation provider owns its live state and publishes checkpoints directly; the UI consumes the same provider state through its controller. diff --git a/packages/client/ui-sidebar-browser/README.zh.md b/packages/client/ui-sidebar-browser/README.zh.md index d09d32773c..4be25a37c0 100644 --- a/packages/client/ui-sidebar-browser/README.zh.md +++ b/packages/client/ui-sidebar-browser/README.zh.md @@ -118,5 +118,3 @@ Desktop 主进程批准 guest 租约,并执行挂载、导航和权限策略 无。 - -**运行时不变量:** 不发布 companion。每个导航 provider 拥有自身的实时状态并直接发布检查点;UI 通过 controller 消费同一份 provider 状态。 diff --git a/packages/client/ui-sidebar-documentpreview/README.i18n.yaml b/packages/client/ui-sidebar-documentpreview/README.i18n.yaml index de4c4d2265..a31b9eb75e 100644 --- a/packages/client/ui-sidebar-documentpreview/README.i18n.yaml +++ b/packages/client/ui-sidebar-documentpreview/README.i18n.yaml @@ -42,5 +42,5 @@ en: 7be2ad2e198131f8 zh: 21b35eae72a49ad2 /deepseek-ai-dsh-client-ui-sidebar-documentpreview/known-limitations-and-deferred-work/dev-note: - en: 3d8904cb970145a3 - zh: a0c0cddcfedf9e42 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/ui-sidebar-documentpreview/README.md b/packages/client/ui-sidebar-documentpreview/README.md index d420c2f34e..8d87661c8b 100644 --- a/packages/client/ui-sidebar-documentpreview/README.md +++ b/packages/client/ui-sidebar-documentpreview/README.md @@ -153,5 +153,3 @@ No direct effect; what the user reads here never enters a model request. None. - -**Runtime invariant:** No companion is published. Renderer metadata, document loading, and view state belong to the local registry and declared Slot stores, with no independent runtime source to compare against; registration disposal and tab lifetimes are covered by behavior tests. diff --git a/packages/client/ui-sidebar-documentpreview/README.zh.md b/packages/client/ui-sidebar-documentpreview/README.zh.md index 77e3cc79a8..9fc7b1df8a 100644 --- a/packages/client/ui-sidebar-documentpreview/README.zh.md +++ b/packages/client/ui-sidebar-documentpreview/README.zh.md @@ -153,5 +153,3 @@ Office Remote 通过 Connection 的 multipart 二进制传输返回原生 `Uint8 无。 - -**运行时不变式:** 不发布伴生入口。渲染器元数据、文档加载和视图状态归本地注册表与声明的 Slot store 所有,没有可比对的独立运行时来源;注册释放和 tab 生命周期由行为测试覆盖。 diff --git a/packages/client/ui-sidebar-files/README.i18n.yaml b/packages/client/ui-sidebar-files/README.i18n.yaml index 4e7e344973..c9475ae68a 100644 --- a/packages/client/ui-sidebar-files/README.i18n.yaml +++ b/packages/client/ui-sidebar-files/README.i18n.yaml @@ -30,5 +30,5 @@ en: c0e1831d8975186c zh: 332aac8738458bb0 /deepseek-ai-dsh-client-ui-sidebar-files/known-limitations-and-deferred-work/dev-note: - en: 3e8f2f242d43973b - zh: fb919fb393d51b20 + en: d8b51fb935f38650 + zh: fee9d330ecf8ec0e diff --git a/packages/client/ui-sidebar-files/README.md b/packages/client/ui-sidebar-files/README.md index 22431cfc52..5c04f195d1 100644 --- a/packages/client/ui-sidebar-files/README.md +++ b/packages/client/ui-sidebar-files/README.md @@ -75,5 +75,3 @@ None. The page refresh shortcut refreshes the focused file tree through its ordinary directory reader. The reload control displays the effective binding on hover and keyboard focus. - -**Runtime invariant:** No companion is published. The tab-owned face keeps directory reads and watches private, and writes displayed state through its Slot store; tab cancellation releases both. The package exposes no independent observation for a runtime comparison. diff --git a/packages/client/ui-sidebar-files/README.zh.md b/packages/client/ui-sidebar-files/README.zh.md index 72e6a9bd80..ec092a362d 100644 --- a/packages/client/ui-sidebar-files/README.zh.md +++ b/packages/client/ui-sidebar-files/README.zh.md @@ -75,5 +75,3 @@ kind: "package-reference" 页面刷新快捷键通过常规目录读取器刷新聚焦的文件树。重新读取控件在悬停和键盘聚焦时显示有效绑定。 - -**运行时不变量:** 不发布 companion。tab 持有的 face 内部管理目录读取与监听,并通过 Slot store 写入显示状态;tab 取消时释放二者。本包未暴露可用于运行时比对的独立观测源。 diff --git a/packages/client/ui-sidebar-right/README.i18n.yaml b/packages/client/ui-sidebar-right/README.i18n.yaml index dfd1b69803..bc018856ab 100644 --- a/packages/client/ui-sidebar-right/README.i18n.yaml +++ b/packages/client/ui-sidebar-right/README.i18n.yaml @@ -51,5 +51,5 @@ en: 76a763b4dd71babd zh: fdc966dd427a82fb /deepseek-ai-dsh-client-ui-sidebar-right/known-limitations-and-deferred-work/dev-note: - en: 9fcbcccf09b43c5d - zh: 224b8a0881ab6890 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/ui-sidebar-right/README.md b/packages/client/ui-sidebar-right/README.md index 980b192ce0..350dedd8cb 100644 --- a/packages/client/ui-sidebar-right/README.md +++ b/packages/client/ui-sidebar-right/README.md @@ -155,5 +155,3 @@ None; this package neither assembles nor sends a provider request. None. - -**Runtime invariant:** No companion is published. The two services (`sidebarRight`, `sidebarRightTabs`) are provided through `ctx.reflect.provide` inside one effect and torn down with it; the on-screen Session and the Tab domain's occurrence lifetimes are asserted directly by this package's specs, and no independent observation exists to diverge from them. diff --git a/packages/client/ui-sidebar-right/README.zh.md b/packages/client/ui-sidebar-right/README.zh.md index 96f8e284d9..83ee48c6e1 100644 --- a/packages/client/ui-sidebar-right/README.zh.md +++ b/packages/client/ui-sidebar-right/README.zh.md @@ -155,5 +155,3 @@ Tab 域按(Session,Tab id)保留品牌化 occurrence id、导航、中止 无。 - -**运行时不变量:** 不发布 companion。两个服务(`sidebarRight`、`sidebarRightTabs`)在同一个 effect 内经 `ctx.reflect.provide` 提供并随之拆除;屏幕上的会话与 Tab 域 occurrence 的生命周期由本包的 spec 直接断言,不存在会与之分歧的独立观察。 diff --git a/packages/client/ui-sidebar-terminal/README.i18n.yaml b/packages/client/ui-sidebar-terminal/README.i18n.yaml index 14e566dbc8..6694f6d1db 100644 --- a/packages/client/ui-sidebar-terminal/README.i18n.yaml +++ b/packages/client/ui-sidebar-terminal/README.i18n.yaml @@ -33,5 +33,5 @@ en: 53d0b4b028b69a33 zh: c7adf9e9c9ede59c /deepseek-ai-dsh-client-ui-sidebar-terminal/known-limitations-and-deferred-work/dev-note: - en: 2acda84e5b9a3ce2 - zh: f86e92da1c66ffff + en: e67f633dc6fe7773 + zh: 7aeea62eda52b83b diff --git a/packages/client/ui-sidebar-terminal/README.md b/packages/client/ui-sidebar-terminal/README.md index fb2097bea1..45b23f460f 100644 --- a/packages/client/ui-sidebar-terminal/README.md +++ b/packages/client/ui-sidebar-terminal/README.md @@ -83,6 +83,6 @@ None; terminal output travels only between the browser and Host.
Working context for maintainers — click to expand -No runtime invariant companion is published. One owner orders terminal metadata and screen updates; the provider exposes no independently observed dimensions to compare. +None.
diff --git a/packages/client/ui-sidebar-terminal/README.zh.md b/packages/client/ui-sidebar-terminal/README.zh.md index 13a26f821f..afb87ddea0 100644 --- a/packages/client/ui-sidebar-terminal/README.zh.md +++ b/packages/client/ui-sidebar-terminal/README.zh.md @@ -83,6 +83,6 @@ kind: "package-reference"
维护说明 -不发布运行时 invariant companion。终端元数据与屏幕更新由同一对象按序写入,没有独立的进程尺寸观测可供比较。 +无。
diff --git a/packages/client/ui-sidebar/README.i18n.yaml b/packages/client/ui-sidebar/README.i18n.yaml index b1ffa3bf3e..5c84eb18fe 100644 --- a/packages/client/ui-sidebar/README.i18n.yaml +++ b/packages/client/ui-sidebar/README.i18n.yaml @@ -51,5 +51,5 @@ en: 262db5b10dfb7d98 zh: c1b5cf6aa58b054e /deepseek-ai-dsh-client-ui-sidebar/known-limitations-and-deferred-work/dev-note: - en: c8d74533efbc8d23 - zh: eb58f7098335f55e + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-sidebar/README.md b/packages/client/ui-sidebar/README.md index 0314df8089..65e11ae3ee 100644 --- a/packages/client/ui-sidebar/README.md +++ b/packages/client/ui-sidebar/README.md @@ -115,5 +115,3 @@ These limits define what the shell owns versus what its occupants own; they are None. - -**Runtime invariant:** No companion is published. Panel metadata is a read-only presentation projection of the Slot registry and locale, with no independent write API. The registry owns entry identity and disposal; this package's assembly tests assert the projection after registration and locale notifications settle. The shell owns no separate navigation state to reconcile with those sources. diff --git a/packages/client/ui-sidebar/README.zh.md b/packages/client/ui-sidebar/README.zh.md index 3255915dcb..6cb420c9e2 100644 --- a/packages/client/ui-sidebar/README.zh.md +++ b/packages/client/ui-sidebar/README.zh.md @@ -115,5 +115,3 @@ dsh Web 客户端的侧边栏让用户识别当前构建、启动新会话、将 无。 - -**运行时不变式:** 不发布伴生入口。面板元数据是 Slot 注册表与 locale 的只读呈现投影,没有独立写入 API。注册表负责条目身份与资源释放;本包的装配测试在注册和 locale 通知完成后断言该投影。外壳没有需要与这些来源协调的独立导航状态。 diff --git a/packages/client/ui-skill/README.i18n.yaml b/packages/client/ui-skill/README.i18n.yaml index a02c562096..3f5d54b740 100644 --- a/packages/client/ui-skill/README.i18n.yaml +++ b/packages/client/ui-skill/README.i18n.yaml @@ -54,5 +54,5 @@ en: aed53b17a91d4b02 zh: fac571e9228e69c5 /deepseek-ai-dsh-client-ui-skill/known-limitations-and-deferred-work/dev-note: - en: f3cb9f98b665c2f9 - zh: 2275c60173ce961b + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-skill/README.md b/packages/client/ui-skill/README.md index ad93c06a1a..08d2e1137c 100644 --- a/packages/client/ui-skill/README.md +++ b/packages/client/ui-skill/README.md @@ -112,5 +112,3 @@ These limits define where the reference and the row fall back to generic behavio None. - -**Runtime invariant:** No companion is published. The slash source, locale dictionaries, and keyed toolview are registry-owned registrations whose disposal is proven by the HMR-safety spec. They emit no Cordis events and own no cross-plugin mutable state. diff --git a/packages/client/ui-skill/README.zh.md b/packages/client/ui-skill/README.zh.md index 69ba568ea8..e07a59326b 100644 --- a/packages/client/ui-skill/README.zh.md +++ b/packages/client/ui-skill/README.zh.md @@ -112,5 +112,3 @@ source 不实现任何裁决钩子,也没有引用 codec:pick 落下字面 无。 - -**运行时不变式:** 不发布伴生入口。slash source、locale dictionary 与 keyed toolview 都是由注册表持有的注册项,其释放行为已由 HMR(热模块替换)安全规范证明;它们不发出 Cordis 事件或持有跨插件可变状态。 diff --git a/packages/client/ui-slots/README.i18n.yaml b/packages/client/ui-slots/README.i18n.yaml index a06b6a5010..3473988b4b 100644 --- a/packages/client/ui-slots/README.i18n.yaml +++ b/packages/client/ui-slots/README.i18n.yaml @@ -51,5 +51,5 @@ en: 093b1c5076ddfd6d zh: 3069c423115492fe /deepseek-ai-dsh-client-ui-slots/known-limitations-and-deferred-work/dev-note: - en: 89e67d6054319245 - zh: b55de4cfddf136a5 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-slots/README.md b/packages/client/ui-slots/README.md index d82b303d5d..e109e19211 100644 --- a/packages/client/ui-slots/README.md +++ b/packages/client/ui-slots/README.md @@ -107,5 +107,3 @@ These limits define the registry's scaling behavior and accepted type noise; the None. - -**Runtime invariant:** No companion is published. This is a zero-dependency pure registry core; it emits no Cordis events itself (the `ui-renderer` SlotRegistry owns the event bridge and its invariants); define/register/dispose sequencing is asserted directly by this package's behavior specs. diff --git a/packages/client/ui-slots/README.zh.md b/packages/client/ui-slots/README.zh.md index 2a9d0364ed..cfcf8c7375 100644 --- a/packages/client/ui-slots/README.zh.md +++ b/packages/client/ui-slots/README.zh.md @@ -107,5 +107,3 @@ register 调用可以用 `store: defineStore(...)` 声明 store 席位:`init` 无。 - -**运行时不变式:** 不发布伴生入口。这是零依赖的纯注册表核心,本身不发出 Cordis 事件;`ui-renderer` SlotRegistry 负责事件桥及其不变式。本包的行为规范直接断言 define/register/dispose 的执行顺序。 diff --git a/packages/client/ui-subagent/README.i18n.yaml b/packages/client/ui-subagent/README.i18n.yaml index 60769dea9e..d59efffb7b 100644 --- a/packages/client/ui-subagent/README.i18n.yaml +++ b/packages/client/ui-subagent/README.i18n.yaml @@ -60,5 +60,5 @@ en: ee310ff3cd487639 zh: 23793aa1c28a23af /deepseek-ai-dsh-client-ui-subagent/known-limitations-and-deferred-work/dev-note: - en: 3dd148cecbb1a4df - zh: 626cc5ed9f65c72a + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-subagent/README.md b/packages/client/ui-subagent/README.md index 037c84c9c8..d33a0fc61a 100644 --- a/packages/client/ui-subagent/README.md +++ b/packages/client/ui-subagent/README.md @@ -124,5 +124,3 @@ These limits define what the catalog can show and what `@` references mean; they None. - -**Runtime invariant:** No companion is published. The plugin registers a single slash source whose disposal is proven by the HMR-safety spec; it emits no Cordis events and owns no cross-plugin mutable state. diff --git a/packages/client/ui-subagent/README.zh.md b/packages/client/ui-subagent/README.zh.md index e7c3fb052c..be6e04e492 100644 --- a/packages/client/ui-subagent/README.zh.md +++ b/packages/client/ui-subagent/README.zh.md @@ -124,5 +124,3 @@ one-shot child 始终选用只读编辑器。可继续 child 仅在其确切 par 无。 - -**运行时不变式:** 不发布伴生入口。插件只注册一个 slash source,其资源释放已由 HMR(热模块替换)安全规范验证;它不发出 Cordis 事件,也不持有跨插件可变状态。 diff --git a/packages/client/ui-theme/README.i18n.yaml b/packages/client/ui-theme/README.i18n.yaml index c3ab1c7cdc..ef2f22eaaa 100644 --- a/packages/client/ui-theme/README.i18n.yaml +++ b/packages/client/ui-theme/README.i18n.yaml @@ -51,5 +51,5 @@ en: 0d328dbced31a041 zh: 2951de356ba6bb82 /deepseek-ai-dsh-client-ui-theme/known-limitations-and-deferred-work/dev-note: - en: f1a2942985b11203 - zh: 8196d82321e024b0 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-theme/README.md b/packages/client/ui-theme/README.md index 38f1ac3372..97f6ce7981 100644 --- a/packages/client/ui-theme/README.md +++ b/packages/client/ui-theme/README.md @@ -134,5 +134,3 @@ These limits define the theme extension surface and the color authority; they ar None. - -**Runtime invariant:** No companion is published. The settings scope validates and publishes the durable theme section, while the registry emits `theme/change` synchronously with its own mutations. Store/registry agreement is covered directly by this package's Host, scope, and service behavior specs. diff --git a/packages/client/ui-theme/README.zh.md b/packages/client/ui-theme/README.zh.md index 5a345a4e8e..e8522e8dc3 100644 --- a/packages/client/ui-theme/README.zh.md +++ b/packages/client/ui-theme/README.zh.md @@ -134,5 +134,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。settings scope 校验并发布持久 theme section,注册表与自身变更同步发出 `theme/change`;存储与注册表的一致性由本包针对 Host、scope 与服务行为的测试直接覆盖。 diff --git a/packages/client/ui-tool/README.i18n.yaml b/packages/client/ui-tool/README.i18n.yaml index 0dacd90707..5bd1cf8b10 100644 --- a/packages/client/ui-tool/README.i18n.yaml +++ b/packages/client/ui-tool/README.i18n.yaml @@ -48,5 +48,5 @@ en: 7475317a656f1d11 zh: 787f10cc0f2c586e /deepseek-ai-dsh-client-ui-tool/known-limitations-and-deferred-work/dev-note: - en: 0fdeab2e4db0261f - zh: 156d2b2952cf112e + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-tool/README.md b/packages/client/ui-tool/README.md index 5f4243229e..bad8a1fbc2 100644 --- a/packages/client/ui-tool/README.md +++ b/packages/client/ui-tool/README.md @@ -142,5 +142,3 @@ These limits define the dispatch depth and the view ownership; they are current None. - -**Runtime invariant:** No companion is published. Tool composition is browser-only and contributes no events or cross-plugin mutable state; slot ownership is checked by ui-slots. diff --git a/packages/client/ui-tool/README.zh.md b/packages/client/ui-tool/README.zh.md index c1b396ee4a..40e9acdc2f 100644 --- a/packages/client/ui-tool/README.zh.md +++ b/packages/client/ui-tool/README.zh.md @@ -142,5 +142,3 @@ terminal model 使用浏览器安全入口 `@deepseek-ai/dsh-spill-policy/notice 无。 - -**运行时不变式:** 不发布伴生入口。工具组合只存在于浏览器,不贡献事件或跨插件可变状态;slot 所有权由 ui-slots 校验。 diff --git a/packages/client/ui-trajectory/README.i18n.yaml b/packages/client/ui-trajectory/README.i18n.yaml index 75060f69f4..a83e6ecca5 100644 --- a/packages/client/ui-trajectory/README.i18n.yaml +++ b/packages/client/ui-trajectory/README.i18n.yaml @@ -45,5 +45,5 @@ en: d7da620f56eb3c65 zh: fb030b68322b94cd /deepseek-ai-dsh-client-ui-trajectory/known-limitations-and-deferred-work/dev-note: - en: f1893e3a6c69cc58 - zh: b6e0dfe20567c35f + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-trajectory/README.md b/packages/client/ui-trajectory/README.md index ca4a84a44f..cbb6cb6167 100644 --- a/packages/client/ui-trajectory/README.md +++ b/packages/client/ui-trajectory/README.md @@ -112,5 +112,3 @@ These limits define what the view can show while work is in flight; they are cur None. - -**Runtime invariant:** No companion is published. It is a pure-consumer plugin: it emits no Cordis events and owns no mutable cross-plugin state; its view-slot registration is a plain effect whose disposal the slot ledger's own specs and this package's behavior specs observe directly. diff --git a/packages/client/ui-trajectory/README.zh.md b/packages/client/ui-trajectory/README.zh.md index 1b22723b23..7b3d938c7c 100644 --- a/packages/client/ui-trajectory/README.zh.md +++ b/packages/client/ui-trajectory/README.zh.md @@ -112,5 +112,3 @@ Trajectory 要求会话壳把 composer 作为浮层置于全高记录表上方 无。 - -**运行时不变式:** 不发布伴生入口。这是纯消费插件,不发出 Cordis 事件,也不持有跨插件可变状态;其 view-slot 注册是普通 effect,slot ledger 自身的规格测试与本包的行为规格测试会直接观察其释放。 diff --git a/packages/client/ui-user-questions/README.i18n.yaml b/packages/client/ui-user-questions/README.i18n.yaml index 7cbe2978c6..78ff5697f1 100644 --- a/packages/client/ui-user-questions/README.i18n.yaml +++ b/packages/client/ui-user-questions/README.i18n.yaml @@ -54,5 +54,5 @@ en: d61767a407077aaf zh: 891e7bffa61f3017 /deepseek-ai-dsh-client-ui-user-questions/known-limitations-and-deferred-work/dev-note: - en: 5dd8c89b228f4037 - zh: cf8d2774370d1b74 + en: 4ff0c667c1b130df + zh: 3f42eaff797dc46d diff --git a/packages/client/ui-user-questions/README.md b/packages/client/ui-user-questions/README.md index c8c182444f..d57e359de7 100644 --- a/packages/client/ui-user-questions/README.md +++ b/packages/client/ui-user-questions/README.md @@ -128,4 +128,4 @@ None. -**Runtime invariant:** The Host Session projection is authoritative for durable timed questions. Browser storage only preserves unfinished input and never creates or keeps a question open. No runtime invariant companion is published because the Host validates and projects the durable state before this client package renders it. +The Host Session projection is authoritative for durable timed questions. Browser storage only preserves unfinished input and never creates or keeps a question open. diff --git a/packages/client/ui-user-questions/README.zh.md b/packages/client/ui-user-questions/README.zh.md index 03ed5fc906..2145663976 100644 --- a/packages/client/ui-user-questions/README.zh.md +++ b/packages/client/ui-user-questions/README.zh.md @@ -128,4 +128,4 @@ Web 客户端在聊天输入框旁显示 agent(智能体)的提问。用户 -**运行时不变式:** Host 会话投影是持久计时问题的权威来源。浏览器存储只保留未完成输入,绝不会创建问题或让问题继续保持打开。此包不发布运行时不变量 companion,因为 Host 会先校验并投影持久状态,客户端随后才渲染它。 +Host 会话投影是持久计时问题的权威来源。浏览器存储只保留未完成输入,绝不会创建问题或让问题继续保持打开。 diff --git a/packages/client/ui-workflow-run/README.i18n.yaml b/packages/client/ui-workflow-run/README.i18n.yaml index 90aefbb9a3..7120209b83 100644 --- a/packages/client/ui-workflow-run/README.i18n.yaml +++ b/packages/client/ui-workflow-run/README.i18n.yaml @@ -45,5 +45,5 @@ en: 3c5afa2d75f50eb0 zh: 84f2e93d6dcb0ada /deepseek-ai-dsh-client-ui-workflow-run/known-limitations-and-deferred-work/dev-note: - en: 8e5a74ff5f3bc069 - zh: b5d05c4d192ac837 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-workflow-run/README.md b/packages/client/ui-workflow-run/README.md index 9eb9ed8750..46d0190df5 100644 --- a/packages/client/ui-workflow-run/README.md +++ b/packages/client/ui-workflow-run/README.md @@ -98,5 +98,3 @@ These limits define which runs produce records and what the node exposes; they a None. - -**Runtime invariant:** No companion is published. The browser plugin contributes one effect-owned Conversation Definition, keyed renderer, and dictionary; tests prove their disposal and the Host tool package owns the durable event invariant. diff --git a/packages/client/ui-workflow-run/README.zh.md b/packages/client/ui-workflow-run/README.zh.md index 8c2989b746..9cac24f953 100644 --- a/packages/client/ui-workflow-run/README.zh.md +++ b/packages/client/ui-workflow-run/README.zh.md @@ -98,5 +98,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。浏览器插件只贡献由 effect 持有的 Conversation Definition、keyed renderer 与 dictionary;测试证明资源释放时会撤销这三项贡献;Host tool 包负责持久事件不变式。 diff --git a/packages/client/ui-workspace/README.i18n.yaml b/packages/client/ui-workspace/README.i18n.yaml index 1d71954157..868397d50f 100644 --- a/packages/client/ui-workspace/README.i18n.yaml +++ b/packages/client/ui-workspace/README.i18n.yaml @@ -69,5 +69,5 @@ en: 00d7b1ddda7390f5 zh: 1e5a1055d1be4d1d /deepseek-ai-dsh-client-ui-workspace/known-limitations-and-deferred-work/dev-note: - en: 585813ff2c68c20c - zh: 18b43c0d64fa8a72 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-workspace/README.md b/packages/client/ui-workspace/README.md index 7a5f94d110..4e72657ce2 100644 --- a/packages/client/ui-workspace/README.md +++ b/packages/client/ui-workspace/README.md @@ -224,5 +224,3 @@ These limits define the search depth, the archive surface, and the picking carri None. - -**Runtime invariant:** No companion is published. This is a pure-consumer plugin that registers presentational components into two host-declared slots and registers its locale dictionaries; its inject face consists of stateless RPC wrappers plus a create-and-open call. It emits no Cordis events and owns no cross-plugin mutable state. diff --git a/packages/client/ui-workspace/README.zh.md b/packages/client/ui-workspace/README.zh.md index 634d293535..f4eb5a2ed1 100644 --- a/packages/client/ui-workspace/README.zh.md +++ b/packages/client/ui-workspace/README.zh.md @@ -224,5 +224,3 @@ Workspace 与 Session 悬浮卡片会复制对应行被截断的值:激活 Wor 无。 - -**运行时不变式:** 不发布伴生入口。这是一个纯消费方插件,只向两个由宿主声明的 slot 注册展示组件,并注册自身的 locale dictionaries;inject face 由无状态 RPC 包装层和一次 create-and-open 调用组成;本插件不发出 Cordis 事件,也不持有跨插件可变状态。 diff --git a/packages/client/web/README.i18n.yaml b/packages/client/web/README.i18n.yaml index 3f8cb1d37e..816b541cd8 100644 --- a/packages/client/web/README.i18n.yaml +++ b/packages/client/web/README.i18n.yaml @@ -57,5 +57,5 @@ en: f498ba05d4ea96d5 zh: c3e57dd8ca9b5bda /deepseek-ai-dsh-client-web/known-limitations-and-deferred-work/dev-note: - en: 80ab3d00f7aac4de - zh: 3c08f30322413856 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/web/README.md b/packages/client/web/README.md index 6b84bc4a87..8fbbd89fa1 100644 --- a/packages/client/web/README.md +++ b/packages/client/web/README.md @@ -131,5 +131,3 @@ These limits define what the boot kernel does not support. They are current pack None. - -**Runtime invariant:** No companion is published. The Vite entry shell provides boot glue and module-table seeding, emits no Cordis events, and holds no cross-plugin mutable state; the boot chain (loading page → settled → one-flip UI) is verified by the web smoke e2e against the real carrier. diff --git a/packages/client/web/README.zh.md b/packages/client/web/README.zh.md index fe3b5ce1a9..4e64bf2978 100644 --- a/packages/client/web/README.zh.md +++ b/packages/client/web/README.zh.md @@ -131,5 +131,3 @@ kind: "package-library" 无。 - -**运行时不变式:** 不发布伴生入口。这是 Vite entry shell,只负责 boot glue 与 module-table seeding,不发出 Cordis 事件或持有跨插件可变状态;boot chain(加载页 → 启动就绪 → 一次切换至 UI)由真实 carrier 上的 web e2e 冒烟测试验证。 diff --git a/packages/compaction/command-compact/README.i18n.yaml b/packages/compaction/command-compact/README.i18n.yaml index 663500b013..126db34873 100644 --- a/packages/compaction/command-compact/README.i18n.yaml +++ b/packages/compaction/command-compact/README.i18n.yaml @@ -39,8 +39,8 @@ en: 498a645054ff192f zh: f993a288e1408355 /deepseek-ai-dsh-command-compact/understand-the-implementation/source-map: - en: 84b40f60d465fd94 - zh: aac534b232817c05 + en: d68c7fb0aa68ce47 + zh: 069462827640bc18 /deepseek-ai-dsh-command-compact/further-exploration: en: 2d8e82f68867898e zh: bb18a352091adf85 diff --git a/packages/compaction/command-compact/README.md b/packages/compaction/command-compact/README.md index 4c9a550feb..9e2cb7aee4 100644 --- a/packages/compaction/command-compact/README.md +++ b/packages/compaction/command-compact/README.md @@ -95,7 +95,6 @@ Every resolved invocation records the executor-owned log-only pair `command/run` | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `/compact` registration, argument rejection, error-code mapping, lifecycle drain | -| — | No runtime invariant companion is published; this command adapter owns no state or event stream; the compaction seam owns the balanced durable transaction and the command registry owns registration and dispatch lifecycle. | diff --git a/packages/compaction/command-compact/README.zh.md b/packages/compaction/command-compact/README.zh.md index ad31604e9e..fab2abc8b9 100644 --- a/packages/compaction/command-compact/README.zh.md +++ b/packages/compaction/command-compact/README.zh.md @@ -95,7 +95,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`/compact` 注册、参数拒绝、错误码映射、生命周期排空 | -| — | 不发布运行时不变式伴随条目;该命令适配器不拥有任何状态或事件流;压缩 seam 拥有平衡且具持久性的事务,命令注册表拥有注册与分发生命周期。 | diff --git a/packages/compaction/compaction-basic/README.i18n.yaml b/packages/compaction/compaction-basic/README.i18n.yaml index aca17e342e..c16287dd16 100644 --- a/packages/compaction/compaction-basic/README.i18n.yaml +++ b/packages/compaction/compaction-basic/README.i18n.yaml @@ -54,8 +54,8 @@ en: 246736aa86d3013d zh: 35e261927ba0699c /deepseek-ai-dsh-compaction-basic/understand-the-implementation/source-map: - en: bd9d1e01ec833593 - zh: cbadca50d8e9ed07 + en: 3f1d7a55b790e623 + zh: 39e878c0785a885c /deepseek-ai-dsh-compaction-basic/further-exploration: en: 84b5b81eb9b5965d zh: 7000af0fa502fcc1 diff --git a/packages/compaction/compaction-basic/README.md b/packages/compaction/compaction-basic/README.md index fa4ae2fb48..1207cd81fe 100644 --- a/packages/compaction/compaction-basic/README.md +++ b/packages/compaction/compaction-basic/README.md @@ -137,7 +137,6 @@ The transaction validates the surface span and the durable lock, appends `compac | [`src/summarizer.ts`](src/summarizer.ts) | Default `ctx.llm.stream()` summarization, checkpoint framing, safe-summary projection | | [`src/config.ts`](src/config.ts) | Load-time validation and routed-model policy resolution | | [`src/types.ts`](src/types.ts) | `BasicCompactionConfig` and resolved policy vocabulary | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. The durable bracket remains observable in the session log. | diff --git a/packages/compaction/compaction-basic/README.zh.md b/packages/compaction/compaction-basic/README.zh.md index 1cb48123cd..86cec3c9e9 100644 --- a/packages/compaction/compaction-basic/README.zh.md +++ b/packages/compaction/compaction-basic/README.zh.md @@ -137,7 +137,6 @@ kind: "package-reference" | [`src/summarizer.ts`](src/summarizer.ts) | 默认 `ctx.llm.stream()` 摘要、检查点框定、安全摘要投影 | | [`src/config.ts`](src/config.ts) | 加载时验证与路由模型策略解析 | | [`src/types.ts`](src/types.ts) | `BasicCompactionConfig` 与已解析策略词汇 | -| — | 不发布运行时不变式配套条目;除所属 seam 强制执行的约定外,本包不公开独立事件序列或可变数据关系。持久标记对仍可在会话日志中观察。 | diff --git a/packages/compaction/compaction-basic/package.json b/packages/compaction/compaction-basic/package.json index d5f128998c..879855c269 100644 --- a/packages/compaction/compaction-basic/package.json +++ b/packages/compaction/compaction-basic/package.json @@ -55,7 +55,6 @@ "@deepseek-ai/dsh-commands": "workspace:*", "@deepseek-ai/dsh-compaction": "workspace:*", "@deepseek-ai/dsh-compaction-tool-result-pruner": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-llm-retry": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", diff --git a/packages/compaction/compaction-basic/tests/compaction-loop-repro.spec.ts b/packages/compaction/compaction-basic/tests/compaction-loop-repro.spec.ts index b6c8ff7f86..8d762591eb 100644 --- a/packages/compaction/compaction-basic/tests/compaction-loop-repro.spec.ts +++ b/packages/compaction/compaction-basic/tests/compaction-loop-repro.spec.ts @@ -8,10 +8,6 @@ import { defineContentToolFixture } from '@deepseek-ai/dsh-tools' 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 InvariantRegistry from '@deepseek-ai/dsh-invariants' -import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant' -import * as AgentInvariant from '@deepseek-ai/dsh-agent/invariant' -import * as AgentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant' import { BasicCompactionEngine } from '@deepseek-ai/dsh-compaction-basic' import TokenMeter from '@deepseek-ai/dsh-token-meter' import * as LlmRetry from '@deepseek-ai/dsh-llm-retry' @@ -139,17 +135,9 @@ class OverflowRecoveryAdapter extends LlmAdapter { } } -async function mountInvariants(ctx: Context): Promise { - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SessionInvariant) - await ctx.plugin(AgentInvariant) - await ctx.plugin(AgentLoopInvariant) -} - async function harness(toolSteps: number): Promise<{ ctx: Context; compact: ReproCompactionEngine }> { const ctx = new Context() await mountAgentLoopTestDependencies(ctx) - await mountInvariants(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(TokenMeter) ctx.llm.registerAdapter(['mock'], new StepwiseToolAdapter(toolSteps)) @@ -389,7 +377,6 @@ describe('context-overflow recovery across the real loop and compaction-basic', const ctx = new Context() const adapter = new OverflowRecoveryAdapter(delivery) await mountAgentLoopTestDependencies(ctx) - await mountInvariants(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(TokenMeter) ctx.llm.registerAdapter(['mock'], adapter) @@ -468,7 +455,6 @@ describe('context-overflow recovery across the real loop and compaction-basic', const ctx = new Context() const adapter = new OverflowRecoveryAdapter('thrown', true) await mountAgentLoopTestDependencies(ctx) - await mountInvariants(ctx) await ctx.plugin(LlmRetry) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(TokenMeter) diff --git a/packages/compaction/compaction-basic/tests/manual-compaction.spec.ts b/packages/compaction/compaction-basic/tests/manual-compaction.spec.ts index 744416e219..051ae88d93 100644 --- a/packages/compaction/compaction-basic/tests/manual-compaction.spec.ts +++ b/packages/compaction/compaction-basic/tests/manual-compaction.spec.ts @@ -3,12 +3,7 @@ import { describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' import { CommandId } from '@deepseek-ai/dsh-commands/brand' -import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant' -import * as AgentInvariant from '@deepseek-ai/dsh-agent/invariant' -import * as AgentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant' -import * as CompactionInvariant from '@deepseek-ai/dsh-compaction/invariant' import { BasicCompactionEngine } from '@deepseek-ai/dsh-compaction-basic' import { CompactionId, isCompactCheckpointSource, ManualCompactionError } from '@deepseek-ai/dsh-compaction' import type { CompactionResult } from '@deepseek-ai/dsh-compaction' @@ -106,16 +101,11 @@ interface LoopHarness { readonly log: string[] } -/** Real loop, session store, and invariant companions around manual compaction. */ +/** Real loop and session store around manual compaction. */ async function loopHarness(): Promise { const ctx = new Context() await mountAgentLoopTestDependencies(ctx) ctx.sessions.registerMessageProjection(imageOffloadProjection) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SessionInvariant) - await ctx.plugin(AgentInvariant) - await ctx.plugin(AgentLoopInvariant) - await ctx.plugin(CompactionInvariant) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(TokenMeter) const adapter = new TextAdapter() diff --git a/packages/compaction/compaction-image-offload/README.i18n.yaml b/packages/compaction/compaction-image-offload/README.i18n.yaml index 0f34aa7e25..c23237b66e 100644 --- a/packages/compaction/compaction-image-offload/README.i18n.yaml +++ b/packages/compaction/compaction-image-offload/README.i18n.yaml @@ -27,8 +27,8 @@ en: 5e4ac1980eec905a zh: 0284e597c82cb179 /deepseek-ai-dsh-compaction-image-offload/understand-the-implementation: - en: 884900df82bc9029 - zh: 2a7a69ae08bbf202 + en: 61d7de1373f1c420 + zh: bec696992dfa774a /deepseek-ai-dsh-compaction-image-offload/further-exploration: en: 081a5eafe9160c31 zh: a4ed86e9389c91d0 diff --git a/packages/compaction/compaction-image-offload/README.md b/packages/compaction/compaction-image-offload/README.md index 8e4d812c04..d1d3f60687 100644 --- a/packages/compaction/compaction-image-offload/README.md +++ b/packages/compaction/compaction-image-offload/README.md @@ -53,7 +53,7 @@ This package owns the event declaration, reference validation, immutable image p Summary failures use the synchronous `compaction/summary-error` waterfall. The plugin selects only the supplied summary region and returns true after recording an omission. The compaction backend re-derives and re-prices that region before retrying; each retry omits additional retained occurrences, so recovery ends when none remain. Cancellation and unrelated selection changes reject the summary. Recorded omissions survive later failure or cancellation. -No runtime invariant companion is published: the pure projection rejects invalid or repeated image references before Session commits the event, and this executor retains no separate mutable offload state. +The pure projection rejects invalid or repeated image references before Session commits the event, and this executor retains no separate mutable offload state. diff --git a/packages/compaction/compaction-image-offload/README.zh.md b/packages/compaction/compaction-image-offload/README.zh.md index 06ed168e75..ed00a4dfd6 100644 --- a/packages/compaction/compaction-image-offload/README.zh.md +++ b/packages/compaction/compaction-image-offload/README.zh.md @@ -53,7 +53,7 @@ kind: "package-reference" 摘要失败使用同步的 `compaction/summary-error` waterfall。插件只在传入的摘要选区内选图,记录省略后返回 true。压缩后端重新派生该选区并计价,然后重试。每次重试都会继续省略保留的图片,没有可省略的位置时结束恢复。取消或无关的选区变更会使摘要失败。已经记录的省略在后续失败或取消后仍然有效。 -本包不发布运行时 invariant 伴生插件:纯投影在 Session 提交事件前拒绝无效或重复省略的图片引用,执行器不维护独立可变的省略状态。 +纯投影在 Session 提交事件前拒绝无效或重复省略的图片引用,执行器不维护独立可变的省略状态。 diff --git a/packages/compaction/compaction-tool-result-pruner/README.i18n.yaml b/packages/compaction/compaction-tool-result-pruner/README.i18n.yaml index 4092b69a1c..7212e1507d 100644 --- a/packages/compaction/compaction-tool-result-pruner/README.i18n.yaml +++ b/packages/compaction/compaction-tool-result-pruner/README.i18n.yaml @@ -39,8 +39,8 @@ en: 9813749e4f016897 zh: f2b2b5d6e5e8977a /deepseek-ai-dsh-compaction-tool-result-pruner/understand-the-implementation/source-map: - en: 9c10b4382c2347b5 - zh: b713a6dcf7589aee + en: 4544e91902960a0e + zh: 0b07cb7e3d96267b /deepseek-ai-dsh-compaction-tool-result-pruner/further-exploration: en: 3253e154e7610bbd zh: ba28c7411fd5c8bb diff --git a/packages/compaction/compaction-tool-result-pruner/README.md b/packages/compaction/compaction-tool-result-pruner/README.md index 7b05154bc0..dd955d0507 100644 --- a/packages/compaction/compaction-tool-result-pruner/README.md +++ b/packages/compaction/compaction-tool-result-pruner/README.md @@ -88,7 +88,6 @@ Pruning measures `text` blocks by Unicode code point (non-text blocks cost zero) | [`src/index.ts`](src/index.ts) | Plugin entry: `ToolResultPruner` service, `pruneSession` / `pruneContent` / `measureContent` | | [`src/config.ts`](src/config.ts) | `PRUNE_MARKER`, defaults, code-point counting, budget validation | | [`src/types.ts`](src/types.ts) | `ToolResultPruneConfig`, `ResolvedConfig`, `PrunedEntry`, `PruneResult` | -| — | No runtime invariant companion is published; Session validates each content-only rewrite and its companion owns cross-event enclosure. | diff --git a/packages/compaction/compaction-tool-result-pruner/README.zh.md b/packages/compaction/compaction-tool-result-pruner/README.zh.md index 06ecb2b3a6..869d82a72c 100644 --- a/packages/compaction/compaction-tool-result-pruner/README.zh.md +++ b/packages/compaction/compaction-tool-result-pruner/README.zh.md @@ -88,7 +88,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:`ToolResultPruner` 服务、`pruneSession` / `pruneContent` / `measureContent` | | [`src/config.ts`](src/config.ts) | `PRUNE_MARKER`、默认值、码点计数、预算验证 | | [`src/types.ts`](src/types.ts) | `ToolResultPruneConfig`、`ResolvedConfig`、`PrunedEntry`、`PruneResult` | -| — | 不发布运行时不变式伴生入口;Session 会验证每次仅改写内容的操作,其伴生条目负责维护跨事件包围关系。 | diff --git a/packages/compaction/compaction-tool-result-pruner/package.json b/packages/compaction/compaction-tool-result-pruner/package.json index 72f87ef9f1..63c817e46e 100644 --- a/packages/compaction/compaction-tool-result-pruner/package.json +++ b/packages/compaction/compaction-tool-result-pruner/package.json @@ -42,7 +42,6 @@ "@deepseek-ai/cordis-plugin-include": "workspace:~", "@deepseek-ai/cordis-plugin-loader": "workspace:~", "@deepseek-ai/dsh-compaction": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-projection": "workspace:*", diff --git a/packages/compaction/compaction-tool-result-pruner/tests/tool-result-pruner.spec.ts b/packages/compaction/compaction-tool-result-pruner/tests/tool-result-pruner.spec.ts index d7bbbae5c9..f5479ebc5c 100644 --- a/packages/compaction/compaction-tool-result-pruner/tests/tool-result-pruner.spec.ts +++ b/packages/compaction/compaction-tool-result-pruner/tests/tool-result-pruner.spec.ts @@ -3,14 +3,12 @@ import { describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import { ToolCallId , createMessage, createToolResultMessage } from '@deepseek-ai/dsh-llm' import type { ContentBlock } from '@deepseek-ai/dsh-llm' -import SessionStore, { +import { Session, SessionId, SessionSeq, } from '@deepseek-ai/dsh-session' import type { SurfaceEvent } from '@deepseek-ai/dsh-session' -import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' import TokenMeter from '@deepseek-ai/dsh-token-meter' import ToolResultPruner, { @@ -279,21 +277,4 @@ describe('ToolResultPruner session transaction', () => { expect(replay.deriveMessages()).toEqual(session.deriveMessages()) expect(replay.surface.replaceGeneration).toBe(session.surface.replaceGeneration) }) - - it('runs under real invariants between closed steps but not outside a turn', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(SessionProjectionRegistry) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SessionInvariant) - await ctx.plugin(TokenMeter) - const prune = new ToolResultPruner(ctx, SMALL) - const session = ctx.sessions.create(SessionId('invariants')) - appendToolStep(session, 1, 'a', [{ type: 'text', text: 'A'.repeat(100) }]) - expect(() => prune.pruneSession(session)).toThrow(/outside any open turn/) - session.append('turn/start', { - turn: 2, - }) - expect(() => prune.pruneSession(session)).not.toThrow() - }) }) diff --git a/packages/compaction/compaction/README.i18n.yaml b/packages/compaction/compaction/README.i18n.yaml index 26d6405507..51704c75d7 100644 --- a/packages/compaction/compaction/README.i18n.yaml +++ b/packages/compaction/compaction/README.i18n.yaml @@ -51,8 +51,8 @@ en: 28980d8259e59700 zh: c452e0bb4b778824 /deepseek-ai-dsh-compaction/understand-the-implementation/source-map: - en: 121139cf1878d7cf - zh: f8f9d8d3eb9866b5 + en: 076448ecc9911baf + zh: 1b671f96fb217d5e /deepseek-ai-dsh-compaction/understand-the-implementation/locking-and-serialization: en: 68caa51bed28bada zh: 3f8261f9dcba95ed diff --git a/packages/compaction/compaction/README.md b/packages/compaction/compaction/README.md index fecb3b6731..70c4c3a1ff 100644 --- a/packages/compaction/compaction/README.md +++ b/packages/compaction/compaction/README.md @@ -105,7 +105,6 @@ The replacement sits inside the lock bracket, so a crash between `compaction/sta | [`src/tool-pairing.ts`](src/tool-pairing.ts) | Per-session cut-balance cache behind the two boundary helpers | | [`src/checkpoint.ts`](src/checkpoint.ts) | Cordis-free checkpoint source constructor and predicate (`./checkpoint` leaf) | | [`src/brand.ts`](src/brand.ts) | `CompactionId` branded identity | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: validates the `compaction/start`→`summary`→`end` bracket, its owner-turn enclosure, and checkpoint correlation | ### Locking and serialization diff --git a/packages/compaction/compaction/README.zh.md b/packages/compaction/compaction/README.zh.md index 86e6a2935c..7f18baa42e 100644 --- a/packages/compaction/compaction/README.zh.md +++ b/packages/compaction/compaction/README.zh.md @@ -105,7 +105,6 @@ kind: "package-reference" | [`src/tool-pairing.ts`](src/tool-pairing.ts) | 两个边界 helper 背后的每会话切分点平衡缓存 | | [`src/checkpoint.ts`](src/checkpoint.ts) | 无 Cordis 依赖的检查点来源构造函数与谓词(`./checkpoint` 叶子) | | [`src/brand.ts`](src/brand.ts) | `CompactionId` 品牌化标识 | -| [`src/invariant.ts`](src/invariant.ts) | 不变量配套组件:校验 `compaction/start`→`summary`→`end` 标记对、其属主轮次包裹与检查点关联 | ### 锁与串行化 diff --git a/packages/compaction/compaction/package.json b/packages/compaction/compaction/package.json index b12a033302..44d1da79a5 100644 --- a/packages/compaction/compaction/package.json +++ b/packages/compaction/compaction/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -35,7 +31,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -43,7 +38,6 @@ "peerDependencies": { "@deepseek-ai/dsh-brand": "workspace:*", "@deepseek-ai/dsh-commands": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/cordis": "workspace:~" @@ -51,7 +45,6 @@ "devDependencies": { "@deepseek-ai/dsh-brand": "workspace:*", "@deepseek-ai/dsh-commands": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/cordis": "workspace:~" diff --git a/packages/compaction/compaction/src/invariant.ts b/packages/compaction/compaction/src/invariant.ts deleted file mode 100644 index 0b347a71eb..0000000000 --- a/packages/compaction/compaction/src/invariant.ts +++ /dev/null @@ -1,361 +0,0 @@ -/** Package-owned compaction log-stream invariants. @module @deepseek-ai/dsh-compaction/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import { isReplacementSurfaceEvent, SessionSeq } from '@deepseek-ai/dsh-session' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import { SurfaceManager } from '@deepseek-ai/dsh-session/surface' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { CompactionId } from './brand.ts' -import { isCompactCheckpointSource } from './checkpoint.ts' -import type { CompactionCheckpointSource } from './checkpoint.ts' -import type {} from './types.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-compaction' - -/** Cordis companion plugin name. */ -export const name = 'compaction-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -interface CompactionTrace { - compactionId: CompactionId - sourceCommandId: string | undefined - startSeq: SessionSeq - turn: number | null - summarized: boolean -} - -interface SessionTrace { - openTurn: number | null - compaction: CompactionTrace | undefined - surfaceEvents: SessionEvent[] - surface: SurfaceManager -} - -type CompactionTransition = - | { kind: 'start'; compactionId: CompactionId; sourceCommandId: string | undefined; startSeq: SessionSeq; turn: number | null } - | { kind: 'summary'; compactionId: CompactionId; sourceCommandId: string | undefined; startSeq: SessionSeq; turn: number | null } - | { kind: 'end' } - | { kind: 'end-seed' } - -/** Require a durable opaque identity to be a non-empty string. */ -function validateId(value: unknown, label: string, fail: InvariantFailure): asserts value is string { - if (typeof value !== 'string' || value.length === 0) fail(`${label} must be a non-empty string`) -} - -/** Validate a durable event-sequence identity at this package's event boundary. */ -function validateSeq(value: unknown, label: string, fail: InvariantFailure): SessionSeq { - if (typeof value !== 'number') return fail(`${label} must be a non-negative safe integer event seq`) - try { - return SessionSeq(value) - } catch { - return fail(`${label} must be a non-negative safe integer event seq`) - } -} - -/** Validate one shadowed surface span and its complete ordered identity list. */ -function validateShadowedSeqs( - trace: SessionTrace, - event: SessionEvent<'compaction/summary' | 'compaction/prune'>, - fail: InvariantFailure, -): void { - const eventType = event.type - const { data } = event - const start = validateSeq(data.shadowedRange.start, `${eventType} shadowedRange.start`, fail) - const end = validateSeq(data.shadowedRange.end, `${eventType} shadowedRange.end`, fail) - const seqs = data.shadowedSeqs.map((seq, index) => validateSeq(seq, `${eventType} shadowedSeqs[${index}]`, fail)) - if (seqs.length === 0) fail(`${eventType} shadowedSeqs must be non-empty`) - if (seqs[0] !== start || seqs.at(-1) !== end) { - fail(`${eventType} shadowedRange must match the first and last shadowedSeqs`) - } - const surface = trace.surface.nodes - const startIndex = surface.indexOf(start) - const endIndex = surface.indexOf(end) - if (startIndex < 0 || endIndex < startIndex) { - fail(`${eventType} shadowed seqs must name an earlier current surface span`) - } - const expected = surface.slice(startIndex, endIndex + 1) - if (expected.length !== seqs.length - || expected.some((seq, index) => seq !== seqs[index])) { - fail(`${eventType} shadowedSeqs must list every node in the current surface span`) - } -} - -/** Keep the optional initiating command identity stable across one transaction. */ -function validateSourceCommandId( - eventType: string, - value: unknown, - expected: string | undefined, - fail: InvariantFailure, -): void { - if (value !== undefined) validateId(value, `${eventType} sourceCommandId`, fail) - if (value !== expected) { - fail(`${eventType} sourceCommandId ${String(value)} does not match compaction/start sourceCommandId ${String(expected)}`) - } -} - -/** Validate one replacement checkpoint against its open compaction transaction. */ -function validateCheckpoint( - trace: SessionTrace, - source: CompactionCheckpointSource, - fail: InvariantFailure, -): void { - validateId(source.compactionId, 'compaction checkpoint compactionId', fail) - if (source.sourceCommandId !== undefined) { - validateId(source.sourceCommandId, 'compaction checkpoint sourceCommandId', fail) - } - const open = trace.compaction - if (open === undefined) fail('compaction checkpoint has no matching compaction/start') - if (source.compactionId !== open.compactionId) { - fail(`compaction checkpoint id ${source.compactionId} does not match compaction/start id ${open.compactionId}`) - } - validateSourceCommandId('compaction checkpoint', source.sourceCommandId, open.sourceCommandId, fail) -} - -/** Compaction starts still unmatched when a later seed boundary made them stale. */ -function inheritedOrphanStartSeqs( - events: readonly SessionEvent[], -): ReadonlySet { - const stale = new Set() - let openStartSeq: SessionSeq | undefined - for (const event of events) { - if (event.type === 'compaction/start') { - openStartSeq = event.seq - } else if (event.type === 'compaction/end') { - openStartSeq = undefined - } else if (event.type === 'session/end-seed') { - if (openStartSeq !== undefined) stale.add(openStartSeq) - openStartSeq = undefined - } - } - return stale -} - -/** Keep every live compaction bracket on one side of each turn boundary. */ -function validateTurnBoundary( - trace: SessionTrace, - event: SessionEvent, - fail: InvariantFailure, -): void { - if ( - (event.type !== 'turn/start' && event.type !== 'turn/end') - || trace.compaction === undefined - ) return - const owner = trace.compaction.turn === null - ? 'standalone compaction' - : `compaction for turn ${trace.compaction.turn}` - fail(`${event.type} cannot cross an open ${owner}`) -} - -/** Advance the committed turn cursor after its boundary has been accepted. */ -function applyTurnBoundary(trace: SessionTrace, event: SessionEvent): boolean { - if (event.type === 'turn/start') { - trace.openTurn = event.data.turn - return true - } - if (event.type === 'turn/end') { - trace.openTurn = null - return true - } - return false -} - -/** Require a numbered bracket inside its exact turn, or a standalone bracket between turns. */ -function validateOwner( - owner: number | null, - openTurn: number | null, - eventType: 'compaction/start' | 'compaction/summary' | 'compaction/end', - fail: InvariantFailure, -): void { - if (owner === null) { - if (openTurn !== null) fail(`${eventType} is standalone but turn ${openTurn} is open`) - return - } - if (openTurn === null) fail(`${eventType} for turn ${owner} appended outside any open turn`) - if (owner !== openTurn) fail(`${eventType} names turn ${owner} but open turn is ${openTurn}`) -} - -/** Validate one compaction event without advancing committed trace state. */ -function validateCompactionEvent( - trace: SessionTrace, - event: SessionEvent, - fail: InvariantFailure, -): CompactionTransition | undefined { - if (event.type === 'session/end-seed') return { kind: 'end-seed' } - if (event.type === 'compaction/prune') { - validateShadowedSeqs(trace, event, fail) - return undefined - } - if (event.type === 'user/message' - && isReplacementSurfaceEvent(event) - && isCompactCheckpointSource(event.data.source)) { - validateCheckpoint(trace, event.data.source, fail) - return undefined - } - if (event.type !== 'compaction/start' && event.type !== 'compaction/summary' && event.type !== 'compaction/end') { - return undefined - } - const open = trace.compaction - if (event.type === 'compaction/start') { - validateId(event.data.compactionId, 'compaction/start compactionId', fail) - if (event.data.sourceCommandId !== undefined) { - validateId(event.data.sourceCommandId, 'compaction/start sourceCommandId', fail) - } - if (open !== undefined) { - const owner = open.turn === null ? 'standalone compaction' : `turn ${open.turn}` - fail(`compaction/start while ${owner} is still compacting`) - } - validateOwner(event.data.turn, trace.openTurn, event.type, fail) - return { - kind: 'start', - compactionId: event.data.compactionId, - sourceCommandId: event.data.sourceCommandId, - startSeq: event.seq, - turn: event.data.turn, - } - } - if (event.type === 'compaction/summary') { - validateId(event.data.compactionId, 'compaction/summary compactionId', fail) - if (event.data.sourceCommandId !== undefined) { - validateId(event.data.sourceCommandId, 'compaction/summary sourceCommandId', fail) - } - if (open === undefined) fail('compaction/summary has no matching compaction/start') - if (event.data.compactionId !== open.compactionId) { - fail(`compaction/summary id ${event.data.compactionId} does not match compaction/start id ${open.compactionId}`) - } - validateSourceCommandId('compaction/summary', event.data.sourceCommandId, open.sourceCommandId, fail) - validateOwner(open.turn, trace.openTurn, event.type, fail) - if (open.summarized) fail('compaction/summary repeated within one compaction') - validateShadowedSeqs(trace, event, fail) - if (!Number.isSafeInteger(event.data.shadowedTokenCount) || event.data.shadowedTokenCount < 0) { - fail('compaction/summary shadowedTokenCount must be a non-negative safe integer') - } - return { - kind: 'summary', - compactionId: open.compactionId, - sourceCommandId: open.sourceCommandId, - startSeq: open.startSeq, - turn: open.turn, - } - } - validateId(event.data.compactionId, 'compaction/end compactionId', fail) - if (event.data.sourceCommandId !== undefined) { - validateId(event.data.sourceCommandId, 'compaction/end sourceCommandId', fail) - } - if (open === undefined) fail('compaction/end has no matching compaction/start') - if (event.data.compactionId !== open.compactionId) { - fail(`compaction/end id ${event.data.compactionId} does not match compaction/start id ${open.compactionId}`) - } - validateSourceCommandId('compaction/end', event.data.sourceCommandId, open.sourceCommandId, fail) - if (event.data.turn !== open.turn) { - fail(`compaction/end owner ${String(event.data.turn)} does not match compaction/start owner ${String(open.turn)}`) - } - validateOwner(open.turn, trace.openTurn, event.type, fail) - if (event.data.error === undefined && !open.summarized) { - fail('successful compaction/end requires one compaction/summary') - } - return { kind: 'end' } -} - -/** Apply one committed compaction transition. */ -function applyCompactionTransition( - transition: CompactionTransition, -): CompactionTrace | undefined { - if (transition.kind === 'start') { - return { - compactionId: transition.compactionId, - sourceCommandId: transition.sourceCommandId, - startSeq: transition.startSeq, - turn: transition.turn, - summarized: false, - } - } - if (transition.kind === 'summary') { - return { - compactionId: transition.compactionId, - sourceCommandId: transition.sourceCommandId, - startSeq: transition.startSeq, - turn: transition.turn, - summarized: true, - } - } - return undefined -} - -/** Install compaction start/summary/end checks. */ -// Event owners keep precommit staging local so their vocabularies never move into a central helper. -/* jscpd:ignore-start */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const traces = new WeakMap() - const staged = new WeakMap() - const seed = (session: Session): SessionTrace => { - const surfaceEvents: SessionEvent[] = [] - const trace: SessionTrace = { - openTurn: null, - compaction: undefined, - surfaceEvents, - surface: new SurfaceManager(surfaceEvents, undefined, ctx.sessions.messageProjections), - } - traces.set(session, trace) - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - const events = session.snapshotEvents() - const staleOrphanStartSeqs = inheritedOrphanStartSeqs(events) - for (const event of events) { - // Constructor-seed repair boundaries can precede the end-seed marker - // that proves an inherited orphan stale. Replay that inherited prefix - // without letting the soon-to-be-cleared bracket veto its repair. - if ( - trace.compaction === undefined - || !staleOrphanStartSeqs.has(trace.compaction.startSeq) - ) { - validateTurnBoundary(trace, event, fail) - } - const transition = validateCompactionEvent(trace, event, fail) - if (transition !== undefined) trace.compaction = applyCompactionTransition(transition) - applyTurnBoundary(trace, event) - trace.surfaceEvents.push(event) - } - return trace - } - const traceFor = (session: Session): SessionTrace => traces.get(session) ?? seed(session) - - for (const session of ctx.sessions.list()) seed(session) - ctx.on('session/created', (session) => { seed(session) }, { global: true }) - ctx.on('session/event', (session, event) => { - const trace = traceFor(session) - validateTurnBoundary(trace, event, fail) - const changedTurn = applyTurnBoundary(trace, event) - if (!changedTurn && event.type !== 'session/end-seed' - && event.type !== 'compaction/start' - && event.type !== 'compaction/summary' - && event.type !== 'compaction/end') { - trace.surfaceEvents.push(event) - return - } - if (!changedTurn) { - const candidate = staged.get(event) - /* v8 ignore next -- internal/dispatch stages every compaction event */ - if (candidate === undefined || candidate.session !== session) return fail('compaction event published without pre-commit validation') - staged.delete(event) - trace.compaction = applyCompactionTransition(candidate.transition) - } - trace.surfaceEvents.push(event) - }, { global: true }) - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - const trace = traceFor(session) - validateTurnBoundary(trace, event, fail) - const transition = validateCompactionEvent(trace, event, fail) - if (transition !== undefined) staged.set(event, { session, transition }) - }, { global: true }) -}, { inject: ['sessions'] }) -/* jscpd:ignore-end */ - -/** - * Register the compact 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)) diff --git a/packages/compaction/compaction/tests/invariant.spec.ts b/packages/compaction/compaction/tests/invariant.spec.ts deleted file mode 100644 index 20269e558e..0000000000 --- a/packages/compaction/compaction/tests/invariant.spec.ts +++ /dev/null @@ -1,469 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import SessionStore, { Session, SessionId, SessionSeq } from '@deepseek-ai/dsh-session' -import { createUserMessage } from '@deepseek-ai/dsh-llm' -import { CompactionId, compactCheckpointSource } from '@deepseek-ai/dsh-compaction' -import * as CompactionInvariant from '@deepseek-ai/dsh-compaction/invariant' -import { CommandId } from '@deepseek-ai/dsh-commands/brand' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(CompactionInvariant) - return ctx -} - -const TEST_COMPACTION_ID = CompactionId('test-compaction') -const NEXT_COMPACTION_ID = CompactionId('next-test-compaction') -const TEST_COMMAND_ID = CommandId('test-command') -const NEXT_COMMAND_ID = CommandId('next-test-command') - -const summary = (session: Session, overrides: Record = {}) => { - const appendMessage = (text: string) => session.append( - 'user/message', - createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'user' } }), - { surfaceOp: 'append' }, - ) - const first = appendMessage('first') - const second = appendMessage('second') - const third = appendMessage('third') - const shadowedSeqs: [SessionSeq, SessionSeq, SessionSeq] = [first.seq, second.seq, third.seq] - return { - compactionId: TEST_COMPACTION_ID, - summary: [{ type: 'text' as const, text: 'short' }], - shadowedRange: { start: first.seq, end: third.seq }, - shadowedSeqs, - shadowedTokenCount: 12, - provider: 'mock', - model: 'mock', - ...overrides, - } -} - -function startTurn(session: ReturnType, turn = 1): void { - session.append('turn/start', { turn }) -} - -describe('compaction invariants', () => { - it('accepts successful and failed compaction lifecycles', async () => { - const ctx = await setup() - const success = ctx.sessions.create() - startTurn(success) - success.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - const summaryData = summary(success) - success.append('compaction/summary', summaryData) - success.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'checkpoint' }], - source: compactCheckpointSource(TEST_COMPACTION_ID), - }), { - surfaceOp: { - op: 'replace', - startSeq: summaryData.shadowedRange.start, - endSeq: summaryData.shadowedRange.end, - }, - sourceEventSeqs: summaryData.shadowedSeqs, - }) - success.append('compaction/end', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - - const failed = ctx.sessions.create() - startTurn(failed, 2) - failed.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 2 }) - failed.append('compaction/end', { compactionId: TEST_COMPACTION_ID, turn: 2, error: 'provider failed' }) - }) - - it('rejects shadow identities outside the exact earlier surface span', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - const turn = session.append('turn/start', { turn: 1 }) - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - const valid = summary(session) - const current = SessionSeq(session.seq) - expect(() => session.append('compaction/summary', { - ...valid, - shadowedRange: { start: current, end: current }, - shadowedSeqs: [current], - })).toThrow(/must name an earlier current surface span/) - expect(() => session.append('compaction/summary', { - ...valid, - shadowedRange: { start: turn.seq, end: turn.seq }, - shadowedSeqs: [turn.seq], - })).toThrow(/must name an earlier current surface span/) - expect(() => session.append('compaction/summary', { - ...valid, - shadowedSeqs: [valid.shadowedSeqs[0], valid.shadowedSeqs[2]], - })).toThrow(/must list every node in the current surface span/) - expect(() => session.append('compaction/summary', { - ...valid, - shadowedRange: { start: valid.shadowedSeqs[2], end: valid.shadowedSeqs[0] }, - shadowedSeqs: [valid.shadowedSeqs[2], valid.shadowedSeqs[0]], - })).toThrow(/must name an earlier current surface span/) - expect(() => session.append('compaction/summary', { - ...valid, - shadowedSeqs: [valid.shadowedSeqs[0], valid.shadowedSeqs[2], valid.shadowedSeqs[2]], - })).toThrow(/must list every node in the current surface span/) - }) - - it('validates a model-free prune against the current surface', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - const shadow = summary(session) - expect(() => session.append('compaction/prune', { - shadowedRange: shadow.shadowedRange, - shadowedSeqs: shadow.shadowedSeqs, - shadowedTokenCount: shadow.shadowedTokenCount, - })).not.toThrow() - }) - - it('accepts standalone successful and failed compaction lifecycles between turns', async () => { - const ctx = await setup() - const success = ctx.sessions.create() - success.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: null }) - success.append('compaction/summary', summary(success)) - success.append('compaction/end', { compactionId: TEST_COMPACTION_ID, turn: null }) - - const failed = ctx.sessions.create() - failed.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: null }) - failed.append('compaction/end', { compactionId: TEST_COMPACTION_ID, turn: null, error: 'provider failed' }) - }) - - it('clears an inherited open compaction trace at end-seed during replay', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const source = Session.create(SessionId('stale-compaction-source')) - source.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: null }) - const replayed = ctx.sessions.create(SessionId('stale-compaction-replay'), { - seed: source.snapshotEvents(), - }) - expect(replayed.snapshotEvents().map(event => event.type)) - .toEqual(['compaction/start', 'session/end-seed']) - - await ctx.plugin(InvariantRegistry) - await ctx.plugin(CompactionInvariant) - - expect(() => { - replayed.append('compaction/start', { compactionId: NEXT_COMPACTION_ID, turn: null }) - replayed.append('compaction/end', { compactionId: NEXT_COMPACTION_ID, turn: null, error: 'new attempt failed' }) - }).not.toThrow() - }) - - it('allows repair turn boundaries after end-seed clears a seeded numbered orphan', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const source = Session.create(SessionId('stale-numbered-compaction-source')) - startTurn(source) - source.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - const replayed = ctx.sessions.create(SessionId('stale-numbered-compaction-replay'), { - seed: source.snapshotEvents(), - }) - expect(replayed.snapshotEvents().map(event => event.type)) - .toEqual(['turn/start', 'compaction/start', 'session/end-seed']) - - await ctx.plugin(InvariantRegistry) - await ctx.plugin(CompactionInvariant) - - expect(() => replayed.append( - 'turn/end', - { turn: 1, reason: { kind: 'interrupted' } }, - )).not.toThrow() - }) - - it('accepts inherited repair boundaries before the end-seed that clears a standalone orphan', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const source = Session.create(SessionId('stale-repaired-compaction-source')) - source.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: null }) - startTurn(source) - source.append('turn/end', { turn: 1, reason: { kind: 'interrupted' } }) - const replayed = ctx.sessions.create(SessionId('stale-repaired-compaction-replay'), { - seed: source.snapshotEvents(), - }) - expect(replayed.snapshotEvents().map(event => event.type)).toEqual([ - 'compaction/start', - 'turn/start', - 'turn/end', - 'session/end-seed', - ]) - - await ctx.plugin(InvariantRegistry) - await expect(ctx.plugin(CompactionInvariant).then(() => undefined)).resolves.toBeUndefined() - - expect(() => { - startTurn(replayed, 2) - replayed.append('turn/end', { turn: 2, reason: { kind: 'completed' } }) - }).not.toThrow() - }) - - it('rejects a closed standalone bracket that contains a turn before end-seed', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const source = Session.create(SessionId('closed-nested-compaction-source')) - source.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: null }) - startTurn(source) - source.append('turn/end', { turn: 1, reason: { kind: 'interrupted' } }) - source.append('compaction/end', { compactionId: TEST_COMPACTION_ID, turn: null, error: 'failed after crossing turn' }) - const replayed = ctx.sessions.create(SessionId('closed-nested-compaction-replay'), { - seed: source.snapshotEvents(), - }) - expect(replayed.snapshotEvents().at(-1)?.type).toBe('session/end-seed') - - await ctx.plugin(InvariantRegistry) - await expect(ctx.plugin(CompactionInvariant).then(() => undefined)) - .rejects.toThrow(/turn\/start cannot cross an open standalone compaction/) - }) - - it('rebuilds an open trace when the companion loads after the session', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(CompactionInvariant) - expect(() => session.append('compaction/end', { - compactionId: TEST_COMPACTION_ID, - turn: 1, - error: 'resume failed', - })).not.toThrow() - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - }) - - it('adopts a bare session and ignores unrelated committed events', async () => { - const ctx = await setup() - const session = Session.create(SessionId('bare-compaction-session')) - expect(() => { - ctx.emit('session/event', session, { - type: 'turn/start', seq: SessionSeq(0), time: 0, - data: { turn: 1 }, - }) - ctx.emit('session/event', session, { - type: 'step/start', seq: SessionSeq(1), time: 1, data: { turn: 1, step: 1 }, - }) - ctx.emit('session/event', session, { - type: 'compaction/start', seq: SessionSeq(2), time: 2, - data: { compactionId: TEST_COMPACTION_ID, turn: 1 }, - }) - }).not.toThrow() - }) - - it('rejects compaction outside or for a different open turn', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - expect(() => session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 })) - .toThrow(/outside any open turn/) - startTurn(session) - expect(() => session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 2 })) - .toThrow(/but open turn is 1/) - }) - - it('rejects a standalone bracket while a turn is open and a numbered bracket between turns', async () => { - const ctx = await setup() - const open = ctx.sessions.create() - startTurn(open) - expect(() => open.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: null })) - .toThrow(/standalone but turn 1 is open/) - - const idle = ctx.sessions.create() - expect(() => idle.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 })) - .toThrow(/outside any open turn/) - }) - - it('attributes a nested standalone start to the standalone owner', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: null }) - expect(() => session.append('compaction/start', { compactionId: NEXT_COMPACTION_ID, turn: null })) - .toThrow(/standalone compaction is still compacting/) - }) - - it('rejects an unenclosed compaction event when replaying an existing session', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create() - startTurn(session) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - await ctx.plugin(InvariantRegistry) - await expect(ctx.plugin(CompactionInvariant).then(() => undefined)).rejects.toThrow(/outside any open turn/) - }) - - it('rejects turn boundaries that cross live standalone or numbered compaction brackets', async () => { - const ctx = await setup() - const standalone = ctx.sessions.create() - standalone.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: null }) - expect(() => { startTurn(standalone) }) - .toThrow(/turn\/start cannot cross an open standalone compaction/) - standalone.append('compaction/end', { compactionId: TEST_COMPACTION_ID, turn: null, error: 'cancelled' }) - expect(() => { - startTurn(standalone) - standalone.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - }).not.toThrow() - - const numbered = ctx.sessions.create() - startTurn(numbered) - numbered.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - expect(() => numbered.append( - 'turn/end', - { turn: 1, reason: { kind: 'completed' } }, - )).toThrow(/turn\/end cannot cross an open compaction for turn 1/) - numbered.append('compaction/end', { compactionId: TEST_COMPACTION_ID, turn: 1, error: 'cancelled' }) - expect(() => numbered.append( - 'turn/end', - { turn: 1, reason: { kind: 'completed' } }, - )).not.toThrow() - }) - - it('rejects a replacement checkpoint for another compaction transaction', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - const original = session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'original' }], - source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - startTurn(session) - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - session.append('compaction/summary', summary(session)) - - expect(() => session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'checkpoint' }], - source: compactCheckpointSource(NEXT_COMPACTION_ID), - }), { - surfaceOp: { op: 'replace', startSeq: original.seq, endSeq: original.seq }, - sourceEventSeqs: [original.seq], - })).toThrow(/compaction checkpoint id .* does not match compaction\/start id/) - }) - - it('requires a checkpoint source to name an open transaction', async () => { - const ctx = await setup() - const withoutStart = ctx.sessions.create() - const original = withoutStart.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'original' }], - source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - expect(() => withoutStart.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'checkpoint' }], - source: compactCheckpointSource(TEST_COMPACTION_ID), - }), { - surfaceOp: { op: 'replace', startSeq: original.seq, endSeq: original.seq }, - sourceEventSeqs: [original.seq], - })).toThrow(/no matching compaction\/start/) - - const emptyCommand = ctx.sessions.create() - const replaced = emptyCommand.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'original' }], - source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - startTurn(emptyCommand) - emptyCommand.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - expect(() => emptyCommand.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'checkpoint' }], - source: compactCheckpointSource(TEST_COMPACTION_ID, CommandId('')), - }), { - surfaceOp: { op: 'replace', startSeq: replaced.seq, endSeq: replaced.seq }, - sourceEventSeqs: [replaced.seq], - })).toThrow(/checkpoint sourceCommandId must be a non-empty string/) - }) - - it.each([ - ['empty start id', (session: ReturnType) => { - session.append('compaction/start', { compactionId: CompactionId(''), turn: 1 }) - }, /compaction\/start compactionId must be a non-empty string/], - ['empty start source command id', (session: ReturnType) => { - session.append('compaction/start', { - compactionId: TEST_COMPACTION_ID, - sourceCommandId: CommandId(''), - turn: 1, - }) - }, /compaction\/start sourceCommandId must be a non-empty string/], - ['summary without start', (session: ReturnType) => { - session.append('compaction/summary', summary(session)) - }, /no matching compaction\/start/], - ['nested start', (session: ReturnType) => { - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - session.append('compaction/start', { compactionId: NEXT_COMPACTION_ID, turn: 2 }) - }, /still compacting/], - ['repeated summary', (session: ReturnType) => { - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - session.append('compaction/summary', summary(session)) - session.append('compaction/summary', summary(session)) - }, /repeated within one compaction/], - ['summary for another compaction', (session: ReturnType) => { - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - session.append('compaction/summary', summary(session, { compactionId: NEXT_COMPACTION_ID })) - }, /compaction\/summary id .* does not match compaction\/start id/], - ['summary for another source command', (session: ReturnType) => { - session.append('compaction/start', { - compactionId: TEST_COMPACTION_ID, - sourceCommandId: TEST_COMMAND_ID, - turn: 1, - }) - session.append('compaction/summary', summary(session, { sourceCommandId: NEXT_COMMAND_ID })) - }, /compaction\/summary sourceCommandId .* does not match compaction\/start sourceCommandId/], - ['empty shadow set', (session: ReturnType) => { - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - session.append('compaction/summary', summary(session, { shadowedSeqs: [] })) - }, /shadowedSeqs must be non-empty/], - ['wrong endpoints', (session: ReturnType) => { - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - session.append('compaction/summary', summary(session, { shadowedRange: { start: 1, end: 4 } })) - }, /shadowedRange must match/], - ['non-numeric shadow seq', (session: ReturnType) => { - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - session.append('compaction/summary', summary(session, { - shadowedRange: { start: 'bad', end: 'bad' }, shadowedSeqs: ['bad'], - })) - }, /non-negative safe integer event seq/], - ['negative shadow seq', (session: ReturnType) => { - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - session.append('compaction/summary', summary(session, { - shadowedRange: { start: -1, end: -1 }, shadowedSeqs: [-1], - })) - }, /non-negative safe integer event seq/], - ['invalid token count', (session: ReturnType) => { - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - session.append('compaction/summary', summary(session, { shadowedTokenCount: -1 })) - }, /non-negative safe integer/], - ['end without start', (session: ReturnType) => { - session.append('compaction/end', { compactionId: TEST_COMPACTION_ID, turn: 1, error: 'failed' }) - }, /no matching compaction\/start/], - ['wrong end turn', (session: ReturnType) => { - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - session.append('compaction/end', { compactionId: TEST_COMPACTION_ID, turn: 2, error: 'failed' }) - }, /does not match/], - ['end for another compaction', (session: ReturnType) => { - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - session.append('compaction/end', { compactionId: NEXT_COMPACTION_ID, turn: 1, error: 'failed' }) - }, /compaction\/end id .* does not match compaction\/start id/], - ['end missing the source command', (session: ReturnType) => { - session.append('compaction/start', { - compactionId: TEST_COMPACTION_ID, - sourceCommandId: TEST_COMMAND_ID, - turn: 1, - }) - session.append('compaction/end', { compactionId: TEST_COMPACTION_ID, turn: 1, error: 'failed' }) - }, /compaction\/end sourceCommandId .* does not match compaction\/start sourceCommandId/], - ['empty end source command id', (session: ReturnType) => { - session.append('compaction/start', { - compactionId: TEST_COMPACTION_ID, - sourceCommandId: TEST_COMMAND_ID, - turn: 1, - }) - session.append('compaction/end', { - compactionId: TEST_COMPACTION_ID, - sourceCommandId: CommandId(''), - turn: 1, - error: 'failed', - }) - }, /compaction\/end sourceCommandId must be a non-empty string/], - ['success without summary', (session: ReturnType) => { - session.append('compaction/start', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - session.append('compaction/end', { compactionId: TEST_COMPACTION_ID, turn: 1 }) - }, /requires one compaction\/summary/], - ])('rejects %s', async (_name, action, message) => { - const ctx = await setup() - const session = ctx.sessions.create() - startTurn(session) - expect(() => { action(session) }).toThrow(message) - }) -}) diff --git a/packages/compaction/compaction/tsconfig.json b/packages/compaction/compaction/tsconfig.json index d14b4afcc2..ac7f17c795 100644 --- a/packages/compaction/compaction/tsconfig.json +++ b/packages/compaction/compaction/tsconfig.json @@ -25,9 +25,6 @@ }, { "path": "../../core/session" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/compaction/compaction/tsdown.config.ts b/packages/compaction/compaction/tsdown.config.ts index 6284421125..6cee0a027b 100644 --- a/packages/compaction/compaction/tsdown.config.ts +++ b/packages/compaction/compaction/tsdown.config.ts @@ -6,8 +6,4 @@ export default defineConfig([ entry: ['lib/types/index.js'], outDir: 'lib', format: ['esm'], platform: 'node', target: 'es2024', fixedExtension: false, outputOptions: { codeSplitting: false }, dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], outDir: 'lib', format: ['esm'], platform: 'node', target: 'es2024', - fixedExtension: false, outputOptions: { codeSplitting: false }, dts: false, clean: false, - }, ]) diff --git a/packages/computer-use/computer-use/README.i18n.yaml b/packages/computer-use/computer-use/README.i18n.yaml index dd26510975..f35f34159f 100644 --- a/packages/computer-use/computer-use/README.i18n.yaml +++ b/packages/computer-use/computer-use/README.i18n.yaml @@ -18,8 +18,8 @@ en: 0a298fbbcd8dbc00 zh: 950de2af46c87f82 /deepseek-ai-dsh-computer-use/understand-the-implementation: - en: c8a8d72c6944586c - zh: 0b216f8e9599543d + en: 181679887837591a + zh: cc2e96766ce0aa63 /deepseek-ai-dsh-computer-use/further-exploration: en: b1bdbcbc150036b3 zh: 1d356e0522587abc diff --git a/packages/computer-use/computer-use/README.md b/packages/computer-use/computer-use/README.md index cd282a1f74..32fccb6d6f 100644 --- a/packages/computer-use/computer-use/README.md +++ b/packages/computer-use/computer-use/README.md @@ -45,8 +45,6 @@ Providers stop admitting tool calls, close their resources, and await owned work One private name owns the slot. Cordis effects remove contributions when their plugin unloads; a repeated disposer cannot remove a later registration. The [source](src/index.ts) contains no driver object, operation interface, or provider selector. -No runtime invariant companion is published: the registry has one authoritative field and exposes no independently maintained observation that can diverge. Duplicate rejection and plugin disposal are covered by the owning tests. - ----- diff --git a/packages/computer-use/computer-use/README.zh.md b/packages/computer-use/computer-use/README.zh.md index 872431d3a7..1b3dfb4e77 100644 --- a/packages/computer-use/computer-use/README.zh.md +++ b/packages/computer-use/computer-use/README.zh.md @@ -45,8 +45,6 @@ kind: "package-reference" 一个私有名称占用注册位置。Cordis effect 在插件卸载时移除贡献;重复调用清理函数不会移除后续注册。[源码](src/index.ts)不包含驱动对象、操作接口或提供方选择器。 -不发布 `./invariant` 伴随入口:注册表只有一个权威字段,没有可能与之分歧的独立维护观测值。所属测试覆盖重复注册拒绝和插件卸载。 - ----- diff --git a/packages/context/agent-instructions/README.i18n.yaml b/packages/context/agent-instructions/README.i18n.yaml index 572468170f..41cb730c35 100644 --- a/packages/context/agent-instructions/README.i18n.yaml +++ b/packages/context/agent-instructions/README.i18n.yaml @@ -33,8 +33,8 @@ en: 4708b51bf8fe86f7 zh: 869fc4b1f78383ee /deepseek-ai-dsh-agent-instructions/understand-the-implementation/source-map: - en: 737fdf08415bc08d - zh: 625d53b1418ab6bb + en: bbb499f8498f454c + zh: b6517adffd0ccb80 /deepseek-ai-dsh-agent-instructions/understand-the-implementation/main-flow: en: d881b9055f823929 zh: 87b25f0bf8e204e9 diff --git a/packages/context/agent-instructions/README.md b/packages/context/agent-instructions/README.md index 34feeb2ac5..4ada77b715 100644 --- a/packages/context/agent-instructions/README.md +++ b/packages/context/agent-instructions/README.md @@ -95,7 +95,6 @@ The plugin is built on one principle: workspace instructions are durable convers | [`src/render.ts`](src/render.ts) | Instruction rendering, budget truncation, change records | | [`src/state.ts`](src/state.ts) | Durable message sources, version/digest cache, reconciliation | | [`src/digest.ts`](src/digest.ts) | SHA-1 content identity and per-directory duplicate keys | -| — | No runtime invariant companion is published; replay intentionally tolerates unknown or malformed workspace sources, while focused pipeline tests own its private pending/cache state transitions. | ### Main flow diff --git a/packages/context/agent-instructions/README.zh.md b/packages/context/agent-instructions/README.zh.md index 311d6f636f..471c384f88 100644 --- a/packages/context/agent-instructions/README.zh.md +++ b/packages/context/agent-instructions/README.zh.md @@ -95,7 +95,6 @@ export interface Config { | [`src/render.ts`](src/render.ts) | 指令渲染、预算截断、变更记录 | | [`src/state.ts`](src/state.ts) | 持久消息来源、版本/digest 缓存、对账 | | [`src/digest.ts`](src/digest.ts) | SHA-1 内容标识与每目录重复键 | -| — | 不发布运行时不变式伴生入口;回放会容忍未知或格式错误的 workspace source,私有 pending/cache 状态转换由针对性流水线测试覆盖。 | ### 主要流程 diff --git a/packages/context/file-reference-local/README.i18n.yaml b/packages/context/file-reference-local/README.i18n.yaml index 289187f745..4480cedf1f 100644 --- a/packages/context/file-reference-local/README.i18n.yaml +++ b/packages/context/file-reference-local/README.i18n.yaml @@ -33,8 +33,8 @@ en: f0023b9c833747b5 zh: 42aef0c0fce9ce8c /deepseek-ai-dsh-file-reference-local/understand-the-implementation/source-map: - en: a04bc6a11619cae7 - zh: 4fb1a0d04398260b + en: 50b4cb9acee6e4a7 + zh: a1455ea70c837ebc /deepseek-ai-dsh-file-reference-local/understand-the-implementation/main-flow: en: bb338cacc1ef8c01 zh: d7ad96a5646f5444 diff --git a/packages/context/file-reference-local/README.md b/packages/context/file-reference-local/README.md index b80f5863e2..7bf94e5e6f 100644 --- a/packages/context/file-reference-local/README.md +++ b/packages/context/file-reference-local/README.md @@ -71,7 +71,6 @@ The provider maintains one reusable `WorkspaceFileSearch` per agent, rooted at t |---|---| | [`src/index.ts`](src/index.ts) | `LocalFileReferenceService`: config validation, per-agent searches, prompt install | | [`src/search.ts`](src/search.ts) | `WorkspaceFileSearch`: traversal, ranking, exclusion, staleness and background rebuild | -| — | No runtime invariant companion is published; per-agent indexes are private advisory caches whose invalidation and disposal are observed directly through service tests. | ### Main flow diff --git a/packages/context/file-reference-local/README.zh.md b/packages/context/file-reference-local/README.zh.md index 343d095098..84d07363b4 100644 --- a/packages/context/file-reference-local/README.zh.md +++ b/packages/context/file-reference-local/README.zh.md @@ -71,7 +71,6 @@ agent(智能体)及宿主 UI 可以用各 agent 本地工作区中经过排 |---|---| | [`src/index.ts`](src/index.ts) | `LocalFileReferenceService`:配置校验、按 agent 搜索、提示词安装 | | [`src/search.ts`](src/search.ts) | `WorkspaceFileSearch`:遍历、排序、排除、陈旧标记与后台重建 | -| — | 不发布运行时不变式伴生入口;按 agent 的 index 是私有 advisory cache,其失效与 dispose 行为通过服务测试直接观察。 | ### 主要流程 diff --git a/packages/context/file-reference/README.i18n.yaml b/packages/context/file-reference/README.i18n.yaml index 3efb30e977..9a8cf38301 100644 --- a/packages/context/file-reference/README.i18n.yaml +++ b/packages/context/file-reference/README.i18n.yaml @@ -33,8 +33,8 @@ en: 191332951f81b136 zh: 8336659a9ffa20f1 /deepseek-ai-dsh-file-reference/understand-the-implementation/source-map: - en: c4abf9f6f86cf29f - zh: 60d15ef1acd20b93 + en: 7ca26c752fab5c46 + zh: 961bc4283792155d /deepseek-ai-dsh-file-reference/understand-the-implementation/main-flow: en: fd65f309c940de96 zh: c1517e592d646d6c diff --git a/packages/context/file-reference/README.md b/packages/context/file-reference/README.md index 3b759a88bd..59ad5ba784 100644 --- a/packages/context/file-reference/README.md +++ b/packages/context/file-reference/README.md @@ -60,7 +60,6 @@ The package separates an abstract discovery service from a shared, browser-safe | [`src/index.ts`](src/index.ts) | Abstract `FileReferenceService` and `FILE_REFERENCE_PROMPT` | | [`src/grammar.ts`](src/grammar.ts) | `activeAtToken` recognition and `formatFileMention` rendering | | [`src/types.ts`](src/types.ts) | `FileReferenceCandidate` path-only result type | -| — | No runtime invariant companion is published; the interface retains no candidate or lifecycle state; concrete providers own their cache and invalidation relationships. | ### Main flow diff --git a/packages/context/file-reference/README.zh.md b/packages/context/file-reference/README.zh.md index d7dedc4636..9a0d3a95c8 100644 --- a/packages/context/file-reference/README.zh.md +++ b/packages/context/file-reference/README.zh.md @@ -60,7 +60,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 抽象 `FileReferenceService` 与 `FILE_REFERENCE_PROMPT` | | [`src/grammar.ts`](src/grammar.ts) | `activeAtToken` 识别与 `formatFileMention` 渲染 | | [`src/types.ts`](src/types.ts) | 仅含路径的结果类型 `FileReferenceCandidate` | -| — | 不发布运行时不变式伴生入口;接口不保留 candidate 或 lifecycle 状态;具体提供方负责自己的 cache 与 invalidation 关系。 | ### 主要流程 diff --git a/packages/context/session-reference/README.i18n.yaml b/packages/context/session-reference/README.i18n.yaml index 0c2a188ea7..80874b9f2e 100644 --- a/packages/context/session-reference/README.i18n.yaml +++ b/packages/context/session-reference/README.i18n.yaml @@ -36,8 +36,8 @@ en: 5c7712ca91eaae1c zh: 2d923b605afa7a88 /deepseek-ai-dsh-session-reference/understand-the-implementation/source-map: - en: 3a91f258bdfbe488 - zh: 80301b5d6b218389 + en: 7c7b413082507150 + zh: 0229eb73d441b305 /deepseek-ai-dsh-session-reference/understand-the-implementation/main-flow: en: 019e5e19f9b3d7fd zh: 52ccf291f53d41fc diff --git a/packages/context/session-reference/README.md b/packages/context/session-reference/README.md index e1afeef9b0..7e0cd46485 100644 --- a/packages/context/session-reference/README.md +++ b/packages/context/session-reference/README.md @@ -83,7 +83,6 @@ The budget uses the provider and model captured after `system-prompt/assemble` c | [`src/serialization.ts`](src/serialization.ts) | Tag-safe JSON escaping for snapshot payloads | | [`src/spill.ts`](src/spill.ts) | Full transcript serialization and model-visible omission notices | | [`src/types.ts`](src/types.ts) | `SessionReferenceInput`/`Candidate` and source types | -| — | No runtime invariant companion is published; preparation returns immutable per-call snapshots validated while they are built, and the agent/session layers own durable context admission, freezing, and replay. | ### Main flow diff --git a/packages/context/session-reference/README.zh.md b/packages/context/session-reference/README.zh.md index e55dab4f64..ae898de038 100644 --- a/packages/context/session-reference/README.zh.md +++ b/packages/context/session-reference/README.zh.md @@ -83,7 +83,6 @@ kind: "package-reference" | [`src/serialization.ts`](src/serialization.ts) | 快照载荷的标签安全 JSON 转义 | | [`src/spill.ts`](src/spill.ts) | 完整 transcript 序列化与模型可见省略通知 | | [`src/types.ts`](src/types.ts) | `SessionReferenceInput`/`Candidate` 与来源类型 | -| — | 不发布运行时不变式伴生入口;准备过程返回构建时已校验的不可变单次快照;持久上下文的准入、冻结与回放由 agent 层和会话层负责。 | ### 主要流程 diff --git a/packages/context/time-context/README.i18n.yaml b/packages/context/time-context/README.i18n.yaml index beec147239..64c8c14f86 100644 --- a/packages/context/time-context/README.i18n.yaml +++ b/packages/context/time-context/README.i18n.yaml @@ -30,11 +30,11 @@ en: 2d21fa2aad5a729d zh: cfbfada0eb647435 /deepseek-ai-dsh-time-context/understand-the-implementation/design-concept: - en: 8887d5d7453ef8cc - zh: a80b54028f75fbc5 + en: 48381ad4596a177a + zh: be025dba74956fc8 /deepseek-ai-dsh-time-context/understand-the-implementation/source-map: - en: 05a0a4627013d3d2 - zh: 281429e6ef7f26ec + en: 269d7aa1f64c4278 + zh: 086018a60cc3a351 /deepseek-ai-dsh-time-context/understand-the-implementation/main-flow: en: ae9f13873dde5271 zh: 873f32e67f8b202b diff --git a/packages/context/time-context/README.md b/packages/context/time-context/README.md index 0e512884a3..4d5f6785ee 100644 --- a/packages/context/time-context/README.md +++ b/packages/context/time-context/README.md @@ -64,7 +64,7 @@ This section explains the design of the plugin; the observable behavior is cover ### Design concept -The plugin prepends an `agent/pre-step` listener that delegates first and appends one sourced `UserMessage` when an injection is due and the downstream decision enters the step. Each reading uses the exact snapshot source `{ kind: 'time-context', form: 'snapshot', sections: [{ name: 'time-context', text }] }`, and the invariant companion validates that shape, re-derives the current-turn browser policy from the original `user-rpc` messages, and checks the timestamp zone and elapsed baseline. +The plugin prepends an `agent/pre-step` listener that delegates first and appends one sourced `UserMessage` when an injection is due and the downstream decision enters the step. Each reading uses the exact snapshot source `{ kind: 'time-context', form: 'snapshot', sections: [{ name: 'time-context', text }] }`. ### Source map @@ -73,7 +73,6 @@ The plugin prepends an `agent/pre-step` listener that delegates first and append | [`src/index.ts`](src/index.ts) | Plugin entry: pre-step listener, due scheduling, reading composition | | [`src/request-zone.ts`](src/request-zone.ts) | Browser-zone policy derivation from open-turn `user-rpc` sources | | [`src/timestamp.ts`](src/timestamp.ts) | `Intl.DateTimeFormat` creation and timestamp formatting | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion for the snapshot contract | ### Main flow diff --git a/packages/context/time-context/README.zh.md b/packages/context/time-context/README.zh.md index 2f29103235..47dd54048a 100644 --- a/packages/context/time-context/README.zh.md +++ b/packages/context/time-context/README.zh.md @@ -64,7 +64,7 @@ kind: "package-reference" ### 设计理念 -插件前置注册一个 `agent/pre-step` 监听器,先委托下游,需要注入且下游决策进入步骤时追加一条带来源的 `UserMessage`。每个读数都使用确切的快照来源 `{ kind: 'time-context', form: 'snapshot', sections: [{ name: 'time-context', text }] }`,不变式配套模块会校验该形状,根据原始 `user-rpc` 消息重新派生当前轮次的浏览器策略,并检查时间戳时区与经过时长基线。 +插件前置注册一个 `agent/pre-step` 监听器,先委托下游,需要注入且下游决策进入步骤时追加一条带来源的 `UserMessage`。每个读数都使用确切的快照来源 `{ kind: 'time-context', form: 'snapshot', sections: [{ name: 'time-context', text }] }`。 ### 源码地图 @@ -73,7 +73,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:pre-step 监听器、到期调度、读数组合 | | [`src/request-zone.ts`](src/request-zone.ts) | 从开放轮次 `user-rpc` 来源派生浏览器时区策略 | | [`src/timestamp.ts`](src/timestamp.ts) | `Intl.DateTimeFormat` 创建与时间戳格式化 | -| [`src/invariant.ts`](src/invariant.ts) | 快照约定的不变式配套模块 | ### 主要流程 diff --git a/packages/context/time-context/package.json b/packages/context/time-context/package.json index fb9e0546e3..a023a5181a 100644 --- a/packages/context/time-context/package.json +++ b/packages/context/time-context/package.json @@ -18,10 +18,6 @@ "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", "./locale/*.json": "./locale/*.json" @@ -29,7 +25,6 @@ "files": [ "locale/*.json", "lib/index.js", - "lib/invariant.js", "lib/types/**/*.d.ts" ], "license": "MIT", @@ -41,7 +36,6 @@ "peerDependencies": { "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-projection": "workspace:*" @@ -53,7 +47,6 @@ "@deepseek-ai/dsh-agent-loop-testkit": "workspace:*", "@deepseek-ai/dsh-app-boot": "workspace:*", "@deepseek-ai/dsh-bash-local": "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 deleted file mode 100644 index e9f362c7e4..0000000000 --- a/packages/context/time-context/src/invariant.ts +++ /dev/null @@ -1,194 +0,0 @@ -/** Package-owned durable clock-context invariants. @module @deepseek-ai/dsh-time-context/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import { - deriveBrowserTimeZoneContext, - renderBrowserTimeZoneContext, -} from './request-zone.ts' -import { createTimestampFormatter, formatTimestamp } from './timestamp.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-time-context' -const SOURCE_NAME = 'time-context' -const READING = new RegExp( - '^Time sampled while preparing turn (\\d+), step (\\d+): ' - + '(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:Z|[+-]\\d{2}:\\d{2})\\[[^\\]]+\\])\\n' - + '(Browser time zone for this request: .+)\\n' - + 'Elapsed since the preceding (model-visible message|step context): ' - + '(?:unavailable|(?:(?:\\d+d )?(?:\\d+h )?(?:\\d+m )?\\d+s))\\.$', -) - -/** Cordis companion plugin name. */ -export const name = 'time-context-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Derive the open step boundary at which a time-context reading may append. */ -function preparationPosition(history: readonly SessionEvent[], fail: InvariantFailure): { turn: number; step: number } { - let openTurn: number | undefined - let openStep: number | undefined - let requestStarted = false - for (const event of history) { - switch (event.type) { - case 'turn/start': { - openTurn = event.data.turn - openStep = undefined - requestStarted = false - break - } - case 'step/start': { - openStep = event.data.step - requestStarted = false - break - } - case 'request/header': { - requestStarted = true - break - } - case 'step/end': { - openStep = undefined - requestStarted = false - break - } - case 'turn/end': { - openTurn = undefined - openStep = undefined - requestStarted = false - break - } - default: - break - } - } - if (openTurn === undefined) fail('time-context reading must be appended inside an open turn') - if (openStep === undefined) fail('time-context reading must follow step/start') - if (requestStarted) fail('time-context reading must precede request/header') - return { turn: openTurn, step: openStep } -} - -/** Collect the entered user messages belonging to one open turn. */ -function requestMessages(history: readonly SessionEvent[], turn: number) { - const start = history.findLastIndex(event => event.type === 'turn/start' && event.data.turn === turn) - return history.slice(start + 1) - .flatMap(event => event.type === 'user/message' ? [event.data] : []) -} - -/** Validate one plugin-attributed time reading against its session position and timestamp. */ -function validateReading( - history: readonly SessionEvent[], - event: SessionEvent<'user/message'>, - fail: InvariantFailure, -): void { - const blockValue: unknown = event.data.content[0] - const block = typeof blockValue === 'object' && blockValue !== null - ? blockValue as Record - : undefined - const blockText = block?.text - if (event.data.content.length !== 1 - || block === undefined - || Object.keys(block).length !== 2 - || block.type !== 'text' - || typeof blockText !== 'string') { - fail('time-context messages must contain exactly one text block') - } - const match = READING.exec(blockText) - if (match === null) fail('time-context message does not match the durable reading format') - const turn = Number(match[1]) - const step = Number(match[2]) - if (!Number.isSafeInteger(turn) || turn < 1 || !Number.isSafeInteger(step) || step < 1) { - fail('time-context turn and step must be positive safe integers') - } - const expected = preparationPosition(history, fail) - if (turn !== expected.turn || step !== expected.step) { - fail(`time-context reading names turn ${turn}/step ${step}, expected turn ${expected.turn}/step ${expected.step}`) - } - const source = event.data.source - /* v8 ignore next 2 -- replay and dispatch callers select this exact package-owned source before validation. */ - if (source.kind !== SOURCE_NAME) { - fail('time-context source must retain package ownership') - } - const sections: unknown = 'sections' in source ? source.sections : undefined - const sectionValue: unknown = Array.isArray(sections) ? sections[0] : undefined - const section = typeof sectionValue === 'object' && sectionValue !== null - ? sectionValue as Record - : undefined - if (Object.keys(source).length !== 3 - || source.form !== 'snapshot' - || !Array.isArray(sections) - || sections.length !== 1 - || section === undefined - || Object.keys(section).length !== 2 - || section.name !== SOURCE_NAME - || section.text !== blockText) { - fail('time-context source must carry only the exact snapshot text, not request authority') - } - const renderedBrowserContext = match[4] - const browserContext = deriveBrowserTimeZoneContext(requestMessages(history, turn)) - const expectedBrowserContext = renderBrowserTimeZoneContext(browserContext) - if (renderedBrowserContext !== expectedBrowserContext) { - fail('time-context browser-zone text does not match current-turn user messages') - } - const baseline = match[5] - if ((step === 1) !== (baseline === 'model-visible message')) { - fail(`time-context step ${step} uses the wrong elapsed-time baseline ${JSON.stringify(baseline)}`) - } - const rendered = match[3] - /* v8 ignore next -- the preceding fixed regexp always supplies capture group three. */ - if (rendered === undefined) fail('time-context reading omitted its rendered timestamp') - const renderedTime = Date.parse(rendered.replace(/\[[^\]]+\]$/, '')) - if (!Number.isFinite(renderedTime) || !Number.isSafeInteger(event.time) - || event.time < renderedTime) { - fail('time-context rendered timestamp must parse and not postdate its durable event') - } - if (browserContext.kind === 'resolved') { - let expectedTimestamp: string - try { - expectedTimestamp = formatTimestamp( - renderedTime, - createTimestampFormatter(browserContext.timeZone), - browserContext.timeZone, - ) - } catch (error: unknown) { - fail(`time-context browser zone cannot format its durable timestamp: ${String(error)}`) - } - if (rendered !== expectedTimestamp) { - fail('time-context rendered timestamp does not match the unique browser zone') - } - } -} - -/* jscpd:ignore-start -- package companions share replay and dispatch plumbing */ -/** Validate all package-owned readings already present in one session. */ -function validateSession(session: Session, fail: InvariantFailure): void { - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - const events = session.snapshotEvents() - for (const [index, event] of events.entries()) { - if (event.type !== 'user/message' - || event.data.source.kind !== SOURCE_NAME) continue - validateReading(events.slice(0, index), event, fail) - } -} - -/** Install validation for loaded and newly appended context readings. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - for (const session of ctx.sessions.list()) validateSession(session, fail) - ctx.on('session/created', (session) => { validateSession(session, fail) }, { global: true }) - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - if (event.type !== 'user/message' - || event.data.source.kind !== SOURCE_NAME) return - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - validateReading(session.snapshotEvents(), event, fail) - }, { global: true }) -}, { inject: ['sessions'] }) -/* jscpd:ignore-end */ - -/** - * Register the time-context 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)) diff --git a/packages/context/time-context/tests/invariant.spec.ts b/packages/context/time-context/tests/invariant.spec.ts deleted file mode 100644 index cba6fc48d0..0000000000 --- a/packages/context/time-context/tests/invariant.spec.ts +++ /dev/null @@ -1,349 +0,0 @@ -import { createUserMessage } from '@deepseek-ai/dsh-llm' -import { describe, expect, it, vi } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import type { ContentBlock } from '@deepseek-ai/dsh-llm' -import SessionStore, { Session, SessionId, SessionSeq, type SessionEvent } from '@deepseek-ai/dsh-session' -import * as TimeInvariant from '@deepseek-ai/dsh-time-context/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -const SECOND = Date.parse('2026-07-14T00:00:00Z') - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(TimeInvariant) - return ctx -} - -function event( - text: string, - time = SECOND + 456, - content?: unknown[], - plugin = 'time-context', -): SessionEvent<'user/message'> { - return { - type: 'user/message', - surfaceOp: 'append', - seq: SessionSeq(0), - time, - data: createUserMessage({ - content: (content ?? [{ type: 'text', text }]) as ContentBlock[], - source: plugin === 'time-context' - ? { - kind: 'time-context', - form: 'snapshot', - sections: [{ name: plugin, text }], - } - : { kind: 'other' }, - }), - } -} - -function reading( - turn = '1', - step = '1', - baseline = 'model-visible message', - timestamp = '2026-07-14T00:00:00+00:00[UTC]', - browser = 'Browser time zone for this request: unavailable. Ask the user to clarify otherwise-unqualified dates and times.', -): string { - return `Time sampled while preparing turn ${turn}, step ${step}: ${timestamp}\n` - + `${browser}\n` - + `Elapsed since the preceding ${baseline}: unavailable.` -} - -function preparing(turn: number, step: number, clientTimeZone?: string): Session { - const session = Session.create(SessionId(`time-invariant-${turn}-${step}`)) - for (let priorTurn = 1; priorTurn < turn; priorTurn += 1) { - session.append('turn/start', { turn: priorTurn }) - session.append('turn/end', { turn: priorTurn, reason: { kind: 'completed' } }) - } - session.append('turn/start', { turn }) - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: `turn ${turn}` }], - source: clientTimeZone === undefined - ? { kind: 'user' } - : { kind: 'user', rpcId: `turn-${String(turn)}`, clientTimeZone } as never, - }), { surfaceOp: 'append' }) - for (let priorStep = 1; priorStep < step; priorStep += 1) { - session.append('step/start', { turn, step: priorStep }) - session.append('step/end', { turn, step: priorStep }) - } - session.append('step/start', { turn, step }) - return session -} - -function appendReading(session: Session, text: string): void { - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text }], - source: { - kind: 'time-context', - form: 'snapshot', - sections: [{ name: 'time-context', text }], - }, - }), { surfaceOp: 'append' }) -} - -describe('time-context invariants', () => { - it('accepts a reading whose turn, step, baseline, and timestamp agree', async () => { - const ctx = await setup() - const text = 'Time sampled while preparing turn 2, step 3: 2026-07-14T00:00:00+00:00[UTC]\n' - + 'Browser time zone for this request: unavailable. Ask the user to clarify otherwise-unqualified dates and times.\n' - + 'Elapsed since the preceding step context: 4m 2s.' - expect(() => { ctx.emit('session/event', preparing(2, 3), event(text)) }).not.toThrow() - }) - - it('accepts a reading durably appended after a long process pause', async () => { - const ctx = await setup() - expect(() => { - ctx.emit('session/event', preparing(1, 1), event(reading(), SECOND + 60_000)) - }).not.toThrow() - }) - - it('requires browser-zone policy and timestamp to match current-turn request sources', async () => { - const ctx = await setup() - const policy = 'Browser time zone for this request: Asia/Shanghai. ' - + 'Interpret otherwise-unqualified dates and times in this zone.' - expect(() => { - ctx.emit('session/event', preparing(1, 1, 'Asia/Shanghai'), event(reading( - '1', - '1', - 'model-visible message', - '2026-07-14T08:00:00+08:00[Asia/Shanghai]', - policy, - ), SECOND + 456)) - }).not.toThrow() - expect(() => { - ctx.emit('session/event', preparing(1, 1, 'Asia/Shanghai'), event(reading())) - }).toThrow(/browser-zone text/) - expect(() => { - ctx.emit('session/event', preparing(1, 1, 'Asia/Shanghai'), event(reading( - '1', - '1', - 'model-visible message', - '2026-07-14T00:00:00+00:00[UTC]', - policy, - ))) - }).toThrow(/rendered timestamp does not match the unique browser zone/) - }) - - it('reports browser-zone timestamp formatter failures as invariant violations', async () => { - const ctx = await setup() - const policy = 'Browser time zone for this request: Asia/Shanghai. ' - + 'Interpret otherwise-unqualified dates and times in this zone.' - const formatToParts = vi.spyOn(Intl.DateTimeFormat.prototype, 'formatToParts') - .mockImplementationOnce(() => { throw new RangeError('formatter unavailable') }) - try { - expect(() => { - ctx.emit('session/event', preparing(1, 1, 'Asia/Shanghai'), event(reading( - '1', - '1', - 'model-visible message', - '2026-07-14T08:00:00+08:00[Asia/Shanghai]', - policy, - ))) - }).toThrow(/browser zone cannot format its durable timestamp: RangeError: formatter unavailable/) - } finally { - formatToParts.mockRestore() - } - }) - - it('rejects invalid browser-zone data loaded across the durable boundary', async () => { - const ctx = await setup() - const timeZone = 'Not/A_Real_Zone' - const policy = `Browser time zone for this request: ${timeZone}. ` - + 'Interpret otherwise-unqualified dates and times in this zone.' - expect(() => { - ctx.emit('session/event', preparing(1, 1, timeZone), event(reading( - '1', - '1', - 'model-visible message', - `2026-07-14T00:00:00+00:00[${timeZone}]`, - policy, - ))) - }).toThrow(/browser time zone is unsupported/) - }) - - it('rejects one corrupt zone even when another zone would classify the turn as mixed', async () => { - const ctx = await setup() - const session = preparing(1, 1, 'Asia/Shanghai') - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'second browser prompt' }], - source: { - kind: 'user', - rpcId: 'turn-1-invalid', - clientTimeZone: 'Not/A_Real_Zone', - } as never, - }), { surfaceOp: 'append' }) - expect(() => { - ctx.emit('session/event', session, event(reading( - '1', - '1', - 'model-visible message', - '2026-07-14T00:00:00+00:00[UTC]', - 'Browser time zone for this request: mixed ["Asia/Shanghai","Not/A_Real_Zone"]. ' - + 'Ask the user to clarify otherwise-unqualified dates and times.', - ))) - }).toThrow(/browser time zone is unsupported/) - }) - - it('validates each existing reading against its preceding durable prefix', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create(SessionId('time-invariant-late-valid')) - session.append('turn/start', { turn: 1 }) - session.append('step/start', { turn: 1, step: 1 }) - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'prepare' }], - source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - appendReading(session, reading()) - - await ctx.plugin(InvariantRegistry, { enabled: true }) - await expect(ctx.plugin(TimeInvariant)).resolves.toBeDefined() - }) - - it('rejects an invalid existing reading on late registration', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create(SessionId('time-invariant-late-invalid')) - session.append('turn/start', { turn: 1 }) - session.append('step/start', { turn: 1, step: 1 }) - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'prepare' }], - source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - appendReading(session, reading('1', '2', 'step context')) - - await ctx.plugin(InvariantRegistry, { enabled: true }) - await expect(ctx.plugin(TimeInvariant).then(() => undefined)).rejects.toThrow(/expected turn 1\/step 1/) - }) - - it.each([ - [reading('1', '3', 'step context'), /expected turn 2\/step 3/], - [reading('2', '2', 'step context'), /expected turn 2\/step 3/], - ])('rejects a reading that disagrees with its session position', async (text, message) => { - const ctx = await setup() - expect(() => { ctx.emit('session/event', preparing(2, 3), event(text)) }).toThrow(message) - }) - - it('rejects a reading after cancellation closes the turn', async () => { - const ctx = await setup() - const session = preparing(1, 2) - session.append('turn/end', { turn: 1, reason: { kind: 'aborted', reason: { kind: 'user' } } }) - expect(() => { ctx.emit('session/event', session, event(reading('1', '2', 'step context'))) }) - .toThrow(/inside an open turn/) - }) - - it('rejects a reading outside prompt assembly', async () => { - const ctx = await setup() - const ended = preparing(1, 1) - ended.append('step/end', { turn: 1, step: 1 }) - expect(() => { ctx.emit('session/event', ended, event(reading())) }).toThrow(/follow step\/start/) - const notEntered = Session.create(SessionId('time-invariant-turn-only')) - notEntered.append('turn/start', { turn: 1 }) - expect(() => { ctx.emit('session/event', notEntered, event(reading())) }).toThrow(/follow step\/start/) - expect(() => { - ctx.emit('session/event', Session.create(SessionId('time-invariant-empty')), event(reading())) - }).toThrow(/inside an open turn/) - const requested = preparing(1, 1) - requested.append('request/header', { - header: { config: { provider: 'mock', model: 'model' } }, - reason: 'initial', - }) - expect(() => { ctx.emit('session/event', requested, event(reading())) }).toThrow(/precede request\/header/) - }) - - it.each([ - ['not a reading', SECOND, undefined, /durable reading format/], - [reading('0'), SECOND, undefined, /positive safe integers/], - [reading('999999999999999999999'), SECOND, undefined, /positive safe integers/], - [reading('1', '0', 'step context'), SECOND, undefined, /positive safe integers/], - [reading('1', '999999999999999999999', 'step context'), SECOND, undefined, /positive safe integers/], - [reading('1', '1', 'step context'), SECOND, undefined, /wrong elapsed-time baseline/], - [reading('1', '2', 'model-visible message'), SECOND, undefined, /wrong elapsed-time baseline/], - [reading('1', '1', 'model-visible message', '2026-99-99T00:00:00+00:00[UTC]'), SECOND, undefined, /must parse and not postdate/], - [reading(), Number.NaN, undefined, /must parse and not postdate/], - [reading(), SECOND - 1, undefined, /must parse and not postdate/], - ['ignored', SECOND, [], /exactly one text block/], - ['ignored', SECOND, [{ type: 'image', data: 'x', mimeType: 'image/png' }], /exactly one text block/], - ['ignored', SECOND, [{ type: 'text', text: 'one' }, { type: 'text', text: 'two' }], /exactly one text block/], - [reading(), SECOND, [{ type: 'text', text: reading(), extra: true }], /exactly one text block/], - ] as const)('rejects an incoherent durable reading', async (text, time, content, message) => { - const ctx = await setup() - const preparationStep = text.includes('turn 1, step 2:') ? 2 : 1 - expect(() => { - ctx.emit('session/event', preparing(1, preparationStep), event( - text, - time, - content === undefined ? undefined : [...content], - )) - }).toThrow(message) - }) - - it('requires the exact snapshot source without copied request authority', async () => { - const ctx = await setup() - const base = event(reading()) - for (const source of [ - { kind: 'time-context' }, - { ...base.data.source, authority: {} }, - { - kind: 'time-context', - form: 'snapshot', - sections: [{ name: 'time-context', text: 'different' }], - }, - { - kind: 'time-context', - form: 'snapshot', - sections: { 0: { name: 'time-context', text: reading() }, length: 1 }, - }, - { - kind: 'time-context', - form: 'snapshot', - sections: [{ name: 'time-context', text: reading(), extra: true }], - }, - ]) { - const malformed: SessionEvent<'user/message'> = { - ...base, - data: { ...base.data, source: source as never }, - } - expect(() => { ctx.emit('session/event', preparing(1, 1), malformed) }) - .toThrow(/must carry only the exact snapshot text/) - } - }) - - it('validates a seeded Session created after invariant registration', async () => { - const ctx = await setup() - const text = reading('1', '2', 'step context') - expect(() => { - ctx.sessions.create(SessionId('time-invariant-created-invalid'), { - seed: [ - { type: 'turn/start', seq: SessionSeq(0), time: SECOND, data: { turn: 1 } }, - { type: 'step/start', seq: SessionSeq(1), time: SECOND, data: { turn: 1, step: 1 } }, - { ...event(text), seq: SessionSeq(2), surfaceOp: 'append' }, - ], - }) - }).toThrow(/expected turn 1\/step 1/) - expect(ctx.sessions.get(SessionId('time-invariant-created-invalid'))).toBeUndefined() - }) - - it('ignores context messages owned by another package', async () => { - const ctx = await setup() - const other = event('unrelated', SECOND + 456, undefined, 'other') - expect(() => { ctx.emit('session/event', preparing(1, 1), other) }).not.toThrow() - const user: SessionEvent<'user/message'> = { - ...event('unrelated'), - data: createUserMessage({ - content: [{ type: 'text', text: 'unrelated' }], - source: { kind: 'user' }, - }), - } - expect(() => { ctx.emit('session/event', preparing(1, 1), user) }).not.toThrow() - expect(() => { - ctx.emit('session/event', preparing(1, 1), { - type: 'turn/start', seq: SessionSeq(0), time: 0, data: { turn: 1 }, - }) - ctx.emit('tools/change') - }).not.toThrow() - }) -}) diff --git a/packages/context/time-context/tsconfig.json b/packages/context/time-context/tsconfig.json index bb6a7d6927..c8284be70f 100644 --- a/packages/context/time-context/tsconfig.json +++ b/packages/context/time-context/tsconfig.json @@ -32,9 +32,6 @@ { "path": "../../test-support/loader-smoke" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../core/session" }, diff --git a/packages/context/time-context/tsdown.config.ts b/packages/context/time-context/tsdown.config.ts index c575cae3c5..85663f05f9 100644 --- a/packages/context/time-context/tsdown.config.ts +++ b/packages/context/time-context/tsdown.config.ts @@ -12,14 +12,4 @@ export default defineConfig([ dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, - }, ]) diff --git a/packages/context/tmux-context/README.i18n.yaml b/packages/context/tmux-context/README.i18n.yaml index 764312518b..5535862beb 100644 --- a/packages/context/tmux-context/README.i18n.yaml +++ b/packages/context/tmux-context/README.i18n.yaml @@ -33,8 +33,8 @@ en: 1902de51becf7c7d zh: 130e673b471c38c4 /deepseek-ai-dsh-tmux-context/understand-the-implementation/source-map: - en: 57d240751bb587a0 - zh: 0a4195a9e3024208 + en: 14bc5e3279f9da27 + zh: 90953dfbf892033a /deepseek-ai-dsh-tmux-context/understand-the-implementation/main-flow: en: 2d5768fe44120780 zh: cd601c59d9604e6e diff --git a/packages/context/tmux-context/README.md b/packages/context/tmux-context/README.md index 1a5255e11f..50c3f47312 100644 --- a/packages/context/tmux-context/README.md +++ b/packages/context/tmux-context/README.md @@ -70,7 +70,6 @@ The plugin prepends an `agent/pre-step` listener that runs only on the first ste | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: first-step listener, shell query, change suppression, scheduling | -| — | No runtime invariant companion is published; a reading is a per-turn snapshot of external tmux state, so the session holds no cross-event relation to check; scheduling and format are owned by pipeline tests. | ### Main flow diff --git a/packages/context/tmux-context/README.zh.md b/packages/context/tmux-context/README.zh.md index 3dab566a4f..0f4e707d40 100644 --- a/packages/context/tmux-context/README.zh.md +++ b/packages/context/tmux-context/README.zh.md @@ -70,7 +70,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:第一步监听器、shell 查询、变化抑制、调度 | -| — | 不发布运行时不变式伴生入口;每次读取都是外部 tmux 状态的单轮快照,会话没有可检查的跨事件关系;调度与格式由流水线测试负责。 | ### 主要流程 diff --git a/packages/core/agent-default-model/README.i18n.yaml b/packages/core/agent-default-model/README.i18n.yaml index f6cc08808c..2bf3657666 100644 --- a/packages/core/agent-default-model/README.i18n.yaml +++ b/packages/core/agent-default-model/README.i18n.yaml @@ -30,8 +30,8 @@ en: 9ff35825ebe9200d zh: 613ebd35df840aea /deepseek-ai-dsh-agent-default-model/understand-the-implementation/source-map: - en: 68c75c85685bb265 - zh: f3c50b3078d1500e + en: 77dbcd572cb3de7b + zh: 20650cab1af57220 /deepseek-ai-dsh-agent-default-model/understand-the-implementation/behavior-notes: en: 82a433fa05760420 zh: d4459d6a631143cf diff --git a/packages/core/agent-default-model/README.md b/packages/core/agent-default-model/README.md index 24583a732f..5da7eb6eea 100644 --- a/packages/core/agent-default-model/README.md +++ b/packages/core/agent-default-model/README.md @@ -75,7 +75,6 @@ The service retains its validated Config references and samples them in `current | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Live default selection and profile-backed writes | -| — | No invariant companion is published because Config references are the only owned values. | ### Behavior notes diff --git a/packages/core/agent-default-model/README.zh.md b/packages/core/agent-default-model/README.zh.md index 4612a1fb3f..f635b44a95 100644 --- a/packages/core/agent-default-model/README.zh.md +++ b/packages/core/agent-default-model/README.zh.md @@ -75,7 +75,6 @@ await ctx.agentDefaultModel.saveSelection({ provider, model, reasoningEffort: 'h | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 即时默认选择与 profile 写入 | -| — | 不发布 invariant 配套模块,因为 Config 引用是唯一由此包维护的值。 | ### 行为说明 diff --git a/packages/core/agent-default-model/tsdown.config.ts b/packages/core/agent-default-model/tsdown.config.ts index d593c00b27..76c21c44d4 100644 --- a/packages/core/agent-default-model/tsdown.config.ts +++ b/packages/core/agent-default-model/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from 'tsdown' -/** Build the package root and invariant companion as independent bundles. */ +/** Build the package root as a self-contained bundle. */ export default defineConfig([ { entry: ['lib/types/index.js'], diff --git a/packages/core/agent-loop/README.i18n.yaml b/packages/core/agent-loop/README.i18n.yaml index 787f531a31..3548fe69d5 100644 --- a/packages/core/agent-loop/README.i18n.yaml +++ b/packages/core/agent-loop/README.i18n.yaml @@ -36,8 +36,8 @@ en: 6fa5c8b2f91552d8 zh: 75886c7e922d23df /deepseek-ai-dsh-agent-loop/understand-the-implementation/source-map: - en: d7f6b2947ec9b2f3 - zh: 8a3d566fb8cf2938 + en: 2a8b93869eb5a2f9 + zh: ddab8e738d188a61 /deepseek-ai-dsh-agent-loop/understand-the-implementation/creation-and-teardown: en: 6bc52b8ef503eeda zh: 3290052ebc47baf6 diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index 19e356affc..408bd016e1 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -104,7 +104,6 @@ The loop deep-freezes each derived message identity on its first request and reu | [`src/tool-calls.ts`](src/tool-calls.ts) | Tool scheduling: exclusive barriers and the bounded parallel pool | | [`src/runtime-context.ts`](src/runtime-context.ts) | Per-step runtime-context snapshot handling | | [`src/constants.ts`](src/constants.ts) | `DEFAULT_MAX_PARALLEL_TOOL_CALLS` | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: request reconstruction from the session log | ### Creation and teardown diff --git a/packages/core/agent-loop/README.zh.md b/packages/core/agent-loop/README.zh.md index a6a8e874c0..20e05c65a1 100644 --- a/packages/core/agent-loop/README.zh.md +++ b/packages/core/agent-loop/README.zh.md @@ -104,7 +104,6 @@ const handle = await ctx.agents.create({ | [`src/tool-calls.ts`](src/tool-calls.ts) | 工具调度:独占屏障与有界并行池 | | [`src/runtime-context.ts`](src/runtime-context.ts) | 逐步骤运行时上下文快照处理 | | [`src/constants.ts`](src/constants.ts) | `DEFAULT_MAX_PARALLEL_TOOL_CALLS` | -| [`src/invariant.ts`](src/invariant.ts) | 不变式配套:从会话日志重建请求 | ### 创建与拆除 diff --git a/packages/core/agent-loop/package.json b/packages/core/agent-loop/package.json index f1f6641d8a..af909170fd 100644 --- a/packages/core/agent-loop/package.json +++ b/packages/core/agent-loop/package.json @@ -18,22 +18,16 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./package.json": "./package.json" }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.d.ts" ], "license": "MIT", "peerDependencies": { "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", @@ -53,7 +47,6 @@ "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-llm-pi-ai": "workspace:*", "@deepseek-ai/dsh-agent": "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/core/agent-loop/src/invariant.ts b/packages/core/agent-loop/src/invariant.ts deleted file mode 100644 index 3b1c62d081..0000000000 --- a/packages/core/agent-loop/src/invariant.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Package-owned request-reconstruction invariant for loop-built LLM calls. - * @module @deepseek-ai/dsh-agent-loop/invariant - */ - -import type { Context } from '@deepseek-ai/cordis' -import { isAgentLoopRequest, type GenerateOptions } from '@deepseek-ai/dsh-llm' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import { foldRequestHeader } from '@deepseek-ai/dsh-session' - -const PACKAGE_NAME = '@deepseek-ai/dsh-agent-loop' - -/** Cordis companion plugin name. */ -export const name = 'agent-loop-invariant' -/** 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) => { - // Prepend prevents a short-circuiting replay listener from silencing the check. - ctx.on('llm/stream', (options: GenerateOptions, next) => { - if (!isAgentLoopRequest(options)) return next() - if (!Object.isFrozen(options)) fail('a loop-built request must be frozen') - if (options.sessionId === undefined) fail('a loop-built request must carry a session id') - const session = ctx.sessions.get(options.sessionId) - if (!session) fail(`a loop-built request must carry a live session id, got "${String(options.sessionId)}"`) - if (!Object.isFrozen(options.messages)) { - fail('a loop-built request must carry a frozen messages array') - } - - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - const events = session.snapshotEvents() - if (!events.some(event => event.type === 'step/start')) { - return fail('a loop-built request with no step/start in its session log') - } - const header = foldRequestHeader(events) - if (header === undefined) { - return fail('a loop-built request with no request/header event in its session log') - } - const expected = session.deriveMessages() - if (JSON.stringify(options.messages) !== JSON.stringify(expected)) { - fail(`llm request for session "${String(session.id)}" diverges from the dispatch-time durable derivation (log-reconstruction desync)`) - } - - // The system prompt travels inside `messages` as surface node 0, never as `system`. - const headerMatches = options.model === header.config.model - && options.system === undefined - && options.temperature === header.config.temperature - && options.maxTokens === header.config.maxTokens - && JSON.stringify(options.stop) === JSON.stringify(header.config.stop) - && JSON.stringify(options.tools ?? []) === JSON.stringify(header.tools ?? []) - if (!headerMatches) { - fail(`llm request for session "${String(session.id)}" diverges from the folded request header`) - } - return next() - }, { global: true, prepend: true }) -}, { inject: ['sessions'] }) - -/** - * Register the agent-loop 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)) diff --git a/packages/core/agent-loop/tests/contract-regressions.spec.ts b/packages/core/agent-loop/tests/contract-regressions.spec.ts index c5c606ae54..e6df4e6b45 100644 --- a/packages/core/agent-loop/tests/contract-regressions.spec.ts +++ b/packages/core/agent-loop/tests/contract-regressions.spec.ts @@ -8,10 +8,6 @@ import ToolRuntime, { defineContentToolFixture, type PostToolDecision } from '@d import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant' -import * as AgentInvariant from '@deepseek-ai/dsh-agent/invariant' -import * as AgentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant' import { MockAdapter, textResponse, toolCallResponse } from './mock-adapter.ts' declare module '@deepseek-ai/dsh-llm' { @@ -20,13 +16,6 @@ declare module '@deepseek-ai/dsh-llm' { } } -async function mountInvariants(ctx: Context): Promise { - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SessionInvariant) - await ctx.plugin(AgentInvariant) - await ctx.plugin(AgentLoopInvariant) -} - function driverDone(agent: Agent): Promise { return (agent as Agent & { done: Promise }).done } @@ -698,7 +687,6 @@ describe('forked tool history reaches the next model request', () => { ]) const ctx = await harness(adapter) try { - await mountInvariants(ctx) const executions: string[] = [] for (const name of ['first', 'second']) { ctx.tools.register(defineContentToolFixture({ @@ -880,21 +868,6 @@ describe('step boundary publication order', () => { }) describe('turn and step boundary recovery', () => { - // The session invariant companion makes an unbalanced log fail the test. - async function balancedHarness(adapter: MockAdapter) { - const ctx = new Context() - await ctx.plugin(LlmRuntime) - await ctx.plugin(SessionStore) - await ctx.plugin(SessionProjectionRegistry) - await ctx.plugin(SystemPrompt) - await ctx.plugin(ToolRuntime) - await ctx.plugin(AgentRegistry) - await ctx.plugin(AgentLoop, { agents: [] }) - await mountInvariants(ctx) - ctx.llm.registerAdapter(['mock'], adapter) - return ctx - } - /** Count turn/step boundary events for balance assertions. */ function boundaryCounts(agent: Agent) { const e = agent.session.snapshotEvents() @@ -910,7 +883,7 @@ describe('turn and step boundary recovery', () => { it('a throwing step/start observer cannot change a successful turn', async () => { const adapter = new MockAdapter([textResponse('request completed')]) - const ctx = await balancedHarness(adapter) + const ctx = await harness(adapter) const agent = await ctx.agentLoop.create(SessionId('a-stepstart'), { provider: 'mock', model: 'mock' }) // Session owns post-commit containment. The loop sees a successful append, @@ -931,8 +904,7 @@ describe('turn and step boundary recovery', () => { const c = boundaryCounts(agent) expect(c).toMatchObject({ turnStart: 1, turnEnd: 1, stepStart: 1, stepEnd: 1, errors: 0 }) expect(errors).toEqual([]) - // step/end precedes turn/end (the invariants oracle would reject - // turn/end-while-step-open, but assert the order explicitly too). + // step/end precedes turn/end. const stepEndIdx = e.findIndex(x => x.type === 'step/end') const turnEndIdx = e.findIndex(x => x.type === 'turn/end') expect(stepEndIdx).toBeGreaterThanOrEqual(0) @@ -941,7 +913,7 @@ describe('turn and step boundary recovery', () => { it('a pre-commit turn/start rejection leaves no durable turn state', async () => { const adapter = new MockAdapter([]) - const ctx = await balancedHarness(adapter) + const ctx = await harness(adapter) const agent = await ctx.agentLoop.create(SessionId('a-turnstart-veto'), { provider: 'mock', model: 'mock' }) let rejected = false ctx.on('internal/dispatch', (_mode, name, args) => { @@ -969,7 +941,7 @@ describe('turn and step boundary recovery', () => { it('a pre-commit step/start validation failure does not invent a step boundary', async () => { const adapter = new MockAdapter([textResponse('never reached')]) - const ctx = await balancedHarness(adapter) + const ctx = await harness(adapter) const agent = await ctx.agentLoop.create(SessionId('a-stepstart-veto'), { provider: 'mock', model: 'mock' }) let rejected = false ctx.on('internal/dispatch', (_mode, name, args) => { @@ -996,9 +968,9 @@ describe('turn and step boundary recovery', () => { }) }) - it('a step/end validation failure surfaces the resulting open-step invariant', async () => { + it('a step/end validation failure ends the turn with that error', async () => { const adapter = new MockAdapter([textResponse('completed before close validation')]) - const ctx = await balancedHarness(adapter) + const ctx = await harness(adapter) const agent = await ctx.agentLoop.create(SessionId('a-stepend-veto'), { provider: 'mock', model: 'mock' }) let rejected = false ctx.on('internal/dispatch', (_mode, name, args) => { @@ -1020,14 +992,16 @@ describe('turn and step boundary recovery', () => { expect(adapter.requests).toHaveLength(1) expect(errors.map(error => error.message)).toEqual([ 'reject first step-end', - 'invariant violated by "@deepseek-ai/dsh-session": turn/end 1 while step 1 is still open', ]) expect(boundaryCounts(agent)).toMatchObject({ turnStart: 1, - turnEnd: 0, + turnEnd: 1, stepStart: 1, stepEnd: 0, - errors: 0, + errors: 1, + }) + expect(agent.session.snapshotEvents().findLast(event => event.type === 'turn/end')).toMatchObject({ + data: { reason: { kind: 'error', error: { message: 'reject first step-end', code: 'UNKNOWN' } } }, }) }) @@ -1035,7 +1009,7 @@ describe('turn and step boundary recovery', () => { // Listener failure cannot interrupt error finalization or the next turn. const errorStream: StreamChunk[] = [{ type: 'finish', reason: { kind: 'error', failure: { message: 'provider 500', code: 'SERVER' } } }] const adapter = new MockAdapter([errorStream, textResponse('turn 2 ok')]) - const ctx = await balancedHarness(adapter) + const ctx = await harness(adapter) const agent = await ctx.agentLoop.create(SessionId('a-errorlistener'), { provider: 'mock', model: 'mock' }) let threw = false @@ -1055,8 +1029,7 @@ describe('turn and step boundary recovery', () => { }) expect(threw).toBe(true) - // loop survives: a second turn runs to completion (invariants oracle would - // throw on its turn/start if turn 1 had been left open). + // loop survives: a second turn runs to completion. send(agent, 'again') await waitForIdle(ctx, agent) const c2 = boundaryCounts(agent) @@ -1070,7 +1043,7 @@ describe('turn and step boundary recovery', () => { // the agent's fiber mid-turn aborts the in-flight step. The turn must close // balanced with reason disposed (no error event for a disposal). const adapter = new MockAdapter(['hang']) - const ctx = await balancedHarness(adapter) + const ctx = await harness(adapter) let agent!: Agent const fiber = await ctx.plugin(Object.assign(async (inner: Context) => { agent = await inner.agentLoop.create(SessionId('a-dispose'), { provider: 'mock', model: 'mock' }) @@ -1096,7 +1069,7 @@ describe('turn and step boundary recovery', () => { it('contains a pre-step throw after disposal inside a balanced no-step turn', async () => { const adapter = new MockAdapter([textResponse('never reached')]) - const ctx = await balancedHarness(adapter) + const ctx = await harness(adapter) let agent!: Agent const fiber = await ctx.plugin(Object.assign(async (inner: Context) => { agent = await inner.agentLoop.create(SessionId('a-prestep-dispose-throw'), { provider: 'mock', model: 'mock' }) @@ -1161,7 +1134,7 @@ describe('turn and step boundary recovery', () => { it('a throwing step/end observer cannot rewrite the turn outcome', async () => { const adapter = new MockAdapter([textResponse('all good'), textResponse('turn 2 ok')]) - const ctx = await balancedHarness(adapter) + const ctx = await harness(adapter) const agent = await ctx.agentLoop.create(SessionId('a-stepend-throw'), { provider: 'mock', model: 'mock' }) let threw = false @@ -1321,7 +1294,6 @@ describe('disposal and cancellation during pre-step assembly', () => { await ctx.plugin(ToolRuntime) await ctx.plugin(AgentRegistry) await ctx.plugin(AgentLoop, { agents: [] }) - await mountInvariants(ctx) ctx.llm.registerAdapter(['mock'], adapter) // Parent-owned listener survives agent-fiber disposal. @@ -1372,7 +1344,6 @@ describe('disposal and cancellation during pre-step assembly', () => { await ctx.plugin(ToolRuntime) await ctx.plugin(AgentRegistry) await ctx.plugin(AgentLoop, { agents: [] }) - await mountInvariants(ctx) ctx.llm.registerAdapter(['mock'], adapter) const unlisten = ctx.on('system-prompt/assemble', async function (_assembly, _context, next) { @@ -1423,7 +1394,6 @@ describe('disposal and cancellation during pre-step assembly', () => { await ctx.plugin(ToolRuntime) await ctx.plugin(AgentRegistry) await ctx.plugin(AgentLoop, { agents: [] }) - await mountInvariants(ctx) ctx.llm.registerAdapter(['mock'], adapter) ctx.on('agent/pre-step', async (_payload, next) => { @@ -1470,7 +1440,6 @@ describe('disposal and cancellation during pre-step assembly', () => { await ctx.plugin(ToolRuntime) await ctx.plugin(AgentRegistry) await ctx.plugin(AgentLoop, { agents: [] }) - await mountInvariants(ctx) ctx.llm.registerAdapter(['mock'], adapter) ctx.on('agent/pre-step', async (_payload, next) => { @@ -1519,7 +1488,6 @@ describe('disposal and cancellation during pre-step assembly', () => { await ctx.plugin(ToolRuntime) await ctx.plugin(AgentRegistry) await ctx.plugin(AgentLoop, { agents: [] }) - await mountInvariants(ctx) ctx.llm.registerAdapter(['mock'], adapter) ctx.on('system-prompt/assemble', async function (_assembly, _context, next) { diff --git a/packages/core/agent-loop/tests/invariant.spec.ts b/packages/core/agent-loop/tests/invariant.spec.ts deleted file mode 100644 index 14ca873c8c..0000000000 --- a/packages/core/agent-loop/tests/invariant.spec.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import * as AgentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant' -import { createUserMessage, markAgentLoopRequest, type GenerateOptions } from '@deepseek-ai/dsh-llm' -import type { ContextFormed } from '@deepseek-ai/dsh-llm' - -declare module '@deepseek-ai/dsh-llm' { - interface MessageSourceMap { - 'x': { kind: 'x' } & ContextFormed - } -} - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(AgentLoopInvariant) - return ctx -} - -function dispatch(ctx: Context, options: unknown): void { - void ctx.waterfall('llm/stream', options as never, () => (async function* () {})() as never) -} - -function loopRequest(options: T): Readonly { - markAgentLoopRequest(options as GenerateOptions) - return Object.freeze(options) -} - -async function requestSetup() { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('req-check')) - session.append('turn/start', { turn: 1 }) - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - const boundary = session.deriveMessages() - session.append('step/start', { turn: 1, step: 1 }) - session.append('request/header', { header: { config: { provider: 'mock', model: 'm' } }, reason: 'initial' }) - return { ctx, session, boundary } -} - -describe('request-reconstruction invariant', () => { - it('accepts a frozen request equal to the boundary derivation and folded header', async () => { - const { ctx, session, boundary } = await requestSetup() - const options = loopRequest({ model: 'm', messages: Object.freeze(boundary), sessionId: session.id }) - expect(() => { dispatch(ctx, options) }).not.toThrow() - }) - - it('includes context appended inside the open step before dispatch', async () => { - const { ctx, session } = await requestSetup() - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: '[step context]' }], source: { kind: 'x' }, - }), { surfaceOp: 'append' }) - const options = loopRequest({ - model: 'm', - messages: Object.freeze(session.deriveMessages()), - sessionId: session.id, - }) - expect(() => { dispatch(ctx, options) }).not.toThrow() - }) - - it('requires the messages to equal the boundary derivation exactly (no unlogged prefix)', async () => { - const { ctx, session, boundary } = await requestSetup() - const extra = { role: 'user' as const, content: [{ type: 'text' as const, text: 'catalog' }] } - expect(() => { dispatch(ctx, loopRequest({ model: 'm', messages: Object.freeze([...boundary]), sessionId: session.id })) }) - .not.toThrow() - expect(() => { dispatch(ctx, loopRequest({ model: 'm', messages: Object.freeze([extra, ...boundary]), sessionId: session.id })) }) - .toThrow(/diverges from the dispatch-time durable derivation/) - expect(() => { dispatch(ctx, loopRequest({ model: 'm', messages: Object.freeze([...boundary, extra]), sessionId: session.id })) }) - .toThrow(/diverges from the dispatch-time durable derivation/) - }) - - it('rejects message and header divergence', async () => { - const { ctx, session, boundary } = await requestSetup() - const divergent = [...boundary, { role: 'user', content: [{ type: 'text', text: 'phantom' }] }] - expect(() => { dispatch(ctx, loopRequest({ model: 'm', messages: Object.freeze(divergent), sessionId: session.id })) }) - .toThrow(/diverges from the dispatch-time durable derivation/) - expect(() => { dispatch(ctx, loopRequest({ model: 'other', messages: Object.freeze(boundary), sessionId: session.id })) }) - .toThrow(/diverges from the folded request header/) - // The system prompt is surface node 0 inside `messages`; a `system` field is an unlogged prefix. - expect(() => { dispatch(ctx, loopRequest({ model: 'm', system: 'unlogged', messages: Object.freeze(boundary), sessionId: session.id })) }) - .toThrow(/diverges from the folded request header/) - }) - - it('rejects loop requests with no boundary or header', async () => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('req-bare')) - session.append('turn/start', { turn: 1 }) - const bare = loopRequest({ model: 'm', messages: Object.freeze([]), sessionId: session.id }) - expect(() => { dispatch(ctx, bare) }).toThrow(/no step\/start/) - session.append('step/start', { turn: 1, step: 1 }) - expect(() => { dispatch(ctx, bare) }).toThrow(/no request\/header event/) - }) - - it('rejects an unfrozen messages array but skips requests outside the loop contract', async () => { - const { ctx, session, boundary } = await requestSetup() - expect(() => { dispatch(ctx, loopRequest({ model: 'm', messages: [...boundary], sessionId: session.id })) }) - .toThrow(/frozen messages array/) - expect(() => { dispatch(ctx, { model: 'summarizer', messages: [], sessionId: session.id }) }).not.toThrow() - expect(() => { dispatch(ctx, Object.freeze({ model: 'm', messages: Object.freeze([]) })) }).not.toThrow() - expect(() => { dispatch(ctx, Object.freeze({ model: 'm', messages: Object.freeze([]), sessionId: SessionId('ghost') })) }) - .not.toThrow() - - const directSession = ctx.sessions.create(SessionId('direct-one-shot')) - expect(() => { - dispatch(ctx, Object.freeze({ model: 'one-shot', messages: Object.freeze([]), sessionId: directSession.id })) - }).not.toThrow() - }) - - it('rejects malformed requests carrying the loop marker', async () => { - const { ctx, session } = await requestSetup() - const messages: GenerateOptions['messages'] = [] - Object.freeze(messages) - expect(() => { - dispatch(ctx, markAgentLoopRequest({ provider: 'p', model: 'm', messages, sessionId: session.id })) - }).toThrow(/request must be frozen/) - expect(() => { - dispatch(ctx, loopRequest({ model: 'm', messages: Object.freeze([]) })) - }).toThrow(/carry a session id/) - expect(() => { - dispatch(ctx, loopRequest({ - model: 'm', - messages: Object.freeze([]), - sessionId: SessionId('missing-loop-session'), - })) - }).toThrow(/live session id/) - }) - - it('prepends ahead of a short-circuiting stream listener', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - ctx.on('llm/stream', () => (async function* () {})() as never) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(AgentLoopInvariant) - const session = ctx.sessions.create(SessionId('prepend-check')) - session.append('turn/start', { turn: 1 }) - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - session.append('step/start', { turn: 1, step: 1 }) - session.append('request/header', { header: { config: { provider: 'mock', model: 'm' } }, reason: 'initial' }) - const divergent = loopRequest({ - model: 'm', - messages: Object.freeze([{ role: 'user', content: [{ type: 'text', text: 'phantom' }] }]), - sessionId: session.id, - }) - expect(() => { dispatch(ctx, divergent) }).toThrow(/diverges from the dispatch-time durable derivation/) - }) -}) diff --git a/packages/core/agent-loop/tsconfig.json b/packages/core/agent-loop/tsconfig.json index 076c7aef49..5c846680ee 100644 --- a/packages/core/agent-loop/tsconfig.json +++ b/packages/core/agent-loop/tsconfig.json @@ -38,9 +38,6 @@ { "path": "../../core/scope" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../session/session-projection" } diff --git a/packages/core/agent-loop/tsdown.config.ts b/packages/core/agent-loop/tsdown.config.ts index e92275a7f5..76c21c44d4 100644 --- a/packages/core/agent-loop/tsdown.config.ts +++ b/packages/core/agent-loop/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from 'tsdown' -/** Build the package root and optional invariant companion as independent bundles. */ +/** Build the package root as a self-contained bundle. */ export default defineConfig([ { entry: ['lib/types/index.js'], @@ -12,14 +12,4 @@ export default defineConfig([ dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, - }, ]) diff --git a/packages/core/agent-tool-presentation/README.i18n.yaml b/packages/core/agent-tool-presentation/README.i18n.yaml index faf4f3ab65..7543887eef 100644 --- a/packages/core/agent-tool-presentation/README.i18n.yaml +++ b/packages/core/agent-tool-presentation/README.i18n.yaml @@ -33,8 +33,8 @@ en: 6f8bb6bd853cbc13 zh: 422fca4b20b79fbd /deepseek-ai-dsh-agent-tool-presentation/understand-the-implementation/source-map: - en: 506105ea2c83c4a8 - zh: 1a60101534b10dc4 + en: 2a0a0fb2dfdfce75 + zh: c425560fedcdb31a /deepseek-ai-dsh-agent-tool-presentation/understand-the-implementation/behavior-notes: en: 562acace386b25ca zh: 3eb362bd2730a64c diff --git a/packages/core/agent-tool-presentation/README.md b/packages/core/agent-tool-presentation/README.md index fe45190931..036aaf75d0 100644 --- a/packages/core/agent-tool-presentation/README.md +++ b/packages/core/agent-tool-presentation/README.md @@ -68,7 +68,6 @@ The tool registry cannot move into a preset: its consumers are all host-plane | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `mode` config, `apply` wiring `ctx.tools.presentAs` for the mounting scope | -| — | No runtime invariant companion is published; this package makes exactly one scoped call into `ctx.tools` and owns no event or snapshot of its own; the relation it establishes — which presentation one agent's assembly uses — is the tool registry's to hold, and `dsh-tools` observes it there. | ### Behavior notes diff --git a/packages/core/agent-tool-presentation/README.zh.md b/packages/core/agent-tool-presentation/README.zh.md index 62d7ce3ad5..87e5e6a456 100644 --- a/packages/core/agent-tool-presentation/README.zh.md +++ b/packages/core/agent-tool-presentation/README.zh.md @@ -68,7 +68,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`mode` 配置、把 `ctx.tools.presentAs` 接到挂载作用域的 `apply` | -| — | 不发布运行时不变式伴生入口;本包只对 `ctx.tools` 发起一次 scoped 调用,不持有自己的事件或快照;它建立的是「某个 agent 的组装采用哪种呈现方式」这一关系,该关系由工具注册表持有,`dsh-tools` 会在工具注册表中观察该关系。 | ### 行为说明 diff --git a/packages/core/agent/README.i18n.yaml b/packages/core/agent/README.i18n.yaml index 19639a27c0..befdc09642 100644 --- a/packages/core/agent/README.i18n.yaml +++ b/packages/core/agent/README.i18n.yaml @@ -42,8 +42,8 @@ en: f6802c7f94f623b3 zh: cc3885db80765970 /deepseek-ai-dsh-agent/understand-the-implementation/source-map: - en: c08cf19efc131171 - zh: 7e9f7b6a85e0aa1d + en: 7e09b94c79dc97ca + zh: 3f9c5455c4dc563a /deepseek-ai-dsh-agent/understand-the-implementation/registry-and-lifecycle: en: df92e97cedd0d02e zh: f6040c145bffd933 diff --git a/packages/core/agent/README.md b/packages/core/agent/README.md index 679634b236..9da1f06f64 100644 --- a/packages/core/agent/README.md +++ b/packages/core/agent/README.md @@ -100,7 +100,6 @@ The package is built on one separation: the public `Agent` surface and registry | [`src/dispatch.ts`](src/dispatch.ts) | `agentEvents` fused dispatcher and `assembleContextFor(agent)` | | [`src/consumed-work.ts`](src/consumed-work.ts) | `foldConsumedWork(events)`: what the log's consumed work became | | [`src/model-selection.ts`](src/model-selection.ts) | `installModelSelection`: coupling one selection to assembly and routing | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: no-op `agent/status` transitions fail | | [`src/archive-admission.ts`](src/archive-admission.ts) | The `turn` family of the Workspace registry's archive admission: a running turn and its user-cause cancel | ### Registry and lifecycle diff --git a/packages/core/agent/README.zh.md b/packages/core/agent/README.zh.md index f7f9bf2a0a..0166f96a80 100644 --- a/packages/core/agent/README.zh.md +++ b/packages/core/agent/README.zh.md @@ -100,7 +100,6 @@ await handle.agent.whenIdle() | [`src/dispatch.ts`](src/dispatch.ts) | `agentEvents` 融合分发器与 `assembleContextFor(agent)` | | [`src/consumed-work.ts`](src/consumed-work.ts) | `foldConsumedWork(events)`:日志消费掉的工作最终怎样了 | | [`src/model-selection.ts`](src/model-selection.ts) | `installModelSelection`:把一个选择耦合到组装与路由 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式配套:无操作的 `agent/status` 转换会失败 | | [`src/archive-admission.ts`](src/archive-admission.ts) | Workspace 注册表归档准入中的 `turn` 族:运行中的回合及其用户原因取消 | ### 注册表与生命周期 diff --git a/packages/core/agent/package.json b/packages/core/agent/package.json index 121af3b02b..11277a6a32 100644 --- a/packages/core/agent/package.json +++ b/packages/core/agent/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -31,13 +27,11 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], "license": "MIT", "peerDependencies": { - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", @@ -54,7 +48,6 @@ } }, "devDependencies": { - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", diff --git a/packages/core/agent/src/invariant.ts b/packages/core/agent/src/invariant.ts deleted file mode 100644 index fc3ebb2599..0000000000 --- a/packages/core/agent/src/invariant.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** Package-owned agent lifecycle invariants. @module @deepseek-ai/dsh-agent/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { Agent, AgentStatus } from '@deepseek-ai/dsh-agent' - -const PACKAGE_NAME = '@deepseek-ai/dsh-agent' - -/** Cordis companion plugin name. */ -export const name = 'agent-invariant' -/** Services required before the companion can register. */ -export const inject = ['invariants'] - -/** Install the agent contribution into its child registration fiber. */ -const install: InvariantInstaller = (ctx, fail) => { - const lastStatus = new WeakMap() - ctx.on('agent/status', ({ agent, status }) => { - const previous = lastStatus.get(agent) - if (previous === status) { - fail(`agent/status repeated ${status} (no-op transition)`) - } - lastStatus.set(agent, status) - }, { global: true }) -} - -/** - * Register the agent 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)) diff --git a/packages/core/agent/tests/invariant.spec.ts b/packages/core/agent/tests/invariant.spec.ts deleted file mode 100644 index d6d1d6f8da..0000000000 --- a/packages/core/agent/tests/invariant.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import type { Agent } from '@deepseek-ai/dsh-agent' -import * as AgentInvariant from '@deepseek-ai/dsh-agent/invariant' -import { scopeTarget } from '@deepseek-ai/dsh-scope' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(AgentInvariant) - return ctx -} - -function mockAgent(id: string): Agent { - return { id } as unknown as Agent -} - -describe('agent status invariants', () => { - it('accepts lifecycle transitions between idle and running', async () => { - const ctx = await setup() - const agent = mockAgent('a1') - expect(() => { - ctx.emit(scopeTarget(agent, agent), 'agent/status', { agent, status: 'idle' }) - ctx.emit(scopeTarget(agent, agent), 'agent/status', { agent, status: 'running' }) - ctx.emit(scopeTarget(agent, agent), 'agent/status', { agent, status: 'idle' }) - }).not.toThrow() - }) - - it('rejects a no-op transition', async () => { - const ctx = await setup() - const agent = mockAgent('a3') - ctx.emit(scopeTarget(agent, agent), 'agent/status', { agent, status: 'running' }) - expect(() => { ctx.emit(scopeTarget(agent, agent), 'agent/status', { agent, status: 'running' }) }) - .toThrow(/no-op transition/) - }) - - it('tracks agents independently', async () => { - const ctx = await setup() - const a = mockAgent('a5') - const b = mockAgent('b5') - ctx.emit(scopeTarget(a, a), 'agent/status', { agent: a, status: 'running' }) - expect(() => { ctx.emit(scopeTarget(b, b), 'agent/status', { agent: b, status: 'running' }) }).not.toThrow() - }) -}) diff --git a/packages/core/agent/tsconfig.json b/packages/core/agent/tsconfig.json index a65ffd9212..b597fc7068 100644 --- a/packages/core/agent/tsconfig.json +++ b/packages/core/agent/tsconfig.json @@ -29,9 +29,6 @@ { "path": "../../core/system-prompt" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../typert/protocol" }, diff --git a/packages/core/agent/tsdown.config.ts b/packages/core/agent/tsdown.config.ts index 3a0934ccf8..76c21c44d4 100644 --- a/packages/core/agent/tsdown.config.ts +++ b/packages/core/agent/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from 'tsdown' -/** Build the package root and companions as independent bundles. */ +/** Build the package root as a self-contained bundle. */ export default defineConfig([ { entry: ['lib/types/index.js'], @@ -12,14 +12,4 @@ export default defineConfig([ dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, - }, ]) diff --git a/packages/core/scope/README.i18n.yaml b/packages/core/scope/README.i18n.yaml index 0b818aafe7..030e218e1c 100644 --- a/packages/core/scope/README.i18n.yaml +++ b/packages/core/scope/README.i18n.yaml @@ -33,8 +33,8 @@ en: 1d19d421fa6f3546 zh: ed6679cfc9166f14 /deepseek-ai-dsh-scope/understand-the-implementation/source-map: - en: 52dd18180452c174 - zh: 47d0198266697794 + en: 95547c563d2f7677 + zh: 8a830f9643455207 /deepseek-ai-dsh-scope/understand-the-implementation/the-parent-chain: en: f0c433a9b0091de4 zh: 21cecb4194aff7d0 diff --git a/packages/core/scope/README.md b/packages/core/scope/README.md index 72c41e4c1a..9c6a6c55e4 100644 --- a/packages/core/scope/README.md +++ b/packages/core/scope/README.md @@ -65,8 +65,6 @@ The registration context determines both visibility and ownership: a registratio |---|---| | [`src/index.ts`](src/index.ts) | `createScope`, `scopeOf`, `scopeTarget`, `bindScopeParent`/`scopeParentOf`/`scopeChainOf`, carrier marks | | [`src/store.ts`](src/store.ts) | `ScopedLayers`, `NamedEntries`, `AnonymousEntries`, `ScopeLayer` | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion over the generated scoped-event map | -| [`src/scoped-events.generated.ts`](src/scoped-events.generated.ts) | Generated resolver map of declared scoped events | ### The parent chain diff --git a/packages/core/scope/README.zh.md b/packages/core/scope/README.zh.md index b48842570c..918aba4723 100644 --- a/packages/core/scope/README.zh.md +++ b/packages/core/scope/README.zh.md @@ -65,8 +65,6 @@ await scope.dispose() // unwinds every registration made through scope.ctx |---|---| | [`src/index.ts`](src/index.ts) | `createScope`、`scopeOf`、`scopeTarget`、`bindScopeParent`/`scopeParentOf`/`scopeChainOf`、载体标记 | | [`src/store.ts`](src/store.ts) | `ScopedLayers`、`NamedEntries`、`AnonymousEntries`、`ScopeLayer` | -| [`src/invariant.ts`](src/invariant.ts) | 基于生成的作用域事件映射的不变式配套 | -| [`src/scoped-events.generated.ts`](src/scoped-events.generated.ts) | 已声明带作用域事件的生成解析器映射 | ### 父链 diff --git a/packages/core/scope/package.json b/packages/core/scope/package.json index aec0ba1ee8..fc015c0d52 100644 --- a/packages/core/scope/package.json +++ b/packages/core/scope/package.json @@ -18,25 +18,18 @@ "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" ], "license": "MIT", "peerDependencies": { - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/cordis": "workspace:~" }, "devDependencies": { - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/cordis": "workspace:~" } } diff --git a/packages/core/scope/src/invariant.ts b/packages/core/scope/src/invariant.ts deleted file mode 100644 index b478b9417d..0000000000 --- a/packages/core/scope/src/invariant.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** Package-owned scoped-dispatch invariants. @module @deepseek-ai/dsh-scope/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import { carrierKeyOf, isScopeCarrier } from '@deepseek-ai/dsh-scope' -import { scopedSubjectResolverFor } from './scoped-events.generated.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-scope' - -/** Cordis companion plugin name. */ -export const name = 'scope-invariant' -/** Services required before the companion can register. */ -export const inject = ['invariants'] - -/** Install the scoped-dispatch contribution into its child registration fiber. */ -const install: InvariantInstaller = (ctx, fail) => { - ctx.on('internal/dispatch', (_mode, eventName, args, thisArg) => { - const subjectOf = scopedSubjectResolverFor(eventName) - if (subjectOf === undefined) return - if (!isScopeCarrier(thisArg)) { - fail( - `"${eventName}" is a scope-filtered event but was dispatched without a scope carrier — ` - + 'pass scopeTarget(base, subject) as the dispatch thisArg (agent events: use agentEvents(ctx, agent))', - ) - } - if (subjectOf !== null && carrierKeyOf(thisArg) !== subjectOf(args)) { - fail( - `"${eventName}" was dispatched with a scope carrier keyed to a DIFFERENT subject than its arguments name — ` - + 'the carrier key and the event\'s subject must be the same object (use agentEvents(ctx, agent))', - ) - } - }, { global: true }) -} - -/** - * Register the scope 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)) diff --git a/packages/core/scope/src/scoped-events.generated.ts b/packages/core/scope/src/scoped-events.generated.ts deleted file mode 100644 index d0e4a9cb97..0000000000 --- a/packages/core/scope/src/scoped-events.generated.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Generated scoped-event routing-subject resolvers for dsh-scope invariants. - * Do not edit by hand; run `pnpm run gen-scoped-events`. - * - * @module @deepseek-ai/dsh-scope/scoped-events.generated - */ - -type ScopedSubjectResolver = (args: readonly unknown[]) => unknown - -const scopedSubjectResolvers: Readonly> = Object.freeze({ - 'agent/assistant-stream': args => (args[0] as Record)['agent'], - 'agent/created': args => (args[0] as Record)['agent'], - 'agent/disposed': args => (args[0] as Record)['agent'], - 'agent/error': args => (args[0] as Record)['agent'], - 'agent/inbox/claimed': args => (args[0] as Record)['agent'], - 'agent/inbox/discarded': args => (args[0] as Record)['agent'], - 'agent/inbox/inserted': args => (args[0] as Record)['agent'], - 'agent/pre-step': args => (args[0] as Record)['agent'], - 'agent/request': args => (args[0] as Record)['agent'], - 'agent/request-error': args => (args[0] as Record)['agent'], - 'agent/status': args => (args[0] as Record)['agent'], - 'agent/turn-stopping': args => (args[0] as Record)['agent'], - 'approval/request': args => (args[0] as Record)['agent'], - 'goal/changed': args => (args[0] as Record)['agent'], - 'session/created': null, - 'session/disposed': null, - 'session/event': null, - 'session/flush': null, - 'subagent/end': null, - 'subagent/start': null, - 'system-prompt/assemble': args => (args[1] as Record)['scope'], - 'tools/execute': args => (args[0] as Record)['agent'], - 'tools/post-execute': args => (args[0] as Record)['agent'], - 'tools/pre-execute': args => (args[0] as Record)['agent'], - 'tools/ptc-dispatch-log': args => (args[0] as Record)['agent'], - 'tools/result': args => (args[0] as Record)['agent'], - 'user-questions/request': args => (args[0] as Record)['agent'], -}) - -/** - * Resolve the routing key named by one scoped event payload. A null - * resolver means the payload cannot expose its external routing key, so the - * invariant checks carrier presence only. - * @param event - runtime Cordis event name. - * @returns the generated subject resolver, null for presence-only, - * or undefined when the event is not scope-filtered. - */ -export function scopedSubjectResolverFor(event: string): ScopedSubjectResolver | null | undefined { - return scopedSubjectResolvers[event] -} diff --git a/packages/core/scope/tests/invariant.spec.ts b/packages/core/scope/tests/invariant.spec.ts deleted file mode 100644 index 8e3e358a27..0000000000 --- a/packages/core/scope/tests/invariant.spec.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { freezeMessage, MessageId } from '@deepseek-ai/dsh-llm' -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import type { Events } from '@deepseek-ai/cordis' -import type { Agent } from '@deepseek-ai/dsh-agent' -import { scopeTarget } from '@deepseek-ai/dsh-scope' -import * as ScopeInvariant from '@deepseek-ai/dsh-scope/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(ScopeInvariant) - return ctx -} - -function emit(ctx: Context, receiver: object | undefined, event: string, args: unknown[]): void { - const dispatch = ctx.emit.bind(ctx) as (...values: unknown[]) => void - if (receiver === undefined) dispatch(event, ...args) - else dispatch(receiver, event, ...args) -} - -describe('scoped-dispatch invariants', () => { - type AgentEventName = Extract - type EventArgs = Events[K] extends (...args: infer Args) => unknown ? Args : never - - it('ignores ordinary events and rejects a scoped dispatch without a carrier', async () => { - const ctx = await setup() - expect(() => { emit(ctx, undefined, 'ordinary/event', []) }).not.toThrow() - const agent = { id: 'a1' } - expect(() => { emit(ctx, undefined, 'agent/error', [{ agent, turn: 1, step: 0, error: new Error('x') }]) }) - .toThrow(/dispatched without a scope carrier/) - }) - - it('checks every generated subject resolver against the carrier key', async () => { - const ctx = await setup() - const agent = { id: 'a1' } as unknown as Agent - const other = { id: 'a2' } as unknown as Agent - const signal = new AbortController().signal - const config = { provider: 'p', model: 'm' } - const message = freezeMessage({ - id: MessageId('m'), - role: 'user', - content: [], - source: { kind: 'user' }, - }) - const agentRows = { - 'agent/created': [{ agent, source: 'startup' }], - 'agent/disposed': [{ agent }], - 'agent/status': [{ agent, status: 'idle' }], - 'agent/inbox/inserted': [{ agent, message }], - 'agent/inbox/claimed': [{ agent, message, turn: 1 }], - 'agent/inbox/discarded': [{ agent, message }], - 'agent/pre-step': [{ agent, messages: [message], turn: 1, step: 1, signal }, () => Promise.resolve({ kind: 'enter', messages: [message] })], - 'agent/request': [{ agent, turn: 1, step: 1, signal }, () => Promise.resolve(config)], - 'agent/assistant-stream': [{ - agent, - frame: { - type: 'start', attemptId: 'attempt-1' as never, revision: 1, turn: 1, step: 1, - }, - }], - 'agent/request-error': [ - { - agent, - turn: 1, - step: 1, - provider: 'p', - failure: { message: 'request', code: 'UNKNOWN' }, - retryPolicy: undefined, - signal, - }, - () => Promise.resolve(undefined), - ], - 'agent/turn-stopping': [{ agent, turn: 1, signal }], - 'agent/error': [{ agent, turn: 1, step: 0, error: new Error('x') }], - } satisfies { [K in AgentEventName]: EventArgs } - const rows: Array<[string, unknown[]]> = [ - ...Object.entries(agentRows), - ['approval/request', [{ agent, toolName: 'echo' }, () => Promise.resolve('unavailable')]], - ['goal/changed', [{ agent, change: { operation: 'create', ref: { id: 'goal-a', revision: 1 } } }]], - ['system-prompt/assemble', [[], { scope: agent }]], - ['tools/ptc-dispatch-log', [{ exec: { callId: 'c', name: 't', arguments: {} }, agent, subCallId: 'c:code:1', name: 't', isError: false, content: [] }, () => Promise.resolve([])]], - ['tools/execute', [{ callId: 'c', name: 't', arguments: {}, agent }, () => Promise.resolve({ content: [], isError: false })]], - ['tools/post-execute', [{ callId: 'c', name: 't', arguments: {}, agent }, { content: [], isError: false }, () => Promise.resolve({ kind: 'accept' })]], - ['tools/pre-execute', [{ callId: 'c', name: 't', arguments: {}, agent }, () => Promise.resolve({ kind: 'allow' })]], - ['tools/result', [{ callId: 'c', name: 't', arguments: {}, agent }, { content: [], isError: false }]], - ['user-questions/request', [{ agent, questions: [] }, () => Promise.resolve({ answers: [] })]], - ] - - for (const [event, args] of rows) { - expect(() => { emit(ctx, scopeTarget(agent, agent), event, args) }, `${event} matching`).not.toThrow() - expect(() => { emit(ctx, scopeTarget(agent, other), event, args) }, `${event} mismatched`) - .toThrow(/DIFFERENT subject/) - } - }) - - it('requires carriers for generated presence-only scoped events without comparing a payload subject', async () => { - const ctx = await setup() - const agent = { id: 'a1' } - const rows: Array<[string, unknown[]]> = [ - ['session/created', [{}]], - ['session/disposed', [{}]], - ['session/event', [{}, {}]], - ['session/flush', [{}]], - ['subagent/end', [{}]], - ['subagent/start', [{}]], - ] - for (const [event, args] of rows) { - expect(() => { emit(ctx, scopeTarget(agent, agent), event, args) }, `${event} carrier`).not.toThrow() - expect(() => { emit(ctx, undefined, event, args) }, `${event} no carrier`) - .toThrow(/dispatched without a scope carrier/) - } - }) -}) diff --git a/packages/core/scope/tsconfig.json b/packages/core/scope/tsconfig.json index bb46910c07..754725418e 100644 --- a/packages/core/scope/tsconfig.json +++ b/packages/core/scope/tsconfig.json @@ -13,9 +13,6 @@ }, { "path": "../../../vendor/cordis" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/core/scope/tsdown.config.ts b/packages/core/scope/tsdown.config.ts index 1dbbf38372..76c21c44d4 100644 --- a/packages/core/scope/tsdown.config.ts +++ b/packages/core/scope/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from 'tsdown' -/** Build the package root and optional invariant companion as independent bundles. */ +/** Build the package root as a self-contained bundle. */ export default defineConfig([ { entry: ['lib/types/index.js'], @@ -12,16 +12,4 @@ export default defineConfig([ dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, - // Preserve the root entry's carrier WeakMap identity across bundles. - deps: { neverBundle: ['@deepseek-ai/dsh-scope'] }, - }, ]) diff --git a/packages/core/session/README.i18n.yaml b/packages/core/session/README.i18n.yaml index d2ab139d2c..c5627377d6 100644 --- a/packages/core/session/README.i18n.yaml +++ b/packages/core/session/README.i18n.yaml @@ -42,8 +42,8 @@ en: bbfb53c50100ab13 zh: 303a8ee47f4f656a /deepseek-ai-dsh-session/understand-the-implementation/source-map: - en: 1268738282846f1d - zh: 75a9fe0d5088ea09 + en: c596a8f74b4f1b79 + zh: a29f23c4c1d1ff75 /deepseek-ai-dsh-session/understand-the-implementation/append-validation: en: 5c09583bc8e1ffc6 zh: 3b25552de2d1f9b6 diff --git a/packages/core/session/README.md b/packages/core/session/README.md index 4a73a20c95..64ac976851 100644 --- a/packages/core/session/README.md +++ b/packages/core/session/README.md @@ -101,7 +101,6 @@ The package is built on event sourcing: a `Session` is an append-only log of typ | [`src/request-header.ts`](src/request-header.ts) | `request/header` folding and reconstruction | | [`dsh-util-values`](../../util/values/README.md) | Shared lossless JSON validation and detached snapshots | | [`src/repair.ts`](src/repair.ts) | Shared tool-result recovery for failed steps, interrupted logs, and fork seeds | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: seq, turn/step enclosure, tool call/result pairing | ### Append validation diff --git a/packages/core/session/README.zh.md b/packages/core/session/README.zh.md index 7fb334cfaa..1435c9fe3e 100644 --- a/packages/core/session/README.zh.md +++ b/packages/core/session/README.zh.md @@ -101,7 +101,6 @@ session.deriveMessages() // the derived model history | [`src/request-header.ts`](src/request-header.ts) | `request/header` 折叠与重建 | | [`dsh-util-values`](../../util/values/README.zh.md) | 共享无损 JSON 校验与分离式快照 | | [`src/repair.ts`](src/repair.ts) | 失败步骤、中断日志与 fork 种子共享的工具结果恢复 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式配套:序号、轮次/步骤闭合、工具调用/结果配对 | ### 追加校验 diff --git a/packages/core/session/package.json b/packages/core/session/package.json index 7bdcdaebd8..0267cbba33 100644 --- a/packages/core/session/package.json +++ b/packages/core/session/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -39,7 +35,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -49,7 +44,6 @@ "@deepseek-ai/cordis": "workspace:~" }, "devDependencies": { - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/dsh-typert-protocol": "workspace:*", "@deepseek-ai/dsh-typert-registry": "workspace:*", diff --git a/packages/core/session/src/index.ts b/packages/core/session/src/index.ts index b0cfa0c19b..c3b783bf38 100644 --- a/packages/core/session/src/index.ts +++ b/packages/core/session/src/index.ts @@ -49,7 +49,6 @@ declare module '@deepseek-ai/cordis' { * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners * receive only sessions entered through that agent's context. * @param session - the session just entered and announced. - * @dshScopeScan unsupported * @mode emit */ 'session/created'(this: Scoped, session: Session): void @@ -59,7 +58,6 @@ declare module '@deepseek-ai/cordis' { * did not begin. Listener failures are logged and contained. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope. * @param session - the session that is no longer live in the store. - * @dshScopeScan unsupported * @mode emit */ 'session/disposed'(this: Scoped, session: Session): void @@ -71,7 +69,6 @@ declare module '@deepseek-ai/cordis' { * receive only events from sessions entered through that agent's context. * @param session - the session whose log grew. * @param event - the appended event, exactly as recorded. - * @dshScopeScan unsupported * @mode emit */ 'session/event'(this: Scoped, session: Session, event: SessionEvent): void @@ -80,7 +77,6 @@ declare module '@deepseek-ai/cordis' { * caller awaits all of them, with no waterfall veto. Scope-filtered dispatch * (`@deepseek-ai/dsh-scope`) reuses the session's owner scope. * @param session - the session whose buffered events must reach durable storage. - * @dshScopeScan unsupported * @mode parallel */ 'session/flush'(this: Scoped, session: Session): Promise | void @@ -1184,8 +1180,8 @@ export class SessionStore extends Service { * store owns the carrier, so callers (the checkpoint policy's per-request * barrier, goal-round-driver's idle checkpoint, teardown drains, and consumers * that flush themselves before reading storage) must come through here - * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner, - * one spelling, and the scoped-dispatch invariant can pin it. + * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner + * and one spelling. * @param session - the session whose buffered events must reach durable storage. * @returns whether at least one durability listener participated, after every * listener has settled successfully. diff --git a/packages/core/session/src/invariant.ts b/packages/core/session/src/invariant.ts deleted file mode 100644 index 154c953438..0000000000 --- a/packages/core/session/src/invariant.ts +++ /dev/null @@ -1,258 +0,0 @@ -/** - * Package-owned relational invariants for the session event log. Load this - * companion beside `@deepseek-ai/dsh-invariants` to enable the checks. - * - * @module @deepseek-ai/dsh-session/invariant - */ - -import type { Context } from '@deepseek-ai/cordis' -import type { ToolCallId } from '@deepseek-ai/dsh-llm' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { Session, SessionEvent, SessionSeqCursor } from '@deepseek-ai/dsh-session' -import { assertNever } from '@deepseek-ai/dsh-util-values' -import { TOOL_NOT_STARTED } from './repair.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-session' - -/** Cordis companion plugin name. */ -export const name = 'session-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Per-session bookkeeping for relational log checks. */ -interface SessionTrace { - lastSeq: SessionSeqCursor - openTurn: number | null - openStep: number | null - nextTurn: number - nextStep: number - pendingCalls: Set -} - -/** One accepted event's deferred mutation of a committed session trace. */ -interface SessionTraceTransition { - scalars: Pick - pendingCalls: - | { kind: 'none' } - | { kind: 'add' | 'delete'; callId: ToolCallId } - | { kind: 'clear' } -} - -/** Assert that a step-scoped event names the currently open turn and step. */ -function requireOpenStep( - trace: SessionTrace, - kind: string, - turn: number, - step: number, - fail: InvariantFailure, -): void { - if (trace.openTurn !== turn || trace.openStep !== step) { - fail(`${kind} names turn ${turn}/step ${step} but open is turn ${trace.openTurn}/step ${trace.openStep}`) - } -} - -/** Validate one candidate event without mutating the committed trace. */ -function validateEvent( - trace: SessionTrace, - event: SessionEvent, - fail: InvariantFailure, -): SessionTraceTransition { - if (event.seq <= trace.lastSeq) { - fail(`seq must strictly increase: saw ${event.seq} after ${trace.lastSeq}`) - } - let openTurn = trace.openTurn - let openStep = trace.openStep - let nextTurn = trace.nextTurn - let nextStep = trace.nextStep - let pendingCalls: SessionTraceTransition['pendingCalls'] = { kind: 'none' } - - // Context and plugin-owned log-only events may be appended between model - // executions. Core execution events retain their explicit turn relations. - switch (event.type) { - case 'turn/start': { - if (trace.openTurn !== null) { - fail(`turn/start ${event.data.turn} while turn ${trace.openTurn} is still open`) - } - if (event.data.turn !== trace.nextTurn) { - fail(`turn/start expected turn ${trace.nextTurn}, got ${event.data.turn}`) - } - openTurn = event.data.turn - nextStep = 1 - break - } - case 'turn/end': { - if (trace.openTurn !== event.data.turn) { - fail(`turn/end ${event.data.turn} does not match open turn ${trace.openTurn}`) - } - if (trace.openStep !== null) { - fail(`turn/end ${event.data.turn} while step ${trace.openStep} is still open`) - } - openTurn = null - nextTurn += 1 - break - } - case 'step/start': { - if (trace.openTurn !== event.data.turn) { - fail(`step/start in turn ${event.data.turn} but open turn is ${trace.openTurn}`) - } - if (trace.openStep !== null) { - fail(`step/start ${event.data.step} while step ${trace.openStep} is still open`) - } - if (event.data.step !== trace.nextStep) { - fail(`step/start expected step ${trace.nextStep} in turn ${event.data.turn}, got ${event.data.step}`) - } - openStep = event.data.step - break - } - case 'developer/message': { - requireOpenStep(trace, 'developer/message', event.data.turn, event.data.step, fail) - break - } - case 'step/end': { - requireOpenStep(trace, 'step/end', event.data.turn, event.data.step, fail) - pendingCalls = { kind: 'clear' } - openStep = null - nextStep += 1 - break - } - case 'assistant/attempt': { - requireOpenStep(trace, 'assistant/attempt', event.data.turn, event.data.step, fail) - break - } - case 'assistant/message': { - requireOpenStep(trace, 'assistant/message', event.data.turn, event.data.step, fail) - break - } - case 'tool/call': { - requireOpenStep(trace, 'tool/call', event.data.turn, event.data.step, fail) - pendingCalls = { kind: 'add', callId: event.data.callId } - break - } - case 'tool/result': { - // Session has already validated a content rewrite that cites its replaced event. - // It is durable turn work, not a second execution of the original call. - if (event.surfaceOp !== 'append') { - if (trace.openTurn === null) { - fail('tool/result surface replacement appended outside any open turn') - } - break - } - requireOpenStep(trace, 'tool/result', event.data.turn, event.data.step, fail) - const callId = event.data.message.source.callId - const syntheticNotStarted = event.data.message.isError === true && event.data.error?.code === TOOL_NOT_STARTED - if (!trace.pendingCalls.has(callId) && !syntheticNotStarted) { - fail(`tool/result for ${callId} with no prior tool/call in this step`) - } - pendingCalls = { kind: 'delete', callId } - break - } - case 'system/message': { - requireOpenStep(trace, 'system/message', event.data.turn, event.data.step, fail) - break - } - case 'user/message': - break - case 'session/end-seed': - // Unconstrained: an unbalanced seed legally puts it inside an open turn. - break - case 'request/header': - case 'request/context': { - if (trace.openTurn === null) { - fail(`${event.type} appended outside any open turn (core execution events must be turn-enclosed)`) - } - break - } - default: - // Merge-extensible event relations belong to their owning plugin. - break - } - return { - scalars: { lastSeq: event.seq, openTurn, openStep, nextTurn, nextStep }, - pendingCalls, - } -} - -/** Apply one already-validated transition after its event commits. */ -function applyTransition(trace: SessionTrace, transition: SessionTraceTransition): void { - Object.assign(trace, transition.scalars) - switch (transition.pendingCalls.kind) { - case 'none': - break - case 'add': - trace.pendingCalls.add(transition.pendingCalls.callId) - break - case 'delete': - trace.pendingCalls.delete(transition.pendingCalls.callId) - break - case 'clear': - trace.pendingCalls.clear() - break - /* v8 ignore next -- validateEvent produces this closed transition union */ - default: - assertNever(transition.pendingCalls, 'session trace pending-call transition') - } -} - -/** Install the session contribution into its child registration fiber. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const traces = new WeakMap() - const stagedTransitions = new WeakMap() - - const freshTrace = (): SessionTrace => ({ - lastSeq: -1, - openTurn: null, - openStep: null, - nextTurn: 1, - nextStep: 1, - pendingCalls: new Set(), - }) - - const seedSession = (session: Session): SessionTrace => { - const trace = freshTrace() - traces.set(session, trace) - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) { - applyTransition(trace, validateEvent(trace, event, fail)) - } - return trace - } - - /* v8 ignore next -- session/event always follows list() or session/created seeding */ - const traceFor = (session: Session): SessionTrace => traces.get(session) ?? seedSession(session) - - for (const session of ctx.sessions.list()) seedSession(session) - - ctx.on('session/created', (session) => { seedSession(session) }, { global: true }) - - ctx.on('session/event', (session, event) => { - const staged = stagedTransitions.get(event) - /* v8 ignore next 2 -- internal/dispatch stages the exact callback arguments */ - if (staged === undefined || staged.session !== session) { - return fail('session/event reached publication without matching pre-commit validation') - } - stagedTransitions.delete(event) - applyTransition(staged.trace, staged.transition) - }, { global: true }) - - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - const trace = traceFor(session) - const transition = validateEvent(trace, event, fail) - // A later dispatch listener may veto. Validation is pure, so abandoning - // this weakly keyed transition does not advance or retain the session. - stagedTransitions.set(event, { session, trace, transition }) - }, { global: true }) -}, { inject: ['sessions'] }) - -/** - * Register the session 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)) diff --git a/packages/core/session/src/types.ts b/packages/core/session/src/types.ts index 593c86d52a..8458137659 100644 --- a/packages/core/session/src/types.ts +++ b/packages/core/session/src/types.ts @@ -414,8 +414,8 @@ export interface SessionEventMap { * keep ordinary restore and replay lifecycle boundaries. * * Only the `Session` constructor and `buildForkSeed` may create this marker. - * The invariant companion deliberately constrains nothing here, so a plugin - * appending one would silently classify every live bracket before it as seed history. + * Session append does not reject other writers, so a plugin appending one + * would silently classify every live bracket before it as seed history. * * An owner of a standalone open/close bracket (`compaction/start` … * `compaction/end`) reads it because seed history and live work are otherwise diff --git a/packages/core/session/tests/invariant.spec.ts b/packages/core/session/tests/invariant.spec.ts deleted file mode 100644 index db75fbc744..0000000000 --- a/packages/core/session/tests/invariant.spec.ts +++ /dev/null @@ -1,469 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import { createScope, scopeTarget } from '@deepseek-ai/dsh-scope' -import { createDeveloperMessage, createSystemMessage, createUserMessage, ToolCallId, createMessage, createToolResultMessage, freezeMessage } from '@deepseek-ai/dsh-llm' -import type { ContextFormed } from '@deepseek-ai/dsh-llm' -import SessionStore, { SessionId, SessionSeq, TOOL_NOT_STARTED } from '@deepseek-ai/dsh-session' -import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant' -import InvariantRegistry, { InvariantError } from '@deepseek-ai/dsh-invariants' - -declare module '@deepseek-ai/dsh-llm' { - interface MessageSourceMap { - 'test': { kind: 'test' } & ContextFormed - } -} - -async function setup(): Promise<{ ctx: Context; fiber: Awaited> }> { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry) - const fiber = await ctx.plugin(SessionInvariant) - return { ctx, fiber } -} - -describe('session-log invariants', () => { - it('keeps registration global when the companion is mounted under a scope', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry) - let scopedCtx!: Context - await ctx.plugin(Object.assign((inner: Context) => { - scopedCtx = createScope(inner, {}).ctx - }, { inject: ['sessions', 'invariants'] })) - await scopedCtx.plugin(SessionInvariant) - const session = ctx.sessions.create(SessionId('global-under-scoped-invariants')) - expect(() => { - session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - }).not.toThrow() - }) - - it('requires developer messages to belong to the open turn and step', async () => { - const { ctx } = await setup() - try { - const session = ctx.sessions.create() - const message = createDeveloperMessage({ content: [{ type: 'tool-removal', toolName: 'search' }], source: { kind: 'test' } }) - const append = (turn: number, step: number) => session.append('developer/message', { turn, step, message }, { surfaceOp: 'append' }) - expect(() => append(1, 1)).toThrow('developer/message names turn 1/step 1') - session.append('turn/start', { turn: 1 }) - expect(() => append(1, 1)).toThrow('developer/message names turn 1/step 1') - session.append('step/start', { turn: 1, step: 1 }) - expect(() => append(2, 1)).toThrow('developer/message names turn 2/step 1') - expect(() => append(1, 2)).toThrow('developer/message names turn 1/step 2') - expect(() => append(1, 1)).not.toThrow() - expect(session.deriveMessages()).toEqual([message]) - session.append('step/end', { turn: 1, step: 1 }) - expect(() => append(1, 1)).toThrow('developer/message names turn 1/step 1') - } finally { - await ctx.fiber.dispose() - } - }) - - it('accepts a well-formed turn, step, and tool sequence', async () => { - const { ctx } = await setup() - const session = ctx.sessions.create() - expect(() => { - session.append('turn/start', { turn: 1 }) - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - session.append('step/start', { turn: 1, step: 1 }) - session.append('assistant/attempt', { - turn: 1, step: 1, - stream: [{ type: 'text-chunks', time0: 1, index: 0, dt: [], texts: ['h'] }], - }) - session.append('assistant/message', { - stream: [], - turn: 1, - step: 1, - message: createMessage({ - role: 'assistant', - content: [{ type: 'tool-call', id: ToolCallId('c1'), name: 'echo', arguments: '{}' }], - source: { - kind: 'model', - ...{ provider: 'mock', model: 'mock' }, - }, - }), - }, { surfaceOp: 'append' }) - session.append('tool/call', { turn: 1, step: 1, callId: ToolCallId('c1'), name: 'echo', arguments: '{}' }) - session.append('tool/result', { - turn: 1, step: 1, - message: createToolResultMessage({ - callId: ToolCallId('c1'), - content: [], - isError: false, - }), - }, { surfaceOp: 'append' }) - session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - }).not.toThrow() - }) - - it('does not advance committed trace state when a later dispatch listener vetoes', async () => { - const { ctx } = await setup() - const session = ctx.sessions.create(SessionId('dispatch-veto-rollback')) - let veto = true - ctx.on('internal/dispatch', (_mode, name) => { - if (name !== 'session/event' || !veto) return - veto = false - throw new Error('later dispatch veto') - }) - expect(() => session.append('turn/start', { - turn: 1, - })).toThrow('later dispatch veto') - expect(session.snapshotEvents()).toEqual([]) - expect(() => { - session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - }).not.toThrow() - }) - - it('applies the committed transition after another postcommit observer throws', async () => { - const { ctx } = await setup() - const warnings: string[] = [] - ctx.logger.warn = ((message: unknown) => { warnings.push(String(message)) }) as typeof ctx.logger.warn - const session = ctx.sessions.create(SessionId('postcommit-peer')) - ctx.on('session/event', () => { throw new Error('hostile observer') }, { prepend: true }) - expect(() => { - session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - }).not.toThrow() - expect(warnings).toHaveLength(2) - }) - - it('rejects non-monotonic event sequence numbers', async () => { - const { ctx } = await setup() - const session = ctx.sessions.create() - ctx.emit(scopeTarget(session, undefined), 'session/event', session, { - type: 'turn/start', - seq: 0, - time: 1, - data: { turn: 1 }, - } as never) - expect(() => { ctx.emit(scopeTarget(session, undefined), 'session/event', session, { - type: 'turn/end', - seq: 0, - time: 2, - data: { turn: 1, reason: { kind: 'completed' } }, - } as never) }).toThrow(/seq must strictly increase/) - }) - - it('enforces turn numbering and core execution enclosure', async () => { - const first = await setup() - const open = first.ctx.sessions.create() - open.append('turn/start', { turn: 1 }) - expect(() => open.append('turn/start', { turn: 2 })) - .toThrow(/turn 1 is still open/) - expect(() => open.append('turn/end', { turn: 2, reason: { kind: 'completed' } })) - .toThrow(/does not match open turn 1/) - - const second = (await setup()).ctx.sessions.create() - second.append('turn/start', { turn: 1 }) - second.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - expect(() => second.append('turn/start', { turn: 3 })) - .toThrow(/expected turn 2, got 3/) - - const third = (await setup()).ctx.sessions.create() - third.append('turn/start', { turn: 1 }) - third.append('step/start', { turn: 1, step: 1 }) - third.append('step/end', { turn: 1, step: 1 }) - expect(() => third.append('turn/end', { turn: 1, reason: { kind: 'completed' } })) - .not.toThrow() - - const enclosed = (await setup()).ctx.sessions.create() - enclosed.append('turn/start', { turn: 1 }) - enclosed.append('step/start', { turn: 1, step: 1 }) - expect(() => enclosed.append('request/header', { - header: { config: { provider: 'mock', model: 'mock' } }, - reason: 'initial', - } as never)).not.toThrow() - expect(() => enclosed.append('request/context', { - provider: 'mock', model: 'mock', - })).not.toThrow() - - const outside = (await setup()).ctx.sessions.create() - expect(() => outside.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'idle context' }], - source: { kind: 'test' }, - }), { surfaceOp: 'append' })).not.toThrow() - // Route capacity is core execution state like the header beside it. - expect(() => outside.append('request/context', { - provider: 'mock', - model: 'm', - contextWindow: 128_000, - })).toThrow(/outside any open turn/) - // The owning plugin decides whether a merge-extensible event is log-only. - const appendUnknown = outside.append.bind(outside) as (type: string, data: unknown) => unknown - expect(() => { appendUnknown('plugin/marker', {}) }).not.toThrow() - expect(() => outside.append('turn/start', { - turn: 1, - })).not.toThrow() - }) - - it('enforces open-step identity and numbering', async () => { - const wrongTurn = (await setup()).ctx.sessions.create() - wrongTurn.append('turn/start', { turn: 1 }) - expect(() => wrongTurn.append('step/start', { turn: 2, step: 1 })).toThrow(/open turn is 1/) - - const nested = (await setup()).ctx.sessions.create() - nested.append('turn/start', { turn: 1 }) - nested.append('step/start', { turn: 1, step: 1 }) - expect(() => nested.append('step/start', { turn: 1, step: 2 })).toThrow(/while step 1 is still open/) - expect(() => nested.append('turn/end', { turn: 1, reason: { kind: 'completed' } })) - .toThrow(/while step 1 is still open/) - expect(() => nested.append('step/end', { turn: 1, step: 2 })).toThrow(/open is turn 1\/step 1/) - expect(() => nested.append('assistant/message', { - stream: [], - turn: 1, - step: 2, - message: createMessage({ - role: 'assistant', - content: [], - source: { - kind: 'model', - ...{ provider: 'mock', model: 'mock' }, - }, - }), - }, { surfaceOp: 'append' })).toThrow(/open is turn 1\/step 1/) - - const skipped = (await setup()).ctx.sessions.create() - skipped.append('turn/start', { turn: 1 }) - skipped.append('step/start', { turn: 1, step: 1 }) - skipped.append('step/end', { turn: 1, step: 1 }) - expect(() => skipped.append('step/start', { turn: 1, step: 3 })) - .toThrow(/expected step 2 in turn 1, got 3/) - - expect(() => skipped.append('turn/end', { - turn: 1, - reason: { kind: 'completed' }, - })).not.toThrow() - }) - - it('requires step-scoped stream and tool events to name the open step', async () => { - const chunk = (await setup()).ctx.sessions.create() - chunk.append('turn/start', { turn: 1 }) - expect(() => chunk.append('assistant/attempt', { - turn: 1, - step: 1, - stream: [{ type: 'text-chunks', time0: 1, index: 0, dt: [], texts: ['x'] }], - })).toThrow(/open is turn 1\/step null/) - - const tool = (await setup()).ctx.sessions.create() - tool.append('turn/start', { turn: 1 }) - tool.append('step/start', { turn: 1, step: 1 }) - expect(() => tool.append('tool/result', { - turn: 1, - step: 1, - message: createToolResultMessage({ - callId: ToolCallId('ghost'), - content: [], - isError: false, - }), - }, { surfaceOp: 'append' })).toThrow(/no prior tool\/call/) - }) - - it('requires a system/message to name the open step', async () => { - const session = (await setup()).ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - const message = createSystemMessage('You are terse.') - expect(() => session.append('system/message', { turn: 1, step: 1, message }, { surfaceOp: 'append' })) - .toThrow(/open is turn 1\/step null/) - session.append('step/start', { turn: 1, step: 1 }) - expect(() => session.append('system/message', { turn: 1, step: 1, message }, { surfaceOp: 'append' })).not.toThrow() - }) - - it('keeps fresh tool-result appends open-step checked', async () => { - const { ctx } = await setup() - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - expect(() => session.append('tool/result', { - turn: 1, - step: 1, - message: createToolResultMessage({ - callId: ToolCallId('closed'), - content: [], - isError: false, - }), - }, { surfaceOp: 'append' })).toThrow(/open is turn 1\/step null/) - }) - - it('treats a validated tool-result replacement as a turn-enclosed rewrite', async () => { - const { ctx } = await setup() - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('step/start', { turn: 1, step: 1 }) - session.append('tool/call', { - turn: 1, - step: 1, - callId: ToolCallId('rewrite'), - name: 'echo', - arguments: '{}', - }) - const original = session.append('tool/result', { - turn: 1, - step: 1, - message: createToolResultMessage({ - callId: ToolCallId('rewrite'), - content: [{ type: 'text', text: 'original' }], - isError: false, - }), - }, { surfaceOp: 'append' }) - session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - - session.append('turn/start', { turn: 2 }) - expect(() => session.append('tool/result', { - ...original.data, - message: freezeMessage({ - ...original.data.message, - content: [{ type: 'text', text: 'pruned' }], - }), - }, { - surfaceOp: { op: 'replace', startSeq: original.seq, endSeq: original.seq }, - sourceEventSeqs: [original.seq], - })).not.toThrow() - }) - - it('rejects a tool-result replacement outside a turn', async () => { - const { ctx } = await setup() - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('step/start', { turn: 1, step: 1 }) - session.append('tool/call', { - turn: 1, - step: 1, - callId: ToolCallId('rewrite'), - name: 'echo', - arguments: '{}', - }) - const original = session.append('tool/result', { - turn: 1, - step: 1, - message: createToolResultMessage({ - callId: ToolCallId('rewrite'), - content: [{ type: 'text', text: 'original' }], - isError: false, - }), - }, { surfaceOp: 'append' }) - session.append('step/end', { turn: 1, step: 1 }) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - - expect(() => session.append('tool/result', { - ...original.data, - message: freezeMessage({ - ...original.data.message, - content: [{ type: 'text', text: 'pruned' }], - }), - }, { - surfaceOp: { op: 'replace', startSeq: original.seq, endSeq: original.seq }, - sourceEventSeqs: [original.seq], - })).toThrow(/outside any open turn/) - }) - - it('allows not-started repair results and unresolved calls at step end', async () => { - const repaired = (await setup()).ctx.sessions.create() - expect(() => { - repaired.append('turn/start', { turn: 1 }) - repaired.append('step/start', { turn: 1, step: 1 }) - repaired.append('tool/result', { - turn: 1, - step: 1, - message: createToolResultMessage({ - callId: ToolCallId('crashed'), - content: [], - isError: true, - }), - error: { name: 'ToolNotStartedError', code: TOOL_NOT_STARTED }, - }, { surfaceOp: 'append' }) - repaired.append('step/end', { turn: 1, step: 1 }) - repaired.append('turn/end', { turn: 1, reason: { kind: 'interrupted' } }) - }).not.toThrow() - - const unresolved = (await setup()).ctx.sessions.create() - expect(() => { - unresolved.append('turn/start', { turn: 1 }) - unresolved.append('step/start', { turn: 1, step: 1 }) - unresolved.append('tool/call', { turn: 1, step: 1, callId: ToolCallId('c1'), name: 'echo', arguments: '{}' }) - unresolved.append('step/end', { turn: 1, step: 1 }) - unresolved.append('turn/end', { turn: 1, reason: { kind: 'error', error: { message: 'boom', code: 'UNKNOWN' } } }) - }).not.toThrow() - }) - - it('does not let a result in a later step satisfy an earlier call', async () => { - const { ctx } = await setup() - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('step/start', { turn: 1, step: 1 }) - session.append('tool/call', { turn: 1, step: 1, callId: ToolCallId('c1'), name: 'echo', arguments: '{}' }) - session.append('step/end', { turn: 1, step: 1 }) - session.append('step/start', { turn: 1, step: 2 }) - expect(() => session.append('tool/result', { - turn: 1, - step: 2, - message: createToolResultMessage({ - callId: ToolCallId('c1'), - content: [], - isError: false, - }), - }, { surfaceOp: 'append' })).toThrow(/no prior tool\/call in this step/) - }) - - it('replays seeded sessions and tracks each session independently', async () => { - const { ctx } = await setup() - const badSeed = [ - { type: 'turn/start' as const, seq: SessionSeq(0), time: 0, data: { turn: 1 } }, - { type: 'turn/start' as const, seq: SessionSeq(1), time: 0, data: { turn: 2 } }, - ] - expect(() => ctx.sessions.create(undefined, { seed: badSeed })).toThrow(InvariantError) - - const a = ctx.sessions.create(SessionId('a')) - const b = ctx.sessions.create(SessionId('b')) - a.append('turn/start', { turn: 1 }) - expect(() => b.append('turn/start', { turn: 1 })) - .not.toThrow() - }) - - it('rebuilds trace state for sessions that exist when the companion reloads', async () => { - const { ctx, fiber } = await setup() - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('step/start', { turn: 1, step: 1 }) - await fiber.dispose() - await ctx.plugin(SessionInvariant) - expect(() => session.append('assistant/attempt', { - turn: 1, - step: 1, - stream: [{ type: 'text-chunks', time0: 1, index: 0, dt: [], texts: ['h'] }], - })).not.toThrow() - expect(() => session.append('turn/start', { turn: 2 })) - .toThrow(/turn 1 is still open/) - }) - - it('accepts end-seed whether or not a turn is open', async () => { - const { ctx } = await setup() - // Balanced seed: between turns. - expect(() => ctx.sessions.create(SessionId('inherited-between-turns'), { seed: [ - { type: 'turn/start', seq: SessionSeq(0), time: 1, data: { turn: 1 } }, - { type: 'turn/end', seq: SessionSeq(1), time: 2, data: { turn: 1, reason: { kind: 'completed' } } }, - ] })).not.toThrow() - // Unbalanced seed: inside the open turn, which the relation permits. - const open = ctx.sessions.create(SessionId('inherited-inside-open-turn'), { seed: [ - { type: 'turn/start', seq: SessionSeq(0), time: 1, data: { turn: 1 } }, - ] }) - expect(open.snapshotEvents().map(event => event.type)).toEqual(['turn/start', 'session/end-seed']) - // Still open afterwards: the boundary moves no cursor. - expect(() => open.append('turn/start', { turn: 2 })) - .toThrow(/turn 1 is still open/) - expect(() => open.append('turn/end', { turn: 1, reason: { kind: 'completed' } })).not.toThrow() - }) - - it('removes all listeners when the companion is disposed', async () => { - const { ctx, fiber } = await setup() - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - await fiber.dispose() - expect(() => session.append('turn/start', { - turn: 2, - })).not.toThrow() - }) -}) diff --git a/packages/core/session/tsconfig.json b/packages/core/session/tsconfig.json index a581e0a78d..d4e73cafb6 100644 --- a/packages/core/session/tsconfig.json +++ b/packages/core/session/tsconfig.json @@ -26,9 +26,6 @@ { "path": "../../core/scope" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../typert/protocol" } diff --git a/packages/core/session/tsdown.config.ts b/packages/core/session/tsdown.config.ts index e92275a7f5..76c21c44d4 100644 --- a/packages/core/session/tsdown.config.ts +++ b/packages/core/session/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from 'tsdown' -/** Build the package root and optional invariant companion as independent bundles. */ +/** Build the package root as a self-contained bundle. */ export default defineConfig([ { entry: ['lib/types/index.js'], @@ -12,14 +12,4 @@ export default defineConfig([ dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, - }, ]) diff --git a/packages/core/system-prompt/README.i18n.yaml b/packages/core/system-prompt/README.i18n.yaml index d05e3b8c57..77fa522bc1 100644 --- a/packages/core/system-prompt/README.i18n.yaml +++ b/packages/core/system-prompt/README.i18n.yaml @@ -39,8 +39,8 @@ en: c91963b89a866fed zh: ef9ef4940145778f /deepseek-ai-dsh-system-prompt/understand-the-implementation/source-map: - en: a93197314e803d86 - zh: 8cc650f60e868631 + en: d9575bcb07a1145b + zh: 0f1a388780d055b1 /deepseek-ai-dsh-system-prompt/understand-the-implementation/assembly-and-rendering: en: 54977e6c431f851e zh: ef60007054b97ff0 diff --git a/packages/core/system-prompt/README.md b/packages/core/system-prompt/README.md index a76ae5c66f..a7de1d4ecc 100644 --- a/packages/core/system-prompt/README.md +++ b/packages/core/system-prompt/README.md @@ -100,7 +100,6 @@ The package is a registry plus a cooperative assembly pipeline. One `assemble()` | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `SystemPrompt` service, config, assembly pipeline, `renderPrompt` | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion | ### Assembly and rendering diff --git a/packages/core/system-prompt/README.zh.md b/packages/core/system-prompt/README.zh.md index 98c6e73339..41e41227de 100644 --- a/packages/core/system-prompt/README.zh.md +++ b/packages/core/system-prompt/README.zh.md @@ -100,7 +100,6 @@ ctx.systemPrompt.variable('cwd', ({ agent }) => agent?.session.header.cwd) | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`SystemPrompt` 服务、配置、组装流水线、`renderPrompt` | -| [`src/invariant.ts`](src/invariant.ts) | 不变式配套 | ### 组装与渲染 diff --git a/packages/core/system-prompt/package.json b/packages/core/system-prompt/package.json index 748263b4c8..fe4bc2cd2f 100644 --- a/packages/core/system-prompt/package.json +++ b/packages/core/system-prompt/package.json @@ -18,21 +18,15 @@ "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" ], "license": "MIT", "peerDependencies": { - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/cordis": "workspace:~" @@ -41,7 +35,6 @@ "@deepseek-ai/schemastery": "workspace:~" }, "devDependencies": { - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/cordis": "workspace:~" diff --git a/packages/core/system-prompt/src/invariant.ts b/packages/core/system-prompt/src/invariant.ts deleted file mode 100644 index bbe587516f..0000000000 --- a/packages/core/system-prompt/src/invariant.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** Package-owned prompt-assembly invariants. @module @deepseek-ai/dsh-system-prompt/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { PromptAssembly } from './index.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-system-prompt' -const VARIABLE_NAME = /^[a-z][a-z0-9_]*$/ - -/** Cordis companion plugin name. */ -export const name = 'system-prompt-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Validate the authoritative assembly returned by the waterfall. */ -function validateAssembly(assembly: PromptAssembly, fail: InvariantFailure): void { - const sectionNames = new Set() - for (const section of assembly.sections) { - if (section.name.length === 0) fail('assembled section names must be non-empty') - if (sectionNames.has(section.name)) fail(`assembled section name ${JSON.stringify(section.name)} is duplicated`) - sectionNames.add(section.name) - if (typeof section.text !== 'string') fail(`assembled section ${JSON.stringify(section.name)} text must be a string`) - } - - const contextNames = new Set() - for (const context of assembly.contexts) { - if (context.name.length === 0) fail('assembled context names must be non-empty') - if (contextNames.has(context.name)) fail(`assembled context name ${JSON.stringify(context.name)} is duplicated`) - contextNames.add(context.name) - if (typeof context.text !== 'string') fail(`assembled context ${JSON.stringify(context.name)} text must be a string`) - } - - for (const tool of assembly.tools) { - if (tool.name.length === 0) fail('assembled tool names must be non-empty') - } - - for (const [name, value] of Object.entries(assembly.variables)) { - if (!VARIABLE_NAME.test(name)) fail(`assembled variable name ${JSON.stringify(name)} is invalid`) - if (value !== undefined && typeof value !== 'string') { - fail(`assembled variable ${JSON.stringify(name)} must be a string or undefined`) - } - } -} - -/** Install validation around the authoritative assembly waterfall result. */ -const install: InvariantInstaller = (ctx, fail) => { - ctx.on('system-prompt/assemble', async (_assembly, _context, next) => { - const assembled = await next() - validateAssembly(assembled, fail) - return assembled - }, { global: true, prepend: true }) -} - -/** - * Register the system-prompt 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)) diff --git a/packages/core/system-prompt/tests/invariant.spec.ts b/packages/core/system-prompt/tests/invariant.spec.ts deleted file mode 100644 index 08231a9779..0000000000 --- a/packages/core/system-prompt/tests/invariant.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import type { PromptAssembly } from '@deepseek-ai/dsh-system-prompt' -import * as SystemPromptInvariant from '@deepseek-ai/dsh-system-prompt/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SystemPromptInvariant) - return ctx -} - -const valid = (): PromptAssembly => ({ - sections: [{ name: 'identity', text: 'prompt' }], - contexts: [{ name: 'policy', text: 'current policy' }], - tools: [{ name: 'echo', description: 'Echo', parameters: {} }], - variables: { cwd: '/repo', optional: undefined }, -}) - -async function assemble(ctx: Context, result: PromptAssembly): Promise { - return ctx.waterfall( - ctx as never, 'system-prompt/assemble', valid(), {}, - () => Promise.resolve(result), - ) -} - -describe('system-prompt invariants', () => { - it('accepts a well-formed authoritative assembly', async () => { - const ctx = await setup() - await expect(assemble(ctx, valid())).resolves.toEqual(valid()) - }) - - it.each([ - [{ ...valid(), sections: [{ name: '', text: 'x' }] }, /section names must be non-empty/], - [{ ...valid(), sections: [{ name: 'x', text: 'a' }, { name: 'x', text: 'b' }] }, /section name "x" is duplicated/], - [{ ...valid(), sections: [{ name: 'x', text: 1 as never }] }, /section "x" text must be a string/], - [{ ...valid(), contexts: [{ name: '', text: 'x' }] }, /context names must be non-empty/], - [{ ...valid(), contexts: [{ name: 'x', text: 'a' }, { name: 'x', text: 'b' }] }, /context name "x" is duplicated/], - [{ ...valid(), contexts: [{ name: 'x', text: 1 as never }] }, /context "x" text must be a string/], - [{ ...valid(), tools: [{ name: '', description: 'x', parameters: {} }] }, /tool names must be non-empty/], - [{ ...valid(), variables: { Bad: 'x' } }, /variable name "Bad" is invalid/], - [{ ...valid(), variables: { value: 1 as never } }, /variable "value" must be a string or undefined/], - ])('rejects malformed authoritative assembly %#', async (assembly, message) => { - const ctx = await setup() - await expect(assemble(ctx, assembly)).rejects.toThrow(message) - }) -}) diff --git a/packages/core/system-prompt/tsconfig.json b/packages/core/system-prompt/tsconfig.json index 6fe8d764a3..91e7bf1ba4 100644 --- a/packages/core/system-prompt/tsconfig.json +++ b/packages/core/system-prompt/tsconfig.json @@ -22,9 +22,6 @@ }, { "path": "../../core/scope" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/core/tools/README.i18n.yaml b/packages/core/tools/README.i18n.yaml index a33e9507b1..cc83780e54 100644 --- a/packages/core/tools/README.i18n.yaml +++ b/packages/core/tools/README.i18n.yaml @@ -39,8 +39,8 @@ en: 92e8af53e6067bad zh: 33c7b99b42c5acac /deepseek-ai-dsh-tools/understand-the-implementation/source-map: - en: 7f61ed30a231b7ba - zh: c7587af984650f8b + en: c14e20d6a5c4c408 + zh: ab9dc0fd38006749 /deepseek-ai-dsh-tools/understand-the-implementation/execution-and-cancellation: en: dc44a88997777988 zh: 56454aeace1c8b77 diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index b96592b137..0a532bee4b 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -116,7 +116,6 @@ The registry holds typed `ToolDefinition`s in scoped layers and projects them on | [`src/ptc.ts`](src/ptc.ts) | PTC mode: SDK generation, `run_code` dispatch bridge, settlement | | [`src/ts-types.ts`](src/ts-types.ts) | TypeScript SDK type rendering | | [`src/py-types.ts`](src/py-types.ts) | Python SDK type rendering | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion | ### Execution and cancellation diff --git a/packages/core/tools/README.zh.md b/packages/core/tools/README.zh.md index dfd729c2bc..9c7e7e055e 100644 --- a/packages/core/tools/README.zh.md +++ b/packages/core/tools/README.zh.md @@ -116,7 +116,6 @@ ctx.tools.register(defineTool({ | [`src/ptc.ts`](src/ptc.ts) | PTC mode:SDK 生成、`run_code` 分发桥接层、结算 | | [`src/ts-types.ts`](src/ts-types.ts) | TypeScript SDK 类型渲染 | | [`src/py-types.ts`](src/py-types.ts) | Python SDK 类型渲染 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式配套 | ### 执行与取消 diff --git a/packages/core/tools/package.json b/packages/core/tools/package.json index aad896486b..8019ca00d0 100644 --- a/packages/core/tools/package.json +++ b/packages/core/tools/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -35,7 +31,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -44,7 +39,6 @@ "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-ptc-runtime": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", @@ -62,7 +56,6 @@ "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-ptc-runtime": "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/core/tools/src/invariant.ts b/packages/core/tools/src/invariant.ts deleted file mode 100644 index d5edf99697..0000000000 --- a/packages/core/tools/src/invariant.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** Package-owned tool-pipeline invariants. @module @deepseek-ai/dsh-tools/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { ToolExecution, ToolExecutionResult } from './index.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-tools' - -/** Cordis companion plugin name. */ -export const name = 'tools-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -type ToolStage = 'pre' | 'execute' | 'post' - -/** Validate the immutable final execution/result snapshot. */ -function validateResult( - exec: Readonly, - result: Readonly, - fail: InvariantFailure, -): void { - if (!Object.isFrozen(exec)) fail('tools/result execution must be frozen before publication') - if (!Object.isFrozen(result) || !Object.isFrozen(result.content)) { - fail('tools/result outcome and content must be frozen before publication') - } - if (exec.name.length === 0 || String(exec.callId).length === 0) { - fail('tools/result execution must carry non-empty name and callId') - } -} - -/** Install monotonic pipeline, final-snapshot, and PTC dispatch enclosure checks. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const stages = new WeakMap() - const openTurns = new WeakMap() - const dispatchRoots = new WeakMap>() - const validateDispatch = (session: Session, event: SessionEvent): void => { - if (event.type !== 'tool/ptc-dispatch-start' && event.type !== 'tool/ptc-dispatch') return - const root = String(event.data.rootCallId) - const parent = String(event.data.parentCallId) - const child = String(event.data.subCallId) - if (root.length === 0 || parent.length === 0 || child.length === 0) { - fail(`${event.type} must carry non-empty rootCallId, parentCallId, and subCallId`) - return - } - const roots = dispatchRoots.get(session) - const known = roots?.get(child) - if (known !== undefined && known !== root) fail(`${event.type} changed rootCallId for subCallId ${child}`) - if (parent !== root && roots?.get(parent) !== root) { - fail(`${event.type} parentCallId ${parent} does not belong to rootCallId ${root}`) - } - } - const commitDispatch = (session: Session, event: SessionEvent): void => { - if (event.type !== 'tool/ptc-dispatch-start' && event.type !== 'tool/ptc-dispatch') return - const roots = dispatchRoots.get(session) as Map - roots.set(String(event.data.subCallId), String(event.data.rootCallId)) - } - const seed = (session: Session): number | null => { - let openTurn: number | null = null - dispatchRoots.set(session, new Map()) - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) { - validateDispatch(session, event) - commitDispatch(session, event) - if (event.type === 'turn/start') openTurn = event.data.turn - else if (event.type === 'turn/end') openTurn = null - else if ((event.type === 'tool/ptc-dispatch-start' || event.type === 'tool/ptc-dispatch') - && openTurn === null) { - fail(`${event.type} appended outside any open turn`) - } - } - openTurns.set(session, openTurn) - return openTurn - } - const openTurnFor = (session: Session): number | null => openTurns.get(session) ?? seed(session) - - for (const session of ctx.sessions.list()) seed(session) - ctx.on('session/created', (session) => { seed(session) }, { global: true }) - ctx.on('session/event', (session, event) => { - validateDispatch(session, event) - commitDispatch(session, event) - if (event.type === 'turn/start') openTurns.set(session, event.data.turn) - else if (event.type === 'turn/end') openTurns.set(session, null) - }, { global: true }) - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName === 'session/event') { - const [session, event] = args as [Session, SessionEvent] - validateDispatch(session, event) - if ((event.type === 'tool/ptc-dispatch-start' || event.type === 'tool/ptc-dispatch') - && openTurnFor(session) === null) { - fail(`${event.type} appended outside any open turn`) - } - return - } - if (eventName === 'tools/pre-execute') { - const exec = args[0] as ToolExecution - if (stages.has(exec)) fail('tools/pre-execute repeated for one execution') - stages.set(exec, 'pre') - return - } - if (eventName === 'tools/execute') { - const exec = args[0] as ToolExecution - if (stages.get(exec) !== 'pre') fail('tools/execute must follow tools/pre-execute') - stages.set(exec, 'execute') - return - } - if (eventName === 'tools/post-execute') { - const exec = args[0] as ToolExecution - const previous = stages.get(exec) - if (previous !== 'pre' && previous !== 'execute') { - fail('tools/post-execute must follow tools/pre-execute or tools/execute') - } - stages.set(exec, 'post') - return - } - if (eventName !== 'tools/result') return - const [exec, result] = args as [Readonly, Readonly] - validateResult(exec, result, fail) - stages.delete(exec) - }, { global: true }) -}, { inject: ['sessions'] }) - -/** - * Register the tools 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)) diff --git a/packages/core/tools/tests/invariant.spec.ts b/packages/core/tools/tests/invariant.spec.ts deleted file mode 100644 index 3cf19afa5c..0000000000 --- a/packages/core/tools/tests/invariant.spec.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import { scopeTarget } from '@deepseek-ai/dsh-scope' -import { ToolCallId } from '@deepseek-ai/dsh-llm' -import SessionStore, { Session, SessionId } from '@deepseek-ai/dsh-session' -import type { ToolExecution, ToolExecutionResult, ToolExecutionToken } from '@deepseek-ai/dsh-tools' -import * as ToolsInvariant from '@deepseek-ai/dsh-tools/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -const testToolSignal = new AbortController().signal - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(ToolsInvariant) - return ctx -} - -const execution = (overrides: Partial = {}): ToolExecution => ({ - token: Symbol('tool') as ToolExecutionToken, - callId: ToolCallId('call-1'), - name: 'echo', - arguments: Object.freeze({ text: 'hi' }), - ...overrides, - signal: overrides.signal ?? testToolSignal, - rootCallId: overrides.rootCallId ?? overrides.callId ?? ToolCallId('call-1'), -}) - -const outcome = (): ToolExecutionResult => Object.freeze({ - content: Object.freeze([{ type: 'text' as const, text: 'ok' }]) as never, - isError: false, - value: null, -}) - -function emitResult(ctx: Context, exec: ToolExecution, result: ToolExecutionResult): void { - ctx.emit(scopeTarget(ctx as never, undefined), 'tools/result', exec, result) -} - -async function stage(ctx: Context, name: 'tools/pre-execute' | 'tools/execute', exec: ToolExecution): Promise { - if (name === 'tools/pre-execute') { - await ctx.waterfall(ctx as never, name, exec, () => Promise.resolve({ kind: 'allow' as const })) - } else { - await ctx.waterfall(ctx as never, name, exec, () => Promise.resolve(outcome())) - } -} - -describe('tool-pipeline invariants', () => { - it('accepts dispatch and denial stage orders with frozen results', async () => { - const ctx = await setup() - const dispatched = execution() - await stage(ctx, 'tools/pre-execute', dispatched) - await stage(ctx, 'tools/execute', dispatched) - await ctx.waterfall(ctx as never, 'tools/post-execute', dispatched, outcome(), () => Promise.resolve({ kind: 'accept' as const })) - Object.freeze(dispatched) - emitResult(ctx, dispatched, outcome()) - - const denied = execution({ callId: ToolCallId('call-2') }) - await stage(ctx, 'tools/pre-execute', denied) - await ctx.waterfall(ctx as never, 'tools/post-execute', denied, outcome(), () => Promise.resolve({ kind: 'accept' as const })) - Object.freeze(denied) - emitResult(ctx, denied, outcome()) - ctx.emit('tools/change') - }) - - it('rejects repeated and out-of-order pipeline stages', async () => { - const ctx = await setup() - const exec = execution() - await stage(ctx, 'tools/pre-execute', exec) - await expect(stage(ctx, 'tools/pre-execute', exec)).rejects.toThrow(/repeated/) - - const noPre = execution({ callId: ToolCallId('call-2') }) - await expect(stage(ctx, 'tools/execute', noPre)).rejects.toThrow(/must follow tools\/pre-execute/) - expect(() => ctx.waterfall( - ctx as never, 'tools/post-execute', noPre, outcome(), - () => Promise.resolve({ kind: 'accept' as const }), - )).toThrow(/must follow tools\/pre-execute or tools\/execute/) - }) - - it('rejects mutable or anonymous final snapshots', async () => { - const ctx = await setup() - expect(() => { emitResult(ctx, execution(), outcome()) }).toThrow(/execution must be frozen/) - - const exec = Object.freeze(execution()) - expect(() => { emitResult(ctx, exec, { content: [], isError: false, value: null }) }) - .toThrow(/outcome and content must be frozen/) - - const anonymous = Object.freeze(execution({ name: '' })) - expect(() => { emitResult(ctx, anonymous, outcome()) }).toThrow(/non-empty name and callId/) - }) - - it('requires ptc-dispatch records to be turn-enclosed', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - const data = { - rootCallId: ToolCallId('parent'), - parentCallId: ToolCallId('parent'), - subCallId: ToolCallId('child'), - name: 'echo', - arguments: {}, - } - expect(() => session.append('tool/ptc-dispatch-start', data)).toThrow(/outside any open turn/) - session.append('turn/start', { turn: 1 }) - expect(() => session.append('tool/ptc-dispatch-start', data)).not.toThrow() - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - }) - - it('does not commit a rejected dispatch edge into the root index', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - expect(() => session.append('tool/ptc-dispatch-start', { - rootCallId: ToolCallId('rejected-root'), - parentCallId: ToolCallId('rejected-root'), - subCallId: ToolCallId('reused-child'), - name: 'echo', - arguments: {}, - })).toThrow(/outside any open turn/) - - session.append('turn/start', { turn: 1 }) - expect(() => session.append('tool/ptc-dispatch-start', { - rootCallId: ToolCallId('accepted-root'), - parentCallId: ToolCallId('accepted-root'), - subCallId: ToolCallId('reused-child'), - name: 'echo', - arguments: {}, - })).not.toThrow() - }) - - it('rejects a nested PTC dispatch that changes its parent chain root before append', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('tool/ptc-dispatch-start', { - rootCallId: ToolCallId('root'), - parentCallId: ToolCallId('root'), - subCallId: ToolCallId('child'), - name: 'run_code', - arguments: {}, - }) - session.append('tool/ptc-dispatch-start', { - rootCallId: ToolCallId('root'), - parentCallId: ToolCallId('child'), - subCallId: ToolCallId('grandchild'), - name: 'echo', - arguments: {}, - }) - - expect(() => session.append('tool/ptc-dispatch-start', { - rootCallId: ToolCallId('another-root'), - parentCallId: ToolCallId('child'), - subCallId: ToolCallId('invalid-grandchild'), - name: 'echo', - arguments: {}, - })).toThrow(/parentCallId child does not belong to rootCallId another-root/) - expect(session.snapshotEvents().some(event => event.type === 'tool/ptc-dispatch-start' - && String(event.data.subCallId) === 'invalid-grandchild')).toBe(false) - }) - - it('requires non-empty dispatch identities and keeps one subcall on one root', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - expect(() => session.append('tool/ptc-dispatch-start', { - rootCallId: ToolCallId(''), - parentCallId: ToolCallId('root'), - subCallId: ToolCallId('child'), - name: 'echo', - arguments: {}, - })).toThrow(/must carry non-empty rootCallId/) - - session.append('tool/ptc-dispatch-start', { - rootCallId: ToolCallId('root'), - parentCallId: ToolCallId('root'), - subCallId: ToolCallId('child'), - name: 'echo', - arguments: {}, - }) - expect(() => session.append('tool/ptc-dispatch-start', { - rootCallId: ToolCallId('other-root'), - parentCallId: ToolCallId('other-root'), - subCallId: ToolCallId('child'), - name: 'echo', - arguments: {}, - })).toThrow(/changed rootCallId for subCallId child/) - }) - - it('indexes dispatch records emitted for a bare session', async () => { - const ctx = await setup() - const session = Session.create(SessionId('bare-dispatch-session')) - session.append('turn/start', { turn: 1 }) - expect(() => { - ctx.emit('session/event', session as never, { - type: 'tool/ptc-dispatch-start', - seq: 1, - time: 1, - data: { - rootCallId: ToolCallId('root'), - parentCallId: ToolCallId('root'), - subCallId: ToolCallId('child'), - name: 'echo', - arguments: {}, - }, - } as never) - }).not.toThrow() - }) - - it('replays enclosed ptc-dispatch records on late registration', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('tool/ptc-dispatch', { - rootCallId: ToolCallId('parent'), - parentCallId: ToolCallId('parent'), - subCallId: ToolCallId('child'), - name: 'echo', - arguments: {}, - isError: false, - content: [{ type: 'text', text: 'ok' }], - }) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.plugin(InvariantRegistry) - await expect(ctx.plugin(ToolsInvariant).then(() => undefined)).resolves.toBeUndefined() - }) - - it('rejects an unenclosed ptc-dispatch record on late registration', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - ctx.sessions.create().append('tool/ptc-dispatch-start', { - rootCallId: ToolCallId('parent'), - parentCallId: ToolCallId('parent'), - subCallId: ToolCallId('child'), - name: 'echo', - arguments: {}, - }) - await ctx.plugin(InvariantRegistry) - await expect(ctx.plugin(ToolsInvariant).then(() => undefined)).rejects.toThrow(/outside any open turn/) - }) -}) diff --git a/packages/core/tools/tsconfig.json b/packages/core/tools/tsconfig.json index 669b55d8d4..5c04b53e2a 100644 --- a/packages/core/tools/tsconfig.json +++ b/packages/core/tools/tsconfig.json @@ -20,6 +20,9 @@ { "path": "../../../vendor/cordis" }, + { + "path": "../../../vendor/schemastery" + }, { "path": "../../llm/llm" }, @@ -35,9 +38,6 @@ { "path": "../../interaction/user-approval" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../sandbox/sandbox" }, diff --git a/packages/credentials/authorization/README.i18n.yaml b/packages/credentials/authorization/README.i18n.yaml index 1ccc6810b1..330417765c 100644 --- a/packages/credentials/authorization/README.i18n.yaml +++ b/packages/credentials/authorization/README.i18n.yaml @@ -36,8 +36,8 @@ en: d4cbeb418479eb5b zh: b254988fae29eadc /deepseek-ai-dsh-authorization/understand-the-implementation/source-map: - en: b208bb36b648e2a3 - zh: d3580b3589a88c84 + en: 675985adda601f82 + zh: 62c07748bcad5728 /deepseek-ai-dsh-authorization/understand-the-implementation/lifecycle: en: 535c027fe250a32a zh: c0372ee717e035a2 @@ -60,5 +60,5 @@ en: 0f8ee6f7e6acf8d4 zh: 97dda218cc5cd287 /deepseek-ai-dsh-authorization/known-limitations-and-deferred-work/dev-note: - en: 31a73d9f4e68d98e - zh: 43bf2a9b2fcd06d1 + en: eff14570a6292a5a + zh: c3d604f9298d102e diff --git a/packages/credentials/authorization/README.md b/packages/credentials/authorization/README.md index 4952b08de3..ee69135f1f 100644 --- a/packages/credentials/authorization/README.md +++ b/packages/credentials/authorization/README.md @@ -99,7 +99,6 @@ This section explains the design decisions behind the seam and points at the cod |---|---| | [`src/index.ts`](src/index.ts) | Service Definition: flow registry, one-attempt-per-key lifecycle, interaction routing, commit confirmation | | [`src/types.ts`](src/types.ts) | Wire-safe vocabulary: methods, notices, prompts, outcomes, entries | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: `authorization/settled` always names a released key | ### Lifecycle @@ -158,7 +157,7 @@ These limits define when this package is a poor fit or needs special care. They This Dev Note is working context for maintainers: open questions and undecided directions. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above, the package code, and the linked Agent Note. -The limitations above name the open directions — resumable attempts, server-side revocation, orphaned-record discovery — each needing its own design and store before landing. The invariant companion is the one load-bearing runtime check: settlement must always find the key released, because a wedged key is indistinguishable from a busy one and only a restart frees it. +The limitations above name the open directions — resumable attempts, server-side revocation, orphaned-record discovery — each needing its own design and store before landing. diff --git a/packages/credentials/authorization/README.zh.md b/packages/credentials/authorization/README.zh.md index 3c4b0b1200..c05f4baea8 100644 --- a/packages/credentials/authorization/README.zh.md +++ b/packages/credentials/authorization/README.zh.md @@ -99,7 +99,6 @@ flow 声明它写入的凭据记录、面向用户的标签以及它提供的登 |---|---| | [`src/index.ts`](src/index.ts) | Service Definition:flow 注册表、每键单尝试生命周期、交互路由、提交确认 | | [`src/types.ts`](src/types.ts) | 跨进程安全的词汇:方法、notice、prompt、结果、entry | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:`authorization/settled` 点名的键必已释放 | ### 生命周期 @@ -158,7 +157,7 @@ notice 是单向的,且从不携带机密:一条消息,以及可选的「 本开发备注是维护者的工作上下文:开放问题与尚未决定的探索方向。它明确不具权威性——已交付的行为、限制与既定理由以上文、包代码和相关 Agent Note 为准。 -上文限制点名的开放方向——可恢复的尝试、服务端吊销、孤儿记录发现——每一项落地前都需要各自的设计与存储。不变式伴生插件是唯一承重的运行时检查:结算时键必须已释放,因为卡死的键与繁忙的键无法区分,只有重启才能释放它。 +上文限制点名的开放方向——可恢复的尝试、服务端吊销、孤儿记录发现——每一项落地前都需要各自的设计与存储。 diff --git a/packages/credentials/authorization/package.json b/packages/credentials/authorization/package.json index 2a6ed92ff2..11985ecbf8 100644 --- a/packages/credentials/authorization/package.json +++ b/packages/credentials/authorization/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -31,20 +27,17 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], "license": "MIT", "peerDependencies": { "@deepseek-ai/dsh-credentials": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/cordis": "workspace:~" }, "devDependencies": { "@deepseek-ai/dsh-credentials": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/cordis": "workspace:~" } diff --git a/packages/credentials/authorization/src/index.ts b/packages/credentials/authorization/src/index.ts index fcd71640fe..f54e04896a 100644 --- a/packages/credentials/authorization/src/index.ts +++ b/packages/credentials/authorization/src/index.ts @@ -329,14 +329,12 @@ export class AuthorizationService extends Service { /** * Fan `authorization/settled` out with contained listener failures: every * listener runs, and a sync throw or async rejection is logged without - * changing the finished attempt's own outcome — except `INVARIANT`-coded - * failures, which rethrow after every listener ran. The attempt is already + * changing the finished attempt's own outcome. The attempt is already * over and its key released when this fires, so a broken watcher (that * second browser tab) can never turn the caller's settled result into a * failure of its own. */ private settle(key: CredentialKey, settlement: AuthorizationSettlement): void { - let invariantFailure: unknown const args = ['authorization/settled', key, settlement] for (const listener of this.ctx.events.dispatch('emit', args) as Array<(...listenerArgs: unknown[]) => unknown>) { try { @@ -347,14 +345,9 @@ export class AuthorizationService extends Service { }) } } catch (error) { - if ((error as { code?: unknown } | null)?.code === 'INVARIANT') { - invariantFailure ??= error - continue - } this.warnSettledListenerFailure(key, error) } } - if (invariantFailure !== undefined) throw invariantFailure as Error } /* jscpd:ignore-end */ diff --git a/packages/credentials/authorization/src/invariant.ts b/packages/credentials/authorization/src/invariant.ts deleted file mode 100644 index ec89229566..0000000000 --- a/packages/credentials/authorization/src/invariant.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-authorization`. - * @module @deepseek-ai/dsh-authorization/invariant - */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' - -const PACKAGE_NAME = '@deepseek-ai/dsh-authorization' - -/** Cordis companion plugin name. */ -export const name = 'authorization-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** - * Install the single-flight release contract: `authorization/settled` names a - * finished attempt, and the seam admits one attempt per key, so the key must - * already be free when the event fires. A slot still held at settlement is - * unrecoverable — every later `begin()` for that key is refused as - * `ALREADY_IN_FLIGHT` until the process restarts — and it is invisible from the - * outside, because a wedged key looks exactly like a busy one. - */ -const install: InvariantInstaller = (ctx: Context, fail: InvariantFailure) => { - ctx.on('authorization/settled', (key) => { - const authorization = ctx.get('authorization') - if (authorization === undefined) { - fail(`authorization/settled for "${key}" emitted without a live authorization service`) - return - } - // A flow withdrawn during its own attempt settles with nothing left to - // describe, which is the disposer's documented behavior rather than a leak. - if (authorization.describe(key)?.inFlight === true) { - fail(`authorization/settled for "${key}" left the key in flight, wedging every later attempt`) - } - }) -} - -/** - * 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)) diff --git a/packages/credentials/authorization/tests/authorization.spec.ts b/packages/credentials/authorization/tests/authorization.spec.ts index 2a4f8a4ed3..175283d3f4 100644 --- a/packages/credentials/authorization/tests/authorization.spec.ts +++ b/packages/credentials/authorization/tests/authorization.spec.ts @@ -450,21 +450,6 @@ describe('the settled fan-out', () => { await new Promise(resolve => setTimeout(resolve, 10)) }) - it('rethrows an invariant-coded listener failure after the remaining listeners', async () => { - const ctx = await harness() - ctx.authorization.registerFlow(committingFlow(ctx)) - ctx.on('authorization/settled', () => { - throw Object.assign(new Error('forged relation'), { code: 'INVARIANT' }) - }) - const second = vi.fn() - ctx.on('authorization/settled', second) - - await expect(ctx.authorization.begin({ key: KEY, interaction: surface() })) - .rejects.toThrow(/forged relation/) - // Harness-fatal by design — but the record itself committed first. - expect(second).toHaveBeenCalledWith(KEY, 'authorized') - expect(await ctx.credentials.readRecord(KEY)).toEqual({ kind: 'grant', payload: { token: 'granted' } }) - }) }) it('rejects a commit attempted after local cancellation', async () => { diff --git a/packages/credentials/authorization/tests/invariant.spec.ts b/packages/credentials/authorization/tests/invariant.spec.ts deleted file mode 100644 index 9f49bdf58a..0000000000 --- a/packages/credentials/authorization/tests/invariant.spec.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import { credentialKey } from '@deepseek-ai/dsh-credentials' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import AuthorizationService from '@deepseek-ai/dsh-authorization' -import * as AuthorizationInvariant from '../src/invariant.ts' -import { MemoryCredentials } from './memory.ts' - -const KEY = credentialKey('llm-pi-ai', 'openai-codex') - -describe('authorization invariant companion', () => { - it('accepts an attempt that released its key before settling', async () => { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(AuthorizationInvariant) - await ctx.plugin(MemoryCredentials) - await ctx.plugin(AuthorizationService) - ctx.authorization.registerFlow({ - key: KEY, - label: 'ChatGPT (Codex)', - methods: [{ id: 'oauth', label: 'Sign in' }], - run: () => ctx.credentials - .modifyRecord(KEY, () => Promise.resolve({ kind: 'grant', payload: {} })) - .then(() => undefined), - }) - - await expect(ctx.authorization.begin({ - key: KEY, - interaction: { notify: () => {}, prompt: () => Promise.reject(new Error('unused')) }, - })).resolves.toEqual({ status: 'authorized' }) - }) - - it('fails a settlement that left its key in flight', async () => { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(AuthorizationInvariant) - await ctx.plugin(MemoryCredentials) - await ctx.plugin(AuthorizationService) - const started = Promise.withResolvers() - ctx.authorization.registerFlow({ - key: KEY, - label: 'ChatGPT (Codex)', - methods: [{ id: 'oauth', label: 'Sign in' }], - run: () => { - started.resolve(undefined) - return new Promise(() => {}) - }, - }) - void ctx.authorization.begin({ - key: KEY, - interaction: { notify: () => {}, prompt: () => Promise.reject(new Error('unused')) }, - }) - await started.promise - - expect(() => { ctx.emit('authorization/settled', KEY, 'authorized') }) - .toThrow(/left the key in flight/) - }) - - it('fails a settlement emitted without a live service', async () => { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(AuthorizationInvariant) - - expect(() => { ctx.emit('authorization/settled', KEY, 'cancelled') }) - .toThrow(/without a live authorization service/) - }) - - it('accepts a settlement whose flow left during its own attempt', async () => { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(AuthorizationInvariant) - await ctx.plugin(MemoryCredentials) - await ctx.plugin(AuthorizationService) - - expect(() => { ctx.emit('authorization/settled', KEY, 'cancelled') }).not.toThrow() - }) - - it('reserves the package name against duplicate registration', async () => { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(AuthorizationInvariant) - - expect(() => { - ctx.invariants.register('@deepseek-ai/dsh-authorization', () => {}) - }).toThrow(/already registered/) - }) -}) diff --git a/packages/credentials/authorization/tsconfig.json b/packages/credentials/authorization/tsconfig.json index cdf84f6f5d..414fd879c6 100644 --- a/packages/credentials/authorization/tsconfig.json +++ b/packages/credentials/authorization/tsconfig.json @@ -19,9 +19,6 @@ }, { "path": "../../llm/llm" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/credentials/credentials-local/README.i18n.yaml b/packages/credentials/credentials-local/README.i18n.yaml index e3d1566c94..4448779c37 100644 --- a/packages/credentials/credentials-local/README.i18n.yaml +++ b/packages/credentials/credentials-local/README.i18n.yaml @@ -45,8 +45,8 @@ en: 697bda9e5ee4dd28 zh: d7e23a8e8cd398e2 /deepseek-ai-dsh-credentials-local/understand-the-implementation/source-map: - en: ab20e314c0610350 - zh: ba991f1ea5bbd8f1 + en: a83715807279a902 + zh: fef1884e2a16bd46 /deepseek-ai-dsh-credentials-local/understand-the-implementation/resolution-and-write-paths: en: 8841486f9eda2f6b zh: 015a2bbfe8a6fdbf diff --git a/packages/credentials/credentials-local/README.md b/packages/credentials/credentials-local/README.md index 94d06bc48e..afcc903f06 100644 --- a/packages/credentials/credentials-local/README.md +++ b/packages/credentials/credentials-local/README.md @@ -143,7 +143,6 @@ This section explains the design decisions behind the provider and points at the | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Provider: layer resolution, strict document parse, reference and record write paths under the writer lock, watcher lifecycle, permissions check | -| — | No runtime invariant companion is published; the Service Definition companion (`dsh-credentials/invariant`) owns the `credentials/reference-updated` lifecycle contract; this provider's file/environment layering is asynchronous I/O pinned by its unit suite. | ### Resolution and write paths diff --git a/packages/credentials/credentials-local/README.zh.md b/packages/credentials/credentials-local/README.zh.md index 64566fe462..5273e774ee 100644 --- a/packages/credentials/credentials-local/README.zh.md +++ b/packages/credentials/credentials-local/README.zh.md @@ -143,7 +143,6 @@ records: | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 提供方:层解析、严格文档解析、写锁下的引用与记录写路径、watcher 生命周期、权限检查 | -| — | 未发布运行时不变式配套入口;Service Definition 的配套入口(`dsh-credentials/invariant`)负责 `credentials/reference-updated` 生命周期约定;本提供方的文件与环境分层属于异步 I/O,并由其单元测试套件加以约束。 | ### 解析与写入路径 diff --git a/packages/credentials/credentials-local/src/index.ts b/packages/credentials/credentials-local/src/index.ts index 98d70ee1f3..f00e31d00b 100644 --- a/packages/credentials/credentials-local/src/index.ts +++ b/packages/credentials/credentials-local/src/index.ts @@ -766,7 +766,7 @@ export class LocalCredentialProvider extends CredentialProvider { if (value === undefined) this.values.delete(ref) else this.values.set(ref, value) // After the commit: a broken observer must never make the durable - // write look failed (an INVARIANT failure still rethrows). + // write look failed. this.notifyUpdated(ref) }, { waitMs: DOCUMENT_LOCK_WAIT_MS }) }) @@ -845,15 +845,13 @@ export class LocalCredentialProvider extends CredentialProvider { * Re-read the document after a watcher event. Unchanged content (including * this provider's own writes) is a no-op; an unreadable document keeps the * last good snapshot and warns — a live hot-reload must never take the - * process down. An invariant violation escaping the fan-out is not a reload - * failure and propagates to the queue's error surface. + * process down. */ private async refresh(): Promise { if (this.closed) return try { await this.reconcileFromDisk() } catch (error) { - if ((error as { code?: unknown } | null)?.code === 'INVARIANT') throw error this.ctx.logger.warn('credentials-local: reload failed at %s; keeping the last good document', this.spec.filename) this.ctx.logger.warn(error) } diff --git a/packages/credentials/credentials-local/tests/review-fixes.spec.ts b/packages/credentials/credentials-local/tests/review-fixes.spec.ts index 42fced481c..835b9ee747 100644 --- a/packages/credentials/credentials-local/tests/review-fixes.spec.ts +++ b/packages/credentials/credentials-local/tests/review-fixes.spec.ts @@ -133,21 +133,6 @@ describe('contained update fan-out', () => { await new Promise(resolve => setTimeout(resolve, 10)) }) - it('rethrows an invariant-coded failure after the commit and the remaining listeners', async () => { - const dir = await tempDir() - const path = join(dir, '.credentials.yaml') - const ctx = await boot({ path, watch: false }) - ctx.on('credentials/reference-updated', () => { - throw Object.assign(new Error('forged relation'), { code: 'INVARIANT' }) - }) - const second = vi.fn() - ctx.on('credentials/reference-updated', second) - await expect(ctx.credentials.set(ALPHA, 'one')).rejects.toThrow(/forged relation/) - // Harness-fatal by design — but the write itself committed first. - expect(second).toHaveBeenCalledWith(ALPHA) - expect(await readFile(path, 'utf8')).toContain(`${ALPHA}: one`) - expect(await ctx.credentials.resolve(ALPHA)).toEqual({ value: 'one', source: 'file' }) - }) }) describe('document editor', () => { diff --git a/packages/credentials/credentials-local/tests/watcher.spec.ts b/packages/credentials/credentials-local/tests/watcher.spec.ts index 314d425d1f..02436e94d9 100644 --- a/packages/credentials/credentials-local/tests/watcher.spec.ts +++ b/packages/credentials/credentials-local/tests/watcher.spec.ts @@ -141,33 +141,6 @@ describe('watcher pipeline', () => { expect(await ctx.credentials.resolve(KEY)).toEqual({ value: 'good', source: 'file' }) }) - it('keeps the reload queue alive after an invariant violation escapes the fan-out', async () => { - const dir = await tempDir() - const path = join(dir, '.credentials.yaml') - const ctx = await boot({ path, debounceMs: 5 }) - let arm = true - ctx.on('credentials/reference-updated', () => { - if (!arm) return - throw Object.assign(new Error('forged relation'), { code: 'INVARIANT' }) - }) - const [instance] = await fakeInstances() - - await writeCredentials(path, 'version: 1\nrefs:\n DSH_CRED_PIPE: first\n') - instance!.watcher.emit('all', 'change', path) - // The snapshot commits before the fan-out, so the value lands even though - // the listener threw out of the refresh. - await vi.waitFor(async () => { - expect(await ctx.credentials.resolve(KEY)).toEqual({ value: 'first', source: 'file' }) - }) - - arm = false - await writeCredentials(path, 'version: 1\nrefs:\n DSH_CRED_PIPE: second\n') - instance!.watcher.emit('all', 'change', path) - await vi.waitFor(async () => { - expect(await ctx.credentials.resolve(KEY)).toEqual({ value: 'second', source: 'file' }) - }) - }) - it('quiesces the refresh pipeline before dispose completes', async () => { const dir = await tempDir() const path = join(dir, '.credentials.yaml') diff --git a/packages/credentials/credentials/README.i18n.yaml b/packages/credentials/credentials/README.i18n.yaml index e0058b1f23..d057a2f73d 100644 --- a/packages/credentials/credentials/README.i18n.yaml +++ b/packages/credentials/credentials/README.i18n.yaml @@ -39,8 +39,8 @@ en: 16370ef642feec6a zh: a546774827234300 /deepseek-ai-dsh-credentials/understand-the-implementation/design-philosophy: - en: 3f82aa86b2e7f40f - zh: 25e86b8939a013ea + en: db901a54462a2743 + zh: d51d91b187ce2630 /deepseek-ai-dsh-credentials/understand-the-implementation/the-credentials-reference-updated-event: en: 0192e26fe2f8abd9 zh: 5a8f65be88e930ff @@ -48,14 +48,14 @@ en: 1d56a35fe82e78f3 zh: 1fffc3e104c36805 /deepseek-ai-dsh-credentials/understand-the-implementation/source-map: - en: 2aec30ce79d0e4cf - zh: 3579a6ce6efd5801 + en: f7c85d12a7179da0 + zh: 347ac9e9f89005aa /deepseek-ai-dsh-credentials/understand-the-implementation/client-safe-types: en: b955d1b41f0ad904 zh: 3f49a25e157d3672 /deepseek-ai-dsh-credentials/understand-the-implementation/lifecycle: - en: 080ac62861a7ebad - zh: 3a7689455b9457de + en: bd4d990f92138fff + zh: 67a57f2a29577bc3 /deepseek-ai-dsh-credentials/further-exploration: en: cf1f3628dcce73eb zh: c8c9ce761a6b8f55 diff --git a/packages/credentials/credentials/README.md b/packages/credentials/credentials/README.md index 6e44a298fa..33d537dac3 100644 --- a/packages/credentials/credentials/README.md +++ b/packages/credentials/credentials/README.md @@ -114,7 +114,7 @@ One doctrine and four consequences: - **Consumers resolve per operation.** Resolution is a per-call read with no cross-operation cache; that read is the hot-update mechanism. - **An empty stored value is absent.** `resolve` skips it, `describe` reports it unconfigured — a blank can never masquerade as a configured secret. - **Records are durable, and presence is the fact.** A record is stored per `/` and survives restarts; the empty-value rule does not apply, so an `api-key` record carrying neither a key nor environment values is a deliberate statement, not a blank. -- **Listener failures are contained.** `notifyUpdated` fans `credentials/reference-updated` out so every listener runs; a sync throw or async rejection is logged without changing the committed operation's outcome, except `INVARIANT`-coded failures, which rethrow after every listener ran. +- **Listener failures are contained.** `notifyUpdated` fans `credentials/reference-updated` out so every listener runs; a sync throw or async rejection is logged without changing the committed operation's outcome. ### The credentials/reference-updated event @@ -132,7 +132,6 @@ One doctrine and four consequences: |---|---| | [`src/index.ts`](src/index.ts) | Service Definition: the `credentialRef`/`credentialKey` brands, `ResolvedCredential`/`CredentialRecordInfo`, the abstract provider over both key spaces, contained fan-out | | [`src/types.ts`](src/types.ts) | Client-safe type surface: the `CredentialRef` and `CredentialKey` brands, the stored-record union, the `CredentialInfo` reference view, the `credentials/reference-updated` and `credentials/record-updated` declarations | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: `credentials/reference-updated` only fires while a credentials service is live | ### Client-safe types @@ -140,7 +139,7 @@ The `./types` subpath export holds the event declarations together with the `Cre ### Lifecycle -The service is a Cordis `Service` registered by the provider: disposing the mounting fiber removes `ctx.credentials`. The invariant companion checks that `credentials/reference-updated` never fires without a live service — an emission after disposal means a provider leaked work past its teardown quiescence. +The service is a Cordis `Service` registered by the provider: disposing the mounting fiber removes `ctx.credentials`. diff --git a/packages/credentials/credentials/README.zh.md b/packages/credentials/credentials/README.zh.md index a87c2ed4e3..41e09f7e47 100644 --- a/packages/credentials/credentials/README.zh.md +++ b/packages/credentials/credentials/README.zh.md @@ -114,7 +114,7 @@ apiKeyEnv: DEEPSEEK_API_KEY - **消费方按操作解析。** 解析是一次按调用读取,无跨操作缓存;这次读取正是热更新机制。 - **空的存储值等于不存在。** `resolve` 跳过它,`describe` 报告未配置——空白永远不会伪装成已配置的机密。 - **记录是持久化的,存在即事实。** 记录按 `/` 存储并跨重启保留;空值规则不适用,因此一条既无 key 也无环境值的 `api-key` 记录是有意陈述,而不是空白。 -- **监听器失败受到隔离。** `notifyUpdated` 扇出 `credentials/reference-updated`,保证每个监听器都会运行;同步抛出与异步拒绝都会被记录,不改变已提交操作的结果,`INVARIANT` 编码的失败除外——它们在所有监听器运行完毕后重新抛出。 +- **监听器失败受到隔离。** `notifyUpdated` 扇出 `credentials/reference-updated`,保证每个监听器都会运行;同步抛出与异步拒绝都会被记录,不改变已提交操作的结果。 ### credentials/reference-updated 事件 @@ -132,7 +132,6 @@ apiKeyEnv: DEEPSEEK_API_KEY |---|---| | [`src/index.ts`](src/index.ts) | Service Definition:`credentialRef`/`credentialKey` 品牌、`ResolvedCredential`/`CredentialRecordInfo`、覆盖两个键空间的抽象提供方、带失败隔离的扇出 | | [`src/types.ts`](src/types.ts) | 客户端安全类型面:`CredentialRef` 与 `CredentialKey` 品牌、存储记录联合类型、`CredentialInfo` 引用视图、`credentials/reference-updated` 与 `credentials/record-updated` 事件声明 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:`credentials/reference-updated` 只在凭据服务存活时触发 | ### 客户端安全类型 @@ -140,7 +139,7 @@ apiKeyEnv: DEEPSEEK_API_KEY ### 生命周期 -服务是提供方注册的 Cordis `Service`:释放挂载 fiber 会移除 `ctx.credentials`。不变式伴生插件检查 `credentials/reference-updated` 绝不在服务未存活时触发——释放后仍有发射意味着提供方把工作泄漏到了 teardown 完全停稳之后。 +服务是提供方注册的 Cordis `Service`:释放挂载 fiber 会移除 `ctx.credentials`。 diff --git a/packages/credentials/credentials/package.json b/packages/credentials/credentials/package.json index c222f0c20c..47a4a66831 100644 --- a/packages/credentials/credentials/package.json +++ b/packages/credentials/credentials/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -31,18 +27,15 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], "license": "MIT", "peerDependencies": { - "@deepseek-ai/cordis": "workspace:~", - "@deepseek-ai/dsh-invariants": "workspace:*" + "@deepseek-ai/cordis": "workspace:~" }, "devDependencies": { - "@deepseek-ai/cordis": "workspace:~", - "@deepseek-ai/dsh-invariants": "workspace:*" + "@deepseek-ai/cordis": "workspace:~" }, "dependencies": { "@deepseek-ai/dsh-brand": "workspace:*" diff --git a/packages/credentials/credentials/src/index.ts b/packages/credentials/credentials/src/index.ts index f63ee08cd1..fa8dd8164a 100644 --- a/packages/credentials/credentials/src/index.ts +++ b/packages/credentials/credentials/src/index.ts @@ -258,10 +258,7 @@ export abstract class CredentialProvider extends Service { /** * Fan `credentials/reference-updated` out with contained listener failures: every * listener runs, and a sync throw or async rejection is logged without - * changing the committed operation's outcome — except `INVARIANT`-coded - * failures, which rethrow after every listener ran (the rethrow reaches the - * caller only from synchronous listeners, so invariant checks on this event - * must not be async functions). Providers call this only after the write or + * changing the committed operation's outcome. Providers call this only after the write or * reload actually committed, so a broken observer can never make a durable * change look failed. * @param ref - the reference whose stored value changed. @@ -284,7 +281,6 @@ export abstract class CredentialProvider extends Service { contract, and extracting it would couple the two seams' event semantics. */ /** The contained dispatch both notifications run through; see {@link notifyUpdated}. */ private fanOut(event: 'credentials/reference-updated' | 'credentials/record-updated', subject: string): void { - let invariantFailure: unknown const args = [event, subject] for (const listener of this.ctx.events.dispatch('emit', args) as Array<(...listenerArgs: unknown[]) => unknown>) { try { @@ -295,14 +291,9 @@ export abstract class CredentialProvider extends Service { }) } } catch (error) { - if ((error as { code?: unknown } | null)?.code === 'INVARIANT') { - invariantFailure ??= error - continue - } this.warnListenerFailure(event, subject, error) } } - if (invariantFailure !== undefined) throw invariantFailure as Error } /* jscpd:ignore-end */ diff --git a/packages/credentials/credentials/src/invariant.ts b/packages/credentials/credentials/src/invariant.ts deleted file mode 100644 index 3cfb89d6ab..0000000000 --- a/packages/credentials/credentials/src/invariant.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-credentials`. - * @module @deepseek-ai/dsh-credentials/invariant - */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' - -const PACKAGE_NAME = '@deepseek-ai/dsh-credentials' - -/** Cordis companion plugin name. */ -export const name = 'credentials-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** - * Install the commit-event lifecycle contract: `credentials/reference-updated` names a - * committed provider-source change, so it can only fire while a credentials - * service is live — an emission after disposal means a provider leaked work - * past its teardown quiescence. The value relation itself (`describe` - * agreeing with `resolve`) is asynchronous provider I/O and stays pinned by - * each provider's own suite. - */ -const install: InvariantInstaller = (ctx: Context, fail: InvariantFailure) => { - ctx.on('credentials/reference-updated', (ref) => { - if (ctx.get('credentials') === undefined) { - fail(`credentials/reference-updated for "${ref}" emitted without a live credentials service`) - } - }) -} - -/** - * 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)) diff --git a/packages/credentials/credentials/src/types.ts b/packages/credentials/credentials/src/types.ts index 57e0f99706..5c9ae99350 100644 --- a/packages/credentials/credentials/src/types.ts +++ b/packages/credentials/credentials/src/types.ts @@ -80,10 +80,7 @@ declare module '@deepseek-ai/cordis' { * `unset`, or an external edit observed in storage. Ambient * process-environment changes are not observable and never emit. Listener * failures are contained and logged — a sync throw and an async rejection - * alike — without changing the committed operation's outcome, except - * `INVARIANT`-coded failures, which rethrow after every listener ran; - * that rethrow reaches the emitter only from synchronous listeners, so - * invariant checks on this event must not be async functions. + * alike — without changing the committed operation's outcome. * @param ref - the reference whose stored value changed. * @mode emit */ diff --git a/packages/credentials/credentials/tests/invariant.spec.ts b/packages/credentials/credentials/tests/invariant.spec.ts deleted file mode 100644 index 38b786bca0..0000000000 --- a/packages/credentials/credentials/tests/invariant.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import { credentialRef } from '../src/index.ts' -import * as CredentialsInvariant from '../src/invariant.ts' -import { MemoryCredentials } from './memory.ts' - -const REF = credentialRef('DEEPSEEK_API_KEY') - -describe('credentials invariant companion', () => { - it('accepts a committed change emitted by a live service', async () => { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(CredentialsInvariant) - await ctx.plugin(MemoryCredentials) - - await expect(ctx.credentials.set(REF, 'sk-live')).resolves.toBeUndefined() - }) - - it('fails an update event emitted without a live service', async () => { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(CredentialsInvariant) - - expect(() => { ctx.emit('credentials/reference-updated', REF) }).toThrow(/invariant violated by "@deepseek-ai\/dsh-credentials"/) - }) - - it('reserves the package name against duplicate registration', async () => { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(CredentialsInvariant) - - expect(() => { - ctx.invariants.register('@deepseek-ai/dsh-credentials', () => {}) - }).toThrow(/already registered/) - }) -}) diff --git a/packages/credentials/credentials/tsconfig.json b/packages/credentials/credentials/tsconfig.json index 3a6424faaa..342f636170 100644 --- a/packages/credentials/credentials/tsconfig.json +++ b/packages/credentials/credentials/tsconfig.json @@ -16,9 +16,6 @@ }, { "path": "../../util/brand" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/credentials/deepseek-account-platform/README.i18n.yaml b/packages/credentials/deepseek-account-platform/README.i18n.yaml index 95c17ea670..ff67f91e36 100644 --- a/packages/credentials/deepseek-account-platform/README.i18n.yaml +++ b/packages/credentials/deepseek-account-platform/README.i18n.yaml @@ -18,8 +18,8 @@ en: ec9bb416eec2dcae zh: 15b9c498388be972 /deepseek-ai-dsh-deepseek-account-platform/understand-the-implementation: - en: 0a829b20071a01d4 - zh: 83f54ac08f3609f2 + en: 76d2da2dab100ac3 + zh: 2c9404f27915daab /deepseek-ai-dsh-deepseek-account-platform/further-exploration: en: c8b43a641360ec9e zh: a545f6a0b6a5066b diff --git a/packages/credentials/deepseek-account-platform/README.md b/packages/credentials/deepseek-account-platform/README.md index abaa130b40..18d5810aa8 100644 --- a/packages/credentials/deepseek-account-platform/README.md +++ b/packages/credentials/deepseek-account-platform/README.md @@ -68,7 +68,7 @@ At provider initialization, a valid stored grant whose issuer differs from platf ## Understand the implementation -No runtime invariant companion is published: account presence reads the credential store, and attempt state projects private state directly. There is no independently maintained account index to compare. Behavior tests verify asynchronous cancellation and commit ordering. +Account presence reads the credential store, and attempt state projects private state directly. Behavior tests verify asynchronous cancellation and commit ordering. ## Further Exploration diff --git a/packages/credentials/deepseek-account-platform/README.zh.md b/packages/credentials/deepseek-account-platform/README.zh.md index f976a1890b..8cc3e740a4 100644 --- a/packages/credentials/deepseek-account-platform/README.zh.md +++ b/packages/credentials/deepseek-account-platform/README.zh.md @@ -68,7 +68,7 @@ inferenceOrigin 默认为 `https://api.deepseek.com`。私有部署 patch 可将 ## 理解实现 -不发布运行时 invariant:账号是否存在直接读取凭证存储,尝试状态直接投影私有状态;不存在可与另一个独立索引比较的账号索引。异步取消和提交由行为测试验证。 +账号是否存在直接读取凭证存储,尝试状态直接投影私有状态。异步取消和提交由行为测试验证。 ## 深入探索 diff --git a/packages/credentials/deepseek-account/README.i18n.yaml b/packages/credentials/deepseek-account/README.i18n.yaml index 4771c5591d..0b618d31f5 100644 --- a/packages/credentials/deepseek-account/README.i18n.yaml +++ b/packages/credentials/deepseek-account/README.i18n.yaml @@ -18,8 +18,8 @@ en: d7825ac71d3acb22 zh: 337bb5048aaade09 /deepseek-ai-dsh-deepseek-account/understand-the-implementation: - en: 455017c6c03b3016 - zh: 59b108051984241e + en: ca361e6eb61faf02 + zh: d14923826e86293a /deepseek-ai-dsh-deepseek-account/further-exploration: en: 5a57b4b331031f41 zh: 8b72ac64ed244f58 diff --git a/packages/credentials/deepseek-account/README.md b/packages/credentials/deepseek-account/README.md index 0c134ca325..61aeae0c60 100644 --- a/packages/credentials/deepseek-account/README.md +++ b/packages/credentials/deepseek-account/README.md @@ -43,7 +43,7 @@ Account model failures with `ACCOUNT_SIGN_IN_REQUIRED` emit `deepseek-account/mo ## Understand the implementation -The service defines account operations without maintaining a second credential index; no invariant companion is published. The provider owns persistence and login lifecycle checks. +The service defines account operations without maintaining a second credential index. The provider owns persistence and login lifecycle checks. ## Further Exploration diff --git a/packages/credentials/deepseek-account/README.zh.md b/packages/credentials/deepseek-account/README.zh.md index 24844185dc..1d6b4ea5cc 100644 --- a/packages/credentials/deepseek-account/README.zh.md +++ b/packages/credentials/deepseek-account/README.zh.md @@ -43,7 +43,7 @@ getUnnotifiedBonuses 返回平台尚未记录为已展示的赠金及其所属 ## 理解实现 -服务只定义账号操作,不维护第二份凭证索引,因此不发布 invariant。提供者负责持久化和登录生命周期检查。 +服务只定义账号操作,不维护第二份凭证索引。提供者负责持久化和登录生命周期检查。 ## 深入探索 diff --git a/packages/deliverables/tool-present/README.i18n.yaml b/packages/deliverables/tool-present/README.i18n.yaml index 81a50dd1b9..547406262f 100644 --- a/packages/deliverables/tool-present/README.i18n.yaml +++ b/packages/deliverables/tool-present/README.i18n.yaml @@ -18,8 +18,8 @@ en: f2726b49d9f4d5b3 zh: 10428317260e5cf1 /deepseek-ai-dsh-tool-present/understand-the-implementation: - en: 159f6961c4582e8f - zh: af4cc40de153d758 + en: 3ae69eb991942c18 + zh: 6824644dc0cee1ae /deepseek-ai-dsh-tool-present/further-exploration: en: 66269384f8c0d0f7 zh: b3d694d2f0ae588a diff --git a/packages/deliverables/tool-present/README.md b/packages/deliverables/tool-present/README.md index d3e2625ba8..5c32c43f85 100644 --- a/packages/deliverables/tool-present/README.md +++ b/packages/deliverables/tool-present/README.md @@ -53,8 +53,6 @@ The tool resolves paths through the configured filesystem provider and checks re The pure `./types` entry declares `PresentedFile` and the Session event without importing Host runtime code. The Web consumer validates persisted declarations before displaying or opening them. The event stores no Session ID, so forked history resolves relative paths against the viewed Session's workspace. -**Runtime invariant:** No companion is published. Tool and event registrations are effect-owned, and the Session log owns file declarations; the plugin maintains no independent file-content store. - ----- diff --git a/packages/deliverables/tool-present/README.zh.md b/packages/deliverables/tool-present/README.zh.md index 639c7d272e..656a5c7cd1 100644 --- a/packages/deliverables/tool-present/README.zh.md +++ b/packages/deliverables/tool-present/README.zh.md @@ -53,8 +53,6 @@ kind: "package-reference" 纯 `./types` 入口声明 `PresentedFile` 与 Session 事件,不导入 Host 运行时代码。Web 消费方在展示或打开文件前校验持久声明。事件不保存 Session ID,因此 fork 历史中的相对路径按当前查看的 Session 工作区解析。 -**运行时不变式:** 不发布伴生入口。工具与事件注册归 effect 所有,Session 日志拥有文件声明;插件不维护独立的文件内容存储。 - ----- diff --git a/packages/deliverables/workspace-changes/README.i18n.yaml b/packages/deliverables/workspace-changes/README.i18n.yaml index f3dcdf36dd..7bf0108ef0 100644 --- a/packages/deliverables/workspace-changes/README.i18n.yaml +++ b/packages/deliverables/workspace-changes/README.i18n.yaml @@ -18,8 +18,8 @@ en: d6364de942d83439 zh: b958129205655ba9 /deepseek-ai-dsh-workspace-changes/understand-the-implementation: - en: e5b20d508763a2cf - zh: 4e35b0c46ffd5fb0 + en: efb5cc9657ce290d + zh: b659836fb1fba36a /deepseek-ai-dsh-workspace-changes/further-exploration: en: b1b7ffc015a7efd7 zh: 500f142e6ae7d1fd diff --git a/packages/deliverables/workspace-changes/README.md b/packages/deliverables/workspace-changes/README.md index 168a30cd99..a363be08ac 100644 --- a/packages/deliverables/workspace-changes/README.md +++ b/packages/deliverables/workspace-changes/README.md @@ -59,8 +59,6 @@ One `TurnRecorder` per Session serializes its git work. `turn/start` queues the Git runs through the `subprocess` capability with a scrubbed environment, `GIT_CONFIG_COUNT=0` (ambient indexed configuration is excluded because the credential scrub removes its key entries), `GIT_TERMINAL_PROMPT=0`, `GIT_OPTIONAL_LOCKS=0`, the configured timeout, and bounded output. A failing step abandons that turn's record with a warning; the next turn starts afresh. Session disposal and plugin disposal abort queued work, forget the summaries, and remove the temporary directory. -**Runtime invariant:** No companion is published. Event listeners are effect-owned and the recorder owns the summaries, the snapshot trees, and the captured copies for its Session's lifetime; no independent observation can diverge from them. - ----- diff --git a/packages/deliverables/workspace-changes/README.zh.md b/packages/deliverables/workspace-changes/README.zh.md index 42a96ffdd7..db8a31625b 100644 --- a/packages/deliverables/workspace-changes/README.zh.md +++ b/packages/deliverables/workspace-changes/README.zh.md @@ -59,8 +59,6 @@ kind: "package-reference" git 通过 `subprocess` 能力运行,使用净化后的环境、`GIT_CONFIG_COUNT=0`(凭据清理会移除索引配置的键,因此不继承这些环境配置)、`GIT_TERMINAL_PROMPT=0`、`GIT_OPTIONAL_LOCKS=0`、配置的超时与有界输出。任何步骤失败都会放弃本轮记录并给出警告;下一轮重新开始。Session 释放与插件释放会中止排队的工作、忘记摘要并删除临时目录。 -**运行时不变式:** 不发布伴生入口。事件监听归 effect 所有,记录器在其 Session 存活期间同时拥有摘要、快照树与捕获的副本;没有独立观察会与它们分歧。 - ----- diff --git a/packages/document/office-to-pdf/README.i18n.yaml b/packages/document/office-to-pdf/README.i18n.yaml index e2bc833786..c46897b1ea 100644 --- a/packages/document/office-to-pdf/README.i18n.yaml +++ b/packages/document/office-to-pdf/README.i18n.yaml @@ -18,8 +18,8 @@ en: 52227a5bd93ee125 zh: 5b48a8dc2db7c3b9 /deepseek-ai-dsh-office-to-pdf/understand-the-implementation: - en: 2cd10fdd5204d64a - zh: 769a79b7d684fda4 + en: 7365ecf20a1ce0d9 + zh: 98a66de0eb89451e /deepseek-ai-dsh-office-to-pdf/further-exploration: en: 4d5e635227d14056 zh: 6c72a032786acd55 diff --git a/packages/document/office-to-pdf/README.md b/packages/document/office-to-pdf/README.md index e7c38a5567..52491d0862 100644 --- a/packages/document/office-to-pdf/README.md +++ b/packages/document/office-to-pdf/README.md @@ -62,7 +62,7 @@ The defaults retain 8 PDFs/128 MiB and 64 source aliases, admit 32 readers and 8 Invalid engine metadata, missing required assets, and conversion errors reject the request instead of switching engines. The shared WASM engine uses LibreOffice's CPU image filter. Native conversion uses its separate platform engine. -Each concurrent slot lazily creates and reuses one kit converter. The provider writes authorized input into a private temporary directory, reads a bounded regular PDF, and removes the directory before settling. Canceling an admitted reader does not block later queued work. Reader cancellation releases only that reader; the final reader and provider disposal cancel shared work. Disposal reports `unavailable` to outstanding readers and joins conversions and converter teardown. No runtime invariant companion is published because active operations and scratch cleanup have one lifetime owner. +Each concurrent slot lazily creates and reuses one kit converter. The provider writes authorized input into a private temporary directory, reads a bounded regular PDF, and removes the directory before settling. Canceling an admitted reader does not block later queued work. Reader cancellation releases only that reader; the final reader and provider disposal cancel shared work. Disposal reports `unavailable` to outstanding readers and joins conversions and converter teardown. Remote file reads recheck content authorization and source version before consulting the conversion cache. Deferred reads run after conversion admission and verify the source version after reading. Source-read failures pass through; conversion failures return `document-render/failed` with a classified reason and no engine diagnostics. Unload cancels and joins authorization reads, Remote requests, and conversion work. diff --git a/packages/document/office-to-pdf/README.zh.md b/packages/document/office-to-pdf/README.zh.md index 8376502bd1..fd4e031015 100644 --- a/packages/document/office-to-pdf/README.zh.md +++ b/packages/document/office-to-pdf/README.zh.md @@ -62,7 +62,7 @@ kind: "package-reference" 引擎元数据无效、必需资源缺失和转换错误会拒绝请求,不会切换引擎。共享的 WASM 引擎使用 LibreOffice 的 CPU 图像过滤器。原生转换使用独立的平台引擎。 -每个并发槽按需创建并复用一个 kit 转换器。提供方将已授权输入写入私有临时目录,读取有大小上限的普通 PDF 文件,并在完成前删除目录。取消已准入的读取方不会阻塞后续排队工作。读取方取消只会释放该读取方;最后一个读取方和提供方卸载会取消共享工作。卸载向未完成读取方报告 `unavailable`,并等待转换及转换器清理结束。活跃操作和临时目录清理由同一生命周期负责,因此不发布运行时不变量伴随入口。 +每个并发槽按需创建并复用一个 kit 转换器。提供方将已授权输入写入私有临时目录,读取有大小上限的普通 PDF 文件,并在完成前删除目录。取消已准入的读取方不会阻塞后续排队工作。读取方取消只会释放该读取方;最后一个读取方和提供方卸载会取消共享工作。卸载向未完成读取方报告 `unavailable`,并等待转换及转换器清理结束。 Remote 文件读取在查询转换缓存前重新检查内容读取授权和源版本。延迟读取在取得转换容量后执行,并在读取后验证源版本。源读取失败直接传递;转换失败返回 `document-render/failed` 及分类原因,不暴露引擎诊断。卸载会取消并等待授权读取、Remote 请求和转换工作全部结束。 diff --git a/packages/experimental/agent-team-profile/README.i18n.yaml b/packages/experimental/agent-team-profile/README.i18n.yaml index 78761dfa6d..8de8435c4d 100644 --- a/packages/experimental/agent-team-profile/README.i18n.yaml +++ b/packages/experimental/agent-team-profile/README.i18n.yaml @@ -24,8 +24,8 @@ en: f0e95e60095fc5cb zh: 8c36603b423475d0 /deepseek-ai-dsh-experimental-agent-team-profile/understand-the-implementation: - en: dbfca16ca57c0723 - zh: 38ccf89793e21f68 + en: cac23b0774e00717 + zh: 4446c09b6525587a /deepseek-ai-dsh-experimental-agent-team-profile/further-exploration: en: 76e92afc23c0ec7c zh: 7bd671bd557a91ee diff --git a/packages/experimental/agent-team-profile/README.md b/packages/experimental/agent-team-profile/README.md index 053f44b673..e2454445e2 100644 --- a/packages/experimental/agent-team-profile/README.md +++ b/packages/experimental/agent-team-profile/README.md @@ -64,7 +64,6 @@ The package's runtime content is [`cordis.patch.yml`](cordis.patch.yml). Applied |---|---| | [`cordis.patch.yml`](cordis.patch.yml) | Ordered patch over `dsh-base` | | [`src/index.ts`](src/index.ts) | Empty module entry; the patch is the runtime content | -| — | No runtime invariant companion is published; the package carries only a static profile patch. The Team service and tools own their mutable relationships; the UI package owns its disposable slot registration. | diff --git a/packages/experimental/agent-team-profile/README.zh.md b/packages/experimental/agent-team-profile/README.zh.md index bd68373392..a02080f58f 100644 --- a/packages/experimental/agent-team-profile/README.zh.md +++ b/packages/experimental/agent-team-profile/README.zh.md @@ -64,7 +64,6 @@ dsh plugin --profile web add @deepseek-ai/dsh-experimental-agent-team-profile |---|---| | [`cordis.patch.yml`](cordis.patch.yml) | 叠加在 `dsh-base` 之上的有序 patch | | [`src/index.ts`](src/index.ts) | 空模块入口;patch 是运行时内容 | -| — | 不发布运行时不变式伴生入口;本包只携带静态 profile patch。Team 服务与工具各自持有其可变关系,UI 包持有其可释放的 slot 注册。 | diff --git a/packages/experimental/agent-team/README.i18n.yaml b/packages/experimental/agent-team/README.i18n.yaml index 67ddc0b76f..9f1b7b705f 100644 --- a/packages/experimental/agent-team/README.i18n.yaml +++ b/packages/experimental/agent-team/README.i18n.yaml @@ -45,8 +45,8 @@ en: c08e8eb5bbac71ae zh: d6fb32f1243a28d6 /deepseek-ai-dsh-experimental-agent-team/understand-the-implementation/source-map: - en: 53e2dda7c2adb34c - zh: 562590e3d42c029e + en: 4e4189d2009a4e42 + zh: 0ccbf29cf78f10af /deepseek-ai-dsh-experimental-agent-team/understand-the-implementation/team-identity-and-roster: en: 85f97723e45e43de zh: def66721e21339cc @@ -60,8 +60,8 @@ en: 3becd36b407aac95 zh: c9e4895ee59696bb /deepseek-ai-dsh-experimental-agent-team/understand-the-implementation/durability-model: - en: 66dcbc543f2d3576 - zh: cd8de23c555696df + en: f7206b1c06f337fe + zh: acd3af1159bb6cd4 /deepseek-ai-dsh-experimental-agent-team/understand-the-implementation/disposal: en: 3ae69e3b7905cffe zh: 0fb159101f6d646b diff --git a/packages/experimental/agent-team/README.md b/packages/experimental/agent-team/README.md index 6d2aa60410..d4d1ce7bd4 100644 --- a/packages/experimental/agent-team/README.md +++ b/packages/experimental/agent-team/README.md @@ -122,7 +122,6 @@ The [Agent Teams Agent Note](../../../.agents/notes/implemented/feature/2026-08- | [`src/task-view.ts`](src/task-view.ts) | Pure task readiness, owner-name, and write-overlap derivation shared by the task board and the client view | | [`src/activity.ts`](src/activity.ts) | One-shot change waiters and disposal release | | [`src/lifecycle.ts`](src/lifecycle.ts) | Shared admission cutoff and bounded settlement | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion that replays candidate events before append | ### Team identity and roster @@ -144,7 +143,7 @@ Tasks are complete versioned snapshots; every mutation carries `expectedRevision ### Durability model -Team events are appended to the exact live Lead Session and flushed before the operation reports success or wakes waiters. `team/member`, `team/task`, `team/message/queued`, and `team/message/delivered` are log-only: they never enter the conversation surface, so derived model history is untouched by coordination records. Session event `seq` and `time` own ordering and timing; snapshots do not duplicate them. The `./invariant` companion replays each candidate Team event against its committed prefix and rejects invalid transitions before append. +Team events are appended to the exact live Lead Session and flushed before the operation reports success or wakes waiters. `team/member`, `team/task`, `team/message/queued`, and `team/message/delivered` are log-only: they never enter the conversation surface, so derived model history is untouched by coordination records. Session event `seq` and `time` own ordering and timing; snapshots do not duplicate them. Native V4 Team event and checkpoint admission reject retired `tool-result` content before it can enter mailbox state. Historical conversion belongs to the Session-format migration; the Team projection does not convert old wrappers. diff --git a/packages/experimental/agent-team/README.zh.md b/packages/experimental/agent-team/README.zh.md index 9ea2977a97..9aab8e0e83 100644 --- a/packages/experimental/agent-team/README.zh.md +++ b/packages/experimental/agent-team/README.zh.md @@ -122,7 +122,6 @@ Lead 可以停止 teammate 的当前轮次,而不会删除其排队的消息 | [`src/task-view.ts`](src/task-view.ts) | 任务板与客户端视图共用的纯任务派生:就绪状态、owner 名称与写入范围重叠 | | [`src/activity.ts`](src/activity.ts) | 一次性变更等待者与 dispose(资源释放)时的等待解除 | | [`src/lifecycle.ts`](src/lifecycle.ts) | 共享准入截止与有界结算 | -| [`src/invariant.ts`](src/invariant.ts) | 在 append 前回放候选事件的不变式伴生插件 | ### Team 身份与 roster @@ -144,7 +143,7 @@ Lead 可以停止 teammate 的当前轮次,而不会删除其排队的消息 ### 持久性模型 -Team 事件追加到精确的 live Lead 会话,并在操作报告成功或唤醒等待者之前 flush。`team/member`、`team/task`、`team/message/queued` 与 `team/message/delivered` 仅存在于日志:它们从不进入会话表面,因此派生模型历史不受协作记录影响。顺序与时间由会话事件的 `seq` 与 `time` 负责,快照不重复保存。`./invariant` 伴生插件把每条候选 Team 事件对照已提交前缀回放,并在 append 前拒绝非法转换。 +Team 事件追加到精确的 live Lead 会话,并在操作报告成功或唤醒等待者之前 flush。`team/member`、`team/task`、`team/message/queued` 与 `team/message/delivered` 仅存在于日志:它们从不进入会话表面,因此派生模型历史不受协作记录影响。顺序与时间由会话事件的 `seq` 与 `time` 负责,快照不重复保存。 原生 V4 的 Team 事件及检查点准入会拒绝退役的 `tool-result` 内容,防止它进入邮箱状态。历史转换由 Session 格式迁移负责,Team 投影不转换旧包装。 diff --git a/packages/experimental/agent-team/package.json b/packages/experimental/agent-team/package.json index 1dc6c72c00..fe29db9a61 100644 --- a/packages/experimental/agent-team/package.json +++ b/packages/experimental/agent-team/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./client": { "types": "./lib/types/client.d.ts", "default": "./lib/types/client.js" @@ -37,7 +33,6 @@ "files": [ "locale/*.json", "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -50,7 +45,6 @@ "peerDependencies": { "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-persistence": "workspace:*", @@ -62,7 +56,6 @@ "@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-session-persistence": "workspace:*", diff --git a/packages/experimental/agent-team/src/invariant.ts b/packages/experimental/agent-team/src/invariant.ts deleted file mode 100644 index 442df6a6e6..0000000000 --- a/packages/experimental/agent-team/src/invariant.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** Agent Teams runtime invariant companion. */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import { - isTeamEvent, - teamProjectionDefinition, - type TeamProjectionState, -} from './projection.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-experimental-agent-team' - -/** Cordis companion plugin name. */ -export const name = 'team-invariant' -/** Invariant registry required by the companion. */ -export const inject = ['invariants'] - -/** Validate candidate Team events against the projected committed prefix; `apply` never mutates the committed state. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - /* v8 ignore next -- non-Team Session events have no Agent Teams invariant. */ - if (!isTeamEvent(event)) return - const state = ctx.sessionProjections.stateOf(session, 'agentTeam') as TeamProjectionState - const candidate = teamProjectionDefinition.apply(state, event) - if (candidate.failure !== undefined) { - fail(`session event ${event.seq} violates the Agent Teams stream: ${candidate.failure}`) - } - }, { global: true }) -}, { inject: ['sessionProjections'] }) - -/** Register the package invariant companion. */ -export const apply = (ctx: Context): Promise<() => void> => - Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) diff --git a/packages/experimental/agent-team/tests/invariant.spec.ts b/packages/experimental/agent-team/tests/invariant.spec.ts deleted file mode 100644 index 5dd8dba256..0000000000 --- a/packages/experimental/agent-team/tests/invariant.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import InvariantService, { InvariantError } from '@deepseek-ai/dsh-invariants' -import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' -import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' -import * as TeamInvariant from '../src/invariant.ts' -import { teamProjectionDefinition } from '../src/projection.ts' -import { TeamId, TeamTaskId } from '../src/types.ts' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(SessionProjectionRegistry) - ctx.sessionProjections.register(teamProjectionDefinition) - await ctx.plugin(InvariantService, { enabled: true }) - await ctx.plugin(TeamInvariant) - return ctx -} - -describe('Agent Teams stream invariant', () => { - it('accepts provisioning and rejects a terminal member as the first edge', async () => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('team-invariant')) - const member = { - id: SessionId('team-invariant-child'), - name: 'worker', - description: 'worker responsibility', - provider: 'spawn', - context: 'fresh' as const, - phase: 'provisioning' as const, - } - expect(() => { - session.append('team/member', { version: 2, teamId: TeamId(session.id), member }) - }).not.toThrow() - - const invalid = ctx.sessions.create(SessionId('team-invariant-invalid')) - expect(() => { - invalid.append('team/member', { - version: 2, - teamId: TeamId(invalid.id), - member: { ...member, phase: 'active' }, - }) - }).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-experimental-agent-team', - })) - expect(invalid.snapshotEvents()).toEqual([]) - }) - - it('rejects an invalid task dependency before publication', async () => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('team-task-invariant')) - - expect(() => { - session.append('team/task', { - version: 2, - teamId: TeamId(session.id), - task: { - id: TeamTaskId('task-1'), - revision: 1, - subject: 'invalid dependency', - description: 'references a missing blocker', - status: 'pending', - blockedBy: [TeamTaskId('missing')], - writeScopes: [], - }, - }) - }).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-experimental-agent-team', - })) - expect(session.snapshotEvents()).toEqual([]) - }) -}) diff --git a/packages/experimental/agent-team/tsconfig.json b/packages/experimental/agent-team/tsconfig.json index 669a476e44..e9c2a2f0df 100644 --- a/packages/experimental/agent-team/tsconfig.json +++ b/packages/experimental/agent-team/tsconfig.json @@ -15,7 +15,6 @@ { "path": "../../core/agent" }, { "path": "../../subagent/subagent" }, { "path": "../../session/session-persistence" }, - { "path": "../../session/session-projection" }, - { "path": "../../runtime-diagnostics/invariants" } + { "path": "../../session/session-projection" } ] } diff --git a/packages/experimental/agent-team/tsdown.config.ts b/packages/experimental/agent-team/tsdown.config.ts index cc38b29b83..f96442ba55 100644 --- a/packages/experimental/agent-team/tsdown.config.ts +++ b/packages/experimental/agent-team/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from 'tsdown' -/** Build the runtime and invariant companion as independent package exports. */ +/** Build the runtime as a self-contained package export. */ export default defineConfig([ { entry: ['lib/types/index.js'], @@ -12,14 +12,4 @@ export default defineConfig([ dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, - }, ]) diff --git a/packages/experimental/api-speech-to-text/README.i18n.yaml b/packages/experimental/api-speech-to-text/README.i18n.yaml index 3fe875cf4e..1db635bcd9 100644 --- a/packages/experimental/api-speech-to-text/README.i18n.yaml +++ b/packages/experimental/api-speech-to-text/README.i18n.yaml @@ -18,8 +18,8 @@ en: 23efd2789fe70cd9 zh: 3edd559ee5975742 /deepseek-ai-dsh-experimental-api-speech-to-text/understand-the-implementation: - en: 83c580c412f4026a - zh: cd6742fef27de9e5 + en: cffe2ab0b7cd0b8b + zh: 5fd4f71cb083de80 /deepseek-ai-dsh-experimental-api-speech-to-text/further-exploration: en: 5c64426add9d79d6 zh: 4e06aa2ead8d2ccd diff --git a/packages/experimental/api-speech-to-text/README.md b/packages/experimental/api-speech-to-text/README.md index 113c1f9445..226df827ad 100644 --- a/packages/experimental/api-speech-to-text/README.md +++ b/packages/experimental/api-speech-to-text/README.md @@ -37,7 +37,7 @@ Compose with the speech Service Definition and Typert. `maxAudioBytes` and `maxD
Maintainer details — click to expand -`catalog()` exposes provider identities, the default selection and recording limits. `transcribe()` validates canonical base64 and 16 kHz mono PCM16 WAV before resolving the selected provider. The existing gateway owns authentication and cancellation transport. Audio is transient, never a Session event or attachment; only the user’s later ordinary submission records recognized text. No runtime invariant companion is published because validation is stateless and preparation belongs to the provider. +`catalog()` exposes provider identities, the default selection and recording limits. `transcribe()` validates canonical base64 and 16 kHz mono PCM16 WAV before resolving the selected provider. The existing gateway owns authentication and cancellation transport. Audio is transient, never a Session event or attachment; only the user’s later ordinary submission records recognized text. `follow()` streams complete catalogs, including preparation states and current preferences. `prepare()` starts or joins a Host task; `cancelPreparation()` explicitly cancels it. `configure()` persists the supplied preference fields. A disconnected observer does not cancel preparation. diff --git a/packages/experimental/api-speech-to-text/README.zh.md b/packages/experimental/api-speech-to-text/README.zh.md index bc853bd00d..27927b922f 100644 --- a/packages/experimental/api-speech-to-text/README.zh.md +++ b/packages/experimental/api-speech-to-text/README.zh.md @@ -37,7 +37,7 @@ kind: "package-reference"
维护者信息 — 点击展开 -`catalog()` 暴露 Provider 标识、默认选择与录音限制。`transcribe()` 在解析指定 Provider 前校验规范 base64 与 16 kHz 单声道 PCM16 WAV。现有网关负责认证和取消传输。音频是临时数据,不成为 Session 事件或附件;只有用户之后的普通提交才记录识别文字。不发布运行时不变量伴随模块,因为校验无状态,准备属于 Provider。 +`catalog()` 暴露 Provider 标识、默认选择与录音限制。`transcribe()` 在解析指定 Provider 前校验规范 base64 与 16 kHz 单声道 PCM16 WAV。现有网关负责认证和取消传输。音频是临时数据,不成为 Session 事件或附件;只有用户之后的普通提交才记录识别文字。 `follow()` 推送包含准备状态和当前偏好的完整目录。`prepare()` 启动或加入 Host 任务;`cancelPreparation()` 显式取消任务。`configure()` 持久化传入的偏好字段。观察连接断开不会取消准备。 diff --git a/packages/experimental/auto-review/README.i18n.yaml b/packages/experimental/auto-review/README.i18n.yaml index 59c8d65cee..863ec0e2b9 100644 --- a/packages/experimental/auto-review/README.i18n.yaml +++ b/packages/experimental/auto-review/README.i18n.yaml @@ -24,8 +24,8 @@ en: 556e89c0055ae9cc zh: 75afbcb9a2af6467 /deepseek-ai-dsh-experimental-auto-review/understand-the-implementation: - en: e52dbe245aef6233 - zh: e1cbde78676fc129 + en: 36950514e3b99263 + zh: 8d2b8252cdc8d75e /deepseek-ai-dsh-experimental-auto-review/further-exploration: en: 8b81d32ea370ecd3 zh: dc53ff4da78232ac diff --git a/packages/experimental/auto-review/README.md b/packages/experimental/auto-review/README.md index 9f787064cb..e6c99df323 100644 --- a/packages/experimental/auto-review/README.md +++ b/packages/experimental/auto-review/README.md @@ -61,8 +61,6 @@ The reviewer reconstructs five sections from the current Session surface and pen Unloading closes selection and review admission, migrates live Auto Sessions to Full access through the existing preset writer, then aborts and drains reviews before withdrawing the listener and contribution. The migration writes the `never` approval policy through the Session writer without queuing a policy-change notice; the model sees the new policy in the next runtime-context snapshot. The sandbox value and persistent terminals survive the migration. A persisted Auto Session cannot publish without the complete integration; reopening it after installation is an explicit user action. Reinstalling the layer restores the option but does not switch live Sessions back to Auto. -No runtime invariant companion is published: this single effect owns selection admission, review enrollment, cancellation, and cleanup; it has no independent observation that can diverge from those owned operations. -
----- diff --git a/packages/experimental/auto-review/README.zh.md b/packages/experimental/auto-review/README.zh.md index 8e4246a566..fadc513043 100644 --- a/packages/experimental/auto-review/README.zh.md +++ b/packages/experimental/auto-review/README.zh.md @@ -61,8 +61,6 @@ Reviewer 从当前 Session surface 与待执行调用重建五个分区:固定 卸载时先关闭选择与 review admission,经由既有 preset writer 将存活 Auto Session 迁移到 Full access,再中止并等待在途 review 结清,最后撤回 listener 与 contribution。迁移通过 Session writer 写入 `never` 审批策略,不排入策略变更通知;模型在下一次 runtime-context 快照中看到新策略。沙箱值与持久终端在迁移中保持不变。持久 Auto Session 缺少完整 integration 时不能发布;安装后重新打开需要用户显式操作。重装只恢复选项,不把存活 Session 切回 Auto。 -本包不发布 runtime invariant companion:同一个 effect 拥有选择准入、review 登记、取消与清理,不存在能与这些自有操作相互偏离的独立观察。 -
----- diff --git a/packages/experimental/browser-use-chrome-devtools-mcp/README.i18n.yaml b/packages/experimental/browser-use-chrome-devtools-mcp/README.i18n.yaml index 5c5d20d19e..a910b278ef 100644 --- a/packages/experimental/browser-use-chrome-devtools-mcp/README.i18n.yaml +++ b/packages/experimental/browser-use-chrome-devtools-mcp/README.i18n.yaml @@ -18,8 +18,8 @@ en: 6257bdaa22e67fb5 zh: a083c080c6fadac1 /deepseek-ai-dsh-experimental-browser-use-chrome-devtools-mcp/understand-the-implementation: - en: 32b0f9382ebff464 - zh: 1865ee4afa815c40 + en: 70402ba45a01074f + zh: 67c4777921953478 /deepseek-ai-dsh-experimental-browser-use-chrome-devtools-mcp/further-exploration: en: f97bf132ae1ff7ac zh: 74c01d1cbca5281a diff --git a/packages/experimental/browser-use-chrome-devtools-mcp/README.md b/packages/experimental/browser-use-chrome-devtools-mcp/README.md index b4ef51079d..0892864895 100644 --- a/packages/experimental/browser-use-chrome-devtools-mcp/README.md +++ b/packages/experimental/browser-use-chrome-devtools-mcp/README.md @@ -57,7 +57,7 @@ When configuring the system prompt's `toolOrder` for the whole process, leave br
Implementation internals — click to expand -The provider resolves its pinned npm entry and starts it under the current Node executable. A temporary protocol probe may precede the serving process. The [shared runtime](../browser-use-runtime/README.md) owns awaited Agent initialization, per-Session serialization, and cleanup; the [MCP client](../../mcp/mcp-client/README.md) owns transport, discovery, and result projection. No runtime invariant companion is published because the provider maintains no independent connection observation. +The provider resolves its pinned npm entry and starts it under the current Node executable. A temporary protocol probe may precede the serving process. The [shared runtime](../browser-use-runtime/README.md) owns awaited Agent initialization, per-Session serialization, and cleanup; the [MCP client](../../mcp/mcp-client/README.md) owns transport, discovery, and result projection. Browser state survives turns while its live Session remains attached. Disposal waits for server shutdown before releasing resources. Resume after reload starts fresh browser runtime state; stored conversation history does not restore cookies or pages. diff --git a/packages/experimental/browser-use-chrome-devtools-mcp/README.zh.md b/packages/experimental/browser-use-chrome-devtools-mcp/README.zh.md index 5ce248b975..7e4f3babdd 100644 --- a/packages/experimental/browser-use-chrome-devtools-mcp/README.zh.md +++ b/packages/experimental/browser-use-chrome-devtools-mcp/README.zh.md @@ -57,7 +57,7 @@ kind: "package-reference"
实现细节 — 点击展开 -提供方解析固定版本 npm 包的可执行入口,并使用当前 Node 启动。服务进程之前可能运行临时协议探测进程。[共享运行时](../browser-use-runtime/README.zh.md)负责等待 Agent 初始化、逐 Session 串行执行与清理;[MCP 客户端](../../mcp/mcp-client/README.zh.md)负责传输、发现和结果投影。提供方不维护独立的连接观测,因此不发布运行时不变量配套入口。 +提供方解析固定版本 npm 包的可执行入口,并使用当前 Node 启动。服务进程之前可能运行临时协议探测进程。[共享运行时](../browser-use-runtime/README.zh.md)负责等待 Agent 初始化、逐 Session 串行执行与清理;[MCP 客户端](../../mcp/mcp-client/README.zh.md)负责传输、发现和结果投影。 只要活动 Session 保持连接,浏览器状态就会跨轮次保留。销毁会等待服务器关闭,再释放资源。重新加载后恢复 Session 会创建新的浏览器运行状态;已保存的对话历史不会还原 Cookie 或页面。 diff --git a/packages/experimental/browser-use-playwright-mcp/README.i18n.yaml b/packages/experimental/browser-use-playwright-mcp/README.i18n.yaml index d35a5f9c21..0a4fce8105 100644 --- a/packages/experimental/browser-use-playwright-mcp/README.i18n.yaml +++ b/packages/experimental/browser-use-playwright-mcp/README.i18n.yaml @@ -18,8 +18,8 @@ en: 42a60bded230b697 zh: 2bc2efd2ba8c5e5a /deepseek-ai-dsh-experimental-browser-use-playwright-mcp/understand-the-implementation: - en: 32b0f9382ebff464 - zh: 1865ee4afa815c40 + en: 70402ba45a01074f + zh: 67c4777921953478 /deepseek-ai-dsh-experimental-browser-use-playwright-mcp/further-exploration: en: c3adc5fd9103efab zh: 4374dffa1415d2ff diff --git a/packages/experimental/browser-use-playwright-mcp/README.md b/packages/experimental/browser-use-playwright-mcp/README.md index 6397c1f641..c1dfc7c8bf 100644 --- a/packages/experimental/browser-use-playwright-mcp/README.md +++ b/packages/experimental/browser-use-playwright-mcp/README.md @@ -57,7 +57,7 @@ When configuring the system prompt's `toolOrder` for the whole process, leave br
Implementation internals — click to expand -The provider resolves its pinned npm entry and starts it under the current Node executable. A temporary protocol probe may precede the serving process. The [shared runtime](../browser-use-runtime/README.md) owns awaited Agent initialization, per-Session serialization, and cleanup; the [MCP client](../../mcp/mcp-client/README.md) owns transport, discovery, and result projection. No runtime invariant companion is published because the provider maintains no independent connection observation. +The provider resolves its pinned npm entry and starts it under the current Node executable. A temporary protocol probe may precede the serving process. The [shared runtime](../browser-use-runtime/README.md) owns awaited Agent initialization, per-Session serialization, and cleanup; the [MCP client](../../mcp/mcp-client/README.md) owns transport, discovery, and result projection. Browser state survives turns while its live Session remains attached. Disposal waits for server shutdown before releasing resources. Resume after reload starts fresh browser runtime state; stored conversation history does not restore cookies or pages. diff --git a/packages/experimental/browser-use-playwright-mcp/README.zh.md b/packages/experimental/browser-use-playwright-mcp/README.zh.md index 41162e440a..20479cfa6e 100644 --- a/packages/experimental/browser-use-playwright-mcp/README.zh.md +++ b/packages/experimental/browser-use-playwright-mcp/README.zh.md @@ -57,7 +57,7 @@ kind: "package-reference"
实现细节 — 点击展开 -提供方解析固定版本 npm 包的可执行入口,并使用当前 Node 启动。服务进程之前可能运行临时协议探测进程。[共享运行时](../browser-use-runtime/README.zh.md)负责等待 Agent 初始化、逐 Session 串行执行与清理;[MCP 客户端](../../mcp/mcp-client/README.zh.md)负责传输、发现和结果投影。提供方不维护独立的连接观测,因此不发布运行时不变量配套入口。 +提供方解析固定版本 npm 包的可执行入口,并使用当前 Node 启动。服务进程之前可能运行临时协议探测进程。[共享运行时](../browser-use-runtime/README.zh.md)负责等待 Agent 初始化、逐 Session 串行执行与清理;[MCP 客户端](../../mcp/mcp-client/README.zh.md)负责传输、发现和结果投影。 只要活动 Session 保持连接,浏览器状态就会跨轮次保留。销毁会等待服务器关闭,再释放资源。重新加载后恢复 Session 会创建新的浏览器运行状态;已保存的对话历史不会还原 Cookie 或页面。 diff --git a/packages/experimental/browser-use-runtime/README.i18n.yaml b/packages/experimental/browser-use-runtime/README.i18n.yaml index 8ed0c64ae0..b3c950369e 100644 --- a/packages/experimental/browser-use-runtime/README.i18n.yaml +++ b/packages/experimental/browser-use-runtime/README.i18n.yaml @@ -18,8 +18,8 @@ en: a26a2f00e2c93e99 zh: 9e13a68213a24f10 /deepseek-ai-dsh-experimental-browser-use-runtime/understand-the-implementation: - en: 2b0a54595044ea15 - zh: 9fc96f082e948b77 + en: ad923b17fcb5421c + zh: c2449382d6e0c7ff /deepseek-ai-dsh-experimental-browser-use-runtime/further-exploration: en: 2a8d2c9b217b9e95 zh: 084c9ad664e55498 diff --git a/packages/experimental/browser-use-runtime/README.md b/packages/experimental/browser-use-runtime/README.md index f29b5e3e2a..3f357b783f 100644 --- a/packages/experimental/browser-use-runtime/README.md +++ b/packages/experimental/browser-use-runtime/README.md @@ -51,8 +51,6 @@ Disposed-cause cancellation starts resource cleanup before AgentHandle waits for The [MCP helper](src/mcp.ts) connects within serial `agent/created`; [AgentLoop](../../core/agent-loop/README.md#understand-the-implementation) holds queued input and owns creation rollback. Prompt assembly reads the initialized catalog. The helper retains the provider registration through resource cleanup and uses the [MCP client](../../mcp/mcp-client/README.md) for transport, schema discovery, result conversion, and durable image admission. -No runtime invariant companion is published: resource ownership and pending work are private lifecycle state, with no separately maintained runtime projection to compare. Owner tests cover isolation, disposal, and failed cleanup. -
----- diff --git a/packages/experimental/browser-use-runtime/README.zh.md b/packages/experimental/browser-use-runtime/README.zh.md index 65f5a912fc..a58375c277 100644 --- a/packages/experimental/browser-use-runtime/README.zh.md +++ b/packages/experimental/browser-use-runtime/README.zh.md @@ -51,8 +51,6 @@ MCP 提供方使用 `@deepseek-ai/dsh-experimental-browser-use-runtime/mcp` 中 [MCP 辅助库](src/mcp.ts)在串行 `agent/created` 中建立连接;[AgentLoop](../../core/agent-loop/README.zh.md#understand-the-implementation)保留排队输入并负责创建回滚。提示词组装读取已初始化的目录。辅助库在资源清理期间保留提供方注册,并通过 [MCP 客户端](../../mcp/mcp-client/README.zh.md)处理传输、schema 发现、结果转换与持久图像接纳。 -不发布运行时不变量伴随入口:资源所有权与待处理工作是私有生命周期状态,没有可供比较的独立维护运行时投影。所属测试覆盖隔离、释放与清理失败。 -
----- diff --git a/packages/experimental/browser-use-stagehand-native/README.i18n.yaml b/packages/experimental/browser-use-stagehand-native/README.i18n.yaml index 6befe7b2bf..d65d51165d 100644 --- a/packages/experimental/browser-use-stagehand-native/README.i18n.yaml +++ b/packages/experimental/browser-use-stagehand-native/README.i18n.yaml @@ -24,8 +24,8 @@ en: fea3ad16a77fc478 zh: cf551a8a2d183f93 /deepseek-ai-dsh-experimental-browser-use-stagehand-native/understand-the-implementation: - en: 63e42e22af64f7df - zh: d8218ad3e12a8ebe + en: ba13e1703d5aaca4 + zh: 91db415ed074307c /deepseek-ai-dsh-experimental-browser-use-stagehand-native/further-exploration: en: eff8f4541eb26ca7 zh: 76a61fe9bd48df2a diff --git a/packages/experimental/browser-use-stagehand-native/README.md b/packages/experimental/browser-use-stagehand-native/README.md index 671a900d5d..61f6c82c0a 100644 --- a/packages/experimental/browser-use-stagehand-native/README.md +++ b/packages/experimental/browser-use-stagehand-native/README.md @@ -92,8 +92,6 @@ The host owns each launched Chromium process and its temporary profile before wa The [native runtime](src/native.ts) passes the explicit model configuration to Stagehand's public initialization API. Stagehand owns the model requests, response validation, and token accounting inside its browser extension. DSH records browser tool inputs and returned data, including SDK result metadata, through the existing Session log. Underlying inference request/response capture and integration into DSH Session usage accounting are deferred. -No invariant companion is published: every browser operation uses the resource owner's single acquired handle, with no separately maintained browser relationship to compare. -
----- diff --git a/packages/experimental/browser-use-stagehand-native/README.zh.md b/packages/experimental/browser-use-stagehand-native/README.zh.md index 69f3c115c6..cc64481db9 100644 --- a/packages/experimental/browser-use-stagehand-native/README.zh.md +++ b/packages/experimental/browser-use-stagehand-native/README.zh.md @@ -92,8 +92,6 @@ env -u NODE_USE_ENV_PROXY DSH_STAGEHAND_E2E=1 pnpm exec vitest run --config vite [原生运行时](src/native.ts)将显式模型配置传入 Stagehand 的公开初始化 API。Stagehand 在其浏览器扩展中负责模型请求、响应验证与 token 计量。DSH 通过现有 Session 日志记录浏览器工具输入和返回数据,包括 SDK 结果元数据。底层推理请求/响应捕获及其与 DSH Session 用量计量的集成均属暂缓工作。 -此包不发布不变量配套模块:每次浏览器操作都使用资源所有者获取的唯一句柄,没有独立维护、需要比较的浏览器关系。 -
----- diff --git a/packages/experimental/client-ui-agent-team/README.i18n.yaml b/packages/experimental/client-ui-agent-team/README.i18n.yaml index 998aff921b..643f2124cc 100644 --- a/packages/experimental/client-ui-agent-team/README.i18n.yaml +++ b/packages/experimental/client-ui-agent-team/README.i18n.yaml @@ -39,5 +39,5 @@ en: 254e4aa2f81037d2 zh: 3cdf19dffafb51df /deepseek-ai-dsh-experimental-client-ui-agent-team/known-limitations-and-deferred-work/dev-note: - en: 317e07f2fd605e55 - zh: 66836ceef6216892 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/experimental/client-ui-agent-team/README.md b/packages/experimental/client-ui-agent-team/README.md index 17990a84eb..718aa773eb 100644 --- a/packages/experimental/client-ui-agent-team/README.md +++ b/packages/experimental/client-ui-agent-team/README.md @@ -100,5 +100,3 @@ No direct effect; the Team tools and ordinary conversation submission own any la None. - -**Runtime invariant:** No companion is published. The Host projection is authoritative and the package owns only one disposable slot registration. diff --git a/packages/experimental/client-ui-agent-team/README.zh.md b/packages/experimental/client-ui-agent-team/README.zh.md index 90bed11f28..d912200054 100644 --- a/packages/experimental/client-ui-agent-team/README.zh.md +++ b/packages/experimental/client-ui-agent-team/README.zh.md @@ -100,5 +100,3 @@ Client export 通过 Cordis effect 注册 locale dictionary 与一个 conversati 无。 - -**运行时不变式:** 不发布伴生入口。Host 投影是权威来源,本包只持有一个可释放的 slot 注册。 diff --git a/packages/experimental/client-ui-voice-input/README.i18n.yaml b/packages/experimental/client-ui-voice-input/README.i18n.yaml index b4dcb861cc..dd577cb202 100644 --- a/packages/experimental/client-ui-voice-input/README.i18n.yaml +++ b/packages/experimental/client-ui-voice-input/README.i18n.yaml @@ -18,8 +18,8 @@ en: 44f25ad14ceda123 zh: 43c4680be42af1cf /deepseek-ai-dsh-experimental-client-ui-voice-input/understand-the-implementation: - en: 18807008bccfdb6e - zh: bb5a82c92d4aa056 + en: b3a7bb1a4f0b3c1e + zh: 233d1c5ea2b16de8 /deepseek-ai-dsh-experimental-client-ui-voice-input/further-exploration: en: 5c64426add9d79d6 zh: 4e06aa2ead8d2ccd diff --git a/packages/experimental/client-ui-voice-input/README.md b/packages/experimental/client-ui-voice-input/README.md index afa8c2c135..ea4b42a6fe 100644 --- a/packages/experimental/client-ui-voice-input/README.md +++ b/packages/experimental/client-ui-voice-input/README.md @@ -37,7 +37,7 @@ Before downloading or retrying, **Model download source** offers Automatic and t
Maintainer details — click to expand -Native MediaRecorder captures audio and Web Audio converts it to the Host PCM format after flushing the final recorder chunks. Window blur during the microphone permission request does not cancel capture; blur during recording does. Capture failures immediately end the recording UI and offer retry inside the toolbar. Cancellation, failure and plugin disposal share one resource-release promise; the plugin retains ownership until AudioContext closure settles. The original editor selection carries a draft revision: changed drafts retain the transcript for explicit insertion or discard beside the microphone. Session changes, hiding the page during capture and plugin disposal invalidate late results and release tracks. One plugin-owned readiness subscription serves the composer, installation prompt and details. These views use the existing Slot lifecycle and own no preparation tasks. No runtime invariant companion is published because readiness comes from one Host subscription and recording state belongs to one capture operation. +Native MediaRecorder captures audio and Web Audio converts it to the Host PCM format after flushing the final recorder chunks. Window blur during the microphone permission request does not cancel capture; blur during recording does. Capture failures immediately end the recording UI and offer retry inside the toolbar. Cancellation, failure and plugin disposal share one resource-release promise; the plugin retains ownership until AudioContext closure settles. The original editor selection carries a draft revision: changed drafts retain the transcript for explicit insertion or discard beside the microphone. Session changes, hiding the page during capture and plugin disposal invalidate late results and release tracks. One plugin-owned readiness subscription serves the composer, installation prompt and details. These views use the existing Slot lifecycle and own no preparation tasks.
diff --git a/packages/experimental/client-ui-voice-input/README.zh.md b/packages/experimental/client-ui-voice-input/README.zh.md index 282acf7431..ab742a6043 100644 --- a/packages/experimental/client-ui-voice-input/README.zh.md +++ b/packages/experimental/client-ui-voice-input/README.zh.md @@ -37,7 +37,7 @@ kind: "package-reference"
维护者信息 — 点击展开 -原生 MediaRecorder 捕获音频,等待最后一块数据后由 Web Audio 转换为 Host 的 PCM 格式。等待麦克风授权时窗口失焦不会取消采集;录音期间失焦会取消。采集失败立即结束录音界面,并在活动栏内提供重试。取消、失败和插件释放共享同一个资源释放 Promise;插件保留所有权,直到 AudioContext 关闭任务结束。原编辑器选区携带草稿版本:草稿变化时,转写文字保留在麦克风旁,供显式插入或丢弃。切换 Session、录音期间隐藏页面和释放插件会使迟到结果失效并释放轨道。插件拥有的一份准备状态订阅服务于输入框、安装引导和详情。这些视图使用既有 Slot 生命周期,不拥有准备任务。不发布运行时不变量伴随模块,因为准备状态来自一份 Host 订阅,录音状态属于一个捕获操作。 +原生 MediaRecorder 捕获音频,等待最后一块数据后由 Web Audio 转换为 Host 的 PCM 格式。等待麦克风授权时窗口失焦不会取消采集;录音期间失焦会取消。采集失败立即结束录音界面,并在活动栏内提供重试。取消、失败和插件释放共享同一个资源释放 Promise;插件保留所有权,直到 AudioContext 关闭任务结束。原编辑器选区携带草稿版本:草稿变化时,转写文字保留在麦克风旁,供显式插入或丢弃。切换 Session、录音期间隐藏页面和释放插件会使迟到结果失效并释放轨道。插件拥有的一份准备状态订阅服务于输入框、安装引导和详情。这些视图使用既有 Slot 生命周期,不拥有准备任务。
diff --git a/packages/experimental/computer-use-cua-driver-mcp/README.i18n.yaml b/packages/experimental/computer-use-cua-driver-mcp/README.i18n.yaml index 892b8a9978..37615a0cba 100644 --- a/packages/experimental/computer-use-cua-driver-mcp/README.i18n.yaml +++ b/packages/experimental/computer-use-cua-driver-mcp/README.i18n.yaml @@ -24,8 +24,8 @@ en: 425155bfbc7c6c3c zh: 6b567b3595388b2e /deepseek-ai-dsh-experimental-computer-use-cua-driver-mcp/understand-the-implementation: - en: 95f7a180b2b3eb6e - zh: b5916cec6c4d4ad5 + en: 13e164882813f89e + zh: dd8abaad16d1e76a /deepseek-ai-dsh-experimental-computer-use-cua-driver-mcp/understand-the-implementation/verify-an-installed-driver: en: a25c4307524e25cf zh: 38ff4f5fd45b0ffb diff --git a/packages/experimental/computer-use-cua-driver-mcp/README.md b/packages/experimental/computer-use-cua-driver-mcp/README.md index 33d5e0fbf8..cb31a5d490 100644 --- a/packages/experimental/computer-use-cua-driver-mcp/README.md +++ b/packages/experimental/computer-use-cua-driver-mcp/README.md @@ -64,8 +64,6 @@ The model sees tools under the fixed `mcp__cua-driver-mcp__` namespace. Tool nam [`src/index.ts`](src/index.ts) groups the computer-use reservation and owned MCP child into one ordered effect. Child teardown finishes before the reservation disposer runs, including during failed activation. The MCP client owns credential scrubbing, subprocess termination, tool synchronization, cancellation, and durable image projection. -No runtime invariant companion is published: the provider exposes no independent driver state to compare with its registration, and the child owns its connection and tool generations. - ### Verify an installed driver From the repository root, opt into the live compatibility test with the absolute path of a Cua Driver executable. It discovers tools, calls `check_permissions` with `prompt: false`, and verifies teardown. On macOS, `--direct` runs the runtime in the MCP process using the launching host's permissions; omit `DSH_COMPUTER_USE_MCP_ARGS` to use the default `["mcp"]` arguments. diff --git a/packages/experimental/computer-use-cua-driver-mcp/README.zh.md b/packages/experimental/computer-use-cua-driver-mcp/README.zh.md index 2c2dbee631..8ccdbb6212 100644 --- a/packages/experimental/computer-use-cua-driver-mcp/README.zh.md +++ b/packages/experimental/computer-use-cua-driver-mcp/README.zh.md @@ -64,8 +64,6 @@ kind: "package-reference" [`src/index.ts`](src/index.ts) 将计算机使用注册和所属 MCP 子插件归入同一个有序副作用。子插件完成清理后才运行注册释放函数,激活失败时也遵循这一顺序。MCP 客户端负责凭据过滤、子进程终止、工具同步、取消和持久化图像投影。 -本包不发布运行时不变量伴随插件:提供者没有独立的驱动状态可与注册比较,连接和工具代次由子插件持有。 - ### 验证已安装的驱动 在仓库根目录,用 Cua Driver 可执行程序的绝对路径显式启用真实兼容性测试。测试发现工具,以 `prompt: false` 调用 `check_permissions`,并验证清理结果。在 macOS 上,`--direct` 使用启动宿主的权限在 MCP 进程中运行驱动;省略 `DSH_COMPUTER_USE_MCP_ARGS` 则使用默认的 `["mcp"]` 参数。 diff --git a/packages/experimental/computer-use-cua-driver-native/README.i18n.yaml b/packages/experimental/computer-use-cua-driver-native/README.i18n.yaml index 71d862bdb7..6cd4a52c83 100644 --- a/packages/experimental/computer-use-cua-driver-native/README.i18n.yaml +++ b/packages/experimental/computer-use-cua-driver-native/README.i18n.yaml @@ -27,8 +27,8 @@ en: c3022cbadb94589b zh: b426ea3dbeafd352 /deepseek-ai-dsh-experimental-computer-use-cua-driver-native/understand-the-implementation: - en: 956c3b0fb827a163 - zh: 88bdf97cc5e36230 + en: b397becc466ba383 + zh: 758a149cf002bd11 /deepseek-ai-dsh-experimental-computer-use-cua-driver-native/further-exploration: en: d24023319ead888c zh: ab28be480aced0a9 diff --git a/packages/experimental/computer-use-cua-driver-native/README.md b/packages/experimental/computer-use-cua-driver-native/README.md index 28e47f1702..3d5fb83810 100644 --- a/packages/experimental/computer-use-cua-driver-native/README.md +++ b/packages/experimental/computer-use-cua-driver-native/README.md @@ -63,7 +63,6 @@ The provider reserves the shared computer-use registration before loading native | File | Role | |---|---| | [src/index.ts](src/index.ts) | Native runtime ownership, catalog validation, tool registration, and provider guidance | -| — | No runtime invariant companion is published; resource ownership has no independently observed state to compare. | Tool definitions reuse the existing MCP result adapter. Cua Driver's JSON catalog determines the schemas; its raw result supplies canonical text, structured output, and image bytes. The computer-use service carries only the provider name and exclusive registration. diff --git a/packages/experimental/computer-use-cua-driver-native/README.zh.md b/packages/experimental/computer-use-cua-driver-native/README.zh.md index ce25309e77..5fba221cc0 100644 --- a/packages/experimental/computer-use-cua-driver-native/README.zh.md +++ b/packages/experimental/computer-use-cua-driver-native/README.zh.md @@ -63,7 +63,6 @@ env -u NODE_USE_ENV_PROXY DSH_COMPUTER_USE_NATIVE_E2E=1 node node_modules/vitest | 文件 | 职责 | |---|---| | [src/index.ts](src/index.ts) | 原生运行时所有权、目录校验、工具注册和提供者指导文本 | -| — | 不发布运行时不变量伴随模块;资源所有权没有可独立观测并比较的状态。 | 工具定义复用现有 MCP 结果适配器。Cua Driver 的 JSON 目录决定 schema,其原始结果提供规范文本、结构化输出和图像字节。电脑操作服务只保存提供者名称并保证独占注册。 diff --git a/packages/experimental/inspector/README.i18n.yaml b/packages/experimental/inspector/README.i18n.yaml index 626859a4e3..17121e821e 100644 --- a/packages/experimental/inspector/README.i18n.yaml +++ b/packages/experimental/inspector/README.i18n.yaml @@ -42,5 +42,5 @@ en: a35e1559aff5e7aa zh: fbe20422dbe4a0f6 /deepseek-ai-dsh-experimental-inspector/known-limitations-and-deferred-work/dev-note: - en: 59a59a4788c80163 - zh: db23a6b9dc0880ba + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/experimental/inspector/README.md b/packages/experimental/inspector/README.md index 6a16e5f7c4..7f16348e73 100644 --- a/packages/experimental/inspector/README.md +++ b/packages/experimental/inspector/README.md @@ -152,5 +152,3 @@ None; this package neither assembles nor sends a provider request. None. - -**Runtime invariant:** No companion is published. Wire parsing, generations, Worker lifecycle, and CDP sessions reject invalid relationships in their owning operations. diff --git a/packages/experimental/inspector/README.zh.md b/packages/experimental/inspector/README.zh.md index 05756181e1..af80f63ae6 100644 --- a/packages/experimental/inspector/README.zh.md +++ b/packages/experimental/inspector/README.zh.md @@ -152,5 +152,3 @@ CDP target 通过 `Runtime.evaluate` 提供 Host 和已连接 Client realm 中 无。 - -**运行时不变式:** 不发布伴生入口。wire 解析、generation、Worker 生命周期与 CDP 会话会在所属操作中拒绝无效关系。 diff --git a/packages/experimental/ptc-runtime-python/README.i18n.yaml b/packages/experimental/ptc-runtime-python/README.i18n.yaml index 9edf84d505..05ce54258a 100644 --- a/packages/experimental/ptc-runtime-python/README.i18n.yaml +++ b/packages/experimental/ptc-runtime-python/README.i18n.yaml @@ -42,8 +42,8 @@ en: 167a08a71870f227 zh: 43f671b45104849f /deepseek-ai-dsh-experimental-ptc-runtime-python/understand-the-implementation/source-map: - en: 254759d380b5a58a - zh: 471f21e1c06c324d + en: 4f322d79bbf578f6 + zh: 7f7f9187f49af439 /deepseek-ai-dsh-experimental-ptc-runtime-python/further-exploration: en: ab4a08f301de599d zh: e698b35ccf3ce665 diff --git a/packages/experimental/ptc-runtime-python/README.md b/packages/experimental/ptc-runtime-python/README.md index 287617991c..e9ab2f984e 100644 --- a/packages/experimental/ptc-runtime-python/README.md +++ b/packages/experimental/ptc-runtime-python/README.md @@ -77,7 +77,6 @@ Completion values and binding arguments cross as exact JSON: values serialize wi | [`py/protocol.py`](py/protocol.py) | Python side: `PROTOCOL_FD`, `TypedDict` frame mirrors, `log_truncation_marker` | | [`tests/runtime.spec.ts`](tests/runtime.spec.ts) | Real-subprocess suite: budgets, containment, hostile frames, name rebinding | | [`tests/protocol-mirror.e2e.ts`](tests/protocol-mirror.e2e.ts) | Cross-language mirror test against a real `python3` | -| — | No runtime invariant companion is published: frame ordering, budget accounting, and teardown live in the CPython child or on fd 3, so this package exposes no same-process event sequence or independently maintained mutable relation for a Cordis listener to compare; the protocol mirror and real-subprocess tests cover those process-boundary behaviors. | diff --git a/packages/experimental/ptc-runtime-python/README.zh.md b/packages/experimental/ptc-runtime-python/README.zh.md index f0c64a535c..fe3a6b75ae 100644 --- a/packages/experimental/ptc-runtime-python/README.zh.md +++ b/packages/experimental/ptc-runtime-python/README.zh.md @@ -77,7 +77,6 @@ kind: "package-reference" | [`py/protocol.py`](py/protocol.py) | Python 侧:`PROTOCOL_FD`、`TypedDict` 帧镜像、`log_truncation_marker` | | [`tests/runtime.spec.ts`](tests/runtime.spec.ts) | 真实子进程套件:预算、隔离、敌意帧、名称重绑 | | [`tests/protocol-mirror.e2e.ts`](tests/protocol-mirror.e2e.ts) | 对照真实 `python3` 的跨语言镜像测试 | -| — | 不发布运行时不变式配套项:帧顺序、预算计量与拆卸发生在 CPython 子进程或 fd 3 上,因此本包没有可供 Cordis listener 比较的同进程事件序列或独立维护的可变关系;协议镜像与真实子进程测试覆盖这些进程边界行为。 | diff --git a/packages/experimental/schedule-bundle/README.i18n.yaml b/packages/experimental/schedule-bundle/README.i18n.yaml index e4b5d36a4f..d7e5d0abc8 100644 --- a/packages/experimental/schedule-bundle/README.i18n.yaml +++ b/packages/experimental/schedule-bundle/README.i18n.yaml @@ -18,8 +18,8 @@ en: 632033572617d7bc zh: b2a050e68cf3f3e1 /deepseek-ai-dsh-experimental-schedule-bundle/understand-the-implementation: - en: fce278be320c1ade - zh: 8f81ac0154ea01a1 + en: c8ab7f8f8c3e682d + zh: ec72ef23076f8060 /deepseek-ai-dsh-experimental-schedule-bundle/further-exploration: en: c66c056c527188f1 zh: 2781b2ff3420c319 diff --git a/packages/experimental/schedule-bundle/README.md b/packages/experimental/schedule-bundle/README.md index b679f176ff..6ebd140517 100644 --- a/packages/experimental/schedule-bundle/README.md +++ b/packages/experimental/schedule-bundle/README.md @@ -35,7 +35,7 @@ Open Plugins in the Web sidebar and enable Automation tasks, marked by an alarm
Maintainer details — click to expand -`cordis.patch.yml` inserts the three rows, and `package.json` depends on their packages so each row resolves from this bundle. `OPTIONAL_BUNDLES` in `packages/boot/app-boot/src/profile.ts` names this package and `apps/cli` depends on it, so every installation ships it switched off and the plugin manager offers it in the Official group. Selecting it appends the bundle to the profile's `dsh.profile.bundles` list. No runtime invariant companion is published because this configuration-only package owns no mutable runtime state. +`cordis.patch.yml` inserts the three rows, and `package.json` depends on their packages so each row resolves from this bundle. `OPTIONAL_BUNDLES` in `packages/boot/app-boot/src/profile.ts` names this package and `apps/cli` depends on it, so every installation ships it switched off and the plugin manager offers it in the Official group. Selecting it appends the bundle to the profile's `dsh.profile.bundles` list. | File | Role | |---|---| diff --git a/packages/experimental/schedule-bundle/README.zh.md b/packages/experimental/schedule-bundle/README.zh.md index 99a07f00ae..b480155549 100644 --- a/packages/experimental/schedule-bundle/README.zh.md +++ b/packages/experimental/schedule-bundle/README.zh.md @@ -35,7 +35,7 @@ kind: "package-bundle"
维护者信息 — 点击展开 -`cordis.patch.yml` 插入这三个条目,`package.json` 依赖它们的包,使每个条目都从此 Bundle 解析。`packages/boot/app-boot/src/profile.ts` 的 `OPTIONAL_BUNDLES` 列出此包,`apps/cli` 依赖它,因此每次安装都随包携带且默认禁用,插件管理页在“官方”分组中提供它。选中后会把该 Bundle 追加到 profile 的 `dsh.profile.bundles` 列表。此纯配置包不拥有可变的运行时状态,因此不发布不变量伴随模块。 +`cordis.patch.yml` 插入这三个条目,`package.json` 依赖它们的包,使每个条目都从此 Bundle 解析。`packages/boot/app-boot/src/profile.ts` 的 `OPTIONAL_BUNDLES` 列出此包,`apps/cli` 依赖它,因此每次安装都随包携带且默认禁用,插件管理页在“官方”分组中提供它。选中后会把该 Bundle 追加到 profile 的 `dsh.profile.bundles` 列表。 | 文件 | 作用 | |---|---| diff --git a/packages/experimental/speech-to-text-sensevoice/README.i18n.yaml b/packages/experimental/speech-to-text-sensevoice/README.i18n.yaml index fb905617ff..42e155127e 100644 --- a/packages/experimental/speech-to-text-sensevoice/README.i18n.yaml +++ b/packages/experimental/speech-to-text-sensevoice/README.i18n.yaml @@ -18,8 +18,8 @@ en: 5205fc2456b2c2c2 zh: ed9dbcb71d48eb98 /deepseek-ai-dsh-experimental-speech-to-text-sensevoice/understand-the-implementation: - en: bb96ebc59b8ed6a5 - zh: e59452eb0ddd9fe6 + en: 73f576b08a6f2639 + zh: e7189948a7f2c3dd /deepseek-ai-dsh-experimental-speech-to-text-sensevoice/further-exploration: en: 5c64426add9d79d6 zh: 4e06aa2ead8d2ccd diff --git a/packages/experimental/speech-to-text-sensevoice/README.md b/packages/experimental/speech-to-text-sensevoice/README.md index 3d553d2a23..ed4497546a 100644 --- a/packages/experimental/speech-to-text-sensevoice/README.md +++ b/packages/experimental/speech-to-text-sensevoice/README.md @@ -43,7 +43,7 @@ Every activation checks the selected model, tokens and VAD on disk. Complete ver
Maintainer details — click to expand -One managed Node subprocess serializes inference. Recordings submitted during wake-up join the bounded queue. At execution, the worker requires ready or standby resources. Transcription never starts preparation downloads. `threads`, `segmentSeconds`, `vadThreshold`, `minSpeechSeconds` and `minSilenceSeconds` tune CPU inference and segmentation; `maxPending` bounds accepted work. Preparation and inference have separate deadlines. Invalid language or WAV input is rejected before native inference and retains the loaded worker; inference failures, malformed responses and transport failures reclaim it. Cancellation terminates an active worker and joins its process range; cancelled queued recordings never run. `idleTimeoutMs` releases the worker after inactivity, with zero retaining it until disposal. Models remain cached. Authenticated loopback requests keep audio in memory. Electron workers run in Node mode and copy VAD buffers to satisfy the V8 memory cage. No runtime invariant companion is published because `dsh-subprocess` owns process-range observations and this provider has no independent projection to reconcile. +One managed Node subprocess serializes inference. Recordings submitted during wake-up join the bounded queue. At execution, the worker requires ready or standby resources. Transcription never starts preparation downloads. `threads`, `segmentSeconds`, `vadThreshold`, `minSpeechSeconds` and `minSilenceSeconds` tune CPU inference and segmentation; `maxPending` bounds accepted work. Preparation and inference have separate deadlines. Invalid language or WAV input is rejected before native inference and retains the loaded worker; inference failures, malformed responses and transport failures reclaim it. Cancellation terminates an active worker and joins its process range; cancelled queued recordings never run. `idleTimeoutMs` releases the worker after inactivity, with zero retaining it until disposal. Models remain cached. Authenticated loopback requests keep audio in memory. Electron workers run in Node mode and copy VAD buffers to satisfy the V8 memory cage. The Host owns preparation across page and Session changes: check resources, download recognition weights and tokens, download VAD, verify files, and load the worker. Explicit model paths omit their download steps. Downloads report bytes; other steps report elapsed time. An idle worker retains prepared paths and needs only model loading on wake-up. Cancellation after a preparation result is published waits for cleanup without replacing that result. diff --git a/packages/experimental/speech-to-text-sensevoice/README.zh.md b/packages/experimental/speech-to-text-sensevoice/README.zh.md index e4ab23cc48..27d8584fdb 100644 --- a/packages/experimental/speech-to-text-sensevoice/README.zh.md +++ b/packages/experimental/speech-to-text-sensevoice/README.zh.md @@ -43,7 +43,7 @@ kind: "package-reference"
维护者信息 — 点击展开 -一个托管 Node 子进程串行执行推理。唤醒期间提交的录音进入有界队列。工作进程在执行时要求资源处于就绪或待唤醒状态。转写不会启动准备下载。`threads`、`segmentSeconds`、`vadThreshold`、`minSpeechSeconds` 和 `minSilenceSeconds` 调节 CPU 推理与分段;`maxPending` 限制已接收任务数。准备和推理分别有截止时间。无效语言或 WAV 输入在原生推理前被拒绝,保留已加载的工作进程;推理失败、响应格式错误和传输失败会回收进程。取消会终止运行中的工作进程并等待其进程范围退出;已取消的排队录音不会执行。`idleTimeoutMs` 在空闲后释放进程,零表示保留至插件释放。模型继续保留在缓存中。带认证的环回请求仅在内存中保存音频。Electron 工作进程使用 Node 模式,并复制 VAD 缓冲区以兼容 V8 内存笼。`dsh-subprocess` 拥有进程范围观测;本包没有需要协调的独立投影,因此不发布不变量伴随模块。 +一个托管 Node 子进程串行执行推理。唤醒期间提交的录音进入有界队列。工作进程在执行时要求资源处于就绪或待唤醒状态。转写不会启动准备下载。`threads`、`segmentSeconds`、`vadThreshold`、`minSpeechSeconds` 和 `minSilenceSeconds` 调节 CPU 推理与分段;`maxPending` 限制已接收任务数。准备和推理分别有截止时间。无效语言或 WAV 输入在原生推理前被拒绝,保留已加载的工作进程;推理失败、响应格式错误和传输失败会回收进程。取消会终止运行中的工作进程并等待其进程范围退出;已取消的排队录音不会执行。`idleTimeoutMs` 在空闲后释放进程,零表示保留至插件释放。模型继续保留在缓存中。带认证的环回请求仅在内存中保存音频。Electron 工作进程使用 Node 模式,并复制 VAD 缓冲区以兼容 V8 内存笼。 Host 在页面和 Session 变化期间拥有准备任务:检查资源、下载识别权重与词表、下载 VAD、校验文件、加载工作进程。显式模型路径会省略对应下载步骤。下载报告字节,其他步骤报告等待时间。空闲进程保留已准备路径,唤醒时只需加载模型。准备结果发布后再取消,只等待任务收尾,不会覆盖该结果。 diff --git a/packages/experimental/speech-to-text/README.i18n.yaml b/packages/experimental/speech-to-text/README.i18n.yaml index e81c2e3b42..5149d7e2ea 100644 --- a/packages/experimental/speech-to-text/README.i18n.yaml +++ b/packages/experimental/speech-to-text/README.i18n.yaml @@ -18,8 +18,8 @@ en: faf7c49d8870eaad zh: 9c1c42eba9660f75 /deepseek-ai-dsh-experimental-speech-to-text/understand-the-implementation: - en: ed5da4ea3ef6a0a8 - zh: 7f12a1f42cd48c36 + en: 2b1648ac92551bc0 + zh: 41c5a2405711d7c2 /deepseek-ai-dsh-experimental-speech-to-text/further-exploration: en: 5c64426add9d79d6 zh: 4e06aa2ead8d2ccd diff --git a/packages/experimental/speech-to-text/README.md b/packages/experimental/speech-to-text/README.md index e96d3c09d8..afaa0089dc 100644 --- a/packages/experimental/speech-to-text/README.md +++ b/packages/experimental/speech-to-text/README.md @@ -37,7 +37,7 @@ Providers advertise preparation origins through `downloadSources`. `prepare(id,
Maintainer details — click to expand -`resolve()` captures the provider instance, recording and language. `transcribe()` rejects a withdrawn or replaced registration. A registration disposer closes admission, aborts accepted requests and joins provider settlement; providers must honor cancellation. No fallback selects a different recognizer or uploads audio. No runtime invariant companion is published because the registry is the sole source of provider and preparation observations. +`resolve()` captures the provider instance, recording and language. `transcribe()` rejects a withdrawn or replaced registration. A registration disposer closes admission, aborts accepted requests and joins provider settlement; providers must honor cancellation. No fallback selects a different recognizer or uploads audio. `defaultProvider` and `language` are volatile Config fields: `configure()` writes the supplied fields into this plugin's profile entry through the `settings` service, and the running instance reads the updated values without remounting; composition defaults apply until an override is saved. Settings addresses the entry by its configured id (`entry.options.id`), without the Loader's Include path. `configure()` fails without `settings` or a profile entry, and rejects a language unsupported by the selected provider before saving. Providers advertise accepted language hints through `languages`; `resolve()` validates the selected hint before transcription. Provider-owned preparation is observed through complete `SpeechSnapshot` values; closing an observer never cancels preparation. diff --git a/packages/experimental/speech-to-text/README.zh.md b/packages/experimental/speech-to-text/README.zh.md index 23a6a4850a..a613480e0d 100644 --- a/packages/experimental/speech-to-text/README.zh.md +++ b/packages/experimental/speech-to-text/README.zh.md @@ -37,7 +37,7 @@ kind: "package-reference"
维护者信息 — 点击展开 -`resolve()` 捕获 Provider 实例、录音和语言。`transcribe()` 拒绝已撤销或替换的注册。注册的清理函数先拒绝新请求,再取消已接收的请求并等待 Provider 完成;Provider 必须响应取消。服务不会回退到其他识别器或上传音频。不发布运行时不变量伴随模块,因为注册表是 Provider 与准备状态观测的唯一来源。 +`resolve()` 捕获 Provider 实例、录音和语言。`transcribe()` 拒绝已撤销或替换的注册。注册的清理函数先拒绝新请求,再取消已接收的请求并等待 Provider 完成;Provider 必须响应取消。服务不会回退到其他识别器或上传音频。 `defaultProvider` 与 `language` 是 volatile Config 字段:`configure()` 通过 `settings` 服务把传入的字段写入本插件的 profile 条目,运行中的实例无需重挂载即可读到更新值;保存覆盖值之前使用组合配置默认值。Settings 使用条目配置中的 id(`entry.options.id`)定位条目,不包含 Loader 的 Include 路径。没有 `settings` 或 profile 条目时 `configure()` 失败,并在保存前拒绝所选 Provider 不支持的语言。Provider 通过 `languages` 公布支持的语言提示;`resolve()` 在转写前校验所选提示。观察者通过完整 `SpeechSnapshot` 接收 Provider 拥有的准备状态;关闭观察者不会取消准备。 diff --git a/packages/experimental/tool-agent-team/README.i18n.yaml b/packages/experimental/tool-agent-team/README.i18n.yaml index 1eaa7bd4c0..ff1e662939 100644 --- a/packages/experimental/tool-agent-team/README.i18n.yaml +++ b/packages/experimental/tool-agent-team/README.i18n.yaml @@ -36,8 +36,8 @@ en: 445806c13f887a09 zh: 8b2b67044abfd65a /deepseek-ai-dsh-experimental-tool-agent-team/understand-the-implementation/source-map: - en: 0e9eb6c8bebebbb6 - zh: 6acd0bc8af90ce0a + en: 29c1bff5023123be + zh: 079c4819ec9ef277 /deepseek-ai-dsh-experimental-tool-agent-team/understand-the-implementation/policy-and-tools: en: 3e730667f904c7bd zh: 7f48e6c07c615d77 diff --git a/packages/experimental/tool-agent-team/README.md b/packages/experimental/tool-agent-team/README.md index 0918686d28..5783096657 100644 --- a/packages/experimental/tool-agent-team/README.md +++ b/packages/experimental/tool-agent-team/README.md @@ -92,7 +92,6 @@ The [Agent Teams Agent Note](../../../.agents/notes/implemented/feature/2026-08- | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: config, the fixed policy text, and the nine scoped tool registrations | -| — | No runtime invariant companion is published; the Team service owns durable and authorization relations. | ### Policy and tools diff --git a/packages/experimental/tool-agent-team/README.zh.md b/packages/experimental/tool-agent-team/README.zh.md index 8243f8471a..66529ce99d 100644 --- a/packages/experimental/tool-agent-team/README.zh.md +++ b/packages/experimental/tool-agent-team/README.zh.md @@ -92,7 +92,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:配置、固定策略文本与九个 scoped 工具注册 | -| — | 不发布运行时不变式伴生入口;Team 服务拥有持久化与授权关系。 | ### 策略与工具 diff --git a/packages/experimental/voice-input-bundle/README.i18n.yaml b/packages/experimental/voice-input-bundle/README.i18n.yaml index fb360727a0..1bcaba6dac 100644 --- a/packages/experimental/voice-input-bundle/README.i18n.yaml +++ b/packages/experimental/voice-input-bundle/README.i18n.yaml @@ -18,8 +18,8 @@ en: 9151bf11e64b4f1c zh: 7c9638c16ac6a0db /deepseek-ai-dsh-experimental-voice-input-bundle/understand-the-implementation: - en: fd8c48acf2a8c0e9 - zh: 6aabab11912f0e3d + en: 45c5f442f85483a8 + zh: 1e6d54bcaef39b89 /deepseek-ai-dsh-experimental-voice-input-bundle/further-exploration: en: 5c64426add9d79d6 zh: 4e06aa2ead8d2ccd diff --git a/packages/experimental/voice-input-bundle/README.md b/packages/experimental/voice-input-bundle/README.md index 48320f7430..70046ec145 100644 --- a/packages/experimental/voice-input-bundle/README.md +++ b/packages/experimental/voice-input-bundle/README.md @@ -35,7 +35,7 @@ Open Plugins in the Web sidebar and enable Voice Input, marked by a blue wavefor
Maintainer details — click to expand -The static `cordis.patch.yml` adds the four voice rows, selects `sensevoice-local` as the default recognizer and supplies the provider cache directory with `dshHomePath`. Optional-bundle installation makes the package available to management without selecting it in default profiles. The browser contribution owns its generated Remote mount; stable API Remotes do not import experimental code. No runtime invariant companion is published because this configuration-only package has no independently mutable runtime state. +The static `cordis.patch.yml` adds the four voice rows, selects `sensevoice-local` as the default recognizer and supplies the provider cache directory with `dshHomePath`. Optional-bundle installation makes the package available to management without selecting it in default profiles. The browser contribution owns its generated Remote mount; stable API Remotes do not import experimental code.
diff --git a/packages/experimental/voice-input-bundle/README.zh.md b/packages/experimental/voice-input-bundle/README.zh.md index f0a5b49741..51ed5a6c34 100644 --- a/packages/experimental/voice-input-bundle/README.zh.md +++ b/packages/experimental/voice-input-bundle/README.zh.md @@ -35,7 +35,7 @@ kind: "package-bundle"
维护者信息 — 点击展开 -静态 `cordis.patch.yml` 添加四个语音条目,选择 `sensevoice-local` 作为默认识别器,并通过 `dshHomePath` 提供 Provider 缓存目录。可选 Bundle 安装使插件管理器能够发现此包,但不会在默认配置中选择它。浏览器贡献拥有其生成 Remote 的挂载;稳定 API Remotes 不导入实验性代码。此纯配置包没有独立可变的运行时状态,因此不发布不变量伴随模块。 +静态 `cordis.patch.yml` 添加四个语音条目,选择 `sensevoice-local` 作为默认识别器,并通过 `dshHomePath` 提供 Provider 缓存目录。可选 Bundle 安装使插件管理器能够发现此包,但不会在默认配置中选择它。浏览器贡献拥有其生成 Remote 的挂载;稳定 API Remotes 不导入实验性代码。
diff --git a/packages/experimental/webworker-packer/README.i18n.yaml b/packages/experimental/webworker-packer/README.i18n.yaml index 1e8ca36746..c57717a498 100644 --- a/packages/experimental/webworker-packer/README.i18n.yaml +++ b/packages/experimental/webworker-packer/README.i18n.yaml @@ -27,5 +27,5 @@ en: 6cd07b34072a2d61 zh: 44c4017e727c19ff /deepseek-ai-dsh-experimental-webworker-packer/known-limitations-and-deferred-work/dev-note: - en: 46176f90f22845c7 - zh: 8e6ba8ba2958fba3 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/experimental/webworker-packer/README.md b/packages/experimental/webworker-packer/README.md index dfb3eccf83..5bf40de1aa 100644 --- a/packages/experimental/webworker-packer/README.md +++ b/packages/experimental/webworker-packer/README.md @@ -71,5 +71,3 @@ None; this package neither assembles nor sends a provider request. None.
- -**Runtime invariant:** No companion is published. This package is a build-time pass with no production event stream or mutable data; the pack's own gates (unresolvable own requests, the all-or-nothing wrapper contract) fail the pack instead. diff --git a/packages/experimental/webworker-packer/README.zh.md b/packages/experimental/webworker-packer/README.zh.md index 3c528628a8..988c9574db 100644 --- a/packages/experimental/webworker-packer/README.zh.md +++ b/packages/experimental/webworker-packer/README.zh.md @@ -71,5 +71,3 @@ VFS 镜像打包器:把一份合成 profile 变成浏览器 worker 挂载为 无。
- -**运行时不变式:** 不发布伴生入口。这是没有生产事件流或可变数据的构建时 pass;无法解析的自有代码请求与全有或全无的包装层约定会直接让 pack 失败。 diff --git a/packages/experimental/webworker-packer/tests/image-loadable.spec.ts b/packages/experimental/webworker-packer/tests/image-loadable.spec.ts index cd21241810..c9f4d002d9 100644 --- a/packages/experimental/webworker-packer/tests/image-loadable.spec.ts +++ b/packages/experimental/webworker-packer/tests/image-loadable.spec.ts @@ -84,12 +84,11 @@ describe('preview example overlays', () => { * preview builds exercise the same path against complete real artifacts. */ // The subject is zero-dep, but its peer/dependency closure (cordis, loader, -// include, cosmokit, invariants) must also be built: on the complete lane +// include, cosmokit) must also be built: on the complete lane // build and coverage run concurrently, so checking only the subject lets the // pack start before its real workspace dependencies exist. const subjectBuilt = [ 'packages/util/timeout/lib/index.js', - 'packages/runtime-diagnostics/invariants/lib/index.js', 'vendor/cordis/lib/index.js', 'vendor/cosmokit/lib/index.js', 'vendor/include/lib/index.js', diff --git a/packages/experimental/webworker-runtime/README.i18n.yaml b/packages/experimental/webworker-runtime/README.i18n.yaml index d720e524cd..ec67a1235f 100644 --- a/packages/experimental/webworker-runtime/README.i18n.yaml +++ b/packages/experimental/webworker-runtime/README.i18n.yaml @@ -27,5 +27,5 @@ en: 6fe586817a42fdad zh: c19ab678d61e82d3 /deepseek-ai-dsh-experimental-webworker-runtime/known-limitations-and-deferred-work/dev-note: - en: f14ab6f2449c3c53 - zh: e3f7d3a3d772c0b7 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/experimental/webworker-runtime/README.md b/packages/experimental/webworker-runtime/README.md index f968b40ec0..2062660bc1 100644 --- a/packages/experimental/webworker-runtime/README.md +++ b/packages/experimental/webworker-runtime/README.md @@ -74,5 +74,3 @@ None; this package neither assembles nor sends a provider request. None.
- -**Runtime invariant:** No companion is published. This package is pre-Cordis platform glue — the tree it boots runs the product packages' own invariants, and the assembly's contracts (image contract gate, tunnel refusals) fail loud at boot rather than drifting at run time. diff --git a/packages/experimental/webworker-runtime/README.zh.md b/packages/experimental/webworker-runtime/README.zh.md index 43efcf7227..c71e9ae299 100644 --- a/packages/experimental/webworker-runtime/README.zh.md +++ b/packages/experimental/webworker-runtime/README.zh.md @@ -74,5 +74,3 @@ kind: "package-library" 无。
- -**运行时不变式:** 不发布伴生入口。这是 Cordis 启动前的平台 glue;其启动的产品树运行各包自己的不变式,image 与 tunnel 约定在 boot 时失败。 diff --git a/packages/extensions/cordis-client-runner/README.i18n.yaml b/packages/extensions/cordis-client-runner/README.i18n.yaml index df73bddc5e..f68b557073 100644 --- a/packages/extensions/cordis-client-runner/README.i18n.yaml +++ b/packages/extensions/cordis-client-runner/README.i18n.yaml @@ -75,5 +75,5 @@ en: a728aeb4aa18db7d zh: b596309cd6707da7 /deepseek-ai-dsh-cordis-client-runner/known-limitations-and-deferred-work/dev-note: - en: c4cff307d238c93a - zh: d1a9ff4164056741 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/extensions/cordis-client-runner/README.md b/packages/extensions/cordis-client-runner/README.md index c5d9a10bde..07b371528a 100644 --- a/packages/extensions/cordis-client-runner/README.md +++ b/packages/extensions/cordis-client-runner/README.md @@ -143,5 +143,3 @@ These limits define where the browser half needs special care. They are current None.
- -**Runtime invariant:** No companion is published. The owned relation (a live Plugin's loader entry exists exactly while one Plugin Run ID is live) is browser-only state reachable through the client half's service, which the node-plane companion cannot observe. The relation is asserted by the package's own load/teardown coverage instead. diff --git a/packages/extensions/cordis-client-runner/README.zh.md b/packages/extensions/cordis-client-runner/README.zh.md index c6463de9cd..3753a9beca 100644 --- a/packages/extensions/cordis-client-runner/README.zh.md +++ b/packages/extensions/cordis-client-runner/README.zh.md @@ -143,5 +143,3 @@ React 可能在加载成功后失败。Client 报告其拥有的每个 entry 失 无。
- -**运行时不变式:** 不发布伴生入口。所属关系(一个 live Plugin 的 loader entry 仅在一个 Plugin Run ID 存活期间存在)是只能通过 Client 半服务访问的浏览器侧状态,Node 平面的伴生入口无法观察。该关系改由本包自己的装载与拆除测试直接断言。 diff --git a/packages/extensions/cordis-host-runner/README.i18n.yaml b/packages/extensions/cordis-host-runner/README.i18n.yaml index 11b133e12a..ebf1c6ea07 100644 --- a/packages/extensions/cordis-host-runner/README.i18n.yaml +++ b/packages/extensions/cordis-host-runner/README.i18n.yaml @@ -66,5 +66,5 @@ en: 1f10688ae249a077 zh: ab2ff597e1769b81 /deepseek-ai-dsh-cordis-host-runner/known-limitations-and-deferred-work/dev-note: - en: 8111315620c52980 - zh: 261bf47eecf96bad + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/extensions/cordis-host-runner/README.md b/packages/extensions/cordis-host-runner/README.md index 0386aa11e5..9ec47f2cfb 100644 --- a/packages/extensions/cordis-host-runner/README.md +++ b/packages/extensions/cordis-host-runner/README.md @@ -150,5 +150,3 @@ These limits define when the runner needs special care. They are current package None. - -**Runtime invariant:** No companion is published. The definition registry is process memory with no event stream to observe, and its one owned relation (a running definition owns a settled host-half fiber and its handler table) is established and unwound inside single awaited verbs, so package tests assert it directly. diff --git a/packages/extensions/cordis-host-runner/README.zh.md b/packages/extensions/cordis-host-runner/README.zh.md index eb93de475a..5118aa2509 100644 --- a/packages/extensions/cordis-host-runner/README.zh.md +++ b/packages/extensions/cordis-host-runner/README.zh.md @@ -150,5 +150,3 @@ runner 基于两项职责划分。**注册表与沙箱是同一个服务。** `D 无。 - -**运行时不变式:** 不发布伴生入口。definition registry 位于进程内存中且没有可观察的事件流;它唯一负责的关系是运行中的 definition 拥有已结算的 host-half fiber 及其 handler table,该关系在单个等待完成的操作中建立和解除,因此由包测试直接断言。 diff --git a/packages/extensions/tool-cordis/README.i18n.yaml b/packages/extensions/tool-cordis/README.i18n.yaml index 9a445f9a0f..b751e7b216 100644 --- a/packages/extensions/tool-cordis/README.i18n.yaml +++ b/packages/extensions/tool-cordis/README.i18n.yaml @@ -18,8 +18,8 @@ en: e8c90b0ad037513c zh: 563454508c30d9b0 /deepseek-ai-dsh-tool-cordis/understand-the-implementation: - en: de18fe3471a4a341 - zh: fda07860cfe1e325 + en: ff959016d633c6c0 + zh: d84dfa7e9d73532f /deepseek-ai-dsh-tool-cordis/further-exploration: en: 1863373f02bcc196 zh: 3efdbf5569411562 diff --git a/packages/extensions/tool-cordis/README.md b/packages/extensions/tool-cordis/README.md index 54f16c1d2c..acf575c6cf 100644 --- a/packages/extensions/tool-cordis/README.md +++ b/packages/extensions/tool-cordis/README.md @@ -35,7 +35,7 @@ Creator mode includes this toolset. Other compositions mount `@deepseek-ai/dsh-t
Implementation internals — click to expand -Host providers combine generated Service/Event catalogs, the live Loader tree projected through the app-boot Config projector, and the requesting agent's tool registry. Client providers synchronize their manifests through the existing inspection registry and answer queries from a connected page. The host entry owns the Host provider registrations and the preset row owns the two tools, each through Cordis effects; the registry rejects a duplicate provider id, which is why the providers register once per process rather than per preset. No invariant companion is published because inspection reads its providers directly and maintains no independent runtime projection. +Host providers combine generated Service/Event catalogs, the live Loader tree projected through the app-boot Config projector, and the requesting agent's tool registry. Client providers synchronize their manifests through the existing inspection registry and answer queries from a connected page. The host entry owns the Host provider registrations and the preset row owns the two tools, each through Cordis effects; the registry rejects a duplicate provider id, which is why the providers register once per process rather than per preset.
diff --git a/packages/extensions/tool-cordis/README.zh.md b/packages/extensions/tool-cordis/README.zh.md index f6d7f1f85d..06b50786d9 100644 --- a/packages/extensions/tool-cordis/README.zh.md +++ b/packages/extensions/tool-cordis/README.zh.md @@ -35,7 +35,7 @@ kind: "package-reference"
实现细节 — 点击展开 -Host provider 结合生成的 Service/Event 目录、经 app-boot Config 投影器投影的运行中 Loader 树,以及请求 agent 的工具注册表。Client provider 通过现有检查注册表同步清单,并从已连接页面回答查询。宿主入口持有 Host provider 的注册,preset 行持有两个工具,都通过 Cordis effect;注册表拒绝重复的 provider id,所以 provider 按进程注册一次而不是按 preset 注册。检查直接读取 provider,不维护独立运行时投影,因此不发布不变式配套插件。 +Host provider 结合生成的 Service/Event 目录、经 app-boot Config 投影器投影的运行中 Loader 树,以及请求 agent 的工具注册表。Client provider 通过现有检查注册表同步清单,并从已连接页面回答查询。宿主入口持有 Host provider 的注册,preset 行持有两个工具,都通过 Cordis effect;注册表拒绝重复的 provider id,所以 provider 按进程注册一次而不是按 preset 注册。
diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts index a6cf24b0b6..c3e6909511 100644 --- a/packages/extensions/tool-cordis/src/api-catalog.ts +++ b/packages/extensions/tool-cordis/src/api-catalog.ts @@ -1258,19 +1258,6 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ }, ], }, - { - key: 'invariants', - summary: 'Package-owned invariant registry with global and regex-based selection.', - description: 'Package-owned invariant registry with global and regex-based selection.', - methods: [ - { - signature: 'register(packageName: string, installer: InvariantInstaller): () => void', - description: 'Register one package\'s invariant installer. The package name is reserved even when filtering disables its checks. Enabled installers run in a child fiber; failure disposes that fiber and releases the reservation.', - parameters: [{ name: 'packageName', description: 'full npm package name that owns the contribution.' }, { name: 'installer', description: 'listener or startup-check installer for the child context.' }], - returns: 'an effect-scoped disposer for the registration.', - }, - ], - }, { key: 'jobController', summary: 'Host service backing the generated `ctx.remote.job` namespace.', @@ -2391,7 +2378,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ }, { signature: 'async flush(session: Session): Promise', - description: 'Dispatch the awaited `session/flush` durability checkpoint for `session`, with the carrier captured at enter. THE flush entry point: the store owns the carrier, so callers (the checkpoint policy\'s per-request barrier, goal-round-driver\'s idle checkpoint, teardown drains, and consumers that flush themselves before reading storage) must come through here rather than dispatch a raw `ctx.parallel(\'session/flush\', …)` — one owner, one spelling, and the scoped-dispatch invariant can pin it.', + description: 'Dispatch the awaited `session/flush` durability checkpoint for `session`, with the carrier captured at enter. THE flush entry point: the store owns the carrier, so callers (the checkpoint policy\'s per-request barrier, goal-round-driver\'s idle checkpoint, teardown drains, and consumers that flush themselves before reading storage) must come through here rather than dispatch a raw `ctx.parallel(\'session/flush\', …)` — one owner and one spelling.', parameters: [{ name: 'session', description: 'the session whose buffered events must reach durable storage.' }], returns: 'whether at least one durability listener participated, after every listener has settled successfully.', throws: ['the first registered listener failure after every listener settles.'], @@ -2836,7 +2823,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ }, { signature: 'get(name: string): DomainImpl | undefined', - description: 'Look up an open domain by name, untyped. Diagnostic surface (the package invariant cross-checks change events against live domain state); typed consumers hold the handle returned by open.', + description: 'Look up an open domain by name, untyped. Diagnostic surface; typed consumers hold the handle returned by open.', parameters: [{ name: 'name', description: 'Domain name.' }], returns: 'the open domain runtime, or `undefined` when not open.', }, @@ -4018,7 +4005,7 @@ export const EVENT_API: readonly EventApiEntry[] = [ mode: 'emit', signature: '\'credentials/reference-updated\'(ref: CredentialRef): void', summary: 'Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage.', - description: 'Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation\'s outcome, except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions.', + description: 'Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation\'s outcome.', parameters: [{ name: 'ref', description: 'the reference whose stored value changed.' }], }, { @@ -5409,14 +5396,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [ name: 'InvalidTimeZoneError', declaration: 'export interface InvalidTimeZoneError {\n readonly code: \'invalid_time_zone\';\n readonly message: string;\n}', }, - { - name: 'InvariantFailure', - declaration: 'export type InvariantFailure = (message: string) => never;', - }, - { - name: 'InvariantInstaller', - declaration: 'export interface InvariantInstaller {\n (ctx: Context, fail: InvariantFailure): void | Promise;\n readonly inject?: Inject;\n}', - }, { name: 'InvocationDescriptor', declaration: 'export interface InvocationDescriptor {\n readonly id: string;\n readonly service: string;\n readonly namespace: string;\n readonly method: string;\n readonly implementation?: string;\n readonly mode?: \'stream\';\n readonly invocation: {\n readonly kind: \'direct\';\n } | {\n readonly kind: \'context\';\n readonly context: string;\n readonly wire: string;\n readonly codec: TypertCodec;\n };\n readonly scope?: {\n readonly context: string;\n readonly wire: string;\n };\n readonly parameters: readonly InvocationParameterDescriptor[];\n readonly uplink?: {\n readonly codec: TypertCodec;\n };\n readonly cancellation?: {\n readonly parameter: \'signal\';\n };\n readonly result: TypertCodec;\n readonly sourceLocation?: InvocationSourceLocation;\n}', diff --git a/packages/extensions/ui-cordis/README.i18n.yaml b/packages/extensions/ui-cordis/README.i18n.yaml index fe60a049e0..53fee52360 100644 --- a/packages/extensions/ui-cordis/README.i18n.yaml +++ b/packages/extensions/ui-cordis/README.i18n.yaml @@ -51,5 +51,5 @@ en: 3b9cfd230ab18a75 zh: 47b87bb34b9c9d1c /deepseek-ai-dsh-client-ui-cordis/known-limitations-and-deferred-work/dev-note: - en: 82bc60a2bd964744 - zh: 900679483cdbde4b + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/extensions/ui-cordis/README.md b/packages/extensions/ui-cordis/README.md index 02ffc54ebb..027abe4be1 100644 --- a/packages/extensions/ui-cordis/README.md +++ b/packages/extensions/ui-cordis/README.md @@ -124,5 +124,3 @@ These limits define where the surfaces need special care. They are current packa None. - -**Runtime invariant:** No companion is published. A single keyed toolview registration whose disposal is proven by the HMR-safety spec. The one mutable relation this package owns — the per-definition run-state observable — lives in the browser process, out of reach of the host invariant service, and the node half emits no cordis events and holds no cross-plugin state. diff --git a/packages/extensions/ui-cordis/README.zh.md b/packages/extensions/ui-cordis/README.zh.md index 235310346b..c65410af41 100644 --- a/packages/extensions/ui-cordis/README.zh.md +++ b/packages/extensions/ui-cordis/README.zh.md @@ -124,5 +124,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。插件只注册一个 keyed toolview,其资源释放已由 HMR 安全性测试证明。本包拥有的唯一可变关系,即 per-definition run-state 观察量,只存在于浏览器进程中,Host 不变式服务无法触及;Node 端不发出任何 Cordis 事件,也不持有任何跨插件状态。 diff --git a/packages/feedback/command-feedback/README.i18n.yaml b/packages/feedback/command-feedback/README.i18n.yaml index 35e1a1db5a..d36df23378 100644 --- a/packages/feedback/command-feedback/README.i18n.yaml +++ b/packages/feedback/command-feedback/README.i18n.yaml @@ -36,8 +36,8 @@ en: 0c6ceecbb1f45224 zh: 8138bb0fd3e68317 /deepseek-ai-dsh-command-feedback/understand-the-implementation/source-map: - en: 4fd622508f032a28 - zh: fa336bcbaabca58e + en: cb5f1c6c4dfd08b5 + zh: f5785924c0f42afc /deepseek-ai-dsh-command-feedback/further-exploration: en: 3f3b9c53cd589403 zh: 13ca15c564be3521 diff --git a/packages/feedback/command-feedback/README.md b/packages/feedback/command-feedback/README.md index 92d8511c18..d155520a5b 100644 --- a/packages/feedback/command-feedback/README.md +++ b/packages/feedback/command-feedback/README.md @@ -92,7 +92,6 @@ The producer trims the text, records blank text as absent, and writes one event |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `recordFeedback` producer, the `sessionFeedback` Remote service, `/feedback` command registration | | [`src/types.ts`](src/types.ts) | `feedback/record` event declaration, the category taxonomy, and the Remote request and result types | -| — | No runtime invariant companion is published; each `feedback/record` is an independent append-only fact with no cross-event or mutable-data relationship. | diff --git a/packages/feedback/command-feedback/README.zh.md b/packages/feedback/command-feedback/README.zh.md index 4f698f735d..2c0600682d 100644 --- a/packages/feedback/command-feedback/README.zh.md +++ b/packages/feedback/command-feedback/README.zh.md @@ -92,7 +92,6 @@ Web 客户端随附该命令。无头模式、ACP 自动化和 JSON-RPC 不提 |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`recordFeedback` 生产方、`sessionFeedback` Remote 服务、`/feedback` 命令注册 | | [`src/types.ts`](src/types.ts) | `feedback/record` 事件声明、分类表,以及 Remote 请求与结果类型 | -| — | 未发布配套的运行时不变式;每个 `feedback/record` 都是独立的仅追加事实,不涉及跨事件关系或与可变数据的关系。 | diff --git a/packages/feedback/message-feedback/README.i18n.yaml b/packages/feedback/message-feedback/README.i18n.yaml index 8c427f7f60..31431e3281 100644 --- a/packages/feedback/message-feedback/README.i18n.yaml +++ b/packages/feedback/message-feedback/README.i18n.yaml @@ -30,8 +30,8 @@ en: 73ddbcf524f213a1 zh: 050f1a72c7f56fd6 /deepseek-ai-dsh-message-feedback/understand-the-implementation/source-map: - en: 7846ae54837ca631 - zh: 40198060b97891af + en: ab7bd48b93b32f65 + zh: 05d31558beb68581 /deepseek-ai-dsh-message-feedback/model-experience: en: 215e7ba838619b7b zh: a311da3843709f90 diff --git a/packages/feedback/message-feedback/README.md b/packages/feedback/message-feedback/README.md index bd10014ce6..43ad654eef 100644 --- a/packages/feedback/message-feedback/README.md +++ b/packages/feedback/message-feedback/README.md @@ -64,8 +64,6 @@ Cold material mutations emit `feedback/committed` after flush with a borrowed re | [`src/index.ts`](src/index.ts) | Remote service, payload validation, event projection, and persistence ownership | | [`src/types.ts`](src/types.ts) | Requests, results, and Session event declarations; types only | -No runtime invariant companion is published: the service derives feedback directly from validated canonical events and owns no independently mutable projection. - See the [feedback subsystem](../../../docs/subsystems/feedback.md), [Session persistence](../../../docs/subsystems/persistence.md), and [browser consumer](../../client/ui-message-feedback/README.md) for their respective APIs. diff --git a/packages/feedback/message-feedback/README.zh.md b/packages/feedback/message-feedback/README.zh.md index a0c2286294..92bfad1184 100644 --- a/packages/feedback/message-feedback/README.zh.md +++ b/packages/feedback/message-feedback/README.zh.md @@ -64,8 +64,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | Remote 服务、payload 校验、事件投影与持久化所有权 | | [`src/types.ts`](src/types.ts) | 请求、结果和 Session 事件声明;仅类型 | -不发布运行时不变式伴生入口:服务直接从校验后的权威事件推导反馈,不持有可独立修改的投影。 - 各自的 API 见[反馈子系统](../../../docs/subsystems/feedback.zh.md)、[Session 持久化](../../../docs/subsystems/persistence.zh.md)和[浏览器消费方](../../client/ui-message-feedback/README.zh.md)。 diff --git a/packages/fs/fs-local/README.i18n.yaml b/packages/fs/fs-local/README.i18n.yaml index c82192ba90..d5e1ee2008 100644 --- a/packages/fs/fs-local/README.i18n.yaml +++ b/packages/fs/fs-local/README.i18n.yaml @@ -57,5 +57,5 @@ en: 653e3d81d6a5f82d zh: 633f47afeb0da9ba /deepseek-ai-dsh-fs-local/known-limitations-and-deferred-work/dev-note: - en: 9979632783da7b78 - zh: c42c1feec2905268 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/fs/fs-local/README.md b/packages/fs/fs-local/README.md index 9c005443eb..92d853bf86 100644 --- a/packages/fs/fs-local/README.md +++ b/packages/fs/fs-local/README.md @@ -147,5 +147,3 @@ These limits define when the local backend is a poor fit or needs special operat None. - -**Runtime invariant:** No companion is published. This package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. diff --git a/packages/fs/fs-local/README.zh.md b/packages/fs/fs-local/README.zh.md index 3adc249afe..e493ede1bc 100644 --- a/packages/fs/fs-local/README.zh.md +++ b/packages/fs/fs-local/README.zh.md @@ -147,5 +147,3 @@ Chokidar 通过 OS 事件观察单个文件或目录的直接子项,不使用 无。 - -**运行时不变式:** 不发布伴生入口。本包没有独立事件序列或可变数据关系,相关约定在所属 seam 强制执行。 diff --git a/packages/fs/fs-observation-policy/README.i18n.yaml b/packages/fs/fs-observation-policy/README.i18n.yaml index f341718c0f..ef917a29f5 100644 --- a/packages/fs/fs-observation-policy/README.i18n.yaml +++ b/packages/fs/fs-observation-policy/README.i18n.yaml @@ -66,5 +66,5 @@ en: 973f6b0e64b1905c zh: 080396a1283fde2b /deepseek-ai-dsh-fs-observation-policy/known-limitations-and-deferred-work/dev-note: - en: 9979632783da7b78 - zh: c42c1feec2905268 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/fs/fs-observation-policy/README.md b/packages/fs/fs-observation-policy/README.md index 3a4a8fd094..a369cb1c0d 100644 --- a/packages/fs/fs-observation-policy/README.md +++ b/packages/fs/fs-observation-policy/README.md @@ -137,5 +137,3 @@ These limits define when the policy is a poor fit or needs special operational c None. - -**Runtime invariant:** No companion is published. This package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. diff --git a/packages/fs/fs-observation-policy/README.zh.md b/packages/fs/fs-observation-policy/README.zh.md index f79ce256e3..3b0070775c 100644 --- a/packages/fs/fs-observation-policy/README.zh.md +++ b/packages/fs/fs-observation-policy/README.zh.md @@ -137,5 +137,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。本包没有独立事件序列或可变数据关系,相关约定在所属 seam 强制执行。 diff --git a/packages/fs/fs-sandbox/README.i18n.yaml b/packages/fs/fs-sandbox/README.i18n.yaml index fae51c5294..016de77b13 100644 --- a/packages/fs/fs-sandbox/README.i18n.yaml +++ b/packages/fs/fs-sandbox/README.i18n.yaml @@ -63,5 +63,5 @@ en: c0ba1642a5e62459 zh: 10a27abcd6f27888 /deepseek-ai-dsh-fs-sandbox/known-limitations-and-deferred-work/dev-note: - en: e83aa1d2ff6b4366 - zh: f0feffa169cb8bd3 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/fs/fs-sandbox/README.md b/packages/fs/fs-sandbox/README.md index fde670f9a5..9b1e0246e5 100644 --- a/packages/fs/fs-sandbox/README.md +++ b/packages/fs/fs-sandbox/README.md @@ -133,5 +133,3 @@ These limits define when the sandbox backend is a poor fit or needs special oper None. - -**Runtime invariant:** No companion is published. This stateless adapter delegates policy and filesystem relations to their owning seams. diff --git a/packages/fs/fs-sandbox/README.zh.md b/packages/fs/fs-sandbox/README.zh.md index ff58cd0a4e..fc9e4709fb 100644 --- a/packages/fs/fs-sandbox/README.zh.md +++ b/packages/fs/fs-sandbox/README.zh.md @@ -133,5 +133,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。这个无状态适配器把策略与文件系统关系委托给各自所属的 seam。 diff --git a/packages/fs/fs/package.json b/packages/fs/fs/package.json index 47e5d21da4..f1121b55f9 100644 --- a/packages/fs/fs/package.json +++ b/packages/fs/fs/package.json @@ -18,29 +18,22 @@ "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" ], "license": "MIT", "peerDependencies": { "@deepseek-ai/dsh-brand": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-sandbox": "workspace:*", "@deepseek-ai/cordis": "workspace:~" }, "devDependencies": { "@deepseek-ai/dsh-brand": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-sandbox": "workspace:*", "@deepseek-ai/cordis": "workspace:~" diff --git a/packages/fs/fs/src/invariant.ts b/packages/fs/fs/src/invariant.ts deleted file mode 100644 index ec5a22e021..0000000000 --- a/packages/fs/fs/src/invariant.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** Package-owned filesystem event-data invariants. @module @deepseek-ai/dsh-fs/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { FsObservation, FsTarget } from './types.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-fs' - -/** Cordis companion plugin name. */ -export const name = 'fs-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Assert that an event carries a usable opaque target identity. */ -function validateTarget(target: FsTarget, fail: (message: string) => never): void { - if (target.targetKey.length === 0) fail('filesystem event targetKey must be non-empty') - if (target.displayPath.length === 0) fail('filesystem event displayPath must be non-empty') -} - -/** Install checks over the filesystem decision and observation event stream. */ -const install: InvariantInstaller = (ctx, fail) => { - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'fs/write-intent' - && eventName !== 'fs/edit-intent' - && eventName !== 'fs/observed') return - validateTarget(args[0] as FsTarget, fail) - if (eventName === 'fs/observed') { - const observation = args[1] as FsObservation - switch (observation.kind) { - case 'present': - if (observation.version.length === 0) fail('fs/observed present version must be non-empty') - break - case 'absent': - break - default: - fail('fs/observed kind must be present or absent') - } - } - }, { global: true }) -} - -/** - * Register the filesystem 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)) diff --git a/packages/fs/fs/tests/invariant.spec.ts b/packages/fs/fs/tests/invariant.spec.ts deleted file mode 100644 index 794763228b..0000000000 --- a/packages/fs/fs/tests/invariant.spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import { FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs' -import type { FsTarget } from '@deepseek-ai/dsh-fs' -import * as FsInvariant from '@deepseek-ai/dsh-fs/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(FsInvariant) - return ctx -} - -const target = (key = 'file:1', displayPath = 'file.txt'): FsTarget => ({ - targetKey: FsTargetKey(key), - displayPath, -}) - -describe('filesystem invariants', () => { - it('accepts decision and observation events with usable identities', async () => { - const ctx = await setup() - await expect(ctx.waterfall( - ctx as never, 'fs/write-intent', target(), undefined, - () => Promise.resolve(undefined), - )).resolves.toBeUndefined() - await expect(ctx.waterfall( - ctx as never, 'fs/edit-intent', target(), undefined, - () => Promise.resolve(undefined), - )).resolves.toBeUndefined() - expect(() => { - ctx.emit('fs/observed', target(), { kind: 'present', version: FsVersion('v1') }, undefined) - }).not.toThrow() - expect(() => { ctx.emit('fs/observed', target(), { kind: 'absent' }, undefined) }).not.toThrow() - expect(() => { ctx.emit('tools/change') }).not.toThrow() - }) - - it('rejects empty target and version identities', async () => { - const ctx = await setup() - expect(() => { - ctx.emit('fs/observed', target(''), { kind: 'present', version: FsVersion('v1') }, undefined) - }) - .toThrow(/targetKey must be non-empty/) - expect(() => { - ctx.emit('fs/observed', target('file:1', ''), { kind: 'present', version: FsVersion('v1') }, undefined) - }) - .toThrow(/displayPath must be non-empty/) - expect(() => { - ctx.emit('fs/observed', target(), { kind: 'present', version: FsVersion('') }, undefined) - }).toThrow(/present version must be non-empty/) - expect(() => { - ctx.emit('fs/observed', target(), { kind: 'unknown' } as never, undefined) - }).toThrow(/kind must be present or absent/) - }) -}) diff --git a/packages/fs/fs/tsconfig.json b/packages/fs/fs/tsconfig.json index 54a5130d15..563c605906 100644 --- a/packages/fs/fs/tsconfig.json +++ b/packages/fs/fs/tsconfig.json @@ -18,9 +18,6 @@ { "path": "../../llm/llm" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../sandbox/sandbox" } diff --git a/packages/fs/tool-fs-search/README.i18n.yaml b/packages/fs/tool-fs-search/README.i18n.yaml index f1573f81c2..2f41c4f0e9 100644 --- a/packages/fs/tool-fs-search/README.i18n.yaml +++ b/packages/fs/tool-fs-search/README.i18n.yaml @@ -111,5 +111,5 @@ en: 4fca846226cb8ef2 zh: fb797ec586cec7f4 /deepseek-ai-dsh-tool-fs-search/known-limitations-and-deferred-work/dev-note: - en: 0c4d0c465413d319 - zh: 2e062f9e20a99241 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/fs/tool-fs-search/README.md b/packages/fs/tool-fs-search/README.md index d9f5e63fc9..c64e8dee99 100644 --- a/packages/fs/tool-fs-search/README.md +++ b/packages/fs/tool-fs-search/README.md @@ -219,5 +219,3 @@ These limits define when the search tools are a poor fit or need special operati None. - -**Runtime invariant:** No companion is published. This model-facing adapter has no independent lifecycle stream; execution relations are owned by the capability seam it calls. diff --git a/packages/fs/tool-fs-search/README.zh.md b/packages/fs/tool-fs-search/README.zh.md index 611d5d5159..4b7e2debec 100644 --- a/packages/fs/tool-fs-search/README.zh.md +++ b/packages/fs/tool-fs-search/README.zh.md @@ -219,5 +219,3 @@ glob 描述声明了配置的超过上限排序方式。生成的 [`glob` 和 `g 无。 - -**运行时不变式:** 不发布伴生入口。这个面向模型的适配器没有独立生命周期流;执行关系由它调用的能力 seam 负责。 diff --git a/packages/fs/tool-fs/README.i18n.yaml b/packages/fs/tool-fs/README.i18n.yaml index 90d4a216dd..a831d526ba 100644 --- a/packages/fs/tool-fs/README.i18n.yaml +++ b/packages/fs/tool-fs/README.i18n.yaml @@ -138,5 +138,5 @@ en: ce7a91d41d2f7597 zh: d4a3c905e124fd7f /deepseek-ai-dsh-tool-fs/known-limitations-and-deferred-work/dev-note: - en: 0c4d0c465413d319 - zh: a20efeb10121b927 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/fs/tool-fs/README.md b/packages/fs/tool-fs/README.md index a6e889f1d6..f15591b40b 100644 --- a/packages/fs/tool-fs/README.md +++ b/packages/fs/tool-fs/README.md @@ -255,5 +255,3 @@ These limits define when the tool suite is a poor fit or needs special operation None. - -**Runtime invariant:** No companion is published. This model-facing adapter has no independent lifecycle stream; execution relations are owned by the capability seam it calls. diff --git a/packages/fs/tool-fs/README.zh.md b/packages/fs/tool-fs/README.zh.md index 5e56478d92..efaa9a2451 100644 --- a/packages/fs/tool-fs/README.zh.md +++ b/packages/fs/tool-fs/README.zh.md @@ -255,5 +255,3 @@ Read a file before editing it (the default fs-observation-policy requires it), u 无。 - -**运行时不变式:** 不发布伴生入口。这个模型侧 adapter 没有独立 lifecycle stream;执行关系由它调用的 capability seam 负责。 diff --git a/packages/fs/tool-str-replace-editor/README.i18n.yaml b/packages/fs/tool-str-replace-editor/README.i18n.yaml index b516be19bc..4e1527febe 100644 --- a/packages/fs/tool-str-replace-editor/README.i18n.yaml +++ b/packages/fs/tool-str-replace-editor/README.i18n.yaml @@ -75,5 +75,5 @@ en: bce77fc35b8a0140 zh: 48f227222bf5f3dc /deepseek-ai-dsh-tool-str-replace-editor/known-limitations-and-deferred-work/dev-note: - en: 493672b381457d04 - zh: d3e08ed16765a468 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/fs/tool-str-replace-editor/README.md b/packages/fs/tool-str-replace-editor/README.md index 66663b2ab1..950ad54b57 100644 --- a/packages/fs/tool-str-replace-editor/README.md +++ b/packages/fs/tool-str-replace-editor/README.md @@ -145,5 +145,3 @@ These limits define when the editor tool is a poor fit or needs special operatio None. - -**Runtime invariant:** No companion is published. The tool adapter owns no independent durable state; filesystem mutation relations stay with the provider and policy plugins. diff --git a/packages/fs/tool-str-replace-editor/README.zh.md b/packages/fs/tool-str-replace-editor/README.zh.md index 01a942cbc8..b1b4bc448e 100644 --- a/packages/fs/tool-str-replace-editor/README.zh.md +++ b/packages/fs/tool-str-replace-editor/README.zh.md @@ -145,5 +145,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。工具适配器不持有独立持久状态;文件系统修改关系属于提供方与策略插件。 diff --git a/packages/goal/command-goal/README.i18n.yaml b/packages/goal/command-goal/README.i18n.yaml index 9d40d3547e..2810dd19e9 100644 --- a/packages/goal/command-goal/README.i18n.yaml +++ b/packages/goal/command-goal/README.i18n.yaml @@ -36,8 +36,8 @@ en: ab637815b21bf065 zh: 56eb4ae2ad368994 /deepseek-ai-dsh-command-goal/understand-the-implementation/source-map: - en: 08050fc601600dc9 - zh: 1a42ebf4f32334b6 + en: a34756578cce7cc4 + zh: 61079561a2bf742d /deepseek-ai-dsh-command-goal/further-exploration: en: 15b3a8eab8ff2cfe zh: c42af8e2f6d9452c diff --git a/packages/goal/command-goal/README.md b/packages/goal/command-goal/README.md index 4027bcd18c..ce29459fae 100644 --- a/packages/goal/command-goal/README.md +++ b/packages/goal/command-goal/README.md @@ -84,7 +84,6 @@ This section explains how the command parses input and renders output; the obser | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: command grammar, status rendering, attachment submission | -| — | No runtime invariant companion is published; this command adapter owns no event stream or state projection; accepted mutations are checked by the goal domain and command dispatch behavior is covered by package tests. | diff --git a/packages/goal/command-goal/README.zh.md b/packages/goal/command-goal/README.zh.md index 6e28390764..3103c0dada 100644 --- a/packages/goal/command-goal/README.zh.md +++ b/packages/goal/command-goal/README.zh.md @@ -84,7 +84,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:命令语法、状态渲染、附件提交 | -| — | 不发布运行时不变式伴生入口;此命令适配器不拥有事件流或状态投影;已接受的变更由 goal 领域检查,命令分发行为由包测试覆盖。 | diff --git a/packages/goal/goal-round-driver/README.i18n.yaml b/packages/goal/goal-round-driver/README.i18n.yaml index bb766ba46b..7b9a6995ef 100644 --- a/packages/goal/goal-round-driver/README.i18n.yaml +++ b/packages/goal/goal-round-driver/README.i18n.yaml @@ -36,11 +36,11 @@ en: 58a2316725fe1f38 zh: d42d60cf02ad518e /deepseek-ai-dsh-goal-round-driver/understand-the-implementation/source-map: - en: 2636a395ea78f621 - zh: f169bc50b4c79ffb + en: 80fe3f7859194ce5 + zh: 6d2090722d94718c /deepseek-ai-dsh-goal-round-driver/understand-the-implementation/the-round-prompt: - en: 88c61c1dcb13352b - zh: 8dd618c327469bbf + en: dc95a473296236c6 + zh: ec69b7081cd9fa73 /deepseek-ai-dsh-goal-round-driver/further-exploration: en: 4bf1ec9b975f92b7 zh: 00b4b2eba534c127 diff --git a/packages/goal/goal-round-driver/README.md b/packages/goal/goal-round-driver/README.md index 95ed476888..231692b939 100644 --- a/packages/goal/goal-round-driver/README.md +++ b/packages/goal/goal-round-driver/README.md @@ -79,11 +79,10 @@ This section explains how the driver schedules rounds without races; the observa |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: driver state machine, race fences, teardown | | [`src/prompt.ts`](src/prompt.ts) | The retained `` continuation prompt | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: goal-round messages must match the package-owned prompt | ### The round prompt -The retained prompt is one text block: the JSON-quoted objective and `round/maxGoalRounds` on the first lines, then the working instructions. The invariant companion reconstructs the goal from the durable prefix and rejects any goal-sourced message whose content does not match the prompt exactly. +The retained prompt is one text block: the JSON-quoted objective and `round/maxGoalRounds` on the first lines, then the working instructions. diff --git a/packages/goal/goal-round-driver/README.zh.md b/packages/goal/goal-round-driver/README.zh.md index 894ee79b4f..4c99700319 100644 --- a/packages/goal/goal-round-driver/README.zh.md +++ b/packages/goal/goal-round-driver/README.zh.md @@ -79,11 +79,10 @@ Round 只在整个 agent 进入 idle 时启动;完成、暂停和阻塞会阻 |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:驱动器状态机、竞态防护、teardown | | [`src/prompt.ts`](src/prompt.ts) | 保留的 `` 续行提示词 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生:goal-round 消息必须与包自有提示词一致 | ### Round 提示词 -保留的提示词是一个文本块:前几行为 JSON 引用的目标与 `round/maxGoalRounds`,其后是工作指令。不变式伴生会从持久前缀重建 goal,并拒绝内容与该提示词不完全一致的任何 goal 来源消息。 +保留的提示词是一个文本块:前几行为 JSON 引用的目标与 `round/maxGoalRounds`,其后是工作指令。 diff --git a/packages/goal/goal-round-driver/package.json b/packages/goal/goal-round-driver/package.json index 5048e295b4..3aa917a8c8 100644 --- a/packages/goal/goal-round-driver/package.json +++ b/packages/goal/goal-round-driver/package.json @@ -18,23 +18,17 @@ "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" ], "license": "MIT", "peerDependencies": { "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-goal": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/cordis": "workspace:~" @@ -44,7 +38,6 @@ "@deepseek-ai/dsh-agent-loop": "workspace:*", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:*", "@deepseek-ai/dsh-goal": "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/goal/goal-round-driver/src/invariant.ts b/packages/goal/goal-round-driver/src/invariant.ts deleted file mode 100644 index e716422f27..0000000000 --- a/packages/goal/goal-round-driver/src/invariant.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** Package-owned goal-round prompt invariants. @module @deepseek-ai/dsh-goal-round-driver/invariant */ - -import { isDeepStrictEqual } from 'node:util' -import type { Context } from '@deepseek-ai/cordis' -import { foldGoal, type FoldedGoal, type GoalMessageSource, type GoalView } from '@deepseek-ai/dsh-goal' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import { renderGoalRoundPrompt } from './prompt.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-goal-round-driver' - -/** Cordis companion plugin name. */ -export const name = 'goal-round-driver-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Attribute strict goal-fold failures to this companion's reconstruction. */ -function foldChecked(events: readonly SessionEvent[], fail: InvariantFailure): FoldedGoal { - try { - return foldGoal(events) - } catch (error: unknown) { - /* v8 ignore next -- the strict goal decoder throws Error instances */ - const message = error instanceof Error ? error.message : String(error) - return fail(`cannot reconstruct the goal before a continuation message: ${message}`) - } -} - -/** Recreate the live-shaped view consumed by the package's pure prompt renderer. */ -function goalView(folded: FoldedGoal, source: GoalMessageSource, fail: InvariantFailure): GoalView { - const goal = folded.goal - if (goal === undefined || folded.createdAt === undefined || folded.updatedAt === undefined - || goal.phase !== 'active' || goal.id !== source.goalId || goal.revision !== source.revision - || source.round !== folded.roundsStarted + 1 || source.round > goal.maxGoalRounds) { - return fail(`goal round ${source.round} cannot be reconstructed from the preceding durable goal state`) - } - return { - ...goal, - roundsStarted: folded.roundsStarted, - createdAt: folded.createdAt, - updatedAt: folded.updatedAt, - activation: 'armed', - } -} - -/** Validate one package-owned continuation message against its durable prefix. */ -function validateEvent( - prior: readonly SessionEvent[], - event: SessionEvent, - fail: InvariantFailure, -): void { - if (event.type !== 'user/message') return - const source = event.data.source - if (source.kind !== 'goal' || source.round <= 0) return - const expected = renderGoalRoundPrompt(goalView(foldChecked(prior, fail), source, fail), source.round) - if (!isDeepStrictEqual(event.data.content, expected)) { - fail(`goal round ${source.round} content does not match the package-owned continuation prompt`) - } -} - -/** Check existing sessions and every candidate event before Session publishes it. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - for (const session of ctx.sessions.list()) { - const prior: SessionEvent[] = [] - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) { - validateEvent(prior, event, fail) - prior.push(event) - } - } - /* jscpd:ignore-start -- package companions share dispatch and registration plumbing */ - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - validateEvent(session.snapshotEvents(), event, fail) - }, { global: true }) -}, { inject: ['sessions'] }) - -/** - * Register the goal-round-driver 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/goal/goal-round-driver/tests/invariant.spec.ts b/packages/goal/goal-round-driver/tests/invariant.spec.ts deleted file mode 100644 index 3e3ff8c556..0000000000 --- a/packages/goal/goal-round-driver/tests/invariant.spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { createUserMessage } from '@deepseek-ai/dsh-llm' -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import { - GoalId, - type GoalSnapshotChangeMeta, - type GoalView, -} from '@deepseek-ai/dsh-goal' -import * as GoalSessionInvariant from '@deepseek-ai/dsh-goal-round-driver/invariant' -import { renderGoalRoundPrompt } from '@deepseek-ai/dsh-goal-round-driver' -import InvariantRegistry, { InvariantError } from '@deepseek-ai/dsh-invariants' -import SessionStore, { SessionId, type Session } from '@deepseek-ai/dsh-session' - -const change: GoalSnapshotChangeMeta = { - kind: 'goal/change', - version: 1, - operation: 'create', - goal: { - id: GoalId('goal-round-driver-invariant'), - revision: 1, - objective: 'verify every continuation prompt', - phase: 'active', - maxGoalRounds: 2, - }, - roundsStarted: 0, - createdAt: 1, - updatedAt: 1, -} - -function view(roundsStarted: number): GoalView { - return { ...change.goal, roundsStarted, createdAt: 1, updatedAt: 1, activation: 'armed' } -} - -function appendChange(session: Session): void { - session.append('goal/change', change) -} - -function appendRound(session: Session, turn: number, content = renderGoalRoundPrompt(view(turn - 2), turn - 1)): void { - const source = { kind: 'goal', goalId: change.goal.id, revision: 1, round: turn - 1 } as const - session.append('turn/start', { turn }) - session.append('user/message', createUserMessage({ - content, source, - }), { surfaceOp: 'append' }) - session.append('turn/end', { turn, reason: { kind: 'completed' } }) -} - -async function mount(sessionFirst = false): Promise<{ ctx: Context; session: Session }> { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create(SessionId('goal-round-driver-invariant')) - if (!sessionFirst) { - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(GoalSessionInvariant) - } - return { ctx, session } -} - -describe('goal-round-driver prompt invariants', () => { - it('reconstructs existing rounds and accepts the next canonical prompt', async () => { - const { ctx, session } = await mount(true) - appendChange(session) - appendRound(session, 2) - - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(GoalSessionInvariant) - - expect(() => { appendRound(session, 3) }).not.toThrow() - ctx.sessions.create(SessionId('goal-session-invariant-dispatch')) - - const userSource = { kind: 'user' } as const - session.append('turn/start', { turn: 4 }) - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'ordinary human message' }], - source: userSource, - }), { surfaceOp: 'append' }) - session.append('turn/end', { turn: 4, reason: { kind: 'completed' } }) - - const stateSource = { - kind: 'goal', goalId: change.goal.id, revision: change.goal.revision, round: 0, - } as never - session.append('turn/start', { turn: 5 }) - expect(() => { - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'round zero is not a driver continuation' }], - source: stateSource, - }), { surfaceOp: 'append' }) - }).not.toThrow() - }) - - it('rejects a continuation whose content differs from the package renderer', async () => { - const { session } = await mount() - appendChange(session) - - expect(() => { - appendRound(session, 2, [{ type: 'text', text: 'counterfeit continuation' }]) - }).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-goal-round-driver', - })) - }) - - it('rejects a goal round without a reconstructable active goal', async () => { - const { session } = await mount() - const source = { kind: 'goal', goalId: change.goal.id, revision: 1, round: 1 } as const - session.append('turn/start', { turn: 1 }) - - expect(() => { - session.append('user/message', createUserMessage({ - content: renderGoalRoundPrompt(view(0), 1), - source, - }), { surfaceOp: 'append' }) - }).toThrow(expect.objectContaining>({ - packageName: '@deepseek-ai/dsh-goal-round-driver', - })) - }) - - it('attributes an invalid durable prefix during late loading', async () => { - const { ctx, session } = await mount(true) - session.append('goal/change', { ...change, extra: true } as never) - appendRound(session, 2) - await ctx.plugin(InvariantRegistry, { enabled: true }) - - await expect(ctx.plugin(GoalSessionInvariant)).rejects.toMatchObject({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-goal-round-driver', - }) - }) -}) diff --git a/packages/goal/goal-round-driver/tsconfig.json b/packages/goal/goal-round-driver/tsconfig.json index 377086068f..d3cf5d5294 100644 --- a/packages/goal/goal-round-driver/tsconfig.json +++ b/packages/goal/goal-round-driver/tsconfig.json @@ -25,9 +25,6 @@ }, { "path": "../../core/session" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/goal/goal-round-driver/tsdown.config.ts b/packages/goal/goal-round-driver/tsdown.config.ts index ab8dc26ee8..76c21c44d4 100644 --- a/packages/goal/goal-round-driver/tsdown.config.ts +++ b/packages/goal/goal-round-driver/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from 'tsdown' -/** Build the package root and invariant companion as independent bundles. */ +/** Build the package root as a self-contained bundle. */ export default defineConfig([ { entry: ['lib/types/index.js'], @@ -12,14 +12,4 @@ export default defineConfig([ dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, - }, ]) diff --git a/packages/goal/goal/README.i18n.yaml b/packages/goal/goal/README.i18n.yaml index e95a051419..bc30037df8 100644 --- a/packages/goal/goal/README.i18n.yaml +++ b/packages/goal/goal/README.i18n.yaml @@ -42,8 +42,8 @@ en: 6f19e891ffc79754 zh: 9599e3b142f1e6b5 /deepseek-ai-dsh-goal/understand-the-implementation/source-map: - en: f0451101cf73da7d - zh: 73176ab69bb63a7f + en: aef62dff93f32796 + zh: dba79da0466218a7 /deepseek-ai-dsh-goal/understand-the-implementation/events-and-attribution: en: 502cf61dba8b22ca zh: 2b6f672bb2ac84ba diff --git a/packages/goal/goal/README.md b/packages/goal/goal/README.md index 8c282dd94c..6fa8edb4cc 100644 --- a/packages/goal/goal/README.md +++ b/packages/goal/goal/README.md @@ -109,7 +109,6 @@ This section explains how the service realizes the behavior above; the observabl | [`src/types.ts`](src/types.ts) | Pure client-safe types: `GoalView`, `GoalSnapshot`, `GoalActivationChanged`, projection-key declaration | | [`src/fold.ts`](src/fold.ts) | Strict replay fold and decoder for durable goal changes | | [`src/runtime.ts`](src/runtime.ts) | `GoalId` brand, `GoalError` codes, change-version constant | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: independent incremental fold over every attached session | ### Events and attribution diff --git a/packages/goal/goal/README.zh.md b/packages/goal/goal/README.zh.md index a5b3f7d713..a1778a68b8 100644 --- a/packages/goal/goal/README.zh.md +++ b/packages/goal/goal/README.zh.md @@ -109,7 +109,6 @@ view.activation // 'armed' | 'disarmed' — not persisted | [`src/types.ts`](src/types.ts) | 纯客户端安全类型:`GoalView`、`GoalSnapshot`、`GoalActivationChanged`、投影键声明 | | [`src/fold.ts`](src/fold.ts) | 持久 goal 变更的严格回放折叠与解码器 | | [`src/runtime.ts`](src/runtime.ts) | `GoalId` 品牌、`GoalError` 代码、变更版本常量 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式配套模块:对每个已挂接会话的独立增量折叠 | ### 事件与归属 diff --git a/packages/goal/goal/package.json b/packages/goal/goal/package.json index 35f256f51b..f552064e53 100644 --- a/packages/goal/goal/package.json +++ b/packages/goal/goal/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -43,7 +39,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts", "lib/typert.host.js", @@ -56,7 +51,6 @@ "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-session-projection": "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:*", @@ -74,7 +68,6 @@ "@deepseek-ai/dsh-bash-local": "workspace:*", "@deepseek-ai/dsh-session-projection": "workspace:*", "@deepseek-ai/dsh-brand": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-loader-smoke": "workspace:*", "@deepseek-ai/dsh-scope": "workspace:*", diff --git a/packages/goal/goal/src/invariant.ts b/packages/goal/goal/src/invariant.ts deleted file mode 100644 index 8c0004f42a..0000000000 --- a/packages/goal/goal/src/invariant.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** Package-owned durable goal-stream invariants. @module @deepseek-ai/dsh-goal/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import { applyGoalEvent, emptyGoalFoldState } from './fold.ts' -import type { GoalFoldState } from './fold.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-goal' - -/** Cordis companion plugin name. */ -export const name = 'goal-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Copy the independent fold before validating one candidate event. */ -function cloneState(state: GoalFoldState): GoalFoldState { - return { - goal: state.goal, - roundsStarted: state.roundsStarted, - createdAt: state.createdAt, - updatedAt: state.updatedAt, - lastRef: state.lastRef, - seenGoalIds: new Set(state.seenGoalIds), - } -} - -/** Apply one event through the strict goal decoder and attribute failures. */ -function applyChecked(state: GoalFoldState, event: SessionEvent, fail: InvariantFailure): void { - try { - applyGoalEvent(state, event) - } catch (error) { - /* v8 ignore next -- the strict goal decoder throws Error instances */ - const message = error instanceof Error ? error.message : String(error) - fail(`session event ${event.seq} violates the durable goal stream: ${message}`) - } -} - -/** Install an independent incremental fold over every attached session. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const states = new WeakMap() - const staged = new WeakMap() - - const seed = (session: Session): GoalFoldState => { - const state = emptyGoalFoldState() - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) applyChecked(state, event, fail) - states.set(session, state) - return state - } - /* v8 ignore next -- session/event always follows list() or session/created seeding */ - const stateFor = (session: Session): GoalFoldState => states.get(session) ?? seed(session) - - for (const session of ctx.sessions.list()) seed(session) - ctx.on('session/created', (session) => { seed(session) }, { global: true }) - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - const state = cloneState(stateFor(session)) - applyChecked(state, event, fail) - staged.set(event, { session, state }) - }, { global: true }) - ctx.on('session/event', (session, event) => { - const candidate = staged.get(event) - /* v8 ignore next 2 -- internal/dispatch stages the exact callback arguments */ - if (candidate === undefined || candidate.session !== session) { - return fail('session/event reached publication without matching goal-fold validation') - } - staged.delete(event) - states.set(session, candidate.state) - }, { global: true }) -}, { inject: ['sessions'] }) - -/** - * Register the goal-stream 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)) diff --git a/packages/goal/goal/tests/goal.spec.ts b/packages/goal/goal/tests/goal.spec.ts index 06e31d7196..fe9c2d0030 100644 --- a/packages/goal/goal/tests/goal.spec.ts +++ b/packages/goal/goal/tests/goal.spec.ts @@ -538,31 +538,6 @@ describe('GoalService mutations', () => { }) }) - it('rejects a corrupt append while preserving the valid prefix', async () => { - const { ctx, agent, session } = await harness() - expect(ctx.goals.get(agent)).toBeUndefined() - const change: GoalSnapshotChangeMeta = { - kind: 'goal/change', - version: 1, - operation: 'create', - goal: { - id: GoalId('goal-valid-prefix'), - revision: 1, - objective: 'valid prefix', - phase: 'active', - maxGoalRounds: 4, - }, - roundsStarted: 0, - createdAt: 12, - updatedAt: 12, - } - session.append('goal/change', change) - expect(() => { - session.append('goal/change', { ...change, operation: 'edit', extra: true } as never) - }).toThrow('snapshot change must have exactly') - - expect(ctx.goals.get(agent)).toMatchObject({ id: change.goal.id, objective: 'valid prefix' }) - }) }) describe('goal replay validation', () => { diff --git a/packages/goal/goal/tests/invariant.spec.ts b/packages/goal/goal/tests/invariant.spec.ts deleted file mode 100644 index 34659fcf56..0000000000 --- a/packages/goal/goal/tests/invariant.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { createUserMessage } from '@deepseek-ai/dsh-llm' -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import { - GoalId, - type GoalSnapshotChangeMeta, -} from '@deepseek-ai/dsh-goal' -import * as GoalInvariantCompanion from '@deepseek-ai/dsh-goal/invariant' -import InvariantRegistry, { InvariantError } from '@deepseek-ai/dsh-invariants' -import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' - -const change: GoalSnapshotChangeMeta = { - kind: 'goal/change', - version: 1, - operation: 'create', - goal: { - id: GoalId('goal-invariant'), - revision: 1, - objective: 'check the stream', - phase: 'active', - maxGoalRounds: 2, - }, - roundsStarted: 0, - createdAt: 1, - updatedAt: 1, -} - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(GoalInvariantCompanion) - return ctx -} - -describe('goal stream invariants', () => { - it('accepts canonical goal snapshots and sequential admitted rounds', async () => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('goal-invariant-valid')) - session.append('goal/change', change) - session.append('turn/start', { turn: 1 }) - expect(() => { - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'continue' }], - source: { kind: 'goal', goalId: change.goal.id, revision: 1, round: 1 }, - }), { surfaceOp: 'append' }) - }).not.toThrow() - }) - - it('rejects a malformed goal change before committing it and keeps the fold reusable', async () => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('goal-invariant-invalid')) - expect(() => { - session.append('goal/change', { ...change, extra: true } as never) - }).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-goal', - })) - expect(session.seq).toBe(0) - expect(() => { - session.append('goal/change', change) - }).not.toThrow() - }) - - it('reconstructs an existing durable goal before checking later rounds', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create(SessionId('goal-invariant-late-load')) - session.append('goal/change', change) - - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(GoalInvariantCompanion) - session.append('turn/start', { turn: 1 }) - expect(() => { - session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'continue after load' }], - source: { kind: 'goal', goalId: change.goal.id, revision: 1, round: 1 }, - }), { surfaceOp: 'append' }) - }).not.toThrow() - }) -}) diff --git a/packages/goal/goal/tsconfig.json b/packages/goal/goal/tsconfig.json index a7bde55c1b..8b8c742b14 100644 --- a/packages/goal/goal/tsconfig.json +++ b/packages/goal/goal/tsconfig.json @@ -37,9 +37,6 @@ }, { "path": "../../typert/protocol" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/goal/goal/tsdown.config.ts b/packages/goal/goal/tsdown.config.ts index ab8dc26ee8..76c21c44d4 100644 --- a/packages/goal/goal/tsdown.config.ts +++ b/packages/goal/goal/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from 'tsdown' -/** Build the package root and invariant companion as independent bundles. */ +/** Build the package root as a self-contained bundle. */ export default defineConfig([ { entry: ['lib/types/index.js'], @@ -12,14 +12,4 @@ export default defineConfig([ dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, - }, ]) diff --git a/packages/goal/tool-goal/README.i18n.yaml b/packages/goal/tool-goal/README.i18n.yaml index d8d062db20..492ae8374e 100644 --- a/packages/goal/tool-goal/README.i18n.yaml +++ b/packages/goal/tool-goal/README.i18n.yaml @@ -33,8 +33,8 @@ en: a30f36299c8107f7 zh: 65e6e1e19aeb5281 /deepseek-ai-dsh-tool-goal/understand-the-implementation/source-map: - en: 33e8469f99ffecb8 - zh: f1b44e8dd51ffb48 + en: 4d5c648787409868 + zh: b4938bfe660f18aa /deepseek-ai-dsh-tool-goal/understand-the-implementation/tool-output: en: 79e54f40e0bfbe2b zh: 895f7ba22749bc07 diff --git a/packages/goal/tool-goal/README.md b/packages/goal/tool-goal/README.md index 8fed9a79b6..158446e156 100644 --- a/packages/goal/tool-goal/README.md +++ b/packages/goal/tool-goal/README.md @@ -80,7 +80,6 @@ This section explains how the tools enforce authority and render output; the obs | [`src/index.ts`](src/index.ts) | Plugin entry: tool registration, config, system-prompt section, result rendering | | [`src/authority.ts`](src/authority.ts) | Execution-time authority checks and goal-round acceptance | | [`src/wrapup.ts`](src/wrapup.ts) | Closing-message instruction for terminal autonomous updates | -| — | No runtime invariant companion is published; this model-facing adapter owns no independent state or event protocol; accepted mutations are checked by the goal domain and authority behavior is package-tested. | ### Tool output diff --git a/packages/goal/tool-goal/README.zh.md b/packages/goal/tool-goal/README.zh.md index 5fe27d0eb7..1ec0a8e1f8 100644 --- a/packages/goal/tool-goal/README.zh.md +++ b/packages/goal/tool-goal/README.zh.md @@ -80,7 +80,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:工具注册、配置、系统提示词章节、结果渲染 | | [`src/authority.ts`](src/authority.ts) | 执行时权限检查与 Goal Round 接受 | | [`src/wrapup.ts`](src/wrapup.ts) | 终局自主更新的结束消息指令 | -| — | 不发布运行时不变式伴生入口;此面向模型的适配器不拥有独立状态或事件协议;已接受的变更由 goal 领域检查,权限行为则由本包测试验证。 | ### 工具输出 diff --git a/packages/guard/repeat-tool-reminder/README.i18n.yaml b/packages/guard/repeat-tool-reminder/README.i18n.yaml index 2b8fb8a331..862f1f4ddb 100644 --- a/packages/guard/repeat-tool-reminder/README.i18n.yaml +++ b/packages/guard/repeat-tool-reminder/README.i18n.yaml @@ -39,8 +39,8 @@ en: a3edc5c0f98b0d1e zh: 173f6166ce3af779 /deepseek-ai-dsh-repeat-tool-reminder/understand-the-implementation/source-map: - en: 567a964105e1340b - zh: 7a8b062b2d9b39d1 + en: ba2eb423e165510d + zh: db5155503a8504cb /deepseek-ai-dsh-repeat-tool-reminder/further-exploration: en: 77b646e29c1aefa8 zh: f63b7df7412e524d diff --git a/packages/guard/repeat-tool-reminder/README.md b/packages/guard/repeat-tool-reminder/README.md index 9554ea16b8..e50e47a8e8 100644 --- a/packages/guard/repeat-tool-reminder/README.md +++ b/packages/guard/repeat-tool-reminder/README.md @@ -95,7 +95,6 @@ Reminders ride the post-execute decision's `additionalContexts` (source `{kind: | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, fail-loud validation, chain listeners | -| — | No runtime invariant companion is published; the repeat chain is private to one post-execute listener and exposes no package-owned event or snapshot that an independent companion can observe. | diff --git a/packages/guard/repeat-tool-reminder/README.zh.md b/packages/guard/repeat-tool-reminder/README.zh.md index 43326ab434..75c2928967 100644 --- a/packages/guard/repeat-tool-reminder/README.zh.md +++ b/packages/guard/repeat-tool-reminder/README.zh.md @@ -95,7 +95,6 @@ guard 建立在四项承诺之上: | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、快速失败校验、链监听器 | -| — | 不发布运行时不变式配套组件;重复链私有于一个 post-execute 监听器,且不公开任何可供独立配套组件观察的包自有事件或快照。 | diff --git a/packages/guard/timeout-policy/README.i18n.yaml b/packages/guard/timeout-policy/README.i18n.yaml index ec8f7ff38e..4ef2bc4407 100644 --- a/packages/guard/timeout-policy/README.i18n.yaml +++ b/packages/guard/timeout-policy/README.i18n.yaml @@ -39,8 +39,8 @@ en: 09fba7333cf511f6 zh: 965c60634d370287 /deepseek-ai-dsh-tool-call-timeout-policy/understand-the-implementation/source-map: - en: 35abf9419848ba92 - zh: 3c08b161064abdcd + en: 1fa5646d5e9d18eb + zh: d09dfc46cbc654f4 /deepseek-ai-dsh-tool-call-timeout-policy/further-exploration: en: fb2426b1c4c3da4a zh: ff9a7cd6a6b9f631 diff --git a/packages/guard/timeout-policy/README.md b/packages/guard/timeout-policy/README.md index a9381595b2..dbc5e00609 100644 --- a/packages/guard/timeout-policy/README.md +++ b/packages/guard/timeout-policy/README.md @@ -77,7 +77,6 @@ Multiple `tools/execute` listeners compose by Cordis registration order, which c | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `TOOL_TIMEOUT`, `name`/`inject`/`apply`, the `tools/execute` wrapper | -| — | No runtime invariant companion is published; this stateless policy plugin owns no package-local event history or mutable data relation beyond the seam it intercepts. | diff --git a/packages/guard/timeout-policy/README.zh.md b/packages/guard/timeout-policy/README.zh.md index 27938b1793..5838ebb263 100644 --- a/packages/guard/timeout-policy/README.zh.md +++ b/packages/guard/timeout-policy/README.zh.md @@ -77,7 +77,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`TOOL_TIMEOUT`、`name`/`inject`/`apply`、`tools/execute` 包装层 | -| — | 不发布运行时不变式伴生入口;此无状态策略插件不拥有包级事件历史,也不拥有所拦截 seam 之外的可变数据关系。 | diff --git a/packages/hooks/hook-protocol/README.i18n.yaml b/packages/hooks/hook-protocol/README.i18n.yaml index bd23e8f5ce..f2e75da70c 100644 --- a/packages/hooks/hook-protocol/README.i18n.yaml +++ b/packages/hooks/hook-protocol/README.i18n.yaml @@ -30,17 +30,17 @@ en: 08b3960150e9c5e2 zh: 6bb2f236d697de01 /deepseek-ai-dsh-hook-protocol/understand-the-implementation/processing-pipeline: - en: 5672f4151de0b50a - zh: d8fdc68d806e0d7b + en: b25d80f19b551f1a + zh: 9c6602f008d89bce /deepseek-ai-dsh-hook-protocol/understand-the-implementation/hook-session-events: - en: ad77f468168fcbff - zh: 897bc6880fe76e1b + en: f01ab4ab98f052b3 + zh: 1da25fa50378bd19 /deepseek-ai-dsh-hook-protocol/understand-the-implementation/design-philosophy: - en: ccf3eacdd2219631 - zh: 0732935a47a17c9d + en: 7f9239eaea5a945c + zh: 9ba6a2ea955d4934 /deepseek-ai-dsh-hook-protocol/understand-the-implementation/source-map: - en: d613d9bd98d499fb - zh: afe2939d7df250ea + en: aa4983adf89ea45c + zh: cc83b0ede5ff156c /deepseek-ai-dsh-hook-protocol/further-exploration: en: d93c5107a12af16c zh: 89da3abc953a4c6b diff --git a/packages/hooks/hook-protocol/README.md b/packages/hooks/hook-protocol/README.md index 383a75c694..43f53a34a9 100644 --- a/packages/hooks/hook-protocol/README.md +++ b/packages/hooks/hook-protocol/README.md @@ -60,20 +60,20 @@ This section explains the design decisions behind the library and points at the ### Processing pipeline -The library is a chain of single-purpose steps, one function each: validate the matcher pattern, run the command through the `dsh-shell` executor, decode the outcome, merge every matched hook's outcome into one most-restrictive result, and record the durable `hook/*` event pair. The matcher's `mode` parameter is the single axis the dialects differ on — `claude-code` interprets a pattern as literal alternatives or a regex, `codex` always as an unanchored regex. Every step degrades to a contained outcome instead of throwing, so a hook can never crash the calling turn: an invalid regex is a non-match, an executor rejection becomes a `HookOutput` with no exit code, exit 2 blocks with stderr as the reason, and every other failure stays non-blocking. Merging applies `deny > ask > allow` precedence, keeps the first `continue: false` stop sticky, and accumulates context in hook order. Detached runs are tracked so `fiber.dispose()` reaches quiescence, and the invariant companion rejects `hook/*` records outside an open turn. The steps live in [`src/matcher.ts`](src/matcher.ts), [`src/runner.ts`](src/runner.ts), [`src/codec.ts`](src/codec.ts), [`src/merge.ts`](src/merge.ts), [`src/events.ts`](src/events.ts), [`src/detached.ts`](src/detached.ts), and [`src/invariant.ts`](src/invariant.ts). +The library is a chain of single-purpose steps, one function each: validate the matcher pattern, run the command through the `dsh-shell` executor, decode the outcome, merge every matched hook's outcome into one most-restrictive result, and record the durable `hook/*` event pair. The matcher's `mode` parameter is the single axis the dialects differ on — `claude-code` interprets a pattern as literal alternatives or a regex, `codex` always as an unanchored regex. Every step degrades to a contained outcome instead of throwing, so a hook can never crash the calling turn: an invalid regex is a non-match, an executor rejection becomes a `HookOutput` with no exit code, exit 2 blocks with stderr as the reason, and every other failure stays non-blocking. Merging applies `deny > ask > allow` precedence, keeps the first `continue: false` stop sticky, and accumulates context in hook order. Detached runs are tracked so `fiber.dispose()` reaches quiescence. The steps live in [`src/matcher.ts`](src/matcher.ts), [`src/runner.ts`](src/runner.ts), [`src/codec.ts`](src/codec.ts), [`src/merge.ts`](src/merge.ts), [`src/events.ts`](src/events.ts), and [`src/detached.ts`](src/detached.ts). ### `hook/*` session events The `hook/invoked` and `hook/result` events are declaration-merged into `SessionEventMap` as log-only records: like `compaction/*`, they are not surface events and carry no `surfaceOp`. A `hook/result` pairs with its `hook/invoked` by `handlerId`, and `appendHookResult` owns the decision rule. Payloads and per-event JSDoc live in the generated [persistence log event catalog](../../../docs/persistence-catalog.md). -Invocation and result records must sit inside an open turn: `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, and `Stop` satisfy that relation by construction, while `SessionStart` runs before turn 1 and gets no `hook/*` record — its injected context is delivered instead. The invariant companion registers on `ctx.invariants` and rejects `hook/*` events appended outside an open turn, a result without a matching invoked, an unknown dialect, or a non-finite duration. +Invocation and result records must sit inside an open turn: `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, and `Stop` satisfy that relation by construction, while `SessionStart` runs before turn 1 and gets no `hook/*` record — its injected context is delivered instead. ### Design philosophy - **One axis of difference collapsed into `mode`.** The dialects differ only in how a matcher pattern is interpreted, so the matcher takes the mode as a parameter instead of duplicating the engine. - **The executor owns process control.** Commands run through the `dsh-shell` executor rather than a bespoke spawn: the executor already provides the scrubbed-but-overridable environment, process-group cancellation, and timeout the protocol needs. - **Never throw into the loop.** Every failure mode — malformed JSON, an invalid regex, an executor rejection — degrades to a contained outcome or a non-match, so a hook can never crash the calling turn. -- **Log-only, turn-enclosed events.** The `hook/*` records are durable evidence of what ran and what it decided; they are not surface events, and the invariant companion rejects them outside an open turn. +- **Log-only, turn-enclosed events.** The `hook/*` records are durable evidence of what ran and what it decided; they are not surface events. The [hook-protocol-lib Agent Note](../../../.agents/notes/archived/feature/2026-06-30-hook-protocol-lib.md) records the shared-versus-per-dialect split and the alternatives considered. @@ -89,7 +89,6 @@ The [hook-protocol-lib Agent Note](../../../.agents/notes/archived/feature/2026- | [`src/events.ts`](src/events.ts) | `hook/*` event declaration, append helpers, stderr summary | | [`src/detached.ts`](src/detached.ts) | Detached-run quiescence tracking | | [`src/types.ts`](src/types.ts) | `HookOutput`, `MatcherGroup`, `CommandHook`, and the `hook/*` payload types | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: pairing, turn enclosure, dialect, and duration checks | diff --git a/packages/hooks/hook-protocol/README.zh.md b/packages/hooks/hook-protocol/README.zh.md index f3b6a7a03e..b4bbf8b5fe 100644 --- a/packages/hooks/hook-protocol/README.zh.md +++ b/packages/hooks/hook-protocol/README.zh.md @@ -60,20 +60,20 @@ kind: "package-library" ### 处理流水线 -本库是一串单一用途的步骤,每个步骤一个函数:校验 matcher pattern、通过 `dsh-shell` 执行器运行命令、解码结果、把每个匹配 hook 的结果合并为最严格的一个结果,并记录持久的 `hook/*` 事件对。matcher 的 `mode` 参数是两个方言唯一的差异轴——`claude-code` 把 pattern 解释为字面量备选或正则,`codex` 始终解释为未锚定正则。每个步骤都会降级为受控结果而不是抛异常,因此钩子永远不会使调用轮次崩溃:无效正则是运行时的不匹配,执行器拒绝会变成没有退出码的 `HookOutput`,退出码 2 以 stderr 作为原因阻塞,其他失败均不阻塞。合并应用 `deny > ask > allow` 优先级,保持首个 `continue: false` 停止的粘性,并按 hook 顺序累积上下文。脱离运行会被跟踪,因此 `fiber.dispose()` 能达到完全停稳;不变式伴生插件会拒绝位于尚未结束的轮次之外的 `hook/*` 记录。这些步骤位于 [`src/matcher.ts`](src/matcher.ts)、[`src/runner.ts`](src/runner.ts)、[`src/codec.ts`](src/codec.ts)、[`src/merge.ts`](src/merge.ts)、[`src/events.ts`](src/events.ts)、[`src/detached.ts`](src/detached.ts) 与 [`src/invariant.ts`](src/invariant.ts)。 +本库是一串单一用途的步骤,每个步骤一个函数:校验 matcher pattern、通过 `dsh-shell` 执行器运行命令、解码结果、把每个匹配 hook 的结果合并为最严格的一个结果,并记录持久的 `hook/*` 事件对。matcher 的 `mode` 参数是两个方言唯一的差异轴——`claude-code` 把 pattern 解释为字面量备选或正则,`codex` 始终解释为未锚定正则。每个步骤都会降级为受控结果而不是抛异常,因此钩子永远不会使调用轮次崩溃:无效正则是运行时的不匹配,执行器拒绝会变成没有退出码的 `HookOutput`,退出码 2 以 stderr 作为原因阻塞,其他失败均不阻塞。合并应用 `deny > ask > allow` 优先级,保持首个 `continue: false` 停止的粘性,并按 hook 顺序累积上下文。脱离运行会被跟踪,因此 `fiber.dispose()` 能达到完全停稳。这些步骤位于 [`src/matcher.ts`](src/matcher.ts)、[`src/runner.ts`](src/runner.ts)、[`src/codec.ts`](src/codec.ts)、[`src/merge.ts`](src/merge.ts)、[`src/events.ts`](src/events.ts) 与 [`src/detached.ts`](src/detached.ts)。 ### `hook/*` 会话事件 `hook/invoked` 与 `hook/result` 事件通过 declaration merging 合并进 `SessionEventMap`,作为仅日志记录:与 `compaction/*` 相同,它们不是 surface 事件,也不携带 `surfaceOp`。`hook/result` 按 `handlerId` 与其 `hook/invoked` 配对,决策规则由 `appendHookResult` 负责。载荷与逐事件 JSDoc 位于生成的[持久化日志事件目录](../../../docs/persistence-catalog.zh.md)中。 -调用与结果记录必须位于尚未结束的轮次内:`UserPromptSubmit`、`PreToolUse`、`PostToolUse` 与 `Stop` 按构造满足该关系,而 `SessionStart` 在轮次 1 之前运行、没有 `hook/*` 记录——改为投递其注入的上下文。不变式伴生插件注册到 `ctx.invariants`,拒绝在尚未结束的轮次之外追加的 `hook/*` 事件、没有匹配 invoked 的结果、未知方言或非有限时长。 +调用与结果记录必须位于尚未结束的轮次内:`UserPromptSubmit`、`PreToolUse`、`PostToolUse` 与 `Stop` 按构造满足该关系,而 `SessionStart` 在轮次 1 之前运行、没有 `hook/*` 记录——改为投递其注入的上下文。 ### 设计理念 - **把唯一差异轴收拢进 `mode`。** 两个方言只在 matcher pattern 的解读方式上不同,因此 matcher 把 mode 作为参数,而不是复制引擎。 - **执行器拥有进程控制。** 命令通过 `dsh-shell` 执行器运行,而非自建 spawn:执行器已经提供了协议所需的已清理但可覆盖的环境、进程组取消与超时。 - **绝不向循环抛异常。** 每种失败模式——格式错误的 JSON、无效正则、执行器拒绝——都会降级为受控的结果或不匹配,因此钩子永远不能使调用轮次崩溃。 -- **仅日志、轮次内的事件。** `hook/*` 记录是「运行了什么、决定了什么」的持久证据;它们不是 surface 事件,不变式伴生插件会拒绝尚未结束的轮次之外的记录。 +- **仅日志、轮次内的事件。** `hook/*` 记录是「运行了什么、决定了什么」的持久证据;它们不是 surface 事件。 [hook-protocol-lib Agent Note](../../../.agents/notes/archived/feature/2026-06-30-hook-protocol-lib.md) 记录了共享与逐方言的划分以及备选方案。 @@ -89,7 +89,6 @@ kind: "package-library" | [`src/events.ts`](src/events.ts) | `hook/*` 事件声明、追加辅助函数、stderr 摘要 | | [`src/detached.ts`](src/detached.ts) | 脱离运行的完全停稳跟踪 | | [`src/types.ts`](src/types.ts) | `HookOutput`、`MatcherGroup`、`CommandHook` 与 `hook/*` 载荷类型 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:配对、轮次包裹、方言与时长检查 | diff --git a/packages/hooks/hook-protocol/package.json b/packages/hooks/hook-protocol/package.json index 71c8754285..409b7d688f 100644 --- a/packages/hooks/hook-protocol/package.json +++ b/packages/hooks/hook-protocol/package.json @@ -18,28 +18,21 @@ "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" ], "license": "MIT", "peerDependencies": { "@deepseek-ai/dsh-shell": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/cordis": "workspace:~" }, "devDependencies": { "@deepseek-ai/dsh-shell": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/cordis": "workspace:~" } diff --git a/packages/hooks/hook-protocol/src/invariant.ts b/packages/hooks/hook-protocol/src/invariant.ts deleted file mode 100644 index 73a817f7de..0000000000 --- a/packages/hooks/hook-protocol/src/invariant.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** Package-owned hook invocation/result stream invariants. @module @deepseek-ai/dsh-hook-protocol/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type {} from './types.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-hook-protocol' - -/** Cordis companion plugin name. */ -export const name = 'hook-protocol-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -interface HookTransition { - key: string - delta: 1 | -1 -} - -interface HookTrace { - openTurn: number | null - pending: Map -} - -/** Correlation key shared by an invoked/result pair. */ -function hookKey(data: { turn: number; point: string; handlerId: string }): string { - return `${data.turn}\0${data.point}\0${data.handlerId}` -} - -/** Validate one hook event against committed pending invocations. */ -function validateHookEvent( - trace: HookTrace, - event: SessionEvent, - fail: InvariantFailure, -): HookTransition | undefined { - if (event.type !== 'hook/invoked' && event.type !== 'hook/result') return undefined - if (trace.openTurn === null) fail(`${event.type} appended outside any open turn`) - if (event.data.turn !== trace.openTurn) { - fail(`${event.type} names turn ${event.data.turn} but open turn is ${trace.openTurn}`) - } - if (event.type === 'hook/invoked') { - if (event.data.point.length === 0 || event.data.handlerId.length === 0) { - fail('hook/invoked point and handlerId must be non-empty') - } - const dialect: string = event.data.dialect - if (dialect !== 'claude-code' && dialect !== 'codex') { - fail(`hook/invoked carries unknown dialect ${JSON.stringify(dialect)}`) - } - return { key: hookKey(event.data), delta: 1 } - } - const key = hookKey(event.data) - if ((trace.pending.get(key) ?? 0) === 0) { - fail(`hook/result has no matching hook/invoked for ${JSON.stringify(event.data.handlerId)}`) - } - if (!Number.isFinite(event.data.durationMs) || event.data.durationMs < 0) { - fail('hook/result durationMs must be a non-negative finite number') - } - return { key, delta: -1 } -} - -/** Apply one committed hook-pair transition. */ -function applyHookTransition(pending: Map, transition: HookTransition): void { - const next = (pending.get(transition.key) ?? 0) + transition.delta - if (next === 0) pending.delete(transition.key) - else pending.set(transition.key, next) -} - -/** Install hook invoked/result pairing checks. */ -// Event owners keep precommit staging local so their vocabularies never move into a central helper. -/* jscpd:ignore-start */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const traces = new WeakMap() - const staged = new WeakMap() - const seed = (session: Session): HookTrace => { - const trace: HookTrace = { openTurn: null, pending: new Map() } - traces.set(session, trace) - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) { - if (event.type === 'turn/start') trace.openTurn = event.data.turn - else if (event.type === 'turn/end') trace.openTurn = null - const transition = validateHookEvent(trace, event, fail) - if (transition !== undefined) applyHookTransition(trace.pending, transition) - } - return trace - } - const traceFor = (session: Session): HookTrace => traces.get(session) ?? seed(session) - - for (const session of ctx.sessions.list()) seed(session) - ctx.on('session/created', (session) => { seed(session) }, { global: true }) - ctx.on('session/event', (session, event) => { - const trace = traceFor(session) - if (event.type === 'turn/start') { - trace.openTurn = event.data.turn - return - } - if (event.type === 'turn/end') { - trace.openTurn = null - return - } - if (event.type !== 'hook/invoked' && event.type !== 'hook/result') return - const candidate = staged.get(event) - /* v8 ignore next -- internal/dispatch stages every hook invocation/result event */ - if (candidate === undefined || candidate.session !== session) return fail('hook event published without pre-commit validation') - staged.delete(event) - applyHookTransition(trace.pending, candidate.transition) - }, { global: true }) - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - const transition = validateHookEvent(traceFor(session), event, fail) - if (transition !== undefined) staged.set(event, { session, transition }) - }, { global: true }) -}, { inject: ['sessions'] }) -/* jscpd:ignore-end */ - -/** - * Register the hook-protocol 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)) diff --git a/packages/hooks/hook-protocol/tests/invariant.spec.ts b/packages/hooks/hook-protocol/tests/invariant.spec.ts deleted file mode 100644 index 449ce4c6ae..0000000000 --- a/packages/hooks/hook-protocol/tests/invariant.spec.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import SessionStore, { Session, SessionId, SessionSeq } from '@deepseek-ai/dsh-session' -import * as HookInvariant from '@deepseek-ai/dsh-hook-protocol/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(HookInvariant) - return ctx -} - -const invoked = (overrides: Record = {}) => ({ - turn: 1, - point: 'PreToolUse', - dialect: 'claude-code' as const, - handlerId: 'hook-1', - ...overrides, -}) - -const result = (overrides: Record = {}) => ({ - turn: 1, - point: 'PreToolUse', - handlerId: 'hook-1', - decision: 'pass', - durationMs: 3, - ...overrides, -}) - -function startTurn(session: Session, turn = 1): void { - session.append('turn/start', { turn }) -} - -describe('hook-protocol invariants', () => { - it('pairs serial and repeated handler invocations', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - startTurn(session) - session.append('hook/invoked', invoked()) - session.append('hook/invoked', invoked()) - session.append('step/start', { turn: 1, step: 1 }) - session.append('hook/result', result()) - session.append('hook/result', result()) - }) - - it('rebuilds pending hook invocations from an existing session', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('hook/invoked', invoked()) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(HookInvariant) - expect(() => session.append('hook/result', result())).not.toThrow() - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - }) - - it('adopts a bare session first observed through publication', async () => { - const ctx = await setup() - const session = Session.create(SessionId('bare-hook-session')) - expect(() => { - ctx.emit('session/event', session, { - type: 'turn/start', seq: SessionSeq(0), time: 0, - data: { turn: 1 }, - }) - ctx.emit('session/event', session, { - type: 'hook/invoked', seq: SessionSeq(1), time: 1, data: invoked(), - }) - ctx.emit('session/event', session, { - type: 'hook/result', seq: SessionSeq(2), time: 2, data: result(), - }) - }).not.toThrow() - }) - - it('rejects hook events outside or for a different open turn', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - expect(() => session.append('hook/invoked', invoked())).toThrow(/outside any open turn/) - startTurn(session) - expect(() => session.append('hook/invoked', invoked({ turn: 2 }))).toThrow(/but open turn is 1/) - }) - - it('rejects an unenclosed hook event when replaying an existing session', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create() - startTurn(session) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - session.append('hook/invoked', invoked()) - await ctx.plugin(InvariantRegistry) - await expect(ctx.plugin(HookInvariant).then(() => undefined)).rejects.toThrow(/outside any open turn/) - }) - - it.each([ - [invoked({ point: '' }), /point and handlerId must be non-empty/], - [invoked({ handlerId: '' }), /point and handlerId must be non-empty/], - [invoked({ dialect: 'other' }), /unknown dialect/], - ])('rejects malformed hook invocation %#', async (data, message) => { - const ctx = await setup() - const session = ctx.sessions.create() - startTurn(session) - expect(() => session.append('hook/invoked', data as never)).toThrow(message) - }) - - it('rejects unmatched and malformed results', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - startTurn(session) - expect(() => session.append('hook/result', result())).toThrow(/no matching hook\/invoked/) - session.append('hook/invoked', invoked()) - expect(() => session.append('hook/result', result({ durationMs: -1 }))) - .toThrow(/durationMs must be a non-negative finite number/) - expect(() => session.append('hook/result', result({ point: 'Stop' }))) - .toThrow(/no matching hook\/invoked/) - }) -}) diff --git a/packages/hooks/hook-protocol/tsconfig.json b/packages/hooks/hook-protocol/tsconfig.json index be56eb5fd8..28fa1ce23c 100644 --- a/packages/hooks/hook-protocol/tsconfig.json +++ b/packages/hooks/hook-protocol/tsconfig.json @@ -19,9 +19,6 @@ }, { "path": "../../core/session" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/hooks/hooks-claude-code/README.i18n.yaml b/packages/hooks/hooks-claude-code/README.i18n.yaml index d50545f54a..34844786a7 100644 --- a/packages/hooks/hooks-claude-code/README.i18n.yaml +++ b/packages/hooks/hooks-claude-code/README.i18n.yaml @@ -48,8 +48,8 @@ en: e9dc2e482e9a7b50 zh: 5fa4e92b75884f93 /deepseek-ai-dsh-hooks-claude-code/understand-the-implementation/source-map: - en: e09d368cbacd0d37 - zh: 1c5d9060facbe340 + en: 0289ee9e374d3606 + zh: b88841c10ad5c10f /deepseek-ai-dsh-hooks-claude-code/further-exploration: en: 03aa2ae7d5d5e675 zh: 97c723b6905d31de diff --git a/packages/hooks/hooks-claude-code/README.md b/packages/hooks/hooks-claude-code/README.md index 8a9f235019..1eaba4e483 100644 --- a/packages/hooks/hooks-claude-code/README.md +++ b/packages/hooks/hooks-claude-code/README.md @@ -114,7 +114,6 @@ The [hook-bridges Agent Note](../../../.agents/notes/archived/feature/2026-06-30 |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: config validation, listener registration, per-event payloads, decision mapping | | [`src/config.ts`](src/config.ts) | Claude Code config parsing: supported events, matcher validation, command substitution | -| — | No runtime invariant companion is published; this bridge publishes hook-protocol session events, whose companion owns which invocation event each result cites. | diff --git a/packages/hooks/hooks-claude-code/README.zh.md b/packages/hooks/hooks-claude-code/README.zh.md index c7e3607b57..216b2ac58c 100644 --- a/packages/hooks/hooks-claude-code/README.zh.md +++ b/packages/hooks/hooks-claude-code/README.zh.md @@ -114,7 +114,6 @@ matcher subject 是工具名称(`PreToolUse`/`PostToolUse`)、会话源( |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:配置校验、监听器注册、逐事件 payload、决策映射 | | [`src/config.ts`](src/config.ts) | Claude Code 配置解析:受支持事件、matcher 校验、命令替换 | -| — | 不发布运行时不变式伴生入口;本桥接发布 hook-protocol 会话事件,既有 companion 负责校验每个结果所引用的调用事件。 | diff --git a/packages/hooks/hooks-codex/README.i18n.yaml b/packages/hooks/hooks-codex/README.i18n.yaml index 23c5895c36..d4e65ee6e1 100644 --- a/packages/hooks/hooks-codex/README.i18n.yaml +++ b/packages/hooks/hooks-codex/README.i18n.yaml @@ -48,8 +48,8 @@ en: 6f882aab3d924115 zh: 7ed83a6d16a22c77 /deepseek-ai-dsh-hooks-codex/understand-the-implementation/source-map: - en: 83f46561508c0787 - zh: 11ba5b16d92dfe8c + en: 8f74796cf859a750 + zh: be4dd22dccba1782 /deepseek-ai-dsh-hooks-codex/further-exploration: en: acab570c5bd3223f zh: a78803c243239aac diff --git a/packages/hooks/hooks-codex/README.md b/packages/hooks/hooks-codex/README.md index 14c7df7a87..959db1f4f1 100644 --- a/packages/hooks/hooks-codex/README.md +++ b/packages/hooks/hooks-codex/README.md @@ -110,7 +110,6 @@ The [hook-bridges Agent Note](../../../.agents/notes/archived/feature/2026-06-30 |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: config validation, listener registration, per-event payloads, decision mapping | | [`src/config.ts`](src/config.ts) | Codex config parsing: the five supported events, matcher validation, skip reasons | -| — | No runtime invariant companion is published; this bridge publishes hook-protocol session events, whose companion owns which invocation event each result cites. | diff --git a/packages/hooks/hooks-codex/README.zh.md b/packages/hooks/hooks-codex/README.zh.md index b26eebe4ca..1ccc84dd5c 100644 --- a/packages/hooks/hooks-codex/README.zh.md +++ b/packages/hooks/hooks-codex/README.zh.md @@ -110,7 +110,6 @@ matcher subject 是工具名称(`PreToolUse`/`PostToolUse`)或会话源( |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:配置校验、监听器注册、逐事件 payload、决策映射 | | [`src/config.ts`](src/config.ts) | Codex 配置解析:五个受支持事件、matcher 校验、跳过原因 | -| — | 不发布运行时不变式伴生入口;本桥接发布 hook-protocol 会话事件,既有 companion 负责校验每个结果所引用的调用事件。 | diff --git a/packages/host/directory-picker-auto/README.i18n.yaml b/packages/host/directory-picker-auto/README.i18n.yaml index 06c6c884a8..9917868e53 100644 --- a/packages/host/directory-picker-auto/README.i18n.yaml +++ b/packages/host/directory-picker-auto/README.i18n.yaml @@ -54,5 +54,5 @@ en: 2caf1f412c050856 zh: 0e71bc66bc54bbb4 /deepseek-ai-dsh-host-directory-picker-auto/known-limitations-and-deferred-work/dev-note: - en: ac7266ba5ad8bd79 - zh: d9143a2061a07cfd + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/host/directory-picker-auto/README.md b/packages/host/directory-picker-auto/README.md index a36d7e115f..561317dabe 100644 --- a/packages/host/directory-picker-auto/README.md +++ b/packages/host/directory-picker-auto/README.md @@ -118,5 +118,3 @@ These limits define when the boot-time sample can misjudge the host. They are cu None. - -**Runtime invariant:** No companion is published. The sole effect is one boot-time Loader-entry mount owned by the plugin fiber; the store is authoritative. diff --git a/packages/host/directory-picker-auto/README.zh.md b/packages/host/directory-picker-auto/README.zh.md index 283f8c5bcb..d4be1cf518 100644 --- a/packages/host/directory-picker-auto/README.zh.md +++ b/packages/host/directory-picker-auto/README.zh.md @@ -118,5 +118,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。唯一 effect 是由插件 fiber 持有的 boot-time Loader-entry mount,存储是权威来源。 diff --git a/packages/host/directory-picker-browse/README.i18n.yaml b/packages/host/directory-picker-browse/README.i18n.yaml index e5d1be5078..f61daf8a2e 100644 --- a/packages/host/directory-picker-browse/README.i18n.yaml +++ b/packages/host/directory-picker-browse/README.i18n.yaml @@ -42,8 +42,8 @@ en: adffb1740e726f22 zh: edfe4a1f3141a1b5 /deepseek-ai-dsh-host-directory-picker-browse/understand-the-implementation/source-map: - en: 1a8d1b820a0f97e7 - zh: 377cc9d4832f93df + en: 014babc8e5a26317 + zh: b1a1039dd3dd48a9 /deepseek-ai-dsh-host-directory-picker-browse/further-exploration: en: 25ae7d1a481cacca zh: f49741dd1677000d diff --git a/packages/host/directory-picker-browse/README.md b/packages/host/directory-picker-browse/README.md index 9618b6b2f7..4476f0f806 100644 --- a/packages/host/directory-picker-browse/README.md +++ b/packages/host/directory-picker-browse/README.md @@ -72,7 +72,6 @@ Every filesystem await races the caller's signal (`raceAbort`), so a stalled net | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | `BrowseDirectoryPicker` service: listing, creation, bounded window, error mapping | -| — | No runtime invariant companion is published; each list/create is one stateless filesystem round trip; the filesystem itself is the authoritative state. | diff --git a/packages/host/directory-picker-browse/README.zh.md b/packages/host/directory-picker-browse/README.zh.md index 20c9267bd4..8befbc2555 100644 --- a/packages/host/directory-picker-browse/README.zh.md +++ b/packages/host/directory-picker-browse/README.zh.md @@ -72,7 +72,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | `BrowseDirectoryPicker` 服务:列举、创建、有界窗口、错误映射 | -| — | 不发布运行时不变式伴生入口;每次列举或创建都是一次无状态的文件系统往返;文件系统本身保存的状态具有权威性。 | diff --git a/packages/host/directory-picker-native/README.i18n.yaml b/packages/host/directory-picker-native/README.i18n.yaml index 0b51f0276e..94389b7331 100644 --- a/packages/host/directory-picker-native/README.i18n.yaml +++ b/packages/host/directory-picker-native/README.i18n.yaml @@ -51,5 +51,5 @@ en: 08faf27d90fde463 zh: b8edfb7ec238f2b4 /deepseek-ai-dsh-host-directory-picker-native/known-limitations-and-deferred-work/dev-note: - en: 321aef90f4cdb291 - zh: c6cdc00db1ee9a42 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/host/directory-picker-native/README.md b/packages/host/directory-picker-native/README.md index 3f98083184..1449e3e2fc 100644 --- a/packages/host/directory-picker-native/README.md +++ b/packages/host/directory-picker-native/README.md @@ -109,5 +109,3 @@ These limits define when the native interaction is unavailable or fragile. They None. - -**Runtime invariant:** No companion is published. Each pick is one stateless subprocess round trip; the chooser outcome is only the returned path. diff --git a/packages/host/directory-picker-native/README.zh.md b/packages/host/directory-picker-native/README.zh.md index 7a221cbbfe..968c26555e 100644 --- a/packages/host/directory-picker-native/README.zh.md +++ b/packages/host/directory-picker-native/README.zh.md @@ -109,5 +109,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。每次选择都是一次无状态的子进程往返;选择器的结果仅为返回的路径。 diff --git a/packages/host/directory-picker/README.i18n.yaml b/packages/host/directory-picker/README.i18n.yaml index a1e583a299..9289bc2b6d 100644 --- a/packages/host/directory-picker/README.i18n.yaml +++ b/packages/host/directory-picker/README.i18n.yaml @@ -54,5 +54,5 @@ en: 8c2c5c2dcb2bd5ec zh: 17005c66ae6029ad /deepseek-ai-dsh-host-directory-picker/known-limitations-and-deferred-work/dev-note: - en: 9a5e8587ac555934 - zh: b5bb3a7d1702b055 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/host/directory-picker/README.md b/packages/host/directory-picker/README.md index 5dc44cbf8a..ba660ab4e0 100644 --- a/packages/host/directory-picker/README.md +++ b/packages/host/directory-picker/README.md @@ -109,5 +109,3 @@ These limits define when the seam contract leaves a decision to a future consume None. - -**Runtime invariant:** No companion is published. This stateless Service Definition owns the capability vocabulary, while backends and the Remote controller own observations. diff --git a/packages/host/directory-picker/README.zh.md b/packages/host/directory-picker/README.zh.md index 972f5631eb..1dfbb90b89 100644 --- a/packages/host/directory-picker/README.zh.md +++ b/packages/host/directory-picker/README.zh.md @@ -109,5 +109,3 @@ web GUI 让操作者通过 OS 选择器或应用内浏览器选择工作区目 无。 - -**运行时不变式:** 不发布伴生入口。这个无状态 Service Definition 只定义 capability vocabulary,观察由 backend 与 Remote controller 负责。 diff --git a/packages/host/frontend-static/README.i18n.yaml b/packages/host/frontend-static/README.i18n.yaml index f651511555..254fc82b3c 100644 --- a/packages/host/frontend-static/README.i18n.yaml +++ b/packages/host/frontend-static/README.i18n.yaml @@ -51,5 +51,5 @@ en: 1c47bab1d5936873 zh: 737d7188036e7bde /deepseek-ai-dsh-host-frontend-static/known-limitations-and-deferred-work/dev-note: - en: b0c11e19ebc773d3 - zh: 897d6eaf60489227 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/host/frontend-static/README.md b/packages/host/frontend-static/README.md index e9bf19b48a..43e3d23e2c 100644 --- a/packages/host/frontend-static/README.md +++ b/packages/host/frontend-static/README.md @@ -115,5 +115,3 @@ These limits define when a served asset class is not yet covered. They are curre None. - -**Runtime invariant:** No companion is published. The only owned relation is the single fallback seat, which cannot be probed from the teardown stream — `internal/plugin` fires before the disposing fiber's effects run, so the legitimate owner still holds the seat at notification time and any claim probe would false-positive on every correct disposal (unlike the webserver companion, whose reserved-path probes never collide with a live registration). The seat's register/release symmetry is covered by the package's real-composition HMR-safety test instead. diff --git a/packages/host/frontend-static/README.zh.md b/packages/host/frontend-static/README.zh.md index 3021d6de08..e3da3829cf 100644 --- a/packages/host/frontend-static/README.zh.md +++ b/packages/host/frontend-static/README.zh.md @@ -115,5 +115,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。唯一受本包所有的关系是单个回退席位,但无法从 teardown 流中探测它:`internal/plugin` 在正在释放的 fiber 执行 effect disposer 前触发,因此通知发出时合法所有者仍占据席位,任何占位探测都会把每次正确释放误报为失败;这不同于 webserver companion 对保留路径的探测,后者不会与存活注册冲突。席位的注册/释放对称性由本包真实组合的 HMR(热模块替换)安全测试覆盖。 diff --git a/packages/host/open-in-app/README.i18n.yaml b/packages/host/open-in-app/README.i18n.yaml index b518081699..4a95ce5bb8 100644 --- a/packages/host/open-in-app/README.i18n.yaml +++ b/packages/host/open-in-app/README.i18n.yaml @@ -48,5 +48,5 @@ en: 6e1cf559c4ce0d56 zh: f78c5548bb3d6236 /deepseek-ai-dsh-host-open-in-app/known-limitations-and-deferred-work/dev-note: - en: bb259436e8626317 - zh: 3814e0f77b36a92a + en: 092fc8cb42f93445 + zh: 9eed73a927bd27bd diff --git a/packages/host/open-in-app/README.md b/packages/host/open-in-app/README.md index 39cc7b8547..4cbc084e98 100644 --- a/packages/host/open-in-app/README.md +++ b/packages/host/open-in-app/README.md @@ -123,5 +123,3 @@ None; the package never assembles or sends provider requests. The promotion decisions — the host/`ui-` package split, why raw webServer routes instead of a Typert Remote, why the catalog stays compile-time fixed, the resolver redesign (verified launchers, one resolution pass, no per-click re-detection), the three-deadline configuration, and the per-platform icon strategies with their rejected alternatives — are recorded in the [promotion Agent Note](../../../.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.md). - -**Runtime invariant:** No companion is published. The package serves one host resolution pass over three stateless routes; the route registrations prove disposal through their HMR-safety specs, and no independent observations can diverge. diff --git a/packages/host/open-in-app/README.zh.md b/packages/host/open-in-app/README.zh.md index c47a47c43f..b5831fe34b 100644 --- a/packages/host/open-in-app/README.zh.md +++ b/packages/host/open-in-app/README.zh.md @@ -123,5 +123,3 @@ Linux 桌面文件字段和图标使用共享的 [native-command](../../util/nat 转正期的各项决定——host/`ui-` 分包、为什么用裸 webServer 路由而非 Typert Remote、目录为什么保持编译期固定、resolver 重设计(已验证启动器、单趟解析、点击不再重新检测)、三期限配置、以及各平台图标策略与被拒的替代方案——记录在[转正 Agent Note](../../../.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.zh.md)。 - -**运行时不变式:** 不发布伴生入口。本包经三条无状态路由提供一趟主机解析的结果;路由注册已由各自的 HMR(热模块替换)安全测试证明可处置,不存在可能分叉的独立观测。 diff --git a/packages/host/plugin-inventory/README.i18n.yaml b/packages/host/plugin-inventory/README.i18n.yaml index 7321333b1f..28b07168b3 100644 --- a/packages/host/plugin-inventory/README.i18n.yaml +++ b/packages/host/plugin-inventory/README.i18n.yaml @@ -36,8 +36,8 @@ en: 67fd54c8c49f6dbc zh: f6743f8f5c34b886 /deepseek-ai-dsh-host-plugin-inventory/understand-the-implementation/source-map: - en: facbb5472e32e362 - zh: de793d69285e56b3 + en: 0182cb9dbcff5eae + zh: fce809d7b06d2ed8 /deepseek-ai-dsh-host-plugin-inventory/further-exploration: en: 47de2da39cf6aefa zh: c7e945dcba775177 diff --git a/packages/host/plugin-inventory/README.md b/packages/host/plugin-inventory/README.md index 8450339c14..5dbd882172 100644 --- a/packages/host/plugin-inventory/README.md +++ b/packages/host/plugin-inventory/README.md @@ -65,7 +65,6 @@ Fiber states map onto the public phase vocabulary, with `disposed` folding into |---|---| | [`src/index.ts`](src/index.ts) | `PluginInventoryGateway`: the `pluginInventory` Remote service and the Loader projection | | [`src/types.ts`](src/types.ts) | Public payload types: `PluginInventoryEntry`, `PluginInventorySnapshot`, `PluginFiberPhase` | -| — | No runtime invariant companion is published; every snapshot is projected directly from Loader-owned state. | Typert generates the Host and Client Remote artifacts exposed by `./typert` and `./remote`. diff --git a/packages/host/plugin-inventory/README.zh.md b/packages/host/plugin-inventory/README.zh.md index 345bf367ce..9e63575f23 100644 --- a/packages/host/plugin-inventory/README.zh.md +++ b/packages/host/plugin-inventory/README.zh.md @@ -65,7 +65,6 @@ Fiber 状态映射到公共阶段词汇,其中 `disposed` 折叠为 `null`— |---|---| | [`src/index.ts`](src/index.ts) | `PluginInventoryGateway`:`pluginInventory` Remote 服务与 Loader 投影 | | [`src/types.ts`](src/types.ts) | 公共 payload 类型:`PluginInventoryEntry`、`PluginInventorySnapshot`、`PluginFiberPhase` | -| — | 不发布运行时不变式配套项;每个快照都投影 Loader 持有的状态。 | Typert 生成由 `./typert` 与 `./remote` 导出的 Host 和 Client Remote 产物。 diff --git a/packages/host/product-telemetry-otel/README.i18n.yaml b/packages/host/product-telemetry-otel/README.i18n.yaml index 5e2db318e2..0fbc743bea 100644 --- a/packages/host/product-telemetry-otel/README.i18n.yaml +++ b/packages/host/product-telemetry-otel/README.i18n.yaml @@ -18,8 +18,8 @@ en: 4e6a118dbf53bb08 zh: fcc8b09daaf77eea /deepseek-ai-dsh-host-product-telemetry-otel/understand-the-implementation: - en: 2e1c6440c480dcef - zh: e1928cf1d178a03e + en: dde663af2ea10783 + zh: fde20282265177c3 /deepseek-ai-dsh-host-product-telemetry-otel/further-exploration: en: 6bfb5e8a723ff0a9 zh: fffc6b02e2c1a3cb diff --git a/packages/host/product-telemetry-otel/README.md b/packages/host/product-telemetry-otel/README.md index 8e831ca9a7..051441d1f7 100644 --- a/packages/host/product-telemetry-otel/README.md +++ b/packages/host/product-telemetry-otel/README.md @@ -68,7 +68,7 @@ The collector expects a string body and attributes containing strings, numbers, The adapter injects `otel` and creates an independent ordinary-event channel through `ctx.otel.createEventReporter()`. The [shared OTel plugin](../../telemetry/otel/README.md) owns transport and SDK batching; this adapter owns analytics configuration and its shutdown cancellation deadline. No global OTel provider is installed. -[`src/index.ts`](src/index.ts) owns configuration and submission. No runtime invariant companion is published: delivery has no independent local acknowledgement to compare with the SDK's queue. +[`src/index.ts`](src/index.ts) owns configuration and submission. diff --git a/packages/host/product-telemetry-otel/README.zh.md b/packages/host/product-telemetry-otel/README.zh.md index 41404f10a5..e5a2bb59a8 100644 --- a/packages/host/product-telemetry-otel/README.zh.md +++ b/packages/host/product-telemetry-otel/README.zh.md @@ -68,7 +68,7 @@ kind: "package-reference" 适配器注入 `otel`,通过 `ctx.otel.createEventReporter()` 创建独立的普通事件通道。[共享 OTel 插件](../../telemetry/otel/README.zh.md) 负责传输和 SDK 聚合;本适配器负责埋点配置及关闭取消期限。不安装全局 OTel provider。 -[`src/index.ts`](src/index.ts) 负责配置与提交。不发布运行时不变量伴随模块:本地没有独立的送达确认可与 SDK 队列比较。 +[`src/index.ts`](src/index.ts) 负责配置与提交。 diff --git a/packages/host/webserver/README.i18n.yaml b/packages/host/webserver/README.i18n.yaml index 1e66ea7779..374d2d671e 100644 --- a/packages/host/webserver/README.i18n.yaml +++ b/packages/host/webserver/README.i18n.yaml @@ -39,8 +39,8 @@ en: 70dc6875e07497dd zh: a1d041236e361f84 /deepseek-ai-dsh-host-webserver/understand-the-implementation/source-map: - en: 5b9e4c57f7865323 - zh: edeb68dc544bef0a + en: ce33753861660637 + zh: 09ecb20f47f59e55 /deepseek-ai-dsh-host-webserver/further-exploration: en: 95c0c6bc24dab6cf zh: ec30fe2bb45a7e61 diff --git a/packages/host/webserver/README.md b/packages/host/webserver/README.md index 9dd6984047..f95b298f3f 100644 --- a/packages/host/webserver/README.md +++ b/packages/host/webserver/README.md @@ -75,7 +75,6 @@ The package is a plain route registry with no harness vocabulary: `WebServer` ex | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | `WebServer` service: route tables, fallback seat, index rendering, matching, lifecycle | -| — | No runtime invariant companion is published; route registration and disposal mutate one route table through the same service, so a register/dispose probe only re-executes the implementation. Real routing and HMR tests own the behavior. | | [`src/injections.ts`](src/injections.ts) | Structured `IndexInjection` rows and `renderIndexInjections` row rendering | diff --git a/packages/host/webserver/README.zh.md b/packages/host/webserver/README.zh.md index d4701b79e4..6270d2faa4 100644 --- a/packages/host/webserver/README.zh.md +++ b/packages/host/webserver/README.zh.md @@ -75,7 +75,6 @@ index 启动输入分两层。`collectIndexInjections()` 收集一张全新的 | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | `WebServer` 服务:路由表、回退席位、index 渲染、匹配、生命周期 | -| — | 不发布运行时不变式伴生入口;路由注册与释放通过同一服务修改同一张路由表,register/dispose 探针只会重复执行实现。真实路由与 HMR 测试负责验证该行为。 | | [`src/injections.ts`](src/injections.ts) | 结构化 `IndexInjection` 行与 `renderIndexInjections` 行渲染 | diff --git a/packages/identity/anonymous-user-id/README.i18n.yaml b/packages/identity/anonymous-user-id/README.i18n.yaml index f6e1b0e60c..64559d48d9 100644 --- a/packages/identity/anonymous-user-id/README.i18n.yaml +++ b/packages/identity/anonymous-user-id/README.i18n.yaml @@ -30,11 +30,11 @@ en: 16370ef642feec6a zh: a546774827234300 /deepseek-ai-dsh-anonymous-user-id/understand-the-implementation/design-philosophy: - en: a2803ccc98c906bb - zh: b2bdb8bdaf1aca6c + en: d7c0a68199976734 + zh: f78f4ef1d11bde94 /deepseek-ai-dsh-anonymous-user-id/understand-the-implementation/source-map: - en: 9d5c4a91346f43ff - zh: d5fc162f91906d3f + en: b816e46019ff3b61 + zh: a3c7be6ca0683c03 /deepseek-ai-dsh-anonymous-user-id/understand-the-implementation/the-api: en: 23acd1995e7dfa39 zh: 18b13b5351458aab @@ -57,8 +57,5 @@ en: 124d523659df72f1 zh: 50c25ecb9268109f /deepseek-ai-dsh-anonymous-user-id/known-limitations-and-deferred-work/dev-note/open-file-format-evolution: - en: 334ca8ec1326f6fd - zh: da5c85b6f264cd70 -/deepseek-ai-dsh-anonymous-user-id/known-limitations-and-deferred-work/dev-note/open-invariant-observation-point: - en: 9bcd1765116ee52c - zh: 296c60cd65f3016a + en: 42a6aabaa8d88937 + zh: 26581bc5f900c9ce diff --git a/packages/identity/anonymous-user-id/README.md b/packages/identity/anonymous-user-id/README.md index c1c6950e7e..c42fcc667d 100644 --- a/packages/identity/anonymous-user-id/README.md +++ b/packages/identity/anonymous-user-id/README.md @@ -66,14 +66,13 @@ This section explains the design decisions behind the package and points at the - **Random, never derived.** The id comes from `crypto.randomUUID()`; it is never derived from the hostname, network address, git remote, or any other identifying source, so anonymity is a property of the mint. - **Synchronous and memoized.** One process touches each resolved file path once: reads and writes are synchronous, and the result is memoized per resolved file path. - **Best-effort persistence.** A write failure still returns a usable id for the run, so telemetry and feedback never block on an unwritable home. -- **Library, not plugin.** There is no Cordis plugin entry or config. No invariant companion is published because the package owns no event stream or public mutable relation to compare without creating the id as a side effect. +- **Library, not plugin.** There is no Cordis plugin entry or config. ### Source map | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Library entry: `getOrCreateAnonymousUserId`, file persistence, per-path memoization | -| — | No runtime invariant companion is published; the API owns one private memo and one best-effort file, with no independent event stream or public mutable relation for a companion to compare without creating the identity as a side effect. | | [`tests/anonymous-user-id.spec.ts`](tests/anonymous-user-id.spec.ts) | Exercised behavior: mint, persistence, corruption, concurrency, memoization | ### The API @@ -136,8 +135,4 @@ This Dev Note is working context for maintainers: open questions and directions The persistence contract is a bare UUID line with no version marker. Adding a second value beside the id, or wrapping the line in a container, has no migration story for existing files; a versioned line format is one way to make such a change safe. -#### Open: invariant observation point - -No invariant companion is published because no relation can be checked without creating the id as a side effect. A future observation point could support comparing a re-read of the persisted file against the memoized id. - diff --git a/packages/identity/anonymous-user-id/README.zh.md b/packages/identity/anonymous-user-id/README.zh.md index d533c1d1de..5fa745a9b2 100644 --- a/packages/identity/anonymous-user-id/README.zh.md +++ b/packages/identity/anonymous-user-id/README.zh.md @@ -66,14 +66,13 @@ const userId = getOrCreateAnonymousUserId() // stable for the process lifetime - **随机生成,绝不派生。** id 来自 `crypto.randomUUID()`;绝不从 hostname、网络地址、git remote 或任何其他可识别来源派生,因此匿名性是生成过程的属性。 - **同步且记忆化。** 一个进程对每个解析后的文件路径只触碰一次磁盘:读写都是同步的,结果按解析后的文件路径记忆化。 - **Best-effort 持久化。** 写入失败仍会为本次运行返回可用 id,遥测与反馈因此不会因 home 不可写而阻塞。 -- **库而非插件。** 没有 Cordis 插件入口或配置。不发布不变式伴生入口,因为本包不拥有任何事件流或公开可变关系,无法在不产生创建 id 这一副作用的情况下比较。 +- **库而非插件。** 没有 Cordis 插件入口或配置。 ### 源码地图 | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 库入口:`getOrCreateAnonymousUserId`、文件持久化、按路径记忆化 | -| — | 不发布运行时不变式伴生入口;该 API 仅拥有一个私有记忆化值和一个 best-effort 文件,不存在独立事件流或公开可变关系可供伴生入口在不产生创建身份这一副作用的情况下比较。 | | [`tests/anonymous-user-id.spec.ts`](tests/anonymous-user-id.spec.ts) | 测试覆盖的行为:生成、持久化、损坏、并发、记忆化 | ### API @@ -136,8 +135,4 @@ const userId = getOrCreateAnonymousUserId() // stable for the process lifetime 持久化约定是没有任何版本标记的裸 UUID 行。在 id 旁边增加第二个值,或用容器包裹该行,对现有文件都没有迁移方案;带版本的行格式是让此类变更安全的一种方式。 -#### 开放:不变式观测点 - -不发布不变式伴生入口,因为任何关系都无法在不产生创建 id 这一副作用的情况下检查。未来若有安全的观测点,可以把重新读取的持久化文件与记忆化的 id 进行比较。 - diff --git a/packages/interaction/commands/README.i18n.yaml b/packages/interaction/commands/README.i18n.yaml index b8f8ff2e40..c7546df886 100644 --- a/packages/interaction/commands/README.i18n.yaml +++ b/packages/interaction/commands/README.i18n.yaml @@ -39,8 +39,8 @@ en: 063b49f26f48d628 zh: 2ec899aa6f01359f /deepseek-ai-dsh-commands/understand-the-implementation/source-map: - en: c0a1ffbf3bd434e7 - zh: 6b0b5699d749e38f + en: 1617729234228bb0 + zh: 3bee55cd286212e3 /deepseek-ai-dsh-commands/understand-the-implementation/lifecycle-events: en: c9fcc06089f8035d zh: 74072476994a2093 diff --git a/packages/interaction/commands/README.md b/packages/interaction/commands/README.md index bfcb82786a..1e679fdd14 100644 --- a/packages/interaction/commands/README.md +++ b/packages/interaction/commands/README.md @@ -82,7 +82,6 @@ The observable behavior is covered in [Use this package](#use-this-package); thi | [`src/index.ts`](src/index.ts) | `CommandRuntime` service: registration, scoping, dispatch, lifecycle events | | [`src/types.ts`](src/types.ts) | Command definition, descriptor, execution, and result types | | [`src/brand.ts`](src/brand.ts) | Stable command-definition identities and per-execution lifecycle ids | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion pairing `command/run` with `command/done` per session log | ### Lifecycle events diff --git a/packages/interaction/commands/README.zh.md b/packages/interaction/commands/README.zh.md index 33e128f12f..36325faadb 100644 --- a/packages/interaction/commands/README.zh.md +++ b/packages/interaction/commands/README.zh.md @@ -82,7 +82,6 @@ ctx.commands.register({ | [`src/index.ts`](src/index.ts) | `CommandRuntime` 服务:注册、作用域、分派、生命周期事件 | | [`src/types.ts`](src/types.ts) | 命令定义、描述符、执行与结果类型 | | [`src/brand.ts`](src/brand.ts) | 稳定命令定义标识和每次执行的生命周期 id | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:按会话日志配对 `command/run` 与 `command/done` | ### 生命周期事件 diff --git a/packages/interaction/commands/package.json b/packages/interaction/commands/package.json index 78c097c210..ea793bcf4c 100644 --- a/packages/interaction/commands/package.json +++ b/packages/interaction/commands/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -43,7 +39,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts", "lib/typert.host.js", @@ -56,7 +51,6 @@ "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-attachment": "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:*", @@ -71,7 +65,6 @@ "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-attachment": "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/interaction/commands/src/invariant.ts b/packages/interaction/commands/src/invariant.ts deleted file mode 100644 index 5eb8194b1a..0000000000 --- a/packages/interaction/commands/src/invariant.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-commands`: - * command lifecycle events pair by commandId within one session log. - * @module @deepseek-ai/dsh-commands/invariant - */ - -import type { Context } from '@deepseek-ai/cordis' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' - -const PACKAGE_NAME = '@deepseek-ai/dsh-commands' - -/** Cordis companion plugin name. */ -export const name = 'commands-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/* jscpd:ignore-start -- package companions share replay and dispatch plumbing */ -/** Install pairing validation over loaded logs and newly appended lifecycle events. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - // Install-scoped so a dispose/re-register cycle re-sweeps from a clean slate. - const runIds = new WeakMap>() - const validateEvent = (session: Session, event: SessionEvent): void => { - if (event.type === 'command/run') { - const ids = runIds.get(session) ?? new Set() - if (ids.has(event.data.commandId)) { - fail(`command/run repeats commandId ${JSON.stringify(event.data.commandId)}`) - } - ids.add(event.data.commandId) - runIds.set(session, ids) - return - } - if (event.type !== 'command/done') return - if (runIds.get(session)?.has(event.data.commandId) !== true) { - fail(`command/done ${JSON.stringify(event.data.commandId)} pairs no prior command/run in this log`) - } - const source = event.data.sourceEventSeq - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - const sourceEvent = source === undefined ? undefined : session.eventAt(source) - if (source !== undefined - && (event.data.kind !== 'success' - || !Number.isSafeInteger(source) || source < 0 || source >= event.seq - || sourceEvent?.seq !== source - || sourceEvent.type === 'command/run' - || sourceEvent.type === 'command/done')) { - fail(`command/done ${JSON.stringify(event.data.commandId)} has invalid sourceEventSeq ${String(source)}`) - } - } - for (const session of ctx.sessions.list()) { - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) validateEvent(session, event) - } - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - validateEvent(session, event) - }, { global: true }) -}, { inject: ['sessions'] }) -/* jscpd:ignore-end */ - -/** - * 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)) diff --git a/packages/interaction/commands/tests/invariant.spec.ts b/packages/interaction/commands/tests/invariant.spec.ts deleted file mode 100644 index f6dbd9c997..0000000000 --- a/packages/interaction/commands/tests/invariant.spec.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import * as CommandInvariant from '@deepseek-ai/dsh-commands/invariant' -import InvariantRegistry, { InvariantError } from '@deepseek-ai/dsh-invariants' -import SessionStore, { SessionId, SessionSeq, type Session } from '@deepseek-ai/dsh-session' -import { CommandId } from '@deepseek-ai/dsh-commands' - -async function mount(installCompanion = true): Promise<{ ctx: Context; session: Session }> { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create(SessionId('commands-invariant')) - await ctx.plugin(InvariantRegistry, { enabled: true }) - if (installCompanion) await ctx.plugin(CommandInvariant) - return { ctx, session } -} - -function appendRun(session: Session, id: string): void { - session.append('command/run', { - commandId: CommandId(id), - name: 'linked', - args: '', - source: { kind: 'user' }, - }) -} - -describe('command lifecycle invariants', () => { - it('accepts a success outcome linked to an earlier non-command domain event', async () => { - const { session } = await mount() - const source = session.append('turn/start', { turn: 1 }) - appendRun(session, 'cmd-valid') - - expect(() => { - session.append('command/done', { - commandId: CommandId('cmd-valid'), - kind: 'success', - sourceEventSeq: source.seq, - }) - }).not.toThrow() - }) - - it.each([-1, 1.5, 1])('rejects invalid or non-prior sourceEventSeq %s', async (sourceEventSeq) => { - const { session } = await mount() - appendRun(session, 'cmd-invalid') - - expect(() => { - session.append('command/done', { - commandId: CommandId('cmd-invalid'), - kind: 'success', - sourceEventSeq: sourceEventSeq as never, - }) - }).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-commands', - })) - }) - - it('rejects an error settlement carrying a success-only source reference', async () => { - const { session } = await mount() - const source = session.append('turn/start', { turn: 1 }) - appendRun(session, 'cmd-error-source') - - expect(() => { - session.append('command/done', { - commandId: CommandId('cmd-error-source'), - kind: 'error', - text: 'failed', - sourceEventSeq: source.seq, - }) - }).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-commands', - })) - }) - - it('attributes an invalid durable prefix during late companion loading', async () => { - const { ctx, session } = await mount(false) - appendRun(session, 'cmd-late') - session.append('command/done', { - commandId: CommandId('cmd-late'), - kind: 'success', - sourceEventSeq: SessionSeq(0), - }) - - await expect(ctx.plugin(CommandInvariant)).rejects.toMatchObject({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-commands', - }) - }) -}) diff --git a/packages/interaction/commands/tsconfig.json b/packages/interaction/commands/tsconfig.json index 436f5c9472..a8af5f45f7 100644 --- a/packages/interaction/commands/tsconfig.json +++ b/packages/interaction/commands/tsconfig.json @@ -32,9 +32,6 @@ { "path": "../../util/brand" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../typert/protocol" }, diff --git a/packages/interaction/permission-presets/README.i18n.yaml b/packages/interaction/permission-presets/README.i18n.yaml index 29228184d7..d5f1ad8d11 100644 --- a/packages/interaction/permission-presets/README.i18n.yaml +++ b/packages/interaction/permission-presets/README.i18n.yaml @@ -33,8 +33,8 @@ en: f5c37bc882b3f0d1 zh: 8203bea78976aefc /deepseek-ai-dsh-permission-presets/understand-the-implementation/source-map: - en: c240ef830a066e26 - zh: 423ae941bed7e96c + en: 172f5c8af5705ada + zh: b424834e38adb25d /deepseek-ai-dsh-permission-presets/understand-the-implementation/write-path: en: b01849a5fa73aae9 zh: cbd7fcf50117502b diff --git a/packages/interaction/permission-presets/README.md b/packages/interaction/permission-presets/README.md index e4d6421dc7..486c361db7 100644 --- a/packages/interaction/permission-presets/README.md +++ b/packages/interaction/permission-presets/README.md @@ -79,7 +79,6 @@ The observable behavior is covered in [Use this package](#use-this-package); thi |---|---| | [`src/index.ts`](src/index.ts) | `PermissionPresetService`: configured table, fixed Auto registration, write path, settings namespace, session pinning, children | | [`src/types.ts`](src/types.ts) | Process catalog, catalog-change event, and `permissions` current-selection types | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion validating configured preset names; Auto restore is checked before publication | ### Write path diff --git a/packages/interaction/permission-presets/README.zh.md b/packages/interaction/permission-presets/README.zh.md index 2d2f3e46ca..9cc7b3f42c 100644 --- a/packages/interaction/permission-presets/README.zh.md +++ b/packages/interaction/permission-presets/README.zh.md @@ -79,7 +79,6 @@ kind: "package-reference" |---|---| | [`src/index.ts`](src/index.ts) | `PermissionPresetService`:配置表、固定 Auto 注册、写入路径、设置命名空间、会话固定、子功能 | | [`src/types.ts`](src/types.ts) | 进程级目录、目录变化事件与 `permissions` 当前选择类型 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:校验配置预设名称;Auto 恢复在发布前另行检查 | ### 写入路径 diff --git a/packages/interaction/permission-presets/package.json b/packages/interaction/permission-presets/package.json index b62f95b780..fd7be83a48 100644 --- a/packages/interaction/permission-presets/package.json +++ b/packages/interaction/permission-presets/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -43,7 +39,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts", "lib/typert.host.js", @@ -55,7 +50,6 @@ "peerDependencies": { "@deepseek-ai/dsh-shell": "workspace:*", "@deepseek-ai/dsh-commands": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-sandbox": "workspace:*", "@deepseek-ai/dsh-sandbox-policy": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", @@ -71,7 +65,6 @@ "devDependencies": { "@deepseek-ai/dsh-shell": "workspace:*", "@deepseek-ai/dsh-commands": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-sandbox": "workspace:*", "@deepseek-ai/dsh-sandbox-policy": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", diff --git a/packages/interaction/permission-presets/src/invariant.ts b/packages/interaction/permission-presets/src/invariant.ts deleted file mode 100644 index 1f87ec426d..0000000000 --- a/packages/interaction/permission-presets/src/invariant.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** Package-owned permission-preset event invariants. @module @deepseek-ai/dsh-permission-presets/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import { AUTO_PRESET } from './index.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-permission-presets' - -/** Cordis companion plugin name. */ -export const name = 'permission-presets-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Validate the package-owned event fields and ignore unrelated events. */ -function validateEvent(ctx: Context, event: SessionEvent, fail: InvariantFailure): void { - if (event.type === 'permission/preset' - && event.data.preset !== AUTO_PRESET - && !ctx.permissionPresets.names.includes(event.data.preset)) { - fail(`permission/preset names unknown preset ${JSON.stringify(event.data.preset)}`) - } -} - -/** Install validation that loaded and newly appended preset events remain resolvable. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - for (const session of ctx.sessions.list()) { - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) validateEvent(ctx, event, fail) - } - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const event = (args as [Session, SessionEvent])[1] - validateEvent(ctx, event, fail) - }, { global: true }) -}, { inject: ['permissionPresets', 'sessions'] }) - -/** - * Register the permission 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)) diff --git a/packages/interaction/permission-presets/tests/invariant.spec.ts b/packages/interaction/permission-presets/tests/invariant.spec.ts deleted file mode 100644 index 2d821482f3..0000000000 --- a/packages/interaction/permission-presets/tests/invariant.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context, Service } from '@deepseek-ai/cordis' -import SessionStore, { SessionSeq, type Session, type SessionEvent } from '@deepseek-ai/dsh-session' -import * as PermissionInvariant from '@deepseek-ai/dsh-permission-presets/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -class PermissionProbe extends Service { - readonly names = ['safe', 'trusted'] - - constructor(ctx: Context) { - super(ctx, 'permissionPresets') - } -} - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(PermissionProbe) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(PermissionInvariant) - return ctx -} - -function presetEvent(preset: string): SessionEvent { - return { type: 'permission/preset', seq: SessionSeq(0), time: 0, data: { preset } } -} - -describe('permission invariants', () => { - it('accepts configured preset events and ignores other session data', async () => { - const ctx = await setup() - expect(() => { ctx.emit('session/event', {} as Session, presetEvent('safe')) }).not.toThrow() - expect(() => { ctx.emit('session/event', {} as Session, { - type: 'turn/end', seq: SessionSeq(0), time: 0, data: {}, - } as SessionEvent) }).not.toThrow() - expect(() => { ctx.emit('tools/change') }).not.toThrow() - }) - - it('rejects a durable preset that the active table cannot resolve', async () => { - const ctx = await setup() - expect(() => { ctx.emit('session/event', {} as Session, presetEvent('missing')) }) - .toThrow(/unknown preset "missing"/) - }) - - it('rejects an unknown preset already present on late registration', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(PermissionProbe) - ctx.sessions.create().append('permission/preset', { preset: 'missing' }) - await ctx.plugin(InvariantRegistry, { enabled: true }) - - await expect(ctx.plugin(PermissionInvariant).then(() => undefined)).rejects.toThrow(/unknown preset "missing"/) - }) -}) diff --git a/packages/interaction/permission-presets/tsconfig.json b/packages/interaction/permission-presets/tsconfig.json index b4dec0574b..aab4a6f538 100644 --- a/packages/interaction/permission-presets/tsconfig.json +++ b/packages/interaction/permission-presets/tsconfig.json @@ -32,9 +32,6 @@ { "path": "../user-approval" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../session/session-projection" }, diff --git a/packages/interaction/tool-ask-user/README.i18n.yaml b/packages/interaction/tool-ask-user/README.i18n.yaml index 2d3f5f2bc2..a5d61e2fd2 100644 --- a/packages/interaction/tool-ask-user/README.i18n.yaml +++ b/packages/interaction/tool-ask-user/README.i18n.yaml @@ -30,8 +30,8 @@ en: e9b9d46be0b45ca2 zh: c02403486473cd80 /deepseek-ai-dsh-tool-ask-user/understand-the-implementation/source-map: - en: 95a6f15bb08e239b - zh: 9def25d23f0e131d + en: e7babda77aaa0bd8 + zh: ba068b0670451efe /deepseek-ai-dsh-tool-ask-user/understand-the-implementation/consumer-role: en: 7de88d25287b969f zh: be36dc5a39aeac3b diff --git a/packages/interaction/tool-ask-user/README.md b/packages/interaction/tool-ask-user/README.md index 13ff23cb79..535da6f975 100644 --- a/packages/interaction/tool-ask-user/README.md +++ b/packages/interaction/tool-ask-user/README.md @@ -89,7 +89,6 @@ The observable behavior is covered in [Use this package](#use-this-package); thi |---|---| | [`src/index.ts`](src/index.ts) | Default blocking tool definition and mode selection | | [`src/timed.ts`](src/timed.ts) | Opt-in timed tool definition and result rendering | -| — | No runtime invariant companion is published; this model-facing adapter has no independent lifecycle stream; execution relations are owned by the capability seam it calls. | ### Consumer role diff --git a/packages/interaction/tool-ask-user/README.zh.md b/packages/interaction/tool-ask-user/README.zh.md index d03bbbc006..3ab957df64 100644 --- a/packages/interaction/tool-ask-user/README.zh.md +++ b/packages/interaction/tool-ask-user/README.zh.md @@ -89,7 +89,6 @@ Legacy 调用和 `timeout: -1` 调用会等待回答或取消;没有 answerer |---|---| | [`src/index.ts`](src/index.ts) | 默认阻塞式工具定义与模式选择 | | [`src/timed.ts`](src/timed.ts) | 显式启用的计时工具定义与结果渲染 | -| — | 不发布运行时不变式伴生入口;此模型侧适配器没有独立的生命周期流;执行关系由其调用的能力 seam 负责。 | ### 消费方角色 diff --git a/packages/interaction/user-approval/README.i18n.yaml b/packages/interaction/user-approval/README.i18n.yaml index a3d4a3ca8e..b480ae37c5 100644 --- a/packages/interaction/user-approval/README.i18n.yaml +++ b/packages/interaction/user-approval/README.i18n.yaml @@ -33,8 +33,8 @@ en: d07be7ba08baf010 zh: 665e68e9188e5d60 /deepseek-ai-dsh-user-approval/understand-the-implementation/source-map: - en: 3abbfecbac6271f1 - zh: b14f235a230f561e + en: ac27642b3a91082f + zh: 769a56eaad4218f7 /deepseek-ai-dsh-user-approval/understand-the-implementation/dispatch: en: 38c3646e45d3dddd zh: 3363dde8c786d6b2 @@ -42,8 +42,8 @@ en: a8df8823aa0b015d zh: ed0906f11645e7fd /deepseek-ai-dsh-user-approval/understand-the-implementation/audit: - en: 2b8650dcd0032440 - zh: 3e1f3070924b6052 + en: 145530678bac5912 + zh: a464effa3a29fce0 /deepseek-ai-dsh-user-approval/further-exploration: en: cb43cb6a03fdc26a zh: e150e41d99be0d45 diff --git a/packages/interaction/user-approval/README.md b/packages/interaction/user-approval/README.md index 82d84222de..b9cd3fc97c 100644 --- a/packages/interaction/user-approval/README.md +++ b/packages/interaction/user-approval/README.md @@ -71,7 +71,6 @@ The observable behavior is covered in [Use this package](#use-this-package); thi |---|---| | [`src/index.ts`](src/index.ts) | `ApprovalService`: request dispatch, policy fold and write path, runtime-context contribution | | [`src/types.ts`](src/types.ts) | `ApprovalRequestId` brand and outcome types | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion pairing `approval/asked` with `approval/decided` inside an open turn | ### Dispatch @@ -83,7 +82,7 @@ The system-prompt contribution `approval:policy` states the complete current mea ### Audit -`request()` appends `approval/asked` with the request identity and tool, then `approval/decided` with the closed outcome; the exact appended fields live in [`src/index.ts`](src/index.ts). Both are log-only; the invariant validates the pair by id within one open turn and the closed outcome vocabulary. +`request()` appends `approval/asked` with the request identity and tool, then `approval/decided` with the closed outcome; the exact appended fields live in [`src/index.ts`](src/index.ts). Both are log-only. diff --git a/packages/interaction/user-approval/README.zh.md b/packages/interaction/user-approval/README.zh.md index e6e4029e3a..37616fe3c2 100644 --- a/packages/interaction/user-approval/README.zh.md +++ b/packages/interaction/user-approval/README.zh.md @@ -71,7 +71,6 @@ kind: "package-reference" |---|---| | [`src/index.ts`](src/index.ts) | `ApprovalService`:请求分发、策略折叠与写入路径、运行时上下文贡献 | | [`src/types.ts`](src/types.ts) | `ApprovalRequestId` brand 与结果类型 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:在未结束的轮次内配对 `approval/asked` 与 `approval/decided` | ### 分发 @@ -83,7 +82,7 @@ kind: "package-reference" ### 审计 -`request()` 先追加携带请求身份与工具的 `approval/asked`,再追加携带最终结果的 `approval/decided`;确切追加字段见 [`src/index.ts`](src/index.ts)。两者都只写入日志;不变式会在同一个未结束轮次内按 id 校验这一事件对,并校验结果属于封闭词汇。 +`request()` 先追加携带请求身份与工具的 `approval/asked`,再追加携带最终结果的 `approval/decided`;确切追加字段见 [`src/index.ts`](src/index.ts)。两者都只写入日志。 diff --git a/packages/interaction/user-approval/package.json b/packages/interaction/user-approval/package.json index b98d7f5fb0..2c8cb0478d 100644 --- a/packages/interaction/user-approval/package.json +++ b/packages/interaction/user-approval/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -31,7 +27,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -39,7 +34,6 @@ "peerDependencies": { "@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:*", @@ -52,7 +46,6 @@ "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/interaction/user-approval/src/invariant.ts b/packages/interaction/user-approval/src/invariant.ts deleted file mode 100644 index d356ca3698..0000000000 --- a/packages/interaction/user-approval/src/invariant.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** Package-owned approval audit-stream invariants. @module @deepseek-ai/dsh-user-approval/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { ApprovalRequestId } from './index.ts' -import { APPROVAL_POLICIES } from './index.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-user-approval' -const APPROVAL_OUTCOMES = ['allowed-once', 'rejected', 'cancelled', 'unavailable'] as const - -/** Cordis companion plugin name. */ -export const name = 'user-approval-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -type ApprovalTransition = - | { kind: 'asked'; id: ApprovalRequestId } - | { kind: 'decided'; id: ApprovalRequestId } - -interface ApprovalTrace { - openTurn: number | null - pending: Set -} - -/** Validate one approval event against committed unmatched questions. */ -function validateApprovalEvent( - trace: ApprovalTrace, - event: SessionEvent, - fail: InvariantFailure, -): ApprovalTransition | undefined { - if (event.type === 'approval/asked') { - if (trace.openTurn === null) fail('approval/asked appended outside any open turn') - if (event.data.toolName.length === 0) fail('approval/asked toolName must be non-empty') - if (trace.pending.has(event.data.id)) fail(`approval/asked repeated open id ${JSON.stringify(event.data.id)}`) - return { kind: 'asked', id: event.data.id } - } - if (event.type === 'approval/decided') { - if (trace.openTurn === null) fail('approval/decided appended outside any open turn') - if (!trace.pending.has(event.data.id)) fail(`approval/decided has no matching approval/asked for id ${JSON.stringify(event.data.id)}`) - if (!APPROVAL_OUTCOMES.includes(event.data.outcome)) { - fail(`approval/decided carries unknown outcome ${JSON.stringify(event.data.outcome)}`) - } - return { kind: 'decided', id: event.data.id } - } - if (event.type === 'approval/policy' && !APPROVAL_POLICIES.includes(event.data.policy)) { - fail(`approval/policy carries unknown policy ${JSON.stringify(event.data.policy)}`) - } - return undefined -} - -/** Apply one accepted approval-pair transition. */ -function applyApprovalTransition(pending: Set, transition: ApprovalTransition): void { - if (transition.kind === 'asked') pending.add(transition.id) - else pending.delete(transition.id) -} - -/** Install audit pairing and closed-vocabulary checks. */ -// Event owners keep precommit staging local so their vocabularies never move into a central helper. -/* jscpd:ignore-start */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const traces = new WeakMap() - const staged = new WeakMap() - const seed = (session: Session): ApprovalTrace => { - const trace: ApprovalTrace = { openTurn: null, pending: new Set() } - traces.set(session, trace) - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) { - if (event.type === 'turn/start') trace.openTurn = event.data.turn - else if (event.type === 'turn/end') trace.openTurn = null - const transition = validateApprovalEvent(trace, event, fail) - if (transition !== undefined) applyApprovalTransition(trace.pending, transition) - } - return trace - } - const traceFor = (session: Session): ApprovalTrace => traces.get(session) ?? seed(session) - - for (const session of ctx.sessions.list()) seed(session) - ctx.on('session/created', (session) => { seed(session) }, { global: true }) - ctx.on('session/event', (session, event) => { - const trace = traceFor(session) - if (event.type === 'turn/start') { - trace.openTurn = event.data.turn - return - } - if (event.type === 'turn/end') { - trace.openTurn = null - return - } - if (event.type !== 'approval/asked' && event.type !== 'approval/decided') return - const candidate = staged.get(event) - /* v8 ignore next -- internal/dispatch stages every package-owned pair event */ - if (candidate === undefined || candidate.session !== session) return fail('approval audit event published without pre-commit validation') - staged.delete(event) - applyApprovalTransition(trace.pending, candidate.transition) - }, { global: true }) - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - const transition = validateApprovalEvent(traceFor(session), event, fail) - if (transition !== undefined) staged.set(event, { session, transition }) - }, { global: true }) -}, { inject: ['sessions'] }) -/* jscpd:ignore-end */ - -/** - * Register the approval 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)) diff --git a/packages/interaction/user-approval/tests/invariant.spec.ts b/packages/interaction/user-approval/tests/invariant.spec.ts deleted file mode 100644 index d5c845cc21..0000000000 --- a/packages/interaction/user-approval/tests/invariant.spec.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import SessionStore, { Session, SessionId, SessionSeq } from '@deepseek-ai/dsh-session' -import { ApprovalRequestId } from '@deepseek-ai/dsh-user-approval' -import * as ApprovalInvariant from '@deepseek-ai/dsh-user-approval/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(ApprovalInvariant) - return ctx -} - -function startTurn(session: Session): void { - session.append('turn/start', { turn: 1 }) -} - -describe('approval invariants', () => { - it('accepts paired audit events and closed policy values', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - startTurn(session) - const id = ApprovalRequestId('ask-1') - session.append('approval/asked', { id, toolName: 'bash' }) - session.append('approval/decided', { id, outcome: 'allowed-once' }) - session.append('approval/policy', { policy: 'never' }) - }) - - it('rebuilds an unmatched question from an existing session', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - const id = ApprovalRequestId('ask-resume') - session.append('approval/asked', { id, toolName: 'bash' }) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(ApprovalInvariant) - expect(() => session.append('approval/decided', { id, outcome: 'cancelled' })).not.toThrow() - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - }) - - it('adopts a bare session first observed through publication', async () => { - const ctx = await setup() - const session = Session.create(SessionId('bare-approval-session')) - const id = ApprovalRequestId('bare-ask') - const asked = { - type: 'approval/asked', seq: SessionSeq(0), time: 0, data: { id, toolName: 'bash' }, - } as const - const decided = { - type: 'approval/decided', seq: SessionSeq(1), time: 1, data: { id, outcome: 'rejected' as const }, - } as const - expect(() => { - ctx.emit('session/event', session, { - type: 'turn/start', seq: SessionSeq(0), time: 0, - data: { turn: 1 }, - }) - ctx.emit('session/event', session, asked) - ctx.emit('session/event', session, decided) - }).not.toThrow() - }) - - it('rejects audit events outside any open turn', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - expect(() => session.append('approval/asked', { - id: ApprovalRequestId('ask-1'), toolName: 'bash', - })).toThrow(/outside any open turn/) - expect(() => session.append('approval/decided', { - id: ApprovalRequestId('ask-1'), outcome: 'rejected', - })).toThrow(/outside any open turn/) - }) - - it('rejects an unenclosed audit event when replaying an existing session', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create() - startTurn(session) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - session.append('approval/asked', { - id: ApprovalRequestId('ask-replay'), toolName: 'bash', - }) - await ctx.plugin(InvariantRegistry) - await expect(ctx.plugin(ApprovalInvariant).then(() => undefined)).rejects.toThrow(/outside any open turn/) - }) - - it('rejects malformed and unpaired audit events', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - startTurn(session) - const id = ApprovalRequestId('ask-1') - expect(() => session.append('approval/asked', { id, toolName: '' })) - .toThrow(/toolName must be non-empty/) - session.append('approval/asked', { id, toolName: 'bash' }) - expect(() => session.append('approval/asked', { id, toolName: 'bash' })) - .toThrow(/repeated open id/) - expect(() => session.append('approval/decided', { - id: ApprovalRequestId('missing'), outcome: 'rejected', - })).toThrow(/no matching approval\/asked/) - expect(() => session.append('approval/decided', { id, outcome: 'maybe' as never })) - .toThrow(/unknown outcome/) - expect(() => session.append('approval/policy', { policy: 'always' as never })) - .toThrow(/unknown policy/) - }) -}) diff --git a/packages/interaction/user-approval/tsconfig.json b/packages/interaction/user-approval/tsconfig.json index 2c8d26e1f6..fb9a9e6e2d 100644 --- a/packages/interaction/user-approval/tsconfig.json +++ b/packages/interaction/user-approval/tsconfig.json @@ -34,9 +34,6 @@ }, { "path": "../../core/system-prompt" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/interaction/user-approval/tsdown.config.ts b/packages/interaction/user-approval/tsdown.config.ts index 5ebe27accb..76c21c44d4 100644 --- a/packages/interaction/user-approval/tsdown.config.ts +++ b/packages/interaction/user-approval/tsdown.config.ts @@ -1,11 +1,6 @@ import { defineConfig } from 'tsdown' -/** - * Build index and the invariant companion as separate single-entry bundles. - * Both entries import src/types.ts (the browser-safe subpath), so a - * multi-entry build emits a shared chunk the package's exact `files` - * whitelist omits; separate builds inline it. - */ +/** Build the package root as a self-contained bundle. */ export default defineConfig([ { entry: ['lib/types/index.js'], @@ -17,14 +12,4 @@ export default defineConfig([ dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, - }, ]) diff --git a/packages/interaction/user-questions/README.i18n.yaml b/packages/interaction/user-questions/README.i18n.yaml index 633192920e..7f8068bc63 100644 --- a/packages/interaction/user-questions/README.i18n.yaml +++ b/packages/interaction/user-questions/README.i18n.yaml @@ -30,5 +30,5 @@ en: a2334c0fcb02d2d8 zh: 4592c204397387ac /deepseek-ai-dsh-user-questions/known-limitations-and-deferred-work/dev-note: - en: afb3435052693913 - zh: 675913943166be2d + en: 23f77437b2447181 + zh: 30115a0a4bda7a61 diff --git a/packages/interaction/user-questions/README.md b/packages/interaction/user-questions/README.md index 63821c92ef..3cdb0dd87c 100644 --- a/packages/interaction/user-questions/README.md +++ b/packages/interaction/user-questions/README.md @@ -67,4 +67,4 @@ None. -**Runtime invariant:** the `userQuestions` projection is derived from recorded tool calls, PTC dispatches, and admitted user messages; no separate question state is stored. A continued question can create a new user turn, but it cannot resume a finished tool call. No runtime invariant companion is published because the projection fold and the continued-only Remote methods enforce this boundary at their owners. +**Runtime invariant:** the `userQuestions` projection is derived from recorded tool calls, PTC dispatches, and admitted user messages; no separate question state is stored. A continued question can create a new user turn, but it cannot resume a finished tool call. diff --git a/packages/interaction/user-questions/README.zh.md b/packages/interaction/user-questions/README.zh.md index 134a9b76c5..cea78870d6 100644 --- a/packages/interaction/user-questions/README.zh.md +++ b/packages/interaction/user-questions/README.zh.md @@ -67,4 +67,4 @@ kind: "package-reference" -**运行时不变式:** `userQuestions` projection 由已记录的工具调用、PTC 子调用和准入的用户消息推导,不存储单独的问题状态。已继续的问题可以创建新的用户轮次,但不能恢复已结束的工具调用。此包不发布运行时不变量 companion,因为 projection 折叠和只接受已继续问题的 Remote 方法已在各自归属处保证这一边界。 +**运行时不变式:** `userQuestions` projection 由已记录的工具调用、PTC 子调用和准入的用户消息推导,不存储单独的问题状态。已继续的问题可以创建新的用户轮次,但不能恢复已结束的工具调用。 diff --git a/packages/jobs/jobs-local/README.i18n.yaml b/packages/jobs/jobs-local/README.i18n.yaml index 374ce96891..c98f5c04ca 100644 --- a/packages/jobs/jobs-local/README.i18n.yaml +++ b/packages/jobs/jobs-local/README.i18n.yaml @@ -39,8 +39,8 @@ en: b92e6e5415553e2e zh: c3e0be579af4c7fb /deepseek-ai-dsh-jobs-local/understand-the-implementation/source-map: - en: 6b4071b9c15ee800 - zh: 40410e03c9a38937 + en: cf99103141ed9f13 + zh: 3a650aa8c4019156 /deepseek-ai-dsh-jobs-local/understand-the-implementation/scope-layers: en: 2c53e11a60965985 zh: 63adc1628c71e62e diff --git a/packages/jobs/jobs-local/README.md b/packages/jobs/jobs-local/README.md index d141d19b06..c1b77cebd2 100644 --- a/packages/jobs/jobs-local/README.md +++ b/packages/jobs/jobs-local/README.md @@ -87,7 +87,6 @@ This section explains the design decisions behind the registry and points at the | [`src/events.ts`](src/events.ts) | Scope-layered event routing: `{ owner }`, `{ owners: 'all' }`, and `{ owners: 'scope' }` subscriptions | | [`src/ring.ts`](src/ring.ts) | The bounded per-job output ring: append, retention trim, offset reads | | [`src/pump.ts`](src/pump.ts) | The registry-owned pull pump: one timer per job, final drain before settlement | -| — | No runtime invariant companion is published; `@deepseek-ai/dsh-jobs/invariant` owns the event-protocol and event-versus-read checks. This provider's admission decision uses private configuration and must fail before a backend starter runs; `LocalJobRegistry.start()` enforces it synchronously for current producers. Repeating an aggregate after publication would expose private configuration solely to this companion and would not verify the fail-closed pre-start guarantee. | ### Scope layers diff --git a/packages/jobs/jobs-local/README.zh.md b/packages/jobs/jobs-local/README.zh.md index 38f8989f23..a025c4c6ad 100644 --- a/packages/jobs/jobs-local/README.zh.md +++ b/packages/jobs/jobs-local/README.zh.md @@ -87,7 +87,6 @@ kind: "package-reference" | [`src/events.ts`](src/events.ts) | 按 scope 分层的事件路由:`{ owner }`、`{ owners: 'all' }` 与 `{ owners: 'scope' }` 订阅 | | [`src/ring.ts`](src/ring.ts) | 每个任务的有界输出环:追加、保留裁剪、按偏移读取 | | [`src/pump.ts`](src/pump.ts) | 注册表拥有的拉取泵:每个任务一个定时器,结算前最后一次排干 | -| — | 不发布运行时不变式伴生入口;事件协议与事件对读取的检查位于 `@deepseek-ai/dsh-jobs/invariant`。此提供方的准入决策使用私有配置,并且必须在后端启动器运行前失败;当前生产方由 `LocalJobRegistry.start()` 同步执行该决策。发布后再重复聚合只会向 companion 暴露私有配置,也无法验证失败发生在启动前。 | ### scope 分层 diff --git a/packages/jobs/jobs/README.i18n.yaml b/packages/jobs/jobs/README.i18n.yaml index 80cc92a4e8..3769f106b8 100644 --- a/packages/jobs/jobs/README.i18n.yaml +++ b/packages/jobs/jobs/README.i18n.yaml @@ -39,8 +39,8 @@ en: 2a42e1e06dfc469e zh: 5c2c77eca52846f0 /deepseek-ai-dsh-jobs/understand-the-implementation/source-map: - en: 2416d5be526d4dba - zh: 1090a9373258d7bb + en: d48098f76de74fa9 + zh: e69cb8e7f316ce95 /deepseek-ai-dsh-jobs/understand-the-implementation/service-operations: en: b2e4ef34ccf1e749 zh: 128b57b57ed09316 diff --git a/packages/jobs/jobs/README.md b/packages/jobs/jobs/README.md index 38454678cd..d1a373108a 100644 --- a/packages/jobs/jobs/README.md +++ b/packages/jobs/jobs/README.md @@ -81,7 +81,6 @@ This section explains the design decisions behind the contract and points at the | [`src/view.ts`](src/view.ts) | Client-safe leaf: `JobView`, `JobChunk`, `JobStatus`, and the merge-extensible `JobKindMap` | | [`src/brand.ts`](src/brand.ts) | `JobId` branded identifier, importable without the agent dependency | | [`src/archive-admission.ts`](src/archive-admission.ts) | The `job` family of the Workspace registry's archive admission, installed by the seam's constructor for every implementation | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: checks the announced event protocol per job (registered first, one settlement, removal last) and each announced projection against the registry's own read | ### Service operations diff --git a/packages/jobs/jobs/README.zh.md b/packages/jobs/jobs/README.zh.md index ab13074381..9ef21770f1 100644 --- a/packages/jobs/jobs/README.zh.md +++ b/packages/jobs/jobs/README.zh.md @@ -81,7 +81,6 @@ kind: "package-reference" | [`src/view.ts`](src/view.ts) | 客户端安全叶子:`JobView`、`JobChunk`、`JobStatus` 与可合并扩展的 `JobKindMap` | | [`src/brand.ts`](src/brand.ts) | `JobId` 带类型标记的标识符,无需 agent 依赖即可导入 | | [`src/archive-admission.ts`](src/archive-admission.ts) | Workspace 注册表归档准入中的 `job` 族,由接缝构造函数为每个实现安装 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:校验每个 job 的事件协议(先 registered、恰一次结算、最后 removed)以及每个通告的投影与注册表自身读取的一致性 | ### 服务操作 diff --git a/packages/jobs/jobs/package.json b/packages/jobs/jobs/package.json index d90287f4d5..c0301d69d1 100644 --- a/packages/jobs/jobs/package.json +++ b/packages/jobs/jobs/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./brand": { "types": "./lib/types/brand.d.ts", "default": "./lib/types/brand.js" @@ -35,7 +31,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -43,7 +38,6 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-brand": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-workspace": "workspace:*", "@deepseek-ai/cordis": "workspace:~" @@ -56,7 +50,6 @@ "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-brand": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-workspace": "workspace:*", "@deepseek-ai/cordis": "workspace:~" diff --git a/packages/jobs/jobs/src/invariant.ts b/packages/jobs/jobs/src/invariant.ts deleted file mode 100644 index 110a94c34b..0000000000 --- a/packages/jobs/jobs/src/invariant.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** Package-owned background-job event-protocol invariants. @module @deepseek-ai/dsh-jobs/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { JobId } from './brand.ts' -import type { JobEvent, JobView } from './types.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-jobs' -const TERMINAL_STATUSES = new Set(['completed', 'killed', 'failed']) - -/** - * Where one announced job stands: `live` from its first event until its - * settlement, `settled` until its removal. An id first seen through a later - * event is adopted at that phase, so a companion mounted after the job - * started raises nothing. - */ -type Phase = 'live' | 'settled' - -/** Cordis companion plugin name. */ -export const name = 'jobs-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** The registry's own current projection of `id`, or undefined once the record is gone. */ -function readBack(ctx: Context, id: JobId, owner: JobView['owner']): JobView | undefined { - try { - return ctx.jobs.get(id, owner) - } catch { - // The registry throws for an id outside the caller's set; after a removal that is the expected answer. - return undefined - } -} - -/** - * The announced projection must agree with the registry's own read: the - * immutable identity matches, and the announced output total is not ahead of - * the committed one. - */ -function checkAnnounced(read: JobView, announced: JobView, what: string, fail: InvariantFailure): void { - const id = String(announced.id) - for (const key of ['kind', 'label', 'owner', 'startedAt'] as const) { - if (read[key] !== announced[key]) { - fail(`${what} for job ${id} announces ${key} ${JSON.stringify(announced[key])} while the registry reads ${JSON.stringify(read[key])}`) - } - } - if (announced.output.total > read.output.total) { - fail(`${what} for job ${id} announces output total ${announced.output.total} ahead of the registry's ${read.output.total}`) - } -} - -/** Install the per-job protocol and event-versus-read checks over the whole event stream. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const phases = new Map() - ctx.jobs.events.subscribe({ owners: 'all' }, (event: JobEvent) => { - if (event.type === 'output') { - const id = String(event.id) - const read = readBack(ctx, event.id, event.owner) - if (read === undefined) fail(`output announced for job ${id} that the registry no longer returns`) - if (event.total > read.output.total) { - fail(`output announced for job ${id} at total ${event.total} ahead of the registry's ${read.output.total}`) - } - if (!phases.has(id)) phases.set(id, 'live') - return - } - const { job } = event - const id = String(job.id) - const phase = phases.get(id) - const terminal = TERMINAL_STATUSES.has(job.status) - switch (event.type) { - case 'registered': - if (phase !== undefined) fail(`registered announced for job ${id} after earlier events for the same id`) - if (terminal || job.finishedAt !== undefined) fail(`registered job ${id} must announce a live status without finishedAt`) - break - case 'progress': - case 'stopping': - if (phase === 'settled') fail(`${event.type} announced for job ${id} after its settlement`) - if (terminal) fail(`${event.type} announced for job ${id} with a terminal status`) - break - case 'settled': - if (phase === 'settled') fail(`settled announced twice for job ${id}`) - if (!terminal) fail(`settled job ${id} must announce a terminal status, got ${JSON.stringify(job.status)}`) - if (job.finishedAt === undefined || job.finishedAt < job.startedAt) { - fail(`settled job ${id} must announce finishedAt no earlier than startedAt`) - } - if (job.progress !== undefined) fail(`settled job ${id} must announce a cleared progress line`) - break - case 'removed': - if (phase === 'live') fail(`removed announced for job ${id} before its settlement`) - if (readBack(ctx, job.id, job.owner) !== undefined) fail(`removed announced for job ${id} that the registry still returns`) - phases.delete(id) - return - /* v8 ignore start -- defensive: the lifecycle event union is closed and exhaustive here; a new variant fails loudly */ - default: { - const exhaustive: never = event - fail(`unknown job event ${String(exhaustive)} for job ${id}`) - } - /* v8 ignore stop */ - } - const read = readBack(ctx, job.id, job.owner) - if (read === undefined) fail(`${event.type} announced for job ${id} that the registry does not return`) - checkAnnounced(read, job, event.type, fail) - if (event.type === 'settled' && (read.status !== job.status || read.finishedAt !== job.finishedAt)) { - fail(`settled job ${id} announces ${job.status} at ${String(job.finishedAt)}` - + ` while the registry reads ${read.status} at ${String(read.finishedAt)}`) - } - phases.set(id, event.type === 'settled' ? 'settled' : 'live') - }) -}, { inject: ['jobs'] }) - -/** - * Register the job-registry 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)) diff --git a/packages/jobs/jobs/tests/invariant.spec.ts b/packages/jobs/jobs/tests/invariant.spec.ts deleted file mode 100644 index 0f4defcbc9..0000000000 --- a/packages/jobs/jobs/tests/invariant.spec.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import { SessionId } from '@deepseek-ai/dsh-session' -import JobRegistry, { JobId } from '@deepseek-ai/dsh-jobs' -import type { JobEvent, JobEventFilter, JobEventListener, JobView } from '@deepseek-ai/dsh-jobs' -import * as JobsInvariant from '@deepseek-ai/dsh-jobs/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -const ID = JobId('bash-1') - -const RUNNING: JobView = { - id: ID, - kind: 'bash', - label: 'compile', - status: 'running', - startedAt: 10, - output: { total: 0, earliest: 0 }, -} - -const DONE: JobView = { ...RUNNING, status: 'completed', finishedAt: 20 } - -/** - * A stub registry whose reads come from a map the test edits: the companion - * compares every announced projection against these reads. - */ -async function setup() { - const ctx = new Context() - const reads = new Map() - let listener: JobEventListener | undefined - /** A registry serving only the reads and the subscription the companion uses. */ - class ProbeRegistry extends JobRegistry { - readonly events = { - subscribe: (_filter: JobEventFilter, value: JobEventListener): (() => void) => { - listener = value - return () => { listener = undefined } - }, - } - get(id: JobId, caller?: SessionId): JobView { - const view = reads.get(String(id)) - if (view === undefined || view.owner !== caller) throw new Error(`unknown job ${String(id)}`) - return view - } - start(): never { throw new Error('unsupported') } - list(): never { throw new Error('unsupported') } - read(): never { throw new Error('unsupported') } - readAt(): never { throw new Error('unsupported') } - kill(): never { throw new Error('unsupported') } - wait(): never { throw new Error('unsupported') } - remove(): never { throw new Error('unsupported') } - attachController(): never { throw new Error('unsupported') } - } - await ctx.plugin(InvariantRegistry) - await ctx.plugin(ProbeRegistry) - await ctx.plugin(JobsInvariant) - if (listener === undefined) throw new Error('job invariant did not subscribe to the event stream') - const emit = (event: JobEvent): void => { listener!(event) } - const read = (view: JobView | undefined): void => { - if (view === undefined) reads.delete(String(ID)) - else reads.set(String(view.id), view) - } - return { emit, read } -} - -describe('job-registry invariants', () => { - it('accepts one job announced from registration to removal, each event agreeing with the read', async () => { - const { emit, read } = await setup() - read(RUNNING) - emit({ type: 'registered', job: RUNNING }) - read({ ...RUNNING, progress: '1/2' }) - emit({ type: 'progress', job: { ...RUNNING, progress: '1/2' } }) - read({ ...RUNNING, progress: '1/2', output: { total: 4, earliest: 0 } }) - emit({ type: 'output', id: ID, total: 4 }) - read({ ...RUNNING, status: 'stopping', output: { total: 4, earliest: 0 } }) - emit({ type: 'stopping', job: { ...RUNNING, status: 'stopping', output: { total: 4, earliest: 0 } } }) - read({ ...DONE, status: 'killed', output: { total: 4, earliest: 0 } }) - emit({ type: 'settled', job: { ...DONE, status: 'killed', output: { total: 4, earliest: 0 } }, cause: 'kill', awaited: false }) - emit({ type: 'output', id: ID, total: 4 }) - read(undefined) - expect(() => { emit({ type: 'removed', job: { ...DONE, status: 'killed', output: { total: 4, earliest: 0 } } }) }).not.toThrow() - }) - - it('adopts a job first seen through a later event, so a companion mounted late raises nothing', async () => { - const { emit, read } = await setup() - read({ ...RUNNING, progress: '3/4' }) - expect(() => { emit({ type: 'progress', job: { ...RUNNING, progress: '3/4' } }) }).not.toThrow() - read(DONE) - expect(() => { emit({ type: 'settled', job: DONE, cause: 'producer', awaited: false }) }).not.toThrow() - read(undefined) - expect(() => { emit({ type: 'removed', job: DONE }) }).not.toThrow() - }) - - it('accepts an unowned job read through the unowned view and an owned job through its owner', async () => { - const { emit, read } = await setup() - const owner = SessionId('owner') - const owned: JobView = { ...RUNNING, id: JobId('subagent-2'), kind: 'subagent', owner } - read(RUNNING) - read(owned) - expect(() => { emit({ type: 'registered', job: RUNNING }) }).not.toThrow() - expect(() => { emit({ type: 'registered', job: owned }) }).not.toThrow() - expect(() => { emit({ type: 'output', id: owned.id, owner, total: 0 }) }).not.toThrow() - }) - - it.each([ - ['registered after an earlier event for the same id', ({ emit, read }) => { - read(RUNNING) - emit({ type: 'output', id: ID, total: 0 }) - emit({ type: 'registered', job: RUNNING }) - }, /registered announced for job bash-1 after earlier events/], - ['registered twice', ({ emit, read }) => { - read(RUNNING) - emit({ type: 'registered', job: RUNNING }) - emit({ type: 'registered', job: RUNNING }) - }, /registered announced for job bash-1 after earlier events/], - ['registered with a terminal status', ({ emit, read }) => { - read(DONE) - emit({ type: 'registered', job: DONE }) - }, /must announce a live status without finishedAt/], - ['progress after settlement', ({ emit, read }) => { - read(DONE) - emit({ type: 'settled', job: DONE, cause: 'producer', awaited: false }) - emit({ type: 'progress', job: { ...DONE, progress: 'late' } }) - }, /progress announced for job bash-1 after its settlement/], - ['stopping with a terminal status', ({ emit, read }) => { - read(DONE) - emit({ type: 'stopping', job: DONE }) - }, /stopping announced for job bash-1 with a terminal status/], - ['settled twice', ({ emit, read }) => { - read(DONE) - emit({ type: 'settled', job: DONE, cause: 'producer', awaited: false }) - emit({ type: 'settled', job: DONE, cause: 'producer', awaited: false }) - }, /settled announced twice for job bash-1/], - ['settled with a live status', ({ emit, read }) => { - read(RUNNING) - emit({ type: 'settled', job: { ...RUNNING, finishedAt: 20 }, cause: 'producer', awaited: false }) - }, /must announce a terminal status, got "running"/], - ['settled without finishedAt', ({ emit, read }) => { - read(DONE) - emit({ type: 'settled', job: { ...RUNNING, status: 'completed' }, cause: 'producer', awaited: false }) - }, /finishedAt no earlier than startedAt/], - ['settled before it started', ({ emit, read }) => { - read(DONE) - emit({ type: 'settled', job: { ...DONE, finishedAt: 9 }, cause: 'producer', awaited: false }) - }, /finishedAt no earlier than startedAt/], - ['settled with a progress line', ({ emit, read }) => { - read(DONE) - emit({ type: 'settled', job: { ...DONE, progress: '9/10' }, cause: 'producer', awaited: false }) - }, /must announce a cleared progress line/], - ['settled while the registry still reads it live', ({ emit, read }) => { - read(RUNNING) - emit({ type: 'settled', job: DONE, cause: 'producer', awaited: false }) - }, /announces completed at 20 while the registry reads running at undefined/], - ['removed before settlement', ({ emit, read }) => { - read(RUNNING) - emit({ type: 'registered', job: RUNNING }) - read(undefined) - emit({ type: 'removed', job: RUNNING }) - }, /removed announced for job bash-1 before its settlement/], - ['removed while the registry still returns the job', ({ emit, read }) => { - read(DONE) - emit({ type: 'settled', job: DONE, cause: 'producer', awaited: false }) - emit({ type: 'removed', job: DONE }) - }, /removed announced for job bash-1 that the registry still returns/], - ['output ahead of the read total', ({ emit, read }) => { - read(RUNNING) - emit({ type: 'output', id: ID, total: 8 }) - }, /output announced for job bash-1 at total 8 ahead of the registry's 0/], - ['output for a job the registry no longer returns', ({ emit }) => { - emit({ type: 'output', id: ID, total: 0 }) - }, /output announced for job bash-1 that the registry no longer returns/], - ['a lifecycle event for a job the registry does not return', ({ emit }) => { - emit({ type: 'registered', job: RUNNING }) - }, /registered announced for job bash-1 that the registry does not return/], - ['an announced label the registry does not read', ({ emit, read }) => { - read(RUNNING) - emit({ type: 'registered', job: { ...RUNNING, label: 'link' } }) - }, /announces label "link" while the registry reads "compile"/], - ['an announced owner the registry does not read', ({ emit, read }) => { - read({ ...RUNNING, owner: SessionId('alice') }) - emit({ type: 'registered', job: { ...RUNNING, owner: SessionId('alice') } }) - read({ ...RUNNING, owner: SessionId('alice'), startedAt: 11 }) - emit({ type: 'progress', job: { ...RUNNING, owner: SessionId('alice'), progress: 'x' } }) - }, /announces startedAt 10 while the registry reads 11/], - ['an announced output total ahead of the read', ({ emit, read }) => { - read(RUNNING) - emit({ type: 'registered', job: { ...RUNNING, output: { total: 2, earliest: 0 } } }) - }, /announces output total 2 ahead of the registry's 0/], - ] as const satisfies readonly (readonly [string, (probe: Awaited>) => void, RegExp])[])( - 'rejects %s', - async (_name, script, message) => { - const probe = await setup() - expect(() => { script(probe) }).toThrow(message) - }, - ) -}) diff --git a/packages/jobs/jobs/tsconfig.json b/packages/jobs/jobs/tsconfig.json index 274d8808e2..88933b81d1 100644 --- a/packages/jobs/jobs/tsconfig.json +++ b/packages/jobs/jobs/tsconfig.json @@ -23,9 +23,6 @@ { "path": "../../core/session" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../workspace/workspace" } diff --git a/packages/jobs/tool-jobs/README.i18n.yaml b/packages/jobs/tool-jobs/README.i18n.yaml index 9cb7591fa7..edce451969 100644 --- a/packages/jobs/tool-jobs/README.i18n.yaml +++ b/packages/jobs/tool-jobs/README.i18n.yaml @@ -36,8 +36,8 @@ en: 5b69ae2716a52951 zh: fec78d84af7192b1 /deepseek-ai-dsh-tool-jobs/understand-the-implementation/source-map: - en: 354782dee6840110 - zh: dfd46a4eee6b937d + en: 6f006ff2d901bc92 + zh: 779d18ba7bec4202 /deepseek-ai-dsh-tool-jobs/understand-the-implementation/output-capping: en: b47638fb0e80287a zh: c47fa4b99b6df4c3 diff --git a/packages/jobs/tool-jobs/README.md b/packages/jobs/tool-jobs/README.md index 2836f10e16..a4a4e69f53 100644 --- a/packages/jobs/tool-jobs/README.md +++ b/packages/jobs/tool-jobs/README.md @@ -84,7 +84,6 @@ This section explains the design decisions behind the tools and points at the co |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: tool registrations, the settlement subscription and model-kill set, prompt section, output capping | | [`src/render.ts`](src/render.ts) | Model-facing rendering: the public projection, status lines, and the consuming delta (stdout, `[stderr]` section, dropped-output notice) | -| — | No runtime invariant companion is published; this model-facing adapter has no independent lifecycle stream; execution relations are owned by the capability seam it calls. | ### Output capping diff --git a/packages/jobs/tool-jobs/README.zh.md b/packages/jobs/tool-jobs/README.zh.md index 1311f5656d..df2c01d9ed 100644 --- a/packages/jobs/tool-jobs/README.zh.md +++ b/packages/jobs/tool-jobs/README.zh.md @@ -84,7 +84,6 @@ kind: "package-reference" |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:工具注册、结算订阅与模型杀停集合、提示词区段、输出上限 | | [`src/render.ts`](src/render.ts) | 面向模型的渲染:公共投影、状态行与消耗式增量(stdout、`[stderr]` 段、丢失输出提示) | -| — | 不发布运行时不变式伴生入口;执行关系归能力 seam 所有。 | ### 输出上限 diff --git a/packages/llm/deepseek-llm-api-extensions/README.i18n.yaml b/packages/llm/deepseek-llm-api-extensions/README.i18n.yaml index ad3321a809..6814858047 100644 --- a/packages/llm/deepseek-llm-api-extensions/README.i18n.yaml +++ b/packages/llm/deepseek-llm-api-extensions/README.i18n.yaml @@ -27,5 +27,5 @@ en: 440169524f1a1323 zh: 935424afe40201b7 /deepseek-ai-dsh-deepseek-llm-api-extensions/known-limitations-and-deferred-work/dev-note: - en: 0718d089fcbe4223 - zh: 5d6f73b7ecfe86ef + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/llm/deepseek-llm-api-extensions/README.md b/packages/llm/deepseek-llm-api-extensions/README.md index 1505c644a3..fc98e2d666 100644 --- a/packages/llm/deepseek-llm-api-extensions/README.md +++ b/packages/llm/deepseek-llm-api-extensions/README.md @@ -57,5 +57,3 @@ None; registry fields are model-hidden provider metadata and do not alter the se None. - -**Runtime invariant:** No companion is published. Duplicate ownership, detached output, and one acceptance settlement are enforced inside the registry operation that owns each decision. diff --git a/packages/llm/deepseek-llm-api-extensions/README.zh.md b/packages/llm/deepseek-llm-api-extensions/README.zh.md index d27eca175c..4e47666326 100644 --- a/packages/llm/deepseek-llm-api-extensions/README.zh.md +++ b/packages/llm/deepseek-llm-api-extensions/README.zh.md @@ -57,5 +57,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。重复所有权、detached output 与单次 acceptance settlement 都在拥有该决策的注册表操作中强制。 diff --git a/packages/llm/llm-deepseek-account/README.i18n.yaml b/packages/llm/llm-deepseek-account/README.i18n.yaml index 41b725fb21..81cb4147f8 100644 --- a/packages/llm/llm-deepseek-account/README.i18n.yaml +++ b/packages/llm/llm-deepseek-account/README.i18n.yaml @@ -18,8 +18,8 @@ en: 4fde33a269d60030 zh: 22e522f4a3e25e8c /deepseek-ai-dsh-llm-deepseek-account/understand-the-implementation: - en: 695b9c7263f6f3b5 - zh: 84d5fc9d958c4fe7 + en: c9ba244b5df11b97 + zh: d8016c03bfcb3eef /deepseek-ai-dsh-llm-deepseek-account/further-exploration: en: e8bf99d78aa7035f zh: e4a74d2d48b6d271 diff --git a/packages/llm/llm-deepseek-account/README.md b/packages/llm/llm-deepseek-account/README.md index 539a5980eb..492765a59c 100644 --- a/packages/llm/llm-deepseek-account/README.md +++ b/packages/llm/llm-deepseek-account/README.md @@ -41,7 +41,7 @@ An account request HTTP 401 maps to `ACCOUNT_TOKEN_INVALID` independently of the ## Understand the implementation -Registrations and listeners dispose with the plugin. Shared Host wiring supplies attachments, request extensions, anonymous identity, and atomic retry-policy updates; this plugin registers only its own route. No invariant companion is published: discovery derives directly from configuration and credentials without an independent copy. +Registrations and listeners dispose with the plugin. Shared Host wiring supplies attachments, request extensions, anonymous identity, and atomic retry-policy updates; this plugin registers only its own route. A failed request the shared transport classifies as `QUOTA` is rewritten to `ACCOUNT_QUOTA` before it leaves this provider, covering an HTTP 402 response and an in-band SSE error alike. The shared transport and the `deepseek-official` route keep `QUOTA`, so the account route's top-up action never appears for an API-key or third-party failure. diff --git a/packages/llm/llm-deepseek-account/README.zh.md b/packages/llm/llm-deepseek-account/README.zh.md index f436e0231d..c8e9da2e8f 100644 --- a/packages/llm/llm-deepseek-account/README.zh.md +++ b/packages/llm/llm-deepseek-account/README.zh.md @@ -41,7 +41,7 @@ kind: "package-reference" ## 理解实现 -注册和监听随插件生命周期释放。共享 Host 绑定提供附件访问、请求扩展、匿名标识和重试策略的原子更新;本包只注册自己的路由。无需 invariant companion:目录直接由同一次配置和凭据读取派生,不维护独立副本。 +注册和监听随插件生命周期释放。共享 Host 绑定提供附件访问、请求扩展、匿名标识和重试策略的原子更新;本包只注册自己的路由。 共享传输判定为 `QUOTA` 的失败请求离开本提供方前会被改写为 `ACCOUNT_QUOTA`,HTTP 402 响应与流内 SSE 错误都适用。共享传输与 `deepseek-official` 路由保留 `QUOTA`,因此账号路由的充值操作不会出现在 API Key 或第三方失败上。 diff --git a/packages/llm/llm-deepseek-api-key/README.i18n.yaml b/packages/llm/llm-deepseek-api-key/README.i18n.yaml index ed861f822b..3fbdaa2316 100644 --- a/packages/llm/llm-deepseek-api-key/README.i18n.yaml +++ b/packages/llm/llm-deepseek-api-key/README.i18n.yaml @@ -18,8 +18,8 @@ en: ab3d8cc5be370482 zh: 8a8b9871d5afe1df /deepseek-ai-dsh-llm-deepseek-api-key/understand-the-implementation: - en: a2a213e5629a569f - zh: eb23478d0081270a + en: 002ac925beb4e75b + zh: 815d06f116604a09 /deepseek-ai-dsh-llm-deepseek-api-key/further-exploration: en: e8bf99d78aa7035f zh: e4a74d2d48b6d271 diff --git a/packages/llm/llm-deepseek-api-key/README.md b/packages/llm/llm-deepseek-api-key/README.md index 4b22ef2ae9..078972091e 100644 --- a/packages/llm/llm-deepseek-api-key/README.md +++ b/packages/llm/llm-deepseek-api-key/README.md @@ -42,7 +42,7 @@ The endpoint and credential reference come from one configuration resolution. In ## Understand the implementation -Registrations and listeners dispose with the plugin. Shared Host wiring supplies attachments, request extensions, anonymous identity, and atomic retry-policy updates; this plugin registers only its own route. No invariant companion is published: discovery derives directly from configuration without an independent copy. +Registrations and listeners dispose with the plugin. Shared Host wiring supplies attachments, request extensions, anonymous identity, and atomic retry-policy updates; this plugin registers only its own route. ## Further Exploration diff --git a/packages/llm/llm-deepseek-api-key/README.zh.md b/packages/llm/llm-deepseek-api-key/README.zh.md index df6baea123..53be7fa230 100644 --- a/packages/llm/llm-deepseek-api-key/README.zh.md +++ b/packages/llm/llm-deepseek-api-key/README.zh.md @@ -42,7 +42,7 @@ kind: "package-reference" ## 理解实现 -注册和监听随插件生命周期释放。共享 Host 绑定提供附件访问、请求扩展、匿名标识和重试策略的原子更新;本包只注册自己的路由。无需 invariant companion:目录直接由配置派生,不维护独立副本。 +注册和监听随插件生命周期释放。共享 Host 绑定提供附件访问、请求扩展、匿名标识和重试策略的原子更新;本包只注册自己的路由。 ## 进一步探索 diff --git a/packages/llm/llm-deepseek/README.i18n.yaml b/packages/llm/llm-deepseek/README.i18n.yaml index 04a7e52140..8695f448e8 100644 --- a/packages/llm/llm-deepseek/README.i18n.yaml +++ b/packages/llm/llm-deepseek/README.i18n.yaml @@ -87,5 +87,5 @@ en: ff85701e1d416ad9 zh: 55c8b93908ab53c1 /deepseek-ai-dsh-llm-deepseek/known-limitations-and-deferred-work/dev-note: - en: 5b9e571c20e4ac72 - zh: dbb07a72ddc8f08b + en: fc7d8c8a6258331a + zh: 491b454fe920a17e diff --git a/packages/llm/llm-deepseek/README.md b/packages/llm/llm-deepseek/README.md index 651258fcbb..be2d1a187b 100644 --- a/packages/llm/llm-deepseek/README.md +++ b/packages/llm/llm-deepseek/README.md @@ -214,6 +214,4 @@ These limits define where the adapter stops and future work begins. They are cur None. -**Runtime invariant:** No companion is published. This package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. - `deepseek-official` uses only its configured API-key reference; `deepseek-account` uses only the stored DSH grant for the account provider’s allowed inference origin. Both routes share the Messages transport with independently configured model and file settings. Missing or ineligible account credentials reject the request with a sign-in prompt; neither route falls back to the other. Chat and Files requests reject redirects. The account provider owns sign-out cancellation using running Agents’ logged request contexts, including tool execution; the transport receives the existing request abort signal. diff --git a/packages/llm/llm-deepseek/README.zh.md b/packages/llm/llm-deepseek/README.zh.md index 0c4694bd8f..0d991ab660 100644 --- a/packages/llm/llm-deepseek/README.zh.md +++ b/packages/llm/llm-deepseek/README.zh.md @@ -214,6 +214,4 @@ loop 保留的响应块会追加到下一个请求,并保留其更早的可复 无。 -**运行时不变式:** 不发布伴生入口。本包没有独立事件序列或可变数据关系,相关约定在所属 seam 强制执行。 - `deepseek-official` 仅使用配置的 API Key 引用;`deepseek-account` 仅在账号提供方允许的推理来源使用已保存的 DSH 授权。两条路由共享 Messages 传输,模型与文件设置独立配置。账号凭证缺失或不适用于目标时拒绝请求并提示登录;两条路由均不回退到另一凭证。Chat 和 Files 请求拒绝重定向。账号提供方根据运行中 Agent 已记录的请求上下文负责退登取消,包括工具执行阶段;传输层接收现有请求的中止信号。 diff --git a/packages/llm/llm-pi-ai/README.i18n.yaml b/packages/llm/llm-pi-ai/README.i18n.yaml index 19311117b6..360574b758 100644 --- a/packages/llm/llm-pi-ai/README.i18n.yaml +++ b/packages/llm/llm-pi-ai/README.i18n.yaml @@ -90,5 +90,5 @@ en: bb240ee5dd3f81c7 zh: 884506a36f4aac62 /deepseek-ai-dsh-llm-pi-ai/known-limitations-and-deferred-work/dev-note: - en: be67a4547256efbf - zh: 8c32799683ba292c + en: 3678dd0f6642c2a5 + zh: 75cbdd064cc64282 diff --git a/packages/llm/llm-pi-ai/README.md b/packages/llm/llm-pi-ai/README.md index 0e8a801b53..ea0adbe524 100644 --- a/packages/llm/llm-pi-ai/README.md +++ b/packages/llm/llm-pi-ai/README.md @@ -243,5 +243,3 @@ This Dev Note is non-authoritative working context: undecided directions and not - The `compat` switch set is pinned to pi-ai's compat types by drift gates; an upstream upgrade that adds a field, gives a further protocol a compat type, or widens a value union fails the build until someone classifies it. - -**Runtime invariant:** No companion is published. This package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. diff --git a/packages/llm/llm-pi-ai/README.zh.md b/packages/llm/llm-pi-ai/README.zh.md index 5126fe25ed..8a72e3867b 100644 --- a/packages/llm/llm-pi-ai/README.zh.md +++ b/packages/llm/llm-pi-ai/README.zh.md @@ -243,5 +243,3 @@ pi-ai 事件变成 harness 的推理、文本、工具调用、用量与 finish - `compat` 开关集合由漂移门禁钉在 pi-ai 的 compat 类型上;上游升级若新增字段、为更多协议赋予 compat 类型或扩大值联合,会在有人分类前让构建失败。 - -**运行时不变式:** 不发布伴生入口。本包没有独立事件序列或可变数据关系,相关约定在所属 seam 强制执行。 diff --git a/packages/llm/llm-retry/README.i18n.yaml b/packages/llm/llm-retry/README.i18n.yaml index 4f57ab2ad9..1f0dc625c6 100644 --- a/packages/llm/llm-retry/README.i18n.yaml +++ b/packages/llm/llm-retry/README.i18n.yaml @@ -66,5 +66,5 @@ en: bef1baa545592b9c zh: 61984a1e10de8294 /deepseek-ai-dsh-llm-retry/known-limitations-and-deferred-work/dev-note: - en: ff38027230617ebb - zh: 4a7913e851407b0c + en: add61ee91e4ae8e4 + zh: 3877c147b541b2c9 diff --git a/packages/llm/llm-retry/README.md b/packages/llm/llm-retry/README.md index f923140df3..7e699c8f4b 100644 --- a/packages/llm/llm-retry/README.md +++ b/packages/llm/llm-retry/README.md @@ -144,6 +144,5 @@ These limits define where the executor stops and future work begins. They are cu This Dev Note is non-authoritative working context: notes for maintainers and open questions. Shipped behavior and accepted rationale live in the sections above, the package code, and the linked Agent Notes. - Retry numbers continue only across events with the same provider and complete policy key, so a route replacement with different limits, code membership, or backoff starts its own history; the key includes every behavior-affecting field and sorts normal-mode codes because eligibility uses set membership. -- The separately published `./invariant` companion validates each scheduled retry against the session log — naming the current open turn and latest closed step, matching the failed request's durable provider, and requiring each `llm/retry-started` event to name one prior scheduled attempt with the same retry id, turn, step, and retry number. diff --git a/packages/llm/llm-retry/README.zh.md b/packages/llm/llm-retry/README.zh.md index f8cc3f21f4..c32972426d 100644 --- a/packages/llm/llm-retry/README.zh.md +++ b/packages/llm/llm-retry/README.zh.md @@ -144,6 +144,5 @@ kind: "package-reference" 本开发备注是不具权威性的工作上下文:维护者备注与开放问题。已交付的行为与既定理由以上文、包代码和相关 Agent Note 为准。 - 重试编号只在同一提供方与完整策略键的事件间延续,因此限额、code 成员或退避不同的路由替换会开启自己的历史;该键包含每个影响行为的字段,并因资格判断使用集合成员而对 normal mode code 排序。 -- 单独发布的 `./invariant` 伴生插件会对照会话日志校验每次计划的重试——点名当前打开轮次与最新闭合步骤、匹配失败请求的持久提供方,并要求每个 `llm/retry-started` 事件点名一次带相同重试 id、轮次、步骤与重试编号的先前计划尝试。 diff --git a/packages/llm/llm-retry/package.json b/packages/llm/llm-retry/package.json index 23a9af15a4..48e7e31ee0 100644 --- a/packages/llm/llm-retry/package.json +++ b/packages/llm/llm-retry/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -30,7 +26,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -38,7 +33,6 @@ "peerDependencies": { "@deepseek-ai/dsh-brand": "workspace:*", "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-timeout": "workspace:*", @@ -56,7 +50,6 @@ "@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-llm-deepseek": "workspace:*", "@deepseek-ai/dsh-llm-deepseek-api-key": "workspace:*", @@ -67,7 +60,6 @@ "@deepseek-ai/dsh-system-prompt": "workspace:*", "@deepseek-ai/dsh-timeout": "workspace:*", "@deepseek-ai/dsh-tools": "workspace:*", - "@deepseek-ai/cordis": "workspace:~", - "@deepseek-ai/dsh-session-projection": "workspace:*" + "@deepseek-ai/cordis": "workspace:~" } } diff --git a/packages/llm/llm-retry/src/invariant.ts b/packages/llm/llm-retry/src/invariant.ts deleted file mode 100644 index afd1be1ccd..0000000000 --- a/packages/llm/llm-retry/src/invariant.ts +++ /dev/null @@ -1,178 +0,0 @@ -/** Package-owned durable retry-event invariants. @module @deepseek-ai/dsh-llm-retry/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { LlmFailure } from '@deepseek-ai/dsh-llm' -import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import { providerForOpenStep } from './history.ts' -import type {} from './index.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-llm-retry' - -/** Cordis companion plugin name. */ -export const name = 'llm-retry-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Validate the complete provider-neutral failure payload at the durable boundary. */ -function validateFailure(value: unknown, fail: InvariantFailure): asserts value is LlmFailure { - if (typeof value !== 'object' || value === null) { - fail('llm/retry failure must be an object') - } - const failure = value as Partial - if (typeof failure.message !== 'string' || failure.message.length === 0) { - fail('llm/retry failure.message must be a non-empty string') - } - if (typeof failure.code !== 'string' || failure.code.length === 0) { - fail('llm/retry failure.code must be a non-empty string') - } - if (failure.status !== undefined - && (!Number.isInteger(failure.status) || failure.status < 100 || failure.status > 599)) { - fail('llm/retry failure.status must be an integer from 100 through 599 when present') - } - if (failure.providerRetryAfterMs !== undefined - && (!Number.isFinite(failure.providerRetryAfterMs) || failure.providerRetryAfterMs <= 0)) { - fail('llm/retry failure.providerRetryAfterMs must be a positive finite number when present') - } - if (failure.requestId !== undefined - && (typeof failure.requestId !== 'string' || failure.requestId.length === 0)) { - fail('llm/retry failure.requestId must be a non-empty string when present') - } -} - -/** Validate one retry record against the currently open request step. */ -function validateRetry( - history: readonly SessionEvent[], - event: SessionEvent<'llm/retry'>, - fail: InvariantFailure, -): void { - const { retryId, turn, step, provider, mode, policyKey, retry, delayMs } = event.data - if (typeof retryId !== 'string' || retryId.length === 0) { - fail('llm/retry retryId must be a non-empty string') - } - const failure: unknown = event.data.failure - validateFailure(failure, fail) - if (!Number.isSafeInteger(retry) || retry < 1) { - fail('llm/retry retry must be a positive safe integer') - } - if (typeof provider !== 'string' || provider.length === 0) { - fail('llm/retry provider must be a non-empty string') - } - if (typeof policyKey !== 'string' || policyKey.length === 0) { - fail('llm/retry policyKey must be a non-empty string') - } - switch (mode) { - case 'normal': { - const { maxRetries } = event.data - if (!Number.isSafeInteger(maxRetries) || maxRetries < 1 || retry > maxRetries) { - fail(`llm/retry retry ${retry} must not exceed a positive safe maxRetries ${maxRetries}`) - } - break - } - case 'always': - if ('maxRetries' in event.data) fail('llm/retry always mode must omit maxRetries') - break - default: - fail(`llm/retry mode must be normal or always, got ${String(mode)}`) - } - if (typeof delayMs !== 'number' || !Number.isFinite(delayMs) - || delayMs < 0 || delayMs > MAX_TIMER_DELAY_MS) { - fail(`llm/retry delayMs must be a finite number within 0..${MAX_TIMER_DELAY_MS}`) - } - - const turnBoundary = history.findLast(prior => - prior.type === 'turn/start' || prior.type === 'turn/end') - if (turnBoundary?.type !== 'turn/start') { - fail('llm/retry must be appended inside an open turn') - } - if (turn !== turnBoundary.data.turn) { - fail(`llm/retry names turn ${turn}, but the open turn is ${turnBoundary.data.turn}`) - } - - const stepBoundary = history.findLast(prior => - prior.type === 'step/start' || prior.type === 'step/end') - if (stepBoundary?.type !== 'step/start') { - fail('llm/retry must be appended inside an open step') - } - if (step !== stepBoundary.data.step || turn !== stepBoundary.data.turn) { - fail(`llm/retry names turn ${turn}/step ${step}, but the open step is ${stepBoundary.data.turn}/${stepBoundary.data.step}`) - } - const routedProvider = providerForOpenStep(history, turn, step) - if (routedProvider !== provider) { - fail(`llm/retry provider ${provider} does not match the failed request provider ${String(routedProvider)}`) - } - - const priorPolicyRetry = history.findLast((prior): prior is SessionEvent<'llm/retry'> => - prior.type === 'llm/retry' - && prior.data.turn === turn - && prior.data.step === step - && prior.data.provider === provider - && prior.data.policyKey === policyKey) - const expectedRetry = (priorPolicyRetry?.data.retry ?? 0) + 1 - if (retry !== expectedRetry) { - fail(`llm/retry retry ${retry} must equal provider policy retry ${expectedRetry}`) - } - if (priorPolicyRetry !== undefined && priorPolicyRetry.data.retryId !== retryId) { - fail('llm/retry must preserve retryId across one provider-policy chain') - } - if (priorPolicyRetry === undefined && history.some(prior => - (prior.type === 'llm/retry' || prior.type === 'llm/retry-started') - && prior.data.retryId === retryId)) { - fail(`llm/retry retryId ${JSON.stringify(retryId)} is already owned by another chain`) - } -} - -/** Validate one wait-complete transition against its scheduled attempt. */ -function validateStarted( - history: readonly SessionEvent[], - event: SessionEvent<'llm/retry-started'>, - fail: InvariantFailure, -): void { - const { retryId, turn, step, retry } = event.data - if (typeof retryId !== 'string' || retryId.length === 0) { - fail('llm/retry-started retryId must be a non-empty string') - } - const scheduled = history.findLast((prior): prior is SessionEvent<'llm/retry'> => - prior.type === 'llm/retry' && prior.data.retryId === retryId && prior.data.retry === retry) - if (scheduled === undefined) fail('llm/retry-started pairs no prior scheduled attempt') - if (scheduled.data.turn !== turn || scheduled.data.step !== step) { - fail('llm/retry-started turn/step must match its scheduled attempt') - } - if (history.some(prior => prior.type === 'llm/retry-started' - && prior.data.retryId === retryId && prior.data.retry === retry)) { - fail('llm/retry-started repeats one scheduled attempt') - } -} - -/** Validate every retry record already present in one loaded session. */ -function validateSession(session: Session, fail: InvariantFailure): void { - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - const events = session.snapshotEvents() - for (const [index, event] of events.entries()) { - if (event.type === 'llm/retry') validateRetry(events.slice(0, index), event, fail) - else if (event.type === 'llm/retry-started') validateStarted(events.slice(0, index), event, fail) - } -} - -/** Install validation for loaded and newly appended retry records. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - for (const session of ctx.sessions.list()) validateSession(session, fail) - ctx.on('session/created', (session) => { validateSession(session, fail) }, { global: true }) - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - if (event.type === 'llm/retry') validateRetry(session.snapshotEvents(), event, fail) - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - else if (event.type === 'llm/retry-started') validateStarted(session.snapshotEvents(), event, fail) - }, { global: true }) -}, { inject: ['sessions'] }) - -/** - * Register the LLM retry 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)) diff --git a/packages/llm/llm-retry/tests/invariant.spec.ts b/packages/llm/llm-retry/tests/invariant.spec.ts deleted file mode 100644 index 0c6c9c6b6b..0000000000 --- a/packages/llm/llm-retry/tests/invariant.spec.ts +++ /dev/null @@ -1,360 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import SessionStore, { SessionId, type Session } from '@deepseek-ai/dsh-session' -import { createUserMessage, ProviderRequestId } from '@deepseek-ai/dsh-llm' -import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' -import AgentRegistry from '@deepseek-ai/dsh-agent' -import * as RetryInvariant from '@deepseek-ai/dsh-llm-retry/invariant' -import * as retry from '../src/index.ts' -import { RetryId } from '@deepseek-ai/dsh-llm-retry' -import { providerForOpenStep } from '../src/history.ts' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(RetryInvariant) - return ctx -} - -function openStep(ctx: Context, id: string, turn = 1, step = 1) { - const session = ctx.sessions.create(SessionId(id)) - session.append('turn/start', { turn }) - session.append('step/start', { turn, step }) - session.append('request/header', { - header: { config: { provider: 'mock', model: 'mock' } }, - reason: 'initial', - }) - return session -} - -function appendRetryTurn(session: Session, turn: number) { - session.append('turn/start', { turn }) - session.append('step/start', { turn, step: 1 }) - session.append('request/header', { - header: { config: { provider: 'mock', model: 'mock' } }, - reason: 'initial', - }) - session.append('llm/retry', { turn, step: 1, ...normal }) -} - -const failure = { message: 'provider busy', code: 'RATE_LIMIT', status: 429 } -const normal = { - retryId: RetryId('normal-retry-chain'), - provider: 'mock', - mode: 'normal' as const, - policyKey: 'normal-policy', - retry: 1, - maxRetries: 2, - delayMs: 1, - failure, -} -const always = { - retryId: RetryId('always-retry-chain'), - provider: 'mock', - mode: 'always' as const, - policyKey: 'always-policy', - retry: 1, - delayMs: 1, - failure, -} - -describe('llm-retry projection fold', () => { - it('keeps one retry count per dispatch key across duplicate records', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(AgentRegistry) - await ctx.plugin(SessionProjectionRegistry) - await ctx.plugin(retry) - const session = ctx.sessions.create(SessionId('retry-dup')) - session.append('llm/retry', { turn: 1, step: 1, ...normal }) - const first = ctx.sessionProjections.stateOf(session, 'llmRetry') - session.append('llm/retry', { turn: 1, step: 1, ...normal }) - const second = ctx.sessionProjections.stateOf(session, 'llmRetry') - expect(second).toBe(first) - expect(second).toEqual({ - '["mock","normal-policy"]': { - retry: 1, - retryId: 'normal-retry-chain', - }, - }) - }) -}) - -describe('llm-retry invariants', () => { - it('has no provider without the requested open step or a route marker', () => { - expect(providerForOpenStep([], 1, 1)).toBeUndefined() - expect(providerForOpenStep([{ - type: 'step/start', - data: { turn: 1, step: 1 }, - }] as never, 1, 1)).toBeUndefined() - }) - - it('accepts successive bounded and unbounded records inside their open steps', async () => { - const ctx = await setup() - const session = openStep(ctx, 'retry-invariant-valid') - - expect(() => { - session.append('llm/retry', { turn: 1, step: 1, ...normal }) - session.append('llm/retry', { - turn: 1, step: 1, ...normal, retry: 2, delayMs: 0, - }) - const unbounded = openStep(ctx, 'retry-invariant-always') - unbounded.append('llm/retry', { turn: 1, step: 1, ...always }) - }).not.toThrow() - expect(() => { ctx.emit('tools/change') }).not.toThrow() - }) - - it('validates the complete durable failure payload', async () => { - const ctx = await setup() - const complete = openStep(ctx, 'retry-invariant-complete-failure') - expect(() => { - complete.append('llm/retry', { - turn: 1, - step: 1, - ...always, - failure: { - message: 'provider busy', - code: 'RATE_LIMIT', - status: 429, - providerRetryAfterMs: 25, - requestId: ProviderRequestId('request-1'), - }, - }) - }).not.toThrow() - - const invalidFailures: readonly [string, unknown, RegExp][] = [ - ['null', null, /failure must be an object/], - ['message-type', { message: 1, code: 'RATE_LIMIT' }, /failure\.message/], - ['message-empty', { message: '', code: 'RATE_LIMIT' }, /failure\.message/], - ['code-type', { message: 'failed', code: 1 }, /failure\.code/], - ['code-empty', { message: 'failed', code: '' }, /failure\.code/], - ['status-type', { message: 'failed', code: 'RATE_LIMIT', status: 429.5 }, /failure\.status/], - ['status-low', { message: 'failed', code: 'RATE_LIMIT', status: 99 }, /failure\.status/], - ['status-high', { message: 'failed', code: 'RATE_LIMIT', status: 600 }, /failure\.status/], - [ - 'retry-after-type', - { message: 'failed', code: 'RATE_LIMIT', providerRetryAfterMs: '25' }, - /failure\.providerRetryAfterMs/, - ], - [ - 'retry-after-zero', - { message: 'failed', code: 'RATE_LIMIT', providerRetryAfterMs: 0 }, - /failure\.providerRetryAfterMs/, - ], - ['request-id-type', { message: 'failed', code: 'RATE_LIMIT', requestId: 1 }, /failure\.requestId/], - ['request-id-empty', { message: 'failed', code: 'RATE_LIMIT', requestId: '' }, /failure\.requestId/], - ] - for (const [name, invalidFailure, message] of invalidFailures) { - const session = openStep(ctx, `retry-invariant-failure-${name}`) - expect(() => { - session.append('llm/retry', { - turn: 1, step: 1, ...always, failure: invalidFailure, - } as never) - }).toThrow(message) - } - }) - - it.each([ - ['empty-retry-id', { ...normal, retryId: RetryId('') }, /retryId must be a non-empty string/], - ['retry-zero', { ...normal, retry: 0 }, /positive safe integer/], - ['retry-fraction', { ...normal, retry: 1.5 }, /positive safe integer/], - ['max-zero', { ...normal, maxRetries: 0 }, /positive safe maxRetries/], - ['max-fraction', { ...normal, maxRetries: 1.5 }, /positive safe maxRetries/], - ['over-budget', { ...normal, retry: 3 }, /must not exceed/], - ['always-maximum', { ...always, maxRetries: 2 }, /always mode must omit maxRetries/], - ['unknown-mode', { ...always, mode: 'sometimes' }, /mode must be normal or always/], - ['empty-provider', { ...always, provider: '' }, /provider must be a non-empty string/], - ['empty-policy-key', { ...always, policyKey: '' }, /policyKey must be a non-empty string/], - ['delay-negative', { ...normal, delayMs: -1 }, /delayMs/], - ['delay-overflow', { ...normal, delayMs: MAX_TIMER_DELAY_MS + 1 }, /delayMs/], - ['delay-type', { ...normal, delayMs: '1' }, /delayMs/], - ])('rejects invalid retry data: %s', async (name, data, message) => { - const ctx = await setup() - const session = openStep(ctx, `retry-invariant-${name}`) - expect(() => { - session.append('llm/retry', { turn: 1, step: 1, ...data } as never) - }).toThrow(message) - }) - - it('rejects records outside the currently open turn and step', async () => { - const ctx = await setup() - const absent = ctx.sessions.create(SessionId('retry-invariant-no-turn')) - expect(() => { - absent.append('llm/retry', { turn: 1, step: 1, ...normal }) - }).toThrow(/inside an open turn/) - - const wrongTurn = openStep(ctx, 'retry-invariant-wrong-turn') - expect(() => { - wrongTurn.append('llm/retry', { turn: 2, step: 1, ...normal }) - }).toThrow(/open turn is 1/) - - const closedStep = openStep(ctx, 'retry-invariant-closed-step') - closedStep.append('step/end', { turn: 1, step: 1 }) - expect(() => { - closedStep.append('llm/retry', { turn: 1, step: 1, ...normal }) - }).toThrow(/inside an open step/) - - const noStep = ctx.sessions.create(SessionId('retry-invariant-no-step')) - noStep.append('turn/start', { turn: 1 }) - expect(() => { - noStep.append('llm/retry', { turn: 1, step: 1, ...normal }) - }).toThrow(/inside an open step/) - - const wrongStep = openStep(ctx, 'retry-invariant-wrong-step') - expect(() => { - wrongStep.append('llm/retry', { turn: 1, step: 2, ...normal }) - }).toThrow(/open step is 1\/1/) - - const closedTurn = openStep(ctx, 'retry-invariant-closed-turn') - closedTurn.append('step/end', { turn: 1, step: 1 }) - closedTurn.append('turn/end', { turn: 1, reason: { kind: 'aborted', reason: { kind: 'user' } }, - }) - expect(() => { - closedTurn.append('llm/retry', { turn: 1, step: 1, ...normal }) - }).toThrow(/inside an open turn/) - }) - - it('accepts successive retries in one step and rejects skipped numbering', async () => { - const ctx = await setup() - const session = openStep(ctx, 'retry-invariant-number-sequence') - session.append('llm/retry', { turn: 1, step: 1, ...normal }) - session.append('llm/retry', { turn: 1, step: 1, ...normal, retry: 2 }) - - expect(() => { - session.append('llm/retry', { turn: 1, step: 1, ...always, retry: 2 }) - }).toThrow(/must equal provider policy retry 1/) - }) - - it('binds retry numbering to the provider policy and resets it for a new step', async () => { - const ctx = await setup() - const mismatch = openStep(ctx, 'retry-invariant-numbering') - mismatch.append('llm/retry', { turn: 1, step: 1, ...normal }) - expect(() => { - mismatch.append('llm/retry', { turn: 1, step: 1, ...normal, retry: 1 }) - }).toThrow(/must equal provider policy retry 2/) - - const reset = openStep(ctx, 'retry-invariant-reset') - reset.append('llm/retry', { turn: 1, step: 1, ...normal }) - reset.append('step/end', { turn: 1, step: 1 }) - reset.append('step/start', { turn: 1, step: 2 }) - expect(() => { - reset.append('llm/retry', { - turn: 1, - step: 2, - ...normal, - retryId: RetryId('reset-step-2-retry-chain'), - }) - }).not.toThrow() - }) - - it('keeps one retry identity per provider-policy chain', async () => { - const ctx = await setup() - const changed = openStep(ctx, 'retry-invariant-changed-chain-id') - changed.append('llm/retry', { turn: 1, step: 1, ...normal }) - expect(() => changed.append('llm/retry', { - turn: 1, - step: 1, - ...normal, - retry: 2, - retryId: RetryId('changed-retry-chain'), - })).toThrow(/must preserve retryId/) - - const reused = openStep(ctx, 'retry-invariant-reused-chain-id') - reused.append('llm/retry', { turn: 1, step: 1, ...normal }) - expect(() => reused.append('llm/retry', { - turn: 1, - step: 1, - ...always, - retryId: normal.retryId, - })).toThrow(/already owned by another chain/) - }) - - it('validates retry-started correlation and uniqueness', async () => { - const ctx = await setup() - const empty = openStep(ctx, 'retry-started-empty-id') - expect(() => empty.append('llm/retry-started', { - retryId: RetryId(''), turn: 1, step: 1, retry: 1, - })).toThrow(/retryId must be a non-empty string/) - - const missing = openStep(ctx, 'retry-started-missing-schedule') - expect(() => missing.append('llm/retry-started', { - retryId: RetryId('missing-retry-chain'), turn: 1, step: 1, retry: 1, - })).toThrow(/pairs no prior scheduled attempt/) - - const mismatch = openStep(ctx, 'retry-started-location-mismatch') - mismatch.append('llm/retry', { turn: 1, step: 1, ...normal }) - expect(() => mismatch.append('llm/retry-started', { - retryId: normal.retryId, turn: 2, step: 1, retry: 1, - })).toThrow(/turn\/step must match/) - - const repeated = openStep(ctx, 'retry-started-repeated') - repeated.append('llm/retry', { turn: 1, step: 1, ...normal }) - repeated.append('llm/retry-started', { - retryId: normal.retryId, turn: 1, step: 1, retry: 1, - }) - expect(() => repeated.append('llm/retry-started', { - retryId: normal.retryId, turn: 1, step: 1, retry: 1, - })).toThrow(/repeats one scheduled attempt/) - }) - - it('starts a fresh retry chain after incomplete predecessor boundaries', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - - const missingEnd = ctx.sessions.create(SessionId('retry-invariant-missing-end')) - missingEnd.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'idle context' }], - source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - appendRetryTurn(missingEnd, 2) - - const nonFailureEnd = ctx.sessions.create(SessionId('retry-invariant-non-failure-end')) - nonFailureEnd.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - nonFailureEnd.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'idle context' }], - source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - appendRetryTurn(nonFailureEnd, 2) - - const missingStart = ctx.sessions.create(SessionId('retry-invariant-missing-start')) - missingStart.append('turn/end', { turn: 1, reason: { kind: 'error', error: failure }, - }) - appendRetryTurn(missingStart, 2) - - await ctx.plugin(InvariantRegistry) - await expect(ctx.plugin(RetryInvariant)).resolves.toBeDefined() - }) - - it('rejects a provider that does not match the failed request route', async () => { - const ctx = await setup() - const session = openStep(ctx, 'retry-invariant-provider') - expect(() => { - session.append('llm/retry', { turn: 1, step: 1, ...always, provider: 'other' }) - }).toThrow(/does not match the failed request provider mock/) - }) - - it('validates existing histories on late registration', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create(SessionId('retry-invariant-late')) - session.append('step/start', { turn: 1, step: 1 }) - session.append('llm/retry', { turn: 1, step: 1, ...normal }) - await ctx.plugin(InvariantRegistry) - await expect(ctx.plugin(RetryInvariant)).rejects.toThrow(/inside an open turn/) - }) - - it('accepts a scheduled and started attempt on late registration', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = openStep(ctx, 'retry-invariant-late-started') - session.append('llm/retry', { turn: 1, step: 1, ...normal }) - session.append('llm/retry-started', { - retryId: normal.retryId, turn: 1, step: 1, retry: 1, - }) - await ctx.plugin(InvariantRegistry) - await expect(ctx.plugin(RetryInvariant)).resolves.toBeDefined() - }) -}) diff --git a/packages/llm/llm-retry/tsconfig.json b/packages/llm/llm-retry/tsconfig.json index b60b470ea3..fe04c8564d 100644 --- a/packages/llm/llm-retry/tsconfig.json +++ b/packages/llm/llm-retry/tsconfig.json @@ -29,9 +29,6 @@ { "path": "../../core/agent" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../util/timeout" } diff --git a/packages/llm/llm-retry/tsdown.config.ts b/packages/llm/llm-retry/tsdown.config.ts index ab8dc26ee8..76c21c44d4 100644 --- a/packages/llm/llm-retry/tsdown.config.ts +++ b/packages/llm/llm-retry/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from 'tsdown' -/** Build the package root and invariant companion as independent bundles. */ +/** Build the package root as a self-contained bundle. */ export default defineConfig([ { entry: ['lib/types/index.js'], @@ -12,14 +12,4 @@ export default defineConfig([ dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, - }, ]) diff --git a/packages/llm/llm/package.json b/packages/llm/llm/package.json index 045e9f9d60..7beb1469a1 100644 --- a/packages/llm/llm/package.json +++ b/packages/llm/llm/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -51,7 +47,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts", "lib/typert.host.js", @@ -74,7 +69,6 @@ }, "devDependencies": { "@deepseek-ai/dsh-attachment": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/cordis": "workspace:~" } } diff --git a/packages/llm/llm/src/error.ts b/packages/llm/llm/src/error.ts index 430f031ea1..9be7cf9c00 100644 --- a/packages/llm/llm/src/error.ts +++ b/packages/llm/llm/src/error.ts @@ -6,7 +6,7 @@ /** * Base class for all harness errors. Carries a `code` (stable, programmatic — - * e.g. `NO_ADAPTER`, `INVALID_ARGS`, `INVARIANT`) distinct from the + * e.g. `NO_ADAPTER`, `INVALID_ARGS`) distinct from the * human-readable `message`, and supports `cause` chaining via the standard * `ErrorOptions`. `name` defaults to the subclass constructor name. */ diff --git a/packages/llm/llm/src/index.ts b/packages/llm/llm/src/index.ts index d67245d216..b1c9081bfe 100644 --- a/packages/llm/llm/src/index.ts +++ b/packages/llm/llm/src/index.ts @@ -355,28 +355,21 @@ export class LlmRuntime extends TypertRemoteService { private emitAdaptersUpdated(): void { // Cordis emit uses Array.map: one synchronous throw starves later // listeners. Registry notifications are non-vetoing, so contain each - // callback independently; INVARIANT-coded failures still surface. - let invariantFailure: unknown + // callback independently. for (const listener of this.ctx.events.dispatch('emit', ['llm/adapters-updated']) as Array<() => unknown>) { try { const returned = listener() if (returned != null && typeof (returned as PromiseLike).then === 'function') { // An emit listener may still be an async function; its rejection - // cannot reach the synchronous INVARIANT rethrow below, so it is - // contained here instead of becoming an unhandled rejection. + // is contained here instead of becoming an unhandled rejection. void Promise.resolve(returned as PromiseLike).then(undefined, (error: unknown) => { this.warnAdaptersListenerFailure(error) }) } } catch (error) { - if ((error as { code?: unknown } | null)?.code === 'INVARIANT') { - invariantFailure ??= error - continue - } this.warnAdaptersListenerFailure(error) } } - if (invariantFailure !== undefined) throw invariantFailure as Error } /** Contained-listener diagnostic shared by the sync and async failure paths. */ diff --git a/packages/llm/llm/src/invariant.ts b/packages/llm/llm/src/invariant.ts deleted file mode 100644 index 2106ca354d..0000000000 --- a/packages/llm/llm/src/invariant.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** Package-owned LLM stream-protocol invariants. @module @deepseek-ai/dsh-llm/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { ContentBlockType, StreamChunk } from './types.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-llm' - -/** Cordis companion plugin name. */ -export const name = 'llm-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Require one chunk index to be a non-negative safe integer. */ -function validateIndex(index: number, fail: InvariantFailure): void { - if (!Number.isSafeInteger(index) || index < 0) { - fail(`LLM stream block index must be a non-negative safe integer, got ${index}`) - } -} - -/** Require a delta to address an open block of its matching type. */ -function validateDelta( - open: ReadonlyMap, - index: number, - expected: ContentBlockType, - fail: InvariantFailure, -): void { - validateIndex(index, fail) - const actual = open.get(index) - if (actual !== expected) { - fail(`${expected} delta at index ${index} requires an open ${expected} block, got ${String(actual)}`) - } -} - -/** Wrap one provider stream and enforce its grammar as chunks are consumed. */ -async function* validateStream( - source: AsyncIterable, - fail: InvariantFailure, -): AsyncIterable { - const open = new Map() - let usageSeen = false - let finished = false - for await (const chunk of source) { - if (finished) fail(`LLM stream emitted ${chunk.type} after terminal finish`) - switch (chunk.type) { - case 'block-start': - validateIndex(chunk.index, fail) - if (open.has(chunk.index)) fail(`LLM stream repeated block-start index ${chunk.index}`) - open.set(chunk.index, chunk.blockType) - break - case 'text-delta': - validateDelta(open, chunk.index, 'text', fail) - break - case 'reasoning-delta': - validateDelta(open, chunk.index, 'reasoning', fail) - break - case 'tool-call-delta': - validateDelta(open, chunk.index, 'tool-call', fail) - break - case 'block-end': { - validateIndex(chunk.index, fail) - const blockType = open.get(chunk.index) - if (blockType === undefined) fail(`LLM stream block-end index ${chunk.index} has no open block`) - if (chunk.block.type !== blockType) { - fail(`LLM stream block-end index ${chunk.index} closes ${chunk.block.type}, expected ${blockType}`) - } - open.delete(chunk.index) - break - } - case 'usage': - if (usageSeen) fail('LLM stream emitted usage more than once') - usageSeen = true - break - case 'finish': - if (open.size > 0 && chunk.reason.kind !== 'error' && chunk.reason.kind !== 'aborted') { - fail(`LLM stream finished with ${open.size} open block(s)`) - } - finished = true - break - } - yield chunk - } - if (!finished) fail('LLM stream ended without a terminal finish chunk') -} - -/** Install validation around every provider stream. */ -const install: InvariantInstaller = (ctx, fail) => { - ctx.on('llm/stream', (_options, next) => validateStream(next(), fail), { global: true, prepend: true }) - ctx.on('llm/adapters-updated', () => { - // A disposer-time emit can outlive the service-store entry during whole- - // context teardown; only a live service promises a readable registry. - const llm = ctx.get('llm') - if (llm === undefined) return - for (const provider of llm.listProviders()) { - try { - llm.providerRetryPolicy(provider.id) - } catch { - // Reaching here IS the violation: the notification promised a readable - // registry, and only that broken promise can make the lookup throw. - fail(`llm/adapters-updated fired while provider "${provider.id}" has no readable registration`) - } - } - }, { global: true }) -} - -/** - * Register the LLM 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)) diff --git a/packages/llm/llm/tests/invariant.spec.ts b/packages/llm/llm/tests/invariant.spec.ts deleted file mode 100644 index fdb538f5dc..0000000000 --- a/packages/llm/llm/tests/invariant.spec.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import LlmRuntime, { ToolCallId, LlmAdapter } from '@deepseek-ai/dsh-llm' -import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' -import * as LlmInvariant from '@deepseek-ai/dsh-llm/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(LlmInvariant) - return ctx -} - -const options: GenerateOptions = { provider: 'mock', model: 'mock', messages: [] } - -async function* source(chunks: readonly StreamChunk[]): AsyncIterable { - yield* chunks -} - -async function consume(ctx: Context, chunks: readonly StreamChunk[]): Promise { - const stream = ctx.waterfall(ctx as never, 'llm/stream', options, () => source(chunks)) - const consumed: StreamChunk[] = [] - for await (const chunk of stream) consumed.push(chunk) - return consumed -} - -const finish: StreamChunk = { type: 'finish', reason: { kind: 'stop' } } - -describe('LLM stream invariants', () => { - it('accepts a complete interleaved stream grammar', async () => { - const ctx = await setup() - const chunks: StreamChunk[] = [ - { type: 'block-start', index: 0, blockType: 'text' }, - { type: 'text-delta', index: 0, text: 'a' }, - { type: 'block-start', index: 1, blockType: 'reasoning' }, - { type: 'reasoning-delta', index: 1, text: 'b' }, - { type: 'block-end', index: 1, block: { type: 'reasoning', text: 'b' } }, - { type: 'block-end', index: 0, block: { type: 'text', text: 'a' } }, - { type: 'block-start', index: 2, blockType: 'tool-call' }, - { type: 'tool-call-delta', index: 2, id: ToolCallId('c1'), name: 'echo', argumentsDelta: '{}' }, - { type: 'block-end', index: 2, block: { type: 'tool-call', id: ToolCallId('c1'), name: 'echo', arguments: '{}' } }, - { type: 'usage', usage: { inputTokens: 1, outputTokens: 1 } }, - finish, - ] - await expect(consume(ctx, chunks)).resolves.toEqual(chunks) - }) - - it.each([ - [[{ type: 'block-start', index: -1, blockType: 'text' }, finish], /non-negative safe integer/], - [[ - { type: 'block-start', index: 0, blockType: 'text' }, - { type: 'block-start', index: 0, blockType: 'text' }, - ], /repeated block-start/], - [[{ type: 'text-delta', index: 0, text: 'x' }], /requires an open text block/], - [[ - { type: 'block-start', index: 0, blockType: 'reasoning' }, - { type: 'text-delta', index: 0, text: 'x' }, - ], /got reasoning/], - [[{ type: 'block-end', index: 0, block: { type: 'text', text: '' } }], /has no open block/], - [[ - { type: 'block-start', index: 0, blockType: 'text' }, - { type: 'block-end', index: 0, block: { type: 'reasoning', text: '' } }, - ], /closes reasoning, expected text/], - [[ - { type: 'usage', usage: { inputTokens: 1, outputTokens: 1 } }, - { type: 'usage', usage: { inputTokens: 1, outputTokens: 1 } }, - ], /usage more than once/], - [[{ type: 'block-start', index: 0, blockType: 'text' }, finish], /finished with 1 open block/], - [[finish, { type: 'usage', usage: { inputTokens: 1, outputTokens: 1 } }], /usage after terminal finish/], - [[], /ended without a terminal finish/], - ] as Array<[StreamChunk[], RegExp]>)('rejects malformed stream %#', async (chunks, message) => { - const ctx = await setup() - await expect(consume(ctx, chunks)).rejects.toThrow(message) - }) - - it('preserves provider exceptions without inventing a missing-finish failure', async () => { - const ctx = await setup() - const stream = ctx.waterfall(ctx as never, 'llm/stream', options, async function* () { - throw new Error('provider failed') - }) - await expect((async () => { - for await (const _chunk of stream) { /* consume */ } - })()).rejects.toThrow('provider failed') - }) -}) - -describe('adapters-updated invariants', () => { - class NoopAdapter extends LlmAdapter { - - async * stream(_options: GenerateOptions): AsyncIterable { - throw new Error('not exercised') - } - } - - it('accepts a coherent registry at every topology notification', async () => { - const ctx = await setup() - await ctx.plugin(LlmRuntime) - const dispose = ctx.llm.registerAdapter(['coherent'], new NoopAdapter()) - ctx.llm.registerConfigurableProviders([ - { provider: 'dormant', displayName: 'Dormant', settingsNs: 'ns', settingsPath: [] }, - ]) - dispose() - expect(ctx.llm.listProviders()).toEqual([]) - }) - - it('skips the check when the service store has no llm entry', async () => { - const ctx = await setup() - expect(() => { ctx.emit('llm/adapters-updated') }).not.toThrow() - }) - - it('reports a notification whose registry cannot be re-read', async () => { - class BrokenLlm extends LlmRuntime { - override providerRetryPolicy(_provider: string): never { - throw new Error('registration vanished') - } - } - const ctx = await setup() - await ctx.plugin(BrokenLlm) - expect(() => ctx.llm.registerAdapter(['ghost'], new NoopAdapter())) - .toThrow(/no readable registration/) - }) -}) diff --git a/packages/llm/llm/tests/topology.spec.ts b/packages/llm/llm/tests/topology.spec.ts index ad30380642..b785d811b6 100644 --- a/packages/llm/llm/tests/topology.spec.ts +++ b/packages/llm/llm/tests/topology.spec.ts @@ -89,16 +89,6 @@ describe('llm/adapters-updated', () => { expect(observed).toEqual([['a']]) }) - it('rethrows the first INVARIANT-coded listener failure after notifying the rest', async () => { - const ctx = await setup() - const later = vi.fn() - ctx.on('llm/adapters-updated', () => { - throw Object.assign(new Error('registry incoherent'), { code: 'INVARIANT' }) - }) - ctx.on('llm/adapters-updated', later) - expect(() => ctx.llm.registerAdapter(['a'], new NoopAdapter())).toThrow('registry incoherent') - expect(later).toHaveBeenCalledTimes(1) - }) }) describe('configurable-provider directory', () => { diff --git a/packages/llm/llm/tsconfig.json b/packages/llm/llm/tsconfig.json index a244fa845e..8af7fccf61 100644 --- a/packages/llm/llm/tsconfig.json +++ b/packages/llm/llm/tsconfig.json @@ -14,15 +14,15 @@ { "path": "../../../vendor/cordis" }, + { + "path": "../../../vendor/schemastery" + }, { "path": "../../util/brand" }, { "path": "../../attachment/attachment" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../util/timeout" }, diff --git a/packages/llm/plugin-package-inventory-deepseek/README.i18n.yaml b/packages/llm/plugin-package-inventory-deepseek/README.i18n.yaml index fbe6d14eb4..526cee2b50 100644 --- a/packages/llm/plugin-package-inventory-deepseek/README.i18n.yaml +++ b/packages/llm/plugin-package-inventory-deepseek/README.i18n.yaml @@ -39,5 +39,5 @@ en: d46a9293ac3dcaa2 zh: 53e86d9d54dd3f8a /deepseek-ai-dsh-plugin-package-inventory-deepseek/known-limitations-and-deferred-work/dev-note: - en: 39e2b90768772390 - zh: d49e34ab1d02adbb + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/llm/plugin-package-inventory-deepseek/README.md b/packages/llm/plugin-package-inventory-deepseek/README.md index 72af2b9e62..f4478480fa 100644 --- a/packages/llm/plugin-package-inventory-deepseek/README.md +++ b/packages/llm/plugin-package-inventory-deepseek/README.md @@ -76,5 +76,3 @@ None; package lifecycle changes do not alter the model-visible prefix. None. - -**Runtime invariant:** No companion is published. Each request reads authoritative Loader fiber state and package manifests directly; the plugin retains no independently mutable inventory. diff --git a/packages/llm/plugin-package-inventory-deepseek/README.zh.md b/packages/llm/plugin-package-inventory-deepseek/README.zh.md index e1e5189239..2c44a427a3 100644 --- a/packages/llm/plugin-package-inventory-deepseek/README.zh.md +++ b/packages/llm/plugin-package-inventory-deepseek/README.zh.md @@ -76,5 +76,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。每次请求直接读取权威 Loader fiber 状态与 package manifest,插件不保留独立可变 inventory。 diff --git a/packages/llm/token-meter/README.i18n.yaml b/packages/llm/token-meter/README.i18n.yaml index 161e2e3988..8e50701bef 100644 --- a/packages/llm/token-meter/README.i18n.yaml +++ b/packages/llm/token-meter/README.i18n.yaml @@ -60,5 +60,5 @@ en: a8c57e6f8c5009a2 zh: 5975018d14bfbdbb /deepseek-ai-dsh-token-meter/known-limitations-and-deferred-work/dev-note: - en: ce725ee5fe2ebaa3 - zh: 0cd07dddbf4011c0 + en: 50b57427bfda172e + zh: 48d666dcc8c7290f diff --git a/packages/llm/token-meter/README.md b/packages/llm/token-meter/README.md index e6d957bd14..f329fa1c56 100644 --- a/packages/llm/token-meter/README.md +++ b/packages/llm/token-meter/README.md @@ -156,4 +156,4 @@ This Dev Note is non-authoritative working context: notes for maintainers and op -**Runtime invariant:** No companion is published. Usage folds replace samples within each attempt; totals need not be monotone. Composition and measurement share the positional replacement planner and fixed estimator, so their heuristic surface totals agree by construction rather than through independent mutable observations. Route-priced totals deliberately differ. +Usage folds replace samples within each attempt; totals need not be monotone. Composition and measurement share the positional replacement planner and fixed estimator, so their heuristic surface totals agree by construction. Route-priced totals deliberately differ. diff --git a/packages/llm/token-meter/README.zh.md b/packages/llm/token-meter/README.zh.md index 526be7b2e3..0fbbbdc2be 100644 --- a/packages/llm/token-meter/README.zh.md +++ b/packages/llm/token-meter/README.zh.md @@ -156,4 +156,4 @@ const price = ctx.tokenMeter.estimateMessage(message) -**运行时不变式:** 不发布伴生入口。用量 fold 在每次尝试内替换样本,总量不必单调。构成和测量共享位置替换规划器与固定估算器,因此启发式 surface 总量按构造一致,而非需要比较的独立可变观测。路由定价总量有意与之不同。 +用量 fold 在每次尝试内替换样本,总量不必单调。构成和测量共享位置替换规划器与固定估算器,因此启发式 surface 总量按构造一致。路由定价总量有意与之不同。 diff --git a/packages/lsp/lsp-stdio/README.i18n.yaml b/packages/lsp/lsp-stdio/README.i18n.yaml index 68cd4e3c5c..1d00d484cc 100644 --- a/packages/lsp/lsp-stdio/README.i18n.yaml +++ b/packages/lsp/lsp-stdio/README.i18n.yaml @@ -36,8 +36,8 @@ en: 7e5e63f993a287f2 zh: a5a01b22551e5d46 /deepseek-ai-dsh-lsp-stdio/understand-the-implementation/source-map: - en: c1eb566e6aa64d09 - zh: 6fa10484e329e570 + en: 601baa446f830c66 + zh: 8aaebaaae7081c82 /deepseek-ai-dsh-lsp-stdio/understand-the-implementation/protocol-behavior: en: 0f9bac88ad9448b8 zh: f5d3a2e61a596003 diff --git a/packages/lsp/lsp-stdio/README.md b/packages/lsp/lsp-stdio/README.md index 5f0f19255e..ddcfb78292 100644 --- a/packages/lsp/lsp-stdio/README.md +++ b/packages/lsp/lsp-stdio/README.md @@ -106,7 +106,6 @@ This section explains the design decisions behind the provider and where the cod | [`src/protocol.ts`](src/protocol.ts) | Wire-type subset: capabilities, locations, hover, text-document synchronization | | [`src/translate.ts`](src/translate.ts) | Capability checks, UTF-16 negotiation, `Location`/`LocationLink`/hover normalization | | [`src/abort.ts`](src/abort.ts) | Cancellation helpers fusing caller and disposal signals | -| — | No runtime invariant companion is published; process pools and per-workspace queues are private implementation state, and this provider publishes no independent lifecycle event stream or enumerable snapshot. | ### Protocol behavior diff --git a/packages/lsp/lsp-stdio/README.zh.md b/packages/lsp/lsp-stdio/README.zh.md index 5380f2cec7..aef549f4bf 100644 --- a/packages/lsp/lsp-stdio/README.zh.md +++ b/packages/lsp/lsp-stdio/README.zh.md @@ -106,7 +106,6 @@ kind: "package-reference" | [`src/protocol.ts`](src/protocol.ts) | 协议类型子集:能力、位置、悬停、文本文档同步 | | [`src/translate.ts`](src/translate.ts) | 能力检查、UTF-16 协商、`Location`/`LocationLink`/hover 规范化 | | [`src/abort.ts`](src/abort.ts) | 融合调用方与释放信号的取消辅助 | -| — | 不发布运行时不变式伴生入口;进程池与队列是私有状态,本提供方也不发布独立的生命周期事件流或可枚举快照。 | ### 协议行为 diff --git a/packages/lsp/lsp/README.i18n.yaml b/packages/lsp/lsp/README.i18n.yaml index cd79e675c4..885290ba8d 100644 --- a/packages/lsp/lsp/README.i18n.yaml +++ b/packages/lsp/lsp/README.i18n.yaml @@ -36,8 +36,8 @@ en: cde960c2cecb7265 zh: b6b2b355a68bf2e7 /deepseek-ai-dsh-lsp/understand-the-implementation/source-map: - en: 25af1e58f8ab8854 - zh: 90aec5ee2441bd96 + en: 052a99d148619000 + zh: 76624984e9bdbdd2 /deepseek-ai-dsh-lsp/understand-the-implementation/registration-and-selection-lifecycle: en: 549e97f436b1570c zh: df0436c58dd31175 diff --git a/packages/lsp/lsp/README.md b/packages/lsp/lsp/README.md index 2dcd3c39ab..88eb022a9f 100644 --- a/packages/lsp/lsp/README.md +++ b/packages/lsp/lsp/README.md @@ -87,7 +87,6 @@ This section explains the design decisions behind the seam and where the code re | [`src/index.ts`](src/index.ts) | Plugin entry: `Lsp` service, `registerProvider`/`query`, `finalExtension`, `LspError` codes | | [`src/types.ts`](src/types.ts) | Seam vocabulary: request, result, provider, and service contracts | | [`src/brand.ts`](src/brand.ts) | `LspProviderId` branded-id type and factory | -| — | No runtime invariant companion is published; provider ids and extension routes are private, atomically updated state; the seam exposes neither an enumerable snapshot nor lifecycle events to compare independently. | ### Registration and selection lifecycle diff --git a/packages/lsp/lsp/README.zh.md b/packages/lsp/lsp/README.zh.md index 57d4660f14..2d528d180f 100644 --- a/packages/lsp/lsp/README.zh.md +++ b/packages/lsp/lsp/README.zh.md @@ -87,7 +87,6 @@ seam 需要提供方与消费方才能发挥作用。最小组合挂载服务、 | [`src/index.ts`](src/index.ts) | 插件入口:`Lsp` 服务、`registerProvider`/`query`、`finalExtension`、`LspError` code | | [`src/types.ts`](src/types.ts) | seam 词汇:请求、结果、提供方与服务约定 | | [`src/brand.ts`](src/brand.ts) | `LspProviderId` 品牌化 id 类型与工厂 | -| — | 不发布运行时不变式伴生入口;提供方 id 与扩展名路由属于以原子方式更新的私有状态;该 seam 既不公开可枚举快照,也不公开可供独立比较的生命周期事件。 | ### 注册与选择生命周期 diff --git a/packages/lsp/tool-lsp/README.i18n.yaml b/packages/lsp/tool-lsp/README.i18n.yaml index 68d60a7492..bef5ef2a8f 100644 --- a/packages/lsp/tool-lsp/README.i18n.yaml +++ b/packages/lsp/tool-lsp/README.i18n.yaml @@ -36,8 +36,8 @@ en: 9e9f50894b853946 zh: 7137a1894339ad5f /deepseek-ai-dsh-tool-lsp/understand-the-implementation/source-map: - en: 6b14444729010597 - zh: de5b7453bb7fd3be + en: 21e86e54d1961cb7 + zh: d04271944d9b4340 /deepseek-ai-dsh-tool-lsp/further-exploration: en: f4965ba5ea4f8ee3 zh: b85ee14edf9fdef0 diff --git a/packages/lsp/tool-lsp/README.md b/packages/lsp/tool-lsp/README.md index 2a5483aabe..c9503de87a 100644 --- a/packages/lsp/tool-lsp/README.md +++ b/packages/lsp/tool-lsp/README.md @@ -75,7 +75,6 @@ This section explains the design decisions behind the tool and where the code re | [`src/index.ts`](src/index.ts) | Plugin entry: config schema, tool registration, system-prompt section, execution | | [`src/render.ts`](src/render.ts) | Pure formatting, coordinate conversion, URI resolution, result caps, UI presentation | | [`src/session-cwd.ts`](src/session-cwd.ts) | Workspace root from the session `header.cwd` | -| — | No runtime invariant companion is published; this stateless adapter contributes one tool and prompt section, while query lifecycle and result relations remain owned by the tool and LSP seams it composes. | diff --git a/packages/lsp/tool-lsp/README.zh.md b/packages/lsp/tool-lsp/README.zh.md index 8a5b91e049..7b0c36283e 100644 --- a/packages/lsp/tool-lsp/README.zh.md +++ b/packages/lsp/tool-lsp/README.zh.md @@ -75,7 +75,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:配置 schema、工具注册、系统提示词区段、执行 | | [`src/render.ts`](src/render.ts) | 纯格式化、坐标转换、URI 解析、结果上限、UI 呈现 | | [`src/session-cwd.ts`](src/session-cwd.ts) | 从会话 `header.cwd` 取得工作区根目录 | -| — | 不发布运行时不变式伴生入口;该无状态适配器提供一个工具和一个提示词区段,而查询生命周期与结果关系仍由它所组合的工具 seam 和 LSP seam 负责。 | diff --git a/packages/mcp/mcp-client/README.i18n.yaml b/packages/mcp/mcp-client/README.i18n.yaml index 9e74c74c4b..ecae5d61fb 100644 --- a/packages/mcp/mcp-client/README.i18n.yaml +++ b/packages/mcp/mcp-client/README.i18n.yaml @@ -36,8 +36,8 @@ en: 4e213d68082b07f0 zh: 7617949ba46a3b63 /deepseek-ai-dsh-mcp-client/understand-the-implementation/source-map: - en: ab2bbaa701f24c2c - zh: c6db79438239388e + en: f134d7502d573290 + zh: 5dd8ad502f8db776 /deepseek-ai-dsh-mcp-client/understand-the-implementation/lifecycle-and-sync: en: ae9029b48b2a2e4e zh: 048f08361b799667 diff --git a/packages/mcp/mcp-client/README.md b/packages/mcp/mcp-client/README.md index d9f81a79b8..d6f238fe63 100644 --- a/packages/mcp/mcp-client/README.md +++ b/packages/mcp/mcp-client/README.md @@ -119,7 +119,6 @@ This section explains the design decisions behind the bridge and points at the c | [`src/server-context.ts`](src/server-context.ts) | Resource-provider registration and literal server instructions | | [`src/tools.ts`](src/tools.ts) | Tool bridge: discovery, naming, registration swap, execution, image projection | | [`src/transport.ts`](src/transport.ts) | Transport factory: stdio spawn with scrubbed env, Streamable HTTP | -| — | No runtime invariant companion is published; MCP generations contribute through the tool registry, but the bridge exposes no independent server-to-tool snapshot after an asynchronous resync. | The exported `createMcpToolDefinition(ctx, options)` adapts an upstream tool schema and raw-result callback to the same canonical values, errors, and durable image projection. Each callback receives the exact `ToolExecution`, including its Agent and cancellation signal; SDK spec-type validation checks its result before projection. Callers own registration, cancellation deadlines, and provider teardown. The native Cua Driver provider uses this adapter without opening an MCP transport. diff --git a/packages/mcp/mcp-client/README.zh.md b/packages/mcp/mcp-client/README.zh.md index d6f3e9337c..505381d4e0 100644 --- a/packages/mcp/mcp-client/README.zh.md +++ b/packages/mcp/mcp-client/README.zh.md @@ -119,7 +119,6 @@ kind: "package-reference" | [`src/server-context.ts`](src/server-context.ts) | 资源提供方注册与字面服务器指令 | | [`src/tools.ts`](src/tools.ts) | 工具桥接:发现、命名、注册交换、执行、图片投影 | | [`src/transport.ts`](src/transport.ts) | 传输工厂:带清洗环境的 stdio spawn、Streamable HTTP | -| — | 不发布运行时不变式伴生入口;MCP 世代会通过工具注册表发挥作用,但桥接在异步重新同步后不提供独立的服务器工具映射快照。 | 导出的 `createMcpToolDefinition(ctx, options)` 将上游工具 schema 和原始结果回调适配到相同的规范值、错误和持久化图像投影。每次回调都收到原样的 `ToolExecution`,包括其 Agent 和取消信号;SDK 的规范类型校验会在投影前检查返回结果。调用方负责注册、取消截止时间和提供方卸载。原生 Cua Driver 提供方使用此适配函数,无需打开 MCP 传输。 diff --git a/packages/mcp/mcp-resources/README.i18n.yaml b/packages/mcp/mcp-resources/README.i18n.yaml index 180978c72c..c1bfa9eb91 100644 --- a/packages/mcp/mcp-resources/README.i18n.yaml +++ b/packages/mcp/mcp-resources/README.i18n.yaml @@ -24,8 +24,8 @@ en: f7fb6597acd3c5dc zh: 9c59219fb4ab00d9 /deepseek-ai-dsh-mcp-resources/understand-the-implementation: - en: e0e93efbb88e6047 - zh: b941463944659422 + en: ab308dbe70e82838 + zh: 1eb73edc14c76516 /deepseek-ai-dsh-mcp-resources/further-exploration: en: 66e212708f2fa883 zh: da8e26a414e86d55 diff --git a/packages/mcp/mcp-resources/README.md b/packages/mcp/mcp-resources/README.md index 050cce658f..593a308de2 100644 --- a/packages/mcp/mcp-resources/README.md +++ b/packages/mcp/mcp-resources/README.md @@ -64,8 +64,6 @@ Canonical results retain the complete JSON for programmatic callers. The pure te | [`src/tools.ts`](src/tools.ts) | Shared resource operations and argument schemas | | [`src/render.ts`](src/render.ts) | Attributed text projection without inline binary payloads | -No runtime invariant companion is published: tools, prompt names, and dispatch derive from the same effect-owned provider registrations. They supply no independent observation to reconcile; registry-effect checks are not runtime invariants. - ----- diff --git a/packages/mcp/mcp-resources/README.zh.md b/packages/mcp/mcp-resources/README.zh.md index 693c58face..2ed1614551 100644 --- a/packages/mcp/mcp-resources/README.zh.md +++ b/packages/mcp/mcp-resources/README.zh.md @@ -64,8 +64,6 @@ kind: "package-reference" | [`src/tools.ts`](src/tools.ts) | 共享资源操作与参数 schema | | [`src/render.ts`](src/render.ts) | 带归属信息且不内联二进制载荷的文本投影 | -不发布 `./invariant` 配套入口:工具、提示词名称与派发均源于同一组由 effect 拥有的提供方注册。它们没有可供核对的独立观测值;注册表 effect 检查不属于运行时不变式。 - ----- diff --git a/packages/plan/plan-mode/README.i18n.yaml b/packages/plan/plan-mode/README.i18n.yaml index 38990a3225..5aa889ea65 100644 --- a/packages/plan/plan-mode/README.i18n.yaml +++ b/packages/plan/plan-mode/README.i18n.yaml @@ -51,8 +51,8 @@ en: 74cd6bdf50af51f5 zh: 67609cf7202ececb /deepseek-ai-dsh-plan-mode/understand-the-implementation/source-map: - en: 4ee4cfb5fceccdf1 - zh: 12035b72e1996a55 + en: fa97e858495a8105 + zh: 8be1fbd7e420ff8a /deepseek-ai-dsh-plan-mode/further-exploration: en: b6f3ce071f1b7e9b zh: 84b7fe88e09866d8 diff --git a/packages/plan/plan-mode/README.md b/packages/plan/plan-mode/README.md index 642caf0c6d..0f0eb33cb4 100644 --- a/packages/plan/plan-mode/README.md +++ b/packages/plan/plan-mode/README.md @@ -103,7 +103,6 @@ When `ctx.sessionProjections` is composed, the package registers the `plan` unit | [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, the `ctx.planMode` service, `plan:policy` section, `/plan` command, `exit_plan_mode` tool | | [`src/types.ts`](src/types.ts) | The `plan` projection-key declaration and `PlanProjection` wire value | | [`src/client.ts`](src/client.ts) | Client-namespace re-export of the types outlet | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: validates the `plan/mode` payload shape | The review intent carries the originating tool-call id, so the Web client can reopen the same submitted plan after the review closes. The complete Markdown remains in the existing native call or PTC dispatch log. diff --git a/packages/plan/plan-mode/README.zh.md b/packages/plan/plan-mode/README.zh.md index 4165d5b925..a94291a96b 100644 --- a/packages/plan/plan-mode/README.zh.md +++ b/packages/plan/plan-mode/README.zh.md @@ -103,7 +103,6 @@ agent 完成计划后,会以 markdown 形式、从标题开头书写计划并 | [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、`ctx.planMode` 服务、`plan:policy` 段落、`/plan` 命令、`exit_plan_mode` 工具 | | [`src/types.ts`](src/types.ts) | `plan` 投影 key 声明与 `PlanProjection` 协议值 | | [`src/client.ts`](src/client.ts) | types 出口的客户端命名空间再导出 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:校验 `plan/mode` 载荷结构 | 审批意图携带发起工具调用的标识,让 Web 客户端在审批关闭后重新打开同一份计划。完整 Markdown 保留在已有的原生调用或 PTC dispatch 日志中。 diff --git a/packages/plan/plan-mode/package.json b/packages/plan/plan-mode/package.json index 4993239e90..99847d3e0c 100644 --- a/packages/plan/plan-mode/package.json +++ b/packages/plan/plan-mode/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -35,7 +31,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -43,7 +38,6 @@ "peerDependencies": { "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-commands": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-projection": "workspace:*", @@ -66,7 +60,6 @@ "@deepseek-ai/dsh-agent-loop": "workspace:*", "@deepseek-ai/dsh-ptc-runtime": "workspace:*", "@deepseek-ai/dsh-commands": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-projection": "workspace:*", diff --git a/packages/plan/plan-mode/src/invariant.ts b/packages/plan/plan-mode/src/invariant.ts deleted file mode 100644 index 60389c8879..0000000000 --- a/packages/plan/plan-mode/src/invariant.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** Package-owned durable plan-mode invariants. @module @deepseek-ai/dsh-plan-mode/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' - -const PACKAGE_NAME = '@deepseek-ai/dsh-plan-mode' - -/** Cordis companion plugin name. */ -export const name = 'plan-mode-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** - * Validate one `plan/mode` event before it reaches the durable log. - * `plan/mode` is a standalone whole-value event: an idle selection commits - * between turns and a mid-turn selection commits at the step boundary, so - * no turn-enclosure relation exists — only the payload shape is checkable. - */ -function validateEvent(event: SessionEvent, fail: InvariantFailure): void { - if (event.type !== 'plan/mode') return - const active = (event.data as { active?: unknown }).active - if (typeof active !== 'boolean') { - fail(`plan/mode carries invalid active state ${JSON.stringify(active)}; expected a boolean`) - } -} - -/** Install validation for loaded and newly appended plan-mode state. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const seed = (session: Session): void => { - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) validateEvent(event, fail) - } - for (const session of ctx.sessions.list()) seed(session) - ctx.on('session/created', (session) => { seed(session) }, { global: true }) - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [, event] = args as [Session, SessionEvent] - validateEvent(event, fail) - }, { global: true }) -}, { inject: ['sessions'] }) - -/** - * Register the plan-mode 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)) diff --git a/packages/plan/plan-mode/tests/invariant.spec.ts b/packages/plan/plan-mode/tests/invariant.spec.ts deleted file mode 100644 index d44cadc67a..0000000000 --- a/packages/plan/plan-mode/tests/invariant.spec.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import SessionStore, { Session, SessionId, SessionSeq, type SessionEvent } from '@deepseek-ai/dsh-session' -import * as PlanModeInvariant from '@deepseek-ai/dsh-plan-mode/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(PlanModeInvariant) - return ctx -} - -function event(active: unknown): SessionEvent { - return { type: 'plan/mode', seq: SessionSeq(0), time: 0, data: { active } } as SessionEvent -} - -function emitTurnStart(ctx: Context, session: Session): void { - ctx.emit('session/event', session, { - type: 'turn/start', seq: SessionSeq(0), time: 0, - data: { turn: 1 }, - }) -} - -describe('plan-mode stream invariants', () => { - it('accepts either boolean state', async () => { - const ctx = await setup() - const session = Session.create(SessionId('plan-state')) - emitTurnStart(ctx, session) - expect(() => { ctx.emit('session/event', session, event(true)) }).not.toThrow() - expect(() => { ctx.emit('session/event', session, event(false)) }).not.toThrow() - ctx.emit('session/event', session, { - type: 'turn/end', seq: SessionSeq(3), time: 3, data: { turn: 1, reason: { kind: 'completed' } }, - }) - }) - - it.each([42, 'plan', undefined])('rejects invalid durable plan state %j', async (active) => { - const ctx = await setup() - const session = Session.create(SessionId(`invalid-${String(active)}`)) - emitTurnStart(ctx, session) - expect(() => { ctx.emit('session/event', session, event(active)) }) - .toThrow(/expected a boolean/) - }) - - it('accepts standalone plan state between turns (the idle immediate commit)', async () => { - const ctx = await setup() - expect(() => ctx.sessions.create().append('plan/mode', { active: true })) - .not.toThrow() - }) - - it('ignores unrelated dispatches and session events', async () => { - const ctx = await setup() - const session = Session.create(SessionId('unrelated')) - expect(() => { - ctx.emit('tools/change') - ctx.emit('session/event', session, { - type: 'turn/start', seq: SessionSeq(0), time: 0, data: { turn: 1 }, - }) - }).not.toThrow() - }) - - it('rejects invalid existing state on late registration', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('plan/mode', { active: 'plan' as unknown as boolean }) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.plugin(InvariantRegistry, { enabled: true }) - - await expect(ctx.plugin(PlanModeInvariant).then(() => undefined)).rejects.toThrow(/expected a boolean/) - }) - - it('replays enclosed existing plan state through its closing boundary', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('plan/mode', { active: true }) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await ctx.plugin(InvariantRegistry, { enabled: true }) - - await expect(ctx.plugin(PlanModeInvariant).then(() => undefined)).resolves.toBeUndefined() - }) - - it('accepts standalone existing plan state on late registration', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - ctx.sessions.create().append('plan/mode', { active: true }) - await ctx.plugin(InvariantRegistry, { enabled: true }) - - await expect(ctx.plugin(PlanModeInvariant).then(() => undefined)).resolves.toBeUndefined() - }) -}) diff --git a/packages/plan/plan-mode/tsconfig.json b/packages/plan/plan-mode/tsconfig.json index 2886c4da12..f187853290 100644 --- a/packages/plan/plan-mode/tsconfig.json +++ b/packages/plan/plan-mode/tsconfig.json @@ -34,9 +34,6 @@ }, { "path": "../../session/session-projection" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/preset/agent-preset-registry/README.i18n.yaml b/packages/preset/agent-preset-registry/README.i18n.yaml index 411d7b4a02..fbcf9f79ae 100644 --- a/packages/preset/agent-preset-registry/README.i18n.yaml +++ b/packages/preset/agent-preset-registry/README.i18n.yaml @@ -45,5 +45,5 @@ en: 8a32adf5c133fca3 zh: a066085a05e9b103 /deepseek-ai-dsh-agent-preset-registry/known-limitations-and-deferred-work/dev-note: - en: b84837a00b08ed7b - zh: 9de7aee8b9357fe9 + en: e67f633dc6fe7773 + zh: 8ec3e73528ddb1a6 diff --git a/packages/preset/agent-preset-registry/README.md b/packages/preset/agent-preset-registry/README.md index 1db29f2383..e205d0f2b5 100644 --- a/packages/preset/agent-preset-registry/README.md +++ b/packages/preset/agent-preset-registry/README.md @@ -103,5 +103,3 @@ Existing Agents retain their plugins and prompts. New Agents build their prefixe None. - -**Runtime invariant:** The companion checks services leaked globally after activation and Agents addressing a model without joining a configured preset. diff --git a/packages/preset/agent-preset-registry/README.zh.md b/packages/preset/agent-preset-registry/README.zh.md index 3549afc916..26aba6703b 100644 --- a/packages/preset/agent-preset-registry/README.zh.md +++ b/packages/preset/agent-preset-registry/README.zh.md @@ -103,5 +103,3 @@ Web 内置定义来自 `dsh-web-app` bundle。定义使用普通插件行;注 无。 - -**运行时不变量:** companion 检查挂载后泄漏到全局的服务,以及模型请求前尚未绑定 preset 的 Agent。 diff --git a/packages/preset/agent-preset-registry/package.json b/packages/preset/agent-preset-registry/package.json index 3a6155037d..b7606daa06 100644 --- a/packages/preset/agent-preset-registry/package.json +++ b/packages/preset/agent-preset-registry/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -43,7 +39,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts", "lib/typert.host.js", @@ -56,7 +51,6 @@ "@deepseek-ai/cordis-plugin-include": "workspace:~", "@deepseek-ai/cordis-plugin-loader": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-projection": "workspace:*", @@ -79,7 +73,6 @@ "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-agent-loop": "workspace:*", "@deepseek-ai/dsh-atomic-write": "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/preset/agent-preset-registry/src/invariant.ts b/packages/preset/agent-preset-registry/src/invariant.ts deleted file mode 100644 index d4eb9c2b1b..0000000000 --- a/packages/preset/agent-preset-registry/src/invariant.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-agent-preset-registry`. - * @module @deepseek-ai/dsh-agent-preset-registry/invariant - */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' -// Type-only: resolves the `system-prompt/assemble` waterfall this companion -// joins, and the `agent` field `dsh-agent` merges into its context. -import type {} from '@deepseek-ai/dsh-system-prompt' -import type {} from '@deepseek-ai/dsh-agent' -// Imported through the package name, not `./mount.ts`: a module shared between -// the two build entry points becomes a third chunk that the published `files` -// list does not carry, which `verify-built-package-invariants` rejects. -import { leakedServices, livePresetMounts } from '@deepseek-ai/dsh-agent-preset-registry' - -const PACKAGE_NAME = '@deepseek-ai/dsh-agent-preset-registry' - -/** Cordis companion plugin name. */ -export const name = 'agent-presets-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** - * Assert that no installed preset composition reaches the root service realm, - * and that a deployment configuring a roster composes every agent from it. - * - * `mountPreset` proves the first once, when the subtree settles. A row that - * publishes later — from a timer, or an asynchronous continuation after its - * plugin returned — would escape that one-shot audit, so re-check every live - * mount whenever a service registration changes. - */ -const install: InvariantInstaller = (ctx, fail) => { - ctx.on('internal/service', function (this: Context, name) { - for (const mount of livePresetMounts()) { - const leaked = leakedServices(ctx, mount.fiber) - if (leaked.length === 0) continue - fail( - `preset "${mount.presetId}" published process-global service(s) [${leaked.join(', ')}] ` - + `after its mount was audited (observed while notifying "${name}") — ` - + 'a preset service must sit behind an `isolate` realm or move to the host composition', - ) - } - }, { global: true }) - - // Bare Agents are valid until model assembly. Cold scoped reads carry no - // Agent and do not require a session binding. - ctx.on('system-prompt/assemble', (_assembly, context, next) => { - const presets = ctx.get('agentPresets') - const agent = context.agent - if (presets !== undefined - && agent !== undefined && presets.composedPreset(agent.ctx) === undefined) { - fail( - `agent "${agent.id}" addressed a model without joining any agent preset while a roster is ` - + 'composed; its tools, prompt sections, and skill catalog resolve against the empty global layer', - ) - } - return next() - }) -} - -/** - * 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)) diff --git a/packages/preset/agent-preset-registry/tests/invariant.spec.ts b/packages/preset/agent-preset-registry/tests/invariant.spec.ts deleted file mode 100644 index 53362400fc..0000000000 --- a/packages/preset/agent-preset-registry/tests/invariant.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { expect, it, onTestFinished } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import Invariants from '@deepseek-ai/dsh-invariants' -import { assembleContextFor } from '@deepseek-ai/dsh-agent' -import { SessionId } from '@deepseek-ai/dsh-session' -import { harness, declare, contribution, agentOn } from './harness.ts' -import { livePresetMounts } from '../src/index.ts' -import * as invariant from '../src/invariant.ts' - -it('rejects model use by an unjoined Agent but permits Host and cold-scope reads', async () => { - const ctx = await harness() - onTestFinished(() => ctx.fiber.dispose()) - await ctx.plugin(Invariants) - await ctx.plugin(invariant) - await declare(ctx, contribution('standard')) - const handle = await ctx.agents.create({ sessionId: SessionId('unjoined') }) - await expect(ctx.systemPrompt.assemble(assembleContextFor(handle.agent))).rejects.toThrow('without joining') - const agent = await agentOn(ctx, 'joined') - await expect(ctx.systemPrompt.assemble(assembleContextFor(agent))).resolves.toBeDefined() - await expect(ctx.systemPrompt.assemble({})).resolves.toBeDefined() - await using lease = await ctx.agentPresets.acquireScope() - await expect(ctx.systemPrompt.assemble({ scope: lease.key })).resolves.toBeDefined() -}) - -it('detects a late global service without confusing independent runtimes', async () => { - const first = await harness() - const second = await harness() - onTestFinished(async () => { await first.fiber.dispose(); await second.fiber.dispose() }) - await first.plugin(Invariants) - await first.plugin(invariant) - let publish!: () => void - first.loader.builtins.late = { apply(ctx: Context) { - publish = () => { ctx.effect(() => ctx.reflect.provide('latePresetService', {})) } - } } - await declare(first, { id: 'standard', plugins: [{ name: 'cordis:late' }] }) - await declare(second, contribution('standard')) - expect(livePresetMounts(first.fiber)).toHaveLength(1) - expect(livePresetMounts(second.fiber)).toHaveLength(1) - expect(() =>{ publish() }).toThrow('published process-global') -}) diff --git a/packages/preset/agent-preset-registry/tsconfig.json b/packages/preset/agent-preset-registry/tsconfig.json index 74a8b2e7af..1323291364 100644 --- a/packages/preset/agent-preset-registry/tsconfig.json +++ b/packages/preset/agent-preset-registry/tsconfig.json @@ -53,9 +53,6 @@ { "path": "../../util/atomic-write" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../boot/hmr" }, diff --git a/packages/preset/agent-preset/README.i18n.yaml b/packages/preset/agent-preset/README.i18n.yaml index 8742b7ef45..a2b45603c1 100644 --- a/packages/preset/agent-preset/README.i18n.yaml +++ b/packages/preset/agent-preset/README.i18n.yaml @@ -33,5 +33,5 @@ en: 4546a11627bbbca1 zh: eaa260cdfe37e31f /deepseek-ai-dsh-agent-preset/known-limitations-and-deferred-work/dev-note: - en: af1f8da6d3acb3e2 - zh: e0c4f6afeefd5214 + en: e67f633dc6fe7773 + zh: 8ec3e73528ddb1a6 diff --git a/packages/preset/agent-preset/README.md b/packages/preset/agent-preset/README.md index 407200ace1..243d771ec7 100644 --- a/packages/preset/agent-preset/README.md +++ b/packages/preset/agent-preset/README.md @@ -93,5 +93,3 @@ A live Agent’s composition stays stable; new Agents can use an updated definit None. - -**Runtime invariant:** No companion is published. This plugin submits registry-owned definitions and has no independently mutable runtime state. diff --git a/packages/preset/agent-preset/README.zh.md b/packages/preset/agent-preset/README.zh.md index 32e3b0dbd2..c7bde250e4 100644 --- a/packages/preset/agent-preset/README.zh.md +++ b/packages/preset/agent-preset/README.zh.md @@ -93,5 +93,3 @@ kind: "package-reference" 无。 - -**运行时不变量:** 不发布 companion;本插件仅提交由注册表管理的定义,没有可独立偏离的运行时状态。 diff --git a/packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md b/packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md index bf12975283..791fa90920 100644 --- a/packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md +++ b/packages/preset/agent-preset/skills/cordis-composition-reference/references/packages.md @@ -322,12 +322,6 @@ Every package below exports a Cordis plugin that a bundle patch can name in a Lo |---|---|---| | `@deepseek-ai/dsh-ptc-runtime-node` | yes | Sandboxed Node process implementation of the DeepSeek Harness PTC execution capability | -## runtime-diagnostics - -| Package | Config | Description | -|---|---|---| -| `@deepseek-ai/dsh-invariants` | yes | Registry service for package-owned DeepSeek Harness runtime invariants | - ## sandbox | Package | Config | Description | diff --git a/packages/preset/persona/README.i18n.yaml b/packages/preset/persona/README.i18n.yaml index d9529819eb..fbc7f9c6d2 100644 --- a/packages/preset/persona/README.i18n.yaml +++ b/packages/preset/persona/README.i18n.yaml @@ -36,8 +36,8 @@ en: bcd57eaefa6d1ff8 zh: 8d7de63d2bfaa5ef /deepseek-ai-dsh-persona/understand-the-implementation/source-map: - en: 6d3b20dc423825bf - zh: 2ada808509e73edc + en: 45e09ad507fbe1fb + zh: 006ec9c79c1771a7 /deepseek-ai-dsh-persona/further-exploration: en: 2636f1f672f3d32e zh: f1b4a371cc90cbd1 diff --git a/packages/preset/persona/README.md b/packages/preset/persona/README.md index f5525c5ff7..51d7185852 100644 --- a/packages/preset/persona/README.md +++ b/packages/preset/persona/README.md @@ -73,7 +73,6 @@ The row registers scoped persona prefix and suffix sections using the registry's | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, persona section registration, runtime-context suppression | -| — | No runtime invariant companion is published; this row owns no event stream or mutable runtime data — it registers prompt sections and the prompt registry owns identity, complete-prompt enforcement, shadowing, and disposal. | diff --git a/packages/preset/persona/README.zh.md b/packages/preset/persona/README.zh.md index 96f126cd94..d63f6df10e 100644 --- a/packages/preset/persona/README.zh.md +++ b/packages/preset/persona/README.zh.md @@ -73,7 +73,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、人设段落注册、runtime-context 抑制 | -| — | 不发布运行时不变式伴生入口;本行不拥有事件流或可变运行时数据,而是注册提示词段落;身份、完整提示词强制执行、遮蔽与资源释放均归提示词注册表。 | diff --git a/packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml b/packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml index 37c52ac21c..26813e78eb 100644 --- a/packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml +++ b/packages/ptc-runtime/ptc-runtime-node/README.i18n.yaml @@ -39,8 +39,8 @@ en: c01071304a9d563a zh: 2938a64b13a257d8 /deepseek-ai-dsh-ptc-runtime-node/understand-the-implementation/source-map: - en: a6702856dec3d449 - zh: 0c442a74c8005571 + en: e4cab2eb3f09e74b + zh: 5c10e8c4c0d9e56b /deepseek-ai-dsh-ptc-runtime-node/further-exploration: en: ac2e2ae13d3f1394 zh: 5d08b9c658fcf83a diff --git a/packages/ptc-runtime/ptc-runtime-node/README.md b/packages/ptc-runtime/ptc-runtime-node/README.md index 5196d6982c..e0ca8633a8 100644 --- a/packages/ptc-runtime/ptc-runtime-node/README.md +++ b/packages/ptc-runtime/ptc-runtime-node/README.md @@ -103,7 +103,6 @@ Source execution loads an erasable-only bootstrap closure without relying on sib | [`src/bootstrap.ts`](src/bootstrap.ts) | Program evaluation, binding proxies and output capture | | [`src/channel.ts`](src/channel.ts) | Framing, bounded writes and protocol failures | | [`src/output-ledger.ts`](src/output-ledger.ts) | Host accounting for the outer result | -| — | No runtime invariant companion is published; framing and process cleanup are enforced across the process boundary rather than through independent same-process observations. | diff --git a/packages/ptc-runtime/ptc-runtime-node/README.zh.md b/packages/ptc-runtime/ptc-runtime-node/README.zh.md index 16c9f8fc42..5a4628e097 100644 --- a/packages/ptc-runtime/ptc-runtime-node/README.zh.md +++ b/packages/ptc-runtime/ptc-runtime-node/README.zh.md @@ -103,7 +103,6 @@ Host 擦除可擦除类型,在配置的执行世界中解析可执行文件与 | [`src/bootstrap.ts`](src/bootstrap.ts) | 程序求值、绑定代理与输出捕获 | | [`src/channel.ts`](src/channel.ts) | 分帧、有界写入与协议失败 | | [`src/output-ledger.ts`](src/output-ledger.ts) | Host 外层结果计量 | -| — | 不发布运行时不变式配套模块;分帧与进程清理跨进程边界强制执行,不依靠同进程中的独立观测。 | diff --git a/packages/ptc-runtime/ptc-runtime/README.i18n.yaml b/packages/ptc-runtime/ptc-runtime/README.i18n.yaml index 6d1b6c9c2a..84b229d234 100644 --- a/packages/ptc-runtime/ptc-runtime/README.i18n.yaml +++ b/packages/ptc-runtime/ptc-runtime/README.i18n.yaml @@ -45,8 +45,8 @@ en: 01c82c6e73180979 zh: e29b066f9f82c8b4 /deepseek-ai-dsh-ptc-runtime/understand-the-implementation/source-map: - en: 73b7deab23f3b00d - zh: 780ddf5ff0dd5f65 + en: 85b65ceb9be0c49f + zh: 52df7f06d4e49d6e /deepseek-ai-dsh-ptc-runtime/further-exploration: en: a77e68bc4f5e66d9 zh: 101a57bcad581668 diff --git a/packages/ptc-runtime/ptc-runtime/README.md b/packages/ptc-runtime/ptc-runtime/README.md index 658697a73f..121ea270fd 100644 --- a/packages/ptc-runtime/ptc-runtime/README.md +++ b/packages/ptc-runtime/ptc-runtime/README.md @@ -90,7 +90,6 @@ Binding-global and error-class names are language-portable: they must match the |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: abstract `PtcRuntime` service and the portable-identifier exclusion sets | | [`src/types.ts`](src/types.ts) | Vocabulary: `PtcRunRequest`, `PtcRunSpec`, bindings, results, failures and sandbox facts | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | diff --git a/packages/ptc-runtime/ptc-runtime/README.zh.md b/packages/ptc-runtime/ptc-runtime/README.zh.md index 70c2315e9a..8b51c1e012 100644 --- a/packages/ptc-runtime/ptc-runtime/README.zh.md +++ b/packages/ptc-runtime/ptc-runtime/README.zh.md @@ -90,7 +90,6 @@ binding-global 与 error-class 名称是语言可移植的:必须匹配标识 |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:抽象 `PtcRuntime` 服务与可移植标识符排除集 | | [`src/types.ts`](src/types.ts) | 词汇:`PtcRunRequest`、`PtcRunSpec`、绑定、结果、失败与沙箱事实 | -| — | 不发布运行时不变式伴生入口;本包不公开任何独立的事件序列或可变数据关系,相关约束仅由其所属 seam 的约定实施。 | diff --git a/packages/runtime-diagnostics/README.i18n.yaml b/packages/runtime-diagnostics/README.i18n.yaml deleted file mode 100644 index 64ed4d88e3..0000000000 --- a/packages/runtime-diagnostics/README.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/runtime-diagnostics/README.md -/: - en: 72ac685be7c23a54 - zh: 8bd65fdc41719ab0 -/packages-runtime-diagnostics: - en: d6f767a17b54561c - zh: 44cbd169cba5892b -/packages-runtime-diagnostics/summary: - en: 578162d5f05b52d1 - zh: c0f63fadbc090e4d -/packages-runtime-diagnostics/table-of-contents: - en: 1eb35b015a6c423f - zh: a8a3dceee2c9e943 -/packages-runtime-diagnostics/packages: - en: b8028d51121730a2 - zh: 1850138a730976c1 -/packages-runtime-diagnostics/related-documentation: - en: 69997d5b7c4c17de - zh: 82199b9f1b117210 -/packages-runtime-diagnostics/dev-note: - en: 5de1d6304e00c58f - zh: 8d8225361cfa29e5 diff --git a/packages/runtime-diagnostics/README.md b/packages/runtime-diagnostics/README.md deleted file mode 100644 index cf52e03087..0000000000 --- a/packages/runtime-diagnostics/README.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -description: "The runtime-diagnostics group map: package-owned runtime invariant checks for live compositions, for users and maintainers navigating the group." -kind: "package-group" ---- - -# packages/runtime-diagnostics - -English | [中文](README.zh.md) - -## Summary - -The runtime-diagnostics group provides runtime self-checking for DeepSeek Harness compositions: one package, `invariants`, runs package-owned checks that verify each package's durable event and data relationships while the composition is live. A violation surfaces as an error attributed to the package that owns the relationship; a global switch and package-name filters control which checks run. Use this group's package when a composition should verify its own runtime contracts as part of normal operation. - -## Table of Contents - -- [Packages](#packages) -- [Related documentation](#related-documentation) -- [Dev Note](#dev-note) - ------ - - -## Packages - -| Package | Role | ctx key | -|---|---|---| -| [`invariants`](invariants/README.md) | Runs package-owned runtime checks and reports each failure by owning package | registers on `ctx.invariants` | - ------ - - -## Related documentation - -- [Runtime invariants subsystem](../../docs/subsystems/invariants.md) — the generated service reference: selection, installer, and companion contract. -- [Invariant runtime contracts Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md) — what a runtime invariant may assert and the mechanical gate enforcing companion wiring. -- [Package conventions](../AGENTS.md) — the `./invariant` companion rule every package follows. - ------ - - -## Dev Note - -
-Working context for maintainers — click to expand - -None. - -
diff --git a/packages/runtime-diagnostics/README.zh.md b/packages/runtime-diagnostics/README.zh.md deleted file mode 100644 index 74f6aa7487..0000000000 --- a/packages/runtime-diagnostics/README.zh.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -description: "runtime-diagnostics 组地图:针对运行中组合的包自有运行时不变式检查,供浏览本组的用户与维护者参考。" -kind: "package-group" ---- - -# packages/runtime-diagnostics - -[English](README.md) | 中文 - -## 概述 - -runtime-diagnostics 组为 DeepSeek Harness 组合提供运行时自检:一个包 `invariants` 在组合运行期间运行包自有检查,验证每个包的持久化事件与数据关系。违规会以归因到拥有该关系的包的错误呈现;全局开关与包名过滤器控制运行哪些检查。当组合需要在正常运行中验证自身运行时约定时,请使用本组的包。 - -## 目录 - -- [包](#packages) -- [相关文档](#related-documentation) -- [开发备注](#dev-note) - ------ - - -## 包 - -| 包 | 职责 | ctx 键 | -|---|---|---| -| [`invariants`](invariants/README.zh.md) | 运行包自有运行时检查,并按所属包报告每次失败 | 注册到 `ctx.invariants` | - ------ - - -## 相关文档 - -- [运行时不变式子系统](../../docs/subsystems/invariants.zh.md)——生成的服务参考:选择、installer 与配套入口约定。 -- [不变式运行时约定 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md)——运行时不变式可以断言什么,以及强制配套入口接线的机械门禁。 -- [包约定](../AGENTS.md)——每个包都必须遵循的 `./invariant` 配套入口规则。 - ------ - - -## 开发备注 - -
-维护者的工作上下文——点击展开 - -无。 - -
diff --git a/packages/runtime-diagnostics/invariants/README.i18n.yaml b/packages/runtime-diagnostics/invariants/README.i18n.yaml deleted file mode 100644 index 76e368400c..0000000000 --- a/packages/runtime-diagnostics/invariants/README.i18n.yaml +++ /dev/null @@ -1,61 +0,0 @@ -# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/runtime-diagnostics/invariants/README.md -/: - en: 42e5357bcc6d4525 - zh: 59891074b3705f9e -/deepseek-ai-dsh-invariants: - en: 355d61820fca8c0b - zh: c272797e46060b16 -/deepseek-ai-dsh-invariants/summary: - en: cb7b0a0d555fafa6 - zh: 23522958d2c367dd -/deepseek-ai-dsh-invariants/table-of-contents: - en: d152484eb41ac6b4 - zh: 09388d293f9be9cb -/deepseek-ai-dsh-invariants/use-this-package: - en: f0ff84f9852dca9b - zh: dd4ea4390478f0b7 -/deepseek-ai-dsh-invariants/use-this-package/when-to-use-it: - en: 597ae83b2c71897a - zh: f740fc18cbd3122f -/deepseek-ai-dsh-invariants/use-this-package/enabling-checks-and-selecting-packages: - en: ca8d647080cfb8c6 - zh: bbcc004ed3e81fae -/deepseek-ai-dsh-invariants/use-this-package/which-checks-run: - en: 9bb18846b6eeb906 - zh: fb79a5d6a71988fd -/deepseek-ai-dsh-invariants/use-this-package/adding-a-companion-to-a-custom-composition: - en: 25319ae89e0d6f21 - zh: e447f24be7b6a2d1 -/deepseek-ai-dsh-invariants/use-this-package/when-a-check-fails: - en: 74ced4bc91ead262 - zh: 966b5e607f507aee -/deepseek-ai-dsh-invariants/understand-the-implementation: - en: 5b23215f6331afac - zh: e2beea6daa73b614 -/deepseek-ai-dsh-invariants/understand-the-implementation/design-philosophy: - en: 556effb42afcaefd - zh: ff23afa308434b73 -/deepseek-ai-dsh-invariants/understand-the-implementation/source-map: - en: 08ade90614209295 - zh: d4862b249cb44379 -/deepseek-ai-dsh-invariants/understand-the-implementation/selection-and-registration-lifecycle: - en: 314e6dd14cc9228b - zh: da047ded06701b40 -/deepseek-ai-dsh-invariants/further-exploration: - en: 7ed5b0b9908a611f - zh: 76a8c00915ccda49 -/deepseek-ai-dsh-invariants/model-experience: - en: 269727a21a370a19 - zh: 9b9ab038146410cb -/deepseek-ai-dsh-invariants/model-experience/kv-cache-effect: - en: dffdf5a0451054bb - zh: 2a4a419d5b52f85e -/deepseek-ai-dsh-invariants/known-limitations-and-deferred-work: - en: 668ce3dea799676e - zh: 1970fbd1fa11ae17 -/deepseek-ai-dsh-invariants/known-limitations-and-deferred-work/dev-note: - en: e67f633dc6fe7773 - zh: 3bb0c20c93f16f98 diff --git a/packages/runtime-diagnostics/invariants/README.md b/packages/runtime-diagnostics/invariants/README.md deleted file mode 100644 index edb5123146..0000000000 --- a/packages/runtime-diagnostics/invariants/README.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -description: "Runtime invariant checks for live compositions: the registry service that runs package-owned checks, for users and maintainers choosing, configuring, or debugging them." -kind: "package-reference" ---- - -# @deepseek-ai/dsh-invariants - -English | [中文](README.zh.md) - -## Summary - -`dsh-invariants` runs package-owned runtime checks — invariants — inside a DeepSeek Harness composition: any package can ship a `./invariant` companion that verifies its own durable relationships (authoritative event streams and mutable snapshots) while the composition runs. Checks run automatically, and a failed check reports an `InvariantError` attributed to the package that owns the violated relationship. Choose it for compositions that want self-checking diagnostics with a global switch and package-name filters; the standard agent composition already mounts it with the four core companions, and loading the service alone installs no checks. - -## Table of Contents - -- [Use this package](#use-this-package) -- [Understand the implementation](#understand-the-implementation) -- [Further Exploration](#further-exploration) -- [Model Experience](#model-experience) -- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work) -- [Dev Note](#dev-note) - ------ - - -## Use this package - -Mount the registry when a composition should verify its own runtime contracts, then decide which packages' checks run. The service exposes `ctx.invariants`; companions register checks under their package's exact npm name, and every failure carries the owning package name. - -### When to use it - -Use the registry for compositions that want live diagnostics. [`dsh-sdk-minimal`](../../bundle/sdk-minimal/README.md) mounts it with the four core stateful companions — `dsh-session`, `dsh-agent`, `dsh-scope`, and `dsh-agent-loop`; `dsh-base` deliberately omits runtime diagnostics. Custom compositions mount the registry and add companions for any other loaded package whose contracts they want checked. Loading the registry alone installs no checks: it ships no product checks of its own, so a composition that never mounts a companion observes no diagnostic behavior. - -### Enabling checks and selecting packages - -The registry is enabled by default and checks every registered package unless filters say otherwise. Use `enabled` as a global switch, `package_allowlist` to admit only named packages, and `package_blocklist` to exclude packages after allowlist matching — a blocklist match overrides an allowlist match. Patterns are case-sensitive JavaScript regular-expression sources (unanchored unless they supply `^` and `$`), and an invalid, blank, or duplicate entry fails service startup instead of being skipped. - -```yaml -- name: '@deepseek-ai/dsh-invariants' - config: - enabled: true - package_allowlist: - - '^@deepseek-ai/dsh-' -``` - -| Field | Default | Meaning | -|---|---|---| -| `enabled` | `true` | Global switch for all registered checks | -| `package_allowlist` | `[]` | Regex sources admitting package names; empty admits all | -| `package_blocklist` | `[]` | Regex sources excluding package names after allowlist matching | - -The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-invariants) is the exhaustive source for every accepted field and its JSDoc. - -### Which checks run - -Each companion protects relationships its package owns, and a companion installs a check only for an observable event or mutable-data relationship — never for a service or method presence. The shipped executable companions cover: - -| Companion | Checks | -|---|---| -| `dsh-session`, `dsh-agent`, `dsh-scope`, `dsh-agent-loop` | Session log enclosure and call/result trace, agent-status transitions, scope-filtered dispatch subjects, loop-built request reconstruction | -| `dsh-llm`, `dsh-llm-retry`, `dsh-tools`, `dsh-system-prompt` | LLM stream grammar, retry-failure shape, tool-pipeline stage pairing and frozen results, prompt-assembly section names | -| `dsh-compaction`, `dsh-hook-protocol`, `dsh-sandbox-policy` | Compaction stream pairing, hook invocation/result pairing, sandbox mode values | -| `dsh-fs`, `dsh-subagent`, `dsh-workflow`, `dsh-tool-workflow` | Filesystem event identity, subagent provider and start/end pairing, workflow lifecycle identity, workflow record shape | -| `dsh-goal`, `dsh-goal-round-driver` | Durable goal-stream folds and reconstructed continuation prompts | -| `dsh-permission-presets`, `dsh-user-approval`, `dsh-commands` | Preset references to live presets, approval asked/decided pairing, command run/done pairing | -| `dsh-jobs`, `dsh-tool-todo`, `dsh-time-context` | Job snapshot field relationships, whole-list todo shape, durable clock readings | -| `dsh-credentials`, `dsh-settings`, `dsh-storage-domain`, `dsh-workspace` | Commit events against the live service or memory state, entity-cache mirroring | -| `dsh-agent-preset-registry`, `dsh-session-title`, `dsh-plan-mode`, `dsh-schedule` | Preset mount placement, title source citation, plan-mode payload, schedule stream | -| `dsh-client-hmr`, `dsh-client-modules`, `dsh-client-runtime` | Browser/node-half stat-watcher lifecycle, boot entry graph, slot mutation versioning | - -Every other workspace package omits the companion and states the package-specific reason in its README. - -### Adding a companion to a custom composition - -A companion is a normal plugin you mount beside the registry. It declares any services it needs and registers under its package's exact npm name; the registry joins its setup before the registration completes. - -```ts -import type { Context } from '@deepseek-ai/cordis' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant' - -declare const ctx: Context - -ctx.plugin(InvariantRegistry, { enabled: true }) -ctx.plugin(SessionInvariant) -``` - -### When a check fails - -A violation throws an `InvariantError` from the context that reported it: it carries the stable code `INVARIANT`, the full npm `packageName` of the owning package, and a message prefixed `invariant violated by "": …`. The failure is therefore attributable to a package without the registry importing any product code. A companion whose installer itself fails is disposed and its registration rolled back, so a broken check cannot leave partial listeners behind. - ------ - - -## Understand the implementation - -
-Implementation internals — click to expand - -This section explains the design behind the registry; the observable behavior is covered in [Use this package](#use-this-package). - -### Design philosophy - -- **Product-independent registry.** The service imports no session, agent, scope, or agent-loop package and contains none of their checks; companions carry checks next to their owners. -- **Real relationships, not synthetic assertions.** A companion checks an event-stream or mutable-data relationship its package owns; confirming a method, plugin name, injection, or fixed pure result is a type, load, or unit-test concern, never a runtime invariant. -- **Registration reserves ownership.** A package name is reserved even when filters keep its installer inactive, so two plugins can never silently claim the same name. -- **Companion wiring is mechanically enforced.** `pnpm run verify-package-invariants` rejects empty installers, installers that omit or ignore the reporter, wrong registration names, incomplete publication wiring, and stale wiring for omitted companions ([companion-omission note](../../../.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md)). - -### Source map - -| File | Role | -|---|---| -| [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, `InvariantRegistry` service, selection, registration, `InvariantError` | -| — | No runtime invariant companion is published; registration ownership and child lifecycle are the service's mutation boundary itself; observing them from the same registry would only duplicate its implementation. | - -### Selection and registration lifecycle - -`register(packageName, installer)` reserves the full npm name and returns an effect-scoped disposer. An enabled installer runs in a dedicated child fiber; `installer.inject` declares the services that fiber may access, and synchronous or asynchronous completion is joined before registration succeeds. Failure disposes the child and releases the reservation atomically. The service owns every registration fiber, while the returned disposer also belongs to the companion fiber, so unloading either side removes listeners, trace state, and the reservation — a companion can reload and register the same name again without retained state. Session-backed companions rebuild their baseline from durable events; live-only companions observe operations that begin after reload. - -
- ------ - - -## Further Exploration - -Read these pages when the package-level contract is not enough. They move from the generated service reference to the decision evidence and the group map. - -- [Runtime invariants subsystem](../../../docs/subsystems/invariants.md) — the generated reference for `Config`, the installer, the service, and the companion contract. -- [Generated configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-invariants) — every accepted config field and its source declaration. -- [Invariant runtime contracts Agent Note](../../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md) — what a runtime invariant may assert and the mechanical gate that enforces companion wiring. -- [Runtime-diagnostics group map](../../README.md) — adjacent diagnostics packages. - ------ - - -## Model Experience - -None, as the observer validates requests but never rewrites their context. - -#### KV Cache effect - -Checks observe assembled requests and durable state without mutating request content, so provider cache reuse is exactly what the underlying composition produces. - -## Known Limitations and Deferred Work - - - - -These limits define when the registry is a poor fit or needs operational care. They are current package constraints, not a task backlog. - -- **Filters are fixed for the service lifetime** — `enabled`, `package_allowlist`, and `package_blocklist` are compiled once at startup; changing them requires a Cordis plugin reload. -- **Live-only companions miss pre-reload operations** — a companion that only observes live operations cannot reconstruct operations that began before its own reload; session-backed companions rebuild their baseline from durable events. -- **Request reconstruction covers loop-built requests only** — the `dsh-agent-loop` companion reconstructs requests explicitly built by the loop; direct one-shot LLM calls remain outside that contract even when callers freeze them or attach a session id. -- **No checks without a companion** — the registry ships no product checks; a composition that mounts the service alone observes nothing. - - -### Dev Note - -
-Working context for maintainers — click to expand - -None. - -
diff --git a/packages/runtime-diagnostics/invariants/README.zh.md b/packages/runtime-diagnostics/invariants/README.zh.md deleted file mode 100644 index 1e2216a3b0..0000000000 --- a/packages/runtime-diagnostics/invariants/README.zh.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -description: "用于实时组合的运行时不变量检查:运行包自有检查的注册表服务,供用户和维护者选择、配置或排查。" -kind: "package-reference" ---- - -# @deepseek-ai/dsh-invariants - -[English](README.md) | 中文 - -## 概述 - -`dsh-invariants` 在 DeepSeek Harness 组合中运行包自有的运行时检查——不变量:任何包都可以发布一个 `./invariant` 配套入口,在组合运行期间验证其自身的持久关系(权威事件流与可变快照)。检查自动运行,失败的检查会报告归因到拥有被违反关系的包的 `InvariantError`。需要带全局开关与包名过滤器的自检诊断时选择它;标准 agent(智能体)组合已挂载它及四个核心配套入口,而单独加载服务不会安装任何检查。 - -## 目录 - -- [使用本包](#use-this-package) -- [理解实现](#understand-the-implementation) -- [进一步探索](#further-exploration) -- [模型体验](#model-experience) -- [已知限制与延期工作](#known-limitations-and-deferred-work) -- [开发备注](#dev-note) - ------ - - -## 使用本包 - -当组合需要验证自身运行时约定时挂载注册表,然后决定运行哪些包的检查。服务暴露 `ctx.invariants`;配套入口以其包的精确 npm 名称注册检查,每次失败都会携带所属包名。 - -### 何时使用 - -需要实时诊断的组合请使用注册表。[`dsh-sdk-minimal`](../../bundle/sdk-minimal/README.zh.md) 挂载它及四个核心有状态配套入口——`dsh-session`、`dsh-agent`、`dsh-scope` 与 `dsh-agent-loop`;`dsh-base` 刻意省略运行时诊断。自定义组合挂载注册表,并为任何其他已加载、且希望检查其约定的包添加配套入口。单独加载注册表不会安装任何检查:它自身不携带任何产品检查,因此从不挂载配套入口的组合不会观察到任何诊断行为。 - -### 启用检查与选择包 - -注册表默认启用,并在没有过滤器的情况下检查每个已注册的包。用 `enabled` 作全局开关,用 `package_allowlist` 只接纳指定包,用 `package_blocklist` 在 allowlist 匹配之后排除包——blocklist 匹配优先于 allowlist 匹配。模式是区分大小写的 JavaScript 正则表达式源(除非自带 `^` 与 `$`,否则不锚定);无效、空白或重复的条目会使服务启动失败,而不是被跳过。 - -```yaml -- name: '@deepseek-ai/dsh-invariants' - config: - enabled: true - package_allowlist: - - '^@deepseek-ai/dsh-' -``` - -| 字段 | 默认值 | 含义 | -|---|---|---| -| `enabled` | `true` | 所有已注册检查的全局开关 | -| `package_allowlist` | `[]` | 接纳包名的正则源;为空则全部接纳 | -| `package_blocklist` | `[]` | 在 allowlist 匹配之后排除包名的正则源 | - -生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-invariants)是每个受支持字段及其 JSDoc 的穷尽式真源。 - -### 运行哪些检查 - -每个配套入口保护其包拥有的关系,且只为可观察的事件或可变数据关系安装检查——绝不针对服务或方法是否存在。已发布的可执行配套入口覆盖: - -| 配套入口 | 检查 | -|---|---| -| `dsh-session`、`dsh-agent`、`dsh-scope`、`dsh-agent-loop` | 会话日志包含关系与调用/结果跟踪、agent 状态转换、经过作用域过滤的分发主体、loop 所构建请求的重建 | -| `dsh-llm`、`dsh-llm-retry`、`dsh-tools`、`dsh-system-prompt` | LLM(大语言模型)流语法、重试失败形状、工具流水线阶段配对与冻结结果、提示词组装章节名 | -| `dsh-compaction`、`dsh-hook-protocol`、`dsh-sandbox-policy` | 压缩(compaction)流配对、钩子调用/结果配对、沙箱 mode 值 | -| `dsh-fs`、`dsh-subagent`、`dsh-workflow`、`dsh-tool-workflow` | 文件系统事件身份、subagent 提供方与开始/结束配对、工作流生命周期身份、工作流记录形状 | -| `dsh-goal`、`dsh-goal-round-driver` | 持久 goal 流折叠与重建的继续提示词 | -| `dsh-permission-presets`、`dsh-user-approval`、`dsh-commands` | preset 引用指向活动 preset、审批询问/决定配对、命令运行/完成配对 | -| `dsh-jobs`、`dsh-tool-todo`、`dsh-time-context` | 任务快照字段关系、整表 todo 形状、持久时钟读数 | -| `dsh-credentials`、`dsh-settings`、`dsh-storage-domain`、`dsh-workspace` | 提交事件对照活动服务或内存状态、实体缓存镜像 | -| `dsh-agent-preset-registry`、`dsh-session-title`、`dsh-plan-mode`、`dsh-schedule` | preset 挂载位置、标题来源引用、plan-mode 载荷、schedule 流 | -| `dsh-client-hmr`、`dsh-client-modules`、`dsh-client-runtime` | 浏览器/node 侧 stat-watcher 生命周期、启动入口图、slot 变更版本化 | - -其余工作区包省略配套入口,并在各自 README 中说明包级原因。 - -### 向自定义组合添加配套入口 - -配套入口就是挂载在注册表旁的普通插件。它声明所需的服务,并以其包的精确 npm 名称注册;注册表会先完成其设置再完成注册。 - -```ts -import type { Context } from '@deepseek-ai/cordis' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant' - -declare const ctx: Context - -ctx.plugin(InvariantRegistry, { enabled: true }) -ctx.plugin(SessionInvariant) -``` - -### 检查失败时 - -违规会从报告它的上下文抛出 `InvariantError`:它携带稳定的 `INVARIANT` 代码、所属包的完整 npm `packageName`,以及以 `invariant violated by "": …` 开头的消息。失败因此可以归因到某个包,而注册表无需导入任何产品代码。installer 本身失败的配套入口会被释放,其注册会回滚,因此损坏的检查不会遗留部分监听器。 - ------ - - -## 理解实现 - -
-实现细节——点击展开 - -本节解释注册表背后的设计;可观察行为已在[使用本包](#use-this-package)中说明。 - -### 设计理念 - -- **与产品无关的注册表。** 服务不导入任何 session、agent、scope 或 agent-loop 包,也不包含它们的检查;配套入口把检查放在其归属者旁边。 -- **真实关系,而非人为断言。** 配套入口只检查其包拥有的事件流或可变数据关系;确认方法、插件名、注入或固定纯函数结果是类型、加载或单元测试关注点,绝不是运行时不变量。 -- **注册保留归属。** 即使过滤器让 installer 保持非活动,包名也会被保留,因此两个插件永远不会静默认领同一个名字。 -- **配套入口接线由机械规则强制。** `pnpm run verify-package-invariants` 拒绝空 installer、省略或忽略 reporter 的 installer、错误注册名、不完整的发布接线,以及省略配套入口后残留的接线([省略配套入口笔记](../../../.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md))。 - -### 源码地图 - -| 文件 | 职责 | -|---|---| -| [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、`InvariantRegistry` 服务、选择、注册、`InvariantError` | -| — | 不发布运行时不变量配套入口;注册归属与子级生命周期本身就是服务的变更边界;由同一注册表观察它们只会重复其实现。 | - -### 选择与注册生命周期 - -`register(packageName, installer)` 保留完整 npm 名称并返回作用域化 disposer。启用的 installer 在专用子 fiber 中运行;`installer.inject` 声明该 fiber 可访问的服务,同步或异步完成都会在注册成功前被 join。失败会释放子级并原子地收回保留。服务拥有每个注册 fiber,返回的 disposer 同时属于配套 fiber,因此卸载任一侧都会移除监听器、跟踪状态与保留——配套入口可以重新加载并再次注册同一名称而不保留旧状态。由会话支撑的配套入口从持久事件重建 baseline;仅实时配套入口观察重新加载后开始的操作。 - -
- ------ - - -## 进一步探索 - -当包级约定不够用时阅读以下页面。它们从生成的服务参考逐步进入决策证据与组地图。 - -- [运行时不变量子系统](../../../docs/subsystems/invariants.zh.md)——`Config`、installer、服务与配套入口约定的生成参考。 -- [生成的配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-invariants)——每个受支持配置字段及其源声明。 -- [运行时不变量约定 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md)——运行时不变量可以断言什么,以及强制配套入口接线的机械门禁。 -- [runtime-diagnostics 组地图](../../README.zh.md)——相邻的诊断包。 - ------ - - -## 模型体验 - -无。作为观察者,本包验证请求但从不改写其上下文。 - -#### KV Cache 影响 - -检查只观察已组装的请求与持久状态,不修改请求内容,因此提供方缓存复用与底层组合产生的结果完全一致。 - -## 已知限制与延期工作 - - - - -这些限制说明注册表何时不合适或需要特别运维。它们是当前包约束,不是任务积压。 - -- **过滤器在服务生命周期内固定**——`enabled`、`package_allowlist` 与 `package_blocklist` 在启动时编译一次;更改它们需要执行 Cordis 插件重新加载。 -- **仅实时配套入口会遗漏重载前的操作**——只观察实时操作的配套入口无法重建自身重新加载前开始的操作;由会话支撑的配套入口从持久事件重建 baseline。 -- **请求重建只覆盖 loop 构建的请求**——`dsh-agent-loop` 配套入口只重建 loop 显式构建的请求;直接一次性 LLM 调用即使由调用方冻结或附加会话 id,仍不在此约定内。 -- **没有配套入口就没有检查**——注册表自身不携带产品检查;只挂载服务的组合观察不到任何行为。 - - -### 开发备注 - -
-维护者的工作上下文——点击展开 - -无。 - -
diff --git a/packages/runtime-diagnostics/invariants/package.json b/packages/runtime-diagnostics/invariants/package.json deleted file mode 100644 index 9754335f36..0000000000 --- a/packages/runtime-diagnostics/invariants/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@deepseek-ai/dsh-invariants", - "description": "Registry service for package-owned DeepSeek Harness runtime invariants", - "version": "0.2.0-rc.2", - "publishConfig": { - "access": "public" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", - "directory": "packages/runtime-diagnostics/invariants" - }, - "type": "module", - "main": "lib/index.js", - "types": "lib/types/index.d.ts", - "exports": { - ".": { - "types": "./lib/types/index.d.ts", - "default": "./lib/index.js" - }, - "./src/*": "./src/*", - "./package.json": "./package.json" - }, - "files": [ - "lib/index.js", - "lib/types/**/*.d.ts" - ], - "license": "MIT", - "peerDependencies": { - "@deepseek-ai/cordis": "workspace:~" - }, - "dependencies": { - "@deepseek-ai/schemastery": "workspace:~" - }, - "devDependencies": { - "@deepseek-ai/cordis": "workspace:~" - } -} diff --git a/packages/runtime-diagnostics/invariants/src/index.ts b/packages/runtime-diagnostics/invariants/src/index.ts deleted file mode 100644 index 5d96664f3a..0000000000 --- a/packages/runtime-diagnostics/invariants/src/index.ts +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Configurable registry for package-owned runtime invariant contributions. - * Every workspace package registers checks from a `./invariant` companion; - * ordinary package entrypoints stay independent of diagnostics. - * - * @module @deepseek-ai/dsh-invariants - */ - -import { Context, Service } from '@deepseek-ai/cordis' -import type { Inject } from '@deepseek-ai/cordis' -import z from '@deepseek-ai/schemastery' -import type Schema from '@deepseek-ai/schemastery' - -/** Runtime invariant selection configured on the service plugin. */ -export interface Config { - /** Global switch; defaults to `true`. */ - readonly enabled?: boolean - /** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */ - readonly package_allowlist?: string[] - /** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */ - readonly package_blocklist?: string[] -} - -/** - * Throw a package-attributed invariant failure. - * @param message - violated package contract without the standard prefix. - * @returns never because reporting a violation throws. - */ -export type InvariantFailure = (message: string) => never - -/** Install one package's checks into the registration's child context. */ -export interface InvariantInstaller { - /** - * Install the package contribution. - * @param ctx - child context owned by this invariant registration. - * @param fail - reporter bound to the registering package name. - * @returns nothing, or a promise settling after asynchronous checks finish. - */ - (ctx: Context, fail: InvariantFailure): void | Promise - /** Services the child installer fiber may access. */ - readonly inject?: Inject -} - -/** Internal effect shape used to join child startup before a companion loads. */ -interface PendingInvariantRegistration extends PromiseLike<() => void> { - (): void | Promise -} - -/** Thrown when a package-owned runtime invariant is violated. */ -export class InvariantError extends Error { - /** Stable machine-readable invariant failure code. */ - readonly code = 'INVARIANT' as const - /** Full npm package name that owns the violated invariant. */ - readonly packageName: string - - /** - * Construct a package-attributed invariant failure. - * @param packageName - full npm package name that registered the check. - * @param message - violated contract, without the standard error prefix. - */ - constructor(packageName: string, message: string) { - super(`invariant violated by "${packageName}": ${message}`) - this.name = 'InvariantError' - this.packageName = packageName - } -} - -declare module '@deepseek-ai/cordis' { - interface Context { - invariants: InvariantRegistry - } -} - -/** Compile and validate one package-filter list. */ -function compilePatterns(field: 'package_allowlist' | 'package_blocklist', values: readonly string[]): RegExp[] { - const seen = new Set() - return values.map((value) => { - if (value.length === 0 || value.trim() !== value) { - throw new Error(`invariants: ${field} entries must be non-blank and have no surrounding whitespace`) - } - if (seen.has(value)) { - throw new Error(`invariants: ${field} contains duplicate regex ${JSON.stringify(value)}`) - } - seen.add(value) - try { - return new RegExp(value) - } catch (cause) { - throw new Error(`invariants: ${field} contains invalid regex ${JSON.stringify(value)}`, { cause }) - } - }) -} - -/** Package-owned invariant registry with global and regex-based selection. */ -export class InvariantRegistry extends Service { - static Config: Schema = z.object({ - enabled: z.boolean().default(true), - package_allowlist: z.array(z.string()).default([]), - package_blocklist: z.array(z.string()).default([]), - }) - - private readonly enabled: boolean - private readonly ownerCtx: Context - private readonly packageAllowlist: readonly RegExp[] - private readonly packageBlocklist: readonly RegExp[] - private readonly registrations = new Set() - - /** - * Create and install the invariant registry. - * @param ctx - Cordis context that owns the service. - * @param config - global enablement and package-name regex filters. - */ - constructor(ctx: Context, config: Config = {}) { - super(ctx, 'invariants') - this.ownerCtx = ctx - this.enabled = config.enabled ?? true - this.packageAllowlist = compilePatterns('package_allowlist', config.package_allowlist ?? []) - this.packageBlocklist = compilePatterns('package_blocklist', config.package_blocklist ?? []) - } - - /** Return whether one full package name passes the configured filters. */ - private selected(packageName: string): boolean { - if (!this.enabled) return false - if (this.packageAllowlist.length > 0 - && !this.packageAllowlist.some(pattern => pattern.test(packageName))) return false - return !this.packageBlocklist.some(pattern => pattern.test(packageName)) - } - - /** - * Register one package's invariant installer. The package name is reserved - * even when filtering disables its checks. Enabled installers run in a child - * fiber; failure disposes that fiber and releases the reservation. - * @param packageName - full npm package name that owns the contribution. - * @param installer - listener or startup-check installer for the child context. - * @returns an effect-scoped disposer for the registration. - */ - register(packageName: string, installer: InvariantInstaller): () => void { - if (packageName.length === 0 || packageName.trim() !== packageName || /\s/.test(packageName)) { - throw new Error('invariants: packageName must be non-blank and contain no whitespace') - } - if (this.registrations.has(packageName)) { - throw new Error(`invariants: package "${packageName}" is already registered`) - } - - // Service method tracing binds `this.ctx` to the caller. This explicit - // origin keeps registrations and their child fibers owned by the service; - // companion disposal is covered independently by the returned disposer. - const ctx = this.ownerCtx - const registrations = this.registrations - registrations.add(packageName) - - let registration: PendingInvariantRegistration - try { - registration = ctx.effect(async () => { - if (!this.selected(packageName)) { - return () => { - registrations.delete(packageName) - } - } - - const installInvariant = (childCtx: Context) => ( - installer(childCtx, (message): never => { - throw new InvariantError(packageName, message) - }) - ) - try { - const child = ctx.plugin(installer.inject === undefined - ? installInvariant - : Object.assign(installInvariant, { inject: installer.inject })) - - try { - await child - } catch (error) { - await child.dispose() - throw error - } - - return async () => { - try { - await child.dispose() - } finally { - registrations.delete(packageName) - } - } - } catch (error) { - registrations.delete(packageName) - throw error - } - }, `invariants.register(${JSON.stringify(packageName)})`) - } catch (error) { - registrations.delete(packageName) - throw error - } - // Cordis attaches setup thenability and async teardown to this callable; - // the service contract intentionally exposes only the conventional disposer. - // oxlint-disable-next-line typescript/no-misused-promises -- the extra runtime shape stays private. - return registration - } -} - -export default InvariantRegistry diff --git a/packages/runtime-diagnostics/invariants/tests/service.spec.ts b/packages/runtime-diagnostics/invariants/tests/service.spec.ts deleted file mode 100644 index 7035fe2ce9..0000000000 --- a/packages/runtime-diagnostics/invariants/tests/service.spec.ts +++ /dev/null @@ -1,310 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { Context, Service } from '@deepseek-ai/cordis' -import InvariantRegistry, { - InvariantError, - type Config, -} from '@deepseek-ai/dsh-invariants' - -declare module '@deepseek-ai/cordis' { - interface Context { - invariantProbe: InvariantProbeService - } - - interface Events { - 'invariants-test/ping'(): void - } -} - -class InvariantProbeService extends Service { - constructor(ctx: Context) { - super(ctx, 'invariantProbe') - } -} - -interface RuntimeRegistration extends PromiseLike<() => void> { - (): void | Promise -} - -interface InstalledRegistration { - dispose(): Promise -} - -function runtimeRegistration(registration: () => void): RuntimeRegistration { - return registration as RuntimeRegistration -} - -async function setup(config: Config = {}): Promise<{ ctx: Context; fiber: Awaited> }> { - const ctx = new Context() - const fiber = await ctx.plugin(InvariantRegistry, config) - return { ctx, fiber } -} - -async function registerProbe( - ctx: Context, - packageName: string, - probe: () => void, -): Promise { - const registration = runtimeRegistration(ctx.invariants.register(packageName, (child) => { - child.on('invariants-test/ping', probe, { global: true }) - })) - await registration - return { - async dispose() { await registration() }, - } -} - -describe('InvariantRegistry selection', () => { - it('applies defaults when constructed directly without schema normalization', async () => { - const ctx = new Context() - const service = new InvariantRegistry(ctx) - const probe = vi.fn() - const registration = runtimeRegistration(service.register('@deepseek-ai/dsh-session', (child) => { - child.on('invariants-test/ping', probe, { global: true }) - })) - await registration - ctx.emit('invariants-test/ping') - expect(probe).toHaveBeenCalledOnce() - await registration() - }) - - it('enables registrations by default and treats empty lists as admit-all and exclude-none', async () => { - for (const config of [{}, { package_allowlist: [], package_blocklist: [] }]) { - const { ctx } = await setup(config) - const probe = vi.fn() - await registerProbe(ctx, '@deepseek-ai/dsh-session', probe) - ctx.emit('invariants-test/ping') - expect(probe).toHaveBeenCalledOnce() - } - }) - - it('disables every installer while still reserving package ownership', async () => { - const { ctx } = await setup({ enabled: false }) - const probe = vi.fn() - const registration = await registerProbe(ctx, '@deepseek-ai/dsh-session', probe) - expect(() => ctx.invariants.register('@deepseek-ai/dsh-session', () => {})) - .toThrow(/already registered/) - ctx.emit('invariants-test/ping') - expect(probe).not.toHaveBeenCalled() - await registration.dispose() - }) - - it('uses unanchored, case-sensitive JavaScript regex sources', async () => { - const unanchored = await setup({ package_allowlist: ['session'] }) - const unanchoredProbe = vi.fn() - await registerProbe(unanchored.ctx, '@deepseek-ai/dsh-session-extra', unanchoredProbe) - unanchored.ctx.emit('invariants-test/ping') - expect(unanchoredProbe).toHaveBeenCalledOnce() - - const anchored = await setup({ package_allowlist: ['^@deepseek-ai/dsh-session$'] }) - const anchoredProbe = vi.fn() - await registerProbe(anchored.ctx, '@deepseek-ai/dsh-session-extra', anchoredProbe) - anchored.ctx.emit('invariants-test/ping') - expect(anchoredProbe).not.toHaveBeenCalled() - - const caseSensitive = await setup({ package_allowlist: ['Session'] }) - const caseProbe = vi.fn() - await registerProbe(caseSensitive.ctx, '@deepseek-ai/dsh-session', caseProbe) - caseSensitive.ctx.emit('invariants-test/ping') - expect(caseProbe).not.toHaveBeenCalled() - }) - - it('lets the blocklist override an allowlist match', async () => { - const { ctx } = await setup({ - package_allowlist: ['^@deepseek-ai/dsh-'], - package_blocklist: ['session'], - }) - const sessionProbe = vi.fn() - const agentProbe = vi.fn() - await registerProbe(ctx, '@deepseek-ai/dsh-session', sessionProbe) - await registerProbe(ctx, '@deepseek-ai/dsh-agent', agentProbe) - ctx.emit('invariants-test/ping') - expect(sessionProbe).not.toHaveBeenCalled() - expect(agentProbe).toHaveBeenCalledOnce() - }) - - it('accepts zero-match patterns for packages registered later', async () => { - const { ctx } = await setup({ package_allowlist: ['^@later/invariants$'] }) - const now = vi.fn() - const later = vi.fn() - await registerProbe(ctx, '@deepseek-ai/dsh-session', now) - await registerProbe(ctx, '@later/invariants', later) - ctx.emit('invariants-test/ping') - expect(now).not.toHaveBeenCalled() - expect(later).toHaveBeenCalledOnce() - }) - - it('allows the same source in both lists and applies blocklist precedence', async () => { - const { ctx } = await setup({ package_allowlist: ['agent'], package_blocklist: ['agent'] }) - const probe = vi.fn() - await registerProbe(ctx, '@deepseek-ai/dsh-agent', probe) - ctx.emit('invariants-test/ping') - expect(probe).not.toHaveBeenCalled() - }) -}) - -describe('InvariantRegistry validation', () => { - it.each([ - [{ package_allowlist: [''] }, /non-blank/], - [{ package_allowlist: [' '] }, /non-blank/], - [{ package_allowlist: [' session'] }, /surrounding whitespace/], - [{ package_blocklist: ['session '] }, /surrounding whitespace/], - [{ package_allowlist: ['session', 'session'] }, /duplicate regex/], - [{ package_blocklist: ['agent', 'agent'] }, /duplicate regex/], - [{ package_allowlist: ['['] }, /invalid regex/], - [{ package_blocklist: ['('] }, /invalid regex/], - ])('rejects malformed filter config %#', async (config, message) => { - await expect((async () => { - const ctx = new Context() - await ctx.plugin(InvariantRegistry, config) - })()).rejects.toThrow(message) - }) - - it.each(['', ' ', ' package', 'pack age', 'package\n'])('rejects malformed package name %j', async (packageName) => { - const { ctx } = await setup() - expect(() => ctx.invariants.register(packageName, () => {})).toThrow(/packageName/) - }) -}) - -describe('InvariantRegistry lifecycle', () => { - it('honors the installer dependency API in its child fiber', async () => { - const { ctx } = await setup() - await ctx.plugin(InvariantProbeService) - let registration!: RuntimeRegistration - await ctx.plugin({ - inject: ['invariants', 'invariantProbe'], - apply(child: Context) { - const installer = Object.assign((installerCtx: Context) => { - expect(Object.keys(installerCtx.fiber.inject)).toContain('invariantProbe') - expect(Object.keys(installerCtx.fiber.store ?? {})).toContain('invariantProbe') - expect(installerCtx.invariantProbe).toBeInstanceOf(InvariantProbeService) - }, { inject: ['invariantProbe'] }) - expect(installer.inject).toEqual(['invariantProbe']) - registration = runtimeRegistration(child.invariants.register('@deepseek-ai/dsh-probe', installer)) - return Promise.resolve(registration) - }, - }) - await registration - }) - - it('attributes failures to the registering package with the stable code', async () => { - const { ctx } = await setup() - const registration = runtimeRegistration(ctx.invariants.register('@deepseek-ai/dsh-session', (child, fail) => { - child.on('invariants-test/ping', () => fail('seq must strictly increase'), { global: true }) - })) - await registration - let caught: unknown - try { - ctx.emit('invariants-test/ping') - } catch (error) { - caught = error - } - expect(caught).toBeInstanceOf(InvariantError) - expect(caught).toMatchObject({ - name: 'InvariantError', - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-session', - message: 'invariant violated by "@deepseek-ai/dsh-session": seq must strictly increase', - }) - }) - - it('disposes the child fiber completely and permits HMR re-registration', async () => { - const { ctx } = await setup() - const first = vi.fn() - const firstRegistration = await registerProbe(ctx, '@deepseek-ai/dsh-session', first) - ctx.emit('invariants-test/ping') - await firstRegistration.dispose() - ctx.emit('invariants-test/ping') - expect(first).toHaveBeenCalledOnce() - - const second = vi.fn() - await registerProbe(ctx, '@deepseek-ai/dsh-session', second) - ctx.emit('invariants-test/ping') - expect(first).toHaveBeenCalledOnce() - expect(second).toHaveBeenCalledOnce() - }) - - it('reserves ownership until asynchronous child disposal completes', async () => { - const { ctx } = await setup() - let finishDisposal!: () => void - const disposalBarrier = new Promise((resolve) => { finishDisposal = resolve }) - const registration = runtimeRegistration(ctx.invariants.register('@deepseek-ai/dsh-session', (child) => { - child.effect(() => async () => { await disposalBarrier }) - })) - await registration - - const disposing = registration() - expect(() => ctx.invariants.register('@deepseek-ai/dsh-session', () => {})) - .toThrow(/already registered/) - finishDisposal() - await disposing - - const replacement = runtimeRegistration(ctx.invariants.register('@deepseek-ai/dsh-session', () => {})) - await replacement - await replacement() - }) - - it('rolls back listeners and ownership atomically when an installer fails', async () => { - const { ctx } = await setup() - const leaked = vi.fn() - const failed = runtimeRegistration(ctx.invariants.register('@deepseek-ai/dsh-session', (child) => { - child.on('invariants-test/ping', leaked, { global: true }) - throw new Error('installer failed') - })) - await expect(Promise.resolve(failed)).rejects.toThrow('installer failed') - ctx.emit('invariants-test/ping') - expect(leaked).not.toHaveBeenCalled() - - const retry = vi.fn() - await registerProbe(ctx, '@deepseek-ai/dsh-session', retry) - ctx.emit('invariants-test/ping') - expect(retry).toHaveBeenCalledOnce() - }) - - it('rolls back publication effects and ownership when child-fiber publication fails', async () => { - const { ctx } = await setup() - const leaked = vi.fn() - let rejectPublication = true - const stopRejecting = ctx.on('internal/plugin', (fiber) => { - if (!rejectPublication || fiber.uid === null) return - rejectPublication = false - fiber.ctx.on('invariants-test/ping', leaked, { global: true }) - throw new Error('publication failed') - }) - - const failed = runtimeRegistration(ctx.invariants.register('@deepseek-ai/dsh-publication-probe', () => {})) - await expect(Promise.resolve(failed)).rejects.toThrow('publication failed') - ctx.emit('invariants-test/ping') - expect(leaked).not.toHaveBeenCalled() - stopRejecting() - - const retry = runtimeRegistration(ctx.invariants.register('@deepseek-ai/dsh-publication-probe', () => {})) - await retry - await retry() - }) - - it('joins asynchronous checks and rolls back their effects on failure', async () => { - const { ctx } = await setup() - const leaked = vi.fn() - const failed = runtimeRegistration(ctx.invariants.register('@deepseek-ai/dsh-async-probe', async (child, fail) => { - child.on('invariants-test/ping', leaked, { global: true }) - await Promise.resolve() - fail('asynchronous check failed') - })) - await expect(Promise.resolve(failed)).rejects.toThrow(/asynchronous check failed/) - ctx.emit('invariants-test/ping') - expect(leaked).not.toHaveBeenCalled() - - const retry = runtimeRegistration(ctx.invariants.register('@deepseek-ai/dsh-async-probe', async () => { - await Promise.resolve() - })) - await retry - await retry() - }) - - it('releases a synchronous reservation if the service fiber is already inactive', async () => { - const { ctx, fiber } = await setup() - const service = ctx.invariants - await fiber.dispose() - expect(() => service.register('@deepseek-ai/dsh-session', () => {})).toThrow(/inactive/i) - }) -}) diff --git a/packages/runtime-diagnostics/invariants/tsconfig.json b/packages/runtime-diagnostics/invariants/tsconfig.json deleted file mode 100644 index f0063ae28d..0000000000 --- a/packages/runtime-diagnostics/invariants/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "src", - "outDir": "lib/types" - }, - "include": [ - "src" - ], - "references": [ - { - "path": "../../../vendor/cosmokit" - }, - { - "path": "../../../vendor/cordis" - }, - { - "path": "../../../vendor/schemastery" - } - ] -} diff --git a/packages/sandbox/sandbox-local/README.i18n.yaml b/packages/sandbox/sandbox-local/README.i18n.yaml index ea1d108692..baa0244b69 100644 --- a/packages/sandbox/sandbox-local/README.i18n.yaml +++ b/packages/sandbox/sandbox-local/README.i18n.yaml @@ -42,8 +42,8 @@ en: 62adfc9229c656cc zh: 11b1534b6d61e5de /deepseek-ai-dsh-sandbox-local/understand-the-implementation/source-map: - en: 8ab5bcc8dd97df3c - zh: ee7a6a237a142cbd + en: 215bb6bac514b8b4 + zh: 41e7777eea10a480 /deepseek-ai-dsh-sandbox-local/further-exploration: en: 53e91b8be78bed12 zh: 70f382c800dc191a diff --git a/packages/sandbox/sandbox-local/README.md b/packages/sandbox/sandbox-local/README.md index a2e0965af6..1591300222 100644 --- a/packages/sandbox/sandbox-local/README.md +++ b/packages/sandbox/sandbox-local/README.md @@ -94,7 +94,6 @@ Each runner's kernel speaks its own denial dialect, carried on every wrap as `de |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: runner chain selection, functional probes, per-call wrap, ACL grant lifecycle | | [`src/profiles.ts`](src/profiles.ts) | Per-platform profile builders: bwrap mounts, Landlock grants, Seatbelt SBPL | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | diff --git a/packages/sandbox/sandbox-local/README.zh.md b/packages/sandbox/sandbox-local/README.zh.md index 9f868b291e..384d3d3c6e 100644 --- a/packages/sandbox/sandbox-local/README.zh.md +++ b/packages/sandbox/sandbox-local/README.zh.md @@ -94,7 +94,6 @@ Windows 档为每个工作区保留一个确定性写入 SID 和常驻 ACE,同 |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:runner 链选择、功能探测、逐调用包装、ACL 授权生命周期 | | [`src/profiles.ts`](src/profiles.ts) | 各平台 profile 构建器:bwrap 挂载、Landlock 授权、Seatbelt SBPL | -| — | 不发布运行时不变式伴生入口;除所属 seam 强制执行的约定外,本包不公开独立的事件序列或可变数据关系。 | diff --git a/packages/sandbox/sandbox-policy/README.i18n.yaml b/packages/sandbox/sandbox-policy/README.i18n.yaml index 3456b48e82..497135d24c 100644 --- a/packages/sandbox/sandbox-policy/README.i18n.yaml +++ b/packages/sandbox/sandbox-policy/README.i18n.yaml @@ -39,11 +39,11 @@ en: d13dabff02f2acfd zh: f593888b4ffddf36 /deepseek-ai-dsh-sandbox-policy/understand-the-implementation/model-visible-text: - en: d427370cafeecb48 - zh: 70494b8400c45655 + en: 999ecd77b3857811 + zh: 2cc0a0b2aee08c17 /deepseek-ai-dsh-sandbox-policy/understand-the-implementation/source-map: - en: 184d0ece30be790f - zh: 84a1fd1b71f5154d + en: cc7dd675c9915f7d + zh: d719c4f9de39873a /deepseek-ai-dsh-sandbox-policy/further-exploration: en: 8525667a02f4bbeb zh: 2f09594fbf8d37ed diff --git a/packages/sandbox/sandbox-policy/README.md b/packages/sandbox/sandbox-policy/README.md index 3b00f6992c..91ae1c9a17 100644 --- a/packages/sandbox/sandbox-policy/README.md +++ b/packages/sandbox/sandbox-policy/README.md @@ -77,7 +77,7 @@ A runtime switch is one log-only `sandbox/mode` event on the session it applies ### Model-visible text -The `sandbox:policy` contribution states the mode's capability-neutral file-effect contract and the recorded session workspace under `workspace-write`. It does not enumerate mounted capabilities; tool plugins retain operation-specific denial and escalation guidance, approval policy contributes separately to the same snapshot, and plan guidance remains `dsh-plan-mode`'s system section. The optional `./invariant` companion rejects a forged durable `sandbox/mode` event whose value falls outside the closed mode vocabulary. +The `sandbox:policy` contribution states the mode's capability-neutral file-effect contract and the recorded session workspace under `workspace-write`. It does not enumerate mounted capabilities; tool plugins retain operation-specific denial and escalation guidance, approval policy contributes separately to the same snapshot, and plan guidance remains `dsh-plan-mode`'s system section. ### Source map @@ -85,7 +85,6 @@ The `sandbox:policy` contribution states the mode's capability-neutral file-effe |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `SandboxPolicyService`, `Config` schema, policy resolution and context contribution | | [`src/session-mode.ts`](src/session-mode.ts) | The `sandbox/mode` event, its fold, and the write path | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: rejects `sandbox/mode` values outside the closed vocabulary | diff --git a/packages/sandbox/sandbox-policy/README.zh.md b/packages/sandbox/sandbox-policy/README.zh.md index fa78849852..4cb369a5e1 100644 --- a/packages/sandbox/sandbox-policy/README.zh.md +++ b/packages/sandbox/sandbox-policy/README.zh.md @@ -77,7 +77,7 @@ kind: "package-reference" ### 模型可见文本 -`sandbox:policy` 贡献说明该模式与具体能力无关的文件操作约定,以及 `workspace-write` 下已记录的会话工作区。它不枚举已挂载能力;工具插件保留特定于操作的拒绝与升权引导,批准策略单独贡献给同一份快照,计划引导仍由 `dsh-plan-mode` 的系统段落管理。可选的 `./invariant` 配套组件会拒绝值超出封闭模式词汇的伪造持久 `sandbox/mode` 事件。 +`sandbox:policy` 贡献说明该模式与具体能力无关的文件操作约定,以及 `workspace-write` 下已记录的会话工作区。它不枚举已挂载能力;工具插件保留特定于操作的拒绝与升权引导,批准策略单独贡献给同一份快照,计划引导仍由 `dsh-plan-mode` 的系统段落管理。 ### 源码地图 @@ -85,7 +85,6 @@ kind: "package-reference" |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`SandboxPolicyService`、`Config` schema、策略解析与上下文贡献 | | [`src/session-mode.ts`](src/session-mode.ts) | `sandbox/mode` 事件、其 fold 与写入路径 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式配套组件:拒绝超出封闭词汇的 `sandbox/mode` 值 | diff --git a/packages/sandbox/sandbox-policy/package.json b/packages/sandbox/sandbox-policy/package.json index 6c68317073..1ebe7bff58 100644 --- a/packages/sandbox/sandbox-policy/package.json +++ b/packages/sandbox/sandbox-policy/package.json @@ -18,22 +18,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" ], "license": "MIT", "peerDependencies": { "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-sandbox": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-system-prompt": "workspace:*", @@ -46,7 +40,6 @@ }, "devDependencies": { "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-sandbox": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-system-prompt": "workspace:*", diff --git a/packages/sandbox/sandbox-policy/src/invariant.ts b/packages/sandbox/sandbox-policy/src/invariant.ts deleted file mode 100644 index 62ae2446ae..0000000000 --- a/packages/sandbox/sandbox-policy/src/invariant.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** Package-owned session-event invariants for sandbox policy. @module @deepseek-ai/dsh-sandbox-policy/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import { SANDBOX_MODES } from './session-mode.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-sandbox-policy' - -/** Cordis companion plugin name. */ -export const name = 'sandbox-policy-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/* jscpd:ignore-start -- package companions share replay and dispatch plumbing */ -/** Validate the package-owned event fields and ignore unrelated events. */ -function validateEvent(event: SessionEvent, fail: InvariantFailure): void { - if (event.type === 'sandbox/mode' && !SANDBOX_MODES.includes(event.data.mode)) { - fail(`sandbox/mode carries unknown mode ${JSON.stringify(event.data.mode)}`) - } -} - -/** Install validation for loaded and newly appended sandbox modes. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - for (const session of ctx.sessions.list()) { - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) validateEvent(event, fail) - } - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const event = (args as [Session, SessionEvent])[1] - validateEvent(event, fail) - }, { global: true }) -}, { inject: ['sessions'] }) -/* jscpd:ignore-end */ - -/** - * 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)) diff --git a/packages/sandbox/sandbox-policy/tests/invariant.spec.ts b/packages/sandbox/sandbox-policy/tests/invariant.spec.ts deleted file mode 100644 index 258f513d33..0000000000 --- a/packages/sandbox/sandbox-policy/tests/invariant.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import SessionStore, { type Session, type SessionEvent } from '@deepseek-ai/dsh-session' -import InvariantRegistry, { InvariantError } from '@deepseek-ai/dsh-invariants' -import * as SandboxPolicyInvariant from '@deepseek-ai/dsh-sandbox-policy/invariant' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(SandboxPolicyInvariant) - return ctx -} - -function modeEvent(mode: string): SessionEvent { - return { type: 'sandbox/mode', seq: 0, time: 0, data: { mode } } as SessionEvent -} - -describe('sandbox-policy invariants', () => { - it.each(['read-only', 'workspace-write', 'danger-full-access'])( - 'accepts the durable %s mode', - async (mode) => { - const ctx = await setup() - expect(() => { ctx.emit('session/event', {} as Session, modeEvent(mode)) }).not.toThrow() - }, - ) - - it('ignores unrelated event streams', async () => { - const ctx = await setup() - expect(() => { ctx.emit('session/event', {} as Session, { - type: 'turn/start', seq: 0, time: 0, data: {}, - } as SessionEvent) }).not.toThrow() - expect(() => { ctx.emit('tools/change') }).not.toThrow() - }) - - it('rejects and attributes an unknown durable sandbox mode', async () => { - const ctx = await setup() - expect(() => { ctx.emit('session/event', {} as Session, modeEvent('host-root')) }) - .toThrow(new InvariantError('@deepseek-ai/dsh-sandbox-policy', 'sandbox/mode carries unknown mode "host-root"')) - }) - - it('rejects an unknown mode already present on late registration', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - ctx.sessions.create().append('sandbox/mode', { mode: 'host-root' as never }) - await ctx.plugin(InvariantRegistry, { enabled: true }) - - await expect(ctx.plugin(SandboxPolicyInvariant).then(() => undefined)).rejects.toMatchObject({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-sandbox-policy', - }) - }) -}) diff --git a/packages/sandbox/sandbox-policy/tsconfig.json b/packages/sandbox/sandbox-policy/tsconfig.json index 3a08a053b6..7a1856559d 100644 --- a/packages/sandbox/sandbox-policy/tsconfig.json +++ b/packages/sandbox/sandbox-policy/tsconfig.json @@ -29,9 +29,6 @@ { "path": "../../core/system-prompt" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../session/session-projection" } diff --git a/packages/sandbox/sandbox-policy/tsdown.config.ts b/packages/sandbox/sandbox-policy/tsdown.config.ts index ab8dc26ee8..76c21c44d4 100644 --- a/packages/sandbox/sandbox-policy/tsdown.config.ts +++ b/packages/sandbox/sandbox-policy/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from 'tsdown' -/** Build the package root and invariant companion as independent bundles. */ +/** Build the package root as a self-contained bundle. */ export default defineConfig([ { entry: ['lib/types/index.js'], @@ -12,14 +12,4 @@ export default defineConfig([ dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, - }, ]) diff --git a/packages/sandbox/sandbox-windows-acl/README.i18n.yaml b/packages/sandbox/sandbox-windows-acl/README.i18n.yaml index 4b2ec75127..94a3e37675 100644 --- a/packages/sandbox/sandbox-windows-acl/README.i18n.yaml +++ b/packages/sandbox/sandbox-windows-acl/README.i18n.yaml @@ -63,5 +63,5 @@ en: cc273320f6a1beaf zh: 5c57a312ddc50651 /deepseek-ai-dsh-sandbox-windows-acl/known-limitations-and-deferred-work/dev-note/future-warnings-and-cleanup-surfaces: - en: 4c65be775f25c367 - zh: 467b2ce99030dcf5 + en: 8872c7b4c8ce1cc7 + zh: c7fb4eefa013c7b0 diff --git a/packages/sandbox/sandbox-windows-acl/README.md b/packages/sandbox/sandbox-windows-acl/README.md index dbb5fdef78..732ca20a35 100644 --- a/packages/sandbox/sandbox-windows-acl/README.md +++ b/packages/sandbox/sandbox-windows-acl/README.md @@ -204,5 +204,3 @@ This Dev Note is working context for maintainers: undecided directions and open The warn-only posture for unusually wide directories and FAT-class volumes is documented in the limitations above but not implemented, and a cleanup command that reaps standing workspace ACEs from renamed workspaces is undecided. Both are open directions, not shipped behavior. - -**Runtime invariant:** No companion is published. This package exposes no independent event sequence or mutable data relation beyond the fail-closed contracts it enforces at each Win32 call boundary. diff --git a/packages/sandbox/sandbox-windows-acl/README.zh.md b/packages/sandbox/sandbox-windows-acl/README.zh.md index bf6669168b..ad9de70659 100644 --- a/packages/sandbox/sandbox-windows-acl/README.zh.md +++ b/packages/sandbox/sandbox-windows-acl/README.zh.md @@ -206,5 +206,3 @@ Windows 上多一个目录条目:技能描述随目录进入上下文,正文 对异常宽的目录与 FAT 类卷的仅警告立场已记录在上方限制中但尚未实现,回收改名工作区常驻 ACE 的清理命令也尚未决定。两者都是开放方向,不是已交付行为。 - -**运行时不变式:** 不发布伴生入口。本包没有独立事件序列或可变数据关系;fail-closed 约定在每个 Win32 调用处强制。 diff --git a/packages/sandbox/sandbox/README.i18n.yaml b/packages/sandbox/sandbox/README.i18n.yaml index 05ff012735..63e7c4d2e7 100644 --- a/packages/sandbox/sandbox/README.i18n.yaml +++ b/packages/sandbox/sandbox/README.i18n.yaml @@ -39,8 +39,8 @@ en: 0fc96495c192b513 zh: 38b51b9b3c208012 /deepseek-ai-dsh-sandbox/understand-the-implementation/source-map: - en: 54cab3e684afb031 - zh: b49b476d2f965985 + en: f58b67217cbc147a + zh: b2cbde0342f3f8aa /deepseek-ai-dsh-sandbox/understand-the-implementation/escalation-choreography: en: 1b418d41f571ab3d zh: 62b289cdd6f4d272 diff --git a/packages/sandbox/sandbox/README.md b/packages/sandbox/sandbox/README.md index 5e089c468c..0836dcd8e3 100644 --- a/packages/sandbox/sandbox/README.md +++ b/packages/sandbox/sandbox/README.md @@ -93,7 +93,6 @@ This section explains the design decisions behind the contract and points at the | [`src/index.ts`](src/index.ts) | Plugin entry: `SandboxProvider` service, mode/enforcement/policy types, fail-closed error | | [`src/escalation.ts`](src/escalation.ts) | Escalation vocabulary: wider-mode ladder, argument validation, denial and hint markers, approval choreography | | [`src/roots.ts`](src/roots.ts) | Writable-root derivation shared by the Seatbelt profile and the in-process fs fence | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | ### Escalation choreography diff --git a/packages/sandbox/sandbox/README.zh.md b/packages/sandbox/sandbox/README.zh.md index dc38008d98..f205d3af77 100644 --- a/packages/sandbox/sandbox/README.zh.md +++ b/packages/sandbox/sandbox/README.zh.md @@ -93,7 +93,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:`SandboxProvider` 服务、模式/强制执行/策略类型、故障关闭错误 | | [`src/escalation.ts`](src/escalation.ts) | 升权词汇:更宽模式阶梯、参数校验、拒绝与提示标记、审批编排 | | [`src/roots.ts`](src/roots.ts) | 可写根目录推导,Seatbelt profile 与进程内 fs 栅栏共享 | -| — | 不发布运行时不变式伴生入口;除所属 seam 强制执行的约定外,本包不公开独立的事件序列或可变数据关系。 | ### 升权编排 diff --git a/packages/schedule/schedule/package.json b/packages/schedule/schedule/package.json index e77c78af07..309cf28afd 100644 --- a/packages/schedule/schedule/package.json +++ b/packages/schedule/schedule/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./client": { "types": "./lib/types/client.d.ts", "default": "./lib/types/client.js" @@ -41,7 +37,6 @@ "files": [ "locale/*.json", "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts", "lib/typert.host.js", @@ -54,7 +49,6 @@ "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-api-session-controller": "workspace:*", "@deepseek-ai/dsh-brand": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-persistence": "workspace:*", @@ -77,7 +71,6 @@ "@deepseek-ai/dsh-agent-loop-testkit": "workspace:*", "@deepseek-ai/dsh-api-session-controller": "workspace:*", "@deepseek-ai/dsh-brand": "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/schedule/schedule/src/invariant.ts b/packages/schedule/schedule/src/invariant.ts deleted file mode 100644 index db876c339a..0000000000 --- a/packages/schedule/schedule/src/invariant.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Package-owned strict Schedule stream invariant. - * @module @deepseek-ai/dsh-schedule/invariant - */ - -import type { Context } from '@deepseek-ai/cordis' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import { foldScheduleEvents, ScheduleLogError } from './domain.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-schedule' - -/** Cordis invariant-companion plugin name. */ -export const name = 'schedule-invariant' -/** Service required before reserving this package's invariant ownership. */ -export const inject = ['invariants'] - -/** Validate a complete exact-session stream under its fork suffix policy. */ -function validate(events: readonly SessionEvent[], fail: InvariantFailure): void { - try { - foldScheduleEvents(events) - } catch (error: unknown) { - /* v8 ignore next -- foldScheduleEvents normalizes every rejected stream to ScheduleLogError. */ - if (!(error instanceof ScheduleLogError)) throw error - fail(error.message) - } -} - -/* jscpd:ignore-start -- package companions share replay and dispatch plumbing */ -/** - * Install replay and pre-append validation for the owned event stream. - * - * The durable task table is not asserted here: `scheduleDomain` validates every stored - * record through its table schema when the domain opens, and its only writer stores each - * task under `task.record.id`, so a check of the write payload would observe the same value - * the writer had just built. - */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - for (const session of ctx.sessions.list()) { - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - validate(session.ownEvents(), fail) - } - ctx.on('session/created', (session) => { - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - validate(session.ownEvents(), fail) - }, { global: true }) - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - if (event.type !== 'schedule/change') return - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - validate([...session.ownEvents(), event], fail) - }, { global: true }) -}, { inject: ['sessions'] }) -/* jscpd:ignore-end */ - -/** - * Register the package-owned invariant companion. - * @param ctx - Cordis context carrying the invariant registry. - * @returns Exact registration disposer after child setup succeeds. - */ -export const apply = (ctx: Context): Promise<() => void> => - Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) diff --git a/packages/schedule/schedule/tests/invariant.spec.ts b/packages/schedule/schedule/tests/invariant.spec.ts deleted file mode 100644 index a1d2d146e2..0000000000 --- a/packages/schedule/schedule/tests/invariant.spec.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import InvariantRegistry, { InvariantError } from '@deepseek-ai/dsh-invariants' -import SessionStore, { SessionId, SessionLogOffset, SessionSeq } from '@deepseek-ai/dsh-session' -import type { SessionEvent } from '@deepseek-ai/dsh-session' -import * as scheduleInvariant from '../src/invariant.ts' -import { ScheduleId } from '../src/domain.ts' -import type { ScheduleChange } from '../src/types.ts' - -function event(data: unknown, seq: SessionSeq): SessionEvent { - return { type: 'schedule/change', seq, time: 1, data } as SessionEvent -} - -function create(id: string): ScheduleChange { - return { - version: 1, - operation: 'create', - schedule: { - id: ScheduleId(id), - kind: 'after', - title: 'check logs', - prompt: 'check logs', - afterSeconds: 1, - scheduledAt: '2026-08-05T12:00:01.000Z', - }, - } -} - -function createEvery(id: string): ScheduleChange { - return { - version: 1, - operation: 'create', - schedule: { - id: ScheduleId(id), - kind: 'every', - title: 'check metrics', - prompt: 'check metrics', - everySeconds: 300, - scheduledAt: '2026-08-05T12:05:00.000Z', - }, - } -} - -async function harness() { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry) - const fiber = await ctx.plugin(scheduleInvariant) - return { ctx, fiber } -} - -describe('Schedule package invariant', () => { - it('accepts valid candidates and rejects invalid transitions before append', async () => { - const { ctx } = await harness() - const session = ctx.sessions.create(SessionId('schedule-invariant')) - session.append('turn/start', { turn: 1 }) - session.append('schedule/change', create('schedule-1')) - expect(session.snapshotEvents()).toHaveLength(2) - - expect(() => session.append('schedule/change', { - version: 1, - operation: 'delete', - id: ScheduleId('missing'), - })).toThrow(InvariantError) - expect(session.snapshotEvents()).toHaveLength(2) - - session.append('schedule/change', { version: 1, operation: 'dispatch', id: ScheduleId('schedule-1') }) - expect(session.snapshotEvents()).toHaveLength(3) - await ctx.fiber.dispose() - }) - - it('requires a decision time for Every dispatch and advances the live stream', async () => { - const { ctx } = await harness() - const session = ctx.sessions.create(SessionId('schedule-every-invariant')) - session.append('schedule/change', createEvery('schedule-every')) - expect(() => session.append('schedule/change', { - version: 1, - operation: 'dispatch', - id: ScheduleId('schedule-every'), - })).toThrow(InvariantError) - session.append('schedule/change', { - version: 1, - operation: 'dispatch', - id: ScheduleId('schedule-every'), - acceptedAt: '2026-08-05T12:17:34.000Z', - }) - expect(session.snapshotEvents()).toHaveLength(2) - await ctx.fiber.dispose() - }) - - it('rejects a malformed existing owned stream during companion setup', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry) - ctx.sessions.create(SessionId('schedule-invalid-seed'), { - seed: [event({ version: 9, operation: 'delete', id: 'schedule-1' }, SessionSeq(0))], - }) - await expect(ctx.plugin(scheduleInvariant).then(() => undefined)).rejects.toThrow(InvariantError) - await ctx.fiber.dispose() - }) - - it('rejects a malformed seeded session created after companion setup', async () => { - const { ctx } = await harness() - const id = SessionId('schedule-invalid-future-seed') - expect(() => ctx.sessions.create(id, { - seed: [event({ version: 9, operation: 'delete', id: 'schedule-1' }, SessionSeq(0))], - })).toThrow(InvariantError) - expect(ctx.sessions.get(id)).toBeUndefined() - await ctx.fiber.dispose() - }) - - it('ignores inherited Schedule events before a fork seed boundary', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry) - const child = ctx.sessions.create(SessionId('schedule-fork'), { - seed: [event({ version: 9, operation: 'delete', id: 'parent' }, SessionSeq(0))], - inheritedEventCount: SessionLogOffset(1), - meta: { parentSession: SessionId('parent'), isSeeded: true }, - }) - const fiber = await ctx.plugin(scheduleInvariant) - child.append('schedule/change', create('child')) - expect(child.snapshotEvents().at(-1)?.data).toMatchObject({ operation: 'create' }) - await fiber.dispose() - await ctx.fiber.dispose() - }) -}) diff --git a/packages/schedule/schedule/tests/legacy-history-invariant.spec.ts b/packages/schedule/schedule/tests/legacy-history.spec.ts similarity index 87% rename from packages/schedule/schedule/tests/legacy-history-invariant.spec.ts rename to packages/schedule/schedule/tests/legacy-history.spec.ts index 7a26c654c3..9756448807 100644 --- a/packages/schedule/schedule/tests/legacy-history-invariant.spec.ts +++ b/packages/schedule/schedule/tests/legacy-history.spec.ts @@ -1,9 +1,6 @@ /** * The Schedule `session/created` listener must not veto Session creation when a historical - * `schedule/change` stream is unreadable. This suite excludes the package invariant companion - * on purpose: the companion owns the failure signal and rejects the stream before this - * listener runs, so only a companion-free topology can observe the listener's warn-and-continue - * behavior for a bad historical stream. + * `schedule/change` stream is unreadable. */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { SessionId, SessionSeq } from '@deepseek-ai/dsh-session' diff --git a/packages/schedule/schedule/tsconfig.json b/packages/schedule/schedule/tsconfig.json index 6c0589042e..3667dd3ce9 100644 --- a/packages/schedule/schedule/tsconfig.json +++ b/packages/schedule/schedule/tsconfig.json @@ -14,6 +14,9 @@ { "path": "../../../vendor/cordis" }, + { + "path": "../../../vendor/schemastery" + }, { "path": "../../util/brand" }, @@ -35,9 +38,6 @@ { "path": "../../session/session-persistence-jsonl" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../api/session-controller/tsconfig.host.json" }, @@ -49,9 +49,6 @@ }, { "path": "../../workspace/workspace" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/schedule/schedule/tsdown.config.ts b/packages/schedule/schedule/tsdown.config.ts index ab8dc26ee8..76c21c44d4 100644 --- a/packages/schedule/schedule/tsdown.config.ts +++ b/packages/schedule/schedule/tsdown.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from 'tsdown' -/** Build the package root and invariant companion as independent bundles. */ +/** Build the package root as a self-contained bundle. */ export default defineConfig([ { entry: ['lib/types/index.js'], @@ -12,14 +12,4 @@ export default defineConfig([ dts: false, clean: false, }, - { - entry: ['lib/types/invariant.js'], - outDir: 'lib', - format: ['esm'], - platform: 'node', - target: 'es2024', - fixedExtension: false, - dts: false, - clean: false, - }, ]) diff --git a/packages/sdk/client/README.i18n.yaml b/packages/sdk/client/README.i18n.yaml index ad72cffd69..d1bc56d7aa 100644 --- a/packages/sdk/client/README.i18n.yaml +++ b/packages/sdk/client/README.i18n.yaml @@ -30,8 +30,8 @@ en: 6632c06d819233cc zh: 207d55d8217ec6b1 /deepseek-ai-dsh-sdk-client/understand-the-implementation/source-map: - en: f8a986c22b7a2953 - zh: 35937eb44e1ee909 + en: a3c7978a80f5aba3 + zh: fdc4dfdafd2eccfa /deepseek-ai-dsh-sdk-client/understand-the-implementation/owned-activity-flow: en: 94a42babac5f1c7d zh: df9e42ed02f7d002 diff --git a/packages/sdk/client/README.md b/packages/sdk/client/README.md index bfbd522d12..baabd41713 100644 --- a/packages/sdk/client/README.md +++ b/packages/sdk/client/README.md @@ -76,7 +76,6 @@ The client is two layers over one wire: `DeepSeekHarness` (owned runs) over `Har | [`src/dispose.ts`](src/dispose.ts) | Private teardown ladder: stdin EOF → SIGTERM → SIGKILL to actual exit | | [`src/types.ts`](src/types.ts) | Launch and timeout options, notification shapes, `RunResult` | | [`src/index.ts`](src/index.ts) | Consumer interface: the two client layers and caller-facing types | -| — | No runtime invariant companion is published; this client library runs outside any harness context (its peer is a separate runtime process); the runtime's own packages own the event-stream relations. | ### Owned activity flow diff --git a/packages/sdk/client/README.zh.md b/packages/sdk/client/README.zh.md index ec54e8ae0e..45a406ee9b 100644 --- a/packages/sdk/client/README.zh.md +++ b/packages/sdk/client/README.zh.md @@ -76,7 +76,6 @@ console.log(result.finalResponse) | [`src/dispose.ts`](src/dispose.ts) | 私有关闭阶梯:stdin EOF → SIGTERM → SIGKILL 直到真正退出 | | [`src/types.ts`](src/types.ts) | 启动与超时选项、通知结构、`RunResult` | | [`src/index.ts`](src/index.ts) | 消费方接口:两层客户端与面向调用方的类型 | -| — | 不发布运行时不变式伴生入口;本客户端库运行在任何 harness 上下文之外(其对端是独立运行时进程);运行时自身的包负责维护事件流关系。 | ### 自有活动流程 diff --git a/packages/sdk/protocol/README.i18n.yaml b/packages/sdk/protocol/README.i18n.yaml index e935628d25..d39a8a2482 100644 --- a/packages/sdk/protocol/README.i18n.yaml +++ b/packages/sdk/protocol/README.i18n.yaml @@ -33,8 +33,8 @@ en: bc4b8dfc36ed4565 zh: 6a707eff1a4bbf2f /deepseek-ai-dsh-sdk-protocol/understand-the-implementation/source-map: - en: bfeb9999d7e71aaf - zh: afc873a229bb4bc5 + en: b62c1e92501ebc93 + zh: d1ac1a11653e97ed /deepseek-ai-dsh-sdk-protocol/understand-the-implementation/frame-dispatch: en: eaf7d095810481c8 zh: 92767f908500be5e diff --git a/packages/sdk/protocol/README.md b/packages/sdk/protocol/README.md index 7cc6a28ac4..a104e8ff9a 100644 --- a/packages/sdk/protocol/README.md +++ b/packages/sdk/protocol/README.md @@ -72,7 +72,6 @@ The package is built on one separation: a single newline-delimited transport cla | [`src/transport.ts`](src/transport.ts) | `JsonRpcLineTransport`: line framing, request/response/notification dispatch, error mapping, pending-request bookkeeping | | [`src/types.ts`](src/types.ts) | Named request/result and notification payload types, indexed by method | | [`src/index.ts`](src/index.ts) | Consumer interface: the transport and the named wire types | -| — | No runtime invariant companion is published; a pure wire library (transport class + type declarations) with no event stream or mutable data relation of its own; both wire ends own their protocol behavior. | ### Frame dispatch diff --git a/packages/sdk/protocol/README.zh.md b/packages/sdk/protocol/README.zh.md index 69b85d6ca7..cc5a6fb7ba 100644 --- a/packages/sdk/protocol/README.zh.md +++ b/packages/sdk/protocol/README.zh.md @@ -72,7 +72,6 @@ kind: "package-library" | [`src/transport.ts`](src/transport.ts) | `JsonRpcLineTransport`:行分帧、请求/响应/通知分发、错误映射、挂起请求记账 | | [`src/types.ts`](src/types.ts) | 具名请求/结果与通知载荷类型,按方法索引 | | [`src/index.ts`](src/index.ts) | 消费方接口:传输与具名协议类型 | -| — | 不发布运行时不变式伴生入口;这是一个由传输类和类型声明组成的纯协议库,自身没有事件流或可变数据关系;两个协议端各自负责其协议行为。 | ### 帧分发 diff --git a/packages/sdk/server/README.i18n.yaml b/packages/sdk/server/README.i18n.yaml index fa434758ab..87b193664b 100644 --- a/packages/sdk/server/README.i18n.yaml +++ b/packages/sdk/server/README.i18n.yaml @@ -39,8 +39,8 @@ en: e83e01184c1a45ee zh: d54ed126d31ee6f1 /deepseek-ai-dsh-sdk-jsonrpc-server/understand-the-implementation/source-map: - en: 27474bf15222a95a - zh: f293e9b9d9d5dca5 + en: 9ef78eca4d95d97c + zh: 45ff05f4d6c1cd0b /deepseek-ai-dsh-sdk-jsonrpc-server/understand-the-implementation/request-flow: en: 2658804b2f90b135 zh: 085aa00c8f0bbbcd diff --git a/packages/sdk/server/README.md b/packages/sdk/server/README.md index 3598fc38c1..8ee8e02637 100644 --- a/packages/sdk/server/README.md +++ b/packages/sdk/server/README.md @@ -71,7 +71,6 @@ The plugin is a thin presentation adapter: [`HarnessSdkJsonRpcServer`](src/serve |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, stdio wiring, request dispatch, shared shutdown/exit task | | [`src/server.ts`](src/server.ts) | `HarnessSdkJsonRpcServer`: protocol methods, per-session agent creation, lifecycle subscriptions, teardown | -| — | No runtime invariant companion is published; this presentation adapter owns no durable package-local event stream; boundary and replay tests cover its protocol mapping. | ### Request flow diff --git a/packages/sdk/server/README.zh.md b/packages/sdk/server/README.zh.md index 9fb80288c1..b7a4edb01d 100644 --- a/packages/sdk/server/README.zh.md +++ b/packages/sdk/server/README.zh.md @@ -71,7 +71,6 @@ Stdout 只承载 JSON-RPC 帧,客户端可以逐字节解析;诊断信息应 |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、stdio 接线、请求分发、共享关闭与退出任务 | | [`src/server.ts`](src/server.ts) | `HarnessSdkJsonRpcServer`:协议方法、逐会话 agent 创建、生命周期订阅、清理 | -| — | 不发布运行时不变式伴生入口;此展示适配器不拥有包内持久事件流;边界与回放测试覆盖协议映射。 | ### 请求流程 diff --git a/packages/session-query/session-log-export/README.i18n.yaml b/packages/session-query/session-log-export/README.i18n.yaml index fc63e8501f..5bcd32d6c0 100644 --- a/packages/session-query/session-log-export/README.i18n.yaml +++ b/packages/session-query/session-log-export/README.i18n.yaml @@ -69,5 +69,5 @@ en: 4ee4d24c26c127bf zh: 1facdfbfa3ccefbc /deepseek-ai-dsh-session-log-export/known-limitations-and-deferred-work/dev-note/future-export-destinations-beyond-the-browser: - en: 226726899a479680 - zh: 26f72519513d4782 + en: 820a5a5f2c5471cd + zh: 59d5852372a1f3ed diff --git a/packages/session-query/session-log-export/README.md b/packages/session-query/session-log-export/README.md index ea55b5ddcc..79eefc0a3b 100644 --- a/packages/session-query/session-log-export/README.md +++ b/packages/session-query/session-log-export/README.md @@ -143,5 +143,3 @@ This Dev Note is working context for maintainers: open design questions and dire The download is deliberately browser-scoped; a Host-path or native folder export would need a new endpoint contract and a decision on where the ZIP lands. - -**Runtime invariant:** No companion is published. Connection and the command registry own both registrations, while each export reads authoritative Session services. diff --git a/packages/session-query/session-log-export/README.zh.md b/packages/session-query/session-log-export/README.zh.md index a1c4d925b9..148102ec04 100644 --- a/packages/session-query/session-log-export/README.zh.md +++ b/packages/session-query/session-log-export/README.zh.md @@ -143,5 +143,3 @@ Host 路由是由该功能拥有的精确 Fetch 路由贡献。Connection 应用 下载刻意限定在浏览器范围;Host 路径或原生文件夹导出需要新的端点约定,并决定 ZIP 的落盘位置。 - -**运行时不变式:** 不发布伴生入口。Connection 与命令注册表持有两个注册,每次导出均读取权威的 Session 服务。 diff --git a/packages/session-query/session-query-sqlite/README.i18n.yaml b/packages/session-query/session-query-sqlite/README.i18n.yaml index 0661de6e19..4283f80008 100644 --- a/packages/session-query/session-query-sqlite/README.i18n.yaml +++ b/packages/session-query/session-query-sqlite/README.i18n.yaml @@ -39,8 +39,8 @@ en: 97d8f71fda5ed9b6 zh: fe1d0bd886dc2437 /deepseek-ai-dsh-session-query-sqlite/understand-the-implementation/source-map: - en: 49f5cc363a886430 - zh: f0e035cc2dc8d513 + en: b3815ff8f97ba2c2 + zh: 2955152a8f33b40a /deepseek-ai-dsh-session-query-sqlite/understand-the-implementation/index-lifecycle: en: 3a99cf9f8f64ec0e zh: 40585b4764282881 diff --git a/packages/session-query/session-query-sqlite/README.md b/packages/session-query/session-query-sqlite/README.md index da99dc20ee..570af4d43f 100644 --- a/packages/session-query/session-query-sqlite/README.md +++ b/packages/session-query/session-query-sqlite/README.md @@ -98,7 +98,6 @@ The design history lives in the [SQLite FTS5 session search note](../../../.agen | [`src/index.ts`](src/index.ts) | Service: config, openAt lifecycle, serialized reconciliation, query execution, cursors | | [`src/query.ts`](src/query.ts) | Request normalization, parameterized predicates, snippets, predicate and binding budgets | | [`src/schema.ts`](src/schema.ts) | Database schema, application-id ownership, in-place reset, owner-only file creation | -| — | No runtime invariant companion is published; reconciliation, cursor generations, and derived-index ownership are validated at each serialized query boundary. | ### Index lifecycle diff --git a/packages/session-query/session-query-sqlite/README.zh.md b/packages/session-query/session-query-sqlite/README.zh.md index 4ba088fc0d..32055370e8 100644 --- a/packages/session-query/session-query-sqlite/README.zh.md +++ b/packages/session-query/session-query-sqlite/README.zh.md @@ -98,7 +98,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 服务:配置、openAt 生命周期、串行化对账、查询执行、游标 | | [`src/query.ts`](src/query.ts) | 请求规范化、参数化谓词、摘录、谓词与绑定预算 | | [`src/schema.ts`](src/schema.ts) | 数据库 schema、application id 归属、原地重置、仅所有者文件创建 | -| — | 不发布运行时不变式伴生入口;系统会在每次串行化查询边界校验对账、游标世代与派生索引归属。 | ### 索引生命周期 diff --git a/packages/session-query/session-query/README.i18n.yaml b/packages/session-query/session-query/README.i18n.yaml index 617e7982a8..ccbe002f80 100644 --- a/packages/session-query/session-query/README.i18n.yaml +++ b/packages/session-query/session-query/README.i18n.yaml @@ -36,8 +36,8 @@ en: b34fb0d209192ff4 zh: 455bdb024d04f16a /deepseek-ai-dsh-session-query/understand-the-implementation/source-map: - en: 7c546162fa3f75d4 - zh: c6910bc53ae0ff71 + en: c58f4f086e3d0728 + zh: 7d66b688c7f54c87 /deepseek-ai-dsh-session-query/understand-the-implementation/corpus-resolution: en: efa560c55a869c71 zh: 451eb107825856ac diff --git a/packages/session-query/session-query/README.md b/packages/session-query/session-query/README.md index f9246b6f3b..5706ff3827 100644 --- a/packages/session-query/session-query/README.md +++ b/packages/session-query/session-query/README.md @@ -100,7 +100,6 @@ The decision history lives in the [unified service decision](../../../.agents/no | [`src/documents.ts`](src/documents.ts) | Surface-aware semantic document projection | | [`src/tracing.ts`](src/tracing.ts) | One-shot session-lineage and event-relationship tracing | | [`src/sources.ts`](src/sources.ts) | Immutable-header compatibility check | -| — | No runtime invariant companion is published; query results are immutable per-call projections whose lineage and event relations are validated while they are built; the service retains no observable result state. | ### Corpus resolution diff --git a/packages/session-query/session-query/README.zh.md b/packages/session-query/session-query/README.zh.md index 9d68910deb..4f83a1187e 100644 --- a/packages/session-query/session-query/README.zh.md +++ b/packages/session-query/session-query/README.zh.md @@ -100,7 +100,6 @@ kind: "package-reference" | [`src/documents.ts`](src/documents.ts) | 表层感知的语义文档投影 | | [`src/tracing.ts`](src/tracing.ts) | 一次性会话血缘与事件关系追踪 | | [`src/sources.ts`](src/sources.ts) | 不可变 header 兼容性检查 | -| — | 不发布运行时不变式伴生入口;查询结果是每次调用产生的不可变投影,其血缘与事件关系会在构建时完成校验;服务不保留可观察的结果状态。 | ### 语料库解析 diff --git a/packages/session-query/tool-session-query/README.i18n.yaml b/packages/session-query/tool-session-query/README.i18n.yaml index 1c8ed85d46..d00e59da33 100644 --- a/packages/session-query/tool-session-query/README.i18n.yaml +++ b/packages/session-query/tool-session-query/README.i18n.yaml @@ -93,5 +93,5 @@ en: d2f0d68fe29fb750 zh: 323d21ee73a9e3f6 /deepseek-ai-dsh-tool-session-query/known-limitations-and-deferred-work/dev-note/future-broader-workspace-semantics: - en: 540af3b250d4486d - zh: 5eae2bc1644b92b5 + en: 10cb8ae346c8183f + zh: d37674a97b5f99d5 diff --git a/packages/session-query/tool-session-query/README.md b/packages/session-query/tool-session-query/README.md index 61aca36ef9..ff8f1c03f6 100644 --- a/packages/session-query/tool-session-query/README.md +++ b/packages/session-query/tool-session-query/README.md @@ -184,5 +184,3 @@ This Dev Note is working context for maintainers: open design questions and dire Exact-string `cwd` equality is deliberately conservative; symlink-aware or canonical-path workspace identity would change which sessions share authority and is undecided. - -**Runtime invariant:** No companion is published. This read-only model adapter owns no event or mutable data relationship beyond the registries that already validate registration. diff --git a/packages/session-query/tool-session-query/README.zh.md b/packages/session-query/tool-session-query/README.zh.md index 2e390e37a6..b8410b6148 100644 --- a/packages/session-query/tool-session-query/README.zh.md +++ b/packages/session-query/tool-session-query/README.zh.md @@ -184,5 +184,3 @@ Use session_search to find relevant work from prior sessions, or session_event_s 字符串精确 `cwd` 相等是刻意保守的选择;符号链接感知或规范路径的工作区身份会改变哪些会话共享权限,尚未决定。 - -**运行时不变式:** 不发布伴生入口。这个只读模型适配器不拥有任何超出注册表范围的事件关系或可变数据关系;这些注册表已经负责校验注册。 diff --git a/packages/session/session-checkpoint-policy/README.i18n.yaml b/packages/session/session-checkpoint-policy/README.i18n.yaml index f1d5d301b8..c4cf350643 100644 --- a/packages/session/session-checkpoint-policy/README.i18n.yaml +++ b/packages/session/session-checkpoint-policy/README.i18n.yaml @@ -36,8 +36,8 @@ en: cd40c7a1f0db9efb zh: 17115c9612497465 /deepseek-ai-dsh-session-checkpoint-policy/understand-the-implementation/source-map: - en: f9d06ce5e6ca68c1 - zh: 08c4c7a170f33737 + en: b5d34d94faba3266 + zh: 522350af3f2ed1e8 /deepseek-ai-dsh-session-checkpoint-policy/further-exploration: en: d9b34c4e3ef78f1d zh: 73e98eecc32ee654 diff --git a/packages/session/session-checkpoint-policy/README.md b/packages/session/session-checkpoint-policy/README.md index 0114ffb82b..afc781dfa6 100644 --- a/packages/session/session-checkpoint-policy/README.md +++ b/packages/session/session-checkpoint-policy/README.md @@ -70,7 +70,6 @@ The plugin is a listener-only composition over three seams, with no state of its | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `apply` installs the three checkpoint listeners | -| — | No runtime invariant companion is published; checkpoint ordering is enforced at the intercepted waterfall and persistence seams; this stateless policy owns no independent mutable relation. | diff --git a/packages/session/session-checkpoint-policy/README.zh.md b/packages/session/session-checkpoint-policy/README.zh.md index 2abe4d4d6e..e9232afd61 100644 --- a/packages/session/session-checkpoint-policy/README.zh.md +++ b/packages/session/session-checkpoint-policy/README.zh.md @@ -70,7 +70,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`apply` 安装三个检查点监听器 | -| — | 不发布运行时不变式伴生入口;顺序由被拦截的 seam 强制。 | diff --git a/packages/session/session-format-catalog/README.i18n.yaml b/packages/session/session-format-catalog/README.i18n.yaml index b8f3360a57..b649ecd50b 100644 --- a/packages/session/session-format-catalog/README.i18n.yaml +++ b/packages/session/session-format-catalog/README.i18n.yaml @@ -18,8 +18,8 @@ en: bbff250eba0f1b89 zh: 0a618f05cc88ee2b /deepseek-ai-dsh-session-format-catalog/use-this-package/when-to-use-it: - en: 705e341a9189d58f - zh: 849007446a7b8a46 + en: 896dc92060e0eaa4 + zh: 93bf0af6ee266b99 /deepseek-ai-dsh-session-format-catalog/use-this-package/entry-point: en: f955b2fb458cf64d zh: 0e0b6f025be928a7 diff --git a/packages/session/session-format-catalog/README.md b/packages/session/session-format-catalog/README.md index 71caf0ac37..5fad64d421 100644 --- a/packages/session/session-format-catalog/README.md +++ b/packages/session/session-format-catalog/README.md @@ -27,7 +27,7 @@ English | [中文](README.zh.md) ### When to use it -Import this library from persistence and test-support readers that need the complete first-party released-format inventory before any feature plugin mounts. Feature compositions do not register or reorder its entries. No runtime invariant companion is published because construction rejects an invalid static inventory and each completed restore validates its result; mutable row-decoder state belongs to one caller-owned streaming restore. +Import this library from persistence and test-support readers that need the complete first-party released-format inventory before any feature plugin mounts. Feature compositions do not register or reorder its entries. ### Entry point diff --git a/packages/session/session-format-catalog/README.zh.md b/packages/session/session-format-catalog/README.zh.md index ab9cbe1e7c..664567ca07 100644 --- a/packages/session/session-format-catalog/README.zh.md +++ b/packages/session/session-format-catalog/README.zh.md @@ -27,7 +27,7 @@ kind: "package-library" ### 何时使用 -当持久化与测试支持读取方需要在任何功能插件挂载前取得完整第一方已发布格式清单时,导入本库。功能组合不会注册或重排其条目。它不发布运行时不变式伴生入口,因为构造过程会拒绝无效静态清单,每次完成的还原也会校验结果;可变行 decoder 状态只属于一次由调用方持有的流式还原。 +当持久化与测试支持读取方需要在任何功能插件挂载前取得完整第一方已发布格式清单时,导入本库。功能组合不会注册或重排其条目。 ### 入口 diff --git a/packages/session/session-format-v0-to-v1/README.i18n.yaml b/packages/session/session-format-v0-to-v1/README.i18n.yaml index f5d06edff4..13c4611bd6 100644 --- a/packages/session/session-format-v0-to-v1/README.i18n.yaml +++ b/packages/session/session-format-v0-to-v1/README.i18n.yaml @@ -18,8 +18,8 @@ en: bbff250eba0f1b89 zh: 0a618f05cc88ee2b /deepseek-ai-dsh-session-format-v0-to-v1/use-this-package/when-to-use-it: - en: 7e0ddf0932b414b4 - zh: 4bf4dd76d465e490 + en: c6464bbd5e9ffaf0 + zh: b96eb7762b924627 /deepseek-ai-dsh-session-format-v0-to-v1/use-this-package/entry-point: en: 12e0858e80044921 zh: 88b4e9709ce24d7d diff --git a/packages/session/session-format-v0-to-v1/README.md b/packages/session/session-format-v0-to-v1/README.md index 2ff830f486..f6b72a58b0 100644 --- a/packages/session/session-format-v0-to-v1/README.md +++ b/packages/session/session-format-v0-to-v1/README.md @@ -27,7 +27,7 @@ This package restores released v0 Session JSONL by decoding each physical row an ### When to use it -Persistence obtains this edge through `dsh-session-format-catalog`; feature compositions do not mount it. Import it directly only when assembling or testing the static released-format catalog. No runtime invariant companion is published because the package has no independently observable runtime registrations whose state can diverge; decoder and migration-stage state belongs to one restore. +Persistence obtains this edge through `dsh-session-format-catalog`; feature compositions do not mount it. Import it directly only when assembling or testing the static released-format catalog. ### Entry point diff --git a/packages/session/session-format-v0-to-v1/README.zh.md b/packages/session/session-format-v0-to-v1/README.zh.md index 057109a70a..6d3ea0849c 100644 --- a/packages/session/session-format-v0-to-v1/README.zh.md +++ b/packages/session/session-format-v0-to-v1/README.zh.md @@ -27,7 +27,7 @@ kind: "package-library" ### 何时使用 -持久化通过 `dsh-session-format-catalog` 获取该迁移边;功能组合不会挂载它。只有在装配或测试静态已发布格式目录时,才直接导入本包。它不发布运行时不变式伴生入口,因为本包没有状态可能彼此分歧的、可独立观测的运行时注册项;decoder 与 migration stage 的状态只属于一次还原。 +持久化通过 `dsh-session-format-catalog` 获取该迁移边;功能组合不会挂载它。只有在装配或测试静态已发布格式目录时,才直接导入本包。 ### 入口 diff --git a/packages/session/session-format-v1-to-v2/README.i18n.yaml b/packages/session/session-format-v1-to-v2/README.i18n.yaml index 07d3093543..f6133f4b4b 100644 --- a/packages/session/session-format-v1-to-v2/README.i18n.yaml +++ b/packages/session/session-format-v1-to-v2/README.i18n.yaml @@ -18,8 +18,8 @@ en: bbff250eba0f1b89 zh: 0a618f05cc88ee2b /deepseek-ai-dsh-session-format-v1-to-v2/use-this-package/when-to-use-it: - en: 09ce83434449c1c2 - zh: 93423b64852fe877 + en: a4045266992e9714 + zh: 3153e2df5611865a /deepseek-ai-dsh-session-format-v1-to-v2/use-this-package/entry-point: en: da1fa33842f615b6 zh: a43323304d35e4e8 diff --git a/packages/session/session-format-v1-to-v2/README.md b/packages/session/session-format-v1-to-v2/README.md index 9fc2e2b837..50a79741e3 100644 --- a/packages/session/session-format-v1-to-v2/README.md +++ b/packages/session/session-format-v1-to-v2/README.md @@ -27,7 +27,7 @@ English | [中文](README.zh.md) ### When to use it -Persistence obtains this edge through `dsh-session-format-catalog`; feature compositions do not mount it. Import it directly only when assembling or testing the static released-format catalog or inspecting the exact v1-to-v2 transformation. No runtime invariant companion is published because the package has no independently observable runtime registrations whose state can diverge; decoder and transformer state belongs to one restore. +Persistence obtains this edge through `dsh-session-format-catalog`; feature compositions do not mount it. Import it directly only when assembling or testing the static released-format catalog or inspecting the exact v1-to-v2 transformation. ### Entry point diff --git a/packages/session/session-format-v1-to-v2/README.zh.md b/packages/session/session-format-v1-to-v2/README.zh.md index 9bc82a5b16..fd7e21fba8 100644 --- a/packages/session/session-format-v1-to-v2/README.zh.md +++ b/packages/session/session-format-v1-to-v2/README.zh.md @@ -27,7 +27,7 @@ kind: "package-reference" ### 何时使用 -持久化通过 `dsh-session-format-catalog` 获取该迁移边;功能组合不会挂载它。只有在装配或测试静态已发布格式目录,或检查精确的 v1 到 v2 转换时,才直接导入本包。它不发布运行时不变式伴生入口,因为本包没有状态可能彼此分歧的、可独立观测的运行时注册项;decoder 与 transformer 状态只属于一次还原。 +持久化通过 `dsh-session-format-catalog` 获取该迁移边;功能组合不会挂载它。只有在装配或测试静态已发布格式目录,或检查精确的 v1 到 v2 转换时,才直接导入本包。 ### 入口 diff --git a/packages/session/session-format-v2-to-v3/README.i18n.yaml b/packages/session/session-format-v2-to-v3/README.i18n.yaml index 1ab0cb56d7..c7a98a0f4a 100644 --- a/packages/session/session-format-v2-to-v3/README.i18n.yaml +++ b/packages/session/session-format-v2-to-v3/README.i18n.yaml @@ -51,8 +51,8 @@ en: 2f9127759a38289f zh: d3c7b539f5e3c670 /deepseek-ai-dsh-session-format-v2-to-v3/understand-the-implementation: - en: 6d630626cd7a5017 - zh: 8312cc7bd3abcab6 + en: 65a3f3ea2eeecc3e + zh: 5f3b0d5cd55c6baa /deepseek-ai-dsh-session-format-v2-to-v3/further-exploration: en: 2c07a2d0f6157c4a zh: db4910ffa738dd72 diff --git a/packages/session/session-format-v2-to-v3/README.md b/packages/session/session-format-v2-to-v3/README.md index 3f3fe7255a..368f8e5747 100644 --- a/packages/session/session-format-v2-to-v3/README.md +++ b/packages/session/session-format-v2-to-v3/README.md @@ -155,7 +155,7 @@ Input already marked V3 does not run V2-to-V3. Native catalog reads with `valida
Implementation internals — click to expand -The [stage](src/migration.ts) owns synchronous per-artifact sequence maps, message identity sets, and prompt/lifecycle state. Compact runs expand incrementally. The [codec](src/codec.ts) reuses frozen V2 framing; the [restorer](src/validation.ts) validates V3 structure before giving frozen ordinary relationship validation a private system/PTC/repair-id and endpoint view. That view retains the actual target generation for delivery checks and never escapes: restoration returns the original V3 artifact and identities. Frozen V0-to-V1 and V1-to-V2 semantics remain unchanged. No runtime invariant companion is published because this library owns no independently observable registrations or state replicas. +The [stage](src/migration.ts) owns synchronous per-artifact sequence maps, message identity sets, and prompt/lifecycle state. Compact runs expand incrementally. The [codec](src/codec.ts) reuses frozen V2 framing; the [restorer](src/validation.ts) validates V3 structure before giving frozen ordinary relationship validation a private system/PTC/repair-id and endpoint view. That view retains the actual target generation for delivery checks and never escapes: restoration returns the original V3 artifact and identities. Frozen V0-to-V1 and V1-to-V2 semantics remain unchanged. [Combined catalog tests](tests/combined-migration.spec.ts) exercise transformation composition and native reopen; [migration tests](tests/migration.spec.ts) and [canonical tests](tests/canonical-envelopes.spec.ts) pin preservation and refusal. [Persistence integration](../session-persistence-jsonl/tests/v2-ptc-migration.spec.ts) owns publication evidence. The [released-format decision](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md) owns the rationale for testing adjacent composition separately from native admission. diff --git a/packages/session/session-format-v2-to-v3/README.zh.md b/packages/session/session-format-v2-to-v3/README.zh.md index 8f26cfcb68..0086abc365 100644 --- a/packages/session/session-format-v2-to-v3/README.zh.md +++ b/packages/session/session-format-v2-to-v3/README.zh.md @@ -155,7 +155,7 @@ V2 `session-log-deepseek/delivery-accepted` 若携带 `data.sessionFormatVersion
实现细节 — 点击展开 -[阶段](src/migration.ts)拥有每份产物独立的同步序列映射、消息身份集合和提示词/生命周期状态。紧凑事件段增量展开。[编解码器](src/codec.ts)复用冻结的 V2 分帧;[恢复器](src/validation.ts)先校验 V3 结构,再向冻结的普通关系校验提供私有 system/PTC/修复标识与端点视图。该视图为投递检查保留实际目标代次,且绝不对外返回:恢复返回原始 V3 产物与身份。冻结的 V0 到 V1 和 V1 到 V2 语义保持不变。本库不拥有可独立观察的注册或状态副本,因此不发布运行时不变量伴随入口。 +[阶段](src/migration.ts)拥有每份产物独立的同步序列映射、消息身份集合和提示词/生命周期状态。紧凑事件段增量展开。[编解码器](src/codec.ts)复用冻结的 V2 分帧;[恢复器](src/validation.ts)先校验 V3 结构,再向冻结的普通关系校验提供私有 system/PTC/修复标识与端点视图。该视图为投递检查保留实际目标代次,且绝不对外返回:恢复返回原始 V3 产物与身份。冻结的 V0 到 V1 和 V1 到 V2 语义保持不变。 [组合目录测试](tests/combined-migration.spec.ts)验证转换组合与原生重新打开;[迁移测试](tests/migration.spec.ts)和[规范测试](tests/canonical-envelopes.spec.ts)固定保留与拒绝规则。[持久化集成](../session-persistence-jsonl/tests/v2-ptc-migration.spec.ts)负责发布证据。[已发布格式决策](../../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md)负责将相邻组合测试与原生准入测试分开的依据。 diff --git a/packages/session/session-format-v3-to-v4/README.i18n.yaml b/packages/session/session-format-v3-to-v4/README.i18n.yaml index f6e87bfbfe..080ca3e8cf 100644 --- a/packages/session/session-format-v3-to-v4/README.i18n.yaml +++ b/packages/session/session-format-v3-to-v4/README.i18n.yaml @@ -63,8 +63,8 @@ en: 1d6db95cf7a82846 zh: 837c690fe87f47db /deepseek-ai-dsh-session-format-v3-to-v4/understand-the-implementation: - en: d27e081c4e2ecd86 - zh: 0dd639484f67dba9 + en: bd4d2271b5844ca1 + zh: b5d00e78d6085a25 /deepseek-ai-dsh-session-format-v3-to-v4/further-exploration: en: d54326f96c09a0a0 zh: 862147d701ce9049 diff --git a/packages/session/session-format-v3-to-v4/README.md b/packages/session/session-format-v3-to-v4/README.md index 35f38cb888..331f170b3b 100644 --- a/packages/session/session-format-v3-to-v4/README.md +++ b/packages/session/session-format-v3-to-v4/README.md @@ -294,7 +294,7 @@ Unknown required events are refused by vocabulary-aware restoration. Unknown ign The migration declaration creates independent streaming stages. Compact runs expand as iterables without an intermediate event array. The V3-to-V4 stage rewrites historical message sources, lifts historical tool-result wrappers, and inserts evidenced interrupted turn endings while emitting V4 events. It retains one source-to-target sequence entry per source event for local reference remapping. The V4 codec uses the released V2 codec only for physical header and source-range framing, and validates native tool-role rows directly; it does not invoke the released V3 validator or source-conversion views. JSONL scanners call `assertV4RowAdmission` before suppressing recoverable rows and the shared mandatory relationship validator before returning the completed logical prefix. -The target restorer validates native fields and mandatory cross-event relationships, then returns the original artifact. Unknown ignorable events remain opaque, and unfinished inherited compactions expire at the end-seed marker. No runtime invariant companion is published because this pure library owns no independently maintained runtime observations. +The target restorer validates native fields and mandatory cross-event relationships, then returns the original artifact. Unknown ignorable events remain opaque, and unfinished inherited compactions expire at the end-seed marker.
diff --git a/packages/session/session-format-v3-to-v4/README.zh.md b/packages/session/session-format-v3-to-v4/README.zh.md index 865ced296d..7735067095 100644 --- a/packages/session/session-format-v3-to-v4/README.zh.md +++ b/packages/session/session-format-v3-to-v4/README.zh.md @@ -294,7 +294,7 @@ Fork 种子构造归核心 Session 所有,不属于此迁移。原生 V4 接 迁移声明创建相互独立的流式 Stage。紧凑事件段通过迭代器展开,不生成中间事件数组。V3 到 V4 Stage 在发出 V4 事件时重写历史消息来源、提升历史工具结果包装,并插入有明确证据的中断回合结束事件。它为每个源事件保留一个源到目标的序号映射项,用于本地引用重映射。V4 编解码器只为物理头部和源范围分帧使用已发布 V2 编解码器,并直接校验原生工具角色行;它不会调用已发布 V3 校验器或源转换视图。JSONL 扫描器在抑制可恢复行之前调用 `assertV4RowAdmission`,并在返回完整逻辑前缀之前调用共用的强制关系校验器。 -目标恢复器校验原生字段和强制跨事件关系,然后返回原始产物。未知的可忽略事件保持不透明,未完成的继承压缩事务在 end-seed 标记处结束。本包不发布运行时不变量伴随插件,因为这个纯函数库不拥有独立维护的运行时观测。 +目标恢复器校验原生字段和强制跨事件关系,然后返回原始产物。未知的可忽略事件保持不透明,未完成的继承压缩事务在 end-seed 标记处结束。
diff --git a/packages/session/session-format/README.i18n.yaml b/packages/session/session-format/README.i18n.yaml index 17d139e04f..4154651acd 100644 --- a/packages/session/session-format/README.i18n.yaml +++ b/packages/session/session-format/README.i18n.yaml @@ -18,8 +18,8 @@ en: bbff250eba0f1b89 zh: 0a618f05cc88ee2b /deepseek-ai-dsh-session-format/use-this-package/when-to-use-it: - en: 0eb581e31a01e375 - zh: 265952f3c3be73d4 + en: 41ecd1db865a9966 + zh: 54cdfb32cde4df8a /deepseek-ai-dsh-session-format/use-this-package/entry-point: en: 8d67ddec8cc4dea3 zh: 0dede278e7c90f37 diff --git a/packages/session/session-format/README.md b/packages/session/session-format/README.md index 040d338386..9ec5e4d5c5 100644 --- a/packages/session/session-format/README.md +++ b/packages/session/session-format/README.md @@ -27,7 +27,7 @@ English | [中文](README.zh.md) ### When to use it -Use this library from persistence or format-catalog code that must classify a physical Session header, restore current logical values, or compose released adjacent migrations. It is not a Cordis plugin and has no profile mount row. No runtime invariant companion is published because each completed operation validates its result; decoder and transformer state belongs to one unfinished streaming restore and is never shared across restores. +Use this library from persistence or format-catalog code that must classify a physical Session header, restore current logical values, or compose released adjacent migrations. It is not a Cordis plugin and has no profile mount row. ### Entry point diff --git a/packages/session/session-format/README.zh.md b/packages/session/session-format/README.zh.md index 48daefb306..f0d5e12075 100644 --- a/packages/session/session-format/README.zh.md +++ b/packages/session/session-format/README.zh.md @@ -27,7 +27,7 @@ kind: "package-library" ### 何时使用 -当持久化或格式目录代码需要分类物理会话 header、还原当前逻辑值或组合已发布相邻迁移时,使用本库。它不是 Cordis 插件,也没有 profile 挂载行。它不发布运行时不变式伴生入口,因为每个已完成操作都会校验结果;decoder 与 transformer 状态只属于一次尚未完成的流式还原,绝不在多次还原间共享。 +当持久化或格式目录代码需要分类物理会话 header、还原当前逻辑值或组合已发布相邻迁移时,使用本库。它不是 Cordis 插件,也没有 profile 挂载行。 ### 入口 diff --git a/packages/session/session-log-deepseek/package.json b/packages/session/session-log-deepseek/package.json index bf6173325e..3d0f73170a 100644 --- a/packages/session/session-log-deepseek/package.json +++ b/packages/session/session-log-deepseek/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -31,7 +27,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -44,7 +39,6 @@ "peerDependencies": { "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*" }, "devDependencies": { @@ -53,7 +47,6 @@ "@deepseek-ai/cordis-plugin-loader": "workspace:~", "@deepseek-ai/dsh-command-feedback": "workspace:*", "@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-llm-deepseek": "workspace:*", "@deepseek-ai/dsh-llm-deepseek-api-key": "workspace:*", diff --git a/packages/session/session-log-deepseek/src/invariant.ts b/packages/session/session-log-deepseek/src/invariant.ts deleted file mode 100644 index f01d5aef9e..0000000000 --- a/packages/session/session-log-deepseek/src/invariant.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** Package-owned invariants for DeepSeek session-log acceptance watermarks. */ - -import type { Context } from '@deepseek-ai/cordis' -import { SessionSeq } from '@deepseek-ai/dsh-session' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type {} from './types.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-session-log-deepseek' - -/** Cordis companion plugin name. */ -export const name = 'session-log-deepseek-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Validate one acceptance watermark against its containing event and session. */ -function validateDeliveryAccepted(session: Session, event: SessionEvent<'session-log-deepseek/delivery-accepted'>, fail: InvariantFailure): void { - const { sessionId, throughSeq } = event.data - const acceptedFormatVersion = event.data.sessionFormatVersion ?? 0 - if (!Number.isSafeInteger(acceptedFormatVersion) - || acceptedFormatVersion < 0 - || Object.is(acceptedFormatVersion, -0)) { - fail( - 'session-log-deepseek/delivery-accepted sessionFormatVersion must be a non-negative safe integer' - + `, got ${String(acceptedFormatVersion)}`, - ) - } - if (acceptedFormatVersion !== session.header.version) return - const inherited = session.header.parentSession !== undefined - && !session.isOwnSeq(event.seq) - if (sessionId !== session.id && !inherited) { - fail('a non-inherited session-log-deepseek/delivery-accepted event must name its containing session') - } - let acceptedSeq: ReturnType - try { - acceptedSeq = SessionSeq(throughSeq) - } catch { - fail(`session-log-deepseek/delivery-accepted throughSeq must identify an earlier event, got ${throughSeq} at seq ${event.seq}`) - } - if (acceptedSeq >= event.seq) { - fail(`session-log-deepseek/delivery-accepted throughSeq must identify an earlier event, got ${throughSeq} at seq ${event.seq}`) - } -} - -/** Validate acceptance watermarks already present in one Session. */ -function validateSession(session: Session, fail: InvariantFailure): void { - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) { - if (event.type === 'session-log-deepseek/delivery-accepted') validateDeliveryAccepted(session, event, fail) - } -} - -/** Validate one live session-event dispatch. */ -function validateDispatched(args: unknown[], fail: InvariantFailure): void { - const [session, event] = args as [Session, SessionEvent] - if (event.type === 'session-log-deepseek/delivery-accepted') validateDeliveryAccepted(session, event, fail) -} - -/** Install validation for restored, newly created, and newly appended watermarks. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const validateExisting = (session: Session): void => { validateSession(session, fail) } - ctx.sessions.list().forEach(validateExisting) - ctx.on('session/created', validateExisting, { global: true }) - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName === 'session/event') validateDispatched(args, fail) - }, { global: true }) -}, { inject: ['sessions'] }) - -/** - * 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)) diff --git a/packages/session/session-log-deepseek/tests/invariant.spec.ts b/packages/session/session-log-deepseek/tests/invariant.spec.ts deleted file mode 100644 index 14e8e61499..0000000000 --- a/packages/session/session-log-deepseek/tests/invariant.spec.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { afterEach, describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import InvariantRegistry, { InvariantError } from '@deepseek-ai/dsh-invariants' -import SessionStore, { - SESSION_FORMAT_VERSION, - SessionId, - SessionLogOffset, - SessionSeq, - type Session, -} from '@deepseek-ai/dsh-session' -import * as SessionLogInvariant from '../src/invariant.ts' -import type {} from '../src/types.ts' - -const contexts: Context[] = [] - -afterEach(async () => { - await Promise.all(contexts.splice(0).map(ctx => ctx.fiber.dispose())) -}) - -async function setup(): Promise { - const ctx = new Context() - contexts.push(ctx) - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(SessionLogInvariant) - return ctx -} - -describe('DeepSeek session-log acceptance invariant', () => { - it('accepts a watermark naming an earlier event in its containing Session', async () => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('valid')) - session.append('turn/start', { turn: 1 }) - expect(() => session.append('session-log-deepseek/delivery-accepted', { - sessionId: session.id, - throughSeq: SessionSeq(0), - sessionFormatVersion: SESSION_FORMAT_VERSION, - })) - .not.toThrow() - }) - - it('treats an omitted format generation as v0 before validating its frozen sequence', async () => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('other-generation')) - session.append('turn/start', { turn: 1 }) - expect(() => session.append('session-log-deepseek/delivery-accepted', { - sessionId: SessionId('unrelated-old-identity'), - throughSeq: SessionSeq(99), - })).not.toThrow() - }) - - it.each([-1, 0.5])('rejects malformed acceptance format version %s', async (sessionFormatVersion) => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId(`invalid-format-${sessionFormatVersion}`)) - session.append('turn/start', { turn: 1 }) - expect(() => session.append('session-log-deepseek/delivery-accepted', { - sessionId: session.id, - throughSeq: SessionSeq(0), - sessionFormatVersion, - })).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-session-log-deepseek', - })) - }) - - it('rejects negative-zero format versions restored across the owned invariant boundary', async () => { - const ctx = new Context() - contexts.push(ctx) - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry, { enabled: true }) - const session = { - header: { version: SESSION_FORMAT_VERSION }, - snapshotEvents: () => [{ - type: 'session-log-deepseek/delivery-accepted' as const, - seq: SessionSeq(0), - time: 1, - data: { sessionId: SessionId('negative-zero'), throughSeq: SessionSeq(0), sessionFormatVersion: -0 }, - }], - isOwnSeq: () => true, - } as unknown as Session - ctx.sessions.list = () => [session] - - await expect(ctx.plugin(SessionLogInvariant)).rejects.toMatchObject({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-session-log-deepseek', - }) - }) - - it('rejects current-generation mismatches and leaves v0 watermarks inert', async () => { - const ctx = await setup() - const wrongId = ctx.sessions.create(SessionId('wrong-id')) - wrongId.append('turn/start', { turn: 1 }) - expect(() => wrongId.append('session-log-deepseek/delivery-accepted', { - sessionId: SessionId('other'), - throughSeq: SessionSeq(0), - sessionFormatVersion: SESSION_FORMAT_VERSION, - })).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-session-log-deepseek', - })) - - const wrongSeq = ctx.sessions.create(SessionId('wrong-seq')) - wrongSeq.append('turn/start', { turn: 1 }) - expect(() => wrongSeq.append('session-log-deepseek/delivery-accepted', { - sessionId: wrongSeq.id, - throughSeq: SessionSeq(1), - sessionFormatVersion: SESSION_FORMAT_VERSION, - })).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-session-log-deepseek', - })) - - const invalidSeq = ctx.sessions.create(SessionId('invalid-seq')) - invalidSeq.append('turn/start', { turn: 1 }) - expect(() => invalidSeq.append('session-log-deepseek/delivery-accepted', { - sessionId: invalidSeq.id, - throughSeq: -1 as never, - sessionFormatVersion: SESSION_FORMAT_VERSION, - })).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-session-log-deepseek', - })) - expect(() => wrongSeq.append('session-log-deepseek/delivery-accepted', { - sessionId: wrongSeq.id, - throughSeq: -1 as never, - })).not.toThrow() - }) - - it('validates existing history when the invariant loads after the Session', async () => { - const ctx = new Context() - contexts.push(ctx) - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry, { enabled: true }) - const id = SessionId('late-invalid') - ctx.sessions.create(id, { seed: [ - { type: 'turn/start', seq: SessionSeq(0), time: 1, data: { turn: 1 } }, - { - type: 'session-log-deepseek/delivery-accepted', - seq: SessionSeq(1), - time: 2, - data: { - sessionId: id, - throughSeq: SessionSeq(1), - sessionFormatVersion: SESSION_FORMAT_VERSION, - }, - }, - ] }) - - let failure: unknown - try { - await ctx.plugin(SessionLogInvariant) - } catch (error) { - failure = error - } - expect(failure).toMatchObject>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-session-log-deepseek', - }) - }) - - it('allows an inherited parent watermark inside a fork seed', async () => { - const ctx = new Context() - contexts.push(ctx) - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry, { enabled: true }) - const parentId = SessionId('fork-parent') - const childId = SessionId('fork-child') - ctx.sessions.create(childId, { - seed: [ - { type: 'turn/start', seq: SessionSeq(0), time: 1, data: { turn: 1 } }, - { - type: 'session-log-deepseek/delivery-accepted', - seq: SessionSeq(1), - time: 2, - data: { - sessionId: parentId, - throughSeq: SessionSeq(0), - sessionFormatVersion: SESSION_FORMAT_VERSION, - }, - }, - ], - inheritedEventCount: SessionLogOffset(2), - meta: { parentSession: parentId, isSeeded: true }, - }) - - await expect(ctx.plugin(SessionLogInvariant)).resolves.toBeDefined() - }) -}) diff --git a/packages/session/session-log-deepseek/tsconfig.json b/packages/session/session-log-deepseek/tsconfig.json index eb85ebeddf..a13b110356 100644 --- a/packages/session/session-log-deepseek/tsconfig.json +++ b/packages/session/session-log-deepseek/tsconfig.json @@ -22,9 +22,6 @@ }, { "path": "../../llm/deepseek-llm-api-extensions" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/session/session-persistence-jsonl/README.i18n.yaml b/packages/session/session-persistence-jsonl/README.i18n.yaml index efd3f41872..3299d7ba06 100644 --- a/packages/session/session-persistence-jsonl/README.i18n.yaml +++ b/packages/session/session-persistence-jsonl/README.i18n.yaml @@ -42,8 +42,8 @@ en: 011d56563b81acd9 zh: 69122dbade603fe9 /deepseek-ai-dsh-session-persistence-jsonl/understand-the-implementation/source-map: - en: 3bdb0f1dd4d69fba - zh: 8198d655d73d42e3 + en: 2a4fd4bde1da0a1b + zh: d7a8cbdad7beee09 /deepseek-ai-dsh-session-persistence-jsonl/further-exploration: en: 827c04be7cd01514 zh: 5e87603b3463db38 diff --git a/packages/session/session-persistence-jsonl/README.md b/packages/session/session-persistence-jsonl/README.md index 5846404961..5ea2356ffb 100644 --- a/packages/session/session-persistence-jsonl/README.md +++ b/packages/session/session-persistence-jsonl/README.md @@ -114,7 +114,6 @@ The default artifact is a standard concatenation of independent [Zstandard frame | [`src/migration-verifier.ts`](src/migration-verifier.ts) | Worker lifecycle for staged and competing-generation verification | | [`src/zstd.ts`](src/zstd.ts) | Zstandard frame compression, decoding, and frame scanning | | [`src/win32.ts`](src/win32.ts) | Windows write-through publish and directory creation | -| — | No runtime invariant companion is published; persistence correctness requires backend round-trip and crash-tail tests; this package exposes no continuously observable in-process relation. | diff --git a/packages/session/session-persistence-jsonl/README.zh.md b/packages/session/session-persistence-jsonl/README.zh.md index b7dd646e57..eb1e3c5294 100644 --- a/packages/session/session-persistence-jsonl/README.zh.md +++ b/packages/session/session-persistence-jsonl/README.zh.md @@ -114,7 +114,6 @@ kind: "package-reference" | [`src/migration-verifier.ts`](src/migration-verifier.ts) | stage 与竞争 generation 校验的 Worker 生命周期 | | [`src/zstd.ts`](src/zstd.ts) | Zstandard 帧压缩、解码与帧扫描 | | [`src/win32.ts`](src/win32.ts) | Windows write-through 发布与目录创建 | -| — | 不发布运行时不变式伴生入口;身份在存储层强制;持久化正确性依赖后端往返与崩溃尾部测试,本包不公开可持续观察的进程内关系。 | diff --git a/packages/session/session-persistence/README.i18n.yaml b/packages/session/session-persistence/README.i18n.yaml index 970120bdcc..e3bde2434a 100644 --- a/packages/session/session-persistence/README.i18n.yaml +++ b/packages/session/session-persistence/README.i18n.yaml @@ -45,8 +45,8 @@ en: a3b5e0e6747f5ab4 zh: fcbcc16c04e15edb /deepseek-ai-dsh-session-persistence/understand-the-implementation/source-map: - en: 3868942fde28ff0a - zh: 865208afd7d04b49 + en: 206eaa18456c97ff + zh: 40afc55b378ec11b /deepseek-ai-dsh-session-persistence/understand-the-implementation/the-write-path-at-a-glance: en: c920cf9262f4a563 zh: 0f8e3ff17e39c861 diff --git a/packages/session/session-persistence/README.md b/packages/session/session-persistence/README.md index 67ace6d102..7c5a63985b 100644 --- a/packages/session/session-persistence/README.md +++ b/packages/session/session-persistence/README.md @@ -96,7 +96,6 @@ The package is a seam, not a backend framework: it exports the abstract `Session | [`src/storage-contract.ts`](src/storage-contract.ts) | Shared validation: version gate, fail-closed vocabulary, batch materialization, contiguity | | [`src/errors.ts`](src/errors.ts) | Stable handle/ownership failures and format refusals | | [`src/revision.ts`](src/revision.ts) | The branded opaque revision token | -| — | No runtime invariant companion is published; persistence correctness requires backend round-trip and crash-tail tests; this package exposes no continuously observable in-process relation. | ### The write path at a glance diff --git a/packages/session/session-persistence/README.zh.md b/packages/session/session-persistence/README.zh.md index f2f5e5f7d0..8012cf54de 100644 --- a/packages/session/session-persistence/README.zh.md +++ b/packages/session/session-persistence/README.zh.md @@ -96,7 +96,6 @@ await ctx.sessionPersistence.flush() // backend-wide d | [`src/storage-contract.ts`](src/storage-contract.ts) | 共享校验:版本门禁、未知事件词汇拒绝、批次实体化、连续性 | | [`src/errors.ts`](src/errors.ts) | 稳定的句柄/所有权失败与格式拒绝 | | [`src/revision.ts`](src/revision.ts) | 带品牌类型的不透明修订值 token | -| — | 不发布运行时不变式伴生入口;持久化正确性需要后端往返与崩溃尾部测试;本包不暴露可持续观察的进程内关系。 | ### 写入路径概览 diff --git a/packages/session/session-projection-cache/README.i18n.yaml b/packages/session/session-projection-cache/README.i18n.yaml index ad963a3aea..4d5408b806 100644 --- a/packages/session/session-projection-cache/README.i18n.yaml +++ b/packages/session/session-projection-cache/README.i18n.yaml @@ -42,8 +42,8 @@ en: 8be14e573e0b3acf zh: be96a2859be64e95 /deepseek-ai-dsh-session-projection-cache/understand-the-implementation/source-map: - en: 546d7d0ff03b92c1 - zh: f3d56e81a738ce56 + en: 1087255b9f80e381 + zh: 08ee0c5c0e04498d /deepseek-ai-dsh-session-projection-cache/further-exploration: en: 63e7495d8853b368 zh: 754db26cb2b0bfba diff --git a/packages/session/session-projection-cache/README.md b/packages/session/session-projection-cache/README.md index 0ba9c5a47f..8aa797b35a 100644 --- a/packages/session/session-projection-cache/README.md +++ b/packages/session/session-projection-cache/README.md @@ -90,7 +90,6 @@ The cache stores one version-stamped document per session in the `session_projca |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `SessionProjectionCache` service, write-behind listeners, cache reads | | [`src/spec.ts`](src/spec.ts) | The `session_projcache` domain spec and record identity types | -| — | No runtime invariant companion is published; the cache's correctness relation (a stored row equals the registry fold at its `seq` watermark) is only checkable by re-running the fold over the persisted log — duplicating the implementation rather than detecting drift — and its staleness is by design (fail-soft writes). The durable boundary is schema-validated by the cache's own zod parse on every read, and the read ladder's version/watermark guards are proven by the package spec. | diff --git a/packages/session/session-projection-cache/README.zh.md b/packages/session/session-projection-cache/README.zh.md index d697b0520c..46d2c13bab 100644 --- a/packages/session/session-projection-cache/README.zh.md +++ b/packages/session/session-projection-cache/README.zh.md @@ -90,7 +90,6 @@ kind: "package-reference" |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`SessionProjectionCache` 服务、后台写入监听器、缓存读取 | | [`src/spec.ts`](src/spec.ts) | `session_projcache` 域 spec 与记录身份类型 | -| — | 不发布运行时不变式伴生入口;完整正确性关系只能通过对持久化日志重新执行折叠来检查;持久化边界通过 schema 校验,读路径的版本与水位防护由包规范证明,相关局部约束在写入与读取路径强制执行。 | diff --git a/packages/session/session-projection/README.i18n.yaml b/packages/session/session-projection/README.i18n.yaml index f88687850a..2af1d9ce09 100644 --- a/packages/session/session-projection/README.i18n.yaml +++ b/packages/session/session-projection/README.i18n.yaml @@ -36,8 +36,8 @@ en: 8afa0fdb49898f6d zh: 6b2da34455963935 /deepseek-ai-dsh-session-projection/understand-the-implementation/source-map: - en: 8b6672b48e7607d6 - zh: 33611e7ea13219f1 + en: b0263d26c5a65d7a + zh: 2e58d3181dde901c /deepseek-ai-dsh-session-projection/understand-the-implementation/drive-and-checkpoint-flow: en: 00136880bb586403 zh: bd35f645111c068c diff --git a/packages/session/session-projection/README.md b/packages/session/session-projection/README.md index 2fa7ddc096..bce4d1baf5 100644 --- a/packages/session/session-projection/README.md +++ b/packages/session/session-projection/README.md @@ -88,7 +88,6 @@ The package is the Service Definition and drive role of a capability seam: the f |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `SessionProjectionRegistry` service, `ProjectionDefinition`, snapshot and checkpoint machinery | | [`src/types.ts`](src/types.ts) | The merge-extensible `SessionProjectionMap` and `SessionProjectionStateMap` type tables | -| — | No runtime invariant companion is published; the registry's own contracts (duplicate-key and stateVersion rejection, effect-tied removal, the `Object.is` change gate) are enforced synchronously inside the service and proven by its spec, the drive relation (every committed `session/event` passes every unit) would require re-running the drive to check — duplicating the implementation rather than detecting drift — and the served-value relation (every served key has a live registration) lives on each carrier's wire path, which emits no cordis event this companion could observe; carrier specs assert it. Synchronous-unit discipline is enforced as far as practical by the boundary `schema.parse` (a Promise-returning view fails loudly). | ### Drive and checkpoint flow diff --git a/packages/session/session-projection/README.zh.md b/packages/session/session-projection/README.zh.md index 565b331cd7..f70e886192 100644 --- a/packages/session/session-projection/README.zh.md +++ b/packages/session/session-projection/README.zh.md @@ -88,7 +88,6 @@ const { asOfSeq, values } = ctx.sessionProjections.snapshot(session) |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`SessionProjectionRegistry` 服务、`ProjectionDefinition`、快照与检查点机制 | | [`src/types.ts`](src/types.ts) | 可合并扩展的 `SessionProjectionMap` 与 `SessionProjectionStateMap` 类型表 | -| — | 不发布运行时不变式伴生入口;注册表自身的约定(拒绝重复键和非法 stateVersion、随 effect 移除、以 `Object.is` 把守变更)由服务同步强制执行并经其规范验证;驱动关系若要检查就必须重新运行驱动,从而重复实现逻辑;所服务值之间的关系由载体协议路径负责。同步单元纪律则尽可能由边界 `schema.parse` 强制执行。 | ### 驱动与检查点流程 diff --git a/packages/session/session-stats/README.i18n.yaml b/packages/session/session-stats/README.i18n.yaml index c64aff7d5a..195b6f0f64 100644 --- a/packages/session/session-stats/README.i18n.yaml +++ b/packages/session/session-stats/README.i18n.yaml @@ -54,5 +54,5 @@ en: 60c96507fc36d60e zh: 06b05dd61bd20ee4 /deepseek-ai-dsh-session-stats/known-limitations-and-deferred-work/dev-note: - en: 2e77468b18faa618 - zh: 22215b9ec8883d88 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/session/session-stats/README.md b/packages/session/session-stats/README.md index 6585b0af7d..6108462bdf 100644 --- a/packages/session/session-stats/README.md +++ b/packages/session/session-stats/README.md @@ -130,5 +130,3 @@ These limits define what the figures describe and when the unit is absent. They None. - -**Runtime invariant:** No companion is published. The package owns a single pure projection fold whose wire payload is schema-validated by the projection registry at every snapshot and change-feed emission, and the event relations the fold relies on (`step/end` exactly once per entered step, monotonic host-assigned turn numbers, chunk and tool events carrying their step coordinates and call ids) are owned and runtime-checked by dsh-agent-loop and the session surface, not here. diff --git a/packages/session/session-stats/README.zh.md b/packages/session/session-stats/README.zh.md index 452dae660f..9af800995b 100644 --- a/packages/session/session-stats/README.zh.md +++ b/packages/session/session-stats/README.zh.md @@ -130,5 +130,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。本包只拥有一个纯投影折叠区,其 wire payload 在每次快照和变更流发射时都由投影注册表进行 schema 校验。该折叠区依赖的事件关系(每个已进入步骤恰有一个 `step/end`、宿主分配的轮次编号单调递增,以及分片和工具事件携带各自的步骤坐标与调用 id)由 dsh-agent-loop 与 Session surface 拥有并在运行时检查,而不由本包拥有。 diff --git a/packages/session/session-telemetry-otel/README.i18n.yaml b/packages/session/session-telemetry-otel/README.i18n.yaml index d26ad40f35..2262216071 100644 --- a/packages/session/session-telemetry-otel/README.i18n.yaml +++ b/packages/session/session-telemetry-otel/README.i18n.yaml @@ -57,5 +57,5 @@ en: e43087aea7af4c9e zh: f8b529e2012b4c63 /deepseek-ai-dsh-session-telemetry-otel/known-limitations-and-deferred-work/dev-note: - en: 0065df2648b09ac9 - zh: b5bac41fc3aec70e + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/session/session-telemetry-otel/README.md b/packages/session/session-telemetry-otel/README.md index f3fc3fa417..818406c6d1 100644 --- a/packages/session/session-telemetry-otel/README.md +++ b/packages/session/session-telemetry-otel/README.md @@ -152,5 +152,3 @@ These limits define where SDK behavior governs and where export guarantees end. None. - -**Runtime invariant:** No companion is published. Mode selection changes capture handoff, SDK setup, and local diagnostics without mutating session or service state an independent companion can compare. Collector delivery cannot be inferred from local queue state. diff --git a/packages/session/session-telemetry-otel/README.zh.md b/packages/session/session-telemetry-otel/README.zh.md index 2220cc4291..f4c663a4c0 100644 --- a/packages/session/session-telemetry-otel/README.zh.md +++ b/packages/session/session-telemetry-otel/README.zh.md @@ -152,5 +152,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。模式选择只改变 capture handoff、SDK setup 与本地 diagnostics,不改变可由独立 companion 对照的会话或服务状态。无法根据本地队列状态推断 collector 是否收到记录。 diff --git a/packages/session/session-telemetry/README.i18n.yaml b/packages/session/session-telemetry/README.i18n.yaml index dda64730e1..8e586ea82f 100644 --- a/packages/session/session-telemetry/README.i18n.yaml +++ b/packages/session/session-telemetry/README.i18n.yaml @@ -60,5 +60,5 @@ en: dc950b1f59a057ec zh: 6f353d426b03abb1 /deepseek-ai-dsh-session-telemetry/known-limitations-and-deferred-work/dev-note: - en: e10a3d2c84d710a6 - zh: 910927bba4d34a85 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/session/session-telemetry/README.md b/packages/session/session-telemetry/README.md index 14d697b93c..79dd040ffc 100644 --- a/packages/session/session-telemetry/README.md +++ b/packages/session/session-telemetry/README.md @@ -127,5 +127,3 @@ These limits define the delivery and data-protection guarantees a deployment get None. - -**Runtime invariant:** No companion is published. The package's whole output is the backend handoff — a synchronous `emit()` call outside every authoritative event stream — and its capture side never appends session events, so no event/data relation exists for an independent companion to observe. diff --git a/packages/session/session-telemetry/README.zh.md b/packages/session/session-telemetry/README.zh.md index 5ac9405d53..0d11813be7 100644 --- a/packages/session/session-telemetry/README.zh.md +++ b/packages/session/session-telemetry/README.zh.md @@ -127,5 +127,3 @@ ledger 记录携带 `sourceEvent`,其中包含所属 Session id 和不含 `dat 无。 - -**运行时不变式:** 不发布伴生入口。本包的全部输出都是后端交接,即在所有权威事件流之外同步调用 `emit()`;捕获侧不追加会话事件,因此不存在可供独立 companion 观察的事件与数据关系。 diff --git a/packages/session/session-title-all-prompts-llm/README.i18n.yaml b/packages/session/session-title-all-prompts-llm/README.i18n.yaml index d8e3e7eaa5..1f92051cb8 100644 --- a/packages/session/session-title-all-prompts-llm/README.i18n.yaml +++ b/packages/session/session-title-all-prompts-llm/README.i18n.yaml @@ -60,5 +60,5 @@ en: d0486c65afaebb85 zh: 186a77e0d68bd968 /deepseek-ai-dsh-session-title-all-prompts-llm/known-limitations-and-deferred-work/dev-note: - en: 954d91e08a470fd6 - zh: e11425f8ec88d40a + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/session/session-title-all-prompts-llm/README.md b/packages/session/session-title-all-prompts-llm/README.md index 449542440d..d8213753ac 100644 --- a/packages/session/session-title-all-prompts-llm/README.md +++ b/packages/session/session-title-all-prompts-llm/README.md @@ -115,5 +115,3 @@ These limits define how the provider treats long and heterogeneous sessions. The None. - -**Runtime invariant:** No companion is published. This thin provider delegates request and result validation to the shared title service and LLM helper and retains no independent mutable state. diff --git a/packages/session/session-title-all-prompts-llm/README.zh.md b/packages/session/session-title-all-prompts-llm/README.zh.md index 7aa8ac7828..8e590367c8 100644 --- a/packages/session/session-title-all-prompts-llm/README.zh.md +++ b/packages/session/session-title-all-prompts-llm/README.zh.md @@ -115,5 +115,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。这个轻量提供方将请求与结果校验委托给共享标题服务和 LLM 辅助模块,并且不保留独立的可变状态。 diff --git a/packages/session/session-title-first-prompt-llm/README.i18n.yaml b/packages/session/session-title-first-prompt-llm/README.i18n.yaml index 25c614ffbc..5165240f5b 100644 --- a/packages/session/session-title-first-prompt-llm/README.i18n.yaml +++ b/packages/session/session-title-first-prompt-llm/README.i18n.yaml @@ -60,5 +60,5 @@ en: 7df1fa6515924136 zh: 39535dca0c9fe9ab /deepseek-ai-dsh-session-title-first-prompt-llm/known-limitations-and-deferred-work/dev-note: - en: 954d91e08a470fd6 - zh: d7e65137396b5d24 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/session/session-title-first-prompt-llm/README.md b/packages/session/session-title-first-prompt-llm/README.md index b8823416f5..29a01e8e26 100644 --- a/packages/session/session-title-first-prompt-llm/README.md +++ b/packages/session/session-title-first-prompt-llm/README.md @@ -115,5 +115,3 @@ These limits define when this provider stops representing the session. They are None. - -**Runtime invariant:** No companion is published. This thin provider delegates request and result validation to the shared title service and LLM helper and retains no independent mutable state. diff --git a/packages/session/session-title-first-prompt-llm/README.zh.md b/packages/session/session-title-first-prompt-llm/README.zh.md index 2d450cd07e..8a60d132bb 100644 --- a/packages/session/session-title-first-prompt-llm/README.zh.md +++ b/packages/session/session-title-first-prompt-llm/README.zh.md @@ -115,5 +115,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。这个轻量提供方将请求与结果校验委托给共享标题服务和 LLM 辅助模块,不保留独立的可变状态。 diff --git a/packages/session/session-title-llm/README.i18n.yaml b/packages/session/session-title-llm/README.i18n.yaml index c455d250dd..87458171b5 100644 --- a/packages/session/session-title-llm/README.i18n.yaml +++ b/packages/session/session-title-llm/README.i18n.yaml @@ -60,5 +60,5 @@ en: 8510fb2c4ed03011 zh: 29fb15ec84dccf56 /deepseek-ai-dsh-session-title-llm/known-limitations-and-deferred-work/dev-note: - en: 2d422956999aa8f8 - zh: 90441550709f6692 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/session/session-title-llm/README.md b/packages/session/session-title-llm/README.md index 6a1cce9e2c..9bca311f63 100644 --- a/packages/session/session-title-llm/README.md +++ b/packages/session/session-title-llm/README.md @@ -127,5 +127,3 @@ These limits define the accepted generation shapes. They are current package con None. - -**Runtime invariant:** No companion is published. This stateless helper validates and freezes each auxiliary request before dispatch; deadline, stream, cited message seqs, and provider/model fields are checked synchronously and by tests. diff --git a/packages/session/session-title-llm/README.zh.md b/packages/session/session-title-llm/README.zh.md index 4b6e063774..d79fae09ea 100644 --- a/packages/session/session-title-llm/README.zh.md +++ b/packages/session/session-title-llm/README.zh.md @@ -127,5 +127,3 @@ kind: "package-library" 无。 - -**运行时不变式:** 不发布伴生入口。这个无状态 helper 会在 dispatch 前校验并冻结每个辅助请求;deadline、stream、message seq、provider 与 model 由同步检查和测试覆盖。 diff --git a/packages/session/session-title/package.json b/packages/session/session-title/package.json index a2c3abdf5f..1c618314e7 100644 --- a/packages/session/session-title/package.json +++ b/packages/session/session-title/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -35,7 +31,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -44,7 +39,6 @@ "@deepseek-ai/cordis": "workspace:~", "@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:*", "@deepseek-ai/dsh-session-projection": "workspace:*" @@ -59,7 +53,6 @@ "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-agent-loop": "workspace:*", "@deepseek-ai/dsh-brand": "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/session/session-title/src/invariant.ts b/packages/session/session-title/src/invariant.ts deleted file mode 100644 index 986b95f81d..0000000000 --- a/packages/session/session-title/src/invariant.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-session-title`. - * @module @deepseek-ai/dsh-session-title/invariant - */ - -/* jscpd:ignore-start */ -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import { SessionSeq } from '@deepseek-ai/dsh-session' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' - -const PACKAGE_NAME = '@deepseek-ai/dsh-session-title' - -/** Cordis companion plugin name. */ -export const name = 'session-title-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** - * Durable title-source invariant: an automatic title always cites at - * least one human `user/message` seq, and an explicit user rename cites none - * — `messageSeqs` is empty iff `source.kind` is `user`. Provider revisions - * are validated by the service before their append; this checks the durable - * relationship every appended `session/title` event must keep, whichever - * writer produced it. - */ -function validate( - session: Session, - event: SessionEvent<'session/title'>, - fail: InvariantFailure, -): void { - const { source, messageSeqs } = event.data - if ((messageSeqs.length === 0) !== (source.kind === 'user')) { - const requirement = source.kind === 'user' ? 'cite no message seqs' : 'cite at least one message seq' - fail(`session/title event ${String(event.seq)} with source "${source.kind}" must ${requirement}; got ${String(messageSeqs.length)}`) - } - const seen = new Set>() - for (const seq of messageSeqs) { - let checked: ReturnType - try { - checked = SessionSeq(seq) - } catch { - fail(`session/title event ${String(event.seq)} has an invalid message seq ${String(seq)}`) - } - if (seen.has(checked)) { - fail(`session/title event ${String(event.seq)} repeats message seq ${checked}`) - } - seen.add(checked) - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - const cited = checked < event.seq ? session.eventAt(checked) : undefined - if (cited?.type !== 'user/message' || cited.data.source.kind !== 'user') { - fail(`session/title event ${String(event.seq)} message seq ${checked} must name an earlier human user/message`) - } - } -} - -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const validateExisting = (session: Session): void => { - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) { - if (event.type === 'session/title') validate(session, event, fail) - } - } - ctx.sessions.list().forEach(validateExisting) - ctx.on('session/created', validateExisting, { global: true }) - // internal/dispatch interception rejects the append before publication - // (the session/event listener would only observe the already-committed log). - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - if (event.type === 'session/title') validate(session, event, fail) - }, { global: true }) -}, { inject: ['sessions'] }) - -/** - * 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/session-title/tests/invariant.spec.ts b/packages/session/session-title/tests/invariant.spec.ts deleted file mode 100644 index 246bd8c67f..0000000000 --- a/packages/session/session-title/tests/invariant.spec.ts +++ /dev/null @@ -1,99 +0,0 @@ -// Title-source invariant: `messageSeqs` is empty iff `source.kind` is `user`. -// — the durable relationship every appended session/title event must keep. -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import * as SessionTitleInvariantCompanion from '@deepseek-ai/dsh-session-title/invariant' -import InvariantRegistry, { InvariantError } from '@deepseek-ai/dsh-invariants' -import SessionStore, { SessionId, SessionSeq } from '@deepseek-ai/dsh-session' -import { createUserMessage } from '@deepseek-ai/dsh-llm' -import type { ContextFormed } from '@deepseek-ai/dsh-llm' - -declare module '@deepseek-ai/dsh-llm' { - interface MessageSourceMap { - 'test': { kind: 'test' } & ContextFormed - } -} - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(SessionTitleInvariantCompanion) - return ctx -} - -describe('session-title source invariant', () => { - it('accepts cited automatic titles and citation-free user renames', async () => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('title-invariant-valid')) - const source = session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'title me' }], source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - expect(() => { - session.append('session/title', { title: 'auto', messageSeqs: [source.seq], source: { kind: 'fallback' } }) - session.append('session/title', { title: 'named', messageSeqs: [], source: { kind: 'user' } }) - }).not.toThrow() - }) - - it('rejects a citation-free automatic title and a user rename that cites messages', async () => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('title-invariant-invalid')) - const source = session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'title me' }], source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - expect(() => { - session.append('session/title', { title: 'auto', messageSeqs: [], source: { kind: 'fallback' } }) - }).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-session-title', - })) - expect(() => { - session.append('session/title', { title: 'named', messageSeqs: [source.seq], source: { kind: 'user' } }) - }).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-session-title', - })) - expect(session.seq).toBe(1) - }) - - it('requires automatic-title citations to name distinct earlier human messages', async () => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('title-invariant-sources')) - const boundary = session.append('turn/start', { turn: 1 }) - expect(() => session.append('session/title', { - title: 'wrong source', messageSeqs: [boundary.seq], source: { kind: 'fallback' }, - })).toThrow(/must name an earlier human user\/message/) - expect(() => session.append('session/title', { - title: 'future source', messageSeqs: [SessionSeq(session.seq)], source: { kind: 'fallback' }, - })).toThrow(/must name an earlier human user\/message/) - expect(() => session.append('session/title', { - title: 'malformed source', messageSeqs: [-1 as never], source: { kind: 'fallback' }, - })).toThrow(/invalid message seq/) - const pluginMessage = session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'plugin context' }], - source: { kind: 'test' }, - }), { surfaceOp: 'append' }) - expect(() => session.append('session/title', { - title: 'plugin source', messageSeqs: [pluginMessage.seq], source: { kind: 'fallback' }, - })).toThrow(/must name an earlier human user\/message/) - const source = session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'title me' }], source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - expect(() => session.append('session/title', { - title: 'duplicate source', messageSeqs: [source.seq, source.seq], source: { kind: 'fallback' }, - })).toThrow(/repeats message seq/) - }) - - it('validates title relations when the companion loads after a Session', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create(SessionId('title-invariant-existing')) - const boundary = session.append('turn/start', { turn: 1 }) - session.append('session/title', { - title: 'wrong source', messageSeqs: [boundary.seq], source: { kind: 'fallback' }, - }) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await expect(ctx.plugin(SessionTitleInvariantCompanion).then(() => undefined)) - .rejects.toThrow(/must name an earlier human user\/message/) - }) -}) diff --git a/packages/session/session-title/tsconfig.json b/packages/session/session-title/tsconfig.json index d4eef17de4..5755ac55e2 100644 --- a/packages/session/session-title/tsconfig.json +++ b/packages/session/session-title/tsconfig.json @@ -20,9 +20,6 @@ { "path": "../../util/brand" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../llm/llm" }, diff --git a/packages/session/session-turn-outline/README.i18n.yaml b/packages/session/session-turn-outline/README.i18n.yaml index 1b1eaee402..b21b6d06e3 100644 --- a/packages/session/session-turn-outline/README.i18n.yaml +++ b/packages/session/session-turn-outline/README.i18n.yaml @@ -33,8 +33,8 @@ en: a20da800a1fbe258 zh: 7123399bb8d80806 /deepseek-ai-dsh-session-turn-outline/understand-the-implementation/source-map: - en: dcf5614200fafbfc - zh: 2b25176a424b7b72 + en: 819a644c4681b4f8 + zh: 0f02af23ab8f59ea /deepseek-ai-dsh-session-turn-outline/understand-the-implementation/fold-rules: en: ac64b54bd3d619a1 zh: 4f33bdf2693179a6 diff --git a/packages/session/session-turn-outline/README.md b/packages/session/session-turn-outline/README.md index 58a71c6977..a90b0eab80 100644 --- a/packages/session/session-turn-outline/README.md +++ b/packages/session/session-turn-outline/README.md @@ -71,7 +71,6 @@ The unit is a pure fold over committed session events. `turn/start` — not the | [`src/index.ts`](src/index.ts) | Plugin entry: `inject`, unit registration on the mounting fiber | | [`src/projection.ts`](src/projection.ts) | The fold: entry append, preview fill, wire view | | [`src/types.ts`](src/types.ts) | One home of the `turnOutline` projection-key declaration and entry types | -| — | No runtime invariant companion is published: the package owns one pure projection fold, `session-projection` schema-validates its served values, and re-folding the same log would duplicate the implementation instead of comparing independently maintained observations; session and agent-loop own turn-boundary ordering. | ### Fold rules diff --git a/packages/session/session-turn-outline/README.zh.md b/packages/session/session-turn-outline/README.zh.md index 49c0aa3c4f..7681ff6a76 100644 --- a/packages/session/session-turn-outline/README.zh.md +++ b/packages/session/session-turn-outline/README.zh.md @@ -71,7 +71,6 @@ wire 值是按 `turn` 严格递增的完整条目数组(整值规则):消 | [`src/index.ts`](src/index.ts) | 插件入口:`inject`、在挂载 fiber 上注册单元 | | [`src/projection.ts`](src/projection.ts) | 折叠:条目追加、预览填充、wire 视图 | | [`src/types.ts`](src/types.ts) | `turnOutline` 投影键声明与条目类型的唯一归属 | -| — | 不发布运行时不变式伴生入口:本包仅拥有一个纯投影折叠,`session-projection` 会对其对外值执行 schema 校验;重新折叠同一日志只会复制实现,无法比较独立维护的观测,而轮次边界顺序由 session 与 agent-loop 负责。 | ### 折叠规则 diff --git a/packages/settings/settings/README.i18n.yaml b/packages/settings/settings/README.i18n.yaml index 87b96005b9..46348fd8da 100644 --- a/packages/settings/settings/README.i18n.yaml +++ b/packages/settings/settings/README.i18n.yaml @@ -18,8 +18,8 @@ en: e0f5e49cf8137839 zh: bdd8aff9b7773e5a /deepseek-ai-dsh-settings/understand-the-implementation: - en: c0f2f476ce7a6e54 - zh: 16cbd0cf117a2c02 + en: d8eae17e891ff44e + zh: 05189989b2786002 /deepseek-ai-dsh-settings/further-exploration: en: 468f217123040420 zh: d7f753033d9f07b4 diff --git a/packages/settings/settings/README.md b/packages/settings/settings/README.md index a44dd50a46..740996e34b 100644 --- a/packages/settings/settings/README.md +++ b/packages/settings/settings/README.md @@ -46,7 +46,7 @@ Each form reports `autoGenerate`, enabled by default, for clients that build pag The [form projection](src/schema.ts) strips runtime references and ordinary fields. The [service](src/index.ts) supplies revisioned descriptors and validates edits against the full plugin Config before delegating persistence. Business plugins read their Config references directly. -Secret roles are redacted from values, inherited values, profile overrides, and schema defaults; clients receive presence markers. Path edits preserve fields a client did not receive. No invariant companion is published because the service projects Loader configuration rather than maintaining an independent authoritative value. +Secret roles are redacted from values, inherited values, profile overrides, and schema defaults; clients receive presence markers. Path edits preserve fields a client did not receive. diff --git a/packages/settings/settings/README.zh.md b/packages/settings/settings/README.zh.md index 0f7df0dec0..c8fae11993 100644 --- a/packages/settings/settings/README.zh.md +++ b/packages/settings/settings/README.zh.md @@ -46,7 +46,7 @@ Settings 启动后、Loader 完成所有条目的加载时,早期版本留在 [表单投影](src/schema.ts) 去除运行时引用和普通字段。[服务](src/index.ts) 提供带修订号的描述符,在委托持久化前按完整插件 Config 验证编辑。业务插件直接读取自己的 Config 引用。 -秘密角色从实际值、继承值、profile 覆盖值和 schema 默认值中隐藏;客户端接收存在性标记。路径编辑保留客户端未收到的字段。此服务投影 Loader 配置,不维护独立的权威值,因此不发布 invariant companion。 +秘密角色从实际值、继承值、profile 覆盖值和 schema 默认值中隐藏;客户端接收存在性标记。路径编辑保留客户端未收到的字段。 diff --git a/packages/settings/settings/package.json b/packages/settings/settings/package.json index ce6b670a12..580ae7afb8 100644 --- a/packages/settings/settings/package.json +++ b/packages/settings/settings/package.json @@ -34,14 +34,12 @@ "peerDependencies": { "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-brand": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/schemastery": "workspace:~" }, "devDependencies": { "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-brand": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/schemastery": "workspace:~", "@deepseek-ai/cordis-plugin-timer": "workspace:~", diff --git a/packages/shell/bash-local/README.i18n.yaml b/packages/shell/bash-local/README.i18n.yaml index 4fbf9ac6ca..52bb869d33 100644 --- a/packages/shell/bash-local/README.i18n.yaml +++ b/packages/shell/bash-local/README.i18n.yaml @@ -36,8 +36,8 @@ en: ec84e5746de85fe0 zh: 1c353325eeae87c6 /deepseek-ai-dsh-bash-local/understand-the-implementation/source-map: - en: 254759ab26151a72 - zh: e94679a11b67dc10 + en: 762bafaf8fb721c2 + zh: cd5dd95212f7c270 /deepseek-ai-dsh-bash-local/understand-the-implementation/main-flow: en: 723bbaedc34e58f3 zh: d0239dcfdd8a53c6 diff --git a/packages/shell/bash-local/README.md b/packages/shell/bash-local/README.md index 578e5284e2..123451f3aa 100644 --- a/packages/shell/bash-local/README.md +++ b/packages/shell/bash-local/README.md @@ -88,7 +88,6 @@ The executor is a Service Provider for the `ctx.shell` seam built on the subproc | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `LocalBashExecutor`, `Config`, settings-section wiring | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | | `tests/executor.spec.ts` | Exercised behavior: budgets, classification, background handles, ownership | | `tests/settings.spec.ts` | Settings layering over the composition entry | diff --git a/packages/shell/bash-local/README.zh.md b/packages/shell/bash-local/README.zh.md index 4bac3b6cce..a2b2853354 100644 --- a/packages/shell/bash-local/README.zh.md +++ b/packages/shell/bash-local/README.zh.md @@ -88,7 +88,6 @@ if (result.timedOut) console.log('timed out after', result.timeoutMs) | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`LocalBashExecutor`、`Config`、设置段接线 | -| — | 不发布运行时不变式伴生入口;除由所属 seam 强制执行的约定外,本包不公开独立的事件序列或可变数据关系。 | | `tests/executor.spec.ts` | 已演练的行为:预算、分类、后台句柄、归属 | | `tests/settings.spec.ts` | 设置段叠加在组合条目之上 | diff --git a/packages/shell/bash-sandbox/README.i18n.yaml b/packages/shell/bash-sandbox/README.i18n.yaml index f9c08ae20c..273f17d87b 100644 --- a/packages/shell/bash-sandbox/README.i18n.yaml +++ b/packages/shell/bash-sandbox/README.i18n.yaml @@ -39,8 +39,8 @@ en: e1b1ac62e94ce535 zh: 5fa7a0353fbf777d /deepseek-ai-dsh-bash-sandbox/understand-the-implementation/source-map: - en: 418d9c2a61061adb - zh: ffbc0d7723b45017 + en: 8963005b41bfc3c6 + zh: 7df1436563c10a19 /deepseek-ai-dsh-bash-sandbox/understand-the-implementation/main-flow: en: 731878f592fa79c6 zh: 3187f4c6907a13c2 diff --git a/packages/shell/bash-sandbox/README.md b/packages/shell/bash-sandbox/README.md index fea09e4ce1..80bcd2472a 100644 --- a/packages/shell/bash-sandbox/README.md +++ b/packages/shell/bash-sandbox/README.md @@ -83,7 +83,6 @@ The executor is the sandboxing Service Provider for the `ctx.shell` seam: it inh |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `SandboxBashExecutor`, per-process fact retention, execution preparation | | [`src/helpers.ts`](src/helpers.ts) | Denial, runner-failure, and runner-spawn-failure classification | -| — | No runtime invariant companion is published; classification is observable in results, and this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | | `tests/` | Exercised behavior across the bwrap, Landlock, and Seatbelt runners | ### Main flow diff --git a/packages/shell/bash-sandbox/README.zh.md b/packages/shell/bash-sandbox/README.zh.md index 5daed6969c..54f4449838 100644 --- a/packages/shell/bash-sandbox/README.zh.md +++ b/packages/shell/bash-sandbox/README.zh.md @@ -83,7 +83,6 @@ kind: "package-reference" |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`SandboxBashExecutor`、按进程保留事实、execute 包装 | | [`src/helpers.ts`](src/helpers.ts) | 拒绝、runner 失败与 runner spawn 失败分类 | -| — | 不发布运行时不变式伴生入口;分类可在结果中观察,且除归属 seam 所强制执行的约定外,本包不公开独立事件序列或可变数据关系。 | | `tests/` | 跨 bwrap、Landlock 与 Seatbelt runner 演练的行为 | ### 主要流程 diff --git a/packages/shell/pwsh-local/README.i18n.yaml b/packages/shell/pwsh-local/README.i18n.yaml index 285a4e15ef..d1cc1e6602 100644 --- a/packages/shell/pwsh-local/README.i18n.yaml +++ b/packages/shell/pwsh-local/README.i18n.yaml @@ -39,8 +39,8 @@ en: d065be99a0cea908 zh: 50eee843e3b6860c /deepseek-ai-dsh-pwsh-local/understand-the-implementation/source-map: - en: 299f4ffe25b4806d - zh: d0aced83a1950a91 + en: aa160f9ec57ff627 + zh: 048d3292dddf1d8b /deepseek-ai-dsh-pwsh-local/understand-the-implementation/main-flow: en: 1cb78cfddb484576 zh: 94e16b60c9cd458b diff --git a/packages/shell/pwsh-local/README.md b/packages/shell/pwsh-local/README.md index f4001263ba..55bda0cd7f 100644 --- a/packages/shell/pwsh-local/README.md +++ b/packages/shell/pwsh-local/README.md @@ -94,7 +94,6 @@ The executor is the PowerShell Service Provider for the `ctx.shell` seam built o |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `PwshLocalExecutor`, `Config`, settings wiring, argv seam | | [`src/resolve.ts`](src/resolve.ts) | Pure `resolvePwshPath`/`candidatePwshPaths` executable resolution | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | | `tests/` | Exercised behavior: budgets, classification, resolution, background handles | ### Main flow diff --git a/packages/shell/pwsh-local/README.zh.md b/packages/shell/pwsh-local/README.zh.md index e9e1c3246b..83e7f91636 100644 --- a/packages/shell/pwsh-local/README.zh.md +++ b/packages/shell/pwsh-local/README.zh.md @@ -94,7 +94,6 @@ if (result.timedOut) console.log('timed out after', result.timeoutMs) |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`PwshLocalExecutor`、`Config`、设置接线、argv seam | | [`src/resolve.ts`](src/resolve.ts) | 纯函数 `resolvePwshPath`/`candidatePwshPaths` 可执行文件解析 | -| — | 不发布运行时不变式伴生入口;除所属 seam 强制执行的约定外,本包不公开独立的事件序列或可变数据关系。 | | `tests/` | 已演练的行为:预算、分类、解析、后台句柄 | ### 主要流程 diff --git a/packages/shell/pwsh-sandbox/README.i18n.yaml b/packages/shell/pwsh-sandbox/README.i18n.yaml index af34c521b0..8463f39be4 100644 --- a/packages/shell/pwsh-sandbox/README.i18n.yaml +++ b/packages/shell/pwsh-sandbox/README.i18n.yaml @@ -39,8 +39,8 @@ en: 0209cfdeae41c9b5 zh: f968694c21c96ec2 /deepseek-ai-dsh-pwsh-sandbox/understand-the-implementation/source-map: - en: 71709a90dada216b - zh: 3679af6c61d08487 + en: e7af8c5552806441 + zh: 2ad6a90d828bbd73 /deepseek-ai-dsh-pwsh-sandbox/understand-the-implementation/main-flow: en: 93f0842800cb0e52 zh: cfde2946230560ee diff --git a/packages/shell/pwsh-sandbox/README.md b/packages/shell/pwsh-sandbox/README.md index 8d75347efa..e1859ae7ee 100644 --- a/packages/shell/pwsh-sandbox/README.md +++ b/packages/shell/pwsh-sandbox/README.md @@ -83,7 +83,6 @@ The executor is the pwsh twin of `dsh-bash-sandbox`: it inherits `dsh-pwsh-local |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `SandboxPwshExecutor`, per-process fact retention, execution preparation | | [`src/helpers.ts`](src/helpers.ts) | Denial, runner-failure, and runner-spawn-failure classification | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seams. Classification is observable in results. | | `tests/` | Exercised behavior across the ACL and platform runners | ### Main flow diff --git a/packages/shell/pwsh-sandbox/README.zh.md b/packages/shell/pwsh-sandbox/README.zh.md index 295d60d9a6..ded45a5278 100644 --- a/packages/shell/pwsh-sandbox/README.zh.md +++ b/packages/shell/pwsh-sandbox/README.zh.md @@ -83,7 +83,6 @@ kind: "package-reference" |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`SandboxPwshExecutor`、按进程保留事实、执行准备 | | [`src/helpers.ts`](src/helpers.ts) | 拒绝、runner 失败与 runner spawn 失败分类 | -| — | 不发布运行时不变式伴生入口;除所属 seam 所执行的约定外,本包不暴露独立事件序列或可变数据关系;分类可在结果中观察。 | | `tests/` | 跨 ACL 与平台 runner 演练的行为 | ### 主要流程 diff --git a/packages/shell/shell-env/README.i18n.yaml b/packages/shell/shell-env/README.i18n.yaml index b59fe5576f..6468fd1da2 100644 --- a/packages/shell/shell-env/README.i18n.yaml +++ b/packages/shell/shell-env/README.i18n.yaml @@ -36,8 +36,8 @@ en: 311b66ea29b5702f zh: 3a98c2fb1fcaa519 /deepseek-ai-dsh-shell-env/understand-the-implementation/source-map: - en: d37531b816607220 - zh: 1f867ff7f422746b + en: 72eba3bfadcfa581 + zh: dcd83e02a1910f27 /deepseek-ai-dsh-shell-env/understand-the-implementation/collection: en: 0c2d7d5b2131630f zh: b44d4f0ddf89421b diff --git a/packages/shell/shell-env/README.md b/packages/shell/shell-env/README.md index 6557049c82..fc53004803 100644 --- a/packages/shell/shell-env/README.md +++ b/packages/shell/shell-env/README.md @@ -87,7 +87,6 @@ This section explains the design decisions behind the registry and points at the | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry, `ShellEnvRegistry` service, and the built-in facts | -| — | No runtime invariant companion is published; the environment registry validates ownership and collected values at each registration/collection; it publishes no independent snapshot that a companion could cross-check. | ### Collection diff --git a/packages/shell/shell-env/README.zh.md b/packages/shell/shell-env/README.zh.md index 32bd5e744a..1abd456c82 100644 --- a/packages/shell/shell-env/README.zh.md +++ b/packages/shell/shell-env/README.zh.md @@ -87,7 +87,6 @@ contributor 必须声明它返回的每个键;返回未声明或非字符串 | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口、`ShellEnvRegistry` 服务与内置事实 | -| — | 不发布运行时不变式伴生入口;环境注册表会在每次注册和收集时校验所有权与收集值,也不发布可供伴生入口交叉检查的独立快照。 | ### 收集 diff --git a/packages/shell/shell/README.i18n.yaml b/packages/shell/shell/README.i18n.yaml index 31ba0b07d8..2255ab1dba 100644 --- a/packages/shell/shell/README.i18n.yaml +++ b/packages/shell/shell/README.i18n.yaml @@ -42,8 +42,8 @@ en: a6467114855249e8 zh: 65b53a2839cb411e /deepseek-ai-dsh-shell/understand-the-implementation/source-map: - en: 80d45b2de500588a - zh: 23da8d99b05f4db6 + en: 1e9231bae2662059 + zh: 2bac2b8e8fc08eb9 /deepseek-ai-dsh-shell/understand-the-implementation/settings-namespace: en: a5b81ecee7fe9938 zh: 25fcf060b48e14cf diff --git a/packages/shell/shell/README.md b/packages/shell/shell/README.md index b72440a38c..0e87c9f25e 100644 --- a/packages/shell/shell/README.md +++ b/packages/shell/shell/README.md @@ -90,7 +90,6 @@ The package is one role of a standard capability seam: the Service Definition th | [`src/index.ts`](src/index.ts) | Plugin entry: abstract `ShellExecutor` service and the shared settings namespace | | [`src/types.ts`](src/types.ts) | Request/spec vocabulary, `ShellExecution`, `ShellRunResult`, and sandbox facts | | [`src/render.ts`](src/render.ts) | `parseExitStatus`: the exit-status marker contract the shell tools share | -| — | No runtime invariant companion is published; this stateless Service Definition owns request/result types, while executors and policy own observations. | ### Settings namespace diff --git a/packages/shell/shell/README.zh.md b/packages/shell/shell/README.zh.md index 272ffc85f8..cf12395912 100644 --- a/packages/shell/shell/README.zh.md +++ b/packages/shell/shell/README.zh.md @@ -90,7 +90,6 @@ seam 本身不是执行器:每个组合只挂载一个提供方,工具即可 | [`src/index.ts`](src/index.ts) | 插件入口:抽象 `ShellExecutor` 服务与共享设置命名空间 | | [`src/types.ts`](src/types.ts) | 请求/spec 词汇、`ShellExecution`、`ShellRunResult` 与沙箱事实 | | [`src/render.ts`](src/render.ts) | `parseExitStatus`:shell 工具共享的退出状态标记约定 | -| — | 不发布运行时不变式伴生入口;该无状态 Service Definition 负责请求/结果类型,执行器与策略负责观察。 | ### 设置命名空间 diff --git a/packages/shell/tool-bash-persistent/README.i18n.yaml b/packages/shell/tool-bash-persistent/README.i18n.yaml index 947168a29f..51e9bf43ea 100644 --- a/packages/shell/tool-bash-persistent/README.i18n.yaml +++ b/packages/shell/tool-bash-persistent/README.i18n.yaml @@ -36,8 +36,8 @@ en: 61c39f52982612e3 zh: 92bc7e3c8d13a211 /deepseek-ai-dsh-tool-bash-persistent/understand-the-implementation/source-map: - en: 8dc5a2fb04ac01a2 - zh: ad7822d2f49fafce + en: 857e036f41c913ac + zh: e9f383f7ee384c59 /deepseek-ai-dsh-tool-bash-persistent/understand-the-implementation/command-flow: en: 3b7bf7c20b266c31 zh: 85a325ef65fa7ac7 diff --git a/packages/shell/tool-bash-persistent/README.md b/packages/shell/tool-bash-persistent/README.md index f22004aa61..7954fa3d7e 100644 --- a/packages/shell/tool-bash-persistent/README.md +++ b/packages/shell/tool-bash-persistent/README.md @@ -80,7 +80,6 @@ This section explains the design decisions behind the tool and points at the cod | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: shell registry, command wrapping, scrollback polling, extraction and rendering | -| — | No runtime invariant companion is published; the adapter's private owner-to-shell cache has no observable event or data relation. Shell reuse remains observable through tool execution. Lifecycle tests prove its cleanup without adding a public API solely for an invariant. | ### Command flow diff --git a/packages/shell/tool-bash-persistent/README.zh.md b/packages/shell/tool-bash-persistent/README.zh.md index 6735ede17f..2615da6321 100644 --- a/packages/shell/tool-bash-persistent/README.zh.md +++ b/packages/shell/tool-bash-persistent/README.zh.md @@ -80,7 +80,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:shell 注册表、命令包装、scrollback 轮询、提取与渲染 | -| — | 不发布运行时不变式伴生入口;适配器私有的 owner-to-shell 缓存没有可观察的事件或数据关系。shell 复用仍可通过工具执行观察。生命周期测试会验证其清理行为,无需仅为不变式增加公共 API。 | ### 命令流程 diff --git a/packages/shell/tool-bash/README.i18n.yaml b/packages/shell/tool-bash/README.i18n.yaml index 5802f193d9..cde11c11e4 100644 --- a/packages/shell/tool-bash/README.i18n.yaml +++ b/packages/shell/tool-bash/README.i18n.yaml @@ -42,8 +42,8 @@ en: 9332353fda6ac7be zh: 45c17e5e83478032 /deepseek-ai-dsh-tool-bash/understand-the-implementation/source-map: - en: 5fe11138a292e82d - zh: e3e94af07b90b5ba + en: 0547d68864628d08 + zh: 5adb79f945b17a0d /deepseek-ai-dsh-tool-bash/understand-the-implementation/request-resolution: en: b03c1d84aa4b5cd4 zh: 111eba387b7082ae diff --git a/packages/shell/tool-bash/README.md b/packages/shell/tool-bash/README.md index 357fe0a610..418d4c6efd 100644 --- a/packages/shell/tool-bash/README.md +++ b/packages/shell/tool-bash/README.md @@ -95,7 +95,6 @@ This section explains the design decisions behind the tool and points at the cod | [`src/index.ts`](src/index.ts) | Plugin entry: tool registration, prompt section, arg validation, escalation, request assembly | | [`src/background.ts`](src/background.ts) | Own asynchronous shell preparation, map process settlement onto job outcomes, and render a ring read as a process read | | [`src/render.ts`](src/render.ts) | Model-facing result text: streams, markers, truncation notices | -| — | No runtime invariant companion is published; the environment registry validates ownership and collected values at each mutation/read; it publishes no independent snapshot that a companion could cross-check. | ### Request resolution diff --git a/packages/shell/tool-bash/README.zh.md b/packages/shell/tool-bash/README.zh.md index 578e613bd4..97db91cb0b 100644 --- a/packages/shell/tool-bash/README.zh.md +++ b/packages/shell/tool-bash/README.zh.md @@ -95,7 +95,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:工具注册、提示词区段、参数校验、升权、请求组装 | | [`src/background.ts`](src/background.ts) | 管理异步 shell 准备、将进程结算映射为任务结果,并把输出环读取渲染为进程读取 | | [`src/render.ts`](src/render.ts) | 模型侧结果文本:流、标记、截断通知 | -| — | 不发布运行时不变式伴生入口;环境注册表在每次变更和读取时校验所有权及收集值,且不发布可供伴生入口交叉核对的独立快照;执行关系由能力 seam 负责。 | ### 请求解析 diff --git a/packages/shell/tool-pwsh-persistent/README.i18n.yaml b/packages/shell/tool-pwsh-persistent/README.i18n.yaml index 178cdcd384..1e500ed433 100644 --- a/packages/shell/tool-pwsh-persistent/README.i18n.yaml +++ b/packages/shell/tool-pwsh-persistent/README.i18n.yaml @@ -36,8 +36,8 @@ en: b9b75c3251f29f02 zh: 9114aad4726f6d0c /deepseek-ai-dsh-tool-pwsh-persistent/understand-the-implementation/source-map: - en: bb2d17f1385fb11d - zh: 8e103837238ccda2 + en: 857e036f41c913ac + zh: e9f383f7ee384c59 /deepseek-ai-dsh-tool-pwsh-persistent/understand-the-implementation/command-flow: en: 7440916d998ed5bd zh: 66f70f803c519802 diff --git a/packages/shell/tool-pwsh-persistent/README.md b/packages/shell/tool-pwsh-persistent/README.md index 871db34210..0a301afee8 100644 --- a/packages/shell/tool-pwsh-persistent/README.md +++ b/packages/shell/tool-pwsh-persistent/README.md @@ -82,7 +82,6 @@ This section explains the design decisions behind the tool and points at the cod | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: shell registry, command wrapping, scrollback polling, extraction and rendering | -| — | No runtime invariant companion is published; the adapter's private owner-to-shell cache has no observable event or data relation. Lifecycle tests prove its cleanup without adding a public API solely for an invariant. | ### Command flow diff --git a/packages/shell/tool-pwsh-persistent/README.zh.md b/packages/shell/tool-pwsh-persistent/README.zh.md index caea272185..87b93d2d1f 100644 --- a/packages/shell/tool-pwsh-persistent/README.zh.md +++ b/packages/shell/tool-pwsh-persistent/README.zh.md @@ -82,7 +82,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:shell 注册表、命令包装、scrollback 轮询、提取与渲染 | -| — | 不发布运行时不变式伴生入口;适配器私有的 owner-to-shell 缓存不存在可观察的事件或数据关系。生命周期测试无需仅为不变式增加公共 API 即可验证其清理。 | ### 命令流程 diff --git a/packages/shell/tool-pwsh/README.i18n.yaml b/packages/shell/tool-pwsh/README.i18n.yaml index 8c40146dcd..7a857a8302 100644 --- a/packages/shell/tool-pwsh/README.i18n.yaml +++ b/packages/shell/tool-pwsh/README.i18n.yaml @@ -42,8 +42,8 @@ en: 73b8e46017f19e22 zh: 70f9f82b010b3dd0 /deepseek-ai-dsh-tool-pwsh/understand-the-implementation/source-map: - en: 09557aa79cbe3a7d - zh: e5f36b411d01f844 + en: aa2adb29a0f27fcd + zh: 612829167064acd1 /deepseek-ai-dsh-tool-pwsh/understand-the-implementation/rendering-and-exit-markers: en: facd01af9a403fec zh: 7a943dac19a01c39 diff --git a/packages/shell/tool-pwsh/README.md b/packages/shell/tool-pwsh/README.md index 14b905355b..384d529081 100644 --- a/packages/shell/tool-pwsh/README.md +++ b/packages/shell/tool-pwsh/README.md @@ -90,7 +90,6 @@ This section explains the design decisions behind the tool and points at the cod | [`src/index.ts`](src/index.ts) | Plugin entry: tool registration, prompt section, arg validation, escalation, request assembly | | [`src/background.ts`](src/background.ts) | Map a settled process onto generic job outcome vocabulary and render a ring read as a process read | | [`src/render.ts`](src/render.ts) | Model-facing result text: streams, markers, truncation notices (bash twin) | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | ### Rendering and exit markers diff --git a/packages/shell/tool-pwsh/README.zh.md b/packages/shell/tool-pwsh/README.zh.md index 54a87709a5..5817a2f46a 100644 --- a/packages/shell/tool-pwsh/README.zh.md +++ b/packages/shell/tool-pwsh/README.zh.md @@ -90,7 +90,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:工具注册、提示词区段、参数校验、升权、请求组装 | | [`src/background.ts`](src/background.ts) | 把已结算的进程映射为通用任务结果词汇,并把输出环读取渲染为进程读取 | | [`src/render.ts`](src/render.ts) | 模型侧结果文本:流、标记、截断通知(bash 孪生) | -| — | 不发布运行时不变式伴生入口;除所属 seam 强制执行的约定外,本包不公开独立的事件序列或可变数据关系。 | ### 渲染与退出标记 diff --git a/packages/skill/skill-badge/README.i18n.yaml b/packages/skill/skill-badge/README.i18n.yaml index fcaa721314..230a31d901 100644 --- a/packages/skill/skill-badge/README.i18n.yaml +++ b/packages/skill/skill-badge/README.i18n.yaml @@ -36,8 +36,8 @@ en: 52ff92fad6d26ee9 zh: 071c7859c158513d /deepseek-ai-dsh-skill-badge/understand-the-implementation/source-map: - en: 028c0f62dca4cf28 - zh: 15deebc72d86e753 + en: 259b9b2073da8ddd + zh: e0d621f0a3b0470c /deepseek-ai-dsh-skill-badge/further-exploration: en: 1b5434b8bcbd5b0b zh: d170cddf3837e655 diff --git a/packages/skill/skill-badge/README.md b/packages/skill/skill-badge/README.md index 973c4a003c..26bcadc13f 100644 --- a/packages/skill/skill-badge/README.md +++ b/packages/skill/skill-badge/README.md @@ -69,7 +69,6 @@ The provider is an immutable, synchronously registered skill source: it register | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry and the immutable provider: one candidate, resource base, body load | -| — | No runtime invariant companion is published; the package owns one immutable provider registration, while the skill registry owns registration uniqueness and lifecycle checks. | | [`assets/`](assets/) | Packaged skill body (`dsh-badge.md`) and PNG asset (`dsh-badge.png`) | diff --git a/packages/skill/skill-badge/README.zh.md b/packages/skill/skill-badge/README.zh.md index 1c2dbc056a..2e71214a20 100644 --- a/packages/skill/skill-badge/README.zh.md +++ b/packages/skill/skill-badge/README.zh.md @@ -69,7 +69,6 @@ agent(智能体)可以通过该内置提供方加载官方「powered by dsh | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口与不可变提供方:一个候选项、资源基底、正文加载 | -| — | 不发布运行时不变式伴生入口;本包只持有一个不可变的提供方注册,注册唯一性与生命周期检查由 skill 注册表负责。 | | [`assets/`](assets/) | 随包分发的 skill 正文(`dsh-badge.md`)与 PNG 资源(`dsh-badge.png`) | diff --git a/packages/skill/skill-filesystem/README.i18n.yaml b/packages/skill/skill-filesystem/README.i18n.yaml index 07add8fa79..964de20efe 100644 --- a/packages/skill/skill-filesystem/README.i18n.yaml +++ b/packages/skill/skill-filesystem/README.i18n.yaml @@ -42,8 +42,8 @@ en: 8529fb4c1989d5fc zh: 115372aaa959dc6c /deepseek-ai-dsh-skill-filesystem/understand-the-implementation/source-map: - en: 1b1bc2d6b523f308 - zh: f7d0c0e0ab1adcf0 + en: f9d3b4bdedb897a6 + zh: 962f7638d30741fd /deepseek-ai-dsh-skill-filesystem/understand-the-implementation/discovery-flow: en: 7e26750408a63587 zh: 9eed631a7bdf00ae diff --git a/packages/skill/skill-filesystem/README.md b/packages/skill/skill-filesystem/README.md index 55ec66938f..7d99666ca3 100644 --- a/packages/skill/skill-filesystem/README.md +++ b/packages/skill/skill-filesystem/README.md @@ -103,7 +103,6 @@ The provider is built on two separations. First, catalog versus body: discovery | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry, provider, root resolution, frontmatter parsing, watch manager | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | ### Discovery flow diff --git a/packages/skill/skill-filesystem/README.zh.md b/packages/skill/skill-filesystem/README.zh.md index f5ffb1f60b..23c948c88b 100644 --- a/packages/skill/skill-filesystem/README.zh.md +++ b/packages/skill/skill-filesystem/README.zh.md @@ -103,7 +103,6 @@ skill 可以是被扫描根目录顶层的目录 bundle `/SKILL.md`,也 | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口、提供方、根解析、frontmatter 解析、监视管理器 | -| — | 不发布运行时不变式伴生入口;本包没有独立事件序列或可变数据关系,相关约定在所属 seam 强制执行。 | ### 发现流程 diff --git a/packages/skill/skill-office/README.i18n.yaml b/packages/skill/skill-office/README.i18n.yaml index 7c3388e39e..63b5a70500 100644 --- a/packages/skill/skill-office/README.i18n.yaml +++ b/packages/skill/skill-office/README.i18n.yaml @@ -24,8 +24,8 @@ en: 1bb872d63bdd78b7 zh: 355dee19bdfb3798 /deepseek-ai-dsh-skill-office/understand-the-implementation: - en: 3febb0cf7bad13a6 - zh: c3ab05db2b2f8828 + en: 9c45ee3de51adb4d + zh: 0f24d44dfa58df25 /deepseek-ai-dsh-skill-office/further-exploration: en: b08fc992559d8fda zh: ad74e037ecd81c0b diff --git a/packages/skill/skill-office/README.md b/packages/skill/skill-office/README.md index 2e5bd0b007..a30f86f57d 100644 --- a/packages/skill/skill-office/README.md +++ b/packages/skill/skill-office/README.md @@ -63,7 +63,6 @@ The provider registers three bundled candidates and reads their descriptions fro |---|---| | [`src/index.ts`](src/index.ts) | Provider registration and configured resource paths. | | [`assets/`](assets/) | Three workflows and the read-only OOXML checker. | -| — | No runtime invariant companion is published: the provider owns immutable candidates, and the skill registry owns registration lifecycle and precedence. | diff --git a/packages/skill/skill-office/README.zh.md b/packages/skill/skill-office/README.zh.md index c2d6e22cde..d4ee104feb 100644 --- a/packages/skill/skill-office/README.zh.md +++ b/packages/skill/skill-office/README.zh.md @@ -63,7 +63,6 @@ Excel 数据和公式任务跳过视觉检查;涉及格式、排版、图表 |---|---| | [`src/index.ts`](src/index.ts) | 提供方注册与配置资源路径。 | | [`assets/`](assets/) | 三种工作流和只读 OOXML 检查器。 | -| — | 不发布运行时不变量伴随入口:提供方拥有不可变候选项,skill 注册表负责注册生命周期和优先级。 | diff --git a/packages/skill/skill/README.i18n.yaml b/packages/skill/skill/README.i18n.yaml index 20a537f1bf..9d1d9d8d72 100644 --- a/packages/skill/skill/README.i18n.yaml +++ b/packages/skill/skill/README.i18n.yaml @@ -36,8 +36,8 @@ en: c0fb1850f00f24b1 zh: 226883ab5635435d /deepseek-ai-dsh-skill/understand-the-implementation/source-map: - en: 6cfbcb24c960a60c - zh: 5db5bd40e76b492a + en: c7f6f2ecb227c68b + zh: b2e924975aec9692 /deepseek-ai-dsh-skill/understand-the-implementation/catalog-collection: en: 58a9b57dc504b69f zh: 8fe8011247bc3f2b diff --git a/packages/skill/skill/README.md b/packages/skill/skill/README.md index 00857c826d..bfa04c9ca6 100644 --- a/packages/skill/skill/README.md +++ b/packages/skill/skill/README.md @@ -88,7 +88,6 @@ The registry is host+per-scope layered, the shape the tools registry established | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry, `SkillRegistry` service, candidate and definition validation, shared model-facing rendering | -| — | No runtime invariant companion is published; provider/runtime maps and revisioned caches mutate atomically inside the registry, which exposes no independent change event or snapshot for cross-checking them. | ### Catalog collection diff --git a/packages/skill/skill/README.zh.md b/packages/skill/skill/README.zh.md index b2f150cda8..69309b3bef 100644 --- a/packages/skill/skill/README.zh.md +++ b/packages/skill/skill/README.zh.md @@ -88,7 +88,6 @@ Skill 摘要保留胜出提供方可选的指令文件 `path`,供提供文件 | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口、`SkillRegistry` 服务、候选项与定义验证、共享的面向模型渲染 | -| — | 不发布运行时不变式伴生入口;提供方/运行时 map 与带 revision 的 cache 在注册表内原子变更,且没有独立变更事件或快照可供交叉核对。 | ### 目录收集 diff --git a/packages/skill/tool-skill/README.i18n.yaml b/packages/skill/tool-skill/README.i18n.yaml index 38fa753ad6..aef921496e 100644 --- a/packages/skill/tool-skill/README.i18n.yaml +++ b/packages/skill/tool-skill/README.i18n.yaml @@ -36,8 +36,8 @@ en: ee4380c98437d699 zh: 25f35ec8c8c47c00 /deepseek-ai-dsh-tool-skill/understand-the-implementation/source-map: - en: 9a8fa8b8305ef86a - zh: f80fbdfb2a8f1818 + en: 509699e3a467df41 + zh: ff7fc30ec4998e5a /deepseek-ai-dsh-tool-skill/understand-the-implementation/catalog-lifecycle: en: 847745cf78860904 zh: 55137ebef224ad48 diff --git a/packages/skill/tool-skill/README.md b/packages/skill/tool-skill/README.md index eeee637c25..e3c6e3ade9 100644 --- a/packages/skill/tool-skill/README.md +++ b/packages/skill/tool-skill/README.md @@ -77,7 +77,6 @@ The package is built on two ideas. First, the catalog is a durable projection, d | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: tool registration, catalog and gesture pre-step listeners, rendering and digest | -| — | No runtime invariant companion is published; this model-facing adapter has no independent lifecycle stream; execution relations are owned by the capability seam it calls. | ### Catalog lifecycle diff --git a/packages/skill/tool-skill/README.zh.md b/packages/skill/tool-skill/README.zh.md index f2c1adf49a..cea813cb16 100644 --- a/packages/skill/tool-skill/README.zh.md +++ b/packages/skill/tool-skill/README.zh.md @@ -77,7 +77,6 @@ agent 可以在会话期间发现并加载 skill。在首次请求前,如果 | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:工具注册、目录与手势 pre-step 监听器、渲染与 digest | -| — | 不发布运行时不变式伴生入口;这个面向模型的适配器没有独立的生命周期流;执行关系由它调用的能力 seam 负责。 | ### 目录生命周期 diff --git a/packages/skill/tool-workspace-dependencies/README.i18n.yaml b/packages/skill/tool-workspace-dependencies/README.i18n.yaml index 2855c549de..21841a72b4 100644 --- a/packages/skill/tool-workspace-dependencies/README.i18n.yaml +++ b/packages/skill/tool-workspace-dependencies/README.i18n.yaml @@ -27,8 +27,8 @@ en: 0f3e570525053f09 zh: 1b0cd3b653e1bd5d /deepseek-ai-dsh-tool-workspace-dependencies/understand-the-implementation: - en: 6483ea7f648cf57f - zh: 772c40e5cbed21f7 + en: 0121e57a754cb26f + zh: 2933fc0415da013d /deepseek-ai-dsh-tool-workspace-dependencies/further-exploration: en: e78508a7d92652c4 zh: 5788b94b7af15ada diff --git a/packages/skill/tool-workspace-dependencies/README.md b/packages/skill/tool-workspace-dependencies/README.md index 1f7147a1fe..ebfd2d4409 100644 --- a/packages/skill/tool-workspace-dependencies/README.md +++ b/packages/skill/tool-workspace-dependencies/README.md @@ -65,7 +65,6 @@ A container can copy both directories into an immutable image layer and set `DSH | File | Responsibility | |---|---| | [`src/index.ts`](src/index.ts) | Manifest validation, path derivation, in-place and installed preparation, tool registration. | -| — | No runtime invariant companion is published: the payload manifest is validated on every preparation, and the tool registry owns registration lifecycle. | diff --git a/packages/skill/tool-workspace-dependencies/README.zh.md b/packages/skill/tool-workspace-dependencies/README.zh.md index f2a1934256..41f1d5857d 100644 --- a/packages/skill/tool-workspace-dependencies/README.zh.md +++ b/packages/skill/tool-workspace-dependencies/README.zh.md @@ -65,7 +65,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 清单校验、路径推导、原地与安装两种准备方式、工具注册。 | -| — | 不发布运行时不变量伴随入口:每次准备都校验 payload 清单,工具注册表负责注册生命周期。 | diff --git a/packages/spill/spill-local/README.i18n.yaml b/packages/spill/spill-local/README.i18n.yaml index ef311ddf34..62cd34b4ae 100644 --- a/packages/spill/spill-local/README.i18n.yaml +++ b/packages/spill/spill-local/README.i18n.yaml @@ -39,8 +39,8 @@ en: 905f7242345308cd zh: 66907177a6dd4b94 /deepseek-ai-dsh-spill-local/understand-the-implementation/source-map: - en: d7746db31950fddb - zh: d7e2190e5c821801 + en: 4396717876acc2be + zh: a08321bdb3768143 /deepseek-ai-dsh-spill-local/understand-the-implementation/file-naming-and-write: en: cb92a60310c465a8 zh: ce474c16abd2df5d diff --git a/packages/spill/spill-local/README.md b/packages/spill/spill-local/README.md index 1c7ea86631..5ebb404f0b 100644 --- a/packages/spill/spill-local/README.md +++ b/packages/spill/spill-local/README.md @@ -85,7 +85,6 @@ The backend owns storage details only, on one principle: **a spilled artifact mu | [`src/index.ts`](src/index.ts) | Plugin entry: `Config`, the `LocalSpillStore` service, cleanup lifecycle, locator and retrieval-hint assembly | | [`src/cleanup.ts`](src/cleanup.ts) | One-shot age sweep, filesystem-identity checks, symlink and ownership safeguards | | [`src/store.ts`](src/store.ts) | Cordis-free storage mechanics: private root, session directory, safe-name encoding, exclusive write | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | ### File naming and write diff --git a/packages/spill/spill-local/README.zh.md b/packages/spill/spill-local/README.zh.md index 93ed836b9f..aba434f495 100644 --- a/packages/spill/spill-local/README.zh.md +++ b/packages/spill/spill-local/README.zh.md @@ -85,7 +85,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:`Config`、`LocalSpillStore` 服务、清理生命周期、定位信息与取回提示的组装 | | [`src/cleanup.ts`](src/cleanup.ts) | 一次性按年龄扫描、文件系统身份检查、符号链接和所有权保护 | | [`src/store.ts`](src/store.ts) | 与 Cordis 无关的存储机制:私有根目录、会话目录、安全名称编码、排他写入 | -| — | 不发布运行时不变式伴生入口;除由所属 seam 强制执行的约定外,本包不暴露独立事件序列或可变数据关系。 | ### 文件命名与写入 diff --git a/packages/spill/spill-policy/README.i18n.yaml b/packages/spill/spill-policy/README.i18n.yaml index 4ccd736930..ebaf6f5f3c 100644 --- a/packages/spill/spill-policy/README.i18n.yaml +++ b/packages/spill/spill-policy/README.i18n.yaml @@ -45,8 +45,8 @@ en: 916aa902ea376c42 zh: 51fbf25c1f1560d7 /deepseek-ai-dsh-spill-policy/understand-the-implementation/source-map: - en: 9a63af71cc19194d - zh: 0937a72b5444cff2 + en: 355ca3b01de5233e + zh: 19a7c28a57171573 /deepseek-ai-dsh-spill-policy/understand-the-implementation/failure-modes: en: 5640d59fdf0e064f zh: 7f92c52a1bdf5bff diff --git a/packages/spill/spill-policy/README.md b/packages/spill/spill-policy/README.md index d9caa9b2eb..faadc09562 100644 --- a/packages/spill/spill-policy/README.md +++ b/packages/spill/spill-policy/README.md @@ -98,7 +98,6 @@ The browser-safe `./notice` entry owns `formatSpillNotice(omitted, ref, images)` | [`src/index.ts`](src/index.ts) | Plugin entry: `Config` validation, the two waterfall listeners, the shared replacement helper | | [`src/notice.ts`](src/notice.ts) | Browser-safe notice formatting and recognition, published as `./notice` | | [`src/retention.ts`](src/retention.ts) | Pure ordered text/image head-tail retention | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | ### Failure modes diff --git a/packages/spill/spill-policy/README.zh.md b/packages/spill/spill-policy/README.zh.md index 4d2b6e8fe8..2521f8ec70 100644 --- a/packages/spill/spill-policy/README.zh.md +++ b/packages/spill/spill-policy/README.zh.md @@ -98,7 +98,6 @@ PTC 程序收到完整的规范值。含图片的子结果在转发给模型前 | [`src/index.ts`](src/index.ts) | 插件入口:`Config` 校验、两个 waterfall 监听器、共享替换辅助函数 | | [`src/notice.ts`](src/notice.ts) | 浏览器安全的通知格式化与识别,以 `./notice` 发布 | | [`src/retention.ts`](src/retention.ts) | 有序图文首尾保留的纯函数 | -| — | 不发布运行时不变式伴生入口;除在所属 seam 处强制执行的约定外,本包不公开独立的事件序列或可变数据关系。 | ### 故障模式 diff --git a/packages/spill/spill/README.i18n.yaml b/packages/spill/spill/README.i18n.yaml index 9ab3849f4c..3e94b36b2c 100644 --- a/packages/spill/spill/README.i18n.yaml +++ b/packages/spill/spill/README.i18n.yaml @@ -39,8 +39,8 @@ en: e7ef4635f8ff511a zh: 388fb525e843258e /deepseek-ai-dsh-spill/understand-the-implementation/source-map: - en: 6d40fb287fcc3a06 - zh: a04c301c57df9a39 + en: c24cc87afcef2824 + zh: c5138e85650eb20e /deepseek-ai-dsh-spill/understand-the-implementation/data-model: en: 7b1480904f62a1a8 zh: c51e680012536f2b diff --git a/packages/spill/spill/README.md b/packages/spill/spill/README.md index 9208da2f63..dbc448fc16 100644 --- a/packages/spill/spill/README.md +++ b/packages/spill/spill/README.md @@ -89,7 +89,6 @@ The package is built on one separation and a deliberate minimum: |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: the abstract `SpillStore` service and its `saveText` contract | | [`src/types.ts`](src/types.ts) | Vocabulary: `SaveTextSpill`, `SpillRef`, branded `SpillLocator`, `SpillOwner`, `SpillSource` | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | ### Data model diff --git a/packages/spill/spill/README.zh.md b/packages/spill/spill/README.zh.md index 279881b557..daa38007f0 100644 --- a/packages/spill/spill/README.zh.md +++ b/packages/spill/spill/README.zh.md @@ -89,7 +89,6 @@ const ref = await ctx.spillStore.saveText({ |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:抽象 `SpillStore` 服务及其 `saveText` 约定 | | [`src/types.ts`](src/types.ts) | 词汇:`SaveTextSpill`、`SpillRef`、带品牌类型 `SpillLocator`、`SpillOwner`、`SpillSource` | -| — | 不发布运行时不变式伴生入口;除归属 seam 强制执行的约定外,本包不暴露独立的事件序列或可变数据关系。 | ### 数据模型 diff --git a/packages/ssh/fs-ssh/README.i18n.yaml b/packages/ssh/fs-ssh/README.i18n.yaml index bf60aaadb5..1da392cf7d 100644 --- a/packages/ssh/fs-ssh/README.i18n.yaml +++ b/packages/ssh/fs-ssh/README.i18n.yaml @@ -33,5 +33,5 @@ en: 2e75cffd2ab863d6 zh: f8fdbabe27082f08 /deepseek-ai-dsh-fs-ssh/known-limitations-and-deferred-work/dev-note: - en: 6ca4f1d4b4810fff - zh: e42f580ca971972b + en: e67f633dc6fe7773 + zh: 8ec3e73528ddb1a6 diff --git a/packages/ssh/fs-ssh/README.md b/packages/ssh/fs-ssh/README.md index fe777f672a..6d0373c9bd 100644 --- a/packages/ssh/fs-ssh/README.md +++ b/packages/ssh/fs-ssh/README.md @@ -76,6 +76,6 @@ This provider contributes no request-prefix content. Its consumers own model-vis
Working context for maintainers — click to expand -No invariant companion is published. Wire validation and the owning filesystem, subprocess and sandbox providers enforce the observable obligations; this adapter adds no independently observed state relation. +None.
diff --git a/packages/ssh/fs-ssh/README.zh.md b/packages/ssh/fs-ssh/README.zh.md index 9aa92d9958..735479f260 100644 --- a/packages/ssh/fs-ssh/README.zh.md +++ b/packages/ssh/fs-ssh/README.zh.md @@ -76,6 +76,6 @@ kind: "package-reference"
维护者工作上下文 — 点击展开 -本包不发布不变式伴随入口。消息校验及所属文件系统、子进程和沙箱提供方执行可观察的约束;此适配器没有增加可独立观察的状态关系。 +无。
diff --git a/packages/ssh/sandbox-ssh/README.i18n.yaml b/packages/ssh/sandbox-ssh/README.i18n.yaml index 37863b31a1..ee36a6c423 100644 --- a/packages/ssh/sandbox-ssh/README.i18n.yaml +++ b/packages/ssh/sandbox-ssh/README.i18n.yaml @@ -33,5 +33,5 @@ en: 916d7c0e971765bb zh: 56d347094c07bc5d /deepseek-ai-dsh-sandbox-ssh/known-limitations-and-deferred-work/dev-note: - en: 6ca4f1d4b4810fff - zh: e42f580ca971972b + en: e67f633dc6fe7773 + zh: 8ec3e73528ddb1a6 diff --git a/packages/ssh/sandbox-ssh/README.md b/packages/ssh/sandbox-ssh/README.md index e8f6cb8d47..4837650f44 100644 --- a/packages/ssh/sandbox-ssh/README.md +++ b/packages/ssh/sandbox-ssh/README.md @@ -73,6 +73,6 @@ This provider contributes no request-prefix content. Its consumers own model-vis
Working context for maintainers — click to expand -No invariant companion is published. Wire validation and the owning filesystem, subprocess and sandbox providers enforce the observable obligations; this adapter adds no independently observed state relation. +None.
diff --git a/packages/ssh/sandbox-ssh/README.zh.md b/packages/ssh/sandbox-ssh/README.zh.md index 694ff99cb0..9135f2ffe3 100644 --- a/packages/ssh/sandbox-ssh/README.zh.md +++ b/packages/ssh/sandbox-ssh/README.zh.md @@ -73,6 +73,6 @@ kind: "package-reference"
维护者工作上下文 — 点击展开 -本包不发布不变式伴随入口。消息校验及所属文件系统、子进程和沙箱提供方执行可观察的约束;此适配器没有增加可独立观察的状态关系。 +无。
diff --git a/packages/ssh/ssh/README.i18n.yaml b/packages/ssh/ssh/README.i18n.yaml index 205811246b..6decad7abb 100644 --- a/packages/ssh/ssh/README.i18n.yaml +++ b/packages/ssh/ssh/README.i18n.yaml @@ -36,5 +36,5 @@ en: c173949c197e6deb zh: a951c8cfeca150ab /deepseek-ai-dsh-ssh/known-limitations-and-deferred-work/dev-note: - en: 6ca4f1d4b4810fff - zh: e42f580ca971972b + en: e67f633dc6fe7773 + zh: 8ec3e73528ddb1a6 diff --git a/packages/ssh/ssh/README.md b/packages/ssh/ssh/README.md index c978538491..e4e61497f1 100644 --- a/packages/ssh/ssh/README.md +++ b/packages/ssh/ssh/README.md @@ -101,6 +101,6 @@ This provider contributes no request-prefix content. Its consumers own model-vis
Working context for maintainers — click to expand -No invariant companion is published. Wire validation and the owning filesystem, subprocess and sandbox providers enforce the observable obligations; this adapter adds no independently observed state relation. +None.
diff --git a/packages/ssh/ssh/README.zh.md b/packages/ssh/ssh/README.zh.md index 20c91b199a..633ec9343e 100644 --- a/packages/ssh/ssh/README.zh.md +++ b/packages/ssh/ssh/README.zh.md @@ -101,6 +101,6 @@ OpenSSH 主连接承载私有管理 RPC。每条程序流使用独立转发的 U
维护者工作上下文 — 点击展开 -本包不发布不变式伴随入口。消息校验及所属文件系统、子进程和沙箱提供方执行可观察的约束;此适配器没有增加可独立观察的状态关系。 +无。
diff --git a/packages/ssh/subprocess-ssh/README.i18n.yaml b/packages/ssh/subprocess-ssh/README.i18n.yaml index 41d399c8db..7d620525e1 100644 --- a/packages/ssh/subprocess-ssh/README.i18n.yaml +++ b/packages/ssh/subprocess-ssh/README.i18n.yaml @@ -33,5 +33,5 @@ en: 0aa40f28ee1eb6e1 zh: da785a9b66cdbed9 /deepseek-ai-dsh-subprocess-ssh/known-limitations-and-deferred-work/dev-note: - en: 6ca4f1d4b4810fff - zh: e42f580ca971972b + en: e67f633dc6fe7773 + zh: 8ec3e73528ddb1a6 diff --git a/packages/ssh/subprocess-ssh/README.md b/packages/ssh/subprocess-ssh/README.md index ad5ddd52bd..2911ce164c 100644 --- a/packages/ssh/subprocess-ssh/README.md +++ b/packages/ssh/subprocess-ssh/README.md @@ -82,6 +82,6 @@ This provider contributes no request-prefix content. Its consumers own model-vis
Working context for maintainers — click to expand -No invariant companion is published. Wire validation and the owning filesystem, subprocess and sandbox providers enforce the observable obligations; this adapter adds no independently observed state relation. +None.
diff --git a/packages/ssh/subprocess-ssh/README.zh.md b/packages/ssh/subprocess-ssh/README.zh.md index c7a2e4d15e..56d53a1b90 100644 --- a/packages/ssh/subprocess-ssh/README.zh.md +++ b/packages/ssh/subprocess-ssh/README.zh.md @@ -82,6 +82,6 @@ kind: "package-reference"
维护者工作上下文 — 点击展开 -本包不发布不变式伴随入口。消息校验及所属文件系统、子进程和沙箱提供方执行可观察的约束;此适配器没有增加可独立观察的状态关系。 +无。
diff --git a/packages/storage/storage-domain/README.i18n.yaml b/packages/storage/storage-domain/README.i18n.yaml index 327f412a71..cfcfaac6df 100644 --- a/packages/storage/storage-domain/README.i18n.yaml +++ b/packages/storage/storage-domain/README.i18n.yaml @@ -42,11 +42,8 @@ en: 052da5d6c45205ca zh: 724c875e3c431fee /deepseek-ai-dsh-storage-domain/understand-the-implementation/source-map: - en: faade3f273420fc7 - zh: 2cd48a0e4a415f56 -/deepseek-ai-dsh-storage-domain/understand-the-implementation/invariant: - en: 03cb11c4ce394210 - zh: 0456212bf772c086 + en: d00abad122fbeed3 + zh: bfdcb479fa78242b /deepseek-ai-dsh-storage-domain/further-exploration: en: 45403f2aa04996cf zh: d9abd75cee7d413f diff --git a/packages/storage/storage-domain/README.md b/packages/storage/storage-domain/README.md index bfe5cc6c53..7869de5671 100644 --- a/packages/storage/storage-domain/README.md +++ b/packages/storage/storage-domain/README.md @@ -102,11 +102,6 @@ The domain layer is a single implementation, not an abstracted seam: consumers d | [`src/domain.ts`](src/domain.ts) | Open-domain runtime: write chain, table and global handles, close | | [`src/events.ts`](src/events.ts) | The `domain/changed` event vocabulary | | [`src/error.ts`](src/error.ts) | `DomainError` codes | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: every `domain/changed` agrees with in-memory state | - -### Invariant - -The `storage-domain-invariant` companion registers the owned relationship: every `domain/changed` event must agree with the emitting domain's authoritative in-memory state at emission — a divergence means a write path skipped the chain or emitted a stale value. diff --git a/packages/storage/storage-domain/README.zh.md b/packages/storage/storage-domain/README.zh.md index 692577b1d2..db7cc41dd1 100644 --- a/packages/storage/storage-domain/README.zh.md +++ b/packages/storage/storage-domain/README.zh.md @@ -102,11 +102,6 @@ domain.table('workspaces').update(id, (r) => ({ ...r, path: newPath })) | [`src/domain.ts`](src/domain.ts) | 已打开领域的运行时:写入链、表与全局句柄、关闭 | | [`src/events.ts`](src/events.ts) | `domain/changed` 事件词汇 | | [`src/error.ts`](src/error.ts) | `DomainError` 代码 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:每条 `domain/changed` 与内存状态一致 | - -### 不变式 - -`storage-domain-invariant` 伴生插件注册这条所属关系:每条 `domain/changed` 事件在发出时都必须与所属领域的权威内存状态一致——出现分叉意味着某条写入路径跳过了写入链或发出了陈旧值。 diff --git a/packages/storage/storage-domain/package.json b/packages/storage/storage-domain/package.json index 729a00bd81..331ba4d9d2 100644 --- a/packages/storage/storage-domain/package.json +++ b/packages/storage/storage-domain/package.json @@ -18,21 +18,15 @@ "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" ], "license": "MIT", "peerDependencies": { - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-storage": "workspace:*", "@deepseek-ai/cordis": "workspace:~" }, @@ -41,7 +35,6 @@ "zod": "^4.4.3" }, "devDependencies": { - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-storage": "workspace:*", "@deepseek-ai/cordis": "workspace:~" } diff --git a/packages/storage/storage-domain/src/index.ts b/packages/storage/storage-domain/src/index.ts index 7e0be5e151..e02b97049a 100644 --- a/packages/storage/storage-domain/src/index.ts +++ b/packages/storage/storage-domain/src/index.ts @@ -150,9 +150,8 @@ export class DomainFacility { ? globalSpec.initial : parseRecord(spec.name, '', '', () => globalSpec.schema.parse(snapshot.global)) // The onClosed hook runs strictly after teardown completes: writes - // landing during the drain still emit domain/changed, and the domain - // stays resolvable (the package invariant cross-checks each event) - // until fully closed — only then does the name free up for reopening. + // landing during the drain still emit domain/changed, and the name + // frees up for reopening only once the domain is fully closed. const domain: DomainImpl = new DomainImpl(this.ctx, spec, unit, tables, globalValue, () => { this.domains.delete(spec.name) this.reserved.delete(spec.name) @@ -175,8 +174,7 @@ export class DomainFacility { } /** - * Look up an open domain by name, untyped. Diagnostic surface (the package - * invariant cross-checks change events against live domain state); typed + * Look up an open domain by name, untyped. Diagnostic surface; typed * consumers hold the handle returned by {@link open}. * @param name - Domain name. * @returns the open domain runtime, or `undefined` when not open. @@ -228,7 +226,7 @@ export function apply(ctx: Context, config: Config): Promise { const unmount = domainCtx.storage.mount('domain', facility) return async () => { // Close leftovers before unmounting: draining writes still emit - // domain/changed, whose invariant resolves the facility through the hub. + // domain/changed. await facility.closeAll() unmount() } diff --git a/packages/storage/storage-domain/src/invariant.ts b/packages/storage/storage-domain/src/invariant.ts deleted file mode 100644 index 5386149115..0000000000 --- a/packages/storage/storage-domain/src/invariant.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-storage-domain`: every - * `domain/changed` event must agree with the emitting domain's authoritative - * in-memory state (the owned event-stream ↔ mutable-data relationship of this - * package). Writes emit strictly after mutating memory and the write chain - * serializes them, so at emission time the event's snapshot equals the - * current read — any divergence means a write path skipped the chain or - * emitted a stale value. - * @module @deepseek-ai/dsh-storage-domain/invariant - */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { DomainChanged } from './events.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-storage-domain' - -/** Cordis companion plugin name. */ -export const name = 'storage-domain-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Install the change-event ↔ memory-state agreement check. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - ctx.on('domain/changed', (change: DomainChanged) => { - const domain = ctx.storage.form('domain').get(change.domain) - if (domain === undefined) { - return fail(`domain/changed for '${change.domain}' emitted while that domain is not open`) - } - if (change.table === '') { - // Global write: the event snapshot must be the current global value. - if (domain.global.get() !== change.value) { - return fail(`domain/changed global value for '${change.domain}' differs from the in-memory global`) - } - return - } - const current = domain.table(change.table).get(change.key) - switch (change.operation) { - case 'deleted': - if (current !== undefined) { - return fail( - `domain/changed deletion of '${change.domain}'.'${change.table}'['${change.key}'] ` - + 'emitted while the record is still in memory', - ) - } - return - case 'put': - if (current !== change.value) { - return fail( - `domain/changed value for '${change.domain}'.'${change.table}'['${change.key}'] ` - + 'differs from the in-memory record', - ) - } - return - default: - change satisfies never - } - }, { global: true }) -}, { inject: ['storage'] }) - -/** - * 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)) diff --git a/packages/storage/storage-domain/tests/invariant.spec.ts b/packages/storage/storage-domain/tests/invariant.spec.ts deleted file mode 100644 index fb14ea7d06..0000000000 --- a/packages/storage/storage-domain/tests/invariant.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import { z } from 'zod' -import Storage from '@deepseek-ai/dsh-storage' -import InvariantRegistry, { InvariantError } from '@deepseek-ai/dsh-invariants' -import * as DomainInvariantCompanion from '@deepseek-ai/dsh-storage-domain/invariant' -import { DomainFacility, defineDomain, domainTable } from '../src/index.ts' -import type { DomainChanged } from '../src/events.ts' -import { MemoryStorageBackend } from './helpers/memory-backend.ts' - -const itemSchema = z.object({ n: z.number() }) -type Item = z.infer - -const spec = defineDomain({ - name: 'inv', - version: 1, - global: { schema: itemSchema, initial: { n: 0 } }, - tables: { rows: domainTable(itemSchema) }, -}) - -async function setup() { - const ctx = new Context() - await ctx.plugin(Storage) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(DomainInvariantCompanion) - ctx.storage.backend.register('memory', new MemoryStorageBackend()) - const facility = new DomainFacility(ctx, { backend: 'memory', routes: {} }) - ctx.storage.mount('domain', facility) - return { ctx, facility } -} - -const invariantViolation: unknown = expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-storage-domain', -}) - -describe('domain change-event invariants', () => { - it('accepts every write shape emitted by the real write paths', async () => { - const { facility } = await setup() - const domain = await facility.open(spec) - const rows = domain.table('rows') - await rows.put('a', { n: 1 }) - await rows.update('a', current => ({ n: current.n + 1 })) - await expect(rows.delete('a')).resolves.toBe(true) - await domain.global.set({ n: 5 }) - }) - - it('rejects an event for a domain that is not open', async () => { - const { ctx } = await setup() - expect(() => { ctx.emit('domain/changed', { - domain: 'ghost', table: 'rows', key: 'a', operation: 'put', value: { n: 1 }, - }) }).toThrow(invariantViolation) - }) - - it('rejects a put event whose value is not the in-memory record', async () => { - const { ctx, facility } = await setup() - const domain = await facility.open(spec) - await domain.table('rows').put('a', { n: 1 }) - expect(() => { ctx.emit('domain/changed', { - domain: 'inv', table: 'rows', key: 'a', operation: 'put', value: { n: 999 }, - }) }).toThrow(invariantViolation) - }) - - it('rejects a deletion event while the record is still in memory', async () => { - const { ctx, facility } = await setup() - const domain = await facility.open(spec) - await domain.table('rows').put('a', { n: 1 }) - expect(() => { ctx.emit('domain/changed', { - domain: 'inv', table: 'rows', key: 'a', operation: 'deleted', - }) }).toThrow(invariantViolation) - }) - - it('rejects a global event whose value is not the in-memory global', async () => { - const { ctx, facility } = await setup() - await facility.open(spec) - expect(() => { ctx.emit('domain/changed', { - domain: 'inv', table: '', key: '', operation: 'put', value: { n: 42 }, - }) }).toThrow(invariantViolation) - }) - - it('tolerates operations outside the closed union without failing falsely', async () => { - const { ctx, facility } = await setup() - const domain = await facility.open(spec) - await domain.table('rows').put('a', { n: 1 }) - // Merge-hostile input: the closed union's satisfies-never default arm is - // unreachable in typed code; an untyped emit must not crash the check. - expect(() => { ctx.emit('domain/changed', { - domain: 'inv', table: 'rows', key: 'a', operation: 'exotic', - } as unknown as DomainChanged) }).not.toThrow() - }) -}) diff --git a/packages/storage/storage-domain/tsconfig.json b/packages/storage/storage-domain/tsconfig.json index c0bae3e504..a7bea73bd4 100644 --- a/packages/storage/storage-domain/tsconfig.json +++ b/packages/storage/storage-domain/tsconfig.json @@ -19,9 +19,6 @@ }, { "path": "../storage" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/storage/storage-json/README.i18n.yaml b/packages/storage/storage-json/README.i18n.yaml index edd69413cc..6102e84e8f 100644 --- a/packages/storage/storage-json/README.i18n.yaml +++ b/packages/storage/storage-json/README.i18n.yaml @@ -36,8 +36,8 @@ en: b3de0a697db4f916 zh: 56b26f7823392bd5 /deepseek-ai-dsh-storage-json/understand-the-implementation/source-map: - en: 43ece66ff547f01a - zh: 9670f78047f4e653 + en: 77af81b16be4613e + zh: 29cb5047fa82d12a /deepseek-ai-dsh-storage-json/further-exploration: en: fa243a6d5778a3b4 zh: 64189101520d677c diff --git a/packages/storage/storage-json/README.md b/packages/storage/storage-json/README.md index d36c633339..3fd03738ee 100644 --- a/packages/storage/storage-json/README.md +++ b/packages/storage/storage-json/README.md @@ -96,7 +96,6 @@ A `per-record` table document at `///.json` has the form | [`src/per-record-unit.ts`](src/per-record-unit.ts) | One `per-record` unit: tree reads, path-safe records, and one-document writes | | [`src/format.ts`](src/format.ts) | Whole-unit and record serialization with version validation | | [`src/atomic.ts`](src/atomic.ts) | Atomic file replacement: temp write, fsync, rename, directory fsync | -| — | No runtime invariant companion is published; correctness here is write-durability and publish-then-reparse equivalence, which require medium round-trip tests (the shared backend conformance suite); the backend exposes no continuously observable in-process relation. | diff --git a/packages/storage/storage-json/README.zh.md b/packages/storage/storage-json/README.zh.md index e94e714547..d504694763 100644 --- a/packages/storage/storage-json/README.zh.md +++ b/packages/storage/storage-json/README.zh.md @@ -96,7 +96,6 @@ kind: "package-reference" | [`src/per-record-unit.ts`](src/per-record-unit.ts) | 一个 `per-record` 单元:目录树读取、路径安全记录与单文档写入 | | [`src/format.ts`](src/format.ts) | 带版本校验的整单元与记录序列化 | | [`src/atomic.ts`](src/atomic.ts) | 原子文件替换:临时文件写入、fsync、rename、目录 fsync | -| — | 不发布运行时不变式伴生入口;此处要求保证写入持久性及发布后重新解析的等价性,这两点需要通过介质往返测试(共享后端符合性测试套件)验证;本后端不公开任何可持续观察的进程内关系。 | diff --git a/packages/storage/storage-sqlite/README.i18n.yaml b/packages/storage/storage-sqlite/README.i18n.yaml index 7363a15dd4..da99dbb2f5 100644 --- a/packages/storage/storage-sqlite/README.i18n.yaml +++ b/packages/storage/storage-sqlite/README.i18n.yaml @@ -36,8 +36,8 @@ en: 50d85e8d8e8b5bc1 zh: 2a7067bd7fb47b35 /deepseek-ai-dsh-storage-sqlite/understand-the-implementation/source-map: - en: 67d89a03fae74e28 - zh: 853578def3ab01f1 + en: 79780e581c765030 + zh: fa2bb0eaf915aab6 /deepseek-ai-dsh-storage-sqlite/further-exploration: en: 07d30a2398975dae zh: 747bd86ba1056eab diff --git a/packages/storage/storage-sqlite/README.md b/packages/storage/storage-sqlite/README.md index 4744fa2a45..940a464490 100644 --- a/packages/storage/storage-sqlite/README.md +++ b/packages/storage/storage-sqlite/README.md @@ -84,7 +84,6 @@ Opening the database creates the parent as `0o700`, exclusively creates a missin | [`src/index.ts`](src/index.ts) | Plugin entry: backend registration, `path`/`journalMode` config, unit table | | [`src/schema.ts`](src/schema.ts) | Open sequence, physical layout version, metadata tables, record table naming | | [`src/unit.ts`](src/unit.ts) | One opened unit: prepared statements, JSON value parse, close | -| — | No runtime invariant companion is published; schema-version and unit-version consistency are open-time checks that reject before a unit exists, and durability needs the backend round-trip tests in the shared KV conformance suite; this package exposes no continuously observable in-process relation. | diff --git a/packages/storage/storage-sqlite/README.zh.md b/packages/storage/storage-sqlite/README.zh.md index b8357bfc4e..fb15673c73 100644 --- a/packages/storage/storage-sqlite/README.zh.md +++ b/packages/storage/storage-sqlite/README.zh.md @@ -84,7 +84,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:后端注册、`path`/`journalMode` 配置、单元表 | | [`src/schema.ts`](src/schema.ts) | 打开顺序、物理布局版本、元数据表、记录表命名 | | [`src/unit.ts`](src/unit.ts) | 一个已打开单元:预处理语句、JSON 值解析、关闭 | -| — | 不发布运行时不变式伴生入口;schema 版本与单元版本的一致性在打开时检查,不一致时会在单元创建前拒绝打开;持久性需要由共享 KV 符合性测试套件中的后端往返测试验证;本包不暴露可持续观察的进程内关系。 | diff --git a/packages/storage/storage/README.i18n.yaml b/packages/storage/storage/README.i18n.yaml index 72f8b87b7d..67a8db628b 100644 --- a/packages/storage/storage/README.i18n.yaml +++ b/packages/storage/storage/README.i18n.yaml @@ -39,8 +39,8 @@ en: 28b6c4e8efb4ffd1 zh: 4726c72b925c333f /deepseek-ai-dsh-storage/understand-the-implementation/source-map: - en: dc862f7fc6c02d10 - zh: 4600aeb07e2fdd4f + en: 506e29aac47a1913 + zh: b71dcbf4080e2c49 /deepseek-ai-dsh-storage/further-exploration: en: b900d2accc282f55 zh: 8079b59c6830d079 diff --git a/packages/storage/storage/README.md b/packages/storage/storage/README.md index edb833ad29..4b5f40b5f9 100644 --- a/packages/storage/storage/README.md +++ b/packages/storage/storage/README.md @@ -86,7 +86,6 @@ The hub is a pure registration table with two faces, designed so backends and da | [`src/registry.ts`](src/registry.ts) | `BackendRegistry`: name → backend table, registration disposers | | [`src/backend.ts`](src/backend.ts) | The backend contract: facets, units, `UNIT_NAME_RE` | | [`src/error.ts`](src/error.ts) | `StorageError` codes shared by the hub and every backend | -| — | No runtime invariant companion is published; the hub is a pure registration table (names → backends, forms → facilities) whose consistency is fully enforced at the call sites (duplicate/missing entries fail loud synchronously); it owns no event stream or mutable medium to cross-check. | | [`tests/contract.ts`](tests/contract.ts) | The shared conformance suite run against each backend | diff --git a/packages/storage/storage/README.zh.md b/packages/storage/storage/README.zh.md index 6860937053..b0bc512148 100644 --- a/packages/storage/storage/README.zh.md +++ b/packages/storage/storage/README.zh.md @@ -86,7 +86,6 @@ kind: "package-reference" | [`src/registry.ts`](src/registry.ts) | `BackendRegistry`:名称 → 后端表、注册资源释放函数 | | [`src/backend.ts`](src/backend.ts) | 后端约定:分面、单元、`UNIT_NAME_RE` | | [`src/error.ts`](src/error.ts) | 枢纽与每个后端共享的 `StorageError` 代码 | -| — | 不发布运行时不变式伴生入口;枢纽是纯注册表(名称 → 后端、形式 → 设施),其一致性完全由调用点强制保障(重复项或缺失项会同步明确报错);它既没有事件流,也没有可变介质可供交叉检查。 | | [`tests/contract.ts`](tests/contract.ts) | 针对每个后端运行的共享一致性套件 | diff --git a/packages/subagent/subagent-acp/README.i18n.yaml b/packages/subagent/subagent-acp/README.i18n.yaml index 27abbf3550..352650a23c 100644 --- a/packages/subagent/subagent-acp/README.i18n.yaml +++ b/packages/subagent/subagent-acp/README.i18n.yaml @@ -81,5 +81,5 @@ en: 53a0e7fb49fe6e23 zh: 51096d4eb51fb4ba /deepseek-ai-dsh-subagent-acp/known-limitations-and-deferred-work/dev-note: - en: 5f1add5d61924059 - zh: ab32b1c9d3433681 + en: d8323b3200db6ac9 + zh: f8a5273b887b9552 diff --git a/packages/subagent/subagent-acp/README.md b/packages/subagent/subagent-acp/README.md index 87fb7f3101..7225656d52 100644 --- a/packages/subagent/subagent-acp/README.md +++ b/packages/subagent/subagent-acp/README.md @@ -175,5 +175,3 @@ This Dev Note is working context for maintainers: open questions and undecided d - **Continuable ACP children** — would require persisting the remote session id and a per-child continuation advertisement. - -**Runtime invariant:** No companion is published. This package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. diff --git a/packages/subagent/subagent-acp/README.zh.md b/packages/subagent/subagent-acp/README.zh.md index 72b94cc89d..30f6322b81 100644 --- a/packages/subagent/subagent-acp/README.zh.md +++ b/packages/subagent/subagent-acp/README.zh.md @@ -175,5 +175,3 @@ spawn、初始化或新建会话失败会在发布前拒绝,通常先证明 ma - **可继续执行的 ACP 子 agent**——需要持久化远程会话 id,并为每个子 agent 声明继续执行能力。 - -**运行时不变式:** 不发布伴生入口。本包没有独立事件序列或可变数据关系,相关约定在所属 seam 强制执行。 diff --git a/packages/subagent/subagent-claude-code/README.i18n.yaml b/packages/subagent/subagent-claude-code/README.i18n.yaml index d30826170c..d8b1ffb19a 100644 --- a/packages/subagent/subagent-claude-code/README.i18n.yaml +++ b/packages/subagent/subagent-claude-code/README.i18n.yaml @@ -78,5 +78,5 @@ en: 9fbe41d8185bc0bf zh: 67435a4e57191729 /deepseek-ai-dsh-subagent-claude-code/known-limitations-and-deferred-work/dev-note: - en: f4ee67014233d3f1 - zh: 89f5e6b29ea18392 + en: 185beb4ff55f0946 + zh: 068ca7c57137eb33 diff --git a/packages/subagent/subagent-claude-code/README.md b/packages/subagent/subagent-claude-code/README.md index 20bb0847fb..590de7de7c 100644 --- a/packages/subagent/subagent-claude-code/README.md +++ b/packages/subagent/subagent-claude-code/README.md @@ -193,5 +193,3 @@ This Dev Note is working context for maintainers: open questions and undecided d - **Version-pinned protocol** — the runtime dependency is pinned to Agent SDK 0.3.263; upgrading pins a new SDK version and requires re-running the keyless real-product and loader-composition evidence. - -**Runtime invariant:** No companion is published. Lifecycle pairing belongs to the shared subagent service, and managed-range ownership belongs to the subprocess service. diff --git a/packages/subagent/subagent-claude-code/README.zh.md b/packages/subagent/subagent-claude-code/README.zh.md index eabdadb243..733dfa63ec 100644 --- a/packages/subagent/subagent-claude-code/README.zh.md +++ b/packages/subagent/subagent-claude-code/README.zh.md @@ -193,5 +193,3 @@ Claude Code 子级会在一个全新的 SDK query 中接收独立文本任务。 - **版本锁定的协议**——运行时依赖锁定为 Agent SDK 0.3.263;升级会锁定新的 SDK 版本,并需要重新运行无密钥真实产品与 loader 组合证据。 - -**运行时不变式:** 不发布伴生入口。生命周期配对属于共享 subagent 服务,受管范围的所有权属于 subprocess 服务。 diff --git a/packages/subagent/subagent-codex/README.i18n.yaml b/packages/subagent/subagent-codex/README.i18n.yaml index 79f3fec38e..aaa27ce3f7 100644 --- a/packages/subagent/subagent-codex/README.i18n.yaml +++ b/packages/subagent/subagent-codex/README.i18n.yaml @@ -78,5 +78,5 @@ en: 4716f8c13e7f1f24 zh: ab1e66bc56cde32c /deepseek-ai-dsh-subagent-codex/known-limitations-and-deferred-work/dev-note: - en: 7ab7eb087a0e3abb - zh: 5f3d74e7ad285e36 + en: cacc98025ed8913e + zh: e7673a3087ec0c62 diff --git a/packages/subagent/subagent-codex/README.md b/packages/subagent/subagent-codex/README.md index 3f90c9430c..8be39e30bc 100644 --- a/packages/subagent/subagent-codex/README.md +++ b/packages/subagent/subagent-codex/README.md @@ -191,5 +191,3 @@ This Dev Note is working context for maintainers: open questions and undecided d - **Version-pinned protocol** — the runtime dependency is pinned to `@openai/codex@0.153.4`; upgrading requires regenerating the upstream schema evidence and rerunning the credentialed nonce tests. - -**Runtime invariant:** No companion is published. Lifecycle pairing belongs to the shared subagent service, and managed-range ownership belongs to the subprocess service. diff --git a/packages/subagent/subagent-codex/README.zh.md b/packages/subagent/subagent-codex/README.zh.md index 9b122325ea..004e7c6e47 100644 --- a/packages/subagent/subagent-codex/README.zh.md +++ b/packages/subagent/subagent-codex/README.zh.md @@ -191,5 +191,3 @@ Codex 子级会在一个全新的临时线程中,以单个轮次接收这些 - **版本锁定的协议**——运行时依赖锁定为 `@openai/codex@0.153.4`;升级需要重新生成上游 schema 证据并重新运行带凭证的随机数测试。 - -**运行时不变式:** 不发布伴生入口。生命周期配对属于共享 subagent service,受管范围的所有权属于 subprocess service。 diff --git a/packages/subagent/subagent-dsh-sdk/README.i18n.yaml b/packages/subagent/subagent-dsh-sdk/README.i18n.yaml index bdbebfa328..5236096196 100644 --- a/packages/subagent/subagent-dsh-sdk/README.i18n.yaml +++ b/packages/subagent/subagent-dsh-sdk/README.i18n.yaml @@ -81,5 +81,5 @@ en: 01cbee2a855b1d6a zh: 701e7afc9a7fc862 /deepseek-ai-dsh-subagent-dsh-sdk/known-limitations-and-deferred-work/dev-note: - en: 854b5d4640bb8c07 - zh: acd7a2aa4a4f457c + en: 04a404ccea615ef3 + zh: df2d9d901d75f024 diff --git a/packages/subagent/subagent-dsh-sdk/README.md b/packages/subagent/subagent-dsh-sdk/README.md index 013fc6eed1..9174c85896 100644 --- a/packages/subagent/subagent-dsh-sdk/README.md +++ b/packages/subagent/subagent-dsh-sdk/README.md @@ -186,5 +186,3 @@ This Dev Note is working context for maintainers: open questions and undecided d - **Remote runtimes** — a remote runtime would need its own backend and workspace mapping. - -**Runtime invariant:** No companion is published. Run lifecycle pairing is owned and checked by the subagent seam's invariant; this backend's own state lives in the child process beyond this context's event streams. diff --git a/packages/subagent/subagent-dsh-sdk/README.zh.md b/packages/subagent/subagent-dsh-sdk/README.zh.md index b5d8816fae..c7192620db 100644 --- a/packages/subagent/subagent-dsh-sdk/README.zh.md +++ b/packages/subagent/subagent-dsh-sdk/README.zh.md @@ -186,5 +186,3 @@ kind: "package-reference" - **远程运行时**——远程运行时需要独立的后端与工作区映射。 - -**运行时不变式:** 不发布伴生入口。run 生命周期配对由 subagent seam 的不变式检查;本后端自身的状态位于子进程中,不在当前上下文的事件流内。 diff --git a/packages/subagent/subagent-fork-in-process/README.i18n.yaml b/packages/subagent/subagent-fork-in-process/README.i18n.yaml index 14024bb44e..888a7e0d6e 100644 --- a/packages/subagent/subagent-fork-in-process/README.i18n.yaml +++ b/packages/subagent/subagent-fork-in-process/README.i18n.yaml @@ -36,8 +36,8 @@ en: 3917de9c152828e0 zh: 4c48bb580510ef0e /deepseek-ai-dsh-subagent-fork-in-process/understand-the-implementation/source-map: - en: b629a79a8b4de729 - zh: 3b3019101cf70c54 + en: f1f45255fd9f71c1 + zh: 9a3f016b8fe9754a /deepseek-ai-dsh-subagent-fork-in-process/understand-the-implementation/run-flow: en: 59b82d0f46c21967 zh: aacc5455eaabc8f7 diff --git a/packages/subagent/subagent-fork-in-process/README.md b/packages/subagent/subagent-fork-in-process/README.md index 0c8ff53727..0ea2c64b22 100644 --- a/packages/subagent/subagent-fork-in-process/README.md +++ b/packages/subagent/subagent-fork-in-process/README.md @@ -76,7 +76,6 @@ One difference from spawn, expressed as data: the backend computes the balanced | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Provider registration: prefix computation, `Config` schema, capability declaration | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | ### Run flow diff --git a/packages/subagent/subagent-fork-in-process/README.zh.md b/packages/subagent/subagent-fork-in-process/README.zh.md index c1098825dc..9e4c112886 100644 --- a/packages/subagent/subagent-fork-in-process/README.zh.md +++ b/packages/subagent/subagent-fork-in-process/README.zh.md @@ -76,7 +76,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 提供方注册:前缀计算、`Config` schema、能力声明 | -| — | 不发布运行时不变式伴生入口;本包没有独立事件序列或可变数据关系,相关约定在所属 seam 强制执行。 | ### 运行流程 diff --git a/packages/subagent/subagent-fork-in-process/package.json b/packages/subagent/subagent-fork-in-process/package.json index 30c1e65072..aa47ce3929 100644 --- a/packages/subagent/subagent-fork-in-process/package.json +++ b/packages/subagent/subagent-fork-in-process/package.json @@ -41,7 +41,6 @@ "@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-subagent": "workspace:*", diff --git a/packages/subagent/subagent-fork-in-process/tests/multi-subagent.spec.ts b/packages/subagent/subagent-fork-in-process/tests/multi-subagent.spec.ts index 26b54630d0..c9c20c7db2 100644 --- a/packages/subagent/subagent-fork-in-process/tests/multi-subagent.spec.ts +++ b/packages/subagent/subagent-fork-in-process/tests/multi-subagent.spec.ts @@ -4,10 +4,6 @@ import { Context } from '@deepseek-ai/cordis' import { SessionId } from '@deepseek-ai/dsh-session' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant' -import * as AgentInvariant from '@deepseek-ai/dsh-agent/invariant' -import * as AgentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant' import SubagentRuntime, { type SubagentStartRequest } from '@deepseek-ai/dsh-subagent' import * as Spawn from '@deepseek-ai/dsh-subagent-spawn-in-process' import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -15,13 +11,6 @@ import * as fork from '../src/index.ts' type Script = ConstructorParameters[0] -async function mountInvariants(ctx: Context): Promise { - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SessionInvariant) - await ctx.plugin(AgentInvariant) - await ctx.plugin(AgentLoopInvariant) -} - function start(ctx: Context, provider: string, request: Omit & { signal?: AbortSignal }) { return ctx.subagents.start(provider, { signal: request.signal ?? new AbortController().signal, ...request }) } @@ -35,7 +24,6 @@ function start(ctx: Context, provider: string, request: Omit[0] -async function mountInvariants(ctx: Context): Promise { - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SessionInvariant) - await ctx.plugin(AgentInvariant) - await ctx.plugin(AgentLoopInvariant) -} - function start(ctx: Context, provider: string, request: Omit & { signal?: AbortSignal }) { return ctx.subagents.start(provider, { signal: request.signal ?? new AbortController().signal, ...request }) } @@ -35,15 +24,11 @@ function start(ctx: Context, provider: string, request: Omit { await run.dispose() }) - it('produces an invariant-CLEAN seed: forking mid-turn excludes the open turn', async () => { + it('produces a balanced seed: forking mid-turn excludes the open turn', async () => { // Drive the parent so it has one completed turn, then start a SECOND turn that is still // open (a hanging model call), and fork while it's in flight. The seed must stop after the - // balanced first turn; including the open turn would fail invariant replay during start. + // balanced first turn. const { ctx, parent } = await setup([textResponse('done'), 'hang', textResponse('child')]) parent.followup(createUserMessage({ content: [{ type: 'text', text: 'q1' }], source: { kind: 'user' } })) await parent.whenIdle() diff --git a/packages/subagent/subagent-in-process-driver/README.i18n.yaml b/packages/subagent/subagent-in-process-driver/README.i18n.yaml index 0652ba509c..f5df33f562 100644 --- a/packages/subagent/subagent-in-process-driver/README.i18n.yaml +++ b/packages/subagent/subagent-in-process-driver/README.i18n.yaml @@ -39,8 +39,8 @@ en: 102689042efe8d9d zh: 6e9e8ffe2d4200e1 /deepseek-ai-dsh-subagent-in-process-driver/understand-the-implementation/source-map: - en: 1afa2487d2e7273f - zh: 24ca22c87186d367 + en: d01e9ad4d4106a2d + zh: 598427a5fe09d657 /deepseek-ai-dsh-subagent-in-process-driver/further-exploration: en: 092428bf90f2ed01 zh: b5979a2a7bcfd083 diff --git a/packages/subagent/subagent-in-process-driver/README.md b/packages/subagent/subagent-in-process-driver/README.md index fe9703a2bc..d1ee116a26 100644 --- a/packages/subagent/subagent-in-process-driver/README.md +++ b/packages/subagent/subagent-in-process-driver/README.md @@ -73,7 +73,6 @@ The required request signal covers both startup and the live run. Before publica |---|---| | [`src/index.ts`](src/index.ts) | Run driver: creation, one-turn drive, result reading, disposal | | [`src/structured.ts`](src/structured.ts) | Structured-output runtime: capture tool, prompt section, guard, commit | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | diff --git a/packages/subagent/subagent-in-process-driver/README.zh.md b/packages/subagent/subagent-in-process-driver/README.zh.md index 5ee7447829..8ad22ffb31 100644 --- a/packages/subagent/subagent-in-process-driver/README.zh.md +++ b/packages/subagent/subagent-in-process-driver/README.zh.md @@ -73,7 +73,6 @@ kind: "package-library" |---|---| | [`src/index.ts`](src/index.ts) | 运行驱动器:创建、单轮驱动、结果读取、dispose | | [`src/structured.ts`](src/structured.ts) | 结构化输出运行时:捕获工具、提示词段、防护、提交 | -| — | 不发布运行时不变式伴生入口;本包没有独立事件序列或可变数据关系,相关约定在所属 seam 强制执行。 | diff --git a/packages/subagent/subagent-in-process-driver/package.json b/packages/subagent/subagent-in-process-driver/package.json index 9ce06cc20c..517232c298 100644 --- a/packages/subagent/subagent-in-process-driver/package.json +++ b/packages/subagent/subagent-in-process-driver/package.json @@ -44,7 +44,6 @@ "@deepseek-ai/dsh-agent-loop-testkit": "workspace:*", "@deepseek-ai/dsh-agent-preset-registry": "workspace:*", "@deepseek-ai/dsh-fs-sandbox": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-sandbox-policy": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", diff --git a/packages/subagent/subagent-in-process-driver/tests/structured.spec.ts b/packages/subagent/subagent-in-process-driver/tests/structured.spec.ts index 9d06368496..d0663838aa 100644 --- a/packages/subagent/subagent-in-process-driver/tests/structured.spec.ts +++ b/packages/subagent/subagent-in-process-driver/tests/structured.spec.ts @@ -4,11 +4,7 @@ import { createUserMessage, ToolCallId, type ContentBlock, type GenerateOptions import { SessionId } from '@deepseek-ai/dsh-session' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' import type {} from '@deepseek-ai/dsh-system-prompt' -import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant' -import * as AgentInvariant from '@deepseek-ai/dsh-agent/invariant' -import * as AgentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant' import SubagentRuntime, { type ResolvedSubagentStartRequest, type SubagentStartRequest, @@ -26,13 +22,6 @@ const testToolSignal = new AbortController().signal type Script = ConstructorParameters[0] -async function mountInvariants(ctx: Context): Promise { - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SessionInvariant) - await ctx.plugin(AgentInvariant) - await ctx.plugin(AgentLoopInvariant) -} - interface PtcRunRequestLike { bindings: { global: string; functions: Record Promise> }[] } @@ -67,7 +56,6 @@ async function setup(script: Script, options: SetupOptions = {}) { run: options.codeRun ?? (() => Promise.resolve({ logs: [] })), } as never) } - await mountInvariants(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SubagentRuntime) const disposeProvider = ctx.subagents.registerProvider({ diff --git a/packages/subagent/subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts b/packages/subagent/subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts index 3c50c9debf..e72af0fe6c 100644 --- a/packages/subagent/subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts +++ b/packages/subagent/subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts @@ -6,10 +6,6 @@ import { type Agent, type AgentOptions } from '@deepseek-ai/dsh-agent' import { SessionId } from '@deepseek-ai/dsh-session' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant' -import * as AgentInvariant from '@deepseek-ai/dsh-agent/invariant' -import * as AgentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant' import SubagentRuntime, { snapshotSubagentDescriptor } from '@deepseek-ai/dsh-subagent' import { defineContentToolFixture } from '@deepseek-ai/dsh-tools' import { maxTokensResponse, MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -23,17 +19,9 @@ declare module '@deepseek-ai/dsh-llm' { type Script = ConstructorParameters[0] -async function mountInvariants(ctx: Context): Promise { - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SessionInvariant) - await ctx.plugin(AgentInvariant) - await ctx.plugin(AgentLoopInvariant) -} - async function setup(script: Script, parentOptions: Partial = {}) { const ctx = new Context() await mountAgentLoopTestDependencies(ctx) - await mountInvariants(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SubagentRuntime) const adapter = new MockAdapter(script) diff --git a/packages/subagent/subagent-spawn-in-process/README.i18n.yaml b/packages/subagent/subagent-spawn-in-process/README.i18n.yaml index 252819dbfb..184df1d3aa 100644 --- a/packages/subagent/subagent-spawn-in-process/README.i18n.yaml +++ b/packages/subagent/subagent-spawn-in-process/README.i18n.yaml @@ -33,8 +33,8 @@ en: 80716c24d7a0ee78 zh: 9bead3317f5a357e /deepseek-ai-dsh-subagent-spawn-in-process/understand-the-implementation/source-map: - en: 6252313b7a004fda - zh: 85c1b0a872d4d846 + en: f23771c3d213ffb1 + zh: c0b16b102dc26345 /deepseek-ai-dsh-subagent-spawn-in-process/understand-the-implementation/run-flow: en: 7b55a5649bc38155 zh: 51fe220c84ca759a diff --git a/packages/subagent/subagent-spawn-in-process/README.md b/packages/subagent/subagent-spawn-in-process/README.md index ce3909934e..d9f388a56e 100644 --- a/packages/subagent/subagent-spawn-in-process/README.md +++ b/packages/subagent/subagent-spawn-in-process/README.md @@ -72,7 +72,6 @@ One separation: this backend contributes only the provider registration and the | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Provider registration: `Config` schema, capability declaration, `start()` | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | ### Run flow diff --git a/packages/subagent/subagent-spawn-in-process/README.zh.md b/packages/subagent/subagent-spawn-in-process/README.zh.md index 998b90ff08..0c531851a2 100644 --- a/packages/subagent/subagent-spawn-in-process/README.zh.md +++ b/packages/subagent/subagent-spawn-in-process/README.zh.md @@ -72,7 +72,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 提供方注册:`Config` schema、能力声明、`start()` | -| — | 不发布运行时不变式伴生入口;本包没有独立事件序列或可变数据关系,相关约定在所属 seam 强制执行。 | ### 运行流程 diff --git a/packages/subagent/subagent-spawn-in-process/package.json b/packages/subagent/subagent-spawn-in-process/package.json index d73e9a649a..8d067e8b79 100644 --- a/packages/subagent/subagent-spawn-in-process/package.json +++ b/packages/subagent/subagent-spawn-in-process/package.json @@ -41,7 +41,6 @@ "@deepseek-ai/dsh-agent-loop-testkit": "workspace:*", "@deepseek-ai/dsh-bash-local": "workspace:*", "@deepseek-ai/dsh-subprocess-local": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-llm-deepseek": "workspace:*", "@deepseek-ai/dsh-llm-deepseek-api-key": "workspace:*", diff --git a/packages/subagent/subagent-spawn-in-process/tests/subagent-spawn-in-process.spec.ts b/packages/subagent/subagent-spawn-in-process/tests/subagent-spawn-in-process.spec.ts index e199b7c9aa..4fd7802069 100644 --- a/packages/subagent/subagent-spawn-in-process/tests/subagent-spawn-in-process.spec.ts +++ b/packages/subagent/subagent-spawn-in-process/tests/subagent-spawn-in-process.spec.ts @@ -6,10 +6,6 @@ import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent' import { SessionId } from '@deepseek-ai/dsh-session' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant' -import * as AgentInvariant from '@deepseek-ai/dsh-agent/invariant' -import * as AgentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant' import SubagentRuntime, { type SubagentStartRequest } from '@deepseek-ai/dsh-subagent' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' import { MockAdapter, maxTokensResponse, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' @@ -19,17 +15,9 @@ import { defineContentToolFixture } from '@deepseek-ai/dsh-tools' type Script = ConstructorParameters[0] -async function mountInvariants(ctx: Context): Promise { - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SessionInvariant) - await ctx.plugin(AgentInvariant) - await ctx.plugin(AgentLoopInvariant) -} - /** * Drives the REAL spawn backend end-to-end: a real agent loop + a scripted mock - * MODEL (the only mocked boundary) + the real SubagentRuntime + the real - * invariant service plus package companions (so a malformed child session log would fail the test). + * MODEL (the only mocked boundary) + the real SubagentRuntime. * The parent is a real config agent; the spawn provider creates a real child * agent on the same context and we assert its output. */ @@ -37,7 +25,6 @@ async function setup(script: Script) { const ctx = new Context() const adapter = new MockAdapter(script) await mountAgentLoopTestDependencies(ctx) - await mountInvariants(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SubagentRuntime) await ctx.plugin(spawn, { providerName: 'spawn' }) @@ -334,7 +321,6 @@ describe('dsh-subagent-spawn-in-process', () => { const ctx = new Context() const adapter = new MockAdapter(['hang']) await mountAgentLoopTestDependencies(ctx) - await mountInvariants(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SubagentRuntime) const fiber = await ctx.plugin(spawn, { providerName: 'spawn' }) diff --git a/packages/subagent/subagent/package.json b/packages/subagent/subagent/package.json index 44dc2295fb..c854328f43 100644 --- a/packages/subagent/subagent/package.json +++ b/packages/subagent/subagent/package.json @@ -22,10 +22,6 @@ "types": "./lib/types/internal.d.ts", "default": "./lib/types/internal.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./client": { "types": "./lib/types/client.d.ts", "default": "./lib/types/client.js" @@ -43,7 +39,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts", "lib/typert.host.js", @@ -64,7 +59,6 @@ "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-agent-preset-registry": "workspace:*", "@deepseek-ai/dsh-attachment": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-jobs": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-sandbox": "workspace:*", @@ -122,7 +116,6 @@ "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-agent-preset-registry": "workspace:*", "@deepseek-ai/dsh-attachment": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-jobs": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-sandbox": "workspace:*", diff --git a/packages/subagent/subagent/src/index.ts b/packages/subagent/subagent/src/index.ts index 6654c1c0e5..baf4ef3f26 100644 --- a/packages/subagent/subagent/src/index.ts +++ b/packages/subagent/subagent/src/index.ts @@ -159,7 +159,6 @@ declare module '@deepseek-ai/cordis' { * parent-scoped listener observes only its own delegations. Paired with * `subagent/end`. * @param info - the provider and published child identity. - * @dshScopeScan unsupported * @mode emit */ 'subagent/start'(this: Scoped, info: SubagentRunInfo): void @@ -168,7 +167,6 @@ declare module '@deepseek-ai/cordis' { * parent carrier as `subagent/start`, so the lifecycle pair reaches the * same scoped audience. * @param info - the run identity and terminal outcome. - * @dshScopeScan unsupported * @mode emit */ 'subagent/end'(this: Scoped, info: SubagentRunEndInfo): void diff --git a/packages/subagent/subagent/src/invariant.ts b/packages/subagent/subagent/src/invariant.ts deleted file mode 100644 index c2ef451920..0000000000 --- a/packages/subagent/subagent/src/invariant.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** Package-owned subagent registry and lifecycle invariants. @module @deepseek-ai/dsh-subagent/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { SubagentProvider, SubagentRunEndInfo, SubagentRunInfo } from './types.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-subagent' - -/** Cordis companion plugin name. */ -export const name = 'subagent-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Assert that a terminal lifecycle payload matches its start identity. */ -function validateRunEnd(start: SubagentRunInfo, end: SubagentRunEndInfo, fail: InvariantFailure): void { - if (start.provider !== end.provider || start.id !== end.id || start.local !== end.local) { - fail(`subagent/end identity diverges from subagent/start for run ${JSON.stringify(end.runId)}`) - } -} - -/** Install provider-registry and start/end pairing checks. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const providers = new Set(ctx.subagents.list()) - const runs = new Map() - const stagedProviders = new WeakSet() - const stagedRemovals = new Set() - const stagedStarts = new WeakSet() - const stagedEnds = new WeakSet() - - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName === 'subagent/provider-added') { - const provider = args[0] as SubagentProvider - if (provider.name.length === 0) fail('subagent provider names must be non-empty') - if (providers.has(provider.name)) fail(`subagent/provider-added repeated ${JSON.stringify(provider.name)}`) - stagedProviders.add(provider) - return - } - if (eventName === 'subagent/provider-removed') { - const providerName = args[0] as string - if (!providers.has(providerName)) fail(`subagent/provider-removed names unknown provider ${JSON.stringify(providerName)}`) - stagedRemovals.add(providerName) - return - } - if (eventName === 'subagent/start') { - const info = args[0] as SubagentRunInfo - // Provider availability is an admission-time relationship. A published - // one-shot run may outlive provider removal, and a cold-resumed Activation - // records the initial provider name without dispatching through it. - if (info.provider.length === 0 || String(info.runId).length === 0 || String(info.id).length === 0) { - fail('subagent/start provider, runId, and child id must be non-empty') - } - if (runs.has(info.runId)) fail(`subagent/start repeated run id ${JSON.stringify(info.runId)}`) - stagedStarts.add(info) - return - } - if (eventName !== 'subagent/end') return - const info = args[0] as SubagentRunEndInfo - const start = runs.get(info.runId) - if (start === undefined) fail(`subagent/end has no matching subagent/start for run ${JSON.stringify(info.runId)}`) - validateRunEnd(start, info, fail) - stagedEnds.add(info) - }, { global: true }) - - ctx.on('subagent/provider-added', (provider) => { - /* v8 ignore next -- internal/dispatch stages the same provider object */ - if (!stagedProviders.delete(provider)) return - providers.add(provider.name) - }, { global: true }) - ctx.on('subagent/provider-removed', (providerName) => { - /* v8 ignore next -- internal/dispatch stages the same provider name */ - if (!stagedRemovals.delete(providerName)) return - providers.delete(providerName) - }, { global: true }) - ctx.on('subagent/start', (info) => { - /* v8 ignore next -- internal/dispatch stages the same lifecycle object */ - if (!stagedStarts.delete(info)) return - runs.set(info.runId, info) - }, { global: true }) - ctx.on('subagent/end', (info) => { - /* v8 ignore next -- internal/dispatch stages the same lifecycle object */ - if (!stagedEnds.delete(info)) return - runs.delete(info.runId) - }, { global: true }) -}, { inject: ['subagents'] }) - -/** - * Register the subagent 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)) diff --git a/packages/subagent/subagent/tests/continuation.spec.ts b/packages/subagent/subagent/tests/continuation.spec.ts index 3078d28e71..bb2b5badb1 100644 --- a/packages/subagent/subagent/tests/continuation.spec.ts +++ b/packages/subagent/subagent/tests/continuation.spec.ts @@ -19,7 +19,6 @@ import * as SubagentFork from '@deepseek-ai/dsh-subagent-fork-in-process' import type { ContentBlock, GenerateOptions, MessageId, StreamChunk } from '@deepseek-ai/dsh-llm' import { ToolCallId, createUserMessage, LlmAdapter, ReasoningEffortId } from '@deepseek-ai/dsh-llm' import { defineTool } from '@deepseek-ai/dsh-tools' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' import { MockAdapter, maxTokensResponse, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' import SubagentRuntime, { SubagentError, @@ -27,7 +26,6 @@ import SubagentRuntime, { } from '../src/index.ts' import type { SubagentRunEndInfo, SubagentRunInfo } from '../src/index.ts' import type { SubagentPromptRequestId } from '../src/control-types.ts' -import * as SubagentInvariant from '../src/invariant.ts' import { TestSessionQuery } from './test-session-query.ts' import { loadStoredSession } from './persistence-helpers.ts' import { @@ -1109,8 +1107,6 @@ describe('direct-child Queue residency routing', () => { it('cold-resumes after the initial provider unregisters', async () => { const { ctx, parent } = await setup([textResponse('first'), textResponse('after resume')]) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SubagentInvariant) const disposeProvider = ctx.subagents.registerProvider({ name: 'retired', capabilities: { agentOptions: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false }, diff --git a/packages/subagent/subagent/tests/invariant.spec.ts b/packages/subagent/subagent/tests/invariant.spec.ts deleted file mode 100644 index a902c63b0d..0000000000 --- a/packages/subagent/subagent/tests/invariant.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import { scopeTarget } from '@deepseek-ai/dsh-scope' -import { SessionId } from '@deepseek-ai/dsh-session' -import SubagentRuntime, { SubagentRunId } from '@deepseek-ai/dsh-subagent' -import type { - SubagentProvider, - SubagentRunEndInfo, - SubagentRunInfo, -} from '@deepseek-ai/dsh-subagent' -import * as SubagentInvariant from '@deepseek-ai/dsh-subagent/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' - -async function setup(): Promise { - const ctx = new Context() - // The registry is a required injection of SubagentRuntime (its projection - // units register in the constructor). - await ctx.plugin(SessionProjectionRegistry) - await ctx.plugin(SubagentRuntime) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SubagentInvariant) - return ctx -} - -const provider = (name: string): SubagentProvider => ({ - name, - capabilities: { agentOptions: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false }, - inheritsParentContext: false, - start: async () => { throw new Error('not used') }, -}) - -const start = (overrides: Partial = {}): SubagentRunInfo => ({ - runId: SubagentRunId('run-1'), - provider: 'mock', - id: SessionId('child-1'), - local: false, - ...overrides, -}) - -const end = (overrides: Partial = {}): SubagentRunEndInfo => ({ - ...start(), - stopReason: 'completed', - ...overrides, -}) - -function emitRun(ctx: Context, name: 'subagent/start', info: SubagentRunInfo): void -function emitRun(ctx: Context, name: 'subagent/end', info: SubagentRunEndInfo): void -function emitRun(ctx: Context, name: 'subagent/start' | 'subagent/end', info: SubagentRunInfo | SubagentRunEndInfo): void { - ctx.emit(scopeTarget(ctx.subagents, {}), name as 'subagent/start', info) -} - -describe('subagent invariants', () => { - it('accepts provider and run lifecycle pairs', async () => { - const ctx = await setup() - const mock = provider('mock') - ctx.emit('subagent/provider-added', mock) - emitRun(ctx, 'subagent/start', start()) - emitRun(ctx, 'subagent/end', end()) - ctx.emit('subagent/provider-removed', 'mock') - ctx.emit('tools/change') - }) - - it('rejects malformed provider transitions', async () => { - const ctx = await setup() - expect(() => { ctx.emit('subagent/provider-added', provider('')) }).toThrow(/names must be non-empty/) - const mock = provider('mock') - ctx.emit('subagent/provider-added', mock) - expect(() => { ctx.emit('subagent/provider-added', mock) }).toThrow(/repeated "mock"/) - expect(() => { ctx.emit('subagent/provider-removed', 'missing') }).toThrow(/unknown provider/) - }) - - it('rejects malformed and unpaired run transitions', async () => { - const ctx = await setup() - expect(() => { emitRun(ctx, 'subagent/start', start({ provider: '' })) }) - .toThrow(/provider, runId, and child id must be non-empty/) - expect(() => { emitRun(ctx, 'subagent/start', start({ runId: SubagentRunId('') })) }) - .toThrow(/provider, runId, and child id must be non-empty/) - emitRun(ctx, 'subagent/start', start()) - expect(() => { emitRun(ctx, 'subagent/start', start()) }).toThrow(/repeated run id/) - expect(() => { emitRun(ctx, 'subagent/end', end({ runId: SubagentRunId('missing') })) }) - .toThrow(/no matching subagent\/start/) - expect(() => { emitRun(ctx, 'subagent/end', end({ id: SessionId('other') })) }) - .toThrow(/identity diverges/) - }) - - it('accepts the recorded provider name after registration ends', async () => { - const ctx = await setup() - const historical = provider('historical') - ctx.emit('subagent/provider-added', historical) - ctx.emit('subagent/provider-removed', historical.name) - - emitRun(ctx, 'subagent/start', start({ provider: historical.name })) - emitRun(ctx, 'subagent/end', end({ provider: historical.name })) - }) -}) diff --git a/packages/subagent/subagent/tsconfig.json b/packages/subagent/subagent/tsconfig.json index b72eb79441..6e3cfd9882 100644 --- a/packages/subagent/subagent/tsconfig.json +++ b/packages/subagent/subagent/tsconfig.json @@ -68,9 +68,6 @@ { "path": "../../util/time" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../interaction/permission-presets" }, diff --git a/packages/subagent/tool-subagent-control/README.i18n.yaml b/packages/subagent/tool-subagent-control/README.i18n.yaml index 3acdd337fd..8dd9b1bcfa 100644 --- a/packages/subagent/tool-subagent-control/README.i18n.yaml +++ b/packages/subagent/tool-subagent-control/README.i18n.yaml @@ -42,8 +42,8 @@ en: 70e344022e6e535e zh: 355488590648a82e /deepseek-ai-dsh-tool-subagent-control/understand-the-implementation/source-map: - en: a2a918cbad1a8313 - zh: 5bcddba1b2bdd26e + en: 4baec67b26df21e4 + zh: 7582f996e269e7f8 /deepseek-ai-dsh-tool-subagent-control/further-exploration: en: df2bf39d0ffffd43 zh: 4cd02551f7433c90 diff --git a/packages/subagent/tool-subagent-control/README.md b/packages/subagent/tool-subagent-control/README.md index 55271ec817..4be2b24416 100644 --- a/packages/subagent/tool-subagent-control/README.md +++ b/packages/subagent/tool-subagent-control/README.md @@ -84,7 +84,6 @@ The tool forwards its execution signal, which owns admission only until inbox ac |---|---| | [`src/index.ts`](src/index.ts) | `send_message` and `interrupt_agent` registration | | [`src/list-agents.ts`](src/list-agents.ts) | `list_agents` registration: scopes, status refinement, projection | -| — | No runtime invariant companion is published; this model-facing adapter has no independent lifecycle stream; delivery and activation relations are owned by the subagent service it calls. | diff --git a/packages/subagent/tool-subagent-control/README.zh.md b/packages/subagent/tool-subagent-control/README.zh.md index 13f7cafa6c..d8a28a8d84 100644 --- a/packages/subagent/tool-subagent-control/README.zh.md +++ b/packages/subagent/tool-subagent-control/README.zh.md @@ -84,7 +84,6 @@ kind: "package-reference" |---|---| | [`src/index.ts`](src/index.ts) | `send_message` 与 `interrupt_agent` 注册 | | [`src/list-agents.ts`](src/list-agents.ts) | `list_agents` 注册:作用域、状态细化、投影 | -| — | 不发布运行时不变式伴生入口;这个面向模型的适配器没有独立的生命周期流;投递与激活关系由其调用的 subagent 服务负责。 | diff --git a/packages/subagent/tool-subagent/package.json b/packages/subagent/tool-subagent/package.json index f1c0488372..6db4c5df8c 100644 --- a/packages/subagent/tool-subagent/package.json +++ b/packages/subagent/tool-subagent/package.json @@ -22,23 +22,17 @@ "types": "./lib/types/model-selection-settings.d.ts", "default": "./lib/model-selection-settings.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/model-selection-settings.js", "lib/types/**/*.d.ts" ], "license": "MIT", "peerDependencies": { "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-projection": "workspace:*", @@ -56,7 +50,6 @@ "devDependencies": { "@deepseek-ai/cordis-plugin-loader": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-scope": "workspace:*", diff --git a/packages/subagent/tool-subagent/src/invariant.ts b/packages/subagent/tool-subagent/src/invariant.ts deleted file mode 100644 index c7a7c21f55..0000000000 --- a/packages/subagent/tool-subagent/src/invariant.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-tool-subagent`. - * @module @deepseek-ai/dsh-tool-subagent/invariant - */ - -/* jscpd:ignore-start */ -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import { subagentModelSelectionPolicy } from './model-selection-state.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-tool-subagent' - -/** Cordis companion plugin name. */ -export const name = 'tool-subagent-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Assert that model-selectable definitions are complete and reconstructable. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - ctx.on('agent/pre-step', async ({ agent }, next) => { - const schemas = ctx.tools.schemas(agent) - const selectable = schemas.some((schema) => { - const properties = (schema.parameters as { properties?: Record }).properties - return properties?.['provider'] !== undefined - && properties['model'] !== undefined - && properties['reasoning_effort'] !== undefined - }) - const discoverable = schemas.some(schema => schema.name === 'list_subagent_models') - if ( - (selectable || discoverable) - && ( - subagentModelSelectionPolicy(ctx.sessionProjections, agent.session) === undefined - || !selectable - || !discoverable - ) - ) { - fail('model-selectable subagent definitions require a durable policy, route fields, and list_subagent_models') - } - return next() - }, { global: true }) -}, { inject: ['tools', 'sessionProjections'] }) - -/** - * 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/tests/model-selection-settings.spec.ts b/packages/subagent/tool-subagent/tests/model-selection-settings.spec.ts index 0907efed8f..d4aa7fcee6 100644 --- a/packages/subagent/tool-subagent/tests/model-selection-settings.spec.ts +++ b/packages/subagent/tool-subagent/tests/model-selection-settings.spec.ts @@ -7,14 +7,12 @@ import { Session, SESSION_FORMAT_VERSION, SessionId } from '@deepseek-ai/dsh-ses import type { SessionEvent } from '@deepseek-ai/dsh-session' import { bindScopeParent, createScope, scopeOf, scopeTarget } from '@deepseek-ai/dsh-scope' import { liveConfig } from '../../../settings/settings/tests/live-config.ts' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' import SubagentRuntime from '@deepseek-ai/dsh-subagent' import * as SubagentSpawn from '@deepseek-ai/dsh-subagent-spawn-in-process' import * as tool from '../src/index.ts' -import * as ToolInvariant from '../src/invariant.ts' import SubagentModelSelectionConfig from '../src/model-selection-settings.ts' import { subagentModelSelectionPolicy, @@ -241,8 +239,6 @@ describe('SubagentModelSelectionConfig', () => { it('installs per-Agent definitions for a shared preset scope', async () => { const ctx = await boot(false) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(ToolInvariant) const preset = createScope(ctx, { preset: 'standard' }) const other = createScope(ctx, { preset: 'minimal' }) await preset.ctx.plugin(tool, { @@ -421,49 +417,6 @@ describe('SubagentModelSelectionConfig', () => { await ctx.fiber.dispose() } }) - - it('checks model-selectable definitions without rejecting a policy-only preset', async () => { - const ctx = await boot() - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(ToolInvariant) - const disabled = await createAgent(ctx, 'invariant-disabled') - const next = () => Promise.resolve({ kind: 'enter' as const, messages: [] }) - const payload = { - agent: disabled, - messages: [], - turn: 1, - step: 1, - signal: new AbortController().signal, - } - await expect(ctx.waterfall(ctx as never, 'agent/pre-step', payload, next)).resolves.toEqual({ - kind: 'enter', messages: [], - }) - - disabled.session.append('subagent/model-selection-policy', { allowedModels: ALLOWED_MODELS }) - await expect(ctx.waterfall(ctx as never, 'agent/pre-step', payload, next)) - .resolves.toEqual({ kind: 'enter', messages: [] }) - - await selectionConfigs.get(ctx)!.update({ - enabled: true, - allowedModels: ALLOWED_MODELS, - }) - const enabled = await createAgent(ctx, 'invariant-enabled') - await expect(ctx.waterfall(ctx as never, 'agent/pre-step', { ...payload, agent: enabled }, next)) - .resolves.toEqual({ kind: 'enter', messages: [] }) - - const enabledSchemas = ctx.tools.schemas(enabled) - const schemas = vi.spyOn(ctx.tools, 'schemas') - schemas.mockReturnValue(enabledSchemas.filter(schema => schema.name !== 'list_subagent_models')) - await expect(ctx.waterfall(ctx as never, 'agent/pre-step', { ...payload, agent: enabled }, next)) - .rejects.toThrow('require a durable policy, route fields, and list_subagent_models') - - schemas.mockReturnValue(enabledSchemas) - await selectionConfigs.get(ctx)!.update({ enabled: false }) - const withoutPolicy = await createAgent(ctx, 'invariant-without-policy') - await expect(ctx.waterfall(ctx as never, 'agent/pre-step', { ...payload, agent: withoutPolicy }, next)) - .rejects.toThrow('require a durable policy, route fields, and list_subagent_models') - await ctx.fiber.dispose() - }) }) diff --git a/packages/subagent/tool-subagent/tsconfig.json b/packages/subagent/tool-subagent/tsconfig.json index 84feade601..97d42e076b 100644 --- a/packages/subagent/tool-subagent/tsconfig.json +++ b/packages/subagent/tool-subagent/tsconfig.json @@ -43,9 +43,6 @@ }, { "path": "../../jobs/jobs" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/subagent/tool-subagent/tsdown.config.ts b/packages/subagent/tool-subagent/tsdown.config.ts index d91febcc25..4a467f2c5e 100644 --- a/packages/subagent/tool-subagent/tsdown.config.ts +++ b/packages/subagent/tool-subagent/tsdown.config.ts @@ -15,5 +15,4 @@ const entry = (path: string) => ({ export default defineConfig([ entry('lib/types/index.js'), entry('lib/types/model-selection-settings.js'), - entry('lib/types/invariant.js'), ]) diff --git a/packages/subprocess/subprocess-local/README.i18n.yaml b/packages/subprocess/subprocess-local/README.i18n.yaml index e5827af896..714e3d7534 100644 --- a/packages/subprocess/subprocess-local/README.i18n.yaml +++ b/packages/subprocess/subprocess-local/README.i18n.yaml @@ -45,8 +45,8 @@ en: c1f9a5ee756547b9 zh: d47d9f6175f5cbc9 /deepseek-ai-dsh-subprocess-local/understand-the-implementation/source-map: - en: 155e6b83a97de7a2 - zh: b8a2c29bb1635d7d + en: 3d5fd99e4ad1f7af + zh: 241490c1052f415c /deepseek-ai-dsh-subprocess-local/understand-the-implementation/main-flow: en: 6b6e17fa0d1b39b7 zh: 0b40691719ce658f diff --git a/packages/subprocess/subprocess-local/README.md b/packages/subprocess/subprocess-local/README.md index 0e5423aecd..7b95b994a9 100644 --- a/packages/subprocess/subprocess-local/README.md +++ b/packages/subprocess/subprocess-local/README.md @@ -101,7 +101,6 @@ Each spawn selects one owner for both signalling and quiescence. Supported Linux | [`src/terminal.ts`](src/terminal.ts) | `node-pty` handle: Linux scope attachment, foreground inspection, and fallback cleanup | | [`src/process-inspector.ts`](src/process-inspector.ts) | POSIX process-tree and session inspection | | [`src/windows-inspector.ts`](src/windows-inspector.ts) | Windows Toolhelp32 process-table inspection via koffi | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | ### Main flow diff --git a/packages/subprocess/subprocess-local/README.zh.md b/packages/subprocess/subprocess-local/README.zh.md index 32a1b937e5..10719a162a 100644 --- a/packages/subprocess/subprocess-local/README.zh.md +++ b/packages/subprocess/subprocess-local/README.zh.md @@ -101,7 +101,6 @@ Linux 普通进程和终端进程即使在 bootstrap 消费启动请求前被取 | [`src/terminal.ts`](src/terminal.ts) | `node-pty` 终端句柄:Linux scope 绑定、前台检查与 fallback 清理 | | [`src/process-inspector.ts`](src/process-inspector.ts) | POSIX 进程树与会话检查 | | [`src/windows-inspector.ts`](src/windows-inspector.ts) | 经 koffi 的 Windows Toolhelp32 进程表检查 | -| — | 不发布运行时不变式伴生入口;除所属 seam 强制执行的约定外,本包不公开独立的事件序列或可变数据关系。 | ### 主流程 diff --git a/packages/subprocess/subprocess/README.i18n.yaml b/packages/subprocess/subprocess/README.i18n.yaml index 73986014a7..f76390eb90 100644 --- a/packages/subprocess/subprocess/README.i18n.yaml +++ b/packages/subprocess/subprocess/README.i18n.yaml @@ -48,8 +48,8 @@ en: 07201ec2256c25f6 zh: 046109cfa92194f3 /deepseek-ai-dsh-subprocess/understand-the-implementation/source-map: - en: 30de3ce459e02570 - zh: ff32b0cb28da5772 + en: 85e3d2109f49356d + zh: 071ccfef37e8e33f /deepseek-ai-dsh-subprocess/understand-the-implementation/data-model-and-flow: en: 9998dc0a990fbcf1 zh: ba179617f60cb9bc diff --git a/packages/subprocess/subprocess/README.md b/packages/subprocess/subprocess/README.md index 0c5a56987a..4839a508e5 100644 --- a/packages/subprocess/subprocess/README.md +++ b/packages/subprocess/subprocess/README.md @@ -103,7 +103,6 @@ The seam is built on one separation: the service owns process coordinates and li |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: abstract `SubprocessRuntime`, `ctx.subprocess` registration, the shared `scrubbedParentEnv` scrub | | [`src/types.ts`](src/types.ts) | Vocabulary: spawn spec, stdio modes, handles, readers, outcomes, `DSH_*` namespace | -| — | No runtime invariant companion is published; this stateless Service Definition owns spawn-spec/handle types, while Service Providers own observations. | ### Data model and flow diff --git a/packages/subprocess/subprocess/README.zh.md b/packages/subprocess/subprocess/README.zh.md index 42141d9828..843dcdb568 100644 --- a/packages/subprocess/subprocess/README.zh.md +++ b/packages/subprocess/subprocess/README.zh.md @@ -103,7 +103,6 @@ const output = handle.collected.stdout?.readFrom(0) |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:抽象 `SubprocessRuntime`、`ctx.subprocess` 注册、共享的 `scrubbedParentEnv` 清除 | | [`src/types.ts`](src/types.ts) | 词汇:spawn spec、stdio 模式、句柄、读取器、结果、`DSH_*` 命名空间 | -| — | 不发布运行时不变式伴生入口;这个无状态 Service Definition 负责 spawn spec 与句柄类型,观察则由 Service Providers 负责。 | ### 数据模型与流程 diff --git a/packages/subprocess/win32-process/README.i18n.yaml b/packages/subprocess/win32-process/README.i18n.yaml index 09c8ba1b52..6cfa81bc6d 100644 --- a/packages/subprocess/win32-process/README.i18n.yaml +++ b/packages/subprocess/win32-process/README.i18n.yaml @@ -39,5 +39,5 @@ en: e97c61be17a1db4f zh: 11dbf9721db63877 /deepseek-ai-dsh-win32-process/known-limitations-and-deferred-work/dev-note: - en: b395f1375ae2d653 - zh: 39f0fb47eedc996c + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/subprocess/win32-process/README.md b/packages/subprocess/win32-process/README.md index 155ca38077..af7cc79503 100644 --- a/packages/subprocess/win32-process/README.md +++ b/packages/subprocess/win32-process/README.md @@ -87,5 +87,3 @@ The package contributes no stable request prefix, so it does not invalidate mode None. - -**Runtime invariant:** No companion is published. Operations own only call-local native handles. diff --git a/packages/subprocess/win32-process/README.zh.md b/packages/subprocess/win32-process/README.zh.md index f0c4439af2..7c5685b77a 100644 --- a/packages/subprocess/win32-process/README.zh.md +++ b/packages/subprocess/win32-process/README.zh.md @@ -87,5 +87,3 @@ Koffi 的 `STARTUPINFOW` 与 `PROCESS_INFORMATION` 定义还会在模块加载 无。 - -**运行时不变式:** 不发布伴生入口。操作只持有调用内的原生句柄。 diff --git a/packages/telemetry/otel/README.i18n.yaml b/packages/telemetry/otel/README.i18n.yaml index 16787b0056..1528a60b0b 100644 --- a/packages/telemetry/otel/README.i18n.yaml +++ b/packages/telemetry/otel/README.i18n.yaml @@ -18,8 +18,8 @@ en: f141a9d909c555d8 zh: e4f5b690ccb1337c /deepseek-ai-dsh-otel/understand-the-implementation: - en: 0976ab2c661529bb - zh: 9653db33fc70195d + en: d1b6f8f2b25ea252 + zh: 73d6147bae811ce0 /deepseek-ai-dsh-otel/further-exploration: en: ee17b991de098cc0 zh: f90257e411921f2b diff --git a/packages/telemetry/otel/README.md b/packages/telemetry/otel/README.md index 6d993d0a8a..3b0730da04 100644 --- a/packages/telemetry/otel/README.md +++ b/packages/telemetry/otel/README.md @@ -36,7 +36,7 @@ Headers are explicit. The service does not add a channel header or inherit ambie `src/index.ts` registers the service; `event-log.ts` owns ordinary SDK batching; `session-log.ts` owns byte/count scheduling; `transport.ts` supplies the Session SDK JSON HTTP delegate; `event-transport.ts` uses Got for cancellable ordinary-event HTTP requests and retry waits with SDK serialization and export accounting. No global OTel provider is installed. The Session processor measures each record once, groups conservative sizes, and waits for the SDK concurrency queue to clear after every callback before sending another request. -No runtime invariant companion is published: channel creation registers no independent domain state, and collector delivery cannot be inferred from local queues. Composition tests exercise independent channels and service removal; adapter tests cover dependent-fiber cleanup and feedback authorization. +Composition tests exercise independent channels and service removal; adapter tests cover dependent-fiber cleanup and feedback authorization. ## Further Exploration diff --git a/packages/telemetry/otel/README.zh.md b/packages/telemetry/otel/README.zh.md index b90596a0d6..74d29f7eaf 100644 --- a/packages/telemetry/otel/README.zh.md +++ b/packages/telemetry/otel/README.zh.md @@ -36,7 +36,7 @@ Header 显式提供。服务不会附加 channel header,也不继承环境中 `src/index.ts` 注册服务;`event-log.ts` 负责普通事件的 SDK 聚合;`session-log.ts` 负责字节和条数调度;`transport.ts` 提供 Session 的 SDK JSON HTTP delegate;`event-transport.ts` 使用 Got 实现可取消的普通事件 HTTP 请求和重试等待,序列化与导出计数仍由 SDK 负责。不安装全局 OTel provider。Session processor 对每条记录只计量一次,按保守大小组包,并在每次回调后等待 SDK 并发队列清理完成才发送下一请求。 -不发布运行时不变式伴生入口:通道创建不注册独立领域状态,无法从本地队列推断 collector 是否收到记录。组合测试覆盖独立通道和服务移除;适配器测试覆盖依赖 fiber 清理与反馈授权。 +组合测试覆盖独立通道和服务移除;适配器测试覆盖依赖 fiber 清理与反馈授权。 ## 进一步探索 diff --git a/packages/terminal/terminal-bash/README.i18n.yaml b/packages/terminal/terminal-bash/README.i18n.yaml index 64e9a76a8f..489d9e08c3 100644 --- a/packages/terminal/terminal-bash/README.i18n.yaml +++ b/packages/terminal/terminal-bash/README.i18n.yaml @@ -87,5 +87,5 @@ en: 2dc221de0d3a76a7 zh: 822cef044e765459 /deepseek-ai-dsh-terminal-bash/known-limitations-and-deferred-work/dev-note: - en: 979c4600ed8a81d5 - zh: 4016ea615f638036 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/terminal/terminal-bash/README.md b/packages/terminal/terminal-bash/README.md index a7c331c127..b1c9f9806d 100644 --- a/packages/terminal/terminal-bash/README.md +++ b/packages/terminal/terminal-bash/README.md @@ -179,5 +179,3 @@ These limits define where the backend is a poor fit or needs special operational None. - -**Runtime invariant:** No companion is published. Readiness, terminal buffers, and process-tree state are private per-session implementation state, and the backend publishes no independent lifecycle stream or snapshot. diff --git a/packages/terminal/terminal-bash/README.zh.md b/packages/terminal/terminal-bash/README.zh.md index bd8f44e2ea..ada0427b9a 100644 --- a/packages/terminal/terminal-bash/README.zh.md +++ b/packages/terminal/terminal-bash/README.zh.md @@ -179,5 +179,3 @@ Scrollback 和尚未读取的发送输出保留独立拥有的字符串,并增 无。 - -**运行时不变式:** 不发布伴生入口。就绪状态、终端缓冲区与进程树状态都是各会话私有的实现状态,后端不发布独立的生命周期流或快照。 diff --git a/packages/terminal/terminal/README.i18n.yaml b/packages/terminal/terminal/README.i18n.yaml index ec4e1fe120..e51c2910e1 100644 --- a/packages/terminal/terminal/README.i18n.yaml +++ b/packages/terminal/terminal/README.i18n.yaml @@ -39,8 +39,8 @@ en: 6bae42fb7275816f zh: 5f75e1b26074108c /deepseek-ai-dsh-terminal/understand-the-implementation/source-map: - en: a958f52bab5cf58d - zh: 5ae1e96dc2a5a09c + en: 48dab6fe2f770ced + zh: 9bbfd23bcc5ac2b3 /deepseek-ai-dsh-terminal/understand-the-implementation/data-model-and-lifecycle: en: a2adfa2708882e19 zh: 002f257fd8c4addb diff --git a/packages/terminal/terminal/README.md b/packages/terminal/terminal/README.md index 99b2a687cf..3ad10ff7f8 100644 --- a/packages/terminal/terminal/README.md +++ b/packages/terminal/terminal/README.md @@ -75,7 +75,6 @@ The service owns everything except terminal mechanics: session identity, publica |---|---| | [`src/index.ts`](src/index.ts) | `TerminalSessionService`: backend registry, spawn/send/read/signal/kill/list, owner cleanups, disposal | | [`src/types.ts`](src/types.ts) | Shared contracts: backend interface, session types, wait reasons, signal set, error codes | -| — | No runtime invariant companion is published; backend and owner-scoped session registries are private mutable state, and the service exposes neither an independent lifecycle stream nor an unscoped snapshot. | ### Data model and lifecycle diff --git a/packages/terminal/terminal/README.zh.md b/packages/terminal/terminal/README.zh.md index ea4f926c20..f4fa796b9a 100644 --- a/packages/terminal/terminal/README.zh.md +++ b/packages/terminal/terminal/README.zh.md @@ -75,7 +75,6 @@ kind: "package-reference" |---|---| | [`src/index.ts`](src/index.ts) | `TerminalSessionService`:后端注册表、spawn/send/read/signal/kill/list、所有者清理与 dispose | | [`src/types.ts`](src/types.ts) | 共享约定:后端接口、会话类型、等待原因、信号集合、错误码 | -| — | 不发布运行时不变式伴生入口;后端与限定所有者范围的会话注册表均为私有可变状态,且服务既不暴露独立的生命周期流,也不暴露不限定范围的快照。 | ### 数据模型与生命周期 diff --git a/packages/terminal/tool-terminal/README.i18n.yaml b/packages/terminal/tool-terminal/README.i18n.yaml index 6898124046..3373de36fd 100644 --- a/packages/terminal/tool-terminal/README.i18n.yaml +++ b/packages/terminal/tool-terminal/README.i18n.yaml @@ -96,5 +96,5 @@ en: ba974b703d6ed0bc zh: 655810d5d338343c /deepseek-ai-dsh-tool-terminal/known-limitations-and-deferred-work/dev-note: - en: a4c4901129addb49 - zh: 78f339ece6da423b + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/terminal/tool-terminal/README.md b/packages/terminal/tool-terminal/README.md index 20fec33477..c29d101bc7 100644 --- a/packages/terminal/tool-terminal/README.md +++ b/packages/terminal/tool-terminal/README.md @@ -182,5 +182,3 @@ These limits define the model-facing surface that is absent. They are current pa None. - -**Runtime invariant:** No companion is published. This stateless adapter contributes tools and prompt guidance, while PTY lifecycle and background-job relationships remain owned by the services it composes. diff --git a/packages/terminal/tool-terminal/README.zh.md b/packages/terminal/tool-terminal/README.zh.md index bc78e1800e..12fcf5322a 100644 --- a/packages/terminal/tool-terminal/README.zh.md +++ b/packages/terminal/tool-terminal/README.zh.md @@ -182,5 +182,3 @@ spawn 返回 id 与有界启动输出。发送与读取返回有界终端文本 无。 - -**运行时不变式:** 不发布伴生入口。这个无状态适配器只贡献工具与提示词指引;PTY 生命周期与后台任务关系仍由其组合的服务持有。 diff --git a/packages/test-support/README.i18n.yaml b/packages/test-support/README.i18n.yaml index bd9356f42c..ac02c6c827 100644 --- a/packages/test-support/README.i18n.yaml +++ b/packages/test-support/README.i18n.yaml @@ -18,8 +18,8 @@ en: 94395ff952ce77ee zh: 4ae6afc3fe0c71dd /packages-test-support/related-documentation: - en: 268c38e4774534b5 - zh: 73ebf722cc254644 + en: 938bb087afd96c3e + zh: 0d51d0d7c0915602 /packages-test-support/dev-note: en: 5de1d6304e00c58f zh: 8d8225361cfa29e5 diff --git a/packages/test-support/README.md b/packages/test-support/README.md index 88dfcb941a..7608dc8696 100644 --- a/packages/test-support/README.md +++ b/packages/test-support/README.md @@ -38,7 +38,6 @@ The test-support group gives repository tests deterministic, keyless ways to exe ## Related documentation - [Testing policy](../../docs/testing.md) — the keyless snapshot tier these harnesses serve and when it is required. -- [Runtime invariants subsystem](../../docs/subsystems/invariants.md) — registration and execution of package-owned runtime checks. - [Package groups](../README.md) — how support groups relate to the product groups. ----- diff --git a/packages/test-support/README.zh.md b/packages/test-support/README.zh.md index 8034b8fb79..f82c47d835 100644 --- a/packages/test-support/README.zh.md +++ b/packages/test-support/README.zh.md @@ -38,7 +38,6 @@ test-support 组为仓库测试提供确定且无须密钥的真实产品测试 ## 相关文档 - [测试策略](../../docs/testing.zh.md)——这些 harness 所服务的无密钥快照层,以及何时必须使用该层。 -- [运行时不变式子系统](../../docs/subsystems/invariants.zh.md)——包自有运行时检查的注册与执行。 - [包组](../README.zh.md)——支持组与产品组的关系。 ----- diff --git a/packages/test-support/agent-loop-testkit/README.i18n.yaml b/packages/test-support/agent-loop-testkit/README.i18n.yaml index d937d7bfb5..45cc1a0f72 100644 --- a/packages/test-support/agent-loop-testkit/README.i18n.yaml +++ b/packages/test-support/agent-loop-testkit/README.i18n.yaml @@ -33,8 +33,8 @@ en: b2297e57abf5258a zh: 4595256c059896ce /deepseek-ai-dsh-agent-loop-testkit/understand-the-implementation/design: - en: 8fd7577505e2efa7 - zh: 6bbf46bcf9e3f5bc + en: 88506ae955e07c17 + zh: 9d09a35a9286105d /deepseek-ai-dsh-agent-loop-testkit/further-exploration: en: 60f39b14b0ccbcf6 zh: f193710189d3cc10 diff --git a/packages/test-support/agent-loop-testkit/README.md b/packages/test-support/agent-loop-testkit/README.md index abcd596639..bcbf0cdb40 100644 --- a/packages/test-support/agent-loop-testkit/README.md +++ b/packages/test-support/agent-loop-testkit/README.md @@ -86,7 +86,7 @@ This section explains the design of the test utilities; the observable behavior ### Design -`mountAgentLoopTestDependencies` mounts six service plugins in a fixed dependency order — LLM, session, session-projection registry, system-prompt registry, tool registry, then agent registry — and stops before `AgentLoop`, so the caller controls loop load order. `mountAgentLoopTestHarness` mounts the public production plugin, creates Agents through its service, and exposes the production driver's claim operation without exporting the loop's concrete Inbox class or projection definition. [`src/inbox.ts`](src/inbox.ts) contains only the process-local mutable stub and the fail-fast unsupported placeholder; it owns no projection or durable event implementation. The mounting and driver implementation lives in [`src/index.ts`](src/index.ts). No invariant companion is published because the package owns only test helpers and has no independent production observations that can diverge. +`mountAgentLoopTestDependencies` mounts six service plugins in a fixed dependency order — LLM, session, session-projection registry, system-prompt registry, tool registry, then agent registry — and stops before `AgentLoop`, so the caller controls loop load order. `mountAgentLoopTestHarness` mounts the public production plugin, creates Agents through its service, and exposes the production driver's claim operation without exporting the loop's concrete Inbox class or projection definition. [`src/inbox.ts`](src/inbox.ts) contains only the process-local mutable stub and the fail-fast unsupported placeholder; it owns no projection or durable event implementation. The mounting and driver implementation lives in [`src/index.ts`](src/index.ts). diff --git a/packages/test-support/agent-loop-testkit/README.zh.md b/packages/test-support/agent-loop-testkit/README.zh.md index 1ff404930d..a55d28af13 100644 --- a/packages/test-support/agent-loop-testkit/README.zh.md +++ b/packages/test-support/agent-loop-testkit/README.zh.md @@ -86,7 +86,7 @@ harness 不会挂载任何 LLM(大语言模型)适配器。若测试发送 ### 设计 -`mountAgentLoopTestDependencies` 按固定依赖顺序——LLM、会话、会话投影注册表、系统提示词注册表、工具注册表、agent 注册表——挂载六个服务插件,并在 `AgentLoop` 之前停下,使调用方控制 loop 加载顺序。`mountAgentLoopTestHarness` 挂载公开的生产插件,通过其服务创建 Agent,并公开生产驱动的认领操作,而不导出 loop 的具体 Inbox 类或投影定义。[`src/inbox.ts`](src/inbox.ts) 仅包含进程内可变桩和快速失败且不支持操作的占位值;它不持有投影或持久事件实现。挂载与驱动实现位于 [`src/index.ts`](src/index.ts)。本包不发布 invariant companion,因为它只持有测试辅助工具,不存在可能相互偏离的独立生产观测。 +`mountAgentLoopTestDependencies` 按固定依赖顺序——LLM、会话、会话投影注册表、系统提示词注册表、工具注册表、agent 注册表——挂载六个服务插件,并在 `AgentLoop` 之前停下,使调用方控制 loop 加载顺序。`mountAgentLoopTestHarness` 挂载公开的生产插件,通过其服务创建 Agent,并公开生产驱动的认领操作,而不导出 loop 的具体 Inbox 类或投影定义。[`src/inbox.ts`](src/inbox.ts) 仅包含进程内可变桩和快速失败且不支持操作的占位值;它不持有投影或持久事件实现。挂载与驱动实现位于 [`src/index.ts`](src/index.ts)。 diff --git a/packages/test-support/client-runtime/README.i18n.yaml b/packages/test-support/client-runtime/README.i18n.yaml index 55ec4b8fd6..f6cd4f1ab9 100644 --- a/packages/test-support/client-runtime/README.i18n.yaml +++ b/packages/test-support/client-runtime/README.i18n.yaml @@ -45,8 +45,8 @@ en: 3264640018c1a0e8 zh: d777e0b8ad8b602f /deepseek-ai-dsh-client-test-runtime/understand-the-implementation/source-map: - en: 1c38acf76aef7251 - zh: 6c104a965263f443 + en: b77fd15bc12265db + zh: 1a14b6999978b72a /deepseek-ai-dsh-client-test-runtime/understand-the-implementation/lifecycle: en: b42779f364504719 zh: a10bad7ea1161388 diff --git a/packages/test-support/client-runtime/README.md b/packages/test-support/client-runtime/README.md index 06455888a8..54d3cda6a6 100644 --- a/packages/test-support/client-runtime/README.md +++ b/packages/test-support/client-runtime/README.md @@ -135,7 +135,6 @@ The bench copies no production logic: it mounts the production `SlotRegistry`, p | [`src/assembly/remote-default-responses.ts`](src/assembly/remote-default-responses.ts) | `remoteDefaultResponses`: default responses of the Remote endpoints the roster calls at boot | | [`src/assembly/remote-proxies.ts`](src/assembly/remote-proxies.ts) | Contract-free `remote.` proxies over the Connection: `remoteNamespacesOf`, `remoteProxiesPlugin` | | [`src/assembly/bundle-roster.ts`](src/assembly/bundle-roster.ts) | `bundleRoster` and `webApp`: the browser roster read from bundle patch files with the include plugin's own schema and patch application | -| — | No runtime invariant companion is published; this test-support package owns no production event stream or mutable data — it assembles the runtime SlotRegistry and renderer (whose packages own their invariants) around test doubles; its own behavior is exercised by its package tests. | ### Lifecycle diff --git a/packages/test-support/client-runtime/README.zh.md b/packages/test-support/client-runtime/README.zh.md index edad3ae12b..122b77768b 100644 --- a/packages/test-support/client-runtime/README.zh.md +++ b/packages/test-support/client-runtime/README.zh.md @@ -135,7 +135,6 @@ test('registers into the sidebar', async ({ remote, start }) => { | [`src/assembly/remote-default-responses.ts`](src/assembly/remote-default-responses.ts) | `remoteDefaultResponses`:roster 启动期 Remote 端点的默认响应 | | [`src/assembly/remote-proxies.ts`](src/assembly/remote-proxies.ts) | 经 Connection 的无契约 `remote.` 代理:`remoteNamespacesOf`、`remoteProxiesPlugin` | | [`src/assembly/bundle-roster.ts`](src/assembly/bundle-roster.ts) | `bundleRoster` 与 `webApp`:用 include 插件自己的 schema 与补丁应用从 bundle 补丁文件读出浏览器 roster | -| — | 不发布运行时不变式伴生入口;本测试支持包不拥有生产事件流或可变数据,而是围绕测试替身组装生产 SlotRegistry 与渲染器。所挂载的生产包拥有各自的不变式,本包行为由本包测试检验。 | ### 生命周期 diff --git a/packages/test-support/llm-mock-server/README.i18n.yaml b/packages/test-support/llm-mock-server/README.i18n.yaml index fea5816802..f056a94e6f 100644 --- a/packages/test-support/llm-mock-server/README.i18n.yaml +++ b/packages/test-support/llm-mock-server/README.i18n.yaml @@ -39,8 +39,8 @@ en: 393a6ca8f1eb021e zh: d71bc369d550b527 /deepseek-ai-dsh-llm-mock-server/understand-the-implementation/source-map: - en: ac683a0f364b53e4 - zh: 3b4c782b56e024db + en: 893c33eff2af34d1 + zh: 44eed7b3810023a9 /deepseek-ai-dsh-llm-mock-server/understand-the-implementation/wire-flow: en: 34abeb79b2e0cdcd zh: d175185e4f1cc190 diff --git a/packages/test-support/llm-mock-server/README.md b/packages/test-support/llm-mock-server/README.md index 2f83cf66c0..a0058ff226 100644 --- a/packages/test-support/llm-mock-server/README.md +++ b/packages/test-support/llm-mock-server/README.md @@ -117,7 +117,6 @@ The server is built on one rule: each accepted Messages request consumes exactly | [`src/index.ts`](src/index.ts) | `startMockLlmServer`: listener, behavior table, seeded randomness, telemetry, captured request records | | [`src/cli.ts`](src/cli.ts) | `--sequence` and timing/content option parsing, JSONL stdout telemetry | | [`src/bin.ts`](src/bin.ts) | The `pnpm run mock:llm` source entry | -| — | No runtime invariant companion is published; this standalone test server owns no Cordis event stream or shared data; its wire behavior and lifecycle are exercised through direct HTTP and assembled-loop tests. | ### Wire flow diff --git a/packages/test-support/llm-mock-server/README.zh.md b/packages/test-support/llm-mock-server/README.zh.md index ad85853fb4..49558bf2cd 100644 --- a/packages/test-support/llm-mock-server/README.zh.md +++ b/packages/test-support/llm-mock-server/README.zh.md @@ -117,7 +117,6 @@ CLI 公开 `--success-text`、`--partial-text`、`--reasoning-text`、`--chunk-s | [`src/index.ts`](src/index.ts) | `startMockLlmServer`:listener、行为表、种子随机、遥测(telemetry)、捕获的请求记录 | | [`src/cli.ts`](src/cli.ts) | `--sequence` 与时序/内容选项解析、JSONL stdout 遥测 | | [`src/bin.ts`](src/bin.ts) | `pnpm run mock:llm` 源入口 | -| — | 不发布运行时不变式伴生组件;该独立测试服务器不拥有 Cordis 事件流或共享数据;其协议行为和生命周期通过直接 HTTP 测试及组装后的循环测试进行检验。 | ### 协议流程 diff --git a/packages/test-support/llm-replay/README.i18n.yaml b/packages/test-support/llm-replay/README.i18n.yaml index 13543bcd00..cc8d1c27ee 100644 --- a/packages/test-support/llm-replay/README.i18n.yaml +++ b/packages/test-support/llm-replay/README.i18n.yaml @@ -39,8 +39,8 @@ en: dfd1dd70ea389c66 zh: 3c0f2e0ee601e3f5 /deepseek-ai-dsh-llm-replay/understand-the-implementation/source-map: - en: cbf5c51583663671 - zh: 794ec0b8873736cc + en: 0542116615161c60 + zh: 66fe2603b04760b6 /deepseek-ai-dsh-llm-replay/understand-the-implementation/binding-and-stream-flow: en: 484c98a2679b218f zh: 8d3266a0f3deac87 diff --git a/packages/test-support/llm-replay/README.md b/packages/test-support/llm-replay/README.md index 1124572ce2..58149f64e2 100644 --- a/packages/test-support/llm-replay/README.md +++ b/packages/test-support/llm-replay/README.md @@ -107,7 +107,6 @@ The [committed-corpus test](tests/session-format-corpus.spec.ts) restores each v |---|---| | [`src/index.ts`](src/index.ts) | Types, fixture derivation, override validation, placeholder resolution, session binding, `installLlmReplay`, and the plugin export | | [`tests/session-format-corpus.spec.ts`](tests/session-format-corpus.spec.ts) | Committed-generation restoration and exact historical refusal checks | -| — | No runtime invariant companion is published; this test-only adapter consumes a fixed replay script; its stream grammar is checked by the LLM companion and fixture derivation tests. | ### Binding and stream flow diff --git a/packages/test-support/llm-replay/README.zh.md b/packages/test-support/llm-replay/README.zh.md index ee7f755096..ec59b78552 100644 --- a/packages/test-support/llm-replay/README.zh.md +++ b/packages/test-support/llm-replay/README.zh.md @@ -107,7 +107,6 @@ parent agent 委托给进程内 subagent 的场景会为每个 Session 记录一 |---|---| | [`src/index.ts`](src/index.ts) | 类型、fixture 派生、override 校验、占位符解析、会话绑定、`installLlmReplay` 与插件导出 | | [`tests/session-format-corpus.spec.ts`](tests/session-format-corpus.spec.ts) | 已提交代际还原与精确历史拒绝检查 | -| — | 不发布运行时不变式伴生入口;该仅测试适配器消费固定的回放脚本;其流语法由 LLM 伴生插件与 fixture 派生测试检验。 | ### 绑定与流式流程 diff --git a/packages/test-support/loader-smoke/README.i18n.yaml b/packages/test-support/loader-smoke/README.i18n.yaml index a3ba0d25f0..c0d19442ea 100644 --- a/packages/test-support/loader-smoke/README.i18n.yaml +++ b/packages/test-support/loader-smoke/README.i18n.yaml @@ -39,8 +39,8 @@ en: f3407557b8aafb9c zh: 06c9faf218c69829 /deepseek-ai-dsh-loader-smoke/understand-the-implementation/source-map: - en: 4212f963f739e76e - zh: 1a08bcb7654df951 + en: 1dd2e4e1cd1fad2d + zh: 4783ddacfbcf2cd5 /deepseek-ai-dsh-loader-smoke/further-exploration: en: f6719ecd2dbb7870 zh: 758b46fefe666dbd diff --git a/packages/test-support/loader-smoke/README.md b/packages/test-support/loader-smoke/README.md index 2bdee79f8e..74fe406a88 100644 --- a/packages/test-support/loader-smoke/README.md +++ b/packages/test-support/loader-smoke/README.md @@ -81,7 +81,6 @@ The harness is built on one separation: the smoke runs in a child process under |---|---| | [`src/index.ts`](src/index.ts) | Mode resolver, `runLoaderSmoke` subprocess harness, options and result types | | [`src/agent-turn.ts`](src/agent-turn.ts) | `runFixtureTurn` direct-agent driver and result envelope | -| — | No runtime invariant companion is published; this test-support package owns no production event stream or mutable data; consuming test suites exercise its behavior. | | [`tests/fixtures/production-profile.ts`](tests/fixtures/production-profile.ts) | Repository-only shipped-profile composition helper for integration fixtures | diff --git a/packages/test-support/loader-smoke/README.zh.md b/packages/test-support/loader-smoke/README.zh.md index ba83f0def2..d5a4708c43 100644 --- a/packages/test-support/loader-smoke/README.zh.md +++ b/packages/test-support/loader-smoke/README.zh.md @@ -81,7 +81,6 @@ harness 建立在一个分离之上:冒烟测试在隔离世界中的子进程 |---|---| | [`src/index.ts`](src/index.ts) | 模式解析器、`runLoaderSmoke` 子进程 harness、选项与结果类型 | | [`src/agent-turn.ts`](src/agent-turn.ts) | `runFixtureTurn` 直接 agent driver 与结果信封 | -| — | 不发布运行时不变量伴生入口;该测试支持包不负责维护生产事件流或可变数据;消费它的测试套件会检验该 harness。 | | [`tests/fixtures/production-profile.ts`](tests/fixtures/production-profile.ts) | 仅限仓库内部、供集成 fixture 使用的交付 profile 组装 helper | diff --git a/packages/test-support/remote-mock/README.i18n.yaml b/packages/test-support/remote-mock/README.i18n.yaml index 54555bfc73..46a63fead4 100644 --- a/packages/test-support/remote-mock/README.i18n.yaml +++ b/packages/test-support/remote-mock/README.i18n.yaml @@ -45,8 +45,8 @@ en: 6385fe57822aa455 zh: 53eda1b6a602f357 /deepseek-ai-dsh-remote-mock/understand-the-implementation/source-map: - en: ddecff98fb835572 - zh: 66a17c9059f79069 + en: 4d308c2e883b6d86 + zh: f4eb0f86627c6e57 /deepseek-ai-dsh-remote-mock/model-experience: en: 53b590e95981d312 zh: 231c0245f6b6b827 diff --git a/packages/test-support/remote-mock/README.md b/packages/test-support/remote-mock/README.md index 7f76ce6217..cfe9a840bc 100644 --- a/packages/test-support/remote-mock/README.md +++ b/packages/test-support/remote-mock/README.md @@ -112,7 +112,6 @@ A fake that stands in for a generated stream method returns the `RemoteStreamHan | [`src/remote-proxy.ts`](src/remote-proxy.ts) | Namespace/method lookup and generated-map mock types | | [`src/streams.ts`](src/streams.ts) | `frames` / `openStream` scripts, `streamHandle` / `streamMethod` fake typing, and `MockStream` (handle + `AsyncIterable`) | | [`src/log.ts`](src/log.ts) | Log store with the shared `seq` counter | -| — | No runtime invariant companion is published; this test-support library owns no production event stream or mutable process state, and its behavior is exercised by its package tests. | diff --git a/packages/test-support/remote-mock/README.zh.md b/packages/test-support/remote-mock/README.zh.md index b79849fe3b..b898972409 100644 --- a/packages/test-support/remote-mock/README.zh.md +++ b/packages/test-support/remote-mock/README.zh.md @@ -112,7 +112,6 @@ await mock.streams.drained('session/follow') | [`src/remote-proxy.ts`](src/remote-proxy.ts) | 命名空间/方法查找与生成映射的 mock 类型 | | [`src/streams.ts`](src/streams.ts) | `frames` / `openStream` 脚本、`streamHandle` / `streamMethod` 假实现标注,以及 `MockStream`(句柄 + `AsyncIterable`) | | [`src/log.ts`](src/log.ts) | 带共享 `seq` 计数器的日志 | -| — | 不发布运行时不变量伴生件;本测试支持库不拥有任何生产事件流或可变进程状态,其行为由本包测试覆盖。 | diff --git a/packages/test-support/session-snapshot/README.i18n.yaml b/packages/test-support/session-snapshot/README.i18n.yaml index 13f96ad137..0ddbc8b440 100644 --- a/packages/test-support/session-snapshot/README.i18n.yaml +++ b/packages/test-support/session-snapshot/README.i18n.yaml @@ -39,8 +39,8 @@ en: 8771607f15c4f4e1 zh: 7c2cf395f704717c /deepseek-ai-dsh-session-snapshot/understand-the-implementation/source-map: - en: 4c3ff235ca8fe261 - zh: 0f03425b926721cc + en: 1510b3b09c3bd553 + zh: 1931fba0891f7d65 /deepseek-ai-dsh-session-snapshot/understand-the-implementation/data-flow: en: a71c8ab2e78798c5 zh: 419cebed1c023fbf diff --git a/packages/test-support/session-snapshot/README.md b/packages/test-support/session-snapshot/README.md index ffb81013a6..05e7d813a6 100644 --- a/packages/test-support/session-snapshot/README.md +++ b/packages/test-support/session-snapshot/README.md @@ -124,7 +124,6 @@ The shared core owns manifests, generation-qualified role selection, workspace s | [`src/workspace.ts`](src/workspace.ts) | Scenario workspace setup and complete expected-state comparison | | [`src/suite.ts`](src/suite.ts) | Scenario-table suite factory, fixture guards, record/refresh write-back | | [`src/index.ts`](src/index.ts) | Package entry re-exporting the four layers | -| — | No runtime invariant companion is published; this test-support package owns no production event stream or mutable data; consuming test suites exercise its behavior. | ### Data flow diff --git a/packages/test-support/session-snapshot/README.zh.md b/packages/test-support/session-snapshot/README.zh.md index e678e2c75d..8549f289e9 100644 --- a/packages/test-support/session-snapshot/README.zh.md +++ b/packages/test-support/session-snapshot/README.zh.md @@ -124,7 +124,6 @@ Headless/ACP 与 SDK 适配器在规范化之前,将原始目录中的子创 | [`src/workspace.ts`](src/workspace.ts) | 场景 workspace 设置与完整预期状态比较 | | [`src/suite.ts`](src/suite.ts) | 场景表套件工厂、fixture 保护、录制/刷新回写 | | [`src/index.ts`](src/index.ts) | 再导出四个层的包入口 | -| — | 不发布运行时不变式伴生入口;该测试支持包不拥有任何生产事件流或可变数据;消费它的测试套件会检验该工具包。 | ### 数据流 diff --git a/packages/todo/tool-todo/README.i18n.yaml b/packages/todo/tool-todo/README.i18n.yaml index c476aeb2bc..2899c17810 100644 --- a/packages/todo/tool-todo/README.i18n.yaml +++ b/packages/todo/tool-todo/README.i18n.yaml @@ -33,20 +33,17 @@ en: 8d94a7381bca3fa9 zh: f4bd2866fff6fa8d /deepseek-ai-dsh-tool-todo/understand-the-implementation/design-philosophy: - en: 79e0ce717098e5eb - zh: ec34658fb269c2e1 + en: e9cf462797eaf9f2 + zh: 02d9a7fce90936c3 /deepseek-ai-dsh-tool-todo/understand-the-implementation/source-map: - en: d53829e90f7f63ca - zh: fc3006d788a84013 + en: 51328ad8c7fcbfd0 + zh: 2614ec42b61ff3ef /deepseek-ai-dsh-tool-todo/understand-the-implementation/export-shape: en: 35b4f6c718e60dab zh: 9bffe86c88a7ad6d /deepseek-ai-dsh-tool-todo/understand-the-implementation/session-projection: en: 2d49c8af46b16dd2 zh: 71a3073433b579d4 -/deepseek-ai-dsh-tool-todo/understand-the-implementation/durable-log-invariant: - en: 3234e0405b88e314 - zh: 135a5b12d810e6dd /deepseek-ai-dsh-tool-todo/understand-the-implementation/call-mechanics: en: d55f6a32b05ed686 zh: c815f930a2acebda diff --git a/packages/todo/tool-todo/README.md b/packages/todo/tool-todo/README.md index 71e00cabca..1c31e14ddd 100644 --- a/packages/todo/tool-todo/README.md +++ b/packages/todo/tool-todo/README.md @@ -71,7 +71,7 @@ The tool is built on four commitments: - **Whole-list replace, log-backed state.** The model resends the entire list; the `todo/write` snapshot lives on the event-sourced session log, so durability, replay, and resume reconstruction come from the log rather than a service. - **Single owner.** The list belongs to the calling agent session; there is no shared or swarm scope, and non-agent callers are rejected. -- **Deployment policy, not a coded rule.** `allowParallelInProgress` is a required composition choice because the tool cannot observe runtime concurrency; the durable-log invariant deliberately stays silent on the active count so a log written under one policy still replays under another. +- **Deployment policy, not a coded rule.** `allowParallelInProgress` is a required composition choice because the tool cannot observe runtime concurrency. - **Validation keeps the logged snapshot honest.** Schema-level rejection of unknown keys and `execute`-level rejection of empty or duplicate content keep the durable snapshot equal to what the model believes it wrote. The [todo_write tool Agent Note](../../../.agents/notes/archived/feature/2026-06-29-todo-write-tool.md) records the original design and alternatives; the [parallel in-progress Agent Note](../../../.agents/notes/archived/feature/2026-07-26-todo-parallel-in-progress.md) records the policy decision. @@ -83,7 +83,6 @@ The [todo_write tool Agent Note](../../../.agents/notes/archived/feature/2026-06 | [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, tool registration, `todos` projection unit | | [`src/types.ts`](src/types.ts) | The one home of the `todos` projection-key declaration and its payload types | | [`src/client.ts`](src/client.ts) | Client-namespace re-export of the types outlet | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: validates durable whole-list snapshots and open-turn ownership | ### Export shape @@ -93,10 +92,6 @@ The plugin is a function/namespace plugin: it exports `name` / `inject` / `apply When the composition mounts `ctx.sessionProjections` ([`@deepseek-ai/dsh-session-projection`](../../session/session-projection/README.md)), this package registers the `todos` unit on an injected child: the projection is the standing plan — the latest whole `todo/write` list, `null` before the first write, cleared when the next turn starts while `turn/end` keeps the finished checklist visible. The key merges into `SessionProjectionMap` here; carriers serve the value on the history tail page and the `session/projection` push frame. Compositions without the registry are unaffected; see [src/index.ts](src/index.ts) for the unit registration. -### Durable-log invariant - -The invariant companion registers on `ctx.invariants`, validates existing and newly announced sessions once, and then advances a committed per-session turn trace for live appends. It rejects malformed entries, empty or duplicated content, unknown statuses, and any durable `todo/write` outside an open turn; core session treats declaration-merged events generically, while this producing package owns todo-specific rules. It deliberately says nothing about how many items are `in_progress`, because that is the tool's per-deployment policy, not a durable-data rule. - ### Call mechanics Each call validates the submitted list against the schema, rejects incoherent input, and on success appends the full snapshot as a `todo/write` session event and returns the new counts; the current list is always the most recent `todo/write` in the log (last-write-wins on replay). See [src/index.ts](src/index.ts) for the exact validation and append steps. diff --git a/packages/todo/tool-todo/README.zh.md b/packages/todo/tool-todo/README.zh.md index a2dc044fa7..6ceae4cc3e 100644 --- a/packages/todo/tool-todo/README.zh.md +++ b/packages/todo/tool-todo/README.zh.md @@ -71,7 +71,7 @@ agent 每次更新都发送完整列表;新列表替换旧列表,因此没 - **整表替换、日志承载状态。** 模型重新发送整个列表;`todo/write` 快照存放在事件溯源的会话日志上,持久性、回放与恢复重建都来自日志而非服务。 - **单一所有者。** 列表属于调用 agent 会话;不存在共享或 swarm 作用域,非 agent 调用方会被拒绝。 -- **部署策略,而非编码规则。** `allowParallelInProgress` 是必填组合选择,因为工具无法观测运行时并发;持久日志不变式刻意不跟随它,因此一种策略下写入的日志在切换到另一种策略后仍可回放。 +- **部署策略,而非编码规则。** `allowParallelInProgress` 是必填组合选择,因为工具无法观测运行时并发。 - **校验确保日志快照如实反映输入。** schema 层拒绝未知键、`execute` 层拒绝空或重复 content,使持久快照与模型自认为写入的内容一致。 [todo_write 工具 Agent Note](../../../.agents/notes/archived/feature/2026-06-29-todo-write-tool.md) 记录原始设计与备选方案;[并行 in-progress Agent Note](../../../.agents/notes/archived/feature/2026-07-26-todo-parallel-in-progress.md) 记录该策略决策。 @@ -83,7 +83,6 @@ agent 每次更新都发送完整列表;新列表替换旧列表,因此没 | [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、工具注册、`todos` 投影单元 | | [`src/types.ts`](src/types.ts) | `todos` 投影键声明及其载荷类型的唯一归属地 | | [`src/client.ts`](src/client.ts) | 客户端命名空间对类型出口的再导出 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:校验持久整表快照与开放轮次归属 | ### 导出形状 @@ -93,10 +92,6 @@ agent 每次更新都发送完整列表;新列表替换旧列表,因此没 当组合挂载 `ctx.sessionProjections`([`@deepseek-ai/dsh-session-projection`](../../session/session-projection/README.zh.md))时,本包在注入的子插件中注册 `todos` 单元:投影即有效计划——最新的整份 `todo/write` 列表,首次写入前为 `null`,下一轮次开始时清空,而 `turn/end` 保留刚完成的清单。该键在此处合并进 `SessionProjectionMap`;载体通过历史尾页与 `session/projection` 推送帧提供该值。未挂载注册表的组合不受影响;单元注册见 [src/index.ts](src/index.ts)。 -### 持久日志不变式 - -不变式伴生插件注册到 `ctx.invariants`,先分别校验既有会话与新公布会话一次,再为实时追加推进按会话提交的轮次轨迹。它会拒绝畸形条目、空或重复 content、未知状态,以及开放轮次之外的持久 `todo/write`;核心 session 通用处理声明合并事件,而本生产包拥有 todo 专用规则。它刻意不约束有多少条目处于 `in_progress`,因为那是工具按部署制定的策略,而非持久数据规则。 - ### 调用机制 每次调用都会先校验提交的列表是否符合 schema,拒绝不一致的输入,成功后把完整快照作为 `todo/write` 会话事件追加并返回新的计数;当前列表始终是日志中最近一次 `todo/write`(回放时后写覆盖先写)。确切的校验与追加步骤见 [src/index.ts](src/index.ts)。 diff --git a/packages/todo/tool-todo/package.json b/packages/todo/tool-todo/package.json index 1641c5d9fb..9d75ee1c6a 100644 --- a/packages/todo/tool-todo/package.json +++ b/packages/todo/tool-todo/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./client": { "types": "./lib/types/client.d.ts", "default": "./lib/types/client.js" @@ -31,7 +27,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -42,7 +37,6 @@ }, "peerDependencies": { "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-projection": "workspace:*", "@deepseek-ai/dsh-tools": "workspace:*", @@ -54,7 +48,6 @@ "@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-session-projection": "workspace:*", diff --git a/packages/todo/tool-todo/src/invariant.ts b/packages/todo/tool-todo/src/invariant.ts deleted file mode 100644 index d92563a06f..0000000000 --- a/packages/todo/tool-todo/src/invariant.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** Package-owned durable todo-snapshot invariants. @module @deepseek-ai/dsh-tool-todo/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' - -const PACKAGE_NAME = '@deepseek-ai/dsh-tool-todo' -const TODO_STATUSES = new Set(['pending', 'in_progress', 'completed']) - -/** Cordis companion plugin name. */ -export const name = 'tool-todo-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** - * Validate one whole-list todo snapshot before it reaches the durable log. - * - * Deliberately silent on how many items are `in_progress`. That is the tool's - * per-deployment policy (`Config.allowParallelInProgress`), not a durable-shape - * rule: a log written while parallel work was allowed must still replay after a - * deployment tightens the policy, so tying the invariant to the current config - * would reject history that was valid when it was written. - */ -function validateTodos(value: unknown, fail: InvariantFailure): void { - if (!Array.isArray(value)) fail('todo/write todos must be an array') - const seen = new Set() - for (const item of value) { - if (typeof item !== 'object' || item === null) fail('todo/write entries must be objects') - const { content, status } = item as Record - if (typeof content !== 'string' || content.length === 0 || content.trim() !== content) { - fail('todo/write content must be non-empty and already trimmed') - } - if (seen.has(content)) fail(`todo/write repeats content ${JSON.stringify(content)}`) - seen.add(content) - if (typeof status !== 'string' || !TODO_STATUSES.has(status)) { - fail(`todo/write carries unknown status ${JSON.stringify(status)}`) - } - } -} - -/* jscpd:ignore-start -- package companions share replay and dispatch plumbing */ -/** Incremental turn state for one committed session log. */ -interface TurnTrace { - open: boolean -} - -/** Advance the trace after one event has committed. */ -function advanceTrace(trace: TurnTrace, event: SessionEvent): void { - if (event.type === 'turn/start') trace.open = true - if (event.type === 'turn/end') trace.open = false -} - -/** Validate one package-owned event against the preceding committed trace. */ -function validateEvent(event: SessionEvent, trace: TurnTrace, fail: InvariantFailure): void { - if (event.type !== 'todo/write') return - validateTodos(event.data.todos, fail) - if (!trace.open) fail('todo/write appended outside any open turn') -} - -/** Validate one existing log in a single pass and return its tail trace. */ -function seedTrace(session: Session, fail: InvariantFailure): TurnTrace { - const trace: TurnTrace = { open: false } - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents()) { - validateEvent(event, trace, fail) - advanceTrace(trace, event) - } - return trace -} - -/** Install validation for loaded and newly appended whole-list todo snapshots. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const traces = new WeakMap() - const seed = (session: Session): void => { - traces.set(session, seedTrace(session, fail)) - } - const traceFor = (session: Session): TurnTrace => { - let trace = traces.get(session) - if (trace === undefined) { - trace = seedTrace(session, fail) - traces.set(session, trace) - } - return trace - } - for (const session of ctx.sessions.list()) seed(session) - ctx.on('session/created', (session) => { seed(session) }, { global: true }) - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - validateEvent(event, traceFor(session), fail) - }, { global: true }) - ctx.on('session/event', (session, event) => { - advanceTrace(traceFor(session), event) - }, { global: true }) -}, { inject: ['sessions'] }) -/* jscpd:ignore-end */ - -/** - * Register the todo 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)) diff --git a/packages/todo/tool-todo/tests/invariant.spec.ts b/packages/todo/tool-todo/tests/invariant.spec.ts deleted file mode 100644 index 4b41b6a301..0000000000 --- a/packages/todo/tool-todo/tests/invariant.spec.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import SessionStore, { SessionId, SessionSeq } from '@deepseek-ai/dsh-session' -import ToolRuntime from '@deepseek-ai/dsh-tools' -import * as ToolTodo from '@deepseek-ai/dsh-tool-todo' -import * as TodoInvariant from '@deepseek-ai/dsh-tool-todo/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(TodoInvariant) - return ctx -} - -describe('todo snapshot invariants', () => { - it('accepts historical and live parallel snapshots under the single-active tool policy', async () => { - const todos = [ - { content: 'Inspect state', status: 'completed' }, - { content: 'Apply fix', status: 'in_progress' }, - { content: 'Watch background build', status: 'in_progress' }, - { content: 'Run checks', status: 'pending' }, - ] as const - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(ToolRuntime) - await ctx.plugin(ToolTodo, { allowParallelInProgress: false }) - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('todo/write', { todos: [...todos] }) - await ctx.plugin(InvariantRegistry, { enabled: true }) - - await expect(ctx.plugin(TodoInvariant).then(() => undefined)).resolves.toBeUndefined() - expect(() => { session.append('todo/write', { todos: [...todos] }) }).not.toThrow() - }) - - it.each([ - ['not-an-array', /must be an array/], - [[null], /entries must be objects/], - [[42], /entries must be objects/], - [[{ content: 42, status: 'pending' }], /content must be non-empty/], - [[{ content: '', status: 'pending' }], /content must be non-empty/], - [[{ content: ' padded ', status: 'pending' }], /already trimmed/], - [[{ content: 'same', status: 'pending' }, { content: 'same', status: 'completed' }], /repeats content/], - [[{ content: 'task', status: 42 }], /unknown status/], - [[{ content: 'task', status: 'paused' }], /unknown status/], - ])('rejects an incoherent durable todo snapshot', async (todos, message) => { - const ctx = await setup() - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - expect(() => { session.append('todo/write', { todos } as never) }).toThrow(message) - }) - - it('ignores unrelated dispatches and session events', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - expect(() => { - ctx.emit('tools/change') - session.append('turn/start', { turn: 1 }) - }).not.toThrow() - }) - - it('rejects a live snapshot outside an open turn before it enters the log', async () => { - const ctx = await setup() - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - const before = session.snapshotEvents() - - expect(() => session.append('todo/write', { todos: [] })).toThrow(/outside any open turn/) - expect(session.snapshotEvents()).toEqual(before) - }) - - it('rejects an existing snapshot outside an open turn on late registration', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - ctx.sessions.create().append('todo/write', { todos: [] }) - await ctx.plugin(InvariantRegistry, { enabled: true }) - - await expect(ctx.plugin(TodoInvariant).then(() => undefined)).rejects.toThrow(/outside any open turn/) - }) - - it('rejects an invalid existing snapshot on late registration', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const session = ctx.sessions.create() - session.append('turn/start', { turn: 1 }) - session.append('todo/write', { - todos: [ - { content: 'duplicate', status: 'pending' }, - { content: 'duplicate', status: 'completed' }, - ], - }) - await ctx.plugin(InvariantRegistry, { enabled: true }) - - await expect(ctx.plugin(TodoInvariant).then(() => undefined)).rejects.toThrow(/repeats content "duplicate"/) - }) - - it('validates seeded sessions announced after companion installation', async () => { - const ctx = await setup() - const valid = ctx.sessions.create(SessionId('todo-seeded-valid'), { seed: [ - { type: 'turn/start', seq: SessionSeq(0), time: 1, data: { turn: 1 } }, - { type: 'todo/write', seq: SessionSeq(1), time: 2, data: { todos: [] } }, - ] }) - expect(() => valid.append('todo/write', { todos: [] })).not.toThrow() - - expect(() => ctx.sessions.create(SessionId('todo-seeded-invalid'), { seed: [ - { type: 'todo/write', seq: SessionSeq(0), time: 1, data: { todos: [] } }, - ] })).toThrow(/outside any open turn/) - }) - - it('tracks events committed before a prepared session is announced', async () => { - const ctx = await setup() - const session = ctx.sessions.prepare(SessionId('todo-prepared')) - const detach = ctx.sessions.enter(session) - try { - session.append('turn/start', { turn: 1 }) - expect(() => session.append('todo/write', { todos: [] })).not.toThrow() - ctx.sessions.announce(session) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - expect(() => session.append('todo/write', { todos: [] })).toThrow(/outside any open turn/) - } finally { - detach() - } - }) -}) diff --git a/packages/todo/tool-todo/tsconfig.json b/packages/todo/tool-todo/tsconfig.json index cde8c834fe..ae150deb67 100644 --- a/packages/todo/tool-todo/tsconfig.json +++ b/packages/todo/tool-todo/tsconfig.json @@ -14,6 +14,9 @@ { "path": "../../../vendor/cordis" }, + { + "path": "../../../vendor/schemastery" + }, { "path": "../../core/tools" }, @@ -25,9 +28,6 @@ }, { "path": "../../session/session-projection" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/typert/generator/README.i18n.yaml b/packages/typert/generator/README.i18n.yaml index 47d0408069..2267cc29ad 100644 --- a/packages/typert/generator/README.i18n.yaml +++ b/packages/typert/generator/README.i18n.yaml @@ -57,5 +57,5 @@ en: f3b1002133fe8736 zh: e8d5fecd10db5e3a /deepseek-ai-dsh-typert-generator/known-limitations-and-deferred-work/dev-note: - en: cc7e47f271475e4c - zh: 8426eb322816bd28 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/typert/generator/README.md b/packages/typert/generator/README.md index 9c207574e9..c55e740080 100644 --- a/packages/typert/generator/README.md +++ b/packages/typert/generator/README.md @@ -141,5 +141,3 @@ These limits define what the generator cannot model or emit; they are current pa None. - -**Runtime invariant:** No companion is published. The source-project analyzer and build-time emitter run outside any Cordis runtime; model snapshots, executable artifacts, and consuming-package typechecks enforce the output contract. diff --git a/packages/typert/generator/README.zh.md b/packages/typert/generator/README.zh.md index 9a6fa9e81d..30ba39b8fb 100644 --- a/packages/typert/generator/README.zh.md +++ b/packages/typert/generator/README.zh.md @@ -141,5 +141,3 @@ Host 与 Client 是两个独立的 TypeScript 程序。直接项目引用确定 无。 - -**运行时不变式:** 不发布伴生入口。源码项目分析器与构建时 emitter 均不在任何 Cordis 运行时中运行;模型快照、可执行产物与消费方包的类型检查会强制执行其输出约定。 diff --git a/packages/typert/loader/README.i18n.yaml b/packages/typert/loader/README.i18n.yaml index d3ba7af24a..17cfc16273 100644 --- a/packages/typert/loader/README.i18n.yaml +++ b/packages/typert/loader/README.i18n.yaml @@ -39,8 +39,8 @@ en: 38e781e052fdf44e zh: d8f6b4d119bf9856 /deepseek-ai-dsh-typert-loader/understand-the-implementation/source-map: - en: 40a8a6723d33b335 - zh: 5b806e49b6279818 + en: 92aa195f1408a900 + zh: 1841e39a53de7873 /deepseek-ai-dsh-typert-loader/further-exploration: en: 9665bb4850bb92de zh: 508eaf81dee2a10b diff --git a/packages/typert/loader/README.md b/packages/typert/loader/README.md index c45fbdebe7..f58c0fae63 100644 --- a/packages/typert/loader/README.md +++ b/packages/typert/loader/README.md @@ -77,7 +77,6 @@ Verdicts (resolvable specifier, export presence) and imported manifests are cach | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: `Config`, scanner, manifest validation, registration wiring | -| — | No runtime invariant companion is published; the Loader entry lifecycle directly owns each exact registry disposer, and integration tests observe registration and removal. | diff --git a/packages/typert/loader/README.zh.md b/packages/typert/loader/README.zh.md index 9cb6ae6da5..0f5a32594c 100644 --- a/packages/typert/loader/README.zh.md +++ b/packages/typert/loader/README.zh.md @@ -77,7 +77,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`Config`、扫描器、manifest 校验、注册装配逻辑 | -| — | 不发布运行时不变式伴生入口;Loader 配置项的生命周期直接持有每个对应的注册表资源释放函数,集成测试会观察注册与移除。 | diff --git a/packages/typert/protocol/README.i18n.yaml b/packages/typert/protocol/README.i18n.yaml index 45d814c323..7c55f6a3c6 100644 --- a/packages/typert/protocol/README.i18n.yaml +++ b/packages/typert/protocol/README.i18n.yaml @@ -45,8 +45,8 @@ en: aac79a7b4682371e zh: bc9cc513c1ced3e6 /deepseek-ai-dsh-typert-protocol/understand-the-implementation/source-map: - en: c8834e55e3e3db73 - zh: d820bc330814b240 + en: 62da405f16379f39 + zh: fcbd0f10dd198018 /deepseek-ai-dsh-typert-protocol/further-exploration: en: 84191d1b07fb8bb6 zh: 9a5212732ed38ab6 diff --git a/packages/typert/protocol/README.md b/packages/typert/protocol/README.md index 8a97bb339d..4bbad52e61 100644 --- a/packages/typert/protocol/README.md +++ b/packages/typert/protocol/README.md @@ -107,7 +107,6 @@ Every namespace, method, lookup, and Context segment must satisfy `isTypertRemot | [`src/json-value.ts`](src/json-value.ts) | `isRemoteJsonValue` and `isRemoteUplinkItem`, the lossless JSON checks every carrier shares | | [`src/remote-error.ts`](src/remote-error.ts) | `RemoteError` and the structural `remoteErrorOf` recognizer | | [`src/types.ts`](src/types.ts) | Protocol maps, `RemoteErrorDetailsMap`, `RemoteResult`, `RemoteStream`, `RemoteStreamHandle`, `PeerScope`, `RemoteInvocation`, `InvocationDescriptor`, codecs, provider contracts, registry interfaces, `TypertClientRemote` | -| — | No runtime invariant companion is published; decorators retain private immutable declarations and bindings are frozen values with no independent event stream to cross-check. | diff --git a/packages/typert/protocol/README.zh.md b/packages/typert/protocol/README.zh.md index 9bb2cae765..684a7c321d 100644 --- a/packages/typert/protocol/README.zh.md +++ b/packages/typert/protocol/README.zh.md @@ -107,7 +107,6 @@ Host 装配以转发给消费方的 Cordis 事件扩展 `TypertRemoteEventSelect | [`src/json-value.ts`](src/json-value.ts) | 各载体共享的无损 JSON 校验 `isRemoteJsonValue` 与 `isRemoteUplinkItem` | | [`src/remote-error.ts`](src/remote-error.ts) | `RemoteError` 与结构式识别函数 `remoteErrorOf` | | [`src/types.ts`](src/types.ts) | 协议映射、`RemoteErrorDetailsMap`、`RemoteResult`、`RemoteStream`、`RemoteStreamHandle`、`PeerScope`、`RemoteInvocation`、`InvocationDescriptor`、编解码器、提供方约定、注册表接口、`TypertClientRemote` | -| — | 不发布运行时不变量伴生入口;decorator 只保留私有不可变声明,binding 也是冻结值,没有可供交叉核对的独立事件流。 | diff --git a/packages/typert/registry/README.i18n.yaml b/packages/typert/registry/README.i18n.yaml index ca9dffd1ce..6c12a2aedc 100644 --- a/packages/typert/registry/README.i18n.yaml +++ b/packages/typert/registry/README.i18n.yaml @@ -42,8 +42,8 @@ en: 4764fc33a7e16e60 zh: 1ddf03cbe16197da /deepseek-ai-dsh-typert-registry/understand-the-implementation/source-map: - en: ec30fd742ee246b1 - zh: 7fff86709e5ea4a1 + en: 0cb84ac1dd0131da + zh: 155ea8fbe0957664 /deepseek-ai-dsh-typert-registry/further-exploration: en: da1350150eaff076 zh: c7099f0b2b31ec4f diff --git a/packages/typert/registry/README.md b/packages/typert/registry/README.md index 75a3d2bc49..b3be03a8a8 100644 --- a/packages/typert/registry/README.md +++ b/packages/typert/registry/README.md @@ -81,7 +81,6 @@ Keys are stable: `#` for reflection, `#` for schem | [`src/service.ts`](src/service.ts) | `TypertRegistry` service, stores, validation, effect wiring | | [`src/types.ts`](src/types.ts) | Contribution, record, and filter types | | [`src/client/index.ts`](src/client/index.ts) | Client face installing the same registry | -| — | No runtime invariant companion is published; schema and package-reflection records mutate together inside register/dispose, with no independent event or second data source to cross-check; duplicate identities fail at the owning operation boundary. | diff --git a/packages/typert/registry/README.zh.md b/packages/typert/registry/README.zh.md index b47d735c17..75660beb9c 100644 --- a/packages/typert/registry/README.zh.md +++ b/packages/typert/registry/README.zh.md @@ -81,7 +81,6 @@ Remote 调用通过 `ctx.typert.lookups` 与 `ctx.typert.contexts` 解析 Host | [`src/service.ts`](src/service.ts) | `TypertRegistry` 服务、存储、校验、effect 接线 | | [`src/types.ts`](src/types.ts) | 贡献、记录与过滤器类型 | | [`src/client/index.ts`](src/client/index.ts) | 安装同一注册表的 Client face | -| — | 不发布运行时不变式伴生入口;schema 与 package-reflection record 在 register/dispose 内一起变更,没有独立 event 或第二数据源可供交叉核对;重复 identity 在所属操作处失败。 | diff --git a/packages/util/atomic-write/README.i18n.yaml b/packages/util/atomic-write/README.i18n.yaml index cb293fdbb8..3cf002ff89 100644 --- a/packages/util/atomic-write/README.i18n.yaml +++ b/packages/util/atomic-write/README.i18n.yaml @@ -30,8 +30,8 @@ en: 93b537308f7c88c7 zh: 282e0d2042aeb1be /deepseek-ai-dsh-atomic-write/understand-the-implementation/source-map: - en: 725509a8b621c668 - zh: c11e3ffdf73af844 + en: aee5320dc8746377 + zh: f35dbbf350f7b5cf /deepseek-ai-dsh-atomic-write/understand-the-implementation/write-path: en: 2e6561c9282f0c0d zh: 7f0e76f6dbdea501 diff --git a/packages/util/atomic-write/README.md b/packages/util/atomic-write/README.md index bddff7852e..b360f9de93 100644 --- a/packages/util/atomic-write/README.md +++ b/packages/util/atomic-write/README.md @@ -77,7 +77,6 @@ The package is built on one separation: the atomic commit owns the swap, and the | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | `writeFileAtomic` and `withFileLock`, the package's whole surface | -| — | No runtime invariant companion is published; this pure filesystem primitive owns no event stream or mutable runtime data; its replacement contract is enforced by unit tests. | ### Write path diff --git a/packages/util/atomic-write/README.zh.md b/packages/util/atomic-write/README.zh.md index ef60663f84..ac2cd2a940 100644 --- a/packages/util/atomic-write/README.zh.md +++ b/packages/util/atomic-write/README.zh.md @@ -77,7 +77,6 @@ Windows 在无法观察到锁时会对 `EPERM` 重试一次,因为持锁方可 | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | `writeFileAtomic` 与 `withFileLock`,即本包的全部接口 | -| — | 不发布运行时不变式伴生入口;这个纯文件系统原语不维护事件流或可变运行时数据;其替换约定由单元测试覆盖。 | ### 写入路径 diff --git a/packages/util/brand/README.i18n.yaml b/packages/util/brand/README.i18n.yaml index 8a62a68d07..908bf60bf6 100644 --- a/packages/util/brand/README.i18n.yaml +++ b/packages/util/brand/README.i18n.yaml @@ -30,8 +30,8 @@ en: 98a62299f46d5050 zh: 042d3c626e1fbe34 /deepseek-ai-dsh-brand/understand-the-implementation/source-map: - en: 4adda24125bd07aa - zh: bfcc7be63f134658 + en: 5032eec37cf68046 + zh: 971402fa5c6c0e63 /deepseek-ai-dsh-brand/understand-the-implementation/how-values-stay-portable: en: 6a126aa2e31591c0 zh: c85e395cdb0bbb0e diff --git a/packages/util/brand/README.md b/packages/util/brand/README.md index f9bcea11d2..6670f920af 100644 --- a/packages/util/brand/README.md +++ b/packages/util/brand/README.md @@ -72,7 +72,6 @@ The package defines two intersection types, `string & { readonly [BRAND]: B }` a | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Branded string and number types with stateless constructors | -| — | No runtime invariant companion is published; this pure utility owns no event stream or mutable runtime data; its value algebra is enforced by unit tests. | ### How values stay portable diff --git a/packages/util/brand/README.zh.md b/packages/util/brand/README.zh.md index f27be07053..ba41930937 100644 --- a/packages/util/brand/README.zh.md +++ b/packages/util/brand/README.zh.md @@ -72,7 +72,6 @@ const seq = brandNumber(7) | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 品牌化字符串与数字类型及其无状态构造函数 | -| — | 不发布运行时不变量配套入口;这个纯工具不拥有事件流或可变运行时数据;其值代数由单元测试保障。 | ### 值为何可移植 diff --git a/packages/util/chunked-list/README.i18n.yaml b/packages/util/chunked-list/README.i18n.yaml index dacfe41255..559497bb9e 100644 --- a/packages/util/chunked-list/README.i18n.yaml +++ b/packages/util/chunked-list/README.i18n.yaml @@ -18,8 +18,8 @@ en: 6808bfba89c5ffbf zh: f80761fdcd3db317 /deepseek-ai-dsh-chunked-list/understand-the-implementation: - en: c1d571642af3feeb - zh: c942c041a396ea94 + en: 7cd6e9d9367246f3 + zh: 127a64392a7dd976 /deepseek-ai-dsh-chunked-list/further-exploration: en: dc2a6afae1585ff7 zh: 133604f9e6f08072 diff --git a/packages/util/chunked-list/README.md b/packages/util/chunked-list/README.md index f0af89e5dd..e2eff9dcf5 100644 --- a/packages/util/chunked-list/README.md +++ b/packages/util/chunked-list/README.md @@ -52,8 +52,6 @@ The newest chunk stores up to 64 values. Appends copy at most that chunk and sha | [`src/index.ts`](src/index.ts) | Persistent list operations and checkpoint validation | | [`tests/chunked-list.spec.ts`](tests/chunked-list.spec.ts) | Version isolation, ordering, structural sharing, and checkpoint acceptance | -No runtime invariant companion is published because this library has no independently changing observations; its operations return caller-owned immutable values. - ----- diff --git a/packages/util/chunked-list/README.zh.md b/packages/util/chunked-list/README.zh.md index 429554742a..bc14bdfb34 100644 --- a/packages/util/chunked-list/README.zh.md +++ b/packages/util/chunked-list/README.zh.md @@ -52,8 +52,6 @@ console.log([...iterateChunkedList(second)]) | [`src/index.ts`](src/index.ts) | 持久化列表操作与检查点校验 | | [`tests/chunked-list.spec.ts`](tests/chunked-list.spec.ts) | 版本隔离、顺序、结构共享与检查点接受条件 | -此库没有独立变化的观测值,因此不发布运行时不变式伴随模块;其操作返回调用方拥有的不可变值。 - ----- diff --git a/packages/util/code-language/README.i18n.yaml b/packages/util/code-language/README.i18n.yaml index f9bf11499f..7767fb5eaa 100644 --- a/packages/util/code-language/README.i18n.yaml +++ b/packages/util/code-language/README.i18n.yaml @@ -21,5 +21,5 @@ en: 18e4dc8978a0dd29 zh: 6f5715ae89229864 /dsh-util-code-language/known-limitations-and-deferred-work/dev-note: - en: cfc7173facebc57c - zh: 4ea51377d7c9c147 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/util/code-language/README.md b/packages/util/code-language/README.md index e6b0c1404f..a21cc7f1de 100644 --- a/packages/util/code-language/README.md +++ b/packages/util/code-language/README.md @@ -46,5 +46,3 @@ The set is curated for common source, config, script, data, and markup extension None. - -**Runtime invariant:** No companion is published. This utility owns no mutable runtime relationship. diff --git a/packages/util/code-language/README.zh.md b/packages/util/code-language/README.zh.md index 650c9c879a..dd7a08f98a 100644 --- a/packages/util/code-language/README.zh.md +++ b/packages/util/code-language/README.zh.md @@ -46,5 +46,3 @@ kind: "package-library" 无。 - -**运行时不变式:** 不发布伴生入口。这个工具不持有可变运行时关系。 diff --git a/packages/util/crypto/README.i18n.yaml b/packages/util/crypto/README.i18n.yaml index 20c6829162..c4d000c753 100644 --- a/packages/util/crypto/README.i18n.yaml +++ b/packages/util/crypto/README.i18n.yaml @@ -30,5 +30,5 @@ en: 2c79cf7c12f39ab1 zh: c6f2ad3e53a62473 /dsh-util-crypto/known-limitations-and-deferred-work/dev-note: - en: 214df5375bd84f76 - zh: fa3912dd609838fb + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/util/crypto/README.md b/packages/util/crypto/README.md index 5f35d51656..d52e847755 100644 --- a/packages/util/crypto/README.md +++ b/packages/util/crypto/README.md @@ -67,5 +67,3 @@ No direct invalidation; identifier-minting consumers own any request changes. None. - -**Runtime invariant:** No companion is published. This pure utility owns no event stream or mutable runtime data; its value algebra is enforced by unit tests. diff --git a/packages/util/crypto/README.zh.md b/packages/util/crypto/README.zh.md index c79f2838b0..dcdc53f31d 100644 --- a/packages/util/crypto/README.zh.md +++ b/packages/util/crypto/README.zh.md @@ -67,5 +67,3 @@ import { bytesToBase64, randomUUID, type Uuid } from '@deepseek-ai/dsh-util-cryp 无。 - -**运行时不变式:** 不发布伴生入口。这个纯工具不持有事件流或可变运行时数据;其值运算由单元测试覆盖。 diff --git a/packages/util/deque/README.i18n.yaml b/packages/util/deque/README.i18n.yaml index 802e53d21a..024addc724 100644 --- a/packages/util/deque/README.i18n.yaml +++ b/packages/util/deque/README.i18n.yaml @@ -27,8 +27,8 @@ en: 1439df275cf4305f zh: 09fb7ae6048c065c /deepseek-ai-dsh-deque/understand-the-implementation/source-map: - en: f3f22e80f9919ff5 - zh: 839ed17098fdfee9 + en: 6444d79a894f4251 + zh: e13d851b2f691f6c /deepseek-ai-dsh-deque/further-exploration: en: 0b3c7ebb1012b51a zh: f35eb5cebe4ddeba diff --git a/packages/util/deque/README.md b/packages/util/deque/README.md index 7ddf9ae164..abca041a71 100644 --- a/packages/util/deque/README.md +++ b/packages/util/deque/README.md @@ -62,7 +62,6 @@ The deque stores entries in a circular array. Removing an entry clears that slot | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Circular deque operations and backing-storage lifecycle | -| — | No runtime invariant companion is published because this collection owns no event stream or shared mutable state; unit tests cover its ordering and storage lifecycle. | | [`tests/deque.spec.ts`](tests/deque.spec.ts) | FIFO, front insertion, wrapping, growth, compaction, clearing, and reuse coverage | | [`benchmarks/drain.ts`](benchmarks/drain.ts) | Reproducible backlog-drain timing across increasing queue sizes | diff --git a/packages/util/deque/README.zh.md b/packages/util/deque/README.zh.md index 14b89c0589..90cbbf3e92 100644 --- a/packages/util/deque/README.zh.md +++ b/packages/util/deque/README.zh.md @@ -62,7 +62,6 @@ while (frames.size > 0) { | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 环形双端队列操作与后备存储生命周期 | -| — | 不发布运行时不变式伴生入口;这个集合不拥有事件流或共享可变状态,其顺序与存储生命周期由单元测试覆盖。 | | [`tests/deque.spec.ts`](tests/deque.spec.ts) | FIFO、前插、环绕、扩容、压缩(compaction)、清空和复用覆盖 | | [`benchmarks/drain.ts`](benchmarks/drain.ts) | 随队列规模增长的可复现 backlog 排空计时 | diff --git a/packages/util/home-paths/README.i18n.yaml b/packages/util/home-paths/README.i18n.yaml index 59150c4332..1c721bf446 100644 --- a/packages/util/home-paths/README.i18n.yaml +++ b/packages/util/home-paths/README.i18n.yaml @@ -33,8 +33,8 @@ en: a3896db883f77b47 zh: 45c71eccc31682b5 /deepseek-ai-dsh-home-paths/understand-the-implementation/source-map: - en: ab001f10961a5a98 - zh: 2209a79d2d5d9b68 + en: 584610e67c4aa386 + zh: 766f77e35c65193d /deepseek-ai-dsh-home-paths/understand-the-implementation/resolution-rules: en: 5356ed79a458f46b zh: ec00a3151bbba0aa diff --git a/packages/util/home-paths/README.md b/packages/util/home-paths/README.md index eb595a7c46..2cda290493 100644 --- a/packages/util/home-paths/README.md +++ b/packages/util/home-paths/README.md @@ -67,7 +67,6 @@ The package is built on one principle: all harness user data lives under one roo | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Home resolution, path joining, display, tilde expansion, and watch-path canonicalization | -| — | No runtime invariant companion is published; this pure utility owns no event stream or mutable runtime data; its resolution rules and value algebra are enforced by unit tests. | ### Resolution rules diff --git a/packages/util/home-paths/README.zh.md b/packages/util/home-paths/README.zh.md index 215317cd42..96bcb6a64e 100644 --- a/packages/util/home-paths/README.zh.md +++ b/packages/util/home-paths/README.zh.md @@ -67,7 +67,6 @@ const cache = dshCachePath('models') // $DSH_HOME/cache/models, default | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 主目录解析、路径拼接、展示、波浪号展开与监听路径规范化 | -| — | 不发布运行时不变式伴生入口;这个纯工具包不持有事件流或可变运行时数据;其解析规则和值代数由单元测试保障。 | ### 解析规则 diff --git a/packages/util/http-proxy/README.i18n.yaml b/packages/util/http-proxy/README.i18n.yaml index 90ae297a32..ce15b017b0 100644 --- a/packages/util/http-proxy/README.i18n.yaml +++ b/packages/util/http-proxy/README.i18n.yaml @@ -51,5 +51,5 @@ en: 26e0a7fd9dccf81c zh: c61ad5d4dfc91cb8 /deepseek-ai-dsh-http-proxy/known-limitations-and-deferred-work/dev-note: - en: e7d8628ccd7e0d36 - zh: 751da266bfb84bef + en: 8f915ac6f4fc1525 + zh: 29b06e0aae5a30ec diff --git a/packages/util/http-proxy/README.md b/packages/util/http-proxy/README.md index 9b098432f3..fd6f1e9603 100644 --- a/packages/util/http-proxy/README.md +++ b/packages/util/http-proxy/README.md @@ -120,5 +120,3 @@ These limits define when the package is a poor fit. They are current package con Reaching Node's built-in `fetch` from a userland undici relies on both writing the legacy `Symbol.for('undici.globalDispatcher.1')` slot. That is an implicit cross-version coupling, not a contract — see [corepack#834](https://github.com/nodejs/corepack/issues/834) for it breaking. `tests/install.spec.ts` asserts a real request reaches a loopback proxy, so a version bump that breaks the coupling fails there rather than in the field. - -**Runtime invariant:** No companion is published. The one piece of mutable state here — the active policy — is asserted against the dispatcher it installs by unit tests that dispose the registration and observe a real loopback proxy. diff --git a/packages/util/http-proxy/README.zh.md b/packages/util/http-proxy/README.zh.md index 70b81429bc..f85f91e2e0 100644 --- a/packages/util/http-proxy/README.zh.md +++ b/packages/util/http-proxy/README.zh.md @@ -120,5 +120,3 @@ loopback 始终被绕过——`localhost`、整个 `127.0.0.0/8` 段、`::1`、` userland undici 能触及 Node 内置的 `fetch`,依赖的是两者都会写入 legacy 的 `Symbol.for('undici.globalDispatcher.1')` 槽位。那是跨版本的隐式耦合,不是约定——参见 [corepack#834](https://github.com/nodejs/corepack/issues/834) 中它失效的实例。`tests/install.spec.ts` 断言真实请求会抵达一个 loopback 代理,因此破坏该耦合的版本升级会在那里失败,而不是流到线上。 - -**运行时不变式:** 不发布伴生入口。本包唯一的可变状态——生效中的策略——由单元测试对照它所安装的 dispatcher 断言:测试会对该注册执行 dispose(资源释放)并观察一个真实的 loopback 代理。 diff --git a/packages/util/http-proxy/package.json b/packages/util/http-proxy/package.json index 2275e12629..4d3edd7411 100644 --- a/packages/util/http-proxy/package.json +++ b/packages/util/http-proxy/package.json @@ -33,7 +33,6 @@ "undici": "^8.10.0" }, "devDependencies": { - "@deepseek-ai/cordis": "workspace:~", - "@deepseek-ai/dsh-invariants": "workspace:*" + "@deepseek-ai/cordis": "workspace:~" } } diff --git a/packages/util/launch-environment/README.i18n.yaml b/packages/util/launch-environment/README.i18n.yaml index 3edc680101..3822e9333c 100644 --- a/packages/util/launch-environment/README.i18n.yaml +++ b/packages/util/launch-environment/README.i18n.yaml @@ -30,8 +30,8 @@ en: c2cdb9ea66c61cc5 zh: 5ccf1802da65483d /deepseek-ai-dsh-launch-environment/understand-the-implementation/source-map: - en: eed450e5587bdd6e - zh: 986cf651a118b3f2 + en: c693fdeed31fab31 + zh: 5d25a3c90cfa8f80 /deepseek-ai-dsh-launch-environment/understand-the-implementation/how-the-snapshot-stays-frozen: en: f933fc349e258d27 zh: 717166c9a80a4821 diff --git a/packages/util/launch-environment/README.md b/packages/util/launch-environment/README.md index 43d3e27b19..509dfd70ef 100644 --- a/packages/util/launch-environment/README.md +++ b/packages/util/launch-environment/README.md @@ -68,7 +68,6 @@ The snapshot is built on one separation: the launcher owns which files exist, an | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | `createLaunchEnvironmentSnapshot`, `launchEnvironmentOf`, and the `ctx.launchEnvironment` slot | -| — | No runtime invariant companion is published; the snapshot is frozen before any fiber starts and this package owns no event stream or mutable runtime data; its lookup and rejection rules are enforced by unit tests. | ### How the snapshot stays frozen diff --git a/packages/util/launch-environment/README.zh.md b/packages/util/launch-environment/README.zh.md index 0e161e4621..2da183fe0f 100644 --- a/packages/util/launch-environment/README.zh.md +++ b/packages/util/launch-environment/README.zh.md @@ -68,7 +68,6 @@ const endpoint = launchEnvironmentOf(ctx).get('DEEPSEEK_BASE_URL')?.value | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | `createLaunchEnvironmentSnapshot`、`launchEnvironmentOf` 与 `ctx.launchEnvironment` 槽位 | -| — | 不发布运行时不变式伴生入口;快照在任何 fiber 启动前即已冻结,并且本包不拥有任何事件流或可变运行时数据;单元测试会强制检查其查找与拒绝规则。 | ### 快照如何保持冻结 diff --git a/packages/util/lazy-require/README.i18n.yaml b/packages/util/lazy-require/README.i18n.yaml index 0af9d94e93..f3a8496e52 100644 --- a/packages/util/lazy-require/README.i18n.yaml +++ b/packages/util/lazy-require/README.i18n.yaml @@ -33,8 +33,8 @@ en: 4f0eadf94828e66e zh: 4f8a6ec170ebbfb6 /deepseek-ai-dsh-lazy-require/known-limitations-and-deferred-work: - en: 0973de93cdfa0bee - zh: 22e1e00a8df2f3f2 + en: 2a27f7f398a68bf4 + zh: bea423499191a7aa /deepseek-ai-dsh-lazy-require/known-limitations-and-deferred-work/dev-note: en: 0a1253c6f7b5e8b9 zh: 9f56528562c7c914 diff --git a/packages/util/lazy-require/README.md b/packages/util/lazy-require/README.md index d75a7626da..56be2cbc69 100644 --- a/packages/util/lazy-require/README.md +++ b/packages/util/lazy-require/README.md @@ -80,8 +80,6 @@ None. - **CommonJS-compatible dependencies only** — ESM-only packages require an asynchronous factory owned by their caller. - **WebWorker packaging needs an explicit request** — the static packer does not discover dependencies named only in `createLazyRequire()` calls. A package used in a Preview image must keep the dependency reachable through a supported literal request until the packer recognizes this helper. -No runtime invariant companion is published because the loader holds no independently observable mutable relationship. - ### Dev Note diff --git a/packages/util/lazy-require/README.zh.md b/packages/util/lazy-require/README.zh.md index 7d8e66f159..06a9e0f092 100644 --- a/packages/util/lazy-require/README.zh.md +++ b/packages/util/lazy-require/README.zh.md @@ -80,8 +80,6 @@ const requireNative = createLazyRequire('native-package', import.m - **仅限兼容 CommonJS 的依赖**——仅 ESM package 需要由调用方拥有异步 factory。 - **WebWorker 打包需要显式请求**——静态 packer 无法发现仅在 `createLazyRequire()` 调用中命名的依赖。Preview image 使用的 package 必须通过受支持的字面量请求保持该依赖可达,直到 packer 能够识别此 helper。 -本包不发布运行时 invariant companion,因为 loader 不持有可独立观测的可变关系。 - ### 开发备注 diff --git a/packages/util/native-command/README.i18n.yaml b/packages/util/native-command/README.i18n.yaml index 5a89618423..1649126ec2 100644 --- a/packages/util/native-command/README.i18n.yaml +++ b/packages/util/native-command/README.i18n.yaml @@ -30,8 +30,8 @@ en: 78e86818bea36f9f zh: 71dc1ad3e4cbe9f1 /deepseek-ai-dsh-native-command/understand-the-implementation/source-map: - en: a19bb4cb9f6de785 - zh: cc930d8facf1ca07 + en: fe54b68049be8239 + zh: 3766e0954df12917 /deepseek-ai-dsh-native-command/understand-the-implementation/what-execfile-gives-the-runner: en: ce6180ce8209db94 zh: 30000a1a073110e8 diff --git a/packages/util/native-command/README.md b/packages/util/native-command/README.md index 8741942dc8..386ccfac49 100644 --- a/packages/util/native-command/README.md +++ b/packages/util/native-command/README.md @@ -68,7 +68,6 @@ The command runner is a thin wrapper over Node's `execFile`. The path opener sel | [`src/index.ts`](src/index.ts) | Public command-runner and path-opener exports | | [`src/runner.ts`](src/runner.ts) | Shell-free `execFile` adapter | | [`src/path-opener.ts`](src/path-opener.ts) | Desktop detection, open intents, browser preference, and WSL translation | -| — | No runtime invariant companion is published; each run is one stateless child-process round trip with no owned event stream or mutable runtime data; behavior is enforced by unit tests. | ### What execFile gives the runner diff --git a/packages/util/native-command/README.zh.md b/packages/util/native-command/README.zh.md index a034187613..20f465a42e 100644 --- a/packages/util/native-command/README.zh.md +++ b/packages/util/native-command/README.zh.md @@ -68,7 +68,6 @@ const { stdout, stderr } = await runNativeCommand('osascript', ['-e', script], s | [`src/index.ts`](src/index.ts) | 命令运行器与路径打开器的公共导出 | | [`src/runner.ts`](src/runner.ts) | 无 shell 的 `execFile` 适配器 | | [`src/path-opener.ts`](src/path-opener.ts) | 桌面探测、打开意图、浏览器偏好与 WSL 转换 | -| — | 不发布运行时不变式伴生入口;每次运行都是一次无状态的子进程往返,不拥有事件流或可变运行时数据;相关行为由单元测试保障。 | ### execFile 给了运行器什么 diff --git a/packages/util/output-retention/README.i18n.yaml b/packages/util/output-retention/README.i18n.yaml index f43c5b2501..3dd894250a 100644 --- a/packages/util/output-retention/README.i18n.yaml +++ b/packages/util/output-retention/README.i18n.yaml @@ -39,8 +39,8 @@ en: fd2a6c959bfe864e zh: 25b11d28aced568c /deepseek-ai-dsh-output-retention/understand-the-implementation/source-map: - en: cb49af8772b97f13 - zh: 23810afb65f350ee + en: 1345eb3d839aa564 + zh: 4470f726cb8fb3b0 /deepseek-ai-dsh-output-retention/understand-the-implementation/two-retainers-two-resource-models: en: 6a5dd228d9f9808b zh: 9bba82c5771ee705 diff --git a/packages/util/output-retention/README.md b/packages/util/output-retention/README.md index 439e83438b..38ecfc6aee 100644 --- a/packages/util/output-retention/README.md +++ b/packages/util/output-retention/README.md @@ -109,7 +109,6 @@ The library is built on one separation: it owns the mechanical question of what | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | `ItemRetainer`, `TextRetainer`, `describeOmitted`, `formatRetentionNotice`, and `truncateWithoutSplittingSurrogatePair` | -| — | No runtime invariant companion is published; this pure utility owns no event stream or mutable runtime data; its value algebra is enforced by unit tests. | ### Two retainers, two resource models diff --git a/packages/util/output-retention/README.zh.md b/packages/util/output-retention/README.zh.md index b38af85854..fa1432a68f 100644 --- a/packages/util/output-retention/README.zh.md +++ b/packages/util/output-retention/README.zh.md @@ -109,7 +109,6 @@ const footer = formatRetentionNotice( | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | `ItemRetainer`、`TextRetainer`、`describeOmitted`、`formatRetentionNotice` 与 `truncateWithoutSplittingSurrogatePair` | -| — | 不发布运行时不变式伴生入口;这个纯工具不拥有事件流或可变运行时数据;其值代数由单元测试保证。 | ### 两个 retainer,两种资源模型 diff --git a/packages/util/package-manifest/README.i18n.yaml b/packages/util/package-manifest/README.i18n.yaml index 41446bb257..599cc5697e 100644 --- a/packages/util/package-manifest/README.i18n.yaml +++ b/packages/util/package-manifest/README.i18n.yaml @@ -18,8 +18,8 @@ en: 43fb882676bf22dd zh: 04e512bc4059e5d4 /deepseek-ai-dsh-package-manifest/understand-the-implementation: - en: 64e1e57d1c3761e0 - zh: 9fc039770ce43c63 + en: 41ec449dee927f04 + zh: ec1b583bd9025d84 /deepseek-ai-dsh-package-manifest/further-exploration: en: f2352ec5d94ac3bd zh: c2eb0889554f1a7f diff --git a/packages/util/package-manifest/README.md b/packages/util/package-manifest/README.md index 7c4c493541..03b7843daf 100644 --- a/packages/util/package-manifest/README.md +++ b/packages/util/package-manifest/README.md @@ -64,7 +64,7 @@ Public composition declarations are defined in [`src/types.ts`](src/types.ts). I
Implementation internals — click to expand -The package root only re-exports declarations from [`src/types.ts`](src/types.ts). No runtime invariant companion is published because the package has no runtime state or independently observable relationships. +The package root only re-exports declarations from [`src/types.ts`](src/types.ts).
diff --git a/packages/util/package-manifest/README.zh.md b/packages/util/package-manifest/README.zh.md index 58a150ae32..1b06f49571 100644 --- a/packages/util/package-manifest/README.zh.md +++ b/packages/util/package-manifest/README.zh.md @@ -64,7 +64,7 @@ const manifest: DshPackageManifest = {
实现细节——点击展开 -包根仅重新导出 [`src/types.ts`](src/types.ts) 中的声明。本包不发布运行时不变量伴随模块,因为它没有运行时状态或可独立观察的关系。 +包根仅重新导出 [`src/types.ts`](src/types.ts) 中的声明。本包
diff --git a/packages/util/time/README.i18n.yaml b/packages/util/time/README.i18n.yaml index 6e0e48d71a..b49ed1f8e7 100644 --- a/packages/util/time/README.i18n.yaml +++ b/packages/util/time/README.i18n.yaml @@ -15,8 +15,8 @@ en: cd2a60a905c978d6 zh: cc71d387d0cbd442 /dsh-util-time/use-this-package: - en: 1472cf19b0bd7447 - zh: 73bf36759bc2d492 + en: 1485e3ff508a8960 + zh: 47ba622434bd2625 /dsh-util-time/api: en: cbb4b726e939fc03 zh: e9df4a7487433146 diff --git a/packages/util/time/README.md b/packages/util/time/README.md index 788d7310f5..40f6b4d9ef 100644 --- a/packages/util/time/README.md +++ b/packages/util/time/README.md @@ -26,8 +26,6 @@ Zero-dependency zone vocabulary for the wire boundaries that accept a caller's t It is a **library, not a service or plugin**: no `ctx`, registers nothing, holds no state. -No runtime invariant companion is published because this pure utility owns no event stream or mutable runtime data; unit tests verify zone canonicalization. - Call it at the boundary that receives the zone, before the value reaches anything durable. An unusable name answers `undefined`, and the caller raises its own refusal — `session/invalid-time-zone` for the Session prompt, `subagent/invalid-time-zone` for a subagent continuation. ----- diff --git a/packages/util/time/README.zh.md b/packages/util/time/README.zh.md index 48f9d60919..d58f52853c 100644 --- a/packages/util/time/README.zh.md +++ b/packages/util/time/README.zh.md @@ -26,8 +26,6 @@ kind: "package-library" 它是**库,不是服务也不是插件**:无 `ctx`、不注册任何东西、不持有状态。 -不发布运行时不变量伴生入口;这个纯工具不拥有事件流或可变运行时数据,时区规范化由单元测试验证。 - 在接收时区的那个边界上调用它,让值在进入任何持久物之前先过一遍。不可用的名称回答 `undefined`,由调用方抛出自己的拒绝——Session 提示词用 `session/invalid-time-zone`,subagent 续话用 `subagent/invalid-time-zone`。 ----- diff --git a/packages/util/timeout/README.i18n.yaml b/packages/util/timeout/README.i18n.yaml index 48d6dc1658..f1d77a55a7 100644 --- a/packages/util/timeout/README.i18n.yaml +++ b/packages/util/timeout/README.i18n.yaml @@ -36,8 +36,8 @@ en: 6c052aa1171a2e3b zh: 2a37575eb6059cc9 /deepseek-ai-dsh-timeout/understand-the-implementation/source-map: - en: be1b396f4153b9d0 - zh: a762ab1642b3431e + en: b21d7b48571ebfba + zh: e31188863ece408d /deepseek-ai-dsh-timeout/understand-the-implementation/how-a-deadline-fuses-sources: en: 940080c53d1d0dac zh: b86e7f51434a3f2c diff --git a/packages/util/timeout/README.md b/packages/util/timeout/README.md index 80f8dc7c42..ed52895139 100644 --- a/packages/util/timeout/README.md +++ b/packages/util/timeout/README.md @@ -92,7 +92,6 @@ The library is built on one boundary: share the timing and classification, keep | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | `clampTimeout`, `deadline`, `idleWatchdog`, `timeoutOf`, `TimeoutReason`, `MAX_TIMER_DELAY_MS` | -| — | No runtime invariant companion is published; this pure utility owns no event stream or mutable runtime data; its value algebra is enforced by unit tests. | ### How a deadline fuses sources diff --git a/packages/util/timeout/README.zh.md b/packages/util/timeout/README.zh.md index 9d711ecfd2..55a8dca59e 100644 --- a/packages/util/timeout/README.zh.md +++ b/packages/util/timeout/README.zh.md @@ -92,7 +92,6 @@ timer 只在某个迭代器 `next()` 尚未完成时启动,并会因不产生 | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | `clampTimeout`、`deadline`、`idleWatchdog`、`timeoutOf`、`TimeoutReason`、`MAX_TIMER_DELAY_MS` | -| — | 不发布运行时不变式伴生入口;这个纯工具不拥有事件流或可变运行时数据;其值代数约束由单元测试保障。 | ### deadline 如何融合来源 diff --git a/packages/util/values/README.i18n.yaml b/packages/util/values/README.i18n.yaml index 71a78967d6..10030d9613 100644 --- a/packages/util/values/README.i18n.yaml +++ b/packages/util/values/README.i18n.yaml @@ -30,8 +30,8 @@ en: 36866610a9a7f898 zh: aa2f0e4c3cc12335 /deepseek-ai-dsh-util-values/understand-the-implementation/source-map: - en: a3e80436a6006043 - zh: 1de25c1bf436e494 + en: ac05d5b0dfbf5471 + zh: b81d399f2a7e884b /deepseek-ai-dsh-util-values/further-exploration: en: 3be59be736dc8e9a zh: 4f419cb5db38fd12 diff --git a/packages/util/values/README.md b/packages/util/values/README.md index e969e806ec..94635312e1 100644 --- a/packages/util/values/README.md +++ b/packages/util/values/README.md @@ -62,7 +62,6 @@ The JSON validator uses an explicit work stack and tracks only the active ancest | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | JSON value type, validation and snapshot traversal, structural equality, deep freezing, weak-key/strong-value associations, and exhaustive-union failure | -| — | No runtime invariant companion is published because these value operations have no shared runtime state; unit tests cover their algebra. | diff --git a/packages/util/values/README.zh.md b/packages/util/values/README.zh.md index f723a9b48f..f27d55decc 100644 --- a/packages/util/values/README.zh.md +++ b/packages/util/values/README.zh.md @@ -62,7 +62,6 @@ JSON 校验器使用显式工作栈,并只跟踪当前祖先链,因此深层 | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | JSON 值类型、校验与快照遍历、结构相等、深度冻结、弱键/强值关联和穷尽联合类型失败 | -| — | 不发布运行时不变量伴生入口;这些值操作没有共享运行时状态,其代数行为由单元测试覆盖。 | diff --git a/packages/util/workspace-path/README.i18n.yaml b/packages/util/workspace-path/README.i18n.yaml index 65048d95e2..4a509fafe9 100644 --- a/packages/util/workspace-path/README.i18n.yaml +++ b/packages/util/workspace-path/README.i18n.yaml @@ -21,5 +21,5 @@ en: 1e6d448f4f0030c4 zh: c050dd5e470fe47b /dsh-util-workspace-path/known-limitations-and-deferred-work/dev-note: - en: cfc7173facebc57c - zh: 4ea51377d7c9c147 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/util/workspace-path/README.md b/packages/util/workspace-path/README.md index 815b646983..c6b9b1c762 100644 --- a/packages/util/workspace-path/README.md +++ b/packages/util/workspace-path/README.md @@ -47,5 +47,3 @@ A resource address is `dsh-resource:///…`, and the type — the URI host None. - -**Runtime invariant:** No companion is published. This utility owns no mutable runtime relationship. diff --git a/packages/util/workspace-path/README.zh.md b/packages/util/workspace-path/README.zh.md index d9840f99e8..3bf54cf264 100644 --- a/packages/util/workspace-path/README.zh.md +++ b/packages/util/workspace-path/README.zh.md @@ -47,5 +47,3 @@ kind: "package-library" 无。 - -**运行时不变式:** 不发布伴生入口。这个工具不持有可变运行时关系。 diff --git a/packages/web/tool-web/README.i18n.yaml b/packages/web/tool-web/README.i18n.yaml index 45a3635e9c..b3feba1660 100644 --- a/packages/web/tool-web/README.i18n.yaml +++ b/packages/web/tool-web/README.i18n.yaml @@ -42,8 +42,8 @@ en: d0cb2277a9a79e1b zh: 6a37f975b3075ea3 /deepseek-ai-dsh-tool-web/understand-the-implementation/source-map: - en: 245e4ef7953ba060 - zh: 8551a198c701653c + en: c3a302a909a27248 + zh: 32ba8b63d2b5f45c /deepseek-ai-dsh-tool-web/understand-the-implementation/search-flow: en: 80b7de753fe6b0de zh: 09727a7727248888 diff --git a/packages/web/tool-web/README.md b/packages/web/tool-web/README.md index c102023e23..da42068b2f 100644 --- a/packages/web/tool-web/README.md +++ b/packages/web/tool-web/README.md @@ -103,7 +103,6 @@ The package is built on one separation and one registration rule: | [`src/index.ts`](src/index.ts) | Plugin entry: config schema, enablement, timeout budgets, tool registration | | [`src/search.ts`](src/search.ts) | The `web_search` tool: argument validation, query fan-out, merge, formatting, presentation meta | | [`src/fetch.ts`](src/fetch.ts) | The `web_fetch` tool: HTML→markdown conversion, output caps, formatting, presentation meta | -| — | No runtime invariant companion is published; this model-facing adapter has no independent lifecycle stream; execution relations are owned by the capability seam it calls. | ### Search flow diff --git a/packages/web/tool-web/README.zh.md b/packages/web/tool-web/README.zh.md index eecc47485e..870bad2575 100644 --- a/packages/web/tool-web/README.zh.md +++ b/packages/web/tool-web/README.zh.md @@ -103,7 +103,6 @@ schema 校验会在执行前拒绝缺失或非数组的 `queries` 字段、非 | [`src/index.ts`](src/index.ts) | 插件入口:配置 schema、启用状态、超时预算、工具注册 | | [`src/search.ts`](src/search.ts) | `web_search` 工具:参数校验、查询扇出、合并、格式化、呈现元数据 | | [`src/fetch.ts`](src/fetch.ts) | `web_fetch` 工具:HTML→markdown 转换、输出上限、格式化、呈现元数据 | -| — | 不发布运行时不变量配套入口;这个面向模型的适配器没有独立的生命周期事件流;执行关系由它调用的能力 seam 负责。 | ### 搜索流程 diff --git a/packages/web/web-fetch-http/README.i18n.yaml b/packages/web/web-fetch-http/README.i18n.yaml index 0c12396788..5315b70e40 100644 --- a/packages/web/web-fetch-http/README.i18n.yaml +++ b/packages/web/web-fetch-http/README.i18n.yaml @@ -39,8 +39,8 @@ en: a8d806f0706f02e2 zh: d922732a5b7e88bd /deepseek-ai-dsh-web-fetch-http/understand-the-implementation/source-map: - en: e22ec70d55e0d8f7 - zh: 1a333660eae37b1f + en: 023ca0c9a9ef4968 + zh: 74a5785a493f5111 /deepseek-ai-dsh-web-fetch-http/understand-the-implementation/read-path: en: 749c0ffeac145807 zh: 1468821a33a9518a diff --git a/packages/web/web-fetch-http/README.md b/packages/web/web-fetch-http/README.md index a3e6da087d..ab2e82d179 100644 --- a/packages/web/web-fetch-http/README.md +++ b/packages/web/web-fetch-http/README.md @@ -92,7 +92,6 @@ The package is built on one separation and one layered timeout: | [`src/provider.ts`](src/provider.ts) | The `HttpFetchProvider`: pinned transport, redirect following, capped reads, charset decoding | | [`src/network.ts`](src/network.ts) | Public-address resolution, DNS64 discovery, and connection pinning | | [`src/policy.ts`](src/policy.ts) | URL validation, same-origin checks, content-type classification, charset parsing | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | ### Read path diff --git a/packages/web/web-fetch-http/README.zh.md b/packages/web/web-fetch-http/README.zh.md index d57d10545a..2ae10c4b7d 100644 --- a/packages/web/web-fetch-http/README.zh.md +++ b/packages/web/web-fetch-http/README.zh.md @@ -92,7 +92,6 @@ const page = await ctx.web.fetch({ url: 'https://example.com' }) | [`src/provider.ts`](src/provider.ts) | `HttpFetchProvider`:固定连接、重定向跟随、有界读取、charset 解码 | | [`src/network.ts`](src/network.ts) | 公开地址解析、DNS64 发现与连接固定 | | [`src/policy.ts`](src/policy.ts) | URL 校验、同源检查、内容类型分类、charset 解析 | -| — | 不发布运行时不变量配套入口;除所属 seam 强制执行的约定外,本包没有独立的事件序列或可变数据关系。 | ### 读取路径 diff --git a/packages/web/web-search-deepseek/README.i18n.yaml b/packages/web/web-search-deepseek/README.i18n.yaml index c6c339e567..dcc66727f8 100644 --- a/packages/web/web-search-deepseek/README.i18n.yaml +++ b/packages/web/web-search-deepseek/README.i18n.yaml @@ -42,8 +42,8 @@ en: 1b1760efbf4568c1 zh: fca06c959e51ce16 /deepseek-ai-dsh-web-search-deepseek/understand-the-implementation/source-map: - en: d354ae3f0f21f7c6 - zh: 95eb16d30d9c7a9c + en: 680941350c454325 + zh: 939225eb3a483bb8 /deepseek-ai-dsh-web-search-deepseek/understand-the-implementation/request-flow: en: d6469ab9ac915762 zh: 07ddf7b12c748eaf diff --git a/packages/web/web-search-deepseek/README.md b/packages/web/web-search-deepseek/README.md index 6feddeec26..ff72719ef6 100644 --- a/packages/web/web-search-deepseek/README.md +++ b/packages/web/web-search-deepseek/README.md @@ -96,7 +96,6 @@ The provider is built on two commitments: | [`src/index.ts`](src/index.ts) | Config schema and per-search option capture | | [`src/provider.ts`](src/provider.ts) | The `DeepSeekSearchProvider`: Messages request dispatch, block parsing, citation joining, credential resolution | | [`src/types.ts`](src/types.ts) | Anthropic wire types for the search response | -| — | No runtime invariant companion is published; the package emits a pre-dispatch log event but owns no later authoritative dispatch event to relate it to. Exact envelope equality is pinned at the provider boundary instead. | ### Request flow diff --git a/packages/web/web-search-deepseek/README.zh.md b/packages/web/web-search-deepseek/README.zh.md index 40579e7bb9..671e672367 100644 --- a/packages/web/web-search-deepseek/README.zh.md +++ b/packages/web/web-search-deepseek/README.zh.md @@ -96,7 +96,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | Config schema 与每次搜索的选项捕获 | | [`src/provider.ts`](src/provider.ts) | `DeepSeekSearchProvider`:Messages 请求分发、块解析、引用拼接、凭据解析 | | [`src/types.ts`](src/types.ts) | 搜索响应的 Anthropic 协议类型 | -| — | 不发布运行时不变量配套入口;本包会在分发前发出日志事件,但没有后续的权威分发事件可与之关联;精确的请求包络相等性改由提供方边界保障。 | ### 请求流程 diff --git a/packages/web/web-search-exa/README.i18n.yaml b/packages/web/web-search-exa/README.i18n.yaml index 62d49d070a..b02cb565b7 100644 --- a/packages/web/web-search-exa/README.i18n.yaml +++ b/packages/web/web-search-exa/README.i18n.yaml @@ -36,8 +36,8 @@ en: 341f17a367eca5b0 zh: ca3a4e4b4956892d /deepseek-ai-dsh-web-search-exa/understand-the-implementation/source-map: - en: 58dfdd4423914f34 - zh: 82c95e00bbb88028 + en: ca70cbf87a47c506 + zh: bb440072bbd6a478 /deepseek-ai-dsh-web-search-exa/understand-the-implementation/request-and-mapping-flow: en: 1fe92c2ad0408273 zh: cc8c573e0accc8dc diff --git a/packages/web/web-search-exa/README.md b/packages/web/web-search-exa/README.md index 2bfa2a40ad..b1e7862ccd 100644 --- a/packages/web/web-search-exa/README.md +++ b/packages/web/web-search-exa/README.md @@ -84,7 +84,6 @@ The provider is a thin adapter over Exa's API with two deliberate rules: | [`src/index.ts`](src/index.ts) | Plugin entry: config schema, environment fallback, provider registration | | [`src/provider.ts`](src/provider.ts) | The `ExaSearchProvider`: request dispatch, abort classification, result mapping | | [`src/types.ts`](src/types.ts) | Exa wire types: `ExaSearchResponse`, `ExaResult`, `ExaError` | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | ### Request and mapping flow diff --git a/packages/web/web-search-exa/README.zh.md b/packages/web/web-search-exa/README.zh.md index 0511710df6..5781e30d61 100644 --- a/packages/web/web-search-exa/README.zh.md +++ b/packages/web/web-search-exa/README.zh.md @@ -84,7 +84,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:配置 schema、环境变量回退、提供方注册 | | [`src/provider.ts`](src/provider.ts) | `ExaSearchProvider`:请求分发、中止分类、结果映射 | | [`src/types.ts`](src/types.ts) | Exa 协议类型:`ExaSearchResponse`、`ExaResult`、`ExaError` | -| — | 不发布运行时不变量配套入口;除所属 seam 强制执行的约定外,本包没有独立的事件序列或可变数据关系。 | ### 请求与映射流程 diff --git a/packages/web/web-search-perplexity/README.i18n.yaml b/packages/web/web-search-perplexity/README.i18n.yaml index 9c22ccaeb6..d208117640 100644 --- a/packages/web/web-search-perplexity/README.i18n.yaml +++ b/packages/web/web-search-perplexity/README.i18n.yaml @@ -36,8 +36,8 @@ en: aee86ee45c4635c0 zh: 45cecc44ae467520 /deepseek-ai-dsh-web-search-perplexity/understand-the-implementation/source-map: - en: f481a8541f09f1cf - zh: c0c47902eae6973d + en: 401317176e3117f5 + zh: 2fad7652819eaa03 /deepseek-ai-dsh-web-search-perplexity/understand-the-implementation/request-and-mapping-flow: en: ff5efa9c7b1a9cda zh: ca1e246eb8e7eaee diff --git a/packages/web/web-search-perplexity/README.md b/packages/web/web-search-perplexity/README.md index 21754cea3a..88990ab324 100644 --- a/packages/web/web-search-perplexity/README.md +++ b/packages/web/web-search-perplexity/README.md @@ -84,7 +84,6 @@ The provider is a thin adapter over Perplexity's chat-completions endpoint with | [`src/index.ts`](src/index.ts) | Plugin entry: config schema, environment fallback, provider registration | | [`src/provider.ts`](src/provider.ts) | The `PerplexitySearchProvider`: request dispatch, abort classification, answer and source mapping | | [`src/types.ts`](src/types.ts) | Perplexity wire types for the chat-completions response | -| — | No runtime invariant companion is published; this package exposes no independent event sequence or mutable data relation beyond contracts enforced at its owning seam. | ### Request and mapping flow diff --git a/packages/web/web-search-perplexity/README.zh.md b/packages/web/web-search-perplexity/README.zh.md index 03ee456cc1..9c29b9cf5c 100644 --- a/packages/web/web-search-perplexity/README.zh.md +++ b/packages/web/web-search-perplexity/README.zh.md @@ -84,7 +84,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:配置 schema、环境变量回退、提供方注册 | | [`src/provider.ts`](src/provider.ts) | `PerplexitySearchProvider`:请求分发、中止分类、答案与来源映射 | | [`src/types.ts`](src/types.ts) | chat-completions 响应的 Perplexity 协议类型 | -| — | 不发布运行时不变量配套入口;除所属 seam 强制执行的约定外,本包不公开独立的事件序列或可变数据关系。 | ### 请求与映射流程 diff --git a/packages/web/web/README.i18n.yaml b/packages/web/web/README.i18n.yaml index af1d8e19b2..236529977f 100644 --- a/packages/web/web/README.i18n.yaml +++ b/packages/web/web/README.i18n.yaml @@ -39,8 +39,8 @@ en: c4d5437cfb1e2157 zh: 411b1b398de73426 /deepseek-ai-dsh-web/understand-the-implementation/source-map: - en: 75e450b263def321 - zh: 7f28ff78ba2a5f03 + en: fd09f717e9b70e51 + zh: 22a8ba43b0e0ebe0 /deepseek-ai-dsh-web/understand-the-implementation/data-model: en: 300340ff8465a03e zh: 34ab66eb6969dc94 diff --git a/packages/web/web/README.md b/packages/web/web/README.md index 157c6d9e3a..fa6ce6652e 100644 --- a/packages/web/web/README.md +++ b/packages/web/web/README.md @@ -105,7 +105,6 @@ The package is built on one deliberate separation: |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: the `WebRuntime` service, both provider registries, and execution-time selection | | [`src/types.ts`](src/types.ts) | Vocabulary: request/result types, the closed `WebFetchBody` union, and the `WebError` taxonomy | -| — | No runtime invariant companion is published; provider maps are private and selection/result caps are enforced on each call; the seam publishes no independent registry or request/result observation stream. | ### Data model diff --git a/packages/web/web/README.zh.md b/packages/web/web/README.zh.md index f937fc127b..e2b335f39e 100644 --- a/packages/web/web/README.zh.md +++ b/packages/web/web/README.zh.md @@ -105,7 +105,6 @@ const page = await ctx.web.fetch({ url: 'https://example.com' }) |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:`WebRuntime` 服务、两个提供方注册表与执行时选择 | | [`src/types.ts`](src/types.ts) | 词汇:请求/结果类型、封闭的 `WebFetchBody` 联合与 `WebError` 分类体系 | -| — | 不发布运行时不变式配套项;提供方映射是私有数据,服务会在每次调用时执行提供方选择并强制执行结果上限;该 seam 不发布独立注册表,也不发布请求/结果观测流。 | ### 数据模型 diff --git a/packages/webhook/webhook-github/README.i18n.yaml b/packages/webhook/webhook-github/README.i18n.yaml index 56b4a4c005..a540bf7a00 100644 --- a/packages/webhook/webhook-github/README.i18n.yaml +++ b/packages/webhook/webhook-github/README.i18n.yaml @@ -33,5 +33,5 @@ en: 0c3192c3e300bb46 zh: 7c84d20e6a8a768e /deepseek-ai-dsh-webhook-github/known-limitations-and-deferred-work/dev-note: - en: cd9dd4a041ca520e - zh: 9fd648221ae335c4 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/webhook/webhook-github/README.md b/packages/webhook/webhook-github/README.md index 9c2b20f4ab..ce0e2da0aa 100644 --- a/packages/webhook/webhook-github/README.md +++ b/packages/webhook/webhook-github/README.md @@ -84,5 +84,3 @@ Independent. Authentication and HTTP dispatch do not touch a model request; any None. - -**Runtime invariant:** No companion is published. Authentication and input validation occur at the exact HTTP operation; dsh-host-webserver owns route/disposer symmetry. diff --git a/packages/webhook/webhook-github/README.zh.md b/packages/webhook/webhook-github/README.zh.md index 8dae90aaa7..5d2221e06d 100644 --- a/packages/webhook/webhook-github/README.zh.md +++ b/packages/webhook/webhook-github/README.zh.md @@ -84,5 +84,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。authentication 与 input validation 在对应 HTTP 操作中完成;route/disposer 对称性由 `dsh-host-webserver` 负责。 diff --git a/packages/webhook/webhook/package.json b/packages/webhook/webhook/package.json index 0988950137..46207e7327 100644 --- a/packages/webhook/webhook/package.json +++ b/packages/webhook/webhook/package.json @@ -22,16 +22,11 @@ "types": "./lib/types/types.d.ts", "default": "./lib/types/types.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/**/*.js", "lib/types/**/*.d.ts" ], @@ -41,7 +36,6 @@ "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-agent-default-model": "workspace:*", "@deepseek-ai/dsh-agent-preset-registry": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-permission-presets": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", @@ -55,7 +49,6 @@ "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-agent-default-model": "workspace:*", "@deepseek-ai/dsh-agent-preset-registry": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-permission-presets": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", diff --git a/packages/webhook/webhook/src/invariant.ts b/packages/webhook/webhook/src/invariant.ts deleted file mode 100644 index 604eee43bd..0000000000 --- a/packages/webhook/webhook/src/invariant.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** Package-owned relationship invariant for webhook-origin prompt admission. */ - -import type { Context } from '@deepseek-ai/cordis' -import type {} from '@deepseek-ai/dsh-agent' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type {} from '@deepseek-ai/dsh-workspace' -import type {} from './types.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-webhook' - -/** Cordis invariant-companion plugin name. */ -export const name = 'webhook-invariant' -/** Registry required before reserving this package's invariant ownership. */ -export const inject = ['invariants'] - -/** Verify that one webhook-origin message already belongs to its cwd Workspace. */ -const install: InvariantInstaller = Object.assign(function installWebhookMessages( - ctx: Context, - fail: InvariantFailure, -): void { - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - if (event.type !== 'agent/inbox/spliced') return - const webhookMessages = event.data.inserted.filter(message => message.source.kind === 'webhook') - if (webhookMessages.length === 0) return - const cwd = session.header.cwd - if (cwd === undefined) return fail(`webhook Session "${session.id}" has no cwd`) - const owners = ctx.workspaceRegistry.list().filter(workspace => workspace.sessionIds.includes(session.id)) - if (owners.length !== 1) { - return fail(`webhook Session "${session.id}" belongs to ${owners.length} Workspaces at prompt admission`) - } - if (owners[0]?.path !== cwd) { - fail(`webhook Session "${session.id}" cwd ${JSON.stringify(cwd)} differs from its Workspace path`) - } - }, { global: true }) -}, { - inject: ['workspaceRegistry'], -}) - -/** - * Register this package's relationship invariant. - * @param ctx - Cordis context carrying the invariant registry. - * @returns the invariant registration disposer. - */ -export const apply = (ctx: Context): Promise<() => void> => - Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) diff --git a/packages/webhook/webhook/tests/invariant.spec.ts b/packages/webhook/webhook/tests/invariant.spec.ts deleted file mode 100644 index 796b27d54e..0000000000 --- a/packages/webhook/webhook/tests/invariant.spec.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Context } from '@deepseek-ai/cordis' -import { createUserMessage } from '@deepseek-ai/dsh-llm' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' -import { describe, expect, it } from 'vitest' -import { WebhookDeliveryId, WebhookRuleId, WebhookSourceId } from '../src/index.ts' -import * as WebhookInvariant from '../src/invariant.ts' - -/** Install the invariant over one mutable Workspace projection. */ -async function harness(): Promise<{ - ctx: Context - workspaces: { path: string; sessionIds: readonly SessionId[] }[] -}> { - const ctx = new Context() - const workspaces: { path: string; sessionIds: readonly SessionId[] }[] = [] - ctx.provide('workspaceRegistry', { list: () => workspaces } as never) - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry) - await ctx.plugin(WebhookInvariant) - return { ctx, workspaces } -} - -/** Append one candidate webhook inbox insertion. */ -function insert(ctx: Context, id: SessionId, cwd?: string): void { - const session = ctx.sessions.create(id, { meta: { ...(cwd === undefined ? {} : { cwd }) } }) - session.append('agent/inbox/spliced', { - target: 'next-turn', - start: 0, - inserted: [createUserMessage({ - content: [{ type: 'text', text: 'review' }], - source: { - kind: 'webhook', - provider: 'github', - source: WebhookSourceId('primary'), - deliveryId: WebhookDeliveryId('delivery'), - ruleId: WebhookRuleId('review'), - form: 'notice', - summary: 'review', - }, - })], - }) -} - -describe('webhook prompt invariant', () => { - it('accepts prompt admission after matching Workspace attachment', async () => { - const { ctx, workspaces } = await harness() - const id = SessionId('attached') - workspaces.push({ path: '/workspace', sessionIds: [id] }) - expect(() => { insert(ctx, id, '/workspace') }).not.toThrow() - await ctx.fiber.dispose() - }) - - it('rejects a missing cwd, missing or duplicate Workspace, and path mismatch', async () => { - const missingCwd = await harness() - const noCwdId = SessionId('no-cwd') - missingCwd.workspaces.push({ path: '/workspace', sessionIds: [noCwdId] }) - expect(() => { insert(missingCwd.ctx, noCwdId) }).toThrow(/has no cwd/) - await missingCwd.ctx.fiber.dispose() - - const missing = await harness() - expect(() => { insert(missing.ctx, SessionId('missing'), '/workspace') }).toThrow(/belongs to 0 Workspaces/) - await missing.ctx.fiber.dispose() - - const duplicate = await harness() - const duplicateId = SessionId('duplicate') - duplicate.workspaces.push( - { path: '/workspace', sessionIds: [duplicateId] }, - { path: '/workspace', sessionIds: [duplicateId] }, - ) - expect(() => { insert(duplicate.ctx, duplicateId, '/workspace') }).toThrow(/belongs to 2 Workspaces/) - await duplicate.ctx.fiber.dispose() - - const mismatch = await harness() - const mismatchId = SessionId('mismatch') - mismatch.workspaces.push({ path: '/other', sessionIds: [mismatchId] }) - expect(() => { insert(mismatch.ctx, mismatchId, '/workspace') }).toThrow(/differs from its Workspace path/) - await mismatch.ctx.fiber.dispose() - }) - - it('ignores non-webhook inbox messages', async () => { - const { ctx } = await harness() - const session = ctx.sessions.create(SessionId('human'), { meta: { cwd: '/workspace' } }) - expect(() => session.append('agent/inbox/spliced', { - target: 'next-turn', - start: 0, - inserted: [createUserMessage({ - content: [{ type: 'text', text: 'hello' }], - source: { kind: 'user' }, - })], - })).not.toThrow() - expect(() => session.append('todo/write', { todos: [] })).not.toThrow() - await ctx.fiber.dispose() - }) -}) diff --git a/packages/webhook/webhook/tests/runtime.spec.ts b/packages/webhook/webhook/tests/runtime.spec.ts index 88c49bac19..fba98bd02c 100644 --- a/packages/webhook/webhook/tests/runtime.spec.ts +++ b/packages/webhook/webhook/tests/runtime.spec.ts @@ -196,7 +196,6 @@ describe('WebhookRuntime', () => { '../src/types.ts', '../src/session.ts', '../src/index.ts', - '../src/invariant.ts', ].map(path => readFileSync(new URL(path, import.meta.url), 'utf8')).join('\n') const forbidden: ReadonlyArray = [ ['execution records', /\bWebhook(?:Execution|Status)\b/], diff --git a/packages/webhook/webhook/tsconfig.json b/packages/webhook/webhook/tsconfig.json index 11b8fb4180..2a0de260fc 100644 --- a/packages/webhook/webhook/tsconfig.json +++ b/packages/webhook/webhook/tsconfig.json @@ -43,9 +43,6 @@ }, { "path": "../../workspace/workspace" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/workflow/tool-ralph/README.i18n.yaml b/packages/workflow/tool-ralph/README.i18n.yaml index 15437507a1..6f226dffab 100644 --- a/packages/workflow/tool-ralph/README.i18n.yaml +++ b/packages/workflow/tool-ralph/README.i18n.yaml @@ -45,8 +45,8 @@ en: 07e071bd44b1df65 zh: 26f1cb2eab8c25b0 /deepseek-ai-dsh-tool-ralph/understand-the-implementation/source-map: - en: b0cf0927913968fb - zh: 6170b4c8d2529a41 + en: 1bc11234792b4e3f + zh: 872d2c5f8df37b72 /deepseek-ai-dsh-tool-ralph/further-exploration: en: 9ab6ec527d57aee1 zh: e0258a158b8027ea diff --git a/packages/workflow/tool-ralph/README.md b/packages/workflow/tool-ralph/README.md index d94d3a6ca9..57764d544f 100644 --- a/packages/workflow/tool-ralph/README.md +++ b/packages/workflow/tool-ralph/README.md @@ -81,7 +81,6 @@ The pending call is a `generic` card titled `ralph` with the immutable objective | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | Plugin entry: fixed script, provider routing, report validation, tool registration | -| — | No runtime invariant companion is published; this model-facing orchestration adapter owns no independent event stream; workflow and subagent owners validate the runs and child lifecycles it starts. | diff --git a/packages/workflow/tool-ralph/README.zh.md b/packages/workflow/tool-ralph/README.zh.md index bb7b3bd81d..48aed3cf87 100644 --- a/packages/workflow/tool-ralph/README.zh.md +++ b/packages/workflow/tool-ralph/README.zh.md @@ -81,7 +81,6 @@ kind: "package-reference" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | 插件入口:固定脚本、提供方路由、报告校验、工具注册 | -| — | 不发布运行时不变式伴生入口;该面向模型的编排适配器不拥有独立事件流;工作流与 subagent 归属方会校验该适配器启动的运行及其子 agent 生命周期。 | diff --git a/packages/workflow/tool-workflow/README.i18n.yaml b/packages/workflow/tool-workflow/README.i18n.yaml index 5b8432a419..97184e784a 100644 --- a/packages/workflow/tool-workflow/README.i18n.yaml +++ b/packages/workflow/tool-workflow/README.i18n.yaml @@ -42,14 +42,14 @@ en: 35950b2015800ec5 zh: 5ae6f5a5e53dc585 /deepseek-ai-dsh-tool-workflow/understand-the-implementation/durable-session-records: - en: b491e7ab9590e625 - zh: c0a2fb797eb1cca0 + en: e5bbbb7788d6dcdf + zh: aa035f074f7ac021 /deepseek-ai-dsh-tool-workflow/understand-the-implementation/render-intent: en: 389b3740ec1f5ac2 zh: 78430014f5e270e2 /deepseek-ai-dsh-tool-workflow/understand-the-implementation/source-map: - en: 8a426c65b06a2320 - zh: e2bb8f12252fbd4d + en: c22defb9681d39c7 + zh: 0856b88d731fb127 /deepseek-ai-dsh-tool-workflow/further-exploration: en: cf5f97e745586c05 zh: 32fa36224cde5d83 diff --git a/packages/workflow/tool-workflow/README.md b/packages/workflow/tool-workflow/README.md index 084c073cd3..e90773894c 100644 --- a/packages/workflow/tool-workflow/README.md +++ b/packages/workflow/tool-workflow/README.md @@ -75,7 +75,7 @@ A background call registers the run through `jobs.start` inside the job starter, ### Durable session records -For a root transport execution (`exec.parent` absent), the tool projects the run into the calling Agent's Session with four log-only events: run-start after `start()` returns, member starts and endings filtered by `run.id`, then run-end only after the result is available and disposal reaches quiescence. Nested transport calls execute normally but write no record. The first failed Session append disables later recording for that run with one warning, leaving either no record or a legal continuous prefix without changing the tool result or cleanup. The package invariant rejects duplicate starts, unpaired members, terminal events with open members, and updates after run-end on both cold load and live append, while accepting missing terminal suffixes. +For a root transport execution (`exec.parent` absent), the tool projects the run into the calling Agent's Session with four log-only events: run-start after `start()` returns, member starts and endings filtered by `run.id`, then run-end only after the result is available and disposal reaches quiescence. Nested transport calls execute normally but write no record. The first failed Session append disables later recording for that run with one warning, leaving either no record or a legal continuous prefix without changing the tool result or cleanup. The engine's `workflow/phase` and `workflow/log` events have no per-line durable surface from this tool: the session log deliberately records run and member lifecycle only, and the Web transcript derives from those records. A background run's lines reach a human through the job observation record instead, which is transient by design. @@ -90,7 +90,6 @@ Decided up front per the [render-intent Agent Note](../../../.agents/notes/imple | [`src/index.ts`](src/index.ts) | Plugin entry: tool registration, run lifecycle, background job registration, recorder wiring | | [`src/record.ts`](src/record.ts) | Background runs' live-progress mirror into the job's output ring | | [`src/types.ts`](src/types.ts) | The four log-only record event payloads and their `SessionEventMap` declaration | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: durable workflow-record protocol validation | diff --git a/packages/workflow/tool-workflow/README.zh.md b/packages/workflow/tool-workflow/README.zh.md index 276175136b..b33d451687 100644 --- a/packages/workflow/tool-workflow/README.zh.md +++ b/packages/workflow/tool-workflow/README.zh.md @@ -75,7 +75,7 @@ kind: "package-reference" ### 持久会话记录 -对于根 transport 执行(`exec.parent` 缺省),工具会用四个 log-only 事件把运行投影到调用方 agent 的会话:`start()` 返回后写 run-start,只记录 `run.id` 匹配的成员开始与结束,并且只在结果可用且 dispose 完全停稳后写 run-end。嵌套 transport 调用照常执行,但不写任何记录。会话追加操作首次失败后,本运行会停止后续记录并只告警一次,留下空记录或合法连续前缀,同时不改变工具结果和清理。包 invariant 会在冷加载与实时追加时拒绝重复 start、未配对成员、仍有开放成员的终点与 run-end 后更新,同时允许缺失终态后缀的连续前缀。 +对于根 transport 执行(`exec.parent` 缺省),工具会用四个 log-only 事件把运行投影到调用方 agent 的会话:`start()` 返回后写 run-start,只记录 `run.id` 匹配的成员开始与结束,并且只在结果可用且 dispose 完全停稳后写 run-end。嵌套 transport 调用照常执行,但不写任何记录。会话追加操作首次失败后,本运行会停止后续记录并只告警一次,留下空记录或合法连续前缀,同时不改变工具结果和清理。 引擎的 `workflow/phase` 与 `workflow/log` 事件在本工具没有逐行的持久面:会话日志刻意只记录 run 与成员生命周期,Web transcript 由这些记录派生。后台运行的这些行改经任务观察 record 抵达人类,而 record 的瞬态是设计使然。 @@ -90,7 +90,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:工具注册、运行生命周期、后台任务注册、记录器接线 | | [`src/record.ts`](src/record.ts) | 后台运行进任务输出环的实时进度镜像 | | [`src/types.ts`](src/types.ts) | 四个 log-only 记录事件 payload 及其 `SessionEventMap` 声明 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式配套入口:持久工作流记录协议校验 | diff --git a/packages/workflow/tool-workflow/package.json b/packages/workflow/tool-workflow/package.json index 7b68a0b232..9153f14e1d 100644 --- a/packages/workflow/tool-workflow/package.json +++ b/packages/workflow/tool-workflow/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -31,7 +27,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -39,7 +34,6 @@ "peerDependencies": { "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-jobs": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", @@ -54,7 +48,6 @@ "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-agent-loop-testkit": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-jobs": "workspace:*", "@deepseek-ai/dsh-jobs-local": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", diff --git a/packages/workflow/tool-workflow/src/invariant.ts b/packages/workflow/tool-workflow/src/invariant.ts deleted file mode 100644 index 1d41b5c216..0000000000 --- a/packages/workflow/tool-workflow/src/invariant.ts +++ /dev/null @@ -1,168 +0,0 @@ -/** Package-owned durable workflow-record invariants. @module @deepseek-ai/dsh-tool-workflow/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { Session, SessionEvent } from '@deepseek-ai/dsh-session' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type {} from './types.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-tool-workflow' - -/** Cordis companion plugin name. */ -export const name = 'tool-workflow-invariant' -/** Services required to validate existing and newly appended Session logs. */ -export const inject = ['invariants'] - -interface RunTrace { - ended: boolean - readonly members: Map -} - -type WorkflowTrace = Map - -/** Whether this package owns the candidate Session event. */ -function isWorkflowRecordEvent(event: SessionEvent): boolean { - return event.type.startsWith('tool-workflow/') -} - -/** Require a durable opaque identity to be a non-empty string. */ -function stringId(value: unknown, label: string, fail: InvariantFailure): string { - if (typeof value !== 'string' || value.length === 0) fail(`${label} must be a non-empty string`) - return value -} - -/** Require one workflow member's 1-based sequence identity. */ -function memberSeq(value: unknown, fail: InvariantFailure): number { - if (!Number.isSafeInteger(value) || (value as number) < 1) { - fail('tool-workflow member seq must be a positive safe integer') - } - return value as number -} - -/** Read one plain payload field without trusting restored plugin data. */ -function recordOf(event: SessionEvent, fail: InvariantFailure): Record { - const data: unknown = event.data - if (data === null || typeof data !== 'object' || Array.isArray(data)) { - fail(`${event.type} data must be a JSON object`) - } - return data as Record -} - -/** Copy only the run one candidate can mutate; other committed states stay shared. */ -function cloneTraceForEvent( - source: WorkflowTrace, - event: SessionEvent, - fail: InvariantFailure, -): WorkflowTrace { - const trace = new Map(source) - if (event.type === 'tool-workflow/run-start') return trace - const data = recordOf(event, fail) - const runId = stringId(data.runId, `${event.type} runId`, fail) - const run = source.get(runId) - if (run !== undefined) { - trace.set(runId, { ended: run.ended, members: new Map(run.members) }) - } - return trace -} - -/** Require the named run to exist and remain open. */ -function openRun(trace: WorkflowTrace, runId: string, eventType: string, fail: InvariantFailure): RunTrace { - const run = trace.get(runId) - if (run === undefined) fail(`${eventType} has no matching tool-workflow/run-start for run ${runId}`) - if (run.ended) fail(`${eventType} appears after tool-workflow/run-end for run ${runId}`) - return run -} - -/** Advance the workflow-record fold with one relevant Session event. */ -function applyEvent(trace: WorkflowTrace, event: SessionEvent, fail: InvariantFailure): void { - const data = recordOf(event, fail) - const runId = stringId(data.runId, `${event.type} runId`, fail) - - switch (event.type) { - case 'tool-workflow/run-start': { - if (typeof data.name !== 'string' || data.name.length === 0) { - fail('tool-workflow/run-start name must be a non-empty string') - } - if (trace.has(runId)) fail(`tool-workflow/run-start repeats run ${runId}`) - trace.set(runId, { ended: false, members: new Map() }) - return - } - case 'tool-workflow/agent-start': { - const run = openRun(trace, runId, event.type, fail) - const seq = memberSeq(data.seq, fail) - if (typeof data.label !== 'string') fail('tool-workflow/agent-start label must be a string') - if (data.phase !== undefined && typeof data.phase !== 'string') { - fail('tool-workflow/agent-start phase must be a string when present') - } - stringId(data.childId, 'tool-workflow/agent-start childId', fail) - if (run.members.has(seq)) fail(`tool-workflow/agent-start repeats member seq ${seq} in run ${runId}`) - run.members.set(seq, false) - return - } - case 'tool-workflow/agent-end': { - const run = openRun(trace, runId, event.type, fail) - const seq = memberSeq(data.seq, fail) - if (data.outcome !== 'completed' && data.outcome !== 'failed' && data.outcome !== 'cancelled') { - fail(`tool-workflow/agent-end outcome ${String(data.outcome)} is invalid`) - } - const ended = run.members.get(seq) - if (ended === undefined) fail(`tool-workflow/agent-end has no matching member seq ${seq} in run ${runId}`) - if (ended) fail(`tool-workflow/agent-end repeats member seq ${seq} in run ${runId}`) - run.members.set(seq, true) - return - } - case 'tool-workflow/run-end': { - const run = openRun(trace, runId, event.type, fail) - if (data.stopReason !== 'completed' && data.stopReason !== 'cancelled' && data.stopReason !== 'error') { - fail(`tool-workflow/run-end stopReason ${String(data.stopReason)} is invalid`) - } - const openMembers = [...run.members].filter(([, ended]) => !ended).map(([seq]) => seq) - if (openMembers.length > 0) { - fail(`tool-workflow/run-end leaves member seq ${openMembers.join(', ')} open in run ${runId}`) - } - run.ended = true - run.members.clear() - return - } - default: - fail(`unknown tool-workflow event type ${event.type}`) - } -} - -/** Install an independent incremental fold over every attached Session. */ -const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => { - const traces = new WeakMap() - const staged = new WeakMap() - - const seed = (session: Session): WorkflowTrace => { - const trace: WorkflowTrace = new Map() - // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred. - for (const event of session.snapshotEvents().filter(isWorkflowRecordEvent)) applyEvent(trace, event, fail) - traces.set(session, trace) - return trace - } - ctx.sessions.list().forEach(seed) - ctx.on('session/created', (session) => { seed(session) }, { global: true }) - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName !== 'session/event') return - const [session, event] = args as [Session, SessionEvent] - if (!isWorkflowRecordEvent(event)) return - // session/event dispatch follows list() or session/created seeding. - const trace = cloneTraceForEvent(traces.get(session) as WorkflowTrace, event, fail) - applyEvent(trace, event, fail) - staged.set(event, { session, trace }) - }, { global: true }) - ctx.on('session/event', (session, event) => { - if (!isWorkflowRecordEvent(event)) return - const candidate = staged.get(event) - /* v8 ignore next 2 -- internal/dispatch stages the exact session/event callback arguments. */ - if (candidate === undefined || candidate.session !== session) { - return fail('session/event reached publication without matching workflow-record validation') - } - staged.delete(event) - traces.set(session, candidate.trace) - }, { global: true }) -}, { inject: ['sessions'] }) - -/** Register this package's invariant companion. */ -export const apply = (ctx: Context): Promise<() => void> => - Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) diff --git a/packages/workflow/tool-workflow/tests/invariant.spec.ts b/packages/workflow/tool-workflow/tests/invariant.spec.ts deleted file mode 100644 index f6b99fe7f9..0000000000 --- a/packages/workflow/tool-workflow/tests/invariant.spec.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import InvariantRegistry, { InvariantError } from '@deepseek-ai/dsh-invariants' -import SessionStore, { SessionId, type Session } from '@deepseek-ai/dsh-session' -import { WorkflowRunId, type WorkflowRunId as WorkflowRunIdType } from '@deepseek-ai/dsh-workflow/types' -import * as ToolWorkflowInvariant from '../src/invariant.ts' -import type {} from '../src/types.ts' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(ToolWorkflowInvariant) - return ctx -} - -describe('durable workflow-record invariants', () => { - it('accepts interleaved complete runs and an unfinished continuous prefix', async () => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('workflow-record-valid')) - session.append('turn/start', { turn: 1 }) - session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - const first = WorkflowRunId('first') - const second = WorkflowRunId('second') - const third = WorkflowRunId('third') - session.append('tool-workflow/run-start', { runId: first, name: 'first' }) - session.append('tool-workflow/run-start', { runId: second, name: 'second' }) - session.append('tool-workflow/agent-start', { - runId: second, seq: 1, label: '', phase: '', childId: SessionId('child'), - }) - session.append('tool-workflow/run-end', { runId: first, stopReason: 'completed' }) - session.append('tool-workflow/agent-end', { runId: second, seq: 1, outcome: 'cancelled' }) - session.append('tool-workflow/run-end', { runId: second, stopReason: 'cancelled' }) - session.append('tool-workflow/run-start', { runId: third, name: 'third' }) - session.append('tool-workflow/agent-start', { - runId: third, seq: 1, label: 'failed', childId: SessionId('failed-child'), - }) - session.append('tool-workflow/agent-end', { runId: third, seq: 1, outcome: 'failed' }) - session.append('tool-workflow/run-end', { runId: third, stopReason: 'error' }) - session.append('tool-workflow/run-start', { runId: WorkflowRunId('prefix'), name: 'prefix' }) - expect(() => session.append('tool-workflow/agent-start', { - runId: WorkflowRunId('prefix'), seq: 1, label: 'open', childId: SessionId('open-child'), - })).not.toThrow() - }) - - it('rejects a malformed candidate before commit and keeps the fold reusable', async () => { - const ctx = await setup() - const session = ctx.sessions.create(SessionId('workflow-record-invalid')) - const runId = WorkflowRunId('run') - session.append('tool-workflow/run-start', { runId, name: 'run' }) - const before = session.seq - expect(() => session.append('tool-workflow/agent-end', { - runId, seq: 1, outcome: 'completed', - })).toThrow(expect.objectContaining>({ - code: 'INVARIANT', - packageName: '@deepseek-ai/dsh-tool-workflow', - })) - expect(session.seq).toBe(before) - expect(() => session.append('tool-workflow/run-end', { - runId, stopReason: 'completed', - })).not.toThrow() - }) - - type Mutation = (session: Session, runId: WorkflowRunIdType) => void - const appendRaw = (session: Session, type: string, data: unknown): void => { - const append = session.append.bind(session) as (eventType: string, eventData: unknown) => unknown - append(type, data) - } - const invalidCases: readonly [string, Mutation, RegExp][] = [ - ['null event data', (session) => { - appendRaw(session, 'tool-workflow/run-start', null) - }, /data must be a JSON object/], - ['primitive event data', (session) => { - appendRaw(session, 'tool-workflow/run-start', 1) - }, /data must be a JSON object/], - ['array event data', (session) => { - appendRaw(session, 'tool-workflow/run-start', []) - }, /data must be a JSON object/], - ['numeric run id', (session) => { - session.append('tool-workflow/agent-start', { - runId: 1 as never, seq: 1, label: 'bad', childId: SessionId('child'), - }) - }, /runId must be a non-empty string/], - ['empty run id', (session) => { - session.append('tool-workflow/agent-start', { - runId: WorkflowRunId(''), seq: 1, label: 'bad', childId: SessionId('child'), - }) - }, /runId must be a non-empty string/], - ['empty run name', (session) => { - session.append('tool-workflow/run-start', { runId: WorkflowRunId('empty-name'), name: '' }) - }, /name must be a non-empty string/], - ['non-string run name', (session) => { - session.append('tool-workflow/run-start', { runId: WorkflowRunId('bad-name'), name: 1 as never }) - }, /name must be a non-empty string/], - ['duplicate run', (session, runId) => { - session.append('tool-workflow/run-start', { runId, name: 'again' }) - }, /repeats run/], - ['missing run', (session) => { - session.append('tool-workflow/agent-start', { - runId: WorkflowRunId('missing'), seq: 1, label: 'bad', childId: SessionId('child'), - }) - }, /no matching tool-workflow\/run-start/], - ['non-positive member seq', (session, runId) => { - session.append('tool-workflow/agent-start', { - runId, seq: 0, label: 'bad', childId: SessionId('child'), - }) - }, /positive safe integer/], - ['non-integer member seq', (session, runId) => { - session.append('tool-workflow/agent-start', { - runId, seq: 1.5, label: 'bad', childId: SessionId('child'), - }) - }, /positive safe integer/], - ['non-string member label', (session, runId) => { - session.append('tool-workflow/agent-start', { - runId, seq: 1, label: 1 as never, childId: SessionId('child'), - }) - }, /label must be a string/], - ['non-string member phase', (session, runId) => { - session.append('tool-workflow/agent-start', { - runId, seq: 1, label: 'bad', phase: 1 as never, childId: SessionId('child'), - }) - }, /phase must be a string/], - ['empty child id', (session, runId) => { - session.append('tool-workflow/agent-start', { - runId, seq: 1, label: 'bad', childId: SessionId(''), - }) - }, /childId must be a non-empty string/], - ['duplicate member start', (session, runId) => { - session.append('tool-workflow/agent-start', { - runId, seq: 1, label: 'one', childId: SessionId('child'), - }) - session.append('tool-workflow/agent-start', { - runId, seq: 1, label: 'two', childId: SessionId('child-2'), - }) - }, /repeats member seq/], - ['invalid member outcome', (session, runId) => { - session.append('tool-workflow/agent-start', { - runId, seq: 1, label: 'one', childId: SessionId('child'), - }) - session.append('tool-workflow/agent-end', { runId, seq: 1, outcome: 'unknown' as never }) - }, /outcome unknown is invalid/], - ['duplicate member end', (session, runId) => { - session.append('tool-workflow/agent-start', { - runId, seq: 1, label: 'one', childId: SessionId('child'), - }) - session.append('tool-workflow/agent-end', { runId, seq: 1, outcome: 'completed' }) - session.append('tool-workflow/agent-end', { runId, seq: 1, outcome: 'completed' }) - }, /repeats member seq/], - ['run end with an open member', (session, runId) => { - session.append('tool-workflow/agent-start', { - runId, seq: 1, label: 'open', childId: SessionId('child'), - }) - session.append('tool-workflow/run-end', { runId, stopReason: 'completed' }) - }, /leaves member seq 1 open/], - ['invalid run stop reason', (session, runId) => { - session.append('tool-workflow/run-end', { runId, stopReason: 'unknown' as never }) - }, /stopReason unknown is invalid/], - ['event after run end', (session, runId) => { - session.append('tool-workflow/run-end', { runId, stopReason: 'completed' }) - session.append('tool-workflow/agent-start', { - runId, seq: 1, label: 'late', childId: SessionId('child'), - }) - }, /appears after/], - ['unknown workflow event', (session, runId) => { - appendRaw(session, 'tool-workflow/unknown', { runId }) - }, /unknown tool-workflow event type/], - ] - - it.each(invalidCases)('rejects %s', async (_name, mutate, pattern) => { - const ctx = await setup() - const session = ctx.sessions.create() - const runId = WorkflowRunId('run') - session.append('tool-workflow/run-start', { runId, name: 'run' }) - expect(() => { mutate(session, runId) }).toThrow(pattern) - }) - - it('validates existing cold history while allowing an unfinished prefix', async () => { - const ctx = new Context() - await ctx.plugin(SessionStore) - const valid = ctx.sessions.create(SessionId('workflow-record-cold-valid')) - valid.append('tool-workflow/run-start', { runId: WorkflowRunId('valid'), name: 'valid' }) - valid.append('tool-workflow/agent-start', { - runId: WorkflowRunId('valid'), seq: 1, label: 'open', childId: SessionId('child'), - }) - await ctx.plugin(InvariantRegistry, { enabled: true }) - await expect(ctx.plugin(ToolWorkflowInvariant)).resolves.toBeDefined() - - const brokenCtx = new Context() - await brokenCtx.plugin(SessionStore) - const broken = brokenCtx.sessions.create(SessionId('workflow-record-cold-invalid')) - broken.append('tool-workflow/run-start', { runId: WorkflowRunId('broken'), name: 'broken' }) - broken.append('tool-workflow/run-end', { runId: WorkflowRunId('broken'), stopReason: 'completed' }) - broken.append('tool-workflow/agent-start', { - runId: WorkflowRunId('broken'), seq: 1, label: 'late', childId: SessionId('late'), - }) - await brokenCtx.plugin(InvariantRegistry, { enabled: true }) - await expect(brokenCtx.plugin(ToolWorkflowInvariant)).rejects.toThrow(/appears after/) - }) -}) diff --git a/packages/workflow/tool-workflow/tests/tool-workflow.spec.ts b/packages/workflow/tool-workflow/tests/tool-workflow.spec.ts index 27b445894c..a0fbed76df 100644 --- a/packages/workflow/tool-workflow/tests/tool-workflow.spec.ts +++ b/packages/workflow/tool-workflow/tests/tool-workflow.spec.ts @@ -20,6 +20,7 @@ import SubagentRuntime from '@deepseek-ai/dsh-subagent' import PtcWorkflowEngine from '@deepseek-ai/dsh-workflow-ptc' import { mountWorkflowRuntime } from '../../workflow-ptc/tests/setup.ts' import * as toolWorkflow from '../src/index.ts' +import type {} from '../src/types.ts' import { Session, SessionId } from '@deepseek-ai/dsh-session' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' diff --git a/packages/workflow/tool-workflow/tsconfig.json b/packages/workflow/tool-workflow/tsconfig.json index a241acbd2b..89be1c3d3d 100644 --- a/packages/workflow/tool-workflow/tsconfig.json +++ b/packages/workflow/tool-workflow/tsconfig.json @@ -35,9 +35,6 @@ { "path": "../workflow" }, - { - "path": "../../runtime-diagnostics/invariants" - }, { "path": "../../jobs/jobs" } diff --git a/packages/workflow/workflow-ptc/README.i18n.yaml b/packages/workflow/workflow-ptc/README.i18n.yaml index b4149410cf..17f13a90c1 100644 --- a/packages/workflow/workflow-ptc/README.i18n.yaml +++ b/packages/workflow/workflow-ptc/README.i18n.yaml @@ -33,8 +33,8 @@ en: 9b3beda22fb408f7 zh: ac8701b4a7591b0e /deepseek-ai-dsh-workflow-ptc/understand-the-implementation/source-map: - en: 5196e336f2c9323c - zh: 6663f239334ee9fe + en: d94db278c7b67278 + zh: 054d0cf46a8f6f07 /deepseek-ai-dsh-workflow-ptc/understand-the-implementation/values-and-child-ownership: en: 4e866f00a49d76e7 zh: 15a02d9ccc33d08f diff --git a/packages/workflow/workflow-ptc/README.md b/packages/workflow/workflow-ptc/README.md index ddc160a272..956bb160ac 100644 --- a/packages/workflow/workflow-ptc/README.md +++ b/packages/workflow/workflow-ptc/README.md @@ -85,7 +85,6 @@ One self-contained guest program runs the existing VM and workflow helpers insid | [`src/runtime.ts`](src/runtime.ts) | VM evaluation, helper contracts and combinators | | [`src/realm.ts`](src/realm.ts) | Lossless-JSON materialization across VM realms | | [`src/meta.ts`](src/meta.ts) | Metadata validation and normalization | -| — | No runtime invariant companion is published; the workflow service owns event pairing and PTC owns managed-process observations. | ### Values and child ownership diff --git a/packages/workflow/workflow-ptc/README.zh.md b/packages/workflow/workflow-ptc/README.zh.md index 3b562bfe12..0ab8b6e9fe 100644 --- a/packages/workflow/workflow-ptc/README.zh.md +++ b/packages/workflow/workflow-ptc/README.zh.md @@ -85,7 +85,6 @@ Node PTC 提供方的 `maxPendingCalls` 也限制工作流并发:子 agent 启 | [`src/runtime.ts`](src/runtime.ts) | VM 求值、辅助函数约定与组合器 | | [`src/realm.ts`](src/realm.ts) | 跨 VM realm 的无损 JSON 物化 | | [`src/meta.ts`](src/meta.ts) | 元数据校验与规范化 | -| — | 不发布运行时不变式伴生入口;工作流服务负责事件配对,PTC 负责受管进程观测。 | ### 值与子 agent 归属 diff --git a/packages/workflow/workflow-ptc/package.json b/packages/workflow/workflow-ptc/package.json index 3c3e06f1db..c7090874fe 100644 --- a/packages/workflow/workflow-ptc/package.json +++ b/packages/workflow/workflow-ptc/package.json @@ -49,7 +49,6 @@ "@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-session-projection": "workspace:*", diff --git a/packages/workflow/workflow-ptc/src/guest-source.ts b/packages/workflow/workflow-ptc/src/guest-source.ts index ed380d0559..6174a32043 100644 --- a/packages/workflow/workflow-ptc/src/guest-source.ts +++ b/packages/workflow/workflow-ptc/src/guest-source.ts @@ -1,4 +1,4 @@ /** Generated by scripts/gen-workflow-guest.ts. Do not edit directly. */ /** Self-contained ESM guest transported into the mounted PTC runtime. */ -export const WORKFLOW_GUEST_SOURCE = "import * as vm from \"node:vm\";\n//#region packages/workflow/workflow-ptc/src/realm.ts\n/**\n* Materializes script VM values as plain JSON and renders thrown values.\n* Getters and proxy traps may execute inside the confined Node process;\n* process isolation and cancellation belong to PTC, not the VM.\n* @module @deepseek-ai/dsh-workflow-ptc/realm\n*/\n/** Thrown by {@link materializeFromRealm}; the caller wraps it into the right `WorkflowError` code. */\nvar MaterializeError = class extends Error {\n\tpath;\n\treason;\n\tconstructor(path, reason) {\n\t\tsuper(`${path}: ${reason}`);\n\t\tthis.path = path;\n\t\tthis.reason = reason;\n\t\tthis.name = \"MaterializeError\";\n\t}\n};\n/**\n* Render a thrown value to failure text without ever throwing: prefer the\n* `stack` (host or realm — a realm error's `stack` is a plain string read),\n* fall back to `message`, then `String()`. Reading those properties MAY run\n* script code; if that code itself throws, a fixed label is returned instead.\n* @param error - any value thrown in the host or guest realm.\n* @returns human-readable text for the failure report; prefers the stack.\n*/\nfunction renderThrown(error) {\n\ttry {\n\t\tconst stack = error?.stack;\n\t\tif (typeof stack === \"string\" && stack.length > 0) return stack;\n\t\tconst message = error?.message;\n\t\tif (typeof message === \"string\" && message.length > 0) return message;\n\t\treturn String(error);\n\t} catch {\n\t\treturn \"[unrenderable thrown value]\";\n\t}\n}\n/**\n* Whether an object's prototype chain represents a plain data object: `null`, or a prototype\n* whose own prototype is `null` (the realm's `Object.prototype` — which we\n* cannot compare by identity across realms). A `Date`/`Map`/class instance\n* has a longer chain and is rejected.\n*/\nfunction hasPlainPrototype(value) {\n\tconst proto = Object.getPrototypeOf(value);\n\tif (proto === null) return true;\n\treturn Object.getPrototypeOf(proto) === null;\n}\n/**\n* Copy `value` (typically from the vm realm) into plain host JSON data. Root `undefined` is\n* returned unchanged; nested `undefined` and values JSON cannot represent losslessly fail\n* with the offending path. Property accessors run normally, and a throwing read is wrapped\n* with its rendered failure.\n*\n* @param value - the realm value to materialize.\n* @param root - the path label for the root value (error messages).\n* @returns the host-realm copy (plain objects/arrays/scalars only).\n* @throws {@link MaterializeError} for unsupported values, cycles, sparse arrays, exotic\n* prototypes, or property reads that throw.\n*/\nfunction materializeFromRealm(value, root = \"value\") {\n\tif (value === void 0) return void 0;\n\ttry {\n\t\treturn materialize(value, root, /* @__PURE__ */ new Set());\n\t} catch (error) {\n\t\tif (error instanceof MaterializeError) throw error;\n\t\tthrow new MaterializeError(root, `reading the value threw: ${renderThrown(error)}`);\n\t}\n}\nfunction materialize(value, path, seen) {\n\tswitch (typeof value) {\n\t\tcase \"boolean\":\n\t\tcase \"string\": return value;\n\t\tcase \"number\":\n\t\t\tif (!Number.isFinite(value)) throw new MaterializeError(path, \"non-finite numbers are not JSON data\");\n\t\t\treturn value;\n\t\tcase \"bigint\": throw new MaterializeError(path, \"bigints are not JSON data\");\n\t\tcase \"function\": throw new MaterializeError(path, \"functions are not plain JSON data\");\n\t\tcase \"symbol\": throw new MaterializeError(path, \"symbols are not plain JSON data\");\n\t\tcase \"undefined\": throw new MaterializeError(path, \"undefined is not JSON data\");\n\t\tcase \"object\": break;\n\t}\n\tif (value === null) return null;\n\tconst objectValue = value;\n\tif (seen.has(objectValue)) throw new MaterializeError(path, \"circular references are not JSON data\");\n\tseen.add(objectValue);\n\ttry {\n\t\tif (Array.isArray(objectValue)) return materializeArray(objectValue, path, seen);\n\t\treturn materializeObject(objectValue, path, seen);\n\t} finally {\n\t\tseen.delete(objectValue);\n\t}\n}\nfunction materializeArray(value, path, seen) {\n\tconst out = [];\n\tfor (let index = 0; index < value.length; index++) {\n\t\tif (!(index in value)) throw new MaterializeError(`${path}[${index}]`, \"sparse arrays are not JSON data\");\n\t\tout.push(materialize(value[index], `${path}[${index}]`, seen));\n\t}\n\tfor (const key of Object.keys(value)) {\n\t\tconst index = Number(key);\n\t\tif (!Number.isInteger(index) || index < 0 || index >= value.length) throw new MaterializeError(`${path}.${key}`, \"arrays with non-index properties are not JSON data\");\n\t}\n\tif (Object.getOwnPropertySymbols(value).length > 0) throw new MaterializeError(path, \"symbol-keyed properties are not plain JSON data\");\n\treturn out;\n}\nfunction materializeObject(value, path, seen) {\n\tif (!hasPlainPrototype(value)) throw new MaterializeError(path, \"only plain objects and arrays are JSON data (exotic prototype)\");\n\tif (Object.getOwnPropertySymbols(value).length > 0) throw new MaterializeError(path, \"symbol-keyed properties are not plain JSON data\");\n\tconst out = {};\n\tfor (const key of Object.keys(value)) Object.defineProperty(out, key, {\n\t\tvalue: materialize(value[key], `${path}.${key}`, seen),\n\t\tenumerable: true,\n\t\twritable: true,\n\t\tconfigurable: true\n\t});\n\treturn out;\n}\n//#endregion\n//#region packages/util/brand/src/index.ts\n/**\n* Apply a compile-time string brand without changing the value.\n* @param value - string admitted by the domain that owns the target brand.\n* @returns the same string with the requested compile-time brand.\n*/\nfunction brandString(value) {\n\treturn value;\n}\n//#endregion\n//#region packages/llm/llm/src/error.ts\n/**\n* Harness error base with a stable machine-routable code and chained cause.\n* Package errors extend it so tool results and replay can retain failure class.\n* @module @deepseek-ai/dsh-llm/error\n*/\n/**\n* Base class for all harness errors. Carries a `code` (stable, programmatic —\n* e.g. `NO_ADAPTER`, `INVALID_ARGS`, `INVARIANT`) distinct from the\n* human-readable `message`, and supports `cause` chaining via the standard\n* `ErrorOptions`. `name` defaults to the subclass constructor name.\n*/\nvar HarnessError = class extends Error {\n\t/** Stable machine-routable failure class (e.g. `RATE_LIMIT`); route on this, never by parsing `message`. */\n\tcode;\n\tconstructor(message, code, options) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t\tthis.name = new.target.name;\n\t}\n};\nnew RegExp(String.raw`(?:^|[^a-z0-9])context[\\s_-](?:length|window)[\\s_-]` + String.raw`(?:exceed(?:ed|s)?|overflow(?:ed)?|limit[\\s_-]exceeded)(?:$|[^a-z0-9])`, \"i\");\nnew RegExp(String.raw`\\b(?:request|prompt|input|messages?)\\s+(?:is\\s+|are\\s+)?` + String.raw`too\\s+(?:large|long)\\s+for\\s+(?:(?:this|the)\\s+)?` + String.raw`(?:model(?:'s)?\\s+)?context(?:\\s+window)?\\b`, \"i\");\nnew RegExp(String.raw`\\b(?:input|prompt|request|messages?)\\b.{0,40}` + String.raw`\\b(?:exceed(?:s|ed)?|overflows?|is\\s+larger\\s+than)\\b.{0,40}` + String.raw`\\b(?:the\\s+)?(?:model(?:'s)?\\s+)?context(?:\\s+(?:length|window))?\\b`, \"i\");\n//#endregion\n//#region packages/util/values/src/index.ts\n/**\n* Mark an unreachable closed-union branch.\n* @param value - impossible value; an unhandled typed variant fails at the call site.\n* @param context - optional switch-site label included in the failure message.\n* @returns never; a runtime value that escaped its type always throws.\n*/\nfunction assertNever(value, context) {\n\tconst rendered = JSON.stringify(value) ?? String(value);\n\tthrow new Error(`unreachable variant${context ? ` in ${context}` : \"\"}: ${rendered}`);\n}\n/** Whether a realm-owned intrinsic prototype has a native constructor matching this engine's representation. */\nfunction hasIntrinsicConstructor$1(prototype, name) {\n\tconst constructor = Object.getOwnPropertyDescriptor(prototype, \"constructor\")?.value;\n\tif (typeof constructor !== \"function\") return false;\n\ttry {\n\t\treturn constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === Function.prototype.toString.call(name === \"Array\" ? Array : Object);\n\t} catch {\n\t\treturn false;\n\t}\n}\n/** Whether a candidate is one realm's intrinsic `Object.prototype`. */\nfunction isIntrinsicObjectPrototype$1(value) {\n\treturn Object.getPrototypeOf(value) === null && hasIntrinsicConstructor$1(value, \"Object\");\n}\n/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */\nfunction hasPlainArrayPrototype$1(value) {\n\tconst prototype = Object.getPrototypeOf(value);\n\tif (!Array.isArray(prototype) || !hasIntrinsicConstructor$1(prototype, \"Array\")) return false;\n\tconst objectPrototype = Object.getPrototypeOf(prototype);\n\treturn typeof objectPrototype === \"object\" && objectPrototype !== null && isIntrinsicObjectPrototype$1(objectPrototype);\n}\n/** Whether an object is a plain or null-prototype record from any JavaScript realm. */\nfunction hasPlainObjectPrototype(value) {\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === null || typeof prototype === \"object\" && isIntrinsicObjectPrototype$1(prototype);\n}\n/** Return every JSON-visible object key, or reject own data JSON would discard. */\nfunction enumerableStringKeys(value) {\n\tconst keys = Reflect.ownKeys(value);\n\tif (keys.some((key) => typeof key !== \"string\" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;\n\treturn keys;\n}\n/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */\nfunction walkJsonValue(value, detach) {\n\tconst ancestors = /* @__PURE__ */ new Set();\n\tlet root;\n\tconst assign = (destination, item) => {\n\t\tif (destination === void 0) return;\n\t\tif (destination.kind === \"root\") root = item;\n\t\telse if (destination.kind === \"array\") destination.target[destination.index] = item;\n\t\telse Object.defineProperty(destination.target, destination.key, {\n\t\t\tvalue: item,\n\t\t\tenumerable: true,\n\t\t\tconfigurable: true,\n\t\t\twritable: true\n\t\t});\n\t};\n\tconst tasks = [{\n\t\tkind: \"visit\",\n\t\tvalue,\n\t\t...detach ? { destination: { kind: \"root\" } } : {}\n\t}];\n\tfor (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {\n\t\tif (task.kind === \"leave\") {\n\t\t\tancestors.delete(task.source);\n\t\t\tcontinue;\n\t\t}\n\t\tif (task.kind === \"array-item\") {\n\t\t\tif (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;\n\t\t\ttasks.push({\n\t\t\t\tkind: \"visit\",\n\t\t\t\tvalue: task.source[task.index],\n\t\t\t\t...task.target === void 0 ? {} : { destination: {\n\t\t\t\t\tkind: \"array\",\n\t\t\t\t\ttarget: task.target,\n\t\t\t\t\tindex: task.index\n\t\t\t\t} }\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (task.kind === \"object-property\") {\n\t\t\ttasks.push({\n\t\t\t\tkind: \"visit\",\n\t\t\t\tvalue: task.source[task.key],\n\t\t\t\t...task.target === void 0 ? {} : { destination: {\n\t\t\t\t\tkind: \"object\",\n\t\t\t\t\ttarget: task.target,\n\t\t\t\t\tkey: task.key\n\t\t\t\t} }\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tconst current = task.value;\n\t\tif (current === null) {\n\t\t\tassign(task.destination, null);\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeof current === \"boolean\" || typeof current === \"string\") {\n\t\t\tassign(task.destination, current);\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeof current === \"number\") {\n\t\t\tif (!Number.isFinite(current) || Object.is(current, -0)) return void 0;\n\t\t\tassign(task.destination, current);\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeof current !== \"object\") return void 0;\n\t\tif (ancestors.has(current)) return void 0;\n\t\tif (Array.isArray(current)) {\n\t\t\tif (!hasPlainArrayPrototype$1(current)) return void 0;\n\t\t\tconst length = current.length;\n\t\t\tif (Reflect.ownKeys(current).length !== length + 1) return void 0;\n\t\t\tconst target = detach ? [] : void 0;\n\t\t\tif (target !== void 0) assign(task.destination, target);\n\t\t\tancestors.add(current);\n\t\t\ttasks.push({\n\t\t\t\tkind: \"leave\",\n\t\t\t\tsource: current\n\t\t\t});\n\t\t\tfor (let index = length - 1; index >= 0; index--) tasks.push({\n\t\t\t\tkind: \"array-item\",\n\t\t\t\tsource: current,\n\t\t\t\tindex,\n\t\t\t\t...target === void 0 ? {} : { target }\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (!hasPlainObjectPrototype(current)) return void 0;\n\t\tconst keys = enumerableStringKeys(current);\n\t\tif (keys === void 0) return void 0;\n\t\tconst target = detach ? {} : void 0;\n\t\tif (target !== void 0) assign(task.destination, target);\n\t\tancestors.add(current);\n\t\ttasks.push({\n\t\t\tkind: \"leave\",\n\t\t\tsource: current\n\t\t});\n\t\tfor (let index = keys.length - 1; index >= 0; index--) {\n\t\t\tconst key = keys[index];\n\t\t\t/* v8 ignore next -- the loop is bounded by the captured key count. */\n\t\t\tif (key === void 0) return void 0;\n\t\t\ttasks.push({\n\t\t\t\tkind: \"object-property\",\n\t\t\t\tsource: current,\n\t\t\t\tkey,\n\t\t\t\t...target === void 0 ? {} : { target }\n\t\t\t});\n\t\t}\n\t}\n\treturn detach ? root : true;\n}\n/**\n* Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.\n* @param value - candidate value to test.\n* @returns whether the value survives a JSON round trip without loss.\n*/\nfunction isJsonValue(value) {\n\treturn walkJsonValue(value, false) === true;\n}\n//#endregion\n//#region packages/core/tools/src/json-schema.ts\n/**\n* Enforced JSON Schema subset shared by tool outputs, generated PTC mode\n* types, subagents, and workflows. The subset accepts any JSON root, an\n* annotation-only schema for unconstrained JSON, one scalar `type`, object\n* `properties`/`required`/boolean `additionalProperties`, array `items`,\n* type-correct scalar `enum`/`const`, and exact-one `oneOf`.\n*\n* Unsupported or misplaced keywords reject rather than being accepted without\n* enforcement. Consumers that require an object root apply\n* {@link assertObjectJsonSchema} before accepting input.\n* @module dsh-tools/json-schema\n*/\n/**\n* Thrown when a raw schema falls outside the enforced subset. `violations`\n* lists every offending path instead of stopping at the first author error.\n*/\nvar JsonSchemaError = class extends HarnessError {\n\t/** Individual schema violations in walk order. */\n\tviolations;\n\tconstructor(violations) {\n\t\tsuper(`unsupported JSON schema: ${violations.join(\"; \")}`, \"UNSUPPORTED_SCHEMA\");\n\t\tthis.name = \"JsonSchemaError\";\n\t\tthis.violations = violations;\n\t}\n};\nconst CONSTRAINT_KEYWORDS = new Set([\n\t\"type\",\n\t\"oneOf\",\n\t\"properties\",\n\t\"required\",\n\t\"additionalProperties\",\n\t\"items\",\n\t\"enum\",\n\t\"const\"\n]);\nconst ANNOTATION_KEYWORDS = new Set([\n\t\"description\",\n\t\"title\",\n\t\"default\",\n\t\"examples\"\n]);\nconst SCHEMA_TYPES = [\n\t\"object\",\n\t\"array\",\n\t\"string\",\n\t\"number\",\n\t\"integer\",\n\t\"boolean\",\n\t\"null\"\n];\n/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */\nfunction hasIntrinsicConstructor(prototype, name) {\n\tconst constructor = Object.getOwnPropertyDescriptor(prototype, \"constructor\")?.value;\n\tif (typeof constructor !== \"function\") return false;\n\ttry {\n\t\treturn constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;\n\t} catch {\n\t\treturn false;\n\t}\n}\n/** Whether a candidate is one realm's intrinsic `Object.prototype`. */\nfunction isIntrinsicObjectPrototype(value) {\n\treturn Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, \"Object\");\n}\n/**\n* Test for a realm-agnostic plain JSON record without accepting arrays or\n* exotic objects.\n* @param value - candidate record from any JavaScript realm.\n* @returns Whether the value has a plain-object prototype chain.\n*/\nfunction isPlainJsonRecord(value) {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n\ttry {\n\t\tconst prototype = Object.getPrototypeOf(value);\n\t\treturn prototype === null || typeof prototype === \"object\" && isIntrinsicObjectPrototype(prototype);\n\t} catch {\n\t\treturn false;\n\t}\n}\n/** Whether an array uses one realm's intrinsic `Array.prototype`. */\nfunction hasPlainArrayPrototype(value) {\n\tconst prototype = Object.getPrototypeOf(value);\n\tif (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, \"Array\")) return false;\n\tconst objectPrototype = Object.getPrototypeOf(prototype);\n\treturn typeof objectPrototype === \"object\" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);\n}\n/** Return whether a record contains only own enumerable string keys. */\nfunction hasOnlyEnumerableStringKeys(value) {\n\ttry {\n\t\treturn Reflect.ownKeys(value).every((key) => typeof key === \"string\" && Object.prototype.propertyIsEnumerable.call(value, key));\n\t} catch {\n\t\treturn false;\n\t}\n}\n/**\n* Test for an ordinary schema record whose keys survive JSON projection.\n* @param value - candidate record from any JavaScript realm.\n* @returns Whether the record has an intrinsic prototype and only own enumerable string keys.\n*/\nfunction isJsonSchemaRecord(value) {\n\treturn isPlainJsonRecord(value) && hasOnlyEnumerableStringKeys(value);\n}\n/**\n* Test for a dense ordinary array with no JSON-invisible decorations.\n* @param value - candidate array from any JavaScript realm.\n* @returns Whether the array is intrinsic, dense, and undecorated.\n*/\nfunction isPlainJsonArray(value) {\n\tif (!Array.isArray(value)) return false;\n\ttry {\n\t\tif (!hasPlainArrayPrototype(value) || Reflect.ownKeys(value).length !== value.length + 1) return false;\n\t\tfor (let index = 0; index < value.length; index++) if (!Object.hasOwn(value, index)) return false;\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n/** Lossless finite JSON number, excluding negative zero. */\nfunction isJsonNumber(value) {\n\treturn typeof value === \"number\" && Number.isFinite(value) && !Object.is(value, -0);\n}\n/** Whether a scalar is valid for one declared schema type. */\nfunction scalarMatches(type, value) {\n\tswitch (type) {\n\t\tcase \"string\": return typeof value === \"string\";\n\t\tcase \"number\": return isJsonNumber(value);\n\t\tcase \"integer\": return isJsonNumber(value) && Number.isInteger(value);\n\t\tcase \"boolean\": return typeof value === \"boolean\";\n\t\tcase \"null\": return value === null;\n\t\t/* v8 ignore next -- JsonSchemaScalarType is closed; this retains compile-time exhaustiveness. */\n\t\tdefault: return assertNever(type, \"JsonSchemaType\");\n\t}\n}\n/** Keywords that are invalid beside `oneOf`. */\nconst ONE_OF_SIBLING_KEYWORDS = [\n\t\"properties\",\n\t\"required\",\n\t\"additionalProperties\",\n\t\"items\",\n\t\"enum\",\n\t\"const\"\n];\n/** Validate object-only fields after its property schemas have been visited. */\nfunction checkObjectSchemaTail(node, path, properties, violations) {\n\tconst hasRequired = Object.hasOwn(node, \"required\");\n\tconst required = hasRequired ? node.required : void 0;\n\tif (hasRequired) if (!isPlainJsonArray(required) || required.some((entry) => typeof entry !== \"string\")) violations.push(`${path}.required must be an array of strings`);\n\telse {\n\t\tconst declared = isJsonSchemaRecord(properties) ? properties : {};\n\t\tfor (const key of required) if (!Object.hasOwn(declared, key)) violations.push(`${path}.required names \"${key}\" which is not in properties`);\n\t}\n\tif (Object.hasOwn(node, \"additionalProperties\") && typeof node.additionalProperties !== \"boolean\") violations.push(`${path}.additionalProperties must be a boolean`);\n}\n/** Collect every violation for one raw schema tree without using the JavaScript call stack. */\nfunction checkSchemaNode(root, rootPath, violations, seen) {\n\tconst tasks = [{\n\t\tkind: \"enter\",\n\t\tnode: root,\n\t\tpath: rootPath\n\t}];\n\tfor (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {\n\t\tif (task.kind === \"leave\") {\n\t\t\tseen.delete(task.node);\n\t\t\tcontinue;\n\t\t}\n\t\tif (task.kind === \"one-of-tail\") {\n\t\t\tfor (const key of ONE_OF_SIBLING_KEYWORDS) if (Object.hasOwn(task.node, key)) violations.push(`${task.path}.${key} is not supported beside oneOf`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (task.kind === \"object-tail\") {\n\t\t\tcheckObjectSchemaTail(task.node, task.path, task.properties, violations);\n\t\t\tcontinue;\n\t\t}\n\t\tconst { node, path } = task;\n\t\tif (!isJsonSchemaRecord(node)) {\n\t\t\tviolations.push(`${path} must be a schema object`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (seen.has(node)) {\n\t\t\tviolations.push(`${path} is circular`);\n\t\t\tcontinue;\n\t\t}\n\t\tseen.add(node);\n\t\ttasks.push({\n\t\t\tkind: \"leave\",\n\t\t\tnode\n\t\t});\n\t\tfor (const key of Object.keys(node)) {\n\t\t\tif (CONSTRAINT_KEYWORDS.has(key)) continue;\n\t\t\tif (ANNOTATION_KEYWORDS.has(key)) {\n\t\t\t\ttry {\n\t\t\t\t\tif (!isJsonValue(node[key])) violations.push(`${path}.${key} annotation must be lossless JSON data`);\n\t\t\t\t} catch {\n\t\t\t\t\tviolations.push(`${path}.${key} annotation must be lossless JSON data`);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tviolations.push(`${path}.${key} is not a supported keyword (subset: type/oneOf/properties/required/additionalProperties/items/enum/const + annotations)`);\n\t\t}\n\t\tif (Object.hasOwn(node, \"description\") && typeof node.description !== \"string\") violations.push(`${path}.description must be a string`);\n\t\tif (Object.hasOwn(node, \"title\") && typeof node.title !== \"string\") violations.push(`${path}.title must be a string`);\n\t\tconst hasType = Object.hasOwn(node, \"type\");\n\t\tconst hasOneOf = Object.hasOwn(node, \"oneOf\");\n\t\tif (hasType && hasOneOf) {\n\t\t\tviolations.push(`${path} cannot declare both type and oneOf`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!hasType && !hasOneOf) {\n\t\t\tfor (const key of ONE_OF_SIBLING_KEYWORDS) if (Object.hasOwn(node, key)) violations.push(`${path}.${key} requires type or oneOf`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (hasOneOf) {\n\t\t\tconst oneOf = node.oneOf;\n\t\t\ttasks.push({\n\t\t\t\tkind: \"one-of-tail\",\n\t\t\t\tnode,\n\t\t\t\tpath\n\t\t\t});\n\t\t\tif (!isPlainJsonArray(oneOf) || oneOf.length < 2) violations.push(`${path}.oneOf must be an array of at least two schemas`);\n\t\t\telse for (let index = oneOf.length - 1; index >= 0; index--) tasks.push({\n\t\t\t\tkind: \"enter\",\n\t\t\t\tnode: oneOf[index],\n\t\t\t\tpath: `${path}.oneOf[${index}]`\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tconst type = node.type;\n\t\tif (typeof type !== \"string\" || !SCHEMA_TYPES.includes(type)) {\n\t\t\tviolations.push(Array.isArray(type) ? `${path}.type must be a single type string (type arrays are not supported)` : `${path}.type must be one of ${SCHEMA_TYPES.join(\"/\")}`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst schemaType = type;\n\t\tfor (const [key, types] of Object.entries({\n\t\t\tproperties: [\"object\"],\n\t\t\trequired: [\"object\"],\n\t\t\tadditionalProperties: [\"object\"],\n\t\t\titems: [\"array\"],\n\t\t\tenum: [\n\t\t\t\t\"string\",\n\t\t\t\t\"number\",\n\t\t\t\t\"integer\",\n\t\t\t\t\"boolean\",\n\t\t\t\t\"null\"\n\t\t\t],\n\t\t\tconst: [\n\t\t\t\t\"string\",\n\t\t\t\t\"number\",\n\t\t\t\t\"integer\",\n\t\t\t\t\"boolean\",\n\t\t\t\t\"null\"\n\t\t\t]\n\t\t})) if (Object.hasOwn(node, key) && !types.includes(schemaType)) violations.push(`${path}.${key} is not supported on type \"${schemaType}\"`);\n\t\tswitch (schemaType) {\n\t\t\tcase \"object\": {\n\t\t\t\tconst properties = Object.hasOwn(node, \"properties\") ? node.properties : void 0;\n\t\t\t\ttasks.push({\n\t\t\t\t\tkind: \"object-tail\",\n\t\t\t\t\tnode,\n\t\t\t\t\tpath,\n\t\t\t\t\tproperties\n\t\t\t\t});\n\t\t\t\tif (Object.hasOwn(node, \"properties\")) if (!isJsonSchemaRecord(properties)) violations.push(`${path}.properties must be an object of schemas`);\n\t\t\t\telse {\n\t\t\t\t\tconst entries = Object.entries(properties);\n\t\t\t\t\tfor (let index = entries.length - 1; index >= 0; index--) {\n\t\t\t\t\t\tconst entry = entries[index];\n\t\t\t\t\t\t/* v8 ignore next -- the loop is bounded by the captured entry count. */\n\t\t\t\t\t\tif (entry === void 0) continue;\n\t\t\t\t\t\ttasks.push({\n\t\t\t\t\t\t\tkind: \"enter\",\n\t\t\t\t\t\t\tnode: entry[1],\n\t\t\t\t\t\t\tpath: `${path}.properties.${entry[0]}`\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"array\":\n\t\t\t\tif (Object.hasOwn(node, \"items\")) tasks.push({\n\t\t\t\t\tkind: \"enter\",\n\t\t\t\t\tnode: node.items,\n\t\t\t\t\tpath: `${path}.items`\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\tcase \"string\":\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\tcase \"boolean\":\n\t\t\tcase \"null\": {\n\t\t\t\tconst hasEnum = Object.hasOwn(node, \"enum\");\n\t\t\t\tconst allowed = hasEnum ? node.enum : void 0;\n\t\t\t\tconst enumValid = isPlainJsonArray(allowed) && allowed.length > 0 && allowed.every((entry) => scalarMatches(schemaType, entry));\n\t\t\t\tif (hasEnum && !enumValid) violations.push(`${path}.enum must be a non-empty array of ${schemaType} values`);\n\t\t\t\tconst hasConst = Object.hasOwn(node, \"const\");\n\t\t\t\tconst declaredConst = hasConst ? node.const : void 0;\n\t\t\t\tconst constValid = scalarMatches(schemaType, declaredConst);\n\t\t\t\tif (hasConst) {\n\t\t\t\t\tif (!constValid) violations.push(`${path}.const must be a ${schemaType} value`);\n\t\t\t\t\telse if (enumValid && !allowed.includes(declaredConst)) violations.push(`${path}.const must be one of ${path}.enum when both are declared`);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t/* v8 ignore next -- schemaType was narrowed from the closed SCHEMA_TYPES table above. */\n\t\t\tdefault: assertNever(schemaType, \"JsonSchemaType\");\n\t\t}\n\t}\n}\n/**\n* Assert the enforced subset plus the object-root constraint retained by\n* subagent and workflow structured outputs.\n* @param schema - untrusted caller-supplied schema.\n* @returns Assertion that the schema belongs to the supported subset and has an object root.\n*/\nfunction assertObjectJsonSchema(schema) {\n\tconst violations = [];\n\tcheckSchemaNode(schema, \"schema\", violations, /* @__PURE__ */ new Set());\n\tif (violations.length === 0 && (!isJsonSchemaRecord(schema) || !Object.hasOwn(schema, \"type\") || schema.type !== \"object\")) violations.push(\"schema.type must be \\\"object\\\" (structured output is object-rooted)\");\n\tif (violations.length > 0) throw new JsonSchemaError(violations);\n}\n//#endregion\n//#region packages/workflow/workflow/src/index.ts\n/**\n* Typed error for workflow-seam failures. Extends {@link HarnessError}, so the\n* `code` is machine-routable taxonomy. `fatal` drives the combinator\n* discipline: `parallel()`/`pipeline()` re-throw a fatal error (a typo'd\n* option or a tripped cap must kill the script loudly), and reserve the\n* per-item `null` for child-run failures and ordinary in-stage script errors.\n* Every {@link WorkflowErrorCode} is fatal; the flag exists so the\n* distinction is explicit at every catch site rather than implied.\n*/\nvar WorkflowError = class extends HarnessError {\n\t/** Whether combinators must propagate this error instead of nulling the item. */\n\tfatal;\n\tconstructor(message, code, options) {\n\t\tsuper(message, code, options);\n\t\tthis.name = \"WorkflowError\";\n\t\tthis.fatal = options?.fatal ?? true;\n\t}\n};\n/**\n* Whether combinators must re-throw `error` instead of mapping the item to `null`.\n* @param error - any thrown value; fatality is host `instanceof` (unforgeable from a script realm).\n* @returns true iff `error` is a {@link WorkflowError} whose `fatal` flag is set.\n*/\nfunction isFatalWorkflowError(error) {\n\treturn error instanceof WorkflowError && error.fatal;\n}\n//#endregion\n//#region packages/workflow/workflow-ptc/src/runtime.ts\n/**\n* Workflow VM hooks, child callbacks, ordinary concurrency limits and result serialization.\n* PTC owns process confinement and cancellation. Fatal hook and provider failures propagate\n* through combinators; ordinary child failures and stage errors become per-item nulls.\n* @module @deepseek-ai/dsh-workflow-ptc/runtime\n*/\n/** The `agent()` options the script may pass; everything else rejects loud. */\nconst SUPPORTED_AGENT_OPTIONS = new Set([\n\t\"label\",\n\t\"phase\",\n\t\"schema\",\n\t\"provider\",\n\t\"model\"\n]);\n/** Deferred Claude Code options we name explicitly in the rejection message. */\nconst DEFERRED_AGENT_OPTIONS = new Set([\n\t\"effort\",\n\t\"isolation\",\n\t\"agentType\"\n]);\n/** Flatten a child's final output blocks to text (the non-schema `agent()` result). */\nfunction outputText(blocks) {\n\treturn blocks.filter((block) => block.type === \"text\").map((block) => block.text).join(\"\");\n}\n/** A short display label derived from the prompt when the script passes none. */\nfunction defaultLabel(prompt) {\n\tconst newline = prompt.indexOf(\"\\n\");\n\tconst line = newline === -1 ? prompt : prompt.slice(0, newline);\n\treturn line.length <= 48 ? line : `${line.slice(0, 47)}…`;\n}\n/**\n* One script execution inside the confined Node process. The host owns\n* cancellation and cleanup of any dropped child work.\n*/\nvar WorkflowExecution = class {\n\tlimits;\n\tobserver;\n\tchildren;\n\t/** 1-based count of `agent()` calls started (the `agentsStarted` result field). */\n\tstarted = 0;\n\tactiveSlots = 0;\n\tslotWaiters = [];\n\tcurrentPhase;\n\tcontext;\n\tcompiled;\n\tconstructor(meta, body, args, limits, observer, children) {\n\t\tthis.limits = limits;\n\t\tthis.observer = observer;\n\t\tthis.children = children;\n\t\ttry {\n\t\t\tthis.compiled = new vm.Script(`(async () => {\\n${body}\\n})()`, {\n\t\t\t\tfilename: `workflow:${meta.name}`,\n\t\t\t\tlineOffset: -1\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow new WorkflowError(`workflow script does not parse: ${String(error)}`, \"SCRIPT_PARSE\", { cause: error });\n\t\t}\n\t\tthis.context = vm.createContext({}, { name: `workflow:${meta.name}` });\n\t\tconst globals = {\n\t\t\tagent: (prompt, opts) => this.contain(this.agent(prompt, opts)),\n\t\t\tparallel: (thunks) => this.contain(this.parallel(thunks)),\n\t\t\tpipeline: (items, ...stages) => this.contain(this.pipeline(items, stages)),\n\t\t\tphase: (title) => {\n\t\t\t\tthis.phase(title);\n\t\t\t},\n\t\t\tlog: (message) => {\n\t\t\t\tthis.log(message);\n\t\t\t},\n\t\t\targs\n\t\t};\n\t\tfor (const [key, value] of Object.entries(globals)) this.context[key] = typeof value === \"function\" ? Object.freeze(value) : value;\n\t}\n\t/**\n\t* Run the script and materialize its JSON return value.\n\t* @returns A completed or error result; script failures never reject.\n\t*/\n\tasync drive() {\n\t\ttry {\n\t\t\tconst scriptPromise = this.compiled.runInContext(this.context, { timeout: this.limits.syncTimeoutMs });\n\t\t\tconst raw = await this.contain(Promise.resolve(scriptPromise));\n\t\t\treturn {\n\t\t\t\tvalue: raw === void 0 ? null : this.materializeResult(raw),\n\t\t\t\tstopReason: \"completed\",\n\t\t\t\tagentsStarted: this.started\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tvalue: null,\n\t\t\t\tstopReason: \"error\",\n\t\t\t\terror: renderThrown(error),\n\t\t\t\tagentsStarted: this.started\n\t\t\t};\n\t\t}\n\t}\n\t/**\n\t* Attach a no-op rejection consumer WITHOUT changing what the caller\n\t* receives: if the script drops the promise, a host rejection cannot become\n\t* an unhandled rejection that kills the process; if\n\t* the script does await it, it still observes the rejection.\n\t*/\n\tcontain(promise) {\n\t\tpromise.catch(() => {});\n\t\treturn promise;\n\t}\n\t/** Materialize the script's return value; violations become RESULT_UNSERIALIZABLE. */\n\tmaterializeResult(raw) {\n\t\ttry {\n\t\t\treturn materializeFromRealm(raw, \"workflow result\");\n\t\t} catch (error) {\n\t\t\t/* v8 ignore next -- defensive rethrow arm: materializeFromRealm only throws MaterializeError */\n\t\t\tif (!(error instanceof MaterializeError)) throw error;\n\t\t\tthrow new WorkflowError(`the workflow's return value is not plain JSON data — ${error.message}. Return only JSON-serializable objects/arrays/scalars.`, \"RESULT_UNSERIALIZABLE\", { cause: error });\n\t\t}\n\t}\n\t/** Acquire one concurrency slot in FIFO order. */\n\tacquireSlot() {\n\t\tif (this.activeSlots < this.limits.maxConcurrentAgents) {\n\t\t\tthis.activeSlots += 1;\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.slotWaiters.push(() => {\n\t\t\t\tthis.activeSlots += 1;\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n\treleaseSlot() {\n\t\tthis.activeSlots -= 1;\n\t\tconst next = this.slotWaiters.shift();\n\t\tif (next) next();\n\t}\n\t/** The `agent(prompt, opts)` hook. */\n\tasync agent(rawPrompt, rawOpts) {\n\t\tif (typeof rawPrompt !== \"string\" || rawPrompt.length === 0) throw new WorkflowError(\"agent() requires a non-empty prompt string\", \"INVALID_ARGUMENT\");\n\t\tconst opts = this.readAgentOptions(rawOpts);\n\t\tif (this.started >= this.limits.maxTotalAgents) throw new WorkflowError(`this run reached its total agent cap (${this.limits.maxTotalAgents}) — a runaway-loop backstop; raise the applicable maxTotalAgents limit if the scale is intentional`, \"AGENT_CAP\");\n\t\tthis.started += 1;\n\t\tconst seq = this.started;\n\t\tconst label = opts.label ?? defaultLabel(rawPrompt);\n\t\tconst phase = opts.phase ?? this.currentPhase;\n\t\tawait this.acquireSlot();\n\t\ttry {\n\t\t\tlet run;\n\t\t\ttry {\n\t\t\t\trun = await this.children.startAgent({\n\t\t\t\t\tprompt: rawPrompt,\n\t\t\t\t\t...opts.schema !== void 0 ? { schema: opts.schema } : {},\n\t\t\t\t\t...opts.provider !== void 0 ? { provider: opts.provider } : {},\n\t\t\t\t\t...opts.model !== void 0 ? { model: opts.model } : {}\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tthrow new WorkflowError(`agent() could not start a child: ${renderThrown(error)}`, \"AGENT_START\", { cause: error });\n\t\t\t}\n\t\t\tconst info = {\n\t\t\t\tseq,\n\t\t\t\tlabel,\n\t\t\t\t...phase !== void 0 ? { phase } : {},\n\t\t\t\tchildId: brandString(run.id)\n\t\t\t};\n\t\t\tthis.observer.agentStart(info);\n\t\t\ttry {\n\t\t\t\tlet result;\n\t\t\t\ttry {\n\t\t\t\t\tresult = await run.result;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.observer.agentEnd({\n\t\t\t\t\t\t...info,\n\t\t\t\t\t\toutcome: \"failed\"\n\t\t\t\t\t});\n\t\t\t\t\tthrow new WorkflowError(`child agent run failed: ${renderThrown(error)}`, \"AGENT_RESULT\", { cause: error });\n\t\t\t\t}\n\t\t\t\tif (result.stopReason === \"completed\") {\n\t\t\t\t\tif (opts.schema !== void 0) {\n\t\t\t\t\t\tif (result.structured === void 0) {\n\t\t\t\t\t\t\tthis.observer.agentEnd({\n\t\t\t\t\t\t\t\t...info,\n\t\t\t\t\t\t\t\toutcome: \"failed\"\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.observer.agentEnd({\n\t\t\t\t\t\t\t...info,\n\t\t\t\t\t\t\toutcome: \"completed\"\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn result.structured;\n\t\t\t\t\t}\n\t\t\t\t\tthis.observer.agentEnd({\n\t\t\t\t\t\t...info,\n\t\t\t\t\t\toutcome: \"completed\"\n\t\t\t\t\t});\n\t\t\t\t\treturn outputText(result.output);\n\t\t\t\t}\n\t\t\t\tthis.observer.agentEnd({\n\t\t\t\t\t...info,\n\t\t\t\t\toutcome: \"failed\"\n\t\t\t\t});\n\t\t\t\treturn null;\n\t\t\t} finally {\n\t\t\t\tawait run.dispose();\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.releaseSlot();\n\t\t}\n\t}\n\t/** Materialize + validate the `agent()` options bag from the realm. */\n\treadAgentOptions(rawOpts) {\n\t\tif (rawOpts === void 0) return {};\n\t\tlet opts;\n\t\ttry {\n\t\t\topts = materializeFromRealm(rawOpts, \"agent() options\");\n\t\t} catch (error) {\n\t\t\t/* v8 ignore next -- defensive rethrow arm: materializeFromRealm only throws MaterializeError */\n\t\t\tif (!(error instanceof MaterializeError)) throw error;\n\t\t\tthrow new WorkflowError(`agent() options must be plain JSON data — ${error.message}`, \"INVALID_ARGUMENT\", { cause: error });\n\t\t}\n\t\tif (typeof opts !== \"object\" || opts === null || Array.isArray(opts)) throw new WorkflowError(\"agent() options must be an object\", \"INVALID_ARGUMENT\");\n\t\tconst record = opts;\n\t\tfor (const key of Object.keys(record)) {\n\t\t\tif (SUPPORTED_AGENT_OPTIONS.has(key)) continue;\n\t\t\tif (DEFERRED_AGENT_OPTIONS.has(key)) throw new WorkflowError(`agent() option \"${key}\" is deferred and not supported by this engine (supported: label, phase, schema, provider, model)`, \"UNSUPPORTED_OPTION\");\n\t\t\tthrow new WorkflowError(`agent() option \"${key}\" is not recognized (supported: label, phase, schema, provider, model)`, \"UNSUPPORTED_OPTION\");\n\t\t}\n\t\tfor (const key of [\n\t\t\t\"label\",\n\t\t\t\"phase\",\n\t\t\t\"provider\",\n\t\t\t\"model\"\n\t\t]) if (record[key] !== void 0 && typeof record[key] !== \"string\") throw new WorkflowError(`agent() option \"${key}\" must be a string`, \"INVALID_ARGUMENT\");\n\t\tlet schema;\n\t\tif (record.schema !== void 0) try {\n\t\t\tassertObjectJsonSchema(record.schema);\n\t\t\tschema = record.schema;\n\t\t} catch (error) {\n\t\t\t/* v8 ignore next -- defensive rethrow arm: assertObjectJsonSchema only throws JsonSchemaError */\n\t\t\tif (!(error instanceof JsonSchemaError)) throw error;\n\t\t\tthrow new WorkflowError(`agent() schema is outside the supported subset — ${error.message}`, \"UNSUPPORTED_SCHEMA\", { cause: error });\n\t\t}\n\t\treturn {\n\t\t\t...record.label !== void 0 ? { label: record.label } : {},\n\t\t\t...record.phase !== void 0 ? { phase: record.phase } : {},\n\t\t\t...record.provider !== void 0 ? { provider: record.provider } : {},\n\t\t\t...record.model !== void 0 ? { model: record.model } : {},\n\t\t\t...schema !== void 0 ? { schema } : {}\n\t\t};\n\t}\n\t/** The `parallel(thunks)` hook: each thunk caught → `null`; fatal errors propagate. */\n\tasync parallel(rawThunks) {\n\t\tif (!Array.isArray(rawThunks)) throw new WorkflowError(\"parallel() requires an array of zero-argument functions\", \"INVALID_ARGUMENT\");\n\t\tthis.assertItemCap(rawThunks.length, \"parallel()\");\n\t\tconst thunks = rawThunks.map((thunk, index) => {\n\t\t\tif (typeof thunk !== \"function\") throw new WorkflowError(`parallel() item ${index} is not a function`, \"INVALID_ARGUMENT\");\n\t\t\treturn thunk;\n\t\t});\n\t\treturn Promise.all(thunks.map(async (thunk) => {\n\t\t\ttry {\n\t\t\t\treturn await thunk();\n\t\t\t} catch (error) {\n\t\t\t\tif (isFatalWorkflowError(error)) throw error;\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}));\n\t}\n\t/** The `pipeline(items, ...stages)` hook: per-item stage chains, NO cross-stage barrier. */\n\tasync pipeline(rawItems, rawStages) {\n\t\tif (!Array.isArray(rawItems)) throw new WorkflowError(\"pipeline() requires an items array\", \"INVALID_ARGUMENT\");\n\t\tthis.assertItemCap(rawItems.length, \"pipeline()\");\n\t\tif (rawStages.length === 0) throw new WorkflowError(\"pipeline() requires at least one stage function\", \"INVALID_ARGUMENT\");\n\t\tconst stages = rawStages.map((stage, index) => {\n\t\t\tif (typeof stage !== \"function\") throw new WorkflowError(`pipeline() stage ${index} is not a function`, \"INVALID_ARGUMENT\");\n\t\t\treturn stage;\n\t\t});\n\t\treturn Promise.all(rawItems.map(async (item, index) => {\n\t\t\tlet value = item;\n\t\t\ttry {\n\t\t\t\tfor (const stage of stages) value = await stage(value, item, index);\n\t\t\t\treturn value;\n\t\t\t} catch (error) {\n\t\t\t\tif (isFatalWorkflowError(error)) throw error;\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}));\n\t}\n\tassertItemCap(length, hook) {\n\t\tif (length > this.limits.maxItemsPerCall) throw new WorkflowError(`${hook} received ${length} items — over the per-call cap (${this.limits.maxItemsPerCall}); split the work or raise maxItemsPerCall in the engine config`, \"ITEM_CAP\");\n\t}\n\t/** The `phase(title)` hook: sets the current label for subsequent `agent()` calls and notifies observers. */\n\tphase(title) {\n\t\tif (typeof title !== \"string\" || title.length === 0) throw new WorkflowError(\"phase() requires a non-empty title string\", \"INVALID_ARGUMENT\");\n\t\tthis.currentPhase = title;\n\t\tthis.observer.phase(title);\n\t}\n\t/** The `log(message)` hook: narration to observers. */\n\tlog(message) {\n\t\tif (typeof message !== \"string\") throw new WorkflowError(\"log() requires a message string\", \"INVALID_ARGUMENT\");\n\t\tthis.observer.log(message);\n\t}\n};\n//#endregion\n//#region packages/workflow/workflow-ptc/src/guest.ts\n/**\n* Run a workflow with one progress batch in flight. Drain progress before child\n* disposal and the terminal result; PTC and the host own cancellation and cleanup.\n* @param host - JSON callbacks owned by this workflow run.\n* @returns The script result after progress delivery; initialization failures reject.\n*/\nasync function runWorkflowGuest(host) {\n\tconst init = await host.begin({});\n\tlet queued = [];\n\tlet inFlight;\n\tlet progressError;\n\tconst flush = () => {\n\t\tif (inFlight !== void 0 || queued.length === 0) return;\n\t\tconst batch = queued;\n\t\tqueued = [];\n\t\tinFlight = host.progress(batch).then(() => {\n\t\t\tinFlight = void 0;\n\t\t\tflush();\n\t\t}, (error) => {\n\t\t\tprogressError = renderThrown(error);\n\t\t\tqueued = [];\n\t\t\tinFlight = void 0;\n\t\t});\n\t};\n\tconst send = (event) => {\n\t\tif (progressError !== void 0) return;\n\t\tqueued.push(event);\n\t\tflush();\n\t};\n\tconst drain = async () => {\n\t\twhile (inFlight !== void 0) await inFlight;\n\t};\n\tconst result = await new WorkflowExecution(init.meta, init.body, init.args, init.limits, {\n\t\tphase: (title) => {\n\t\t\tsend({\n\t\t\t\ttype: \"phase\",\n\t\t\t\ttitle\n\t\t\t});\n\t\t},\n\t\tlog: (message) => {\n\t\t\tsend({\n\t\t\t\ttype: \"log\",\n\t\t\t\tmessage\n\t\t\t});\n\t\t},\n\t\tagentStart: (info) => {\n\t\t\tsend({\n\t\t\t\ttype: \"agent-start\",\n\t\t\t\tinfo\n\t\t\t});\n\t\t},\n\t\tagentEnd: (info) => {\n\t\t\tsend({\n\t\t\t\ttype: \"agent-end\",\n\t\t\t\tinfo\n\t\t\t});\n\t\t}\n\t}, { async startAgent(request) {\n\t\tconst { callId, childId } = await host.startChild(request);\n\t\tconst result = host.childResult({ callId });\n\t\tresult.catch(() => {});\n\t\treturn {\n\t\t\tid: childId,\n\t\t\tresult,\n\t\t\tasync dispose() {\n\t\t\t\tawait drain();\n\t\t\t\tawait host.disposeChild({ callId });\n\t\t\t}\n\t\t};\n\t} }).drive();\n\tawait drain();\n\treturn progressError === void 0 ? result : {\n\t\tvalue: null,\n\t\tstopReason: \"error\",\n\t\terror: progressError,\n\t\tagentsStarted: result.agentsStarted\n\t};\n}\n//#endregion\nexport { runWorkflowGuest };\n//# sourceURL=dsh-workflow-guest.js\n" +export const WORKFLOW_GUEST_SOURCE = "import * as vm from \"node:vm\";\n//#region packages/workflow/workflow-ptc/src/realm.ts\n/**\n* Materializes script VM values as plain JSON and renders thrown values.\n* Getters and proxy traps may execute inside the confined Node process;\n* process isolation and cancellation belong to PTC, not the VM.\n* @module @deepseek-ai/dsh-workflow-ptc/realm\n*/\n/** Thrown by {@link materializeFromRealm}; the caller wraps it into the right `WorkflowError` code. */\nvar MaterializeError = class extends Error {\n\tpath;\n\treason;\n\tconstructor(path, reason) {\n\t\tsuper(`${path}: ${reason}`);\n\t\tthis.path = path;\n\t\tthis.reason = reason;\n\t\tthis.name = \"MaterializeError\";\n\t}\n};\n/**\n* Render a thrown value to failure text without ever throwing: prefer the\n* `stack` (host or realm — a realm error's `stack` is a plain string read),\n* fall back to `message`, then `String()`. Reading those properties MAY run\n* script code; if that code itself throws, a fixed label is returned instead.\n* @param error - any value thrown in the host or guest realm.\n* @returns human-readable text for the failure report; prefers the stack.\n*/\nfunction renderThrown(error) {\n\ttry {\n\t\tconst stack = error?.stack;\n\t\tif (typeof stack === \"string\" && stack.length > 0) return stack;\n\t\tconst message = error?.message;\n\t\tif (typeof message === \"string\" && message.length > 0) return message;\n\t\treturn String(error);\n\t} catch {\n\t\treturn \"[unrenderable thrown value]\";\n\t}\n}\n/**\n* Whether an object's prototype chain represents a plain data object: `null`, or a prototype\n* whose own prototype is `null` (the realm's `Object.prototype` — which we\n* cannot compare by identity across realms). A `Date`/`Map`/class instance\n* has a longer chain and is rejected.\n*/\nfunction hasPlainPrototype(value) {\n\tconst proto = Object.getPrototypeOf(value);\n\tif (proto === null) return true;\n\treturn Object.getPrototypeOf(proto) === null;\n}\n/**\n* Copy `value` (typically from the vm realm) into plain host JSON data. Root `undefined` is\n* returned unchanged; nested `undefined` and values JSON cannot represent losslessly fail\n* with the offending path. Property accessors run normally, and a throwing read is wrapped\n* with its rendered failure.\n*\n* @param value - the realm value to materialize.\n* @param root - the path label for the root value (error messages).\n* @returns the host-realm copy (plain objects/arrays/scalars only).\n* @throws {@link MaterializeError} for unsupported values, cycles, sparse arrays, exotic\n* prototypes, or property reads that throw.\n*/\nfunction materializeFromRealm(value, root = \"value\") {\n\tif (value === void 0) return void 0;\n\ttry {\n\t\treturn materialize(value, root, /* @__PURE__ */ new Set());\n\t} catch (error) {\n\t\tif (error instanceof MaterializeError) throw error;\n\t\tthrow new MaterializeError(root, `reading the value threw: ${renderThrown(error)}`);\n\t}\n}\nfunction materialize(value, path, seen) {\n\tswitch (typeof value) {\n\t\tcase \"boolean\":\n\t\tcase \"string\": return value;\n\t\tcase \"number\":\n\t\t\tif (!Number.isFinite(value)) throw new MaterializeError(path, \"non-finite numbers are not JSON data\");\n\t\t\treturn value;\n\t\tcase \"bigint\": throw new MaterializeError(path, \"bigints are not JSON data\");\n\t\tcase \"function\": throw new MaterializeError(path, \"functions are not plain JSON data\");\n\t\tcase \"symbol\": throw new MaterializeError(path, \"symbols are not plain JSON data\");\n\t\tcase \"undefined\": throw new MaterializeError(path, \"undefined is not JSON data\");\n\t\tcase \"object\": break;\n\t}\n\tif (value === null) return null;\n\tconst objectValue = value;\n\tif (seen.has(objectValue)) throw new MaterializeError(path, \"circular references are not JSON data\");\n\tseen.add(objectValue);\n\ttry {\n\t\tif (Array.isArray(objectValue)) return materializeArray(objectValue, path, seen);\n\t\treturn materializeObject(objectValue, path, seen);\n\t} finally {\n\t\tseen.delete(objectValue);\n\t}\n}\nfunction materializeArray(value, path, seen) {\n\tconst out = [];\n\tfor (let index = 0; index < value.length; index++) {\n\t\tif (!(index in value)) throw new MaterializeError(`${path}[${index}]`, \"sparse arrays are not JSON data\");\n\t\tout.push(materialize(value[index], `${path}[${index}]`, seen));\n\t}\n\tfor (const key of Object.keys(value)) {\n\t\tconst index = Number(key);\n\t\tif (!Number.isInteger(index) || index < 0 || index >= value.length) throw new MaterializeError(`${path}.${key}`, \"arrays with non-index properties are not JSON data\");\n\t}\n\tif (Object.getOwnPropertySymbols(value).length > 0) throw new MaterializeError(path, \"symbol-keyed properties are not plain JSON data\");\n\treturn out;\n}\nfunction materializeObject(value, path, seen) {\n\tif (!hasPlainPrototype(value)) throw new MaterializeError(path, \"only plain objects and arrays are JSON data (exotic prototype)\");\n\tif (Object.getOwnPropertySymbols(value).length > 0) throw new MaterializeError(path, \"symbol-keyed properties are not plain JSON data\");\n\tconst out = {};\n\tfor (const key of Object.keys(value)) Object.defineProperty(out, key, {\n\t\tvalue: materialize(value[key], `${path}.${key}`, seen),\n\t\tenumerable: true,\n\t\twritable: true,\n\t\tconfigurable: true\n\t});\n\treturn out;\n}\n//#endregion\n//#region packages/util/brand/src/index.ts\n/**\n* Apply a compile-time string brand without changing the value.\n* @param value - string admitted by the domain that owns the target brand.\n* @returns the same string with the requested compile-time brand.\n*/\nfunction brandString(value) {\n\treturn value;\n}\n//#endregion\n//#region packages/llm/llm/src/error.ts\n/**\n* Harness error base with a stable machine-routable code and chained cause.\n* Package errors extend it so tool results and replay can retain failure class.\n* @module @deepseek-ai/dsh-llm/error\n*/\n/**\n* Base class for all harness errors. Carries a `code` (stable, programmatic —\n* e.g. `NO_ADAPTER`, `INVALID_ARGS`) distinct from the\n* human-readable `message`, and supports `cause` chaining via the standard\n* `ErrorOptions`. `name` defaults to the subclass constructor name.\n*/\nvar HarnessError = class extends Error {\n\t/** Stable machine-routable failure class (e.g. `RATE_LIMIT`); route on this, never by parsing `message`. */\n\tcode;\n\tconstructor(message, code, options) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t\tthis.name = new.target.name;\n\t}\n};\nnew RegExp(String.raw`(?:^|[^a-z0-9])context[\\s_-](?:length|window)[\\s_-]` + String.raw`(?:exceed(?:ed|s)?|overflow(?:ed)?|limit[\\s_-]exceeded)(?:$|[^a-z0-9])`, \"i\");\nnew RegExp(String.raw`\\b(?:request|prompt|input|messages?)\\s+(?:is\\s+|are\\s+)?` + String.raw`too\\s+(?:large|long)\\s+for\\s+(?:(?:this|the)\\s+)?` + String.raw`(?:model(?:'s)?\\s+)?context(?:\\s+window)?\\b`, \"i\");\nnew RegExp(String.raw`\\b(?:input|prompt|request|messages?)\\b.{0,40}` + String.raw`\\b(?:exceed(?:s|ed)?|overflows?|is\\s+larger\\s+than)\\b.{0,40}` + String.raw`\\b(?:the\\s+)?(?:model(?:'s)?\\s+)?context(?:\\s+(?:length|window))?\\b`, \"i\");\n//#endregion\n//#region packages/util/values/src/index.ts\n/**\n* Mark an unreachable closed-union branch.\n* @param value - impossible value; an unhandled typed variant fails at the call site.\n* @param context - optional switch-site label included in the failure message.\n* @returns never; a runtime value that escaped its type always throws.\n*/\nfunction assertNever(value, context) {\n\tconst rendered = JSON.stringify(value) ?? String(value);\n\tthrow new Error(`unreachable variant${context ? ` in ${context}` : \"\"}: ${rendered}`);\n}\n/** Whether a realm-owned intrinsic prototype has a native constructor matching this engine's representation. */\nfunction hasIntrinsicConstructor$1(prototype, name) {\n\tconst constructor = Object.getOwnPropertyDescriptor(prototype, \"constructor\")?.value;\n\tif (typeof constructor !== \"function\") return false;\n\ttry {\n\t\treturn constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === Function.prototype.toString.call(name === \"Array\" ? Array : Object);\n\t} catch {\n\t\treturn false;\n\t}\n}\n/** Whether a candidate is one realm's intrinsic `Object.prototype`. */\nfunction isIntrinsicObjectPrototype$1(value) {\n\treturn Object.getPrototypeOf(value) === null && hasIntrinsicConstructor$1(value, \"Object\");\n}\n/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */\nfunction hasPlainArrayPrototype$1(value) {\n\tconst prototype = Object.getPrototypeOf(value);\n\tif (!Array.isArray(prototype) || !hasIntrinsicConstructor$1(prototype, \"Array\")) return false;\n\tconst objectPrototype = Object.getPrototypeOf(prototype);\n\treturn typeof objectPrototype === \"object\" && objectPrototype !== null && isIntrinsicObjectPrototype$1(objectPrototype);\n}\n/** Whether an object is a plain or null-prototype record from any JavaScript realm. */\nfunction hasPlainObjectPrototype(value) {\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === null || typeof prototype === \"object\" && isIntrinsicObjectPrototype$1(prototype);\n}\n/** Return every JSON-visible object key, or reject own data JSON would discard. */\nfunction enumerableStringKeys(value) {\n\tconst keys = Reflect.ownKeys(value);\n\tif (keys.some((key) => typeof key !== \"string\" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;\n\treturn keys;\n}\n/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */\nfunction walkJsonValue(value, detach) {\n\tconst ancestors = /* @__PURE__ */ new Set();\n\tlet root;\n\tconst assign = (destination, item) => {\n\t\tif (destination === void 0) return;\n\t\tif (destination.kind === \"root\") root = item;\n\t\telse if (destination.kind === \"array\") destination.target[destination.index] = item;\n\t\telse Object.defineProperty(destination.target, destination.key, {\n\t\t\tvalue: item,\n\t\t\tenumerable: true,\n\t\t\tconfigurable: true,\n\t\t\twritable: true\n\t\t});\n\t};\n\tconst tasks = [{\n\t\tkind: \"visit\",\n\t\tvalue,\n\t\t...detach ? { destination: { kind: \"root\" } } : {}\n\t}];\n\tfor (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {\n\t\tif (task.kind === \"leave\") {\n\t\t\tancestors.delete(task.source);\n\t\t\tcontinue;\n\t\t}\n\t\tif (task.kind === \"array-item\") {\n\t\t\tif (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;\n\t\t\ttasks.push({\n\t\t\t\tkind: \"visit\",\n\t\t\t\tvalue: task.source[task.index],\n\t\t\t\t...task.target === void 0 ? {} : { destination: {\n\t\t\t\t\tkind: \"array\",\n\t\t\t\t\ttarget: task.target,\n\t\t\t\t\tindex: task.index\n\t\t\t\t} }\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (task.kind === \"object-property\") {\n\t\t\ttasks.push({\n\t\t\t\tkind: \"visit\",\n\t\t\t\tvalue: task.source[task.key],\n\t\t\t\t...task.target === void 0 ? {} : { destination: {\n\t\t\t\t\tkind: \"object\",\n\t\t\t\t\ttarget: task.target,\n\t\t\t\t\tkey: task.key\n\t\t\t\t} }\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tconst current = task.value;\n\t\tif (current === null) {\n\t\t\tassign(task.destination, null);\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeof current === \"boolean\" || typeof current === \"string\") {\n\t\t\tassign(task.destination, current);\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeof current === \"number\") {\n\t\t\tif (!Number.isFinite(current) || Object.is(current, -0)) return void 0;\n\t\t\tassign(task.destination, current);\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeof current !== \"object\") return void 0;\n\t\tif (ancestors.has(current)) return void 0;\n\t\tif (Array.isArray(current)) {\n\t\t\tif (!hasPlainArrayPrototype$1(current)) return void 0;\n\t\t\tconst length = current.length;\n\t\t\tif (Reflect.ownKeys(current).length !== length + 1) return void 0;\n\t\t\tconst target = detach ? [] : void 0;\n\t\t\tif (target !== void 0) assign(task.destination, target);\n\t\t\tancestors.add(current);\n\t\t\ttasks.push({\n\t\t\t\tkind: \"leave\",\n\t\t\t\tsource: current\n\t\t\t});\n\t\t\tfor (let index = length - 1; index >= 0; index--) tasks.push({\n\t\t\t\tkind: \"array-item\",\n\t\t\t\tsource: current,\n\t\t\t\tindex,\n\t\t\t\t...target === void 0 ? {} : { target }\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tif (!hasPlainObjectPrototype(current)) return void 0;\n\t\tconst keys = enumerableStringKeys(current);\n\t\tif (keys === void 0) return void 0;\n\t\tconst target = detach ? {} : void 0;\n\t\tif (target !== void 0) assign(task.destination, target);\n\t\tancestors.add(current);\n\t\ttasks.push({\n\t\t\tkind: \"leave\",\n\t\t\tsource: current\n\t\t});\n\t\tfor (let index = keys.length - 1; index >= 0; index--) {\n\t\t\tconst key = keys[index];\n\t\t\t/* v8 ignore next -- the loop is bounded by the captured key count. */\n\t\t\tif (key === void 0) return void 0;\n\t\t\ttasks.push({\n\t\t\t\tkind: \"object-property\",\n\t\t\t\tsource: current,\n\t\t\t\tkey,\n\t\t\t\t...target === void 0 ? {} : { target }\n\t\t\t});\n\t\t}\n\t}\n\treturn detach ? root : true;\n}\n/**\n* Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.\n* @param value - candidate value to test.\n* @returns whether the value survives a JSON round trip without loss.\n*/\nfunction isJsonValue(value) {\n\treturn walkJsonValue(value, false) === true;\n}\n//#endregion\n//#region packages/core/tools/src/json-schema.ts\n/**\n* Enforced JSON Schema subset shared by tool outputs, generated PTC mode\n* types, subagents, and workflows. The subset accepts any JSON root, an\n* annotation-only schema for unconstrained JSON, one scalar `type`, object\n* `properties`/`required`/boolean `additionalProperties`, array `items`,\n* type-correct scalar `enum`/`const`, and exact-one `oneOf`.\n*\n* Unsupported or misplaced keywords reject rather than being accepted without\n* enforcement. Consumers that require an object root apply\n* {@link assertObjectJsonSchema} before accepting input.\n* @module dsh-tools/json-schema\n*/\n/**\n* Thrown when a raw schema falls outside the enforced subset. `violations`\n* lists every offending path instead of stopping at the first author error.\n*/\nvar JsonSchemaError = class extends HarnessError {\n\t/** Individual schema violations in walk order. */\n\tviolations;\n\tconstructor(violations) {\n\t\tsuper(`unsupported JSON schema: ${violations.join(\"; \")}`, \"UNSUPPORTED_SCHEMA\");\n\t\tthis.name = \"JsonSchemaError\";\n\t\tthis.violations = violations;\n\t}\n};\nconst CONSTRAINT_KEYWORDS = new Set([\n\t\"type\",\n\t\"oneOf\",\n\t\"properties\",\n\t\"required\",\n\t\"additionalProperties\",\n\t\"items\",\n\t\"enum\",\n\t\"const\"\n]);\nconst ANNOTATION_KEYWORDS = new Set([\n\t\"description\",\n\t\"title\",\n\t\"default\",\n\t\"examples\"\n]);\nconst SCHEMA_TYPES = [\n\t\"object\",\n\t\"array\",\n\t\"string\",\n\t\"number\",\n\t\"integer\",\n\t\"boolean\",\n\t\"null\"\n];\n/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */\nfunction hasIntrinsicConstructor(prototype, name) {\n\tconst constructor = Object.getOwnPropertyDescriptor(prototype, \"constructor\")?.value;\n\tif (typeof constructor !== \"function\") return false;\n\ttry {\n\t\treturn constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;\n\t} catch {\n\t\treturn false;\n\t}\n}\n/** Whether a candidate is one realm's intrinsic `Object.prototype`. */\nfunction isIntrinsicObjectPrototype(value) {\n\treturn Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, \"Object\");\n}\n/**\n* Test for a realm-agnostic plain JSON record without accepting arrays or\n* exotic objects.\n* @param value - candidate record from any JavaScript realm.\n* @returns Whether the value has a plain-object prototype chain.\n*/\nfunction isPlainJsonRecord(value) {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n\ttry {\n\t\tconst prototype = Object.getPrototypeOf(value);\n\t\treturn prototype === null || typeof prototype === \"object\" && isIntrinsicObjectPrototype(prototype);\n\t} catch {\n\t\treturn false;\n\t}\n}\n/** Whether an array uses one realm's intrinsic `Array.prototype`. */\nfunction hasPlainArrayPrototype(value) {\n\tconst prototype = Object.getPrototypeOf(value);\n\tif (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, \"Array\")) return false;\n\tconst objectPrototype = Object.getPrototypeOf(prototype);\n\treturn typeof objectPrototype === \"object\" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);\n}\n/** Return whether a record contains only own enumerable string keys. */\nfunction hasOnlyEnumerableStringKeys(value) {\n\ttry {\n\t\treturn Reflect.ownKeys(value).every((key) => typeof key === \"string\" && Object.prototype.propertyIsEnumerable.call(value, key));\n\t} catch {\n\t\treturn false;\n\t}\n}\n/**\n* Test for an ordinary schema record whose keys survive JSON projection.\n* @param value - candidate record from any JavaScript realm.\n* @returns Whether the record has an intrinsic prototype and only own enumerable string keys.\n*/\nfunction isJsonSchemaRecord(value) {\n\treturn isPlainJsonRecord(value) && hasOnlyEnumerableStringKeys(value);\n}\n/**\n* Test for a dense ordinary array with no JSON-invisible decorations.\n* @param value - candidate array from any JavaScript realm.\n* @returns Whether the array is intrinsic, dense, and undecorated.\n*/\nfunction isPlainJsonArray(value) {\n\tif (!Array.isArray(value)) return false;\n\ttry {\n\t\tif (!hasPlainArrayPrototype(value) || Reflect.ownKeys(value).length !== value.length + 1) return false;\n\t\tfor (let index = 0; index < value.length; index++) if (!Object.hasOwn(value, index)) return false;\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n/** Lossless finite JSON number, excluding negative zero. */\nfunction isJsonNumber(value) {\n\treturn typeof value === \"number\" && Number.isFinite(value) && !Object.is(value, -0);\n}\n/** Whether a scalar is valid for one declared schema type. */\nfunction scalarMatches(type, value) {\n\tswitch (type) {\n\t\tcase \"string\": return typeof value === \"string\";\n\t\tcase \"number\": return isJsonNumber(value);\n\t\tcase \"integer\": return isJsonNumber(value) && Number.isInteger(value);\n\t\tcase \"boolean\": return typeof value === \"boolean\";\n\t\tcase \"null\": return value === null;\n\t\t/* v8 ignore next -- JsonSchemaScalarType is closed; this retains compile-time exhaustiveness. */\n\t\tdefault: return assertNever(type, \"JsonSchemaType\");\n\t}\n}\n/** Keywords that are invalid beside `oneOf`. */\nconst ONE_OF_SIBLING_KEYWORDS = [\n\t\"properties\",\n\t\"required\",\n\t\"additionalProperties\",\n\t\"items\",\n\t\"enum\",\n\t\"const\"\n];\n/** Validate object-only fields after its property schemas have been visited. */\nfunction checkObjectSchemaTail(node, path, properties, violations) {\n\tconst hasRequired = Object.hasOwn(node, \"required\");\n\tconst required = hasRequired ? node.required : void 0;\n\tif (hasRequired) if (!isPlainJsonArray(required) || required.some((entry) => typeof entry !== \"string\")) violations.push(`${path}.required must be an array of strings`);\n\telse {\n\t\tconst declared = isJsonSchemaRecord(properties) ? properties : {};\n\t\tfor (const key of required) if (!Object.hasOwn(declared, key)) violations.push(`${path}.required names \"${key}\" which is not in properties`);\n\t}\n\tif (Object.hasOwn(node, \"additionalProperties\") && typeof node.additionalProperties !== \"boolean\") violations.push(`${path}.additionalProperties must be a boolean`);\n}\n/** Collect every violation for one raw schema tree without using the JavaScript call stack. */\nfunction checkSchemaNode(root, rootPath, violations, seen) {\n\tconst tasks = [{\n\t\tkind: \"enter\",\n\t\tnode: root,\n\t\tpath: rootPath\n\t}];\n\tfor (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {\n\t\tif (task.kind === \"leave\") {\n\t\t\tseen.delete(task.node);\n\t\t\tcontinue;\n\t\t}\n\t\tif (task.kind === \"one-of-tail\") {\n\t\t\tfor (const key of ONE_OF_SIBLING_KEYWORDS) if (Object.hasOwn(task.node, key)) violations.push(`${task.path}.${key} is not supported beside oneOf`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (task.kind === \"object-tail\") {\n\t\t\tcheckObjectSchemaTail(task.node, task.path, task.properties, violations);\n\t\t\tcontinue;\n\t\t}\n\t\tconst { node, path } = task;\n\t\tif (!isJsonSchemaRecord(node)) {\n\t\t\tviolations.push(`${path} must be a schema object`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (seen.has(node)) {\n\t\t\tviolations.push(`${path} is circular`);\n\t\t\tcontinue;\n\t\t}\n\t\tseen.add(node);\n\t\ttasks.push({\n\t\t\tkind: \"leave\",\n\t\t\tnode\n\t\t});\n\t\tfor (const key of Object.keys(node)) {\n\t\t\tif (CONSTRAINT_KEYWORDS.has(key)) continue;\n\t\t\tif (ANNOTATION_KEYWORDS.has(key)) {\n\t\t\t\ttry {\n\t\t\t\t\tif (!isJsonValue(node[key])) violations.push(`${path}.${key} annotation must be lossless JSON data`);\n\t\t\t\t} catch {\n\t\t\t\t\tviolations.push(`${path}.${key} annotation must be lossless JSON data`);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tviolations.push(`${path}.${key} is not a supported keyword (subset: type/oneOf/properties/required/additionalProperties/items/enum/const + annotations)`);\n\t\t}\n\t\tif (Object.hasOwn(node, \"description\") && typeof node.description !== \"string\") violations.push(`${path}.description must be a string`);\n\t\tif (Object.hasOwn(node, \"title\") && typeof node.title !== \"string\") violations.push(`${path}.title must be a string`);\n\t\tconst hasType = Object.hasOwn(node, \"type\");\n\t\tconst hasOneOf = Object.hasOwn(node, \"oneOf\");\n\t\tif (hasType && hasOneOf) {\n\t\t\tviolations.push(`${path} cannot declare both type and oneOf`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!hasType && !hasOneOf) {\n\t\t\tfor (const key of ONE_OF_SIBLING_KEYWORDS) if (Object.hasOwn(node, key)) violations.push(`${path}.${key} requires type or oneOf`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (hasOneOf) {\n\t\t\tconst oneOf = node.oneOf;\n\t\t\ttasks.push({\n\t\t\t\tkind: \"one-of-tail\",\n\t\t\t\tnode,\n\t\t\t\tpath\n\t\t\t});\n\t\t\tif (!isPlainJsonArray(oneOf) || oneOf.length < 2) violations.push(`${path}.oneOf must be an array of at least two schemas`);\n\t\t\telse for (let index = oneOf.length - 1; index >= 0; index--) tasks.push({\n\t\t\t\tkind: \"enter\",\n\t\t\t\tnode: oneOf[index],\n\t\t\t\tpath: `${path}.oneOf[${index}]`\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tconst type = node.type;\n\t\tif (typeof type !== \"string\" || !SCHEMA_TYPES.includes(type)) {\n\t\t\tviolations.push(Array.isArray(type) ? `${path}.type must be a single type string (type arrays are not supported)` : `${path}.type must be one of ${SCHEMA_TYPES.join(\"/\")}`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst schemaType = type;\n\t\tfor (const [key, types] of Object.entries({\n\t\t\tproperties: [\"object\"],\n\t\t\trequired: [\"object\"],\n\t\t\tadditionalProperties: [\"object\"],\n\t\t\titems: [\"array\"],\n\t\t\tenum: [\n\t\t\t\t\"string\",\n\t\t\t\t\"number\",\n\t\t\t\t\"integer\",\n\t\t\t\t\"boolean\",\n\t\t\t\t\"null\"\n\t\t\t],\n\t\t\tconst: [\n\t\t\t\t\"string\",\n\t\t\t\t\"number\",\n\t\t\t\t\"integer\",\n\t\t\t\t\"boolean\",\n\t\t\t\t\"null\"\n\t\t\t]\n\t\t})) if (Object.hasOwn(node, key) && !types.includes(schemaType)) violations.push(`${path}.${key} is not supported on type \"${schemaType}\"`);\n\t\tswitch (schemaType) {\n\t\t\tcase \"object\": {\n\t\t\t\tconst properties = Object.hasOwn(node, \"properties\") ? node.properties : void 0;\n\t\t\t\ttasks.push({\n\t\t\t\t\tkind: \"object-tail\",\n\t\t\t\t\tnode,\n\t\t\t\t\tpath,\n\t\t\t\t\tproperties\n\t\t\t\t});\n\t\t\t\tif (Object.hasOwn(node, \"properties\")) if (!isJsonSchemaRecord(properties)) violations.push(`${path}.properties must be an object of schemas`);\n\t\t\t\telse {\n\t\t\t\t\tconst entries = Object.entries(properties);\n\t\t\t\t\tfor (let index = entries.length - 1; index >= 0; index--) {\n\t\t\t\t\t\tconst entry = entries[index];\n\t\t\t\t\t\t/* v8 ignore next -- the loop is bounded by the captured entry count. */\n\t\t\t\t\t\tif (entry === void 0) continue;\n\t\t\t\t\t\ttasks.push({\n\t\t\t\t\t\t\tkind: \"enter\",\n\t\t\t\t\t\t\tnode: entry[1],\n\t\t\t\t\t\t\tpath: `${path}.properties.${entry[0]}`\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"array\":\n\t\t\t\tif (Object.hasOwn(node, \"items\")) tasks.push({\n\t\t\t\t\tkind: \"enter\",\n\t\t\t\t\tnode: node.items,\n\t\t\t\t\tpath: `${path}.items`\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\tcase \"string\":\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\tcase \"boolean\":\n\t\t\tcase \"null\": {\n\t\t\t\tconst hasEnum = Object.hasOwn(node, \"enum\");\n\t\t\t\tconst allowed = hasEnum ? node.enum : void 0;\n\t\t\t\tconst enumValid = isPlainJsonArray(allowed) && allowed.length > 0 && allowed.every((entry) => scalarMatches(schemaType, entry));\n\t\t\t\tif (hasEnum && !enumValid) violations.push(`${path}.enum must be a non-empty array of ${schemaType} values`);\n\t\t\t\tconst hasConst = Object.hasOwn(node, \"const\");\n\t\t\t\tconst declaredConst = hasConst ? node.const : void 0;\n\t\t\t\tconst constValid = scalarMatches(schemaType, declaredConst);\n\t\t\t\tif (hasConst) {\n\t\t\t\t\tif (!constValid) violations.push(`${path}.const must be a ${schemaType} value`);\n\t\t\t\t\telse if (enumValid && !allowed.includes(declaredConst)) violations.push(`${path}.const must be one of ${path}.enum when both are declared`);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t/* v8 ignore next -- schemaType was narrowed from the closed SCHEMA_TYPES table above. */\n\t\t\tdefault: assertNever(schemaType, \"JsonSchemaType\");\n\t\t}\n\t}\n}\n/**\n* Assert the enforced subset plus the object-root constraint retained by\n* subagent and workflow structured outputs.\n* @param schema - untrusted caller-supplied schema.\n* @returns Assertion that the schema belongs to the supported subset and has an object root.\n*/\nfunction assertObjectJsonSchema(schema) {\n\tconst violations = [];\n\tcheckSchemaNode(schema, \"schema\", violations, /* @__PURE__ */ new Set());\n\tif (violations.length === 0 && (!isJsonSchemaRecord(schema) || !Object.hasOwn(schema, \"type\") || schema.type !== \"object\")) violations.push(\"schema.type must be \\\"object\\\" (structured output is object-rooted)\");\n\tif (violations.length > 0) throw new JsonSchemaError(violations);\n}\n//#endregion\n//#region packages/workflow/workflow/src/index.ts\n/**\n* Typed error for workflow-seam failures. Extends {@link HarnessError}, so the\n* `code` is machine-routable taxonomy. `fatal` drives the combinator\n* discipline: `parallel()`/`pipeline()` re-throw a fatal error (a typo'd\n* option or a tripped cap must kill the script loudly), and reserve the\n* per-item `null` for child-run failures and ordinary in-stage script errors.\n* Every {@link WorkflowErrorCode} is fatal; the flag exists so the\n* distinction is explicit at every catch site rather than implied.\n*/\nvar WorkflowError = class extends HarnessError {\n\t/** Whether combinators must propagate this error instead of nulling the item. */\n\tfatal;\n\tconstructor(message, code, options) {\n\t\tsuper(message, code, options);\n\t\tthis.name = \"WorkflowError\";\n\t\tthis.fatal = options?.fatal ?? true;\n\t}\n};\n/**\n* Whether combinators must re-throw `error` instead of mapping the item to `null`.\n* @param error - any thrown value; fatality is host `instanceof` (unforgeable from a script realm).\n* @returns true iff `error` is a {@link WorkflowError} whose `fatal` flag is set.\n*/\nfunction isFatalWorkflowError(error) {\n\treturn error instanceof WorkflowError && error.fatal;\n}\n//#endregion\n//#region packages/workflow/workflow-ptc/src/runtime.ts\n/**\n* Workflow VM hooks, child callbacks, ordinary concurrency limits and result serialization.\n* PTC owns process confinement and cancellation. Fatal hook and provider failures propagate\n* through combinators; ordinary child failures and stage errors become per-item nulls.\n* @module @deepseek-ai/dsh-workflow-ptc/runtime\n*/\n/** The `agent()` options the script may pass; everything else rejects loud. */\nconst SUPPORTED_AGENT_OPTIONS = new Set([\n\t\"label\",\n\t\"phase\",\n\t\"schema\",\n\t\"provider\",\n\t\"model\"\n]);\n/** Deferred Claude Code options we name explicitly in the rejection message. */\nconst DEFERRED_AGENT_OPTIONS = new Set([\n\t\"effort\",\n\t\"isolation\",\n\t\"agentType\"\n]);\n/** Flatten a child's final output blocks to text (the non-schema `agent()` result). */\nfunction outputText(blocks) {\n\treturn blocks.filter((block) => block.type === \"text\").map((block) => block.text).join(\"\");\n}\n/** A short display label derived from the prompt when the script passes none. */\nfunction defaultLabel(prompt) {\n\tconst newline = prompt.indexOf(\"\\n\");\n\tconst line = newline === -1 ? prompt : prompt.slice(0, newline);\n\treturn line.length <= 48 ? line : `${line.slice(0, 47)}…`;\n}\n/**\n* One script execution inside the confined Node process. The host owns\n* cancellation and cleanup of any dropped child work.\n*/\nvar WorkflowExecution = class {\n\tlimits;\n\tobserver;\n\tchildren;\n\t/** 1-based count of `agent()` calls started (the `agentsStarted` result field). */\n\tstarted = 0;\n\tactiveSlots = 0;\n\tslotWaiters = [];\n\tcurrentPhase;\n\tcontext;\n\tcompiled;\n\tconstructor(meta, body, args, limits, observer, children) {\n\t\tthis.limits = limits;\n\t\tthis.observer = observer;\n\t\tthis.children = children;\n\t\ttry {\n\t\t\tthis.compiled = new vm.Script(`(async () => {\\n${body}\\n})()`, {\n\t\t\t\tfilename: `workflow:${meta.name}`,\n\t\t\t\tlineOffset: -1\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthrow new WorkflowError(`workflow script does not parse: ${String(error)}`, \"SCRIPT_PARSE\", { cause: error });\n\t\t}\n\t\tthis.context = vm.createContext({}, { name: `workflow:${meta.name}` });\n\t\tconst globals = {\n\t\t\tagent: (prompt, opts) => this.contain(this.agent(prompt, opts)),\n\t\t\tparallel: (thunks) => this.contain(this.parallel(thunks)),\n\t\t\tpipeline: (items, ...stages) => this.contain(this.pipeline(items, stages)),\n\t\t\tphase: (title) => {\n\t\t\t\tthis.phase(title);\n\t\t\t},\n\t\t\tlog: (message) => {\n\t\t\t\tthis.log(message);\n\t\t\t},\n\t\t\targs\n\t\t};\n\t\tfor (const [key, value] of Object.entries(globals)) this.context[key] = typeof value === \"function\" ? Object.freeze(value) : value;\n\t}\n\t/**\n\t* Run the script and materialize its JSON return value.\n\t* @returns A completed or error result; script failures never reject.\n\t*/\n\tasync drive() {\n\t\ttry {\n\t\t\tconst scriptPromise = this.compiled.runInContext(this.context, { timeout: this.limits.syncTimeoutMs });\n\t\t\tconst raw = await this.contain(Promise.resolve(scriptPromise));\n\t\t\treturn {\n\t\t\t\tvalue: raw === void 0 ? null : this.materializeResult(raw),\n\t\t\t\tstopReason: \"completed\",\n\t\t\t\tagentsStarted: this.started\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tvalue: null,\n\t\t\t\tstopReason: \"error\",\n\t\t\t\terror: renderThrown(error),\n\t\t\t\tagentsStarted: this.started\n\t\t\t};\n\t\t}\n\t}\n\t/**\n\t* Attach a no-op rejection consumer WITHOUT changing what the caller\n\t* receives: if the script drops the promise, a host rejection cannot become\n\t* an unhandled rejection that kills the process; if\n\t* the script does await it, it still observes the rejection.\n\t*/\n\tcontain(promise) {\n\t\tpromise.catch(() => {});\n\t\treturn promise;\n\t}\n\t/** Materialize the script's return value; violations become RESULT_UNSERIALIZABLE. */\n\tmaterializeResult(raw) {\n\t\ttry {\n\t\t\treturn materializeFromRealm(raw, \"workflow result\");\n\t\t} catch (error) {\n\t\t\t/* v8 ignore next -- defensive rethrow arm: materializeFromRealm only throws MaterializeError */\n\t\t\tif (!(error instanceof MaterializeError)) throw error;\n\t\t\tthrow new WorkflowError(`the workflow's return value is not plain JSON data — ${error.message}. Return only JSON-serializable objects/arrays/scalars.`, \"RESULT_UNSERIALIZABLE\", { cause: error });\n\t\t}\n\t}\n\t/** Acquire one concurrency slot in FIFO order. */\n\tacquireSlot() {\n\t\tif (this.activeSlots < this.limits.maxConcurrentAgents) {\n\t\t\tthis.activeSlots += 1;\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\treturn new Promise((resolve) => {\n\t\t\tthis.slotWaiters.push(() => {\n\t\t\t\tthis.activeSlots += 1;\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n\treleaseSlot() {\n\t\tthis.activeSlots -= 1;\n\t\tconst next = this.slotWaiters.shift();\n\t\tif (next) next();\n\t}\n\t/** The `agent(prompt, opts)` hook. */\n\tasync agent(rawPrompt, rawOpts) {\n\t\tif (typeof rawPrompt !== \"string\" || rawPrompt.length === 0) throw new WorkflowError(\"agent() requires a non-empty prompt string\", \"INVALID_ARGUMENT\");\n\t\tconst opts = this.readAgentOptions(rawOpts);\n\t\tif (this.started >= this.limits.maxTotalAgents) throw new WorkflowError(`this run reached its total agent cap (${this.limits.maxTotalAgents}) — a runaway-loop backstop; raise the applicable maxTotalAgents limit if the scale is intentional`, \"AGENT_CAP\");\n\t\tthis.started += 1;\n\t\tconst seq = this.started;\n\t\tconst label = opts.label ?? defaultLabel(rawPrompt);\n\t\tconst phase = opts.phase ?? this.currentPhase;\n\t\tawait this.acquireSlot();\n\t\ttry {\n\t\t\tlet run;\n\t\t\ttry {\n\t\t\t\trun = await this.children.startAgent({\n\t\t\t\t\tprompt: rawPrompt,\n\t\t\t\t\t...opts.schema !== void 0 ? { schema: opts.schema } : {},\n\t\t\t\t\t...opts.provider !== void 0 ? { provider: opts.provider } : {},\n\t\t\t\t\t...opts.model !== void 0 ? { model: opts.model } : {}\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tthrow new WorkflowError(`agent() could not start a child: ${renderThrown(error)}`, \"AGENT_START\", { cause: error });\n\t\t\t}\n\t\t\tconst info = {\n\t\t\t\tseq,\n\t\t\t\tlabel,\n\t\t\t\t...phase !== void 0 ? { phase } : {},\n\t\t\t\tchildId: brandString(run.id)\n\t\t\t};\n\t\t\tthis.observer.agentStart(info);\n\t\t\ttry {\n\t\t\t\tlet result;\n\t\t\t\ttry {\n\t\t\t\t\tresult = await run.result;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.observer.agentEnd({\n\t\t\t\t\t\t...info,\n\t\t\t\t\t\toutcome: \"failed\"\n\t\t\t\t\t});\n\t\t\t\t\tthrow new WorkflowError(`child agent run failed: ${renderThrown(error)}`, \"AGENT_RESULT\", { cause: error });\n\t\t\t\t}\n\t\t\t\tif (result.stopReason === \"completed\") {\n\t\t\t\t\tif (opts.schema !== void 0) {\n\t\t\t\t\t\tif (result.structured === void 0) {\n\t\t\t\t\t\t\tthis.observer.agentEnd({\n\t\t\t\t\t\t\t\t...info,\n\t\t\t\t\t\t\t\toutcome: \"failed\"\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.observer.agentEnd({\n\t\t\t\t\t\t\t...info,\n\t\t\t\t\t\t\toutcome: \"completed\"\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn result.structured;\n\t\t\t\t\t}\n\t\t\t\t\tthis.observer.agentEnd({\n\t\t\t\t\t\t...info,\n\t\t\t\t\t\toutcome: \"completed\"\n\t\t\t\t\t});\n\t\t\t\t\treturn outputText(result.output);\n\t\t\t\t}\n\t\t\t\tthis.observer.agentEnd({\n\t\t\t\t\t...info,\n\t\t\t\t\toutcome: \"failed\"\n\t\t\t\t});\n\t\t\t\treturn null;\n\t\t\t} finally {\n\t\t\t\tawait run.dispose();\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.releaseSlot();\n\t\t}\n\t}\n\t/** Materialize + validate the `agent()` options bag from the realm. */\n\treadAgentOptions(rawOpts) {\n\t\tif (rawOpts === void 0) return {};\n\t\tlet opts;\n\t\ttry {\n\t\t\topts = materializeFromRealm(rawOpts, \"agent() options\");\n\t\t} catch (error) {\n\t\t\t/* v8 ignore next -- defensive rethrow arm: materializeFromRealm only throws MaterializeError */\n\t\t\tif (!(error instanceof MaterializeError)) throw error;\n\t\t\tthrow new WorkflowError(`agent() options must be plain JSON data — ${error.message}`, \"INVALID_ARGUMENT\", { cause: error });\n\t\t}\n\t\tif (typeof opts !== \"object\" || opts === null || Array.isArray(opts)) throw new WorkflowError(\"agent() options must be an object\", \"INVALID_ARGUMENT\");\n\t\tconst record = opts;\n\t\tfor (const key of Object.keys(record)) {\n\t\t\tif (SUPPORTED_AGENT_OPTIONS.has(key)) continue;\n\t\t\tif (DEFERRED_AGENT_OPTIONS.has(key)) throw new WorkflowError(`agent() option \"${key}\" is deferred and not supported by this engine (supported: label, phase, schema, provider, model)`, \"UNSUPPORTED_OPTION\");\n\t\t\tthrow new WorkflowError(`agent() option \"${key}\" is not recognized (supported: label, phase, schema, provider, model)`, \"UNSUPPORTED_OPTION\");\n\t\t}\n\t\tfor (const key of [\n\t\t\t\"label\",\n\t\t\t\"phase\",\n\t\t\t\"provider\",\n\t\t\t\"model\"\n\t\t]) if (record[key] !== void 0 && typeof record[key] !== \"string\") throw new WorkflowError(`agent() option \"${key}\" must be a string`, \"INVALID_ARGUMENT\");\n\t\tlet schema;\n\t\tif (record.schema !== void 0) try {\n\t\t\tassertObjectJsonSchema(record.schema);\n\t\t\tschema = record.schema;\n\t\t} catch (error) {\n\t\t\t/* v8 ignore next -- defensive rethrow arm: assertObjectJsonSchema only throws JsonSchemaError */\n\t\t\tif (!(error instanceof JsonSchemaError)) throw error;\n\t\t\tthrow new WorkflowError(`agent() schema is outside the supported subset — ${error.message}`, \"UNSUPPORTED_SCHEMA\", { cause: error });\n\t\t}\n\t\treturn {\n\t\t\t...record.label !== void 0 ? { label: record.label } : {},\n\t\t\t...record.phase !== void 0 ? { phase: record.phase } : {},\n\t\t\t...record.provider !== void 0 ? { provider: record.provider } : {},\n\t\t\t...record.model !== void 0 ? { model: record.model } : {},\n\t\t\t...schema !== void 0 ? { schema } : {}\n\t\t};\n\t}\n\t/** The `parallel(thunks)` hook: each thunk caught → `null`; fatal errors propagate. */\n\tasync parallel(rawThunks) {\n\t\tif (!Array.isArray(rawThunks)) throw new WorkflowError(\"parallel() requires an array of zero-argument functions\", \"INVALID_ARGUMENT\");\n\t\tthis.assertItemCap(rawThunks.length, \"parallel()\");\n\t\tconst thunks = rawThunks.map((thunk, index) => {\n\t\t\tif (typeof thunk !== \"function\") throw new WorkflowError(`parallel() item ${index} is not a function`, \"INVALID_ARGUMENT\");\n\t\t\treturn thunk;\n\t\t});\n\t\treturn Promise.all(thunks.map(async (thunk) => {\n\t\t\ttry {\n\t\t\t\treturn await thunk();\n\t\t\t} catch (error) {\n\t\t\t\tif (isFatalWorkflowError(error)) throw error;\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}));\n\t}\n\t/** The `pipeline(items, ...stages)` hook: per-item stage chains, NO cross-stage barrier. */\n\tasync pipeline(rawItems, rawStages) {\n\t\tif (!Array.isArray(rawItems)) throw new WorkflowError(\"pipeline() requires an items array\", \"INVALID_ARGUMENT\");\n\t\tthis.assertItemCap(rawItems.length, \"pipeline()\");\n\t\tif (rawStages.length === 0) throw new WorkflowError(\"pipeline() requires at least one stage function\", \"INVALID_ARGUMENT\");\n\t\tconst stages = rawStages.map((stage, index) => {\n\t\t\tif (typeof stage !== \"function\") throw new WorkflowError(`pipeline() stage ${index} is not a function`, \"INVALID_ARGUMENT\");\n\t\t\treturn stage;\n\t\t});\n\t\treturn Promise.all(rawItems.map(async (item, index) => {\n\t\t\tlet value = item;\n\t\t\ttry {\n\t\t\t\tfor (const stage of stages) value = await stage(value, item, index);\n\t\t\t\treturn value;\n\t\t\t} catch (error) {\n\t\t\t\tif (isFatalWorkflowError(error)) throw error;\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}));\n\t}\n\tassertItemCap(length, hook) {\n\t\tif (length > this.limits.maxItemsPerCall) throw new WorkflowError(`${hook} received ${length} items — over the per-call cap (${this.limits.maxItemsPerCall}); split the work or raise maxItemsPerCall in the engine config`, \"ITEM_CAP\");\n\t}\n\t/** The `phase(title)` hook: sets the current label for subsequent `agent()` calls and notifies observers. */\n\tphase(title) {\n\t\tif (typeof title !== \"string\" || title.length === 0) throw new WorkflowError(\"phase() requires a non-empty title string\", \"INVALID_ARGUMENT\");\n\t\tthis.currentPhase = title;\n\t\tthis.observer.phase(title);\n\t}\n\t/** The `log(message)` hook: narration to observers. */\n\tlog(message) {\n\t\tif (typeof message !== \"string\") throw new WorkflowError(\"log() requires a message string\", \"INVALID_ARGUMENT\");\n\t\tthis.observer.log(message);\n\t}\n};\n//#endregion\n//#region packages/workflow/workflow-ptc/src/guest.ts\n/**\n* Run a workflow with one progress batch in flight. Drain progress before child\n* disposal and the terminal result; PTC and the host own cancellation and cleanup.\n* @param host - JSON callbacks owned by this workflow run.\n* @returns The script result after progress delivery; initialization failures reject.\n*/\nasync function runWorkflowGuest(host) {\n\tconst init = await host.begin({});\n\tlet queued = [];\n\tlet inFlight;\n\tlet progressError;\n\tconst flush = () => {\n\t\tif (inFlight !== void 0 || queued.length === 0) return;\n\t\tconst batch = queued;\n\t\tqueued = [];\n\t\tinFlight = host.progress(batch).then(() => {\n\t\t\tinFlight = void 0;\n\t\t\tflush();\n\t\t}, (error) => {\n\t\t\tprogressError = renderThrown(error);\n\t\t\tqueued = [];\n\t\t\tinFlight = void 0;\n\t\t});\n\t};\n\tconst send = (event) => {\n\t\tif (progressError !== void 0) return;\n\t\tqueued.push(event);\n\t\tflush();\n\t};\n\tconst drain = async () => {\n\t\twhile (inFlight !== void 0) await inFlight;\n\t};\n\tconst result = await new WorkflowExecution(init.meta, init.body, init.args, init.limits, {\n\t\tphase: (title) => {\n\t\t\tsend({\n\t\t\t\ttype: \"phase\",\n\t\t\t\ttitle\n\t\t\t});\n\t\t},\n\t\tlog: (message) => {\n\t\t\tsend({\n\t\t\t\ttype: \"log\",\n\t\t\t\tmessage\n\t\t\t});\n\t\t},\n\t\tagentStart: (info) => {\n\t\t\tsend({\n\t\t\t\ttype: \"agent-start\",\n\t\t\t\tinfo\n\t\t\t});\n\t\t},\n\t\tagentEnd: (info) => {\n\t\t\tsend({\n\t\t\t\ttype: \"agent-end\",\n\t\t\t\tinfo\n\t\t\t});\n\t\t}\n\t}, { async startAgent(request) {\n\t\tconst { callId, childId } = await host.startChild(request);\n\t\tconst result = host.childResult({ callId });\n\t\tresult.catch(() => {});\n\t\treturn {\n\t\t\tid: childId,\n\t\t\tresult,\n\t\t\tasync dispose() {\n\t\t\t\tawait drain();\n\t\t\t\tawait host.disposeChild({ callId });\n\t\t\t}\n\t\t};\n\t} }).drive();\n\tawait drain();\n\treturn progressError === void 0 ? result : {\n\t\tvalue: null,\n\t\tstopReason: \"error\",\n\t\terror: progressError,\n\t\tagentsStarted: result.agentsStarted\n\t};\n}\n//#endregion\nexport { runWorkflowGuest };\n//# sourceURL=dsh-workflow-guest.js\n" diff --git a/packages/workflow/workflow-ptc/tests/integration.spec.ts b/packages/workflow/workflow-ptc/tests/integration.spec.ts index 95106f84ec..5745960556 100644 --- a/packages/workflow/workflow-ptc/tests/integration.spec.ts +++ b/packages/workflow/workflow-ptc/tests/integration.spec.ts @@ -3,10 +3,6 @@ import { Context } from '@deepseek-ai/cordis' import { SessionId } from '@deepseek-ai/dsh-session' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import * as SessionInvariant from '@deepseek-ai/dsh-session/invariant' -import * as AgentInvariant from '@deepseek-ai/dsh-agent/invariant' -import * as AgentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant' import SubagentRuntime from '@deepseek-ai/dsh-subagent' import * as spawn from '@deepseek-ai/dsh-subagent-spawn-in-process' import { STRUCTURED_OUTPUT_TOOL } from '@deepseek-ai/dsh-subagent-in-process-driver' @@ -16,19 +12,11 @@ import { mountPtcRuntime } from './setup.ts' type Script = ConstructorParameters[0] -async function mountInvariants(ctx: Context): Promise { - await ctx.plugin(InvariantRegistry) - await ctx.plugin(SessionInvariant) - await ctx.plugin(AgentInvariant) - await ctx.plugin(AgentLoopInvariant) -} - async function setup(script: Script) { const ctx = new Context() const adapter = new MockAdapter(script) await mountAgentLoopTestDependencies(ctx) await mountPtcRuntime(ctx) - await mountInvariants(ctx) await ctx.plugin(AgentLoop, { agents: [] }) await ctx.plugin(SubagentRuntime) await ctx.plugin(spawn, { providerName: 'spawn' }) diff --git a/packages/workflow/workflow/README.i18n.yaml b/packages/workflow/workflow/README.i18n.yaml index 6c21794753..27766eb5b8 100644 --- a/packages/workflow/workflow/README.i18n.yaml +++ b/packages/workflow/workflow/README.i18n.yaml @@ -36,8 +36,8 @@ en: 274b3de2f7b713ec zh: e3953c1ae1f59442 /deepseek-ai-dsh-workflow/understand-the-implementation/source-map: - en: 67240e519a3e725f - zh: 495f806856054230 + en: 4980d54aca90b591 + zh: 73a828842b6c6f6a /deepseek-ai-dsh-workflow/understand-the-implementation/lifecycle-and-ownership: en: 95b074d816b52323 zh: dfa070a1531651f9 diff --git a/packages/workflow/workflow/README.md b/packages/workflow/workflow/README.md index de167e520e..99dd6f90cf 100644 --- a/packages/workflow/workflow/README.md +++ b/packages/workflow/workflow/README.md @@ -77,7 +77,6 @@ The package separates the script, run, result, and event contracts from executio | [`src/index.ts`](src/index.ts) | Service definition, `workflow/*` event declarations, `WorkflowError` and its fatal flag | | [`src/types.ts`](src/types.ts) | Browser-safe vocabulary: `WorkflowMeta`, `WorkflowResult`, run and agent event info | | [`src/runtime-types.ts`](src/runtime-types.ts) | Host-only `WorkflowStartRequest` and `WorkflowRun` handles | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: event pairing and identity checks | ### Lifecycle and ownership diff --git a/packages/workflow/workflow/README.zh.md b/packages/workflow/workflow/README.zh.md index 77f2cddd3d..0fc9b37a6a 100644 --- a/packages/workflow/workflow/README.zh.md +++ b/packages/workflow/workflow/README.zh.md @@ -77,7 +77,6 @@ return { reviewed: reviews.length } | [`src/index.ts`](src/index.ts) | 服务定义、`workflow/*` 事件声明、`WorkflowError` 及其 fatal 标志 | | [`src/types.ts`](src/types.ts) | 浏览器安全词汇:`WorkflowMeta`、`WorkflowResult`、运行与 agent 事件信息 | | [`src/runtime-types.ts`](src/runtime-types.ts) | 仅宿主的 `WorkflowStartRequest` 与 `WorkflowRun` 句柄 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:事件配对与身份校验 | ### 生命周期与归属 diff --git a/packages/workflow/workflow/package.json b/packages/workflow/workflow/package.json index 55df3ce8de..f3b09f7269 100644 --- a/packages/workflow/workflow/package.json +++ b/packages/workflow/workflow/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -31,7 +27,6 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], @@ -39,7 +34,6 @@ "peerDependencies": { "@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:*", "@deepseek-ai/cordis": "workspace:~" @@ -47,7 +41,6 @@ "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:*", "@deepseek-ai/cordis": "workspace:~" diff --git a/packages/workflow/workflow/src/invariant.ts b/packages/workflow/workflow/src/invariant.ts deleted file mode 100644 index 47dc82d0bf..0000000000 --- a/packages/workflow/workflow/src/invariant.ts +++ /dev/null @@ -1,136 +0,0 @@ -/** Package-owned workflow lifecycle invariants. @module @deepseek-ai/dsh-workflow/invariant */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { - WorkflowAgentEndInfo, - WorkflowAgentInfo, - WorkflowResultInfo, - WorkflowRunInfo, -} from './types.ts' - -const PACKAGE_NAME = '@deepseek-ai/dsh-workflow' - -/** Cordis companion plugin name. */ -export const name = 'workflow-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -interface WorkflowTrace { - meta: string - agents: Map - starts: number -} - -/** Require every event for a run to retain its validated identity snapshot. */ -function traceFor( - traces: ReadonlyMap, - info: WorkflowRunInfo, - fail: InvariantFailure, -): WorkflowTrace { - const trace = traces.get(info.id) - if (trace === undefined) fail(`workflow event has no matching workflow/start for run ${JSON.stringify(info.id)}`) - if (trace.meta !== JSON.stringify(info.meta)) { - fail(`workflow event meta diverges from workflow/start for run ${JSON.stringify(info.id)}`) - } - return trace -} - -/** Assert the immutable identity fields shared by an agent pair. */ -function validateAgentEnd(start: WorkflowAgentInfo, end: WorkflowAgentEndInfo, fail: InvariantFailure): void { - if (start.label !== end.label || start.phase !== end.phase || start.childId !== end.childId) { - fail(`workflow/agent-end identity diverges from workflow/agent-start for seq ${end.seq}`) - } - const outcome: string = end.outcome - if (outcome !== 'completed' && outcome !== 'failed' && outcome !== 'cancelled') { - fail(`workflow/agent-end carries unknown outcome ${JSON.stringify(outcome)}`) - } -} - -/** Validate a terminal result against the accumulated run trace. */ -function validateWorkflowEnd(trace: WorkflowTrace, result: WorkflowResultInfo, fail: InvariantFailure): void { - if (trace.agents.size > 0) fail(`workflow/end has ${trace.agents.size} agent call(s) without workflow/agent-end`) - if (!Number.isSafeInteger(result.agentsStarted) || result.agentsStarted < trace.starts) { - fail('workflow/end agentsStarted must be a safe integer covering every observed agent start') - } - if (result.stopReason === 'completed' ? result.error !== undefined : typeof result.error !== 'string') { - fail('workflow/end error must be absent exactly for completed runs') - } -} - -/** Install workflow start/end and child-call pairing checks. */ -const install: InvariantInstaller = (ctx, fail) => { - const traces = new Map() - const stagedStarts = new WeakSet() - const stagedAgentStarts = new WeakSet() - const stagedAgentEnds = new WeakSet() - const stagedEnds = new WeakSet() - - ctx.on('internal/dispatch', (_mode, eventName, args) => { - if (eventName === 'workflow/start') { - const info = args[0] as WorkflowRunInfo - if (String(info.id).length === 0 || info.meta.name.length === 0 || info.meta.description.length === 0) { - fail('workflow/start id, meta.name, and meta.description must be non-empty') - } - if (traces.has(info.id)) fail(`workflow/start repeated run id ${JSON.stringify(info.id)}`) - stagedStarts.add(info) - return - } - if (!eventName.startsWith('workflow/')) return - const info = args[0] as WorkflowRunInfo - const trace = traceFor(traces, info, fail) - if (eventName === 'workflow/agent-start') { - const agent = args[1] as WorkflowAgentInfo - if (!Number.isSafeInteger(agent.seq) || agent.seq < 1 || String(agent.childId).length === 0) { - fail('workflow/agent-start seq must be positive and childId must be non-empty') - } - if (trace.agents.has(agent.seq)) fail(`workflow/agent-start repeated seq ${agent.seq}`) - stagedAgentStarts.add(agent) - return - } - if (eventName === 'workflow/agent-end') { - const agent = args[1] as WorkflowAgentEndInfo - const start = trace.agents.get(agent.seq) - if (start === undefined) return fail(`workflow/agent-end has no matching start for seq ${agent.seq}`) - validateAgentEnd(start, agent, fail) - stagedAgentEnds.add(agent) - return - } - if (eventName === 'workflow/end') { - const result = args[1] as WorkflowResultInfo - validateWorkflowEnd(trace, result, fail) - stagedEnds.add(result) - } - }, { global: true }) - - ctx.on('workflow/start', (info) => { - /* v8 ignore next -- internal/dispatch stages the same run-info object */ - if (!stagedStarts.delete(info)) return - traces.set(info.id, { meta: JSON.stringify(info.meta), agents: new Map(), starts: 0 }) - }, { global: true }) - ctx.on('workflow/agent-start', (info, agent) => { - /* v8 ignore next -- internal/dispatch stages the same agent object */ - if (!stagedAgentStarts.delete(agent)) return - const trace = traceFor(traces, info, fail) - trace.agents.set(agent.seq, agent) - trace.starts += 1 - }, { global: true }) - ctx.on('workflow/agent-end', (info, agent) => { - /* v8 ignore next -- internal/dispatch stages the same agent object */ - if (!stagedAgentEnds.delete(agent)) return - traceFor(traces, info, fail).agents.delete(agent.seq) - }, { global: true }) - ctx.on('workflow/end', (info, result) => { - /* v8 ignore next -- internal/dispatch stages the same result object */ - if (!stagedEnds.delete(result)) return - traces.delete(info.id) - }, { global: true }) -} - -/** - * Register the workflow 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)) diff --git a/packages/workflow/workflow/tests/invariant.spec.ts b/packages/workflow/workflow/tests/invariant.spec.ts deleted file mode 100644 index e724e34ce7..0000000000 --- a/packages/workflow/workflow/tests/invariant.spec.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import { SessionId } from '@deepseek-ai/dsh-session' -import { WorkflowRunId } from '@deepseek-ai/dsh-workflow' -import type { - WorkflowAgentEndInfo, - WorkflowAgentInfo, - WorkflowResultInfo, - WorkflowRunInfo, -} from '@deepseek-ai/dsh-workflow' -import * as WorkflowInvariant from '@deepseek-ai/dsh-workflow/invariant' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -async function setup(): Promise { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - await ctx.plugin(WorkflowInvariant) - return ctx -} - -const info = (overrides: Partial = {}): WorkflowRunInfo => ({ - id: WorkflowRunId('workflow-1'), - meta: { name: 'review', description: 'Review a change' }, - ...overrides, -}) - -const agent = (overrides: Partial = {}): WorkflowAgentInfo => ({ - seq: 1, - label: 'reviewer', - childId: SessionId('child-1'), - ...overrides, -}) - -const agentEnd = (overrides: Partial = {}): WorkflowAgentEndInfo => ({ - ...agent(), - outcome: 'completed', - ...overrides, -}) - -const result = (overrides: Partial = {}): WorkflowResultInfo => ({ - stopReason: 'completed', - agentsStarted: 1, - ...overrides, -}) - -describe('workflow invariants', () => { - it('accepts a complete workflow and child lifecycle', async () => { - const ctx = await setup() - const run = info() - ctx.emit('workflow/start', run) - ctx.emit('workflow/phase', run, 'inspect') - ctx.emit('workflow/log', run, 'working') - ctx.emit('workflow/agent-start', run, agent()) - ctx.emit('workflow/agent-end', run, agentEnd()) - ctx.emit('workflow/end', run, result()) - ctx.emit('tools/change') - }) - - it('rejects invalid run identity and enclosure', async () => { - const ctx = await setup() - expect(() => { ctx.emit('workflow/start', info({ id: WorkflowRunId('') })) }).toThrow(/must be non-empty/) - const run = info() - ctx.emit('workflow/start', run) - expect(() => { ctx.emit('workflow/start', run) }).toThrow(/repeated run id/) - expect(() => { ctx.emit('workflow/log', info({ meta: { name: 'other', description: 'x' } }), 'x') }) - .toThrow(/meta diverges/) - const fresh = await setup() - expect(() => { fresh.emit('workflow/log', info(), 'x') }).toThrow(/no matching workflow\/start/) - }) - - it('rejects malformed and unpaired child lifecycles', async () => { - const ctx = await setup() - const run = info() - ctx.emit('workflow/start', run) - expect(() => { ctx.emit('workflow/agent-start', run, agent({ seq: 0 })) }).toThrow(/seq must be positive/) - ctx.emit('workflow/agent-start', run, agent()) - expect(() => { ctx.emit('workflow/agent-start', run, agent()) }).toThrow(/repeated seq/) - expect(() => { ctx.emit('workflow/agent-end', run, agentEnd({ seq: 2 })) }).toThrow(/no matching start/) - expect(() => { ctx.emit('workflow/agent-end', run, agentEnd({ childId: SessionId('other') })) }) - .toThrow(/identity diverges/) - expect(() => { ctx.emit('workflow/agent-end', run, agentEnd({ outcome: 'unknown' as never })) }) - .toThrow(/unknown outcome/) - }) - - it('rejects inconsistent terminal results', async () => { - const active = await setup() - active.emit('workflow/start', info()) - active.emit('workflow/agent-start', info(), agent()) - expect(() => { active.emit('workflow/end', info(), result()) }).toThrow(/without workflow\/agent-end/) - - const count = await setup() - count.emit('workflow/start', info()) - count.emit('workflow/agent-start', info(), agent()) - count.emit('workflow/agent-end', info(), agentEnd()) - expect(() => { count.emit('workflow/end', info(), result({ agentsStarted: 0 })) }) - .toThrow(/covering every observed agent start/) - - const completed = await setup() - completed.emit('workflow/start', info()) - expect(() => { completed.emit('workflow/end', info(), result({ error: 'unexpected' })) }) - .toThrow(/absent exactly for completed/) - - const failed = await setup() - failed.emit('workflow/start', info()) - expect(() => { failed.emit('workflow/end', info(), result({ stopReason: 'error' })) }) - .toThrow(/absent exactly for completed/) - }) -}) diff --git a/packages/workflow/workflow/tsconfig.json b/packages/workflow/workflow/tsconfig.json index ac4206f747..95494b6406 100644 --- a/packages/workflow/workflow/tsconfig.json +++ b/packages/workflow/workflow/tsconfig.json @@ -25,9 +25,6 @@ }, { "path": "../../llm/llm" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/workspace/workspace/README.i18n.yaml b/packages/workspace/workspace/README.i18n.yaml index 7f24797e9d..def0478dc2 100644 --- a/packages/workspace/workspace/README.i18n.yaml +++ b/packages/workspace/workspace/README.i18n.yaml @@ -45,8 +45,8 @@ en: 69327adb9ea52680 zh: fa32eea5019c7abb /deepseek-ai-dsh-workspace/understand-the-implementation/source-map: - en: c6de390af6b1a187 - zh: 85699c7c5c3afe3a + en: 6899c50041c3d1ef + zh: ea70fda89cc50465 /deepseek-ai-dsh-workspace/understand-the-implementation/durable-shape: en: 056a266f4a15b29d zh: e1589f5750662887 @@ -54,11 +54,8 @@ en: e41db10d48acbf9a zh: 457d329fe37ba33c /deepseek-ai-dsh-workspace/understand-the-implementation/failure-and-recovery: - en: 7a2f7802debbdd75 - zh: fbf4603f52863a6c -/deepseek-ai-dsh-workspace/understand-the-implementation/invariant: - en: d20f2bba9aab0d1d - zh: 2ff06f27e7a8a867 + en: 8b53b9369bea6f67 + zh: 612fd3439a23e035 /deepseek-ai-dsh-workspace/further-exploration: en: ea6e4da02bc6da4e zh: 5168484d76a27436 diff --git a/packages/workspace/workspace/README.md b/packages/workspace/workspace/README.md index da2c59d992..228ae09b05 100644 --- a/packages/workspace/workspace/README.md +++ b/packages/workspace/workspace/README.md @@ -109,7 +109,6 @@ Archive admission is a capability seam over two Host events this package declare | [`src/spec.ts`](src/spec.ts) | Domain declaration: record schema, registry state, `defineDomain` spec | | [`src/types.ts`](src/types.ts) | Public `Workspace` interface and `WorkspaceId` brand | | [`src/paths.ts`](src/paths.ts) | The `realpath` uniqueness canon | -| [`src/invariant.ts`](src/invariant.ts) | Invariant companion: the entity cache mirrors the durable table | ### Durable shape @@ -123,10 +122,6 @@ On start, the registry opens the domain, completes a marked mutation if one is p A create or delete whose second write fails rolls the cache and the prior order back; when both the operation and its rollback fail, the durable marker still names the interrupted operation and the next startup completes or rolls it back. A committed delete whose marker cleanup fails still reports success, and the next startup clears the marker idempotently. -### Invariant - -The `workspace-invariant` companion registers the owned relationship: every durable `domain/changed` for the `workspaces` table must name a record the entity cache already holds — a delete is valid only after the registry removed the entity from its cache, so a bypassing write path fails the invariant. - ----- diff --git a/packages/workspace/workspace/README.zh.md b/packages/workspace/workspace/README.zh.md index a2e826fc57..654ede5601 100644 --- a/packages/workspace/workspace/README.zh.md +++ b/packages/workspace/workspace/README.zh.md @@ -110,7 +110,6 @@ ctx.workspaceRegistry.list() // shows the project, newest first | [`src/spec.ts`](src/spec.ts) | 领域声明:记录 schema、注册表状态、`defineDomain` 规范 | | [`src/types.ts`](src/types.ts) | 公开 `Workspace` 接口与 `WorkspaceId` 品牌 | | [`src/paths.ts`](src/paths.ts) | `realpath` 唯一性规范 | -| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件:实体缓存镜像持久表 | ### 持久形态 @@ -124,10 +123,6 @@ ctx.workspaceRegistry.list() // shows the project, newest first 创建或删除的第二次写入失败时,缓存与先前顺序会回滚;当操作与回滚都失败时,持久标记仍指明被中断的操作,下一次启动会补全或回滚它。已提交的删除即使标记清理失败仍报告成功,下一次启动会幂等地清除该标记。 -### 不变式 - -`workspace-invariant` 伴生插件注册归属关系:`workspaces` 表的每个持久 `domain/changed` 都必须指向实体缓存已持有的记录——只有在注册表从缓存移除实体之后删除才有效,因此绕过注册表的写入路径会触发不变式失败。 - ----- diff --git a/packages/workspace/workspace/package.json b/packages/workspace/workspace/package.json index 52ed1ad7e1..93266b7641 100644 --- a/packages/workspace/workspace/package.json +++ b/packages/workspace/workspace/package.json @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./types": { "types": "./lib/types/types.d.ts", "default": "./lib/types/types.js" @@ -31,14 +27,12 @@ }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/types/**/*.js", "lib/types/**/*.d.ts" ], "license": "MIT", "peerDependencies": { "@deepseek-ai/cordis": "workspace:~", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-persistence": "workspace:*", "@deepseek-ai/dsh-storage": "workspace:*", @@ -51,7 +45,6 @@ }, "devDependencies": { "@deepseek-ai/cordis": "workspace:~", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-persistence": "workspace:*", "@deepseek-ai/dsh-storage": "workspace:*", diff --git a/packages/workspace/workspace/src/invariant.ts b/packages/workspace/workspace/src/invariant.ts deleted file mode 100644 index 6bf867d4b2..0000000000 --- a/packages/workspace/workspace/src/invariant.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-workspace`. - * @module @deepseek-ai/dsh-workspace/invariant - */ - -import type { Context } from '@deepseek-ai/cordis' -import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import type { DomainChanged } from '@deepseek-ai/dsh-storage-domain' -import { WorkspaceId } from '@deepseek-ai/dsh-workspace' - -const PACKAGE_NAME = '@deepseek-ai/dsh-workspace' - -/** Cordis companion plugin name. */ -export const name = 'workspace-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** - * Owned relationship: the registry's entity cache mirrors the workspace - * domain's durable table. Every `domain/changed` for the `workspaces` table - * must name a record the cache already holds an entity for (the registry - * caches before the durable put and mutates only through cached entities). - * A delete is valid only after the registry has removed the entity from its - * cache, whether for create rollback or an explicit registration deletion; - * deleting while the cache still publishes the entity proves a bypass. - */ -const install: InvariantInstaller = Object.assign( - (ctx: Context, fail: (message: string) => never) => { - ctx.on('domain/changed', (change: DomainChanged) => { - if (change.domain !== 'workspace' || change.table !== 'workspaces') return - if (change.operation === 'deleted') { - if (ctx.workspaceRegistry.get(WorkspaceId(change.key)) !== undefined) { - fail( - `workspace record '${change.key}' was deleted while the registry cache still ` - + 'publishes it — some write path bypassed ctx.workspaceRegistry', - ) - } - return - } - if (ctx.workspaceRegistry.get(WorkspaceId(change.key)) === undefined) { - fail( - `workspace record '${change.key}' landed durably but the registry cache holds ` - + 'no entity for it — the cache and the domain table have diverged', - ) - } - }) - }, - { inject: ['workspaceRegistry'] }, -) - -/** - * 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)) diff --git a/packages/workspace/workspace/tests/invariant.spec.ts b/packages/workspace/workspace/tests/invariant.spec.ts deleted file mode 100644 index 255117add0..0000000000 --- a/packages/workspace/workspace/tests/invariant.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { Context } from '@deepseek-ai/cordis' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import type { DomainChanged } from '@deepseek-ai/dsh-storage-domain' -import * as WorkspaceInvariant from '../src/invariant.ts' -import { WorkspaceId } from '../src/index.ts' - -/** Boot the invariant service plus the companion over a stubbed registry knowing exactly `ids`. */ -async function setup(ids: string[]): Promise { - const ctx = new Context() - await ctx.plugin(InvariantRegistry) - ctx.provide('workspaceRegistry', { - get: (id: WorkspaceId) => (ids.includes(id) ? { id } : undefined), - }) - await ctx.plugin(WorkspaceInvariant) - return ctx -} - -type ChangeLocation = Partial> - -const put = (overrides?: ChangeLocation): DomainChanged => ({ - domain: 'workspace', - table: 'workspaces', - key: 'w1', - operation: 'put', - value: {}, - ...overrides, -}) - -const deleted = (): DomainChanged => ({ - domain: 'workspace', - table: 'workspaces', - key: 'w1', - operation: 'deleted', -}) - -describe('workspace cache/table invariant', () => { - it('accepts a put whose record has a cached entity and ignores foreign events', async () => { - const ctx = await setup(['w1']) - expect(() => { ctx.emit('domain/changed', put()) }).not.toThrow() - // Other domains and other tables are out of scope, whatever their shape. - expect(() => { ctx.emit('domain/changed', put({ domain: 'other', key: 'missing' })) }).not.toThrow() - expect(() => { ctx.emit('domain/changed', put({ table: 'other', key: 'missing' })) }).not.toThrow() - }) - - it('fails deletion while the registry still publishes the entity', async () => { - const ctx = await setup(['w1']) - expect(() => { ctx.emit('domain/changed', deleted()) }) - .toThrow(/cache still publishes/) - }) - - it('allows deletion after the registry removed the cache entry for rollback or explicit deletion', async () => { - const ctx = await setup([]) - expect(() => { ctx.emit('domain/changed', deleted()) }).not.toThrow() - }) - - it('fails a put whose record the registry cache does not hold', async () => { - const ctx = await setup([]) - expect(() => { ctx.emit('domain/changed', put()) }).toThrow(/diverged/) - }) -}) diff --git a/packages/workspace/workspace/tsconfig.json b/packages/workspace/workspace/tsconfig.json index d007df1796..a4d55728a4 100644 --- a/packages/workspace/workspace/tsconfig.json +++ b/packages/workspace/workspace/tsconfig.json @@ -34,9 +34,6 @@ }, { "path": "../../util/brand" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da91191c1a..bafd30144f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2308,9 +2308,6 @@ importers: '@deepseek-ai/dsh-deepseek-llm-api-extensions': specifier: workspace:* version: link:../../llm/deepseek-llm-api-extensions - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-jobs-local': specifier: workspace:* version: link:../../jobs/jobs-local @@ -2335,9 +2332,6 @@ importers: '@deepseek-ai/dsh-sandbox-policy': specifier: workspace:* version: link:../../sandbox/sandbox-policy - '@deepseek-ai/dsh-scope': - specifier: workspace:* - version: link:../../core/scope '@deepseek-ai/dsh-sdk-app': specifier: workspace:* version: link:../sdk-app @@ -2904,9 +2898,6 @@ importers: '@deepseek-ai/dsh-host-webserver': specifier: workspace:* version: link:../../host/webserver - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants packages/client/locale: dependencies: @@ -2968,9 +2959,6 @@ importers: '@deepseek-ai/dsh-host-webserver': specifier: workspace:* version: link:../../host/webserver - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-package-manifest': specifier: workspace:* version: link:../../util/package-manifest @@ -4561,9 +4549,6 @@ importers: '@deepseek-ai/dsh-client-ui-slots': specifier: workspace:* version: link:../ui-slots - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@types/react': specifier: ~18.3.1 version: 18.3.31 @@ -6373,9 +6358,6 @@ importers: '@deepseek-ai/dsh-commands': specifier: workspace:* version: link:../../interaction/commands - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -6422,9 +6404,6 @@ importers: '@deepseek-ai/dsh-compaction-tool-result-pruner': specifier: workspace:* version: link:../compaction-tool-result-pruner - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -6511,9 +6490,6 @@ importers: '@deepseek-ai/dsh-compaction-image-offload': specifier: workspace:* version: link:../compaction-image-offload - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -6734,9 +6710,6 @@ importers: '@deepseek-ai/dsh-bash-local': specifier: workspace:* version: link:../../shell/bash-local - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -6804,9 +6777,6 @@ importers: '@deepseek-ai/cordis': specifier: workspace:~ version: link:../../../vendor/cordis - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -6878,9 +6848,6 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:* version: link:../agent - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -6945,9 +6912,6 @@ importers: '@deepseek-ai/cordis': specifier: workspace:~ version: link:../../../vendor/cordis - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants packages/core/session: dependencies: @@ -6964,9 +6928,6 @@ importers: '@deepseek-ai/cordis': specifier: workspace:~ version: link:../../../vendor/cordis - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-scope': specifier: workspace:* version: link:../scope @@ -6986,9 +6947,6 @@ importers: '@deepseek-ai/cordis': specifier: workspace:~ version: link:../../../vendor/cordis - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -7014,9 +6972,6 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:* version: link:../agent - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -7053,9 +7008,6 @@ importers: '@deepseek-ai/dsh-credentials': specifier: workspace:* version: link:../credentials - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -7069,9 +7021,6 @@ importers: '@deepseek-ai/cordis': specifier: workspace:~ version: link:../../../vendor/cordis - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants packages/credentials/credentials-local: dependencies: @@ -7282,9 +7231,6 @@ importers: '@deepseek-ai/dsh-agent-loop-testkit': specifier: workspace:* version: link:../../test-support/agent-loop-testkit - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -8479,9 +8425,6 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:* version: link:../../util/brand - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -8775,9 +8718,6 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:* version: link:../../util/brand - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -8823,9 +8763,6 @@ importers: '@deepseek-ai/dsh-goal': specifier: workspace:* version: link:../goal - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -8933,9 +8870,6 @@ importers: '@deepseek-ai/cordis': specifier: workspace:~ version: link:../../../vendor/cordis - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-session': specifier: workspace:* version: link:../../core/session @@ -9271,9 +9205,6 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:* version: link:../../util/brand - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -9302,9 +9233,6 @@ importers: '@deepseek-ai/dsh-commands': specifier: workspace:* version: link:../commands - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-sandbox': specifier: workspace:* version: link:../../sandbox/sandbox @@ -9370,9 +9298,6 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:* version: link:../../util/brand - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -9434,9 +9359,6 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:* version: link:../../util/brand - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-session': specifier: workspace:* version: link:../../core/session @@ -9551,9 +9473,6 @@ importers: '@deepseek-ai/dsh-attachment': specifier: workspace:* version: link:../../attachment/attachment - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants packages/llm/llm-deepseek: dependencies: @@ -9720,7 +9639,7 @@ importers: version: link:../../../vendor/schemastery '@earendil-works/pi-ai': specifier: ^0.87.1 - version: 0.87.1(patch_hash=b9bcce474fb2ac44633dff0fa722816a5bff5451b4575d5874035ea14ba70a4f)(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3) + version: 0.87.1(patch_hash=b9bcce474fb2ac44633dff0fa722816a5bff5451b4575d5874035ea14ba70a4f)(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(supports-color@9.4.0)(ws@8.21.0)(zod@4.4.3) devDependencies: '@deepseek-ai/cordis': specifier: workspace:~ @@ -9792,9 +9711,6 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:* version: link:../../util/brand - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../llm @@ -10111,9 +10027,6 @@ importers: '@deepseek-ai/dsh-commands': specifier: workspace:* version: link:../../interaction/commands - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -10212,9 +10125,6 @@ importers: '@deepseek-ai/dsh-hmr': specifier: workspace:* version: link:../../boot/hmr - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -10323,16 +10233,6 @@ importers: specifier: workspace:* version: link:../../util/timeout - packages/runtime-diagnostics/invariants: - dependencies: - '@deepseek-ai/schemastery': - specifier: link:../../../vendor/schemastery - version: link:../../../vendor/schemastery - devDependencies: - '@deepseek-ai/cordis': - specifier: workspace:~ - version: link:../../../vendor/cordis - packages/sandbox/sandbox: dependencies: '@deepseek-ai/dsh-util-values': @@ -10392,9 +10292,6 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:* version: link:../../core/agent - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-sandbox': specifier: workspace:* version: link:../sandbox @@ -10481,9 +10378,6 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:* version: link:../../util/brand - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -10971,9 +10865,6 @@ importers: '@deepseek-ai/dsh-deepseek-llm-api-extensions': specifier: workspace:* version: link:../../llm/deepseek-llm-api-extensions - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -11227,9 +11118,6 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:* version: link:../../util/brand - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -11400,9 +11288,6 @@ importers: '@deepseek-ai/dsh-hmr': specifier: workspace:* version: link:../../boot/hmr - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-session': specifier: workspace:* version: link:../../core/session @@ -12205,9 +12090,6 @@ importers: '@deepseek-ai/cordis': specifier: workspace:~ version: link:../../../vendor/cordis - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-storage': specifier: workspace:* version: link:../storage @@ -12268,9 +12150,6 @@ importers: '@deepseek-ai/dsh-attachment': specifier: workspace:* version: link:../../attachment/attachment - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-jobs': specifier: workspace:* version: link:../../jobs/jobs @@ -12621,9 +12500,6 @@ importers: '@deepseek-ai/dsh-agent-loop-testkit': specifier: workspace:* version: link:../../test-support/agent-loop-testkit - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -12673,9 +12549,6 @@ importers: '@deepseek-ai/dsh-fs-sandbox': specifier: workspace:* version: link:../../fs/fs-sandbox - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -12728,9 +12601,6 @@ importers: '@deepseek-ai/dsh-bash-local': specifier: workspace:* version: link:../../shell/bash-local - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -12780,9 +12650,6 @@ importers: '@deepseek-ai/dsh-agent': specifier: workspace:* version: link:../../core/agent - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-jobs': specifier: workspace:* version: link:../../jobs/jobs @@ -13404,9 +13271,6 @@ importers: '@deepseek-ai/dsh-agent-loop-testkit': specifier: workspace:* version: link:../../test-support/agent-loop-testkit - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -13548,9 +13412,6 @@ importers: '@deepseek-ai/cordis': specifier: workspace:~ version: link:../../../vendor/cordis - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants packages/util/launch-environment: devDependencies: @@ -13804,9 +13665,6 @@ importers: '@deepseek-ai/dsh-agent-preset-registry': specifier: workspace:* version: link:../../preset/agent-preset-registry - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -13924,9 +13782,6 @@ importers: '@deepseek-ai/dsh-agent-loop-testkit': specifier: workspace:* version: link:../../test-support/agent-loop-testkit - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-jobs': specifier: workspace:* version: link:../../jobs/jobs @@ -13972,9 +13827,6 @@ importers: '@deepseek-ai/dsh-brand': specifier: workspace:* version: link:../../util/brand - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -14015,9 +13867,6 @@ importers: '@deepseek-ai/dsh-http-proxy': specifier: workspace:* version: link:../../util/http-proxy - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../../llm/llm @@ -14079,9 +13928,6 @@ importers: '@deepseek-ai/cordis': specifier: workspace:~ version: link:../../../vendor/cordis - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../runtime-diagnostics/invariants '@deepseek-ai/dsh-session': specifier: workspace:* version: link:../../core/session @@ -14247,9 +14093,6 @@ importers: '@deepseek-ai/dsh-http-proxy': specifier: workspace:* version: link:../../packages/util/http-proxy - '@deepseek-ai/dsh-invariants': - specifier: workspace:* - version: link:../../packages/runtime-diagnostics/invariants '@deepseek-ai/dsh-jobs': specifier: workspace:* version: link:../../packages/jobs/jobs @@ -22695,15 +22538,15 @@ snapshots: transitivePeerDependencies: - '@algolia/client-search' - '@earendil-works/pi-ai@0.87.1(patch_hash=b9bcce474fb2ac44633dff0fa722816a5bff5451b4575d5874035ea14ba70a4f)(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(ws@8.21.0)(zod@4.4.3)': + '@earendil-works/pi-ai@0.87.1(patch_hash=b9bcce474fb2ac44633dff0fa722816a5bff5451b4575d5874035ea14ba70a4f)(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(supports-color@9.4.0)(ws@8.21.0)(zod@4.4.3)': dependencies: '@anthropic-ai/sdk': 0.124.0(zod@4.4.3) '@aws-sdk/client-bedrock-runtime': 3.1127.0 '@earendil-works/pi-telemetry': 0.87.1 '@google/genai': 2.21.0(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)) '@smithy/node-http-handler': 4.12.1 - http-proxy-agent: 9.1.0 - https-proxy-agent: 9.1.0 + http-proxy-agent: 9.1.0(supports-color@9.4.0) + https-proxy-agent: 9.1.0(supports-color@9.4.0) openai: 6.40.0(ws@8.21.0)(zod@4.4.3) partial-json: 0.1.7 typebox: 1.3.27 @@ -26886,7 +26729,7 @@ snapshots: transitivePeerDependencies: - supports-color - http-proxy-agent@9.1.0: + http-proxy-agent@9.1.0(supports-color@9.4.0): dependencies: agent-base: 9.0.0 debug: 4.4.3(supports-color@9.4.0) @@ -26945,7 +26788,7 @@ snapshots: transitivePeerDependencies: - supports-color - https-proxy-agent@9.1.0: + https-proxy-agent@9.1.0(supports-color@9.4.0): dependencies: agent-base: 9.0.0 debug: 4.4.3(supports-color@9.4.0) diff --git a/python/sdk-runtime/package.json b/python/sdk-runtime/package.json index 6fde3d59f4..188f7a188f 100644 --- a/python/sdk-runtime/package.json +++ b/python/sdk-runtime/package.json @@ -57,7 +57,6 @@ "@deepseek-ai/dsh-host-plugin-inventory": "workspace:*", "@deepseek-ai/dsh-host-webserver": "workspace:*", "@deepseek-ai/dsh-http-proxy": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-jobs": "workspace:*", "@deepseek-ai/dsh-jobs-local": "workspace:*", "@deepseek-ai/dsh-launch-environment": "workspace:*", diff --git a/scripts/check-workspace-constraints.ts b/scripts/check-workspace-constraints.ts index db0bfb1951..25643977a1 100644 --- a/scripts/check-workspace-constraints.ts +++ b/scripts/check-workspace-constraints.ts @@ -260,9 +260,6 @@ export function expectedDshPackageFiles(manifest: PackageManifest): readonly str ...typeof manifest.icon === 'string' ? [manifest.icon.replace(/^\.\//u, '')] : [], ...[...localeFiles].sort(), 'lib/index.js', - // Packages with an invariant export publish its runtime as a separate - // bundle; the package-invariant gate validates the source/export pairing. - ...manifest.exports?.['./invariant'] ? ['lib/invariant.js'] : [], ...manifest.bin ? ['lib/bin.js'] : [], // Worker-thread packages ship a CJS worker entry; the browser worker // bundle is an ES module a page loads with `new Worker(type: 'module')`. @@ -487,17 +484,6 @@ export function checkWorkspaceManifest({ dir, manifest }: WorkspaceManifest): st if (rootEntry?.default !== './lib/index.js') { errors.push(`${label}: package.json exports["."].default must be "./lib/index.js"`) } - const invariantRaw = manifest.exports?.['./invariant'] - const invariantExport = typeof invariantRaw === 'object' && invariantRaw !== null ? invariantRaw : undefined - if (invariantExport?.types !== undefined && invariantExport.types !== './lib/types/invariant.d.ts') { - errors.push(`${label}: package.json exports["./invariant"].types must be "./lib/types/invariant.d.ts"`) - } - if (invariantExport?.default !== undefined && invariantExport.default !== './lib/invariant.js') { - errors.push(`${label}: package.json exports["./invariant"].default must be "./lib/invariant.js"`) - } - if (invariantExport && (invariantExport.types === undefined || invariantExport.default === undefined)) { - errors.push(`${label}: package.json exports["./invariant"] must declare both types and default targets`) - } const expectedFiles = expectedDshPackageFiles(manifest) if (!sameStringList(manifest.files, expectedFiles)) { errors.push(`${label}: package.json files must be ${JSON.stringify(expectedFiles)}`) diff --git a/scripts/client-build-environment.client.spec.ts b/scripts/client-build-environment.client.spec.ts index fc239867a3..b1c9c8907e 100644 --- a/scripts/client-build-environment.client.spec.ts +++ b/scripts/client-build-environment.client.spec.ts @@ -234,7 +234,6 @@ describe('client build environment', () => { const configs = clientBundle('@deepseek-ai/dsh-client-ui-sidebar', [ 'lib/types/index.js', - 'lib/types/invariant.js', ])({ env: { DSH_BUILD_FACE: 'client' } }) if (!Array.isArray(configs)) throw new TypeError('client bundle config must be an array') const dynamic = configs.find(config => config.name === '@deepseek-ai/dsh-client-ui-sidebar/client') diff --git a/scripts/client-bundle-css.spec.ts b/scripts/client-bundle-css.spec.ts index b426e541ea..01d34ce91e 100644 --- a/scripts/client-bundle-css.spec.ts +++ b/scripts/client-bundle-css.spec.ts @@ -17,7 +17,7 @@ interface CssPlugin { function cssPlugin(name: 'dsh-css-modules-inline' | 'dsh-css-global-inline' | 'dsh-css-text-inline'): CssPlugin { const configs = clientBundle( '@deepseek-ai/dsh-client-test', - ['lib/types/index.js', 'lib/types/invariant.js'], + ['lib/types/index.js'], )({ env: { DSH_BUILD_FACE: 'client' } }) const client = configs.find(config => config.platform === 'browser') if (client === undefined) throw new Error('client config missing') diff --git a/scripts/client-bundle-purity.spec.ts b/scripts/client-bundle-purity.spec.ts index cf706b334c..f8080e4ad9 100644 --- a/scripts/client-bundle-purity.spec.ts +++ b/scripts/client-bundle-purity.spec.ts @@ -39,7 +39,7 @@ interface InputIsolationPlugin { const REQUESTING_PACKAGE = '@deepseek-ai/dsh-client-ui-conversation' function clientConfigs(id = REQUESTING_PACKAGE) { - return clientBundle(id, ['lib/types/index.js', 'lib/types/invariant.js'])( + return clientBundle(id, ['lib/types/index.js'])( { env: { DSH_BUILD_FACE: 'client' } }, ).filter(config => config.platform === 'browser') } @@ -89,8 +89,6 @@ function clientSourceMapPath(packagePath: string): string { } function purityResolveId(id = REQUESTING_PACKAGE): ResolveId { - // libEntry is spelled at every call site (no default) so the - // package-invariants text check can see the invariant entry per package. const configs = clientConfigs(id) const plugins = (configs[0] as { plugins: { name: string; resolveId?: unknown }[] }).plugins const gate = plugins.find(p => p.name === 'dsh-client-bundle-purity') diff --git a/scripts/doc-typecheck-paths.spec.ts b/scripts/doc-typecheck-paths.spec.ts index 82a88c542f..682606fc1a 100644 --- a/scripts/doc-typecheck-paths.spec.ts +++ b/scripts/doc-typecheck-paths.spec.ts @@ -4,14 +4,14 @@ import { builtDeclarationPath } from './doc-typecheck-paths.ts' describe('builtDeclarationPath', () => { it('maps package source directories and exact entry files to built declarations', () => { expect(builtDeclarationPath('./packages/*/*/src')).toBe('./packages/*/*/lib/types') - expect(builtDeclarationPath('./packages/runtime-diagnostics/invariants/src/index.ts')) - .toBe('./packages/runtime-diagnostics/invariants/lib/types/index.d.ts') - expect(builtDeclarationPath('./packages/core/session/src/invariant.ts')) - .toBe('./packages/core/session/lib/types/invariant.d.ts') + expect(builtDeclarationPath('./packages/core/session/src/index.ts')) + .toBe('./packages/core/session/lib/types/index.d.ts') + expect(builtDeclarationPath('./packages/core/session/src/types.ts')) + .toBe('./packages/core/session/lib/types/types.d.ts') }) it('rejects aliases without a supported source target', () => { - expect(() => builtDeclarationPath('./packages/runtime-diagnostics/invariants/source/index.ts')) + expect(() => builtDeclarationPath('./packages/core/session/source/index.ts')) .toThrow('cannot map workspace source path') }) }) diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 9bcbcda614..d54f5d2f34 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -93,7 +93,6 @@ export const SERVICE_PAGE: Record = { schedule: 'schedule.md', inspector: 'extensions.md', webServer: 'web-server.md', - invariants: 'invariants.md', llm: 'llm-streaming.md', lsp: 'lsp.md', messageFeedback: 'feedback.md', @@ -753,7 +752,6 @@ export const LINK_MAP: Readonly> = { PermissionCatalog: 'permission-presets.md', PresetOption: 'permission-presets.md', PresetSpec: 'permission-presets.md', - InvariantInstaller: 'invariants.md', WebRoute: 'web-server.md', IndexInjection: 'web-server.md', StorageBackend: 'storage.md', @@ -796,14 +794,14 @@ export const LINK_MAP: Readonly> = { ProjectionCheckpoint: 'session-projection.md', DirectoryPickerCapability: 'workspace.md', DirectoryListing: 'workspace.md', - TypertContribution: 'invariants.md', + TypertContribution: 'typert.md', TypertRemoteEventSource: 'typert.md', RemoteEventHostInfo: 'typert.md', - TypertFace: 'invariants.md', - TypertPackageFilter: 'invariants.md', - TypertPackageRecord: 'invariants.md', - TypertSchemaFilter: 'invariants.md', - TypertSchemaRecord: 'invariants.md', + TypertFace: 'typert.md', + TypertPackageFilter: 'typert.md', + TypertPackageRecord: 'typert.md', + TypertSchemaFilter: 'typert.md', + TypertSchemaRecord: 'typert.md', } /** TypeScript lib and pinned framework types with no repository-owned data page. */ @@ -915,7 +913,6 @@ export const TYPE_LINK_EXEMPTIONS: Readonly> = { Translate: 'service-local bound translator is owned by packages/client/i18n/src/index.ts', WebUpgradeRoute: 'upgrade route registration contract is owned by packages/host/webserver/src/index.ts', - InvariantRegistration: 'service-local lifecycle handle is owned by packages/runtime-diagnostics/invariants/README.md', JsonValue: 'JSON value union is owned by packages/core/session/src/json.ts', KnobState: 'projection unit state fields are owned by packages/interaction/permission-presets/README.md', PromptAssembly: 'assembly result is owned by packages/core/system-prompt/README.md', diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index 57ba922462..3968633aa9 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -235,7 +235,7 @@ const SERVICE_ROLES: ServiceRole[] = [ pkg: 'session', title: 'In-memory session store', mode: 'core', - consumers: ['agent-loop', 'agent', 'session-persistence', 'session-query', 'session-query-sqlite', 'subagent-in-process-driver', 'invariants', 'message-feedback'], + consumers: ['agent-loop', 'agent', 'session-persistence', 'session-query', 'session-query-sqlite', 'subagent-in-process-driver', 'message-feedback'], note: 'Owns append-only Session instances and emits the durable session event feed.', }, { @@ -322,14 +322,6 @@ const SERVICE_ROLES: ServiceRole[] = [ mode: 'core', note: 'Carries the picking seam onto the wire: capability gating, cancellation, and the seam-coded failures a browser directory flow discriminates on.', }, - { - key: 'invariants', - pkg: 'invariants', - title: 'Package-owned invariant registry', - mode: 'core', - consumers: ['session', 'agent', 'scope', 'agent-loop'], - note: 'Companion subpaths register owner-local checks; the service owns selection, uniqueness, child fibers, and package-attributed failures.', - }, { key: 'typert', pkg: 'typert-registry', diff --git a/scripts/gen-scoped-events.ts b/scripts/gen-scoped-events.ts deleted file mode 100644 index eecf684d8b..0000000000 --- a/scripts/gen-scoped-events.ts +++ /dev/null @@ -1,399 +0,0 @@ -/** - * Generate dsh-scope's invariant resolver map from the repository TypeScript - * Program. - * - * A scoped event declares `this: Scoped`. Real `scopeTarget(base, key)` - * calls establish the routing-key type for that base. The generator searches - * every event payload parameter and one property level for exactly one type - * equivalent to that key. Each generated resolver compiles against the merged - * `Events` parameter tuple. Zero matches require `@dshScopeScan unsupported`; - * multiple matches are ambiguous and always fail loud. - * - * `pnpm run gen-scoped-events` -> write the generated source - * `pnpm run verify-scoped-events` -> exit 1 when it is stale - */ - -import { existsSync, readFileSync, writeFileSync } from 'node:fs' -import { resolve } from 'node:path' -import ts from 'typescript' -import { pointer, rawJsDoc } from './jsdoc.ts' -import { TypeScriptProject } from './ts-project.ts' - -const root = resolve(import.meta.dirname, '..') -const OUT = 'packages/core/scope/src/scoped-events.generated.ts' -const SCOPE_DOC_MARKER = 'Scope-filtered dispatch' - -interface ScopeTargetContract { - baseType: ts.Type - keyType: ts.Type - source: string -} - -interface SubjectCandidate { - path: string - parameter: number - property?: string - type: ts.Type -} - -interface ScopedEventResolver { - event: string - candidate: SubjectCandidate | null -} - -interface ScopeTag { - present: boolean - unsupported: boolean -} - -/** Program-backed analyzer and renderer for the generated scoped-event resolvers. */ -class ScopedEventGenerator { - private readonly checker: ts.TypeChecker - private readonly packageSources: ts.SourceFile[] - private readonly scopeTargetDeclaration: ts.FunctionDeclaration - private readonly scopedSymbol: ts.Symbol - private readonly violations: string[] = [] - - constructor(private readonly project: TypeScriptProject) { - this.checker = project.checker - this.packageSources = project.sourceFiles().filter((sourceFile) => { - return /^packages\/[^/]+\/[^/]+\/src\/.+\.ts$/.test(project.relativePath(sourceFile)) - }) - this.scopeTargetDeclaration = this.functionDeclaration( - 'packages/core/scope/src/index.ts', - 'scopeTarget', - ) - this.scopedSymbol = this.typeAliasSymbol( - 'packages/core/scope/src/index.ts', - 'Scoped', - ) - } - - /** Render the complete generated TypeScript module or throw every contract violation. */ - render(): string { - const contracts = this.collectScopeTargetContracts() - const resolvers = this.collectScopedEventResolvers(contracts) - if (this.violations.length > 0) { - throw new Error( - `gen-scoped-events: ${this.violations.length} scoped-event contract violation(s):\n` - + this.violations.map(violation => ` - ${violation}`).join('\n'), - ) - } - return [ - '/**', - ' * Generated scoped-event routing-subject resolvers for dsh-scope invariants.', - ' * Do not edit by hand; run `pnpm run gen-scoped-events`.', - ' *', - ' * @module @deepseek-ai/dsh-scope/scoped-events.generated', - ' */', - '', - 'type ScopedSubjectResolver = (args: readonly unknown[]) => unknown', - '', - 'const scopedSubjectResolvers: Readonly> = Object.freeze({', - ...resolvers.map(({ event, candidate }) => { - if (candidate === null) return ` '${event}': null,` - const subject = candidate.property === undefined - ? `args[${candidate.parameter}]` - : `(args[${candidate.parameter}] as Record)[${quote(candidate.property)}]` - return ` '${event}': args => ${subject},` - }), - '})', - '', - '/**', - ' * Resolve the routing key named by one scoped event payload. A null', - ' * resolver means the payload cannot expose its external routing key, so the', - ' * invariant checks carrier presence only.', - ' * @param event - runtime Cordis event name.', - ' * @returns the generated subject resolver, null for presence-only,', - ' * or undefined when the event is not scope-filtered.', - ' */', - 'export function scopedSubjectResolverFor(event: string): ScopedSubjectResolver | null | undefined {', - ' return scopedSubjectResolvers[event]', - '}', - '', - ].join('\n') - } - - /** Resolve one named function declaration from a known source file. */ - private functionDeclaration(relativePath: string, name: string): ts.FunctionDeclaration { - const sourceFile = this.project.sourceFile(relativePath) - const declaration = sourceFile.statements.find((statement): statement is ts.FunctionDeclaration => { - return ts.isFunctionDeclaration(statement) && statement.name?.text === name - }) - if (!declaration) throw new Error(`gen-scoped-events: cannot resolve function ${name} from ${relativePath}`) - return declaration - } - - /** Resolve one named type-alias symbol from a known source file. */ - private typeAliasSymbol(relativePath: string, name: string): ts.Symbol { - const sourceFile = this.project.sourceFile(relativePath) - const declaration = sourceFile.statements.find((statement): statement is ts.TypeAliasDeclaration => { - return ts.isTypeAliasDeclaration(statement) && statement.name.text === name - }) - const symbol = declaration && this.checker.getSymbolAtLocation(declaration.name) - if (!symbol) throw new Error(`gen-scoped-events: cannot resolve type ${name} from ${relativePath}`) - return symbol - } - - /** Collect every real scopeTarget(base, key) base/key type contract. */ - private collectScopeTargetContracts(): ScopeTargetContract[] { - const contracts: ScopeTargetContract[] = [] - const visit = (sourceFile: ts.SourceFile, node: ts.Node): void => { - if (ts.isCallExpression(node) - && this.checker.getResolvedSignature(node)?.declaration === this.scopeTargetDeclaration) { - const base = node.arguments[0] - const key = node.arguments[1] - if (!base || !key) { - const source = pointer(this.project.relativePath(sourceFile), sourceFile, node) - this.violations.push(`${source} calls scopeTarget without base and key arguments`) - } else { - contracts.push({ - baseType: this.checker.getTypeAtLocation(base), - keyType: this.checker.getTypeAtLocation(key), - source: pointer(this.project.relativePath(sourceFile), sourceFile, node), - }) - } - } - ts.forEachChild(node, (child) => { visit(sourceFile, child) }) - } - for (const sourceFile of this.packageSources) visit(sourceFile, sourceFile) - return contracts - } - - /** Collect every Events member and derive its generated resolver. */ - private collectScopedEventResolvers(contracts: readonly ScopeTargetContract[]): ScopedEventResolver[] { - const resolvers: ScopedEventResolver[] = [] - for (const sourceFile of this.packageSources) { - const rel = this.project.relativePath(sourceFile) - const visit = (node: ts.Node): void => { - if (ts.isInterfaceDeclaration(node) && node.name.text === 'Events' && isCordisModuleInterface(node)) { - for (const member of node.members) { - if (!ts.isMethodSignature(member) || !ts.isStringLiteral(member.name)) continue - const event = member.name.text - const raw = rawJsDoc(sourceFile.text, member) - const where = `event '${event}' (${pointer(rel, sourceFile, member)})` - const tag = parseScopeTag(raw, where, this.violations) - const thisParameter = member.parameters.find(isThisParameter) - const scopedBase = thisParameter && this.scopedBaseType(thisParameter) - if (!scopedBase) { - if (raw.includes(SCOPE_DOC_MARKER)) { - this.violations.push( - `${where} documents scope-filtered dispatch but its signature has no this: Scoped<...> receiver`, - ) - } - if (tag.present) { - this.violations.push(`${where} has @dshScopeScan metadata but is not a Scoped event`) - } - continue - } - if (!raw.includes(SCOPE_DOC_MARKER)) { - this.violations.push( - `${where} has this: Scoped<...> but its JSDoc does not explain "${SCOPE_DOC_MARKER}"`, - ) - } - const keyType = this.routingKeyType(where, scopedBase, contracts) - if (!keyType) continue - const candidates = this.subjectCandidates(member) - .filter(candidate => this.typesEquivalent(candidate.type, keyType)) - if (candidates.length > 1) { - this.violations.push( - `${where} has multiple routing-key candidates for ${this.typeText(keyType)}: ` - + candidates.map(candidate => `${candidate.path}: ${this.typeText(candidate.type)}`).join(', '), - ) - continue - } - if (candidates.length === 0) { - if (!tag.unsupported) { - const keyLabel = this.typeText(keyType) - this.violations.push( - `${where} exposes no parameter or one-level property equivalent to routing key type ${keyLabel}; ` - + 'add @dshScopeScan unsupported only when the key is intentionally absent from the payload', - ) - } - resolvers.push({ event, candidate: null }) - continue - } - if (tag.unsupported) { - this.violations.push( - `${where} has unnecessary @dshScopeScan unsupported; ${candidates[0]?.path} exposes the routing key`, - ) - continue - } - resolvers.push({ event, candidate: candidates[0] ?? null }) - } - } - ts.forEachChild(node, visit) - } - visit(sourceFile) - } - return resolvers.sort((left, right) => left.event.localeCompare(right.event)) - } - - /** Extract the Base type from one exact this: Scoped parameter. */ - private scopedBaseType(parameter: ts.ParameterDeclaration): ts.Type | undefined { - const type = this.checker.getTypeAtLocation(parameter) - if (type.aliasSymbol !== this.scopedSymbol) return undefined - return type.aliasTypeArguments?.[0] - } - - /** Resolve one unambiguous key type for a scoped carrier base. */ - private routingKeyType( - where: string, - scopedBase: ts.Type, - contracts: readonly ScopeTargetContract[], - ): ts.Type | undefined { - const matches = contracts.filter((contract) => { - return this.checker.isTypeAssignableTo(this.normalizedType(contract.baseType), this.normalizedType(scopedBase)) - }) - if (matches.length === 0) { - this.violations.push( - `${where} has no matching scopeTarget(base, key) call for carrier base ${this.typeText(scopedBase)}`, - ) - return undefined - } - const keyTypes: ts.Type[] = [] - for (const match of matches) { - if (!keyTypes.some(type => this.typesEquivalent(type, match.keyType))) keyTypes.push(match.keyType) - } - if (keyTypes.length > 1) { - this.violations.push( - `${where} carrier base ${this.typeText(scopedBase)} has inconsistent routing-key types: ` - + matches.map(match => `${this.typeText(match.keyType)} at ${match.source}`).join(', '), - ) - return undefined - } - return keyTypes[0] - } - - /** Enumerate every payload parameter and every accessible one-level property. */ - private subjectCandidates(member: ts.MethodSignature): SubjectCandidate[] { - const candidates: SubjectCandidate[] = [] - let runtimeIndex = 0 - for (const parameter of member.parameters) { - if (isThisParameter(parameter)) continue - const directPath = `args[${runtimeIndex}]` - const parameterType = this.checker.getTypeAtLocation(parameter) - candidates.push({ path: directPath, parameter: runtimeIndex, type: parameterType }) - for (const property of this.checker.getPropertiesOfType(this.normalizedType(parameterType))) { - const name = property.getName() - if (name.startsWith('__@') || hasNonPublicDeclaration(property)) continue - candidates.push({ - path: `${directPath}.${name}`, - parameter: runtimeIndex, - property: name, - type: this.checker.getTypeOfSymbolAtLocation(property, parameter), - }) - } - runtimeIndex += 1 - } - return dedupeCandidates(candidates) - } - - /** Compare exact Program type identities after removing null and undefined. */ - private typesEquivalent(left: ts.Type, right: ts.Type): boolean { - const normalizedLeft = this.normalizedType(left) - const normalizedRight = this.normalizedType(right) - if (normalizedLeft.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) return false - if (normalizedRight.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) return false - return normalizedLeft === normalizedRight - } - - /** Remove null and undefined from a routing or candidate type. */ - private normalizedType(type: ts.Type): ts.Type { - return this.checker.getNonNullableType(type) - } - - /** Render a stable diagnostic type label. */ - private typeText(type: ts.Type): string { - return this.checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation) - } -} - -/** Return whether an Events interface is inside declare module '@deepseek-ai/cordis'. */ -function isCordisModuleInterface(node: ts.InterfaceDeclaration): boolean { - const block = node.parent - const declaration = block.parent - return ts.isModuleBlock(block) - && ts.isModuleDeclaration(declaration) - && ts.isStringLiteral(declaration.name) - && declaration.name.text === '@deepseek-ai/cordis' -} - -/** Return whether a parameter is the explicit TypeScript this receiver. */ -function isThisParameter(parameter: ts.ParameterDeclaration): boolean { - return ts.isIdentifier(parameter.name) && parameter.name.text === 'this' -} - -/** Parse and validate the optional @dshScopeScan unsupported tag. */ -function parseScopeTag(raw: string, where: string, violations: string[]): ScopeTag { - const tags = raw - .replace(/^\/\*\*/, '') - .replace(/\*\/$/, '') - .split('\n') - .map(line => line.replace(/^\s*\*?\s?/, '').trim()) - .filter(line => line.startsWith('@dshScopeScan')) - if (tags.length > 1) violations.push(`${where} has multiple @dshScopeScan tags`) - if (tags.length === 0) return { present: false, unsupported: false } - const unsupported = tags[0] === '@dshScopeScan unsupported' - if (!unsupported) { - violations.push( - `${where} has invalid scoped-event scan metadata '${tags[0]}'; expected '@dshScopeScan unsupported'`, - ) - } - return { present: true, unsupported } -} - -/** Return whether a property has a private or protected declaration. */ -function hasNonPublicDeclaration(symbol: ts.Symbol): boolean { - return (symbol.declarations ?? []).some((declaration) => { - if (!ts.canHaveModifiers(declaration)) return false - return ts.getModifiers(declaration)?.some((modifier) => { - return modifier.kind === ts.SyntaxKind.PrivateKeyword || modifier.kind === ts.SyntaxKind.ProtectedKeyword - }) ?? false - }) -} - -/** Deduplicate candidate paths contributed by merged/intersection types. */ -function dedupeCandidates(candidates: readonly SubjectCandidate[]): SubjectCandidate[] { - const seen = new Set() - return candidates.filter((candidate) => { - if (seen.has(candidate.path)) return false - seen.add(candidate.path) - return true - }) -} - -/** Quote a generated property key as a single-quoted TypeScript string. */ -function quote(value: string): string { - return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'` -} - -/** - * Render the generated scoped-event resolver module for one repository root. - * @param projectRoot - repository root carrying tsconfig.host.json. - * @returns complete generated TypeScript source. - */ -export function renderScopedEvents(projectRoot: string = root): string { - return new ScopedEventGenerator(new TypeScriptProject(projectRoot)).render() -} - -/** Generate or freshness-check the fixed dsh-scope source file. */ -function main(): void { - const content = renderScopedEvents() - const output = resolve(root, OUT) - if (process.argv.includes('--check')) { - const committed = existsSync(output) ? readFileSync(output, 'utf8') : null - if (committed === content) { - console.log(`gen-scoped-events: ${OUT} is up to date.`) - return - } - console.error(`gen-scoped-events: ${OUT} is stale. Run \`pnpm run gen-scoped-events\` and commit it.`) - process.exit(1) - } - writeFileSync(output, content) - console.log(`gen-scoped-events: wrote ${OUT}.`) -} - -if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) { - main() -} diff --git a/scripts/gen-tsconfig-paths.spec.ts b/scripts/gen-tsconfig-paths.spec.ts index c67a4906c1..ef1b48b08a 100644 --- a/scripts/gen-tsconfig-paths.spec.ts +++ b/scripts/gen-tsconfig-paths.spec.ts @@ -20,7 +20,6 @@ describe('generated tsconfig package aliases', () => { expect(session).toEqual({ specifier: '@deepseek-ai/dsh-session', source: './packages/core/session/src', - hasInvariant: true, }) // Sorted, so a package added anywhere lands in a stable spot in the diff. expect([...aliases].sort((a, b) => a.specifier.localeCompare(b.specifier))).toEqual(aliases) @@ -31,15 +30,15 @@ describe('generated tsconfig package aliases', () => { it('yields to a hand-written alias and closes without a trailing comma', () => { const aliases = [ - { specifier: '@deepseek-ai/dsh-a', source: './packages/g/a/src', hasInvariant: true }, - { specifier: '@deepseek-ai/dsh-b', source: './packages/g/b/src', hasInvariant: false }, + { specifier: '@deepseek-ai/dsh-a', source: './packages/g/a/src' }, + { specifier: '@deepseek-ai/dsh-b', source: './packages/g/b/src' }, + { specifier: '@deepseek-ai/dsh-c', source: './packages/g/c/src' }, ] const body = renderAliases(aliases, new Set(['@deepseek-ai/dsh-a'])) - // The hand-written bare alias is skipped; its /invariant sibling is not. expect(body).toBe([ - ' "@deepseek-ai/dsh-a/invariant": ["./packages/g/a/src/invariant.ts"]', ' "@deepseek-ai/dsh-b": ["./packages/g/b/src"]', + ' "@deepseek-ai/dsh-c": ["./packages/g/c/src"]', ].join(',\n')) expect(body.endsWith(',')).toBe(false) }) @@ -72,7 +71,7 @@ describe('generated tsconfig package aliases', () => { // resolve through the workspace symlink to built lib/types instead. expect(uncoveredPackages( ['@deepseek-ai/dsh-a', '@deepseek-ai/dsh-b'], - new Set(['@deepseek-ai/dsh-a', '@deepseek-ai/dsh-a/invariant']), + new Set(['@deepseek-ai/dsh-a']), )).toEqual(['@deepseek-ai/dsh-b']) expect(uncoveredPackages(['@deepseek-ai/dsh-a'], new Set(['@deepseek-ai/dsh-a']))).toEqual([]) @@ -89,10 +88,9 @@ describe('generated tsconfig package aliases', () => { it('leaves no wildcard that probes every package group', () => { const config = readFileSync(resolve(root, 'tsconfig.base.json'), 'utf8') - // These two listed one candidate per group, so resolving a package late in - // the list cost a filesystem probe — and under tsx a decorated module + // A wildcard lists one candidate per group, so resolving a package late in + // the list costs a filesystem probe — and under tsx a decorated module // error — for every group before it. expect(config).not.toContain('"@deepseek-ai/dsh-*":') - expect(config).not.toContain('"@deepseek-ai/dsh-*/invariant":') }) }) diff --git a/scripts/gen-tsconfig-paths.ts b/scripts/gen-tsconfig-paths.ts index 7ee6322067..0589347c5c 100644 --- a/scripts/gen-tsconfig-paths.ts +++ b/scripts/gen-tsconfig-paths.ts @@ -2,10 +2,9 @@ * Expand the workspace path aliases that a wildcard would otherwise resolve by * probing every package group in turn. * - * `tsconfig.base.json` is the resolution facade for the whole repository, and - * two of its aliases used one key per *group* rather than per package: - * `@deepseek-ai/dsh-*` listed 49 candidate globs and `@deepseek-ai/dsh-*\/invariant` - * listed 45. TypeScript and tsx try those candidates in order, so a specifier + * `tsconfig.base.json` is the resolution facade for the whole repository. A + * `@deepseek-ai/dsh-*` wildcard would list one candidate glob per package + * group. TypeScript and tsx try those candidates in order, so a specifier * whose package sits late in the list pays for every earlier miss. Under tsx's * ESM hook each miss is an `ERR_MODULE_NOT_FOUND` that Node decorates with a * full CommonJS resolution walk, which dominated source-launch boot. @@ -37,8 +36,6 @@ interface PackageAlias { readonly specifier: string /** Repository-relative source directory, e.g. `./packages/session/session/src`. */ readonly source: string - /** Whether the package carries `src/invariant.ts`, which earns a second alias. */ - readonly hasInvariant: boolean } /** @@ -62,7 +59,6 @@ function packageName(manifest: string): string | undefined { interface WorkspacePackage { readonly group: string readonly directory: string - readonly packageDir: string readonly name: string } @@ -80,7 +76,7 @@ function workspacePackages(): WorkspacePackage[] { const packageDir = join(groupDir, directory) const name = packageName(join(packageDir, 'package.json')) if (name === undefined || !name.startsWith(PREFIX)) continue - if (existsSync(join(packageDir, 'src'))) found.push({ group, directory, packageDir, name }) + if (existsSync(join(packageDir, 'src'))) found.push({ group, directory, name }) } } return found @@ -100,7 +96,7 @@ function workspacePackages(): WorkspacePackage[] { */ export function collectPackageAliases(): PackageAlias[] { const bySpecifier = new Map() - for (const { group, directory, packageDir, name } of workspacePackages()) { + for (const { group, directory, name } of workspacePackages()) { if (name !== `${PREFIX}${directory}`) continue const previous = bySpecifier.get(name) if (previous !== undefined) { @@ -112,12 +108,11 @@ export function collectPackageAliases(): PackageAlias[] { bySpecifier.set(name, { specifier: name, source: `./packages/${group}/${directory}/src`, - hasInvariant: existsSync(join(packageDir, 'src', 'invariant.ts')), directory: `${group}/${directory}`, }) } return [...bySpecifier.values()] - .map(({ specifier, source, hasInvariant }) => ({ specifier, source, hasInvariant })) + .map(({ specifier, source }) => ({ specifier, source })) .sort((left, right) => left.specifier.localeCompare(right.specifier)) } @@ -182,10 +177,6 @@ export function renderAliases(aliases: readonly PackageAlias[], handWritten: Rea if (!handWritten.has(alias.specifier)) { lines.push(` ${JSON.stringify(alias.specifier)}: [${JSON.stringify(alias.source)}]`) } - const invariant = `${alias.specifier}/invariant` - if (alias.hasInvariant && !handWritten.has(invariant)) { - lines.push(` ${JSON.stringify(invariant)}: [${JSON.stringify(`${alias.source}/invariant.ts`)}]`) - } } // The region closes `paths`, so the last member carries no trailing comma. return lines.join(',\n') diff --git a/scripts/no-unknown-casts.baseline.json b/scripts/no-unknown-casts.baseline.json index 37059ea8fd..b94f4b25a7 100644 --- a/scripts/no-unknown-casts.baseline.json +++ b/scripts/no-unknown-casts.baseline.json @@ -904,16 +904,9 @@ "336d4996145aa5694f6c8fd39fee7d248ba667330de106330224e92fd4511554": 1, "6061de0a1e01547eb1abf9d4e1b5fa79fda112fa35dda980775491123413dcce": 1 }, - "packages/core/agent/tests/invariant.spec.ts": { - "2ed6179a261a6cd1d9e84d8e8c5858eef36330853f2af62ea4222e00a9e578a3": 1 - }, "packages/core/scope/src/index.ts": { "e590ead7cd68f7ec90a646dc2622e3631bf63ee3e7a04cf4c39a4f1b07184865": 1 }, - "packages/core/scope/tests/invariant.spec.ts": { - "001bfd2c7b05e2c76752d6895be67c0640b43aed6b8c9725c7321f02db6de745": 1, - "6cd200a3be79594ca783f345b6047758c19a4c2d744dbf4564ef78ccba0433ca": 1 - }, "packages/core/scope/tests/store.spec.ts": { "612bd4047fa63f957ecb33d7fc7544c489301c3c0592e9a42dafe2cb85311165": 1 }, @@ -1453,9 +1446,6 @@ "packages/plan/plan-mode/src/index.ts": { "1422d291f0118635ac06f265a9018dd86dbbd4a97f76a7ed5abb5c2a42e81b53": 1 }, - "packages/plan/plan-mode/tests/invariant.spec.ts": { - "6f54ffd70daee0f9ad7b6d42e14b6aae5af1ec5a39004d541ca224facc2ad576": 1 - }, "packages/plan/plan-mode/tests/plan-mode.spec.ts": { "036770a58292b891357228338d9876329b657defb6371ecb0b7133fc117771ce": 1, "0ca57693819b64476ce22f00311089435afd085d4a38cfff61852dbf3cfa38c3": 1, @@ -1678,9 +1668,6 @@ "packages/session/session-log-deepseek/tests/feedback-composition.spec.ts": { "59c63f734e8bba862325f776e3fcbebd78a84cfb4161f8df41c216950d9bf351": 1 }, - "packages/session/session-log-deepseek/tests/invariant.spec.ts": { - "7f99ec5e224c4475fc9ce0069040eeef36add5dd27d0941e74a814786ad90062": 1 - }, "packages/session/session-log-deepseek/tests/upload.spec.ts": { "0c25d15bf9489ae7483b0a31a64a047c12174602071e0aa29714e0f9196889c6": 1, "119461c1a63f392b4db54f75a86f852f49b72b7f0342c04d1bc1c8d1677f37c1": 3, @@ -1848,9 +1835,6 @@ "packages/storage/storage-domain/src/index.ts": { "0fae329186de631f08edbf22f01cbfbf15d7c1ea8dce64e4cefa8ad0a8d43dea": 1 }, - "packages/storage/storage-domain/tests/invariant.spec.ts": { - "9199dc978ec64150a1a706ca2e3e5a39925f3d6011c6c0b5453c4e41b89cd896": 1 - }, "packages/subagent/subagent-acp/tests/subagent-acp.e2e.ts": { "daa15206e19d6a8fbae646d678889b2e6933ece824e48dd06c95d2096861d5c1": 1 }, @@ -2243,9 +2227,6 @@ "63a77f914e85aa8d63dd649be95c82f64756e06e8faa0a9aa3b53d14daebd2e5": 1, "9cf0e19dc31ae2d244530fb4853d2877c0490271a5f930ad330e210051b41cd8": 1 }, - "scripts/test-invariants.spec.ts": { - "1d25e54371f9bac32d21541bfe094eb3eb3bfc39e010982c9f25bb8b0f3bea14": 1 - }, "snapshots/sdk/sdk.snapshot.ts": { "782d52268c631008e0fec08f76f87751765690287d5acdfae45021a754900290": 1 }, diff --git a/scripts/package-invariants.spec.ts b/scripts/package-invariants.spec.ts deleted file mode 100644 index 1321d9fc3c..0000000000 --- a/scripts/package-invariants.spec.ts +++ /dev/null @@ -1,294 +0,0 @@ -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, -} from './package-invariants.ts' -import { usesFlattenedPackageDependencies } from './package-dependency-policy.ts' - -const roots: string[] = [] - -afterEach(() => { - for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }) -}) - -function handwrittenInvariant(packageName: string): string { - return ` -export const name = 'probe-invariant' -export const inject = ['invariants'] -const install = (ctx: { on(name: string, listener: (value: number) => void): void }, fail: (message: string) => never) => { - ctx.on('probe/value', (value) => { - if (value < 0) fail('observed values must be non-negative') - }) -} -export const apply = (ctx: { invariants: { register(name: string, install: typeof install): () => void } }) => - Promise.resolve(ctx.invariants.register(${JSON.stringify(packageName)}, install)) -` -} - -function fixture(options: { - companion?: boolean - packageName?: string - packageDirectory?: string - source?: string - clientDeclaration?: boolean - clientExport?: boolean - invariantExport?: boolean - invariantFile?: boolean - invariantDependency?: boolean - invariantRange?: string - invariantReference?: boolean - buildEntry?: boolean - omissionReason?: boolean -} = {}): string { - const root = mkdtempSync(join(tmpdir(), 'dsh-package-invariants-')) - roots.push(root) - const packageDirectory = options.packageDirectory ?? 'packages/core/probe' - const dir = join(root, packageDirectory) - mkdirSync(join(dir, 'src'), { recursive: true }) - const packageName = options.packageName ?? '@deepseek-ai/dsh-probe' - const companion = options.companion ?? true - const invariantExport = options.invariantExport ?? companion - const invariantFile = options.invariantFile ?? companion - const invariantDependency = options.invariantDependency ?? companion - const invariantReference = options.invariantReference ?? companion - const buildEntry = options.buildEntry ?? companion - const exports = { - ...(invariantExport ? { './invariant': { - types: './lib/types/invariant.d.ts', - default: './lib/invariant.js', - } } : {}), - ...(options.clientExport === true ? { - './client': { - types: './lib/types/client/index.d.ts', - default: './lib/client.js', - }, - } : {}), - } - const dsh = options.clientDeclaration === true ? { client: {} } : undefined - const developmentOnlyInvariant = usesFlattenedPackageDependencies( - `${packageDirectory}/package.json`, - packageName, - dsh, - ) - const manifest = { - name: packageName, - ...(dsh === undefined ? {} : { dsh }), - exports, - files: ['lib/index.js', ...invariantFile ? ['lib/invariant.js'] : []], - peerDependencies: !invariantDependency || developmentOnlyInvariant ? {} : { - '@deepseek-ai/dsh-invariants': options.invariantRange ?? 'workspace:*', - }, - devDependencies: !invariantDependency ? {} : { - '@deepseek-ai/dsh-invariants': options.invariantRange ?? 'workspace:*', - }, - } - writeFileSync(join(dir, 'package.json'), `${JSON.stringify(manifest, null, 2)}\n`) - writeFileSync(join(dir, 'tsconfig.json'), `${JSON.stringify({ - references: invariantReference ? [{ path: '../../runtime-diagnostics/invariants' }] : [], - }, null, 2)}\n`) - if (companion) { - writeFileSync(join(dir, 'src/invariant.ts'), options.source ?? handwrittenInvariant(packageName)) - } - writeFileSync( - join(dir, 'tsdown.config.ts'), - buildEntry ? "export default { entry: ['lib/types/index.js', 'lib/types/invariant.js'] }\n" : "export default { entry: ['lib/types/index.js'] }\n", - ) - writeFileSync( - join(dir, 'README.md'), - options.omissionReason === false ? '# Probe\n' : '# Probe\n\nNo runtime invariant companion is published because this fixture owns no diverging observations.\n', - ) - return root -} - -describe('package invariant gate', () => { - it('accepts a hand-owned checking companion with publication metadata', () => { - expect(collectPackageInvariantViolations(fixture())).toEqual([]) - }) - - it('rejects caret ranges for the invariant peer and development dependency', () => { - expect(collectPackageInvariantViolations(fixture({ invariantRange: 'workspace:^' }))).toEqual([ - { path: 'packages/core/probe/package.json', message: '@deepseek-ai/dsh-invariants must be a workspace:* peerDependency' }, - { path: 'packages/core/probe/package.json', message: '@deepseek-ai/dsh-invariants must be a workspace:* devDependency' }, - ]) - }) - - it('accepts a package that cleanly omits an invariant companion', () => { - const root = fixture({ companion: false }) - expect(collectPackageInvariantViolations(root)).toEqual([]) - expect(packageInvariantOwners(root)).toEqual([]) - }) - - it('requires an omitted companion to have a README reason sentence', () => { - const violations = collectPackageInvariantViolations(fixture({ - companion: false, - omissionReason: false, - })) - expect(violations).toContainEqual({ - path: 'packages/core/probe/README.md', - message: 'omitted companion requires a README "No ... companion is published" reason sentence', - }) - }) - - it('accepts development-only invariants for configured Host dependencies', () => { - expect(collectPackageInvariantViolations(fixture({ packageName: '@deepseek-ai/dsh-llm' }))).toEqual([]) - }) - - it('accepts development-only invariants for client packages', () => { - expect(collectPackageInvariantViolations(fixture({ - packageName: '@deepseek-ai/dsh-client-probe', - packageDirectory: 'packages/client/probe', - }))).toEqual([]) - }) - - it('accepts development-only invariants for packages with a dsh.client entry', () => { - expect(collectPackageInvariantViolations(fixture({ clientDeclaration: true, clientExport: true }))).toEqual([]) - }) - - it('keeps invariant peers for packages that only export a Client API', () => { - expect(collectPackageInvariantViolations(fixture({ clientExport: true }))).toEqual([]) - }) - - it('keeps invariant peers for experimental packages with a dsh.client entry', () => { - expect(collectPackageInvariantViolations(fixture({ - packageDirectory: 'packages/experimental/probe', - clientDeclaration: true, - clientExport: true, - }))).toEqual([]) - }) - - it('accepts an invariant reference owned by a package-local leaf project', () => { - const root = fixture({ invariantReference: false }) - const dir = join(root, 'packages/core/probe') - writeFileSync(join(dir, 'tsconfig.json'), `${JSON.stringify({ - files: [], - references: [{ path: './tsconfig.host.json' }], - }, null, 2)}\n`) - writeFileSync(join(dir, 'tsconfig.host.json'), `${JSON.stringify({ - references: [{ path: '../../runtime-diagnostics/invariants' }], - }, null, 2)}\n`) - - expect(collectPackageInvariantViolations(root)).toEqual([]) - }) - - it('rejects missing publication metadata and build output', () => { - const violations = collectPackageInvariantViolations(fixture({ - invariantExport: false, - invariantFile: 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 publication and build wiring left behind after omission', () => { - const violations = collectPackageInvariantViolations(fixture({ - companion: false, - invariantExport: true, - invariantFile: true, - invariantReference: true, - buildEntry: true, - })) - expect(violations.map(violation => violation.message)).toEqual(expect.arrayContaining([ - expect.stringContaining('exports["./invariant"] must be omitted'), - expect.stringContaining('files must omit lib/invariant.js'), - expect.stringContaining('TypeScript project references must omit ../../runtime-diagnostics/invariants'), - expect.stringContaining('build override must omit 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 -const install = (_ctx: unknown, fail: (message: string) => never) => { fail('probe') } -export const apply = (ctx: { invariants: { register(name: string, install: typeof install): () => void } }) => { - ctx.invariants.register('@deepseek-ai/dsh-foreign', install) - return ctx.invariants.register(selected!, install) -} -` - 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 generated markers and reporter-free executable installers', () => { - const generated = fixture({ - source: `/** @generated */\n${handwrittenInvariant('@deepseek-ai/dsh-probe')}`, - }) - expect(collectPackageInvariantViolations(generated).map(violation => violation.message)) - .toContain('invariant companions must be hand-owned and may not carry @generated markers') - - const reporterFree = fixture({ - source: ` -export const name = 'probe-invariant' -export const inject = ['invariants'] -const install = () => { void 0 } -export const apply = (ctx: { invariants: { register(name: string, install: typeof install): () => void } }) => - Promise.resolve(ctx.invariants.register('@deepseek-ai/dsh-probe', install)) -`, - }) - expect(collectPackageInvariantViolations(reporterFree).map(violation => violation.message)) - .toContain('install function must accept the bound failure reporter as its second parameter') - - const unused = fixture({ - source: ` -export const name = 'probe-invariant' -export const inject = ['invariants'] -const install = (_ctx: unknown, _fail: (message: string) => never) => { void 0 } -export const apply = (ctx: { invariants: { register(name: string, install: typeof install): () => void } }) => - Promise.resolve(ctx.invariants.register('@deepseek-ai/dsh-probe', install)) -`, - }) - expect(collectPackageInvariantViolations(unused).map(violation => violation.message)) - .toContain('install function must use its bound failure reporter') - }) - - it('rejects registering a different installer than the checked local function', () => { - const decoy = fixture({ - source: ` -export const name = 'probe-invariant' -export const inject = ['invariants'] -const install = (_ctx: unknown, fail: (message: string) => never) => { fail('checked decoy') } -export const apply = (ctx: { invariants: { register(name: string, install: () => void): () => void } }) => - ctx.invariants.register('@deepseek-ai/dsh-probe', () => {}) -`, - }) - expect(collectPackageInvariantViolations(decoy).map(violation => violation.message)) - .toContain('line 6: ctx.invariants.register must use the checked local install function') - }) - - it.each([ - 'export default { name, inject, apply }', - "export * as default from './probe.ts'", - ])('rejects a default export that would collapse the Loader namespace', (defaultExport) => { - const source = `${handwrittenInvariant('@deepseek-ai/dsh-probe')}\n${defaultExport}\n` - expect(collectPackageInvariantViolations(fixture({ source })).map(violation => violation.message)) - .toContain('must not default-export; Loader must retain the companion namespace') - }) - - it('rejects empty installers because packages without a check omit the companion', () => { - const source = ` -export const name = 'probe-invariant' -export const inject = ['invariants'] -const PACKAGE_NAME = '@deepseek-ai/dsh-probe' -const install = () => {} -export const apply = (ctx: { invariants: { register(name: string, install: () => void): () => void } }) => - ctx.invariants.register(PACKAGE_NAME, install) -` - expect(collectPackageInvariantViolations(fixture({ source })).map(violation => violation.message)) - .toContain('empty install function is unnecessary; omit the companion and its publication wiring') - }) -}) diff --git a/scripts/package-invariants.ts b/scripts/package-invariants.ts deleted file mode 100644 index ef03e60183..0000000000 --- a/scripts/package-invariants.ts +++ /dev/null @@ -1,418 +0,0 @@ -/** - * Package-invariant companion discovery and structural checks. - * The runtime registry stays product-independent; this gate keeps each - * published companion complete without requiring synthetic empty companions. - */ - -import { existsSync, globSync, readFileSync } from 'node:fs' -import { dirname, relative, resolve, sep } from 'node:path' -import ts from 'typescript' -import { usesFlattenedPackageDependencies } from './package-dependency-policy.ts' - -/** Package README sentence that records why an invariant companion is omitted. */ -const OMITTED_COMPANION_REASON = /No (?:(?:runtime )?invariant )?companion is published(?: because|[.:;—])\s+\S/i - -interface PackageManifest { - name?: string - dsh?: unknown - exports?: Record - files?: string[] - peerDependencies?: Record - devDependencies?: Record -} - -/** One package and the files participating in its invariant publication rules. */ -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 packages that own an invariant companion. */ -export function packageInvariantOwners(root: string): PackageInvariantOwner[] { - return packageInvariantPackages(root) - .filter(owner => existsSync(resolve(root, owner.sourcePath))) -} - -/** Discover every package under the repository package tree. */ -function packageInvariantPackages(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, - } - }) -} - -/** Return all violations of the package-invariant companion rules. */ -export function collectPackageInvariantViolations(root: string): PackageInvariantViolation[] { - const violations: PackageInvariantViolation[] = [] - for (const owner of packageInvariantPackages(root)) { - const manifest = readManifest(resolve(root, owner.manifestPath)) - const hasCompanion = existsSync(resolve(root, owner.sourcePath)) - checkManifest(owner, manifest, hasCompanion, violations) - checkBuild(owner, root, hasCompanion, violations) - if (hasCompanion) { - checkSource(owner, root, violations) - } else { - checkOmissionReason(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, - hasCompanion: boolean, - violations: PackageInvariantViolation[], -): void { - const invariantExport = manifest.exports?.['./invariant'] - if (!hasCompanion) { - if (invariantExport !== undefined) { - addViolation( - violations, - owner.manifestPath, - 'exports["./invariant"] must be omitted when src/invariant.ts is absent', - ) - } - if (manifest.files?.includes('lib/invariant.js')) { - addViolation( - violations, - owner.manifestPath, - 'files must omit lib/invariant.js when src/invariant.ts is absent', - ) - } - return - } - if (typeof invariantExport !== 'object' - || invariantExport === null - || 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 - const developmentOnlyInvariant = usesFlattenedPackageDependencies( - owner.manifestPath, - owner.packageName, - manifest.dsh, - ) - const expectedRange = 'workspace:*' - const peerRange = manifest.peerDependencies?.['@deepseek-ai/dsh-invariants'] - if (developmentOnlyInvariant ? peerRange !== undefined : peerRange !== expectedRange) { - addViolation(violations, owner.manifestPath, developmentOnlyInvariant - ? '@deepseek-ai/dsh-invariants must not be a peerDependency under this package dependency policy' - : '@deepseek-ai/dsh-invariants must be a workspace:* peerDependency') - } - if (manifest.devDependencies?.['@deepseek-ai/dsh-invariants'] !== expectedRange) { - addViolation( - violations, - owner.manifestPath, - `@deepseek-ai/dsh-invariants must be a ${expectedRange} devDependency`, - ) - } -} - -function checkBuild( - owner: PackageInvariantOwner, - root: string, - hasCompanion: boolean, - violations: PackageInvariantViolation[], -): void { - const tsconfigPath = `${owner.dir}/tsconfig.json` - if (hasCompanion - && owner.packageName !== '@deepseek-ai/dsh-invariants' - && !projectReferencesInvariants(root, owner.dir, tsconfigPath)) { - addViolation( - violations, - tsconfigPath, - 'TypeScript project references must include ../../runtime-diagnostics/invariants', - ) - } else if (!hasCompanion && projectReferencesInvariants(root, owner.dir, tsconfigPath)) { - addViolation( - violations, - tsconfigPath, - 'TypeScript project references must omit ../../runtime-diagnostics/invariants when src/invariant.ts is absent', - ) - } - - const configPath = `${owner.dir}/tsdown.config.ts` - if (!existsSync(resolve(root, configPath))) return - const source = readFileSync(resolve(root, configPath), 'utf8') - const bundlesCompanion = source.includes('lib/types/invariant.js') - if (hasCompanion && !bundlesCompanion) { - addViolation(violations, configPath, 'package build override must bundle lib/types/invariant.js') - } else if (!hasCompanion && bundlesCompanion) { - addViolation( - violations, - configPath, - 'package build override must omit lib/types/invariant.js when src/invariant.ts is absent', - ) - } -} - -function checkOmissionReason( - owner: PackageInvariantOwner, - root: string, - violations: PackageInvariantViolation[], -): void { - const readmePath = `${owner.dir}/README.md` - const absolutePath = resolve(root, readmePath) - if (!existsSync(absolutePath) || !OMITTED_COMPANION_REASON.test(readFileSync(absolutePath, 'utf8'))) { - addViolation( - violations, - readmePath, - 'omitted companion requires a README "No ... companion is published" reason sentence', - ) - } -} - -function projectReferencesInvariants(root: string, ownerDir: string, entryPath: string): boolean { - const ownerRoot = resolve(root, ownerDir) - const target = resolve(root, 'packages/runtime-diagnostics/invariants') - const pending = [resolve(root, entryPath)] - const visited = new Set() - while (pending.length > 0) { - const configPath = pending.pop() - if (configPath === undefined) break - if (visited.has(configPath)) continue - visited.add(configPath) - const config = JSON.parse(readFileSync(configPath, 'utf8')) as { - references?: Array<{ path?: string }> - } - for (const reference of config.references ?? []) { - if (reference.path === undefined) continue - const referenced = resolve(dirname(configPath), reference.path) - if (referenced === target) return true - if (!referenced.startsWith(`${ownerRoot}${sep}`)) continue - const childConfig = referenced.endsWith('.json') ? referenced : resolve(referenced, 'tsconfig.json') - if (existsSync(childConfig)) pending.push(childConfig) - } - } - return false -} - -function checkSource( - owner: PackageInvariantOwner, - root: string, - violations: PackageInvariantViolation[], -): void { - const absolutePath = resolve(root, owner.sourcePath) - const sourceText = readFileSync(absolutePath, 'utf8') - if (sourceText.includes('@generated')) { - addViolation( - violations, - owner.sourcePath, - 'invariant companions must be hand-owned and may not carry @generated markers', - ) - } - - const sourceFile = ts.createSourceFile( - absolutePath, - sourceText, - ts.ScriptTarget.Latest, - true, - ts.ScriptKind.TS, - ) - const constants = topLevelStringConstants(sourceFile) - const registrations: string[] = [] - const unresolved: number[] = [] - const mismatchedInstallers: number[] = [] - const visit = (node: ts.Node): void => { - if (ts.isCallExpression(node) && isInvariantRegistration(node.expression)) { - const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1 - const argument = node.arguments[0] - const packageName = argument === undefined ? undefined : stringValue(argument, constants) - if (packageName === undefined) unresolved.push(line) - else registrations.push(packageName) - const installer = node.arguments[1] - if (installer === undefined || !ts.isIdentifier(installer) || installer.text !== 'install') { - mismatchedInstallers.push(line) - } - } - 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`, - ) - } - for (const line of mismatchedInstallers) { - addViolation( - violations, - owner.sourcePath, - `line ${line}: ctx.invariants.register must use the checked local install function`, - ) - } - 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}`) - } - } - if (hasDefaultExport(sourceFile)) { - addViolation(violations, owner.sourcePath, 'must not default-export; Loader must retain the companion namespace') - } - checkInstaller(owner, sourceFile, violations) -} - -function checkInstaller( - owner: PackageInvariantOwner, - sourceFile: ts.SourceFile, - violations: PackageInvariantViolation[], -): void { - let initializer: ts.Expression | undefined - for (const statement of sourceFile.statements) { - if (!ts.isVariableStatement(statement)) continue - for (const declaration of statement.declarationList.declarations) { - if (ts.isIdentifier(declaration.name) - && declaration.name.text === 'install' - && declaration.initializer !== undefined) { - initializer = declaration.initializer - } - } - } - const installer = initializer === undefined ? undefined : installerFunction(initializer) - if (installer === undefined) { - addViolation(violations, owner.sourcePath, 'must declare a local install function for package-owned checks') - return - } - if (ts.isBlock(installer.body) && installer.body.statements.length === 0) { - addViolation( - violations, - owner.sourcePath, - 'empty install function is unnecessary; omit the companion and its publication wiring', - ) - return - } - const reporter = installer.parameters[1]?.name - if (reporter === undefined || !ts.isIdentifier(reporter)) { - addViolation(violations, owner.sourcePath, 'install function must accept the bound failure reporter as its second parameter') - return - } - if (!usesIdentifier(installer.body, reporter.text)) { - addViolation(violations, owner.sourcePath, 'install function must use its bound failure reporter') - } -} - -function usesIdentifier(node: ts.Node, name: string): boolean { - return ts.isIdentifier(node) && node.text === name - || node.getChildren().some(child => usesIdentifier(child, name)) -} - -function installerFunction( - initializer: ts.Expression, -): ts.ArrowFunction | ts.FunctionExpression | undefined { - if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) return initializer - if (ts.isCallExpression(initializer) - && ts.isPropertyAccessExpression(initializer.expression) - && ts.isIdentifier(initializer.expression.expression) - && initializer.expression.expression.text === 'Object' - && initializer.expression.name.text === 'assign') { - const target = initializer.arguments[0] - if (target !== undefined && (ts.isArrowFunction(target) || ts.isFunctionExpression(target))) return target - } - return undefined -} - -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) - }) -} - -function hasDefaultExport(sourceFile: ts.SourceFile): boolean { - return sourceFile.statements.some((statement) => { - if (ts.isExportAssignment(statement)) return true - const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : undefined - if (modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword)) return true - if (!ts.isExportDeclaration(statement) || statement.exportClause === undefined) return false - if (ts.isNamespaceExport(statement.exportClause)) { - return statement.exportClause.name.text === 'default' - } - return statement.exportClause.elements.some(element => element.name.text === 'default') - }) -} - -/** 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/project-doc-site.spec.ts b/scripts/project-doc-site.spec.ts index 6f22a0f1e0..a35da334b7 100644 --- a/scripts/project-doc-site.spec.ts +++ b/scripts/project-doc-site.spec.ts @@ -450,7 +450,7 @@ describe('docsPages locale routes', () => { const translated = rootPages.filter(page => page.contentLocale === 'zh-CN') const fallbacks = rootPages.filter(page => page.contentLocale === 'en-US') - expect(translated).toHaveLength(48) + expect(translated).toHaveLength(47) expect(translated.every(page => page.source.endsWith('.zh.md'))).toBe(true) expect(fallbacks).toEqual([]) }) diff --git a/scripts/publish-npm-baseline.ts b/scripts/publish-npm-baseline.ts index 31309b55d8..baf51cb112 100644 --- a/scripts/publish-npm-baseline.ts +++ b/scripts/publish-npm-baseline.ts @@ -561,7 +561,6 @@ class BaselinePackager { ) this.runner.run('pnpm', ['run', 'build'], worktree.path) this.runner.run('pnpm', ['run', 'publint'], worktree.path) - this.runner.run('pnpm', ['run', 'verify-built-package-invariants'], worktree.path) this.runner.run('pnpm', [ '--filter', './vendor/**', '--filter', './packages/**', diff --git a/scripts/python-snapshot-workflow-order.spec.ts b/scripts/python-snapshot-workflow-order.spec.ts index 964244f5da..db1b67e626 100644 --- a/scripts/python-snapshot-workflow-order.spec.ts +++ b/scripts/python-snapshot-workflow-order.spec.ts @@ -9,7 +9,7 @@ import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-test import SubagentRuntime from '@deepseek-ai/dsh-subagent' import * as spawn from '@deepseek-ai/dsh-subagent-spawn-in-process' import { MockAdapter, textResponse } from '../packages/core/agent-loop/tests/mock-adapter.ts' -import type {} from '@deepseek-ai/dsh-tool-workflow' +import type {} from '@deepseek-ai/dsh-tool-workflow/types' import { afterEach, describe, expect, it, vi } from 'vitest' // @ts-expect-error Scenario plugins are runtime JavaScript without declaration artifacts. import * as fixtureModule from './fixtures/python-snapshot-workflow-order.mjs' diff --git a/scripts/rescope-vendor.ts b/scripts/rescope-vendor.ts index 793df0ad81..e6292fb2a5 100644 --- a/scripts/rescope-vendor.ts +++ b/scripts/rescope-vendor.ts @@ -152,7 +152,6 @@ const POSTCONDITIONS: readonly PostCondition[] = [ { file: 'vendor/hmr/package.json', text: '"name": "@deepseek-ai/cordis-plugin-hmr"', count: 1 }, { file: 'scripts/cordis-walk.ts', text: '@deepseek-ai\\/cordis', count: 1 }, { file: 'scripts/cordis-walk.ts', text: '!== \'@deepseek-ai/cordis\'', count: 1 }, - { file: 'scripts/gen-scoped-events.ts', text: '=== \'@deepseek-ai/cordis\'', count: 1 }, { file: 'packages/typert/generator/src/analyzer.ts', text: '!== \'@deepseek-ai/cordis\'', count: 2 }, { file: 'scripts/check-workspace-constraints.ts', text: '?.[\'@deepseek-ai/cordis\']', count: 2 }, { file: 'packages/boot/app-boot/tsdown.config.ts', text: '[\'@deepseek-ai/cordis-plugin-include\']', count: 1 }, diff --git a/scripts/run-gates.spec.ts b/scripts/run-gates.spec.ts index 8f2f636b2e..ddbb42acb6 100644 --- a/scripts/run-gates.spec.ts +++ b/scripts/run-gates.spec.ts @@ -334,7 +334,7 @@ describe('gate graph validation', () => { expect(ids).toEqual([ 'rescope-vendor', 'publint', 'constraints', 'default-product-isolation', 'package-dependencies', 'application-entrypoints', - 'dsh-package-licenses', 'package-invariants', 'built-package-invariants', 'node-next-types', + 'dsh-package-licenses', 'node-next-types', 'optional-dependency-imports', 'client-packages', 'client-ui-i18n', 'client-route-resolution', 'no-bare-dispatcher', 'no-unknown-casts', 'cordis-config', 'runtime-closure', @@ -353,9 +353,9 @@ describe('gate graph validation', () => { it('schedules the longest documentation leaves before short checks', () => { const ids = withPnpmEntrypoint(() => gatesForMode('doc-sync').map(subject => subject.id)) - expect(ids.slice(0, 11)).toEqual([ + expect(ids.slice(0, 10)).toEqual([ 'doc-typecheck', 'docs-site-build', 'doc-graphs', 'markdown-links', 'type-equivalence', - 'cordis-catalog', 'cordis-inspect-catalog', 'workflow-guest', 'mermaid', 'scoped-events', 'translation-pairing', + 'cordis-catalog', 'cordis-inspect-catalog', 'workflow-guest', 'mermaid', 'translation-pairing', ]) }) @@ -803,7 +803,6 @@ describe('Node 24 lane ownership', () => { 'build', 'node-compat', 'publint', - 'built-package-invariants', 'lint-and-duplication', 'snapshot', 'expected-output', @@ -819,8 +818,7 @@ describe('Node 24 lane ownership', () => { expect(subject.find(item => item.id === 'node-compat')?.env).toEqual({ DSH_BUILD_CLIENT_PROFILE: 'official', }) - expect(subject.find(item => item.id === 'built-package-invariants')?.needs).toEqual(['build']) - expect(subject.find(item => item.id === 'lint-and-duplication')?.needs).toEqual(['built-package-invariants']) + expect(subject.find(item => item.id === 'lint-and-duplication')?.needs).toEqual(['build']) for (const id of [ 'snapshot', 'expected-output', @@ -829,7 +827,7 @@ describe('Node 24 lane ownership', () => { 'node-next-types', 'built-bin-smoke', ]) { - expect(subject.find(item => item.id === id)?.needs).toEqual(['built-package-invariants']) + expect(subject.find(item => item.id === id)?.needs).toEqual(['build']) } expect(subject.find(item => item.id === 'snapshot')?.env).toEqual({ DSH_EXAMPLE_MODE: 'lib' }) expect(subject.find(item => item.id === 'expected-output')?.env).toEqual({ DSH_EXAMPLE_MODE: 'lib' }) @@ -871,7 +869,7 @@ describe('Linux primary graph', () => { expect(web).toMatchObject({ displayCommand: 'DSH_SNAPSHOT=replay pnpm run test:web:built', env: { DSH_SNAPSHOT: 'replay' }, - needs: ['built-package-invariants'], + needs: ['build'], }) }) }) diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index fb1a60dd8c..83c053fb29 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -273,7 +273,7 @@ export function gatesForMode(selected: Mode): Gate[] { case 'ci-primary': return ciPrimaryGates() case 'ci-linux-primary': - return [...ciPrimaryGates(), webSnapshotGate(['built-package-invariants'])] + return [...ciPrimaryGates(), webSnapshotGate(['build'])] case 'ci-static': return ciStaticGates({ ownsBuild: false }) case 'ci-lint-contracts-ready': @@ -350,7 +350,6 @@ function ciSharedStaticGates(): Gate[] { pnpmScript('constraints', 'constraints'), pnpmScript('package-dependencies', 'verify-package-dependencies', { label: 'package dependencies' }), pnpmScript('dsh-package-licenses', 'verify-dsh-package-licenses', { label: 'DSH package licenses' }), - pnpmScript('package-invariants', 'verify-package-invariants', { label: 'package invariants' }), pnpmScript('package-meta', 'verify-package-meta', { label: 'package metadata' }), pnpmScript('cordis-config', 'verify-cordis-config', { label: 'Cordis config' }), ...sharedHygieneGates(), @@ -396,7 +395,6 @@ function ciPrimaryGates(): Gate[] { label: 'node-next types', needs: ['build'], }), - builtPackageInvariantsGate(['build']), builtBinSmokeGate(), ] } @@ -503,14 +501,12 @@ function ciArtifactGates(): Gate[] { label: 'node-next types', needs: ['build'], }), - builtPackageInvariantsGate(['build']), builtBinSmokeGate(), ] } function ciConsumerGates(): Gate[] { const builtTree = ['build'] - const validatedBuild = ['built-package-invariants'] // The HMR web test starts `dev:web`, which rewrites the shared `lib/` and // `apps/web/dist/` trees. Let every build-artifact reader settle before that // writer starts; `after` preserves the web diagnostic even if a reader fails. @@ -530,23 +526,22 @@ function ciConsumerGates(): Gate[] { env: { [CLIENT_BUILD_PROFILE_SELECTOR]: 'official' }, }), pnpmScript('publint', 'publint', { needs: builtTree }), - builtPackageInvariantsGate(builtTree), pnpmScript('lint-and-duplication', 'check:ci:lint:contracts-ready', { label: 'lint and duplication', - needs: validatedBuild, + needs: builtTree, }), - snapshotGate(validatedBuild), - expectedOutputGate(validatedBuild), - webSnapshotGate(validatedBuild, buildArtifactReaders), + snapshotGate(builtTree), + expectedOutputGate(builtTree), + webSnapshotGate(builtTree, buildArtifactReaders), pnpmScript('doc-typecheck', 'doc-typecheck:contracts-ready', { - needs: validatedBuild, + needs: builtTree, env: { DSH_DOC_TYPECHECK_USE_BUILD_OUTPUT: '1' }, }), pnpmScript('node-next-types', 'verify-node-next-types', { label: 'node-next types', - needs: validatedBuild, + needs: builtTree, }), - builtBinSmokeGate(validatedBuild), + builtBinSmokeGate(builtTree), ] } @@ -625,7 +620,6 @@ function ciWindowsObservationalGates(): Gate[] { label: 'node-next types', needs: ['build'], }), - builtPackageInvariantsGate(['build']), ] return [ ...predecessors, @@ -748,13 +742,6 @@ function expectedOutputGate(needs: string[] = ['build']): Gate { }) } -function builtPackageInvariantsGate(needs?: string[]): Gate { - return pnpmScript('built-package-invariants', 'verify-built-package-invariants', { - label: 'built package invariants', - ...needs === undefined ? {} : { needs }, - }) -} - function positiveIntArg(envName: string, flag: string): string[] { const raw = process.env[envName] if (raw === undefined || raw === '') return [] @@ -782,8 +769,6 @@ function hygieneLeafGates(options: { artifactNeeds?: string[] } = {}): Gate[] { pnpmScript('package-dependencies', 'verify-package-dependencies', { label: 'package dependencies' }), pnpmScript('application-entrypoints', 'verify-application-entrypoints', { label: 'application entrypoints' }), pnpmScript('dsh-package-licenses', 'verify-dsh-package-licenses', { label: 'DSH package licenses' }), - pnpmScript('package-invariants', 'verify-package-invariants', { label: 'package invariants' }), - builtPackageInvariantsGate(options.artifactNeeds), pnpmScript('node-next-types', 'verify-node-next-types', { label: 'node-next types', ...artifactOptions, @@ -815,7 +800,6 @@ function docSyncLeafGates(options: { pnpmScript('cordis-inspect-catalog', 'verify-cordis-inspect-catalog', { label: 'Cordis inspect catalog' }), pnpmScript('workflow-guest', 'verify-workflow-guest', { label: 'workflow guest source' }), pnpmScript('mermaid', 'verify-mermaid'), - pnpmScript('scoped-events', 'verify-scoped-events', { label: 'scoped events' }), pnpmScript('translation-pairing', 'verify-translation-pairing', { label: 'translation pairing', quick: true }), pnpmScript('markdown-wrap', 'verify-md-wrap', { label: 'markdown wrap', quick: true }), pnpmScript('client-catalog', 'verify-client-catalog', { label: 'client catalog' }), diff --git a/scripts/test-invariants.spec.ts b/scripts/test-invariants.spec.ts deleted file mode 100644 index b92c9905cb..0000000000 --- a/scripts/test-invariants.spec.ts +++ /dev/null @@ -1,419 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { Context, FiberState, Service, ValidationError } from '@deepseek-ai/cordis' -import Loader from '@deepseek-ai/cordis-plugin-loader' -import z from '@deepseek-ai/schemastery' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' -import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' -import { packageInvariantOwners } from './package-invariants.ts' -import { - TEST_INVARIANT_READY_SERVICE, - testInvariantCompanionPaths, - testInvariantCompanions, - type TestInvariantCompanion, - usesManualInvariantTree, -} from './test-invariants.ts' - -declare module '@deepseek-ai/cordis' { - interface Context { - testInvariantProbe: TestInvariantProbe - } -} - -class TestInvariantProbe extends Service { - constructor(ctx: Context) { - super(ctx, 'testInvariantProbe') - } -} - -function deferred(): { readonly promise: Promise; readonly resolve: () => void } { - let resolve!: () => void - const promise = new Promise((done) => { - resolve = done - }) - return { promise, resolve } -} - -function requiredConfig() { - return z.object({ - requiredValue: z.string().required(), - }) -} - -function invalidConfigApply(): never { - throw new Error('invalid plugin apply executed') -} - -async function rejectionOf(fiber: ReturnType): Promise { - return fiber.then( - () => undefined, - (error: unknown) => error, - ) -} - -function expectRequiredConfigValidation(error: unknown): void { - expect(error).toBeInstanceOf(ValidationError) - expect(error).toHaveProperty('message', expect.stringMatching(/requiredValue/)) -} - -async function withFakeCompanions( - create: (path: string, index: number) => () => Promise, - run: () => Promise, -): Promise { - const mutable = testInvariantCompanions as Record Promise> - const originals = Object.entries(mutable) - for (const [index, [path]] of originals.entries()) { - mutable[path] = create(path, index) - } - try { - await run() - } finally { - for (const [path, load] of originals) { - mutable[path] = load - } - } -} - -async function withDelayedFirstCompanion( - run: (control: { readonly started: Promise; readonly release: () => void }) => Promise, -): Promise { - const started = deferred() - const release = deferred() - await withFakeCompanions( - (_path, index) => async () => ({ - name: `test-invariant-${index}`, - inject: ['invariants'], - async apply() { - if (index === 0) { - started.resolve() - await release.promise - } - return () => {} - }, - }), - () => run({ started: started.promise, release: release.resolve }), - ) -} - -describe('global test invariant host', () => { - it('uses one topology to reserve every companion owner 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('mounts an owning companion and leaves omitted or non-package roots service-only', () => { - expect(testInvariantCompanionPaths('/repo/packages/core/tools/tests/tools.spec.ts')) - .toEqual(['../packages/core/tools/src/invariant.ts']) - expect(testInvariantCompanionPaths('/repo/packages/util/brand/tests/brand.spec.ts')).toEqual([]) - expect(testInvariantCompanionPaths('/repo/apps/cli/tests/profiles/headless/example.spec.ts')).toEqual([]) - expect(testInvariantCompanionPaths('/repo/scripts/test-invariants.spec.ts')) - .toEqual(Object.keys(testInvariantCompanions).sort()) - }) - - it('loads and executes every source companion through the real Loader setup', async () => { - const owners = new Map(packageInvariantOwners(process.cwd()).map(owner => [owner.sourcePath, owner.packageName])) - const registrations = new Map() - const loader = Object.create(Loader.prototype) as Loader - 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, load] of Object.entries(testInvariantCompanions)) { - const companion = await load() - const path = rawPath.replace(/^\.\.\//, '') - expect(companion.default, path).toBeUndefined() - const unwrapped = loader.unwrapExports(companion) as typeof companion - expect(unwrapped, path).toBe(companion) - expect(typeof unwrapped.name, path).toBe('string') - expect(unwrapped.inject, path).toContain('invariants') - expect(typeof unwrapped.apply, path).toBe('function') - await unwrapped.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('recognizes focused invariant suites without a package inventory', () => { - expect(usesManualInvariantTree('/repo/packages/core/session/tests/invariant.spec.ts')).toBe(true) - expect(usesManualInvariantTree('/repo/packages/core/session/tests/request-invariant-hmr.spec.ts')).toBe(true) - expect(usesManualInvariantTree('C:\\repo\\packages\\runtime-diagnostics\\invariants\\tests\\service.spec.ts')).toBe(true) - expect(usesManualInvariantTree('/repo/packages/core/session/tests/session.spec.ts')).toBe(false) - }) - - it('preserves config validation failures without starting the rejected plugin', async () => { - const ctx = new Context() - const apply = vi.fn(invalidConfigApply) - const plugin = { - apply, - Config: requiredConfig(), - } - - const fiber = ctx.plugin(plugin, {}) - const firstError = await rejectionOf(fiber) - expectRequiredConfigValidation(firstError) - await ctx.plugin(TestInvariantProbe) - const secondError = await rejectionOf(fiber) - expect(secondError).toBe(firstError) - expect(fiber.state).toBe(FiberState.DISPOSED) - expect(apply).not.toHaveBeenCalled() - }) - - it('disposes invalid config after delayed invariant readiness', async () => { - await withDelayedFirstCompanion( - async ({ started, release }) => { - const ctx = new Context() - const apply = vi.fn(invalidConfigApply) - const plugin = { - apply, - Config: requiredConfig(), - } - - const fiber = ctx.plugin(plugin, {}) - const returnedError = rejectionOf(fiber) - await started - expect(fiber.state).toBe(FiberState.PENDING) - expect(apply).not.toHaveBeenCalled() - - release() - expectRequiredConfigValidation(await returnedError) - expect(fiber.state).toBe(FiberState.DISPOSED) - expect(apply).not.toHaveBeenCalled() - }, - ) - }) - - it('retains a valid plugin failure after delayed invariant readiness', async () => { - await withDelayedFirstCompanion( - async ({ started, release }) => { - const ctx = new Context() - const failure = new Error('valid plugin apply failed') - const apply = vi.fn(function validConfigApply() { - throw failure - }) - const plugin = { - apply, - Config: z.object({}), - } - - const fiber = ctx.plugin(plugin, {}) - const returnedError = rejectionOf(fiber) - await started - expect(fiber.state).toBe(FiberState.PENDING) - expect(apply).not.toHaveBeenCalled() - - release() - expect(await returnedError).toBe(failure) - expect(fiber.state).toBe(FiberState.FAILED) - expect(apply).toHaveBeenCalledOnce() - expect(ctx.registry.has(plugin)).toBe(true) - expect(ctx.registry.get(plugin)?.fibers).toHaveLength(1) - }, - ) - }) - - it('holds a root plugin until every lazy companion is active, then permits nested startup', async () => { - const delayedStarted = deferred() - const releaseDelayed = deferred() - const order: string[] = [] - let delayedCompanion: TestInvariantCompanion | undefined - const companionNestedApply = vi.fn(function companionNestedApply() {}) - - await withFakeCompanions( - (path, index) => async () => { - const companion: TestInvariantCompanion = { - name: `test-invariant-${index}`, - inject: ['invariants'], - async apply(companionCtx) { - order.push(`companion-start:${path}`) - if (index === 0) { - delayedStarted.resolve() - await releaseDelayed.promise - } - if (index === 1) await companionCtx.plugin(companionNestedApply) - order.push(`companion-active:${path}`) - return () => {} - }, - } - if (index === 0) delayedCompanion = companion - return companion - }, - async () => { - const ctx = new Context() - ctx.provide('testInvariantTargetDependency', true) - let nestedFiber: ReturnType | undefined - const nestedApply = vi.fn(function nestedApply() { - order.push('nested') - }) - const targetApply = Object.assign(vi.fn(function targetApply(targetCtx: Context) { - order.push('target') - nestedFiber = targetCtx.plugin(nestedApply) - }), { - inject: ['testInvariantTargetDependency'], - }) - - const targetFiber = ctx.plugin(targetApply) - expect(ctx.registry.get(targetApply)?.callback).toBe(targetApply) - expect(targetFiber.inject).toEqual({ - testInvariantTargetDependency: null, - [TEST_INVARIANT_READY_SERVICE]: null, - }) - - await delayedStarted.promise - await Promise.resolve() - await Promise.resolve() - expect(targetApply).not.toHaveBeenCalled() - - releaseDelayed.resolve() - await targetFiber - if (nestedFiber === undefined) throw new Error('target did not register its nested plugin') - await nestedFiber - - expect(targetFiber.state).toBe(FiberState.ACTIVE) - expect(targetApply).toHaveBeenCalledOnce() - expect(nestedApply).toHaveBeenCalledOnce() - expect(companionNestedApply).toHaveBeenCalledOnce() - const targetIndex = order.indexOf('target') - expect(targetIndex).toBeGreaterThan(-1) - expect(order.slice(0, targetIndex)).toHaveLength(Object.keys(testInvariantCompanions).length * 2) - expect(order.at(-1)).toBe('nested') - - if (delayedCompanion === undefined) throw new Error('delayed companion did not load') - await ctx.plugin(InvariantRegistry, { enabled: true }) - await ctx.plugin(delayedCompanion) - expect(ctx.registry.get(InvariantRegistry)?.fibers).toHaveLength(1) - expect(ctx.registry.get(delayedCompanion)?.fibers).toHaveLength(1) - }, - ) - }) - - it('holds plugins registered on a root-derived context until companion readiness', async () => { - await withDelayedFirstCompanion( - async ({ started, release }) => { - const ctx = new Context() - const rootApply = vi.fn(function rootApply() {}) - const derivedApply = vi.fn(function derivedApply() {}) - const derived = ctx.extend() - .isolate('testInvariantDerived') - .intercept('testInvariantDerived', {}) - - const rootFiber = ctx.plugin(rootApply) - const derivedFiber = derived.plugin(derivedApply) - - await started - await Promise.resolve() - await Promise.resolve() - expect(rootApply).not.toHaveBeenCalled() - expect(derivedApply).not.toHaveBeenCalled() - expect(derivedFiber.inject).toEqual({ - [TEST_INVARIANT_READY_SERVICE]: null, - }) - - release() - await Promise.all([rootFiber, derivedFiber]) - expect(rootFiber.state).toBe(FiberState.ACTIVE) - expect(derivedFiber.state).toBe(FiberState.ACTIVE) - expect(rootApply).toHaveBeenCalledOnce() - expect(derivedApply).toHaveBeenCalledOnce() - }, - ) - }) - - it('holds a child registered externally on a pending target context', async () => { - await withDelayedFirstCompanion( - async ({ started, release }) => { - const ctx = new Context() - const targetApply = vi.fn(function targetApply() {}) - const childApply = vi.fn(function childApply() {}) - - const targetFiber = ctx.plugin(targetApply) - const childFiber = targetFiber.ctx.plugin(childApply) - - await started - await Promise.resolve() - await Promise.resolve() - expect(targetFiber.state).toBe(FiberState.PENDING) - expect(childFiber.state).toBe(FiberState.PENDING) - expect(targetApply).not.toHaveBeenCalled() - expect(childApply).not.toHaveBeenCalled() - expect(childFiber.inject).toEqual({ - [TEST_INVARIANT_READY_SERVICE]: null, - }) - - release() - await Promise.all([targetFiber, childFiber]) - expect(targetFiber.state).toBe(FiberState.ACTIVE) - expect(childFiber.state).toBe(FiberState.ACTIVE) - expect(targetApply).toHaveBeenCalledOnce() - expect(childApply).toHaveBeenCalledOnce() - }, - ) - }) - - it.each(['load', 'startup'] as const)( - 'rejects a target when a lazy companion fails during %s without starting the target', - async (phase) => { - const failure = new Error(`test invariant companion ${phase} failed`) - await withFakeCompanions( - (_path, index) => phase === 'load' && index === 0 - ? async () => { throw failure } - : async () => ({ - name: `test-invariant-${index}`, - inject: ['invariants'], - async apply() { - if (phase === 'startup' && index === 0) throw failure - return () => {} - }, - }), - async () => { - const ctx = new Context() - const targetApply = vi.fn(function targetApply() {}) - const targetFiber = ctx.plugin(targetApply) - - await expect(targetFiber).rejects.toBe(failure) - expect(targetApply).not.toHaveBeenCalled() - expect(targetFiber.state).toBe(FiberState.PENDING) - await expect(targetFiber.dispose()).resolves.toBeUndefined() - expect(targetFiber.state).toBe(FiberState.DISPOSED) - }, - ) - }, - ) - - it('disposes a pending target without waiting for companion readiness', async () => { - await withDelayedFirstCompanion( - async ({ started, release }) => { - const ctx = new Context() - const targetApply = vi.fn(function targetApply() {}) - const targetFiber = ctx.plugin(targetApply) - - await started - await expect(targetFiber.dispose()).resolves.toBeUndefined() - expect(targetFiber.state).toBe(FiberState.DISPOSED) - expect(targetApply).not.toHaveBeenCalled() - - release() - await targetFiber - expect(targetApply).not.toHaveBeenCalled() - }, - ) - }) -}) diff --git a/scripts/test-invariants.ts b/scripts/test-invariants.ts deleted file mode 100644 index 48e5967b28..0000000000 --- a/scripts/test-invariants.ts +++ /dev/null @@ -1,232 +0,0 @@ -/** - * Vitest-wide invariant host. Ordinary Cordis roots receive the invariant - * service with global enablement plus the current test package's companion, - * when it publishes one. - * One topology test mounts every companion; focused invariant tests own their - * service topology explicitly. - */ - -import { expect } from 'vitest' -import { FiberState, Inject, RegistryService, ValidationError } from '@deepseek-ai/cordis' -import type { Context, Plugin } from '@deepseek-ai/cordis' -import InvariantRegistry from '@deepseek-ai/dsh-invariants' - -declare global { - interface ImportMeta { - /** Lazy Vite module-glob expansion used by the Vitest setup file. */ - glob(pattern: string): Record Promise> - } -} - -/** Loader-safe exports shared by every package invariant companion. */ -export interface TestInvariantCompanion { - readonly name: string - readonly inject: readonly string[] - readonly default?: unknown - apply(ctx: Context): Promise<() => void> -} - -/** Private service dependency that holds ordinary root plugins until invariant startup completes. */ -export const TEST_INVARIANT_READY_SERVICE = 'testInvariantReady' - -/** - * Every published package companion as a lazy loader keyed by glob path. Ordinary tests - * load only their owner's module; the exhaustive topology test loads and - * executes all of them, so aggregated coverage still observes every - * registration while per-file setup avoids importing unrelated companions and their - * transitive package sources. - */ -export const testInvariantCompanions: Readonly Promise>> = - import.meta.glob('../packages/*/*/src/invariant.ts') - -/** Manual-topology suites whose names cannot follow the focused invariant convention. */ -const MANUAL_INVARIANT_TEST_EXCEPTIONS = [ - '/packages/runtime-diagnostics/invariants/tests/service.spec.ts', -] as const - -interface InvariantHost { - readonly byCallback: ReadonlyMap - readonly barrierOwners: WeakSet - readonly ready: Promise -} - -type PluginFiber = ReturnType -type PluginCallback = Plugin.Function | Plugin.Constructor - -const hosts = new WeakMap() -// oxlint-disable-next-line typescript/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[]) { - const testPath = expect.getState().testPath ?? '' - if (usesManualInvariantTree(testPath)) 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 hasBarrierOwner(host, this.ctx) ? existing : joinInvariantStartup(existing, host.ready) - } - - // Causal descendants of a gated target have already crossed the barrier. - // Host service and companion descendants also bypass it so their own startup - // cannot depend on the readiness they are responsible for providing. - if (hasBarrierOwner(host, this.ctx)) { - return originalPlugin.call(this, plugin, config, getOuterStack) - } - if (callback === undefined) { - return originalPlugin.call(this, plugin, config, getOuterStack) - } - - const fiber = originalPlugin.call( - this, - withInvariantReadiness(plugin, callback as PluginCallback), - config, - getOuterStack, - ) - const initiallyPending = fiber.ctx.fiber.state === FiberState.PENDING - host.barrierOwners.add(fiber.ctx.fiber) - return joinInvariantStartup(fiber, host.ready, initiallyPending) -} - -/** - * Detect focused suites that construct service selection or companion lifecycle explicitly. - * @param testPath - absolute or repo-relative Vitest file path. - * @returns whether the global invariant host must leave the root untouched. - */ -export function usesManualInvariantTree(testPath: string): boolean { - const normalized = testPath.replaceAll('\\', '/') - if (/\/packages\/[^/]+\/[^/]+\/tests\/[^/]*invariant[^/]*\.spec\.ts$/.test(normalized)) return true - return MANUAL_INVARIANT_TEST_EXCEPTIONS.some(path => normalized.endsWith(path)) -} - -const ALL_COMPANION_TESTS = ['/scripts/test-invariants.spec.ts'] as const - -/** - * Select the package companions that an ordinary test root must register. - * Package tests receive their owner's checks; the dedicated topology test - * receives every companion owner so coverage and runtime registration remain - * independently enforced. - * @param testPath - absolute or repo-relative normalized Vitest file path. - * @returns sorted `import.meta.glob` keys for companions to mount. - */ -export function testInvariantCompanionPaths(testPath: string): string[] { - const normalized = testPath.replaceAll('\\', '/') - const allPaths = Object.keys(testInvariantCompanions).sort() - if (ALL_COMPANION_TESTS.some(path => normalized.endsWith(path))) return allPaths - - const owner = normalized.match(/\/packages\/([^/]+)\/([^/]+)\/tests\//) - if (owner === null) return [] - const companionPath = `../packages/${owner[1]}/${owner[2]}/src/invariant.ts` - return testInvariantCompanions[companionPath] === undefined ? [] : [companionPath] -} - -function startInvariantHost(root: Context): InvariantHost { - const byCallback = new Map() - const barrierOwners = new WeakSet() - const mount = (plugin: Plugin, config?: unknown): PluginFiber => { - 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') - byCallback.set(callback, fiber) - barrierOwners.add(fiber.ctx.fiber) - return fiber - } - - // The service mounts synchronously so the intercepted registration that - // started this host immediately finds its own fiber in byCallback. - // Companions load and mount inside the ready chain (after the service is - // active, so their startup is directly joinable); every joined root plugin - // awaits ready, so none starts ahead of its package checks. Tests plugging - // a companion directly must await an earlier root plugin first — the - // duplicate-mount failure otherwise is loud (owner name already reserved). - const serviceFiber = mount(InvariantRegistry, { enabled: true }) - const testPath = expect.getState().testPath ?? '' - const companionPaths = testInvariantCompanionPaths(testPath) - const ready = requireActive(serviceFiber, 'invariant service').then(async () => { - const companions = await Promise.all(companionPaths.map(async (path) => { - const load = testInvariantCompanions[path] - if (load === undefined) { - throw new Error(`test invariants: selected companion vanished at ${path}`) - } - const companion = await load() - if (!companion.inject.includes('invariants')) { - throw new Error(`test invariants: ${path} must inject the invariant service`) - } - return { companion, path } - })) - const companionFibers = companions.map(({ companion, path }) => ({ - fiber: mount(companion), - path, - })) - await Promise.all(companionFibers.map(({ fiber, path }) => requireActive(fiber, path))) - root.provide(TEST_INVARIANT_READY_SERVICE, true) - }) - const host = { byCallback, barrierOwners, ready } - hosts.set(root, host) - return host -} - -function hasBarrierOwner(host: InvariantHost, ctx: Context): boolean { - let fiber = ctx.fiber - while (true) { - if ( - host.barrierOwners.has(fiber) - && (fiber.state === FiberState.LOADING || fiber.state === FiberState.ACTIVE) - ) { - return true - } - const parent = fiber.parent.fiber - if (parent === fiber) return false - fiber = parent - } -} - -async function requireActive(fiber: PluginFiber, label: string): Promise { - await fiber.await() - if (fiber.state !== FiberState.ACTIVE) { - throw new Error(`test invariants: ${label} settled without becoming active`) - } -} - -function withInvariantReadiness(plugin: Plugin, callback: PluginCallback): Plugin.Object { - return { - apply: callback as Plugin.Function, - inject: { - ...Inject.resolve(plugin.inject), - [TEST_INVARIANT_READY_SERVICE]: null, - }, - ...(plugin.name === undefined ? {} : { name: plugin.name }), - ...(plugin.Config === undefined ? {} : { Config: plugin.Config }), - ...(plugin.provide === undefined ? {} : { provide: plugin.provide }), - ...(plugin.intercept === undefined ? {} : { intercept: plugin.intercept }), - } -} - -function joinInvariantStartup( - fiber: PluginFiber, - invariantReady: Promise, - disposePendingValidationFailure = false, -): PluginFiber { - // RegistryService returns a thenable wrapper whose context still points to - // the raw Fiber. Calling inherited await() on the wrapper would return and - // assimilate that thenable, accidentally following later plugin startup. - const rawFiber = fiber.ctx.fiber - const readiness = invariantReady.then(async () => { - try { - return await rawFiber.await() - } catch (error) { - // Config resolves only after the readiness injection activates. Dispose - // validation failures owned by an initially pending target; ordinary - // callback failures remain inspectable. - if (disposePendingValidationFailure && error instanceof ValidationError) { - await rawFiber.dispose() - } - throw error - } - }) - const joined = Object.create(fiber) as PluginFiber - joined.then = readiness.then.bind(readiness) - return joined -} diff --git a/scripts/test-proxy-environment.spec.ts b/scripts/test-proxy-environment.spec.ts index 160e607a87..3cbd2a671b 100644 --- a/scripts/test-proxy-environment.spec.ts +++ b/scripts/test-proxy-environment.spec.ts @@ -25,7 +25,7 @@ describe('ambient proxy environment', () => { // A runtime assertion that this process is clear would pass either way: importing the module // above already ran it. What can actually regress is the wiring — a new Vitest project, or a - // config that lists only the invariant host — so that is what this pins. + // config that omits the proxy setup — so that is what this pins. const declared = vitestConfigFiles() .map(config => ({ config, slots: readFileSync(config, 'utf8').match(/setupFiles: \[[^\]]*\]/g) ?? [] })) .filter(entry => entry.slots.length > 0) diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index d1a15a9702..9a4d1d529f 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -1886,21 +1886,6 @@ "symbol": "PlanModeConfig", "source": "packages/plan/plan-mode/src/index.ts" }, - { - "doc": "docs/subsystems/invariants.md", - "symbol": "Config", - "source": "packages/runtime-diagnostics/invariants/src/index.ts" - }, - { - "doc": "docs/subsystems/invariants.md", - "symbol": "InvariantFailure", - "source": "packages/runtime-diagnostics/invariants/src/index.ts" - }, - { - "doc": "docs/subsystems/invariants.md", - "symbol": "InvariantInstaller", - "source": "packages/runtime-diagnostics/invariants/src/index.ts" - }, { "doc": "docs/subsystems/web-server.md", "symbol": "WebRouteKind", diff --git a/scripts/verify-built-package-invariants.mjs b/scripts/verify-built-package-invariants.mjs deleted file mode 100644 index b4a37cdc25..0000000000 --- a/scripts/verify-built-package-invariants.mjs +++ /dev/null @@ -1,99 +0,0 @@ -/** Verify every compiled companion through its staged package self-reference under plain Node. */ - -import { - copyFileSync, - cpSync, - existsSync, - globSync, - mkdirSync, - mkdtempSync, - readFileSync, - rmSync, - writeFileSync, -} from 'node:fs' -import { dirname, resolve } from 'node:path' -import { pathToFileURL } from 'node:url' -import { parseArgs } from 'node:util' - -const repositoryRoot = resolve(import.meta.dirname, '..') -const { values: options } = parseArgs({ - args: process.argv.slice(2), - options: { 'packages-root': { type: 'string' }, 'loader-url': { type: 'string' } }, -}) -const packagesRoot = resolve(options['packages-root'] ?? repositoryRoot) -const loaderUrl = options['loader-url'] - ?? pathToFileURL(resolve(repositoryRoot, 'vendor/loader/lib/index.js')).href -const failures = [] -const manifests = globSync('packages/*/*/package.json', { cwd: packagesRoot }).sort() -let companionCount = 0 -const { default: Loader } = await import(loaderUrl) -const loader = Object.create(Loader.prototype) - -for (const manifestPath of manifests) { - const packageDir = dirname(resolve(packagesRoot, manifestPath)) - const manifest = JSON.parse(readFileSync(resolve(packagesRoot, manifestPath), 'utf8')) - const packageName = manifest.name - if (typeof packageName !== 'string' || packageName.length === 0) { - failures.push(`${manifestPath}: missing package name`) - continue - } - const invariantExport = manifest.exports?.['./invariant'] - if (invariantExport === undefined) continue - companionCount += 1 - if (typeof invariantExport !== 'object' - || invariantExport === null - || invariantExport.default !== './lib/invariant.js' - || !manifest.files?.includes('lib/invariant.js')) { - failures.push(`${packageName}: manifest does not publish ./lib/invariant.js as ./invariant`) - continue - } - - // Keep the staged view below its owning package so Node reaches the real - // pnpm dependency links. Junctioning node_modules elsewhere breaks pnpm's - // relative workspace links on Windows. Copy the manifest-declared lib view - // so a companion that imports an undeclared runtime chunk fails here. - const stagedPackageDir = mkdtempSync(resolve(packageDir, '.dsh-built-invariant-')) - try { - copyFileSync(resolve(packageDir, 'package.json'), resolve(stagedPackageDir, 'package.json')) - copyDeclaredLibFiles(packageDir, stagedPackageDir, manifest.files) - const probePath = resolve(stagedPackageDir, 'probe.mjs') - writeFileSync( - probePath, - `import * as companion from ${JSON.stringify(`${packageName}/invariant`)}\nexport default companion\n`, - ) - const { default: companion } = await import(pathToFileURL(probePath).href) - if ('default' in companion) throw new Error('companion has a default export') - const unwrapped = loader.unwrapExports(companion) - if (unwrapped !== companion) throw new Error('Loader collapsed the companion namespace') - if (typeof unwrapped.name !== 'string') throw new Error('companion name is missing') - if (!Array.isArray(unwrapped.inject) || !unwrapped.inject.includes('invariants')) { - throw new Error('companion does not inject invariants') - } - if (typeof unwrapped.apply !== 'function') throw new Error('companion apply is missing') - } catch (error) { - failures.push(`${packageName}: ${error instanceof Error ? error.message : String(error)}`) - } finally { - rmSync(stagedPackageDir, { recursive: true, force: true }) - } -} - -if (failures.length > 0) { - console.error('verify-built-package-invariants: compiled companion failures:') - for (const failure of failures) console.error(` ${failure}`) - process.exit(1) -} - -console.log(`verify-built-package-invariants: ${companionCount} compiled companion(s) passed plain-Node Loader checks.`) - -function copyDeclaredLibFiles(packageDir, stagedPackageDir, files) { - for (const pattern of files) { - if (!pattern.startsWith('lib/')) continue - for (const relativePath of globSync(pattern, { cwd: packageDir })) { - const source = resolve(packageDir, relativePath) - if (!existsSync(source)) continue - const target = resolve(stagedPackageDir, relativePath) - mkdirSync(dirname(target), { recursive: true }) - cpSync(source, target, { recursive: true }) - } - } -} diff --git a/scripts/verify-built-package-invariants.spec.ts b/scripts/verify-built-package-invariants.spec.ts deleted file mode 100644 index 6d5739f8ed..0000000000 --- a/scripts/verify-built-package-invariants.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { fileURLToPath, pathToFileURL } from 'node:url' -import { spawnSync } from 'node:child_process' -import { afterEach, describe, expect, it } from 'vitest' - -const verifier = fileURLToPath(new URL('./verify-built-package-invariants.mjs', import.meta.url)) -const roots: string[] = [] - -afterEach(() => { - for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }) -}) - -function fixture(options: { - companion?: boolean - invariantSource?: string - invariantExport?: string - runtimeChunk?: string -} = {}): { root: string; loaderUrl: string } { - const root = mkdtempSync(join(tmpdir(), 'dsh-built-package-invariants-')) - roots.push(root) - const packageDir = join(root, 'packages/core/probe') - mkdirSync(join(packageDir, 'lib'), { recursive: true }) - const companion = options.companion ?? true - writeFileSync(join(packageDir, 'package.json'), `${JSON.stringify({ - name: '@deepseek-ai/dsh-probe', - type: 'module', - files: companion ? ['lib/invariant.js'] : [], - exports: companion ? { - './invariant': { - default: options.invariantExport ?? './lib/invariant.js', - }, - } : {}, - }, null, 2)}\n`) - if (companion) { - writeFileSync( - join(packageDir, 'lib/invariant.js'), - options.invariantSource ?? "export const name = 'probe-invariant'\nexport const inject = ['invariants']\nexport const apply = () => {}\n", - ) - } - if (options.runtimeChunk !== undefined) { - writeFileSync(join(packageDir, 'lib/chunk.js'), options.runtimeChunk) - } - const loaderPath = join(root, 'loader.mjs') - writeFileSync(loaderPath, 'export default class Loader { unwrapExports(value) { return value } }\n') - return { root, loaderUrl: pathToFileURL(loaderPath).href } -} - -function verify(root: string, loaderUrl: string, timeout: number) { - const result = spawnSync(process.execPath, [ - verifier, - '--packages-root', root, - '--loader-url', loaderUrl, - ], { - encoding: 'utf8', - // Plain-Node startup and staged imports share the runner's platform budget. - timeout, - }) - expect(result.error).toBeUndefined() - expect(result.signal, result.stderr).toBeNull() - return result -} - -describe('built package invariant verifier', () => { - it('loads the staged compiled self-reference through plain Node and Loader normalization', ({ task }) => { - const { root, loaderUrl } = fixture() - const result = verify(root, loaderUrl, task.timeout) - expect(result.status, result.stderr).toBe(0) - expect(result.stdout).toContain('1 compiled companion(s) passed plain-Node Loader checks') - }) - - it('accepts packages that do not publish a companion', ({ task }) => { - const { root, loaderUrl } = fixture({ companion: false }) - const result = verify(root, loaderUrl, task.timeout) - expect(result.status, result.stderr).toBe(0) - expect(result.stdout).toContain('0 compiled companion(s) passed plain-Node Loader checks') - }) - - it('rejects a default export and a broken invariant export map', ({ task }) => { - const withDefault = fixture({ - invariantSource: "export default {}\nexport const name = 'probe-invariant'\nexport const inject = ['invariants']\nexport const apply = () => {}\n", - }) - const defaultResult = verify(withDefault.root, withDefault.loaderUrl, task.timeout) - expect(defaultResult.status).toBe(1) - expect(defaultResult.stderr).toContain('companion has a default export') - - const brokenExport = fixture({ invariantExport: './lib/missing.js' }) - const exportResult = verify(brokenExport.root, brokenExport.loaderUrl, task.timeout) - expect(exportResult.status).toBe(1) - expect(exportResult.stderr).toContain('@deepseek-ai/dsh-probe') - }) - - it('rejects an invariant bundle that needs an unstaged runtime chunk', ({ task }) => { - const { root, loaderUrl } = fixture({ - invariantSource: "export * from './chunk.js'\n", - runtimeChunk: "export const name = 'probe-invariant'\nexport const inject = ['invariants']\nexport const apply = () => {}\n", - }) - const result = verify(root, loaderUrl, task.timeout) - expect(result.status).toBe(1) - expect(result.stderr).toContain('chunk.js') - }) -}) diff --git a/scripts/verify-package-dependencies.spec.ts b/scripts/verify-package-dependencies.spec.ts index 71587f3dfa..42705cde76 100644 --- a/scripts/verify-package-dependencies.spec.ts +++ b/scripts/verify-package-dependencies.spec.ts @@ -471,11 +471,11 @@ describe('face-aware source classification', () => { it('does not treat static browser library entries as Host modules', () => { const subject = sourceFacts({ 'src/index.tsx': "import 'static-input'; export const view =
", - 'src/invariant.ts': "import 'browser-companion'", + 'src/companion.ts': "import 'browser-companion'", }, { exports: { '.': { types: './lib/types/index.d.ts', default: './lib/index.js' }, - './invariant': { types: './lib/types/invariant.d.ts', default: './lib/invariant.js' }, + './companion': { types: './lib/types/companion.d.ts', default: './lib/companion.js' }, }, }, 'client-only') @@ -488,7 +488,7 @@ describe('face-aware source classification', () => { it('scans published Node companions, conditional entries, and emitted-tree subpaths from source', () => { const subject = sourceFacts({ 'src/index.ts': 'export function apply() {}', - 'src/invariant.ts': "import 'invariant-runtime'; import type { Kind } from 'invariant-types'", + 'src/companion.ts': "import 'companion-runtime'; import type { Kind } from 'companion-types'", 'src/node/helper.ts': "export { helper } from 'node-helper'", 'src/node.mts': "import 'node-import'", 'src/node.cts': "require('node-require')", @@ -500,7 +500,7 @@ describe('face-aware source classification', () => { }, { exports: { '.': { types: './lib/types/index.d.ts', default: './lib/index.js' }, - './invariant': { types: './lib/types/invariant.d.ts', default: './lib/invariant.js' }, + './companion': { types: './lib/types/companion.d.ts', default: './lib/companion.js' }, './renamed': { types: './lib/types/node/helper.d.ts', default: './lib/node-bundle.js' }, './conditional': { browser: './lib/browser.js', node: { import: './lib/node.mjs', require: './lib/node.cjs' } }, './emitted': { types: './lib/types/emitted.d.ts', default: './lib/types/emitted.js' }, @@ -515,11 +515,11 @@ describe('face-aware source classification', () => { }) expect([...subject.hostRuntimeSourceUses.keys()].sort()).toEqual([ - 'invariant-runtime', 'node-helper', 'node-import', 'node-require', 'react', 'worker-one', 'worker-two', + 'companion-runtime', 'node-helper', 'node-import', 'node-require', 'react', 'worker-one', 'worker-two', ]) const expected = expectedPackageDependencies(subject) for (const name of subject.hostRuntimeSourceUses.keys()) expect(expected.get(name)?.section).toBe('dependencies') - for (const name of ['browser-only', 'invariant-types', 'type-export-only']) { + for (const name of ['browser-only', 'companion-types', 'type-export-only']) { expect(expected.get(name)?.section).toBe('devDependencies') } }) diff --git a/scripts/verify-package-invariants.ts b/scripts/verify-package-invariants.ts deleted file mode 100644 index dc686b1018..0000000000 --- a/scripts/verify-package-invariants.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** Verify package-owned invariant source and publication rules. */ - -import { resolve } from 'node:path' -import { - collectPackageInvariantViolations, - formatPackageInvariantViolation, - packageInvariantOwners, -} from './package-invariants.ts' - -const root = resolve(import.meta.dirname, '..') -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} hand-owned package companion(s) conform.`) diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 2ca3653361..81f4fa054b 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -204,7 +204,6 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/spill/spill-local': { kind: 'indirect', reason: 'The storage backend delegates model rendering to spill consumers.' }, 'packages/test-support/session-snapshot': { kind: 'none', reason: 'The test harness observes and normalizes transcripts without changing live requests.' }, 'packages/test-support/agent-loop-testkit': { kind: 'none', reason: 'The test helper mounts services but neither drives nor modifies model requests.' }, - 'packages/runtime-diagnostics/invariants': { kind: 'none', reason: 'The observer validates requests but never rewrites their context.' }, 'packages/test-support/loader-smoke': { kind: 'none', reason: 'The test harness submits an ordinary user task but delegates prompt and tool composition to the loaded tree.' }, 'packages/test-support/llm-mock-server': { kind: 'none', reason: 'The test server substitutes provider wire behavior without invoking a real model.' }, 'packages/test-support/llm-replay': { kind: 'none', reason: 'The keyless adapter invokes no provider model.' }, diff --git a/scripts/verify-subsystem-pages.ts b/scripts/verify-subsystem-pages.ts index a2a509d089..bbe0ff3327 100644 --- a/scripts/verify-subsystem-pages.ts +++ b/scripts/verify-subsystem-pages.ts @@ -19,6 +19,7 @@ export const GROUPS_WITHOUT_SUBSYSTEM_PAGE: Readonly> = { bundle: 'Composition patch carriers whose mounted packages own all runtime contracts.', hooks: 'External hook-protocol bridges over existing interception points, not a new Harness service.', sdk: 'Out-of-process protocol and client packages whose package READMEs own the SDK contracts.', + 'test-support': 'Repository test harnesses; docs/testing.md owns the testing policy they serve.', util: 'Low-level primitives whose business semantics remain with their consuming subsystems.', } diff --git a/tsconfig.base.json b/tsconfig.base.json index b2f42eeb5a..5858cf31aa 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -86,7 +86,6 @@ "@deepseek-ai/cordis-plugin-logger-console": ["./vendor/logger-console/src"], "@deepseek-ai/node-addon-system/landlock-run": ["./native/system/packages/entry/src/index.ts"], "@deepseek-ai/node-addon-system/flock": ["./native/system/packages/entry/src/flock.ts"], - "@deepseek-ai/dsh-invariants": ["./packages/runtime-diagnostics/invariants/src/index.ts"], "@deepseek-ai/dsh-tool-call-timeout-policy": ["./packages/guard/timeout-policy/src"], "@deepseek-ai/dsh-spill-policy/notice": ["./packages/spill/spill-policy/src/notice.ts"], "@deepseek-ai/dsh-subprocess/control": ["./packages/subprocess/subprocess/src/control.ts"], @@ -111,7 +110,6 @@ "@deepseek-ai/dsh-typert-protocol": ["./packages/typert/protocol/src/index.ts"], "@deepseek-ai/dsh-typert-protocol/types": ["./packages/typert/protocol/src/types.ts"], "@deepseek-ai/dsh-typert-loader": ["./packages/typert/loader/src/index.ts"], - "@deepseek-ai/dsh-session/invariant": ["./packages/core/session/src/invariant.ts"], "@deepseek-ai/dsh-typert-registry/types": ["./packages/typert/registry/src/types.ts"], "@deepseek-ai/dsh-typert-generator": ["./packages/typert/generator/src/index.ts"], "@deepseek-ai/dsh-attachment/types": ["./packages/attachment/attachment/src/types.ts"], @@ -176,16 +174,6 @@ "@deepseek-ai/dsh-user-questions/types": ["./packages/interaction/user-questions/src/types.ts"], "@deepseek-ai/dsh-agent/types": ["./packages/core/agent/src/types.ts"], "@deepseek-ai/dsh-agent/brand": ["./packages/core/agent/src/brand.ts"], - "@deepseek-ai/dsh-agent/invariant": ["./packages/core/agent/src/invariant.ts"], - "@deepseek-ai/dsh-scope/invariant": ["./packages/core/scope/src/invariant.ts"], - "@deepseek-ai/dsh-agent-loop/invariant": ["./packages/core/agent-loop/src/invariant.ts"], - "@deepseek-ai/dsh-experimental-agent-team/invariant": ["./packages/experimental/agent-team/src/invariant.ts"], - // host/client package names prefix the group dir (dsh-client-), so - // the generated aliases below — which map a package only when its name is - // exactly dsh- — skip them; strip the group prefix with a dedicated - // wildcard per group instead. - "@deepseek-ai/dsh-host-*/invariant": ["./packages/host/*/src/invariant.ts"], - "@deepseek-ai/dsh-client-*/invariant": ["./packages/client/*/src/invariant.ts"], "@deepseek-ai/dsh-headless/startup": ["./packages/bundle/headless/src/startup.ts"], "@deepseek-ai/dsh-web-app/startup": ["./packages/bundle/web-app/src/startup.ts"], "@deepseek-ai/dsh-client-*/client": ["./packages/client/*/src/client"], @@ -237,7 +225,6 @@ "@deepseek-ai/dsh-client-store": ["./packages/client/store/src/index.ts"], "@deepseek-ai/dsh-client-ui-renderer": ["./packages/client/ui-renderer/src"], "@deepseek-ai/dsh-client-ui-renderer/client": ["./packages/client/ui-renderer/src/client"], - "@deepseek-ai/dsh-client-ui-renderer/invariant": ["./packages/client/ui-renderer/src/invariant.ts"], "@deepseek-ai/dsh-client-ui-session": ["./packages/client/ui-session/src"], "@deepseek-ai/dsh-client-ui-session/client": ["./packages/client/ui-session/src/client"], "@deepseek-ai/dsh-client-connection": ["./packages/client/connection/src"], @@ -288,7 +275,6 @@ "@deepseek-ai/dsh-client-ui-settings-web-search": ["./packages/client/ui-settings-web-search/src"], "@deepseek-ai/dsh-client-ui-jobs": ["./packages/client/ui-jobs/src"], "@deepseek-ai/dsh-client-ui-jobs/client": ["./packages/client/ui-jobs/src/client/index.ts"], - "@deepseek-ai/dsh-client-ui-jobs/invariant": ["./packages/client/ui-jobs/src/invariant.ts"], "@deepseek-ai/dsh-client-ui-schedule": ["./packages/client/ui-schedule/src"], "@deepseek-ai/dsh-client-ui-schedule/client": ["./packages/client/ui-schedule/src/client/index.ts"], "@deepseek-ai/dsh-client-ui-plan": ["./packages/client/ui-plan/src"], @@ -348,7 +334,6 @@ "@deepseek-ai/dsh-agent-loop-testkit": ["./packages/test-support/agent-loop-testkit/src"], "@deepseek-ai/dsh-agent-preset": ["./packages/preset/agent-preset/src"], "@deepseek-ai/dsh-agent-preset-registry": ["./packages/preset/agent-preset-registry/src"], - "@deepseek-ai/dsh-agent-preset-registry/invariant": ["./packages/preset/agent-preset-registry/src/invariant.ts"], "@deepseek-ai/dsh-agent-tool-presentation": ["./packages/core/agent-tool-presentation/src"], "@deepseek-ai/dsh-anonymous-user-id": ["./packages/identity/anonymous-user-id/src"], "@deepseek-ai/dsh-app-boot": ["./packages/boot/app-boot/src"], @@ -356,7 +341,6 @@ "@deepseek-ai/dsh-attachment": ["./packages/attachment/attachment/src"], "@deepseek-ai/dsh-attachment-local": ["./packages/attachment/attachment-local/src"], "@deepseek-ai/dsh-authorization": ["./packages/credentials/authorization/src"], - "@deepseek-ai/dsh-authorization/invariant": ["./packages/credentials/authorization/src/invariant.ts"], "@deepseek-ai/dsh-base": ["./packages/bundle/base/src"], "@deepseek-ai/dsh-bash-local": ["./packages/shell/bash-local/src"], "@deepseek-ai/dsh-bash-sandbox": ["./packages/shell/bash-sandbox/src"], @@ -367,44 +351,35 @@ "@deepseek-ai/dsh-command-feedback": ["./packages/feedback/command-feedback/src"], "@deepseek-ai/dsh-command-goal": ["./packages/goal/command-goal/src"], "@deepseek-ai/dsh-commands": ["./packages/interaction/commands/src"], - "@deepseek-ai/dsh-commands/invariant": ["./packages/interaction/commands/src/invariant.ts"], "@deepseek-ai/dsh-compaction": ["./packages/compaction/compaction/src"], - "@deepseek-ai/dsh-compaction/invariant": ["./packages/compaction/compaction/src/invariant.ts"], "@deepseek-ai/dsh-compaction-basic": ["./packages/compaction/compaction-basic/src"], "@deepseek-ai/dsh-compaction-image-offload": ["./packages/compaction/compaction-image-offload/src"], "@deepseek-ai/dsh-compaction-tool-result-pruner": ["./packages/compaction/compaction-tool-result-pruner/src"], "@deepseek-ai/dsh-config-editor": ["./packages/boot/config-editor/src"], "@deepseek-ai/dsh-credentials": ["./packages/credentials/credentials/src"], - "@deepseek-ai/dsh-credentials/invariant": ["./packages/credentials/credentials/src/invariant.ts"], "@deepseek-ai/dsh-credentials-local": ["./packages/credentials/credentials-local/src"], "@deepseek-ai/dsh-deepseek-llm-api-extensions": ["./packages/llm/deepseek-llm-api-extensions/src"], "@deepseek-ai/dsh-deque": ["./packages/util/deque/src"], "@deepseek-ai/dsh-file-reference": ["./packages/context/file-reference/src"], "@deepseek-ai/dsh-file-reference-local": ["./packages/context/file-reference-local/src"], "@deepseek-ai/dsh-fs": ["./packages/fs/fs/src"], - "@deepseek-ai/dsh-fs/invariant": ["./packages/fs/fs/src/invariant.ts"], "@deepseek-ai/dsh-fs-local": ["./packages/fs/fs-local/src"], "@deepseek-ai/dsh-fs-observation-policy": ["./packages/fs/fs-observation-policy/src"], "@deepseek-ai/dsh-fs-sandbox": ["./packages/fs/fs-sandbox/src"], "@deepseek-ai/dsh-fs-ssh": ["./packages/ssh/fs-ssh/src"], "@deepseek-ai/dsh-goal": ["./packages/goal/goal/src"], - "@deepseek-ai/dsh-goal/invariant": ["./packages/goal/goal/src/invariant.ts"], "@deepseek-ai/dsh-goal-round-driver": ["./packages/goal/goal-round-driver/src"], - "@deepseek-ai/dsh-goal-round-driver/invariant": ["./packages/goal/goal-round-driver/src/invariant.ts"], "@deepseek-ai/dsh-headless": ["./packages/bundle/headless/src"], "@deepseek-ai/dsh-home-paths": ["./packages/util/home-paths/src"], "@deepseek-ai/dsh-hook-protocol": ["./packages/hooks/hook-protocol/src"], - "@deepseek-ai/dsh-hook-protocol/invariant": ["./packages/hooks/hook-protocol/src/invariant.ts"], "@deepseek-ai/dsh-hooks-claude-code": ["./packages/hooks/hooks-claude-code/src"], "@deepseek-ai/dsh-hooks-codex": ["./packages/hooks/hooks-codex/src"], "@deepseek-ai/dsh-http-proxy": ["./packages/util/http-proxy/src"], "@deepseek-ai/dsh-jobs": ["./packages/jobs/jobs/src"], - "@deepseek-ai/dsh-jobs/invariant": ["./packages/jobs/jobs/src/invariant.ts"], "@deepseek-ai/dsh-jobs-local": ["./packages/jobs/jobs-local/src"], "@deepseek-ai/dsh-launch-environment": ["./packages/util/launch-environment/src"], "@deepseek-ai/dsh-lazy-require": ["./packages/util/lazy-require/src"], "@deepseek-ai/dsh-llm": ["./packages/llm/llm/src"], - "@deepseek-ai/dsh-llm/invariant": ["./packages/llm/llm/src/invariant.ts"], "@deepseek-ai/dsh-llm-deepseek": ["./packages/llm/llm-deepseek/src"], "@deepseek-ai/dsh-llm-deepseek-account": ["./packages/llm/llm-deepseek-account/src"], "@deepseek-ai/dsh-llm-deepseek-api-key": ["./packages/llm/llm-deepseek-api-key/src"], @@ -412,7 +387,6 @@ "@deepseek-ai/dsh-llm-pi-ai": ["./packages/llm/llm-pi-ai/src"], "@deepseek-ai/dsh-llm-replay": ["./packages/test-support/llm-replay/src"], "@deepseek-ai/dsh-llm-retry": ["./packages/llm/llm-retry/src"], - "@deepseek-ai/dsh-llm-retry/invariant": ["./packages/llm/llm-retry/src/invariant.ts"], "@deepseek-ai/dsh-loader-smoke": ["./packages/test-support/loader-smoke/src"], "@deepseek-ai/dsh-lsp": ["./packages/lsp/lsp/src"], "@deepseek-ai/dsh-lsp-stdio": ["./packages/lsp/lsp-stdio/src"], @@ -424,10 +398,8 @@ "@deepseek-ai/dsh-output-retention": ["./packages/util/output-retention/src"], "@deepseek-ai/dsh-package-manifest": ["./packages/util/package-manifest/src"], "@deepseek-ai/dsh-permission-presets": ["./packages/interaction/permission-presets/src"], - "@deepseek-ai/dsh-permission-presets/invariant": ["./packages/interaction/permission-presets/src/invariant.ts"], "@deepseek-ai/dsh-persona": ["./packages/preset/persona/src"], "@deepseek-ai/dsh-plan-mode": ["./packages/plan/plan-mode/src"], - "@deepseek-ai/dsh-plan-mode/invariant": ["./packages/plan/plan-mode/src/invariant.ts"], "@deepseek-ai/dsh-plugin-package-inventory-deepseek": ["./packages/llm/plugin-package-inventory-deepseek/src"], "@deepseek-ai/dsh-ptc-runtime": ["./packages/ptc-runtime/ptc-runtime/src"], "@deepseek-ai/dsh-ptc-runtime-node": ["./packages/ptc-runtime/ptc-runtime-node/src"], @@ -437,11 +409,9 @@ "@deepseek-ai/dsh-sandbox": ["./packages/sandbox/sandbox/src"], "@deepseek-ai/dsh-sandbox-local": ["./packages/sandbox/sandbox-local/src"], "@deepseek-ai/dsh-sandbox-policy": ["./packages/sandbox/sandbox-policy/src"], - "@deepseek-ai/dsh-sandbox-policy/invariant": ["./packages/sandbox/sandbox-policy/src/invariant.ts"], "@deepseek-ai/dsh-sandbox-ssh": ["./packages/ssh/sandbox-ssh/src"], "@deepseek-ai/dsh-sandbox-windows-acl": ["./packages/sandbox/sandbox-windows-acl/src"], "@deepseek-ai/dsh-schedule": ["./packages/schedule/schedule/src"], - "@deepseek-ai/dsh-schedule/invariant": ["./packages/schedule/schedule/src/invariant.ts"], "@deepseek-ai/dsh-scope": ["./packages/core/scope/src"], "@deepseek-ai/dsh-sdk-app": ["./packages/bundle/sdk-app/src"], "@deepseek-ai/dsh-sdk-minimal": ["./packages/bundle/sdk-minimal/src"], @@ -454,7 +424,6 @@ "@deepseek-ai/dsh-session-format-v2-to-v3": ["./packages/session/session-format-v2-to-v3/src"], "@deepseek-ai/dsh-session-format-v3-to-v4": ["./packages/session/session-format-v3-to-v4/src"], "@deepseek-ai/dsh-session-log-deepseek": ["./packages/session/session-log-deepseek/src"], - "@deepseek-ai/dsh-session-log-deepseek/invariant": ["./packages/session/session-log-deepseek/src/invariant.ts"], "@deepseek-ai/dsh-session-log-export": ["./packages/session-query/session-log-export/src"], "@deepseek-ai/dsh-session-persistence": ["./packages/session/session-persistence/src"], "@deepseek-ai/dsh-session-persistence-jsonl": ["./packages/session/session-persistence-jsonl/src"], @@ -468,7 +437,6 @@ "@deepseek-ai/dsh-session-telemetry": ["./packages/session/session-telemetry/src"], "@deepseek-ai/dsh-session-telemetry-otel": ["./packages/session/session-telemetry-otel/src"], "@deepseek-ai/dsh-session-title": ["./packages/session/session-title/src"], - "@deepseek-ai/dsh-session-title/invariant": ["./packages/session/session-title/src/invariant.ts"], "@deepseek-ai/dsh-session-title-all-prompts-llm": ["./packages/session/session-title-all-prompts-llm/src"], "@deepseek-ai/dsh-session-title-first-prompt-llm": ["./packages/session/session-title-first-prompt-llm/src"], "@deepseek-ai/dsh-session-title-llm": ["./packages/session/session-title-llm/src"], @@ -485,11 +453,9 @@ "@deepseek-ai/dsh-ssh": ["./packages/ssh/ssh/src"], "@deepseek-ai/dsh-storage": ["./packages/storage/storage/src"], "@deepseek-ai/dsh-storage-domain": ["./packages/storage/storage-domain/src"], - "@deepseek-ai/dsh-storage-domain/invariant": ["./packages/storage/storage-domain/src/invariant.ts"], "@deepseek-ai/dsh-storage-json": ["./packages/storage/storage-json/src"], "@deepseek-ai/dsh-storage-sqlite": ["./packages/storage/storage-sqlite/src"], "@deepseek-ai/dsh-subagent": ["./packages/subagent/subagent/src"], - "@deepseek-ai/dsh-subagent/invariant": ["./packages/subagent/subagent/src/invariant.ts"], "@deepseek-ai/dsh-subagent-acp": ["./packages/subagent/subagent-acp/src"], "@deepseek-ai/dsh-subagent-claude-code": ["./packages/subagent/subagent-claude-code/src"], "@deepseek-ai/dsh-subagent-codex": ["./packages/subagent/subagent-codex/src"], @@ -501,11 +467,9 @@ "@deepseek-ai/dsh-subprocess-local": ["./packages/subprocess/subprocess-local/src"], "@deepseek-ai/dsh-subprocess-ssh": ["./packages/ssh/subprocess-ssh/src"], "@deepseek-ai/dsh-system-prompt": ["./packages/core/system-prompt/src"], - "@deepseek-ai/dsh-system-prompt/invariant": ["./packages/core/system-prompt/src/invariant.ts"], "@deepseek-ai/dsh-terminal": ["./packages/terminal/terminal/src"], "@deepseek-ai/dsh-terminal-bash": ["./packages/terminal/terminal-bash/src"], "@deepseek-ai/dsh-time-context": ["./packages/context/time-context/src"], - "@deepseek-ai/dsh-time-context/invariant": ["./packages/context/time-context/src/invariant.ts"], "@deepseek-ai/dsh-timeout": ["./packages/util/timeout/src"], "@deepseek-ai/dsh-tmux-context": ["./packages/context/tmux-context/src"], "@deepseek-ai/dsh-token-meter": ["./packages/llm/token-meter/src"], @@ -525,19 +489,14 @@ "@deepseek-ai/dsh-tool-skill": ["./packages/skill/tool-skill/src"], "@deepseek-ai/dsh-tool-str-replace-editor": ["./packages/fs/tool-str-replace-editor/src"], "@deepseek-ai/dsh-tool-subagent": ["./packages/subagent/tool-subagent/src"], - "@deepseek-ai/dsh-tool-subagent/invariant": ["./packages/subagent/tool-subagent/src/invariant.ts"], "@deepseek-ai/dsh-tool-subagent-control": ["./packages/subagent/tool-subagent-control/src"], "@deepseek-ai/dsh-tool-terminal": ["./packages/terminal/tool-terminal/src"], "@deepseek-ai/dsh-tool-todo": ["./packages/todo/tool-todo/src"], - "@deepseek-ai/dsh-tool-todo/invariant": ["./packages/todo/tool-todo/src/invariant.ts"], "@deepseek-ai/dsh-tool-web": ["./packages/web/tool-web/src"], "@deepseek-ai/dsh-tool-workflow": ["./packages/workflow/tool-workflow/src"], - "@deepseek-ai/dsh-tool-workflow/invariant": ["./packages/workflow/tool-workflow/src/invariant.ts"], "@deepseek-ai/dsh-tool-workspace-dependencies": ["./packages/skill/tool-workspace-dependencies/src"], "@deepseek-ai/dsh-tools": ["./packages/core/tools/src"], - "@deepseek-ai/dsh-tools/invariant": ["./packages/core/tools/src/invariant.ts"], "@deepseek-ai/dsh-user-approval": ["./packages/interaction/user-approval/src"], - "@deepseek-ai/dsh-user-approval/invariant": ["./packages/interaction/user-approval/src/invariant.ts"], "@deepseek-ai/dsh-user-questions": ["./packages/interaction/user-questions/src"], "@deepseek-ai/dsh-web": ["./packages/web/web/src"], "@deepseek-ai/dsh-web-app": ["./packages/bundle/web-app/src"], @@ -546,14 +505,11 @@ "@deepseek-ai/dsh-web-search-exa": ["./packages/web/web-search-exa/src"], "@deepseek-ai/dsh-web-search-perplexity": ["./packages/web/web-search-perplexity/src"], "@deepseek-ai/dsh-webhook": ["./packages/webhook/webhook/src"], - "@deepseek-ai/dsh-webhook/invariant": ["./packages/webhook/webhook/src/invariant.ts"], "@deepseek-ai/dsh-webhook-github": ["./packages/webhook/webhook-github/src"], "@deepseek-ai/dsh-win32-process": ["./packages/subprocess/win32-process/src"], "@deepseek-ai/dsh-workflow": ["./packages/workflow/workflow/src"], - "@deepseek-ai/dsh-workflow/invariant": ["./packages/workflow/workflow/src/invariant.ts"], "@deepseek-ai/dsh-workflow-ptc": ["./packages/workflow/workflow-ptc/src"], "@deepseek-ai/dsh-workspace": ["./packages/workspace/workspace/src"], - "@deepseek-ai/dsh-workspace/invariant": ["./packages/workspace/workspace/src/invariant.ts"], "@deepseek-ai/dsh-workspace-changes": ["./packages/deliverables/workspace-changes/src"] // END generated package aliases } diff --git a/tsconfig.host.json b/tsconfig.host.json index 5c5c81b7e0..57b6af51ba 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -405,7 +405,6 @@ { "path": "./packages/spill/spill-local" }, { "path": "./packages/spill/spill-policy" }, { "path": "./packages/guard/timeout-policy" }, - { "path": "./packages/runtime-diagnostics/invariants" }, { "path": "./packages/test-support/agent-loop-testkit" }, { "path": "./packages/acp/acp" }, { "path": "./packages/bundle/acp-app" }, diff --git a/tsdown.config.ts b/tsdown.config.ts index 2435127329..c378de376a 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -22,7 +22,7 @@ export default defineConfig(({ env }) => { workspace: client ? ['vendor/*', 'packages/*/*', 'apps/cli'] : ['vendor/*', 'packages/*/*', 'apps/cli', 'apps/desktop-host'], - entry: client ? '' : ['lib/types/{index,invariant,startup}.js'], + entry: client ? '' : ['lib/types/{index,startup}.js'], outDir: 'lib', format: ['esm'], platform: 'node', diff --git a/vitest.config.ts b/vitest.config.ts index d0fd3c31f8..86dc766235 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -167,7 +167,7 @@ const processBoundTests = [ export default defineConfig({ plugins: [pathsPlugin(), standardDecoratorPlugin()], test: { - setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-invariants.ts', './scripts/test-dom-environment.ts'], + setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-dom-environment.ts'], // .tsx: client component specs (jsdom via per-file @vitest-environment pragma). include: testIncludes, exclude: platformUnsupportedTests, @@ -184,7 +184,7 @@ export default defineConfig({ // Linux, and Windows. Forked workers avoid that shared thread path. pool: 'forks', ...laneTestBudget, - setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-invariants.ts', './scripts/test-dom-environment.ts'], + setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-dom-environment.ts'], include: testIncludes, exclude: [ ...platformUnsupportedTests, @@ -200,7 +200,7 @@ export default defineConfig({ execArgv: vitestExecArgv, pool: 'forks', ...laneTestBudget, - setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-invariants.ts', './scripts/test-dom-environment.ts'], + setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-dom-environment.ts'], include: processBoundTests, exclude: [ ...platformUnsupportedTests, @@ -299,11 +299,9 @@ export default defineConfig({ // whose remaining branches need real-composition/process harnesses. // TODO(gui): cover and remove with the client test lane above. 'packages/client/modules/src/index.ts', - 'packages/client/modules/src/invariant.ts', 'packages/client/modules/src/client/index.ts', 'packages/client/modules/src/client/manifest.ts', 'packages/client/hmr/src/index.ts', - 'packages/client/hmr/src/invariant.ts', 'packages/client/connection/src/index.ts', 'packages/client/connection/src/http-bridge.ts', // This assembly imports generated Host-for-Client code that exists @@ -356,7 +354,6 @@ export default defineConfig({ // registry's drive tails need the same maturing lanes. TODO(gui): // cover and remove with the client test lane above. 'packages/interaction/commands/src/index.ts', - 'packages/interaction/commands/src/invariant.ts', 'packages/session/session-projection/src/index.ts', ...windowsUnsupportedCoveragePackages.map(path => `${path}/src/**/*.ts`), ...windowsOnlyCoverageExclusions, diff --git a/vitest.e2e.config.ts b/vitest.e2e.config.ts index 882357bb69..159f3da4d8 100644 --- a/vitest.e2e.config.ts +++ b/vitest.e2e.config.ts @@ -39,7 +39,7 @@ export default defineConfig({ plugins: [tsconfigPaths({ projects: ['./tsconfig.base.json'] }), standardDecoratorPlugin()], test: { execArgv: vitestExecArgv, - setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-invariants.ts'], + setupFiles: ['./scripts/test-proxy-environment.ts'], // Explicit Node app owners only: apps/web/tests/*.e2e.ts needs the built // frontend dist and runs under vitest.web.config.ts (the test:web job). include: ['packages/*/*/tests/**/*.e2e.ts', 'apps/cli/tests/**/*.e2e.ts', 'apps/desktop/tests/**/*.e2e.ts'], diff --git a/vitest.expected.config.ts b/vitest.expected.config.ts index bf97c7e30e..d63b24fe12 100644 --- a/vitest.expected.config.ts +++ b/vitest.expected.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ plugins: [tsconfigPaths({ projects: ['./tsconfig.base.json'] }), standardDecoratorPlugin()], test: { execArgv: vitestExecArgv, - setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-invariants.ts'], + setupFiles: ['./scripts/test-proxy-environment.ts'], include: [ 'apps/cli/tests/**/*.expected.e2e.ts', ], diff --git a/vitest.snapshot.config.ts b/vitest.snapshot.config.ts index e29b1a395d..f17973a0f8 100644 --- a/vitest.snapshot.config.ts +++ b/vitest.snapshot.config.ts @@ -47,7 +47,7 @@ export default defineConfig({ plugins: [tsconfigPaths({ projects: ['./tsconfig.base.json'] }), standardDecoratorPlugin()], test: { execArgv: vitestExecArgv, - setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-invariants.ts'], + setupFiles: ['./scripts/test-proxy-environment.ts'], include: [ 'scripts/session-snapshot-corpus.corpus.ts', // The assembled Web snapshot executes generated client bundles; source diff --git a/website/docs.ts b/website/docs.ts index 92016d10c3..62a7ee9263 100644 --- a/website/docs.ts +++ b/website/docs.ts @@ -300,7 +300,6 @@ const subsystemGroups = [ ['内核与作用域', 'Core and scopes', [ ['core.md', '核心', 'Core'], ['scope.md', '作用域', 'Scopes'], - ['invariants.md', '运行时不变式', 'Runtime invariants'], ]], ['会话与持久化', 'Sessions and persistence', [ ['session.md', '会话', 'Sessions'],