@junghanacs/entwurf 0.12.9 → 0.13.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.
Files changed (100) hide show
  1. package/AGENTS.md +135 -248
  2. package/BASELINE.md +46 -7
  3. package/CHANGELOG.md +35 -0
  4. package/CONTRIBUTING.md +4 -2
  5. package/DELIVERY.md +5 -4
  6. package/README.md +46 -18
  7. package/VERIFY.md +10 -6
  8. package/demo/demo.sh +7 -1
  9. package/docs/setup-clean-host.md +10 -4
  10. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +159 -66
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +148 -5
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +16 -4
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +66 -7
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +190 -3
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +12 -4
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +26 -9
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +10 -6
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +15 -17
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +13 -13
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +31 -41
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +8 -9
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +32 -25
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +4 -4
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +5 -4
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +9 -11
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +3 -1
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +17 -20
  28. package/mcp/entwurf-bridge/src/index.ts +190 -82
  29. package/package.json +7 -3
  30. package/pi/settings.reference.json +1 -1
  31. package/pi-extensions/acp-provider.ts +20 -10
  32. package/pi-extensions/entwurf-control.ts +69 -27
  33. package/pi-extensions/lib/acp/augment.ts +60 -2
  34. package/pi-extensions/lib/acp/backend-adapter.ts +183 -8
  35. package/pi-extensions/lib/acp/backend.ts +5 -1
  36. package/pi-extensions/lib/acp/config.ts +19 -5
  37. package/pi-extensions/lib/acp/engraving.ts +3 -1
  38. package/pi-extensions/lib/acp/event-mapper.ts +10 -3
  39. package/pi-extensions/lib/acp/models.ts +69 -7
  40. package/pi-extensions/lib/acp/overlay.ts +234 -3
  41. package/pi-extensions/lib/acp/tool-surface.ts +12 -4
  42. package/pi-extensions/lib/entwurf-core.ts +26 -9
  43. package/pi-extensions/lib/entwurf-deliverability.ts +10 -6
  44. package/pi-extensions/lib/entwurf-fact-provider.ts +15 -17
  45. package/pi-extensions/lib/entwurf-facts.ts +13 -13
  46. package/pi-extensions/lib/entwurf-resume-args.ts +40 -46
  47. package/pi-extensions/lib/entwurf-self-address.ts +8 -9
  48. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +7 -2
  49. package/pi-extensions/lib/entwurf-v2-contract.ts +32 -25
  50. package/pi-extensions/lib/entwurf-v2-decider.ts +8 -8
  51. package/pi-extensions/lib/entwurf-v2-lock.ts +5 -4
  52. package/pi-extensions/lib/entwurf-v2-production.ts +14 -11
  53. package/pi-extensions/lib/entwurf-v2-spawn-production.ts +3 -1
  54. package/pi-extensions/lib/meta-session.ts +17 -20
  55. package/run.sh +200 -62
  56. package/scripts/agy-bridge-config.py +249 -49
  57. package/scripts/agy-bridge.sh +59 -14
  58. package/scripts/check-acp-carrier-augment.ts +34 -2
  59. package/scripts/check-acp-cortex.ts +668 -0
  60. package/scripts/check-acp-provider-surface.ts +50 -6
  61. package/scripts/check-acp-sdk-surface.ts +22 -11
  62. package/scripts/check-acp-session-reuse.ts +64 -1
  63. package/scripts/check-agy-permission-matrix.py +655 -0
  64. package/scripts/check-entwurf-deliverability.ts +9 -6
  65. package/scripts/check-entwurf-fact-provider.ts +7 -7
  66. package/scripts/check-entwurf-facts.ts +12 -12
  67. package/scripts/check-entwurf-resume-args.ts +22 -60
  68. package/scripts/check-entwurf-self-address.ts +187 -4
  69. package/scripts/check-entwurf-v2-contract.ts +2 -2
  70. package/scripts/check-entwurf-v2-production.ts +5 -5
  71. package/scripts/check-entwurf-v2-spawn-production.ts +6 -2
  72. package/scripts/check-entwurf-v2-surface.ts +327 -14
  73. package/scripts/check-gate-qualification.ts +787 -0
  74. package/scripts/check-meta-identity-consumers.ts +1 -1
  75. package/scripts/check-probe-cli-shim.ts +879 -0
  76. package/scripts/check-probe-ordering.ts +2450 -0
  77. package/scripts/check-shell-quote.ts +5 -4
  78. package/scripts/fixtures/probe-cli-shim +20 -0
  79. package/scripts/fixtures/probe-mcp-server.ts +168 -12
  80. package/scripts/lib/mutation-qualify.ts +794 -0
  81. package/scripts/lib/probe-acp-turn.ts +207 -0
  82. package/scripts/lib/probe-cli-shim.ts +464 -0
  83. package/scripts/lib/probe-cli-target.ts +165 -0
  84. package/scripts/lib/probe-event-log.ts +383 -0
  85. package/scripts/lib/probe-verdict.ts +1213 -0
  86. package/scripts/mutants/acp-augment.json +30 -0
  87. package/scripts/mutants/acp-cortex.json +196 -0
  88. package/scripts/mutants/agy-permission.json +144 -0
  89. package/scripts/mutants/meta-identity.json +17 -0
  90. package/scripts/mutants/probe-ordering.json +1032 -0
  91. package/scripts/mutants/self-address.json +59 -0
  92. package/scripts/mutants/v2-surface.json +87 -0
  93. package/scripts/smoke-acp-cortex-live.ts +392 -0
  94. package/scripts/smoke-acp-ordering-probe-live.ts +848 -0
  95. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  96. package/scripts/smoke-acp-v2-send-live.ts +33 -18
  97. package/scripts/smoke-agy-install-state.sh +205 -20
  98. package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -2
  99. package/pi-extensions/lib/entwurf-mailbox-guard.ts +0 -100
  100. package/scripts/check-entwurf-mailbox-guard.ts +0 -266
