dsh-ptc-plus 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,7 +26,7 @@ Compatibility decisions have one owner per public contract and execution
26
26
  environment. `src/client-host-compat.js` owns Client preset reads and subscriptions
27
27
  and composer compatibility. UI consumers own feature eligibility and slot
28
28
  registration. `scripts/dsh-host-contract.mjs` owns selected-installation package
29
- resolution, tools presentation and persona field adaptation for development and
29
+ resolution, tools presentation, persona fields and public persistence plugin selection for development and
30
30
  acceptance commands. Host runtime contracts remain in their existing focused
31
31
  owners, including `internal/session-events.js`, `internal/settings-compat.js`, and
32
32
  `internal/execution-seam-compat.js`, which owns which program-execution service the
@@ -63,7 +63,7 @@ capability, storage and observation rules independently of transport.
63
63
 
64
64
  **Pin one verified DSH release.** A pin makes one historical environment reproducible but turns normal upstream progress into an artificial incompatibility and allows the implementation to drift from the release users actually install.
65
65
 
66
- **Maintain a supported-version range.** A range communicates broader intent, but semver cannot prove compatibility with evolving extension contracts. Maintaining the range duplicates evidence already produced by contract and end-to-end tests while still requiring every new release to be evaluated.
66
+ **Maintain a supported-version range.** A range communicates broader intent, but semver cannot prove compatibility with evolving extension contracts. Maintaining the range duplicates evidence already produced by contract and end-to-end tests while still requiring every new release to be evaluated. The package therefore declares `engines.dsh: "*"` for the Host-owned manifest preflight, preserving capability-based admission without a release allowlist. The packed contract suite runs the selected Host's public `evaluatePluginCompatibility` before importing plugin code; required service peers still establish package resolution.
67
67
 
68
68
  **Treat the upstream default tool projection as an immutable plugin limit.** This avoids scoped presentation work, but discards capabilities that the public registry supports and can force valid model calls through lossy aliases. Defaults describe the host's built-in product choice; public extension contracts define what a plugin may add. Invalid out-of-surface calls may still be normalized when their intended native target is provable.
69
69
 
@@ -77,7 +77,7 @@ The existing REPL supplies execution feedback; the handoff accepts final source
77
77
 
78
78
  The first validated candidate becomes one disabled memory draft. Private `dshPtcPlusBindingDraft` metadata version 2 contains the generation, opaque locator, and the accepted candidate with request ID, command ID, version, mode, and complete source-owned entry. Later results carry `candidate: null`; version 1 remains readable. The `ptcPlusBindingDraft` projection, state version 4, pairs command and turn lifecycle with acceptance, retains read-only history across seeds and owner generations, and never revives a historical locator. Malformed historical presentation cannot block subsequent valid execution. The management Remote uses the authenticated Host Gateway and provides no caller/session identity, so draft operations require the locator rather than a payload session ID. The `draft-review` endpoint serves the accepted source and any completed action while its owning lifecycle remains available.
79
79
 
80
- Confirmed enable/disable of a saved binding prepares a separate `ptc-binding-toggle/v1` receipt. If the Session already has its Host system head, the owner appends it through public `Session.append('user/message', message, {surfaceOp: 'append'})`; otherwise it supplies the pending receipt only with an accepted `agent/pre-step` message batch, after the Host establishes its first system node. Rejected or cancelled steps do not publish it, and the public `session/event` commit notification retires each pending receipt by message identity, so later compaction cannot resurrect it. The receipt describes configuration persistence, never catalog delivery or worker initialization, and never wakes an Agent. Before first acceptance the management result supplies immediate UI feedback; pending history is process-local and does not claim durability before publication. Storage failure emits no success receipt.
80
+ Confirmed enable/disable of a saved binding appends a separate `ptc-binding-toggle/v1` receipt through public `Session.append('user/message', message, {surfaceOp: 'append'})` only after the Session has its Host-owned first accepted system head. Before that boundary, management changes initial configuration and supplies immediate UI feedback without appending or queueing session receipts. The first accepted request receives only the current configured API catalog. The receipt describes configuration persistence, never catalog delivery or worker initialization, and never wakes an Agent. Storage failure emits no success receipt. Existing historical receipts remain readable. Since there is no pending receipt queue, later acceptance or compaction cannot resurrect pre-session toggle history.
81
81
 
82
82
  After a user save or discard completes, the Host consumes the writable locator and appends a structured plugin notice through public `Session.append('user/message', ..., {surfaceOp: 'append', sourceEventSeqs: [acceptingResultSeq]})`. The notice contains only request ID, entry ID, saved/discarded state, and enabled state. It asserts persistence, never session activation, and contains no source or locator. The projection accepts it only when identity and the accepting source relation match. Card remounts use this historical source and action, never a later store entry with the same ID. A failed notice append cannot undo the write or restore save eligibility; missing durable evidence remains unknown after restart. In-flight storage settlement cannot resurrect a locator already revoked by lifecycle cleanup. Clearing an unsaved draft alone does not append a persistence notice.
83
83
 
@@ -28,7 +28,7 @@ A session capability plan records the effective enablement and disclosure choice
28
28
 
29
29
  Explicit session choices and saved choices have separate owners. `/capabilities set session <encoded-id> <full|on-demand|closed|automatic|inherit>` changes only the current session. `automatic` explicitly removes that identity's saved manual override from the effective input; `inherit` removes its temporary override and resumes the adopted source choice. Ordinary `/capabilities refresh` adopts current saved configuration while retaining temporary choices. `/capabilities set global <encoded-id> <full|on-demand|closed|automatic>` saves through the public revision-checked settings writer and updates this session in the same Host maintenance operation, clearing only that identity's temporary choice. Other existing sessions remain frozen. Server choices allow only `closed`, `automatic` and session `inherit`; their closure precedes child choices, and reopening preserves children.
30
30
 
31
- An initialized session publishes the complete new snapshot through its real append-only plan message. Before the protected system head exists, the formal `/capabilities` command returns its validated pending proposal in the structured receipt inside `CommandResult.text`; the Host alone records the paired `command/run` and successful `command/done`. These log-only command facts are not model knowledge. The first accepted step publishes that proposal to the model. Restoration reads successful receipts with exact command identity and raw input pairing, and plan messages, in order after the most recent explicit provider disablement; a routine provider release (`unloaded`) is an infrastructure fact and keeps the recorded plan as the session's restored baseline, so saved configuration still does not redistribute a resumed session. Restoration validates source/effective equality and the planner result, and does not repeat a global write, load a Skill body or execute a historical effect. Failed, cancelled, unpaired or damaged command facts cannot prove a choice; an unconfirmed live proposal is discarded before a later accepted step, and a late handler result cannot revive it. External plugins must not call `appendPluginRecord`, even though it is exported. History observations use the public `sessionQuery` cut; new production reads do not use deprecated synchronous Session readers.
31
+ An initialized session publishes the complete new snapshot through its real append-only plan message. Before the protected system head exists, the formal `/capabilities` command returns its validated pending proposal in the structured receipt inside `CommandResult.text`; the Host alone records the paired `command/run` and successful `command/done`. These log-only command facts are not model knowledge. The first accepted step publishes only the final initial plan to the model, without refresh/replacement wording for an environment the model has never received. Repeated preparation or explicit choices replace that uncommitted initial proposal; they do not append intermediate change notifications. Restoration reads successful receipts with exact command identity and raw input pairing, and plan messages, in order after the most recent explicit provider disablement; a routine provider release (`unloaded`) is an infrastructure fact and keeps the recorded plan as the session's restored baseline, so saved configuration still does not redistribute a resumed session. Restoration validates source/effective equality and the planner result, and does not repeat a global write, load a Skill body or execute a historical effect. Failed, cancelled, unpaired or damaged command facts cannot prove a choice; an unconfirmed live proposal is discarded before a later accepted step, and a late handler result cannot revive it. External plugins must not call `appendPluginRecord`, even though it is exported. History observations use the public `sessionQuery` cut; new production reads do not use deprecated synchronous Session readers.
32
32
 
33
33
  All mutations use public `agent.runMaintenance`, which may reject a busy Agent. The plugin does not queue another session scheduler. Identity, inventory and management preflight precede a global write; the write uses the settings entry's actual namespace and revision. Global saving and session publication are different transactions: a saved choice remains saved if subsequent publication is refused or cancelled, while the old effective environment is retained and the receipt reports saved-but-not-applied. No automatic rollback overwrites a later settings writer. A committed operation followed by lost UI connectivity has an unknown UI outcome, so users inspect the actual plan before retrying. Target-specific read-only catalog commands include closed items for user re-enablement without publishing them to model discovery or counting them as use.
34
34
 
