dsh-ptc-plus 0.3.2 → 0.4.1

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 (199) hide show
  1. package/README.en.md +65 -78
  2. package/README.md +65 -78
  3. package/assets/ptc-plus-bindings-en.png +0 -0
  4. package/assets/ptc-plus-bindings-zh.png +0 -0
  5. package/assets/ptc-plus-repair-en.png +0 -0
  6. package/assets/ptc-plus-repair-zh.png +0 -0
  7. package/assets/ptc-plus-repl-workspace-en.png +0 -0
  8. package/assets/ptc-plus-repl-workspace-zh.png +0 -0
  9. package/assets/ptc-plus-settings-en.png +0 -0
  10. package/assets/ptc-plus-settings-zh.png +0 -0
  11. package/client.js +30596 -742
  12. package/compiler-amaro.cjs +5 -0
  13. package/compiler-amaro.cjs.map +6 -0
  14. package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
  15. package/compiler-assets.json +3 -0
  16. package/compiler-core.cjs +554 -0
  17. package/compiler-core.cjs.map +6 -0
  18. package/compiler-platform.cjs +2 -0
  19. package/compiler-platform.cjs.map +6 -0
  20. package/compiler-typescript.cjs +388 -0
  21. package/compiler-typescript.cjs.map +6 -0
  22. package/docs/README.md +6 -0
  23. package/docs/adr/0004-declare-program-bindings-at-the-host.md +1 -1
  24. package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +5 -3
  25. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +1 -1
  26. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
  27. package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
  28. package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
  29. package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
  30. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +3 -1
  31. package/docs/adr/0014-persist-cell-rewrite-policy.md +6 -2
  32. package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
  33. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  34. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
  35. package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
  36. package/docs/adr/0019-plugin-settings-and-kill-switch.md +5 -3
  37. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  38. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +12 -0
  39. package/docs/adr/0023-global-user-bindings.md +104 -0
  40. package/docs/adr/0024-repl-console-observation.md +33 -0
  41. package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
  42. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
  43. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
  44. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
  45. package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
  46. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
  47. package/docs/architecture.md +73 -21
  48. package/docs/binding-review-placement-design.md +200 -0
  49. package/docs/capability-projection.md +1 -1
  50. package/docs/client-ui.md +95 -19
  51. package/docs/durability-design.md +25 -10
  52. package/docs/evaluation.md +78 -6
  53. package/docs/installation.md +41 -6
  54. package/docs/publishing.md +5 -3
  55. package/docs/runtime-reference.md +72 -25
  56. package/docs/semantic-validation.md +29 -0
  57. package/docs/stateful-binding-updates-proposal.md +755 -0
  58. package/docs/user-bindings.en.md +64 -0
  59. package/docs/user-bindings.md +64 -0
  60. package/docs/value-wire.md +3 -3
  61. package/docs/verification.md +57 -0
  62. package/index.js +369 -92
  63. package/internal/ast-traversal.js +5 -2
  64. package/internal/binding-descriptors.js +1 -0
  65. package/internal/binding-pattern-traversal.js +25 -0
  66. package/internal/binding-pattern.js +26 -29
  67. package/internal/binding-source-analysis.js +391 -0
  68. package/internal/binding-update-policy.js +66 -0
  69. package/internal/bounded-worker-round.js +56 -0
  70. package/internal/callable-definition-environment.js +42 -0
  71. package/internal/callable-source-catalog.js +129 -0
  72. package/internal/callable-source-encoding.js +47 -0
  73. package/internal/callable-source-facts.js +459 -0
  74. package/internal/callable-source-format.js +2 -0
  75. package/internal/callable-source-lowering.js +123 -0
  76. package/internal/cell-analysis-contract.js +25 -0
  77. package/internal/cell-analysis.js +240 -290
  78. package/internal/cell-completion.js +15 -0
  79. package/internal/cell-error.js +7 -0
  80. package/internal/cell-parser.js +66 -0
  81. package/internal/cell-rewriter.js +3 -677
  82. package/internal/commonjs-export-evidence.js +63 -0
  83. package/internal/commonjs-source-evidence.js +72 -0
  84. package/internal/compiler-amaro.cjs +2 -0
  85. package/internal/compiler-compression.js +12 -0
  86. package/internal/compiler-data.js +58 -0
  87. package/internal/compiler-descriptors.js +54 -0
  88. package/internal/compiler-dynamic-cache.js +48 -0
  89. package/internal/compiler-entry.js +35 -0
  90. package/internal/compiler-generated-names.js +17 -0
  91. package/internal/compiler-intrinsics.js +84 -0
  92. package/internal/compiler-module-links.js +34 -0
  93. package/internal/compiler-operations.js +61 -0
  94. package/internal/compiler-platform-factory.js +84 -0
  95. package/internal/compiler-platform.js +136 -0
  96. package/internal/compiler-record-roles.js +53 -0
  97. package/internal/compiler-region-output.js +259 -0
  98. package/internal/compiler-resource-lowering.js +103 -0
  99. package/internal/compiler-scope-facts.js +115 -0
  100. package/internal/compiler-service.js +109 -0
  101. package/internal/compiler-source-cache.js +32 -0
  102. package/internal/compiler-source-pieces.js +93 -0
  103. package/internal/compiler-source-regions.js +132 -0
  104. package/internal/compiler-storage-source.js +54 -0
  105. package/internal/compiler-text.js +15 -0
  106. package/internal/compiler-typescript.cjs +2 -0
  107. package/internal/config-spec.js +83 -27
  108. package/internal/cordis-tools-owner.js +130 -21
  109. package/internal/diagnostic.js +27 -1
  110. package/internal/direct-surface-owner.js +76 -23
  111. package/internal/dynamic-binding-evidence.js +16 -0
  112. package/internal/dynamic-environment-compiler.js +779 -0
  113. package/internal/dynamic-environment-integration.js +87 -0
  114. package/internal/dynamic-environment-runtime.js +362 -0
  115. package/internal/dynamic-native-calls.js +150 -0
  116. package/internal/dynamic-native-runtime.js +248 -0
  117. package/internal/dynamic-scope-analysis.js +90 -0
  118. package/internal/edit-transport-owner.js +27 -2
  119. package/internal/execution-seam-compat.js +193 -0
  120. package/internal/failure-reporting.js +118 -18
  121. package/internal/host-rpc.js +66 -0
  122. package/internal/isolated-worker.js +548 -0
  123. package/internal/kernel-child.js +139 -0
  124. package/internal/kernel-worker.js +904 -120
  125. package/internal/language-semantics.js +14 -0
  126. package/internal/legacy-binding-catalog.js +48 -0
  127. package/internal/legacy-cell-analysis.js +600 -0
  128. package/internal/legacy-cell-parser.js +50 -0
  129. package/internal/legacy-cell-rewriter.js +631 -0
  130. package/internal/legacy-repl-convenience.js +291 -0
  131. package/internal/managed-module-operations.js +170 -0
  132. package/internal/module-compilation.js +321 -0
  133. package/internal/module-runtime-platform.js +19 -0
  134. package/internal/module-transform-contract.js +12 -0
  135. package/internal/native-await-declarations.js +68 -0
  136. package/internal/native-root-compilation.js +54 -0
  137. package/internal/native-root-dynamic.js +67 -0
  138. package/internal/recovery-tips.js +27 -15
  139. package/internal/rejected-cell-editor.js +13 -8
  140. package/internal/repl-convenience.js +2 -289
  141. package/internal/repl-memory-projection.js +81 -12
  142. package/internal/repl-observation-interest.js +70 -0
  143. package/internal/repl-rewrite-contract.js +6 -0
  144. package/internal/repl-scope-normalizer.js +2091 -0
  145. package/internal/repl-value-observer.js +114 -0
  146. package/internal/rpc-contract.js +52 -0
  147. package/internal/runtime-bridge-owner.js +172 -28
  148. package/internal/runtime-config.js +48 -52
  149. package/internal/runtime-contexts.js +82 -10
  150. package/internal/runtime-messages.js +111 -0
  151. package/internal/session-cell-executor.js +401 -87
  152. package/internal/session-journal-recovery.js +684 -0
  153. package/internal/session-journal-schema.js +106 -0
  154. package/internal/session-journal.js +184 -654
  155. package/internal/session-log-view.js +42 -9
  156. package/internal/session-runtime.js +256 -95
  157. package/internal/session-state.js +247 -71
  158. package/internal/source-position-map.js +211 -71
  159. package/internal/stateful-module-compiler.js +154 -0
  160. package/internal/stateful-module-runtime.js +303 -0
  161. package/internal/stateful-root-compiler.js +598 -0
  162. package/internal/stateful-root-runtime.js +340 -0
  163. package/internal/typert-codec-compat.js +28 -0
  164. package/internal/typescript-transform.js +29 -0
  165. package/internal/user-binding-authoring.js +70 -0
  166. package/internal/user-binding-console-worker.js +184 -0
  167. package/internal/user-binding-console.js +148 -0
  168. package/internal/user-binding-draft-projection.js +335 -0
  169. package/internal/user-binding-model-context.js +31 -0
  170. package/internal/user-binding-runner.js +56 -0
  171. package/internal/user-bindings-owner.js +1065 -0
  172. package/internal/user-bindings-store.js +293 -0
  173. package/internal/user-bindings.js +353 -0
  174. package/internal/value-wire-schema.js +28 -0
  175. package/internal/value-wire.js +29 -20
  176. package/internal/worker-client.js +57 -67
  177. package/internal/worker-environment.js +45 -0
  178. package/internal/worker-output-relay.js +29 -0
  179. package/internal/worker-shutdown.js +14 -0
  180. package/package.json +75 -7
  181. package/src/client-activity.js +88 -53
  182. package/src/client-authoring-view.js +580 -0
  183. package/src/client-binding-review.js +230 -0
  184. package/src/client-bindings-data.js +29 -0
  185. package/src/client-catalog.js +127 -0
  186. package/src/client-code-editor.js +84 -0
  187. package/src/client-console.js +112 -0
  188. package/src/client-copy.js +370 -0
  189. package/src/client-feature-gates.js +62 -0
  190. package/src/client-host-compat.js +150 -0
  191. package/src/client-indicator-view.js +267 -0
  192. package/src/client-repl-view.js +292 -0
  193. package/src/client-rpc.js +20 -0
  194. package/src/client-settings-view.js +221 -0
  195. package/src/client-styles.js +106 -0
  196. package/src/client-tool-view.js +108 -0
  197. package/src/client-transport.js +159 -0
  198. package/src/client-workbench.js +521 -0
  199. package/src/client.js +233 -615
