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.
Files changed (206) hide show
  1. package/README.en.md +63 -80
  2. package/README.md +63 -80
  3. package/assets/ptc-plus-bindings-en.png +0 -0
  4. package/assets/ptc-plus-bindings-zh.png +0 -0
  5. package/assets/ptc-plus-repair-en.png +0 -0
  6. package/assets/ptc-plus-repair-zh.png +0 -0
  7. package/assets/ptc-plus-repl-workspace-en.png +0 -0
  8. package/assets/ptc-plus-repl-workspace-zh.png +0 -0
  9. package/assets/ptc-plus-settings-en.png +0 -0
  10. package/assets/ptc-plus-settings-zh.png +0 -0
  11. package/client.js +30827 -742
  12. package/compiler-amaro.cjs +5 -0
  13. package/compiler-amaro.cjs.map +6 -0
  14. package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
  15. package/compiler-assets.json +3 -0
  16. package/compiler-core.cjs +556 -0
  17. package/compiler-core.cjs.map +6 -0
  18. package/compiler-platform.cjs +2 -0
  19. package/compiler-platform.cjs.map +6 -0
  20. package/compiler-typescript.cjs +388 -0
  21. package/compiler-typescript.cjs.map +6 -0
  22. package/docs/README.md +7 -0
  23. package/docs/adr/0004-declare-program-bindings-at-the-host.md +1 -1
  24. package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +5 -3
  25. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +1 -1
  26. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
  27. package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
  28. package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
  29. package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
  30. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +3 -1
  31. package/docs/adr/0014-persist-cell-rewrite-policy.md +6 -2
  32. package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
  33. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  34. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
  35. package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
  36. package/docs/adr/0019-plugin-settings-and-kill-switch.md +5 -3
  37. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  38. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +12 -0
  39. package/docs/adr/0023-global-user-bindings.md +104 -0
  40. package/docs/adr/0024-repl-console-observation.md +33 -0
  41. package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
  42. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
  43. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
  44. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
  45. package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
  46. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
  47. package/docs/architecture.md +77 -21
  48. package/docs/binding-review-placement-design.md +200 -0
  49. package/docs/capability-projection.md +1 -1
  50. package/docs/client-ui.md +95 -19
  51. package/docs/durability-design.md +28 -10
  52. package/docs/evaluation.md +78 -6
  53. package/docs/installation.md +41 -6
  54. package/docs/publishing.md +5 -3
  55. package/docs/runtime-reference.md +76 -25
  56. package/docs/semantic-validation.md +29 -0
  57. package/docs/stateful-binding-updates-proposal.md +759 -0
  58. package/docs/user-bindings.en.md +66 -0
  59. package/docs/user-bindings.md +66 -0
  60. package/docs/value-wire.md +3 -3
  61. package/docs/verification-optimization-work.md +41 -0
  62. package/docs/verification.md +71 -0
  63. package/index.js +369 -92
  64. package/internal/ast-traversal.js +5 -2
  65. package/internal/async-local-storage-intrinsics.js +38 -0
  66. package/internal/binding-declaration-contract.js +8 -0
  67. package/internal/binding-descriptors.js +1 -0
  68. package/internal/binding-pattern-traversal.js +25 -0
  69. package/internal/binding-pattern.js +26 -29
  70. package/internal/binding-source-analysis.js +708 -0
  71. package/internal/binding-update-policy.js +72 -0
  72. package/internal/bounded-worker-round.js +56 -0
  73. package/internal/callable-definition-environment.js +42 -0
  74. package/internal/callable-source-catalog.js +129 -0
  75. package/internal/callable-source-encoding.js +47 -0
  76. package/internal/callable-source-facts.js +459 -0
  77. package/internal/callable-source-format.js +2 -0
  78. package/internal/callable-source-lowering.js +123 -0
  79. package/internal/cell-analysis-contract.js +25 -0
  80. package/internal/cell-analysis.js +253 -290
  81. package/internal/cell-completion.js +15 -0
  82. package/internal/cell-error.js +7 -0
  83. package/internal/cell-parser.js +66 -0
  84. package/internal/cell-rewriter.js +3 -677
  85. package/internal/commonjs-export-evidence.js +63 -0
  86. package/internal/commonjs-source-evidence.js +72 -0
  87. package/internal/compiler-amaro.cjs +2 -0
  88. package/internal/compiler-compression.js +12 -0
  89. package/internal/compiler-data.js +58 -0
  90. package/internal/compiler-descriptors.js +54 -0
  91. package/internal/compiler-dynamic-cache.js +48 -0
  92. package/internal/compiler-entry.js +35 -0
  93. package/internal/compiler-generated-names.js +17 -0
  94. package/internal/compiler-intrinsics.js +50 -0
  95. package/internal/compiler-module-links.js +37 -0
  96. package/internal/compiler-operations.js +61 -0
  97. package/internal/compiler-platform-factory.js +84 -0
  98. package/internal/compiler-platform.js +136 -0
  99. package/internal/compiler-record-roles.js +53 -0
  100. package/internal/compiler-region-output.js +259 -0
  101. package/internal/compiler-resource-lowering.js +103 -0
  102. package/internal/compiler-scope-facts.js +115 -0
  103. package/internal/compiler-service.js +109 -0
  104. package/internal/compiler-source-cache.js +32 -0
  105. package/internal/compiler-source-pieces.js +93 -0
  106. package/internal/compiler-source-regions.js +132 -0
  107. package/internal/compiler-storage-source.js +54 -0
  108. package/internal/compiler-text.js +15 -0
  109. package/internal/compiler-typescript.cjs +2 -0
  110. package/internal/config-spec.js +83 -27
  111. package/internal/cordis-tools-owner.js +130 -21
  112. package/internal/diagnostic.js +27 -1
  113. package/internal/direct-surface-owner.js +76 -23
  114. package/internal/dynamic-binding-evidence.js +16 -0
  115. package/internal/dynamic-environment-compiler.js +779 -0
  116. package/internal/dynamic-environment-integration.js +91 -0
  117. package/internal/dynamic-environment-runtime.js +383 -0
  118. package/internal/dynamic-native-calls.js +153 -0
  119. package/internal/dynamic-native-runtime.js +262 -0
  120. package/internal/dynamic-scope-analysis.js +90 -0
  121. package/internal/edit-transport-owner.js +27 -2
  122. package/internal/execution-seam-compat.js +193 -0
  123. package/internal/failure-reporting.js +136 -28
  124. package/internal/host-rpc.js +66 -0
  125. package/internal/isolated-worker.js +555 -0
  126. package/internal/kernel-child.js +139 -0
  127. package/internal/kernel-worker.js +1179 -233
  128. package/internal/language-semantics.js +14 -0
  129. package/internal/legacy-binding-catalog.js +48 -0
  130. package/internal/legacy-cell-analysis.js +600 -0
  131. package/internal/legacy-cell-parser.js +50 -0
  132. package/internal/legacy-cell-rewriter.js +631 -0
  133. package/internal/legacy-repl-convenience.js +291 -0
  134. package/internal/managed-module-operations.js +170 -0
  135. package/internal/module-compilation.js +322 -0
  136. package/internal/module-runtime-platform.js +19 -0
  137. package/internal/module-transform-contract.js +42 -0
  138. package/internal/native-await-declarations.js +68 -0
  139. package/internal/native-root-compilation.js +54 -0
  140. package/internal/native-root-dynamic.js +67 -0
  141. package/internal/record-utils.js +24 -15
  142. package/internal/recovery-tips.js +27 -15
  143. package/internal/rejected-cell-editor.js +13 -8
  144. package/internal/repl-convenience.js +2 -289
  145. package/internal/repl-memory-projection.js +81 -12
  146. package/internal/repl-observation-interest.js +70 -0
  147. package/internal/repl-rewrite-contract.js +6 -0
  148. package/internal/repl-scope-normalizer.js +2091 -0
  149. package/internal/repl-value-observer.js +117 -0
  150. package/internal/rpc-contract.js +52 -0
  151. package/internal/runtime-bridge-owner.js +172 -28
  152. package/internal/runtime-config.js +48 -52
  153. package/internal/runtime-contexts.js +82 -10
  154. package/internal/runtime-intrinsics.js +101 -0
  155. package/internal/runtime-messages.js +111 -0
  156. package/internal/session-cell-executor.js +402 -87
  157. package/internal/session-journal-recovery.js +684 -0
  158. package/internal/session-journal-schema.js +123 -0
  159. package/internal/session-journal.js +199 -654
  160. package/internal/session-log-view.js +42 -9
  161. package/internal/session-runtime.js +257 -95
  162. package/internal/session-state.js +247 -71
  163. package/internal/source-position-map.js +230 -71
  164. package/internal/stateful-module-compiler.js +154 -0
  165. package/internal/stateful-module-runtime.js +303 -0
  166. package/internal/stateful-root-compiler.js +600 -0
  167. package/internal/stateful-root-runtime.js +340 -0
  168. package/internal/typert-codec-compat.js +28 -0
  169. package/internal/typescript-transform.js +31 -0
  170. package/internal/user-binding-authoring.js +70 -0
  171. package/internal/user-binding-console-worker.js +244 -0
  172. package/internal/user-binding-console.js +148 -0
  173. package/internal/user-binding-draft-projection.js +335 -0
  174. package/internal/user-binding-model-context.js +31 -0
  175. package/internal/user-binding-runner.js +56 -0
  176. package/internal/user-bindings-owner.js +1065 -0
  177. package/internal/user-bindings-store.js +293 -0
  178. package/internal/user-bindings.js +361 -0
  179. package/internal/value-wire-schema.js +28 -0
  180. package/internal/value-wire.js +130 -91
  181. package/internal/worker-client.js +57 -67
  182. package/internal/worker-cwd-virtualization.js +80 -53
  183. package/internal/worker-environment.js +65 -0
  184. package/internal/worker-output-relay.js +29 -0
  185. package/internal/worker-repl-realm.js +134 -0
  186. package/internal/worker-shutdown.js +14 -0
  187. package/package.json +76 -7
  188. package/src/client-activity.js +95 -53
  189. package/src/client-authoring-view.js +580 -0
  190. package/src/client-binding-review.js +230 -0
  191. package/src/client-bindings-data.js +29 -0
  192. package/src/client-catalog.js +127 -0
  193. package/src/client-code-editor.js +84 -0
  194. package/src/client-console.js +112 -0
  195. package/src/client-copy.js +370 -0
  196. package/src/client-feature-gates.js +62 -0
  197. package/src/client-host-compat.js +150 -0
  198. package/src/client-indicator-view.js +267 -0
  199. package/src/client-repl-view.js +292 -0
  200. package/src/client-rpc.js +20 -0
  201. package/src/client-settings-view.js +221 -0
  202. package/src/client-styles.js +106 -0
  203. package/src/client-tool-view.js +108 -0
  204. package/src/client-transport.js +159 -0
  205. package/src/client-workbench.js +521 -0
  206. 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
+ }