@junghanacs/entwurf 0.35.0 → 0.36.0
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/AGENTS.md +2 -2
- package/CHANGELOG.md +338 -0
- package/CONTRIBUTING.md +1 -1
- package/DELIVERY.md +15 -0
- package/FAQ.md +3 -3
- package/README.md +30 -16
- package/VERIFY.md +14 -11
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +29 -3
- package/docs/setup-clean-host.md +31 -23
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +46 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/model-ids.js +67 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +9 -20
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +4 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +11 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +27 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +3 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +2 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/codex-ws-client.js +99 -18
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +3 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +13 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +178 -25
- package/mcp/entwurf-bridge/dist/scripts/codex-socket-classify.js +44 -0
- package/mcp/entwurf-bridge/tsconfig.build.json +5 -0
- package/package.json +20 -15
- package/pi/pi-durable/bootstrap.mjs +12 -8
- package/pi/pi-durable/carrier/runtime.js +52 -2
- package/pi/pi-durable/carrier/subagent.js +6 -1
- package/pi/pi-durable/carrier/tui.js +31 -4
- package/pi/pi-durable/carrier-relocation.mjs +2 -2
- package/pi/pi-durable/overlay/upstream-pin.json +79 -36
- package/pi-extensions/lib/acp/backend-adapter.ts +52 -2
- package/pi-extensions/lib/acp/model-ids.js +77 -0
- package/pi-extensions/lib/acp/models.ts +15 -22
- package/pi-extensions/lib/codex-fresh-preflight.ts +4 -3
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +12 -0
- package/pi-extensions/lib/fresh-call-dispatch.ts +25 -1
- package/pi-extensions/lib/herdr-fresh-call.ts +5 -0
- package/pi-extensions/lib/mux-fresh-call.ts +3 -0
- package/pi-extensions/lib/native-push/codex-ws-client.ts +129 -18
- package/pi-extensions/lib/pi-durable-runtime.ts +4 -4
- package/pi-extensions/lib/resume-launch-identity.ts +12 -4
- package/pi-extensions/meta-bridge-pi-durable.ts +230 -34
- package/run.sh +193 -58
- package/scripts/check-acp-backend-preflight.ts +10 -2
- package/scripts/check-acp-cortex.ts +7 -6
- package/scripts/check-acp-prompt-lifecycle.ts +2 -2
- package/scripts/check-acp-provider-surface.ts +1 -1
- package/scripts/check-acp-session-reuse.ts +5 -5
- package/scripts/check-acp-stop-reason.ts +2 -2
- package/scripts/check-acp-stream-hooks.ts +2 -2
- package/scripts/check-acp-usage-accounting.ts +2 -2
- package/scripts/check-codex-app-server-launch.ts +55 -12
- package/scripts/check-codex-native-push.ts +231 -12
- package/scripts/check-fresh-call-dispatch.ts +47 -4
- package/scripts/check-gate-qualification.ts +8 -6
- package/scripts/check-probe-ordering.ts +13 -6
- package/scripts/check-resume-launch-identity.ts +38 -0
- package/scripts/codex-app-server-launch.sh +54 -29
- package/scripts/codex-socket-classify.ts +46 -0
- package/scripts/emit-pi-durable-carrier.ts +159 -168
- package/scripts/mutants/acp-cortex.json +17 -8
- package/scripts/mutants/acp-set-model-readback.json +28 -0
- package/scripts/mutants/codex-app-server-launch.json +39 -12
- package/scripts/mutants/codex-native.json +15 -0
- package/scripts/mutants/control-send-receipt.json +1 -1
- package/scripts/mutants/fresh-call-dispatch.json +11 -0
- package/scripts/mutants/herdr-dev-checkout.json +70 -0
- package/scripts/mutants/pack-install.json +3 -3
- package/scripts/mutants/pi-durable.json +282 -16
- package/scripts/mutants/pi-floor.json +5 -5
- package/scripts/mutants/resume-launch-identity.json +15 -0
- package/scripts/mutants/v2-visible-resume.json +11 -0
- package/scripts/pi-durable-contact-host.mjs +1 -1
- package/scripts/resolve-acp-bridge.ts +1 -1
- package/scripts/smoke-acp-bundled-mcp-live.ts +1 -1
- package/scripts/smoke-acp-carrier-augment-live.ts +1 -1
- package/scripts/smoke-acp-long-turn-live.ts +1 -1
- package/scripts/smoke-acp-mcp-live.ts +1 -1
- package/scripts/smoke-acp-memory-containment-live.ts +1 -1
- package/scripts/smoke-acp-ordering-probe-live.ts +1 -1
- package/scripts/smoke-acp-overlay-live.ts +1 -1
- package/scripts/smoke-acp-provider-live.ts +1 -1
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-session-reuse-live.ts +1 -1
- package/scripts/smoke-acp-skill-live.ts +1 -1
- package/scripts/smoke-acp-socket-citizen-live.ts +2 -2
- package/scripts/smoke-acp-v2-send-live.ts +1 -1
- package/scripts/smoke-entwurf-chain-live.ts +1 -1
- package/scripts/smoke-mux-lifecycle-live.ts +1 -1
- package/pi/pi-durable/overlay/runtime-contacts.patch +0 -283
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junghanacs/entwurf",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.36.0",
|
|
4
|
+
"description": "Visible sibling sessions across independent agent harnesses: launch in Herdr or tmux, reopen pi in tmux, and deliver messages by garden id; native bridges and Claude/Cortex ACP adapters.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Junghan Kim",
|
|
@@ -30,7 +30,12 @@
|
|
|
30
30
|
"copilot",
|
|
31
31
|
"antigravity",
|
|
32
32
|
"pi-coding-agent",
|
|
33
|
-
"pi-extension"
|
|
33
|
+
"pi-extension",
|
|
34
|
+
"herdr",
|
|
35
|
+
"tmux",
|
|
36
|
+
"visible-sessions",
|
|
37
|
+
"session-lifecycle",
|
|
38
|
+
"multi-agent"
|
|
34
39
|
],
|
|
35
40
|
"type": "module",
|
|
36
41
|
"files": [
|
|
@@ -59,7 +64,6 @@
|
|
|
59
64
|
"pi/pi-durable/carrier-resolver.mjs",
|
|
60
65
|
"pi/pi-durable/carrier/",
|
|
61
66
|
"pi/pi-durable/overlay/upstream-pin.json",
|
|
62
|
-
"pi/pi-durable/overlay/runtime-contacts.patch",
|
|
63
67
|
"pi/meta-bridge/.claude-plugin/marketplace.json",
|
|
64
68
|
"pi/meta-bridge/entwurf-meta-receive/.claude-plugin/plugin.json",
|
|
65
69
|
"pi/meta-bridge/entwurf-meta-receive/hooks/hooks.json",
|
|
@@ -95,15 +99,15 @@
|
|
|
95
99
|
"@agentclientprotocol/claude-agent-acp": "0.85.1",
|
|
96
100
|
"@agentclientprotocol/sdk": "1.6.0",
|
|
97
101
|
"@anthropic-ai/sdk": "0.100.1",
|
|
98
|
-
"@earendil-works/chord": "1.
|
|
99
|
-
"@earendil-works/pi-agent-core": "1.
|
|
100
|
-
"@earendil-works/pi-ai": "1.
|
|
101
|
-
"@earendil-works/pi-codemode": "1.
|
|
102
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
103
|
-
"@earendil-works/pi-durable": "1.
|
|
104
|
-
"@earendil-works/pi-mcp": "1.
|
|
105
|
-
"@earendil-works/pi-telemetry": "1.
|
|
106
|
-
"@earendil-works/pi-tui": "1.
|
|
102
|
+
"@earendil-works/chord": "1.2.0",
|
|
103
|
+
"@earendil-works/pi-agent-core": "1.2.0",
|
|
104
|
+
"@earendil-works/pi-ai": "1.2.0",
|
|
105
|
+
"@earendil-works/pi-codemode": "1.2.0",
|
|
106
|
+
"@earendil-works/pi-coding-agent": "1.2.0",
|
|
107
|
+
"@earendil-works/pi-durable": "1.2.0",
|
|
108
|
+
"@earendil-works/pi-mcp": "1.2.0",
|
|
109
|
+
"@earendil-works/pi-telemetry": "1.2.0",
|
|
110
|
+
"@earendil-works/pi-tui": "1.2.0",
|
|
107
111
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
108
112
|
"proper-lockfile": "4.1.2",
|
|
109
113
|
"smol-toml": "^1.8.0",
|
|
@@ -115,6 +119,7 @@
|
|
|
115
119
|
"devDependencies": {
|
|
116
120
|
"@biomejs/biome": "^2.4.13",
|
|
117
121
|
"@types/node": "^24.3.0",
|
|
122
|
+
"@types/proper-lockfile": "4.1.4",
|
|
118
123
|
"husky": "^9.1.7",
|
|
119
124
|
"rregex": "1.13.1",
|
|
120
125
|
"typescript": "^6.0.2",
|
|
@@ -178,7 +183,7 @@
|
|
|
178
183
|
"test": "vitest run",
|
|
179
184
|
"test:watch": "vitest",
|
|
180
185
|
"test:pack": "pnpm run check-pack && pnpm run check-pack-install",
|
|
181
|
-
"typecheck": "tsc --noEmit && tsc -p mcp/tsconfig.json && tsc -p scripts/tsconfig.json",
|
|
186
|
+
"typecheck": "tsc --noEmit && tsc -p mcp/tsconfig.json && tsc -p scripts/tsconfig.json && tsc -p pi/pi-durable/tsconfig.json",
|
|
182
187
|
"lint": "biome check .",
|
|
183
188
|
"format": "biome check --write .",
|
|
184
189
|
"check-meta-manifest-schema": "./run.sh check-meta-manifest-schema",
|
|
@@ -188,7 +193,7 @@
|
|
|
188
193
|
"check:core": "pnpm run check:toolchain && pnpm run check:vitest && pnpm run check:contracts",
|
|
189
194
|
"check:toolchain": "pnpm lint && pnpm typecheck",
|
|
190
195
|
"check:vitest": "./run.sh check-mux-fresh-call && ./run.sh check-acp-sdk-surface && ./run.sh check-pi-queue-oracle && ./run.sh check-tests-beside-behavior",
|
|
191
|
-
"check:contracts": "./run.sh check-demo-reproducibility && ./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-runner && ./run.sh check-control-send-receipt && ./run.sh check-compaction-send-guard && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-control-socket-path && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-herdr-placement && ./run.sh check-herdr-fresh-call && ./run.sh check-herdr-supply && ./run.sh check-fresh-call-dispatch && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment && ./run.sh check-acp-foreground-only && ./run.sh check-acp-vendor-rejection && ./run.sh check-codex-sender-identity && ./run.sh check-codex-native-push && ./run.sh check-pi-durable-beside",
|
|
196
|
+
"check:contracts": "./run.sh check-demo-reproducibility && ./run.sh check-shell-quote && ./run.sh check-entwurf-session-identity && ./run.sh check-meta-session && ./run.sh check-meta-v3-record && ./run.sh check-mailbox-receipt-state && ./run.sh check-entwurf-capabilities && ./run.sh check-harness-admission-parity && ./run.sh check-capability-bundle-reach && ./run.sh check-meta-mailbox-state-write && ./run.sh check-meta-receiver-marker && ./run.sh check-meta-capability-source && ./run.sh check-model-lock && ./run.sh check-dep-versions && ./run.sh check-node-floor-coherence && ./run.sh check-claude-floor-coherence && ./run.sh check-copilot-statusline && ./run.sh check-pi-import-surface && ./run.sh check-env-namespace && ./run.sh check-pi-runtime-version && ./run.sh check-pi-preflight && ./run.sh check-project-trust-handler && ./run.sh check-entwurf-v2-contract && ./run.sh check-entwurf-v2-send && ./run.sh check-entwurf-v2-runner && ./run.sh check-control-send-receipt && ./run.sh check-compaction-send-guard && ./run.sh check-entwurf-resume-args && ./run.sh check-entwurf-v2-native-push && ./run.sh check-entwurf-v2-production && ./run.sh check-entwurf-v2-surface && ./run.sh check-entwurf-bridge-pi-free && ./run.sh check-resume-launch-identity && ./run.sh check-mux-placement && ./run.sh check-mux-launch && ./run.sh check-mux-resume-call && ./run.sh check-mux-parent-artifact && ./run.sh check-mux-launcher-fence && ./run.sh check-entwurf-v2-visible-resume && ./run.sh check-control-socket-path && ./run.sh check-meta-listing && ./run.sh check-entwurf-fact-provider && ./run.sh check-herdr-placement && ./run.sh check-herdr-fresh-call && ./run.sh check-herdr-supply && ./run.sh check-fresh-call-dispatch && ./run.sh check-entwurf-peers-surface && ./run.sh check-entwurf-self-address && ./run.sh check-entwurf-deliverability && ./run.sh check-native-push-adapter && ./run.sh check-native-push-register && ./run.sh check-auth-boundary && ./run.sh check-acp-overlay && ./run.sh check-acp-tool-surface && ./run.sh check-acp-event-mapper && ./run.sh check-acp-prompt-builder && ./run.sh check-acp-config && ./run.sh check-acp-session-store && ./run.sh check-acp-carrier-augment && ./run.sh check-acp-foreground-only && ./run.sh check-acp-set-model-readback && ./run.sh check-acp-vendor-rejection && ./run.sh check-codex-sender-identity && ./run.sh check-codex-native-push && ./run.sh check-pi-durable-beside",
|
|
192
197
|
"check:hermetic": "./run.sh check-bridge-delivery && ./run.sh smoke-pi-attach && ./run.sh check-fresh-cut-gate && ./run.sh check-hook-launch-topology && ./run.sh check-meta-hook-session-switch && ./run.sh check-copilot-birth-hook && ./run.sh check-copilot-receive-arm && ./run.sh check-copilot-launch && ./run.sh check-pi-launch && ./run.sh check-codex-app-server-launch && ./run.sh check-meta-identity-consumers && ./run.sh check-socket-probe && ./run.sh smoke-meta-honesty && ./run.sh check-meta-doctor-oracle && ./run.sh check-agy-permission-matrix && ./run.sh smoke-agy-statusline-state && ./run.sh smoke-copilot-statusline-state && ./run.sh smoke-copilot-mcp-state && ./run.sh smoke-omp-bridge-state && ./run.sh smoke-omp-mcp-state && ./run.sh smoke-omp-receive-state && ./run.sh check-omp-birth-hook && ./run.sh check-omp-receive-arm && ./run.sh check-omp-fresh-preflight && ./run.sh smoke-agy-hooks-state && ./run.sh smoke-pi-provider-state && ./run.sh smoke-user-scope-citizen && ./run.sh smoke-meta-prune && ./run.sh smoke-meta-keyset-guard && ./run.sh check-meta-manifest-schema && ./run.sh check-entwurf-control-rpc && ./run.sh check-entwurf-bridge-boot && ./run.sh check-probe-bridge-command && ./run.sh check-meta-facts && ./run.sh check-peer-facts && ./run.sh check-herdr-plugin && ./run.sh check-herdr-plugin-profile && ./run.sh check-herdr-plugin-build && ./run.sh check-herdr-runtime-bootstrap && ./run.sh check-herdr-activation && ./run.sh check-agy-sender-identity && ./run.sh check-acp-provider-surface && ./run.sh check-acp-stop-reason && ./run.sh check-acp-prompt-lifecycle && ./run.sh check-acp-usage-accounting && ./run.sh check-acp-launch-namespace && ./run.sh check-acp-stream-hooks && ./run.sh check-acp-backend-preflight && ./run.sh check-acp-session-reuse && ./run.sh check-typing-call-fence && ./run.sh check-gate-manifests && ./run.sh check-release-gate-outcomes && ./run.sh check-probe-ordering && ./run.sh check-probe-cli-shim && ./run.sh check-acp-cortex && ./run.sh check-codex-bridge-identity && ./run.sh check-pi-bridge-sender && ./run.sh check-pi-mcp-register && ./run.sh check-pi-mcp-bridge && ./run.sh check-codex-birth-hook && ./run.sh smoke-codex-config-state && ./run.sh smoke-codex-birth && ./run.sh check-herdr-sandbox",
|
|
193
198
|
"check:package": "./run.sh check-install-surface && ./run.sh smoke-meta-install-state && ./run.sh smoke-agy-install-state && ./run.sh smoke-setup-verdict && ./run.sh check-pack-pin-matcher && ./run.sh check-pack-consumer-roots && ./run.sh check-package-source-routing && ./run.sh check-install-preflight && ./run.sh check-pack"
|
|
194
199
|
},
|
|
@@ -9,15 +9,16 @@
|
|
|
9
9
|
// with these differences:
|
|
10
10
|
// - Entwurf's tools-only extension is handed to `openDurable`;
|
|
11
11
|
// - a NEW session is opened with an explicit model and an explicit width, never a default (below);
|
|
12
|
-
// - the citizen is born once `openDurable` returns, with the
|
|
13
|
-
// doorbell can announce mail to the root conversation
|
|
12
|
+
// - the citizen is born once `openDurable` returns, with the owned runtime's `submitToRoot` so its
|
|
13
|
+
// doorbell can announce mail to the root conversation, and its `onFailed` so a session that
|
|
14
|
+
// fails and closes itself retires that doorbell at once;
|
|
14
15
|
// - a launcher's first input, when given, is admitted to the root ONCE, after that birth;
|
|
15
16
|
// - the TUI is handed the native view with the garden id on every conversation label
|
|
16
17
|
// (`withGardenIdentity`).
|
|
17
18
|
// Nothing of the native registry, controller or TUI is rebuilt here.
|
|
18
19
|
//
|
|
19
|
-
// The durable app itself is this package's CARRIER (./carrier/,
|
|
20
|
-
//
|
|
20
|
+
// The durable app itself is this package's CARRIER (./carrier/, emitted from Entwurf's owned source
|
|
21
|
+
// pi/pi-durable/src — upstream publishes no experimental app), and it runs under the carrier's resolver,
|
|
21
22
|
// which maps the app's pi-coding-agent internals to the published package the carrier resolves.
|
|
22
23
|
// `entwurf pi-durable` (run.sh) verifies the carrier and its SDK set (pi-durable-runtime.ts) and
|
|
23
24
|
// execs:
|
|
@@ -29,11 +30,11 @@
|
|
|
29
30
|
//
|
|
30
31
|
// Explicit means the native API, not a vendor argv: upstream's durable main takes only
|
|
31
32
|
// `--continue`, so `--provider`/`--model` reach pi's own `findInitialAgentModel` through the
|
|
32
|
-
//
|
|
33
|
+
// owned runtime's `model` option, for a new session only, and an unresolvable pair fails the open
|
|
33
34
|
// before any citizen is born (the native session directory may already exist by then). The model
|
|
34
35
|
// must be one registered catalog model of that provider, named by its canonical id or by
|
|
35
36
|
// `id:<level>` as pi's own parser reads a thinking level: a typo, a partial or case-variant match, a
|
|
36
|
-
// provider-prefixed id or an unregistered custom-id fallback is refused by the
|
|
37
|
+
// provider-prefixed id or an unregistered custom-id fallback is refused by the owned runtime as
|
|
37
38
|
// `explicit-model-not-exact`, before the birth and the first input.
|
|
38
39
|
// `--width task-wide` is the one width: no tool filter, so the model is offered exactly the tools
|
|
39
40
|
// the installed extensions register. It is a statement of what is OFFERED, not an approval or
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
// its initialization, and its default export is a native durable Extension, handed to `openDurable`
|
|
51
52
|
// by reference after the contact. Initialization runs before the TUI or the runtime is imported —
|
|
52
53
|
// the runtime creates the provider runtime before it installs any extension (carrier/runtime.js
|
|
53
|
-
//
|
|
54
|
+
// 153-159), so an extension object cannot carry an environment phase. Entwurf checks only what the
|
|
54
55
|
// ingress needs: a default export object with a name, no extension name the app installs itself (a
|
|
55
56
|
// same-name install REPLACES), and no tool name of the contact. Every other field is the native
|
|
56
57
|
// registry's to validate. The module is trusted operator code: the identity carriers and the
|
|
@@ -341,7 +342,10 @@ export async function openDurableCitizen({
|
|
|
341
342
|
}
|
|
342
343
|
let attachment;
|
|
343
344
|
try {
|
|
344
|
-
attachment = await contact.attach(durable.view.current().session, {
|
|
345
|
+
attachment = await contact.attach(durable.view.current().session, {
|
|
346
|
+
submitToRoot: durable.submitToRoot,
|
|
347
|
+
onFailed: durable.onFailed,
|
|
348
|
+
});
|
|
345
349
|
} catch (error) {
|
|
346
350
|
return failAfterCleanup(
|
|
347
351
|
error,
|
|
@@ -62,6 +62,11 @@ const context = BACKGROUND_CONTEXT;
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
65
70
|
|
|
66
71
|
|
|
67
72
|
|
|
@@ -98,6 +103,12 @@ const context = BACKGROUND_CONTEXT;
|
|
|
98
103
|
|
|
99
104
|
|
|
100
105
|
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
101
112
|
|
|
102
113
|
|
|
103
114
|
|
|
@@ -234,8 +245,27 @@ export async function openDurable(options = {})
|
|
|
234
245
|
update({ notices: [...state.notices, { id: nextNotice++, level, message }].slice(-20) });
|
|
235
246
|
};
|
|
236
247
|
const fail = (error ) => notice("error", error instanceof Error ? error.message : String(error));
|
|
237
|
-
report = (error) =>
|
|
248
|
+
report = (error) => {
|
|
249
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
250
|
+
// A failing Session runs its close listeners first and reports the cause right after: that report names it.
|
|
251
|
+
if (state.failure !== undefined && state.failure.error === undefined) update({ failure: { error: message } });
|
|
252
|
+
else notice("warning", message);
|
|
253
|
+
};
|
|
238
254
|
for (const error of pendingReports) report(error);
|
|
255
|
+
const failedListeners = new Set ();
|
|
256
|
+
// The Session closes itself when its Storage fails. Its `closed` settles only after every running task returned,
|
|
257
|
+
// which a task ignoring its abort can delay without end, so the failure is shown from the close listener itself.
|
|
258
|
+
// `close()` unsubscribes before it closes the Session, so only a Session closing itself gets here.
|
|
259
|
+
const unsubscribeClose = harness.subscribeClose(() => {
|
|
260
|
+
update({ failure: {} });
|
|
261
|
+
for (const listener of failedListeners) {
|
|
262
|
+
try {
|
|
263
|
+
listener();
|
|
264
|
+
} catch (error) {
|
|
265
|
+
fail(error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
});
|
|
239
269
|
let unsubscribe = conversation.subscribe((value) => update({ conversation: value }));
|
|
240
270
|
// Subagents appear as their conversations are created. A commit listener only records; it calls no Session API.
|
|
241
271
|
const unsubscribeCommits = harness.subscribeCommits((publication) => {
|
|
@@ -286,7 +316,12 @@ export async function openDurable(options = {})
|
|
|
286
316
|
};
|
|
287
317
|
const controller = {
|
|
288
318
|
submit: (text, whenBusy) =>
|
|
289
|
-
command(async () =>
|
|
319
|
+
command(async () => {
|
|
320
|
+
if (state.failure !== undefined) {
|
|
321
|
+
throw new Error("The session failed and takes no input. Close it, then reopen it with --continue.");
|
|
322
|
+
}
|
|
323
|
+
watchAnswer(await current.submit({ type: "input", content: text, whenBusy }, context));
|
|
324
|
+
}),
|
|
290
325
|
compact: (instructions) =>
|
|
291
326
|
command(async () => {
|
|
292
327
|
const id = await current.compact(instructions, context);
|
|
@@ -374,8 +409,23 @@ export async function openDurable(options = {})
|
|
|
374
409
|
controller,
|
|
375
410
|
settings: settingsManager,
|
|
376
411
|
submitToRoot: (draft) => root.submit(draft, context),
|
|
412
|
+
onFailed(listener) {
|
|
413
|
+
// The failure is kept in the view, so a listener that comes after it is told at once, and still only once.
|
|
414
|
+
if (state.failure !== undefined) {
|
|
415
|
+
try {
|
|
416
|
+
listener();
|
|
417
|
+
} catch (error) {
|
|
418
|
+
fail(error);
|
|
419
|
+
}
|
|
420
|
+
return () => {};
|
|
421
|
+
}
|
|
422
|
+
failedListeners.add(listener);
|
|
423
|
+
return () => failedListeners.delete(listener);
|
|
424
|
+
},
|
|
377
425
|
close() {
|
|
378
426
|
closing ??= (async () => {
|
|
427
|
+
// First: the Session runs its close listeners inside its own close call.
|
|
428
|
+
unsubscribeClose();
|
|
379
429
|
unsubscribe();
|
|
380
430
|
unsubscribeCommits();
|
|
381
431
|
conversation.dispose();
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
} from "@earendil-works/pi-durable";
|
|
12
13
|
|
|
13
14
|
async function answerText(api , answer , context ) {
|
|
@@ -48,7 +49,11 @@ export const Subagent = defineExtension({
|
|
|
48
49
|
throw new Error(`Subagent ${child} failed: ${settled.status}`);
|
|
49
50
|
}
|
|
50
51
|
const text = await answerText(api, settled.answer, context);
|
|
51
|
-
|
|
52
|
+
// `output` is what the model reads; spelled out so the result shape is checked, not just accepted.
|
|
53
|
+
return {
|
|
54
|
+
output: [{ type: "text", text }],
|
|
55
|
+
details: { conversationId: child },
|
|
56
|
+
} ;
|
|
52
57
|
},
|
|
53
58
|
}),
|
|
54
59
|
],
|
|
@@ -46,7 +46,7 @@ import { ToolExecutionComponent, } from "entwurf-pi-dist:mode
|
|
|
46
46
|
import { UserMessageComponent } from "entwurf-pi-dist:modes/interactive/components/user-message.js";
|
|
47
47
|
import { getEditorTheme, getMarkdownTheme, initTheme, theme } from "entwurf-pi-dist:modes/interactive/theme/theme.js";
|
|
48
48
|
import { InteractiveThemeController } from "entwurf-pi-dist:modes/interactive/theme/theme-controller.js";
|
|
49
|
-
import { agentOf,
|
|
49
|
+
import { agentOf, } from "./runtime.js";
|
|
50
50
|
|
|
51
51
|
const SELECT_THEME = {
|
|
52
52
|
selectedPrefix: (text) => theme.fg("accent", text),
|
|
@@ -152,6 +152,26 @@ class CompactionComponent extends Box {
|
|
|
152
152
|
|
|
153
153
|
|
|
154
154
|
|
|
155
|
+
/**
|
|
156
|
+
* The lines above the editor: first what stays true for as long as it is true, the session's failure and the
|
|
157
|
+
* compaction failure a conversation keeps until a compaction produces a summary, then the latest notices.
|
|
158
|
+
*/
|
|
159
|
+
export function durableNotices(view ) {
|
|
160
|
+
const lines = [];
|
|
161
|
+
if (view.failure !== undefined) {
|
|
162
|
+
const cause = view.failure.error === undefined ? "" : `: ${view.failure.error}`;
|
|
163
|
+
lines.push({
|
|
164
|
+
level: "error",
|
|
165
|
+
message: `Session failed${cause}. It takes no more input, and a tool still running may take a while to stop. Close this session, then reopen it with --continue.`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const compaction = ((view.conversation.docs["pi.live"] ?? {}) ).compactionFailure;
|
|
169
|
+
if (compaction !== undefined) {
|
|
170
|
+
lines.push({ level: "error", message: `Compaction failed (${compaction.reason}): ${compaction.error}` });
|
|
171
|
+
}
|
|
172
|
+
return [...lines, ...view.notices.slice(-4)];
|
|
173
|
+
}
|
|
174
|
+
|
|
155
175
|
/**
|
|
156
176
|
* The live background-task badge: the dock's first row, never shrunk, so live background work stays on
|
|
157
177
|
* screen however small the terminal gets. It counts the graph's background tasks whatever their kind and
|
|
@@ -212,7 +232,13 @@ export function durableLayout(transcript , badge , dock
|
|
|
212
232
|
minSize: 1,
|
|
213
233
|
visible: (viewport) => badge.count === 0 || viewport.height > 1,
|
|
214
234
|
},
|
|
215
|
-
{
|
|
235
|
+
{
|
|
236
|
+
component: new VStack([{ component: badge, shrink: 0 }, ...dock]),
|
|
237
|
+
basis: "auto",
|
|
238
|
+
grow: 0,
|
|
239
|
+
shrink: 1,
|
|
240
|
+
minSize: 1,
|
|
241
|
+
},
|
|
216
242
|
]);
|
|
217
243
|
}
|
|
218
244
|
|
|
@@ -368,7 +394,8 @@ class DurableTui {
|
|
|
368
394
|
this.#syncQueue((view.conversation.docs["pi.inbox"] ?? { items: [] }) );
|
|
369
395
|
this.#syncNotices(view);
|
|
370
396
|
this.#editor.borderColor = theme.getThinkingBorderColor(agentOf(view.conversation).thinkingLevel ?? "off");
|
|
371
|
-
|
|
397
|
+
// A failed session's live state is its last one; nothing is working any more.
|
|
398
|
+
this.#syncStatus(view.failure === undefined ? live : {});
|
|
372
399
|
this.#syncFooter(view);
|
|
373
400
|
if (this.#rebuilt) this.#transcript.scrollToEnd();
|
|
374
401
|
this.#ui.requestRender(this.#rebuilt);
|
|
@@ -409,7 +436,7 @@ class DurableTui {
|
|
|
409
436
|
|
|
410
437
|
#syncNotices(view ) {
|
|
411
438
|
this.#notices.clear();
|
|
412
|
-
for (const item of view
|
|
439
|
+
for (const item of durableNotices(view)) {
|
|
413
440
|
const color = item.level === "error" ? "error" : item.level === "warning" ? "warning" : "muted";
|
|
414
441
|
this.#notices.addChild(new TruncatedText(theme.fg(color, item.message), 1, 0));
|
|
415
442
|
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// rewrite the emitter performs and the resolution the runtime accepts cannot drift apart.
|
|
4
4
|
//
|
|
5
5
|
// The durable app is source-only upstream: published `@earendil-works/pi-coding-agent` ships no
|
|
6
|
-
// `dist/experimental`, and its exports map exposes no deep `dist/` path. The carrier is
|
|
7
|
-
//
|
|
6
|
+
// `dist/experimental`, and its exports map exposes no deep `dist/` path. The carrier is Entwurf's
|
|
7
|
+
// owned app source (pi/pi-durable/src, #141), type-stripped, with exactly two rewrites:
|
|
8
8
|
//
|
|
9
9
|
// "./<sibling>.ts" → "./<sibling>.js" a carrier file importing another carrier file
|
|
10
10
|
// "../../<path>.ts" → entwurf-pi-dist:<path>.js the app importing a pi-coding-agent internal
|
|
@@ -1,64 +1,107 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion":
|
|
3
|
-
"comment": "The
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
"schemaVersion": 3,
|
|
3
|
+
"comment": "The pi-durable carrier: what it is emitted from, what it is, and the published SDK set it runs against. The experimental durable app is source-only upstream (published pi-coding-agent ships no dist/experimental), so Entwurf owns its thin application/TUI composition as canonical source (pi/pi-durable/src) and ships it as a CARRIER: those files, type-stripped, with the relocation in pi/pi-durable/carrier-relocation.mjs. The SDK engine (execution, scheduler, queues, storage/transcript, compaction, cancellation/recovery, providers, auth) stays the published packages in `sdk`. scripts/emit-pi-durable-carrier.ts is the only writer of the generated fields (carrier blob, sha256, emitter.node); an installed package never reads source. Model data rides the published @earendil-works/pi-ai.",
|
|
4
|
+
"origin": {
|
|
5
|
+
"comment": "Frozen history, never an input: the upstream commit the owned source was first taken from, its objects as recorded then, the overlay patch that carried Entwurf's changes until it was folded into the source (#141), and the carrier bytes the last patch-built release shipped. Neither install nor emit reads this; `emit-pi-durable-carrier --origin <clone>` checks it as provenance only.",
|
|
6
|
+
"repository": "https://github.com/earendil-works/pi",
|
|
7
|
+
"tag": "v1.1.0",
|
|
8
|
+
"commit": "abe508e1b89912adde45528136c3221eb69acdd7",
|
|
9
|
+
"license": {
|
|
10
|
+
"path": "LICENSE",
|
|
11
|
+
"blob": "b0a8e9b81083294360c69b4ec45d3d39a2b28197"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "packages/coding-agent/src/experimental/durable/runtime.ts",
|
|
16
|
+
"blob": "fcd6e5239e999ebf17dea0335fb3dc9cd7f7ce45"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "packages/coding-agent/src/experimental/durable/tui.ts",
|
|
20
|
+
"blob": "397b276cbeb1c90e1fbc96c3cc59e6a25f92dad7"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "packages/coding-agent/src/experimental/durable/harness-setup.ts",
|
|
24
|
+
"blob": "4d6a4be5333ebb8e0f17eac1987f18ea35a91fc7"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": "packages/coding-agent/src/experimental/durable/prompt.ts",
|
|
28
|
+
"blob": "8e2f5d5273fba58da8b314766956ed62951c3814"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"path": "packages/coding-agent/src/experimental/durable/sessions.ts",
|
|
32
|
+
"blob": "34f9ceeeaeaf874f7478d56115da2e4f726c6a85"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"path": "packages/coding-agent/src/experimental/durable/subagent.ts",
|
|
36
|
+
"blob": "e767d54e0bf2908e0f4f41c6933b55c00562eb25"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"retiredOverlay": {
|
|
40
|
+
"name": "runtime-contacts.patch",
|
|
41
|
+
"sha256": "5b1bd9b48d4ff58ad223006d81cc12c4e0b18959cca5cda5ab3ad89f68bbe898"
|
|
42
|
+
},
|
|
43
|
+
"baselineCarrier": {
|
|
44
|
+
"release": "0.35.1",
|
|
45
|
+
"node": "v24.18.1",
|
|
46
|
+
"files": {
|
|
47
|
+
"runtime.js": "bfd88844fae61e8bd32d57b5b986f48056c64eebb5466d9826318a0783ac9005",
|
|
48
|
+
"tui.js": "f61cc68e5d32552a8196785093ae9e2f4a946088fe71582bb05690517db8c5b4",
|
|
49
|
+
"harness-setup.js": "712b630145dc6f5c655a85b01b1e20299c962542e63c5494d2202bb7bf414a15",
|
|
50
|
+
"prompt.js": "ff1ff765fa313e5cb1c3d7537c901c23adb0562ee433bd050fd35eb3d892134c",
|
|
51
|
+
"sessions.js": "1bd1a04ece66f5ab3ca22860c64dba9abc848334d11a27a9afe543f1365c7254",
|
|
52
|
+
"subagent.js": "8a75d934ef87efbb0bb2006a2141f1ea932560851f1718b991ed947d6efdb54c",
|
|
53
|
+
"LICENSE": "0457f5bcec3b3b211605dfb5d1a49042fd638f3686a410fe099c24a25af13c48"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
8
57
|
"carrier": {
|
|
9
|
-
"comment": "files: the six app files bootstrap.mjs needs (main.ts is replaced by the bootstrap)
|
|
58
|
+
"comment": "files: the six app files bootstrap.mjs needs (main.ts is replaced by the bootstrap), each emitted from its owned `source`. distTargets: every pi-coding-agent internal the stripped files import, as paths under the published dist/; the emitter refuses a set that differs and the resolver answers no other.",
|
|
10
59
|
"emitter": {
|
|
11
60
|
"tool": "node:module.stripTypeScriptTypes",
|
|
12
61
|
"mode": "strip",
|
|
13
62
|
"node": "v24.18.1"
|
|
14
63
|
},
|
|
15
64
|
"license": {
|
|
16
|
-
"source": "LICENSE",
|
|
65
|
+
"source": "pi/pi-durable/src/LICENSE",
|
|
17
66
|
"blob": "b0a8e9b81083294360c69b4ec45d3d39a2b28197",
|
|
18
67
|
"sha256": "0457f5bcec3b3b211605dfb5d1a49042fd638f3686a410fe099c24a25af13c48"
|
|
19
68
|
},
|
|
20
69
|
"files": [
|
|
21
70
|
{
|
|
22
71
|
"name": "runtime.js",
|
|
23
|
-
"source": "
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"sha256": "bfd88844fae61e8bd32d57b5b986f48056c64eebb5466d9826318a0783ac9005"
|
|
72
|
+
"source": "pi/pi-durable/src/experimental/durable/runtime.ts",
|
|
73
|
+
"blob": "36c352291620ab86184172a1b4a0df4562ab9581",
|
|
74
|
+
"sha256": "60c2965eadbdbbe78fce1b7eeb89d97eb3104a30b7d58ebca87009d2e9118403"
|
|
27
75
|
},
|
|
28
76
|
{
|
|
29
77
|
"name": "tui.js",
|
|
30
|
-
"source": "
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"sha256": "f61cc68e5d32552a8196785093ae9e2f4a946088fe71582bb05690517db8c5b4"
|
|
78
|
+
"source": "pi/pi-durable/src/experimental/durable/tui.ts",
|
|
79
|
+
"blob": "dab99cd4778ae1db7f062e64db2d0c068f7e342d",
|
|
80
|
+
"sha256": "0f9ebaf8fbb2a532ccd1a7198e1a25533a99720b5fedbf03f42d5c381db3abc5"
|
|
34
81
|
},
|
|
35
82
|
{
|
|
36
83
|
"name": "harness-setup.js",
|
|
37
|
-
"source": "
|
|
38
|
-
"patched": false,
|
|
84
|
+
"source": "pi/pi-durable/src/experimental/durable/harness-setup.ts",
|
|
39
85
|
"blob": "4d6a4be5333ebb8e0f17eac1987f18ea35a91fc7",
|
|
40
86
|
"sha256": "712b630145dc6f5c655a85b01b1e20299c962542e63c5494d2202bb7bf414a15"
|
|
41
87
|
},
|
|
42
88
|
{
|
|
43
89
|
"name": "prompt.js",
|
|
44
|
-
"source": "
|
|
45
|
-
"patched": false,
|
|
90
|
+
"source": "pi/pi-durable/src/experimental/durable/prompt.ts",
|
|
46
91
|
"blob": "8e2f5d5273fba58da8b314766956ed62951c3814",
|
|
47
92
|
"sha256": "ff1ff765fa313e5cb1c3d7537c901c23adb0562ee433bd050fd35eb3d892134c"
|
|
48
93
|
},
|
|
49
94
|
{
|
|
50
95
|
"name": "sessions.js",
|
|
51
|
-
"source": "
|
|
52
|
-
"patched": false,
|
|
96
|
+
"source": "pi/pi-durable/src/experimental/durable/sessions.ts",
|
|
53
97
|
"blob": "34f9ceeeaeaf874f7478d56115da2e4f726c6a85",
|
|
54
98
|
"sha256": "1bd1a04ece66f5ab3ca22860c64dba9abc848334d11a27a9afe543f1365c7254"
|
|
55
99
|
},
|
|
56
100
|
{
|
|
57
101
|
"name": "subagent.js",
|
|
58
|
-
"source": "
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"sha256": "8a75d934ef87efbb0bb2006a2141f1ea932560851f1718b991ed947d6efdb54c"
|
|
102
|
+
"source": "pi/pi-durable/src/experimental/durable/subagent.ts",
|
|
103
|
+
"blob": "3fa9aebbe93e82cfff436e7b9d4c9877e0ed0e27",
|
|
104
|
+
"sha256": "1cb2390b673a0d914117bc07708c39b004fb10e48b31436bf9d9d2b953a64220"
|
|
62
105
|
}
|
|
63
106
|
],
|
|
64
107
|
"distTargets": [
|
|
@@ -89,17 +132,17 @@
|
|
|
89
132
|
]
|
|
90
133
|
},
|
|
91
134
|
"sdk": {
|
|
92
|
-
"comment": "The published library set the carrier runs against, derived from the 1.
|
|
135
|
+
"comment": "The published library set the carrier runs against, derived from the 1.2.0 declarations. members: the @earendil-works packages the carrier imports and every @earendil-works package those declare. direct: a third-party package a carrier file imports itself (sessions.ts: proper-lockfile, at the version pi-coding-agent 1.2.0 pins). Each is an exact production dependency in package.json; the members' own edges are caret ranges, so the installed physical edges, not semver, are what must equal these versions.",
|
|
93
136
|
"members": {
|
|
94
|
-
"@earendil-works/chord": "1.
|
|
95
|
-
"@earendil-works/pi-agent-core": "1.
|
|
96
|
-
"@earendil-works/pi-ai": "1.
|
|
97
|
-
"@earendil-works/pi-codemode": "1.
|
|
98
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
99
|
-
"@earendil-works/pi-durable": "1.
|
|
100
|
-
"@earendil-works/pi-mcp": "1.
|
|
101
|
-
"@earendil-works/pi-telemetry": "1.
|
|
102
|
-
"@earendil-works/pi-tui": "1.
|
|
137
|
+
"@earendil-works/chord": "1.2.0",
|
|
138
|
+
"@earendil-works/pi-agent-core": "1.2.0",
|
|
139
|
+
"@earendil-works/pi-ai": "1.2.0",
|
|
140
|
+
"@earendil-works/pi-codemode": "1.2.0",
|
|
141
|
+
"@earendil-works/pi-coding-agent": "1.2.0",
|
|
142
|
+
"@earendil-works/pi-durable": "1.2.0",
|
|
143
|
+
"@earendil-works/pi-mcp": "1.2.0",
|
|
144
|
+
"@earendil-works/pi-telemetry": "1.2.0",
|
|
145
|
+
"@earendil-works/pi-tui": "1.2.0"
|
|
103
146
|
},
|
|
104
147
|
"direct": {
|
|
105
148
|
"proper-lockfile": "4.1.2"
|
|
@@ -339,6 +339,50 @@ function resolveClaudeLaunch(): AcpLaunchSpec {
|
|
|
339
339
|
*/
|
|
340
340
|
export const CLAUDE_FOREGROUND_ONLY_ENV = { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1" } as const;
|
|
341
341
|
|
|
342
|
+
/**
|
|
343
|
+
* The set-model answer is read back before the prompt (#128). claude-agent-acp resolves the
|
|
344
|
+
* requested id onto one of the rows it offers and answers with that row's `value` as the model
|
|
345
|
+
* option's `currentValue` (0.85.1 `dist/acp-agent.js:5459-5499`). `[측정 2026-10-12]` 0.85.1, no
|
|
346
|
+
* prompt: `claude-fable-5-1` answers its own row, while `claude-sonnet-5-5` and `claude-opus-5-5`
|
|
347
|
+
* answer the alias rows `sonnet` and `opus`, named `Sonnet 5.5` and `Opus 5.5`. The id an alias row
|
|
348
|
+
* resolves to (`ModelInfo.resolvedModel`) is not on the ACP wire, so an alias answer is accepted
|
|
349
|
+
* only as the exact measured row, value AND name, declared here for the requested id. No other
|
|
350
|
+
* spelling is matched.
|
|
351
|
+
*/
|
|
352
|
+
export const CLAUDE_SET_MODEL_ALIAS_ROWS: Readonly<Record<string, Readonly<{ value: string; name: string }>>> =
|
|
353
|
+
Object.freeze({
|
|
354
|
+
"claude-sonnet-5-5": Object.freeze({ value: "sonnet", name: "Sonnet 5.5" }),
|
|
355
|
+
"claude-opus-5-5": Object.freeze({ value: "opus", name: "Opus 5.5" }),
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
/** Refuse unless the set-model answer names the requested model. A missing model option, another
|
|
359
|
+
* row, or a row that is not the measured alias row would let a different model answer under this id. */
|
|
360
|
+
export function confirmClaudeSetModelAnswer(response: unknown, nativeModelId: string): void {
|
|
361
|
+
const configOptions = (response as { configOptions?: unknown } | null | undefined)?.configOptions;
|
|
362
|
+
const modelOption = Array.isArray(configOptions)
|
|
363
|
+
? (configOptions as Array<Record<string, unknown> | null>).find((option) => option?.id === "model")
|
|
364
|
+
: undefined;
|
|
365
|
+
const currentValue = modelOption?.currentValue;
|
|
366
|
+
if (typeof currentValue === "string" && currentValue === nativeModelId) return;
|
|
367
|
+
const rows = Array.isArray(modelOption?.options)
|
|
368
|
+
? (modelOption.options as Array<Record<string, unknown> | null>).flatMap((row) =>
|
|
369
|
+
Array.isArray(row?.options) ? (row.options as Array<Record<string, unknown> | null>) : [row],
|
|
370
|
+
)
|
|
371
|
+
: [];
|
|
372
|
+
const selected = typeof currentValue === "string" ? rows.find((row) => row?.value === currentValue) : undefined;
|
|
373
|
+
const alias = CLAUDE_SET_MODEL_ALIAS_ROWS[nativeModelId];
|
|
374
|
+
if (alias !== undefined && currentValue === alias.value && selected?.name === alias.name) return;
|
|
375
|
+
const answered =
|
|
376
|
+
modelOption === undefined
|
|
377
|
+
? "no model option"
|
|
378
|
+
: `model ${jsonText(currentValue)} named ${jsonText(selected?.name ?? null)}`;
|
|
379
|
+
throw new Error(
|
|
380
|
+
`entwurf: claude-agent-acp answered set-model ${JSON.stringify(nativeModelId)} with ${answered}, which does not ` +
|
|
381
|
+
`name the requested model. Refusing before the prompt so a different model cannot answer under this id; ` +
|
|
382
|
+
`the installed driver and the curated id disagree, and repeating the turn cannot change that.`,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
342
386
|
export const claudeAdapter: AcpBackendAdapter = {
|
|
343
387
|
backend: "claude",
|
|
344
388
|
|
|
@@ -402,7 +446,12 @@ export const claudeAdapter: AcpBackendAdapter = {
|
|
|
402
446
|
if (typeof setConfig !== "function") {
|
|
403
447
|
throw new Error(`setSessionConfigOption unsupported — cannot enforce model ${modelId}`);
|
|
404
448
|
}
|
|
405
|
-
await setConfig.call(connection, {
|
|
449
|
+
const answer = await setConfig.call(connection, {
|
|
450
|
+
sessionId: acpSessionId,
|
|
451
|
+
configId: "model",
|
|
452
|
+
value: nativeModelId,
|
|
453
|
+
});
|
|
454
|
+
confirmClaudeSetModelAnswer(answer, nativeModelId);
|
|
406
455
|
},
|
|
407
456
|
|
|
408
457
|
configSignatureFields(_adapterSettings) {
|
|
@@ -566,7 +615,8 @@ export const cortexAdapter: AcpBackendAdapter = {
|
|
|
566
615
|
// serves fails loud BEFORE the prompt (`Unsupported model: …`). PR #40's
|
|
567
616
|
// launch-time `-m` pin was retired for this: set-model is the single model
|
|
568
617
|
// authority (resolveLaunch never passes `-m`), and "auto" is set explicitly
|
|
569
|
-
// rather than treated as an unspoken default.
|
|
618
|
+
// rather than treated as an unspoken default. NO answer readback, unlike claude: nobody has
|
|
619
|
+
// measured what cortex's set_config_option response carries, so a check would be a guess.
|
|
570
620
|
async enforceModel({ connection, acpSessionId, nativeModelId, modelId }) {
|
|
571
621
|
const setConfig = connection.setSessionConfigOption;
|
|
572
622
|
if (typeof setConfig !== "function") {
|