dsh-ptc-plus 0.4.2 → 0.4.8
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 +170 -76
- package/README.md +171 -77
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
- package/assets/ptc-plus-icon.svg +5 -0
- package/client.js +599 -310
- package/compiler-core.cjs +162 -160
- package/compiler-core.cjs.map +3 -3
- package/docs/README.md +4 -1
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +49 -11
- package/docs/adr/0008-define-agent-operating-contract.md +2 -2
- package/docs/adr/0010-session-log-derived-recovery-tips.md +7 -0
- package/docs/adr/0016-archive-resolved-review-findings.md +3 -3
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +5 -1
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +3 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +3 -1
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +14 -3
- package/docs/adr/0023-global-user-bindings.md +1 -1
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +23 -7
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +4 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +26 -6
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +2 -2
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +29 -0
- package/docs/adr/README.md +4 -4
- package/docs/architecture.md +8 -5
- package/docs/binding-review-placement-design.md +1 -1
- package/docs/client-ui.md +8 -4
- package/docs/durability-design.md +7 -7
- package/docs/installation.md +48 -12
- package/docs/publishing.md +4 -4
- package/docs/release-notes.md +109 -0
- package/docs/runtime-reference.md +25 -12
- package/docs/semantic-validation.md +7 -3
- package/docs/user-bindings.en.md +7 -6
- package/docs/user-bindings.md +6 -5
- package/docs/value-wire.md +1 -1
- package/docs/verification.md +43 -2
- package/index.js +39 -34
- package/internal/async-local-storage-intrinsics.js +12 -7
- package/internal/callable-source-facts.js +37 -1
- package/internal/cell-analysis.js +64 -9
- package/internal/config-spec.js +1 -1
- package/internal/cordis-tools-owner.js +81 -15
- package/internal/direct-surface-owner.js +331 -7
- package/internal/edit-transport-owner.js +80 -20
- package/internal/execution-seam-compat.js +192 -30
- package/internal/kernel-worker.js +162 -72
- package/internal/message-sources.js +14 -0
- package/internal/module-compilation.js +2 -1
- package/internal/rejected-cell-editor.js +11 -21
- package/internal/repl-memory-projection.js +10 -0
- package/internal/repl-scope-normalizer.js +72 -35
- package/internal/repl-value-observer.js +141 -45
- package/internal/runtime-config.js +62 -2
- package/internal/runtime-intrinsics.js +3 -0
- package/internal/runtime-messages.js +6 -4
- package/internal/session-cell-executor.js +24 -1
- package/internal/session-journal-recovery.js +267 -67
- package/internal/session-journal.js +466 -40
- package/internal/session-log-view.js +5 -4
- package/internal/session-runtime.js +45 -21
- package/internal/session-state.js +7 -29
- package/internal/settings-compat.js +66 -1
- package/internal/source-position-map.js +19 -0
- package/internal/user-binding-console-worker.js +23 -9
- package/internal/user-binding-draft-projection.js +3 -2
- package/internal/user-bindings-owner.js +2 -1
- package/internal/user-bindings.js +6 -1
- package/internal/validated-parse-repair.js +6 -2
- package/internal/value-wire.js +95 -30
- package/internal/worker-client.js +99 -8
- package/internal/worker-cwd-virtualization.js +10 -9
- package/internal/worker-output-capture.js +187 -0
- package/internal/worker-output-fence.js +26 -0
- package/internal/worker-realm-surfaces.js +197 -0
- package/internal/worker-repl-realm.js +10 -4
- package/package.json +42 -4
- package/src/client-activity.js +4 -1
- package/src/client-authoring-view.js +121 -62
- package/src/client-console.js +4 -2
- package/src/client-copy.js +4 -2
- package/src/client-feature-gates.js +7 -9
- package/src/client-host-compat.js +104 -27
- package/src/client-repl-view.js +3 -1
- package/src/client-styles.js +12 -1
- package/src/client-tool-view.js +3 -1
- package/src/client-transport.js +2 -2
- package/src/client-workbench.js +10 -7
- package/src/client.js +56 -17
|
@@ -35,6 +35,19 @@ export const LEGACY_EXECUTION_SEAM_SERVICE = 'codeRuntime'
|
|
|
35
35
|
|
|
36
36
|
const SEAM_SERVICES = Object.freeze([EXECUTION_SEAM_SERVICE, LEGACY_EXECUTION_SEAM_SERVICE])
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* How long the plugin waits for the host's execution seam before it reports the
|
|
40
|
+
* deployment as unsupported.
|
|
41
|
+
*
|
|
42
|
+
* Both seam names belong to the host's code-runtime row, so a composed host
|
|
43
|
+
* publishes one while the loader settles its entries. A profile that never
|
|
44
|
+
* registers either one cannot be served, and its activation promise must settle
|
|
45
|
+
* with a diagnostic: the host awaits loader settlement before it becomes
|
|
46
|
+
* usable, so an unbounded wait would park the whole host instead of failing
|
|
47
|
+
* this entry.
|
|
48
|
+
*/
|
|
49
|
+
const SEAM_ATTACHMENT_TIMEOUT_MS = 30_000
|
|
50
|
+
|
|
38
51
|
/**
|
|
39
52
|
* Descriptors the plugin replaces with the capabilities of its own execution.
|
|
40
53
|
* `executionInstructions` describes the wrapped provider's program model, which
|
|
@@ -105,15 +118,8 @@ export function createExecutionSeam(service, serviceName) {
|
|
|
105
118
|
if (installed !== undefined) throw new Error('ptc-plus: execution seam already taken over')
|
|
106
119
|
const saved = new Map()
|
|
107
120
|
const written = new Map()
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
if (!saved.has(name)) saved.set(name, previous)
|
|
111
|
-
// Preserve the shadowed property's visibility so a consumer that
|
|
112
|
-
// enumerates the provider sees the same member set.
|
|
113
|
-
Object.defineProperty(service, name, { enumerable: previous?.enumerable ?? false, ...descriptor })
|
|
114
|
-
written.set(name, Object.getOwnPropertyDescriptor(service, name))
|
|
115
|
-
}
|
|
116
|
-
define('run', {
|
|
121
|
+
const requested = new Map()
|
|
122
|
+
requested.set('run', {
|
|
117
123
|
configurable: true,
|
|
118
124
|
writable: true,
|
|
119
125
|
value: current
|
|
@@ -125,7 +131,32 @@ export function createExecutionSeam(service, serviceName) {
|
|
|
125
131
|
: request => execute(request),
|
|
126
132
|
})
|
|
127
133
|
for (const name of WITHHELD_DESCRIPTORS) {
|
|
128
|
-
|
|
134
|
+
requested.set(name, { configurable: true, value: name === 'executionInstructions' ? '' : undefined })
|
|
135
|
+
}
|
|
136
|
+
const extensible = Object.isExtensible(service)
|
|
137
|
+
for (const [name, descriptor] of requested) {
|
|
138
|
+
const previous = Object.getOwnPropertyDescriptor(service, name)
|
|
139
|
+
saved.set(name, previous)
|
|
140
|
+
const next = { enumerable: previous?.enumerable ?? false, ...descriptor }
|
|
141
|
+
requested.set(name, next)
|
|
142
|
+
const probe = {}
|
|
143
|
+
if (previous !== undefined) Object.defineProperty(probe, name, previous)
|
|
144
|
+
if (!extensible) Object.preventExtensions(probe)
|
|
145
|
+
Object.defineProperty(probe, name, next)
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
for (const [name, descriptor] of requested) {
|
|
149
|
+
Object.defineProperty(service, name, descriptor)
|
|
150
|
+
written.set(name, Object.getOwnPropertyDescriptor(service, name))
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
for (const name of [...written.keys()].reverse()) {
|
|
154
|
+
if (!descriptorsMatch(Object.getOwnPropertyDescriptor(service, name), written.get(name))) continue
|
|
155
|
+
const descriptor = saved.get(name)
|
|
156
|
+
if (descriptor === undefined) delete service[name]
|
|
157
|
+
else Object.defineProperty(service, name, descriptor)
|
|
158
|
+
}
|
|
159
|
+
throw error
|
|
129
160
|
}
|
|
130
161
|
const restore = () => {
|
|
131
162
|
if (installed !== restore) return
|
|
@@ -145,9 +176,10 @@ export function createExecutionSeam(service, serviceName) {
|
|
|
145
176
|
/**
|
|
146
177
|
* Wait for the host's execution seam and attach the plugin to the live one.
|
|
147
178
|
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
179
|
+
* The current service is preferred whenever both are live; the preceding
|
|
180
|
+
* generation attaches only when the current one is absent, and a current
|
|
181
|
+
* service registered later retires the live legacy injection before attaching.
|
|
182
|
+
* The injected scope requires the plugin's own core
|
|
151
183
|
* services as well, so the callback receives a context the plugin can work in,
|
|
152
184
|
* and everything the plugin registers there belongs to the seam the host
|
|
153
185
|
* registered: unloading that service unloads the plugin, and registering it
|
|
@@ -157,16 +189,107 @@ export function createExecutionSeam(service, serviceName) {
|
|
|
157
189
|
* @param {object} ctx - the plugin context.
|
|
158
190
|
* @param {object} options - `services` are the plugin's own required services,
|
|
159
191
|
* and `attach` is invoked with the injected scope and the seam handle.
|
|
160
|
-
*
|
|
192
|
+
* `attachmentTimeoutMs` bounds the wait for a seam that never appears.
|
|
193
|
+
* @returns {Promise<void>} settles after attachment with a valid Cordis effect value.
|
|
161
194
|
*/
|
|
162
|
-
export function installExecutionSeam(ctx, {
|
|
195
|
+
export function installExecutionSeam(ctx, {
|
|
196
|
+
services = [],
|
|
197
|
+
attach,
|
|
198
|
+
reportFailure,
|
|
199
|
+
attachmentTimeoutMs = SEAM_ATTACHMENT_TIMEOUT_MS,
|
|
200
|
+
}) {
|
|
163
201
|
let live
|
|
202
|
+
let stopping = false
|
|
203
|
+
let startWatcher
|
|
204
|
+
let unusableCurrent
|
|
205
|
+
const report = (error, name) => {
|
|
206
|
+
try {
|
|
207
|
+
reportFailure?.(error, name)
|
|
208
|
+
} catch {
|
|
209
|
+
// A reporting failure cannot replace the seam failure it describes.
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const registerLifecycle = (entry, name, scope) => scope.effect?.(() => () => {
|
|
213
|
+
if (live !== entry) return
|
|
214
|
+
if (entry.retiring !== undefined) return
|
|
215
|
+
live = undefined
|
|
216
|
+
if (!stopping && name === EXECUTION_SEAM_SERVICE
|
|
217
|
+
&& readService(ctx, EXECUTION_SEAM_SERVICE) === undefined
|
|
218
|
+
&& readService(ctx, LEGACY_EXECUTION_SEAM_SERVICE) !== undefined) {
|
|
219
|
+
startWatcher(LEGACY_EXECUTION_SEAM_SERVICE)
|
|
220
|
+
}
|
|
221
|
+
}, 'ptc-plus execution seam lifecycle')
|
|
164
222
|
const select = (name, scope) => {
|
|
165
|
-
|
|
166
|
-
if (
|
|
223
|
+
const currentService = readService(ctx, EXECUTION_SEAM_SERVICE)
|
|
224
|
+
if (unusableCurrent !== undefined && currentService !== unusableCurrent) {
|
|
225
|
+
unusableCurrent = undefined
|
|
226
|
+
}
|
|
227
|
+
if (name === EXECUTION_SEAM_SERVICE && currentService === unusableCurrent) {
|
|
228
|
+
// A current service whose attach already failed cannot supersede the live
|
|
229
|
+
// legacy attachment. A replacement service object clears the marker above.
|
|
230
|
+
return live?.attached
|
|
231
|
+
}
|
|
232
|
+
if (live !== undefined) {
|
|
233
|
+
if (name !== EXECUTION_SEAM_SERVICE || live.name === EXECUTION_SEAM_SERVICE) return live.attached
|
|
234
|
+
const previous = live
|
|
235
|
+
if (previous.retiring !== undefined) return previous.retiring
|
|
236
|
+
const disposeLegacy = previous.scope?.fiber?.dispose
|
|
237
|
+
if (typeof disposeLegacy !== 'function') {
|
|
238
|
+
throw new Error('ptc-plus: live codeRuntime attachment cannot be superseded without a disposable injected scope')
|
|
239
|
+
}
|
|
240
|
+
let resolveActivation
|
|
241
|
+
let rejectActivation
|
|
242
|
+
const activation = new Promise((resolve, reject) => {
|
|
243
|
+
resolveActivation = resolve
|
|
244
|
+
rejectActivation = reject
|
|
245
|
+
})
|
|
246
|
+
previous.retiring = activation
|
|
247
|
+
let retirement
|
|
248
|
+
try {
|
|
249
|
+
retirement = disposeLegacy.call(previous.scope.fiber)
|
|
250
|
+
} catch (error) {
|
|
251
|
+
previous.retiring = undefined
|
|
252
|
+
rejectActivation(error)
|
|
253
|
+
return activation
|
|
254
|
+
}
|
|
255
|
+
let entry
|
|
256
|
+
const operation = Promise.resolve(retirement).then(() => {
|
|
257
|
+
if (live !== previous) return undefined
|
|
258
|
+
previous.retiring = undefined
|
|
259
|
+
entry = { name, scope }
|
|
260
|
+
live = entry
|
|
261
|
+
registerLifecycle(entry, name, scope)
|
|
262
|
+
const attached = attach(scope, createExecutionSeam(scope?.[name] ?? readService(ctx, name), name))
|
|
263
|
+
entry.attached = attached
|
|
264
|
+
return attached
|
|
265
|
+
})
|
|
266
|
+
operation.then(resolveActivation, error => {
|
|
267
|
+
if (live === previous) previous.retiring = undefined
|
|
268
|
+
else if (live === entry) {
|
|
269
|
+
// The current attach failed after the legacy fiber was retired. Keep
|
|
270
|
+
// the plugin attached by re-arming the preceding generation instead
|
|
271
|
+
// of silently returning run_code to the host provider.
|
|
272
|
+
live = undefined
|
|
273
|
+
unusableCurrent = readService(ctx, EXECUTION_SEAM_SERVICE)
|
|
274
|
+
if (readService(ctx, LEGACY_EXECUTION_SEAM_SERVICE) !== undefined) {
|
|
275
|
+
try {
|
|
276
|
+
startWatcher(LEGACY_EXECUTION_SEAM_SERVICE)
|
|
277
|
+
} catch (fallbackError) {
|
|
278
|
+
report(fallbackError, LEGACY_EXECUTION_SEAM_SERVICE)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
report(error, name)
|
|
283
|
+
rejectActivation(error)
|
|
284
|
+
})
|
|
285
|
+
return activation
|
|
286
|
+
}
|
|
287
|
+
if (name === LEGACY_EXECUTION_SEAM_SERVICE
|
|
288
|
+
&& currentService !== undefined
|
|
289
|
+
&& currentService !== unusableCurrent) {
|
|
167
290
|
return undefined
|
|
168
291
|
}
|
|
169
|
-
const entry = { name }
|
|
292
|
+
const entry = { name, scope }
|
|
170
293
|
live = entry
|
|
171
294
|
try {
|
|
172
295
|
entry.attached = attach(scope, createExecutionSeam(scope?.[name] ?? readService(ctx, name), name))
|
|
@@ -174,20 +297,59 @@ export function installExecutionSeam(ctx, { services = [], attach }) {
|
|
|
174
297
|
live = undefined
|
|
175
298
|
throw error
|
|
176
299
|
}
|
|
177
|
-
|
|
178
|
-
if (live === entry) live = undefined
|
|
179
|
-
}, 'ptc-plus execution seam lifecycle')
|
|
300
|
+
registerLifecycle(entry, name, scope)
|
|
180
301
|
return entry.attached
|
|
181
302
|
}
|
|
303
|
+
startWatcher = (name, settlement) => ctx.inject([...services, name], scope => {
|
|
304
|
+
let selected
|
|
305
|
+
try {
|
|
306
|
+
selected = select(name, scope)
|
|
307
|
+
} catch (error) {
|
|
308
|
+
settlement?.reject(error)
|
|
309
|
+
report(error, name)
|
|
310
|
+
throw error
|
|
311
|
+
}
|
|
312
|
+
if (selected !== null && typeof selected === 'object' && typeof selected.then === 'function') {
|
|
313
|
+
const operation = Promise.resolve(selected)
|
|
314
|
+
operation.then(
|
|
315
|
+
value => settlement?.resolve(value),
|
|
316
|
+
error => settlement?.reject(error),
|
|
317
|
+
)
|
|
318
|
+
return operation.then(() => undefined)
|
|
319
|
+
}
|
|
320
|
+
settlement?.resolve(selected)
|
|
321
|
+
return undefined
|
|
322
|
+
})
|
|
182
323
|
const pending = SEAM_SERVICES.map(name => new Promise((resolve, reject) => {
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
resolve(select(name, scope))
|
|
186
|
-
} catch (error) {
|
|
187
|
-
reject(error)
|
|
188
|
-
throw error
|
|
189
|
-
}
|
|
190
|
-
})
|
|
324
|
+
startWatcher(name, { resolve, reject })
|
|
191
325
|
}))
|
|
192
|
-
|
|
326
|
+
ctx.effect?.(() => () => { stopping = true }, 'ptc-plus execution seam watcher lifecycle')
|
|
327
|
+
const bound = seamWaitBound(ctx, attachmentTimeoutMs)
|
|
328
|
+
return Promise.race([Promise.race(pending), bound.promise])
|
|
329
|
+
.then(() => undefined)
|
|
330
|
+
.finally(bound.cancel)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Bound the wait for a seam the deployment never registers.
|
|
335
|
+
*
|
|
336
|
+
* @param {object} ctx - the plugin context owning the timer.
|
|
337
|
+
* @param {number} timeoutMs - positive bound, or a non-positive value for none.
|
|
338
|
+
* @returns the rejection promise and the disposer that clears its timer.
|
|
339
|
+
*/
|
|
340
|
+
function seamWaitBound(ctx, timeoutMs) {
|
|
341
|
+
if (!(timeoutMs > 0)) return { promise: new Promise(() => {}), cancel: () => {} }
|
|
342
|
+
let timer
|
|
343
|
+
const promise = new Promise((_resolve, reject) => {
|
|
344
|
+
timer = setTimeout(() => reject(new Error(
|
|
345
|
+
`ptc-plus: no host execution seam (${SEAM_SERVICES.join(' or ')}) is registered; this deployment does not publish a code runtime`,
|
|
346
|
+
)), timeoutMs)
|
|
347
|
+
})
|
|
348
|
+
// The timer is not unref'd: this rejection is the only thing that settles the
|
|
349
|
+
// activation promise on a deployment that never registers a seam, and an
|
|
350
|
+
// unref'd timer would let the process drain before it fires. It is cleared as
|
|
351
|
+
// soon as the race settles so an attached seam never holds the loop open.
|
|
352
|
+
const cancel = () => clearTimeout(timer)
|
|
353
|
+
ctx.effect?.(() => () => cancel(), 'ptc-plus execution seam wait bound')
|
|
354
|
+
return { promise, cancel }
|
|
193
355
|
}
|