dsh-ptc-plus 0.3.2 → 0.4.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 +63 -80
- package/README.md +63 -80
- 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 +30827 -742
- 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 +556 -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 +7 -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 +5 -3
- package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +1 -1
- 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 +3 -1
- package/docs/adr/0014-persist-cell-rewrite-policy.md +6 -2
- 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 +5 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +12 -0
- 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 +77 -21
- package/docs/binding-review-placement-design.md +200 -0
- package/docs/capability-projection.md +1 -1
- package/docs/client-ui.md +95 -19
- package/docs/durability-design.md +28 -10
- package/docs/evaluation.md +78 -6
- package/docs/installation.md +41 -6
- package/docs/publishing.md +5 -3
- package/docs/runtime-reference.md +76 -25
- package/docs/semantic-validation.md +29 -0
- package/docs/stateful-binding-updates-proposal.md +759 -0
- package/docs/user-bindings.en.md +66 -0
- package/docs/user-bindings.md +66 -0
- package/docs/value-wire.md +3 -3
- package/docs/verification-optimization-work.md +41 -0
- package/docs/verification.md +71 -0
- package/index.js +369 -92
- package/internal/ast-traversal.js +5 -2
- package/internal/async-local-storage-intrinsics.js +38 -0
- package/internal/binding-declaration-contract.js +8 -0
- 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 +708 -0
- package/internal/binding-update-policy.js +72 -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 +253 -290
- 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 -677
- 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 +50 -0
- package/internal/compiler-module-links.js +37 -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 +83 -27
- 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 +91 -0
- package/internal/dynamic-environment-runtime.js +383 -0
- package/internal/dynamic-native-calls.js +153 -0
- package/internal/dynamic-native-runtime.js +262 -0
- package/internal/dynamic-scope-analysis.js +90 -0
- package/internal/edit-transport-owner.js +27 -2
- package/internal/execution-seam-compat.js +193 -0
- package/internal/failure-reporting.js +136 -28
- package/internal/host-rpc.js +66 -0
- package/internal/isolated-worker.js +555 -0
- package/internal/kernel-child.js +139 -0
- package/internal/kernel-worker.js +1179 -233
- 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 +322 -0
- package/internal/module-runtime-platform.js +19 -0
- package/internal/module-transform-contract.js +42 -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/record-utils.js +24 -15
- package/internal/recovery-tips.js +27 -15
- package/internal/rejected-cell-editor.js +13 -8
- package/internal/repl-convenience.js +2 -289
- package/internal/repl-memory-projection.js +81 -12
- package/internal/repl-observation-interest.js +70 -0
- package/internal/repl-rewrite-contract.js +6 -0
- package/internal/repl-scope-normalizer.js +2091 -0
- package/internal/repl-value-observer.js +117 -0
- package/internal/rpc-contract.js +52 -0
- package/internal/runtime-bridge-owner.js +172 -28
- package/internal/runtime-config.js +48 -52
- package/internal/runtime-contexts.js +82 -10
- package/internal/runtime-intrinsics.js +101 -0
- package/internal/runtime-messages.js +111 -0
- package/internal/session-cell-executor.js +402 -87
- package/internal/session-journal-recovery.js +684 -0
- package/internal/session-journal-schema.js +123 -0
- package/internal/session-journal.js +199 -654
- package/internal/session-log-view.js +42 -9
- package/internal/session-runtime.js +257 -95
- package/internal/session-state.js +247 -71
- package/internal/source-position-map.js +230 -71
- package/internal/stateful-module-compiler.js +154 -0
- package/internal/stateful-module-runtime.js +303 -0
- package/internal/stateful-root-compiler.js +600 -0
- package/internal/stateful-root-runtime.js +340 -0
- package/internal/typert-codec-compat.js +28 -0
- package/internal/typescript-transform.js +31 -0
- package/internal/user-binding-authoring.js +70 -0
- package/internal/user-binding-console-worker.js +244 -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 +361 -0
- package/internal/value-wire-schema.js +28 -0
- package/internal/value-wire.js +130 -91
- package/internal/worker-client.js +57 -67
- package/internal/worker-cwd-virtualization.js +80 -53
- package/internal/worker-environment.js +65 -0
- package/internal/worker-output-relay.js +29 -0
- package/internal/worker-repl-realm.js +134 -0
- package/internal/worker-shutdown.js +14 -0
- package/package.json +76 -7
- package/src/client-activity.js +95 -53
- 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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { recoveryTipIdentity } from './runtime-messages.js'
|
|
2
|
+
|
|
1
3
|
const PLATFORM_CAUSE_CODES = new Set(['EACCES', 'EINVAL', 'ENOTDIR', 'ENOENT', 'UNKNOWN'])
|
|
2
4
|
const TIP_CONTEXT_PREFIX = 'tools:ptc-plus-tip/'
|
|
3
5
|
const TIP_PREFIXES = Object.freeze({
|
|
@@ -10,16 +12,20 @@ function tipHistory(view) {
|
|
|
10
12
|
const active = new Map()
|
|
11
13
|
const seen = new Set()
|
|
12
14
|
const history = []
|
|
13
|
-
|
|
15
|
+
const snapshots = [
|
|
16
|
+
...view.systemPromptSnapshots,
|
|
17
|
+
...(view.ptcMessages ?? []).filter(record => record.form === 'notice').map(record => ({
|
|
18
|
+
...record, sections: [{ name: record.name, text: record.text }],
|
|
19
|
+
})),
|
|
20
|
+
].sort((left, right) => left.index - right.index)
|
|
21
|
+
for (const snapshot of snapshots) {
|
|
14
22
|
const next = new Map(snapshot.sections.map(section => [section.name, section.text]))
|
|
15
23
|
for (const [name] of next) {
|
|
16
24
|
if (active.has(name) || seen.has(name)) continue
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
19
|
-
const ordinal = Number(match[2])
|
|
20
|
-
if (!Number.isSafeInteger(ordinal)) continue
|
|
25
|
+
const identity = recoveryTipIdentity(name)
|
|
26
|
+
if (identity === undefined) continue
|
|
21
27
|
seen.add(name)
|
|
22
|
-
history.push({
|
|
28
|
+
history.push({ ...identity, index: snapshot.index, contextStep: snapshot.contextStep })
|
|
23
29
|
}
|
|
24
30
|
active.clear()
|
|
25
31
|
for (const entry of next) active.set(...entry)
|
|
@@ -36,7 +42,7 @@ function hasPlatformCommandDiagnostic(diagnostics) {
|
|
|
36
42
|
const causeCode = typeof diagnostic.cause?.code === 'string' ? diagnostic.cause.code.toUpperCase() : undefined
|
|
37
43
|
if (causeCode !== undefined && PLATFORM_CAUSE_CODES.has(causeCode)
|
|
38
44
|
&& /\b(?:spawn|exec(?:ute)?|command|executable|shell|path|file)\b/i.test(text)) return true
|
|
39
|
-
return
|
|
45
|
+
return /\b(?:spawn|exec(?:ute)?|child process)\b.*\b(?:ENOENT|ENOTDIR|EACCES|EINVAL)\b/i.test(text)
|
|
40
46
|
|| /command not found|no such file|not recognized as (?:an )?internal|cannot find (?:the )?(?:path|file)/i.test(text)
|
|
41
47
|
})
|
|
42
48
|
}
|
|
@@ -45,7 +51,10 @@ function tipCandidate(view) {
|
|
|
45
51
|
const { args, journal } = view.latestRun ?? {}
|
|
46
52
|
if (args === undefined || journal === undefined) return undefined
|
|
47
53
|
const codes = new Set(journal.diagnostics.map(diagnostic => diagnostic.code))
|
|
48
|
-
if (codes.has('PTC-W001')) return {
|
|
54
|
+
if (codes.has('PTC-W001')) return {
|
|
55
|
+
id: 'repeated-binding-failure',
|
|
56
|
+
capability: journal.diagnostics.some(item => item.code === 'PTC-W001' && item.cause?.code === 'PTC-CAPABILITY'),
|
|
57
|
+
}
|
|
49
58
|
if (hasPlatformCommandDiagnostic(journal.diagnostics)) return { id: 'platform-command-failure' }
|
|
50
59
|
return undefined
|
|
51
60
|
}
|
|
@@ -56,6 +65,9 @@ function renderTip(id, detailed, candidate = {}) {
|
|
|
56
65
|
? `${TIP_PREFIXES[id]} Re-check the active execution world and the actual executable before retrying. Use direct argv for a normal executable; use a shell only when its syntax or resolution is required. Windows, WSL, POSIX, and package shims have different paths and launch rules.`
|
|
57
66
|
: `${TIP_PREFIXES[id]} Inspect the executable or path in the current execution world and choose direct argv or a shell only when required; do not assume Windows, WSL, POSIX, or one shell.`
|
|
58
67
|
}
|
|
68
|
+
if (!candidate.capability) {
|
|
69
|
+
return `${TIP_PREFIXES[id]} Inspect the visible cell source and results for the local name, scope, initialization, or declaration conflict. Correct that expression before continuing; capability discovery does not list session-local variables.${detailed ? ' Earlier statements may have run; use current expression evidence without assuming a failed declaration initialized its binding.' : ''}`
|
|
70
|
+
}
|
|
59
71
|
return detailed
|
|
60
72
|
? `${TIP_PREFIXES[id]} Inspect the live request with \`capabilities.tree()\`, \`capabilities.find()\`, or \`capabilities.inspect()\`, then call the typed member through \`tools.*\`. Do not invent hidden bindings or repeat the same failing expression.`
|
|
61
73
|
: `${TIP_PREFIXES[id]} Inspect the live request with \`capabilities.tree()\`, \`capabilities.find()\`, or \`capabilities.inspect()\`, then call the typed member through \`tools.*\`.`
|
|
@@ -65,14 +77,14 @@ export function latestRecoveryTip(view, config) {
|
|
|
65
77
|
if (!config.enabled) return undefined
|
|
66
78
|
const candidate = tipCandidate(view)
|
|
67
79
|
if (candidate === undefined) return undefined
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
// Cooldown, escalation, and ordinal are per trigger kind: one kind's tip must
|
|
81
|
+
// not suppress a different kind.
|
|
82
|
+
const matching = tipHistory(view).filter(item => item.id === candidate.id)
|
|
83
|
+
const lastTip = matching.at(-1)
|
|
70
84
|
if (lastTip !== undefined && view.contextStep - lastTip.contextStep < config.cooldownMessages) return undefined
|
|
71
|
-
const unresolved =
|
|
72
|
-
const ordinal =
|
|
73
|
-
|
|
74
|
-
0,
|
|
75
|
-
) + 1
|
|
85
|
+
const unresolved = matching.filter(item => item.index > (view.lastSuccessfulRunIndex ?? -1))
|
|
86
|
+
const ordinal = matching.reduce((highest, item) => Math.max(highest, item.ordinal), 0) + 1
|
|
87
|
+
if (!Number.isSafeInteger(ordinal)) return undefined
|
|
76
88
|
return {
|
|
77
89
|
name: `${TIP_CONTEXT_PREFIX}${candidate.id}/${ordinal}`,
|
|
78
90
|
text: renderTip(candidate.id, unresolved.length >= config.escalationFailures, candidate),
|
|
@@ -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,289 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
CLASS_REDECLARATION_REWRITE,
|
|
4
|
-
FUNCTION_REDECLARATION_REWRITE,
|
|
5
|
-
MIXED_REDECLARATION_REWRITE,
|
|
6
|
-
redeclarationCommitTarget,
|
|
7
|
-
} from './repl-rewrite-contract.js'
|
|
8
|
-
import { applySourceEdits, createMappedTextBuilder, identitySourceMap } from './source-position-map.js'
|
|
9
|
-
import { SKIP_AST_CHILDREN, walkAst } from './ast-traversal.js'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Optional compatibility policy for a persistent REPL. The cell language does
|
|
13
|
-
* not own this policy: callers may replace it when persistence uses separate
|
|
14
|
-
* lexical frames instead of one shared REPL environment.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
function containsTopLevelAwait(node) {
|
|
18
|
-
let found = false
|
|
19
|
-
walkAst(node, current => {
|
|
20
|
-
if (current.type === 'AwaitExpression') found = true
|
|
21
|
-
if (current.type === 'FunctionDeclaration' || current.type === 'FunctionExpression'
|
|
22
|
-
|| current.type === 'ArrowFunctionExpression') return SKIP_AST_CHILDREN
|
|
23
|
-
})
|
|
24
|
-
return found
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function mixedDeclaratorText(statement, declarator, bindings, code, offset, allocateName) {
|
|
28
|
-
const fresh = bindings.filter(binding => !binding.existing)
|
|
29
|
-
const existing = bindings.filter(binding => binding.existing)
|
|
30
|
-
const valueName = allocateName('mixed_value')
|
|
31
|
-
const commitName = allocateName('mixed_commit')
|
|
32
|
-
const valuesName = allocateName('mixed_values')
|
|
33
|
-
const positions = new Map(bindings.map((binding, index) => [binding.name, index]))
|
|
34
|
-
const asynchronous = containsTopLevelAwait(declarator.id)
|
|
35
|
-
const builder = createMappedTextBuilder(code, offset)
|
|
36
|
-
const appendBindingName = binding => builder.appendMapped(binding.name, binding.start, binding.end)
|
|
37
|
-
builder.append(statement.kind === 'var' ? 'var [' : 'let [')
|
|
38
|
-
fresh.forEach((binding, index) => {
|
|
39
|
-
if (index > 0) builder.append(', ')
|
|
40
|
-
appendBindingName(binding)
|
|
41
|
-
})
|
|
42
|
-
builder.append(`] = ${asynchronous ? 'await ' : ''}(`)
|
|
43
|
-
builder.append(`(${asynchronous ? 'async ' : ''}(${valueName}, ${commitName}) => { ${statement.kind} `)
|
|
44
|
-
builder.appendSource(declarator.id.start, declarator.id.end)
|
|
45
|
-
builder.append(` = ${valueName}; return ${commitName}([`)
|
|
46
|
-
bindings.forEach((binding, index) => {
|
|
47
|
-
if (index > 0) builder.append(', ')
|
|
48
|
-
appendBindingName(binding)
|
|
49
|
-
})
|
|
50
|
-
builder.append(`]); })((`)
|
|
51
|
-
if (declarator.init === null) builder.append('undefined')
|
|
52
|
-
else builder.appendSource(declarator.init.start, declarator.init.end)
|
|
53
|
-
builder.append(`), (${valuesName}) => { `)
|
|
54
|
-
existing.forEach((binding, index) => {
|
|
55
|
-
if (index > 0) builder.append(' ')
|
|
56
|
-
appendBindingName(binding)
|
|
57
|
-
builder.append(` = ${valuesName}[${positions.get(binding.name)}];`)
|
|
58
|
-
})
|
|
59
|
-
builder.append(' return [')
|
|
60
|
-
fresh.forEach((binding, index) => {
|
|
61
|
-
if (index > 0) builder.append(', ')
|
|
62
|
-
builder.append(valuesName)
|
|
63
|
-
builder.append(`[${positions.get(binding.name)}]`)
|
|
64
|
-
})
|
|
65
|
-
builder.append(']; }) );')
|
|
66
|
-
return builder.result()
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function existingDeclaratorText(declarator, code, offset) {
|
|
70
|
-
const builder = createMappedTextBuilder(code, offset)
|
|
71
|
-
builder.append(';(')
|
|
72
|
-
builder.appendSource(declarator.id.start, declarator.id.end)
|
|
73
|
-
builder.append(' = ')
|
|
74
|
-
if (declarator.init === null) builder.append('undefined')
|
|
75
|
-
else builder.appendSource(declarator.init.start, declarator.init.end)
|
|
76
|
-
builder.append(');')
|
|
77
|
-
return builder.result()
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function freshDeclaratorText(statement, declarator, code, offset) {
|
|
81
|
-
const builder = createMappedTextBuilder(code, offset)
|
|
82
|
-
builder.append(statement.kind === 'var' ? 'var ' : 'let ')
|
|
83
|
-
builder.appendSource(declarator.start, declarator.end)
|
|
84
|
-
builder.append(';')
|
|
85
|
-
return builder.result()
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function bindingEntries(pattern, declarationSpan) {
|
|
89
|
-
return bindingNodes(pattern).map(node => ({
|
|
90
|
-
name: node.name,
|
|
91
|
-
start: node.start,
|
|
92
|
-
end: node.end,
|
|
93
|
-
span: declarationSpan(node),
|
|
94
|
-
}))
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function existingFunctionText(statement, code, offset) {
|
|
98
|
-
const builder = createMappedTextBuilder(code, offset)
|
|
99
|
-
builder.append(';(')
|
|
100
|
-
builder.appendMapped(statement.id.name, statement.id.start, statement.id.end)
|
|
101
|
-
builder.append(' = ')
|
|
102
|
-
builder.appendSource(statement.start, statement.id.start)
|
|
103
|
-
builder.appendSource(statement.id.end, statement.end)
|
|
104
|
-
builder.append(');')
|
|
105
|
-
return builder.result()
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function existingClassText(statement, code, offset) {
|
|
109
|
-
const builder = createMappedTextBuilder(code, offset)
|
|
110
|
-
builder.append(';(')
|
|
111
|
-
builder.appendMapped(statement.id.name, statement.id.start, statement.id.end)
|
|
112
|
-
builder.append(' = ')
|
|
113
|
-
builder.appendSource(statement.start, statement.end)
|
|
114
|
-
builder.append(');')
|
|
115
|
-
return builder.result()
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function rewriteReplRedeclarations({
|
|
119
|
-
code,
|
|
120
|
-
sourceMap = identitySourceMap(code.length),
|
|
121
|
-
body,
|
|
122
|
-
offset,
|
|
123
|
-
knownBindings,
|
|
124
|
-
variableRedeclarationBindings = knownBindings,
|
|
125
|
-
writableBindings,
|
|
126
|
-
declarations,
|
|
127
|
-
declarationSpan,
|
|
128
|
-
collisionFor,
|
|
129
|
-
bindingPolicy,
|
|
130
|
-
autoSplitRedeclarations,
|
|
131
|
-
commitSignal,
|
|
132
|
-
}) {
|
|
133
|
-
const redeclared = []
|
|
134
|
-
const rewrites = []
|
|
135
|
-
if (body === undefined) {
|
|
136
|
-
return {
|
|
137
|
-
executableCode: code,
|
|
138
|
-
executableSourceMap: sourceMap,
|
|
139
|
-
collisions: declarations.filter(declaration => knownBindings.has(declaration.name)).map(collisionFor),
|
|
140
|
-
redeclared,
|
|
141
|
-
rewrites,
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
const replacements = []
|
|
145
|
-
const rejected = []
|
|
146
|
-
const allocateName = createGeneratedNameAllocator(body)
|
|
147
|
-
for (const statement of body) {
|
|
148
|
-
if (statement.type !== 'VariableDeclaration') {
|
|
149
|
-
if ((statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration')
|
|
150
|
-
&& statement.id !== null && knownBindings.has(statement.id.name)) {
|
|
151
|
-
const kind = statement.type === 'ClassDeclaration' ? 'class' : 'function'
|
|
152
|
-
if (!writableBindings.has(statement.id.name)) {
|
|
153
|
-
rejected.push({
|
|
154
|
-
name: statement.id.name,
|
|
155
|
-
kind,
|
|
156
|
-
reason: 'binding-not-writable',
|
|
157
|
-
span: declarationSpan(statement.id),
|
|
158
|
-
})
|
|
159
|
-
} else if (!bindingPolicy.functionClassRedeclarations) {
|
|
160
|
-
rejected.push({
|
|
161
|
-
name: statement.id.name,
|
|
162
|
-
kind,
|
|
163
|
-
reason: 'function-class-redeclarations-disabled',
|
|
164
|
-
span: declarationSpan(statement.id),
|
|
165
|
-
})
|
|
166
|
-
} else {
|
|
167
|
-
const commitDependency = redeclarationCommitTarget(statement.id.name, statement.start)
|
|
168
|
-
const replacement = kind === 'class'
|
|
169
|
-
? existingClassText(statement, code, offset)
|
|
170
|
-
: existingFunctionText(statement, code, offset)
|
|
171
|
-
replacement.text += ` void 0; this[${JSON.stringify(commitSignal)}](${JSON.stringify(commitDependency)});`
|
|
172
|
-
replacements.push({
|
|
173
|
-
start: statement.start - offset,
|
|
174
|
-
end: statement.end - offset,
|
|
175
|
-
text: replacement.text,
|
|
176
|
-
mappings: replacement.mappings,
|
|
177
|
-
})
|
|
178
|
-
redeclared.push(collisionFor({
|
|
179
|
-
name: statement.id.name,
|
|
180
|
-
kind,
|
|
181
|
-
commitDependency,
|
|
182
|
-
span: declarationSpan(statement.id),
|
|
183
|
-
}))
|
|
184
|
-
rewrites.push({
|
|
185
|
-
kind: 'redeclaration',
|
|
186
|
-
description: kind === 'class'
|
|
187
|
-
? CLASS_REDECLARATION_REWRITE
|
|
188
|
-
: FUNCTION_REDECLARATION_REWRITE,
|
|
189
|
-
source: statement.id.name,
|
|
190
|
-
})
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
continue
|
|
194
|
-
}
|
|
195
|
-
const entries = []
|
|
196
|
-
let statementRejected = false
|
|
197
|
-
for (const declarator of statement.declarations) {
|
|
198
|
-
const bindings = bindingEntries(declarator.id, declarationSpan)
|
|
199
|
-
const existing = bindings.filter(binding => variableRedeclarationBindings.has(binding.name))
|
|
200
|
-
const immutable = existing.filter(binding => !writableBindings.has(binding.name))
|
|
201
|
-
if (immutable.length > 0) {
|
|
202
|
-
rejected.push(...immutable.map(binding => ({
|
|
203
|
-
...binding,
|
|
204
|
-
reason: 'binding-not-writable',
|
|
205
|
-
})))
|
|
206
|
-
statementRejected = true
|
|
207
|
-
continue
|
|
208
|
-
}
|
|
209
|
-
if (existing.length > 0 && !bindingPolicy.variableRedeclarations) {
|
|
210
|
-
rejected.push(...existing.map(binding => ({
|
|
211
|
-
...binding,
|
|
212
|
-
reason: 'variable-redeclarations-disabled',
|
|
213
|
-
})))
|
|
214
|
-
statementRejected = true
|
|
215
|
-
continue
|
|
216
|
-
}
|
|
217
|
-
if (existing.length > 0 && existing.length < bindings.length) {
|
|
218
|
-
if (autoSplitRedeclarations) entries.push({ declarator, bindings, existing })
|
|
219
|
-
else {
|
|
220
|
-
rejected.push(...existing)
|
|
221
|
-
statementRejected = true
|
|
222
|
-
}
|
|
223
|
-
continue
|
|
224
|
-
}
|
|
225
|
-
entries.push({ declarator, bindings, existing })
|
|
226
|
-
}
|
|
227
|
-
if (statementRejected) continue
|
|
228
|
-
if (!entries.some(entry => entry.existing.length > 0)) {
|
|
229
|
-
if (statement.kind === 'const' && bindingPolicy.variableRedeclarations) {
|
|
230
|
-
replacements.push({
|
|
231
|
-
start: statement.start - offset,
|
|
232
|
-
end: statement.start - offset + statement.kind.length,
|
|
233
|
-
text: 'let',
|
|
234
|
-
})
|
|
235
|
-
}
|
|
236
|
-
continue
|
|
237
|
-
}
|
|
238
|
-
let replacementText = ''
|
|
239
|
-
const replacementMappings = []
|
|
240
|
-
const appendPart = (part) => {
|
|
241
|
-
if (replacementText.length > 0) replacementText += '\n'
|
|
242
|
-
const partOffset = replacementText.length
|
|
243
|
-
replacementText += part.text
|
|
244
|
-
part.mappings.forEach(mapping => replacementMappings.push({
|
|
245
|
-
...mapping,
|
|
246
|
-
generatedStart: mapping.generatedStart + partOffset,
|
|
247
|
-
generatedEnd: mapping.generatedEnd + partOffset,
|
|
248
|
-
}))
|
|
249
|
-
}
|
|
250
|
-
for (const { declarator, bindings, existing } of entries) {
|
|
251
|
-
if (existing.length > 0 && existing.length < bindings.length) {
|
|
252
|
-
appendPart(mixedDeclaratorText(
|
|
253
|
-
statement,
|
|
254
|
-
declarator,
|
|
255
|
-
bindings.map(binding => ({ ...binding, existing: existing.includes(binding) })),
|
|
256
|
-
code,
|
|
257
|
-
offset,
|
|
258
|
-
allocateName,
|
|
259
|
-
))
|
|
260
|
-
redeclared.push(...existing.map(collisionFor))
|
|
261
|
-
rewrites.push({
|
|
262
|
-
kind: 'redeclaration',
|
|
263
|
-
description: MIXED_REDECLARATION_REWRITE,
|
|
264
|
-
source: existing.map(binding => binding.name).join(', '),
|
|
265
|
-
})
|
|
266
|
-
} else if (existing.length === bindings.length && bindings.length > 0) {
|
|
267
|
-
redeclared.push(...existing.map(collisionFor))
|
|
268
|
-
appendPart(existingDeclaratorText(declarator, code, offset))
|
|
269
|
-
} else {
|
|
270
|
-
appendPart(freshDeclaratorText(statement, declarator, code, offset))
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
replacements.push({
|
|
274
|
-
start: statement.start - offset,
|
|
275
|
-
end: statement.end - offset,
|
|
276
|
-
text: replacementText,
|
|
277
|
-
mappings: replacementMappings,
|
|
278
|
-
})
|
|
279
|
-
}
|
|
280
|
-
const collisions = rejected.map(collisionFor)
|
|
281
|
-
let executableCode = code
|
|
282
|
-
let executableSourceMap = sourceMap
|
|
283
|
-
if (collisions.length === 0) {
|
|
284
|
-
const applied = applySourceEdits(code, sourceMap, replacements)
|
|
285
|
-
executableCode = applied.code
|
|
286
|
-
executableSourceMap = applied.sourceMap
|
|
287
|
-
}
|
|
288
|
-
return { executableCode, executableSourceMap, collisions, redeclared, rewrites }
|
|
289
|
-
}
|
|
1
|
+
/** Historical declaration adapter; new cells use logical binding identities. */
|
|
2
|
+
export { rewriteReplRedeclarations } from './legacy-repl-convenience.js'
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isRecord } from './record-utils.js'
|
|
2
|
+
import { REPL_TOOL_NAMES } from './session-journal-schema.js'
|
|
2
3
|
|
|
3
4
|
export const REPL_MEMORY_KEY = 'ptcPlusRepl'
|
|
4
5
|
export const REPL_MEMORY_META_KEY = 'dshPtcPlusBindings'
|
|
@@ -12,13 +13,22 @@ const MAX_PENDING_REPL_CALLS = 256
|
|
|
12
13
|
const MAX_CALL_ID_LENGTH = 512
|
|
13
14
|
const MAX_GENERATION_LENGTH = 128
|
|
14
15
|
const SNAPSHOT_FIELDS = new Set(['available', 'entries', 'total', 'omitted'])
|
|
16
|
+
const COUNTED_SNAPSHOT_FIELDS = new Set([...SNAPSHOT_FIELDS, 'reuseTotal'])
|
|
17
|
+
const OBSERVED_SNAPSHOT_FIELDS = new Set([...SNAPSHOT_FIELDS, 'observation'])
|
|
18
|
+
const OBSERVED_COUNTED_SNAPSHOT_FIELDS = new Set([...COUNTED_SNAPSHOT_FIELDS, 'observation'])
|
|
19
|
+
const OBSERVATION_FIELDS = new Set(['at', 'entries'])
|
|
20
|
+
const PREVIEW_FIELDS = new Set(['name', 'status', 'text', 'truncated'])
|
|
15
21
|
const ENTRY_FIELDS = new Set(['name', 'kind', 'definition'])
|
|
22
|
+
const COUNTED_ENTRY_FIELDS = new Set([...ENTRY_FIELDS, 'reuseCount'])
|
|
16
23
|
const DEFINITION_FIELDS = new Set(['source', 'line', 'column'])
|
|
17
24
|
const MEMORY_META_FIELDS = new Set(['version', 'generation', 'memory'])
|
|
18
25
|
const PROJECTION_STATE_FIELDS = new Set(['generation', 'memory', 'pendingReplCalls'])
|
|
19
26
|
const PENDING_CALL_FIELDS = new Set(['callId', 'seq'])
|
|
20
|
-
const
|
|
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
|
}),
|