@@ -1,5 +1,6 @@
1
1
  // Deterministic gate for the ACP provider registration surface (S0 loader/fence,
2
- // updated S2c when the real backend replaced the fail-loud stub).
2
+ // updated S2c when the real backend replaced the fail-loud stub, and again in
3
+ // 0.13 when cortex joined claude on the adapter rail).
3
4
  //
4
5
  // Three layers:
5
6
  // (1 lib) loads the REAL lib modules and asserts the curated Claude
@@ -8,10 +9,13 @@
8
9
  // imports resolve to real emitted `.js`), imports the compiled
9
10
  // acp-provider.js, and drives its default export against a fake pi
10
11
  // that captures registerProvider — real execution capture of the
11
- // actual entry, idempotency included, and that streamSimple is the
12
- // real streamShellAcp backend (by name, NOT invoked that spawns);
12
+ // actual entry, idempotency included, the EXACT curated model set
13
+ // BOTH adapters contribute, and that streamSimple is the real
14
+ // streamShellAcp backend (by name, NOT invoked — that spawns);
13
15
  // (3 source) an auxiliary source-shape lock on acp-provider.ts.
14
16
  //
17
+ // [QK:*] labels mark the claims kill-qualified by scripts/mutants/acp-cortex.json.
18
+ //
15
19
  // Layer 2 is the GPT-reviewed resolution to a fence tension: acp-provider.ts
16
20
  // imports its lib with `.js` suffixes (the root/jiti runtime convention), which
17
21
  // plain `node --experimental-strip-types` cannot resolve to `.ts`. Rather than
@@ -22,7 +26,7 @@
22
26
 
23
27
  import { strict as assert } from "node:assert";
24
28
  import { execFileSync } from "node:child_process";
