dsh-ptc-plus 0.3.1 → 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 (200) hide show
  1. package/README.en.md +66 -73
  2. package/README.md +66 -73
  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 +30610 -722
  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 +6 -4
  25. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +15 -3
  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 +9 -3
  31. package/docs/adr/0014-persist-cell-rewrite-policy.md +11 -5
  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 +8 -6
  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 +17 -3
  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 +82 -22
  48. package/docs/binding-review-placement-design.md +200 -0
  49. package/docs/capability-projection.md +1 -1
  50. package/docs/client-ui.md +96 -20
  51. package/docs/durability-design.md +61 -24
  52. package/docs/evaluation.md +78 -6
  53. package/docs/installation.md +41 -6
  54. package/docs/program-data-plane.md +6 -0
  55. package/docs/publishing.md +5 -3
  56. package/docs/runtime-reference.md +79 -25
  57. package/docs/semantic-validation.md +29 -0
  58. package/docs/stateful-binding-updates-proposal.md +755 -0
  59. package/docs/user-bindings.en.md +64 -0
  60. package/docs/user-bindings.md +64 -0
  61. package/docs/value-wire.md +3 -3
  62. package/docs/verification.md +57 -0
  63. package/index.js +374 -92
  64. package/internal/ast-traversal.js +5 -2
  65. package/internal/binding-descriptors.js +1 -0
  66. package/internal/binding-pattern-traversal.js +25 -0
  67. package/internal/binding-pattern.js +26 -29
  68. package/internal/binding-source-analysis.js +391 -0
  69. package/internal/binding-update-policy.js +66 -0
  70. package/internal/bounded-worker-round.js +56 -0
  71. package/internal/callable-definition-environment.js +42 -0
  72. package/internal/callable-source-catalog.js +129 -0
  73. package/internal/callable-source-encoding.js +47 -0
  74. package/internal/callable-source-facts.js +459 -0
  75. package/internal/callable-source-format.js +2 -0
  76. package/internal/callable-source-lowering.js +123 -0
  77. package/internal/cell-analysis-contract.js +25 -0
  78. package/internal/cell-analysis.js +252 -242
  79. package/internal/cell-completion.js +15 -0
  80. package/internal/cell-error.js +7 -0
  81. package/internal/cell-parser.js +66 -0
  82. package/internal/cell-rewriter.js +3 -553
  83. package/internal/commonjs-export-evidence.js +63 -0
  84. package/internal/commonjs-source-evidence.js +72 -0
  85. package/internal/compiler-amaro.cjs +2 -0
  86. package/internal/compiler-compression.js +12 -0
  87. package/internal/compiler-data.js +58 -0
  88. package/internal/compiler-descriptors.js +54 -0
  89. package/internal/compiler-dynamic-cache.js +48 -0
  90. package/internal/compiler-entry.js +35 -0
  91. package/internal/compiler-generated-names.js +17 -0
  92. package/internal/compiler-intrinsics.js +84 -0
  93. package/internal/compiler-module-links.js +34 -0
  94. package/internal/compiler-operations.js +61 -0
  95. package/internal/compiler-platform-factory.js +84 -0
  96. package/internal/compiler-platform.js +136 -0
  97. package/internal/compiler-record-roles.js +53 -0
  98. package/internal/compiler-region-output.js +259 -0
  99. package/internal/compiler-resource-lowering.js +103 -0
  100. package/internal/compiler-scope-facts.js +115 -0
  101. package/internal/compiler-service.js +109 -0
  102. package/internal/compiler-source-cache.js +32 -0
  103. package/internal/compiler-source-pieces.js +93 -0
  104. package/internal/compiler-source-regions.js +132 -0
  105. package/internal/compiler-storage-source.js +54 -0
  106. package/internal/compiler-text.js +15 -0
  107. package/internal/compiler-typescript.cjs +2 -0
  108. package/internal/config-spec.js +91 -25
  109. package/internal/cordis-tools-owner.js +130 -21
  110. package/internal/diagnostic.js +27 -1
  111. package/internal/direct-surface-owner.js +76 -23
  112. package/internal/dynamic-binding-evidence.js +16 -0
  113. package/internal/dynamic-environment-compiler.js +779 -0
  114. package/internal/dynamic-environment-integration.js +87 -0
  115. package/internal/dynamic-environment-runtime.js +362 -0
  116. package/internal/dynamic-native-calls.js +150 -0
  117. package/internal/dynamic-native-runtime.js +248 -0
  118. package/internal/dynamic-scope-analysis.js +90 -0
  119. package/internal/edit-transport-owner.js +28 -3
  120. package/internal/execution-seam-compat.js +193 -0
  121. package/internal/failure-reporting.js +118 -18
  122. package/internal/host-rpc.js +66 -0
  123. package/internal/isolated-worker.js +548 -0
  124. package/internal/kernel-child.js +139 -0
  125. package/internal/kernel-worker.js +911 -116
  126. package/internal/language-semantics.js +14 -0
  127. package/internal/legacy-binding-catalog.js +48 -0
  128. package/internal/legacy-cell-analysis.js +600 -0
  129. package/internal/legacy-cell-parser.js +50 -0
  130. package/internal/legacy-cell-rewriter.js +631 -0
  131. package/internal/legacy-repl-convenience.js +291 -0
  132. package/internal/managed-module-operations.js +170 -0
  133. package/internal/module-compilation.js +321 -0
  134. package/internal/module-runtime-platform.js +19 -0
  135. package/internal/module-transform-contract.js +12 -0
  136. package/internal/native-await-declarations.js +68 -0
  137. package/internal/native-root-compilation.js +54 -0
  138. package/internal/native-root-dynamic.js +67 -0
  139. package/internal/recovery-tips.js +27 -15
  140. package/internal/rejected-cell-editor.js +13 -8
  141. package/internal/repl-convenience.js +2 -201
  142. package/internal/repl-memory-projection.js +81 -12
  143. package/internal/repl-observation-interest.js +70 -0
  144. package/internal/repl-rewrite-contract.js +21 -0
  145. package/internal/repl-scope-normalizer.js +2091 -0
  146. package/internal/repl-value-observer.js +114 -0
  147. package/internal/rpc-contract.js +52 -0
  148. package/internal/runtime-bridge-owner.js +172 -28
  149. package/internal/runtime-config.js +48 -51
  150. package/internal/runtime-contexts.js +82 -10
  151. package/internal/runtime-messages.js +111 -0
  152. package/internal/session-cell-executor.js +455 -91
  153. package/internal/session-journal-recovery.js +684 -0
  154. package/internal/session-journal-schema.js +106 -0
  155. package/internal/session-journal.js +237 -440
  156. package/internal/session-log-view.js +42 -9
  157. package/internal/session-runtime.js +325 -80
  158. package/internal/session-state.js +249 -40
  159. package/internal/source-position-map.js +211 -71
  160. package/internal/stateful-module-compiler.js +154 -0
  161. package/internal/stateful-module-runtime.js +303 -0
  162. package/internal/stateful-root-compiler.js +598 -0
  163. package/internal/stateful-root-runtime.js +340 -0
  164. package/internal/typert-codec-compat.js +28 -0
  165. package/internal/typescript-transform.js +29 -0
  166. package/internal/user-binding-authoring.js +70 -0
  167. package/internal/user-binding-console-worker.js +184 -0
  168. package/internal/user-binding-console.js +148 -0
  169. package/internal/user-binding-draft-projection.js +335 -0
  170. package/internal/user-binding-model-context.js +31 -0
  171. package/internal/user-binding-runner.js +56 -0
  172. package/internal/user-bindings-owner.js +1065 -0
  173. package/internal/user-bindings-store.js +293 -0
  174. package/internal/user-bindings.js +353 -0
  175. package/internal/value-wire-schema.js +28 -0
  176. package/internal/value-wire.js +29 -20
  177. package/internal/worker-client.js +57 -67
  178. package/internal/worker-environment.js +45 -0
  179. package/internal/worker-output-relay.js +29 -0
  180. package/internal/worker-shutdown.js +14 -0
  181. package/package.json +81 -11
  182. package/src/client-activity.js +109 -45
  183. package/src/client-authoring-view.js +580 -0
  184. package/src/client-binding-review.js +230 -0
  185. package/src/client-bindings-data.js +29 -0
  186. package/src/client-catalog.js +127 -0
  187. package/src/client-code-editor.js +84 -0
  188. package/src/client-console.js +112 -0
  189. package/src/client-copy.js +370 -0
  190. package/src/client-feature-gates.js +62 -0
  191. package/src/client-host-compat.js +150 -0
  192. package/src/client-indicator-view.js +267 -0
  193. package/src/client-repl-view.js +292 -0
  194. package/src/client-rpc.js +20 -0
  195. package/src/client-settings-view.js +221 -0
  196. package/src/client-styles.js +106 -0
  197. package/src/client-tool-view.js +108 -0
  198. package/src/client-transport.js +159 -0
  199. package/src/client-workbench.js +521 -0
  200. 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,14 +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 { 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'
22
28
  import { mapSourcePosition } from './source-position-map.js'
23
29
  import { durabilityState, transitionDurability } from './session-state.js'
30
+ import { dynamicBindingOrigin } from './dynamic-binding-evidence.js'
24
31
  import { validatedEofClosureRepair } from './validated-parse-repair.js'
32
+ import {
33
+ normalizeUserBindingsSnapshot,
34
+ selectUserBindingsSnapshot,
35
+ } from './user-bindings.js'
25
36
 
26
- 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.`
27
38
 
28
39
  function earlyResult(kind, message) {
29
40
  return { logs: [], error: { kind, message } }
@@ -51,16 +62,15 @@ function isBindingReferenceError(error) {
51
62
  || /Cannot access ['"][^'"]+['"] before initialization/.test(error.message))
52
63
  }
53
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
+ */
54
69
  function parseCellPosition(error) {
55
70
  const cellPosition = error instanceof ModuleRewriteError ? error.cellPosition : undefined
56
- const line = Number.isSafeInteger(cellPosition?.line)
57
- ? cellPosition.line
58
- : Number.isSafeInteger(error?.loc?.line) ? error.loc.line - 1 : undefined
59
- const column = Number.isSafeInteger(cellPosition?.column)
60
- ? cellPosition.column
61
- : Number.isSafeInteger(error?.loc?.column) ? error.loc.column + 1 : undefined
62
- return line !== undefined && line >= 1 && column !== undefined
63
- ? { 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 }
64
74
  : undefined
65
75
  }
66
76
 
@@ -80,7 +90,7 @@ function parseDiagnostic(error, source, position, repair) {
80
90
  : ['this cell was not executed; correct the reported syntax and retry only this cell with run_code']
81
91
  : [
82
92
  `this cell was not executed; validated syntax repair: append ${JSON.stringify(repair.delimiter)} at the end of this cell`,
83
- `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`,
84
94
  ],
85
95
  })
86
96
  }
@@ -105,20 +115,157 @@ function preflightDiagnostic(error) {
105
115
  })
106
116
  }
107
117
 
118
+ function committedRedeclarationSet(message, prepared) {
119
+ if (!Array.isArray(message.committedRedeclarations)) {
120
+ throw new Error('kernel returned an invalid committed redeclaration set')
121
+ }
122
+ const allowed = prepared.commitTargets
123
+ const committed = new Set()
124
+ for (const name of message.committedRedeclarations) {
125
+ if (typeof name !== 'string' || committed.has(name) || !allowed.has(name)) {
126
+ throw new Error('kernel returned an invalid committed redeclaration set')
127
+ }
128
+ committed.add(name)
129
+ }
130
+ return committed
131
+ }
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
+
108
233
  function collisionDiagnostic(collisions) {
109
234
  const names = [...new Set(collisions.map(item => item.name))]
235
+ const reasons = new Set(collisions.map(item => item.reason).filter(Boolean))
236
+ const disabledKinds = new Set(collisions
237
+ .filter(item => item.reason === 'function-class-redeclarations-disabled')
238
+ .map(item => item.kind))
110
239
  const first = collisions[0]
240
+ const alternatives = [
241
+ ...(disabledKinds.size === 2
242
+ ? ['assign function or class expressions to the existing writable bindings']
243
+ : disabledKinds.has('function')
244
+ ? ['assign a function expression to the existing writable binding']
245
+ : disabledKinds.has('class')
246
+ ? ['assign a class expression to the existing writable binding']
247
+ : []),
248
+ ...((reasons.size === 0 || [...reasons].some(reason => REUSE_BINDING_REASONS.has(reason)))
249
+ ? ['reuse the existing bindings']
250
+ : []),
251
+ ]
252
+ const help = [
253
+ ...(reasons.has('binding-not-writable')
254
+ ? ['use a fresh name because the existing binding is immutable']
255
+ : []),
256
+ ...(alternatives.length === 0 ? [] : [alternatives.join('; ')]),
257
+ 'place one-off declarations inside a block',
258
+ ]
259
+ const reservedNote = reservedBindingNote(collisions)
111
260
  return diagnostic({
112
261
  code: 'PTC-N001',
113
262
  severity: 'error',
114
263
  phase: 'preflight',
115
- 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.`,
116
265
  stateEffect: 'unchanged',
117
266
  source: { cell: 'current', start: first.start, end: first.end },
118
- help: [
119
- 'reuse the existing bindings',
120
- 'place one-off declarations inside a block',
121
- ],
267
+ help,
268
+ collisions: collisionRecords(collisions),
122
269
  })
