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.
- package/README.en.md +177 -0
- package/README.md +85 -69
- package/assets/ptc-plus-bindings-en.png +0 -0
- package/assets/ptc-plus-bindings-zh.png +0 -0
- package/assets/ptc-plus-repair-en.png +0 -0
- package/assets/ptc-plus-settings-en.png +0 -0
- package/assets/ptc-plus-settings-zh.png +0 -0
- package/client.js +1168 -144
- package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +1 -1
- package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +15 -3
- package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +7 -3
- package/docs/adr/0014-persist-cell-rewrite-policy.md +7 -5
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +19 -4
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +5 -3
- package/docs/adr/0022-stage-npm-releases-with-oidc.md +2 -2
- package/docs/architecture.md +23 -14
- package/docs/client-ui.md +29 -11
- package/docs/durability-design.md +42 -20
- package/docs/installation.md +11 -3
- package/docs/program-data-plane.md +6 -0
- package/docs/runtime-reference.md +27 -14
- package/docs/value-wire.md +1 -1
- package/index.js +38 -4
- package/internal/cell-analysis.js +89 -12
- package/internal/cell-rewriter.js +162 -4
- package/internal/config-spec.js +192 -117
- package/internal/direct-surface-owner.js +56 -5
- package/internal/edit-transport-owner.js +11 -2
- package/internal/failure-reporting.js +15 -0
- package/internal/kernel-worker.js +11 -0
- package/internal/rejected-cell-editor.js +21 -12
- package/internal/repl-convenience.js +94 -6
- package/internal/repl-memory-projection.js +277 -0
- package/internal/repl-rewrite-contract.js +15 -0
- package/internal/run-code-description.js +34 -0
- package/internal/runtime-bridge-owner.js +63 -15
- package/internal/runtime-config.js +3 -0
- package/internal/session-cell-executor.js +74 -14
- package/internal/session-events.js +7 -0
- package/internal/session-journal.js +331 -63
- package/internal/session-log-view.js +2 -1
- package/internal/session-runtime.js +110 -9
- package/internal/session-state.js +116 -4
- package/package.json +10 -7
- package/src/client-activity.js +501 -0
- package/src/client.js +456 -20
- package/README.zh.md +0 -161
- /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
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =>
|
|
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:
|
|
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:
|
|
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 }
|