@junghanacs/entwurf 0.50.0 → 0.52.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 (39) hide show
  1. package/AGENTS.md +2 -1
  2. package/CHANGELOG.md +140 -0
  3. package/ENTWURF.json +10 -4
  4. package/README.md +16 -8
  5. package/VERIFY.md +16 -9
  6. package/demo/README.md +1 -1
  7. package/docs/acp-backend-rail.md +1 -1
  8. package/docs/external-mcp-host.md +2 -2
  9. package/docs/setup-clean-host.md +92 -35
  10. package/mcp/entwurf-bridge/dist/ENTWURF.json +10 -4
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +4 -1
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +4 -4
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +1 -1
  14. package/package.json +11 -18
  15. package/pi/pi-durable/carrier/harness-setup.js +1 -1
  16. package/pi/pi-durable/carrier/runtime.js +221 -50
  17. package/pi/pi-durable/carrier/subagent.js +6 -3
  18. package/pi/pi-durable/carrier/tui.js +60 -22
  19. package/pi/pi-durable/overlay/upstream-pin.json +19 -18
  20. package/pi-extensions/lib/entwurf-control-rpc.ts +5 -1
  21. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +73 -69
  22. package/pi-extensions/lib/entwurf-v2-contract.ts +4 -4
  23. package/pi-extensions/lib/meta-mailbox-body.ts +1 -1
  24. package/pi-extensions/meta-bridge-pi-durable.ts +1 -1
  25. package/run.sh +148 -46
  26. package/scripts/check-entwurf-v2-contract.ts +4 -4
  27. package/scripts/check-entwurf-v2-surface.ts +7 -21
  28. package/scripts/check-gate-qualification.ts +3 -3
  29. package/scripts/check-pi-durable-send.ts +4 -1
  30. package/scripts/lib/codex-fresh-source-receipts.ts +12 -6
  31. package/scripts/mutants/agent-guidance.json +2 -2
  32. package/scripts/mutants/codex-native.json +2 -2
  33. package/scripts/mutants/control-send-receipt.json +47 -7
  34. package/scripts/mutants/pack-install.json +4 -4
  35. package/scripts/mutants/pi-durable.json +189 -3
  36. package/scripts/mutants/pi-floor.json +20 -5
  37. package/scripts/mutants/v2-surface.json +4 -6
  38. package/scripts/smoke-acp-cortex-live.ts +1 -1
  39. package/scripts/smoke-acp-v2-send-live.ts +1 -1
@@ -70,20 +70,20 @@
70
70
  {
71
71
  "name": "runtime.js",
72
72
  "source": "pi/pi-durable/src/experimental/durable/runtime.ts",
73
- "blob": "bb5715ab143945e9f2c067ff71d890fe29875d09",
74
- "sha256": "437834997b9870ab0383aea7d86ff7880ef7e2ebbbfe4c6360f67d8d36f777ec"
73
+ "blob": "00646b1b1fbd7e935fb3e66b7578a2befc01f3c8",
74
+ "sha256": "b47113364b7525abd3bf9286ecb0069f78ac96688b8a271bdbca3a54b9d864c5"
75
75
  },
76
76
  {
77
77
  "name": "tui.js",
78
78
  "source": "pi/pi-durable/src/experimental/durable/tui.ts",
79
- "blob": "405600f6ba1558bf6bf22d144afdcf3f1295c282",
80
- "sha256": "d4bf4e2861bde917e1dcdea6f063bdc3e101ab500107a9c5fab0e3ce97fb2c4a"
79
+ "blob": "4b5dd1876ceac8f51910bb11ed5f0024003f4f34",
80
+ "sha256": "7fefe490d9d462e6a91621901b82b767546926f4a9f4e88d585fe74b727a02c1"
81
81
  },
82
82
  {
83
83
  "name": "harness-setup.js",
84
84
  "source": "pi/pi-durable/src/experimental/durable/harness-setup.ts",
85
- "blob": "e8561809321ce8833a13e3757c172f4e38faabcc",
86
- "sha256": "eb1158030a8ab95187a4fdd5e7b90816a7995e5f446d09a8184d4f5007e7ac5f"
85
+ "blob": "5a799bfbfbbedbec229761b26f639fbe2702b623",
86
+ "sha256": "70b50044ab14c1293a2ace53b375dc48ad940ea9eeacc131d498ccb1ce040617"
87
87
  },
