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
|
@@ -1,20 +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,
|
|
9
|
-
recoverJournal,
|
|
10
10
|
} from './session-journal.js'
|
|
11
|
+
import {
|
|
12
|
+
pathToHead,
|
|
13
|
+
recoveryBoundaryForHistory,
|
|
14
|
+
recoverJournal,
|
|
15
|
+
visibleExecutableCallSeqs,
|
|
16
|
+
} from './session-journal-recovery.js'
|
|
17
|
+
import { valueLimitsFromConfig } from './value-wire-schema.js'
|
|
11
18
|
import { normalizeBindingDescriptors } from './binding-descriptors.js'
|
|
12
19
|
import { resolveConfig } from './runtime-config.js'
|
|
13
20
|
import { WorkerClient } from './worker-client.js'
|
|
21
|
+
import { compilerWorkerCache } from './compiler-service.js'
|
|
14
22
|
import { BindingCatalog, durabilityState, transitionDurability } from './session-state.js'
|
|
15
23
|
import { SessionCellExecutor } from './session-cell-executor.js'
|
|
24
|
+
import { executionPolicies } from './binding-update-policy.js'
|
|
16
25
|
import {
|
|
17
26
|
createReplMemorySnapshot,
|
|
27
|
+
normalizeReplMemorySnapshot,
|
|
18
28
|
unavailableReplMemorySnapshot,
|
|
19
29
|
} from './repl-memory-projection.js'
|
|
20
30
|
|
|
@@ -33,14 +43,6 @@ function recoveryDiagnostic(count) {
|
|
|
33
43
|
})
|
|
34
44
|
}
|
|
35
45
|
|
|
36
|
-
function rewritePolicy(config) {
|
|
37
|
-
return {
|
|
38
|
-
autoRewriteImports: config.autoRewriteImports,
|
|
39
|
-
autoStripExports: config.autoStripExports,
|
|
40
|
-
autoSplitRedeclarations: config.autoSplitRedeclarations,
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
46
|
function resolvedRuntimeConfig(config) {
|
|
45
47
|
return Object.freeze(resolveConfig(config))
|
|
46
48
|
}
|
|
@@ -64,15 +66,25 @@ class ReplayCancelled extends Error {
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
class SessionKernel {
|
|
67
|
-
constructor({ config, history, cwd, session, withInitiator }) {
|
|
69
|
+
constructor({ config, history, cwd, session, userBindingsCwd, withInitiator, observeValues }) {
|
|
68
70
|
this.config = config
|
|
69
71
|
this.history = history
|
|
72
|
+
this.initialRecoveryBoundary = recoveryBoundaryForHistory(history)
|
|
73
|
+
this.surfaceGeneration = undefined
|
|
74
|
+
try {
|
|
75
|
+
this.surfaceGeneration = session?.surface?.replaceGeneration
|
|
76
|
+
} catch {
|
|
77
|
+
this.surfaceGeneration = undefined
|
|
78
|
+
}
|
|
70
79
|
this.cwd = cwd
|
|
80
|
+
this.userBindingsCwd = userBindingsCwd
|
|
81
|
+
this.observeValues = observeValues
|
|
71
82
|
this.session = session
|
|
72
83
|
this.withInitiator = withInitiator
|
|
73
84
|
this.durability = durabilityState()
|
|
74
85
|
this.bindingCatalog = new BindingCatalog()
|
|
75
86
|
this.replayed = false
|
|
87
|
+
this.liveCallSeqs = new Set()
|
|
76
88
|
this.recoveryNotice = history.volatileSuffix.length === 0
|
|
77
89
|
? undefined
|
|
78
90
|
: recoveryDiagnostic(history.volatileSuffix.length)
|
|
@@ -80,28 +92,56 @@ class SessionKernel {
|
|
|
80
92
|
this.sequence = 0
|
|
81
93
|
this.tail = Promise.resolve()
|
|
82
94
|
this.tentatives = new WeakMap()
|
|
95
|
+
this.unsettledCells = 0
|
|
96
|
+
this.pendingInspection = undefined
|
|
97
|
+
this.pendingObservation = undefined
|
|
98
|
+
this.workerObservation = undefined
|
|
83
99
|
this.workerReservations = new Set()
|
|
84
100
|
this.cellExecutor = new SessionCellExecutor(this)
|
|
85
101
|
this.client = new WorkerClient({
|
|
86
102
|
workerUrl: WORKER_URL,
|
|
87
103
|
cwd,
|
|
104
|
+
compilerCache: compilerWorkerCache,
|
|
88
105
|
onMessage: message => this.cellExecutor.onMessage(message),
|
|
89
|
-
onFailure: message =>
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
onFailure: message => {
|
|
107
|
+
this.pendingInspection?.finish()
|
|
108
|
+
this.workerObservation = undefined
|
|
109
|
+
this.active?.resolve({ logs: [], error: { kind: 'worker-exit', message } }, true)
|
|
110
|
+
},
|
|
92
111
|
})
|
|
93
112
|
this.failures = createFailureTracker()
|
|
113
|
+
this.reclamationSequence = 0
|
|
114
|
+
this.reclamationRecords = new Map()
|
|
115
|
+
this.reclamationAttempts = new Set()
|
|
94
116
|
this.disposed = false
|
|
117
|
+
this.disposal = undefined
|
|
95
118
|
}
|
|
96
119
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
104
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)
|
|
105
145
|
}
|
|
106
146
|
|
|
107
147
|
assertReconfigurationAllowed(config) {
|
|
@@ -135,11 +175,109 @@ class SessionKernel {
|
|
|
135
175
|
return result
|
|
136
176
|
}
|
|
137
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
|
+
|
|
138
229
|
async execute(request, config) {
|
|
139
|
-
const recoveryBoundaries =
|
|
230
|
+
const recoveryBoundaries = this.initialRecoveryBoundary === undefined
|
|
231
|
+
? []
|
|
232
|
+
: [this.initialRecoveryBoundary]
|
|
233
|
+
this.initialRecoveryBoundary = undefined
|
|
140
234
|
const finishResult = result => recoveryBoundaries.length === 0
|
|
141
235
|
? result
|
|
142
236
|
: { ...result, recoveryBoundaries: recoveryBoundaries.map(boundary => ({ ...boundary })) }
|
|
237
|
+
let currentGeneration
|
|
238
|
+
try {
|
|
239
|
+
currentGeneration = this.session?.surface?.replaceGeneration
|
|
240
|
+
} catch {
|
|
241
|
+
currentGeneration = undefined
|
|
242
|
+
}
|
|
243
|
+
if (currentGeneration !== undefined && currentGeneration !== this.surfaceGeneration) {
|
|
244
|
+
this.surfaceGeneration = currentGeneration
|
|
245
|
+
const visibleCallSeqs = visibleExecutableCallSeqs(this.session)
|
|
246
|
+
const contractsLiveState = [...this.liveCallSeqs].some(callSeq => (
|
|
247
|
+
!(visibleCallSeqs instanceof Set) || !visibleCallSeqs.has(callSeq)
|
|
248
|
+
))
|
|
249
|
+
if ((config.durableReplay && !this.replayed) || contractsLiveState) {
|
|
250
|
+
const worker = this.client.worker
|
|
251
|
+
if (worker !== undefined) await this.client.reset(worker)
|
|
252
|
+
this.rollbackToDurable()
|
|
253
|
+
if (config.durableReplay) {
|
|
254
|
+
this.recoveryNotice = undefined
|
|
255
|
+
try {
|
|
256
|
+
this.history = recoverJournal(this.session, request.callSeq, {
|
|
257
|
+
visibleCallSeqs,
|
|
258
|
+
})
|
|
259
|
+
} catch (error) {
|
|
260
|
+
const boundary = recoveryBoundaryForHistory(this.history, undefined, { reset: true })
|
|
261
|
+
if (boundary !== undefined) recoveryBoundaries.push(boundary)
|
|
262
|
+
this.history = emptyHistory()
|
|
263
|
+
this.recoveryNotice = recoveryDiagnostic(1)
|
|
264
|
+
this.initialRecoveryBoundary = undefined
|
|
265
|
+
}
|
|
266
|
+
if (this.history.volatileSuffix.length > 0) {
|
|
267
|
+
this.recoveryNotice = recoveryDiagnostic(this.history.volatileSuffix.length)
|
|
268
|
+
}
|
|
269
|
+
this.initialRecoveryBoundary = recoveryBoundaryForHistory(this.history)
|
|
270
|
+
if (this.initialRecoveryBoundary !== undefined) {
|
|
271
|
+
recoveryBoundaries.push(this.initialRecoveryBoundary)
|
|
272
|
+
this.initialRecoveryBoundary = undefined
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
this.history = emptyHistory()
|
|
276
|
+
this.replayed = true
|
|
277
|
+
this.recoveryNotice = recoveryDiagnostic(1)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
143
281
|
if (!this.replayed && config.durableReplay) {
|
|
144
282
|
let skipped = 0
|
|
145
283
|
while (!this.replayed) {
|
|
@@ -153,44 +291,28 @@ class SessionKernel {
|
|
|
153
291
|
this.completeJournal(request.journal, 'noop', error.result)
|
|
154
292
|
return finishResult(error.result)
|
|
155
293
|
}
|
|
156
|
-
if (!(error instanceof ReplayFailure)) {
|
|
157
|
-
const result = {
|
|
158
|
-
logs: [],
|
|
159
|
-
error: {
|
|
160
|
-
kind: 'recovery',
|
|
161
|
-
message: `cannot reconstruct REPL from session log: ${messageOf(error)}`,
|
|
162
|
-
},
|
|
163
|
-
}
|
|
164
|
-
this.completeJournal(request.journal, 'noop', result)
|
|
165
|
-
return finishResult(result)
|
|
166
|
-
}
|
|
167
|
-
const frontier = error.node.parent === undefined
|
|
168
|
-
? undefined
|
|
169
|
-
: this.history.nodes[error.node.parent]
|
|
170
|
-
const previousPathLength = pathToHead(this.history).length
|
|
171
294
|
try {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
295
|
+
if (!(error instanceof ReplayFailure)) throw error
|
|
296
|
+
const previousPathLength = pathToHead(this.history).length
|
|
297
|
+
const boundary = recoveryBoundaryForHistory(this.history, error.node)
|
|
176
298
|
const recovered = recoverJournal(this.session, request.callSeq, {
|
|
177
299
|
extraBoundaries: [boundary],
|
|
300
|
+
visibleCallSeqs: visibleExecutableCallSeqs(this.session),
|
|
178
301
|
})
|
|
302
|
+
const nextPathLength = pathToHead(recovered).length
|
|
303
|
+
if (nextPathLength >= previousPathLength) throw new Error('recovery did not contract the historical frontier')
|
|
179
304
|
recoveryBoundaries.push(boundary)
|
|
180
305
|
this.history = recovered
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
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++
|
|
191
315
|
}
|
|
192
|
-
const nextPathLength = pathToHead(this.history).length
|
|
193
|
-
skipped += Math.max(1, previousPathLength - nextPathLength)
|
|
194
316
|
this.durability = durabilityState()
|
|
195
317
|
}
|
|
196
318
|
}
|
|
@@ -207,7 +329,8 @@ class SessionKernel {
|
|
|
207
329
|
if (result.error === undefined) {
|
|
208
330
|
this.failures.reset()
|
|
209
331
|
} else {
|
|
210
|
-
const
|
|
332
|
+
const primary = request.journal?.diagnostics.find(item => item.severity === 'error')
|
|
333
|
+
const hint = this.failures.hint(result.error, primary?.stateEffect)
|
|
211
334
|
if (hint !== undefined) {
|
|
212
335
|
result.logs = [...result.logs, renderDiagnostic(hint, request.program)]
|
|
213
336
|
if (request.journal !== undefined) request.journal.diagnostics.push(hint)
|
|
@@ -222,7 +345,12 @@ class SessionKernel {
|
|
|
222
345
|
for (const node of path) {
|
|
223
346
|
try {
|
|
224
347
|
const result = await this.cellExecutor.executeCell(
|
|
225
|
-
{
|
|
348
|
+
{
|
|
349
|
+
...request,
|
|
350
|
+
program: node.code,
|
|
351
|
+
journal: undefined,
|
|
352
|
+
...(node.userBindings === undefined ? { userBindings: undefined } : { userBindings: node.userBindings }),
|
|
353
|
+
},
|
|
226
354
|
node.journal,
|
|
227
355
|
config,
|
|
228
356
|
)
|
|
@@ -245,6 +373,7 @@ class SessionKernel {
|
|
|
245
373
|
throw new ReplayFailure(node, error)
|
|
246
374
|
}
|
|
247
375
|
}
|
|
376
|
+
for (const node of path) this.liveCallSeqs.add(node.callSeq)
|
|
248
377
|
}
|
|
249
378
|
|
|
250
379
|
completeJournal(journal, status, result, volatileReason, diagnostics = [], completion = undefined) {
|
|
@@ -263,21 +392,25 @@ class SessionKernel {
|
|
|
263
392
|
journal.operations = journal.operations.filter(operation => operation.action !== 'save')
|
|
264
393
|
}
|
|
265
394
|
if (status === 'discarded' || status === 'noop') {
|
|
395
|
+
journal.userBindingNames = null
|
|
266
396
|
journal.calls.length = 0
|
|
267
397
|
journal.operations.length = 0
|
|
268
398
|
}
|
|
269
399
|
}
|
|
270
400
|
|
|
271
401
|
rollbackToDurable() {
|
|
402
|
+
this.workerObservation = undefined
|
|
272
403
|
this.durability = durabilityState()
|
|
273
404
|
this.replayed = false
|
|
274
405
|
this.bindingCatalog = new BindingCatalog()
|
|
406
|
+
this.liveCallSeqs.clear()
|
|
275
407
|
}
|
|
276
408
|
|
|
277
409
|
settleCell(active, result, terminate = false) {
|
|
278
410
|
/* c8 ignore next */
|
|
279
411
|
if (this.active !== active) return
|
|
280
|
-
|
|
412
|
+
active.settled = true
|
|
413
|
+
const { request, journal, replay, worker } = active
|
|
281
414
|
clearInterval(active.computeTimer)
|
|
282
415
|
clearTimeout(active.wallTimer)
|
|
283
416
|
request.signal?.removeEventListener('abort', active.onAbort)
|
|
@@ -285,6 +418,10 @@ class SessionKernel {
|
|
|
285
418
|
result = { ...result, rewrites: active.rewrites }
|
|
286
419
|
}
|
|
287
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
|
|
288
425
|
if (terminate) {
|
|
289
426
|
const volatileReason = active.pendingBindings.values().next().value ?? active.durability.reason
|
|
290
427
|
this.completeJournal(journal, 'discarded', result, volatileReason, active.diagnostics)
|
|
@@ -303,25 +440,58 @@ class SessionKernel {
|
|
|
303
440
|
this.tentatives.set(journal, {
|
|
304
441
|
callSeq: request.callSeq,
|
|
305
442
|
program: request.program,
|
|
306
|
-
bindingCatalog: active.appliedBindingCatalog
|
|
307
|
-
|
|
443
|
+
bindingCatalog: active.appliedBindingCatalog,
|
|
444
|
+
userBindings: active.userBindingSnapshot,
|
|
308
445
|
worker,
|
|
309
446
|
})
|
|
447
|
+
this.unsettledCells++
|
|
310
448
|
}
|
|
311
449
|
}
|
|
312
|
-
if (
|
|
450
|
+
if (!terminate) {
|
|
313
451
|
this.bindingCatalog = active.appliedBindingCatalog
|
|
314
|
-
|
|
452
|
+
}
|
|
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
|
|
457
|
+
this.liveCallSeqs.add(request.callSeq ?? request.sourceCallSeq)
|
|
315
458
|
}
|
|
316
459
|
this.active = undefined
|
|
317
|
-
if (terminate)
|
|
318
|
-
|
|
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()
|
|
319
488
|
}
|
|
320
489
|
|
|
321
490
|
finalizeJournal(journal, confirmed) {
|
|
322
491
|
const tentative = this.tentatives.get(journal)
|
|
323
492
|
if (tentative === undefined) return
|
|
324
493
|
this.tentatives.delete(journal)
|
|
494
|
+
this.unsettledCells--
|
|
325
495
|
if (!confirmed) {
|
|
326
496
|
if (journal.status === 'durable' || journal.status === 'volatile') {
|
|
327
497
|
const reason = journal.volatileReason ?? 'run_code journal was not preserved in the final tool result'
|
|
@@ -329,7 +499,6 @@ class SessionKernel {
|
|
|
329
499
|
type: 'volatile',
|
|
330
500
|
reason,
|
|
331
501
|
})
|
|
332
|
-
this.bindingCatalog = tentative.bindingCatalog
|
|
333
502
|
}
|
|
334
503
|
return
|
|
335
504
|
}
|
|
@@ -339,11 +508,11 @@ class SessionKernel {
|
|
|
339
508
|
code: tentative.program,
|
|
340
509
|
journal: normalized,
|
|
341
510
|
...(tentative.callSeq === undefined ? {} : { callSeq: tentative.callSeq }),
|
|
511
|
+
...(tentative.userBindings === undefined ? {} : { userBindings: tentative.userBindings }),
|
|
342
512
|
parent: this.history.head,
|
|
343
513
|
})
|
|
344
514
|
const index = this.history.nodes.push(node) - 1
|
|
345
515
|
this.history.head = index
|
|
346
|
-
this.bindingCatalog = tentative.bindingCatalog
|
|
347
516
|
this.finishStateOperations(journal.operations, index, tentative.worker)
|
|
348
517
|
return
|
|
349
518
|
}
|
|
@@ -352,7 +521,6 @@ class SessionKernel {
|
|
|
352
521
|
type: 'volatile',
|
|
353
522
|
reason: journal.volatileReason,
|
|
354
523
|
})
|
|
355
|
-
this.bindingCatalog = tentative.bindingCatalog
|
|
356
524
|
this.finishStateOperations(journal.operations, undefined, tentative.worker)
|
|
357
525
|
}
|
|
358
526
|
}
|
|
@@ -366,7 +534,11 @@ class SessionKernel {
|
|
|
366
534
|
if (tentative === undefined) {
|
|
367
535
|
return createReplMemorySnapshot(this.bindingCatalog.snapshot())
|
|
368
536
|
}
|
|
369
|
-
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
|
|
370
542
|
}
|
|
371
543
|
|
|
372
544
|
finishStateOperations(operations, index, worker) {
|
|
@@ -375,12 +547,26 @@ class SessionKernel {
|
|
|
375
547
|
this.history.checkpoints = transition.checkpoints
|
|
376
548
|
if (transition.restored) {
|
|
377
549
|
this.rollbackToDurable()
|
|
378
|
-
|
|
550
|
+
this.resetWorker(worker)
|
|
379
551
|
}
|
|
380
552
|
}
|
|
381
553
|
|
|
382
|
-
|
|
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() {
|
|
383
566
|
this.disposed = true
|
|
567
|
+
this.pendingInspection?.finish()
|
|
568
|
+
this.workerObservation = undefined
|
|
569
|
+
// Keep any refused reclamation observable through this kernel's dispose.
|
|
384
570
|
const worker = this.client.worker
|
|
385
571
|
if (worker !== undefined) {
|
|
386
572
|
/* c8 ignore next */
|
|
@@ -388,6 +574,18 @@ class SessionKernel {
|
|
|
388
574
|
}
|
|
389
575
|
await this.client.dispose()
|
|
390
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
|
+
}
|
|
391
589
|
}
|
|
392
590
|
}
|
|
393
591
|
|
|
@@ -418,7 +616,13 @@ export class SessionRuntime {
|
|
|
418
616
|
this.pendingNoops = new Map()
|
|
419
617
|
this.settlements = new WeakSet()
|
|
420
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
|
+
}
|
|
421
623
|
this.withInitiator = typeof options.withInitiator === 'function' ? options.withInitiator : undefined
|
|
624
|
+
this.observeSession = options.observeSession ?? (() => false)
|
|
625
|
+
this.disposal = undefined
|
|
422
626
|
}
|
|
423
627
|
|
|
424
628
|
async run(sessionContext, request) {
|
|
@@ -435,6 +639,13 @@ export class SessionRuntime {
|
|
|
435
639
|
this.config = resolved
|
|
436
640
|
}
|
|
437
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
|
+
|
|
438
649
|
async runTentative(sessionContext, request) {
|
|
439
650
|
const completed = result => Object.freeze({ result, settlement: undefined })
|
|
440
651
|
if (this.disposed) return completed({ logs: [], error: { kind: 'abort', message: 'PTC runtime disposed' } })
|
|
@@ -458,10 +669,13 @@ export class SessionRuntime {
|
|
|
458
669
|
sourceCallSeq = liveToolCallSeq(session, callId, 'run_code')
|
|
459
670
|
callSeq = persistedCallSeq ?? sourceCallSeq
|
|
460
671
|
} else {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
672
|
+
sourceCallSeq = persistedCallSeq
|
|
673
|
+
if (sourceCallSeq === undefined) {
|
|
674
|
+
try {
|
|
675
|
+
sourceCallSeq = liveToolCallSeq(session, callId, 'run_code')
|
|
676
|
+
} catch {
|
|
677
|
+
sourceCallSeq = undefined
|
|
678
|
+
}
|
|
465
679
|
}
|
|
466
680
|
}
|
|
467
681
|
} catch (error) {
|
|
@@ -472,9 +686,13 @@ export class SessionRuntime {
|
|
|
472
686
|
let history
|
|
473
687
|
try {
|
|
474
688
|
history = cellConfig.durableReplay
|
|
475
|
-
? recoverJournal(session, callSeq
|
|
689
|
+
? recoverJournal(session, callSeq, {
|
|
690
|
+
visibleCallSeqs: visibleExecutableCallSeqs(session),
|
|
691
|
+
})
|
|
476
692
|
: emptyHistory()
|
|
477
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.
|
|
478
696
|
return completed({ logs: [], error: { kind: 'recovery', message: `cannot reconstruct REPL from session log: ${messageOf(error)}` } })
|
|
479
697
|
}
|
|
480
698
|
kernel = new SessionKernel({
|
|
@@ -482,15 +700,19 @@ export class SessionRuntime {
|
|
|
482
700
|
history,
|
|
483
701
|
cwd,
|
|
484
702
|
session,
|
|
703
|
+
userBindingsCwd: this.userBindingsCwd,
|
|
485
704
|
withInitiator: this.withInitiator,
|
|
705
|
+
observeValues: () => this.config.replViewEnabled && this.observeSession(sessionId),
|
|
486
706
|
})
|
|
487
707
|
this.kernels.set(sessionId, kernel)
|
|
488
708
|
}
|
|
709
|
+
const policies = executionPolicies(cellConfig)
|
|
489
710
|
const journal = createJournal(
|
|
490
711
|
/* c8 ignore next */
|
|
491
712
|
this.pendingNoops.get(sessionId) ?? [],
|
|
492
|
-
|
|
493
|
-
|
|
713
|
+
policies.bindingPolicy,
|
|
714
|
+
policies.rewritesEnabled,
|
|
715
|
+
policies.languageSemantics,
|
|
494
716
|
)
|
|
495
717
|
const workerReservation = kernel.reserveWorkerConfiguration(cellConfig)
|
|
496
718
|
let result
|
|
@@ -504,6 +726,7 @@ export class SessionRuntime {
|
|
|
504
726
|
kernel,
|
|
505
727
|
sessionId,
|
|
506
728
|
replMemory: kernel.replMemoryFor(journal),
|
|
729
|
+
userBindings: kernel.userBindingsFor(journal),
|
|
507
730
|
...(result.recoveryBoundaries === undefined
|
|
508
731
|
? {}
|
|
509
732
|
: { recoveryBoundaries: result.recoveryBoundaries }),
|
|
@@ -548,14 +771,36 @@ export class SessionRuntime {
|
|
|
548
771
|
const kernel = this.kernels.get(id)
|
|
549
772
|
this.pendingNoops.delete(id)
|
|
550
773
|
if (kernel === undefined) return
|
|
551
|
-
|
|
774
|
+
// The entry survives a failed dispose so a repeated dispose reports the same
|
|
775
|
+
// unreclaimed instance instead of appearing to succeed.
|
|
552
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
|
|
553
789
|
}
|
|
554
790
|
|
|
555
|
-
async dispose() {
|
|
791
|
+
async #dispose() {
|
|
556
792
|
this.disposed = true
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
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
|
+
}
|
|
560
805
|
}
|
|
561
806
|
}
|