dsh-ptc-plus 0.3.2 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +63 -80
- package/README.md +63 -80
- package/assets/ptc-plus-bindings-en.png +0 -0
- package/assets/ptc-plus-bindings-zh.png +0 -0
- package/assets/ptc-plus-repair-en.png +0 -0
- package/assets/ptc-plus-repair-zh.png +0 -0
- package/assets/ptc-plus-repl-workspace-en.png +0 -0
- package/assets/ptc-plus-repl-workspace-zh.png +0 -0
- package/assets/ptc-plus-settings-en.png +0 -0
- package/assets/ptc-plus-settings-zh.png +0 -0
- package/client.js +30827 -742
- package/compiler-amaro.cjs +5 -0
- package/compiler-amaro.cjs.map +6 -0
- package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
- package/compiler-assets.json +3 -0
- package/compiler-core.cjs +556 -0
- package/compiler-core.cjs.map +6 -0
- package/compiler-platform.cjs +2 -0
- package/compiler-platform.cjs.map +6 -0
- package/compiler-typescript.cjs +388 -0
- package/compiler-typescript.cjs.map +6 -0
- package/docs/README.md +7 -0
- package/docs/adr/0004-declare-program-bindings-at-the-host.md +1 -1
- package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +5 -3
- package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +1 -1
- package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
- package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
- package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
- package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
- package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +3 -1
- package/docs/adr/0014-persist-cell-rewrite-policy.md +6 -2
- package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
- package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
- package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +5 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +12 -0
- package/docs/adr/0023-global-user-bindings.md +104 -0
- package/docs/adr/0024-repl-console-observation.md +33 -0
- package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
- package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
- package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
- package/docs/architecture.md +77 -21
- package/docs/binding-review-placement-design.md +200 -0
- package/docs/capability-projection.md +1 -1
- package/docs/client-ui.md +95 -19
- package/docs/durability-design.md +28 -10
- package/docs/evaluation.md +78 -6
- package/docs/installation.md +41 -6
- package/docs/publishing.md +5 -3
- package/docs/runtime-reference.md +76 -25
- package/docs/semantic-validation.md +29 -0
- package/docs/stateful-binding-updates-proposal.md +759 -0
- package/docs/user-bindings.en.md +66 -0
- package/docs/user-bindings.md +66 -0
- package/docs/value-wire.md +3 -3
- package/docs/verification-optimization-work.md +41 -0
- package/docs/verification.md +71 -0
- package/index.js +369 -92
- package/internal/ast-traversal.js +5 -2
- package/internal/async-local-storage-intrinsics.js +38 -0
- package/internal/binding-declaration-contract.js +8 -0
- package/internal/binding-descriptors.js +1 -0
- package/internal/binding-pattern-traversal.js +25 -0
- package/internal/binding-pattern.js +26 -29
- package/internal/binding-source-analysis.js +708 -0
- package/internal/binding-update-policy.js +72 -0
- package/internal/bounded-worker-round.js +56 -0
- package/internal/callable-definition-environment.js +42 -0
- package/internal/callable-source-catalog.js +129 -0
- package/internal/callable-source-encoding.js +47 -0
- package/internal/callable-source-facts.js +459 -0
- package/internal/callable-source-format.js +2 -0
- package/internal/callable-source-lowering.js +123 -0
- package/internal/cell-analysis-contract.js +25 -0
- package/internal/cell-analysis.js +253 -290
- package/internal/cell-completion.js +15 -0
- package/internal/cell-error.js +7 -0
- package/internal/cell-parser.js +66 -0
- package/internal/cell-rewriter.js +3 -677
- package/internal/commonjs-export-evidence.js +63 -0
- package/internal/commonjs-source-evidence.js +72 -0
- package/internal/compiler-amaro.cjs +2 -0
- package/internal/compiler-compression.js +12 -0
- package/internal/compiler-data.js +58 -0
- package/internal/compiler-descriptors.js +54 -0
- package/internal/compiler-dynamic-cache.js +48 -0
- package/internal/compiler-entry.js +35 -0
- package/internal/compiler-generated-names.js +17 -0
- package/internal/compiler-intrinsics.js +50 -0
- package/internal/compiler-module-links.js +37 -0
- package/internal/compiler-operations.js +61 -0
- package/internal/compiler-platform-factory.js +84 -0
- package/internal/compiler-platform.js +136 -0
- package/internal/compiler-record-roles.js +53 -0
- package/internal/compiler-region-output.js +259 -0
- package/internal/compiler-resource-lowering.js +103 -0
- package/internal/compiler-scope-facts.js +115 -0
- package/internal/compiler-service.js +109 -0
- package/internal/compiler-source-cache.js +32 -0
- package/internal/compiler-source-pieces.js +93 -0
- package/internal/compiler-source-regions.js +132 -0
- package/internal/compiler-storage-source.js +54 -0
- package/internal/compiler-text.js +15 -0
- package/internal/compiler-typescript.cjs +2 -0
- package/internal/config-spec.js +83 -27
- package/internal/cordis-tools-owner.js +130 -21
- package/internal/diagnostic.js +27 -1
- package/internal/direct-surface-owner.js +76 -23
- package/internal/dynamic-binding-evidence.js +16 -0
- package/internal/dynamic-environment-compiler.js +779 -0
- package/internal/dynamic-environment-integration.js +91 -0
- package/internal/dynamic-environment-runtime.js +383 -0
- package/internal/dynamic-native-calls.js +153 -0
- package/internal/dynamic-native-runtime.js +262 -0
- package/internal/dynamic-scope-analysis.js +90 -0
- package/internal/edit-transport-owner.js +27 -2
- package/internal/execution-seam-compat.js +193 -0
- package/internal/failure-reporting.js +136 -28
- package/internal/host-rpc.js +66 -0
- package/internal/isolated-worker.js +555 -0
- package/internal/kernel-child.js +139 -0
- package/internal/kernel-worker.js +1179 -233
- package/internal/language-semantics.js +14 -0
- package/internal/legacy-binding-catalog.js +48 -0
- package/internal/legacy-cell-analysis.js +600 -0
- package/internal/legacy-cell-parser.js +50 -0
- package/internal/legacy-cell-rewriter.js +631 -0
- package/internal/legacy-repl-convenience.js +291 -0
- package/internal/managed-module-operations.js +170 -0
- package/internal/module-compilation.js +322 -0
- package/internal/module-runtime-platform.js +19 -0
- package/internal/module-transform-contract.js +42 -0
- package/internal/native-await-declarations.js +68 -0
- package/internal/native-root-compilation.js +54 -0
- package/internal/native-root-dynamic.js +67 -0
- package/internal/record-utils.js +24 -15
- package/internal/recovery-tips.js +27 -15
- package/internal/rejected-cell-editor.js +13 -8
- package/internal/repl-convenience.js +2 -289
- package/internal/repl-memory-projection.js +81 -12
- package/internal/repl-observation-interest.js +70 -0
- package/internal/repl-rewrite-contract.js +6 -0
- package/internal/repl-scope-normalizer.js +2091 -0
- package/internal/repl-value-observer.js +117 -0
- package/internal/rpc-contract.js +52 -0
- package/internal/runtime-bridge-owner.js +172 -28
- package/internal/runtime-config.js +48 -52
- package/internal/runtime-contexts.js +82 -10
- package/internal/runtime-intrinsics.js +101 -0
- package/internal/runtime-messages.js +111 -0
- package/internal/session-cell-executor.js +402 -87
- package/internal/session-journal-recovery.js +684 -0
- package/internal/session-journal-schema.js +123 -0
- package/internal/session-journal.js +199 -654
- package/internal/session-log-view.js +42 -9
- package/internal/session-runtime.js +257 -95
- package/internal/session-state.js +247 -71
- package/internal/source-position-map.js +230 -71
- package/internal/stateful-module-compiler.js +154 -0
- package/internal/stateful-module-runtime.js +303 -0
- package/internal/stateful-root-compiler.js +600 -0
- package/internal/stateful-root-runtime.js +340 -0
- package/internal/typert-codec-compat.js +28 -0
- package/internal/typescript-transform.js +31 -0
- package/internal/user-binding-authoring.js +70 -0
- package/internal/user-binding-console-worker.js +244 -0
- package/internal/user-binding-console.js +148 -0
- package/internal/user-binding-draft-projection.js +335 -0
- package/internal/user-binding-model-context.js +31 -0
- package/internal/user-binding-runner.js +56 -0
- package/internal/user-bindings-owner.js +1065 -0
- package/internal/user-bindings-store.js +293 -0
- package/internal/user-bindings.js +361 -0
- package/internal/value-wire-schema.js +28 -0
- package/internal/value-wire.js +130 -91
- package/internal/worker-client.js +57 -67
- package/internal/worker-cwd-virtualization.js +80 -53
- package/internal/worker-environment.js +65 -0
- package/internal/worker-output-relay.js +29 -0
- package/internal/worker-repl-realm.js +134 -0
- package/internal/worker-shutdown.js +14 -0
- package/package.json +76 -7
- package/src/client-activity.js +95 -53
- package/src/client-authoring-view.js +580 -0
- package/src/client-binding-review.js +230 -0
- package/src/client-bindings-data.js +29 -0
- package/src/client-catalog.js +127 -0
- package/src/client-code-editor.js +84 -0
- package/src/client-console.js +112 -0
- package/src/client-copy.js +370 -0
- package/src/client-feature-gates.js +62 -0
- package/src/client-host-compat.js +150 -0
- package/src/client-indicator-view.js +267 -0
- package/src/client-repl-view.js +292 -0
- package/src/client-rpc.js +20 -0
- package/src/client-settings-view.js +221 -0
- package/src/client-styles.js +106 -0
- package/src/client-tool-view.js +108 -0
- package/src/client-transport.js +159 -0
- package/src/client-workbench.js +521 -0
- package/src/client.js +233 -615
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { createPrivateAsyncLocalStorage } from './async-local-storage-intrinsics.js'
|
|
3
|
+
import { create as createDomain } from 'node:domain'
|
|
4
|
+
import { createRequire, registerHooks } from 'node:module'
|
|
5
|
+
import { resolve } from 'node:path'
|
|
6
|
+
import repl from 'node:repl'
|
|
7
|
+
import { PassThrough } from 'node:stream'
|
|
8
|
+
import { inspect } from 'node:util'
|
|
9
|
+
import { pathToFileURL } from 'node:url'
|
|
10
|
+
import { parentPort, workerData } from 'node:worker_threads'
|
|
11
|
+
import { prepareConsoleProgram } from './compiler-service.js'
|
|
12
|
+
import { createStatefulRootRuntime } from './stateful-root-runtime.js'
|
|
13
|
+
import { managedModuleImport, managedRequire, readModuleImport } from './stateful-module-runtime.js'
|
|
14
|
+
import { compileStatefulModule, createUserModuleCompilationHooks } from './stateful-module-compiler.js'
|
|
15
|
+
import { USER_BINDING_TRANSFORM } from './module-transform-contract.js'
|
|
16
|
+
import { moduleRuntimeIntrinsics } from './compiler-intrinsics.js'
|
|
17
|
+
import { stringifyCompilerData } from './compiler-data.js'
|
|
18
|
+
import { createCellCompletionObserver } from './cell-completion.js'
|
|
19
|
+
import { WORKER_SHUTDOWN_ACKNOWLEDGEMENT, WORKER_SHUTDOWN_REQUEST } from './worker-shutdown.js'
|
|
20
|
+
import { WORKER_REPL_OPTIONS, createWorkerControlPromise, createWorkerReplErrorHandler,
|
|
21
|
+
disableWorkerReplDomain,
|
|
22
|
+
captureWorkerReplGlobals, protectWorkerReplAsyncContext, restoreWorkerReplGlobals, runInWorkerReplRealm,
|
|
23
|
+
workerReplContext } from './worker-repl-realm.js'
|
|
24
|
+
|
|
25
|
+
const { Object, Set, setHas, setAdd, setDelete, appendArray } = moduleRuntimeIntrinsics
|
|
26
|
+
const Promise = globalThis.Promise
|
|
27
|
+
const process = globalThis.process
|
|
28
|
+
const hasProperty = globalThis.Reflect.has
|
|
29
|
+
const ControlPromise = createWorkerControlPromise()
|
|
30
|
+
|
|
31
|
+
const evaluations = createPrivateAsyncLocalStorage()
|
|
32
|
+
const context = globalThis
|
|
33
|
+
const completionObserver = createCellCompletionObserver(runInWorkerReplRealm('Function'))
|
|
34
|
+
context.console = console
|
|
35
|
+
let retainedEvaluator
|
|
36
|
+
const completionKey = `__ptc_console_${randomUUID().replaceAll('-', '')}`
|
|
37
|
+
Object.defineProperty(context, completionKey, { value: value => {
|
|
38
|
+
evaluations.getStore().completed = true
|
|
39
|
+
return value
|
|
40
|
+
} })
|
|
41
|
+
const parentUrl = pathToFileURL(resolve(workerData.cwd, 'ptc-plus-console')).href
|
|
42
|
+
let moduleUrl
|
|
43
|
+
let replParent
|
|
44
|
+
let replModuleRealm
|
|
45
|
+
const adapterParents = new Set()
|
|
46
|
+
const knownBindings = new Set()
|
|
47
|
+
const roots = createStatefulRootRuntime({
|
|
48
|
+
importModule: (source, options) => managedModuleImport(replParent, source, options, replModuleRealm),
|
|
49
|
+
errors: runInWorkerReplRealm('({ ReferenceError, TypeError })'),
|
|
50
|
+
dynamicIntrinsics: runInWorkerReplRealm('({intrinsicEval:eval,realmFunction:Function,globalObject:globalThis,errors:{ReferenceError,TypeError,SyntaxError},reflect:Reflect,object:Object})'),
|
|
51
|
+
readAmbient: name => runInWorkerReplRealm(name),
|
|
52
|
+
typeofAmbient: name => runInWorkerReplRealm(`typeof ${name}`),
|
|
53
|
+
writeAmbient(name, value, strict) {
|
|
54
|
+
const argument = name === '__ptc_value' ? '__ptc_other_value' : '__ptc_value'
|
|
55
|
+
runInWorkerReplRealm(`${strict ? '"use strict";' : ''}(${argument}) => (${name} = ${argument})`)(value)
|
|
56
|
+
},
|
|
57
|
+
deleteAmbient: name => runInWorkerReplRealm(`delete ${name}`),
|
|
58
|
+
hasAmbient: name => hasProperty(context, name),
|
|
59
|
+
publish: name => setAdd(knownBindings, name),
|
|
60
|
+
})
|
|
61
|
+
class ConsoleReturn {
|
|
62
|
+
constructor(value) { this.value = value }
|
|
63
|
+
}
|
|
64
|
+
const moduleCompilation = createUserModuleCompilationHooks({
|
|
65
|
+
transformForParent: parent => parent === replParent || parent === parentUrl ? USER_BINDING_TRANSFORM : undefined,
|
|
66
|
+
})
|
|
67
|
+
const providedRequire = managedRequire(parentUrl, createRequire(parentUrl))
|
|
68
|
+
Object.defineProperty(context, 'require', { configurable: true, value: providedRequire })
|
|
69
|
+
const importCanary = 'data:text/javascript,export default 1'
|
|
70
|
+
registerHooks({ resolve(specifier, context, nextResolve) {
|
|
71
|
+
if (specifier === importCanary) replParent = context.parentURL
|
|
72
|
+
return moduleCompilation.resolve(specifier, context, (selected, original) =>
|
|
73
|
+
nextResolve(selected, original.parentURL === moduleUrl || original.parentURL === replParent || setHas(adapterParents, original.parentURL)
|
|
74
|
+
? { ...original, parentURL: parentUrl } : original))
|
|
75
|
+
}, load: moduleCompilation.load })
|
|
76
|
+
const describe = value => inspect(value, {
|
|
77
|
+
colors: false, customInspect: false, getters: false, depth: 4,
|
|
78
|
+
maxArrayLength: 40, maxStringLength: Math.min(workerData.maxOutputBytes, 8192), breakLength: 100,
|
|
79
|
+
})
|
|
80
|
+
process.on('uncaughtException', error => {
|
|
81
|
+
const state = evaluations.getStore()
|
|
82
|
+
if (state?.finish(true, error) === true || state?.settled === true) return
|
|
83
|
+
parentPort.postMessage({ fatal: true, error: describe(error) })
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
function evaluate(source) {
|
|
87
|
+
const prepared = prepareConsoleProgram(source, { languageSemantics: 'stateful-v1', knownBindings })
|
|
88
|
+
const helpers = [prepared.rootRuntimeName, prepared.returnSignal]
|
|
89
|
+
Object.defineProperty(context, prepared.rootRuntimeName, {
|
|
90
|
+
configurable: true, value: roots.begin({ ...prepared.rootBindings, committed: () => {} }),
|
|
91
|
+
})
|
|
92
|
+
Object.defineProperty(context, prepared.returnSignal, { configurable: true, value: ConsoleReturn })
|
|
93
|
+
if (prepared.moduleLoads.length === 0) {
|
|
94
|
+
const operation = evaluatePrepared(prepared.code, prepared.asyncCompletion)
|
|
95
|
+
return new ControlPromise((resolve, reject) => operation.then(
|
|
96
|
+
value => { removeHelpers(helpers); resolve(value) },
|
|
97
|
+
error => { removeHelpers(helpers); reject(error) },
|
|
98
|
+
))
|
|
99
|
+
}
|
|
100
|
+
return evaluateWithModules(prepared, helpers)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function removeHelpers(helpers) {
|
|
104
|
+
for (let index = 0; index < helpers.length; index++) delete context[helpers[index]]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function evaluateWithModules(prepared, helpers) {
|
|
108
|
+
try {
|
|
109
|
+
for (let index = 0; index < prepared.moduleLoads.length; index++) {
|
|
110
|
+
const load = prepared.moduleLoads[index]
|
|
111
|
+
let attributes = ''
|
|
112
|
+
const entries = Object.entries(load.options?.with ?? {})
|
|
113
|
+
for (let entry = 0; entry < entries.length; entry++) {
|
|
114
|
+
attributes += `${entry === 0 ? ' with {' : ','}${stringifyCompilerData(entries[entry][0])}:${stringifyCompilerData(entries[entry][1])}`
|
|
115
|
+
}
|
|
116
|
+
if (entries.length > 0) attributes += '}'
|
|
117
|
+
const source = stringifyCompilerData(load.source)
|
|
118
|
+
let required
|
|
119
|
+
if (load.requiredExports !== undefined) {
|
|
120
|
+
required = ''
|
|
121
|
+
for (let entry = 0; entry < load.requiredExports.length; entry++) {
|
|
122
|
+
required += `${entry === 0 ? '' : ','}${stringifyCompilerData(load.requiredExports[entry])} as __required_${entry}`
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const adapter = `data:text/javascript,${encodeURIComponent(`import * as namespace from ${source}${attributes};\n${required === undefined ? '' : `export { ${required} } from ${source}${attributes};`}\nexport { namespace };`)}#console-${randomUUID()}`
|
|
126
|
+
setAdd(adapterParents, adapter)
|
|
127
|
+
moduleCompilation.mark(adapter, { compiled: true })
|
|
128
|
+
let namespace
|
|
129
|
+
try {
|
|
130
|
+
const native = await import(adapter)
|
|
131
|
+
namespace = readModuleImport(adapter, load.source, null, () => native.namespace, load.options?.with)
|
|
132
|
+
} finally { setDelete(adapterParents, adapter) }
|
|
133
|
+
if (load.global !== undefined) {
|
|
134
|
+
appendArray(helpers, load.global)
|
|
135
|
+
Object.defineProperty(context, load.global, { configurable: true, value: namespace })
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return await evaluatePrepared(prepared.code, prepared.asyncCompletion)
|
|
139
|
+
} finally {
|
|
140
|
+
removeHelpers(helpers)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function evaluatePrepared(javascript, asyncCompletion = false) {
|
|
145
|
+
releaseRetainedEvaluator()
|
|
146
|
+
const input = new PassThrough()
|
|
147
|
+
const output = new PassThrough()
|
|
148
|
+
output.resume()
|
|
149
|
+
const workerGlobalBaseline = captureWorkerReplGlobals(context)
|
|
150
|
+
const server = repl.start({ input, output, terminal: false, prompt: '', ...WORKER_REPL_OPTIONS,
|
|
151
|
+
handleError: createWorkerReplErrorHandler(evaluations, (state, error) => state.finish(true, error)) })
|
|
152
|
+
protectWorkerReplAsyncContext(server)
|
|
153
|
+
workerReplContext(server)
|
|
154
|
+
restoreWorkerReplGlobals(workerGlobalBaseline, context)
|
|
155
|
+
Object.defineProperty(context, 'require', { configurable: true, value: providedRequire })
|
|
156
|
+
const errorDomain = disableWorkerReplDomain(server.eval.domain ?? createDomain())
|
|
157
|
+
const evaluator = { server, input, output, errorDomain }
|
|
158
|
+
retainedEvaluator = evaluator
|
|
159
|
+
return new ControlPromise((resolve, reject) => {
|
|
160
|
+
const complete = `this[${stringifyCompilerData(completionKey)}]`
|
|
161
|
+
// Preserve expression results while distinguishing rejection with null/undefined.
|
|
162
|
+
const code = `${javascript}\n;{let completed = ${complete}();}\n;`
|
|
163
|
+
let settled = false
|
|
164
|
+
const state = { completed: false, finish(failed, value) {
|
|
165
|
+
if (settled) return false
|
|
166
|
+
settled = true
|
|
167
|
+
state.settled = true
|
|
168
|
+
if (failed && value instanceof ConsoleReturn) completionObserver.settle(value.value, resolve, reject)
|
|
169
|
+
else if (failed) reject(value)
|
|
170
|
+
else completionObserver.settle(value, resolve, reject)
|
|
171
|
+
return true
|
|
172
|
+
} }
|
|
173
|
+
evaluator.state = state
|
|
174
|
+
errorDomain.on('error', error => state.finish(true, error))
|
|
175
|
+
evaluations.run(state, () => errorDomain.run(() => {
|
|
176
|
+
server.eval(`${code}\n`, context, 'ptc-plus-console', (error, value) => {
|
|
177
|
+
if (asyncCompletion && (error === null || error === undefined)) {
|
|
178
|
+
completionObserver.observe(value, value => state.finish(false, value), error => state.finish(true, error))
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
const failed = !state.completed || error !== null && error !== undefined
|
|
182
|
+
state.finish(failed, failed ? error : value)
|
|
183
|
+
})
|
|
184
|
+
}))
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function releaseRetainedEvaluator() {
|
|
189
|
+
const evaluator = retainedEvaluator
|
|
190
|
+
if (evaluator === undefined) return undefined
|
|
191
|
+
retainedEvaluator = undefined
|
|
192
|
+
evaluator.errorDomain.removeAllListeners('error')
|
|
193
|
+
evaluator.server.close()
|
|
194
|
+
evaluator.input.destroy()
|
|
195
|
+
evaluator.output.destroy()
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
replModuleRealm = await evaluatePrepared('({Promise,stringify:value=>`${value}`,importModule:(source,options)=>import(source,options)})')
|
|
199
|
+
const prepared = compileStatefulModule(workerData.source)
|
|
200
|
+
moduleUrl = `data:text/javascript,${encodeURIComponent(prepared.code)}#binding`
|
|
201
|
+
moduleCompilation.mark(moduleUrl, { compiled: true, moduleInterface: prepared.moduleInterface, sourceRegions: prepared.sourceRegions })
|
|
202
|
+
const namespace = await managedModuleImport(moduleUrl, moduleUrl)
|
|
203
|
+
await evaluatePrepared(`import(${stringifyCompilerData(importCanary)})`)
|
|
204
|
+
const names = Object.keys(namespace)
|
|
205
|
+
for (let index = 0; index < names.length; index++) {
|
|
206
|
+
const name = names[index]
|
|
207
|
+
setAdd(knownBindings, name)
|
|
208
|
+
Object.defineProperty(context, name, { configurable: true, enumerable: true, get: () => namespace[name] })
|
|
209
|
+
}
|
|
210
|
+
parentPort.on('message', (message) => {
|
|
211
|
+
if (message?.type === WORKER_SHUTDOWN_REQUEST) {
|
|
212
|
+
// Release the realm, REPL and domain this console owns, then report the
|
|
213
|
+
// release before the host gives up on the thread; see worker-shutdown.js.
|
|
214
|
+
releaseRetainedEvaluator()
|
|
215
|
+
parentPort.postMessage({ type: WORKER_SHUTDOWN_ACKNOWLEDGEMENT })
|
|
216
|
+
parentPort.close()
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
const { id, code } = message
|
|
220
|
+
const send = result => {
|
|
221
|
+
const flushed = new ControlPromise(resolve => process.stdout.write('', () => process.stderr.write('', resolve)))
|
|
222
|
+
moduleRuntimeIntrinsics.observeOwnedPromise(flushed, () => {
|
|
223
|
+
parentPort.postMessage(result)
|
|
224
|
+
releaseRetainedEvaluator()
|
|
225
|
+
/* c8 ignore next 3 -- flushed has no reject capability */
|
|
226
|
+
}, error => {
|
|
227
|
+
releaseRetainedEvaluator()
|
|
228
|
+
parentPort.postMessage({ id, error: describe(error) })
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
let operation
|
|
232
|
+
try {
|
|
233
|
+
operation = evaluate(code)
|
|
234
|
+
} catch (error) {
|
|
235
|
+
send({ id, error: describe(error) })
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
moduleRuntimeIntrinsics.observeOwnedPromise(operation,
|
|
239
|
+
value => send({ id, output: describe(value) }),
|
|
240
|
+
error => send({ id, error: describe(error) }))
|
|
241
|
+
})
|
|
242
|
+
} catch (error) {
|
|
243
|
+
parentPort.postMessage({ fatal: true, error: describe(error) })
|
|
244
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { processExitDescription } from './failure-reporting.js'
|
|
3
|
+
import { IsolatedOwner } from './isolated-worker.js'
|
|
4
|
+
import {
|
|
5
|
+
createBoundedWorkerRound,
|
|
6
|
+
ROUND_CANCELLED,
|
|
7
|
+
ROUND_OUTPUT_LIMIT,
|
|
8
|
+
ROUND_TIMEOUT,
|
|
9
|
+
} from './bounded-worker-round.js'
|
|
10
|
+
import { normalizeWorkerEnvironment } from './worker-client.js'
|
|
11
|
+
const CONSOLE_HELPER = new URL('./kernel-child.js', import.meta.url)
|
|
12
|
+
|
|
13
|
+
export const CONSOLE_IDLE_MS = 10 * 60 * 1000
|
|
14
|
+
const WORKER_URL = new URL('./user-binding-console-worker.js', import.meta.url)
|
|
15
|
+
const MAX_CONSOLES = 4
|
|
16
|
+
const MAX_SOURCE_BYTES = 1024 * 1024
|
|
17
|
+
const ROUND_REASONS = Object.freeze({
|
|
18
|
+
[ROUND_TIMEOUT]: 'execution timed out',
|
|
19
|
+
[ROUND_CANCELLED]: 'stopped',
|
|
20
|
+
[ROUND_OUTPUT_LIMIT]: 'console output limit exceeded',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
/** Own only user workbench workers; no Agent state, replay or persistence. */
|
|
24
|
+
export class UserBindingConsole {
|
|
25
|
+
constructor(options) {
|
|
26
|
+
this.options = options
|
|
27
|
+
this.environments = new Map()
|
|
28
|
+
// Every console transport is owned here until its real exit is observed.
|
|
29
|
+
this.owner = new IsolatedOwner()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
release(capability, reason = 'reset') {
|
|
33
|
+
const environment = this.environments.get(capability)
|
|
34
|
+
if (environment === undefined) return
|
|
35
|
+
this.environments.delete(capability)
|
|
36
|
+
clearTimeout(environment.idleTimer)
|
|
37
|
+
environment.active?.finish({ error: reason, released: true })
|
|
38
|
+
// One close per transport owns the release request, the kill deadline and the
|
|
39
|
+
// real exit; a refusal stays visible instead of becoming an unhandled rejection.
|
|
40
|
+
// The owner keeps this instance (and any refusal) until a real exit arrives.
|
|
41
|
+
void this.owner.stop(environment.hostId).catch(() => {})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Release every environment while this console's owner stays usable. */
|
|
45
|
+
async releaseAll(reason = 'released') {
|
|
46
|
+
for (const capability of [...this.environments.keys()]) this.release(capability, reason)
|
|
47
|
+
return this.owner.releaseAll()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async dispose() {
|
|
51
|
+
for (const capability of this.environments.keys()) this.release(capability, 'disposed')
|
|
52
|
+
return this.owner.dispose()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async reconfigure(options) {
|
|
56
|
+
// Reconfiguration releases the current environments; the owner stays usable so
|
|
57
|
+
// a later run can start again.
|
|
58
|
+
if (['maxWallMs', 'maxOutputBytes', 'maxOldGenerationSizeMb'].some(key => this.options[key] !== options[key])) {
|
|
59
|
+
for (const capability of [...this.environments.keys()]) this.release(capability, 'reconfigured')
|
|
60
|
+
const failures = await this.owner.releaseAll()
|
|
61
|
+
if (failures.length > 0) {
|
|
62
|
+
throw new AggregateError(failures, 'user binding console reconfiguration failed')
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
this.options = options
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async run({ environment: capability, source, code }, signal) {
|
|
69
|
+
for (const [label, value] of [['source', source], ['code', code]]) {
|
|
70
|
+
if (typeof value !== 'string' || value.trim() === '' || Buffer.byteLength(value) > MAX_SOURCE_BYTES) {
|
|
71
|
+
throw new TypeError(`console ${label} must be non-empty TypeScript within 1 MiB`)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
signal?.throwIfAborted()
|
|
75
|
+
let environment = this.environments.get(capability)
|
|
76
|
+
if (environment?.active !== undefined) throw new Error('console is already running')
|
|
77
|
+
const reset = capability !== undefined && (environment === undefined || environment.source !== source)
|
|
78
|
+
if (environment !== undefined && environment.source !== source) {
|
|
79
|
+
this.release(capability, 'source changed')
|
|
80
|
+
environment = undefined
|
|
81
|
+
}
|
|
82
|
+
if (environment === undefined) {
|
|
83
|
+
if (this.environments.size >= MAX_CONSOLES) throw new Error('too many active code consoles')
|
|
84
|
+
capability = randomUUID()
|
|
85
|
+
const started = this.owner.start({
|
|
86
|
+
helper: CONSOLE_HELPER,
|
|
87
|
+
entry: WORKER_URL.href,
|
|
88
|
+
workerData: { source, cwd: this.options.cwd, maxOutputBytes: this.options.maxOutputBytes },
|
|
89
|
+
env: normalizeWorkerEnvironment(process.env),
|
|
90
|
+
resourceLimits: { maxOldGenerationSizeMb: this.options.maxOldGenerationSizeMb },
|
|
91
|
+
protocol: 'parent-port',
|
|
92
|
+
})
|
|
93
|
+
const worker = started.transport
|
|
94
|
+
environment = { worker, hostId: started.id, source, round: undefined, active: undefined, sequence: 0 }
|
|
95
|
+
this.environments.set(capability, environment)
|
|
96
|
+
const fail = reason => this.release(capability, reason)
|
|
97
|
+
for (const [stream, channel] of [[worker.stdout, 'stdout'], [worker.stderr, 'stderr']]) {
|
|
98
|
+
stream.on('data', chunk => environment.round?.capture(channel, String(chunk)))
|
|
99
|
+
}
|
|
100
|
+
worker.on('error', error => fail(error.message))
|
|
101
|
+
worker.on('exit', (code, signal) => {
|
|
102
|
+
fail(`console worker exited (${processExitDescription(code, signal)})`)
|
|
103
|
+
})
|
|
104
|
+
worker.on('message', message => {
|
|
105
|
+
if (message === null || typeof message !== 'object') { fail('invalid console result'); return }
|
|
106
|
+
if (message.fatal === true) { fail(message.error); return }
|
|
107
|
+
if (message.id !== environment.active?.id) return
|
|
108
|
+
if (typeof message.output !== 'string' && typeof message.error !== 'string') {
|
|
109
|
+
fail('invalid console result'); return
|
|
110
|
+
}
|
|
111
|
+
const result = typeof message.error === 'string' ? { error: message.error } : { output: message.output }
|
|
112
|
+
if (environment.round.exceeds(result)) fail('console output limit exceeded')
|
|
113
|
+
else environment.active.finish(result)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
clearTimeout(environment.idleTimer)
|
|
117
|
+
const id = ++environment.sequence
|
|
118
|
+
const started = Date.now()
|
|
119
|
+
return new Promise(resolve => {
|
|
120
|
+
const finish = result => {
|
|
121
|
+
environment.round = undefined
|
|
122
|
+
environment.active = undefined
|
|
123
|
+
const live = this.environments.get(capability) === environment
|
|
124
|
+
const expiresAt = live ? Date.now() + CONSOLE_IDLE_MS : null
|
|
125
|
+
if (live) {
|
|
126
|
+
environment.idleTimer = setTimeout(() => this.release(capability, 'idle'), CONSOLE_IDLE_MS)
|
|
127
|
+
environment.idleTimer.unref()
|
|
128
|
+
}
|
|
129
|
+
resolve({ ...result, id: undefined, environment: live ? capability : null,
|
|
130
|
+
reset, expiresAt, logs: round.logs, durationMs: Date.now() - started })
|
|
131
|
+
}
|
|
132
|
+
const round = createBoundedWorkerRound({
|
|
133
|
+
maxOutputBytes: this.options.maxOutputBytes,
|
|
134
|
+
maxWallMs: this.options.maxWallMs,
|
|
135
|
+
signal,
|
|
136
|
+
settle: ({ ok, result, reason }) => {
|
|
137
|
+
if (ok) { finish(result); return }
|
|
138
|
+
this.release(capability, ROUND_REASONS[reason])
|
|
139
|
+
finish({ error: ROUND_REASONS[reason], released: true })
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
environment.round = round
|
|
143
|
+
environment.active = { id, finish: result => round.succeed(result) }
|
|
144
|
+
try { environment.worker.postMessage({ id, code }) }
|
|
145
|
+
catch (error) { this.release(capability, error.message) }
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
}
|