dsh-ptc-plus 0.4.8 → 0.5.0
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 +61 -123
- package/README.md +61 -123
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/dsh-ptc-plus-banner-zh.webp +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.cjs +42336 -0
- package/cordis.patch.yml +13 -2
- package/docs/README.md +7 -2
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -179
- package/docs/adr/0008-define-agent-operating-contract.md +5 -1
- package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -2
- package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +2 -2
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +14 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
- package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
- package/docs/adr/0023-global-user-bindings.md +3 -1
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +2 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +101 -35
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +3 -3
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
- package/docs/adr/0032-own-session-log-facts-in-a-host-only-projection.md +27 -0
- package/docs/adr/0033-gate-client-visual-usability-in-a-browser.md +23 -0
- package/docs/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
- package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
- package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
- package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
- package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
- package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
- package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
- package/docs/adr/0041-record-cell-execution-directories.md +65 -0
- package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
- package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
- package/docs/adr/README.md +6 -0
- package/docs/architecture.md +25 -27
- package/docs/binding-review-placement-design.md +29 -21
- package/docs/capability-planning-components.md +93 -0
- package/docs/capability-projection.md +7 -0
- package/docs/client-ui.md +88 -29
- package/docs/client-visual-contract.md +65 -0
- package/docs/context-management.md +22 -0
- package/docs/context-optimization-plan.md +353 -0
- package/docs/context-public-interface-coverage.md +25 -0
- package/docs/durability-design.md +31 -14
- package/docs/evaluation.md +41 -6
- package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
- package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
- package/docs/installation.md +50 -8
- package/docs/program-data-plane.md +5 -3
- package/docs/publishing.md +5 -5
- package/docs/release-coverage.md +73 -0
- package/docs/release-notes.md +1 -1
- package/docs/review-procedure.md +56 -0
- package/docs/runtime-reference.md +48 -16
- package/docs/semantic-validation.md +14 -2
- package/docs/stateful-binding-updates-proposal.md +2 -2
- package/docs/user-bindings.en.md +2 -2
- package/docs/user-bindings.md +3 -3
- package/docs/value-wire.md +11 -1
- package/docs/verification-optimization-work.md +52 -15
- package/docs/verification.md +30 -86
- package/index.js +147 -23
- package/internal/binding-toggle-receipt.js +24 -0
- package/internal/capability-command-receipt.js +29 -0
- package/internal/capability-environment.js +1247 -0
- package/internal/capability-plan.js +285 -0
- package/internal/capability-policy.js +96 -0
- package/internal/capability-statistics-store.js +180 -0
- package/internal/capability-statistics.js +210 -0
- package/internal/compiler-platform.js +5 -3
- package/internal/compiler-service.js +2 -9
- package/internal/config-spec.js +79 -9
- package/internal/cordis-tools-owner.js +22 -19
- package/internal/direct-surface-owner.js +29 -29
- package/internal/dynamic-environment-runtime.js +9 -9
- package/internal/edit-transport-owner.js +183 -139
- package/internal/execution-provider-owner.js +160 -0
- package/internal/execution-seam-compat.js +76 -120
- package/internal/isolated-worker.js +12 -8
- package/internal/kernel-worker.js +104 -32
- package/internal/native-root-dynamic.js +12 -13
- package/internal/program-bindings.js +190 -0
- package/internal/recovery-tips.js +53 -23
- package/internal/rejected-cell-editor.js +8 -8
- package/internal/repl-observation-interest.js +11 -3
- package/internal/rpc-contract.js +1 -0
- package/internal/runtime-bridge-owner.js +351 -82
- package/internal/runtime-config.js +58 -0
- package/internal/runtime-contexts.js +85 -16
- package/internal/runtime-intrinsics.js +1 -0
- package/internal/runtime-messages.js +41 -0
- package/internal/sdk-projection.js +10 -82
- package/internal/session-cell-executor.js +42 -19
- package/internal/session-events.js +74 -4
- package/internal/session-journal-recovery.js +904 -184
- package/internal/session-journal-schema.js +6 -3
- package/internal/session-journal.js +31 -2
- package/internal/session-log-view.js +189 -43
- package/internal/session-runtime.js +175 -23
- package/internal/settings-compat.js +23 -6
- package/internal/stateful-module-compiler.js +3 -2
- package/internal/stateful-module-runtime.js +7 -3
- package/internal/stateful-root-runtime.js +9 -4
- package/internal/user-binding-authoring.js +2 -7
- package/internal/user-bindings-owner.js +47 -7
- package/internal/validated-parse-repair.js +1 -1
- package/internal/value-wire.js +68 -21
- package/internal/worker-cwd-virtualization.js +20 -18
- package/internal/worker-realm-surfaces.js +3 -3
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +55 -38
- package/src/client-authoring-view.js +286 -168
- package/src/client-binding-review.js +4 -1
- package/src/client-binding-toggle.js +18 -0
- package/src/client-capabilities-view.js +1341 -0
- package/src/client-capability-sort.js +29 -0
- package/src/client-code-editor.js +6 -0
- package/src/client-copy.js +881 -8
- package/src/client-feature-gates.js +15 -5
- package/src/client-host-compat.js +23 -2
- package/src/client-indicator-view.js +4 -6
- package/src/client-primitives.js +280 -0
- package/src/client-repl-view.js +23 -16
- package/src/client-settings-view.js +412 -69
- package/src/client-styles.js +193 -41
- package/src/client-tool-hover.js +185 -0
- package/src/client-tool-view.js +45 -11
- package/src/client-transport.js +44 -9
- package/src/client-workbench.js +9 -10
- package/src/client.js +148 -68
- 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/client.js +0 -31857
|
@@ -1,39 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Own how PTC Plus attaches to the host's program-execution seam.
|
|
3
|
-
*
|
|
4
|
-
* DSH registers one execution seam per generation. The current release
|
|
5
|
-
* registers `ctx.ptcRuntime` (`@deepseek-ai/dsh-ptc-runtime`); the preceding one
|
|
6
|
-
* registered `ctx.codeRuntime` (`@deepseek-ai/dsh-code-runtime`). Both run one
|
|
7
|
-
* model-written program against host async bindings. They differ in the service
|
|
8
|
-
* name, in the call shape (`resolve(request)` then `run(spec)` against a single
|
|
9
|
-
* `run(request)`), and in the capability descriptors a provider publishes.
|
|
10
|
-
* Which seam is live is decided by the registered service, never by a version
|
|
11
|
-
* string, so a deployment on either generation obtains the same behavior.
|
|
12
|
-
*
|
|
13
|
-
* PTC Plus replaces the execution of session cells with its own persistent
|
|
14
|
-
* REPL. The object DSH describes to the model is therefore the plugin's
|
|
15
|
-
* execution, not the wrapped provider's, so `takeOver()` answers the
|
|
16
|
-
* descriptors the plugin can honor and withholds the ones it cannot. The REPL
|
|
17
|
-
* runs every cell in one long-lived worker rather than a fresh process per
|
|
18
|
-
* call, applies the session's configured budgets rather than a per-call host
|
|
19
|
-
* deadline, and performs no file confinement. DSH gates `timeoutMs` on
|
|
20
|
-
* `timeout` and resolves or escalates file policy on `sandboxMode`, so
|
|
21
|
-
* withholding those descriptors keeps DSH's authority decisions with DSH
|
|
22
|
-
* instead of having this plugin accept an input it would then ignore.
|
|
23
|
-
*
|
|
24
|
-
* A seam-neutral request carries exactly `program`, `bindings`, and `signal`:
|
|
25
|
-
* the fields PTC Plus consumes. A resolved spec's directory, deadline, and
|
|
26
|
-
* authority describe the provider whose execution was replaced, and are neither
|
|
27
|
-
* forwarded to the session kernel nor re-derived here.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
/** Cordis service name registered by the current DSH generation. */
|
|
1
|
+
/** Resolved-spec Cordis execution contract. */
|
|
31
2
|
export const EXECUTION_SEAM_SERVICE = 'ptcRuntime'
|
|
32
3
|
|
|
33
|
-
/** Cordis
|
|
4
|
+
/** Request-only Cordis execution contract. */
|
|
34
5
|
export const LEGACY_EXECUTION_SEAM_SERVICE = 'codeRuntime'
|
|
35
6
|
|
|
36
7
|
const SEAM_SERVICES = Object.freeze([EXECUTION_SEAM_SERVICE, LEGACY_EXECUTION_SEAM_SERVICE])
|
|
8
|
+
const PLUGIN_EXECUTION_SEAM = Symbol.for('dsh-ptc-plus.execution-seam')
|
|
37
9
|
|
|
38
10
|
/**
|
|
39
11
|
* How long the plugin waits for the host's execution seam before it reports the
|
|
@@ -48,34 +20,12 @@ const SEAM_SERVICES = Object.freeze([EXECUTION_SEAM_SERVICE, LEGACY_EXECUTION_SE
|
|
|
48
20
|
*/
|
|
49
21
|
const SEAM_ATTACHMENT_TIMEOUT_MS = 30_000
|
|
50
22
|
|
|
51
|
-
/**
|
|
52
|
-
* Descriptors the plugin replaces with the capabilities of its own execution.
|
|
53
|
-
* `executionInstructions` describes the wrapped provider's program model, which
|
|
54
|
-
* no longer applies; `timeout` and `sandboxMode` gate host inputs the plugin
|
|
55
|
-
* cannot honor. Withdrawal is unconditional because the rule is a property of
|
|
56
|
-
* the plugin's execution, not of a generation: the preceding generation's
|
|
57
|
-
* contract defines none of these members and nothing in its install closure
|
|
58
|
-
* reads them, so withdrawing there is inert, while a provider that published
|
|
59
|
-
* them stays unable to outlive the takeover.
|
|
60
|
-
*/
|
|
61
|
-
const WITHHELD_DESCRIPTORS = Object.freeze(['executionInstructions', 'sandboxMode', 'timeout'])
|
|
62
|
-
|
|
63
23
|
/** Read a registered service without declaring an injection requirement. */
|
|
64
24
|
function readService(ctx, name) {
|
|
65
25
|
if (typeof ctx?.get === 'function') return ctx.get(name)
|
|
66
26
|
return ctx?.[name]
|
|
67
27
|
}
|
|
68
28
|
|
|
69
|
-
function descriptorsMatch(left, right) {
|
|
70
|
-
if (left === undefined || right === undefined) return left === right
|
|
71
|
-
return left.value === right.value
|
|
72
|
-
&& left.get === right.get
|
|
73
|
-
&& left.set === right.set
|
|
74
|
-
&& left.writable === right.writable
|
|
75
|
-
&& left.enumerable === right.enumerable
|
|
76
|
-
&& left.configurable === right.configurable
|
|
77
|
-
}
|
|
78
|
-
|
|
79
29
|
/**
|
|
80
30
|
* Build the seam handle for one live execution service.
|
|
81
31
|
* @param {object} service - the registered `ctx.ptcRuntime` or `ctx.codeRuntime`.
|
|
@@ -85,6 +35,7 @@ export function createExecutionSeam(service, serviceName) {
|
|
|
85
35
|
if (service === null || typeof service !== 'object') {
|
|
86
36
|
throw new TypeError(`ptc-plus: ${serviceName} must be an execution service object`)
|
|
87
37
|
}
|
|
38
|
+
if (typeof service[PLUGIN_EXECUTION_SEAM] === 'function') return service[PLUGIN_EXECUTION_SEAM]()
|
|
88
39
|
const current = serviceName === EXECUTION_SEAM_SERVICE
|
|
89
40
|
const upstreamRun = service.run
|
|
90
41
|
if (typeof upstreamRun !== 'function') {
|
|
@@ -95,90 +46,95 @@ export function createExecutionSeam(service, serviceName) {
|
|
|
95
46
|
throw new TypeError('ptc-plus: ptcRuntime.resolve must be a function')
|
|
96
47
|
}
|
|
97
48
|
let installed
|
|
98
|
-
|
|
49
|
+
const sourceListeners = new Set()
|
|
50
|
+
let available = true
|
|
51
|
+
const requireAvailable = () => {
|
|
52
|
+
if (!available) throw new Error(`ptc-plus: original ${serviceName} generation is unavailable`)
|
|
53
|
+
}
|
|
54
|
+
const requireCompatible = () => {
|
|
55
|
+
requireAvailable()
|
|
56
|
+
if (installed !== undefined && service.language !== installed.language) {
|
|
57
|
+
throw new Error(`ptc-plus: current original language ${JSON.stringify(service.language)} is incompatible with the active ${JSON.stringify(installed.language)} runtime`)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const provider = Object.freeze({
|
|
61
|
+
get language() { requireCompatible(); return service.language },
|
|
62
|
+
get isolation() { requireCompatible(); return installed === undefined ? service.isolation : 'worker-thread' },
|
|
63
|
+
get executionInstructions() { requireCompatible(); return installed === undefined ? service.executionInstructions ?? '' : '' },
|
|
64
|
+
get sandboxMode() { requireCompatible(); return installed === undefined ? service.sandboxMode : undefined },
|
|
65
|
+
get timeout() { requireCompatible(); return installed === undefined ? service.timeout : undefined },
|
|
66
|
+
resolve(request) {
|
|
67
|
+
requireCompatible()
|
|
68
|
+
return current ? upstreamResolve.call(service, request) : request
|
|
69
|
+
},
|
|
70
|
+
run(input) {
|
|
71
|
+
requireCompatible()
|
|
72
|
+
return installed === undefined ? upstreamRun.call(service, input) : installed.execute(input)
|
|
73
|
+
},
|
|
74
|
+
[PLUGIN_EXECUTION_SEAM]() { return seam },
|
|
75
|
+
})
|
|
76
|
+
const seam = Object.freeze({
|
|
99
77
|
serviceName,
|
|
100
|
-
language
|
|
78
|
+
get language() { requireAvailable(); return service.language },
|
|
79
|
+
provider,
|
|
80
|
+
get active() { return installed !== undefined && service.language === installed.language },
|
|
81
|
+
onSourceChange(listener) {
|
|
82
|
+
sourceListeners.add(listener)
|
|
83
|
+
return () => sourceListeners.delete(listener)
|
|
84
|
+
},
|
|
85
|
+
sourceChanged() {
|
|
86
|
+
return Promise.all([...sourceListeners].map(listener => listener())).then(() => undefined)
|
|
87
|
+
},
|
|
101
88
|
/**
|
|
102
|
-
* Run one program through the provider this plugin wraps. The
|
|
103
|
-
*
|
|
104
|
-
*
|
|
89
|
+
* Run one program through the provider this plugin wraps. The resolved-spec
|
|
90
|
+
* contract resolves the request before running it, exactly as its consumer
|
|
91
|
+
* does; the request-only contract accepts the request directly.
|
|
105
92
|
*/
|
|
106
93
|
invokeUpstream(request) {
|
|
94
|
+
requireCompatible()
|
|
107
95
|
return current
|
|
108
96
|
? upstreamRun.call(service, upstreamResolve.call(service, request))
|
|
109
97
|
: upstreamRun.call(service, request)
|
|
110
98
|
},
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (installed !== undefined) throw new Error('ptc-plus: execution seam already
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const requested = new Map()
|
|
122
|
-
requested.set('run', {
|
|
123
|
-
configurable: true,
|
|
124
|
-
writable: true,
|
|
125
|
-
value: current
|
|
126
|
-
? spec => execute({
|
|
127
|
-
program: spec.program,
|
|
128
|
-
bindings: spec.bindings,
|
|
129
|
-
signal: spec.signal,
|
|
130
|
-
})
|
|
131
|
-
: request => execute(request),
|
|
132
|
-
})
|
|
133
|
-
for (const name of WITHHELD_DESCRIPTORS) {
|
|
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
|
|
160
|
-
}
|
|
99
|
+
runUpstream(spec) {
|
|
100
|
+
requireCompatible()
|
|
101
|
+
return upstreamRun.call(service, spec)
|
|
102
|
+
},
|
|
103
|
+
installExecute(execute) {
|
|
104
|
+
requireAvailable()
|
|
105
|
+
if (typeof execute !== 'function') throw new TypeError('ptc-plus: execution entry must be a function')
|
|
106
|
+
if (installed !== undefined) throw new Error('ptc-plus: execution seam already installed')
|
|
107
|
+
const entry = { execute, language: service.language }
|
|
108
|
+
installed = entry
|
|
161
109
|
const restore = () => {
|
|
162
|
-
if (installed !==
|
|
110
|
+
if (installed !== entry) return
|
|
163
111
|
installed = undefined
|
|
164
|
-
for (const [name, descriptor] of saved) {
|
|
165
|
-
if (!descriptorsMatch(Object.getOwnPropertyDescriptor(service, name), written.get(name))) continue
|
|
166
|
-
if (descriptor === undefined) delete service[name]
|
|
167
|
-
else Object.defineProperty(service, name, descriptor)
|
|
168
|
-
}
|
|
169
112
|
}
|
|
170
|
-
installed = restore
|
|
171
113
|
return restore
|
|
172
114
|
},
|
|
115
|
+
retire() {
|
|
116
|
+
available = false
|
|
117
|
+
installed = undefined
|
|
118
|
+
sourceListeners.clear()
|
|
119
|
+
},
|
|
173
120
|
})
|
|
121
|
+
return seam
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function registeredExecutionSeam(service, serviceName) {
|
|
125
|
+
const seam = createExecutionSeam(service, serviceName)
|
|
126
|
+
if (typeof service[PLUGIN_EXECUTION_SEAM] !== 'function') {
|
|
127
|
+
throw new Error(`ptc-plus: ${serviceName} requires the public execution-provider bundle composition`)
|
|
128
|
+
}
|
|
129
|
+
return seam
|
|
174
130
|
}
|
|
175
131
|
|
|
176
132
|
/**
|
|
177
133
|
* Wait for the host's execution seam and attach the plugin to the live one.
|
|
178
134
|
*
|
|
179
|
-
* The
|
|
180
|
-
*
|
|
181
|
-
*
|
|
135
|
+
* The resolved-spec service is preferred whenever both contracts are live; the
|
|
136
|
+
* request-only service attaches when it is absent. A resolved-spec service
|
|
137
|
+
* registered later retires the request-only injection before attaching.
|
|
182
138
|
* The injected scope requires the plugin's own core
|
|
183
139
|
* services as well, so the callback receives a context the plugin can work in,
|
|
184
140
|
* and everything the plugin registers there belongs to the seam the host
|
|
@@ -259,7 +215,7 @@ export function installExecutionSeam(ctx, {
|
|
|
259
215
|
entry = { name, scope }
|
|
260
216
|
live = entry
|
|
261
217
|
registerLifecycle(entry, name, scope)
|
|
262
|
-
const attached = attach(scope,
|
|
218
|
+
const attached = attach(scope, registeredExecutionSeam(scope?.[name] ?? readService(ctx, name), name))
|
|
263
219
|
entry.attached = attached
|
|
264
220
|
return attached
|
|
265
221
|
})
|
|
@@ -292,7 +248,7 @@ export function installExecutionSeam(ctx, {
|
|
|
292
248
|
const entry = { name, scope }
|
|
293
249
|
live = entry
|
|
294
250
|
try {
|
|
295
|
-
entry.attached = attach(scope,
|
|
251
|
+
entry.attached = attach(scope, registeredExecutionSeam(scope?.[name] ?? readService(ctx, name), name))
|
|
296
252
|
} catch (error) {
|
|
297
253
|
live = undefined
|
|
298
254
|
throw error
|
|
@@ -15,6 +15,17 @@ const ISOLATED_STOP_MS = 5000
|
|
|
15
15
|
/** Bound on the helper's output pipes really ending after its process exited. */
|
|
16
16
|
const ISOLATED_OUTPUT_MS = 5000
|
|
17
17
|
|
|
18
|
+
export function isolatedWorkerEntry(entry) {
|
|
19
|
+
if (!(entry instanceof URL) && (typeof entry !== 'string' || entry.length === 0)) {
|
|
20
|
+
throw new TypeError('isolated worker entry must be a URL or a non-empty string filename')
|
|
21
|
+
}
|
|
22
|
+
return entry instanceof URL
|
|
23
|
+
? entry.href
|
|
24
|
+
: entry.slice(0, 5).toLowerCase() === 'file:'
|
|
25
|
+
? new URL(entry).href
|
|
26
|
+
: pathToFileURL(resolve(entry)).href
|
|
27
|
+
}
|
|
28
|
+
|
|
18
29
|
/**
|
|
19
30
|
* A worker-shaped transport whose worker lives in a killable helper process.
|
|
20
31
|
*
|
|
@@ -29,17 +40,10 @@ export class IsolatedWorker extends EventEmitter {
|
|
|
29
40
|
super()
|
|
30
41
|
// The entry follows the worker constructor's contract: a URL or a string
|
|
31
42
|
// filename. Anything else fails here, before a helper process exists.
|
|
32
|
-
if (!(entry instanceof URL) && (typeof entry !== 'string' || entry.length === 0)) {
|
|
33
|
-
throw new TypeError('isolated worker entry must be a URL or a non-empty string filename')
|
|
34
|
-
}
|
|
35
43
|
// The init message crosses advanced IPC, which cannot rebuild a URL object, so
|
|
36
44
|
// the boundary normalizes it to one absolute file URL string. A path follows
|
|
37
45
|
// the worker constructor's rule: resolved from the current working directory.
|
|
38
|
-
const entrySpec = entry
|
|
39
|
-
? entry.href
|
|
40
|
-
: entry.slice(0, 5).toLowerCase() === 'file:'
|
|
41
|
-
? new URL(entry).href
|
|
42
|
-
: pathToFileURL(resolve(entry)).href
|
|
46
|
+
const entrySpec = isolatedWorkerEntry(entry)
|
|
43
47
|
this.stdout = new PassThrough()
|
|
44
48
|
this.stderr = new PassThrough()
|
|
45
49
|
this.protocol = protocol
|
|
@@ -8,13 +8,13 @@ import { USER_BINDING_TRANSFORM, LEGACY_USER_BINDING_TRANSFORM } from './module-
|
|
|
8
8
|
import { isAbsolute, resolve } from 'node:path'
|
|
9
9
|
import repl from 'node:repl'
|
|
10
10
|
import { PassThrough, Writable } from 'node:stream'
|
|
11
|
-
import { pathToFileURL } from '
|
|
11
|
+
import { pathToFileURL } from './module-runtime-platform.js'
|
|
12
12
|
import { MessageChannel, parentPort, workerData } from 'node:worker_threads'
|
|
13
13
|
import { synchronizeBuiltinEsmExports } from './builtin-esm-sync.js'
|
|
14
14
|
import { createPrivateAsyncLocalStorage } from './async-local-storage-intrinsics.js'
|
|
15
15
|
import { createExceptionOriginScope, errorDetails, messageOf, programBindingError } from './failure-reporting.js'
|
|
16
16
|
import { DURABLE_IMPORTS, FORBIDDEN_IMPORTS } from './module-policy.js'
|
|
17
|
-
import { decodeValue, encodeValue } from './value-wire.js'
|
|
17
|
+
import { decodeValue, encodeValue, encodeValueAtBoundary, valueEncodingFailure } from './value-wire.js'
|
|
18
18
|
import { LEGACY_USER_BINDINGS_REUSE_POLICY, LIVE_USER_BINDINGS_SHADOW_POLICY, normalizeUserBindingNames } from './session-journal-schema.js'
|
|
19
19
|
import { installWorkerCwdVirtualization } from './worker-cwd-virtualization.js'
|
|
20
20
|
import {
|
|
@@ -114,6 +114,15 @@ const output = new PassThrough()
|
|
|
114
114
|
output.resume()
|
|
115
115
|
const evaluationScope = createPrivateAsyncLocalStorage()
|
|
116
116
|
const sessionCwd = typeof workerData?.cwd === 'string' ? workerData.cwd : undefined
|
|
117
|
+
// The module source base is fixed when a cell starts; the operation directory
|
|
118
|
+
// follows the Session's working directory while the cell runs, so a confirmed
|
|
119
|
+
// `tools.working_directory` change rebases the rest of the cell exactly as the
|
|
120
|
+
// Host tools, which resolve at call time, already do.
|
|
121
|
+
let executionCwd = sessionCwd
|
|
122
|
+
let cellOperationCwd = sessionCwd
|
|
123
|
+
const resolveModuleFilename = resolve
|
|
124
|
+
const isAbsoluteDirectory = isAbsolute
|
|
125
|
+
const nativeCreateRequire = createRequire
|
|
117
126
|
if (sessionCwd !== undefined && !isAbsolute(sessionCwd)) {
|
|
118
127
|
throw new Error(`ptc-plus session cwd must be absolute, got ${jsonStringify(sessionCwd)}`)
|
|
119
128
|
}
|
|
@@ -145,7 +154,7 @@ const completionObserver = createCellCompletionObserver(runInWorkerReplRealm('Fu
|
|
|
145
154
|
let replModuleRealm
|
|
146
155
|
const statefulRoots = createStatefulRootRuntime({
|
|
147
156
|
refresh: name => refreshLegacyPublication(name),
|
|
148
|
-
importModule: (
|
|
157
|
+
importModule: moduleImportFor(),
|
|
149
158
|
errors: runInWorkerReplRealm('({ ReferenceError, TypeError })'),
|
|
150
159
|
dynamicIntrinsics: runInWorkerReplRealm('({intrinsicEval:eval,realmFunction:Function,globalObject:globalThis,errors:{ReferenceError,TypeError,SyntaxError},reflect:Reflect,object:Object})'),
|
|
151
160
|
readAmbient: name => runInWorkerReplRealm(name),
|
|
@@ -169,7 +178,7 @@ const statefulRoots = createStatefulRootRuntime({
|
|
|
169
178
|
const nativeRootDynamic = createNativeRootDynamic({
|
|
170
179
|
logicalReference: (name, writable) => statefulRoots.legacyReference(name, writable),
|
|
171
180
|
intrinsics: runInWorkerReplRealm('({intrinsicEval:eval,realmFunction:Function,globalObject:globalThis,errors:{ReferenceError,TypeError,SyntaxError},reflect:Reflect,object:Object})'),
|
|
172
|
-
importModule: (
|
|
181
|
+
importModule: moduleImportFor(),
|
|
173
182
|
read: name => runInWorkerReplRealm(name),
|
|
174
183
|
typeOf: name => runInWorkerReplRealm(`typeof ${name}`),
|
|
175
184
|
write(name, value, strict) {
|
|
@@ -181,11 +190,11 @@ const nativeRootDynamic = createNativeRootDynamic({
|
|
|
181
190
|
let valueObserver
|
|
182
191
|
const REPL_IMPORT_CANARY = 'data:text/javascript,export default 1'
|
|
183
192
|
let replParent
|
|
184
|
-
const
|
|
185
|
-
const staticAdapterParents = new
|
|
193
|
+
const cellModuleParent = () => executionCwd === undefined ? replParent : pathToFileURL(resolveModuleFilename(executionCwd, 'repl')).href
|
|
194
|
+
const staticAdapterParents = new Map()
|
|
186
195
|
const userBindingModuleParents = new Map()
|
|
187
196
|
const userModuleCompilation = createUserModuleCompilationHooks({
|
|
188
|
-
transformForParent: parent => (parent === replParent || parent ===
|
|
197
|
+
transformForParent: parent => (parent === replParent || parent === cellModuleParent()) && activeExecution !== undefined
|
|
189
198
|
? activeExecution.moduleTransform : undefined,
|
|
190
199
|
})
|
|
191
200
|
registerHooks({
|
|
@@ -193,10 +202,11 @@ registerHooks({
|
|
|
193
202
|
if (specifier === REPL_IMPORT_CANARY && replParent === undefined) replParent = context.parentURL
|
|
194
203
|
return userModuleCompilation.resolve(specifier, context, (source, resolvedContext) => {
|
|
195
204
|
const userBindingParent = mapGet(userBindingModuleParents, resolvedContext.parentURL)
|
|
205
|
+
const adapterParent = mapGet(staticAdapterParents, resolvedContext.parentURL)
|
|
196
206
|
return nextResolve(source, userBindingParent !== undefined
|
|
197
207
|
? { ...resolvedContext, parentURL: userBindingParent }
|
|
198
|
-
:
|
|
199
|
-
? { ...resolvedContext, parentURL:
|
|
208
|
+
: adapterParent !== undefined
|
|
209
|
+
? { ...resolvedContext, parentURL: adapterParent }
|
|
200
210
|
: resolvedContext)
|
|
201
211
|
})
|
|
202
212
|
},
|
|
@@ -356,23 +366,53 @@ installWorkerCwdVirtualization(sessionCwd, originalRequire, reason => {
|
|
|
356
366
|
return
|
|
357
367
|
}
|
|
358
368
|
markVolatile(reason)
|
|
359
|
-
})
|
|
369
|
+
}, () => cellOperationCwd)
|
|
360
370
|
synchronizeBuiltinEsmExports()
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return managedRequire(activeExecution?.languageSemantics === 'legacy-v1' ? replParent : sessionReplParent ?? replParent,
|
|
367
|
-
activeExecution?.languageSemantics === 'legacy-v1' ? originalRequire : providedRequire)
|
|
371
|
+
function recordCellModuleResolution(transform) {
|
|
372
|
+
// An escaped loader retains its source generation. Native resolution still
|
|
373
|
+
// selects the URL; the module compiler owns adaptation before its first load.
|
|
374
|
+
return (parent, source, attributes, resolution) => userModuleCompilation.recordResolution(
|
|
375
|
+
parent, source, attributes, resolution, transform)
|
|
368
376
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
377
|
+
|
|
378
|
+
function moduleImportFor(parent, recordResolution) {
|
|
379
|
+
return (source, options) => managedModuleImport(parent ?? replParent, source, options, replModuleRealm, recordResolution)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function cellImportModuleFor(parent, transform) {
|
|
383
|
+
return moduleImportFor(parent, recordCellModuleResolution(transform))
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function providedRequireFor(parent, legacy, transform, explicitCwd) {
|
|
387
|
+
const recordResolution = recordCellModuleResolution(transform)
|
|
388
|
+
const nativeRequire = legacy || !explicitCwd ? originalRequire : nativeCreateRequire(parent)
|
|
389
|
+
const select = args => {
|
|
390
|
+
const specifier = args[0]
|
|
391
|
+
if (setHas(FORBIDDEN_IMPORTS, specifier)) throw new Error(`module ${specifier} is forbidden because it exposes kernel control`)
|
|
392
|
+
if (!setHas(DURABLE_IMPORTS, specifier)) markVolatile(`require(${jsonStringify(specifier)})`)
|
|
393
|
+
return managedRequire(legacy ? replParent : parent ?? replParent, legacy ? originalRequire : nativeRequire, recordResolution)
|
|
394
|
+
}
|
|
395
|
+
return new Proxy(nativeRequire, {
|
|
396
|
+
apply: (_, receiver, args) => privateReflect.apply(select(args), receiver, args),
|
|
397
|
+
construct: (_, args, newTarget) => privateReflect.construct(select(args), args, newTarget),
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
let providedRequireParent = cellModuleParent()
|
|
401
|
+
let providedRequireLegacy = false
|
|
402
|
+
let providedRequireTransform
|
|
403
|
+
let providedRequireExplicitCwd
|
|
404
|
+
function installProvidedRequire(parent, legacy = false, transform, explicitCwd = executionCwd !== undefined) {
|
|
405
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'cell module metadata', context, 'require', {
|
|
406
|
+
configurable: true,
|
|
407
|
+
value: providedRequireFor(parent, legacy, transform, explicitCwd),
|
|
408
|
+
})
|
|
409
|
+
providedRequireParent = parent
|
|
410
|
+
providedRequireLegacy = legacy
|
|
411
|
+
providedRequireTransform = transform
|
|
412
|
+
providedRequireExplicitCwd = explicitCwd
|
|
413
|
+
}
|
|
414
|
+
installProvidedRequire(providedRequireParent)
|
|
415
|
+
|
|
376
416
|
function evaluate(program, completionSignal, asyncCompletion = false) {
|
|
377
417
|
return new ControlPromise((resolve, reject) => {
|
|
378
418
|
let settled = false
|
|
@@ -483,14 +523,16 @@ function staticAdapterSource(load) {
|
|
|
483
523
|
}
|
|
484
524
|
|
|
485
525
|
async function loadStaticModule(load) {
|
|
526
|
+
const parent = cellModuleParent()
|
|
527
|
+
const importModule = cellImportModuleFor(parent, activeExecution?.moduleTransform)
|
|
486
528
|
if (load.operation === 'native-dynamic') return { namespace: nativeRootDynamic.environment(load.callableSources,
|
|
487
529
|
load.awaitRoot === true, filterArray(load.logicalRoots, name => statefulRoots.has(name)
|
|
488
|
-
&& rootBindingStorage(name, moduleRuntimeIntrinsics.includes(load.nativeLexicals, name)) !== 'lexical'), load.writableRoots) }
|
|
530
|
+
&& rootBindingStorage(name, moduleRuntimeIntrinsics.includes(load.nativeLexicals, name)) !== 'lexical'), load.writableRoots, importModule) }
|
|
489
531
|
if (load.operation === 'import') {
|
|
490
|
-
return { namespace:
|
|
532
|
+
return { namespace: importModule }
|
|
491
533
|
}
|
|
492
534
|
const adapter = `data:text/javascript,${encodeURIComponent(staticAdapterSource(load))}#${++nextStaticAdapterId}`
|
|
493
|
-
|
|
535
|
+
mapSet(staticAdapterParents, adapter, cellModuleParent())
|
|
494
536
|
userModuleCompilation.mark(adapter, { compiled: true,
|
|
495
537
|
transform: activeExecution?.moduleTransform })
|
|
496
538
|
try {
|
|
@@ -499,7 +541,7 @@ async function loadStaticModule(load) {
|
|
|
499
541
|
const namespace = readModuleImport(adapter, load.source, null, () => completion.value.namespace, load.options?.with)
|
|
500
542
|
return { namespace }
|
|
501
543
|
} finally {
|
|
502
|
-
|
|
544
|
+
mapDelete(staticAdapterParents, adapter)
|
|
503
545
|
}
|
|
504
546
|
}
|
|
505
547
|
|
|
@@ -510,6 +552,18 @@ function hasExecutionLease(runId) {
|
|
|
510
552
|
// BoundError is the error class installed by this cell's wrapper, not a name
|
|
511
553
|
// looked up later: the cell can replace or delete the context binding while a
|
|
512
554
|
// call is pending, and the call must keep its submitted error identity.
|
|
555
|
+
// A confirmed Session directory change rebases the rest of the cell: Host tools
|
|
556
|
+
// resolve against the session service at call time, and this worker's relative
|
|
557
|
+
// operations now resolve against the same directory. The cell's module source
|
|
558
|
+
// base (imports, import.meta, provided require) stays fixed at its start.
|
|
559
|
+
function adoptConfirmedCellDirectory(call, value) {
|
|
560
|
+
if (call.global !== 'tools' || call.member !== 'working_directory') return
|
|
561
|
+
if (activeRun !== call.runId) return
|
|
562
|
+
const cwd = value?.cwd
|
|
563
|
+
if (typeof cwd !== 'string' || cwd.length === 0 || !isAbsoluteDirectory(cwd)) return
|
|
564
|
+
cellOperationCwd = cwd
|
|
565
|
+
}
|
|
566
|
+
|
|
513
567
|
function callHost(runId, global, member, args, BoundError) {
|
|
514
568
|
if (!hasExecutionLease(runId)) {
|
|
515
569
|
return promiseReject(programBindingError('lease', 'PTC execution lease expired'))
|
|
@@ -528,11 +582,11 @@ function callHost(runId, global, member, args, BoundError) {
|
|
|
528
582
|
const settled = new ControlPromise(resolve => {
|
|
529
583
|
moduleRuntimeIntrinsics.Reflect.apply(moduleRuntimeIntrinsics.promiseThen, result, [resolve, resolve])
|
|
530
584
|
})
|
|
531
|
-
mapSet(pending, id, { ...settle, runId, valueLimits, settled, member, BoundError })
|
|
585
|
+
mapSet(pending, id, { ...settle, runId, valueLimits, settled, global, member, BoundError })
|
|
532
586
|
try {
|
|
533
587
|
channel.postMessage({
|
|
534
588
|
type: 'call', runId, id, global, member,
|
|
535
|
-
args:
|
|
589
|
+
args: encodeValueAtBoundary(args, valueLimits, 'parameter'),
|
|
536
590
|
})
|
|
537
591
|
} catch (error) {
|
|
538
592
|
mapDelete(pending, id)
|
|
@@ -1213,7 +1267,7 @@ async function closeExecution(execution) {
|
|
|
1213
1267
|
}
|
|
1214
1268
|
|
|
1215
1269
|
function failureOutcome(error, phase, program) {
|
|
1216
|
-
if (phase === 'encode') return { invalidOutput: messageOf(error) }
|
|
1270
|
+
if (phase === 'encode') return { invalidOutput: messageOf(error), valueFailure: valueEncodingFailure(error) }
|
|
1217
1271
|
const failure = error instanceof StaticImportFailure ? error.error : error
|
|
1218
1272
|
// The V8 frame is reported in the executed text; validate it there so an
|
|
1219
1273
|
// out-of-range frame is an explicit absence instead of a mapped position.
|
|
@@ -1225,6 +1279,7 @@ function failureOutcome(error, phase, program) {
|
|
|
1225
1279
|
return {
|
|
1226
1280
|
error: detail.message,
|
|
1227
1281
|
errorName: detail.name,
|
|
1282
|
+
valueFailure: valueEncodingFailure(failure),
|
|
1228
1283
|
...(detail.toolName === undefined ? {} : { toolName: detail.toolName }),
|
|
1229
1284
|
...(error instanceof StaticImportFailure ? { moduleLoadFailed: true } : {}),
|
|
1230
1285
|
...(position === undefined ? {} : { position }),
|
|
@@ -1306,6 +1361,18 @@ async function sendCompletion(message, execution, userBindings, committedRedecla
|
|
|
1306
1361
|
|
|
1307
1362
|
async function runCell(message) {
|
|
1308
1363
|
if (activeExecution !== undefined) throw new Error('kernel received overlapping cells')
|
|
1364
|
+
if (message.cwd !== undefined && (typeof message.cwd !== 'string' || !isAbsoluteDirectory(message.cwd))) {
|
|
1365
|
+
throw new Error('ptc-plus cell cwd must be absolute')
|
|
1366
|
+
}
|
|
1367
|
+
executionCwd = message.cwd ?? sessionCwd
|
|
1368
|
+
cellOperationCwd = executionCwd
|
|
1369
|
+
const parent = cellModuleParent()
|
|
1370
|
+
const legacyRequire = message.languageSemantics === 'legacy-v1'
|
|
1371
|
+
const explicitCwd = executionCwd !== undefined
|
|
1372
|
+
if (parent !== providedRequireParent || legacyRequire !== providedRequireLegacy || message.moduleTransform !== providedRequireTransform
|
|
1373
|
+
|| explicitCwd !== providedRequireExplicitCwd) {
|
|
1374
|
+
installProvidedRequire(parent, legacyRequire, message.moduleTransform, explicitCwd)
|
|
1375
|
+
}
|
|
1309
1376
|
await writeOutputFence(nativeStdoutWrite, outputFenceMarker(message.outputFence, 'stdout', 'start'))
|
|
1310
1377
|
await writeOutputFence(nativeStderrWrite, outputFenceMarker(message.outputFence, 'stderr', 'start'))
|
|
1311
1378
|
await new ControlPromise(resolve => {
|
|
@@ -1368,6 +1435,7 @@ async function runCell(message) {
|
|
|
1368
1435
|
context, message.rootRuntimeName, {
|
|
1369
1436
|
configurable: true,
|
|
1370
1437
|
value: statefulRoots.begin({ ...message.rootBindings,
|
|
1438
|
+
importModule: cellImportModuleFor(parent, message.moduleTransform),
|
|
1371
1439
|
legacyLexicals,
|
|
1372
1440
|
legacyWritable,
|
|
1373
1441
|
legacyObjects: filterArray(legacyWritable, name => mapGet(legacyStorage, name) !== 'lexical'),
|
|
@@ -1486,7 +1554,11 @@ channel.on('message', (message) => {
|
|
|
1486
1554
|
if (call === undefined || call.runId !== message.runId) return
|
|
1487
1555
|
mapDelete(pending, message.id)
|
|
1488
1556
|
try {
|
|
1489
|
-
if (message.ok)
|
|
1557
|
+
if (message.ok) {
|
|
1558
|
+
const value = decodeValue(message.value, call.valueLimits)
|
|
1559
|
+
adoptConfirmedCellDirectory(call, value)
|
|
1560
|
+
call.resolve(value)
|
|
1561
|
+
}
|
|
1490
1562
|
else if (call.BoundError === undefined) {
|
|
1491
1563
|
const error = new Error(message.error)
|
|
1492
1564
|
if (message.cause !== undefined) error.ptcCause = message.cause
|
|
@@ -6,26 +6,25 @@ const { Map, Set, mapGet, mapSet, mapHas, mapDelete, setHas, setAdd, appendArray
|
|
|
6
6
|
/** Native storage and catalog-proved logical roots share a captured legacy view. */
|
|
7
7
|
export function createNativeRootDynamic({ intrinsics, importModule, read, write, typeOf, remove, logicalReference }) {
|
|
8
8
|
const interfaceOwner = {}
|
|
9
|
-
const runtime = createDynamicEnvironmentRuntime({ ...intrinsics, importModule, interfaceOwner }).installIntrinsics()
|
|
10
9
|
const descriptor = name => ({ kind: 'var', get: () => read(name),
|
|
11
10
|
set: (value, strict) => write(name, value, strict), delete: () => remove(name), typeof: () => typeOf(name) })
|
|
12
|
-
const environment = runtime.environment({
|
|
13
|
-
ambient(name, strict) {
|
|
14
|
-
return runtime.reference(name, descriptor(name), undefined, strict)
|
|
15
|
-
},
|
|
16
|
-
declareVars: runtime.declareGlobals,
|
|
17
|
-
createVar: descriptor,
|
|
18
|
-
})
|
|
19
|
-
runtime.setRootEnvironment(() => environment)
|
|
20
11
|
return {
|
|
21
|
-
environment(callableSources, awaitRoot = false, logicalRoots = [], writableRoots = []) {
|
|
12
|
+
environment(callableSources, awaitRoot = false, logicalRoots = [], writableRoots = [], cellImportModule = importModule) {
|
|
22
13
|
if (logicalRoots.length === 0) {
|
|
23
|
-
|
|
24
|
-
|
|
14
|
+
const cellRuntime = createDynamicEnvironmentRuntime({ ...intrinsics,
|
|
15
|
+
importModule: cellImportModule, interfaceOwner }).installIntrinsics()
|
|
16
|
+
const captured = cellRuntime.environment({
|
|
17
|
+
ambient: (name, strict) => cellRuntime.reference(name, descriptor(name), undefined, strict),
|
|
18
|
+
declareVars: cellRuntime.declareGlobals,
|
|
19
|
+
createVar: descriptor,
|
|
20
|
+
})
|
|
21
|
+
cellRuntime.setRootEnvironment(() => captured)
|
|
22
|
+
cellRuntime.registerSources(callableSources)
|
|
23
|
+
return awaitRoot ? captured.awaitActivation() : captured
|
|
25
24
|
}
|
|
26
25
|
// Each captured legacy environment keeps its own root view. Installing a
|
|
27
26
|
// later bridge must not change escaped eval/Function calls from old cells.
|
|
28
|
-
const bridgedRuntime = createDynamicEnvironmentRuntime({ ...intrinsics, importModule, interfaceOwner }).installIntrinsics()
|
|
27
|
+
const bridgedRuntime = createDynamicEnvironmentRuntime({ ...intrinsics, importModule: cellImportModule, interfaceOwner }).installIntrinsics()
|
|
29
28
|
const writable = new Set()
|
|
30
29
|
for (let index = 0; index < writableRoots.length; index++) setAdd(writable, writableRoots[index])
|
|
31
30
|
const bindings = new Map(), varFrame = new Map(), roots = new Map()
|