dsh-ptc-plus 0.4.2 → 0.4.3

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 (83) hide show
  1. package/README.en.md +166 -76
  2. package/README.md +167 -77
  3. package/assets/dsh-ptc-plus-banner-en.webp +0 -0
  4. package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
  5. package/assets/ptc-plus-icon.svg +5 -0
  6. package/client.js +593 -306
  7. package/compiler-core.cjs +162 -160
  8. package/compiler-core.cjs.map +3 -3
  9. package/docs/README.md +3 -1
  10. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +8 -7
  11. package/docs/adr/0008-define-agent-operating-contract.md +2 -2
  12. package/docs/adr/0010-session-log-derived-recovery-tips.md +7 -0
  13. package/docs/adr/0016-archive-resolved-review-findings.md +3 -3
  14. package/docs/adr/0019-plugin-settings-and-kill-switch.md +3 -3
  15. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -0
  16. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +14 -3
  17. package/docs/adr/0023-global-user-bindings.md +1 -1
  18. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +23 -7
  19. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +4 -0
  20. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +26 -6
  21. package/docs/adr/0031-close-semantic-obligations-by-owner.md +29 -0
  22. package/docs/adr/README.md +4 -4
  23. package/docs/architecture.md +6 -5
  24. package/docs/binding-review-placement-design.md +1 -1
  25. package/docs/client-ui.md +8 -4
  26. package/docs/durability-design.md +7 -7
  27. package/docs/installation.md +26 -8
  28. package/docs/runtime-reference.md +18 -11
  29. package/docs/semantic-validation.md +7 -3
  30. package/docs/user-bindings.en.md +7 -6
  31. package/docs/user-bindings.md +6 -5
  32. package/docs/value-wire.md +1 -1
  33. package/docs/verification.md +38 -1
  34. package/index.js +39 -34
  35. package/internal/async-local-storage-intrinsics.js +12 -7
  36. package/internal/callable-source-facts.js +37 -1
  37. package/internal/cell-analysis.js +64 -9
  38. package/internal/config-spec.js +1 -1
  39. package/internal/cordis-tools-owner.js +20 -10
  40. package/internal/edit-transport-owner.js +80 -20
  41. package/internal/execution-seam-compat.js +192 -30
  42. package/internal/kernel-worker.js +162 -72
  43. package/internal/message-sources.js +14 -0
  44. package/internal/module-compilation.js +2 -1
  45. package/internal/repl-memory-projection.js +10 -0
  46. package/internal/repl-scope-normalizer.js +72 -35
  47. package/internal/repl-value-observer.js +141 -45
  48. package/internal/runtime-config.js +62 -2
  49. package/internal/runtime-intrinsics.js +3 -0
  50. package/internal/runtime-messages.js +6 -4
  51. package/internal/session-cell-executor.js +24 -1
  52. package/internal/session-journal-recovery.js +267 -67
  53. package/internal/session-journal.js +466 -40
  54. package/internal/session-log-view.js +5 -4
  55. package/internal/session-runtime.js +45 -21
  56. package/internal/session-state.js +7 -29
  57. package/internal/settings-compat.js +66 -1
  58. package/internal/source-position-map.js +19 -0
  59. package/internal/user-binding-console-worker.js +23 -9
  60. package/internal/user-binding-draft-projection.js +3 -2
  61. package/internal/user-bindings-owner.js +2 -1
  62. package/internal/user-bindings.js +6 -1
  63. package/internal/validated-parse-repair.js +6 -2
  64. package/internal/value-wire.js +95 -30
  65. package/internal/worker-client.js +99 -8
  66. package/internal/worker-cwd-virtualization.js +10 -9
  67. package/internal/worker-output-capture.js +187 -0
  68. package/internal/worker-output-fence.js +26 -0
  69. package/internal/worker-realm-surfaces.js +197 -0
  70. package/internal/worker-repl-realm.js +10 -4
  71. package/package.json +36 -4
  72. package/src/client-activity.js +4 -1
  73. package/src/client-authoring-view.js +121 -62
  74. package/src/client-console.js +4 -2
  75. package/src/client-copy.js +4 -2
  76. package/src/client-feature-gates.js +7 -9
  77. package/src/client-host-compat.js +104 -27
  78. package/src/client-repl-view.js +3 -1
  79. package/src/client-styles.js +12 -1
  80. package/src/client-tool-view.js +3 -1
  81. package/src/client-transport.js +2 -2
  82. package/src/client-workbench.js +4 -3
  83. package/src/client.js +56 -17