88
88
  {
89
89
  "name": "prompt.js",
@@ -100,8 +100,8 @@
100
100
  {
101
101
  "name": "subagent.js",
102
102
  "source": "pi/pi-durable/src/experimental/durable/subagent.ts",
103
- "blob": "3fa9aebbe93e82cfff436e7b9d4c9877e0ed0e27",
104
- "sha256": "1cb2390b673a0d914117bc07708c39b004fb10e48b31436bf9d9d2b953a64220"
103
+ "blob": "8c18e24a030f0b701167f15a33579195303f5ac5",
104
+ "sha256": "208618441e8e81813de2f44fa9ad5d5bdcdbb78a754679bb7a93d82480e7bece"
105
105
  },
106
106
  {
107
107
  "name": "session-name.js",
@@ -138,17 +138,18 @@
138
138
  ]
139
139
  },
140
140
  "sdk": {
141
- "comment": "The published library set the carrier runs against, derived from the 1.4.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.4.0 pins). Each is an exact production dependency in package.json. Since 1.3.0 the members' own edges are exact too (upstream #10865), and the installed physical edges are still what must equal these versions.",
141
+ "comment": "The published library set the carrier runs against, derived from the 1.5.2 declarations. members: the @earendil-works packages the carrier imports and every @earendil-works package those declare (1.5.0 adds pi-schema, declared by chord, pi-agent-core, pi-ai and pi-durable). direct: a third-party package a carrier file imports itself (sessions.ts: proper-lockfile, at the version pi-coding-agent 1.5.2 pins). Each is an exact production dependency in package.json. Since 1.3.0 the members' own edges are exact too (upstream #10865), and the installed physical edges are still what must equal these versions.",
142
142
  "members": {
143
- "@earendil-works/chord": "1.4.0",
144
- "@earendil-works/pi-agent-core": "1.4.0",
145
- "@earendil-works/pi-ai": "1.4.0",
146
- "@earendil-works/pi-codemode": "1.4.0",
147
- "@earendil-works/pi-coding-agent": "1.4.0",
148
- "@earendil-works/pi-durable": "1.4.0",
149
- "@earendil-works/pi-mcp": "1.4.0",
150
- "@earendil-works/pi-telemetry": "1.4.0",
151
- "@earendil-works/pi-tui": "1.4.0"
143
+ "@earendil-works/chord": "1.5.2",
144
+ "@earendil-works/pi-agent-core": "1.5.2",
145
+ "@earendil-works/pi-ai": "1.5.2",
146
+ "@earendil-works/pi-codemode": "1.5.2",
147
+ "@earendil-works/pi-coding-agent": "1.5.2",
148
+ "@earendil-works/pi-durable": "1.5.2",
149
+ "@earendil-works/pi-mcp": "1.5.2",
150
+ "@earendil-works/pi-schema": "1.5.2",
151
+ "@earendil-works/pi-telemetry": "1.5.2",
152
+ "@earendil-works/pi-tui": "1.5.2"
152
153
  },
153
154
  "direct": {
154
155
  "proper-lockfile": "4.1.2"
@@ -50,7 +50,11 @@ export interface SenderEnvelope {
50
50
  * renders nothing (etiquette marker, not transport contract).
51
51
  */
52
52
  export function formatSenderInfoBlock(sender: SenderEnvelope, wantsReply = false): string {
53
- return `\n\n<sender_info>${JSON.stringify({
53
+ const replyGuidance =
54
+ sender.replyable === true
55
+ ? "\n\n[entwurf: When a reply is requested, send the result via entwurf_v2 to the requested recipient (default: sender_info.sessionId); a local final is not delivery. Honor routing and no-send instructions; an absent or false wants_reply does not cancel a request. A replyable address alone needs no acknowledgment.]"
56
+ : "";
57
+ return `${replyGuidance}\n\n<sender_info>${JSON.stringify({
54
58
  sessionId: sender.sessionId,
55
59
  agentId: sender.agentId,
56
60
  cwd: sender.cwd,
@@ -1,7 +1,7 @@
1
1
  /**
2
- * entwurf-v2-contract-schema — the pi-side TypeBox REPRESENTATION of the frozen
3
- * `entwurf_v2` contract. This module exists ONLY to carry the pi-ai TypeBox
4
- * schema builders (`StringEnum`, `Type`) out of the pi-free core
2
+ * entwurf-v2-contract-schema — the pi-side JSON Schema REPRESENTATION of the frozen
3
+ * `entwurf_v2` contract. This module exists ONLY to carry the pi schema builders
4
+ * (pi-ai's `StringEnum`, pi-schema's `obj`/`str`/…) out of the pi-free core
5
5
  * (`entwurf-v2-contract.ts`); the constants/types/decision logic live there.
6
6
  *
7
7
  * ⚠️ pi LANE ONLY — the MCP bridge (`mcp/entwurf-bridge/src/index.ts`) MUST NOT
@@ -11,18 +11,21 @@
11
11
  * the contract CONSTANTS + `resolveDispatch` (pi-free core), never these schemas.
12
12
  * Consumer: `check-entwurf-v2-contract` ONLY. This is a gate-side REPRESENTATION of the
13
13
  * frozen contract, not a shipped tool schema — measured 2026-07-27, nothing else in the
14
- * import graph reaches it. The two surfaces a model actually reads build their own:
15
- * `pi-extensions/entwurf-control.ts` (TypeBox `entwurfV2Parameters` + tool description) and
16
- * `mcp/entwurf-bridge/src/index.ts` (Zod). Change a model-facing description THERE; changing
17
- * it here reaches no model, and `check-entwurf-v2-surface` is what pins those two.
14
+ * import graph reaches it. The surface a model actually reads builds its own:
15
+ * `mcp/entwurf-bridge/src/index.ts` (Zod, descriptions from ENTWURF.json), which a pi citizen
16
+ * reaches through Pi's built-in MCP since the native pi tools were retired (2f7c883, Pi 0.99).
17
+ * Change a model-facing description THERE; changing it here reaches no model.
18
18
  *
19
- * StringEnum (typebox 1.x) inside Type.Object (typebox 0.34) — the same mix the
20
- * existing entwurf tools use. The logic types in the core are hand-written unions,
21
- * NOT `Static<>` inferences, so the 0.34/1.x widening caveat does not touch them;
22
- * `check-entwurf-v2-contract` keeps these schemas and the core types in lockstep.
19
+ * Pi SDK 1.5 (#155) removed pi-ai's TypeBox re-export (`Type`); its builders are now
20
+ * `@earendil-works/pi-schema`, which emits plain JSON Schema (obj() closed by default,
21
+ * opt() leaves a property out of `required`, lit() is `{const}`), and pi-ai's
22
+ * `StringEnum` still emits `{type:"string", enum}`. The logic types in the core are
23
+ * hand-written unions, NOT `Static<>` inferences; `check-entwurf-v2-contract` keeps
24
+ * these schemas and the core types in lockstep.
23
25
  */
24
26
 
25
- import { StringEnum, Type } from "@earendil-works/pi-ai";
27
+ import { StringEnum } from "@earendil-works/pi-ai";
28
+ import { bool, lit, nul, obj, opt, type Properties, type Schema, str, union } from "@earendil-works/pi-schema";
26
29
  import {
27
30
  ENTWURF_INTENTS,
28
31
  ENTWURF_V2_ACTIONS,
@@ -34,36 +37,43 @@ import {
34
37
  } from "./entwurf-v2-contract.ts";
35
38
  import { SESSION_ID_RE } from "./session-id.js";
36
39
 
37
- export const EntwurfV2InputSchema = Type.Object(
38
- {
39
- // R2/F6 executable: the garden-id shape is enforced by pattern, not prose —
40
- // a malformed/typo gid fails the schema (→ bad-target) and can never reach a
41
- // spawn. SSOT regex = SESSION_ID_RE (pi-extensions/lib/session-id.js).
42
- target: Type.String({
43
- pattern: SESSION_ID_RE.source,
40
+ /**
41
+ * The face the drift guard reads. pi-schema types a schema as `{[keyword]: unknown}`; obj() always emits
42
+ * `properties`, so this names that one keyword without asserting anything else about the shape.
43
+ */
44
+ type ObjectSchema = Schema & { readonly properties: Readonly<Record<string, Schema>> };
45
+
46
+ /** obj() — closed, so it emits `additionalProperties: false` — under the face above. */
47
+ const closedObject = (properties: Properties): ObjectSchema => obj(properties) as ObjectSchema;
48
+
49
+ export const EntwurfV2InputSchema = closedObject({
50
+ // R2/F6 executable: the garden-id shape is enforced by pattern, not prose —
51
+ // a malformed/typo gid fails the schema (→ bad-target) and can never reach a
52
+ // spawn. SSOT regex = SESSION_ID_RE (pi-extensions/lib/session-id.js).
53
+ target: str({
54
+ pattern: SESSION_ID_RE.source,
55
+ description:
56
+ "garden-id of an EXISTING citizen (pattern-enforced). Fresh sibling creation is out of scope on every current surface — there is no fallback verb that still spawns. A malformed/typo gid is bad-target, never a new spawn.",
57
+ }),
58
+ intent: StringEnum(ENTWURF_INTENTS, {
59
+ description:
60
+ "caller's declared outcome contract (F1). ONE member since the visible-first cut: fire-and-forget = ack only. The second, owned-outcome (caller owns completion), resumed a dormant citizen by launching a hidden background child and was withdrawn — a dormant citizen is currently unreachable by this verb.",
61
+ }),
62
+ mode: opt(
63
+ StringEnum(ENTWURF_V2_MODES, {
44
64
  description:
45
- "garden-id of an EXISTING citizen (pattern-enforced). Fresh sibling creation is out of scope on every current surface — there is no fallback verb that still spawns. A malformed/typo gid is bad-target, never a new spawn.",
65
+ "delivery mode — NOT the ownership axis (F1) nor liveness routing. steer (ask the receiver's steering queue) or follow_up (ask its follow-up queue). NEITHER is an interrupt: a busy receiver drains steering after each turn and follow-ups only when its inner loop ends, so a later steer overtakes every earlier follow_up and there is no order between the two. Both queues are volatile process memory — an abort drops what is in them. The receipt names which queue accepted the message (`queued-steer` / `queued-follow-up`); an idle receiver answers `sent`, meaning the turn was TRIGGERED, not that the model has seen the text. MEANINGLESS on the meta-mailbox transport (F-mailbox): a mailbox ack is enqueue+doorbell, not a turn injection, so steer/follow_up does not apply when the verdict transport is meta-mailbox.",
46
66
  }),
47
- intent: StringEnum(ENTWURF_INTENTS, {
48
- description:
49
- "caller's declared outcome contract (F1). ONE member since the visible-first cut: fire-and-forget = ack only. The second, owned-outcome (caller owns completion), resumed a dormant citizen by launching a hidden background child and was withdrawn — a dormant citizen is currently unreachable by this verb.",
67
+ ),
68
+ wantsReply: opt(
69
+ bool({
70
+ description: "conversation etiquette only — NOT ownership; never triggers an auto-send (Q2).",
50
71
  }),
51
- mode: Type.Optional(
52
- StringEnum(ENTWURF_V2_MODES, {
53
- description:
54
- "delivery mode — NOT the ownership axis (F1) nor liveness routing. steer (ask the receiver's steering queue) or follow_up (ask its follow-up queue). NEITHER is an interrupt: a busy receiver drains steering after each turn and follow-ups only when its inner loop ends, so a later steer overtakes every earlier follow_up and there is no order between the two. Both queues are volatile process memory — an abort drops what is in them. The receipt names which queue accepted the message (`queued-steer` / `queued-follow-up`); an idle receiver answers `sent`, meaning the turn was TRIGGERED, not that the model has seen the text. MEANINGLESS on the meta-mailbox transport (F-mailbox): a mailbox ack is enqueue+doorbell, not a turn injection, so steer/follow_up does not apply when the verdict transport is meta-mailbox.",
55
- }),
56
- ),
57
- wantsReply: Type.Optional(
58
- Type.Boolean({
59
- description: "conversation etiquette only — NOT ownership; never triggers an auto-send (Q2).",
60
- }),
61
- ),
62
- // `additionalProperties: false` — a frozen contract input is exact; an unknown
63
- // key is a caller error, not silently ignored.
64
- },
65
- { additionalProperties: false },
66
- );
72
+ ),
73
+ // `additionalProperties: false` — a frozen contract input is exact; an unknown
74
+ // key is a caller error, not silently ignored. obj() is closed by default (it
75
+ // emits `additionalProperties: false` unless `open: true`).
76
+ });
67
77
 
68
78
  // Receipt = a DISCRIMINATED union on `ok` (R3/F6) — NOT one flat object with
69
79
  // optionals. Each branch is EXACT (`additionalProperties: false`): without it,
@@ -72,35 +82,29 @@ export const EntwurfV2InputSchema = Type.Object(
72
82
  // With it, success carries action/transport/ownership and rejects a stray reason;
73
83
  // reject carries reason and rejects any allow facet — the branches are mutually
74
84
  // exclusive at the schema level, not merely by declared-property convention.
75
- export const EntwurfV2ReceiptSuccessSchema = Type.Object(
76
- {
77
- ok: Type.Literal(true),
78
- action: StringEnum(ENTWURF_V2_ACTIONS),
79
- transport: StringEnum(ENTWURF_V2_TRANSPORTS),
80
- ownership: StringEnum(ENTWURF_V2_OWNERSHIPS),
81
- observedLiveness: StringEnum(FACT_LIVENESSES, {
82
- description: "the 4-value fact liveness the verdict was computed from (R1/R3).",
83
- }),
84
- },
85
- { additionalProperties: false },
86
- );
85
+ export const EntwurfV2ReceiptSuccessSchema = closedObject({
86
+ ok: lit(true),
87
+ action: StringEnum(ENTWURF_V2_ACTIONS),
88
+ transport: StringEnum(ENTWURF_V2_TRANSPORTS),
89
+ ownership: StringEnum(ENTWURF_V2_OWNERSHIPS),
90
+ observedLiveness: StringEnum(FACT_LIVENESSES, {
91
+ description: "the 4-value fact liveness the verdict was computed from (R1/R3).",
92
+ }),
93
+ });
87
94
 
88
- export const EntwurfV2ReceiptRejectSchema = Type.Object(
89
- {
90
- ok: Type.Literal(false),
91
- reason: StringEnum(ENTWURF_V2_REJECT_REASONS),
92
- // ?6: required-nullable, NOT optional — a reject branch ALWAYS carries the
93
- // key, and it is `null` for the pre-probe rejects (PRE_PROBE_REJECT_REASONS)
94
- // and a real FactLiveness otherwise. Optional would lose the "key always
95
- // present, value may be null" shape and weaken the discriminated union; the
96
- // reason-dependent null/non-null rule is enforced semantically (the gate's
97
- // rejectObservedLivenessWellFormed fixture), not by this blanket union.
98
- observedLiveness: Type.Union([StringEnum(FACT_LIVENESSES), Type.Null()], {
99
- description:
100
- "the 4-value fact liveness the reject was computed from (R1/R3); null for the pre-probe rejects (bad-target / target-locked / target-address-conflict) where no probe ran.",
101
- }),
102
- },
103
- { additionalProperties: false },
104
- );
95
+ export const EntwurfV2ReceiptRejectSchema = closedObject({
96
+ ok: lit(false),
97
+ reason: StringEnum(ENTWURF_V2_REJECT_REASONS),
98
+ // ?6: required-nullable, NOT optional — a reject branch ALWAYS carries the
99
+ // key, and it is `null` for the pre-probe rejects (PRE_PROBE_REJECT_REASONS)
100
+ // and a real FactLiveness otherwise. Optional would lose the "key always
101
+ // present, value may be null" shape and weaken the discriminated union; the
102
+ // reason-dependent null/non-null rule is enforced semantically (the gate's
103
+ // rejectObservedLivenessWellFormed fixture), not by this blanket union.
104
+ observedLiveness: union([StringEnum(FACT_LIVENESSES), nul()], {
105
+ description:
106
+ "the 4-value fact liveness the reject was computed from (R1/R3); null for the pre-probe rejects (bad-target / target-locked / target-address-conflict) where no probe ran.",
107
+ }),
108
+ });
105
109
 
106
- export const EntwurfV2ReceiptSchema = Type.Union([EntwurfV2ReceiptSuccessSchema, EntwurfV2ReceiptRejectSchema]);
110
+ export const EntwurfV2ReceiptSchema = union([EntwurfV2ReceiptSuccessSchema, EntwurfV2ReceiptRejectSchema]);
@@ -3,7 +3,7 @@
3
3
  * verb (0.11 Stage 0 step 4-pre / 동결결정 10). PURE pi-FREE core: the
4
4
  * intent×liveness decision table + the reject taxonomy + a pure resolver.
5
5
  * NO runtime dispatch, NO spawn/send, NO I/O — step 5 wires this to transports.
6
- * The pi-ai TypeBox REPRESENTATION of this contract lives in the separate
6
+ * The pi-schema REPRESENTATION of this contract lives in the separate
7
7
  * `entwurf-v2-contract-schema.ts` (0.12.1 B-1) so this module — which the
8
8
  * harness-neutral MCP bridge reaches at boot — carries no pi dependency.
9
9
  *
@@ -516,9 +516,9 @@ export function resolveNativePushDispatch(intent: EntwurfIntent, liveness: Nativ
516
516
  };
517
517
  }
518
518
 
519
- // ── TypeBox schemas ────────────────────────────────────────────────────────
520
- // MOVED to `entwurf-v2-contract-schema.ts` (0.12.1 B-1): the pi-ai TypeBox
521
- // builders (StringEnum/Type) are a pi-lane dependency, so they cannot live in
519
+ // ── JSON schemas ───────────────────────────────────────────────────────────
520
+ // MOVED to `entwurf-v2-contract-schema.ts` (0.12.1 B-1): the pi schema
521
+ // builders (pi-ai StringEnum, pi-schema since #155) are a pi-lane dependency, so they cannot live in
522
522
  // this pi-free core — the MCP bridge reaches this module at boot and must stay
523
523
  // harness-neutral (check-entwurf-bridge-pi-free). The schemas import the
524
524
  // constants/types above; pi-side consumers import the schemas from there.
@@ -79,7 +79,7 @@ export function formatMetaMailboxBody(sender: MailboxSenderEnvelope, message: st
79
79
  // SENDING host, so it is neither abbreviated against this host's HOME nor shown as a local path.
80
80
  const remote = !replyable ? sender.remote : undefined;
81
81
  const sessionLine = replyable
82
- ? `${sender.sessionId} (${kind}replyable — reply via entwurf_v2 to this sessionId, intent=fire-and-forget)`
82
+ ? `${sender.sessionId} (${kind}replyable — when a reply is requested, send the result via entwurf_v2 (intent=fire-and-forget) to the requested recipient, defaulting to this sessionId; a local final is not delivery. Honor routing and no-send instructions; an absent or false wants_reply does not cancel a request. A replyable address alone needs no acknowledgment)`
83
83
  : remote
84
84
  ? `${sender.sessionId} (remote via ${remote.via}; ${remote.replyRoute === "declared" ? "reply route declared, not probed" : "no reply route declared"})`
85
85
  : isMeta
@@ -349,7 +349,7 @@ export interface PiDurableRoots {
349
349
  // ---------------------------------------------------------------------------
350
350
  // Durable surface, typed NARROWLY and locally — read at pi-durable 1.0.2 @cd32f77 (#129,
351
351
  // `harness/types.ts`; historical coordinates, not re-anchored here). The current supply is the
352
- // carrier emitted from Entwurf's owned source (pi/pi-durable/src, #141) against the 1.4.0 SDK set
352
+ // carrier emitted from Entwurf's owned source (pi/pi-durable/src, #141) against the 1.5.2 SDK set
353
353
  // `pi/pi-durable/overlay/upstream-pin.json` names (origin `abe508e`). `[read 2026-10-13]` at
354
354
  // v1.2.0 one shape typed below moved: `ToolExecutionResult.content` is now `output`, beside the new
355
355
  // `structuredOutput` (`harness/types.ts`, its CHANGELOG's breaking changes). An omitted `output`