dsh-ptc-plus 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +66 -73
- package/README.md +66 -73
- 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-repair-zh.png +0 -0
- package/assets/ptc-plus-repl-workspace-en.png +0 -0
- package/assets/ptc-plus-repl-workspace-zh.png +0 -0
- package/assets/ptc-plus-settings-en.png +0 -0
- package/assets/ptc-plus-settings-zh.png +0 -0
- package/client.js +30610 -722
- package/compiler-amaro.cjs +5 -0
- package/compiler-amaro.cjs.map +6 -0
- package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
- package/compiler-assets.json +3 -0
- package/compiler-core.cjs +554 -0
- package/compiler-core.cjs.map +6 -0
- package/compiler-platform.cjs +2 -0
- package/compiler-platform.cjs.map +6 -0
- package/compiler-typescript.cjs +388 -0
- package/compiler-typescript.cjs.map +6 -0
- package/docs/README.md +6 -0
- package/docs/adr/0004-declare-program-bindings-at-the-host.md +1 -1
- package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +6 -4
- package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +15 -3
- package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
- package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
- package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
- package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
- package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +9 -3
- package/docs/adr/0014-persist-cell-rewrite-policy.md +11 -5
- package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
- package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
- package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +8 -6
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +17 -3
- package/docs/adr/0023-global-user-bindings.md +104 -0
- package/docs/adr/0024-repl-console-observation.md +33 -0
- package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
- package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
- package/docs/architecture.md +82 -22
- package/docs/binding-review-placement-design.md +200 -0
- package/docs/capability-projection.md +1 -1
- package/docs/client-ui.md +96 -20
- package/docs/durability-design.md +61 -24
- package/docs/evaluation.md +78 -6
- package/docs/installation.md +41 -6
- package/docs/program-data-plane.md +6 -0
- package/docs/publishing.md +5 -3
- package/docs/runtime-reference.md +79 -25
- package/docs/semantic-validation.md +29 -0
- package/docs/stateful-binding-updates-proposal.md +755 -0
- package/docs/user-bindings.en.md +64 -0
- package/docs/user-bindings.md +64 -0
- package/docs/value-wire.md +3 -3
- package/docs/verification.md +57 -0
- package/index.js +374 -92
- package/internal/ast-traversal.js +5 -2
- package/internal/binding-descriptors.js +1 -0
- package/internal/binding-pattern-traversal.js +25 -0
- package/internal/binding-pattern.js +26 -29
- package/internal/binding-source-analysis.js +391 -0
- package/internal/binding-update-policy.js +66 -0
- package/internal/bounded-worker-round.js +56 -0
- package/internal/callable-definition-environment.js +42 -0
- package/internal/callable-source-catalog.js +129 -0
- package/internal/callable-source-encoding.js +47 -0
- package/internal/callable-source-facts.js +459 -0
- package/internal/callable-source-format.js +2 -0
- package/internal/callable-source-lowering.js +123 -0
- package/internal/cell-analysis-contract.js +25 -0
- package/internal/cell-analysis.js +252 -242
- package/internal/cell-completion.js +15 -0
- package/internal/cell-error.js +7 -0
- package/internal/cell-parser.js +66 -0
- package/internal/cell-rewriter.js +3 -553
- package/internal/commonjs-export-evidence.js +63 -0
- package/internal/commonjs-source-evidence.js +72 -0
- package/internal/compiler-amaro.cjs +2 -0
- package/internal/compiler-compression.js +12 -0
- package/internal/compiler-data.js +58 -0
- package/internal/compiler-descriptors.js +54 -0
- package/internal/compiler-dynamic-cache.js +48 -0
- package/internal/compiler-entry.js +35 -0
- package/internal/compiler-generated-names.js +17 -0
- package/internal/compiler-intrinsics.js +84 -0
- package/internal/compiler-module-links.js +34 -0
- package/internal/compiler-operations.js +61 -0
- package/internal/compiler-platform-factory.js +84 -0
- package/internal/compiler-platform.js +136 -0
- package/internal/compiler-record-roles.js +53 -0
- package/internal/compiler-region-output.js +259 -0
- package/internal/compiler-resource-lowering.js +103 -0
- package/internal/compiler-scope-facts.js +115 -0
- package/internal/compiler-service.js +109 -0
- package/internal/compiler-source-cache.js +32 -0
- package/internal/compiler-source-pieces.js +93 -0
- package/internal/compiler-source-regions.js +132 -0
- package/internal/compiler-storage-source.js +54 -0
- package/internal/compiler-text.js +15 -0
- package/internal/compiler-typescript.cjs +2 -0
- package/internal/config-spec.js +91 -25
- package/internal/cordis-tools-owner.js +130 -21
- package/internal/diagnostic.js +27 -1
- package/internal/direct-surface-owner.js +76 -23
- package/internal/dynamic-binding-evidence.js +16 -0
- package/internal/dynamic-environment-compiler.js +779 -0
- package/internal/dynamic-environment-integration.js +87 -0
- package/internal/dynamic-environment-runtime.js +362 -0
- package/internal/dynamic-native-calls.js +150 -0
- package/internal/dynamic-native-runtime.js +248 -0
- package/internal/dynamic-scope-analysis.js +90 -0
- package/internal/edit-transport-owner.js +28 -3
- package/internal/execution-seam-compat.js +193 -0
- package/internal/failure-reporting.js +118 -18
- package/internal/host-rpc.js +66 -0
- package/internal/isolated-worker.js +548 -0
- package/internal/kernel-child.js +139 -0
- package/internal/kernel-worker.js +911 -116
- package/internal/language-semantics.js +14 -0
- package/internal/legacy-binding-catalog.js +48 -0
- package/internal/legacy-cell-analysis.js +600 -0
- package/internal/legacy-cell-parser.js +50 -0
- package/internal/legacy-cell-rewriter.js +631 -0
- package/internal/legacy-repl-convenience.js +291 -0
- package/internal/managed-module-operations.js +170 -0
- package/internal/module-compilation.js +321 -0
- package/internal/module-runtime-platform.js +19 -0
- package/internal/module-transform-contract.js +12 -0
- package/internal/native-await-declarations.js +68 -0
- package/internal/native-root-compilation.js +54 -0
- package/internal/native-root-dynamic.js +67 -0
- package/internal/recovery-tips.js +27 -15
- package/internal/rejected-cell-editor.js +13 -8
- package/internal/repl-convenience.js +2 -201
- package/internal/repl-memory-projection.js +81 -12
- package/internal/repl-observation-interest.js +70 -0
- package/internal/repl-rewrite-contract.js +21 -0
- package/internal/repl-scope-normalizer.js +2091 -0
- package/internal/repl-value-observer.js +114 -0
- package/internal/rpc-contract.js +52 -0
- package/internal/runtime-bridge-owner.js +172 -28
- package/internal/runtime-config.js +48 -51
- package/internal/runtime-contexts.js +82 -10
- package/internal/runtime-messages.js +111 -0
- package/internal/session-cell-executor.js +455 -91
- package/internal/session-journal-recovery.js +684 -0
- package/internal/session-journal-schema.js +106 -0
- package/internal/session-journal.js +237 -440
- package/internal/session-log-view.js +42 -9
- package/internal/session-runtime.js +325 -80
- package/internal/session-state.js +249 -40
- package/internal/source-position-map.js +211 -71
- package/internal/stateful-module-compiler.js +154 -0
- package/internal/stateful-module-runtime.js +303 -0
- package/internal/stateful-root-compiler.js +598 -0
- package/internal/stateful-root-runtime.js +340 -0
- package/internal/typert-codec-compat.js +28 -0
- package/internal/typescript-transform.js +29 -0
- package/internal/user-binding-authoring.js +70 -0
- package/internal/user-binding-console-worker.js +184 -0
- package/internal/user-binding-console.js +148 -0
- package/internal/user-binding-draft-projection.js +335 -0
- package/internal/user-binding-model-context.js +31 -0
- package/internal/user-binding-runner.js +56 -0
- package/internal/user-bindings-owner.js +1065 -0
- package/internal/user-bindings-store.js +293 -0
- package/internal/user-bindings.js +353 -0
- package/internal/value-wire-schema.js +28 -0
- package/internal/value-wire.js +29 -20
- package/internal/worker-client.js +57 -67
- package/internal/worker-environment.js +45 -0
- package/internal/worker-output-relay.js +29 -0
- package/internal/worker-shutdown.js +14 -0
- package/package.json +81 -11
- package/src/client-activity.js +109 -45
- package/src/client-authoring-view.js +580 -0
- package/src/client-binding-review.js +230 -0
- package/src/client-bindings-data.js +29 -0
- package/src/client-catalog.js +127 -0
- package/src/client-code-editor.js +84 -0
- package/src/client-console.js +112 -0
- package/src/client-copy.js +370 -0
- package/src/client-feature-gates.js +62 -0
- package/src/client-host-compat.js +150 -0
- package/src/client-indicator-view.js +267 -0
- package/src/client-repl-view.js +292 -0
- package/src/client-rpc.js +20 -0
- package/src/client-settings-view.js +221 -0
- package/src/client-styles.js +106 -0
- package/src/client-tool-view.js +108 -0
- package/src/client-transport.js +159 -0
- package/src/client-workbench.js +521 -0
- 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: '
|
|
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,
|
|
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
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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,201 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { SKIP_AST_CHILDREN, walkAst } from './ast-traversal.js'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Optional compatibility policy for a persistent REPL. The cell language does
|
|
7
|
-
* not own this policy: callers may replace it when persistence uses separate
|
|
8
|
-
* lexical frames instead of one shared REPL environment.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
function containsTopLevelAwait(node) {
|
|
12
|
-
let found = false
|
|
13
|
-
walkAst(node, current => {
|
|
14
|
-
if (current.type === 'AwaitExpression') found = true
|
|
15
|
-
if (current.type === 'FunctionDeclaration' || current.type === 'FunctionExpression'
|
|
16
|
-
|| current.type === 'ArrowFunctionExpression') return SKIP_AST_CHILDREN
|
|
17
|
-
})
|
|
18
|
-
return found
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function mixedDeclaratorText(statement, declarator, bindings, code, offset, allocateName) {
|
|
22
|
-
const fresh = bindings.filter(binding => !binding.existing)
|
|
23
|
-
const existing = bindings.filter(binding => binding.existing)
|
|
24
|
-
const valueName = allocateName('mixed_value')
|
|
25
|
-
const commitName = allocateName('mixed_commit')
|
|
26
|
-
const valuesName = allocateName('mixed_values')
|
|
27
|
-
const positions = new Map(bindings.map((binding, index) => [binding.name, index]))
|
|
28
|
-
const asynchronous = containsTopLevelAwait(declarator.id)
|
|
29
|
-
const builder = createMappedTextBuilder(code, offset)
|
|
30
|
-
const appendBindingName = binding => builder.appendMapped(binding.name, binding.start, binding.end)
|
|
31
|
-
builder.append(statement.kind === 'var' ? 'var [' : 'let [')
|
|
32
|
-
fresh.forEach((binding, index) => {
|
|
33
|
-
if (index > 0) builder.append(', ')
|
|
34
|
-
appendBindingName(binding)
|
|
35
|
-
})
|
|
36
|
-
builder.append(`] = ${asynchronous ? 'await ' : ''}(`)
|
|
37
|
-
builder.append(`(${asynchronous ? 'async ' : ''}(${valueName}, ${commitName}) => { ${statement.kind} `)
|
|
38
|
-
builder.appendSource(declarator.id.start, declarator.id.end)
|
|
39
|
-
builder.append(` = ${valueName}; return ${commitName}([`)
|
|
40
|
-
bindings.forEach((binding, index) => {
|
|
41
|
-
if (index > 0) builder.append(', ')
|
|
42
|
-
appendBindingName(binding)
|
|
43
|
-
})
|
|
44
|
-
builder.append(`]); })((`)
|
|
45
|
-
if (declarator.init === null) builder.append('undefined')
|
|
46
|
-
else builder.appendSource(declarator.init.start, declarator.init.end)
|
|
47
|
-
builder.append(`), (${valuesName}) => { `)
|
|
48
|
-
existing.forEach((binding, index) => {
|
|
49
|
-
if (index > 0) builder.append(' ')
|
|
50
|
-
appendBindingName(binding)
|
|
51
|
-
builder.append(` = ${valuesName}[${positions.get(binding.name)}];`)
|
|
52
|
-
})
|
|
53
|
-
builder.append(' return [')
|
|
54
|
-
fresh.forEach((binding, index) => {
|
|
55
|
-
if (index > 0) builder.append(', ')
|
|
56
|
-
builder.append(valuesName)
|
|
57
|
-
builder.append(`[${positions.get(binding.name)}]`)
|
|
58
|
-
})
|
|
59
|
-
builder.append(']; }) );')
|
|
60
|
-
return builder.result()
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function existingDeclaratorText(declarator, code, offset) {
|
|
64
|
-
const builder = createMappedTextBuilder(code, offset)
|
|
65
|
-
builder.append(';(')
|
|
66
|
-
builder.appendSource(declarator.id.start, declarator.id.end)
|
|
67
|
-
builder.append(' = ')
|
|
68
|
-
if (declarator.init === null) builder.append('undefined')
|
|
69
|
-
else builder.appendSource(declarator.init.start, declarator.init.end)
|
|
70
|
-
builder.append(');')
|
|
71
|
-
return builder.result()
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function freshDeclaratorText(statement, declarator, code, offset) {
|
|
75
|
-
const builder = createMappedTextBuilder(code, offset)
|
|
76
|
-
builder.append(statement.kind === 'var' ? 'var ' : 'let ')
|
|
77
|
-
builder.appendSource(declarator.start, declarator.end)
|
|
78
|
-
builder.append(';')
|
|
79
|
-
return builder.result()
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function bindingEntries(pattern, declarationSpan) {
|
|
83
|
-
return bindingNodes(pattern).map(node => ({
|
|
84
|
-
name: node.name,
|
|
85
|
-
start: node.start,
|
|
86
|
-
end: node.end,
|
|
87
|
-
span: declarationSpan(node),
|
|
88
|
-
}))
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function rewriteLooseRedeclarations({
|
|
92
|
-
code,
|
|
93
|
-
sourceMap = identitySourceMap(code.length),
|
|
94
|
-
body,
|
|
95
|
-
offset,
|
|
96
|
-
knownBindings,
|
|
97
|
-
declarations,
|
|
98
|
-
declarationSpan,
|
|
99
|
-
collisionFor,
|
|
100
|
-
autoSplitRedeclarations,
|
|
101
|
-
}) {
|
|
102
|
-
const redeclared = []
|
|
103
|
-
const rewrites = []
|
|
104
|
-
if (body === undefined) {
|
|
105
|
-
return {
|
|
106
|
-
executableCode: code,
|
|
107
|
-
executableSourceMap: sourceMap,
|
|
108
|
-
collisions: declarations.filter(declaration => knownBindings.has(declaration.name)).map(collisionFor),
|
|
109
|
-
redeclared,
|
|
110
|
-
rewrites,
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
const replacements = []
|
|
114
|
-
const rejected = []
|
|
115
|
-
const allocateName = createGeneratedNameAllocator(body)
|
|
116
|
-
for (const statement of body) {
|
|
117
|
-
if (statement.type !== 'VariableDeclaration') {
|
|
118
|
-
if ((statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration')
|
|
119
|
-
&& statement.id !== null && knownBindings.has(statement.id.name)) {
|
|
120
|
-
rejected.push({ name: statement.id.name, span: declarationSpan(statement.id) })
|
|
121
|
-
}
|
|
122
|
-
continue
|
|
123
|
-
}
|
|
124
|
-
const entries = []
|
|
125
|
-
let statementRejected = false
|
|
126
|
-
for (const declarator of statement.declarations) {
|
|
127
|
-
const bindings = bindingEntries(declarator.id, declarationSpan)
|
|
128
|
-
const existing = bindings.filter(binding => knownBindings.has(binding.name))
|
|
129
|
-
if (existing.length > 0 && existing.length < bindings.length) {
|
|
130
|
-
if (autoSplitRedeclarations) entries.push({ declarator, bindings, existing })
|
|
131
|
-
else {
|
|
132
|
-
rejected.push(...existing)
|
|
133
|
-
statementRejected = true
|
|
134
|
-
}
|
|
135
|
-
continue
|
|
136
|
-
}
|
|
137
|
-
entries.push({ declarator, bindings, existing })
|
|
138
|
-
}
|
|
139
|
-
if (statementRejected) continue
|
|
140
|
-
if (!entries.some(entry => entry.existing.length > 0)) {
|
|
141
|
-
if (statement.kind === 'const') {
|
|
142
|
-
replacements.push({
|
|
143
|
-
start: statement.start - offset,
|
|
144
|
-
end: statement.start - offset + statement.kind.length,
|
|
145
|
-
text: 'let',
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
continue
|
|
149
|
-
}
|
|
150
|
-
let replacementText = ''
|
|
151
|
-
const replacementMappings = []
|
|
152
|
-
const appendPart = (part) => {
|
|
153
|
-
if (replacementText.length > 0) replacementText += '\n'
|
|
154
|
-
const partOffset = replacementText.length
|
|
155
|
-
replacementText += part.text
|
|
156
|
-
part.mappings.forEach(mapping => replacementMappings.push({
|
|
157
|
-
...mapping,
|
|
158
|
-
generatedStart: mapping.generatedStart + partOffset,
|
|
159
|
-
generatedEnd: mapping.generatedEnd + partOffset,
|
|
160
|
-
}))
|
|
161
|
-
}
|
|
162
|
-
for (const { declarator, bindings, existing } of entries) {
|
|
163
|
-
if (existing.length > 0 && existing.length < bindings.length) {
|
|
164
|
-
appendPart(mixedDeclaratorText(
|
|
165
|
-
statement,
|
|
166
|
-
declarator,
|
|
167
|
-
bindings.map(binding => ({ ...binding, existing: knownBindings.has(binding.name) })),
|
|
168
|
-
code,
|
|
169
|
-
offset,
|
|
170
|
-
allocateName,
|
|
171
|
-
))
|
|
172
|
-
redeclared.push(...existing.map(collisionFor))
|
|
173
|
-
rewrites.push({
|
|
174
|
-
kind: 'redeclaration',
|
|
175
|
-
description: 'split a mixed top-level declaration while preserving native pattern initialization',
|
|
176
|
-
source: existing.map(binding => binding.name).join(', '),
|
|
177
|
-
})
|
|
178
|
-
} else if (existing.length === bindings.length && bindings.length > 0) {
|
|
179
|
-
redeclared.push(...existing.map(collisionFor))
|
|
180
|
-
appendPart(existingDeclaratorText(declarator, code, offset))
|
|
181
|
-
} else {
|
|
182
|
-
appendPart(freshDeclaratorText(statement, declarator, code, offset))
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
replacements.push({
|
|
186
|
-
start: statement.start - offset,
|
|
187
|
-
end: statement.end - offset,
|
|
188
|
-
text: replacementText,
|
|
189
|
-
mappings: replacementMappings,
|
|
190
|
-
})
|
|
191
|
-
}
|
|
192
|
-
const collisions = rejected.map(collisionFor)
|
|
193
|
-
let executableCode = code
|
|
194
|
-
let executableSourceMap = sourceMap
|
|
195
|
-
if (collisions.length === 0) {
|
|
196
|
-
const applied = applySourceEdits(code, sourceMap, replacements)
|
|
197
|
-
executableCode = applied.code
|
|
198
|
-
executableSourceMap = applied.sourceMap
|
|
199
|
-
}
|
|
200
|
-
return { executableCode, executableSourceMap, collisions, redeclared, rewrites }
|
|
201
|
-
}
|
|
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
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
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
|
+
})
|
|
18
|
+
|
|
19
|
+
export function redeclarationCommitTarget(name, statementStart) {
|
|
20
|
+
return `redeclaration:${statementStart}:${name}`
|
|
21
|
+
}
|