@@ -1,4 +1,5 @@
1
1
  import { create as createDomain } from 'node:domain'
2
+ import { Console } from 'node:console'
2
3
  import { createRequire, registerHooks } from 'node:module'
3
4
  import { managedModuleImport, managedRequire, readModuleImport, statefulModuleLink } from './stateful-module-runtime.js'
4
5
  import { staticModuleLinkReference } from './compiler-module-links.js'
@@ -6,9 +7,8 @@ import { compileStatefulModule, createUserModuleCompilationHooks } from './state
6
7
  import { USER_BINDING_TRANSFORM, LEGACY_USER_BINDING_TRANSFORM } from './module-transform-contract.js'
7
8
  import { isAbsolute, resolve } from 'node:path'
8
9
  import repl from 'node:repl'
9
- import { PassThrough } from 'node:stream'
10
+ import { PassThrough, Writable } from 'node:stream'
10
11
  import { pathToFileURL } from 'node:url'
11
- import { formatWithOptions } from 'node:util'
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'
@@ -17,6 +17,13 @@ import { DURABLE_IMPORTS, FORBIDDEN_IMPORTS } from './module-policy.js'
17
17
  import { decodeValue, encodeValue } 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
+ import {
21
+ WORKER_REALM_MUTATION,
22
+ assignWorkerRealmProperty,
23
+ defineWorkerRealmProperty,
24
+ deleteWorkerRealmProperty,
25
+ installProcessControlGuards,
26
+ } from './worker-realm-surfaces.js'
20
27
  import { createReplValueObserver, supportsAwaitLexicals, previewBindingValue } from './repl-value-observer.js'
21
28
  import { createStatefulRootRuntime } from './stateful-root-runtime.js'
22
29
  import { createNativeRootDynamic } from './native-root-dynamic.js'
@@ -24,6 +31,7 @@ import { moduleRuntimeIntrinsics } from './compiler-intrinsics.js'
24
31
  import { compilerDescriptors } from './compiler-descriptors.js'
25
32
  import { createCellCompletionObserver } from './cell-completion.js'
26
33
  import { WORKER_SHUTDOWN_ACKNOWLEDGEMENT, WORKER_SHUTDOWN_REQUEST } from './worker-shutdown.js'
34
+ import { outputFenceMarker } from './worker-output-fence.js'
27
35
  import { WORKER_REPL_OPTIONS, createWorkerControlPromise, createWorkerReplErrorHandler,
28
36
  createWorkerUncaughtExceptionHandler,
29
37
  disableWorkerReplDomain,
@@ -34,7 +42,9 @@ import { installProgramAmbientResolver } from './dynamic-environment-runtime.js'
34
42
  const { Object, Map, Set, Proxy, Error,
35
43
  mapGet, mapSet, mapHas, mapDelete, mapClear, mapSize, mapForEach,
36
44
  setHas, setAdd, setDelete, setClear, setForEach,
37
- everyArray, someArray, appendArray, copyArray, join, Reflect: privateReflect, promiseResolve, promiseReject } = moduleRuntimeIntrinsics
45
+ everyArray, someArray, appendArray, copyArray, join, sliceString, endsWith,
46
+ bufferConcat, bufferIsBuffer, bufferToString,
47
+ Reflect: privateReflect, promiseResolve, promiseReject } = moduleRuntimeIntrinsics
38
48
  const PublicPromise = globalThis.Promise
39
49
  const jsonStringify = globalThis.JSON.stringify
40
50
  const bufferByteLength = globalThis.Buffer.byteLength