123
270
  }
124
271
 
@@ -128,6 +275,8 @@ function exceptionDiagnostic({
128
275
  position,
129
276
  declared,
130
277
  longCellFailure = false,
278
+ failureOrigin,
279
+ languageSemantics = 'legacy-v1',
131
280
  }) {
132
281
  const missingPath = error.name === 'ToolCallError' ? missingDescriptionPath(error) : undefined
133
282
  const missingDescription = missingPath !== undefined
@@ -152,20 +301,24 @@ function exceptionDiagnostic({
152
301
  ...(cause === undefined ? {} : { cause }),
153
302
  ...(source === undefined ? {} : { source }),
154
303
  help: [
155
- ...(missingDescription
156
- ? ['add a string `description` property to the nested native-tool argument object']
157
- : ['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
+ : []),
158
312
  ...(error.name === 'ToolCallError'
159
313
  && typeof error.toolName === 'string'
160
314
  && error.toolName.startsWith('cordis_')
161
315
  ? ['bindings assigned before this Cordis failure remain live; reuse them instead of resending large source']
162
- : []),
163
- ...(longCellFailure
164
- ? ['execution may have occurred; inspect live state in a new short `run_code` cell before deciding whether a correction is safe']
165
- : []),
166
- ...(declared.size === 0 || longCellFailure
167
- ? []
168
- : ['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']),
169
322
  ],
170
323
  })
171
324
  }
@@ -178,12 +331,18 @@ function invalidOutputDiagnostic(detail) {
178
331
  message: `cell result could not cross the PTC Value V1 boundary: ${firstLine(detail, 'unknown output encoding failure')}`,
179
332
  stateEffect: 'partially-applied',
180
333
  help: [
181
- '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',
182
335
  'reduce the returned graph when it exceeds the configured value budget',
183
336
  ],
184
337
  })