@@ -0,0 +1,35 @@
1
+ # Coexist With The Official Persistent REPL
2
+
3
+ ## Problem
4
+
5
+ DSH added an opt-in official Node execution mode (`executionMode: persistent-in-memory`) that retains a confined REPL per Agent, and extended the `ptcRuntime` seam with `PtcRunRequest.stateOwner`, `PtcRuntime.programFormat` and `PtcRuntime.release(owner)`. The host `run_code` consumer registers `runtime.release(agent)` at Agent disposal for every provider, passes `stateOwner` on requests, and program-format consumers (the workflow adapter, cua) wrap programs according to `programFormat`. PTC Plus owns the same seam: without a policy, its provider surface lacked the new members so Agent disposal could fail, non-session consumers could not see the format of the provider that actually executes their programs, and an explicitly enabled official mode stayed silently inert for session cells while the plugin kept owning them.
6
+
7
+ ## Decision
8
+
9
+ `internal/execution-seam-compat.js` and `internal/execution-provider-owner.js` forward `programFormat` to the live original generation and `release(owner)` to the originals that served that owner. These describe the provider that actually performs an execution: owner-keyed retained environments and the executed source format belong to the original, which still serves non-session consumers while the plugin owns session cells, and a bounded generation that lacks the members falls back to `'function-body'` and a resolved no-op. Execution retirement does not retire cleanup: the composition retains weak owner-to-original provenance for dispatched `stateOwner` programs, so Agent cleanup through a captured adapter still reaches a surviving original after bundle removal or source replacement. Successful cleanup removes that original from the owner's pending set; failed cleanup remains available for explicit retry, with synchronous and asynchronous failures observable. An owner without recorded dispatch delegates to the live original, or receives a resolved no-op if execution is unavailable. `executionInstructions`, `sandboxMode` and `timeout` keep their existing withholding policy while PTC Plus owns session execution, because those describe the session kernel ([ADR 0028](0028-attach-to-the-host-ptc-execution-seam.md)).
10
+
11
+ `officialReplPriority` (default `false`, State and recovery) selects who owns PTC session execution when the official persistent REPL is live. The decision reads the live capability — `provider.programFormat === 'repl'` — rather than a release number, and is re-evaluated on every configuration and host source change. Execution ownership is an attachment shape: `owned` installs the session runtime, `deferred` installs `internal/official-repl-priority.js`'s runtime record, which owns no execution resources and answers the attachment contract with empty contexts. Changing the shape retires the current attachment, disposing its live kernels, and installs the other shape before later cells; no committed cell mixes the two contracts. While deferred, `run_code` and its descriptors stay with the official provider and the plugin's REPL guidance, session-state semantics, recovery, editor tool, capability SDK, REPL observation and binding workbench are suspended with the attachment, while the settings card, enablement and stored global bindings stay available so the switch can be reversed.
12
+
13
+ Cells executed under the official mode produce no plugin journal records. They are an unrecorded execution interval: recovery never replays them, and they do not extend plugin state as recovery evidence.
14
+
15
+ ## Alternatives considered
16
+
17
+ **Gate on the DSH release or on reading the original row's `executionMode` configuration.** The mode is a live runtime capability and a configuration fact outside the plugin's public contract; `programFormat` is the provider-owned proof that a REPL actually retains state, and it degrades safely on older generations that lack it.
18
+
19
+ **Keep owning execution unconditionally and only add the missing seam members.** That repairs disposal, but it silently ignores an explicit official mode, cannot honor a user's choice to run the official semantics, and keeps two competing REPL contracts alive for the same session surface.
20
+
21
+ **Suspend execution in place inside the installed runtime.** Toggling individual owners (prompt, tool bridge, plans, projections) mixes the two execution contracts in one attachment and creates states no single owner can prove; the installation shape is the granularity the plugin already uses for language retirement.
22
+
23
+ **Keep management owners (global binding workbench, capability workbench) outside the deferred attachment.** Splitting the management surface from the execution runtime is a larger refactor that would change ownership for every state, not only deferral; it remains possible future work instead of being smuggled into this decision.
24
+
25
+ **Forward `release(owner)` only while delegating execution.** Non-session consumers keep running through the original provider even while PTC Plus owns session cells, so owner-keyed environments can exist in every state; a delegation-only forward would leave those environments unreleased and the registered provider contract incomplete.
26
+
27
+ ## Consequences
28
+
29
+ Agent disposal on the current official host releases cleanly, and both the format and the owner release reach the generation that owns the environment.
30
+
31
+ Leaving plugin ownership disposes its live kernels, and plugin-mode state contraction follows the existing disposal rules. Official in-memory state belongs to DSH's Agent/provider lifecycle and may remain available when priority is switched back to the official provider; the deferred attachment does not clear it. State built during an official interlude is never replayed or claimed by the plugin. After returning to plugin ownership, the session continues from plugin-verifiable evidence only.
32
+
33
+ Deferred mode intentionally reduces the plugin's computation surface (no REPL guidance, session semantics, recovery, editor, capability SDK, REPL observation or binding workbench for that attachment); the setting copy and README state this so the user's choice is informed.
34
+
35
+ The plugin keeps withholding `sandboxMode` while it owns execution, so deployments that require file confinement still need another PTC execution environment — unchanged by this decision, and now also visible in the workflow adapter's explicit refusal.
@@ -22,3 +22,5 @@ This directory records stable design constraints for PTC Plus. An ADR states a d
22
22
  - [0042: Persist Provider Availability And Handback](0042-persist-provider-availability-and-handback.md) — accepted-step availability, public persistent release and separately sourced configuration receipts.
23
23
 
24
24
  - [0043: Compose Capability and Context Policies](0043-compose-capability-and-context-policies.md) — independent availability, disclosure, allocation, collection, recovery and presentation inputs with historical replay generations.
25
+
26
+ - [0044: Coexist With The Official Persistent REPL](0044-coexist-with-the-official-persistent-repl.md) — forwarded owner release and program format, plus live-capability ownership selection between PTC Plus and the official persistent REPL.
@@ -50,7 +50,7 @@ capabilityCoverage(state, { userId, workspaceId, id, revision })
50
50
 
51
51
  同一家族、能力、范围只贡献一个使用位。工作区观察和全局观察分别归约,跨工作区后代不增加全局权重。每项能力和范围使用最近 N 个合格家族;续接修改已有身份的使用事实,不改变原资格顺序。正在运行或结算未知的家族不形成负样本。淘汰前沿阻止旧观察重读后重新进入已有窗口;不使用自然时间衰减。
52
52
 
53
- `capabilityCoverage` 返回样本量、使用量、窗口和覆盖率;没有样本时覆盖率为 `null`。窗口已淘汰旧证据后,近期家族恢复为运行中或结算未知,若剩余已结算样本不足 N,状态为 `incomplete-window`,样本量、使用量和覆盖率均为 `null`;不能把无法重建的窗口子集用于自动分配。再次具有 N 个已结算样本时恢复有效。超过显式容量上限后状态为 `capacity-exceeded`,覆盖依据为 `null`。这两种不可用证据均回退手动基线;未淘汰证据的正常冷启动仍可返回实际样本数。结构损坏由验证器拒绝,存储适配器将读取失败转为统计不可用诊断,保留手动配置并继续合法计算。
53
+ `capabilityCoverage` 返回样本量、使用量、窗口和覆盖率;没有可用样本时覆盖率为 `null`。尚无任何相关记录时状态为 `no-samples`,窗口内已有记录但家族当前未结算时状态为 `pending-settlement`,两者样本量、使用量和覆盖率均为 `null`,界面显示破折号和证据状态而不是零。窗口已淘汰旧证据后,近期家族恢复为运行中或结算未知,若剩余已结算样本不足 N,状态为 `incomplete-window`,样本量、使用量和覆盖率均为 `null`;不能把无法重建的窗口子集用于自动分配。再次具有 N 个已结算样本时恢复有效。超过显式容量上限后状态为 `capacity-exceeded`,覆盖依据为 `null`。这些不可用证据均回退手动基线;未淘汰证据的正常冷启动仍可返回实际样本数。结构损坏由验证器拒绝,存储适配器将读取失败转为统计不可用诊断,保留手动配置并继续合法计算。
54
54
 
55
55
  组件状态是可验证的 JSON 数据。存储适配器使用宿主 home 路径下的 `ptc-plus/capability-statistics.json`,以 `withFileLock` 和 `writeFileAtomic` 串行化跨实例更新,仅保存身份、资格、使用和结算元数据。续接修订原家族使用位,资格顺序保持不变。公开家族 census 不完整、未进入 step 或 turn 非正常完成时不产生负样本。
56
56
 
@@ -86,7 +86,7 @@ Skill 目录的 `snapshot.complete` 由公共 registry 拥有,表示本次 pro
86
86
 
87
87
  设置刷新按钮通过只读 `targets` 获取活跃 PTC 会话身份,并要求明确选择目标;星光菜单从公共 slot 读取当前会话的命令可用性,与绑定编写共用输入框保护,将 `/capabilities refresh` 预填到空输入框并关闭菜单,等待用户发送。设置按钮由 `CapabilityRefreshButton` 通过 authenticated `commands.execute` 直接执行;最终都委托同一 Host 刷新 owner。身份观察不读取或准备冻结方案,管理关闭时仍可主动同步关闭配置。目录/方案重读保持只读。Client 在取消、断线、preset 改变、会话切换、卸载或插件关闭后丢弃迟到回执;停止等待不表示撤销已提交的 Host 效果。
88
88
 