@@ -2,12 +2,15 @@ import {
2
2
  decodeValue,
3
3
  encodeValue,
4
4
  normalizeValueWire,
5
- projectValueWire,
5
+ prepareValueWire,
6
6
  valueWiresEqual,
7
7
  } from './value-wire.js'
8
8
  import { diagnostic, renderDiagnostic } from './diagnostic.js'
9
+ import { normalizeBindingDescriptors } from './binding-descriptors.js'
9
10
  import {
10
11
  firstLine,
12
+ cellPosition,
13
+ exceptionOriginPosition,
11
14
  limitLogs,
12
15
  LONG_CELL_CODE_UNITS,
13
16
  markBindingFailure,
@@ -16,15 +19,22 @@ import {
16
19
  oneLineMessage,
17
20
  safeProperty,
18
21
  } from './failure-reporting.js'
19
- import { assertStateName } from './session-journal.js'
20
- import { PreflightError, prepareProgram } from './cell-analysis.js'
21
- import { LIVE_DEFAULT_EXPORT_BINDING } from './repl-rewrite-contract.js'
22
- import { ModuleRewriteError } from './cell-rewriter.js'
22
+ import { assertStateName, LIVE_USER_BINDINGS_REUSE_POLICY } from './session-journal.js'
23
+ import { LIVE_USER_BINDINGS_SHADOW_POLICY, normalizeUserBindingNames } from './session-journal-schema.js'
24
+ import { PreflightError, prepareProgram } from './compiler-service.js'
25
+ import { executionPolicies } from './binding-update-policy.js'
26
+ import { createReplMemorySnapshot } from './repl-memory-projection.js'
27
+ import { ModuleRewriteError } from './cell-error.js'
23
28
  import { mapSourcePosition } from './source-position-map.js'
24
29
  import { durabilityState, transitionDurability } from './session-state.js'
30
+ import { dynamicBindingOrigin } from './dynamic-binding-evidence.js'
25
31
  import { validatedEofClosureRepair } from './validated-parse-repair.js'
32
+ import {
33
+ normalizeUserBindingsSnapshot,
34
+ selectUserBindingsSnapshot,
35
+ } from './user-bindings.js'
26
36
 
27
- const OUTPUT_LIMIT_MESSAGE = bytes => `output exceeded ${bytes} bytes; reduce the returned value or keep it in a REPL binding`
37
+ const OUTPUT_LIMIT_MESSAGE = bytes => `output exceeded ${bytes} bytes; this cell was discarded and the worker reset. Only bindings in the verified recovery frontier can be restored. External effects may already have occurred and are not undone. For future cells, bound console output and return a smaller value; do not assume this cell's values remain available.`
28
38
 
29
39
  function earlyResult(kind, message) {
30
40
  return { logs: [], error: { kind, message } }
@@ -52,16 +62,15 @@ function isBindingReferenceError(error) {
52
62
  || /Cannot access ['"][^'"]+['"] before initialization/.test(error.message))
53
63
  }
54
64
 
65
+ /**
66
+ * Parse failures carry original cell coordinates in ModuleRewriteError.cellPosition; the rewriter
67
+ * owns any generated-source mapping, so the executor never re-applies wrapper offsets.
68
+ */
55
69
  function parseCellPosition(error) {
56
70
  const cellPosition = error instanceof ModuleRewriteError ? error.cellPosition : undefined
57
- const line = Number.isSafeInteger(cellPosition?.line)
58
- ? cellPosition.line
59
- : Number.isSafeInteger(error?.loc?.line) ? error.loc.line - 1 : undefined
60
- const column = Number.isSafeInteger(cellPosition?.column)
61
- ? cellPosition.column
62
- : Number.isSafeInteger(error?.loc?.column) ? error.loc.column + 1 : undefined
63
- return line !== undefined && line >= 1 && column !== undefined
64
- ? { line, column }
71
+ return Number.isSafeInteger(cellPosition?.line) && cellPosition.line >= 1
72
+ && Number.isSafeInteger(cellPosition?.column) && cellPosition.column >= 1
73
+ ? { line: cellPosition.line, column: cellPosition.column }
65
74
  : undefined
66
75
  }
67
76
 
@@ -81,7 +90,7 @@ function parseDiagnostic(error, source, position, repair) {
81
90
  : ['this cell was not executed; correct the reported syntax and retry only this cell with run_code']
82
91
  : [
83
92
  `this cell was not executed; validated syntax repair: append ${JSON.stringify(repair.delimiter)} at the end of this cell`,
84
- `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`,
93
+ `when this correction matches your intent and edit_run_code is declared, prefer the direct tool call ${repair.invocation} with its target guard to run the complete corrected cell; validation proves syntax/preflight acceptance, not intended behavior; otherwise submit corrected source in run_code`,
85
94
  ],
86
95
  })
