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
@@ -270,7 +270,7 @@ function editRunCodeParameterBranch(operation, operationSchema) {
270
270
  export function editRunCodeSchema() {
271
271
  return {
272
272
  name: 'edit_run_code',
273
- description: 'Make a small exact or regular-expression change to the most recent eligible cell captured when this edit call is dispatched, then run the complete corrected cell. A successful edit becomes the next eligible cell. Use this only when replaying the whole cell is safe. If earlier code may already have caused an external effect, use a new run_code cell and its existing variables instead; this tool does not resume at the error location. Send exactly one atomic edits or regex_edits array, plus expected_target_call_seq only when a diagnostic supplies it to bind a validated repair to its rejected cell. Choose edits for a few unique literal fragments; choose regex_edits when one counted pattern covers repeated fragments. Every resolved range must be non-overlapping in the original cell.',
273
+ description: 'Edit the most recent eligible cell captured at dispatch and run the complete corrected cell. A successful edit becomes the next target. This reruns the entire cell, including prior effects; use it after pre-execution rejection or when repeating those effects is safe under the called API\'s retry rules. Otherwise continue in a new run_code cell using retained values. A diagnostic may supply a localized correction; use it when it matches your intent and preserve its expected_target_call_seq. Send exactly one edits or regex_edits array: edits replaces unique literal fragments; regex_edits replaces a counted set of pattern matches. All ranges must be non-overlapping in the original cell. Regex replacements use JavaScript $1/$<name> captures, not \\1.',
274
274
  parameters: {
275
275
  type: 'object',
276
276
  oneOf: [
@@ -288,13 +288,13 @@ export function editRunCodeSchema() {
288
288
  }),
289
289
  editRunCodeParameterBranch('regex_edits', {
290
290
  type: 'array', minItems: 1, maxItems: EDIT_LIMITS.regexEdits,
291
- description: 'Atomic regular-expression replacements, all matched against the original target cell.',
291
+ description: 'Atomic regular-expression replacements, all matched against the original target cell with JavaScript pattern and replacement semantics; each replacement is a JavaScript template (see replacement).',
292
292
  items: {
293
293
  type: 'object', additionalProperties: false,
294
294
  properties: {
295
- pattern: { type: 'string', minLength: 1, description: 'JavaScript regular-expression source. Zero-length matches are rejected.' },
295
+ pattern: { type: 'string', minLength: 1, description: 'JavaScript regular-expression source without delimiters or flags (for example \\d+px, not /\\d+px/). Zero-length matches are rejected.' },
296
296
  flags: { type: 'string', pattern: '^(?!.*(.).*\\1)[gimsu]*$', description: 'Unique JavaScript g, i, m, s, or u flags. Global matching is always applied.' },
297
- replacement: { type: 'string', description: 'JavaScript replacement template, including capture references such as $1 or $<name>.' },
297
+ replacement: { type: 'string', description: 'JavaScript replacement template. Reference captures as $1, $2, ... or $<name> (for example "<$1>" inserts capture 1); backslash escapes such as \\1 are NOT capture references and stay literal text; write a literal dollar sign as $$.' },
298
298
  expected_matches: { type: 'integer', minimum: 1, maximum: EDIT_LIMITS.regexMatches, description: 'Exact match count required before any replacement is applied.' },
299
299
  },
300
300
  required: ['pattern', 'flags', 'replacement', 'expected_matches'],
@@ -310,11 +310,16 @@ export function editRejectedCell(value, source, timeoutMs = DEFAULT_REGEX_TIMEOU
310
310
  return rejection('edit_run_code expects an object with exactly one edits or regex_edits array')
311
311
  }
312
312
  const keys = Object.keys(value)
313
+ const unsupported = keys.find(key => !['edits', 'regex_edits', EXPECTED_TARGET_CALL_SEQ].includes(key))
314
+ if (unsupported !== undefined) {
315
+ return rejection(`edit_run_code does not accept field ${JSON.stringify(unsupported)}; remove it and retry the edit`)
316
+ }
313
317
  const operationKeys = keys.filter(key => key === 'edits' || key === 'regex_edits')
314
- if (operationKeys.length !== 1 || keys.length > 2
315
- || keys.some(key => !['edits', 'regex_edits', EXPECTED_TARGET_CALL_SEQ].includes(key))
316
- || !Array.isArray(value[operationKeys[0]])) {
317
- return rejection('edit_run_code expects exactly one edits or regex_edits array')
318
+ if (operationKeys.length !== 1) {
319
+ return rejection('edit_run_code expects exactly one of edits or regex_edits; supply one operation and omit the other')
320
+ }
321
+ if (!Array.isArray(value[operationKeys[0]])) {
322
+ return rejection(`edit_run_code ${operationKeys[0]} must be an array`)
318
323
  }
319
324
  if (Object.hasOwn(value, EXPECTED_TARGET_CALL_SEQ)
320
325
  && (!Number.isSafeInteger(value[EXPECTED_TARGET_CALL_SEQ])
@@ -1,289 +1,2 @@
1
- import { bindingNodes, createGeneratedNameAllocator } from './binding-pattern.js'
2
- import {
3
- CLASS_REDECLARATION_REWRITE,
4
- FUNCTION_REDECLARATION_REWRITE,
5
- MIXED_REDECLARATION_REWRITE,
6
- redeclarationCommitTarget,
7
- } from './repl-rewrite-contract.js'
8
- import { applySourceEdits, createMappedTextBuilder, identitySourceMap } from './source-position-map.js'
9
- import { SKIP_AST_CHILDREN, walkAst } from './ast-traversal.js'
10
-
11
- /**
12
- * Optional compatibility policy for a persistent REPL. The cell language does
13
- * not own this policy: callers may replace it when persistence uses separate
14
- * lexical frames instead of one shared REPL environment.
15
- */
16
-
17
- function containsTopLevelAwait(node) {
18
- let found = false
19
- walkAst(node, current => {
20
- if (current.type === 'AwaitExpression') found = true
21
- if (current.type === 'FunctionDeclaration' || current.type === 'FunctionExpression'
22
- || current.type === 'ArrowFunctionExpression') return SKIP_AST_CHILDREN
23
- })
24
- return found
25
- }
26
-
27
- function mixedDeclaratorText(statement, declarator, bindings, code, offset, allocateName) {
28
- const fresh = bindings.filter(binding => !binding.existing)
29
- const existing = bindings.filter(binding => binding.existing)
30
- const valueName = allocateName('mixed_value')
31
- const commitName = allocateName('mixed_commit')
32
- const valuesName = allocateName('mixed_values')
33
- const positions = new Map(bindings.map((binding, index) => [binding.name, index]))
34
- const asynchronous = containsTopLevelAwait(declarator.id)
35
- const builder = createMappedTextBuilder(code, offset)
36
- const appendBindingName = binding => builder.appendMapped(binding.name, binding.start, binding.end)
37
- builder.append(statement.kind === 'var' ? 'var [' : 'let [')
38
- fresh.forEach((binding, index) => {
39
- if (index > 0) builder.append(', ')
40
- appendBindingName(binding)
41
- })
42
- builder.append(`] = ${asynchronous ? 'await ' : ''}(`)
43
- builder.append(`(${asynchronous ? 'async ' : ''}(${valueName}, ${commitName}) => { ${statement.kind} `)
44
- builder.appendSource(declarator.id.start, declarator.id.end)
45
- builder.append(` = ${valueName}; return ${commitName}([`)
46
- bindings.forEach((binding, index) => {
47
- if (index > 0) builder.append(', ')
48
- appendBindingName(binding)
49
- })
50
- builder.append(`]); })((`)
51
- if (declarator.init === null) builder.append('undefined')
52
- else builder.appendSource(declarator.init.start, declarator.init.end)
53
- builder.append(`), (${valuesName}) => { `)
54
- existing.forEach((binding, index) => {
55
- if (index > 0) builder.append(' ')
56
- appendBindingName(binding)
57
- builder.append(` = ${valuesName}[${positions.get(binding.name)}];`)
58
- })
59
- builder.append(' return [')
60
- fresh.forEach((binding, index) => {
61
- if (index > 0) builder.append(', ')
62
- builder.append(valuesName)
63
- builder.append(`[${positions.get(binding.name)}]`)
64
- })
65
- builder.append(']; }) );')
66
- return builder.result()
67
- }
68
-
69
- function existingDeclaratorText(declarator, code, offset) {
70
- const builder = createMappedTextBuilder(code, offset)
71
- builder.append(';(')
72
- builder.appendSource(declarator.id.start, declarator.id.end)
73
- builder.append(' = ')
74
- if (declarator.init === null) builder.append('undefined')
75
- else builder.appendSource(declarator.init.start, declarator.init.end)
76
- builder.append(');')
77
- return builder.result()
78
- }
79
-
80
- function freshDeclaratorText(statement, declarator, code, offset) {
81
- const builder = createMappedTextBuilder(code, offset)
82
- builder.append(statement.kind === 'var' ? 'var ' : 'let ')
83
- builder.appendSource(declarator.start, declarator.end)
84
- builder.append(';')
85
- return builder.result()
86
- }
87
-
88
- function bindingEntries(pattern, declarationSpan) {
89
- return bindingNodes(pattern).map(node => ({
90
- name: node.name,
91
- start: node.start,
92
- end: node.end,
93
- span: declarationSpan(node),
94
- }))
95
- }
96
-
97
- function existingFunctionText(statement, code, offset) {
98
- const builder = createMappedTextBuilder(code, offset)
99
- builder.append(';(')
100
- builder.appendMapped(statement.id.name, statement.id.start, statement.id.end)
101
- builder.append(' = ')
102
- builder.appendSource(statement.start, statement.id.start)
103
- builder.appendSource(statement.id.end, statement.end)
104
- builder.append(');')
105
- return builder.result()
106
- }
107
-
108
- function existingClassText(statement, code, offset) {
109
- const builder = createMappedTextBuilder(code, offset)
110
- builder.append(';(')
111
- builder.appendMapped(statement.id.name, statement.id.start, statement.id.end)
112
- builder.append(' = ')
113
- builder.appendSource(statement.start, statement.end)
114
- builder.append(');')
115
- return builder.result()
116
- }
117
-
118
- export function rewriteReplRedeclarations({
119
- code,
120
- sourceMap = identitySourceMap(code.length),
121
- body,
122
- offset,
123
- knownBindings,
124
- variableRedeclarationBindings = knownBindings,
125
- writableBindings,
126
- declarations,
127
- declarationSpan,
128
- collisionFor,
129
- bindingPolicy,
130
- autoSplitRedeclarations,
131
- commitSignal,
132
- }) {
133
- const redeclared = []
134
- const rewrites = []
135
- if (body === undefined) {
136
- return {
137
- executableCode: code,
138
- executableSourceMap: sourceMap,
139
- collisions: declarations.filter(declaration => knownBindings.has(declaration.name)).map(collisionFor),
140
- redeclared,
141
- rewrites,
142
- }
143
- }
144
- const replacements = []
145
- const rejected = []
146
- const allocateName = createGeneratedNameAllocator(body)
147
- for (const statement of body) {
148
- if (statement.type !== 'VariableDeclaration') {
149
- if ((statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration')
150
- && statement.id !== null && knownBindings.has(statement.id.name)) {
151
- const kind = statement.type === 'ClassDeclaration' ? 'class' : 'function'
152
- if (!writableBindings.has(statement.id.name)) {
153
- rejected.push({
154
- name: statement.id.name,
155
- kind,
156
- reason: 'binding-not-writable',
157
- span: declarationSpan(statement.id),
158
- })
159
- } else if (!bindingPolicy.functionClassRedeclarations) {
160
- rejected.push({
161
- name: statement.id.name,
162
- kind,
163
- reason: 'function-class-redeclarations-disabled',
164
- span: declarationSpan(statement.id),
165
- })
166
- } else {
167
- const commitDependency = redeclarationCommitTarget(statement.id.name, statement.start)
168
- const replacement = kind === 'class'
169
- ? existingClassText(statement, code, offset)
170
- : existingFunctionText(statement, code, offset)
171
- replacement.text += ` void 0; this[${JSON.stringify(commitSignal)}](${JSON.stringify(commitDependency)});`
172
- replacements.push({
173
- start: statement.start - offset,
174
- end: statement.end - offset,
175
- text: replacement.text,
176
- mappings: replacement.mappings,
177
- })
178
- redeclared.push(collisionFor({
179
- name: statement.id.name,
180
- kind,
181
- commitDependency,
182
- span: declarationSpan(statement.id),
183
- }))
184
- rewrites.push({
185
- kind: 'redeclaration',
186
- description: kind === 'class'
187
- ? CLASS_REDECLARATION_REWRITE
188
- : FUNCTION_REDECLARATION_REWRITE,
189
- source: statement.id.name,
190
- })
191
- }
192
- }
193
- continue
194
- }
195
- const entries = []
196
- let statementRejected = false
197
- for (const declarator of statement.declarations) {
198
- const bindings = bindingEntries(declarator.id, declarationSpan)
199
- const existing = bindings.filter(binding => variableRedeclarationBindings.has(binding.name))
200
- const immutable = existing.filter(binding => !writableBindings.has(binding.name))
201
- if (immutable.length > 0) {
202
- rejected.push(...immutable.map(binding => ({
203
- ...binding,
204
- reason: 'binding-not-writable',
205
- })))
206
- statementRejected = true
207
- continue
208
- }
209
- if (existing.length > 0 && !bindingPolicy.variableRedeclarations) {
210
- rejected.push(...existing.map(binding => ({
211
- ...binding,
212
- reason: 'variable-redeclarations-disabled',
213
- })))
214
- statementRejected = true
215
- continue
216
- }
217
- if (existing.length > 0 && existing.length < bindings.length) {
218
- if (autoSplitRedeclarations) entries.push({ declarator, bindings, existing })
219
- else {
220
- rejected.push(...existing)
221
- statementRejected = true
222
- }
223
- continue
224
- }
225
- entries.push({ declarator, bindings, existing })
226
- }
227
- if (statementRejected) continue
228
- if (!entries.some(entry => entry.existing.length > 0)) {
229
- if (statement.kind === 'const' && bindingPolicy.variableRedeclarations) {
230
- replacements.push({
231
- start: statement.start - offset,
232
- end: statement.start - offset + statement.kind.length,
233
- text: 'let',
234
- })
235
- }
236
- continue
237
- }
238
- let replacementText = ''
239
- const replacementMappings = []
240
- const appendPart = (part) => {
241
- if (replacementText.length > 0) replacementText += '\n'
242
- const partOffset = replacementText.length
243
- replacementText += part.text
244
- part.mappings.forEach(mapping => replacementMappings.push({
245
- ...mapping,
246
- generatedStart: mapping.generatedStart + partOffset,
247
- generatedEnd: mapping.generatedEnd + partOffset,
248
- }))
249
- }
250
- for (const { declarator, bindings, existing } of entries) {
251
- if (existing.length > 0 && existing.length < bindings.length) {
252
- appendPart(mixedDeclaratorText(
253
- statement,
254
- declarator,
255
- bindings.map(binding => ({ ...binding, existing: existing.includes(binding) })),
256
- code,
257
- offset,
258
- allocateName,
259
- ))
260
- redeclared.push(...existing.map(collisionFor))
261
- rewrites.push({
262
- kind: 'redeclaration',
263
- description: MIXED_REDECLARATION_REWRITE,
264
- source: existing.map(binding => binding.name).join(', '),
265
- })
266
- } else if (existing.length === bindings.length && bindings.length > 0) {
267
- redeclared.push(...existing.map(collisionFor))
268
- appendPart(existingDeclaratorText(declarator, code, offset))
269
- } else {
270
- appendPart(freshDeclaratorText(statement, declarator, code, offset))
271
- }
272
- }
273
- replacements.push({
274
- start: statement.start - offset,
275
- end: statement.end - offset,
276
- text: replacementText,
277
- mappings: replacementMappings,
278
- })
279
- }
280
- const collisions = rejected.map(collisionFor)
281
- let executableCode = code
282
- let executableSourceMap = sourceMap
283
- if (collisions.length === 0) {
284
- const applied = applySourceEdits(code, sourceMap, replacements)
285
- executableCode = applied.code
286
- executableSourceMap = applied.sourceMap
287
- }
288
- return { executableCode, executableSourceMap, collisions, redeclared, rewrites }
289
- }
1
+ /** Historical declaration adapter; new cells use logical binding identities. */
2
+ export { rewriteReplRedeclarations } from './legacy-repl-convenience.js'
@@ -1,4 +1,5 @@
1
1
  import { isRecord } from './record-utils.js'
2
+ import { REPL_TOOL_NAMES } from './session-journal-schema.js'
2
3
 
3
4
  export const REPL_MEMORY_KEY = 'ptcPlusRepl'
4
5
  export const REPL_MEMORY_META_KEY = 'dshPtcPlusBindings'
@@ -12,13 +13,22 @@ const MAX_PENDING_REPL_CALLS = 256
12
13
  const MAX_CALL_ID_LENGTH = 512
13
14
  const MAX_GENERATION_LENGTH = 128
14
15
  const SNAPSHOT_FIELDS = new Set(['available', 'entries', 'total', 'omitted'])
16
+ const COUNTED_SNAPSHOT_FIELDS = new Set([...SNAPSHOT_FIELDS, 'reuseTotal'])
17
+ const OBSERVED_SNAPSHOT_FIELDS = new Set([...SNAPSHOT_FIELDS, 'observation'])
18
+ const OBSERVED_COUNTED_SNAPSHOT_FIELDS = new Set([...COUNTED_SNAPSHOT_FIELDS, 'observation'])
19
+ const OBSERVATION_FIELDS = new Set(['at', 'entries'])
20
+ const PREVIEW_FIELDS = new Set(['name', 'status', 'text', 'truncated'])
15
21
  const ENTRY_FIELDS = new Set(['name', 'kind', 'definition'])
22
+ const COUNTED_ENTRY_FIELDS = new Set([...ENTRY_FIELDS, 'reuseCount'])
16
23
  const DEFINITION_FIELDS = new Set(['source', 'line', 'column'])
17
24
  const MEMORY_META_FIELDS = new Set(['version', 'generation', 'memory'])
18
25
  const PROJECTION_STATE_FIELDS = new Set(['generation', 'memory', 'pendingReplCalls'])
19
26
  const PENDING_CALL_FIELDS = new Set(['callId', 'seq'])
20
- const REPL_TOOL_NAMES = new Set(['run_code', 'edit_run_code'])
21
- const REPL_MEMORY_META_VERSION = 3
27
+ const REPL_MEMORY_META_VERSION = 5
28
+
29
+ function isReuseCount(value) {
30
+ return Number.isSafeInteger(value) && value >= 0
31
+ }
22
32
 
23
33
  function exactFields(value, fields) {
24
34
  if (!isRecord(value)) return false
@@ -34,6 +44,7 @@ const EMPTY_REPL_MEMORY = Object.freeze({
34
44
  entries: Object.freeze([]),
35
45
  total: 0,
36
46
  omitted: 0,
47
+ reuseTotal: 0,
37
48
  })
38
49
 
39
50
  export function unavailableReplMemorySnapshot() {
@@ -59,20 +70,43 @@ function normalizeDefinition(value) {
59
70
  }
60
71
 
61
72
  function normalizeBinding(value) {
62
- if (!exactFields(value, ENTRY_FIELDS) || typeof value.name !== 'string'
73
+ const counted = exactFields(value, COUNTED_ENTRY_FIELDS)
74
+ if ((!counted && !exactFields(value, ENTRY_FIELDS)) || typeof value.name !== 'string'
63
75
  || value.name.length === 0 || value.name.length > MAX_BINDING_NAME_LENGTH
64
- || !BINDING_KINDS.has(value.kind)) {
76
+ || !BINDING_KINDS.has(value.kind)
77
+ || (counted && !isReuseCount(value.reuseCount))) {
65
78
  throw new Error('invalid dsh-ptc-plus REPL memory binding')
66
79
  }
67
80
  return Object.freeze({
68
81
  name: value.name,
69
82
  kind: value.kind,
70
83
  definition: normalizeDefinition(value.definition),
84
+ reuseCount: counted ? value.reuseCount : 0,
85
+ })
86
+ }
87
+
88
+ export function normalizeReplObservation(value) {
89
+ if (!exactFields(value, OBSERVATION_FIELDS) || !Number.isSafeInteger(value.at) || value.at < 0
90
+ || !Array.isArray(value.entries) || value.entries.length > MAX_BINDINGS) {
91
+ throw new Error('invalid dsh-ptc-plus REPL observation')
92
+ }
93
+ const names = new Set()
94
+ const entries = value.entries.map(entry => {
95
+ if (!exactFields(entry, PREVIEW_FIELDS) || typeof entry.name !== 'string'
96
+ || entry.name.length === 0 || entry.name.length > MAX_BINDING_NAME_LENGTH || names.has(entry.name)
97
+ || !['readable', 'unreadable'].includes(entry.status)
98
+ || typeof entry.text !== 'string' || entry.text.length > 512 || typeof entry.truncated !== 'boolean'
99
+ || (entry.status === 'unreadable' && (entry.text !== '' || entry.truncated))) {
100
+ throw new Error('invalid dsh-ptc-plus REPL value preview')
101
+ }
102
+ names.add(entry.name)
103
+ return Object.freeze({ ...entry })
71
104
  })
105
+ return Object.freeze({ at: value.at, entries: Object.freeze(entries) })
72
106
  }
73
107
 
74
- /** Produce bounded, value-independent presentation metadata from the live binding catalog. */
75
- export function createReplMemorySnapshot(bindings) {
108
+ /** Source inventory and optional observation are UI-only; neither is recovery evidence. */
109
+ export function createReplMemorySnapshot(bindings, observation = undefined) {
76
110
  const all = Array.isArray(bindings) ? bindings : []
77
111
  const candidates = []
78
112
  const names = new Set()
@@ -92,28 +126,45 @@ export function createReplMemorySnapshot(bindings) {
92
126
  entries.push(binding)
93
127
  sourceLength += binding.definition.source.length
94
128
  }
129
+ let observed
130
+ if (observation !== undefined) {
131
+ try {
132
+ const normalized = normalizeReplObservation(observation)
133
+ const visibleNames = new Set(entries.map(entry => entry.name))
134
+ observed = Object.freeze({ ...normalized, entries: Object.freeze(normalized.entries.filter(entry => visibleNames.has(entry.name))) })
135
+ } catch {}
136
+ }
95
137
  return Object.freeze({
96
138
  available: true,
97
139
  entries: Object.freeze(entries),
98
140
  total: candidates.length,
99
141
  omitted: candidates.length - entries.length,
142
+ // Counts survive the presentation truncation: this total covers every valid
143
+ // identity, including entries omitted from the bounded list.
144
+ reuseTotal: candidates.reduce((sum, entry) => sum + entry.reuseCount, 0),
145
+ ...(observed === undefined ? {} : { observation: observed }),
100
146
  })
101
147
  }
102
148
 
103
149
  /** Validate one complete session-projection value without accepting unknown fields. */
104
150
  export function normalizeReplMemorySnapshot(value) {
105
- if (!exactFields(value, SNAPSHOT_FIELDS) || typeof value.available !== 'boolean'
151
+ const counted = exactFields(value, COUNTED_SNAPSHOT_FIELDS) || exactFields(value, OBSERVED_COUNTED_SNAPSHOT_FIELDS)
152
+ const legacy = exactFields(value, SNAPSHOT_FIELDS) || exactFields(value, OBSERVED_SNAPSHOT_FIELDS)
153
+ if ((!counted && !legacy) || typeof value.available !== 'boolean'
106
154
  || !Array.isArray(value.entries) || value.entries.length > MAX_BINDINGS
107
155
  || !Number.isSafeInteger(value.total) || value.total < 0
108
156
  || !Number.isSafeInteger(value.omitted) || value.omitted < 0
109
- || value.total !== value.entries.length + value.omitted) {
157
+ || value.total !== value.entries.length + value.omitted
158
+ || counted && !isReuseCount(value.reuseTotal)) {
110
159
  throw new Error('invalid dsh-ptc-plus REPL memory snapshot')
111
160
  }
112
- if (!value.available && (value.total !== 0 || value.entries.length !== 0)) {
161
+ const reuseTotal = counted ? value.reuseTotal : 0
162
+ if (!value.available && (value.total !== 0 || value.entries.length !== 0 || reuseTotal !== 0)) {
113
163
  throw new Error('unavailable dsh-ptc-plus REPL memory snapshot must be empty')
114
164
  }
115
165
  const names = new Set()
116
166
  let sourceLength = 0
167
+ let entryReuseTotal = 0
117
168
  const entries = value.entries.map((entry) => {
118
169
  const normalized = normalizeBinding(entry)
119
170
  if (names.has(normalized.name)) {
@@ -124,18 +175,36 @@ export function normalizeReplMemorySnapshot(value) {
124
175
  throw new Error('dsh-ptc-plus REPL binding definitions exceed the presentation budget')
125
176
  }
126
177
  names.add(normalized.name)
178
+ entryReuseTotal += normalized.reuseCount
127
179
  return normalized
128
180
  })
181
+ if (reuseTotal < entryReuseTotal) {
182
+ throw new Error('dsh-ptc-plus REPL reuse total is smaller than its entries')
183
+ }
184
+ const observation = value.observation === undefined ? undefined : normalizeReplObservation(value.observation)
185
+ if (observation !== undefined && (!value.available || observation.entries.some(entry => !names.has(entry.name)))) {
186
+ throw new Error('REPL observation requires a matching available inventory')
187
+ }
129
188
  return Object.freeze({
130
189
  available: value.available,
131
190
  entries: Object.freeze(entries),
132
191
  total: value.total,
133
192
  omitted: value.omitted,
193
+ reuseTotal,
194
+ ...(observation === undefined ? {} : { observation }),
134
195
  })
135
196
  }
136
197
 
198
+ /** Reuse fields are version 5 additions; an older metadata version cannot own them. */
199
+ function countedMemory(memory) {
200
+ return memory?.reuseTotal !== undefined
201
+ || Array.isArray(memory?.entries) && memory.entries.some(entry => entry?.reuseCount !== undefined)
202
+ }
203
+
137
204
  function normalizeReplMemoryMetadata(value) {
138
- if (!exactFields(value, MEMORY_META_FIELDS) || value.version !== REPL_MEMORY_META_VERSION) {
205
+ if (!exactFields(value, MEMORY_META_FIELDS) || ![3, 4, REPL_MEMORY_META_VERSION].includes(value.version)
206
+ || (value.version === 3 && value.memory?.observation !== undefined)
207
+ || (value.version < REPL_MEMORY_META_VERSION && countedMemory(value.memory))) {
139
208
  throw new Error('invalid dsh-ptc-plus REPL memory metadata')
140
209
  }
141
210
  return Object.freeze({
@@ -253,12 +322,12 @@ export function withReplMemorySnapshot(meta, snapshot, generation) {
253
322
  return base
254
323
  }
255
324
 
256
- /** Build the current runtime owner's value-independent session projection. */
325
+ /** Build the current runtime owner's model-invisible session projection. */
257
326
  export function createReplMemoryProjection(generation) {
258
327
  const currentGeneration = normalizeGeneration(generation)
259
328
  return Object.freeze({
260
329
  key: REPL_MEMORY_KEY,
261
- stateVersion: 3,
330
+ stateVersion: 5,
262
331
  stateSchema: Object.freeze({
263
332
  parse: value => normalizeProjectionState(value, currentGeneration),
264
333
  }),
@@ -0,0 +1,70 @@
1
+ import { RPC_CONTRACTS } from './rpc-contract.js'
2
+
3
+ export const REPL_OBSERVATION_RPC_CONTRACT = RPC_CONTRACTS.repl
4
+
5
+ /** A visible Client holds a cancellable request; it never requests evaluation. */
6
+ export function createReplObservationInterest(ctx, initiallyEnabled, observe = async () => undefined) {
7
+ let enabled = initiallyEnabled
8
+ const watchers = new Set()
9
+ const inspections = new Set()
10
+ const registrations = new Set()
11
+ const clear = () => {
12
+ for (const watcher of [...watchers]) watcher.release()
13
+ for (const controller of inspections) controller.abort()
14
+ }
15
+ const handler = async (endpoint, payload, signal) => {
16
+ if (!enabled || !['watch', 'observe'].includes(endpoint) || typeof payload?.sessionId !== 'string'
17
+ || payload.sessionId.length === 0 || payload.sessionId.length > 256
18
+ || signal === undefined || (endpoint === 'watch' && watchers.size >= 64)) return { ok: false, error: {
19
+ code: 'repl/watch-unavailable', message: 'REPL observation unavailable', details: {},
20
+ } }
21
+ if (endpoint === 'observe') {
22
+ if (inspections.size >= 64) return { ok: true, value: null }
23
+ const controller = new AbortController()
24
+ inspections.add(controller)
25
+ try {
26
+ const value = await observe(payload.sessionId, payload.memory, AbortSignal.any([signal, controller.signal]))
27
+ return { ok: true, value: value ?? null }
28
+ } catch {
29
+ return { ok: false, error: { code: 'repl/observation-unavailable', message: 'REPL observation unavailable', details: {} } }
30
+ } finally {
31
+ inspections.delete(controller)
32
+ }
33
+ }
34
+ return new Promise(resolve => {
35
+ const watcher = { sessionId: payload.sessionId, release() {
36
+ signal.removeEventListener('abort', watcher.release)
37
+ watchers.delete(watcher)
38
+ resolve({ ok: true, value: null })
39
+ } }
40
+ watchers.add(watcher)
41
+ signal.addEventListener('abort', watcher.release, { once: true })
42
+ if (signal.aborted) watcher.release()
43
+ })
44
+ }
45
+ const injection = ctx.inject?.(['ptcPlusRpc'], scope => {
46
+ if (typeof scope.ptcPlusRpc?.register !== 'function') return
47
+ const owner = typeof scope.effect === 'function' ? scope : ctx
48
+ owner.effect(() => {
49
+ const unregister = scope.ptcPlusRpc.register(REPL_OBSERVATION_RPC_CONTRACT, handler)
50
+ const release = async () => {
51
+ if (!registrations.delete(release)) return
52
+ clear()
53
+ await unregister()
54
+ }
55
+ registrations.add(release)
56
+ return release
57
+ }, 'ptc-plus: visible REPL observation')
58
+ })
59
+ return {
60
+ has(sessionId) { return enabled && [...watchers].some(watcher => watcher.sessionId === sessionId) },
61
+ reconfigure(value) { enabled = value; if (!enabled) clear() },
62
+ async dispose() {
63
+ enabled = false
64
+ clear()
65
+ for (const release of [...registrations]) await release()
66
+ if (typeof injection === 'function') await injection()
67
+ else await injection?.dispose?.()
68
+ },
69
+ }
70
+ }
@@ -9,6 +9,12 @@ export const CLASS_REDECLARATION_REWRITE =
9
9
 
10
10
  export const LEGACY_DEFAULT_EXPORT_BINDING = 'legacy-variable'
11
11
  export const LIVE_DEFAULT_EXPORT_BINDING = 'live-readonly'
12
+ export const LEGACY_IMPORT_EXPRESSION_BOUNDARY = 'legacy'
13
+ export const LIVE_IMPORT_EXPRESSION_BOUNDARY = 'statement-safe'
14
+ export const LIVE_MODULE_SEMANTICS = Object.freeze({
15
+ defaultExportBinding: LIVE_DEFAULT_EXPORT_BINDING,
16
+ importExpressionBoundary: LIVE_IMPORT_EXPRESSION_BOUNDARY,
17
+ })
12
18
 
13
19
  export function redeclarationCommitTarget(name, statementStart) {
14
20
  return `redeclaration:${statementStart}:${name}`