185
338
  }
186
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
+
187
346
  function stateArguments(value) {
188
347
  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
189
348
  throw new TypeError('repl.state expects an object')
@@ -217,24 +376,61 @@ export class SessionCellExecutor {
217
376
  return result
218
377
  }
219
378
 
220
- const catalog = kernel.bindingCatalog.inputs()
221
- const looseTopLevelRedeclarations = replayRecord === undefined
222
- ? config.looseTopLevelRedeclarations
223
- : replayRecord.bindingMode === 'loose'
224
- const rewritesEnabled = replayRecord === undefined ? {
225
- autoRewriteImports: config.autoRewriteImports,
226
- autoStripExports: config.autoStripExports,
227
- autoSplitRedeclarations: config.autoSplitRedeclarations,
228
- } : replayRecord.rewritePolicy
229
- const prepareCell = program => prepareProgram(
230
- program,
231
- catalog.knownBindings,
232
- looseTopLevelRedeclarations,
233
- request.bindingDescriptors.reservedNames,
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,
420
+ bindingPolicy,
421
+ languageSemantics,
422
+ reservedBindings: request.bindingDescriptors.reservedNames,
234
423
  rewritesEnabled,
235
- catalog.importBindings,
236
- catalog.importNamespaces,
237
- )
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,
432
+ moduleSemantics,
433
+ })
238
434
  let prepared