89
- 管理默认关闭。设置工作台分为能力、上下文、统计、配置四个页签。能力页默认直达清单,名称下展示精确 ID,类型和来源独立显示;行内启停只修改可用性,上下文选择、常用集与继承规则在单项详情中独立编辑。筛选在独立小窗中按需打开,提供类型、来源、规则、常用与可用状态筛选;五列表头支持升序、降序、默认三态排序,会话列表复用同一展示排序 owner。批量设置打开一次性小弹窗,将当前筛选中真实可用的目标固化为单项变更,确认才写入;默认、类别和来源的持久继承规则使用独立入口。类型切换清除不兼容来源,Tools 与 MCP 工具的浏览入口区分,来源规则只使用已证明的 server 身份。详情区分保存值与明确选定会话的结果。上下文页以能力说明、说明恢复、结果与日志三个固定目的地组织双栏设置,各项开关及额度独立,关闭保留参数;预算预览位于能力说明详情。全部开关使用官方 capsule track、圆形 thumb、尺寸和主题 token。统计页可切换查看所选活跃 PTC 会话的当前工作区或全局使用数、合格家族样本数和覆盖率;不完整窗口或不可用证据保持明确状态。能力说明的预算预览从该会话当前 SDK/Skill/MCP 清单、已保存配置与实际统计调用同一纯方案 owner,显示完整/按需/关闭选择、原因、正文大小、包含共享依赖的总预算和超额。观察通过 `allocation` RPC,使用只读 assembly observation,不准备或发布会话方案,不登记样本、读取 Skill 正文、调用工具或请求模型;目标退出和迟到回复会失效。配置序列化由 Client 完成,用户无需编写 JSON;最低样本数超过窗口等无效输入不提交。已有配置中暂不可用的能力选择保留并可撤销。配置页以左侧命名配置库、右侧所选详情组织;唯一保存当前设置、导入和更多动作在列表下方,会话观察另行展开,与已保存配置分开。`capabilityProfiles` 的 `ptc-capability-profiles/v1` 文档保存最多 100 份具名能力设置,支持命名保存、重命名、删除、文件导入导出和一次公共设置 mutation 切换;导入不自动应用,同名导入生成新名称。会话观察同时提供源设置,用户可读取并命名保存,不复制统计证据或计算状态。切换配置不刷新旧会话,明确选定目标的正式 Host 命令才提交刷新;原冻结方案继续展示选择、原因、覆盖率、显式超额和待刷新状态。格式与边界见 [ADR 0040](adr/0040-separate-capability-configurations-from-session-observations.md)。有界 `capabilities.search` 的原接口保持兼容,见[能力投影](capability-projection.md)。大结果和日志使用宿主 spill 服务保留完整展示,存储失败回退原文;canonical 程序值不受预览影响。
89
+ 管理默认关闭。设置工作台分为能力、上下文、统计、配置四个页签。能力页默认直达清单,名称下展示精确 ID,类型和来源独立显示;行内启停只修改可用性,上下文选择、常用集与继承规则在单项详情中独立编辑。筛选在独立小窗中按需打开,提供类型、来源、规则、常用与可用状态筛选;五列表头支持升序、降序、默认三态排序,会话列表复用同一展示排序 owner。清单支持勾选多个条目(表头复选框选择当前筛选结果);批量设置打开一次性小弹窗,将所选真实可用目标固化为单项变更,确认才写入,会话工作台以同样的选择方式把批量选择暂存为待应用更改,由底部按钮决定应用到会话或全局;默认、类别和来源的持久继承规则使用独立入口。类型切换清除不兼容来源,Tools 与 MCP 工具的浏览入口区分,来源规则只使用已证明的 server 身份。详情区分保存值与明确选定会话的结果。上下文页以能力说明、说明恢复、结果与日志三个固定目的地组织双栏设置,各项开关及额度独立,关闭保留参数;预算预览位于能力说明详情。全部开关使用官方 capsule track、圆形 thumb、尺寸和主题 token。统计页可切换查看所选活跃 PTC 会话的当前工作区或全局使用数、合格家族样本数和覆盖率;不完整窗口或不可用证据保持明确状态。能力说明的预算预览从该会话当前 SDK/Skill/MCP 清单、已保存配置与实际统计调用同一纯方案 owner,显示完整/按需/关闭选择、原因、正文大小、包含共享依赖的总预算和超额。观察通过 `allocation` RPC,使用只读 assembly observation,不准备或发布会话方案,不登记样本、读取 Skill 正文、调用工具或请求模型;目标退出和迟到回复会失效。配置序列化由 Client 完成,用户无需编写 JSON;最低样本数超过窗口等无效输入不提交。已有配置中暂不可用的能力选择保留并可撤销。配置页以左侧命名配置库、右侧所选详情组织;唯一保存当前设置、导入和更多动作在列表下方,会话观察另行展开,与已保存配置分开。`capabilityProfiles` 的 `ptc-capability-profiles/v1` 文档保存最多 100 份具名能力设置,支持命名保存、重命名、删除、文件导入导出和一次公共设置 mutation 切换;导入不自动应用,同名导入生成新名称。会话观察同时提供源设置,用户可读取并命名保存,不复制统计证据或计算状态。切换配置不刷新旧会话,明确选定目标的正式 Host 命令才提交刷新;原冻结方案继续展示选择、原因、覆盖率、显式超额和待刷新状态。格式与边界见 [ADR 0040](adr/0040-separate-capability-configurations-from-session-observations.md)。有界 `capabilities.search` 的原接口保持兼容,见[能力投影](capability-projection.md)。大结果和日志使用宿主 spill 服务保留完整展示,存储失败回退原文;canonical 程序值不受预览影响。
90
90
 
91
91
  组件和接入证据见 `test/capability-plan.test.js`、`test/capability-statistics.test.js`、`test/capability-environment.test.js`、`test/plugin-program-bindings.test.js`、`test/context-result-preview.test.js` 与能力设置 Client 用例。固定 7 项普通任务加 1 项 canary 的 Command Goat / DeepSeek V4.1 Flash 付费配对已运行并完成 16 份盲评。插件侧盲评总分为 correctness 22/24、efficiency 15/24、clarity 21/24,原生基线为 22/24、10/24、21/24;机器验收因原生基线 3 项超过预设调用或 token 上限而阻止。全任务 traffic 为插件 132,332、基线 340,712(普通任务排除 canary 后为 122,933 对 325,862),这是一轮单 replicate 结果,不构成普遍收益保证。
92
92
 