87
96
  }
@@ -121,6 +130,106 @@ function committedRedeclarationSet(message, prepared) {
121
130
  return committed
122
131
  }
123
132
 
133
+ /** Accept value-free worker facts only for this compilation's logical root. */
134
+ function completedRootBindingFacts(message, prepared, committed) {
135
+ if (prepared.rootRuntimeName === undefined && prepared.rootBindingFacts !== true) return undefined
136
+ if (!Array.isArray(message.rootBindingFacts)) throw new TypeError('kernel returned invalid root binding facts')
137
+ const established = new Map(prepared.rootBindings.established)
138
+ const initialized = new Set(prepared.declarations.filter(declaration => committed.has(declaration.commitDependency)
139
+ && (prepared.languageSemantics !== 'legacy-v1' || established.has(declaration.name)))
140
+ .map(declaration => declaration.name))
141
+ const candidates = new Map(prepared.rootBindings.candidates)
142
+ const dynamicOrigins = new Set(prepared.rootBindings.dynamicOrigins)
143
+ const available = new Set([...prepared.rootBindings.known, ...established.keys(), ...initialized])
144
+ const allowed = new Set([...available, ...candidates.values()])
145
+ const required = new Set([...established.keys(), ...initialized])
146
+ const imports = new Set([...prepared.rootBindings.legacyImports.map(([name]) => name),
147
+ ...[...established].filter(([, source]) => source === 'import').map(([name]) => name),
148
+ ...prepared.declarations.filter(declaration => declaration.kind === 'import' && committed.has(declaration.commitDependency))
149
+ .map(declaration => declaration.name)])
150
+ const seen = new Set()
151
+ const facts = message.rootBindingFacts.map(fact => {
152
+ const dynamicOrigin = dynamicBindingOrigin(fact?.write, fact?.name, dynamicOrigins)
153
+ if (fact === null || typeof fact !== 'object' || Array.isArray(fact)
154
+ || Object.keys(fact).length !== (Object.hasOwn(fact, 'write') ? 3 : 2)
155
+ || !Object.hasOwn(fact, 'name') || !Object.hasOwn(fact, 'source')
156
+ || typeof fact.name !== 'string' || !allowed.has(fact.name) && dynamicOrigin === undefined || seen.has(fact.name)
157
+ || !['local', 'import', 'absent'].includes(fact.source)
158
+ || Object.hasOwn(fact, 'write') && (typeof fact.write !== 'string'
159
+ || candidates.get(fact.write) !== fact.name && dynamicOrigin === undefined || fact.source === 'import')
160
+ || !available.has(fact.name) && !Object.hasOwn(fact, 'write')
161
+ || fact.source === 'import' && !imports.has(fact.name)
162
+ || fact.source === 'absent' && initialized.has(fact.name)) throw new TypeError('kernel returned invalid root binding facts')
163
+ seen.add(fact.name)
164
+ required.delete(fact.name)
165
+ return { name: fact.name, source: fact.source, ...(Object.hasOwn(fact, 'write') ? { write: fact.write } : {}) }
166
+ })
167
+ if (required.size !== 0) throw new TypeError('kernel returned incomplete root binding facts')
168
+ return facts
169
+ }
170
+
171
+ function completedUserBindingNames(active, message, snapshot) {
172
+ const facts = normalizeUserBindingNames(message.userBindingNames)
173
+ const required = active.userBindingBaseCatalog.userBindingNameSet(snapshot)
174
+ const allowed = active.userBindingBaseCatalog.userBindingNameSet(snapshot, true)
175
+ const providers = new Map((snapshot?.entries ?? []).flatMap(entry => (
176
+ (entry.scope === 'namespace' ? [entry.name] : entry.symbols).map(name => [name, entry.id])
177
+ )))
178
+ for (const fact of facts) {
179
+ if (!allowed.has(fact.name) || (fact.state === 'provider'
180
+ && (providers.get(fact.name) !== fact.entryId || active.request.bindingDescriptors.reservedNames.has(fact.name)))) {
181
+ throw new TypeError('kernel returned user binding name evidence with invalid ownership')
182
+ }
183
+ required.delete(fact.name)
184
+ }
185
+ if (required.size !== 0) throw new TypeError('kernel returned incomplete user binding name evidence')
186
+ if (active.replay !== undefined && JSON.stringify(facts) !== JSON.stringify(active.replay.userBindingNames)) {
187
+ throw new TypeError('cell replay produced different user binding name evidence')
188
+ }
189
+ return facts
190
+ }
191
+
192
+ function validateUserBindingActivation(active, message, ids) {
193
+ if (!Array.isArray(message.userBindingFailures)) throw new TypeError('kernel returned invalid user binding failures')
194
+ const expected = new Set(active.userBindings.entries.map(entry => entry.id))
195
+ for (const id of ids) expected.delete(id)
196
+ for (const failure of message.userBindingFailures) {
197
+ if (failure === null || typeof failure !== 'object' || Object.keys(failure).length !== 2
198
+ || typeof failure.error !== 'string' || !expected.delete(failure.id)) {
199
+ throw new TypeError('kernel returned invalid user binding failures')
200
+ }
201
+ }
202
+ if (expected.size !== 0) throw new TypeError('kernel omitted user binding activation outcomes')
203
+ if ([...ids].some(id => active.userBindingFailures.some(failure => failure.id === id))) {
204
+ throw new TypeError('kernel activated a request-owned user binding name')
205
+ }
206
+ }
207
+
208
+ /** A reserved program binding (`tools` and the injected error classes) is not shadowable at the
209
+ * session root, so a collision on it is not a repeated declaration. The collision producer records
210
+ * that reason, and rendering the recorded reason keeps "top-level bindings already exist" from
211
+ * implying the cell redeclared a name the session never created. */
212
+ const RESERVED_BINDING_REASON = 'reserved-program-binding-not-shadowable'
213
+ /** Reasons whose only remaining guidance is to reuse the binding already in session state. */
214
+ const REUSE_BINDING_REASONS = new Set([
215
+ 'variable-redeclarations-disabled', 'redeclaration-splitting-disabled', 'protected-root-redeclaration',
216
+ 'import-binding-redeclaration', RESERVED_BINDING_REASON,
217
+ ])
218
+
219
+ function reservedBindingNote(collisions) {
220
+ const reserved = [...new Set(collisions.filter(collision => collision.reason === RESERVED_BINDING_REASON)
221
+ .map(collision => collision.name))]
222
+ return reserved.length === 0
223
+ ? ''
224
+ : ` ${reserved.join(', ')} cannot be redeclared or overwritten because reserved program bindings are not shadowable.`
225
+ }
226
+
227
+ /** The model-visible structured form of every recorded collision, reduced to public fields. */
228
+ function collisionRecords(collisions) {
229
+ return collisions.map(collision => ({ name: collision.name, kind: collision.kind,
230
+ reason: collision.reason, start: collision.start, end: collision.end }))
231
+ }
232
+
124
233
  function collisionDiagnostic(collisions) {
125
234
  const names = [...new Set(collisions.map(item => item.name))]
126
235
  const reasons = new Set(collisions.map(item => item.reason).filter(Boolean))
@@ -136,7 +245,7 @@ function collisionDiagnostic(collisions) {
136
245
  : disabledKinds.has('class')
137
246
  ? ['assign a class expression to the existing writable binding']
138
247
  : []),
139
- ...((reasons.size === 0 || reasons.has('variable-redeclarations-disabled'))
248
+ ...((reasons.size === 0 || [...reasons].some(reason => REUSE_BINDING_REASONS.has(reason)))
140
249
  ? ['reuse the existing bindings']
141
250
  : []),
142
251
  ]
@@ -147,14 +256,16 @@ function collisionDiagnostic(collisions) {
147
256
  ...(alternatives.length === 0 ? [] : [alternatives.join('; ')]),
148
257
  'place one-off declarations inside a block',
149
258
  ]
259
+ const reservedNote = reservedBindingNote(collisions)
150
260
  return diagnostic({
151
261
  code: 'PTC-N001',
152
262
  severity: 'error',
153
263
  phase: 'preflight',
154
- message: `top-level bindings already exist: ${names.join(', ')}. This cell was not executed; the REPL state is unchanged.`,
264
+ message: `top-level bindings already exist: ${names.join(', ')}.${reservedNote} This cell was not executed; the REPL state is unchanged.`,
155
265
  stateEffect: 'unchanged',
156
266
  source: { cell: 'current', start: first.start, end: first.end },
157
267
  help,
268
+ collisions: collisionRecords(collisions),
158
269
  })
159
270
  }
