dsh-ptc-plus 0.2.4 → 0.3.2

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 (49) hide show
  1. package/README.en.md +177 -0
  2. package/README.md +85 -69
  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-settings-en.png +0 -0
  7. package/assets/ptc-plus-settings-zh.png +0 -0
  8. package/client.js +1168 -144
  9. package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +1 -1
  10. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +15 -3
  11. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +7 -3
  12. package/docs/adr/0014-persist-cell-rewrite-policy.md +7 -5
  13. package/docs/adr/0019-plugin-settings-and-kill-switch.md +19 -4
  14. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  15. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +5 -3
  16. package/docs/adr/0022-stage-npm-releases-with-oidc.md +2 -2
  17. package/docs/architecture.md +23 -14
  18. package/docs/client-ui.md +29 -11
  19. package/docs/durability-design.md +42 -20
  20. package/docs/installation.md +11 -3
  21. package/docs/program-data-plane.md +6 -0
  22. package/docs/runtime-reference.md +27 -14
  23. package/docs/value-wire.md +1 -1
  24. package/index.js +38 -4
  25. package/internal/cell-analysis.js +89 -12
  26. package/internal/cell-rewriter.js +162 -4
  27. package/internal/config-spec.js +192 -117
  28. package/internal/direct-surface-owner.js +56 -5
  29. package/internal/edit-transport-owner.js +11 -2
  30. package/internal/failure-reporting.js +15 -0
  31. package/internal/kernel-worker.js +11 -0
  32. package/internal/rejected-cell-editor.js +21 -12
  33. package/internal/repl-convenience.js +94 -6
  34. package/internal/repl-memory-projection.js +277 -0
  35. package/internal/repl-rewrite-contract.js +15 -0
  36. package/internal/run-code-description.js +34 -0
  37. package/internal/runtime-bridge-owner.js +63 -15
  38. package/internal/runtime-config.js +3 -0
  39. package/internal/session-cell-executor.js +74 -14
  40. package/internal/session-events.js +7 -0
  41. package/internal/session-journal.js +331 -63
  42. package/internal/session-log-view.js +2 -1
  43. package/internal/session-runtime.js +110 -9
  44. package/internal/session-state.js +116 -4
  45. package/package.json +10 -7
  46. package/src/client-activity.js +501 -0
  47. package/src/client.js +456 -20
  48. package/README.zh.md +0 -161
  49. /package/assets/{ptc-plus-repair.png → ptc-plus-repair-zh.png} +0 -0
@@ -252,15 +252,29 @@ function assemble(source, replacements) {
252
252
  return changed ? { code: chunks.join('') } : { error: 'edits must change at least one matched fragment' }
253
253
  }
254
254
 
255
+ function editRunCodeParameterBranch(operation, operationSchema) {
256
+ return {
257
+ type: 'object',
258
+ additionalProperties: false,
259
+ properties: {
260
+ [operation]: operationSchema,
261
+ [EXPECTED_TARGET_CALL_SEQ]: {
262
+ type: 'integer', minimum: 0,
263
+ description: 'Optional target precondition copied from a validated diagnostic. The edit is rejected if the captured cell has another call sequence.',
264
+ },
265
+ },
266
+ required: [operation],
267
+ }
268
+ }
269
+
255
270
  export function editRunCodeSchema() {
256
271
  return {
257
272
  name: 'edit_run_code',
258
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.',
259
274
  parameters: {
260
275
  type: 'object',
261
- additionalProperties: false,
262
- properties: {
263
- edits: {
276
+ oneOf: [
277
+ editRunCodeParameterBranch('edits', {
264
278
  type: 'array', minItems: 1, maxItems: EDIT_LIMITS.exactEdits,
265
279
  description: 'Atomic exact replacements, all resolved against the original target cell.',
266
280
  items: {
@@ -271,8 +285,8 @@ export function editRunCodeSchema() {
271
285
  },
272
286
  required: ['old_string', 'new_string'],
273
287
  },
274
- },
275
- regex_edits: {
288
+ }),
289
+ editRunCodeParameterBranch('regex_edits', {
276
290
  type: 'array', minItems: 1, maxItems: EDIT_LIMITS.regexEdits,
277
291
  description: 'Atomic regular-expression replacements, all matched against the original target cell.',
278
292
  items: {
@@ -285,13 +299,8 @@ export function editRunCodeSchema() {
285
299
  },
286
300
  required: ['pattern', 'flags', 'replacement', 'expected_matches'],
287
301
  },
288
- },
289
- [EXPECTED_TARGET_CALL_SEQ]: {
290
- type: 'integer', minimum: 0,
291
- description: 'Optional target precondition copied from a validated diagnostic. The edit is rejected if the captured cell has another call sequence.',
292
- },
293
- },
294
- oneOf: [{ required: ['edits'] }, { required: ['regex_edits'] }],
302
+ }),
303
+ ],
295
304
  },
296
305
  }
297
306
  }