239
435
  try {
240
436
  prepared = prepareCell(request.program)
@@ -280,7 +476,9 @@ export class SessionCellExecutor {
280
476
  const result = earlyResult('abort', String(request.signal.reason))
281
477
  kernel.completeJournal(request.journal, 'discarded', result)
282
478
  kernel.rollbackToDurable()
283
- 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)
284
482
  return result
285
483
  }
286
484
 
@@ -290,9 +488,9 @@ export class SessionCellExecutor {
290
488
  const bindings = this.withControlBinding(request.bindingDescriptors, journal, replayRecord)
291
489
  const id = ++kernel.sequence
292
490
  return new Promise((resolve) => {
293
- const started = worker.performance.eventLoopUtilization()
294
491
  const active = {
295
492
  id,
493
+ started: false,
296
494
  request: { ...request, bindings: bindings.namespaces },
297
495
  finish: resolve,
298
496
  computeTimer: undefined,
@@ -321,40 +519,111 @@ export class SessionCellExecutor {
321
519
  control: { names: new Set(kernel.history.checkpoints.keys()) },
322
520
  rewrites: prepared.rewrites,
323
521
  prepared,
324
- priorBindingCatalog: kernel.bindingCatalog,
522
+ priorBindingCatalog,
523
+ userBindingBaseCatalog,
524
+ userBindings,
525
+ userBindingsShadowPolicy,
526
+ userBindingFailures,
527
+ userBindingSnapshot: userBindings,
325
528
  worker,
529
+ settled: false,
326
530
  }
327
531
  active.resolve = (result, terminate = false) => kernel.settleCell(active, result, terminate)
328
532
  active.onAbort = () => active.resolve(
329
533
  earlyResult('abort', String(request.signal?.reason)),
330
534
  true,
331
535
  )
332
- active.computeTimer = setInterval(() => {
333
- if (worker.performance.eventLoopUtilization(started).active > config.computeMs) {
334
- 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)
335
608
  }
336
- }, Math.min(100, config.computeMs))
337
- active.wallTimer = setTimeout(() => {
338
- active.resolve(earlyResult('timeout', `wall-clock ceiling reached (${config.maxWallMs}ms); split long-running work into smaller cells`), true)
339
- }, config.maxWallMs)
609
+ }
340
610
  kernel.active = active
341
611
  request.signal?.addEventListener('abort', active.onAbort, { once: true })
342
612
  if (request.signal?.aborted) {
343
613
  active.onAbort()
344
614
  return
345
615
  }
346
- try {
347
- kernel.client.post({
348
- type: 'run', id, program: prepared.code, namespaces: bindings.workerDescriptors,
349
- moduleLoads: prepared.moduleLoads,
350
- returnSignal: prepared.returnSignal,
351
- maxOutputBytes: config.maxOutputBytes,
352
- valueLimits,
353
- durability,
354
- })
355
- } catch (error) {
356
- active.resolve(earlyResult('worker-exit', messageOf(error)), true)
357
- }
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
+ })()
358
627
  })