160
271
 
@@ -164,6 +275,8 @@ function exceptionDiagnostic({
164
275
  position,
165
276
  declared,
166
277
  longCellFailure = false,
278
+ failureOrigin,
279
+ languageSemantics = 'legacy-v1',
167
280
  }) {
168
281
  const missingPath = error.name === 'ToolCallError' ? missingDescriptionPath(error) : undefined
169
282
  const missingDescription = missingPath !== undefined
@@ -188,20 +301,24 @@ function exceptionDiagnostic({
188
301
  ...(cause === undefined ? {} : { cause }),
189
302
  ...(source === undefined ? {} : { source }),
190
303
  help: [
191
- ...(missingDescription
192
- ? ['add a string `description` property to the nested native-tool argument object']
193
- : ['inspect existing bindings and retry only the failing expression']),
304
+ 'earlier statements or the failing operation may have caused effects; choose continuation using the operation owner\'s retry/idempotence contract and available execution facts; a thrown call or recorded-value replay does not prove a live retry is safe',
305
+ ...(failureOrigin === 'lease'
306
+ ? ['this REPL-captured capability reference belongs to an ended cell; obtain the member from the current program namespace, and have retained helpers resolve it at invocation']
307
+ : failureOrigin === 'capability'
308
+ ? ['inspect the current capability with capabilities.tree(), capabilities.find(), or capabilities.inspect(); availability may have changed']
309
+ : missingDescription
310
+ ? ['add a string `description` property to the nested native-tool argument object']
311
+ : []),
194
312
  ...(error.name === 'ToolCallError'
195
313
  && typeof error.toolName === 'string'
196
314
  && error.toolName.startsWith('cordis_')
197
315
  ? ['bindings assigned before this Cordis failure remain live; reuse them instead of resending large source']
198
- : []),
199
- ...(longCellFailure
200
- ? ['execution may have occurred; inspect live state in a new short `run_code` cell before deciding whether a correction is safe']
201
- : []),
202
- ...(declared.size === 0 || longCellFailure
203
- ? []
204
- : ['use fresh names for one-off top-level bindings after partial execution; later declarations may be uninitialized']),
316
+ : longCellFailure
317
+ ? ['inspect relevant live state in a new short `run_code` cell; edit_run_code executes the complete corrected cell, not only the failing expression']
318
+ : languageSemantics !== 'legacy-v1'
319
+ ? ['completed declarations and actual assignments remain available; correct the failed initializer and continue with the same binding names']
320
+ : declared.size === 0 ? []
321
+ : ['use fresh names for one-off top-level bindings after partial execution; later declarations may be uninitialized']),
205
322
  ],
206
323
  })