@@ -195,6 +205,7 @@ registerHooks({
195
205
  const logScope = createPrivateAsyncLocalStorage()
196
206
  const userBindingActivationScope = createPrivateAsyncLocalStorage()
197
207
  const pending = new Map()
208
+ const pendingOutputStarts = new Map()
198
209
  const installedGlobals = new Set()
199
210
  const installedGlobalOriginals = new Map()
200
211
  const PROCESS_CONTROLS = setFromArray(['exit', 'abort', 'kill', 'chdir'])
@@ -252,14 +263,32 @@ class CellReturn extends Error {
252
263
  evaluationScope.getStore().completed = true
253
264
  }
254
265
  }
255
- function appendLog(...values) {
256
- const current = logScope.getStore()
257
- if (current?.open !== true) return
258
- appendText(current, formatWithOptions({ colors: false, depth: 4, maxArrayLength: 100, maxStringLength: 10_000 }, ...values))
266
+ /**
267
+ * Convert one captured descriptor chunk with plugin-private operations.
268
+ *
269
+ * The private Writable decodes string writes into Buffers, and a cell may
270
+ * replace `Buffer.prototype.toString` or a typed-array join. Converting here
271
+ * with captured operations keeps the model-visible log record from running user
272
+ * code that could rewrite, forge, or fail the logs of the cell that printed
273
+ * them.
274
+ */
275
+ function outputText(chunk) {
276
+ if (typeof chunk === 'string') return chunk
277
+ // The private Writable hands over Buffers, and a direct write may pass any
278
+ // Uint8Array view. Any other chunk type fails in the captured conversion,
279
+ // exactly as the replaced stream method would reject it.
280
+ return bufferToString(bufferIsBuffer(chunk) ? chunk : bufferConcat([chunk]), 'utf8')
259
281
  }
260
282
 
261
283
  function appendText(current, text) {
262
- if (current.open !== true || current.outputLimited) return
284
+ if (current.open !== true) {
285
+ if (!current.lateOutputReported) {
286
+ current.lateOutputReported = true
287
+ channel.postMessage({ type: 'late-output', id: current.id })
288
+ }
289
+ return
290
+ }
291
+ if (current.outputLimited) return
263
292
  const bytes = bufferByteLength(jsonStringify(text), 'utf8') + (current.logs.length === 0 ? 0 : 1)
264
293
  if (current.logBytes + bytes > current.maxOutputBytes) {
265
294
  current.outputLimited = true
@@ -270,26 +299,39 @@ function appendText(current, text) {
270
299
  appendArray(current.logs, text)
271
300
  }
272
301
 
273
- const consoleView = Object.freeze({
274
- log: appendLog,
275
- info: appendLog,
276
- warn: appendLog,
277
- error: appendLog,
278
- debug: appendLog,
279
- dir: value => appendLog(value),
302
+ const consoleOutput = new Writable({
303
+ write(chunk, _encoding, callback) {
304
+ const current = logScope.getStore()
305
+ if (current !== undefined) {
306
+ const text = outputText(chunk)
307
+ appendText(current, endsWith(text, '\n') ? sliceString(text, 0, -1) : text)
308
+ }
309
+ callback()
310
+ },
311
+ })
312
+ const nativeStdoutWrite = process.stdout.write.bind(process.stdout)
313
+ const nativeStderrWrite = process.stderr.write.bind(process.stderr)
314
+ const consoleView = new Console({
315
+ stdout: consoleOutput,
316
+ stderr: consoleOutput,
317
+ colorMode: false,
318
+ inspectOptions: { depth: 4, maxArrayLength: 100, maxStringLength: 10_000 },
280
319
  })
281
- Object.defineProperty(context, 'console', { configurable: true, value: consoleView })
320
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'console',
321
+ context, 'console', { configurable: true, value: consoleView })
282
322
 
283
323
  function captureWrite(chunk, ...rest) {
284
324
  const current = logScope.getStore()
285
- if (current?.open === true) appendText(current, typeof chunk === 'string' ? chunk : String(chunk))
325
+ if (current !== undefined) appendText(current, outputText(chunk))
286
326
  const callback = typeof rest[0] === 'function' ? rest[0]
287
327
  : typeof rest[1] === 'function' ? rest[1] : undefined
288
328
  if (callback !== undefined) queueMicrotask(() => callback(null))
289
329
  return true
290
330
  }
291
- process.stdout.write = captureWrite
292
- process.stderr.write = captureWrite
331
+ assignWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'process.stdout.write/process.stderr.write',
332
+ process.stdout, 'write', captureWrite)
333
+ assignWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'process.stdout.write/process.stderr.write',
334
+ process.stderr, 'write', captureWrite)
293
335
 
294
336
  function markVolatile(reason) {
295
337
  const current = activeExecution
@@ -307,23 +349,7 @@ function completionDurability(execution) {
307
349
  }
308
350
  }
309
351
 
310
- function guardProcessControls() {
311
- const properties = setValuesArray(PROCESS_CONTROLS)
312
- for (let index = 0; index < properties.length; index += 1) {
313
- const property = properties[index]
314
- const descriptor = Object.getOwnPropertyDescriptor(process, property)
315
- Object.defineProperty(process, property, {
316
- configurable: false,
317
- enumerable: descriptor?.enumerable ?? true,
318
- writable: false,
319
- value: () => {
320
- throw new Error(`process.${property} is forbidden inside the REPL kernel`)
321
- },
322
- })
323
- }
324
- }
325
-
326
- guardProcessControls()
352
+ installProcessControlGuards(process, setValuesArray(PROCESS_CONTROLS))
327
353
  installWorkerCwdVirtualization(sessionCwd, originalRequire, reason => {
328
354
  if (suppressReplCwdObservation) {
329
355
  suppressReplCwdObservation = false
@@ -340,7 +366,7 @@ function selectProvidedRequire(args) {
340
366
  return managedRequire(activeExecution?.languageSemantics === 'legacy-v1' ? replParent : sessionReplParent ?? replParent,
341
367
  activeExecution?.languageSemantics === 'legacy-v1' ? originalRequire : providedRequire)
342
368
  }
343
- Object.defineProperty(context, 'require', {
369
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'cell module metadata', context, 'require', {
344
370
  configurable: true,
345
371
  value: new Proxy(providedRequire, {
346
372
  apply: (_, receiver, args) => privateReflect.apply(selectProvidedRequire(args), receiver, args),
@@ -393,13 +419,16 @@ async function verifyEvaluation() {
393
419
  await evaluate(CONFORMANCE_CELL)
394
420
  const marker = new Error('PTC Plus REPL settlement probe')
395
421
  Object.defineProperty(marker, 'stack', { get() { throw new Error('REPL must not format an error before settlement') } })
396
- context.__ptc_settlement_probe__ = marker
397
- context.__ptc_completion_probe__ = CellReturn
422
+ assignWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
423
+ context, '__ptc_settlement_probe__', marker)
424
+ assignWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
425
+ context, '__ptc_completion_probe__', CellReturn)
398
426
  try {
399
427
  const values = [marker, null, undefined, false, 0]
400
428
  for (let valueIndex = 0; valueIndex < values.length; valueIndex += 1) {
401
429
  const value = values[valueIndex]
402
- context.__ptc_settlement_probe__ = value
430
+ assignWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
431
+ context, '__ptc_settlement_probe__', value)
403
432
  const sources = ['throw __ptc_settlement_probe__', 'await Promise.reject(__ptc_settlement_probe__)']
404
433
  for (let sourceIndex = 0; sourceIndex < sources.length; sourceIndex += 1) {
405
434
  const source = sources[sourceIndex]
@@ -414,7 +443,8 @@ async function verifyEvaluation() {
414
443
  let syntaxRejected = false
415
444
  try { await evaluate('const =') } catch (error) { syntaxRejected = error?.name === 'SyntaxError' }
416
445
  if (!syntaxRejected) throw new Error('REPL did not preserve syntax failure')
417
- context.__ptc_settlement_probe__ = new CellReturn(promiseResolve(marker))
446
+ assignWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
447
+ context, '__ptc_settlement_probe__', new CellReturn(promiseResolve(marker)))
418
448
  const returned = await evaluate('throw __ptc_settlement_probe__')
419
449
  if (!returned.hasValue || returned.value !== marker) throw new Error('REPL did not preserve cell return')
420
450
  await evaluate(CONFORMANCE_CELL)
@@ -422,8 +452,10 @@ async function verifyEvaluation() {
422
452
  const expression = await evaluate('42', '__ptc_completion_probe__')
423
453
  if (expression.value !== 42) throw new Error('REPL did not preserve expression completion')
424
454
  } finally {
425
- delete context.__ptc_settlement_probe__
426
- delete context.__ptc_completion_probe__
455
+ deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
456
+ context, '__ptc_settlement_probe__')
457
+ deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
458
+ context, '__ptc_completion_probe__')
427
459
  }
428
460
  }
429
461
 
@@ -578,7 +610,7 @@ function namespaceGlobalGetter(name) {
578
610
  }
579
611
 
580
612
  function installNamespaceGlobal(name) {
581
- Object.defineProperty(globalThis, name, {
613
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.PROGRAM, undefined, globalThis, name, {
582
614
  configurable: true,
583
615
  get: namespaceGlobalGetter(name),
584
616
  })
@@ -616,7 +648,9 @@ function restoreDynamicNamespaceGlobals() {
616
648
  const names = mapKeysArray(originalDynamicNamespaceGlobals)
617
649
  for (let index = 0; index < names.length; index += 1) {
618
650
  const name = names[index]
619
- setHas(installedGlobals, name) ? installNamespaceGlobal(name) : privateReflect.deleteProperty(globalThis, name)
651
+ setHas(installedGlobals, name)
652
+ ? installNamespaceGlobal(name)
653
+ : deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, globalThis, name)
620
654
  }
621
655
  mapClear(originalDynamicNamespaceGlobals)
622
656
  }
@@ -634,9 +668,13 @@ function installBindings(message) {
634
668
  } else if (original.userGlobalEntryId !== undefined
635
669
  && (mapGet(userBindingNames, name) !== original.userGlobalEntryId
636
670
  || attachment !== original.attachment)) {
637
- delete context[name]
638
- } else if (original.descriptor === undefined) delete context[name]
639
- else Object.defineProperty(context, name, original.descriptor)
671
+ deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, name)
672
+ } else if (original.descriptor === undefined) {
673
+ deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, name)
674
+ } else {
675
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
676
+ context, name, original.descriptor)
677
+ }
640
678
  }
641
679
  setClear(installedGlobals)
642
680
  mapClear(installedGlobalOriginals)
@@ -692,7 +730,8 @@ function installBindings(message) {
692
730
 
693
731
  if (descriptor !== undefined) {
694
732
  mapSet(installedGlobalOriginals, descriptor.name, capturedGlobalDescriptor(descriptor.name))
695
- Object.defineProperty(context, descriptor.name, { configurable: true, value: BoundError })
733
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.PROGRAM, undefined,
734
+ context, descriptor.name, { configurable: true, value: BoundError })
696
735
  setAdd(installedGlobals, descriptor.name)
697
736
  }
698
737
  }
@@ -751,14 +790,18 @@ function rootBindingStorage(name, nativeLexical = false) {
751
790
  }
752
791
  let propertyRead = false
753
792
  try {
754
- Object.defineProperty(context, name, { configurable: true, get() { propertyRead = true } })
793
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
794
+ context, name, { configurable: true, get() { propertyRead = true } })
755
795
  runInWorkerReplRealm(name, { displayErrors: false })
756
796
  return propertyRead ? 'property' : 'lexical'
757
797
  } catch {
758
798
  return 'unknown'
759
799
  } finally {
760
- if (original === undefined) delete context[name]
761
- else Object.defineProperty(context, name, original)
800
+ if (original === undefined) {
801
+ deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, name)
802
+ } else {
803
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, name, original)
804
+ }
762
805
  }
763
806
  }
