dsh-ptc-plus 0.2.1 → 0.2.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.md +11 -4
- package/README.zh.md +11 -4
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +25 -3
- package/docs/adr/0010-session-log-derived-recovery-tips.md +4 -1
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +5 -2
- package/docs/architecture.md +7 -5
- package/docs/client-ui.md +1 -1
- package/docs/durability-design.md +1 -1
- package/docs/installation.md +3 -3
- package/docs/program-data-plane.md +1 -1
- package/docs/runtime-reference.md +7 -3
- package/internal/cordis-tools-owner.js +66 -6
- package/internal/direct-surface-owner.js +40 -1
- package/internal/edit-transport-owner.js +12 -1
- package/internal/failure-reporting.js +1 -0
- package/internal/recovery-tips.js +0 -1
- package/internal/rejected-cell-editor.js +21 -7
- package/internal/runtime-contexts.js +7 -1
- package/internal/session-cell-executor.js +52 -26
- package/internal/session-log-view.js +17 -0
- package/internal/session-runtime.js +12 -4
- package/internal/validated-parse-repair.js +82 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# dsh-ptc-plus
|
|
2
2
|
|
|
3
|
+
English | [简体中文](README.zh.md)
|
|
4
|
+
|
|
3
5
|
[](package.json)
|
|
6
|
+
[](https://www.npmjs.com/package/dsh-ptc-plus)
|
|
4
7
|
[](LICENSE)
|
|
5
8
|
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
6
9
|
[](docs/publishing.md)
|
|
@@ -70,6 +73,8 @@ edit_run_code({ edits: [{ old_string: 'deps.length', new_string: 'deps' }] })
|
|
|
70
73
|
|
|
71
74
|
Only the diff goes in. The full source stays out of the conversation. Exact replacements and regular expressions both have limits, so a bad pattern cannot hang.
|
|
72
75
|
|
|
76
|
+
When a rejected cell has exactly one validated missing closing token at the end, its diagnostic includes the complete `edit_run_code(...)` call needed to apply that correction and rerun the cell. That generated call carries an `expected_target_call_seq` precondition, so it is rejected without execution if another cell becomes the edit target first. PTC Plus never applies the suggestion automatically; ambiguous repairs or repairs without a persistent target identity still require corrected source.
|
|
77
|
+
|
|
73
78
|
### Module syntax
|
|
74
79
|
|
|
75
80
|
DSH PTC mode executes each `run_code` as an async function body, where static `import` and `export` declarations are invalid. PTC Plus adapts those forms before execution with AST analysis.
|
|
@@ -107,10 +112,12 @@ Open **Settings → Plugin configuration** to use the card shown above. The `ena
|
|
|
107
112
|
|
|
108
113
|
Every setting applies live and keeps existing bindings. A submitted cell uses one configuration for its complete execution; changes made while it runs apply to cells submitted afterward. A failed change rolls back. Node fixes a worker's V8 old-generation limit when the worker starts, so that one setting is rejected while a session worker is active and can be changed after the session is disposed. A failed enable is rolled back and persisted as disabled.
|
|
109
114
|
|
|
110
|
-
`cordisToolsEnabled` is off by default. Turning it on atomically adds DSH's official Cordis tools, owner guidance, and `cordis-plugin-development` companion Skill to PTC agents;
|
|
115
|
+
`cordisToolsEnabled` is off by default. Turning it on atomically adds DSH's official Cordis tools, owner guidance, and exactly the `cordis-plugin-development` companion Skill to PTC agents; sibling Skills in the shipped preset are not exposed. Turning it off removes all three. It neither switches presets nor changes the direct `run_code`/`edit_run_code` surface. Cordis runs model-written plugins against the live DSH runtime, so enabling it requires shell-level trust.
|
|
111
116
|
|
|
112
117
|
When a Cordis call rejects after a cell has assigned a large host or client source string, that top-level binding remains live. Retry only the Cordis call from a short later cell and reuse the binding instead of resending the source.
|
|
113
118
|
|
|
119
|
+
After cold recovery or re-enabling Cordis, recorded Cordis values remain historical data but do not prove that process-local Plugins, Runs, approvals, or earlier Inspect observations are still live. PTC Plus adds a bounded recovery context until a new successful Cordis Inspect call validates the current process.
|
|
120
|
+
|
|
114
121
|
See [Client UI](docs/client-ui.md), [ADR 0019](docs/adr/0019-plugin-settings-and-kill-switch.md), and [ADR 0020](docs/adr/0020-optional-cordis-tools-in-ptc-mode.md).
|
|
115
122
|
|
|
116
123
|
## Scope
|
|
@@ -119,14 +126,14 @@ PTC Plus provides the session-bound persistent `run_code` layer. DSH and the ope
|
|
|
119
126
|
|
|
120
127
|
## Install
|
|
121
128
|
|
|
122
|
-
Requires Node.js `^22.19.0 || >=24.0.0` and DSH with TypeScript PTC mode:
|
|
129
|
+
Requires Node.js `^22.19.0 || >=24.0.0` and DSH with TypeScript PTC mode. Install the published package from npm into the profile you use:
|
|
123
130
|
|
|
124
131
|
```sh
|
|
125
|
-
dsh plugin --profile <profile> add
|
|
132
|
+
dsh plugin --profile <profile> add dsh-ptc-plus
|
|
126
133
|
dsh --profile <profile> --dump-config
|
|
127
134
|
```
|
|
128
135
|
|
|
129
|
-
|
|
136
|
+
Restart that DSH profile after installation. Version-pinned npm, GitHub, local-checkout, and tarball installs are covered in the [installation guide](docs/installation.md).
|
|
130
137
|
|
|
131
138
|
`danger-full-access` is the primary supported experience. The worker isolates lifecycle, not malicious code.
|
|
132
139
|
|
package/README.zh.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# dsh-ptc-plus
|
|
2
2
|
|
|
3
|
+
[English](README.md) | 简体中文
|
|
4
|
+
|
|
3
5
|
[](package.json)
|
|
6
|
+
[](https://www.npmjs.com/package/dsh-ptc-plus)
|
|
4
7
|
[](LICENSE)
|
|
5
8
|
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
6
9
|
[](docs/publishing.md)
|
|
@@ -70,6 +73,8 @@ edit_run_code({ edits: [{ old_string: 'deps.length', new_string: 'deps' }] })
|
|
|
70
73
|
|
|
71
74
|
模型只发改动,完整源码留在对话之外。精确文本替换和正则替换都有限制,坏的正则不会卡住。
|
|
72
75
|
|
|
76
|
+
当被拒 cell 的末尾可唯一验证为缺少一个闭合符时,诊断会直接给出应用该修正并重运行所需的完整 `edit_run_code(...)` 调用。生成的调用带有 `expected_target_call_seq` 前置条件;如果另一个 cell 已先成为 edit 目标,它会拒绝且不执行。PTC Plus 不会自动应用这项建议;有歧义或没有持久目标身份的修复仍需提交修正后的源码。
|
|
77
|
+
|
|
73
78
|
### 模块语法
|
|
74
79
|
|
|
75
80
|
DSH 的 PTC 模式把每个 `run_code` 当作 async function body 执行,静态 `import` 和 `export` 声明在这个函数体里无效。PTC Plus 会在执行前用 AST 分析适配这些形式。
|
|
@@ -107,10 +112,12 @@ import { readFile } from 'node:fs/promises'
|
|
|
107
112
|
|
|
108
113
|
所有设置都会实时应用,并保留已有 binding。已提交的 cell 在完整执行期间使用同一份配置;执行期间发生的更新用于随后提交的 cell。更新失败会回滚。Node 在 worker 创建时固定 V8 old-generation 上限,因此活动 session worker 存在时修改这一项会被拒绝,释放 session 后才能修改。启用失败时,运行时会回滚并把设置保持为停用。
|
|
109
114
|
|
|
110
|
-
`cordisToolsEnabled` 默认关闭。打开后,DSH 官方 Cordis 工具、owner guidance
|
|
115
|
+
`cordisToolsEnabled` 默认关闭。打开后,DSH 官方 Cordis 工具、owner guidance 与精确的 `cordis-plugin-development` companion Skill 会作为一个整体加入 PTC agent;同一 shipped preset 目录中的其他 Skill 不会随之暴露。关闭时这三项也会一起移除;它不切换 preset,也不改变 `run_code`/`edit_run_code` 的直接调用面。Cordis 能在实时 DSH runtime 中运行模型编写的插件,开启它需要接受 shell 级信任。
|
|
111
116
|
|
|
112
117
|
如果 Cordis 调用在 cell 已经赋值大段 host 或 client 源码之后才失败,这些顶层 binding 仍会保留。后续只需在短 cell 中重试 Cordis 调用并复用该 binding,无需再次传输源码。
|
|
113
118
|
|
|
119
|
+
cold recovery 或重新启用 Cordis 后,已记录的 Cordis value 仍是历史数据,但不能证明进程内 Plugin、Run、approval 或先前 Inspect observation 仍然存活。PTC Plus 会提供有界恢复 context,直到一次新的成功 Cordis Inspect 调用验证当前进程。
|
|
120
|
+
|
|
114
121
|
详见 [客户端 UI](docs/client-ui.md)、[ADR 0019](docs/adr/0019-plugin-settings-and-kill-switch.md) 与 [ADR 0020](docs/adr/0020-optional-cordis-tools-in-ptc-mode.md)。
|
|
115
122
|
|
|
116
123
|
## 范围
|
|
@@ -119,14 +126,14 @@ PTC Plus 提供会话绑定的持久 `run_code` 层。原生工具的权限、
|
|
|
119
126
|
|
|
120
127
|
## 安装
|
|
121
128
|
|
|
122
|
-
要求 Node.js `^22.19.0 || >=24.0.0`,并已安装带 TypeScript PTC 模式的 DSH:
|
|
129
|
+
要求 Node.js `^22.19.0 || >=24.0.0`,并已安装带 TypeScript PTC 模式的 DSH。把 npm 正式包安装到你实际使用的 profile:
|
|
123
130
|
|
|
124
131
|
```sh
|
|
125
|
-
dsh plugin --profile <profile> add
|
|
132
|
+
dsh plugin --profile <profile> add dsh-ptc-plus
|
|
126
133
|
dsh --profile <profile> --dump-config
|
|
127
134
|
```
|
|
128
135
|
|
|
129
|
-
|
|
136
|
+
安装后重启对应的 DSH profile。固定 npm 版本、GitHub、本地 checkout 和 tarball 安装方式见[安装指南](docs/installation.md)。
|
|
130
137
|
|
|
131
138
|
`danger-full-access` 是首要支持方式。worker 只隔离生命周期,不隔离恶意代码。
|
|
132
139
|
|
|
@@ -6,7 +6,8 @@ not achieve the intended result. PTC Plus therefore registers `edit_run_code` as
|
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
8
|
edit_run_code({
|
|
9
|
-
edits: Array<{ old_string: string; new_string: string }
|
|
9
|
+
edits: Array<{ old_string: string; new_string: string }>,
|
|
10
|
+
expected_target_call_seq?: number
|
|
10
11
|
})
|
|
11
12
|
|
|
12
13
|
edit_run_code({
|
|
@@ -15,7 +16,8 @@ edit_run_code({
|
|
|
15
16
|
flags: string
|
|
16
17
|
replacement: string
|
|
17
18
|
expected_matches: number
|
|
18
|
-
}
|
|
19
|
+
}>,
|
|
20
|
+
expected_target_call_seq?: number
|
|
19
21
|
})
|
|
20
22
|
```
|
|
21
23
|
|
|
@@ -65,7 +67,11 @@ session-log projection captures the eligible target at the edit call event and a
|
|
|
65
67
|
source only when its target sequence matches that snapshot and its journal is valid and non-noop,
|
|
66
68
|
so live and recovered eligibility derive from the same persisted relation.
|
|
67
69
|
|
|
68
|
-
The caller sends exactly one of `edits` or `regex_edits
|
|
70
|
+
The caller sends exactly one of `edits` or `regex_edits`, plus an optional non-negative
|
|
71
|
+
`expected_target_call_seq` precondition. The precondition must equal the call sequence of the target
|
|
72
|
+
captured for the persisted edit event; a mismatch returns an unedited result before derived
|
|
73
|
+
dispatch. Ordinary model-authored edits omit it and retain latest-target selection. Exact edits
|
|
74
|
+
contain at most 16 items; each
|
|
69
75
|
non-empty `old_string` must differ from `new_string` and occur exactly once in the target. Regular
|
|
70
76
|
expression edits also contain at most 16 items, use unique `gimsu` flags, reject zero-length matches,
|
|
71
77
|
and require an exact `expected_matches` count. All ranges resolve against the original target and
|
|
@@ -98,6 +104,22 @@ operation and outcome; changing an aggregate runtime-context snapshot merely to
|
|
|
98
104
|
would repeat unrelated policy context. Rewrite feedback and unrelated recovery tips retain their
|
|
99
105
|
own independently justified lifecycles.
|
|
100
106
|
|
|
107
|
+
A live `PTC-C001` at the exact source EOF may carry a validated one-call repair. The analyzer tries
|
|
108
|
+
only the three single-token suffixes `}`, `)`, and `]` against the same binding catalog, reserved
|
|
109
|
+
bindings, redeclaration mode, rewrite policy, and import state as the rejected cell. Exactly one
|
|
110
|
+
candidate must complete preparation without a binding collision, and the existing exact editor
|
|
111
|
+
must be able to express it from a bounded unique source suffix. The rejected call must also have a
|
|
112
|
+
persistent event sequence. The diagnostic then renders those already-validated
|
|
113
|
+
`edit_run_code({ edits: [...], expected_target_call_seq })` arguments on one help line. At dispatch,
|
|
114
|
+
the precondition binds the proof to that rejected cell even if another matching source has become
|
|
115
|
+
editable. This evidence takes precedence over the generic source-length recommendation, but remains
|
|
116
|
+
conditional on
|
|
117
|
+
`edit_run_code` being declared for that request. Zero or multiple candidates, non-EOF errors,
|
|
118
|
+
editor budget failures, missing target identity, and candidates rejected later in preparation
|
|
119
|
+
retain the generic guidance.
|
|
120
|
+
The plugin never applies this repair itself: syntax/preflight acceptance does not prove model
|
|
121
|
+
intent, and explicit edit dispatch preserves DSH authority and truthful history.
|
|
122
|
+
|
|
101
123
|
## Alternatives considered
|
|
102
124
|
|
|
103
125
|
**Rewrite `edit_run_code` to `run_code` before persistence.** This reuses DSH's default `run_code`-only
|
|
@@ -6,12 +6,14 @@ The persistent REPL contract must stay short enough for every request, while som
|
|
|
6
6
|
|
|
7
7
|
## Decision
|
|
8
8
|
|
|
9
|
-
PTC Plus keeps the stable REPL guidance limited to invariants, discovery entry points, and environment-neutral boundaries. `internal/session-log-view.js` scans the event sequence once, pairs settled calls within each open turn, validates execution journals and rewrite records once, and returns immutable facts for edit targeting, successful-run reset, context distance, and canonical DSH system-prompt snapshots. Each snapshot preserves its named sections. `internal/runtime-contexts.js` renders rewrite feedback only when the rewritten cell failed or lacks a valid journal, plus at most one context from the reserved `tools:ptc-plus-tip/<trigger>/<ordinal>` name family. Successful transparent rewrites remain result metadata and do not create aggregate snapshot churn. It never emits edit provenance or target-lifecycle context: those facts belong to the registered tool call/result and its private derived-execution metadata.
|
|
9
|
+
PTC Plus keeps the stable REPL guidance limited to invariants, discovery entry points, and environment-neutral boundaries. `internal/session-log-view.js` scans the event sequence once, pairs settled calls within each open turn, validates execution journals and rewrite records once, and returns immutable facts for edit targeting, successful-run reset, context distance, normalized Cordis call/inspection counts, and canonical DSH system-prompt snapshots. Each snapshot preserves its named sections. `internal/runtime-contexts.js` renders rewrite feedback only when the rewritten cell failed or lacks a valid journal, the fixed `tools:ptc-plus-cordis-recovery` context when a new agent or Cordis enable generation contains historical Cordis transcript without a newer successful live Inspect, plus at most one context from the reserved `tools:ptc-plus-tip/<trigger>/<ordinal>` name family. Successful transparent rewrites remain result metadata and do not create aggregate snapshot churn. It never emits edit provenance or target-lifecycle context: those facts belong to the registered tool call/result and its private derived-execution metadata. These contexts never change the tool list, tool schemas, or system sections.
|
|
10
10
|
|
|
11
11
|
The default triggers are a repeated binding failure and a failure whose diagnostic or structured cause identifies an executable, shell, or path problem. The first tip is concise. A matching trigger may produce another tip only after `tipCooldownMessages` model-context steps; unresolved matching tips reach the detailed form after `tipEscalationFailures` occurrences. A successful cell resets the unresolved escalation count. Every emitted tip name carries its stable trigger identity and next per-trigger ordinal. Reconstruction accepts only canonical snapshots from DSH's system-prompt owner, treats a named section as one effective-state transition even when another owner's aggregate update repeats it, and ignores legacy or malformed plugin prose. Visible wording does not determine cooldown or escalation identity.
|
|
12
12
|
|
|
13
13
|
Platform wording names execution-world differences without assuming Windows, WSL, POSIX, a shell, or package-runner availability. `edit_run_code` remains independently available as a fixed real tool and does not depend on a recovery-tip trigger.
|
|
14
14
|
|
|
15
|
+
The Cordis recovery context is not a failure tip and is not controlled by fatigue settings. It narrows the meaning of recorded values after a lifecycle boundary: they remain historical program data but cannot prove current process-local state. It clears only after a newly settled successful `cordis_inspect*` transcript, does not infer whether the original process survived, and never redispatches a historical effect.
|
|
16
|
+
|
|
15
17
|
## Alternatives considered
|
|
16
18
|
|
|
17
19
|
- **Keep all recovery instructions in the stable system prompt.** This makes every request pay for rare failures and makes platform-specific text look universally applicable.
|
|
@@ -20,6 +22,7 @@ Platform wording names execution-world differences without assuming Windows, WSL
|
|
|
20
22
|
- **Infer tip identity from rendered prose or one fixed section name.** Wording changes would reset history, unrelated plugin text could create false matches, and an unchanged aggregate context would not persist repeated occurrences.
|
|
21
23
|
- **Change the available tools when a tip is needed.** This would break the fixed code-only direct-tool prefix and make failure state observable through schema churn.
|
|
22
24
|
- **Let each runtime-context renderer scan the event log.** Independent scans duplicate parsing and can disagree about call/result pairing, turn boundaries, repair consumption, or successful-run resets. A shared projection keeps event interpretation separate from presentation policy.
|
|
25
|
+
- **Mark all Cordis calls volatile.** This would discard reconstructable REPL bindings and special values merely because their external referents need revalidation. Recorded-value replay and live-state proof are separate contracts.
|
|
23
26
|
|
|
24
27
|
## Consequences
|
|
25
28
|
|
|
@@ -14,7 +14,7 @@ Tool registration must finish before the prompt assembly returned to the agent;
|
|
|
14
14
|
|
|
15
15
|
## Decision
|
|
16
16
|
|
|
17
|
-
Add `cordisToolsEnabled: boolean`, default `false`, as a live PTC Plus setting. When enabled, PTC Plus mounts the official `@deepseek-ai/dsh-tool-cordis` plugin and a maintained `@deepseek-ai/dsh-skill-filesystem` provider in each agent scope that exposes `run_code`; disabling it disposes both fibers immediately. The provider resolves the shipped `cordis` preset through the public `agentPresets` service and
|
|
17
|
+
Add `cordisToolsEnabled: boolean`, default `false`, as a live PTC Plus setting. When enabled, PTC Plus mounts the official `@deepseek-ai/dsh-tool-cordis` plugin and a maintained `@deepseek-ai/dsh-skill-filesystem` provider in each agent scope that exposes `run_code`; disabling it disposes both fibers immediately. The provider resolves the shipped `cordis` preset through the public `agentPresets` service and reads its sibling `skills` root without including default roots. At the public `skills.registerProvider` seam, PTC Plus transparently filters discovery and loading to exactly `cordis-plugin-development`; the maintained filesystem provider still owns parsing, resource bases, watch, invalidation, incomplete observations, and body loading. This prevents unrelated or obsolete sibling Skills from silently enlarging the PTC surface without copying the companion content or switching the current preset. If an agent is created before `run_code` becomes visible, the owner retains it as pending and retries on the DSH tools-change signal.
|
|
18
18
|
|
|
19
19
|
The Skill provider and official Cordis child fiber form one reversible mount. Both must settle, and the scoped `skills` service must prove that `cordis-plugin-development` resolves from the added provider as model-invocable and loadable, before the first prompt assembly is returned. A prepended public `system-prompt/assemble` waterfall listener is the final race barrier: if collection began before the mount settled, it waits for readiness, discards that stale assembly, and invokes public assembly again so DSH recollects the official providers. It does not synthesize or mutate Cordis schemas or guidance. The Cordis fiber's resolved `inject` declaration is the authoritative required-service list, and its live registrations are the authoritative tool names, schemas, and guidance. A missing or broken preset, Skill service/tool, declared service, publication, or child fiber is a configuration error and rolls back every contribution.
|
|
20
20
|
|
|
@@ -22,6 +22,8 @@ The official plugin also registers Host inspect providers in the process-global
|
|
|
22
22
|
|
|
23
23
|
Cordis tools remain native program bindings under `tools.*`. The PTC code-only direct-tool projection remains exactly `[run_code, edit_run_code]`; native agents and unrelated scopes do not inherit this option.
|
|
24
24
|
|
|
25
|
+
Cordis definitions and Runs are process-local, while PTC journals replay every settled program binding as a recorded value without redispatch. On the first prompt for an agent or a new Cordis enable generation, PTC Plus derives whether normalized journals contain a Cordis transcript. If they do, it emits the fixed `tools:ptc-plus-cordis-recovery` runtime context: recorded values remain historical data, but the model must perform a live read-only Cordis Inspect before relying on prior IDs, state, approvals, or capability observations. The context remains until a newly settled successful `cordis_inspect*` call appears in the session log. It neither asserts that live state was lost nor reruns a mutating call.
|
|
26
|
+
|
|
25
27
|
## Alternatives Considered
|
|
26
28
|
|
|
27
29
|
1. Add the Cordis row to the PTC preset. Rejected: PTC Plus is installed as a host plugin and does not own or fork DSH's shipped preset files; the operator asked for a plugin setting.
|
|
@@ -30,7 +32,8 @@ Cordis tools remain native program bindings under `tools.*`. The PTC code-only d
|
|
|
30
32
|
4. Enable the tools by default. Rejected: the tools can execute model-written plugins against the live runtime and carry a fixed schema cost. That trust and context expansion must be explicit.
|
|
31
33
|
5. Mount one global Cordis tool fiber and hide it from native agents. Rejected: its prompt hook and `@pluginId` context listener would remain global even if tool schemas were restricted, so unrelated agents would inherit behavior they did not enable.
|
|
32
34
|
6. Copy `SKILL.md` into PTC Plus or add the Cordis preset's directory to global Skill roots. Rejected: copied instructions drift, while a global root leaks the optional workflow into agents that did not enable it.
|
|
35
|
+
7. Publish the complete preset-local Skill directory. Rejected: directory membership is not the PTC feature contract; unrelated sibling additions would silently change the model surface and may target a different authoring workflow or tool generation.
|
|
33
36
|
|
|
34
37
|
## Consequences
|
|
35
38
|
|
|
36
|
-
PTC Plus declares the maintained DSH Cordis tool, Skill filesystem, settings, and tool-runtime packages as required peers through the current release channel and mirrors them in development dependencies. They resolve from DSH's installation-maintained profile fallback; installing PTC Plus must not add a profile-local DSH core package instance. When enabled, the official Cordis schemas, guidance, and companion Skill become available together in PTC agent scopes; disabled configurations remain unchanged. Changing this option updates live agent scopes without a DSH restart, including repeated disable/re-enable cycles. A DSH release may rename or reshape its Cordis surface without requiring a mirrored-name change in PTC Plus,
|
|
39
|
+
PTC Plus declares the maintained DSH Cordis tool, Skill filesystem, settings, and tool-runtime packages as required peers through the current release channel and mirrors them in development dependencies. They resolve from DSH's installation-maintained profile fallback; installing PTC Plus must not add a profile-local DSH core package instance. When enabled, the official Cordis schemas, guidance, and exact companion Skill become available together in PTC agent scopes; disabled configurations remain unchanged. Changing this option updates live agent scopes without a DSH restart, including repeated disable/re-enable cycles. Resume and re-enable may append one bounded recovery snapshot but do not change stable tool schemas or system sections. A DSH release may rename or reshape its Cordis tool surface without requiring a mirrored-name change in PTC Plus, while the named companion Skill remains an explicit compatibility contract and fails activation if unavailable or inconsistent.
|
package/docs/architecture.md
CHANGED
|
@@ -20,7 +20,7 @@ DSH 为一个 agent composition 固定选择 `native`、`code` 或 `both`。首
|
|
|
20
20
|
|
|
21
21
|
`edit_run_code` 对其持久化 `tool/call` 事件出现时当前 open turn 最近的可编辑 cell 生效,无论该 cell 被拒绝、运行失败,还是成功执行但结果需要微调。执行通过 call event sequence 读取该快照,后续 settlement 或 handler 调度不改变目标;派生 cell 则按外层 `tool/result` event sequence 进入恢复历史,使 cold replay 保持 live kernel 的实际结算顺序。
|
|
22
22
|
调查 tool 不擦除目标;edit 发起后的其他 settlement 不追溯改变其目标;一次成功 edit 生成的新 cell 成为下一次 edit 的目标。调用方必须且只能提交 `edits` 或
|
|
23
|
-
`regex_edits`
|
|
23
|
+
`regex_edits` 之一,并可携带 `expected_target_call_seq` 作为目标前置条件;它与 call event 已捕获目标不一致时,在任何派生 dispatch 前返回未编辑结果。`internal/rejected-cell-editor.js` 统一拥有 schema、匹配、replacement 语义、预算和原子源码组装;
|
|
24
24
|
所有位置都针对原始目标源码解析,全部范围不得重叠,预算通过后才以单次线性扫描物化结果。
|
|
25
25
|
|
|
26
26
|
注册工具通过 DSH 公共 `tools.execute()` 将完整修改后源码派生执行为 `run_code`。外层历史仍是模型真实发出的
|
|
@@ -29,7 +29,7 @@ DSH 为一个 agent composition 固定选择 `native`、`code` 或 `both`。首
|
|
|
29
29
|
`run_code`。无目标或参数不合法时不执行,返回 `{ edited: false, reason }` 并保留原目标。
|
|
30
30
|
进程内 claim 区分 executing 与 settled:抛错、取消或缺少有效 PTC journal 的派生结果立即释放;已由 journal 证明进入 runtime 的派生执行继续占用旧 target,直到外层 result 的私有 metadata 通过最终 policy 并由 session log 投影为新 target。最终 result 丢失 metadata 时由 `tools/result` 释放。session log 投影在 edit call event 处记录 eligible target,只接受 target call sequence 匹配该快照且 journal 有效、非 noop 的派生源码,因此 live 与 cold recovery 从同一持久关系决定可编辑目标。
|
|
31
31
|
|
|
32
|
-
`internal/session-log-view.js` 单次前向扫描 session events,分别投影最新执行、可编辑目标、rewrite metadata
|
|
32
|
+
`internal/session-log-view.js` 单次前向扫描 session events,分别投影最新执行、可编辑目标、rewrite metadata、规范 journal 中的 Cordis transcript 计数和恢复 tip
|
|
33
33
|
所需事实。`edit_run_code` 不产生专用 runtime context:真实 call/result 已完整表达操作身份和结果,额外 contribution
|
|
34
34
|
只会触发 DSH 聚合 runtime-context 全量快照并重复无关 policy 文本。只有失败或完成状态不可信的 rewrite feedback 才保留独立生命周期;成功的透明改写不产生 runtime context。
|
|
35
35
|
|
|
@@ -40,11 +40,13 @@ return 或打印需要展示的值。失败恢复先按状态分类:解析或
|
|
|
40
40
|
`run_code` 并复用已有 binding,完整重写只保留给结构性改动或超出编辑预算的修正。能力和命令执行依赖当前 request
|
|
41
41
|
与 execution world,模型必须先探查 live binding、实际 executable 和路径语义,不假设某个平台、shell 或 package runner。
|
|
42
42
|
|
|
43
|
+
live `PTC-C001` 若精确落在源码 EOF,独立的 bounded analyzer 只把追加单个 `}`、`)`、`]` 的三个源码分别送入该 cell 提交时的同一 preparation context。仅当唯一候选通过、无 binding collision,现有 exact editor 能从不超过固定预算的唯一尾部物化同一源码,并且 runtime 已取得该 rejected `run_code` 的持久 call sequence 时,诊断才输出一行可直接调用的 `edit_run_code({ edits: [...], expected_target_call_seq })`。edit transport 把该 sequence 与其 call event 捕获的目标比较,目标已变化时不编辑也不派生执行。这项 validated repair 优先于长度阈值,但只证明语法/preflight 接受;插件不自动 dispatch,也不改写原始 `run_code`。非 EOF、多候选、多 token、超预算、缺少目标身份或后续 preparation 拒绝均回到原有 length-adaptive help。
|
|
44
|
+
|
|
43
45
|
稳定指引保留这些跨任务不变量和失败恢复的优先动作。长 cell 失败的短提示直接作为当前 `PTC-X001` 的结构化 `help` 输出,避免一次性建议触发完整 runtime-context 快照;重复绑定失败和当前 execution world 中由诊断确认的 executable、shell 或 path 错误仍由当前 session log 派生为 `tools:ptc-plus-tip/<trigger>/<ordinal>` runtime context,分别提示能力探查或重新确认环境。投影只接受 DSH system-prompt owner 的规范快照,并按命名 section 的有效状态变化重建提示;聚合快照重复同一 section 不增加次数,正文不参与身份判断。
|
|
44
46
|
相同提示受 `tipCooldownMessages` 间隔约束,连续未解决时才升级为详细版本,成功 cell 会重置未解决计数;提示不会改变
|
|
45
47
|
system sections、tool schema 或 tool order,也不假设 Windows、WSL、POSIX、shell 或 package runner。
|
|
46
48
|
|
|
47
|
-
当前策略位于独立的 `internal/recovery-tips.js` local provider,核心只消费其有界的 named context
|
|
49
|
+
当前策略位于独立的 `internal/recovery-tips.js` local provider,核心只消费其有界的 named context。另一个固定名称 `tools:ptc-plus-cordis-recovery` 只在新 agent 或 Cordis 重新启用代际观察到历史 Cordis transcript 时出现,将 replay value 限定为历史数据,并保持到 session log 出现新的成功 `cordis_inspect*` settlement;它不受疲劳阈值控制、不重放调用,也不推断 live process 是否实际丢失。未来外部决策插件的接入必须等待稳定的 facts/decision contract;在此之前不猜测跨插件 API,local provider 继续作为 fallback。
|
|
48
50
|
|
|
49
51
|
## 设置与启用开关
|
|
50
52
|
|
|
@@ -66,7 +68,7 @@ Client half 通过 `settings.plugin.item` 卡片呈现全部配置。`enabled`
|
|
|
66
68
|
| Replacement | 只有 DSH 拥有的 compaction 或其他显式 surface replacement 可以替换已保留历史;PTC Plus 不用 replacement 表达瞬时状态。 |
|
|
67
69
|
| Independent request | 新的辅助模型调用必须单独说明 route、prefix 和 token 影响,不能用它的缓存表现证明主会话前缀稳定。当前插件不发起辅助模型请求。 |
|
|
68
70
|
|
|
69
|
-
一次性或会变化的 session 状态必须作为 `PromptAssembly.contexts` 的命名贡献交给 DSH。DSH 将完整快照记录为带来源的 `user/message` 并追加到历史尾部;值未变时不重复,值变化或全部消失时追加更新或 clearance。完整快照携带当时所有 owner 的命名 context,因此任一贡献变化都会使未变化贡献在新的尾部消息中再次出现;这会增加 append-only history token,但不会改写已有前缀。PTC Plus 不通过绕过 prompt assembly
|
|
71
|
+
一次性或会变化的 session 状态必须作为 `PromptAssembly.contexts` 的命名贡献交给 DSH。DSH 将完整快照记录为带来源的 `user/message` 并追加到历史尾部;值未变时不重复,值变化或全部消失时追加更新或 clearance。完整快照携带当时所有 owner 的命名 context,因此任一贡献变化都会使未变化贡献在新的尾部消息中再次出现;这会增加 append-only history token,但不会改写已有前缀。PTC Plus 不通过绕过 prompt assembly 的私有消息通道规避这项宿主聚合成本。Cordis 恢复 context 使用规范 journal 的历史 transcript 与当前 agent/enable generation 作为事实源,不能从进程内 Plugin registry 直接生成未记录输入。此类状态不得进入 `PromptAssembly.sections`,也不得通过增删 tool、改变 schema 字段或调整 tool order 传递。
|
|
70
72
|
|
|
71
73
|
所有模型可见输入都必须能从 session log 重建。静态 system/schema 由 `request/header` 保存,动态 context 由带来源的 `user/message` 保存;进程内临时状态不能直接成为未记录的模型输入。在同一插件版本与配置下,普通执行结果、可编辑目标、诊断和其他插件拥有的运行期变化都不得改变 header;`edit_run_code` 的身份与结果由真实 call/result 表达,不生成 edit feedback context。插件升级、显式配置变化、provider/model route 变化、真实 native capability schema 变化,以及 DSH 拥有的 history replacement 可以使缓存从首个变化 token 起失效。
|
|
72
74
|
|
|
@@ -111,7 +113,7 @@ CodeRuntime request 已携带的 owner-provided program namespace 会被原样
|
|
|
111
113
|
}
|
|
112
114
|
```
|
|
113
115
|
|
|
114
|
-
`calls` 只保存 global、member、PTC Value Graph 编码的 args/result 或 error,以及 settlement 序号。cold replay 校验调用名称、参数、数量和提交顺序,并按 recorded settlement order 释放 recorded result;不会重新 dispatch program binding 或重做外部 effect。该规则同样适用于 native tools、owner-provided namespace 和 `code.run
|
|
116
|
+
`calls` 只保存 global、member、PTC Value Graph 编码的 args/result 或 error,以及 settlement 序号。cold replay 校验调用名称、参数、数量和提交顺序,并按 recorded settlement order 释放 recorded result;不会重新 dispatch program binding 或重做外部 effect。该规则同样适用于 native tools、owner-provided namespace 和 `code.run`,不按名称分支。Cordis 的进程内对象不会因此被宣称已恢复;presentation 可以从已验证 transcript 派生重新检查要求,但不能改变 replay。若基础设施终止时仍有未结算 binding,heap 回滚到 durable frontier,discarded journal 以最先观察到的 `global.member` 保留 possible-effect boundary。effect、completeness 和 source metadata 属于 capability explorer,不伪装成 journal 字段。
|
|
115
117
|
|
|
116
118
|
journal 通过 `run_code.output.presentationMeta` 附着到最终 result,再由 `tools/result` 做两阶段确认。缺失、损坏或被替换的 journal 形成 unknown/volatile 边界;未进入 runtime 的 call 由后续 `confirms` 以对应 `tool/call.seq` 证明为 no-op。volatile 源码保留在原 session log,但不参与 cold replay。
|
|
117
119
|
|
package/docs/client-ui.md
CHANGED
|
@@ -12,7 +12,7 @@ Host half 通过 DSH 公共 `settings` 服务注册命名空间 `ptc-plus`。字
|
|
|
12
12
|
| 分组 | 字段 | 说明 |
|
|
13
13
|
| --- | --- | --- |
|
|
14
14
|
| 开关 | `enabled` | 关闭后不注册 `run_code`/`edit_run_code`、不修改 system prompt、不创建 session runtime;只保留设置 UI。 |
|
|
15
|
-
| Cordis | `cordisToolsEnabled` | 默认关闭;开启后为 PTC agent
|
|
15
|
+
| Cordis | `cordisToolsEnabled` | 默认关闭;开启后为 PTC agent 加入官方 Cordis 工具、指引与精确的 `cordis-plugin-development` companion Skill,不发布同目录 sibling。 |
|
|
16
16
|
| 计算 | `computeMs` / `maxWallMs` | 单 cell CPU 与墙钟预算。 |
|
|
17
17
|
| 输出 | `maxOutputBytes` / `maxValueNodes` / `maxValueEdges` / `maxValueArrayLength` / `maxValueBigIntDigits` | Value Graph 与输出字节预算。 |
|
|
18
18
|
| Worker | `maxOldGenerationSizeMb` / `maxNestedRunCodeDepth` | kernel worker 内存与嵌套执行深度。 |
|
|
@@ -87,7 +87,7 @@ journal、diagnostic、source、cause、call、operation、completion 和 comple
|
|
|
87
87
|
}
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
`settle` 必须是从 0 开始的连续序列。重放按源码产生调用,但不重新 dispatch;它校验 global、member、args 和提交数量,再按 recorded settlement order 释放 recorded value/error。这个 `recorded-value` 承诺重建的是 REPL 计算状态,不表示外部 effect
|
|
90
|
+
`settle` 必须是从 0 开始的连续序列。重放按源码产生调用,但不重新 dispatch;它校验 global、member、args 和提交数量,再按 recorded settlement order 释放 recorded value/error。这个 `recorded-value` 承诺重建的是 REPL 计算状态,不表示外部 effect 被重做、撤销或验证。对进程内 Cordis 状态,恢复值明确只是历史数据:Cordis 可见时,固定 runtime context 要求在把旧 ID、Run、approval 或 capability observation 当作当前事实前完成一次新的成功 live Inspect。completeness、effect、authority 和 source evidence 属于 capability metadata,不伪装成 journal 字段。
|
|
91
91
|
|
|
92
92
|
journal 的 capability-call `args`/`value` 与 completion value 保存 `ptc-value-graph/v1` canonical envelope,不保存 decoded rich JS value,也不依赖递归 `JSON.stringify` 或 `structuredClone`。因此深层数组/对象、own `__proto__`、`undefined`、special number、BigInt、hole、shared identity 和 cycle 不会被外层 session JSON 改写。完整支持域和预算见 [PTC Value Graph V1](value-wire.md)。
|
|
93
93
|
|
package/docs/installation.md
CHANGED
|
@@ -23,7 +23,7 @@ The optional `cordisToolsEnabled` integration requires the current DSH installat
|
|
|
23
23
|
Use this form after the selected version is available from the npm registry:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
dsh plugin --profile <profile> add dsh-ptc-plus@0.2.
|
|
26
|
+
dsh plugin --profile <profile> add dsh-ptc-plus@0.2.3
|
|
27
27
|
dsh --profile <profile> --dump-config
|
|
28
28
|
```
|
|
29
29
|
|
|
@@ -60,7 +60,7 @@ pnpm dsh --profile <profile> --dump-config
|
|
|
60
60
|
|
|
61
61
|
```sh
|
|
62
62
|
npm pack
|
|
63
|
-
dsh plugin --profile <profile> add /absolute/path/to/dsh-ptc-plus-0.2.
|
|
63
|
+
dsh plugin --profile <profile> add /absolute/path/to/dsh-ptc-plus-0.2.3.tgz
|
|
64
64
|
dsh --profile <profile> --dump-config
|
|
65
65
|
```
|
|
66
66
|
|
|
@@ -79,4 +79,4 @@ dsh plugin add github:muyuanjin/dsh-ptc-plus#main
|
|
|
79
79
|
dsh --dump-config
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
After an npm release, the package spec may instead be `dsh-ptc-plus@0.2.
|
|
82
|
+
After an npm release, the package spec may instead be `dsh-ptc-plus@0.2.3`. 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.
|
|
@@ -18,7 +18,7 @@ DSH authority/policy -> native dispatch -> canonical result
|
|
|
18
18
|
`-> args/result + settlement transcript
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
cold replay 校验相同的调用序列并返回 recorded value,不重新执行工具。这保证 REPL 计算状态可重建,但不声称外部 effect 可逆或仍与历史相同。
|
|
21
|
+
cold replay 校验相同的调用序列并返回 recorded value,不重新执行工具。这保证 REPL 计算状态可重建,但不声称外部 effect 可逆或仍与历史相同。Cordis Plugin、Run 和 Inspect observation 属于进程内实时状态;恢复出的 Cordis value 只作为历史数据保留,后续状态性决策必须先按恢复 context 执行新的 live Inspect。
|
|
22
22
|
|
|
23
23
|
同一 settlement 规则适用于当前 request 中的全部 program binding,包括 owner-provided namespace 与 `code.run`。已结算的 value/error 可进入 durable transcript;若取消、超时或 worker failure 发生时调用仍未结算,cell heap 回滚,discarded journal 以实际 `global.member` 保留 possible-effect boundary。分类来自可观察生命周期,不来自 capability 名称表。
|
|
24
24
|
|
|
@@ -8,10 +8,12 @@ The default loose binding mode lets a complete top-level `const` or `let` declar
|
|
|
8
8
|
|
|
9
9
|
Every capability namespace is leased to one cell. Captured `tools`, `capabilities`, `repl`, `code`, or member functions expire when that cell ends.
|
|
10
10
|
|
|
11
|
-
For PTC mode requests using the code-only direct-tool projection, PTC Plus presents two tools in stable order: `run_code` and `edit_run_code`. The latter edits the most recent eligible cell visible when the edit call is dispatched, including a cell that completed successfully but needs a small adjustment. That target remains fixed while the call is in flight; later tool settlements cannot retarget it. The edit accepts exactly one atomic `edits` or `regex_edits` array
|
|
11
|
+
For PTC mode requests using the code-only direct-tool projection, PTC Plus presents two tools in stable order: `run_code` and `edit_run_code`. The latter edits the most recent eligible cell visible when the edit call is dispatched, including a cell that completed successfully but needs a small adjustment. That target remains fixed while the call is in flight; later tool settlements cannot retarget it. The edit accepts exactly one atomic `edits` or `regex_edits` array and an optional `expected_target_call_seq` precondition. Invalid, unavailable, or mismatched-target edits return `{ edited: false, reason }` without executing source or consuming the target.
|
|
12
12
|
|
|
13
13
|
`edit_run_code` is a real registered tool call. Session history and later model requests retain its original name and delta arguments. The plugin executes the materialized source as a host-derived `run_code`, returns only the edit status, value, and logs to the model, and keeps the complete source plus journal in private replay metadata. It does not rewrite assistant history or emit edit-specific runtime context.
|
|
14
14
|
|
|
15
|
+
When a live parse error occurs exactly at the cell EOF, PTC Plus checks the three single-closing-token corrections `}`, `)`, and `]` with the same preparation context. If exactly one correction succeeds, the existing editor can express it from a bounded unique suffix, and the rejected call has a persistent event identity, `PTC-C001` includes a directly callable `edit_run_code({ edits: [...], expected_target_call_seq })` invocation. The guard must match the target captured when the edit call is persisted, so a later editable cell suppresses execution even when the same literal replacement would match it. The plugin does not apply or execute the correction automatically; ambiguous, broader, and unbound repairs keep the ordinary length-adaptive guidance.
|
|
16
|
+
|
|
15
17
|
Only values explicitly returned or printed appear in a cell result:
|
|
16
18
|
|
|
17
19
|
```ts
|
|
@@ -71,10 +73,12 @@ The configuration uses `ptc-plus` as the DSH plugin ID and `dsh-ptc-plus` as the
|
|
|
71
73
|
|
|
72
74
|
`enabled: false` is the settings-based kill switch: the Host keeps only the settings namespace and client card, and removes every runtime hook, prompt section, and tool surface. Every setting is applied live when its owner can reconcile it. 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 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 is available under Settings → Plugin configuration, shows “已启用” or “已停用”, and disables every control except `enabled` while the plugin is off. 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.
|
|
73
75
|
|
|
74
|
-
`cordisToolsEnabled: true` atomically mounts the official `@deepseek-ai/dsh-tool-cordis` plugin and the shipped `cordis-plugin-development` companion Skill in PTC agent scopes. PTC Plus resolves the official `cordis` preset through DSH's public preset service
|
|
76
|
+
`cordisToolsEnabled: true` atomically mounts the official `@deepseek-ai/dsh-tool-cordis` plugin and exactly the shipped `cordis-plugin-development` companion Skill in PTC agent scopes. PTC Plus resolves the official `cordis` preset through DSH's public preset service, lets the maintained filesystem provider own its Skill root, 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. A missing or broken preset, Skill/tool service, declared Cordis service, inconsistent inspect manifest, or rejected activation fails the setting change and removes every provisional contribution. 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, edit_run_code]`. Agents whose `run_code` surface appears after creation are retried on the DSH tools-change signal. Cordis can evaluate model-written plugins against the live runtime, so enabling it grants shell-equivalent trust.
|
|
75
77
|
|
|
76
78
|
Keep large Cordis host or client source in a top-level binding before calling a Cordis tool. A parse or validation rejection is a runtime failure, so bindings assigned before it remain live for a short retry cell; reuse the binding instead of resending the source.
|
|
77
79
|
|
|
80
|
+
Cordis Plugins and Runs are process-local. On a resumed agent or after Cordis is re-enabled, normalized journal calls can restore their recorded return values but do not prove that prior IDs, approvals, Runs, or Inspect observations are live. The fixed `tools:ptc-plus-cordis-recovery` context remains present until a new successful `cordis_inspect*` call is settled. Use that live read-only inspection before a stateful Cordis decision; do not rerun a mutating historical call merely to rebuild state.
|
|
81
|
+
|
|
78
82
|
`durableReplay: false` starts new kernels without historical REPL state while preserving live bindings in the current process. It does not delete session logs.
|
|
79
83
|
|
|
80
84
|
Recovery tips are disabled with `tipsEnabled: false`. When enabled, a bounded runtime context in the reserved `tools:ptc-plus-tip/<trigger>/<ordinal>` name family may appear after a repeated binding failure or a diagnostic that identifies an executable, shell, or path problem in the current execution world. The trigger and per-trigger ordinal are reconstructed from canonical named system-prompt snapshots; repeated aggregate copies and visible wording do not advance the history. A tip is subject to `tipCooldownMessages` and becomes detailed only after `tipEscalationFailures` unresolved matches; it never changes the code-only direct-tool list or schema. `edit_run_code` does not emit a runtime context because its real call and result already carry the relevant fact.
|
|
@@ -93,7 +97,7 @@ Rewrites are recorded as `meta.dshPtcPlusRewrites` on the tool result (parallel
|
|
|
93
97
|
|
|
94
98
|
| Code | Meaning | State effect |
|
|
95
99
|
| --- | --- | --- |
|
|
96
|
-
| `PTC-C001` | The cell cannot be parsed | Not executed; REPL unchanged |
|
|
100
|
+
| `PTC-C001` | The cell cannot be parsed | Not executed; REPL unchanged; a uniquely validated and target-bound single-token EOF closure includes a directly callable declared `edit_run_code` repair; otherwise retry a short corrected cell with `run_code`, use edit for a localized long-cell correction, and resend with `run_code` for broad, ambiguous, or unbound repairs |
|
|
97
101
|
| `PTC-C002` | Preflight rejected a kernel-control import | Not executed; REPL unchanged |
|
|
98
102
|
| `PTC-N001` | Top-level binding conflict | Not executed; REPL unchanged |
|
|
99
103
|
| `PTC-O001` | Unsupported or over-budget output | Cell executed; earlier mutations may exist |
|
|
@@ -8,6 +8,63 @@ const CORDIS_PRESET_ID = 'cordis'
|
|
|
8
8
|
const CORDIS_SKILL_NAME = 'cordis-plugin-development'
|
|
9
9
|
const CORDIS_SKILL_PROVIDER = 'ptc-plus-cordis'
|
|
10
10
|
|
|
11
|
+
function exactCompanionCandidates(observation) {
|
|
12
|
+
if (Array.isArray(observation)) {
|
|
13
|
+
return observation.filter(candidate => candidate?.name === CORDIS_SKILL_NAME)
|
|
14
|
+
}
|
|
15
|
+
if (observation === null || typeof observation !== 'object'
|
|
16
|
+
|| !Array.isArray(observation.candidates)) return observation
|
|
17
|
+
return {
|
|
18
|
+
...observation,
|
|
19
|
+
candidates: observation.candidates.filter(candidate => candidate?.name === CORDIS_SKILL_NAME),
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function exactCompanionProvider(provider) {
|
|
24
|
+
if (provider === null || typeof provider !== 'object'
|
|
25
|
+
|| typeof provider.name !== 'string'
|
|
26
|
+
|| typeof provider.list !== 'function'
|
|
27
|
+
|| typeof provider.get !== 'function') {
|
|
28
|
+
throw new Error('ptc-plus: DSH Cordis Skill provider is incompatible')
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
...provider,
|
|
32
|
+
async list(options) {
|
|
33
|
+
return exactCompanionCandidates(await Reflect.apply(provider.list, provider, [options]))
|
|
34
|
+
},
|
|
35
|
+
async get(candidate, options) {
|
|
36
|
+
if (candidate?.name !== CORDIS_SKILL_NAME) return undefined
|
|
37
|
+
const skill = await Reflect.apply(provider.get, provider, [candidate, options])
|
|
38
|
+
return skill?.name === CORDIS_SKILL_NAME ? skill : undefined
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function exactCompanionSkillPlugin(skillFilesystemPlugin) {
|
|
44
|
+
return {
|
|
45
|
+
...skillFilesystemPlugin,
|
|
46
|
+
apply(pluginCtx, config) {
|
|
47
|
+
if (typeof pluginCtx?.extend !== 'function') {
|
|
48
|
+
throw new Error('ptc-plus: cordisToolsEnabled requires the DSH Context.extend API')
|
|
49
|
+
}
|
|
50
|
+
const skills = pluginCtx.get?.('skills')
|
|
51
|
+
if (typeof skills?.registerProvider !== 'function') {
|
|
52
|
+
throw new Error('ptc-plus: cordisToolsEnabled requires the DSH skills.registerProvider API')
|
|
53
|
+
}
|
|
54
|
+
const facade = new Proxy(skills, {
|
|
55
|
+
get(target, property) {
|
|
56
|
+
if (property === 'registerProvider') {
|
|
57
|
+
return create => target.registerProvider(control => exactCompanionProvider(create(control)))
|
|
58
|
+
}
|
|
59
|
+
const value = Reflect.get(target, property, target)
|
|
60
|
+
return typeof value === 'function' ? value.bind(target) : value
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
return skillFilesystemPlugin.apply(pluginCtx.extend({ skills: facade }), config)
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
11
68
|
function requireFiberServices(agent, fiber, owner) {
|
|
12
69
|
if (typeof agent?.ctx?.get !== 'function') {
|
|
13
70
|
throw new Error('ptc-plus: cordisToolsEnabled requires the DSH agent Context.get API')
|
|
@@ -32,8 +89,9 @@ function requireCompanionServices(agent) {
|
|
|
32
89
|
throw new Error('ptc-plus: cordisToolsEnabled requires the DSH agentPresets.resolve API')
|
|
33
90
|
}
|
|
34
91
|
const skills = agent.ctx.get('skills')
|
|
35
|
-
if (typeof skills?.
|
|
36
|
-
|
|
92
|
+
if (typeof skills?.registerProvider !== 'function'
|
|
93
|
+
|| typeof skills.list !== 'function' || typeof skills.get !== 'function') {
|
|
94
|
+
throw new Error('ptc-plus: cordisToolsEnabled requires the DSH skills registerProvider/list/get APIs')
|
|
37
95
|
}
|
|
38
96
|
if (agent.ctx.tools?.get?.('skill', agent) === undefined) {
|
|
39
97
|
throw new Error('ptc-plus: cordisToolsEnabled requires the DSH skill tool in the PTC agent scope')
|
|
@@ -57,10 +115,11 @@ async function requireCompanionSkill(agent, skills) {
|
|
|
57
115
|
cwd: agent.session?.header?.cwd,
|
|
58
116
|
scope: agent,
|
|
59
117
|
}
|
|
60
|
-
const
|
|
61
|
-
|
|
118
|
+
const owned = (await skills.list(lookup)).filter(skill => skill?.provider === CORDIS_SKILL_PROVIDER)
|
|
119
|
+
const summary = owned[0]
|
|
120
|
+
if (owned.length !== 1 || summary?.name !== CORDIS_SKILL_NAME
|
|
62
121
|
|| summary.invocation?.modelInvocable !== true) {
|
|
63
|
-
throw new Error(`ptc-plus: official Cordis
|
|
122
|
+
throw new Error(`ptc-plus: official Cordis Skill provider must publish exactly ${JSON.stringify(CORDIS_SKILL_NAME)} as model-invocable`)
|
|
64
123
|
}
|
|
65
124
|
const skill = await skills.get(CORDIS_SKILL_NAME, lookup)
|
|
66
125
|
if (skill?.provider !== CORDIS_SKILL_PROVIDER
|
|
@@ -177,6 +236,7 @@ export function createCordisToolsOwner(
|
|
|
177
236
|
const pending = new Set()
|
|
178
237
|
const cordisInspectLeases = createCordisInspectLeases(ctx)
|
|
179
238
|
const scopedCordisPlugin = cordisInspectLeases.plugin(cordisPlugin)
|
|
239
|
+
const scopedSkillPlugin = exactCompanionSkillPlugin(skillFilesystemPlugin)
|
|
180
240
|
let disposed = false
|
|
181
241
|
|
|
182
242
|
const leafErrors = error => error instanceof AggregateError
|
|
@@ -242,7 +302,7 @@ export function createCordisToolsOwner(
|
|
|
242
302
|
const skillDirectory = await companionSkillDirectory(agentPresets)
|
|
243
303
|
if (mount.disposed) return
|
|
244
304
|
|
|
245
|
-
const skillFiber = agent.ctx.plugin(
|
|
305
|
+
const skillFiber = agent.ctx.plugin(scopedSkillPlugin, {
|
|
246
306
|
providerName: CORDIS_SKILL_PROVIDER,
|
|
247
307
|
includeDefaultRoots: false,
|
|
248
308
|
customSkillDirs: [skillDirectory],
|
|
@@ -59,6 +59,38 @@ function rejection(message) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
export function createCordisRecoveryPolicy(initiallyEnabled) {
|
|
63
|
+
const states = new WeakMap()
|
|
64
|
+
let enabled = initiallyEnabled
|
|
65
|
+
let generation = enabled ? 1 : 0
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
reconfigure(nextEnabled) {
|
|
68
|
+
if (!enabled && nextEnabled) generation += 1
|
|
69
|
+
enabled = nextEnabled
|
|
70
|
+
},
|
|
71
|
+
required(agent, view) {
|
|
72
|
+
if (!enabled) return false
|
|
73
|
+
let state = states.get(agent)
|
|
74
|
+
if (state?.generation !== generation) {
|
|
75
|
+
state = {
|
|
76
|
+
generation,
|
|
77
|
+
baselineInspections: view.cordisTranscript.inspections,
|
|
78
|
+
required: view.cordisTranscript.calls > 0,
|
|
79
|
+
}
|
|
80
|
+
states.set(agent, state)
|
|
81
|
+
}
|
|
82
|
+
if (state.required
|
|
83
|
+
&& view.cordisTranscript.inspections > state.baselineInspections) {
|
|
84
|
+
state.required = false
|
|
85
|
+
}
|
|
86
|
+
return state.required
|
|
87
|
+
},
|
|
88
|
+
disposeAgent(agent) {
|
|
89
|
+
states.delete(agent)
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
62
94
|
function presentationState(assembly) {
|
|
63
95
|
const tools = Array.isArray(assembly?.tools) ? assembly.tools : []
|
|
64
96
|
if (!tools.some(tool => tool?.name === RUN_CODE)) {
|
|
@@ -110,6 +142,7 @@ export function createDirectSurfaceOwner({
|
|
|
110
142
|
const compositions = new Map()
|
|
111
143
|
const canonicalRequests = new WeakMap()
|
|
112
144
|
const sessions = new Map()
|
|
145
|
+
const cordisRecovery = createCordisRecoveryPolicy(runtimeConfig.cordisToolsEnabled)
|
|
113
146
|
let currentCanonicalizeToolCalls = canonicalizeToolCalls
|
|
114
147
|
const tipConfig = {
|
|
115
148
|
enabled: runtimeConfig.tipsEnabled,
|
|
@@ -175,6 +208,7 @@ export function createDirectSurfaceOwner({
|
|
|
175
208
|
return Object.freeze({
|
|
176
209
|
reconfigure(nextConfig) {
|
|
177
210
|
currentCanonicalizeToolCalls = nextConfig.canonicalizeToolCalls
|
|
211
|
+
cordisRecovery.reconfigure(nextConfig.cordisToolsEnabled)
|
|
178
212
|
tipConfig.enabled = nextConfig.tipsEnabled
|
|
179
213
|
tipConfig.cooldownMessages = nextConfig.tipCooldownMessages
|
|
180
214
|
tipConfig.escalationFailures = nextConfig.tipEscalationFailures
|
|
@@ -231,7 +265,11 @@ export function createDirectSurfaceOwner({
|
|
|
231
265
|
directTools = [adaptRunCodeSchema(runCode), editRunCodeSchema()]
|
|
232
266
|
}
|
|
233
267
|
const runtimeContexts = sessionPtc
|
|
234
|
-
? sessionRuntimeContexts(agent, tipConfig
|
|
268
|
+
? sessionRuntimeContexts(agent, tipConfig, {
|
|
269
|
+
cordisRecoveryRequired(view) {
|
|
270
|
+
return cordisRecovery.required(agent, view)
|
|
271
|
+
},
|
|
272
|
+
})
|
|
235
273
|
: { contexts: [] }
|
|
236
274
|
const contexts = sessionPtc && Array.isArray(assembly.contexts)
|
|
237
275
|
&& runtimeContexts.contexts.length > 0
|
|
@@ -310,6 +348,7 @@ export function createDirectSurfaceOwner({
|
|
|
310
348
|
disposeAgent(agent) {
|
|
311
349
|
const id = sessionId(agent)
|
|
312
350
|
compositions.delete(agent)
|
|
351
|
+
cordisRecovery.disposeAgent(agent)
|
|
313
352
|
if (id !== undefined) clearSession(id)
|
|
314
353
|
},
|
|
315
354
|
disposeSession(session) {
|
|
@@ -12,7 +12,11 @@ import {
|
|
|
12
12
|
normalizeRewrites,
|
|
13
13
|
validatedRewrites,
|
|
14
14
|
} from './session-journal.js'
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
EXPECTED_TARGET_CALL_SEQ,
|
|
17
|
+
editRejectedCell,
|
|
18
|
+
editRunCodeSchema,
|
|
19
|
+
} from './rejected-cell-editor.js'
|
|
16
20
|
import { editTargetForCall, projectSessionLog } from './session-log-view.js'
|
|
17
21
|
import { RUN_CODE } from './runtime-bridge-owner.js'
|
|
18
22
|
import { isRecord } from './record-utils.js'
|
|
@@ -126,6 +130,13 @@ export function createEditTransportOwner(ctx, {
|
|
|
126
130
|
}
|
|
127
131
|
const edited = editRejectedCell(args, target.source)
|
|
128
132
|
if (!edited.edited) return edited
|
|
133
|
+
const expectedTargetCallSeq = args[EXPECTED_TARGET_CALL_SEQ]
|
|
134
|
+
if (expectedTargetCallSeq !== undefined && expectedTargetCallSeq !== target.callSeq) {
|
|
135
|
+
return {
|
|
136
|
+
edited: false,
|
|
137
|
+
reason: `validated repair targets run_code call ${expectedTargetCallSeq}, but this edit captured call ${target.callSeq}`,
|
|
138
|
+
}
|
|
139
|
+
}
|
|
129
140
|
if (typeof ctx.tools.execute !== 'function') {
|
|
130
141
|
throw new Error('ptc-plus: DSH tools.execute is required for derived edit execution')
|
|
131
142
|
}
|
|
@@ -2,6 +2,7 @@ import { diagnostic } from './diagnostic.js'
|
|
|
2
2
|
|
|
3
3
|
export const MAX_ERROR_LOG_BYTES = 4 * 1024
|
|
4
4
|
export const FAILURE_HINT_THRESHOLD = 3
|
|
5
|
+
export const LONG_CELL_CODE_UNITS = 2_000
|
|
5
6
|
const BINDING_FAILURE = Symbol('binding failure')
|
|
6
7
|
|
|
7
8
|
export function markBindingFailure(error) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const PLATFORM_CAUSE_CODES = new Set(['EACCES', 'EINVAL', 'ENOTDIR', 'ENOENT', 'UNKNOWN'])
|
|
2
2
|
const TIP_CONTEXT_PREFIX = 'tools:ptc-plus-tip/'
|
|
3
|
-
export const LONG_FAILED_CELL_TIP_CODE_UNITS = 2_000
|
|
4
3
|
const TIP_PREFIXES = Object.freeze({
|
|
5
4
|
'repeated-binding-failure': 'The same binding failure has recurred.',
|
|
6
5
|
'platform-command-failure': 'An executable, shell, or path failed in the current execution world.',
|
|
@@ -14,6 +14,7 @@ export const EDIT_LIMITS = Object.freeze({
|
|
|
14
14
|
|
|
15
15
|
const EDIT_RUN_CODE_EXECUTION_DESCRIPTION = 'Edit and run TypeScript cell'
|
|
16
16
|
const REGEX_EDIT_RUN_CODE_EXECUTION_DESCRIPTION = 'Regex-edit and run TypeScript cell'
|
|
17
|
+
export const EXPECTED_TARGET_CALL_SEQ = 'expected_target_call_seq'
|
|
17
18
|
|
|
18
19
|
const REGEX_MATCH_SCRIPT = `(() => {
|
|
19
20
|
const replacements = []
|
|
@@ -254,7 +255,7 @@ function assemble(source, replacements) {
|
|
|
254
255
|
export function editRunCodeSchema() {
|
|
255
256
|
return {
|
|
256
257
|
name: 'edit_run_code',
|
|
257
|
-
description: 'Make a small exact or regular-expression change to the most recent eligible cell captured when this edit call is dispatched, then run the complete corrected cell. A successful edit becomes the next eligible cell. Use this only when replaying the whole cell is safe. If earlier code may already have caused an external effect, use a new run_code cell and its existing variables instead; this tool does not resume at the error location. Send exactly one atomic edits or regex_edits array. Choose edits for a few unique literal fragments; choose regex_edits when one counted pattern covers repeated fragments. Every resolved range must be non-overlapping in the original cell.',
|
|
258
|
+
description: 'Make a small exact or regular-expression change to the most recent eligible cell captured when this edit call is dispatched, then run the complete corrected cell. A successful edit becomes the next eligible cell. Use this only when replaying the whole cell is safe. If earlier code may already have caused an external effect, use a new run_code cell and its existing variables instead; this tool does not resume at the error location. Send exactly one atomic edits or regex_edits array, plus expected_target_call_seq only when a diagnostic supplies it to bind a validated repair to its rejected cell. Choose edits for a few unique literal fragments; choose regex_edits when one counted pattern covers repeated fragments. Every resolved range must be non-overlapping in the original cell.',
|
|
258
259
|
parameters: {
|
|
259
260
|
type: 'object',
|
|
260
261
|
additionalProperties: false,
|
|
@@ -285,6 +286,10 @@ export function editRunCodeSchema() {
|
|
|
285
286
|
required: ['pattern', 'flags', 'replacement', 'expected_matches'],
|
|
286
287
|
},
|
|
287
288
|
},
|
|
289
|
+
[EXPECTED_TARGET_CALL_SEQ]: {
|
|
290
|
+
type: 'integer', minimum: 0,
|
|
291
|
+
description: 'Optional target precondition copied from a validated diagnostic. The edit is rejected if the captured cell has another call sequence.',
|
|
292
|
+
},
|
|
288
293
|
},
|
|
289
294
|
oneOf: [{ required: ['edits'] }, { required: ['regex_edits'] }],
|
|
290
295
|
},
|
|
@@ -296,32 +301,41 @@ export function editRejectedCell(value, source, timeoutMs = DEFAULT_REGEX_TIMEOU
|
|
|
296
301
|
return rejection('edit_run_code expects an object with exactly one edits or regex_edits array')
|
|
297
302
|
}
|
|
298
303
|
const keys = Object.keys(value)
|
|
299
|
-
|
|
304
|
+
const operationKeys = keys.filter(key => key === 'edits' || key === 'regex_edits')
|
|
305
|
+
if (operationKeys.length !== 1 || keys.length > 2
|
|
306
|
+
|| keys.some(key => !['edits', 'regex_edits', EXPECTED_TARGET_CALL_SEQ].includes(key))
|
|
307
|
+
|| !Array.isArray(value[operationKeys[0]])) {
|
|
300
308
|
return rejection('edit_run_code expects exactly one edits or regex_edits array')
|
|
301
309
|
}
|
|
310
|
+
if (Object.hasOwn(value, EXPECTED_TARGET_CALL_SEQ)
|
|
311
|
+
&& (!Number.isSafeInteger(value[EXPECTED_TARGET_CALL_SEQ])
|
|
312
|
+
|| value[EXPECTED_TARGET_CALL_SEQ] < 0)) {
|
|
313
|
+
return rejection('edit_run_code expected_target_call_seq must be a non-negative safe integer')
|
|
314
|
+
}
|
|
302
315
|
if (source === undefined) return rejection('no run_code cell is currently eligible for safe editing')
|
|
303
|
-
const
|
|
316
|
+
const operation = operationKeys[0]
|
|
317
|
+
const resolved = operation === 'edits'
|
|
304
318
|
? exactReplacements(value.edits, source)
|
|
305
319
|
: regexReplacements(value.regex_edits, source, timeoutMs)
|
|
306
320
|
if (resolved.error !== undefined) return rejection(resolved.error)
|
|
307
321
|
const replacements = resolved.replacements.sort((left, right) => left.start - right.start)
|
|
308
322
|
for (let index = 1; index < replacements.length; index += 1) {
|
|
309
323
|
if (replacements[index].start < replacements[index - 1].end) {
|
|
310
|
-
return rejection(`${
|
|
324
|
+
return rejection(`${operation}[${replacements[index].index}] overlaps ${operation}[${replacements[index - 1].index}] in the target cell`)
|
|
311
325
|
}
|
|
312
326
|
}
|
|
313
327
|
const preflight = preflightAssembly(source, replacements)
|
|
314
328
|
if (preflight.error !== undefined) {
|
|
315
|
-
return rejection(
|
|
329
|
+
return rejection(operation === 'regex_edits' ? preflight.error.replace(/^edits/, 'regex_edits') : preflight.error)
|
|
316
330
|
}
|
|
317
331
|
const assembled = assemble(source, preflight.replacements)
|
|
318
332
|
if (assembled.error !== undefined) {
|
|
319
|
-
return rejection(
|
|
333
|
+
return rejection(operation === 'regex_edits' ? assembled.error.replace(/^edits/, 'regex_edits') : assembled.error)
|
|
320
334
|
}
|
|
321
335
|
return Object.freeze({
|
|
322
336
|
edited: true,
|
|
323
337
|
code: assembled.code,
|
|
324
|
-
description:
|
|
338
|
+
description: operation === 'edits'
|
|
325
339
|
? EDIT_RUN_CODE_EXECUTION_DESCRIPTION
|
|
326
340
|
: REGEX_EDIT_RUN_CODE_EXECUTION_DESCRIPTION,
|
|
327
341
|
})
|
|
@@ -2,6 +2,8 @@ import { latestRecoveryTip } from './recovery-tips.js'
|
|
|
2
2
|
import { projectSessionLog } from './session-log-view.js'
|
|
3
3
|
|
|
4
4
|
const REWRITE_FEEDBACK = 'tools:ptc-plus-rewrite-info'
|
|
5
|
+
const CORDIS_RECOVERY = 'tools:ptc-plus-cordis-recovery'
|
|
6
|
+
const CORDIS_RECOVERY_TEXT = 'Recorded Cordis values in the recovered REPL are historical data, not proof that process-local Plugins, Runs, approvals, or Inspect observations still exist in the current DSH process. Before relying on prior Cordis IDs, state, or capability data, follow the current Cordis owner guidance and call the live read-only Cordis Inspect bindings through `tools.*`; do not rerun mutating Cordis calls merely to reconstruct history.'
|
|
5
7
|
|
|
6
8
|
function continuationFeedback(view) {
|
|
7
9
|
const run = view.latestRun
|
|
@@ -18,13 +20,17 @@ function continuationFeedback(view) {
|
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
/** Build all dynamic PTC contexts from one session-log projection. */
|
|
21
|
-
export function sessionRuntimeContexts(agent, tipConfig) {
|
|
23
|
+
export function sessionRuntimeContexts(agent, tipConfig, options = {}) {
|
|
22
24
|
const view = projectSessionLog(agent)
|
|
23
25
|
const rewrite = continuationFeedback(view)
|
|
24
26
|
const tip = latestRecoveryTip(view, tipConfig)
|
|
27
|
+
const cordisRecovery = options.cordisRecoveryRequired?.(view) === true
|
|
28
|
+
? { name: CORDIS_RECOVERY, text: CORDIS_RECOVERY_TEXT }
|
|
29
|
+
: undefined
|
|
25
30
|
return Object.freeze({
|
|
26
31
|
contexts: Object.freeze([
|
|
27
32
|
...(rewrite === undefined ? [] : [{ name: REWRITE_FEEDBACK, text: rewrite }]),
|
|
33
|
+
...(cordisRecovery === undefined ? [] : [cordisRecovery]),
|
|
28
34
|
...(tip === undefined ? [] : [tip]),
|
|
29
35
|
]),
|
|
30
36
|
})
|
|
@@ -9,6 +9,7 @@ import { diagnostic, renderDiagnostic } from './diagnostic.js'
|
|
|
9
9
|
import {
|
|
10
10
|
firstLine,
|
|
11
11
|
limitLogs,
|
|
12
|
+
LONG_CELL_CODE_UNITS,
|
|
12
13
|
markBindingFailure,
|
|
13
14
|
messageOf,
|
|
14
15
|
oneLineMessage,
|
|
@@ -19,7 +20,7 @@ import { PreflightError, prepareProgram } from './cell-analysis.js'
|
|
|
19
20
|
import { ModuleRewriteError } from './cell-rewriter.js'
|
|
20
21
|
import { mapSourcePosition } from './source-position-map.js'
|
|
21
22
|
import { durabilityState, transitionDurability } from './session-state.js'
|
|
22
|
-
import {
|
|
23
|
+
import { validatedEofClosureRepair } from './validated-parse-repair.js'
|
|
23
24
|
|
|
24
25
|
const OUTPUT_LIMIT_MESSAGE = bytes => `output exceeded ${bytes} bytes; reduce the returned value or keep it in a REPL binding`
|
|
25
26
|
|
|
@@ -49,7 +50,7 @@ function isBindingReferenceError(error) {
|
|
|
49
50
|
|| /Cannot access ['"][^'"]+['"] before initialization/.test(error.message))
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
function
|
|
53
|
+
function parseCellPosition(error) {
|
|
53
54
|
const cellPosition = error instanceof ModuleRewriteError ? error.cellPosition : undefined
|
|
54
55
|
const line = Number.isSafeInteger(cellPosition?.line)
|
|
55
56
|
? cellPosition.line
|
|
@@ -57,16 +58,29 @@ function parseDiagnostic(error, source) {
|
|
|
57
58
|
const column = Number.isSafeInteger(cellPosition?.column)
|
|
58
59
|
? cellPosition.column
|
|
59
60
|
: Number.isSafeInteger(error?.loc?.column) ? error.loc.column + 1 : undefined
|
|
61
|
+
return line !== undefined && line >= 1 && column !== undefined
|
|
62
|
+
? { line, column }
|
|
63
|
+
: undefined
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseDiagnostic(error, source, position, repair) {
|
|
60
67
|
return diagnostic({
|
|
61
68
|
code: 'PTC-C001',
|
|
62
69
|
severity: 'error',
|
|
63
70
|
phase: 'parse',
|
|
64
71
|
message: `cell could not be parsed: ${oneLineMessage(error)}`,
|
|
65
72
|
stateEffect: 'unchanged',
|
|
66
|
-
...(
|
|
67
|
-
source: { cell: 'current', start:
|
|
68
|
-
}
|
|
69
|
-
help:
|
|
73
|
+
...(position === undefined ? {} : {
|
|
74
|
+
source: { cell: 'current', start: position },
|
|
75
|
+
}),
|
|
76
|
+
help: repair === undefined
|
|
77
|
+
? source.length >= LONG_CELL_CODE_UNITS
|
|
78
|
+
? ['this cell was not executed; when edit_run_code is declared for the current request and the correction is small and localized, use it to avoid resending this long source; otherwise retry only this cell with corrected source in run_code']
|
|
79
|
+
: ['this cell was not executed; correct the reported syntax and retry only this cell with run_code']
|
|
80
|
+
: [
|
|
81
|
+
`this cell was not executed; validated syntax repair: append ${JSON.stringify(repair.delimiter)} at the end of this cell`,
|
|
82
|
+
`when edit_run_code is declared for the current request, call ${repair.invocation} to apply this correction and rerun the cell; otherwise retry only this cell with the corrected source in run_code`,
|
|
83
|
+
],
|
|
70
84
|
})
|
|
71
85
|
}
|
|
72
86
|
|
|
@@ -196,29 +210,41 @@ export class SessionCellExecutor {
|
|
|
196
210
|
return result
|
|
197
211
|
}
|
|
198
212
|
|
|
199
|
-
let prepared
|
|
200
213
|
const catalog = kernel.bindingCatalog.inputs()
|
|
201
|
-
|
|
214
|
+
const looseTopLevelRedeclarations = replayRecord === undefined
|
|
215
|
+
? config.looseTopLevelRedeclarations
|
|
216
|
+
: replayRecord.bindingMode === 'loose'
|
|
217
|
+
const rewritesEnabled = replayRecord === undefined ? {
|
|
218
|
+
autoRewriteImports: config.autoRewriteImports,
|
|
219
|
+
autoStripExports: config.autoStripExports,
|
|
220
|
+
autoSplitRedeclarations: config.autoSplitRedeclarations,
|
|
221
|
+
} : replayRecord.rewritePolicy
|
|
222
|
+
const prepareCell = program => prepareProgram(
|
|
223
|
+
program,
|
|
224
|
+
catalog.knownBindings,
|
|
225
|
+
looseTopLevelRedeclarations,
|
|
226
|
+
request.bindingDescriptors.reservedNames,
|
|
227
|
+
rewritesEnabled,
|
|
228
|
+
catalog.importBindings,
|
|
229
|
+
catalog.importNamespaces,
|
|
230
|
+
)
|
|
231
|
+
let prepared
|
|
202
232
|
try {
|
|
203
|
-
|
|
204
|
-
? config.looseTopLevelRedeclarations
|
|
205
|
-
: replayRecord.bindingMode === 'loose'
|
|
206
|
-
prepared = prepareProgram(
|
|
207
|
-
request.program,
|
|
208
|
-
catalog.knownBindings,
|
|
209
|
-
looseTopLevelRedeclarations,
|
|
210
|
-
request.bindingDescriptors.reservedNames,
|
|
211
|
-
replayRecord === undefined ? {
|
|
212
|
-
autoRewriteImports: config.autoRewriteImports,
|
|
213
|
-
autoStripExports: config.autoStripExports,
|
|
214
|
-
autoSplitRedeclarations: config.autoSplitRedeclarations,
|
|
215
|
-
} : replayRecord.rewritePolicy,
|
|
216
|
-
catalog.importBindings,
|
|
217
|
-
catalog.importNamespaces,
|
|
218
|
-
)
|
|
233
|
+
prepared = prepareCell(request.program)
|
|
219
234
|
} catch (error) {
|
|
220
235
|
const result = earlyResult('exception', messageOf(error))
|
|
221
|
-
const
|
|
236
|
+
const position = parseCellPosition(error)
|
|
237
|
+
const repair = error instanceof PreflightError || replayRecord !== undefined
|
|
238
|
+
? undefined
|
|
239
|
+
: validatedEofClosureRepair({
|
|
240
|
+
source: request.program,
|
|
241
|
+
position,
|
|
242
|
+
prepare: prepareCell,
|
|
243
|
+
targetCallSeq: request.sourceCallSeq,
|
|
244
|
+
})
|
|
245
|
+
const failure = error instanceof PreflightError
|
|
246
|
+
? preflightDiagnostic(error)
|
|
247
|
+
: parseDiagnostic(error, request.program, position, repair)
|
|
222
248
|
result.error.message = renderDiagnostic(failure, request.program)
|
|
223
249
|
kernel.completeJournal(request.journal, 'noop', result, undefined, [failure])
|
|
224
250
|
return result
|
|
@@ -466,7 +492,7 @@ export class SessionCellExecutor {
|
|
|
466
492
|
active.prepared.sourceMap,
|
|
467
493
|
),
|
|
468
494
|
declared: message.moduleLoadFailed === true ? new Set() : active.prepared.declared,
|
|
469
|
-
longCellFailure: active.request.program.length >=
|
|
495
|
+
longCellFailure: active.request.program.length >= LONG_CELL_CODE_UNITS,
|
|
470
496
|
})
|
|
471
497
|
active.appliedBindingCatalog = message.moduleLoadFailed === true
|
|
472
498
|
? active.priorBindingCatalog
|
|
@@ -27,6 +27,20 @@ function isContextStep(event) {
|
|
|
27
27
|
|| (event?.type === 'user/message' && event.data?.source?.kind === 'user')
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function cordisTranscriptFacts(timeline) {
|
|
31
|
+
let calls = 0
|
|
32
|
+
let inspections = 0
|
|
33
|
+
for (const result of timeline.results.values()) {
|
|
34
|
+
for (const call of result?.journal?.calls ?? []) {
|
|
35
|
+
if (call.global !== 'tools' || typeof call.member !== 'string'
|
|
36
|
+
|| !call.member.startsWith('cordis_')) continue
|
|
37
|
+
calls += 1
|
|
38
|
+
if (call.ok === true && call.member.startsWith('cordis_inspect')) inspections += 1
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return Object.freeze({ calls, inspections })
|
|
42
|
+
}
|
|
43
|
+
|
|
30
44
|
/** Project session events into immutable facts consumed by prompt presentation. */
|
|
31
45
|
export function projectSessionLog(agent, requestedEdit) {
|
|
32
46
|
const events = agent?.session?.events
|
|
@@ -39,6 +53,7 @@ export function projectSessionLog(agent, requestedEdit) {
|
|
|
39
53
|
latestRun: undefined,
|
|
40
54
|
editableRun: undefined,
|
|
41
55
|
repairSource: undefined,
|
|
56
|
+
cordisTranscript: Object.freeze({ calls: 0, inspections: 0 }),
|
|
42
57
|
})
|
|
43
58
|
}
|
|
44
59
|
let contextStep = 0
|
|
@@ -60,6 +75,7 @@ export function projectSessionLog(agent, requestedEdit) {
|
|
|
60
75
|
: undefined
|
|
61
76
|
const latestRun = timeline.openTurn ? timeline.latestRun : undefined
|
|
62
77
|
const editableRun = timeline.openTurn ? timeline.editableRun : undefined
|
|
78
|
+
const cordisTranscript = cordisTranscriptFacts(timeline)
|
|
63
79
|
return Object.freeze({
|
|
64
80
|
openTurn: timeline.openTurn,
|
|
65
81
|
contextStep,
|
|
@@ -68,6 +84,7 @@ export function projectSessionLog(agent, requestedEdit) {
|
|
|
68
84
|
latestRun,
|
|
69
85
|
editableRun,
|
|
70
86
|
repairSource: editableRun?.source,
|
|
87
|
+
cordisTranscript,
|
|
71
88
|
...(requestedEdit === undefined ? {} : { requestedEditTarget }),
|
|
72
89
|
})
|
|
73
90
|
}
|
|
@@ -432,14 +432,22 @@ export class SessionRuntime {
|
|
|
432
432
|
request = { ...request, bindings: bindingDescriptors.namespaces, bindingDescriptors }
|
|
433
433
|
const { id: sessionId, session, callId, persistedCallSeq, cwd } = sessionOf(sessionContext)
|
|
434
434
|
let callSeq
|
|
435
|
+
let sourceCallSeq
|
|
435
436
|
try {
|
|
436
437
|
if (persistedCallSeq !== undefined
|
|
437
438
|
&& (!Number.isSafeInteger(persistedCallSeq) || persistedCallSeq < 0)) {
|
|
438
439
|
throw new Error('persisted tool call sequence must be a non-negative safe integer')
|
|
439
440
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
441
|
+
if (cellConfig.durableReplay) {
|
|
442
|
+
sourceCallSeq = liveToolCallSeq(session, callId, 'run_code')
|
|
443
|
+
callSeq = persistedCallSeq ?? sourceCallSeq
|
|
444
|
+
} else {
|
|
445
|
+
try {
|
|
446
|
+
sourceCallSeq = liveToolCallSeq(session, callId, 'run_code')
|
|
447
|
+
} catch {
|
|
448
|
+
sourceCallSeq = undefined
|
|
449
|
+
}
|
|
450
|
+
}
|
|
443
451
|
} catch (error) {
|
|
444
452
|
return completed({ logs: [], error: { kind: 'recovery', message: `cannot identify current run_code call in session log: ${messageOf(error)}` } })
|
|
445
453
|
}
|
|
@@ -471,7 +479,7 @@ export class SessionRuntime {
|
|
|
471
479
|
const workerReservation = kernel.reserveWorkerConfiguration(cellConfig)
|
|
472
480
|
let result
|
|
473
481
|
try {
|
|
474
|
-
result = await kernel.run({ ...request, journal, callSeq }, cellConfig)
|
|
482
|
+
result = await kernel.run({ ...request, journal, callSeq, sourceCallSeq }, cellConfig)
|
|
475
483
|
} finally {
|
|
476
484
|
kernel.releaseWorkerConfiguration(workerReservation)
|
|
477
485
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { EXPECTED_TARGET_CALL_SEQ, editRejectedCell } from './rejected-cell-editor.js'
|
|
2
|
+
|
|
3
|
+
const CLOSING_DELIMITERS = Object.freeze(['}', ')', ']'])
|
|
4
|
+
const LINE_TERMINATORS = /\r\n|[\n\r\u2028\u2029]/gu
|
|
5
|
+
export const MAX_PARSE_REPAIR_ANCHOR_CODE_UNITS = 256
|
|
6
|
+
|
|
7
|
+
function sourceEndPosition(source) {
|
|
8
|
+
let line = 1
|
|
9
|
+
let lineStart = 0
|
|
10
|
+
for (const match of source.matchAll(LINE_TERMINATORS)) {
|
|
11
|
+
line += 1
|
|
12
|
+
lineStart = match.index + match[0].length
|
|
13
|
+
}
|
|
14
|
+
return { line, column: source.length - lineStart + 1 }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isSourceEnd(source, position) {
|
|
18
|
+
if (!Number.isSafeInteger(position?.line) || !Number.isSafeInteger(position?.column)) return false
|
|
19
|
+
const end = sourceEndPosition(source)
|
|
20
|
+
return position.line === end.line && position.column === end.column
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function startsInsideSurrogatePair(source, index) {
|
|
24
|
+
if (index <= 0) return false
|
|
25
|
+
const current = source.charCodeAt(index)
|
|
26
|
+
const previous = source.charCodeAt(index - 1)
|
|
27
|
+
return current >= 0xdc00 && current <= 0xdfff && previous >= 0xd800 && previous <= 0xdbff
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function boundedUniqueSuffix(source) {
|
|
31
|
+
const first = Math.max(0, source.length - MAX_PARSE_REPAIR_ANCHOR_CODE_UNITS)
|
|
32
|
+
for (let start = source.length - 1; start >= first; start -= 1) {
|
|
33
|
+
if (startsInsideSurrogatePair(source, start)) continue
|
|
34
|
+
const suffix = source.slice(start)
|
|
35
|
+
if (source.indexOf(suffix) === source.lastIndexOf(suffix)) return suffix
|
|
36
|
+
}
|
|
37
|
+
return undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function repairArguments(anchor, delimiter, targetCallSeq) {
|
|
41
|
+
const edit = Object.freeze({ old_string: anchor, new_string: anchor + delimiter })
|
|
42
|
+
return Object.freeze({
|
|
43
|
+
edits: Object.freeze([edit]),
|
|
44
|
+
[EXPECTED_TARGET_CALL_SEQ]: targetCallSeq,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function renderInvocation(argumentsValue) {
|
|
49
|
+
const json = JSON.stringify(argumentsValue)
|
|
50
|
+
.replaceAll('\u2028', '\\u2028')
|
|
51
|
+
.replaceAll('\u2029', '\\u2029')
|
|
52
|
+
return `edit_run_code(${json})`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Returns a source-exact edit only when one bounded EOF-closing candidate is valid. */
|
|
56
|
+
export function validatedEofClosureRepair({ source, position, prepare, targetCallSeq }) {
|
|
57
|
+
if (!Number.isSafeInteger(targetCallSeq) || targetCallSeq < 0
|
|
58
|
+
|| !isSourceEnd(source, position)) return undefined
|
|
59
|
+
const anchor = boundedUniqueSuffix(source)
|
|
60
|
+
if (anchor === undefined) return undefined
|
|
61
|
+
const candidates = []
|
|
62
|
+
for (const delimiter of CLOSING_DELIMITERS) {
|
|
63
|
+
const code = source + delimiter
|
|
64
|
+
let prepared
|
|
65
|
+
try {
|
|
66
|
+
prepared = prepare(code)
|
|
67
|
+
} catch {
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
if (prepared.collisions.length > 0) continue
|
|
71
|
+
const argumentsValue = repairArguments(anchor, delimiter, targetCallSeq)
|
|
72
|
+
const edited = editRejectedCell(argumentsValue, source)
|
|
73
|
+
if (!edited.edited || edited.code !== code) continue
|
|
74
|
+
candidates.push({ delimiter, arguments: argumentsValue })
|
|
75
|
+
}
|
|
76
|
+
if (candidates.length !== 1) return undefined
|
|
77
|
+
const candidate = candidates[0]
|
|
78
|
+
return Object.freeze({
|
|
79
|
+
...candidate,
|
|
80
|
+
invocation: renderInvocation(candidate.arguments),
|
|
81
|
+
})
|
|
82
|
+
}
|