207
324
  }
@@ -214,12 +331,18 @@ function invalidOutputDiagnostic(detail) {
214
331
  message: `cell result could not cross the PTC Value V1 boundary: ${firstLine(detail, 'unknown output encoding failure')}`,
215
332
  stateEffect: 'partially-applied',
216
333
  help: [
217
- 'return a PTC Value V1 value or keep the live value in a REPL binding',
334
+ 'the worker remains live; inspect a retained binding in a later cell without rerunning the failed cell',
218
335
  'reduce the returned graph when it exceeds the configured value budget',
219
336
  ],
220
337
  })
221
338
  }
222
339
 
340
+ /** Retain validated recorded wording while still comparing the actual failure. */
341
+ function replayFailureDiagnostic(actual, replay) {
342
+ const recorded = replay?.diagnostics?.find(item => item.code === actual.code)
343
+ return recorded?.message === actual.message ? recorded : actual
344
+ }
345
+
223
346
  function stateArguments(value) {
224
347
  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
225
348
  throw new TypeError('repl.state expects an object')
@@ -253,30 +376,61 @@ export class SessionCellExecutor {
253
376
  return result
254
377
  }
255
378
 
256
- const catalog = kernel.bindingCatalog.inputs()
257
- const bindingPolicy = replayRecord === undefined ? {
258
- variableRedeclarations: config.looseTopLevelRedeclarations,
259
- functionClassRedeclarations: config.looseTopLevelFunctionClassRedeclarations,
260
- } : replayRecord.bindingPolicy
261
- const rewritesEnabled = replayRecord === undefined ? {
262
- autoRewriteImports: config.autoRewriteImports,
263
- autoStripExports: config.autoStripExports,
264
- autoSplitRedeclarations: config.autoSplitRedeclarations,
265
- } : replayRecord.rewritePolicy
266
- const moduleSemantics = replayRecord === undefined ? {
267
- defaultExportBinding: LIVE_DEFAULT_EXPORT_BINDING,
268
- } : replayRecord.moduleSemantics
269
- const prepareCell = program => prepareProgram(
270
- program,
271
- catalog.knownBindings,
379
+ let userBindings
380
+ const userBindingsShadowPolicy = replayRecord?.userBindingsShadowPolicy ?? LIVE_USER_BINDINGS_SHADOW_POLICY
381
+ const perNameUserBindings = userBindingsShadowPolicy === LIVE_USER_BINDINGS_SHADOW_POLICY
382
+ let userBindingBaseCatalog = kernel.bindingCatalog
383
+ let userBindingPlan
384
+ let userBindingFailures = []
385
+ try {
386
+ userBindings = request.userBindings === undefined
387
+ ? undefined
388
+ : normalizeUserBindingsSnapshot(request.userBindings)
389
+ if (userBindings === undefined) {
390
+ const catalog = kernel.bindingCatalog.withoutUserBindings()
391
+ userBindingPlan = perNameUserBindings ? kernel.bindingCatalog.userBindings(undefined, undefined, userBindingsShadowPolicy) : {
392
+ catalog,
393
+ shadowedNames: catalog.inputs().knownBindings,
394
+ }
395
+ } else {
396
+ const activeEntryIds = new Set()
397
+ for (const entry of userBindings.entries) {
398
+ const names = entry.scope === 'namespace' ? [entry.name] : entry.symbols
399
+ const conflict = names.find(name => request.bindingDescriptors.reservedNames.has(name))
400
+ if (conflict === undefined) activeEntryIds.add(entry.id)
401
+ else {
402
+ userBindingFailures.push(Object.freeze({
403
+ id: entry.id,
404
+ error: `conflicts with request-owned program binding ${JSON.stringify(conflict)}`,
405
+ }))
406
+ }
407
+ }
408
+ userBindingPlan = kernel.bindingCatalog.userBindings(userBindings, activeEntryIds, userBindingsShadowPolicy)
409
+ }
410
+ } catch (error) {
411
+ const result = earlyResult('exception', `invalid user binding snapshot: ${messageOf(error)}`)
412
+ kernel.completeJournal(request.journal, 'noop', result)
413
+ return result
414
+ }
415
+ const priorBindingCatalog = userBindingPlan.catalog
416
+ const catalog = priorBindingCatalog.inputs()
417
+ const { bindingPolicy, rewritesEnabled, moduleSemantics, languageSemantics } = executionPolicies(config, replayRecord)
418
+ const prepareCell = program => prepareProgram(program, {
419
+ knownBindings: catalog.knownBindings,
272
420
  bindingPolicy,
273
- request.bindingDescriptors.reservedNames,
421
+ languageSemantics,
422
+ reservedBindings: request.bindingDescriptors.reservedNames,
274
423
  rewritesEnabled,
275
- catalog.importBindings,
276
- catalog.importNamespaces,
277
- catalog.writableBindings,
424
+ importBindings: catalog.importBindings,
425
+ importNamespaces: catalog.importNamespaces,
426
+ writableBindings: catalog.writableBindings,
427
+ nativeBindings: catalog.nativeBindings,
428
+ nativeLexicalBindings: catalog.nativeLexicalBindings,
429
+ rootCandidates: catalog.rootCandidates,
430
+ dynamicOrigins: catalog.dynamicOrigins,
431
+ establishedRoots: catalog.establishedRoots,
278
432
  moduleSemantics,
279
- )
433
+ })
280
434
  let prepared
281
435
  try {
282
436
  prepared = prepareCell(request.program)
@@ -322,7 +476,9 @@ export class SessionCellExecutor {
322
476
  const result = earlyResult('abort', String(request.signal.reason))
323
477
  kernel.completeJournal(request.journal, 'discarded', result)
324
478
  kernel.rollbackToDurable()
325
- void kernel.client.reset(worker)
479
+ // A refused reclamation stays recorded on the kernel so disposal remains
480
+ // observable instead of becoming an unhandled rejection after this abort.
481
+ kernel.resetWorker(worker)
326
482
  return result
327
483
  }
328
484
 
@@ -332,9 +488,9 @@ export class SessionCellExecutor {
332
488
  const bindings = this.withControlBinding(request.bindingDescriptors, journal, replayRecord)
333
489
  const id = ++kernel.sequence
334
490
  return new Promise((resolve) => {
335
- const started = worker.performance.eventLoopUtilization()
336
491
  const active = {
337
492
  id,
493
+ started: false,
338
494
  request: { ...request, bindings: bindings.namespaces },
339
495
  finish: resolve,
340
496
  computeTimer: undefined,
@@ -363,41 +519,111 @@ export class SessionCellExecutor {
363
519
  control: { names: new Set(kernel.history.checkpoints.keys()) },
364
520
  rewrites: prepared.rewrites,
365
521
  prepared,
366
- priorBindingCatalog: kernel.bindingCatalog,
522
+ priorBindingCatalog,
523
+ userBindingBaseCatalog,
524
+ userBindings,
525
+ userBindingsShadowPolicy,
526
+ userBindingFailures,
527
+ userBindingSnapshot: userBindings,
367
528
  worker,
529
+ settled: false,
368
530
  }
369
531
  active.resolve = (result, terminate = false) => kernel.settleCell(active, result, terminate)
370
532
  active.onAbort = () => active.resolve(
371
533
  earlyResult('abort', String(request.signal?.reason)),
372
534
  true,
373
535
  )
374
- active.computeTimer = setInterval(() => {
375
- if (worker.performance.eventLoopUtilization(started).active > config.computeMs) {
376
- active.resolve(earlyResult('timeout', `compute budget exhausted (${config.computeMs}ms busy); split the work into smaller cells`), true)
536
+ active.budgetsStarted = false
537
+ active.startBudgets = () => {
538
+ if (active.budgetsStarted) return active.budgetsReady
539
+ active.budgetsStarted = true
540
+ // The wall bound starts at the request, while the compute baseline is
541
+ // sampled from the worker instead of reusing the helper's last periodic
542
+ // cache. Work that completed before this request must not be charged to
543
+ // the new cell, and work after the sample must remain counted.
544
+ active.wallTimer = setTimeout(() => {
545
+ active.resolve(earlyResult('timeout', `wall-clock ceiling reached (${config.maxWallMs}ms); split long-running work into smaller cells`), true)
546
+ }, config.maxWallMs)
547
+ active.budgetsReady = (async () => {
548
+ let started
549
+ try {
550
+ started = await worker.sampleUtilization()
551
+ } catch (error) {
552
+ // No fresh baseline means this request cannot be measured. Use the
553
+ // existing worker-exit route instead of pretending the last cache
554
+ // sample is this request's starting point.
555
+ active.resolve(earlyResult('worker-exit', messageOf(error)), true)
556
+ return
557
+ }
558
+ if (active.settled) return
559
+ active.computeTimer = setInterval(() => {
560
+ if (active.settled) return
561
+ if (worker.performance.eventLoopUtilization(started).active > config.computeMs) {
562
+ active.resolve(earlyResult('timeout', `compute budget exhausted (${config.computeMs}ms event-loop active, including synchronous blocking); this measure does not establish CPU use or the specific cause. Use asynchronous waiting for long waits, or a DSH-owned managed job if available. This cell was discarded and the worker reset; external effects or processes may continue.`), true)
563
+ }
564
+ }, Math.min(100, config.computeMs))
565
+ })()
566
+ return active.budgetsReady
567
+ }
568
+ active.start = async () => {
569
+ if (active.started) return
570
+ active.started = true
571
+ kernel.workerObservation = undefined
572
+ await active.startBudgets()
573
+ if (active.settled) return
574
+ try {
575
+ kernel.client.post({
576
+ type: 'run', id, program: prepared.code, namespaces: bindings.workerDescriptors,
577
+ moduleLoads: prepared.moduleLoads,
578
+ languageSemantics,
579
+ rootRuntimeName: prepared.rootRuntimeName,
580
+ rootBindings: prepared.rootBindings,
581
+ importBindingNamespaces: new Map([...catalog.importBindings].map(([name, binding]) => [name, binding.namespace])),
582
+ preparedImportBindingNamespaces: new Map([...prepared.imports].map(([name, binding]) => [name, binding.namespace])),
583
+ returnSignal: prepared.returnSignal,
584
+ asyncCompletion: prepared.asyncCompletion,
585
+ commitSignal: prepared.commitSignal,
586
+ maxOutputBytes: config.maxOutputBytes,
587
+ valueLimits,
588
+ durability,
589
+ observeNames: replayRecord === undefined && kernel.observeValues()
590
+ ? createReplMemorySnapshot(
591
+ priorBindingCatalog.advance(prepared, request.program).snapshot(),
592
+ ).entries.map(entry => entry.name)
593
+ : [],
594
+ userBindings,
595
+ userBindingsShadowPolicy,
596
+ userBindingsReusePolicy: replayRecord === undefined
597
+ ? LIVE_USER_BINDINGS_REUSE_POLICY
598
+ : replayRecord.userBindingsReusePolicy,
599
+ ...(userBindings === undefined ? {} : { userBindingsCwd: kernel.userBindingsCwd }),
600
+ userBindingFailures,
601
+ shadowedUserBindingNames: [...new Set([
602
+ ...userBindingPlan.shadowedNames,
603
+ ...request.bindingDescriptors.reservedNames,
604
+ ])],
605
+ })
606
+ } catch (error) {
607
+ active.resolve(earlyResult('worker-exit', messageOf(error)), true)
377
608
  }
378
- }, Math.min(100, config.computeMs))
379
- active.wallTimer = setTimeout(() => {
380
- active.resolve(earlyResult('timeout', `wall-clock ceiling reached (${config.maxWallMs}ms); split long-running work into smaller cells`), true)
381
- }, config.maxWallMs)
609
+ }
382
610
  kernel.active = active
383
611
  request.signal?.addEventListener('abort', active.onAbort, { once: true })
384
612
  if (request.signal?.aborted) {
385
613
  active.onAbort()
386
614
  return
387
615
  }
388
- try {
389
- kernel.client.post({
390
- type: 'run', id, program: prepared.code, namespaces: bindings.workerDescriptors,
391
- moduleLoads: prepared.moduleLoads,
392
- returnSignal: prepared.returnSignal,
393
- commitSignal: prepared.commitSignal,
394
- maxOutputBytes: config.maxOutputBytes,
395
- valueLimits,
396
- durability,
397
- })
398
- } catch (error) {
399
- active.resolve(earlyResult('worker-exit', messageOf(error)), true)
400
- }
616
+ void (async () => {
617
+ try {
618
+ // Only worker-confirmed observation may defer these budgets; never restart them.
619
+ if (kernel.workerObservation?.worker !== worker
620
+ || kernel.workerObservation.started !== true) await active.startBudgets()
621
+ if (active.settled) return
622
+ kernel.client.post({ type: 'prepare', id })
623
+ } catch (error) {
624
+ active.resolve(earlyResult('worker-exit', messageOf(error)), true)
625
+ }
626
+ })()
401
627
  })
402
628
  }
403
629
 
@@ -418,7 +644,7 @@ export class SessionCellExecutor {
418
644
  reservedNames: bindingDescriptors.reservedNames,
419
645
  workerDescriptors: Object.freeze([
420
646
  ...bindingDescriptors.workerDescriptors,
421
- Object.freeze({ global: 'repl', members: namespace.members }),
647
+ ...normalizeBindingDescriptors([namespace]).workerDescriptors,
422
648
  ]),
423
649
  })
424
650
  }
@@ -456,6 +682,30 @@ export class SessionCellExecutor {
456
682
  }
457
683
 
458
684
  onMessage(message) {
685
+ if (message?.type === 'observation-started') {
686
+ const kernel = this.kernel
687
+ const observation = kernel.workerObservation
688
+ if (observation !== undefined && observation.id === message.id
689
+ && observation.worker === kernel.client.worker) observation.started = true
690
+ return
691
+ }
692
+ if (message?.type === 'ready') {
693
+ const inspection = this.kernel.pendingInspection
694
+ if (inspection !== undefined && inspection.id === message.id) inspection.start()
695
+ const active = this.kernel.active
696
+ if (active !== undefined && active.id === message.id) void active.start()
697
+ return
698
+ }
699
+ if (message?.type === 'observation') {
700
+ const kernel = this.kernel
701
+ const observation = kernel.workerObservation
702
+ if (observation !== undefined && observation.id === message.id) {
703
+ kernel.workerObservation = undefined
704
+ if (kernel.active?.worker === observation.worker) kernel.active.startBudgets()
705
+ }
706
+ kernel.finishObservation(message)
707
+ return
708
+ }
459
709
  if (message === null || typeof message !== 'object') return
460
710
  const handler = {
461
711
  volatile: this.handleVolatile,
@@ -515,6 +765,53 @@ export class SessionCellExecutor {
515
765
  active.resolve({ logs, error: { kind: 'recovery', message: 'session log replay consumed a different host-call transcript' } }, true)
516
766
  return
517
767
  }
768
+ let activatedUserBindings
769
+ const perNameUserBindings = active.userBindingsShadowPolicy === LIVE_USER_BINDINGS_SHADOW_POLICY
770
+ try {
771
+ if (active.userBindings === undefined) {
772
+ active.userBindingSnapshot = undefined
773
+ active.priorBindingCatalog = active.userBindingBaseCatalog.withoutUserBindings()
774
+ } else {
775
+ const ids = Array.isArray(message.activatedUserBindings)
776
+ && message.activatedUserBindings.every(id => typeof id === 'string')
777
+ ? new Set(message.activatedUserBindings)
778
+ : undefined
779
+ if (ids === undefined || ids.size !== message.activatedUserBindings.length) {
780
+ throw new TypeError('kernel returned an invalid activated user binding set')
781
+ }
782
+ const expected = new Set(active.userBindings.entries.map(entry => entry.id))
783
+ if ([...ids].some(id => !expected.has(id))) {
784
+ throw new TypeError('kernel activated an unknown user binding entry')
785
+ }
786
+ if (perNameUserBindings) validateUserBindingActivation(active, message, ids)
787
+ activatedUserBindings = selectUserBindingsSnapshot(active.userBindings, ids)
788
+ active.userBindingSnapshot = activatedUserBindings
789
+ active.priorBindingCatalog = active.userBindingBaseCatalog.userBindings(active.userBindings, ids, active.userBindingsShadowPolicy).catalog
790
+ if (!perNameUserBindings) {
791
+ const shadowed = Array.isArray(message.shadowedUserBindings)
792
+ && message.shadowedUserBindings.every(name => typeof name === 'string')
793
+ ? new Set(message.shadowedUserBindings)
794
+ : undefined
795
+ const activeNames = new Set(activatedUserBindings.entries.flatMap(entry => (
796
+ entry.scope === 'namespace' ? [entry.name] : entry.symbols
797
+ )))
798
+ if (shadowed === undefined || shadowed.size !== message.shadowedUserBindings.length
799
+ || [...shadowed].some(name => !activeNames.has(name))) {
800
+ throw new TypeError('kernel returned an invalid shadowed user binding set')
801
+ }
802
+ active.priorBindingCatalog = active.priorBindingCatalog.shadowUserBindings(shadowed)
803
+ }
804
+ if (active.replay !== undefined
805
+ && activatedUserBindings.fingerprint !== active.userBindings.fingerprint) {
806
+ active.resolve({ logs, error: { kind: 'recovery', message: 'recorded user bindings could not be reactivated' } }, true)
807
+ return
808
+ }
809
+ }
810
+ if (perNameUserBindings) active.userBindingNames = completedUserBindingNames(active, message, activatedUserBindings)
811
+ } catch (error) {
812
+ active.resolve(earlyResult('worker-exit', messageOf(error)), true)
813
+ return
814
+ }
518
815
  const bytes = Buffer.byteLength(JSON.stringify({ logs, value: message.value }), 'utf8')
519
816
  if (bytes > active.config.maxOutputBytes) {
520
817
  active.resolve({
@@ -524,8 +821,10 @@ export class SessionCellExecutor {
524
821
  return
525
822
  }
526
823
  let committed
824
+ let rootBindingFacts
527
825
  try {
528
826
  committed = committedRedeclarationSet(message, active.prepared)
827
+ rootBindingFacts = completedRootBindingFacts(message, active.prepared, committed)
529
828
  } catch (error) {
530
829
  active.resolve(earlyResult('worker-exit', messageOf(error)), true)
531
830
  return
@@ -536,7 +835,14 @@ export class SessionCellExecutor {
536
835
  active.prepared,
537
836
  active.request.program,
538
837
  committed,
838
+ rootBindingFacts,
539
839
  )
840
+ if (perNameUserBindings) {
841
+ active.appliedBindingCatalog = active.appliedBindingCatalog.reconcileUserBindingNames(
842
+ activatedUserBindings, active.userBindingNames, active.request.program,
843
+ )
844
+ }
845
+ active.observing = message.observing === true
540
846
  if (typeof message.error === 'string') {
541
847
  const rawError = {
542
848
  kind: 'exception',
@@ -547,30 +853,38 @@ export class SessionCellExecutor {
547
853
  const actualFailure = exceptionDiagnostic({
548
854
  error: rawError,
549
855
  cause: message.cause,
856
+ // A diagnostic caret must exist in the submitted cell. A native frame
857
+ // is mapped first; an unmappable frame falls back to the recorded
858
+ // source origin rather than attaching an out-of-range position.
550
859
  position: message.moduleLoadFailed === true
551
- ? message.position
552
- : mapSourcePosition(
553
- message.position,
554
- active.prepared.code,
860
+ ? cellPosition(message.position, active.request.program)
861
+ : cellPosition(
862
+ mapSourcePosition(
863
+ message.position,
864
+ active.prepared.code,
865
+ active.request.program,
866
+ active.prepared.sourceMap,
867
+ ),
555
868
  active.request.program,
556
- active.prepared.sourceMap,
557
- ),
869
+ ) ?? exceptionOriginPosition(message.exceptionOrigins, active.request.program),
558
870
  declared: message.moduleLoadFailed === true ? new Set() : active.prepared.declared,
871
+ languageSemantics: active.prepared.languageSemantics,
559
872
  longCellFailure: active.request.program.length >= LONG_CELL_CODE_UNITS,
873
+ failureOrigin: message.failureOrigin,
560
874
  })
561
- const recordedFailure = active.replay?.diagnostics?.find(item => item.code === 'PTC-X001')
562
- const failure = recordedFailure?.message === actualFailure.message ? recordedFailure : actualFailure
875
+ const failure = replayFailureDiagnostic(actualFailure, active.replay)
563
876
  const error = {
564
877
  kind: 'exception',
565
878
  message: renderDiagnostic(failure, active.request.program),
566
879
  }
567
880
  if (isBindingReferenceError(rawError)) markBindingFailure(error)
881
+ else if (message.failureOrigin === 'capability') markBindingFailure(error, 'capability')
568
882
  active.diagnostics.push(failure)
569
883
  active.resolve({ logs, error })
570
884
  return
571
885
  }
572
886
  if (typeof message.invalidOutput === 'string') {
573
- const failure = invalidOutputDiagnostic(message.invalidOutput)
887
+ const failure = replayFailureDiagnostic(invalidOutputDiagnostic(message.invalidOutput), active.replay)
574
888
  const error = { kind: 'invalid-output', message: renderDiagnostic(failure, active.request.program) }
575
889
  active.diagnostics.push(failure)
576
890
  active.resolve({ logs, error })
@@ -581,23 +895,23 @@ export class SessionCellExecutor {
581
895
  || (message.hasValue ? message.value === undefined : message.value !== undefined)) {
582
896
  throw new TypeError('invalid PTC completion envelope')
583
897
  }
584
- const value = message.hasValue ? normalizeValueWire(message.value, active.valueLimits) : undefined
898
+ const completion = message.hasValue ? prepareValueWire(message.value, active.valueLimits) : undefined
585
899
  active.completion = {
586
900
  hasValue: message.hasValue,
587
- ...(message.hasValue ? { value } : {}),
901
+ ...(message.hasValue ? { value: completion.wire } : {}),
588
902
  }
589
903
  if (active.replay?.completion?.kind === 'return'
590
904
  && (active.replay.completion.hasValue !== message.hasValue
591
- || (message.hasValue && !valueWiresEqual(active.replay.completion.value, value, active.valueLimits)))) {
905
+ || (message.hasValue && !valueWiresEqual(active.replay.completion.value, completion.wire, active.valueLimits)))) {
592
906
  active.resolve({ logs, error: { kind: 'recovery', message: 'cell replay produced a different completion value' } }, true)
593
907
  return
594
908
  }
595
909
  active.resolve({
596
910
  logs,
597
- ...(message.hasValue ? { value: projectValueWire(value, active.valueLimits) } : {}),
911
+ ...(message.hasValue ? { value: completion.projectedValue } : {}),
598
912
  })
599
913
  } catch (error) {
600
- const failure = invalidOutputDiagnostic(messageOf(error))
914
+ const failure = replayFailureDiagnostic(invalidOutputDiagnostic(messageOf(error)), active.replay)
601
915
  const invalid = { kind: 'invalid-output', message: renderDiagnostic(failure, active.request.program) }
602
916
  active.diagnostics.push(failure)
603
917
  active.resolve({ logs, error: invalid })