25
- import { readFileSync, rmSync } from "node:fs";
29
+ import { readFileSync, rmdirSync, rmSync } from "node:fs";
26
30
  import { resolve } from "node:path";
27
31
  import { pathToFileURL } from "node:url";
28
32
  import {
@@ -30,6 +34,8 @@ import {
30
34
  curatedClaudeModels,
31
35
  ENTWURF_ACP_NO_AUTH_SENTINEL,
32
36
  PROVIDER_ID,
37
+ SUPPORTED_ANTHROPIC_MODEL_IDS,
38
+ SUPPORTED_CORTEX_MODEL_IDS,
33
39
  } from "../pi-extensions/lib/acp/models.ts";
34
40
 
35
41
  // ---------------------------------------------------------------------------
@@ -120,6 +126,32 @@ try {
120
126
  for (const want of ["claude-sonnet-5", CURATED_ANCHOR_MODEL_ID]) {
121
127
  assert.ok(capIds.includes(want), `entry model surface missing ${want} (got: ${capIds.join(", ") || "none"})`);
122
128
  }
129
+
130
+ // The REAL entry must register the curated rows of EVERY adapter on the rail —
131
+ // claude's 2 unprefixed ids AND cortex's 4 `cortex-` ids. This is the JOIN
132
+ // claim, distinct from the per-backend data claims: check-acp-cortex pins the
133
+ // 4-row cortex curation in models.ts, but nothing there proves those rows
134
+ // actually reach `pi.registerProvider`. An entry that filtered `cortex-` out
135
+ // of `allCuratedModels()` would leave models.ts and check-acp-cortex fully
136
+ // green while the operator's model list silently lost the whole backend.
137
+ //
138
+ // Set comparison (sorted, exact) — not `includes`: a substring/anchor probe
139
+ // cannot see a row that VANISHED, and the cortex ids contain the claude ids as
140
+ // substrings. Expected is derived from the two source id constants so there is
141
+ // one SSOT; the literal 6 below is the independent floor that catches a
142
+ // curation list mutated to empty (which would move expected and captured
143
+ // together).
144
+ const expectedIds = [...SUPPORTED_ANTHROPIC_MODEL_IDS, ...SUPPORTED_CORTEX_MODEL_IDS].slice().sort();
145
+ assert.equal(
146
+ expectedIds.length,
147
+ 6,
148
+ `curated id constants must total 6 rows (claude 2 + cortex 4) — got ${expectedIds.length}: ${expectedIds.join(", ")}`,
149
+ );
150
+ assert.deepEqual(
151
+ capIds.slice().sort(),
152
+ expectedIds,
153
+ `[QK:CORTEX-PROVIDER-SIX-ROW-SURFACE] compiled entry must register the EXACT curated set of both adapters (claude 2 + cortex 4) — got: ${capIds.join(", ") || "none"}`,
154
+ );
123
155
  assert.equal(typeof cap.cfg.streamSimple, "function", "entry streamSimple must be a function");
124
156
  // Regression guard: the entry must wire the REAL backend (streamShellAcp),
125
157
  // not the removed S0 stub. We check by name rather than invoking — invoking
@@ -131,6 +163,17 @@ try {
131
163
  );
132
164
  } finally {
133
165
  rmSync(TMP_EMIT, { recursive: true, force: true });
166
+ try {
167
+ // The emit created `.tmp-verify/` as TMP_EMIT's parent. This gate now carries
168
+ // a mutant lane, so it runs inside check-gate-qualification's PURITY-checked
169
+ // snapshot, whose tree manifest walks ignored paths too — a leftover empty
170
+ // parent dir reads as IMPURE drift. Remove it when empty; a concurrent
171
+ // sibling gate's emit keeps it alive and the rmdir just fails.
172
+ // (Same repair as check-acp-cortex / check-acp-session-reuse.)
173
+ rmdirSync(".tmp-verify");
174
+ } catch {
175
+ // non-empty or already gone — fine either way
176
+ }
134
177
  }
135
178
 
136
179
  // ---------------------------------------------------------------------------
@@ -154,6 +197,7 @@ assert.ok(!/backend-stub/.test(entrySrc), "entry must not import the removed S0
154
197
 
155
198
  console.log(
156
199
  `[check-acp-provider-surface] ok — compiled entry registers ${PROVIDER_ID} once (idempotent) with the no-auth ` +
157
- `sentinel + curated Claude surface (sonnet + ${CURATED_ANCHOR_MODEL_ID}) + real streamShellAcp backend (S2c); ` +
158
- `lib surface verified live (${models.length} model(s))`,
200
+ `sentinel + the EXACT curated set of both adapters (claude ${SUPPORTED_ANTHROPIC_MODEL_IDS.length} incl. ` +
201
+ `${CURATED_ANCHOR_MODEL_ID} + cortex ${SUPPORTED_CORTEX_MODEL_IDS.length}) + real streamShellAcp backend (S2c); ` +
202
+ `claude lib surface verified live (${models.length} model(s))`,
159
203
  );
@@ -4,12 +4,23 @@
4
4
  // the peer-resolution that makes the Claude ACP adapter satisfiable:
5
5
  //
6
6
  // @agentclientprotocol/sdk 1.3.0 wire SDK (acp-bridge import source)
7
- // @agentclientprotocol/claude-agent-acp 0.61.0 Claude adapter (spawn binary)
7
+ // @agentclientprotocol/claude-agent-acp 0.62.0 Claude adapter (spawn binary)
8
8
  // @anthropic-ai/sdk 0.100.1 peer-resolution pin ONLY (see below)
9
9
  //
10
+ // 2026-07-27 bump 0.61.0 → 0.62.0: the adapter's own `dist/` is byte-identical
11
+ // across the two releases; 0.62.0 moves only its declared deps
12
+ // (claude-agent-sdk 0.3.217 → 0.3.219; its devDep anthropic sdk, which does not
13
+ // reach us). So this bump is a dependency refresh that carries NO adapter-code
14
+ // change — and specifically NOT a fix for the bundled-MCP readiness race
15
+ // (ROADMAP 🔴): 0.62.0 adds no explicit readiness fence. Effective runtime
16
+ // behavior is NOT claimed identical: the transitive SDK moved, and MCP startup
17
+ // lives inside it, so timing may differ — what is established is only that no
18
+ // new fence was added.
19
+ //
10
20
  // The anthropic SDK is NOT an API client / auth surface here. It is a direct
11
- // dep solely to satisfy @anthropic-ai/claude-agent-sdk@0.3.217's peer floor
12
- // (>=0.93.0); drop it and the tree resolves a stale 0.91.1 so the peer goes
21
+ // dep solely to satisfy @anthropic-ai/claude-agent-sdk@0.3.219's peer floor
22
+ // (>=0.93.0 re-measured at 0.3.219, unchanged from 0.3.217, so the 0.100.1
23
+ // pin stands rather than rising mechanically); drop it and the tree resolves a stale 0.91.1 so the peer goes
13
24
  // unmet — a failure that would only surface at the first raw turn. The
14
25
  // lockfile proves the same shape. Source-level import / API-client
15
26
  // instantiation / credential use stays forbidden — asserted in layer (4).
@@ -43,7 +54,7 @@ const pkg = JSON.parse(read("package.json")) as { dependencies?: Record<string,
43
54
  const deps = pkg.dependencies ?? {};
44
55
  const PINS: Record<string, string> = {
45
56
  "@agentclientprotocol/sdk": "1.3.0",
46
- "@agentclientprotocol/claude-agent-acp": "0.61.0",
57
+ "@agentclientprotocol/claude-agent-acp": "0.62.0",
47
58
  [ANTHROPIC_SDK]: "0.100.1",
48
59
  };
49
60
  for (const [name, ver] of Object.entries(PINS)) {
@@ -63,13 +74,13 @@ for (const [name, ver] of Object.entries(PINS)) {
63
74
  const lock = read("pnpm-lock.yaml");
64
75
  assert.match(
65
76
  lock,
66
- /@agentclientprotocol\/claude-agent-acp@0\.61\.0\(@anthropic-ai\/sdk@0\.100\.1/,
67
- "pnpm-lock: claude-agent-acp@0.61.0 must peer-resolve @anthropic-ai/sdk@0.100.1 (peer-pin), not the stale 0.91.1",
77
+ /@agentclientprotocol\/claude-agent-acp@0\.62\.0\(@anthropic-ai\/sdk@0\.100\.1/,
78
+ "pnpm-lock: claude-agent-acp@0.62.0 must peer-resolve @anthropic-ai/sdk@0.100.1 (peer-pin), not the stale 0.91.1",
68
79
  );
69
80
  assert.match(
70
81
  lock,
71
- /@anthropic-ai\/claude-agent-sdk@0\.3\.217\(@anthropic-ai\/sdk@0\.100\.1/,
72
- "pnpm-lock: claude-agent-sdk@0.3.217 must peer-resolve @anthropic-ai/sdk@0.100.1 — else its >=0.93.0 peer floor is unmet",
82
+ /@anthropic-ai\/claude-agent-sdk@0\.3\.219\(@anthropic-ai\/sdk@0\.100\.1/,
83
+ "pnpm-lock: claude-agent-sdk@0.3.219 must peer-resolve @anthropic-ai/sdk@0.100.1 — else its >=0.93.0 peer floor is unmet",
73
84
  );
74
85
 
75
86
  // ---------------------------------------------------------------------------
@@ -108,8 +119,8 @@ const casEntry = adapterRequire.resolve("@anthropic-ai/claude-agent-sdk");
108
119
  const casInfo = pkgInfoFromEntry(casEntry);
109
120
  assert.equal(
110
121
  casInfo.version,
111
- "0.3.217",
112
- `@anthropic-ai/claude-agent-sdk must runtime-resolve to 0.3.217 from the adapter context (got ${casInfo.version})`,
122
+ "0.3.219",
123
+ `@anthropic-ai/claude-agent-sdk must runtime-resolve to 0.3.219 from the adapter context (got ${casInfo.version})`,
113
124
  );
114
125
  const casRequire = createRequire(resolve(casInfo.dir, "package.json"));
115
126
  const sdkEntry = casRequire.resolve(ANTHROPIC_SDK);
@@ -152,7 +163,7 @@ const mcpEntry = casRequire.resolve("@modelcontextprotocol/sdk/server/index.js")
152
163
  const mcpInfo = pkgInfoFromEntry(mcpEntry);
153
164
  assert.ok(
154
165
  mcpInfo.version.startsWith("1.29."),
155
- `@modelcontextprotocol/sdk must runtime-resolve to 1.29.x from the claude-agent-sdk context (got ${mcpInfo.version}) — claude-agent-sdk 0.3.217 declares a ^1.29.0 peer`,
166
+ `@modelcontextprotocol/sdk must runtime-resolve to 1.29.x from the claude-agent-sdk context (got ${mcpInfo.version}) — claude-agent-sdk 0.3.219 declares a ^1.29.0 peer`,
156
167
  );
157
168
 
158
169
  // ---------------------------------------------------------------------------
@@ -26,7 +26,7 @@
26
26
 
27
27
  import { strict as assert } from "node:assert";
28
28
  import { execFileSync } from "node:child_process";
29
- import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
29
+ import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmdirSync, rmSync, writeFileSync } from "node:fs";
30
30
  import { tmpdir } from "node:os";
31
31
  import { resolve } from "node:path";
32
32
  import { pathToFileURL } from "node:url";
@@ -735,8 +735,71 @@ try {
735
735
  "showToolNotifications is display-only — never perturbs the bridge config signature (no spurious rebuild)",
736
736
  );
737
737
  }
738
+
739
+ // ----------------------------------------------------------------------
740
+ // Section I — overlay scope wiring (P0-1, GPT review 2026-07-29): the turn
741
+ // loop hands adapter.ensureOverlay the AUTHORITATIVE resolveSessionKey value
742
+ // (opts.sessionId → PI_SESSION_ID → cwd). A session-scoped overlay (cortex)
743
+ // scopes its dir on this key, so two same-process/cwd sessions with
744
+ // different opts.sessionId must reach the adapter as two DIFFERENT keys —
745
+ // an ambient re-derivation (PI_SESSION_ID/cwd only) would alias them onto
746
+ // one overlay/mcp.json. Captured by wrapping the routed claudeAdapter's
747
+ // ensureOverlay on the compiled module (claude ignores the params, so a
748
+ // no-op stub is behavior-safe for these two turns).
749
+ // ----------------------------------------------------------------------
750
+ {
751
+ const captured: string[] = [];
752
+ const originalEnsureOverlay = adapterMod.claudeAdapter.ensureOverlay;
753
+ adapterMod.claudeAdapter.ensureOverlay = (p: { sessionKey: string }) => {
754
+ captured.push(p.sessionKey);
755
+ return { envOverrides: {} };
756
+ };
757
+ const savedPiSessionId = process.env.PI_SESSION_ID;
758
+ delete process.env.PI_SESSION_ID;
759
+ try {
760
+ const h = makeHarness(recordDir);
761
+ await collect(
762
+ backend.streamAcpTurn(
763
+ sonnet,
764
+ { messages: [{ role: "user", content: "hi NONCE-I1", timestamp: 0 }] },
765
+ { sessionId: "gate-I-one" },
766
+ h.deps,
767
+ ) as Stream,
768
+ );
769
+ await collect(
770
+ backend.streamAcpTurn(
771
+ sonnet,
772
+ { messages: [{ role: "user", content: "hi NONCE-I2", timestamp: 0 }] },
773
+ { sessionId: "gate-I-two" },
774
+ h.deps,
775
+ ) as Stream,
776
+ );
777
+ assert.deepEqual(
778
+ captured,
779
+ ["pi:gate-I-one", "pi:gate-I-two"],
780
+ "the turn loop must pass adapter.ensureOverlay the AUTHORITATIVE resolveSessionKey value (opts.sessionId " +
781
+ "first) — two sessions in one process/cwd must arrive as two DIFFERENT overlay scope keys " +
782
+ "[QK:ACP-OVERLAY-SESSIONKEY-WIRED]",
783
+ );
784
+ } finally {
785
+ adapterMod.claudeAdapter.ensureOverlay = originalEnsureOverlay;
786
+ if (savedPiSessionId === undefined) delete process.env.PI_SESSION_ID;
787
+ else process.env.PI_SESSION_ID = savedPiSessionId;
788
+ }
789
+ }
738
790
  } finally {
739
791
  rmSync(TMP_EMIT, { recursive: true, force: true });
792
+ try {
793
+ // The emit created `.tmp-verify/` as TMP_EMIT's parent. This gate now runs
794
+ // inside check-gate-qualification's PURITY-checked snapshot (it is the gate
795
+ // for the ACP-OVERLAY-SESSIONKEY-WIRED mutant), and the snapshot tree
796
+ // manifest walks ignored paths too — a leftover empty parent dir reads as
797
+ // IMPURE drift. Remove it when empty; a concurrent sibling gate's emit
798
+ // keeps it alive and the rmdir just fails.
799
+ rmdirSync(".tmp-verify");
800
+ } catch {
801
+ // non-empty or already gone — fine either way
802
+ }
740
803
  rmSync(recordDir, { recursive: true, force: true });
741
804
  }
742
805