package/docs/client-ui.md CHANGED
@@ -29,19 +29,19 @@ Client 按数据、传输、注册与视图分文件,沿用既有 `createX(Rea
29
29
 
30
30
  能力列表通过能力 Remote 的只读 `catalog` 操作观察 code agent 的当前公共工具、Skill inventory 和已捕获的 MCP server 目录,与 `inspect` 返回的冻结方案分开。分组标题旁的总开关保存 `capabilityContextEnabled`,整体暂停而不改写子项;控制仍可编辑,配置库和冻结来源复制保留该维度。重新开启恢复保存组合,仍遵守新会话与明确刷新边界。`src/client-capabilities-view.js` 拥有能力配置、提示设置、使用统计和配置方案四个页签;页签支持方向键与 Home/End,字段仍由原配置 owner 保存。能力页首先显示搜索、类型按钮和能力清单;每行分别展示名称与精确 ID、类型、来源、保存规则的启停结果和上下文提供方式。名称下方不使用 provider 原始介绍。只有公共目录证明 MCP 来源时才标记 MCP,不从工具名称猜测服务器。Skill 来源使用公开 provider/source,冲突显示未确认。
31
31
 
32
- 行内启停只修改 availability;上下文入口打开独立小窗编辑。该小窗不使用折叠入口:可用性与说明两个下拉并排、常用基线为带说明的开关、能力说明与来源规则、所选会话方案全部平铺。保留 availability/disclosure 的独立继承、常用集、Skill 调用资格、完整目录描述及已证明来源规则。来源关闭优先于子项,但子项偏好仍保留。缺失、未知和能力配置未开启分别显示,保存规则不冒充会话实际状态。筛选在独立小窗中按需打开,批量设置使用独立的一次性小弹窗;筛选可组合来源、已保存启停、已保存说明、常用项和库存状态,活跃条件显示摘要并可清除。类型切换清除不兼容来源,五列表头三态排序保留同值项的默认相对顺序,列表独立滚动。默认不打开单项或选取首个会话;详情中的冻结方案只供明确目标观察。目录失败保留最后观察内容并标为未确认,已保存的缺失项仍可编辑或移除。基础 REPL 公共能力不列为受管设置项。
32
+ 行内启停只修改 availability;上下文入口打开独立小窗编辑。该小窗不使用折叠入口:可用性与说明两个下拉并排、常用基线为带说明的开关、能力说明与来源规则、所选会话方案全部平铺。保留 availability/disclosure 的独立继承、常用集、Skill 调用资格、完整目录描述及已证明来源规则。来源关闭优先于子项,但子项偏好仍保留。缺失、未知和能力配置未开启分别显示,保存规则不冒充会话实际状态。清单支持勾选多个条目(表头复选框选择当前筛选结果),批量设置使用独立的一次性小弹窗且只对所选条目生效;筛选可组合来源、已保存启停、已保存说明、常用项和库存状态,活跃条件显示摘要并可清除。类型切换清除不兼容来源,五列表头三态排序保留同值项的默认相对顺序,列表独立滚动。默认不打开单项或选取首个会话;详情中的冻结方案只供明确目标观察。目录失败保留最后观察内容并标为未确认,已保存的缺失项仍可编辑或移除。基础 REPL 公共能力不列为受管设置项。
33
33
 
34
- 提示设置页使用固定功能导航与单项编辑的两栏结构,导航仅包含“初始提示”“提示恢复”“输出节选”,没有编号或新增条目动作。名称旁与摘要复用同一保存状态判定:已选用、未选用、已暂停、沿用 DSH 或未确认。初始提示随能力配置应用,提示恢复与输出节选各有独立开关;总开关关闭时保留选择并显示暂停;状态与导航选中高亮分开,不断言现有会话已同步。短名称作为可访问名,状态作为可访问描述;窄容器使用三条全宽紧凑功能行,名称在左、状态在右。能力说明设置默认提供方式,常用集、使用统计与预算各自选用,分配依据、额度与只读分配预览直接展示。分配只影响 adaptive,显式完整说明不会被预算降级。提示恢复和输出节选分别有独立开关,关闭不清除额度,不依赖能力管理。统计页先选择观察会话并显示真实证据,工作区与全局分别查看使用家族数、可用家族数和覆盖率;未知或不完整显示破折号和证据状态,不能计零。采集开关独立于统计消费,窗口、最低样本数和百分比阈值放在“统计设置”中;保存前校验正整数窗口、最低样本不超过窗口与 0–100% 阈值,无效数字草稿不阻止关闭采集。
34
+ 提示设置页使用固定功能导航与单项编辑的两栏结构,导航仅包含“初始提示”“提示恢复”“输出节选”,没有编号或新增条目动作。名称旁与摘要复用同一保存状态判定:已选用、未选用、已暂停、沿用 DSH 或未确认。初始提示随能力配置应用,提示恢复与输出节选各有独立开关;总开关关闭时保留选择并显示暂停;状态与导航选中高亮分开,不断言现有会话已同步。短名称作为可访问名,状态作为可访问描述;窄容器使用三条全宽紧凑功能行,名称在左、状态在右。能力说明设置默认提供方式,常用集、使用统计与预算各自选用,分配依据、额度与只读分配预览直接展示。分配只影响 adaptive,显式完整说明不会被预算降级。提示恢复和输出节选分别有独立开关,关闭不清除额度,不依赖能力管理。统计页先选择观察会话作为观察锚点,统计表与预算预览都按它的能力清单展示,统计范围切换该会话的当前工作区或全部工作区;已有记录但家族尚未结算显示“已有记录待结算”,尚无记录显示“暂无样本”,两者显示破折号而不是零;只有可计量的窗口才显示数字(真实的 0/N 与 0% 保持数字),窗口证据不完整或容量超限同样显示破折号和状态。采集开关独立于统计消费,窗口、最低样本数和百分比阈值放在“统计设置”中;保存前校验正整数窗口、最低样本不超过窗口与 0–100% 阈值,无效数字草稿不阻止关闭采集。
35
35
 
36
36
  配置页采用左配置库、右详情;唯一“保存当前设置”与导入、配置库更多操作位于列表下方。命名保存、重命名与会话复制仅在对应动作后展开;列表可在只读状态浏览,写入遵循公共 settings 资格。详情显示可复用设置摘要与应用按钮,配置库更多导出全部、详情更多导出所选,导入仅加入库。应用用一次原子 mutation 更新保存设置,读回冲突不能显示成功;从会话复制只读取已证明的 frozen.configuration,不复制统计、变量或执行状态。已有会话观察和明确刷新位于独立折叠入口。异步导入遇到只读、关闭插件或卸载永久失效,即使资格恢复也不提交迟到内容。详见 [ADR 0040](adr/0040-separate-capability-configurations-from-session-observations.md)。
37
37
 
38
38
  页内保存状态来自真实 pending/status 和 settings 资格,说明保存设置供新会话采用;已有会话保留冻结环境,需要明确刷新。任一能力与上下文功能选用时可观察冻结来源,全部关闭时取消方案读取;目录观察与手动配置独立。插件关闭、设置关闭与 transport 释放取消请求,迟到响应不能恢复旧状态。所有开关使用 DSH 尺寸与主题 token,按钮文字居中,边框轻量;六列清单在窄容器中分行,配置页堆叠。
39
39
 
40
- 能力与上下文提供独立的“刷新会话能力”操作,必须明确选择活跃 PTC 会话;“重新读取目录/方案”只观察状态。星光菜单的同名操作与“编写新绑定”“修改绑定”共用输入框保护,只将 `/capabilities refresh` 填入空输入框并关闭菜单,由用户发送;已有草稿保留。设置按钮通过宿主 `commands.execute` 提交命令,不发起模型任务。关闭能力管理后仍可主动同步关闭配置;冻结方案观察跟随各独立功能的选用状态。关闭 Global User Bindings 只隐藏绑定操作,保留星光设置与刷新。未确认目标、命令不可用、只读设置或插件关闭时不能刷新;取消、断线、preset 改变、切换会话或卸载使迟到回执失效。Host 回执区分已接受、通知待首次接受与失败,取消只停止等待,需重读状态确认实际效果。
40
+ 能力与上下文提供独立的“刷新会话能力”操作,必须明确选择活跃 PTC 会话;“重新读取目录/方案”只观察状态。星光菜单的同名操作与“编写新绑定”“修改绑定”共用输入框保护,只将 `/capabilities refresh` 填入空输入框并关闭菜单,由用户发送;已有草稿保留。设置按钮通过宿主 `commands.execute` 提交命令,不发起模型任务。关闭能力管理后仍可主动同步关闭配置,此时回执报出能力关闭并说明会话沿用宿主能力,不声称会话能力已更新;回执区分“能力与上下文”总开关与能力管理开关两种关闭原因。冻结方案观察跟随各独立功能的选用状态。关闭 Global User Bindings 只隐藏绑定操作,保留星光设置与刷新。未确认目标、命令不可用、只读设置或插件关闭时不能刷新;取消、断线、preset 改变、切换会话或卸载使迟到回执失效。Host 回执区分已接受、通知待首次接受、能力关闭与失败,取消只停止等待,需重读状态确认实际效果。
41
41
 
42
- 会话能力工作台使用一份待应用更改,不先切换生效范围。底部提供“放弃更改”“应用到会话”和“应用到全局”:分别清空草稿、写入会话临时选择、保存全局默认并更新当前会话。撤销临时覆盖只允许“应用到会话”,不能把 `inherit` 写入全局设置。全局提交只有重新读取到对应保存选择且该项临时覆盖已清除才从草稿移除;部分失败和无法确认的更改仍可重试。详情区使用独立的 13px/20px 正文与紧凑的状态信息,来源、全局设置、冻结继承、临时覆盖和实际生效分别显示;冻结来源未知时不从全局值猜测。整行都可选中:任意单元格(含空白处)与名称按钮一样打开该条目详情,可用性与上下文单元格保留各自的维度焦点。共享 Select 使用居中的 SVG 箭头,Radix 继续拥有键盘、焦点与独立 Portal。
42
+ 会话能力工作台使用一份待应用更改,不先切换生效范围。底部提供“放弃更改”“应用到会话”和“应用到全局”:分别清空草稿、写入会话临时选择、保存全局默认并更新当前会话;页脚以“应用到会话”为主操作,“应用到全局”作为显式保存全局默认的次要操作,不存在默认发生的全局修改;“批量设置”入口位于同一页脚操作组的右端并显示已选数量,未选择条目时禁用。维度选择按结果命名:“不改动(当前:值)”不产生本会话临时修改,已有临时修改时同一位置显示“撤销本会话修改(恢复为:值)”,该撤销只允许“应用到会话”,不能把 `inherit` 写入全局设置;“由上一级规则决定”只在会话来源对该维度确有显式规则或该值已是当前选择时提供;可用性与说明方式的值选项直接写作“启用”“关闭”等动作。每个维度选择下方用“本会话生效:值 · 来源”预览当前草稿的结果,暂存后标注“待应用”。勾选多个条目后“批量设置”在一次性小窗中为所选条目暂存同一维度选择,或清除其本会话修改;暂存不发送命令,仍由底部按钮决定应用到会话或全局,“放弃更改”清空。已确认的批量提交拥有独立生命周期:逐条命令进行期间关闭按钮禁用且关闭路径拒绝执行,可用性或设置的瞬时重算不会取消在途命令;成功、失败或中止后都在同一收尾路径清理“正在应用”状态并核对已确认草稿。全局提交只有重新读取到对应保存选择且该项临时覆盖已清除才从草稿移除;部分失败和无法确认的更改仍可重试。行内“是否启用”和“上下文”单元格显示实际生效值;该维度的生效值来自会话临时选择时附加“(临时)”,与冻结继承的相同取值区分。设置中的“能力与上下文”总开关或能力管理开关关闭时,即使会话仍冻结着已启用方案,工作台也整体只读,并以警示横幅显示对应关闭原因与恢复路径,重新开启并刷新后才能修改会话选择;冻结事实仍可查看。详情区使用独立的 13px/20px 正文与紧凑的状态信息,来源、全局设置、冻结继承、临时覆盖和实际生效分别显示;冻结来源未知时不从全局值猜测。整行都可选中:任意单元格(含空白处)与名称按钮一样打开该条目详情,可用性与上下文单元格保留各自的维度焦点;行首复选框只切换批量目标,不打开详情。共享 Select 使用居中的 SVG 箭头,Radix 继续拥有键盘、焦点与独立 Portal。
43
43
 
44
- `/capabilities refresh` 与结构化状态回执使用独立的 13px/20px 单行提示,只显示当前界面的结果文案,不显示命令标题、重复状态或原始结果入口。运行、待生效和失败保留各自的真实文案,长错误可以换行;历史刷新回执的已记录通知文本同样转换为简短提示。能力查询保留命令卡片。`ptc-capability-catalog/v1` 结果默认显示条目数,展开后以有高度上限的可滚动表格显示列表;其他 JSON 显示详情入口,不直接铺在正文里。查询的原始结果单独折叠,在展开时显示完整原文,既不改写历史结果也不把成功回执当作完整值或模型投递的新证明。
44
+ `/capabilities refresh` 与结构化状态回执使用独立的 13px/20px 单行提示,只显示当前界面的结果文案,不显示命令标题、重复状态或原始结果入口。运行、待生效、能力关闭和失败保留各自的真实文案,长错误可以换行;历史刷新回执的已记录通知文本同样转换为简短提示。能力查询保留命令卡片。`ptc-capability-catalog/v1` 结果默认显示条目数,展开后以有高度上限的可滚动表格显示列表;其他 JSON 显示详情入口,不直接铺在正文里。查询的原始结果单独折叠,在展开时显示完整原文,既不改写历史结果也不把成功回执当作完整值或模型投递的新证明。
45
45
 
46
46
  ## 可用设置
47
47
 
@@ -71,7 +71,7 @@ Client 按数据、传输、注册与视图分文件,沿用既有 `createX(Rea
71
71
 
72
72
  旧配置的五个开关(`looseTopLevelRedeclarations`、`looseTopLevelFunctionClassRedeclarations`、`autoRewriteImports`、`autoStripExports`、`autoSplitRedeclarations`)先由共享 resolver 迁移。完整策略映射到 `stateful` 或 `protected`;混合选择保留显式兼容态,只有该状态才显示旧控件。兼容态的统一策略控件显示“沿用旧版设置(尚未迁移)”,可直接选择“允许重声明和覆盖”或“启用名称保护”;选择后一次写入同时保存 `bindingUpdates` 并清除兼容标记,随后执行和模型指引采用相同的新语言策略,设置页恢复统一开关。仅查看设置不触发写入。
73
73
 
74
- 能力配置通过可搜索列表、常用勾选与独立可用性/说明方式选择编辑;默认、类别、MCP 来源、单项分别按维度覆盖。“沿用上层规则”撤销当前维度的显式选择,“按依据分配”独立选用常用集、统计和预算。使用统计、提示恢复与输出节选分别设置,Client 负责序列化。统计数字输入保留未完成或无效草稿,失焦时仅保存有效组合;无效草稿显示错误,统计开关仍可独立保存并恢复最近有效的数字组合。方案展示通过公共能力 RPC 读取当前会话的选择原因、覆盖率、预算超额和待刷新状态,保存配置本身不等于旧会话已更新。
74
+ 能力配置通过可搜索列表、常用勾选与独立可用性/说明方式选择编辑;默认、类别、MCP 来源、单项分别按维度覆盖。“不单独设置”撤销当前维度的显式选择,“按依据分配”独立选用常用集、统计和预算。使用统计、提示恢复与输出节选分别设置,Client 负责序列化。统计数字输入保留未完成或无效草稿,失焦时仅保存有效组合;无效草稿显示错误,统计开关仍可独立保存并恢复最近有效的数字组合。方案展示通过公共能力 RPC 读取当前会话的选择原因、覆盖率、预算超额和待刷新状态,保存配置本身不等于旧会话已更新。
75
75
 
76
76
  ## enabled 开关与正文功能标记
77
77
 
@@ -168,7 +168,7 @@ live 配置若因宿主能力缺失或 runtime 安装/重配置失败,会先
168
168
 
169
169
  会话的能力与上下文暂停或能力管理未启用时,工作台明确标注该状态并关闭暂存与提交;清单每行显示沿用宿主的事实,不再显示无法收敛的待生效。只有已启用管理、但尚未刷新折入方案的宿主动态成员才显示待生效。
170
170
 
171
- “批量设置”打开独立小弹窗,对打开时当前筛选中的真实可用能力执行一次单项变更。可用性和说明方式独立选择,初始均为不更改;确认前只保留临时草稿,取消不写入。确认使用最新配置,仅改目标单项的所选维度,保留非目标项、其他维度及继承规则;目标失效或保存失败时显示错误,不能显示成功。默认、类别和 MCP 来源的持久继承规则通过“继承规则”入口编辑,与批量操作分开。保存选择仍只在新会话或明确刷新后生效。
171
+ “批量设置”只对清单中已勾选的行生效,未选择任何行时按钮不可用;表头复选框把当前筛选结果加入选择,选择不随筛选或排序变化而丢失。弹窗对所选真实可用目标执行一次单项变更,选中但当前不可用的目标单独计数并排除。可用性和说明方式独立选择,初始均为不更改;确认前只保留临时草稿,取消不写入。确认使用最新配置,仅改目标单项的所选维度,保留非目标项、其他维度及继承规则;目标失效或保存失败时显示错误,不能显示成功。默认、类别和 MCP 来源的持久继承规则通过“继承规则”入口编辑,与批量操作分开。保存选择仍只在新会话或明确刷新后生效。
172
172
 
173
173
  提示设置的三个侧栏项在选用状态旁显示具体配置摘要,分别来自默认说明方式及其分配依据、恢复额度和输出额度;总开关暂停和未知配置保持明确标识。分配依据、额度与当前能力分配直接展示,统计设置同样直接展示。观察会话旁以灰色说明其只读统计与预算预览用途,窄屏可自然换行。
174
174
 
@@ -201,4 +201,4 @@ npm run build
201
201
  如果 DSH settings service 不可用,Host half 不会因缺少设置服务而加载失败:设置卡片不可用时直接回退为 composition `config`,
202
202
  `enabled` 默认开启,运行时按 composition config 工作。
203
203
 
204
- MCP 列表按 server 分组,来源可用性与说明方式使用和单项一致的独立选择器,均可撤销为沿用上层规则。来源显式关闭是外层移除,优先于子项启用;默认关闭仍可由子项启用覆盖,二者不混同。会话详情显示保存选择、临时规则、冻结来源结果及生效的维度来源,空规则显示继承或无覆盖。目录分别保留当前保存设置与冻结会话各自代际的事实;新保存规则不能把历史 scalar 来源改写成空结构规则。已证明的冻结 `managementEnabled` 决定是否预测来源关闭;管理未选用时保留关闭偏好、显示实际 Host 可用,不报待关闭。待应用的来源关闭会在子项详情预告;正式提交和重新读取确认之后才改变生效状态。
204
+ MCP 列表按 server 分组,来源可用性与说明方式使用和单项一致的独立选择器,均可撤销为不单独设置。来源显式关闭是外层移除,优先于子项启用;默认关闭仍可由子项启用覆盖,二者不混同。会话详情显示保存选择、临时规则、冻结来源结果及生效的维度来源,空规则显示不单独设置或无覆盖。目录分别保留当前保存设置与冻结会话各自代际的事实;新保存规则不能把历史 scalar 来源改写成空结构规则。已证明的冻结 `managementEnabled` 决定是否预测来源关闭;管理未选用时保留关闭偏好、显示实际 Host 可用,不报待关闭。待应用的来源关闭会在子项详情预告;正式提交和重新读取确认之后才改变生效状态。
@@ -43,7 +43,7 @@ The draft oracle measures the CSS-owned preview budget and compact header contro
43
43
 
44
44
  The sparkle-menu typography oracle also requires the public Menu class slot to retain a bordered, rounded card with padding and shadow when Host CSS loads after plugin installation; injected border, radius and shadow removal must fail on the native surface. It requires a plugin-owned 13px/18px content base, 13px disclosure captions and binding names, 12px purposes and 11px states, all with 18px line-height and the inherited font family. It measures text ranges within their row for vertical clipping and checks the caret center against the caption line center, rather than its potentially stretched flex box; injected caption stretching must fail. Horizontal name/purpose ellipsis remains valid. Native/fallback fixtures reproduce a single disabled binding under larger carrier typography, preserve the full purpose title, reject injected inherited font-size and line-height, and accept a smaller carrier. The mixed-catalog root and revision paths and packed official-Host acceptance share the same oracle.
45
45
 
46
- The sparkle-menu oracle requires at least 24 px of catalog reading space, viewport containment, nonoverlapping catalog and footer, no outer scrolling, and fully reachable footer actions of at least 24 px height, including the capability-refresh command shortcut. The root starts with the enabled group without a scope caption, disabled-group disclosure uses a neutral rounded surface, binding states use green enabled and gray disabled pills in both root and revision lists; the oracle rejects missing state attributes or indistinguishable text/background colors, and authoring plus utility actions use two-column buttons with centered captions, balanced icon gutters and a soft fill without borders. The oracle measures caption centering against each button and rejects visible button borders. Command shortcuts fill the empty composer and close the menu; they never execute a command or overwrite unsent text. All settings and workbench switches use the official 36×20 capsule and 16px thumb with public theme tokens. Both checkbox pseudo-element and button/span switch thumbs are measured. The off-state oracle requires exact official geometry and token colors with a distinguishable thumb; checked states retain the contrast assertion and same-color thumb counterexample. MCP server source availability and disclosure use independent selectors with the shared Select appearance contract; browser fixtures change source availability from closed to parent rules while retaining child disclosure preferences. Capability workbench fixtures use the shared refresh component and verify explicit target selection, management-off synchronization and read-only reload separation. Scrolling the expanded mixed catalog and the revision list in both directions must leave footer geometry unchanged. Injecting a shared outer scroll container must fail the oracle; restoring the layout must pass. Actual catalog writes must retain a focused control whose center is inside the catalog clip and passes hit testing: collapsed disable, expanded enable, revision Back, and pending close/reopen are covered. Removing the reveal by resetting only the catalog scrollTop must fail this focus oracle; restoring it must pass. Page scroll and footer geometry remain unchanged. Packed official-Host acceptance repeats the mixed-catalog checks through the isolated profile and exercises actual management and settings actions.
46
+ The sparkle-menu oracle requires at least 24 px of catalog reading space, viewport containment, nonoverlapping catalog and footer, no outer scrolling, and fully reachable footer actions of at least 24 px height, including the capability-refresh command shortcut. The root starts with the enabled group without a scope caption, disabled-group disclosure uses a neutral rounded surface, binding states use green enabled and gray disabled pills in both root and revision lists; the oracle rejects missing state attributes or indistinguishable text/background colors, and authoring plus utility actions use two-column buttons with centered captions, balanced icon gutters and a soft fill without borders. The oracle measures caption centering against each button and rejects visible button borders. Command shortcuts fill the empty composer and close the menu; they never execute a command or overwrite unsent text. All settings and workbench switches use the official 36×20 capsule and 16px thumb with public theme tokens. Both checkbox pseudo-element and button/span switch thumbs are measured. The off-state oracle requires exact official geometry and token colors with a distinguishable thumb; checked states retain the contrast assertion and same-color thumb counterexample. MCP server source availability and disclosure use independent selectors with the shared Select appearance contract; browser fixtures change source availability from closed to parent rules while retaining child disclosure preferences. Capability workbench fixtures use the shared refresh component and verify explicit target selection, management-off synchronization and read-only reload separation. Per-row pick checkboxes with the first-column select-all drive the batch window in both the settings list and the session workbench; the session batch lists exactly the picked rows, stages without running commands, and keeps every action reachable at the smallest captured viewport. Scrolling the expanded mixed catalog and the revision list in both directions must leave footer geometry unchanged. Injecting a shared outer scroll container must fail the oracle; restoring the layout must pass. Actual catalog writes must retain a focused control whose center is inside the catalog clip and passes hit testing: collapsed disable, expanded enable, revision Back, and pending close/reopen are covered. Removing the reveal by resetting only the catalog scrollTop must fail this focus oracle; restoring it must pass. Page scroll and footer geometry remain unchanged. Packed official-Host acceptance repeats the mixed-catalog checks through the isolated profile and exercises actual management and settings actions.
47
47
 
48
48
  ## Limits
49
49
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  PTC Plus requires Node.js `^22.19.0 || >=24.0.0` and targets the latest available DSH release with TypeScript PTC mode. Historical interface branches are retained through capability detection, without promising a fully verified earlier Host installation; when both generations expose competing presentation evidence, the current DSH contract wins. Compatibility follows those live extension surfaces rather than a version allowlist.
4
4
 
5
+ The package declares `engines.dsh: "*"` for DSH's plugin compatibility preflight. This keeps release-number gating open while public capability checks and packed Host contracts establish compatibility; it does not claim that every historical or future Host has been tested. Host-owned service packages remain required peers.
6
+
5
7
  The worker uses the Host's Node executable. Startup verifies native REPL framing, imports, syntax failures, synchronous throws, awaited rejections, and original return values. The adapter combines public eval callbacks with domain error events before REPL formatting; a source-end witness distinguishes empty completion from falsy rejection. A failed or timed-out probe reports a runtime prerequisite failure before user code executes. The engine range follows DSH; CI exercises Node 22.19, 24, and 26, without claiming that untested future releases have already passed.
6
8
 
7
9
  Install the plugin into the profile that actually runs the target DSH surface. Do not assume a profile named `default` is active.
@@ -52,7 +54,7 @@ Capabilities select behavior, never a release number. Historical fixture success
52
54
  Use this form after the selected version is available from the npm registry:
53
55
 
54
56
  ```sh
55
- dsh plugin --profile <profile> add dsh-ptc-plus@0.5.0
57
+ dsh plugin --profile <profile> add dsh-ptc-plus@0.5.1
56
58
  dsh --profile <profile> --dump-config
57
59
  ```
58
60
 
@@ -89,7 +91,7 @@ pnpm dsh --profile <profile> --dump-config
89
91
 
90
92
  ```sh
91
93
  npm pack
92
- dsh plugin --profile <profile> add /absolute/path/to/dsh-ptc-plus-0.5.0.tgz
94
+ dsh plugin --profile <profile> add /absolute/path/to/dsh-ptc-plus-0.5.1.tgz
93
95
  dsh --profile <profile> --dump-config
94
96
  ```
95
97
 
@@ -157,7 +159,7 @@ dsh plugin add github:muyuanjin/dsh-ptc-plus#main
157
159
  dsh --dump-config
158
160
  ```
159
161
 
160
- After an npm release, the package spec may instead be `dsh-ptc-plus@0.5.0`. For a local package, use its absolute tarball path. Restart DSH Desktop after installation. Linux Desktop is not a current DSH Desktop release target; use DSH CLI/Web on Linux.
162
+ After an npm release, the package spec may instead be `dsh-ptc-plus@0.5.1`. For a local package, use its absolute tarball path. Restart DSH Desktop after installation. Linux Desktop is not a current DSH Desktop release target; use DSH CLI/Web on Linux.
161
163
 
162
164
  ## Upgrades
163
165
 
@@ -1,6 +1,6 @@
1
1
  # Publishing
2
2
 
3
- 本仓库是普通 DSH bundle:`package.json` 声明 `dsh.bundle.patch`、入口、依赖、Node 范围和发布白名单,`cordis.patch.yml` 只插入 `dsh-ptc-plus`。插件自带的运行时依赖由 npm 包声明;必须与宿主共享实例的 DSH service 包声明为 peer dependencies,并由宿主按 `inject` 装配。
3
+ 本仓库是普通 DSH bundle:`package.json` 声明 `dsh.bundle.patch`、入口、依赖、Node 范围和发布白名单。`cordis.patch.yml` 隔离原 `ptc-runtime` 的服务,并插入 `ptc-plus-execution`(`dsh-ptc-plus/execution-provider`)与 `ptc-plus`(`dsh-ptc-plus`)两项;安装时保留完整 bundle。插件自带的运行时依赖由 npm 包声明;必须与宿主共享实例的 DSH service 包声明为 peer dependencies,并由宿主按 `inject` 装配。
4
4
 
5
5
  `npm run build` 生成 Client bundle,以及私有编译器使用的 `compiler-core.cjs`、`compiler-platform.cjs`、按需加载的 `compiler-amaro.cjs`、`compiler-typescript.cjs` 和对应 source maps。依赖中的大型字符串参数由构建提取为 `compiler-asset-*.txt`,`compiler-assets.json` 拥有精确加载清单,避免 WASM 等数据同时驻留于源码与字面量池。这些编译器产物由 `scripts/compiler-bundle.mjs` 从源码紧凑生成,并列入发布白名单;不能手工修改生成文件。构建同时核验编译器源码所导入的必需平台成员。`npm run build:check` 同时比较 Client、编译器和资源产物,`prepack` 通过该检查阻止陈旧产物进入 tarball。源码验证还须把私有 realm 的执行映射回原始编译器文件,不能只统计调用服务的外层代码。
6
6
 
@@ -44,7 +44,7 @@ npm.cmd stage approve '<stage-id>' --registry=https://registry.npmjs.org/
44
44
 
45
45
  1. 提交前在稳定工作树上运行完整 `npm run check`,执行无覆盖插桩的完整普通后端测试并生成 review proof。覆盖率只在准备发布新版本时运行;Release workflow 在打包与 npm staging 前对 release tag 执行 `npm run coverage`,要求行与函数覆盖率 100%、分支覆盖率至少 95%。发布准备期间可在本地运行同一命令,并用 `npm run coverage:focus` 诊断覆盖率缺口;日常开发、审查与提交不运行覆盖率专用套件、采集探针或插桩分支;这些测试由 `test/coverage/` 和发布入口的显式标记拥有。仓库 CI 在 Linux Node `22.19.0`、24.x、26.x,以及 Windows 与 macOS Node 24.x 的干净安装上运行 `npm run verify:ci`,检查构建、Client 与跨平台关键运行契约;Ubuntu Node 24.x 另运行打包后的 `latest` / `next` Host contracts。普通 CI 不收集 coverage 或执行穷尽式后端套件。
46
46
  2. 运行 `npm audit --omit=dev`、`npx publint`、`git diff --check`、Markdown local-link check 和 `npm pack --silent --dry-run --json`。audit 检查随包安装的 production 依赖图;宿主 DSH 及其开发依赖不进入插件 tarball,由冻结宿主契约和 CI 单独验证。确认 tarball 只包含发布白名单内的运行时代码、文档和展示资产,不含凭据、本地路径、开发脚本或测试夹具,并执行 `npm run test:package:consumer -- <tarball>`:入口先校验当前安装的宿主与其 lock 一致,再将宿主实际提供的 peer 版本与 tarball 放在同一次 npm 安装事务中,在空目录执行 ESM import、内置编译器栈与内核 worker 加载 smoke。不能让 npm 对各宿主 peer 分别选用 dist-tag;插件继续声明不限制版本的 required peers,不私装宿主服务副本。机器读取 `npm pack --json` 时保持 `--silent`,避免 lifecycle 输出混入 JSON stream。
47
- 3. 更新到最新可用 DSH release 并记录 `dsh --version`。分别从 npm 包、固定 Git commit、源码 checkout 和 tarball 安装到临时 profile,再执行 `dsh --profile <profile> --dump-config`,确认只新增 `ptc-plus` row。每个上游 release 都重新验证公共扩展面、CLI/Web 集成和 profile 装配,不设置版本白名单。
47
+ 3. 更新到最新可用 DSH release 并记录 `dsh --version`。分别从 npm 包、固定 Git commit、源码 checkout 和 tarball 安装到临时 profile,再执行 `dsh --profile <profile> --dump-config`,确认与 `cordis.patch.yml` 一致:新增 `ptc-plus-execution`、`ptc-plus` 两项,并将原 `ptc-runtime` 的 `ptcRuntime` 服务隔离到 `ptc-plus-original`。每个上游 release 都重新验证公共扩展面、CLI/Web 集成和 profile 装配,不设置版本白名单。
48
48
  4. 在 Windows 与 Linux 运行无模型调用的 `npm run test:client:web -- --dsh-entry <latest-installed-dsh/lib/bin.js>`,验证 tarball 安装后的真实 Web Client、设置与对话 surface;浏览器前置条件见 [安装指南](installation.md)。`build:check`、Host ESM import 和 factory smoke 均不能替代浏览器激活验收。macOS 由原生 runner 验证 CLI/Web;Windows 与 macOS Desktop 从托盘打开当前 profile 的 DSH Terminal 安装,重启 Desktop 后做一次 PTC 模式 smoke。Desktop 当前发布平台不包括 Linux。界面有变化时用同一前置条件刷新 README 截图:`npm run readme:screenshots -- --dsh-entry <latest-installed-dsh/lib/bin.js>` 从当前打包候选各写回一帧中英文 REPL 工作台与设置窗口;先用 `--out <directory>` 落到别处检查,命令、前置条件与失败边界见 [安装指南](installation.md)。
49
49
  5. 只有在明确授权模型消耗后才运行 `npm run test:expensive` 与 `npm run test:ab`;发布候选还要用 `DSH_PTC_ACCEPTANCE_SCENARIOS=managed-adaptive-surface,managed-session-resume,managed-surface-change,subagent-continuity` 追加托管能力面、会话恢复、宿主表面变化与子代理场景(运行方式、预算与版本差异判别见[评估](evaluation.md#managed-session-continuity-cohort))。发布结论以结构化测试结果为准,不把一次模型样本推广为普遍保证。
50
50
  6. 确认 npm 包名可用、待发布版本高于 registry 版本、README 截图来自真实 DSH 会话,并核对 repository、homepage、bugs、license 与 `dsh-plugin` topic。
@@ -20,7 +20,7 @@ Every program namespace is leased to one cell. Namespace objects or member funct
20
20
 
21
21
  Official PTC mode declares only top-level `run_code`. PTC Plus registers `edit_run_code` in the program SDK before the first model request. Call `tools.edit_run_code` inside a cell. Actual derived `run_code` dispatches have their own recorded start/settle events and retain the live root transport as their parent. The private root evidence distinguishes the editor cause (`startSeq`) from derived execution identity (`executionStartSeq`); the latter identifies the published revised cell. See [ADR 0005](adr/0005-temporary-rejected-cell-edit-transport.md) for its settlement and recovery contract.
22
22
 
23
- The editor captures the most recent eligible cell when its official nested dispatch starts, including a successful cell that needs a small adjustment. The target remains fixed while the call is in flight. It accepts exactly one atomic `edits` or `regex_edits` array and an optional `expected_target_call_seq` guard. Invalid, unavailable or mismatched-target edits return `{ accepted: false, scheduled: false, reason }` without executing source. Regex replacement uses JavaScript `$1`, `$<name>` and `$$` templates; backslash forms such as `\\1` remain literal. Every regex item requires `pattern`, `flags`, `replacement` and `expected_matches`, including an explicit `""` flags string when no flags are needed.
23
+ The editor captures the most recent eligible cell when its official nested dispatch starts, including a successful cell that needs a small adjustment. The target remains fixed while the call is in flight. It accepts exactly one atomic `edits` or `regex_edits` array and an optional `expected_target_call_seq` guard. Invalid, unavailable or mismatched-target edits return `{ accepted: false, scheduled: false, reason }` without executing source. Every rejection names the captured cell (`run_code call <seq>` plus a bounded first line), so a drifted target stays identifiable, and a guard mismatch also states that only the most recent settled cell remains editable and that corrected source should be re-submitted with `run_code` when the intended cell is gone. The settled log line reports the same target identity. Regex replacement uses JavaScript `$1`, `$<name>` and `$$` templates; backslash forms such as `\\1` remain literal. Every regex item requires `pattern`, `flags`, `replacement` and `expected_matches`, including an explicit `""` flags string when no flags are needed.
24
24
 
25
25
  An accepted nested editor call returns `{ accepted: true, scheduled: true, target_call_seq }`. This is a scheduling receipt. The outer cell continues with its current state and original return value or error; after it finishes, PTC Plus dispatches the complete revised source through DSH's real `run_code` tool pipeline before settling the root result. The root result retains the outer canonical value or error and forwards the derived result's Host-rendered content, images, additional contexts and, for a successful root result, turn-conclusion flag; the next cell sees revised bindings. Official nested dispatch events retain editor identity and arguments. Private root-result metadata retains the derived source and journal for cold recovery without rewriting assistant history or emitting edit-specific runtime context. Rejecting the nested receipt prevents dispatch; losing root metadata after derived execution instead invalidates recovery evidence and does not undo effects. When the public Host skips the nested metadata projector, PTC Plus may retain private derived evidence only if its exact pre-policy frozen canonical value survives final policy with unchanged base metadata and execution identity. Block/error, value replacement, missing capture or removal of projected metadata rejects that evidence. The Host result remains unchanged; see [Derived journal settlement](durability-design.md#两阶段确认).
26
26
 
@@ -101,6 +101,7 @@ Missing-description diagnostics append a complete DSH user message to `additiona
101
101
  autoDescribeRunCode: true
102
102
  bindingUpdates: 'stateful'
103
103
  durableReplay: true
104
+ officialReplPriority: false
104
105
  tipsEnabled: true
105
106
  cordisToolsEnabled: false
106
107
  userBindingsEnabled: true
@@ -121,6 +122,8 @@ The five legacy binding and module switches remain migration inputs. Complete en
121
122
 
122
123
  `enabled: false` is the settings-based kill switch: the Host keeps the settings namespace, client card, and passive cleanup that can withdraw old PTC declarations at the next permitted accepted step. It removes execution hooks, prompt sections, and tool surfaces without reading binding storage. Settings are applied live when their owner can reconcile them, except the capability inputs frozen per session above. A submitted cell retains one configuration snapshot through preflight, execution, binding calls, completion validation, and diagnostics; an update accepted while it runs applies to cells submitted afterward. Node fixes a worker's V8 old-generation limit at creation, so changing `maxOldGenerationSizeMb` while a session worker is active or a submitted cell is preparing to execute is rejected and rolled back rather than reported as applied. Other non-`enabled` updates reconfigure existing owners without replacing session-bound bindings. The settings card renders in whichever settings seat the installed DSH declares — the sidebar **Plugins → Installed** page per-bundle configuration (`plugins.row.config`), or the preceding Host's **Settings → Plugin configuration** card (`settings.plugin.item`) ([ADR 0019](adr/0019-plugin-settings-and-kill-switch.md)) — places the main switch beside the PTC Plus title and disables every other control while the plugin is off. The card does not register in General settings; the sparkle menu opens the same complete form in an application dialog. If a live enable or reconfiguration cannot install a complete runtime, PTC Plus unwinds or restores every owner and persists the last applied settings; a failed compensating settings write is surfaced as an activation diagnostic. The TypeScript language check is deferred until runtime activation, so a non-TypeScript host can load the plugin in disabled mode but cannot enable it.
123
124
 
125
+ `officialReplPriority` defaults to `false` and is evaluated against the live provider capability rather than a release number: a provider reporting `programFormat: 'repl'` is an official persistent in-memory REPL. With the setting enabled and that provider live, this host attachment installs no session runtime, so `run_code` and its descriptors — `programFormat`, `executionInstructions`, `sandboxMode` and `timeout` — stay with the official provider, and the plugin's REPL guidance, session-state semantics, recovery, editor tool, capability SDK, REPL observation and binding workbench are suspended until ownership changes back. The settings card, enablement and stored global bindings remain available so the switch can be reversed. Changing the setting or the host mode re-evaluates ownership as an installation change: the retired attachment disposes its live kernels before the other shape installs, so no committed cell mixes owners. Calls executed under the official mode produce no plugin journal records and are never recovery evidence. The plugin's provider surface forwards `programFormat` to the live original and retains weak owner-to-original dispatch provenance for `release(owner)` — captured Agent cleanup reaches the original instances that served that owner even after adapter removal or source replacement, and a failed cleanup remains available for explicit retry. Owner-keyed retained environments and the executed program format belong to the provider that actually runs the program, and non-session consumers such as the workflow adapter keep executing through it — while `executionInstructions`, `sandboxMode` and `timeout` stay withheld while PTC Plus owns session execution. The plugin's own session kernel state is keyed by session and released through the plugin lifecycle, not by `release(owner)`.
126
+
124
127
  `cordisToolsEnabled: true` atomically mounts the official `@deepseek-ai/dsh-tool-cordis` plugin and exactly the shipped `cordis-plugin-development` companion Skill in agent scopes that expose the `run_code` and `skill` tools plus the public preset and Skill services. PTC Plus resolves the official `cordis` preset through DSH's public preset service, lets the maintained filesystem provider read its Skill root as an immutable bundled Host resource, and filters that provider at the public registration boundary so sibling Skills are never published to the PTC scope. It does not copy the Skill or switch the agent's preset. The first request waits until the Cordis tool fiber, owner guidance, exact Skill provider, and scoped Skill load are all ready. An agent scope that omits one of those surfaces is left for later: the owner publishes nothing there, reports nothing, and retries when the tool surface changes or the next prompt is assembled. A scope whose exposed preset, Skill/tool service, declared Cordis service, inspect manifest, or activation contradicts the host contract fails the setting change at installation and removes every provisional contribution; reached through the host's own `agent/created` or assemble call it withdraws from that scope with one warning instead, so this plugin's unavailability never fails an agent another plugin created ([ADR 0029](adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md)). Disable and agent/runtime disposal remove both fibers. Native agents inherit neither the resulting `tools.*` members nor the Skill, and the code-only direct-tool projection remains `[run_code]`. Cordis can evaluate model-written plugins against the live runtime, so enabling it grants shell-equivalent trust.
125
128
 
126
129
  The Skill root uses the preset's absolute composition path when that generation publishes one. A pathless generation is resolved through the active Host's public `pluginPackages.packageOf` service, which returns the official singular or plural preset package from that installation rather than an ambient package reachable from the plugin profile. An older generation with no such service may resolve only its historical plural package relative to the Host's `ctx.baseUrl`; the fallback is disabled whenever `pluginPackages` exists. The exact `cordis-plugin-development/SKILL.md` must exist before the mount begins. An invalid published path remains authoritative and fails instead of falling through to another generation's package layout.
@@ -157,6 +160,8 @@ Every completed cell records the exact source-derived active snapshot in private
157
160
 
158
161
  `durableReplay: false` starts new kernels without recorded REPL state while preserving live bindings in the current process. It does not delete session logs.
159
162
 
163
+ Submission admission belongs to the exact DSH tool execution. Reentry within that execution shares its admission; a later root execution gets a fresh admission and session observation even when a background callback inherits the same session's asynchronous context or reuses a call ID. Only a proved derived edit may inherit its composite source and settlement evidence. Disposing a child agent/session leaves the parent's live bindings intact. Disposing the parent cancels its pending preparation; the next admitted call can recover from the verified journal without reusing that cancelled admission.
164
+
160
165
  With `durableReplay: true`, recovery claims only bindings backed by both verified journal ancestry and exact provenance available to the model that produced the current call. DSH's append-only event log proves reconstructability; its ordered model-visible surface proves awareness. Before every subsequent cell in a structured host session, the runtime revalidates each existing live call against that exact surface even when `replaceGeneration` is unchanged. Raw events shadowed from the surface and UI-only `dshPtcPlusBindings` metadata do not make a binding model-knowable. A result-only replacement may retain a cell when the assistant call containing its source remains visible. If compaction shadows that call, the live worker and cold recovery contract the affected state unless an explicitly selected bounded structured projection exposes the exact bindings in model context. Such a projection is not injected by default merely to retain hidden state. Natural-language summaries are not parsed as state evidence.
161
166
 
162
167
  An invalid historical association, prune replacement, or recovery boundary is also rejected as evidence for its bindings; it is not by itself a reason to reject every later valid `run_code`. Recovery takes the intersection of the model-knowable and structurally verified frontiers, then discards cells and bindings that may depend on an unknown boundary. Because the current journal does not contain a complete per-binding dependency graph, the conservative unit is an affected cell and suffix unless stronger owned evidence exists. If no non-empty frontier is provable, the worker starts from an empty REPL and executes the current cell as a new root.
package/index.js CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  } from './internal/config-spec.js'
27
27
  import { configFieldSchema, installSettingsSectionCompat } from './internal/settings-compat.js'
