dsh-ptc-plus 0.5.2 → 0.5.3
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.
- package/README.en.md +3 -1
- package/README.md +3 -1
- package/client.cjs +165 -29
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +4 -2
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +2 -2
- package/docs/adr/0023-global-user-bindings.md +12 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +1 -1
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +2 -2
- package/docs/adr/0044-coexist-with-the-official-persistent-repl.md +4 -4
- package/docs/adr/0046-gate-the-commit-with-the-semantic-evidence-tier.md +1 -1
- package/docs/adr/0048-supersede-a-rewritten-cell.md +48 -0
- package/docs/adr/0049-prove-a-settlement-against-its-published-record.md +53 -0
- package/docs/adr/0050-read-host-surfaces-through-their-published-contract.md +48 -0
- package/docs/adr/README.md +7 -1
- package/docs/architecture.md +1 -1
- package/docs/durability-design.md +9 -2
- package/docs/installation.md +9 -9
- package/docs/releases/v0.5.2.md +26 -14
- package/docs/releases/v0.5.3.md +77 -0
- package/docs/runtime-reference.md +7 -1
- package/docs/user-bindings.en.md +15 -0
- package/docs/user-bindings.md +15 -0
- package/docs/v0.5.3-goals.md +117 -0
- package/docs/verification.md +4 -4
- package/index.js +67 -19
- package/internal/binding-adoption.js +136 -0
- package/internal/capability-command-receipt.js +18 -4
- package/internal/capability-delivery-proof.js +70 -0
- package/internal/capability-environment.js +38 -1
- package/internal/config-spec.js +14 -5
- package/internal/cordis-tools-owner.js +2 -1
- package/internal/direct-surface-owner.js +27 -13
- package/internal/edit-transport-owner.js +6 -5
- package/internal/execution-provider-owner.js +6 -0
- package/internal/execution-seam-compat.js +25 -0
- package/internal/host-tool-sdk.js +308 -0
- package/internal/official-repl-priority.js +31 -10
- package/internal/program-bindings.js +12 -1
- package/internal/repl-memory-projection.js +21 -2
- package/internal/runtime-bridge-owner.js +113 -8
- package/internal/runtime-messages.js +5 -0
- package/internal/session-journal-recovery.js +210 -32
- package/internal/session-journal-schema.js +3 -0
- package/internal/session-journal.js +46 -9
- package/internal/session-log-view.js +22 -2
- package/internal/session-runtime.js +106 -39
- package/internal/user-bindings-owner.js +109 -6
- package/package.json +15 -7
- package/src/client-capabilities-view.js +45 -3
- package/src/client-copy.js +24 -0
- package/src/client-indicator-view.js +2 -1
- package/src/client-repl-view.js +35 -16
- package/src/client.js +3 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
[中文](#cn-v053) | [English](#en-v053)
|
|
2
|
+
|
|
3
|
+
<h3 id="cn-v053">宿主适配</h3>
|
|
4
|
+
|
|
5
|
+
* 装在较新的官方 DSH 上照常可用:宿主把 PTC 的工具呈现交给工具执行器后,`run_code` 不再是注册的工具定义。插件向"实际发布它的那一面"要它——注册表注册了就问注册表,执行器拥有它就问执行器——两种形态都不按版本判断。本版本的阻塞通道 `latest` 与 `next` 发布的是注册表形态;执行器形态属于 `alpha` 线,按该线发布的形状适配并只报告结果
|
|
6
|
+
* 状态保留按宿主声明读取:宿主的运行期提供方声明是否为同一个状态所有者保留环境时,插件照它说的报告,不会把一个有状态的程序环境说成无状态;"让位给官方 REPL"也因此看这份声明,而不是看某种源码形式
|
|
7
|
+
* 插件自己的会话执行、`edit_run_code` 改写与其结算证据在新的派发契约上照旧成立:一次工具派发按宿主发布的形式提交(单次调用或整批),改写后的单元格仍读得到新值
|
|
8
|
+
* 宿主若在提示装配之后才声明模型可见的工具,那份声明归宿主所有;插件只在它自己拥有的层面(程序内绑定、SDK、供给规则)继续生效,不会假装替换了宿主未交给它的声明
|
|
9
|
+
* 支持的宿主线用通道表达而不是版本:`latest`(最新发布)、`next`(在途候选)与 `alpha`(尚未发布的那条线)各自对应一条宿主线,插件按每条线发布的形状安装与运行。`latest` 与 `next` 是阻塞通道,本版本的安装与运行证据以它们为准;`alpha` 是提前适配,只报告、不阻塞发布。插件行为不按宿主版本分支
|
|
10
|
+
* 目标发行仍是当前官方 DSH,插件继续使用它的公共扩展面(设置卡片座位、会话投影、能力探测与公共 `Menu`),本版本不要求宿主侧改动
|
|
11
|
+
* 旧版本写下的会话日志照常恢复:本版本之前的结算记录没有新的自证字段,恢复按旧规则读取它们,不需要清空会话或配置
|
|
12
|
+
|
|
13
|
+
<h3>新增</h3>
|
|
14
|
+
|
|
15
|
+
* 绑定取用变成会话自己的决定:`/binding status` 列出全局提供的条目,`/binding use <id>` 与 `/binding skip <id>` 决定这个会话用不用,`/binding reset` 回到缺省。没有表态就是全部取用,所以升级后不设置也没有行为变化;决定记在会话里,重启后从日志重建
|
|
16
|
+
* 全局删掉的条目不能在会话里被"救回来":它已不在目录里,取用只可能收窄,不存在覆盖复活这条路径
|
|
17
|
+
* 设置里新增开关「编辑时替换原单元格」(`editRewritesHistory`,默认关闭)。打开后,已接受的 `edit_run_code` 修订取代目标单元格:后续单元格读到修订的值,目标单元格仍留在日志里但不再参与环境重建,修订不再声明的名字以"被取代"报告(`PTC-R007`),而不是当作丢失
|
|
18
|
+
* 能力设置的收据与本面板的面板把三件事分开说:已保存(全局设置记录了这项选择)、已投递(模型可见目录里已出现该项)、已可用(本会话有完成的执行真的取到了它)。保存或发布都不构成"已可用",没有证据时如实说未证明
|
|
19
|
+
|
|
20
|
+
<h3>修复</h3>
|
|
21
|
+
|
|
22
|
+
* 被事后改动的历史记录不再能充当证据:每个结算在发布时留下自己陈述的摘要,恢复折叠会重算并比较它。status、completion 或诊断措辞被改过的单元格会被判为不可信、被跳过并点名(`PTC-R002`),而不是重放一个从未发生过的单元格;判定是终态,不会进入重试
|
|
23
|
+
* 打开「编辑时替换原单元格」时,被取代单元格的绑定以专门诊断点名,而不是静默消失
|
|
24
|
+
* 取代关系不再取决于修订自己怎么结算:修订读到时间、随机数等 live 状态时按记录值安装,被收缩时干脆不在环境里,但两种情况都仍然取代目标单元格
|
|
25
|
+
* 修订接在被编辑单元格的位置,原本跟在其后的单元格改从修订继续:在编辑之前读到该单元格声明的 cell 不再于冷恢复时丢掉这个声明,恢复也不再因此收缩回被编辑前的值
|
|
26
|
+
* 一次恢复既装入了记录值又取代了单元格时,取代关系照旧报告,不再因为已经发了恢复通知而被丢掉
|
|
27
|
+
* REPL 绑定面板说明清单来自哪个运行期:同一运行期内的投影刷新仍然保留清单;上一个进程留下的快照显示为不可确认,并给出原因,不再静默续用
|
|
28
|
+
|
|
29
|
+
<h3>升级</h3>
|
|
30
|
+
|
|
31
|
+
* 需要 Node.js `^22.19.0 || >=24.0.0`;升级插件后请重启 DSH
|
|
32
|
+
* 不需要配置迁移:新开关默认关闭,其它字段沿用既有配置;旧会话日志不需要清理或重写
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
dsh plugin --profile <profile> add dsh-ptc-plus@0.5.3
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
<h3 id="en-v053">Host Compatibility</h3>
|
|
41
|
+
|
|
42
|
+
* It runs on a newer official DSH: once the host hands PTC tool presentation to a tool executor, `run_code` is no longer a registered tool definition. The plugin asks whichever surface actually publishes it — the registry when one registers it, the executor when one owns it — and neither shape is decided by a version. The blocking channels of this release, `latest` and `next`, publish the registry shape; the executor shape belongs to the `alpha` line, which this release adapts to and reports on without blocking
|
|
43
|
+
* State retention follows the host's declaration: when the host's program runtime declares whether it keeps an environment per state owner, the plugin reports what it says instead of describing a stateful environment as stateless, and handing execution over to the official REPL follows that declaration rather than one source form
|
|
44
|
+
* The plugin's own session execution, its `edit_run_code` revisions and their settlement evidence still hold on the new dispatch contract: one tool dispatch is submitted in the form the host publishes (a single call or a complete batch), and a rewritten cell still reads the revised value
|
|
45
|
+
* When a host declares the model-facing tools after prompt assembly, that declaration belongs to the host; the plugin keeps its own layers (program bindings, SDK, supply rules) and does not claim to have replaced a declaration it was never given
|
|
46
|
+
* Supported host lines are expressed as channels, not versions: `latest` (newest release), `next` (in-flight candidate) and `alpha` (a line the host has not released yet) each name one host line, and the plugin installs and runs on the shape each line publishes. `latest` and `next` are the blocking channels this release's install and run evidence comes from; `alpha` is an early adaptation that reports without blocking a release. Plugin behaviour never branches on a host version
|
|
47
|
+
* This release targets the current official DSH release and keeps using its public extension surfaces (the settings card seat, the session projection, capability probing and the public `Menu`); no host-side change is required
|
|
48
|
+
* Session logs written by earlier versions keep recovering: settlements recorded before this version carry none of the new self-proof fields, and recovery reads them by the older rules, so no session or configuration has to be cleared
|
|
49
|
+
|
|
50
|
+
<h3>New Features</h3>
|
|
51
|
+
|
|
52
|
+
* Binding adoption is now the session's own decision: `/binding status` lists the globally supplied entries, `/binding use <id>` and `/binding skip <id>` decide whether this session uses one, and `/binding reset` returns to the default. Saying nothing means every entry is used, so upgrading without touching it changes nothing; the decision lives in the session and is rebuilt from the log after a restart
|
|
53
|
+
* An entry deleted globally cannot be revived from a session: it is no longer in the catalog, adoption can only narrow what is supplied, and there is no override that brings it back
|
|
54
|
+
* New setting **Replace the edited cell in place** (`editRewritesHistory`, off by default). When it is on, an accepted `edit_run_code` revision supersedes its target: later cells read the revision's value, the target keeps its place in the log but no longer rebuilds the environment, and names the revision no longer declares are reported as superseded (`PTC-R007`) instead of being treated as lost
|
|
55
|
+
* A capability receipt and the capability pane now state three facts separately: saved (the global setting records the choice), delivered (the model-visible catalog lists it) and usable (a completed execution in this session actually reached it). Neither saving nor publishing proves usability, and without evidence the pane says so
|
|
56
|
+
|
|
57
|
+
<h3>Fixes</h3>
|
|
58
|
+
|
|
59
|
+
* A historical record edited after the fact can no longer serve as evidence: every settlement records a digest of what it published, and the fold recomputes and compares it. A cell whose status, completion or diagnostic wording changed is treated as untrusted, skipped and named (`PTC-R002`) instead of replaying a cell that never ran; the decision is terminal and never becomes a retry
|
|
60
|
+
* With history rewriting on, the bindings of a superseded cell are named by their own diagnostic instead of disappearing silently
|
|
61
|
+
* Supersession no longer depends on how the revision itself settled: a revision that read live state (a clock, a random source) is installed from its recorded values, and one the fold contracts is not in the environment at all, but both still replace their target
|
|
62
|
+
* A revision takes the edited cell's place in the recorded order and the cells that followed it continue from the revision: a cell that read a binding of the edited cell before the edit no longer loses it during cold recovery, and recovery no longer contracts back to the value the edit replaced
|
|
63
|
+
* A recovery that also installed recorded values still reports the relation it adopted instead of dropping it because a restore notice already existed
|
|
64
|
+
* The REPL binding panel states which runtime its inventory came from: a projection refresh inside the same runtime keeps the inventory, while a snapshot left by a previous process reads as unconfirmed with its reason instead of being reused silently
|
|
65
|
+
|
|
66
|
+
<h3>Upgrade</h3>
|
|
67
|
+
|
|
68
|
+
* Requires Node.js `^22.19.0 || >=24.0.0`; restart DSH after upgrading the plugin
|
|
69
|
+
* No configuration migration is needed: the new switch is off by default and other fields keep their values; existing session logs need no cleanup or rewrite
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
dsh plugin --profile <profile> add dsh-ptc-plus@0.5.3
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
Full Changelog: https://github.com/muyuanjin/dsh-ptc-plus/compare/v0.5.2...v0.5.3
|
|
@@ -63,6 +63,12 @@ Discovery covers current `tools` plus the advanced `repl` and `code` namespaces.
|
|
|
63
63
|
|
|
64
64
|
For commands, prefer project-declared scripts and use an available typed tool. When direct host process access is more appropriate, inspect the executable that is actually installed and invoke it through Node.js `child_process`. Child processes inherit the recorded cell execution cwd when `options.cwd` is omitted, while an explicit `cwd` remains authoritative; the worker preserves the host execution environment needed to resolve package runners and shells. Add a shell or package runner only when the command requires its syntax or resolution; do not assume a particular shell exists.
|
|
65
65
|
|
|
66
|
+
## Host Surfaces
|
|
67
|
+
|
|
68
|
+
The reserved `run_code` definition is resolved from whichever surface publishes it: the tool registry answers `tools.get('run_code', scope)`, and a host that keeps the definition inside its tool executor publishes it through one of three contracted readers, `requirePtcTransport()`, `requireTransport()` or `transport()`. Those readers and the executor's own declaration reader `describe([])` are the only members invoked: an executor's remaining methods are host code this plugin does not own, and a probe that called them could dispose, stop or start host state during prompt assembly. An executor that declares the name in `describe([])` and publishes no definition through those readers is *declared but unreadable*: the plugin reports one warning and keeps session execution unavailable, which is a different fact from a host without an executor. See [ADR 0050](adr/0050-read-host-surfaces-through-their-published-contract.md).
|
|
69
|
+
|
|
70
|
+
The editable prompt slots (`runCode.description`, the `code` and `description` parameter texts) are written onto that definition as owned properties, and the host descriptors they replaced are restored when the attachment is disposed. A host that publishes the slots as getter-only accessors therefore receives the delivered text, and a definition whose slots cannot be replaced keeps its own text instead of half of the plugin's.
|
|
71
|
+
|
|
66
72
|
## Configuration
|
|
67
73
|
|
|
68
74
|
Capability management is experimental and defaults to off. These inputs are frozen per session; saved changes apply to new sessions or `/capabilities refresh`:
|
|
@@ -134,7 +140,7 @@ The five legacy binding and module switches remain migration inputs. Complete en
|
|
|
134
140
|
|
|
135
141
|
`startupProbeMs` bounds the worker startup conformance probe, which must settle before the kernel reports ready. The worker reads the value from its startup data and falls back to 5000 ms when it is absent; a healthy probe settles inside the worker's own microtasks, so the bound can only fire when startup itself yields or blocks, and the failure is reported as a startup diagnostic without executing any cell. That worker-side read is verified by inspection rather than by a timer test, because a healthy probe never reaches the bound.
|
|
136
142
|
|
|
137
|
-
`officialReplPriority` defaults to `false` and is evaluated against the live provider capability rather than a release number: a provider
|
|
143
|
+
`officialReplPriority` defaults to `false` and is evaluated against the live provider capability rather than a release number: a provider that declares `retainsState: true` keeps a program environment per state owner, and a provider that predates that declaration answers through the one retained source form (`programFormat: 'repl'`). With the setting enabled and such a provider live, this host attachment installs no session runtime, so `run_code` and its descriptors — `programFormat`, `retainsState`, `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, answers `retainsState` for itself (an installed session kernel retains a per-session environment; otherwise the original declaration decides) 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)`.
|
|
138
144
|
|
|
139
145
|
`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.
|
|
140
146
|
|
package/docs/user-bindings.en.md
CHANGED
|
@@ -60,6 +60,21 @@ The code console runs the entry's unsaved source draft. It supports top-level `a
|
|
|
60
60
|
|
|
61
61
|
Bindings are stored in `$DSH_HOME/ptc-plus/bindings.json`. Relative imports in binding modules resolve from that file's directory; ordinary session REPL imports resolve from the session's project directory.
|
|
62
62
|
|
|
63
|
+
## Skipping an entry in this session only
|
|
64
|
+
|
|
65
|
+
The global catalog decides what exists; whether *this* session uses an entry is a separate decision. Inside a session:
|
|
66
|
+
|
|
67
|
+
| Command | Effect |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `/binding status` | Lists every catalog entry and says whether this session uses it |
|
|
70
|
+
| `/binding skip <id>` | This session does not use that entry; `id` is the identifier before the parentheses in `status` |
|
|
71
|
+
| `/binding use <id>` | Undoes that choice |
|
|
72
|
+
| `/binding reset` | Clears every choice for this session, back to "use what the catalog supplies" |
|
|
73
|
+
|
|
74
|
+
Only this session changes: the global catalog and other sessions are untouched, the next cell already follows the new choice, and the choice survives a restart because it is recorded in the session log.
|
|
75
|
+
|
|
76
|
+
An adoption decision can only withdraw a supplied entry. It cannot supply one, so an entry the catalog does not offer — or one disabled globally — stays unavailable here, and "the global switch is off but a session brought it back" cannot happen.
|
|
77
|
+
|
|
63
78
|
## Limits
|
|
64
79
|
|
|
65
80
|
Enabled configuration allows loading but does not guarantee successful initialization. Assigning or redeclaring a same-name variable can override the global default in the current session without modifying its saved entry.
|
package/docs/user-bindings.md
CHANGED
|
@@ -60,6 +60,21 @@
|
|
|
60
60
|
|
|
61
61
|
绑定保存在 `$DSH_HOME/ptc-plus/bindings.json`。条目源码中的相对 import 从该文件所在目录解析,普通会话 REPL 的 import 则从会话项目目录解析。
|
|
62
62
|
|
|
63
|
+
## 只在本会话关闭某个绑定
|
|
64
|
+
|
|
65
|
+
全局条目决定"目录里有什么",某个会话用不用它是另一件事。在会话里输入:
|
|
66
|
+
|
|
67
|
+
| 命令 | 作用 |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `/binding status` | 列出目录里的每个条目,并标明本会话是否使用它 |
|
|
70
|
+
| `/binding skip <id>` | 本会话不使用某个条目;`id` 是 `status` 里括号前那个标识 |
|
|
71
|
+
| `/binding use <id>` | 撤销上一步,恢复为使用 |
|
|
72
|
+
| `/binding reset` | 清空本会话的全部选择,回到"目录给什么就用什么" |
|
|
73
|
+
|
|
74
|
+
它只改变**本会话**:全局条目和其他会话都不受影响,改完立即对下一个单元格生效,会话重启后依然有效(记录在会话日志里,可重建)。
|
|
75
|
+
|
|
76
|
+
它只能"少用一个目录里已有的条目",不能凭空增加一个。目录里没有或已停用的条目,本会话也拿不到——所以不存在"全局关掉、会话又把它复活"的情况。
|
|
77
|
+
|
|
63
78
|
## 使用限制
|
|
64
79
|
|
|
65
80
|
已启用表示绑定配置允许加载,不保证每次初始化都成功。当前会话中同名变量的赋值或重声明可以覆盖全局默认值,不会修改保存的条目。
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# v0.5.3 目标:可证明的状态
|
|
2
|
+
|
|
3
|
+
## 0.5.2 留下了什么
|
|
4
|
+
|
|
5
|
+
0.5.2 解决的是**状态连续**:视图变化不再重建内核,绑定不会因为一次重渲染而丢失,编辑是追加而不是改写历史。它把"REPL 环境活着的时候算数"这件事做扎实了。
|
|
6
|
+
|
|
7
|
+
但它留下一个未回答的问题:**插件对模型说的那些话,凭什么算数?**"这个绑定可用""这条记录没被改过""这个能力已经生效"——这些都只是**声明**,不是**证明**。0.5.2 里已经能看到三种破口:
|
|
8
|
+
|
|
9
|
+
- 恢复提示说某个名字 live,下一格却 `not defined`(`PTC-R002` 收口了一部分);
|
|
10
|
+
- 保存了全局绑定开关,面板只能说"已保存",说不出"已投递""已可用";
|
|
11
|
+
- 历史记录被事后改动,插件从记录本身看不出来,因为记录与它的副本永远自洽。
|
|
12
|
+
|
|
13
|
+
所以 0.5.3 的主题不是再加功能,而是**把声明变成证明**:
|
|
14
|
+
|
|
15
|
+
> **0.5.3:可证明的状态。** 插件对模型陈述的每一条事实,都要有它能产出、模型能检验的证据;并且永远区分"已配置""已投递""已证明"。
|
|
16
|
+
|
|
17
|
+
## 概念基元
|
|
18
|
+
|
|
19
|
+
四个词贯穿本版全部目标,先定义再谈功能。
|
|
20
|
+
|
|
21
|
+
| 概念 | 回答的问题 | 谁拥有 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| **供给 supply** | 产品声明存在哪些能力、绑定、能力开关 | 全局配置与目录 |
|
|
24
|
+
| **取用 adoption** | 这个会话的环境选择用其中哪些 | 会话 |
|
|
25
|
+
| **物化 materialization** | worker 真的把它装进环境了吗 | 运行时与 worker |
|
|
26
|
+
| **证明 proof** | 有什么可被模型检验的证据 | 日志、探测、诊断 |
|
|
27
|
+
|
|
28
|
+
三条不变式:
|
|
29
|
+
|
|
30
|
+
- **I1 层次不可互相代答。** 供给不能代取用,取用不能代物化,物化不能代证明。
|
|
31
|
+
- **I2 只有证明可以宣称"可用"。** 没有证明时如实说"不可确认",而不是默认可用。
|
|
32
|
+
- **I3 可证明的事实必须能从会话日志重建。** 重建不出来的,要显式标注为不可重建,而不是假装它不存在。
|
|
33
|
+
|
|
34
|
+
0.5.2 的教训正好对应 I1:全局开关是供给,会话要不要用是取用,装了没有是物化——三层混成一个"开关",就会出现"我改了全局,为什么这个会话不变"和"配置关了但绑定还在"这类说不清的状态。
|
|
35
|
+
|
|
36
|
+
## 目标
|
|
37
|
+
|
|
38
|
+
### G1 绑定:从"开关"到"环境成员"
|
|
39
|
+
|
|
40
|
+
**概念纠正。** 绑定不是配置项,是**环境成员**。全局决定目录供给,会话决定取用,worker 决定可用。
|
|
41
|
+
|
|
42
|
+
- 全局关闭一个条目 = 它不在目录里,会话无从取用。**"覆盖复活已删除条目"这个概念上不存在**,不需要额外的冲突规则去禁止。
|
|
43
|
+
- 会话取用是二态(用 / 不用)加一个缺省(未表态即全取),不需要三态哨兵,也不借用能力侧的 `automatic`——那个词的含义是"由宿主推导",绑定没有可推导的决策。
|
|
44
|
+
- 取用 ≠ 物化成功:装失败的名字必须点名(接 `PTC-R002`),曾经 live 后消失的必须点名(`PTC-R006`)。
|
|
45
|
+
|
|
46
|
+
**验收**:不设会话取用时行为与 0.5.2 完全一致;A 会话不用某绑定、B 会话仍可用;全局关闭后会话不可取用且无"复活"路径;重启后取用关系随日志重建;物化失败在模型可见处点出名字。
|
|
47
|
+
|
|
48
|
+
### G2 能力与上下文:把"已保存"升级为"已投递"
|
|
49
|
+
|
|
50
|
+
**概念。** 保存设置只改变供给,不证明模型看得到、也不证明 worker 初始化过。面板与提示必须分开说这三件事。
|
|
51
|
+
|
|
52
|
+
**验收**:保存后能报告目录是否出现该能力、新单元格能否真的取到它;三种状态(已保存 / 已投递 / 已可用)在面板与模型可见提示中区分;任一层失败都指名,而不是笼统"已生效"。
|
|
53
|
+
|
|
54
|
+
### G3 历史证据:记录必须能自证未被改动
|
|
55
|
+
|
|
56
|
+
**概念。** 一份可以被事后编辑、且编辑后仍与自身副本一致的记录,不是证据。执行时要写入**不可重算的快照**,折叠时用它比对。
|
|
57
|
+
|
|
58
|
+
现有的半成品:`completion` 与诊断已经互相校验(0.5.2),但 `diagnostics` 被改后再重算 `completion` 就自洽了——因为事件里没有第三份独立副本(`message.content` 为空)。
|
|
59
|
+
|
|
60
|
+
**契约**:快照字段随 journal schema 一起版本化;不一致即判该格不可信;**否决必须是重放终态**,不能进入重试循环(上一次实现因此挂死)。
|
|
61
|
+
|
|
62
|
+
**验收**:篡改 completion 或 diagnostic 都使该格不可信;恢复矩阵不退化;journal 迁移对旧记录保持兼容。
|
|
63
|
+
|
|
64
|
+
### G4 改写历史:`editRewritesHistory`
|
|
65
|
+
|
|
66
|
+
**概念纠正。** "改写历史"不是删除,而是**取代关系**。旧单元格仍然留在日志里,只是不再参与环境构建;它声明的名字以"被取代"报告,而不是以"丢失"报告。
|
|
67
|
+
|
|
68
|
+
这比"追加"更诚实:不假装旧执行没发生过,而是显式声明谁取代了谁(日志里记为 `supersedes`:这条记录取代了哪条)。默认关闭,开启时改写才真正替换目标单元格。
|
|
69
|
+
|
|
70
|
+
**验收**:关闭时全部既有用例不变;开启时"改写 → 后续格子读到新值"成立;被取代单元格的绑定以专门诊断点名;折叠能从日志重建取代关系。
|
|
71
|
+
|
|
72
|
+
### G5 快照归属:REPL 面板的 generation
|
|
73
|
+
|
|
74
|
+
**概念。** 面板显示的不是"当前状态",而是**某个运行期的一次观察**。需要分开两件事:
|
|
75
|
+
|
|
76
|
+
- **新鲜度**:同一运行期内的投影刷新——清单仍然有效,不该闪成"不可确认";
|
|
77
|
+
- **归属**:跨运行期的快照——属于上一个进程,必须判为不可确认(既有契约,`repl-memory-projection` 的用例钉着它)。
|
|
78
|
+
|
|
79
|
+
0.5.2 的客户端兜底只解决了新鲜度,没有表达归属。0.5.3 要在数据里表达"这份清单来自哪个运行期",而不是靠随机 UUID 与对象身份比较去猜。
|
|
80
|
+
|
|
81
|
+
**验收**:两个会话并行、其中一个结束时,另一个面板不闪;真正重建后显示不可确认并说明原因;既有契约用例保持绿。
|
|
82
|
+
|
|
83
|
+
### G6 覆盖率:证据,不是仪式
|
|
84
|
+
|
|
85
|
+
**概念。** 覆盖率是"这段代码被执行过"的证据;阈值是策略,不是证据。0.5.2 为发版引入了"按改动文件 100% + 历史欠账白名单",那是策略例外。
|
|
86
|
+
|
|
87
|
+
**验收**:全局 `lines 100 / functions 100`,白名单清空;`review:finalize` 记录对应证明;发布流程不再需要"按改动文件"的特殊分支。
|
|
88
|
+
|
|
89
|
+
## 非目标
|
|
90
|
+
|
|
91
|
+
- 不新增 PTC 语言特性,不改 DSH 的公共契约;
|
|
92
|
+
- 不做跨会话共享 worker 或跨会话共享 REPL 状态;
|
|
93
|
+
- 不为通过门槛而放宽断言或增加 coverage ignore(除非证明不可达并写入提交信息)。
|
|
94
|
+
|
|
95
|
+
## 发布判据
|
|
96
|
+
|
|
97
|
+
1. 六个目标各自的验收项全部有可执行证据;
|
|
98
|
+
2. `npm run check`、`npm run coverage`(全局 100%)、`semantic:check`(unknown 0)全绿;
|
|
99
|
+
3. `docs/` 中受影响的 owner 文档与 ADR 同步更新;
|
|
100
|
+
4. Release 工作流 `stage` 成功,npm 进入 staged 状态;
|
|
101
|
+
5. 发布说明按 [release-notes.md](release-notes.md) 的用户视角重写,不以功能清单代替变化说明。
|
|
102
|
+
|
|
103
|
+
## 发布时机
|
|
104
|
+
|
|
105
|
+
0.5.2 已经把"提前适配"这条路的代价演示过一遍:为一个尚未发布到 GitHub 的 alpha 改代码,等对应 rc 出来又要再改一轮,而 alpha 的接口形状在正式发布前并不稳定。
|
|
106
|
+
|
|
107
|
+
因此 0.5.3 的发布以**通道**为准,不以"某天适配好了"为准:
|
|
108
|
+
|
|
109
|
+
| 通道 | 含义 | 对发布的作用 |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| `latest` | 最新的已发布 rc | 必须通过;它决定能不能发 |
|
|
112
|
+
| `next` | 正在推进的下一条 rc 线,GitHub 已有发布说明 | 必须通过 |
|
|
113
|
+
| `alpha` | 提前适配,正式发布尚未出现 | 只报告,不阻塞 |
|
|
114
|
+
|
|
115
|
+
规则:**插件可以提前适配 alpha(那是有价值的预警),但版本发布等待包含该形状的正式 rc。** 已经 staged 的包如果对应的是 alpha 形状的实现,就撤掉重新准备,而不是"先发出去再说"。
|
|
116
|
+
|
|
117
|
+
通道的具体版本由 `npm run host:channels:update` 从 dist-tag 解析并写入 [compat/channels.json](../compat/channels.json),不手工编辑版本号;行为代码只做形状探测,由 `npm run host:shape-check` 守住。
|
package/docs/verification.md
CHANGED
|
@@ -13,7 +13,7 @@ Backend and browser entries accept `--list`, and selections can repeat. Omitting
|
|
|
13
13
|
|
|
14
14
|
`npm run check` classifies the complete source candidate against HEAD, runs the appropriate checks below, checks source/ledger stability, and records the verified tree and verification scope for the commit hook. Run `check` alone when the ledger is absent or resolved; while findings remain unresolved, use `verify`. Do not run complete component suites around the same final command: that duplicates its work. Focused evidence is not a source-bound commit proof. Inspect a failed gate's stage/case, reproduce it with a focused entry, repair it, and rerun the final command only after the candidate is stable.
|
|
15
15
|
|
|
16
|
-
The full branch of `check` runs `npm run verify:check`: `review:validate`, `host:
|
|
16
|
+
The full branch of `check` runs `npm run verify:check`: `review:validate`, `host:channels:verify`, `host:shape-check`, `semantic:check`, `build:check`, the syntax checks and `scripts/deterministic-test-groups.mjs --check`, which selects `npm run test:client` and `npm run test:backend:fast`. The Client units run in the gate because a change can be covered only by them; the backend tier is the set of test files `semantic-obligations.json` names as evidence for at least one obligation, excluding coverage-only paths and the verification-tooling probes that fail by construction outside their harness. It targets a few minutes so the commit gate is actually run; [ADR 0046](adr/0046-gate-the-commit-with-the-semantic-evidence-tier.md) owns this split and its evidence.
|
|
17
17
|
|
|
18
18
|
`npm run verify` keeps the complete behavioral gate: `test:client:visual` plus the whole ordinary backend inventory. CI runs `verify:ci`; release preparation runs `verify`, and coverage stays release-only. A change that touches a semantic owner missing from the graph must extend the graph, which widens the fast tier at the same time. The browser suites are not part of the commit gate: they are the complete visual contract and run in `verify`, in CI, and on demand.
|
|
19
19
|
|
|
@@ -45,11 +45,11 @@ The full local and CI behavioral gates run `test:client:visual`: one Client suit
|
|
|
45
45
|
|
|
46
46
|
`npm run verify` and the full branch of `npm run check` validate the ledger, compare generated bundles, check syntax, run Client tests, and run all ordinary backend tests through `npm run test:backend` without coverage instrumentation. The ordinary runner selects the sorted root `test/*.test.js` inventory. Coverage-only runner/report tests and collection probes live under `test/coverage/` and are excluded from daily execution. The release coverage runner includes both inventories and sets `DSH_PTC_TEST_COVERAGE_SUITE=1` for mixed instrumented cases; the ordinary runner clears that marker. Both runners share the concurrency calculation. Backend test files run in separate Node processes. Compiler boundary protocols are divided into disjoint storage, Promise and reflection files so the bounded file pool can execute them alongside the parameter/namespace/error cases without sharing their native or PTC realms. The default process count is half the available CPUs, rounded down and bounded between one and eight; each process can also own runtime Workers. Tests within each file retain their existing ordering. The ordinary runner enables module mocks without preloading coverage hooks and removes inherited coverage and temporary bytecode settings from its test-process environment.
|
|
47
47
|
|
|
48
|
-
After the shared ledger,
|
|
48
|
+
After the shared ledger, channel, semantic, build and syntax prerequisites pass, `scripts/deterministic-test-groups.mjs` selects its groups by gate: `--check` (the commit gate) runs only the semantic-evidence backend tier, the default `verify` runs the complete Client visual and backend groups concurrently on machines with at least 12 available CPUs, and `--ci` keeps the focused runtime group. Smaller machines retain serial phases. Every selected group must settle, and any nonzero exit, signal or startup failure rejects verification. Each browser pool retains its own CPU bound and process isolation.
|
|
49
49
|
|
|
50
|
-
Both verification scopes produce a review-bound proof. Coverage runs only before a new version is released, as specified by [ADR 0035](adr/0035-run-coverage-only-before-a-new-release.md). CI starts from a clean lockfile install and runs `verify:ci` on every supported Node and operating-system entry:
|
|
50
|
+
Both verification scopes produce a review-bound proof. Coverage runs only before a new version is released, as specified by [ADR 0035](adr/0035-run-coverage-only-before-a-new-release.md). CI starts from a clean lockfile install and runs `verify:ci` on every supported Node and operating-system entry: channel-manifest validation, semantic ownership, generated builds, Client tests, and focused runtime contracts covering compiler startup, helper-process lifecycle, plugin takeover, native-call transport, and durable argument diagnostics. A separate job per host channel packs the plugin and runs the host contracts against that channel's installed release. Normal CI therefore proves portable installation and runtime integration without running coverage-specific suites, collecting coverage, or running the exhaustive semantic suite on hosted runners.
|
|
51
51
|
|
|
52
|
-
`host:
|
|
52
|
+
`host:channels:verify`, which is part of deterministic verification, is offline: it validates the tracked channel manifest — schema, per-channel dist-tag and blocking role, full coverage of the probed host packages, one release per pinned cohort, and a recorded integrity for each — without resolving a tag or installing a host. CI runs `test:host-contract` as one job per channel. Each job installs its channel on demand from the manifest into an ignored cache, proves the installed versions against the manifest, packs the plugin, and runs the host contracts through a repository-external consumer; the `alpha` job reports without failing the workflow. Registry resolution belongs only to `host:channels:update` and the scheduled channel-refresh workflow. That workflow publishes a changed manifest as a pull request and explicitly dispatches this CI workflow once because its `GITHUB_TOKEN`-authored events cannot start another run; ordinary gates never install a floating channel.
|
|
53
53
|
|
|
54
54
|
## Release coverage
|
|
55
55
|
|
package/index.js
CHANGED
|
@@ -13,8 +13,9 @@ import { CapabilityStatisticsStore } from './internal/capability-statistics-stor
|
|
|
13
13
|
import { createCapabilityEnvironment } from './internal/capability-environment.js'
|
|
14
14
|
import { capabilityCommandReceipt } from './internal/capability-command-receipt.js'
|
|
15
15
|
import { toolSdkSchemas } from './internal/program-bindings.js'
|
|
16
|
+
import { hostTransportDefinition, hostTransportUnreadable } from './internal/host-tool-sdk.js'
|
|
16
17
|
import { createRuntimeMessageOwner, handBackRuntimeSessions } from './internal/runtime-contexts.js'
|
|
17
|
-
import { createSessionLogOwner } from './internal/session-log-view.js'
|
|
18
|
+
import { createSessionLogOwner, projectionRegistrationDisposer } from './internal/session-log-view.js'
|
|
18
19
|
import { PTC_DELIVERY_CONTEXT } from './internal/runtime-messages.js'
|
|
19
20
|
import { createCordisToolsOwner } from './internal/cordis-tools-owner.js'
|
|
20
21
|
import { createEditTransportOwner, EDIT_RUN_CODE } from './internal/edit-transport-owner.js'
|
|
@@ -32,6 +33,7 @@ import { valueLimitsFromConfig } from './internal/value-wire-schema.js'
|
|
|
32
33
|
import { createReplMemoryProjection } from './internal/repl-memory-projection.js'
|
|
33
34
|
import { createUserBindingDraftProjection } from './internal/user-binding-draft-projection.js'
|
|
34
35
|
import { createUserBindingsOwner } from './internal/user-bindings-owner.js'
|
|
36
|
+
import { latestBindingAdoption } from './internal/binding-adoption.js'
|
|
35
37
|
import { createHostRpc } from './internal/host-rpc.js'
|
|
36
38
|
import { RPC_CONTRACTS } from './internal/rpc-contract.js'
|
|
37
39
|
import { createReplObservationInterest } from './internal/repl-observation-interest.js'
|
|
@@ -112,8 +114,17 @@ function replGuidance({ bindingPolicy, rewritesEnabled, languageSemantics, durab
|
|
|
112
114
|
return paragraphs.filter(paragraph => paragraph.trim().length > 0).join('\n\n')
|
|
113
115
|
}
|
|
114
116
|
|
|
117
|
+
/**
|
|
118
|
+
* The reserved `run_code` transport definition, from whichever surface this
|
|
119
|
+
* host publishes it on. Session execution is available exactly when one of
|
|
120
|
+
* those surfaces answers.
|
|
121
|
+
*/
|
|
122
|
+
function runCodeDefinitionFor(tools, scope) {
|
|
123
|
+
return hostTransportDefinition(tools, scope, RUN_CODE)
|
|
124
|
+
}
|
|
125
|
+
|
|
115
126
|
/** Register the session-bound REPL runtime. */
|
|
116
|
-
function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolSdkSchemasForAgent, sessionId, sessionLog, settingsForCommand) {
|
|
127
|
+
function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolSdkSchemasForAgent, sessionId, sessionLog, settingsForCommand, readPromptText) {
|
|
117
128
|
const presentationGeneration = randomUUID()
|
|
118
129
|
const replMemoryProjection = createReplMemoryProjection(presentationGeneration)
|
|
119
130
|
const userBindingDraftProjection = createUserBindingDraftProjection(presentationGeneration)
|
|
@@ -247,11 +258,17 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolS
|
|
|
247
258
|
const cancelCordisActivation = () => pendingCordisActivation?.cancel()
|
|
248
259
|
const registerProjection = (projection, label) => {
|
|
249
260
|
try {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
261
|
+
// A newer host returns a ProjectionHandle (`stateOf`/`readerFor`/`dispose`,
|
|
262
|
+
// possibly async); earlier hosts returned a disposer. Accept either shape so
|
|
263
|
+
// the projection is not silently dropped on the newer contract.
|
|
264
|
+
const dispose = projectionRegistrationDisposer(projectionService?.register?.(projection))
|
|
265
|
+
if (dispose === undefined) {
|
|
266
|
+
throw new Error('ptc-plus: sessionProjections.register returned neither a disposer nor a handle')
|
|
253
267
|
}
|
|
254
|
-
|
|
268
|
+
// Unregistration failures belong to the caller that is suspending or
|
|
269
|
+
// reloading the projection: it retries and reports them. Swallowing one
|
|
270
|
+
// here would turn a failed disposal into a silent duplicate projection.
|
|
271
|
+
return () => dispose()
|
|
255
272
|
} catch (error) {
|
|
256
273
|
ctx.logger?.warn?.(`ptc-plus: ${label} projection unavailable`, error)
|
|
257
274
|
return undefined
|
|
@@ -348,6 +365,9 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolS
|
|
|
348
365
|
readSessionLog: sessionLog.read,
|
|
349
366
|
enabled: activeConfig.userBindingsEnabled,
|
|
350
367
|
draftProjectionAvailable,
|
|
368
|
+
// A session's adoption is its own recorded decision, so it is read from
|
|
369
|
+
// that session's log instead of a process-local map.
|
|
370
|
+
bindingAdoptionForAgent: agent => latestBindingAdoption(sessionLog.project(agent)?.ptcMessages ?? []),
|
|
351
371
|
maxWallMs: activeConfig.maxWallMs,
|
|
352
372
|
maxOutputBytes: activeConfig.maxOutputBytes,
|
|
353
373
|
maxOldGenerationSizeMb: activeConfig.maxOldGenerationSizeMb,
|
|
@@ -360,6 +380,7 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolS
|
|
|
360
380
|
runtimeBridge = createRuntimeBridgeOwner({
|
|
361
381
|
ctx,
|
|
362
382
|
seam,
|
|
383
|
+
readPromptText,
|
|
363
384
|
observeSession: id => observationInterest.has(id),
|
|
364
385
|
readSessionLog: sessionLog.read,
|
|
365
386
|
sessionConfig: activeConfig,
|
|
@@ -392,7 +413,8 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolS
|
|
|
392
413
|
sessionId,
|
|
393
414
|
toolSchemasForAgent,
|
|
394
415
|
capabilityEnvironment,
|
|
395
|
-
|
|
416
|
+
declaresRunCode: agent => runCodeDefinitionFor(ctx.tools, agent) !== undefined,
|
|
417
|
+
userBindingsForAgent: agent => userBindings.snapshotForAgent(agent),
|
|
396
418
|
setAgentPresentation: (agent, presentation) => (
|
|
397
419
|
userBindings.setAgentPresentation(agent, presentation)
|
|
398
420
|
),
|
|
@@ -401,25 +423,33 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolS
|
|
|
401
423
|
name: 'tools:ptc-plus-repl',
|
|
402
424
|
order: 98,
|
|
403
425
|
text: context => {
|
|
404
|
-
if (ctx.tools
|
|
426
|
+
if (runCodeDefinitionFor(ctx.tools, context?.scope) === undefined) return ''
|
|
405
427
|
return replGuidance(guidancePolicies(activeConfig), resolvePromptTextConfig(activeConfig))
|
|
406
428
|
},
|
|
407
429
|
}))
|
|
430
|
+
let unreadableTransportReported = false
|
|
408
431
|
disposers.push(ctx.on('system-prompt/assemble', async (assembly, context, next) => {
|
|
409
432
|
const projected = await directSurface.assemble(assembly, context, next)
|
|
410
|
-
if (ctx.tools
|
|
433
|
+
if (runCodeDefinitionFor(ctx.tools, context?.agent) !== undefined) runtimeBridge.prepareAgent(context?.agent)
|
|
434
|
+
else if (!unreadableTransportReported && hostTransportUnreadable(ctx.tools, context?.agent)) {
|
|
435
|
+
// The host declares the reserved transport and this plugin cannot read it.
|
|
436
|
+
// Every gate that needs execution answers "unavailable", so the operator is
|
|
437
|
+
// told which of the two facts holds instead of finding the features gone.
|
|
438
|
+
unreadableTransportReported = true
|
|
439
|
+
ctx.logger?.warn?.('ptc-plus: the host tool executor declares run_code but publishes no transport definition this plugin can read; session execution stays unavailable')
|
|
440
|
+
}
|
|
411
441
|
return projected
|
|
412
442
|
}))
|
|
413
443
|
disposers.push(() => capabilityEnvironment.dispose())
|
|
414
444
|
disposers.push(ctx.on('agent/pre-step', (payload, next) => capabilityEnvironment.preStep(payload, next), { prepend: true }))
|
|
415
445
|
disposers.push(ctx.on('agent/created', ({ agent }) => {
|
|
416
|
-
if (ctx.tools
|
|
446
|
+
if (runCodeDefinitionFor(ctx.tools, agent) !== undefined) {
|
|
417
447
|
runtimeBridge.prepareAgent(agent)
|
|
418
448
|
editTransport.ensureInstalled(agent)
|
|
419
449
|
}
|
|
420
450
|
}))
|
|
421
451
|
for (const agent of ctx.agents?.list?.() ?? []) {
|
|
422
|
-
if (ctx.tools
|
|
452
|
+
if (runCodeDefinitionFor(ctx.tools, agent) !== undefined) {
|
|
423
453
|
runtimeBridge.prepareAgent(agent)
|
|
424
454
|
editTransport.ensureInstalled(agent)
|
|
425
455
|
}
|
|
@@ -448,17 +478,22 @@ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, toolS
|
|
|
448
478
|
input: { hint: 'refresh | list [all|skills|tools|mcp|mcp_tools] [query] | set session|global <id> [availability|disclosure] <choice>' },
|
|
449
479
|
handler: async ({ commandId, agent, rawInput, signal }) => {
|
|
450
480
|
const input = rawInput.trim()
|
|
451
|
-
const receipt = (state, error) => capabilityCommandReceipt(state, String(agent.session.id), error,
|
|
481
|
+
const receipt = (state, error, proof) => capabilityCommandReceipt(state, String(agent.session.id), error,
|
|
452
482
|
state === 'pending' && capabilityEnvironment.pendingChoiceMessage(agent)
|
|
453
|
-
? { commandId, rawInput, message: capabilityEnvironment.pendingChoiceMessage(agent) } : undefined
|
|
483
|
+
? { commandId, rawInput, message: capabilityEnvironment.pendingChoiceMessage(agent) } : undefined,
|
|
484
|
+
proof)
|
|
454
485
|
const selection = /^set (session|global) (\S+) (?:(availability|disclosure) )?(full|on-demand|closed|automatic|inherit|enabled|host|adaptive)$/.exec(input)
|
|
455
486
|
if (selection) {
|
|
456
487
|
if (!capabilityEnvironment.canRefresh(agent)) return { kind: 'error', text: receipt('unavailable') }
|
|
457
488
|
const changed = await capabilityEnvironment.change(agent, {
|
|
458
489
|
scope: selection[1], id: decodeURIComponent(selection[2]), dimension: selection[3], selection: selection[4], commandId,
|
|
459
490
|
}, signal, settingsForCommand?.())
|
|
491
|
+
// A save states what it changed and what it did not prove: the
|
|
492
|
+
// model-visible catalog and a cell that reached the capability are
|
|
493
|
+
// separate facts, reported by the layer that owns each one.
|
|
494
|
+
const proof = capabilityEnvironment.describeCapabilityProof(agent, decodeURIComponent(selection[2]))
|
|
460
495
|
return { kind: changed.sessionApplied ? 'success' : 'error',
|
|
461
|
-
text: receipt(changed.sessionApplied ? changed.publication === 'pending' ? 'pending' : 'recorded' : 'saved-only', changed.error) }
|
|
496
|
+
text: receipt(changed.sessionApplied ? changed.publication === 'pending' ? 'pending' : 'recorded' : 'saved-only', changed.error, proof) }
|
|
462
497
|
}
|
|
463
498
|
const listing = /^list(?: (all|skills|tools|mcp|mcp_tools))?(?: (.*))?$/.exec(input)
|
|
464
499
|
if (listing) {
|
|
@@ -730,7 +765,9 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
|
|
|
730
765
|
// Execution ownership is a per-attachment property: `owned` installs the
|
|
731
766
|
// session runtime, `deferred` leaves run_code to the official provider.
|
|
732
767
|
const executionShape = nextConfig => (
|
|
733
|
-
defersToOfficialRepl(nextConfig,
|
|
768
|
+
defersToOfficialRepl(nextConfig, {
|
|
769
|
+
get retainsState() { return seam.originalRetainsState },
|
|
770
|
+
}) ? 'deferred' : 'owned'
|
|
734
771
|
)
|
|
735
772
|
const startActivation = record => {
|
|
736
773
|
if (record.cancelled || disposed || activating !== record) {
|
|
@@ -744,7 +781,7 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
|
|
|
744
781
|
try {
|
|
745
782
|
candidate = record.shape === 'deferred'
|
|
746
783
|
? createDeferredRuntime()
|
|
747
|
-
: installPtCRuntime(ctx, record.config, seam, toolSchemasForAgent, toolSdkSchemasForAgent, sessionId, sessionLog, () => settingsWriter)
|
|
784
|
+
: installPtCRuntime(ctx, record.config, seam, toolSchemasForAgent, toolSdkSchemasForAgent, sessionId, sessionLog, () => settingsWriter, currentPromptText)
|
|
748
785
|
record.runtime = candidate
|
|
749
786
|
} catch (error) {
|
|
750
787
|
const failedRuntime = error?.[INSTALL_CLEANUP]
|
|
@@ -899,8 +936,10 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
|
|
|
899
936
|
const handbackOwners = Object.fromEntries(['sessions', 'sessionPersistence', 'sessionQuery'].map(name => [name, ctx.get(name)]))
|
|
900
937
|
// One spelling of "the current document": the same resolved configuration the
|
|
901
938
|
// reconciliation loop applies, so a delivered message and the assembled prompt can
|
|
902
|
-
// never read two different prompt-text documents.
|
|
903
|
-
//
|
|
939
|
+
// never read two different prompt-text documents. One holder per attachment, handed
|
|
940
|
+
// to the runtime it installs, so two attachments in one process keep their own
|
|
941
|
+
// document. Read lazily: the settings section replaces `configSource` after
|
|
942
|
+
// activation.
|
|
904
943
|
const currentPromptText = () => resolvePromptTextConfig(configSource())
|
|
905
944
|
const messageOwner = createRuntimeMessageOwner(context => (
|
|
906
945
|
committed?.retiring ? [] : committed?.runtime.contextsForRequest(context) ?? []
|
|
@@ -918,8 +957,17 @@ function applyWithExecutionSeam(ctx, resolvedConfig, seam, volatileHost, volatil
|
|
|
918
957
|
ctx.effect(() => ctx.on('agent/disposed', ({ agent }) => messageOwner.disposeAgent(agent)), 'ptc-plus message agent disposal')
|
|
919
958
|
ctx.effect(() => () => messageOwner.dispose(), 'ptc-plus dynamic message lifecycle')
|
|
920
959
|
ctx.effect(() => async () => {
|
|
921
|
-
|
|
960
|
+
// The model-visible handback states a fact about execution, so it must not
|
|
961
|
+
// ride on whether every projection unregistered: a failed disposal is
|
|
962
|
+
// reported by the caller that retries it, and the runtime is gone either way.
|
|
963
|
+
let disposalFailure
|
|
964
|
+
try {
|
|
965
|
+
await controller.dispose()
|
|
966
|
+
} catch (error) {
|
|
967
|
+
disposalFailure = error
|
|
968
|
+
}
|
|
922
969
|
await handBackRuntimeSessions(ctx, handbackOwners)
|
|
970
|
+
if (disposalFailure !== undefined) throw disposalFailure
|
|
923
971
|
}, 'ptc-plus provider handback')
|
|
924
972
|
|
|
925
973
|
// The settings section replaces this source after activation; the prompt-text
|