764
807
 
@@ -806,7 +849,9 @@ function removePerNameUserBindingEntry(id) {
806
849
  if (setHas(installedGlobals, name)) {
807
850
  const original = mapGet(installedGlobalOriginals, name)
808
851
  if (original.attachment === installed) original.descriptor = undefined
809
- } else if (descriptorsEqual(Object.getOwnPropertyDescriptor(context, name), installed)) delete context[name]
852
+ } else if (descriptorsEqual(Object.getOwnPropertyDescriptor(context, name), installed)) {
853
+ deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, name)
854
+ }
810
855
  // Lifecycle removal permits reattachment; explicit deletion retains absent.
811
856
  mapDelete(userBindingSources, name)
812
857
  }
@@ -892,13 +937,14 @@ async function activatePerNameUserBindings(snapshot, shadowedNames, cwd, reusePo
892
937
  const source = mapGet(userBindingSources, name)
893
938
  if (source !== undefined && source.state !== 'provider') continue
894
939
  const descriptor = Object.getOwnPropertyDescriptor(context, name)
895
- Object.defineProperty(context, name, {
940
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined, context, name, {
896
941
  configurable: true,
897
942
  enumerable: true,
898
943
  get: () => entry.scope === 'namespace' ? view : namespace[name],
899
944
  set(value) {
900
945
  const receiver = this === contextGlobal ? context : this
901
- Object.defineProperty(receiver, name, { configurable: true, enumerable: true, writable: true, value })
946
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined,
947
+ receiver, name, { configurable: true, enumerable: true, writable: true, value })
902
948
  if (receiver === context) recordUserBindingAssignment(name)
903
949
  },
904
950
  })
@@ -916,8 +962,13 @@ async function activatePerNameUserBindings(snapshot, shadowedNames, cwd, reusePo
916
962
  for (let index = installedNames.length - 1; index >= 0; index--) {
917
963
  const installed = installedNames[index]
918
964
  mapDelete(userBindingNames, installed.name)
919
- if (installed.descriptor === undefined) delete context[installed.name]
920
- else Object.defineProperty(context, installed.name, installed.descriptor)
965
+ if (installed.descriptor === undefined) {
966
+ deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
967
+ context, installed.name)
968
+ } else {
969
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
970
+ context, installed.name, installed.descriptor)
971
+ }
921
972
  }