28
28
  import { installExecutionSeam } from './internal/execution-seam-compat.js'
29
+ import { createDeferredRuntime, defersToOfficialRepl } from './internal/official-repl-priority.js'
29
30
  import { valueLimitsFromConfig } from './internal/value-wire-schema.js'
30
31
  import { createReplMemoryProjection } from './internal/repl-memory-projection.js'
31
32
  import { createUserBindingDraftProjection } from './internal/user-binding-draft-projection.js'
@@ -415,8 +416,6 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolS
415
416
  editTransport.ensureInstalled(agent)
416
417
  }
417
418
  }
418
- disposers.push(ctx.on('agent/pre-step', (payload, next) => userBindings.preStep(payload, next)))
419
- disposers.push(ctx.on('session/event', (session, event) => userBindings.committedEvent(session, event)))
420
419
  disposers.push(ctx.on('tools/post-execute', (exec, result, next) => capabilityEnvironment.postExecute(exec, result, next), { prepend: true }))
421
420
  disposers.push(ctx.on('tools/ptc-dispatch-log', (dispatch, next) => capabilityEnvironment.dispatchLog(dispatch, next), { prepend: true }))
422
421
  disposers.push(ctx.on('agent/status', payload => { if (payload?.agent) capabilityEnvironment.status(payload.agent, payload.status) }))
