dsh-ptc-plus 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +65 -78
- package/README.md +65 -78
- 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 +30596 -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 +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 +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 +73 -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 +25 -10
- package/docs/evaluation.md +78 -6
- package/docs/installation.md +41 -6
- package/docs/publishing.md +5 -3
- package/docs/runtime-reference.md +72 -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 +369 -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 +240 -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 +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 +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 +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 +27 -2
- 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 +904 -120
- 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 -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 +114 -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-messages.js +111 -0
- package/internal/session-cell-executor.js +401 -87
- package/internal/session-journal-recovery.js +684 -0
- package/internal/session-journal-schema.js +106 -0
- package/internal/session-journal.js +184 -654
- package/internal/session-log-view.js +42 -9
- package/internal/session-runtime.js +256 -95
- package/internal/session-state.js +247 -71
- 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 +75 -7
- package/src/client-activity.js +88 -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,21 +1,30 @@
|
|
|
1
|
+
import { isAbsolute } from 'node:path'
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util'
|
|
1
3
|
import { diagnostic, renderDiagnostic } from './diagnostic.js'
|
|
2
4
|
import { createFailureTracker, messageOf } from './failure-reporting.js'
|
|
3
5
|
import {
|
|
4
6
|
createJournal,
|
|
5
7
|
liveToolCallSeq,
|
|
6
8
|
normalizeJournal,
|
|
7
|
-
pathToHead,
|
|
8
9
|
reduceStateOperations,
|
|
10
|
+
} from './session-journal.js'
|
|
11
|
+
import {
|
|
12
|
+
pathToHead,
|
|
13
|
+
recoveryBoundaryForHistory,
|
|
9
14
|
recoverJournal,
|
|
10
15
|
visibleExecutableCallSeqs,
|
|
11
|
-
} from './session-journal.js'
|
|
16
|
+
} from './session-journal-recovery.js'
|
|
17
|
+
import { valueLimitsFromConfig } from './value-wire-schema.js'
|
|
12
18
|
import { normalizeBindingDescriptors } from './binding-descriptors.js'
|
|
13
19
|
import { resolveConfig } from './runtime-config.js'
|
|
14
20
|
import { WorkerClient } from './worker-client.js'
|
|
21
|
+
import { compilerWorkerCache } from './compiler-service.js'
|
|
15
22
|
import { BindingCatalog, durabilityState, transitionDurability } from './session-state.js'
|
|
16
23
|
import { SessionCellExecutor } from './session-cell-executor.js'
|
|
24
|
+
import { executionPolicies } from './binding-update-policy.js'
|
|
17
25
|
import {
|
|
18
26
|
createReplMemorySnapshot,
|
|
27
|
+
normalizeReplMemorySnapshot,
|
|
19
28
|
unavailableReplMemorySnapshot,
|
|
20
29
|
} from './repl-memory-projection.js'
|
|
21
30
|
|
|
@@ -34,21 +43,6 @@ function recoveryDiagnostic(count) {
|
|
|
34
43
|
})
|
|
35
44
|
}
|
|
36
45
|
|
|
37
|
-
function rewritePolicy(config) {
|
|
38
|
-
return {
|
|
39
|
-
autoRewriteImports: config.autoRewriteImports,
|
|
40
|
-
autoStripExports: config.autoStripExports,
|
|
41
|
-
autoSplitRedeclarations: config.autoSplitRedeclarations,
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function bindingPolicy(config) {
|
|
46
|
-
return {
|
|
47
|
-
variableRedeclarations: config.looseTopLevelRedeclarations,
|
|
48
|
-
functionClassRedeclarations: config.looseTopLevelFunctionClassRedeclarations,
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
46
|
function resolvedRuntimeConfig(config) {
|
|
53
47
|
return Object.freeze(resolveConfig(config))
|
|
54
48
|
}
|
|
@@ -72,15 +66,10 @@ class ReplayCancelled extends Error {
|
|
|
72
66
|
}
|
|
73
67
|
|
|
74
68
|
class SessionKernel {
|
|
75
|
-
constructor({ config, history, cwd, session, withInitiator }) {
|
|
69
|
+
constructor({ config, history, cwd, session, userBindingsCwd, withInitiator, observeValues }) {
|
|
76
70
|
this.config = config
|
|
77
71
|
this.history = history
|
|
78
|
-
this.initialRecoveryBoundary = history
|
|
79
|
-
? {
|
|
80
|
-
failedCallSeq: history.volatileSuffix[0].seq,
|
|
81
|
-
frontierCallSeq: history.head === undefined ? null : history.nodes[history.head]?.callSeq ?? null,
|
|
82
|
-
}
|
|
83
|
-
: undefined
|
|
72
|
+
this.initialRecoveryBoundary = recoveryBoundaryForHistory(history)
|
|
84
73
|
this.surfaceGeneration = undefined
|
|
85
74
|
try {
|
|
86
75
|
this.surfaceGeneration = session?.surface?.replaceGeneration
|
|
@@ -88,6 +77,8 @@ class SessionKernel {
|
|
|
88
77
|
this.surfaceGeneration = undefined
|
|
89
78
|
}
|
|
90
79
|
this.cwd = cwd
|
|
80
|
+
this.userBindingsCwd = userBindingsCwd
|
|
81
|
+
this.observeValues = observeValues
|
|
91
82
|
this.session = session
|
|
92
83
|
this.withInitiator = withInitiator
|
|
93
84
|
this.durability = durabilityState()
|
|
@@ -101,28 +92,56 @@ class SessionKernel {
|
|
|
101
92
|
this.sequence = 0
|
|
102
93
|
this.tail = Promise.resolve()
|
|
103
94
|
this.tentatives = new WeakMap()
|
|
95
|
+
this.unsettledCells = 0
|
|
96
|
+
this.pendingInspection = undefined
|
|
97
|
+
this.pendingObservation = undefined
|
|
98
|
+
this.workerObservation = undefined
|
|
104
99
|
this.workerReservations = new Set()
|
|
105
100
|
this.cellExecutor = new SessionCellExecutor(this)
|
|
106
101
|
this.client = new WorkerClient({
|
|
107
102
|
workerUrl: WORKER_URL,
|
|
108
103
|
cwd,
|
|
104
|
+
compilerCache: compilerWorkerCache,
|
|
109
105
|
onMessage: message => this.cellExecutor.onMessage(message),
|
|
110
|
-
onFailure: message =>
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
onFailure: message => {
|
|
107
|
+
this.pendingInspection?.finish()
|
|
108
|
+
this.workerObservation = undefined
|
|
109
|
+
this.active?.resolve({ logs: [], error: { kind: 'worker-exit', message } }, true)
|
|
110
|
+
},
|
|
113
111
|
})
|
|
114
112
|
this.failures = createFailureTracker()
|
|
113
|
+
this.reclamationSequence = 0
|
|
114
|
+
this.reclamationRecords = new Map()
|
|
115
|
+
this.reclamationAttempts = new Set()
|
|
115
116
|
this.disposed = false
|
|
117
|
+
this.disposal = undefined
|
|
116
118
|
}
|
|
117
119
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
resetWorker(worker) {
|
|
121
|
+
const sequence = ++this.reclamationSequence
|
|
122
|
+
const record = { failed: false, reason: undefined }
|
|
123
|
+
this.reclamationRecords.set(sequence, record)
|
|
124
|
+
let reset
|
|
125
|
+
try {
|
|
126
|
+
reset = this.client.reset(worker)
|
|
127
|
+
} catch (error) {
|
|
128
|
+
record.failed = true
|
|
129
|
+
record.reason = error
|
|
130
|
+
return
|
|
125
131
|
}
|
|
132
|
+
const attempt = Promise.resolve(reset).then(
|
|
133
|
+
() => { this.reclamationRecords.delete(sequence) },
|
|
134
|
+
error => {
|
|
135
|
+
record.failed = true
|
|
136
|
+
record.reason = error
|
|
137
|
+
},
|
|
138
|
+
)
|
|
139
|
+
this.reclamationAttempts.add(attempt)
|
|
140
|
+
attempt.then(() => { this.reclamationAttempts.delete(attempt) })
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
valueLimits(config = this.config) {
|
|
144
|
+
return valueLimitsFromConfig(config)
|
|
126
145
|
}
|
|
127
146
|
|
|
128
147
|
assertReconfigurationAllowed(config) {
|
|
@@ -156,6 +175,57 @@ class SessionKernel {
|
|
|
156
175
|
return result
|
|
157
176
|
}
|
|
158
177
|
|
|
178
|
+
/** Observe only the existing, settled live worker; never recover or execute a cell for the UI. */
|
|
179
|
+
observe(expected, signal) {
|
|
180
|
+
if (this.pendingInspection !== undefined) return Promise.resolve(undefined)
|
|
181
|
+
const current = () => {
|
|
182
|
+
if (this.disposed || !this.config.replViewEnabled || signal?.aborted
|
|
183
|
+
|| this.client.worker === undefined || this.unsettledCells > 0) return false
|
|
184
|
+
try {
|
|
185
|
+
return this.session?.surface?.replaceGeneration === this.surfaceGeneration
|
|
186
|
+
&& isDeepStrictEqual(expected, createReplMemorySnapshot(this.bindingCatalog.snapshot()))
|
|
187
|
+
} catch { return false }
|
|
188
|
+
}
|
|
189
|
+
return new Promise(resolve => {
|
|
190
|
+
let finished = false
|
|
191
|
+
let releaseQueue
|
|
192
|
+
const id = ++this.sequence
|
|
193
|
+
const finish = observation => {
|
|
194
|
+
if (finished) return
|
|
195
|
+
finished = true
|
|
196
|
+
clearTimeout(timer)
|
|
197
|
+
signal?.removeEventListener('abort', onAbort)
|
|
198
|
+
if (this.pendingInspection?.id === id) this.pendingInspection = undefined
|
|
199
|
+
resolve(current() && observation !== undefined
|
|
200
|
+
? createReplMemorySnapshot(this.bindingCatalog.snapshot(), observation) : undefined)
|
|
201
|
+
releaseQueue?.()
|
|
202
|
+
}
|
|
203
|
+
const onAbort = () => finish()
|
|
204
|
+
const timer = setTimeout(onAbort, 250)
|
|
205
|
+
signal?.addEventListener('abort', onAbort, { once: true })
|
|
206
|
+
const inspect = () => {
|
|
207
|
+
if (finished) return
|
|
208
|
+
if (!current()) { finish(); return }
|
|
209
|
+
return new Promise(done => {
|
|
210
|
+
releaseQueue = done
|
|
211
|
+
const worker = this.client.worker
|
|
212
|
+
let started = false
|
|
213
|
+
this.pendingInspection = { id, finish, start: () => {
|
|
214
|
+
if (started) return
|
|
215
|
+
started = true
|
|
216
|
+
if (!current() || this.client.worker !== worker) { finish(); return }
|
|
217
|
+
try {
|
|
218
|
+
this.client.post({ type: 'observe', id, names: expected.entries.map(entry => entry.name) })
|
|
219
|
+
this.workerObservation = { id, worker, started: false }
|
|
220
|
+
} catch { finish() }
|
|
221
|
+
} }
|
|
222
|
+
try { this.client.post({ type: 'prepare', id }) } catch { finish() }
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
this.tail = this.tail.then(inspect, inspect)
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
159
229
|
async execute(request, config) {
|
|
160
230
|
const recoveryBoundaries = this.initialRecoveryBoundary === undefined
|
|
161
231
|
? []
|
|
@@ -187,6 +257,8 @@ class SessionKernel {
|
|
|
187
257
|
visibleCallSeqs,
|
|
188
258
|
})
|
|
189
259
|
} catch (error) {
|
|
260
|
+
const boundary = recoveryBoundaryForHistory(this.history, undefined, { reset: true })
|
|
261
|
+
if (boundary !== undefined) recoveryBoundaries.push(boundary)
|
|
190
262
|
this.history = emptyHistory()
|
|
191
263
|
this.recoveryNotice = recoveryDiagnostic(1)
|
|
192
264
|
this.initialRecoveryBoundary = undefined
|
|
@@ -194,14 +266,7 @@ class SessionKernel {
|
|
|
194
266
|
if (this.history.volatileSuffix.length > 0) {
|
|
195
267
|
this.recoveryNotice = recoveryDiagnostic(this.history.volatileSuffix.length)
|
|
196
268
|
}
|
|
197
|
-
this.initialRecoveryBoundary = this.history
|
|
198
|
-
? {
|
|
199
|
-
failedCallSeq: this.history.volatileSuffix[0].seq,
|
|
200
|
-
frontierCallSeq: this.history.head === undefined
|
|
201
|
-
? null
|
|
202
|
-
: this.history.nodes[this.history.head]?.callSeq ?? null,
|
|
203
|
-
}
|
|
204
|
-
: undefined
|
|
269
|
+
this.initialRecoveryBoundary = recoveryBoundaryForHistory(this.history)
|
|
205
270
|
if (this.initialRecoveryBoundary !== undefined) {
|
|
206
271
|
recoveryBoundaries.push(this.initialRecoveryBoundary)
|
|
207
272
|
this.initialRecoveryBoundary = undefined
|
|
@@ -226,45 +291,28 @@ class SessionKernel {
|
|
|
226
291
|
this.completeJournal(request.journal, 'noop', error.result)
|
|
227
292
|
return finishResult(error.result)
|
|
228
293
|
}
|
|
229
|
-
if (!(error instanceof ReplayFailure)) {
|
|
230
|
-
const result = {
|
|
231
|
-
logs: [],
|
|
232
|
-
error: {
|
|
233
|
-
kind: 'recovery',
|
|
234
|
-
message: `cannot reconstruct REPL from session log: ${messageOf(error)}`,
|
|
235
|
-
},
|
|
236
|
-
}
|
|
237
|
-
this.completeJournal(request.journal, 'noop', result)
|
|
238
|
-
return finishResult(result)
|
|
239
|
-
}
|
|
240
|
-
const frontier = error.node.parent === undefined
|
|
241
|
-
? undefined
|
|
242
|
-
: this.history.nodes[error.node.parent]
|
|
243
|
-
const previousPathLength = pathToHead(this.history).length
|
|
244
294
|
try {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
295
|
+
if (!(error instanceof ReplayFailure)) throw error
|
|
296
|
+
const previousPathLength = pathToHead(this.history).length
|
|
297
|
+
const boundary = recoveryBoundaryForHistory(this.history, error.node)
|
|
249
298
|
const recovered = recoverJournal(this.session, request.callSeq, {
|
|
250
299
|
extraBoundaries: [boundary],
|
|
251
300
|
visibleCallSeqs: visibleExecutableCallSeqs(this.session),
|
|
252
301
|
})
|
|
302
|
+
const nextPathLength = pathToHead(recovered).length
|
|
303
|
+
if (nextPathLength >= previousPathLength) throw new Error('recovery did not contract the historical frontier')
|
|
253
304
|
recoveryBoundaries.push(boundary)
|
|
254
305
|
this.history = recovered
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return finishResult(result)
|
|
306
|
+
skipped += previousPathLength - nextPathLength
|
|
307
|
+
} catch {
|
|
308
|
+
// Unproved historical metadata cannot gate the current request.
|
|
309
|
+
// A failed or noncontracting recovery has no reusable frontier.
|
|
310
|
+
const boundary = recoveryBoundaryForHistory(this.history, undefined, { reset: true })
|
|
311
|
+
if (boundary !== undefined) recoveryBoundaries.push(boundary)
|
|
312
|
+
this.history = emptyHistory()
|
|
313
|
+
this.replayed = true
|
|
314
|
+
skipped++
|
|
265
315
|
}
|
|
266
|
-
const nextPathLength = pathToHead(this.history).length
|
|
267
|
-
skipped += Math.max(1, previousPathLength - nextPathLength)
|
|
268
316
|
this.durability = durabilityState()
|
|
269
317
|
}
|
|
270
318
|
}
|
|
@@ -281,7 +329,8 @@ class SessionKernel {
|
|
|
281
329
|
if (result.error === undefined) {
|
|
282
330
|
this.failures.reset()
|
|
283
331
|
} else {
|
|
284
|
-
const
|
|
332
|
+
const primary = request.journal?.diagnostics.find(item => item.severity === 'error')
|
|
333
|
+
const hint = this.failures.hint(result.error, primary?.stateEffect)
|
|
285
334
|
if (hint !== undefined) {
|
|
286
335
|
result.logs = [...result.logs, renderDiagnostic(hint, request.program)]
|
|
287
336
|
if (request.journal !== undefined) request.journal.diagnostics.push(hint)
|
|
@@ -296,7 +345,12 @@ class SessionKernel {
|
|
|
296
345
|
for (const node of path) {
|
|
297
346
|
try {
|
|
298
347
|
const result = await this.cellExecutor.executeCell(
|
|
299
|
-
{
|
|
348
|
+
{
|
|
349
|
+
...request,
|
|
350
|
+
program: node.code,
|
|
351
|
+
journal: undefined,
|
|
352
|
+
...(node.userBindings === undefined ? { userBindings: undefined } : { userBindings: node.userBindings }),
|
|
353
|
+
},
|
|
300
354
|
node.journal,
|
|
301
355
|
config,
|
|
302
356
|
)
|
|
@@ -338,12 +392,14 @@ class SessionKernel {
|
|
|
338
392
|
journal.operations = journal.operations.filter(operation => operation.action !== 'save')
|
|
339
393
|
}
|
|
340
394
|
if (status === 'discarded' || status === 'noop') {
|
|
395
|
+
journal.userBindingNames = null
|
|
341
396
|
journal.calls.length = 0
|
|
342
397
|
journal.operations.length = 0
|
|
343
398
|
}
|
|
344
399
|
}
|
|
345
400
|
|
|
346
401
|
rollbackToDurable() {
|
|
402
|
+
this.workerObservation = undefined
|
|
347
403
|
this.durability = durabilityState()
|
|
348
404
|
this.replayed = false
|
|
349
405
|
this.bindingCatalog = new BindingCatalog()
|
|
@@ -353,7 +409,8 @@ class SessionKernel {
|
|
|
353
409
|
settleCell(active, result, terminate = false) {
|
|
354
410
|
/* c8 ignore next */
|
|
355
411
|
if (this.active !== active) return
|
|
356
|
-
|
|
412
|
+
active.settled = true
|
|
413
|
+
const { request, journal, replay, worker } = active
|
|
357
414
|
clearInterval(active.computeTimer)
|
|
358
415
|
clearTimeout(active.wallTimer)
|
|
359
416
|
request.signal?.removeEventListener('abort', active.onAbort)
|
|
@@ -361,6 +418,10 @@ class SessionKernel {
|
|
|
361
418
|
result = { ...result, rewrites: active.rewrites }
|
|
362
419
|
}
|
|
363
420
|
if (journal !== undefined && replay === undefined) {
|
|
421
|
+
journal.userBindingsFingerprint = terminate
|
|
422
|
+
? null
|
|
423
|
+
: active.userBindingSnapshot?.fingerprint ?? null
|
|
424
|
+
journal.userBindingNames = terminate ? null : active.userBindingNames ?? null
|
|
364
425
|
if (terminate) {
|
|
365
426
|
const volatileReason = active.pendingBindings.values().next().value ?? active.durability.reason
|
|
366
427
|
this.completeJournal(journal, 'discarded', result, volatileReason, active.diagnostics)
|
|
@@ -379,28 +440,58 @@ class SessionKernel {
|
|
|
379
440
|
this.tentatives.set(journal, {
|
|
380
441
|
callSeq: request.callSeq,
|
|
381
442
|
program: request.program,
|
|
382
|
-
bindingCatalog: active.appliedBindingCatalog
|
|
383
|
-
|
|
443
|
+
bindingCatalog: active.appliedBindingCatalog,
|
|
444
|
+
userBindings: active.userBindingSnapshot,
|
|
384
445
|
worker,
|
|
385
446
|
})
|
|
447
|
+
this.unsettledCells++
|
|
386
448
|
}
|
|
387
449
|
}
|
|
388
|
-
if (
|
|
450
|
+
if (!terminate) {
|
|
389
451
|
this.bindingCatalog = active.appliedBindingCatalog
|
|
390
|
-
?? active.priorBindingCatalog.advance(prepared, request.program)
|
|
391
452
|
}
|
|
392
453
|
if (replay === undefined && !terminate) {
|
|
454
|
+
// A live cell establishes this worker's timeline even when historical
|
|
455
|
+
// replay was disabled. Only a reset may reopen its recovery boundary.
|
|
456
|
+
this.replayed = true
|
|
393
457
|
this.liveCallSeqs.add(request.callSeq ?? request.sourceCallSeq)
|
|
394
458
|
}
|
|
395
459
|
this.active = undefined
|
|
396
|
-
if (terminate)
|
|
397
|
-
|
|
460
|
+
if (terminate) {
|
|
461
|
+
this.workerObservation = undefined
|
|
462
|
+
// A refused reclamation stays recorded on this kernel instead of becoming
|
|
463
|
+
// an unhandled rejection; the kernel keeps the worker until dispose.
|
|
464
|
+
this.resetWorker(worker)
|
|
465
|
+
}
|
|
466
|
+
if (!terminate && active.observing && replay === undefined) {
|
|
467
|
+
// Computation and journal settlement are complete. Observation cannot change either.
|
|
468
|
+
// done with observing=true precedes synchronous observation in the same worker turn.
|
|
469
|
+
this.workerObservation = { id: active.id, worker, started: true }
|
|
470
|
+
const timer = setTimeout(() => this.finishObservation({ id: active.id }), 250)
|
|
471
|
+
this.pendingObservation = { id: active.id, timer, journal, finish: () => active.finish(result) }
|
|
472
|
+
} else active.finish(result)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
finishObservation(message) {
|
|
476
|
+
const inspection = this.pendingInspection
|
|
477
|
+
if (inspection !== undefined && inspection.id === message.id) {
|
|
478
|
+
inspection.finish(message.observation)
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
const pending = this.pendingObservation
|
|
482
|
+
if (pending === undefined || pending.id !== message.id) return
|
|
483
|
+
this.pendingObservation = undefined
|
|
484
|
+
clearTimeout(pending.timer)
|
|
485
|
+
const tentative = this.tentatives.get(pending.journal)
|
|
486
|
+
if (tentative !== undefined) tentative.observation = message.observation
|
|
487
|
+
pending.finish()
|
|
398
488
|
}
|
|
399
489
|
|
|
400
490
|
finalizeJournal(journal, confirmed) {
|
|
401
491
|
const tentative = this.tentatives.get(journal)
|
|
402
492
|
if (tentative === undefined) return
|
|
403
493
|
this.tentatives.delete(journal)
|
|
494
|
+
this.unsettledCells--
|
|
404
495
|
if (!confirmed) {
|
|
405
496
|
if (journal.status === 'durable' || journal.status === 'volatile') {
|
|
406
497
|
const reason = journal.volatileReason ?? 'run_code journal was not preserved in the final tool result'
|
|
@@ -408,7 +499,6 @@ class SessionKernel {
|
|
|
408
499
|
type: 'volatile',
|
|
409
500
|
reason,
|
|
410
501
|
})
|
|
411
|
-
this.bindingCatalog = tentative.bindingCatalog
|
|
412
502
|
}
|
|
413
503
|
return
|
|
414
504
|
}
|
|
@@ -418,11 +508,11 @@ class SessionKernel {
|
|
|
418
508
|
code: tentative.program,
|
|
419
509
|
journal: normalized,
|
|
420
510
|
...(tentative.callSeq === undefined ? {} : { callSeq: tentative.callSeq }),
|
|
511
|
+
...(tentative.userBindings === undefined ? {} : { userBindings: tentative.userBindings }),
|
|
421
512
|
parent: this.history.head,
|
|
422
513
|
})
|
|
423
514
|
const index = this.history.nodes.push(node) - 1
|
|
424
515
|
this.history.head = index
|
|
425
|
-
this.bindingCatalog = tentative.bindingCatalog
|
|
426
516
|
this.finishStateOperations(journal.operations, index, tentative.worker)
|
|
427
517
|
return
|
|
428
518
|
}
|
|
@@ -431,7 +521,6 @@ class SessionKernel {
|
|
|
431
521
|
type: 'volatile',
|
|
432
522
|
reason: journal.volatileReason,
|
|
433
523
|
})
|
|
434
|
-
this.bindingCatalog = tentative.bindingCatalog
|
|
435
524
|
this.finishStateOperations(journal.operations, undefined, tentative.worker)
|
|
436
525
|
}
|
|
437
526
|
}
|
|
@@ -445,7 +534,11 @@ class SessionKernel {
|
|
|
445
534
|
if (tentative === undefined) {
|
|
446
535
|
return createReplMemorySnapshot(this.bindingCatalog.snapshot())
|
|
447
536
|
}
|
|
448
|
-
return createReplMemorySnapshot(tentative.bindingCatalog.snapshot())
|
|
537
|
+
return createReplMemorySnapshot(tentative.bindingCatalog.snapshot(), tentative.observation)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
userBindingsFor(journal) {
|
|
541
|
+
return this.tentatives.get(journal)?.userBindings
|
|
449
542
|
}
|
|
450
543
|
|
|
451
544
|
finishStateOperations(operations, index, worker) {
|
|
@@ -454,12 +547,26 @@ class SessionKernel {
|
|
|
454
547
|
this.history.checkpoints = transition.checkpoints
|
|
455
548
|
if (transition.restored) {
|
|
456
549
|
this.rollbackToDurable()
|
|
457
|
-
|
|
550
|
+
this.resetWorker(worker)
|
|
458
551
|
}
|
|
459
552
|
}
|
|
460
553
|
|
|
461
|
-
|
|
554
|
+
dispose() {
|
|
555
|
+
if (this.disposal !== undefined) return this.disposal
|
|
556
|
+
const operation = this.#dispose()
|
|
557
|
+
this.disposal = operation
|
|
558
|
+
operation.then(
|
|
559
|
+
() => { if (this.disposal === operation) this.disposal = undefined },
|
|
560
|
+
() => { if (this.disposal === operation) this.disposal = undefined },
|
|
561
|
+
)
|
|
562
|
+
return operation
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async #dispose() {
|
|
462
566
|
this.disposed = true
|
|
567
|
+
this.pendingInspection?.finish()
|
|
568
|
+
this.workerObservation = undefined
|
|
569
|
+
// Keep any refused reclamation observable through this kernel's dispose.
|
|
463
570
|
const worker = this.client.worker
|
|
464
571
|
if (worker !== undefined) {
|
|
465
572
|
/* c8 ignore next */
|
|
@@ -467,6 +574,18 @@ class SessionKernel {
|
|
|
467
574
|
}
|
|
468
575
|
await this.client.dispose()
|
|
469
576
|
await this.tail
|
|
577
|
+
await Promise.all([...this.reclamationAttempts])
|
|
578
|
+
// A successful client disposal proves its owner no longer retains the
|
|
579
|
+
// helpers. Report every earlier refusal once, then let a later terminal
|
|
580
|
+
// retry release this already reclaimed kernel.
|
|
581
|
+
const reclamationFailures = [...this.reclamationRecords.values()]
|
|
582
|
+
.filter(record => record.failed)
|
|
583
|
+
.map(record => record.reason)
|
|
584
|
+
this.reclamationRecords.clear()
|
|
585
|
+
if (reclamationFailures.length === 1) throw reclamationFailures[0]
|
|
586
|
+
if (reclamationFailures.length > 1) {
|
|
587
|
+
throw new AggregateError(reclamationFailures, 'ptc-plus session kernel reclamation failed')
|
|
588
|
+
}
|
|
470
589
|
}
|
|
471
590
|
}
|
|
472
591
|
|
|
@@ -497,7 +616,13 @@ export class SessionRuntime {
|
|
|
497
616
|
this.pendingNoops = new Map()
|
|
498
617
|
this.settlements = new WeakSet()
|
|
499
618
|
this.disposed = false
|
|
619
|
+
this.userBindingsCwd = options.userBindingsCwd ?? process.cwd()
|
|
620
|
+
if (typeof this.userBindingsCwd !== 'string' || !isAbsolute(this.userBindingsCwd)) {
|
|
621
|
+
throw new TypeError('userBindingsCwd must be an absolute path')
|
|
622
|
+
}
|
|
500
623
|
this.withInitiator = typeof options.withInitiator === 'function' ? options.withInitiator : undefined
|
|
624
|
+
this.observeSession = options.observeSession ?? (() => false)
|
|
625
|
+
this.disposal = undefined
|
|
501
626
|
}
|
|
502
627
|
|
|
503
628
|
async run(sessionContext, request) {
|
|
@@ -514,6 +639,13 @@ export class SessionRuntime {
|
|
|
514
639
|
this.config = resolved
|
|
515
640
|
}
|
|
516
641
|
|
|
642
|
+
async observe(sessionId, memory, signal) {
|
|
643
|
+
if (this.disposed || !this.config.replViewEnabled) return undefined
|
|
644
|
+
const { observation: _observation, ...expected } = normalizeReplMemorySnapshot(memory)
|
|
645
|
+
if (!expected.available || expected.entries.length === 0) return undefined
|
|
646
|
+
return this.kernels.get(sessionId)?.observe(expected, signal)
|
|
647
|
+
}
|
|
648
|
+
|
|
517
649
|
async runTentative(sessionContext, request) {
|
|
518
650
|
const completed = result => Object.freeze({ result, settlement: undefined })
|
|
519
651
|
if (this.disposed) return completed({ logs: [], error: { kind: 'abort', message: 'PTC runtime disposed' } })
|
|
@@ -559,6 +691,8 @@ export class SessionRuntime {
|
|
|
559
691
|
})
|
|
560
692
|
: emptyHistory()
|
|
561
693
|
} catch (error) {
|
|
694
|
+
// Malformed PTC metadata is contracted by the ordered recovery owner.
|
|
695
|
+
// Failure to read DSH's session source cannot prove a reset boundary.
|
|
562
696
|
return completed({ logs: [], error: { kind: 'recovery', message: `cannot reconstruct REPL from session log: ${messageOf(error)}` } })
|
|
563
697
|
}
|
|
564
698
|
kernel = new SessionKernel({
|
|
@@ -566,15 +700,19 @@ export class SessionRuntime {
|
|
|
566
700
|
history,
|
|
567
701
|
cwd,
|
|
568
702
|
session,
|
|
703
|
+
userBindingsCwd: this.userBindingsCwd,
|
|
569
704
|
withInitiator: this.withInitiator,
|
|
705
|
+
observeValues: () => this.config.replViewEnabled && this.observeSession(sessionId),
|
|
570
706
|
})
|
|
571
707
|
this.kernels.set(sessionId, kernel)
|
|
572
708
|
}
|
|
709
|
+
const policies = executionPolicies(cellConfig)
|
|
573
710
|
const journal = createJournal(
|
|
574
711
|
/* c8 ignore next */
|
|
575
712
|
this.pendingNoops.get(sessionId) ?? [],
|
|
576
|
-
bindingPolicy
|
|
577
|
-
|
|
713
|
+
policies.bindingPolicy,
|
|
714
|
+
policies.rewritesEnabled,
|
|
715
|
+
policies.languageSemantics,
|
|
578
716
|
)
|
|
579
717
|
const workerReservation = kernel.reserveWorkerConfiguration(cellConfig)
|
|
580
718
|
let result
|
|
@@ -588,6 +726,7 @@ export class SessionRuntime {
|
|
|
588
726
|
kernel,
|
|
589
727
|
sessionId,
|
|
590
728
|
replMemory: kernel.replMemoryFor(journal),
|
|
729
|
+
userBindings: kernel.userBindingsFor(journal),
|
|
591
730
|
...(result.recoveryBoundaries === undefined
|
|
592
731
|
? {}
|
|
593
732
|
: { recoveryBoundaries: result.recoveryBoundaries }),
|
|
@@ -632,14 +771,36 @@ export class SessionRuntime {
|
|
|
632
771
|
const kernel = this.kernels.get(id)
|
|
633
772
|
this.pendingNoops.delete(id)
|
|
634
773
|
if (kernel === undefined) return
|
|
635
|
-
|
|
774
|
+
// The entry survives a failed dispose so a repeated dispose reports the same
|
|
775
|
+
// unreclaimed instance instead of appearing to succeed.
|
|
636
776
|
await kernel.dispose()
|
|
777
|
+
this.kernels.delete(id)
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
dispose() {
|
|
781
|
+
if (this.disposal !== undefined) return this.disposal
|
|
782
|
+
const operation = this.#dispose()
|
|
783
|
+
this.disposal = operation
|
|
784
|
+
operation.then(
|
|
785
|
+
() => { if (this.disposal === operation) this.disposal = undefined },
|
|
786
|
+
() => { if (this.disposal === operation) this.disposal = undefined },
|
|
787
|
+
)
|
|
788
|
+
return operation
|
|
637
789
|
}
|
|
638
790
|
|
|
639
|
-
async dispose() {
|
|
791
|
+
async #dispose() {
|
|
640
792
|
this.disposed = true
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
793
|
+
const entries = [...this.kernels]
|
|
794
|
+
const results = await Promise.allSettled(entries.map(async ([id, kernel]) => {
|
|
795
|
+
await kernel.dispose()
|
|
796
|
+
this.kernels.delete(id)
|
|
797
|
+
}))
|
|
798
|
+
const failures = []
|
|
799
|
+
for (const result of results) {
|
|
800
|
+
if (result.status === 'rejected') failures.push(result.reason)
|
|
801
|
+
}
|
|
802
|
+
if (failures.length > 0) {
|
|
803
|
+
throw new AggregateError(failures, 'ptc-plus session runtime disposal failed')
|
|
804
|
+
}
|
|
644
805
|
}
|
|
645
806
|
}
|