359
628
  }
360
629
 
@@ -375,7 +644,7 @@ export class SessionCellExecutor {
375
644
  reservedNames: bindingDescriptors.reservedNames,
376
645
  workerDescriptors: Object.freeze([
377
646
  ...bindingDescriptors.workerDescriptors,
378
- Object.freeze({ global: 'repl', members: namespace.members }),
647
+ ...normalizeBindingDescriptors([namespace]).workerDescriptors,
379
648
  ]),
380
649
  })
381
650
  }
@@ -413,6 +682,30 @@ export class SessionCellExecutor {
413
682
  }
414
683
 
415
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
+ }
416
709
  if (message === null || typeof message !== 'object') return
417
710
  const handler = {
418
711
  volatile: this.handleVolatile,
@@ -472,6 +765,53 @@ export class SessionCellExecutor {
472
765
  active.resolve({ logs, error: { kind: 'recovery', message: 'session log replay consumed a different host-call transcript' } }, true)
473
766
  return
474
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
+ }
475
815
  const bytes = Buffer.byteLength(JSON.stringify({ logs, value: message.value }), 'utf8')
476
816
  if (bytes > active.config.maxOutputBytes) {
477
817
  active.resolve({
@@ -480,6 +820,29 @@ export class SessionCellExecutor {
480
820
  }, true)
481
821
  return
482
822
  }
823
+ let committed
824
+ let rootBindingFacts
825
+ try {
826
+ committed = committedRedeclarationSet(message, active.prepared)
827
+ rootBindingFacts = completedRootBindingFacts(message, active.prepared, committed)
828
+ } catch (error) {
829
+ active.resolve(earlyResult('worker-exit', messageOf(error)), true)
830
+ return
831
+ }
832
+ active.appliedBindingCatalog = message.moduleLoadFailed === true
833
+ ? active.priorBindingCatalog
834
+ : active.priorBindingCatalog.advance(
835
+ active.prepared,
836
+ active.request.program,
837
+ committed,
838
+ rootBindingFacts,
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
483
846
  if (typeof message.error === 'string') {
484
847
  const rawError = {
485
848
  kind: 'exception',
@@ -490,33 +853,38 @@ export class SessionCellExecutor {
490
853
  const actualFailure = exceptionDiagnostic({
491
854
  error: rawError,
492
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.
493
859
  position: message.moduleLoadFailed === true
494
- ? message.position
495
- : mapSourcePosition(
496
- message.position,
497
- 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
+ ),
498
868
  active.request.program,
499
- active.prepared.sourceMap,
500
- ),
869
+ ) ?? exceptionOriginPosition(message.exceptionOrigins, active.request.program),
501
870
  declared: message.moduleLoadFailed === true ? new Set() : active.prepared.declared,
871
+ languageSemantics: active.prepared.languageSemantics,
502
872
  longCellFailure: active.request.program.length >= LONG_CELL_CODE_UNITS,
873
+ failureOrigin: message.failureOrigin,
503
874
  })
504
- active.appliedBindingCatalog = message.moduleLoadFailed === true
505
- ? active.priorBindingCatalog
506
- : active.priorBindingCatalog.advance(active.prepared, active.request.program)
507
- const recordedFailure = active.replay?.diagnostics?.find(item => item.code === 'PTC-X001')
508
- const failure = recordedFailure?.message === actualFailure.message ? recordedFailure : actualFailure
875
+ const failure = replayFailureDiagnostic(actualFailure, active.replay)
509
876
  const error = {
510
877
  kind: 'exception',
511
878
  message: renderDiagnostic(failure, active.request.program),
512
879
  }
513
880
  if (isBindingReferenceError(rawError)) markBindingFailure(error)
881
+ else if (message.failureOrigin === 'capability') markBindingFailure(error, 'capability')
514
882
  active.diagnostics.push(failure)
515
883
  active.resolve({ logs, error })
516
884
  return
517
885
  }
518
886
  if (typeof message.invalidOutput === 'string') {
519
- const failure = invalidOutputDiagnostic(message.invalidOutput)
887
+ const failure = replayFailureDiagnostic(invalidOutputDiagnostic(message.invalidOutput), active.replay)
520
888
  const error = { kind: 'invalid-output', message: renderDiagnostic(failure, active.request.program) }
521
889
  active.diagnostics.push(failure)
522
890
  active.resolve({ logs, error })
@@ -527,27 +895,23 @@ export class SessionCellExecutor {
527
895
  || (message.hasValue ? message.value === undefined : message.value !== undefined)) {
528
896
  throw new TypeError('invalid PTC completion envelope')
529
897
  }
530
- const value = message.hasValue ? normalizeValueWire(message.value, active.valueLimits) : undefined
898
+ const completion = message.hasValue ? prepareValueWire(message.value, active.valueLimits) : undefined
531
899
  active.completion = {
532
900
  hasValue: message.hasValue,
533
- ...(message.hasValue ? { value } : {}),
901
+ ...(message.hasValue ? { value: completion.wire } : {}),
534
902
  }
535
- active.appliedBindingCatalog = active.priorBindingCatalog.advance(
536
- active.prepared,
537
- active.request.program,
538
- )
539
903
  if (active.replay?.completion?.kind === 'return'
540
904
  && (active.replay.completion.hasValue !== message.hasValue
541
- || (message.hasValue && !valueWiresEqual(active.replay.completion.value, value, active.valueLimits)))) {
905
+ || (message.hasValue && !valueWiresEqual(active.replay.completion.value, completion.wire, active.valueLimits)))) {
542
906
  active.resolve({ logs, error: { kind: 'recovery', message: 'cell replay produced a different completion value' } }, true)
543
907
  return
544
908
  }
545
909
  active.resolve({
546
910
  logs,
547
- ...(message.hasValue ? { value: projectValueWire(value, active.valueLimits) } : {}),
911
+ ...(message.hasValue ? { value: completion.projectedValue } : {}),
548
912
  })
549
913
  } catch (error) {
550
- const failure = invalidOutputDiagnostic(messageOf(error))
914
+ const failure = replayFailureDiagnostic(invalidOutputDiagnostic(messageOf(error)), active.replay)
551
915
  const invalid = { kind: 'invalid-output', message: renderDiagnostic(failure, active.request.program) }
552
916
  active.diagnostics.push(failure)
553
917
  active.resolve({ logs, error: invalid })