922
973
  const previousEntries = mapEntriesArray(previousSources)
923
974
  for (let previousIndex = 0; previousIndex < previousEntries.length; previousIndex += 1) {
@@ -941,7 +992,9 @@ function removeUserBindingEntry(id, shadowedNames) {
941
992
  const name = previous.names[index]
942
993
  if (mapGet(userBindingNames, name) !== id) continue
943
994
  mapDelete(userBindingNames, name)
944
- if (!setHas(shadowedNames, name)) delete context[name]
995
+ if (!setHas(shadowedNames, name)) {
996
+ deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, name)
997
+ }
945
998
  }
946
999
  mapDelete(userBindingEntries, id)
947
1000
  }
@@ -1070,13 +1123,13 @@ async function activateUserBindings(snapshot, shadowedNames, cwd, reusePolicy, i
1070
1123
  }
1071
1124
  Object.freeze(view)
1072
1125
  const descriptor = Object.getOwnPropertyDescriptor(context, entry.name)
1073
- Object.defineProperty(context, entry.name, {
1126
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined, context, entry.name, {
1074
1127
  configurable: true,
1075
1128
  enumerable: true,
1076
1129
  get: () => view,
1077
1130
  set(value) {
1078
1131
  recordUserBindingAssignment(entry.name, true)
1079
- Object.defineProperty(context, entry.name, {
1132
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined, context, entry.name, {
1080
1133
  configurable: true,
1081
1134
  enumerable: true,
1082
1135
  writable: true,
@@ -1094,13 +1147,13 @@ async function activateUserBindings(snapshot, shadowedNames, cwd, reusePolicy, i
1094
1147
  for (let symbolIndex = 0; symbolIndex < entry.symbols.length; symbolIndex += 1) {
1095
1148
  const symbol = entry.symbols[symbolIndex]
1096
1149
  const descriptor = Object.getOwnPropertyDescriptor(context, symbol)
1097
- Object.defineProperty(context, symbol, {
1150
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined, context, symbol, {
1098
1151
  configurable: true,
1099
1152
  enumerable: true,
1100
1153
  get: () => namespace[symbol],
1101
1154
  set(value) {
1102
1155
  recordUserBindingAssignment(symbol, true)
1103
- Object.defineProperty(context, symbol, {
1156
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined, context, symbol, {
1104
1157
  configurable: true,
1105
1158
  enumerable: true,
1106
1159
  writable: true,
@@ -1130,8 +1183,13 @@ async function activateUserBindings(snapshot, shadowedNames, cwd, reusePolicy, i
1130
1183
  for (let index = installedNames.length - 1; index >= 0; index--) {
1131
1184
  const installed = installedNames[index]
1132
1185
  mapDelete(userBindingNames, installed.name)
1133
- if (installed.descriptor === undefined) delete context[installed.name]
1134
- else Object.defineProperty(context, installed.name, installed.descriptor)
1186
+ if (installed.descriptor === undefined) {
1187
+ deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
1188
+ context, installed.name)
1189
+ } else {
1190
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
1191
+ context, installed.name, installed.descriptor)
1192
+ }
1135
1193
  }
1136
1194
  if (evaluated !== undefined) mapDelete(userBindingModuleParents, evaluated.moduleUrl)
1137
1195
  removeUserBindingEntry(entry.id, shadowedNames)
@@ -1147,7 +1205,6 @@ async function activateUserBindings(snapshot, shadowedNames, cwd, reusePolicy, i
1147
1205
 
1148
1206
  async function closeExecution(execution) {
1149
1207
  activeRun = undefined
1150
- execution.open = false
1151
1208
  const settling = []
1152
1209
  mapForEach(pending, call => {
1153
1210
  if (call.runId === execution.id) appendArray(settling, call.settled)
@@ -1199,7 +1256,13 @@ function refreshLegacyPublication(name) {
1199
1256
  }
1200
1257
  }
1201
1258
 
1202
- function sendCompletion(message, execution, userBindings, committedRedeclarations, outcome) {
1259
+ function writeOutputFence(write, marker) {
1260
+ return new ControlPromise((resolve, reject) => {
1261
+ write(marker, error => error === null || error === undefined ? resolve() : reject(error))
1262
+ })
1263
+ }
1264
+
1265
+ async function sendCompletion(message, execution, userBindings, committedRedeclarations, outcome) {
1203
1266
  const publicationNames = execution.nativePublications === undefined
1204
1267
  ? [] : mapKeysArray(execution.nativePublications)
1205
1268
  for (let index = 0; index < publicationNames.length; index += 1) refreshLegacyPublication(publicationNames[index])
@@ -1208,9 +1271,18 @@ function sendCompletion(message, execution, userBindings, committedRedeclaration
1208
1271
  if (perName) reconcileUserBindingNames()
1209
1272
  else reconcileUserBindingShadows(shadowedNames)
1210
1273
  const names = message.observeNames ?? []
1274
+ await writeOutputFence(nativeStdoutWrite, outputFenceMarker(message.outputFence, 'stdout', 'end'))
1275
+ await writeOutputFence(nativeStderrWrite, outputFenceMarker(message.outputFence, 'stderr', 'end'))
1276
+ // Program-call settlement and value encoding belong to this physical run, so
1277
+ // structured output from a drained continuation keeps its cell ownership
1278
+ // until this completion is posted. Output from the same scheduling execution
1279
+ // after this point is background output and carries the attribution
1280
+ // diagnostic instead of being attached to a later cell.
1281
+ execution.open = false
1211
1282
  channel.postMessage({
1212
1283
  type: 'done',
1213
1284
  id: message.id,
1285
+ outputFence: message.outputFence,
1214
1286
  logs: execution.logs,
1215
1287
  ...completionDurability(execution),
1216
1288
  committedRedeclarations: setValuesArray(committedRedeclarations),
@@ -1234,6 +1306,12 @@ function sendCompletion(message, execution, userBindings, committedRedeclaration
1234
1306
 
1235
1307
  async function runCell(message) {
1236
1308
  if (activeExecution !== undefined) throw new Error('kernel received overlapping cells')
1309
+ await writeOutputFence(nativeStdoutWrite, outputFenceMarker(message.outputFence, 'stdout', 'start'))
1310
+ await writeOutputFence(nativeStderrWrite, outputFenceMarker(message.outputFence, 'stderr', 'start'))
1311
+ await new ControlPromise(resolve => {
1312
+ mapSet(pendingOutputStarts, message.id, { outputFence: message.outputFence, resolve })
1313
+ channel.postMessage({ type: 'output-start', id: message.id, outputFence: message.outputFence })
1314
+ })
1237
1315
  activeRun = message.id
1238
1316
  const execution = {
1239
1317
  id: message.id,
@@ -1263,12 +1341,12 @@ async function runCell(message) {
1263
1341
  try {
1264
1342
  completion = await logScope.run(execution, async () => {
1265
1343
  const namespaceError = installBindings(message)
1266
- Object.defineProperty(context, message.returnSignal, {
1344
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined, context, message.returnSignal, {
1267
1345
  configurable: true,
1268
1346
  value: CellReturn,
1269
1347
  })
1270
1348
  appendArray(cellGlobals, message.returnSignal)
1271
- Object.defineProperty(context, message.commitSignal, {
1349
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined, context, message.commitSignal, {
1272
1350
  configurable: true,
1273
1351
  value(name) {
1274
1352
  setAdd(committedRedeclarations, name)
@@ -1286,7 +1364,8 @@ async function runCell(message) {
1286
1364
  return storage === 'lexical' || setHas(legacyNative, name)
1287
1365
  && (storage === 'local' || storage === 'property') && writableRootProperty(name)
1288
1366
  })
1289
- Object.defineProperty(context, message.rootRuntimeName, {
1367
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
1368
+ context, message.rootRuntimeName, {
1290
1369
  configurable: true,
1291
1370
  value: statefulRoots.begin({ ...message.rootBindings,
1292
1371
  legacyLexicals,
@@ -1324,7 +1403,7 @@ async function runCell(message) {
1324
1403
  throw new StaticImportFailure(error, load.position)
1325
1404
  }
1326
1405
  if (load.global !== undefined) {
1327
- Object.defineProperty(context, load.global, {
1406
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined, context, load.global, {
1328
1407
  configurable: true,
1329
1408
  value: namespace,
1330
1409
  })
@@ -1364,9 +1443,12 @@ async function runCell(message) {
1364
1443
  outcome = failureOutcome(error, 'encode', message.program)
1365
1444
  }
1366
1445
  }
1367
- sendCompletion(message, execution, userBindings, committedRedeclarations, outcome)
1446
+ await sendCompletion(message, execution, userBindings, committedRedeclarations, outcome)
1368
1447
  } finally {
1369
- for (let index = 0; index < cellGlobals.length; index += 1) delete context[cellGlobals[index]]
1448
+ for (let index = 0; index < cellGlobals.length; index += 1) {
1449
+ deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
1450
+ context, cellGlobals[index])
1451
+ }
1370
1452
  activeRun = undefined
1371
1453
  activeExecution = undefined
1372
1454
  execution.exceptionOrigins.close()
@@ -1415,6 +1497,14 @@ channel.on('message', (message) => {
1415
1497
  }
1416
1498
  return
1417
1499
  }
1500
+ if (message?.type === 'output-start-ack') {
1501
+ const pendingStart = mapGet(pendingOutputStarts, message.id)
1502
+ if (pendingStart !== undefined && pendingStart.outputFence === message.outputFence) {
1503
+ mapDelete(pendingOutputStarts, message.id)
1504
+ pendingStart.resolve()
1505
+ }
1506
+ return
1507
+ }
1418
1508
  if (message?.type === 'run') void runCell(message)
1419
1509
  })
1420
1510
 
@@ -0,0 +1,14 @@
1
+ /** Producer identity shared by Host messages and Client history projections. */
2
+ export const PTC_MESSAGE_SOURCE_KIND = 'plugin:ptc-plus'
3
+
4
+ /** Read the current producer identity and its historical pre-migration wrapper. */
5
+ export function isPtcMessageSource(source) {
6
+ return source?.kind === PTC_MESSAGE_SOURCE_KIND
7
+ || (source?.kind === 'plugin' && source.plugin === 'ptc-plus')
8
+ }
9
+
10
+ /** DSH owns aggregate runtime-context messages independently of PTC messages. */
11
+ export function isHostRuntimeContextSource(source) {
12
+ return source?.kind === 'runtime-context'
13
+ || (source?.kind === 'plugin' && source.plugin === '@deepseek-ai/dsh-system-prompt')
14
+ }
@@ -187,7 +187,7 @@ export function compileStatefulModule(source, {
187
187
  callableSources = marked.callableSources
188
188
  nativeJavaScript = marked.nativeJavaScript
189
189
  mapped = marked
190
- if (!nativeJavaScript) mapped = normalizeTypeScriptValues(mapped.code, mapped.sourceMap, target)
190
+ if (!nativeJavaScript) mapped = normalizeTypeScriptValues(mapped.code, mapped.sourceMap, target, callableSources)
191
191
  if (target !== 'commonjs' && (isStatefulUserBindingTransform(transform)
192
192
  || transform === PROTECTED_MODULE_TRANSFORM && hasModuleResources(mapped.code))) {
193
193
  mapped = normalizeModuleExports(mapped.code, mapped.sourceMap)
@@ -199,6 +199,7 @@ export function compileStatefulModule(source, {
199
199
  ? moduleLinkPlan(mapped.code) : undefined
200
200
  let normalized = transform === LEGACY_USER_BINDING_TRANSFORM ? mapped
201
201
  : normalizeStatefulScopes(mapped.code, mapped.sourceMap, { target, moduleImport: links?.reader, nativeJavaScript, nativeUsing,
202
+ callableSources,
202
203
  mode: transform === PROTECTED_MODULE_TRANSFORM ? 'protected-v1' : 'stateful-v1' })
203
204
  // Protected entries retain native declaration policy; adapters still need
204
205
  // bounded source regions after TypeScript and decorator lowering.
@@ -26,6 +26,16 @@ const PROJECTION_STATE_FIELDS = new Set(['generation', 'memory', 'pendingReplCal
26
26
  const PENDING_CALL_FIELDS = new Set(['callId', 'seq'])
27
27
  const REPL_MEMORY_META_VERSION = 5
28
28
 
29
+ /**
30
+ * Bound one definition source to the presentation budget. Producers apply this
31
+ * before a catalog entry can carry more than the validated metadata shape.
32
+ */
33
+ export function boundedDefinitionSource(source) {
34
+ return source.length <= MAX_DEFINITION_SOURCE_LENGTH
35
+ ? source
36
+ : `${source.slice(0, MAX_DEFINITION_SOURCE_LENGTH - 3)}...`
37
+ }
38
+
29
39
  function isReuseCount(value) {
30
40
  return Number.isSafeInteger(value) && value >= 0
31
41
  }