@@ -1,4 +1,10 @@
1
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'
2
8
  import { applySourceEdits, createMappedTextBuilder, identitySourceMap } from './source-position-map.js'
3
9
  import { SKIP_AST_CHILDREN, walkAst } from './ast-traversal.js'
4
10
 
@@ -88,16 +94,41 @@ function bindingEntries(pattern, declarationSpan) {
88
94
  }))
89
95
  }
90
96
 
91
- export function rewriteLooseRedeclarations({
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({
92
119
  code,
93
120
  sourceMap = identitySourceMap(code.length),
94
121
  body,
95
122
  offset,
96
123
  knownBindings,
124
+ variableRedeclarationBindings = knownBindings,
125
+ writableBindings,
97
126
  declarations,
98
127
  declarationSpan,
99
128
  collisionFor,
129
+ bindingPolicy,
100
130
  autoSplitRedeclarations,
131
+ commitSignal,
101
132
  }) {
102
133
  const redeclared = []
103
134
  const rewrites = []
@@ -117,7 +148,47 @@ export function rewriteLooseRedeclarations({
117
148
  if (statement.type !== 'VariableDeclaration') {
118
149
  if ((statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration')
119
150
  && statement.id !== null && knownBindings.has(statement.id.name)) {
120
- rejected.push({ name: statement.id.name, span: declarationSpan(statement.id) })
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
+ }
121
192
  }
122
193
  continue
123
194
  }
@@ -125,7 +196,24 @@ export function rewriteLooseRedeclarations({
125
196
  let statementRejected = false
126
197
  for (const declarator of statement.declarations) {
127
198
  const bindings = bindingEntries(declarator.id, declarationSpan)
128
- const existing = bindings.filter(binding => knownBindings.has(binding.name))
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
+ }
129
217
  if (existing.length > 0 && existing.length < bindings.length) {
130
218
  if (autoSplitRedeclarations) entries.push({ declarator, bindings, existing })
131
219
  else {
@@ -138,7 +226,7 @@ export function rewriteLooseRedeclarations({
138
226
  }
139
227
  if (statementRejected) continue
140
228
  if (!entries.some(entry => entry.existing.length > 0)) {
141
- if (statement.kind === 'const') {
229
+ if (statement.kind === 'const' && bindingPolicy.variableRedeclarations) {
142
230
  replacements.push({
143
231
  start: statement.start - offset,
144
232
  end: statement.start - offset + statement.kind.length,
@@ -164,7 +252,7 @@ export function rewriteLooseRedeclarations({
164
252
  appendPart(mixedDeclaratorText(
165
253
  statement,
166
254
  declarator,
167
- bindings.map(binding => ({ ...binding, existing: knownBindings.has(binding.name) })),
255
+ bindings.map(binding => ({ ...binding, existing: existing.includes(binding) })),
168
256
  code,
169
257
  offset,
170
258
  allocateName,
@@ -172,7 +260,7 @@ export function rewriteLooseRedeclarations({
172
260
  redeclared.push(...existing.map(collisionFor))
173
261
  rewrites.push({
174
262
  kind: 'redeclaration',
175
- description: 'split a mixed top-level declaration while preserving native pattern initialization',
263
+ description: MIXED_REDECLARATION_REWRITE,
176
264
  source: existing.map(binding => binding.name).join(', '),
177
265
  })
178
266
  } else if (existing.length === bindings.length && bindings.length > 0) {
@@ -0,0 +1,277 @@
1
+ import { isRecord } from './record-utils.js'
2
+
3
+ export const REPL_MEMORY_KEY = 'ptcPlusRepl'
4
+ export const REPL_MEMORY_META_KEY = 'dshPtcPlusBindings'
5
+
6
+ const BINDING_KINDS = new Set(['variable', 'function', 'class', 'import'])
7
+ const MAX_BINDINGS = 128
8
+ const MAX_BINDING_NAME_LENGTH = 128
9
+ const MAX_DEFINITION_SOURCE_LENGTH = 1024
10
+ const MAX_DEFINITION_SOURCE_TOTAL_LENGTH = 16 * 1024
11
+ const MAX_PENDING_REPL_CALLS = 256
12
+ const MAX_CALL_ID_LENGTH = 512
13
+ const MAX_GENERATION_LENGTH = 128
14
+ const SNAPSHOT_FIELDS = new Set(['available', 'entries', 'total', 'omitted'])
15
+ const ENTRY_FIELDS = new Set(['name', 'kind', 'definition'])
16
+ const DEFINITION_FIELDS = new Set(['source', 'line', 'column'])
17
+ const MEMORY_META_FIELDS = new Set(['version', 'generation', 'memory'])
18
+ const PROJECTION_STATE_FIELDS = new Set(['generation', 'memory', 'pendingReplCalls'])
19
+ 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
22
+
23
+ function exactFields(value, fields) {
24
+ if (!isRecord(value)) return false
25
+ const keys = Reflect.ownKeys(value)
26
+ return keys.length === fields.size && keys.every(key => (
27
+ typeof key === 'string' && fields.has(key)
28
+ && Object.prototype.propertyIsEnumerable.call(value, key)
29
+ ))
30
+ }
31
+
32
+ const EMPTY_REPL_MEMORY = Object.freeze({
33
+ available: false,
34
+ entries: Object.freeze([]),
35
+ total: 0,
36
+ omitted: 0,
37
+ })
38
+
39
+ export function unavailableReplMemorySnapshot() {
40
+ return EMPTY_REPL_MEMORY
41
+ }
42
+
43
+ function normalizeGeneration(value) {
44
+ if (typeof value !== 'string' || value.length === 0 || value.length > MAX_GENERATION_LENGTH) {
45
+ throw new Error('invalid dsh-ptc-plus REPL memory generation')
46
+ }
47
+ return value
48
+ }
49
+
50
+ function normalizeDefinition(value) {
51
+ if (!exactFields(value, DEFINITION_FIELDS)
52
+ || typeof value.source !== 'string' || value.source.length === 0
53
+ || value.source.length > MAX_DEFINITION_SOURCE_LENGTH
54
+ || !Number.isSafeInteger(value.line) || value.line < 1
55
+ || !Number.isSafeInteger(value.column) || value.column < 1) {
56
+ throw new Error('invalid dsh-ptc-plus REPL binding definition')
57
+ }
58
+ return Object.freeze({ source: value.source, line: value.line, column: value.column })
59
+ }
60
+
61
+ function normalizeBinding(value) {
62
+ if (!exactFields(value, ENTRY_FIELDS) || typeof value.name !== 'string'
63
+ || value.name.length === 0 || value.name.length > MAX_BINDING_NAME_LENGTH
64
+ || !BINDING_KINDS.has(value.kind)) {
65
+ throw new Error('invalid dsh-ptc-plus REPL memory binding')
66
+ }
67
+ return Object.freeze({
68
+ name: value.name,
69
+ kind: value.kind,
70
+ definition: normalizeDefinition(value.definition),
71
+ })
72
+ }
73
+
74
+ /** Produce bounded, value-independent presentation metadata from the live binding catalog. */
75
+ export function createReplMemorySnapshot(bindings) {
76
+ const all = Array.isArray(bindings) ? bindings : []
77
+ const candidates = []
78
+ const names = new Set()
79
+ for (const binding of all) {
80
+ try {
81
+ const normalized = normalizeBinding(binding)
82
+ if (names.has(normalized.name)) continue
83
+ names.add(normalized.name)
84
+ candidates.push(normalized)
85
+ } catch {}
86
+ }
87
+ const entries = []
88
+ let sourceLength = 0
89
+ for (const binding of candidates) {
90
+ if (entries.length >= MAX_BINDINGS) break
91
+ if (sourceLength + binding.definition.source.length > MAX_DEFINITION_SOURCE_TOTAL_LENGTH) continue
92
+ entries.push(binding)
93
+ sourceLength += binding.definition.source.length
94
+ }
95
+ return Object.freeze({
96
+ available: true,
97
+ entries: Object.freeze(entries),
98
+ total: candidates.length,
99
+ omitted: candidates.length - entries.length,
100
+ })
101
+ }
102
+
103
+ /** Validate one complete session-projection value without accepting unknown fields. */
104
+ export function normalizeReplMemorySnapshot(value) {
105
+ if (!exactFields(value, SNAPSHOT_FIELDS) || typeof value.available !== 'boolean'
106
+ || !Array.isArray(value.entries) || value.entries.length > MAX_BINDINGS
107
+ || !Number.isSafeInteger(value.total) || value.total < 0
108
+ || !Number.isSafeInteger(value.omitted) || value.omitted < 0
109
+ || value.total !== value.entries.length + value.omitted) {
110
+ throw new Error('invalid dsh-ptc-plus REPL memory snapshot')
111
+ }
112
+ if (!value.available && (value.total !== 0 || value.entries.length !== 0)) {
113
+ throw new Error('unavailable dsh-ptc-plus REPL memory snapshot must be empty')
114
+ }
115
+ const names = new Set()
116
+ let sourceLength = 0
117
+ const entries = value.entries.map((entry) => {
118
+ const normalized = normalizeBinding(entry)
119
+ if (names.has(normalized.name)) {
120
+ throw new Error('invalid dsh-ptc-plus REPL memory binding')
121
+ }
122
+ sourceLength += normalized.definition.source.length
123
+ if (sourceLength > MAX_DEFINITION_SOURCE_TOTAL_LENGTH) {
124
+ throw new Error('dsh-ptc-plus REPL binding definitions exceed the presentation budget')
125
+ }
126
+ names.add(normalized.name)
127
+ return normalized
128
+ })
129
+ return Object.freeze({
130
+ available: value.available,
131
+ entries: Object.freeze(entries),
132
+ total: value.total,
133
+ omitted: value.omitted,
134
+ })
135
+ }
136
+
137
+ function normalizeReplMemoryMetadata(value) {
138
+ if (!exactFields(value, MEMORY_META_FIELDS) || value.version !== REPL_MEMORY_META_VERSION) {
139
+ throw new Error('invalid dsh-ptc-plus REPL memory metadata')
140
+ }
141
+ return Object.freeze({
142
+ version: REPL_MEMORY_META_VERSION,
143
+ generation: normalizeGeneration(value.generation),
144
+ memory: normalizeReplMemorySnapshot(value.memory),
145
+ })
146
+ }
147
+
148
+ /** Read current-owner presentation metadata without affecting tool settlement. */
149
+ export function validatedReplMemorySnapshot(meta, generation) {
150
+ if (!isRecord(meta) || !Object.hasOwn(meta, REPL_MEMORY_META_KEY)) return undefined
151
+ try {
152
+ const normalized = normalizeReplMemoryMetadata(meta[REPL_MEMORY_META_KEY])
153
+ return normalized.generation === normalizeGeneration(generation)
154
+ ? normalized.memory
155
+ : undefined
156
+ } catch {
157
+ return undefined
158
+ }
159
+ }
160
+
161
+ function projectionState(generation, memory, pendingReplCalls) {
162
+ return Object.freeze({
163
+ generation,
164
+ memory,
165
+ pendingReplCalls: pendingReplCalls === null
166
+ ? null
167
+ : Object.freeze(pendingReplCalls),
168
+ })
169
+ }
170
+
171
+ function emptyProjectionState(generation) {
172
+ return projectionState(normalizeGeneration(generation), EMPTY_REPL_MEMORY, [])
173
+ }
174
+
175
+ function normalizeProjectionState(value, generation) {
176
+ if (!exactFields(value, PROJECTION_STATE_FIELDS)) {
177
+ throw new Error('invalid dsh-ptc-plus REPL memory projection state')
178
+ }
179
+ const storedGeneration = normalizeGeneration(value.generation)
180
+ const currentGeneration = normalizeGeneration(generation)
181
+ if (storedGeneration !== currentGeneration) return emptyProjectionState(currentGeneration)
182
+ const memory = normalizeReplMemorySnapshot(value.memory)
183
+ if (value.pendingReplCalls === null) {
184
+ if (memory.available) {
185
+ throw new Error('untracked dsh-ptc-plus REPL calls require unavailable memory')
186
+ }
187
+ return projectionState(currentGeneration, memory, null)
188
+ }
189
+ if (!Array.isArray(value.pendingReplCalls)
190
+ || value.pendingReplCalls.length > MAX_PENDING_REPL_CALLS) {
191
+ throw new Error('invalid dsh-ptc-plus pending REPL calls')
192
+ }
193
+ let previousSeq
194
+ const callIds = new Set()
195
+ const pendingReplCalls = value.pendingReplCalls.map((pending) => {
196
+ if (!exactFields(pending, PENDING_CALL_FIELDS)
197
+ || typeof pending.callId !== 'string' || pending.callId.length === 0
198
+ || pending.callId.length > MAX_CALL_ID_LENGTH || callIds.has(pending.callId)
199
+ || !Number.isSafeInteger(pending.seq) || pending.seq < 0
200
+ || (previousSeq !== undefined && previousSeq >= pending.seq)) {
201
+ throw new Error('invalid dsh-ptc-plus pending REPL call')
202
+ }
203
+ callIds.add(pending.callId)
204
+ previousSeq = pending.seq
205
+ return Object.freeze({ callId: pending.callId, seq: pending.seq })
206
+ })
207
+ return projectionState(currentGeneration, memory, pendingReplCalls)
208
+ }
209
+
210
+ function eventCallId(value) {
211
+ return typeof value === 'string' && value.length > 0 && value.length <= MAX_CALL_ID_LENGTH
212
+ ? value
213
+ : undefined
214
+ }
215
+
216
+ function trackReplCall(state, event) {
217
+ if (!REPL_TOOL_NAMES.has(event.data?.name)) return state
218
+ const seq = event.seq
219
+ const callId = eventCallId(event.data?.callId)
220
+ if (!Number.isSafeInteger(seq) || seq < 0 || callId === undefined) return state
221
+ const pending = state.pendingReplCalls
222
+ if (pending === null || pending.some(call => call.callId === callId || call.seq === seq)) return state
223
+ if (pending.length >= MAX_PENDING_REPL_CALLS) {
224
+ return projectionState(state.generation, EMPTY_REPL_MEMORY, null)
225
+ }
226
+ return projectionState(state.generation, state.memory, [
227
+ ...pending,
228
+ Object.freeze({ callId, seq }),
229
+ ].sort((left, right) => left.seq - right.seq))
230
+ }
231
+
232
+ function settleReplCall(state, event, generation) {
233
+ if (event.surfaceOp !== undefined && event.surfaceOp !== 'append') return state
234
+ const pending = state.pendingReplCalls
235
+ if (pending === null) return state
236
+ const callId = eventCallId(event.data?.message?.source?.callId)
237
+ if (callId === undefined) return state
238
+ const index = pending.findIndex(call => call.callId === callId)
239
+ if (index < 0) return state
240
+ const remaining = pending.filter((_call, pendingIndex) => pendingIndex !== index)
241
+ const memory = validatedReplMemorySnapshot(event.data?.meta, generation) ?? state.memory
242
+ return projectionState(state.generation, memory, remaining)
243
+ }
244
+
245
+ /** Merge the binding snapshot into private, model-invisible result presentation metadata. */
246
+ export function withReplMemorySnapshot(meta, snapshot, generation) {
247
+ const base = isRecord(meta) ? { ...meta } : meta === undefined ? {} : { value: meta }
248
+ base[REPL_MEMORY_META_KEY] = normalizeReplMemoryMetadata({
249
+ version: REPL_MEMORY_META_VERSION,
250
+ generation: normalizeGeneration(generation),
251
+ memory: snapshot,
252
+ })
253
+ return base
254
+ }
255
+
256
+ /** Build the current runtime owner's value-independent session projection. */
257
+ export function createReplMemoryProjection(generation) {
258
+ const currentGeneration = normalizeGeneration(generation)
259
+ return Object.freeze({
260
+ key: REPL_MEMORY_KEY,
261
+ stateVersion: 3,
262
+ stateSchema: Object.freeze({
263
+ parse: value => normalizeProjectionState(value, currentGeneration),
264
+ }),
265
+ init: () => emptyProjectionState(currentGeneration),
266
+ apply(state, event) {
267
+ if (event?.type === 'session/end-seed') return emptyProjectionState(currentGeneration)
268
+ if (event?.type === 'tool/call') return trackReplCall(state, event)
269
+ if (event?.type === 'tool/result') return settleReplCall(state, event, currentGeneration)
270
+ return state
271
+ },
272
+ wire: Object.freeze({
273
+ viewSchema: Object.freeze({ parse: normalizeReplMemorySnapshot }),
274
+ view: state => state.memory,
275
+ }),
276
+ })
277
+ }
@@ -0,0 +1,15 @@
1
+ export const MIXED_REDECLARATION_REWRITE =
2
+ 'split a mixed top-level declaration while preserving native pattern initialization'
3
+
4
+ export const FUNCTION_REDECLARATION_REWRITE =
5
+ 'reassigned an existing top-level function declaration for REPL continuity'
6
+
7
+ export const CLASS_REDECLARATION_REWRITE =
8
+ 'reassigned an existing top-level class declaration for REPL continuity'
9
+
10
+ export const LEGACY_DEFAULT_EXPORT_BINDING = 'legacy-variable'
11
+ export const LIVE_DEFAULT_EXPORT_BINDING = 'live-readonly'
12
+
13
+ export function redeclarationCommitTarget(name, statementStart) {
14
+ return `redeclaration:${statementStart}:${name}`
15
+ }
@@ -0,0 +1,34 @@
1
+ const GENERATED_RUN_CODE_DESCRIPTION = 'Execute the next TypeScript cell in this session'
2
+ export const GENERATED_RUN_CODE_DESCRIPTION_KEY = 'dshPtcPlusRunCodeDescription'
3
+
4
+ // Keep presentation provenance outside the canonical JSON argument graph.
5
+ const generatedArguments = new WeakSet()
6
+
7
+ export function markGeneratedRunCodeArguments(argumentsValue) {
8
+ if (argumentsValue !== null
9
+ && (typeof argumentsValue === 'object' || typeof argumentsValue === 'function')) {
10
+ generatedArguments.add(argumentsValue)
11
+ }
12
+ return argumentsValue
13
+ }
14
+
15
+ export function generatedRunCodeExecutionArguments(argumentsValue) {
16
+ markGeneratedRunCodeArguments(argumentsValue)
17
+ return markGeneratedRunCodeArguments({
18
+ ...argumentsValue,
19
+ description: GENERATED_RUN_CODE_DESCRIPTION,
20
+ })
21
+ }
22
+
23
+ export function generatedRunCodeDescriptionMeta(argumentsValue, meta) {
24
+ if (!generatedArguments.has(argumentsValue)) return meta
25
+ const base = meta !== null && typeof meta === 'object' && !Array.isArray(meta)
26
+ ? meta
27
+ : meta === undefined ? {} : { value: meta }
28
+ return {
29
+ ...base,
30
+ [GENERATED_RUN_CODE_DESCRIPTION_KEY]: GENERATED_RUN_CODE_DESCRIPTION,
31
+ }
32
+ }
33
+
34
+ export { GENERATED_RUN_CODE_DESCRIPTION }