@@ -471,7 +470,14 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolS
471
470
  return { kind: 'error', text: receipt('unavailable') }
472
471
  }
473
472
  await capabilityEnvironment.refresh(agent, undefined, signal, commandId)
474
- return { kind: 'success', text: receipt(capabilityEnvironment.describe(agent).publication === 'pending' ? 'pending' : 'recorded') }
473
+ // A refresh always republishes the saved configuration; when that
474
+ // configuration has capability management or context off, report
475
+ // the off state instead of claiming an updated environment.
476
+ const described = capabilityEnvironment.describe(agent)
477
+ const state = described.effective?.enabled !== true
478
+ ? described.sourceSettings?.contextEnabled === false ? 'paused' : 'off'
479
+ : described.publication === 'pending' ? 'pending' : 'recorded'
480
+ return { kind: 'success', text: receipt(state) }
475
481
  }
476
482
  return { kind: 'success', text: JSON.stringify(capabilityEnvironment.describe(agent), null, 2) }
477
483
  } }), 'ptc-plus capability command')
@@ -710,6 +716,11 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
710
716
  )
711
717
  return cleanup
712
718
  }
719
+ // Execution ownership is a per-attachment property: `owned` installs the
720
+ // session runtime, `deferred` leaves run_code to the official provider.
721
+ const executionShape = nextConfig => (
722
+ defersToOfficialRepl(nextConfig, seam.provider) ? 'deferred' : 'owned'
723
+ )
713
724
  const startActivation = record => {
714
725
  if (record.cancelled || disposed || activating !== record) {
715
726
  record.completion.resolve({ status: 'superseded' })
@@ -720,7 +731,9 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
720
731
  }
721
732
  let candidate
722
733
  try {
723
- candidate = installPtCRuntime(ctx, record.config, seam, toolSchemasForAgent, toolSdkSchemasForAgent, sessionId, sessionLog, () => settingsWriter)
734
+ candidate = record.shape === 'deferred'
735
+ ? createDeferredRuntime()
736
+ : installPtCRuntime(ctx, record.config, seam, toolSchemasForAgent, toolSdkSchemasForAgent, sessionId, sessionLog, () => settingsWriter)
724
737
  record.runtime = candidate
725
738
  } catch (error) {
726
739
  const failedRuntime = error?.[INSTALL_CLEANUP]
@@ -765,6 +778,7 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
765
778
  const record = {
766
779
  config: nextConfig,
767
780
  generation,
781
+ shape: executionShape(nextConfig),
768
782
  runtime: undefined,
769
783
  disposal: undefined,
770
784
  disposed: false,
@@ -814,6 +828,15 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
814
828
  return beginActivation(nextConfig, generation, cleanup)
815
829
  }
816
830
  const current = committed
831
+ if (current.shape !== executionShape(nextConfig)) {
832
+ // Ownership changes are installation changes: dispose the live kernels
833
+ // and install the other shape, so no committed cell can mix the two
834
+ // execution contracts or extend state the new owner cannot see.
835
+ current.retiring = true
836
+ void disposeRuntime(current)
837
+ const cleanup = disposeRetainedRuntimes()
838
+ return beginActivation(nextConfig, generation, cleanup)
839
+ }
817
840
  if (!nextConfig.cordisToolsEnabled) current.runtime.cancelCordisActivation()
818
841
  return enqueueTransition(async () => {
819
842
  if (disposed || committed !== current) return { status: 'superseded' }
@@ -6,8 +6,10 @@ const FALLBACKS = Object.freeze({
6
6
  pending: 'Capabilities prepared; they take effect at the first accepted model step for session',
7
7
  unavailable: 'Select an active PTC session; the command did not update session',
8
8
  usage: 'Use /capabilities to inspect or /capabilities refresh to apply saved settings for session',
9
+ paused: 'Capabilities and context are off; the session stays with the Host capabilities for session',
10
+ off: 'Capability management is off; the session stays with the Host capabilities for session',
9
11
  })
10
- const STATES = new Set(['recorded', 'pending', 'unavailable', 'usage', 'saved-only'])
12
+ const STATES = new Set(['recorded', 'pending', 'unavailable', 'usage', 'saved-only', 'paused', 'off'])
11
13
 
12
14
  export function capabilityCommandReceipt(state, sessionId, error, proposal) {
13
15
  if (!STATES.has(state) || typeof sessionId !== 'string' || !sessionId) throw new TypeError('invalid capability command receipt')
@@ -707,7 +707,8 @@ export function createCapabilityEnvironment({ ctx, config, toolSchemasForAgent,
707
707
  const hasChoices = settings.contextEnabled !== undefined || settings.policy !== undefined || choice !== undefined || previous?.schema === 'ptc-capability-environment/v2'
708
708
  const snapshot = deepFreeze({ schema: settings.contextEnabled !== undefined ? 'ptc-capability-environment/v4' : settings.policy ? 'ptc-capability-environment/v3' : hasChoices ? 'ptc-capability-environment/v2' : 'ptc-capability-environment/v1',
709
709
  ...(hasChoices ? { sourceSettings, sessionOverrides } : {}), navigationText: NAVIGATION, settings, capabilities, ...(coverage === undefined ? {} : { coverage }), plan })
710
- const message = planMessage(snapshot, id, force)
710
+ const initialized = sessionEvents(agent.session).some(event => event.type === 'system/message')
711
+ const message = planMessage(snapshot, id, force && initialized)
711
712
  agent.signal?.throwIfAborted?.()
712
713
  signal?.throwIfAborted()
713
714
  // Initialized sessions commit a refresh and its notification atomically.
@@ -716,7 +717,7 @@ export function createCapabilityEnvironment({ ctx, config, toolSchemasForAgent,
716
717
  hostSections: sections ?? [], expanded: new Set(), used: new Map(),
717
718
  ...(requiresCapabilityInventory(settings) ? { liveCapabilities: new Map(capabilities.map(item => [item.id, item])) } : {}) }
718
719
  restoreEvidence(agent, state)
719
- if (choice?.commandId && !sessionEvents(agent.session).some(event => event.type === 'system/message')) {
720
+ if (choice?.commandId && !initialized) {
720
721
  state.pendingCommand = choice.commandId
721
722
  state.pendingPrevious = stateFor(agent)
722
723
  }
@@ -1186,7 +1187,12 @@ export function createCapabilityEnvironment({ ctx, config, toolSchemasForAgent,
1186
1187
  || String(agent.session.id ?? agent.id) !== sessionId) {
1187
1188
  throw new Error('ptc-plus: capability observation target is no longer available')
1188
1189
  }
1189
- return { schema: 'ptc-capability-session-catalog/v1', management, entries }
1190
+ const savedSettings = capabilityConfiguration(currentConfig)
1191
+ // The saved group and management switches gate session writes in change();
1192
+ // report them so the Client can block staged choices with the reason.
1193
+ return { schema: 'ptc-capability-session-catalog/v1', management, entries,
1194
+ savedSettings: { enabled: effectiveCapabilitySettings(savedSettings).enabled === true,
1195
+ contextEnabled: savedSettings.contextEnabled !== false } }
1190
1196
  },
1191
1197
  async observeAllocation({ sessionId, configuration } = {}, signal) {
1192
1198
  signal?.throwIfAborted()
@@ -198,12 +198,16 @@ export function capabilityCoverage(state, query) {
198
198
  const workspaceId = options.workspaceId === null ? null : identity(options.workspaceId, 'coverage workspace identity')
199
199
  if (state.status !== 'usable') return { status: state.status, windowSize: state.limits.windowSize, eligible: null, used: null, coverage: null }
200
200
  const window = state.windows.find(item => item.userId === userId && item.workspaceId === workspaceId && item.id === id && item.revision === revision)
201
- const families = window ? state.families.filter(item => qualifies(item, window)).sort(orderSamples).slice(0, state.limits.windowSize) : []
201
+ // "Nothing recorded yet" and "recorded families are not currently settled"
202
+ // are different facts from a measured window; neither may read as a zero.
203
+ if (window === undefined) return { status: 'no-samples', windowSize: state.limits.windowSize, eligible: null, used: null, coverage: null }
204
+ const families = state.families.filter(item => qualifies(item, window)).sort(orderSamples).slice(0, state.limits.windowSize)
202
205
  // A resumed family can expose a gap behind the irreversible eviction frontier.
203
206
  // Surviving samples do not prove the recent settled window until it is full again.
204
- if (window?.floor > 0 && families.length < state.limits.windowSize) {
207
+ if (window.floor > 0 && families.length < state.limits.windowSize) {
205
208
  return { status: 'incomplete-window', windowSize: state.limits.windowSize, eligible: null, used: null, coverage: null }
206
209
  }
210
+ if (families.length === 0) return { status: 'pending-settlement', windowSize: state.limits.windowSize, eligible: null, used: null, coverage: null }
207
211
  const used = families.filter(family => family.observations.some(item => item.id === id && item.revision === revision && item.used
208
212
  && (workspaceId === null || item.workspaceId === workspaceId))).length
209
213
  return { status: 'usable', windowSize: state.limits.windowSize, eligible: families.length, used, coverage: families.length ? used / families.length : null }
@@ -215,6 +215,15 @@ export const CONFIG_FIELDS = Object.freeze([
215
215
  description: 'worker 重启后从 session journal 重建可恢复的 REPL 状态。',
216
216
  descriptionEn: 'Rebuilds recoverable REPL state from the session journal after a worker restart.',
217
217
  },
218
+ {
219
+ key: 'officialReplPriority',
220
+ type: 'boolean',
221
+ default: false,
222
+ label: '官方 REPL 模式优先',
223
+ labelEn: 'Prioritize the official REPL mode',
224
+ description: '检测到官方持久 REPL 模式时由官方运行时接管 PTC 会话执行;PTC Plus 让出 run_code 及其进程内会话状态语义,关闭时始终由 PTC Plus 接管。',
225
+ descriptionEn: 'Lets the official runtime own PTC session execution when the official persistent REPL mode is active; PTC Plus then leaves run_code and its in-process session-state semantics to DSH. Disabled keeps PTC Plus in charge.',
226
+ },
218
227
  {
219
228
  key: 'tipsEnabled',
220
229
  type: 'boolean',
@@ -367,7 +376,7 @@ export const CONFIG_GROUPS = Object.freeze([
367
376
  key: 'recovery',
368
377
  label: '状态与恢复',
369
378
  labelEn: 'State and recovery',
370
- fields: Object.freeze(['durableReplay', 'tipsEnabled', 'tipCooldownMessages', 'tipEscalationFailures']),
379
+ fields: Object.freeze(['durableReplay', 'officialReplPriority', 'tipsEnabled', 'tipCooldownMessages', 'tipEscalationFailures']),
371
380
  },
372
381
  {
373
382
  key: 'extensions',