dsh-ptc-plus 0.4.2 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.en.md +170 -76
  2. package/README.md +171 -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 +599 -310
  7. package/compiler-core.cjs +162 -160
  8. package/compiler-core.cjs.map +3 -3
  9. package/docs/README.md +4 -1
  10. package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +49 -11
  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/0017-track-the-latest-dsh-public-surface.md +5 -1
  15. package/docs/adr/0019-plugin-settings-and-kill-switch.md +3 -3
  16. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +3 -1
  17. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +14 -3
  18. package/docs/adr/0023-global-user-bindings.md +1 -1
  19. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +23 -7
  20. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +4 -0
  21. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +26 -6
  22. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +2 -2
  23. package/docs/adr/0031-close-semantic-obligations-by-owner.md +29 -0
  24. package/docs/adr/README.md +4 -4
  25. package/docs/architecture.md +8 -5
  26. package/docs/binding-review-placement-design.md +1 -1
  27. package/docs/client-ui.md +8 -4
  28. package/docs/durability-design.md +7 -7
  29. package/docs/installation.md +48 -12
  30. package/docs/publishing.md +4 -4
  31. package/docs/release-notes.md +109 -0
  32. package/docs/runtime-reference.md +25 -12
  33. package/docs/semantic-validation.md +7 -3
  34. package/docs/user-bindings.en.md +7 -6
  35. package/docs/user-bindings.md +6 -5
  36. package/docs/value-wire.md +1 -1
  37. package/docs/verification.md +43 -2
  38. package/index.js +39 -34
  39. package/internal/async-local-storage-intrinsics.js +12 -7
  40. package/internal/callable-source-facts.js +37 -1
  41. package/internal/cell-analysis.js +64 -9
  42. package/internal/config-spec.js +1 -1
  43. package/internal/cordis-tools-owner.js +81 -15
  44. package/internal/direct-surface-owner.js +331 -7
  45. package/internal/edit-transport-owner.js +80 -20
  46. package/internal/execution-seam-compat.js +192 -30
  47. package/internal/kernel-worker.js +162 -72
  48. package/internal/message-sources.js +14 -0
  49. package/internal/module-compilation.js +2 -1
  50. package/internal/rejected-cell-editor.js +11 -21
  51. package/internal/repl-memory-projection.js +10 -0
  52. package/internal/repl-scope-normalizer.js +72 -35
  53. package/internal/repl-value-observer.js +141 -45
  54. package/internal/runtime-config.js +62 -2
  55. package/internal/runtime-intrinsics.js +3 -0
  56. package/internal/runtime-messages.js +6 -4
  57. package/internal/session-cell-executor.js +24 -1
  58. package/internal/session-journal-recovery.js +267 -67
  59. package/internal/session-journal.js +466 -40
  60. package/internal/session-log-view.js +5 -4
  61. package/internal/session-runtime.js +45 -21
  62. package/internal/session-state.js +7 -29
  63. package/internal/settings-compat.js +66 -1
  64. package/internal/source-position-map.js +19 -0
  65. package/internal/user-binding-console-worker.js +23 -9
  66. package/internal/user-binding-draft-projection.js +3 -2
  67. package/internal/user-bindings-owner.js +2 -1
  68. package/internal/user-bindings.js +6 -1
  69. package/internal/validated-parse-repair.js +6 -2
  70. package/internal/value-wire.js +95 -30
  71. package/internal/worker-client.js +99 -8
  72. package/internal/worker-cwd-virtualization.js +10 -9
  73. package/internal/worker-output-capture.js +187 -0
  74. package/internal/worker-output-fence.js +26 -0
  75. package/internal/worker-realm-surfaces.js +197 -0
  76. package/internal/worker-repl-realm.js +10 -4
  77. package/package.json +42 -4
  78. package/src/client-activity.js +4 -1
  79. package/src/client-authoring-view.js +121 -62
  80. package/src/client-console.js +4 -2
  81. package/src/client-copy.js +4 -2
  82. package/src/client-feature-gates.js +7 -9
  83. package/src/client-host-compat.js +104 -27
  84. package/src/client-repl-view.js +3 -1
  85. package/src/client-styles.js +12 -1
  86. package/src/client-tool-view.js +3 -1
  87. package/src/client-transport.js +2 -2
  88. package/src/client-workbench.js +10 -7
  89. package/src/client.js +56 -17
@@ -4,6 +4,7 @@ import { diagnostic, renderDiagnostic } from './diagnostic.js'
4
4
  import { createFailureTracker, messageOf } from './failure-reporting.js'
5
5
  import {
6
6
  createJournal,
7
+ isCanonicalSequence,
7
8
  liveToolCallSeq,
8
9
  normalizeJournal,
9
10
  reduceStateOperations,
@@ -29,6 +30,17 @@ import {
29
30
  } from './repl-memory-projection.js'
30
31
 
31
32
  const WORKER_URL = new URL('./kernel-worker.js', import.meta.url)
33
+ const UNAVAILABLE_SURFACE_GENERATION = Symbol('unavailableSurfaceGeneration')
34
+
35
+ function surfaceGeneration(session, provenanceRequired = true) {
36
+ try {
37
+ return session?.surface?.replaceGeneration
38
+ ?? (provenanceRequired ? UNAVAILABLE_SURFACE_GENERATION : undefined)
39
+ } catch {
40
+ return provenanceRequired ? UNAVAILABLE_SURFACE_GENERATION : undefined
41
+ }
42
+ }
43
+
32
44
  function recoveryDiagnostic(count) {
33
45
  return diagnostic({
34
46
  code: 'PTC-R002',
@@ -66,20 +78,16 @@ class ReplayCancelled extends Error {
66
78
  }
67
79
 
68
80
  class SessionKernel {
69
- constructor({ config, history, cwd, session, userBindingsCwd, withInitiator, observeValues }) {
81
+ constructor({ config, history, cwd, session, surfaceProvenanceRequired, userBindingsCwd, withInitiator, observeValues }) {
70
82
  this.config = config
71
83
  this.history = history
72
84
  this.initialRecoveryBoundary = recoveryBoundaryForHistory(history)
73
- this.surfaceGeneration = undefined
74
- try {
75
- this.surfaceGeneration = session?.surface?.replaceGeneration
76
- } catch {
77
- this.surfaceGeneration = undefined
78
- }
85
+ this.surfaceGeneration = surfaceGeneration(session, surfaceProvenanceRequired)
79
86
  this.cwd = cwd
80
87
  this.userBindingsCwd = userBindingsCwd
81
88
  this.observeValues = observeValues
82
89
  this.session = session
90
+ this.surfaceProvenanceRequired = surfaceProvenanceRequired
83
91
  this.withInitiator = withInitiator
84
92
  this.durability = durabilityState()
85
93
  this.bindingCatalog = new BindingCatalog()
@@ -103,10 +111,19 @@ class SessionKernel {
103
111
  cwd,
104
112
  compilerCache: compilerWorkerCache,
105
113
  onMessage: message => this.cellExecutor.onMessage(message),
114
+ onUnmatchedOutputFailure: message => {
115
+ const active = this.active
116
+ if (active === undefined) return false
117
+ active.resolve({ logs: [], error: { kind: 'worker-exit', message } }, true)
118
+ return true
119
+ },
106
120
  onFailure: message => {
107
121
  this.pendingInspection?.finish()
108
122
  this.workerObservation = undefined
109
- this.active?.resolve({ logs: [], error: { kind: 'worker-exit', message } }, true)
123
+ const active = this.active
124
+ if (active === undefined) return false
125
+ active.resolve({ logs: [], error: { kind: 'worker-exit', message } }, true)
126
+ return true
110
127
  },
111
128
  })
112
129
  this.failures = createFailureTracker()
@@ -181,10 +198,10 @@ class SessionKernel {
181
198
  const current = () => {
182
199
  if (this.disposed || !this.config.replViewEnabled || signal?.aborted
183
200
  || this.client.worker === undefined || this.unsettledCells > 0) return false
184
- try {
185
- return this.session?.surface?.replaceGeneration === this.surfaceGeneration
186
- && isDeepStrictEqual(expected, createReplMemorySnapshot(this.bindingCatalog.snapshot()))
187
- } catch { return false }
201
+ return surfaceGeneration(this.session, this.surfaceProvenanceRequired) === this.surfaceGeneration
202
+ && (!this.surfaceProvenanceRequired
203
+ || this.surfaceGeneration !== UNAVAILABLE_SURFACE_GENERATION)
204
+ && isDeepStrictEqual(expected, createReplMemorySnapshot(this.bindingCatalog.snapshot()))
188
205
  }
189
206
  return new Promise(resolve => {
190
207
  let finished = false
@@ -234,13 +251,10 @@ class SessionKernel {
234
251
  const finishResult = result => recoveryBoundaries.length === 0
235
252
  ? result
236
253
  : { ...result, recoveryBoundaries: recoveryBoundaries.map(boundary => ({ ...boundary })) }
237
- let currentGeneration
238
- try {
239
- currentGeneration = this.session?.surface?.replaceGeneration
240
- } catch {
241
- currentGeneration = undefined
242
- }
243
- if (currentGeneration !== undefined && currentGeneration !== this.surfaceGeneration) {
254
+ const currentGeneration = surfaceGeneration(this.session, this.surfaceProvenanceRequired)
255
+ const hasLiveState = this.liveCallSeqs.size > 0
256
+ if (this.surfaceProvenanceRequired
257
+ && (currentGeneration !== this.surfaceGeneration || hasLiveState)) {
244
258
  this.surfaceGeneration = currentGeneration
245
259
  const visibleCallSeqs = visibleExecutableCallSeqs(this.session)
246
260
  const contractsLiveState = [...this.liveCallSeqs].some(callSeq => (
@@ -597,6 +611,7 @@ function sessionOf(sessionContext) {
597
611
  callId: undefined,
598
612
  persistedCallSeq: undefined,
599
613
  cwd: undefined,
614
+ surfaceProvenanceRequired: false,
600
615
  }
601
616
  }
602
617
  const session = sessionContext.session
@@ -606,6 +621,7 @@ function sessionOf(sessionContext) {
606
621
  callId: sessionContext.callId,
607
622
  persistedCallSeq: sessionContext.persistedCallSeq,
608
623
  cwd: typeof session?.header?.cwd === 'string' ? session.header.cwd : undefined,
624
+ surfaceProvenanceRequired: true,
609
625
  }
610
626
  }
611
627
 
@@ -657,12 +673,19 @@ export class SessionRuntime {
657
673
  return completed({ logs: [], error: { kind: 'exception', message: messageOf(error) } })
658
674
  }
659
675
  request = { ...request, bindings: bindingDescriptors.namespaces, bindingDescriptors }
660
- const { id: sessionId, session, callId, persistedCallSeq, cwd } = sessionOf(sessionContext)
676
+ const {
677
+ id: sessionId,
678
+ session,
679
+ callId,
680
+ persistedCallSeq,
681
+ cwd,
682
+ surfaceProvenanceRequired,
683
+ } = sessionOf(sessionContext)
661
684
  let callSeq
662
685
  let sourceCallSeq
663
686
  try {
664
687
  if (persistedCallSeq !== undefined
665
- && (!Number.isSafeInteger(persistedCallSeq) || persistedCallSeq < 0)) {
688
+ && !isCanonicalSequence(persistedCallSeq)) {
666
689
  throw new Error('persisted tool call sequence must be a non-negative safe integer')
667
690
  }
668
691
  if (cellConfig.durableReplay) {
@@ -700,6 +723,7 @@ export class SessionRuntime {
700
723
  history,
701
724
  cwd,
702
725
  session,
726
+ surfaceProvenanceRequired,
703
727
  userBindingsCwd: this.userBindingsCwd,
704
728
  withInitiator: this.withInitiator,
705
729
  observeValues: () => this.config.replViewEnabled && this.observeSession(sessionId),
@@ -1,10 +1,12 @@
1
1
  // Runtime state transitions shared by the session coordinator and cell executor.
2
2
 
3
3
  import { userBindingCatalogEntries } from './user-bindings.js'
4
+ import { boundedDefinitionSource } from './repl-memory-projection.js'
4
5
  import { LEGACY_USER_BINDINGS_SHADOW_POLICY } from './session-journal-schema.js'
5
6
  import { advanceLegacyBindings } from './legacy-binding-catalog.js'
6
7
  import { dynamicBindingOrigin } from './dynamic-binding-evidence.js'
7
8
  import { LEGACY_LANGUAGE_SEMANTICS, normalizeLanguageSemantics } from './language-semantics.js'
9
+ import { sourceLineStarts, sourceOffsetAtPosition } from './source-position-map.js'
8
10
 
9
11
  /**
10
12
  * Reuse counts belong to a logical binding identity, not to one declaration
@@ -236,7 +238,7 @@ export class BindingCatalog {
236
238
  definition: previous?.origin === undefined && previous?.definition !== undefined
237
239
  ? previous.definition
238
240
  : fact.state === 'local' && typeof source === 'string' && source.length > 0
239
- ? Object.freeze({ source: source.slice(0, MAX_DEFINITION_SOURCE_LENGTH), line: 1, column: 1 }) : undefined,
241
+ ? Object.freeze({ source: boundedDefinitionSource(source), line: 1, column: 1 }) : undefined,
240
242
  writable: previous?.writable ?? true,
241
243
  userBindingState: fact.state,
242
244
  unavailable: fact.state !== 'local',
@@ -290,41 +292,17 @@ function applyRootBindingFacts(entries, facts, rootCandidates, dynamicOrigins, i
290
292
  }
291
293
  }
292
294
 
293
- const MAX_DEFINITION_SOURCE_LENGTH = 1024
294
-
295
- function lineOffsets(source) {
296
- const offsets = [0]
297
- for (let index = 0; index < source.length; index += 1) {
298
- if (source.charCodeAt(index) === 10) offsets.push(index + 1)
299
- }
300
- return offsets
301
- }
302
-
303
- function offsetAt(offsets, sourceLength, position) {
304
- if (position === null || typeof position !== 'object'
305
- || !Number.isSafeInteger(position.line) || position.line < 1
306
- || !Number.isSafeInteger(position.column) || position.column < 1
307
- || position.line > offsets.length) return undefined
308
- const lineStart = offsets[position.line - 1]
309
- const lineEnd = position.line < offsets.length ? offsets[position.line] - 1 : sourceLength
310
- const offset = lineStart + position.column - 1
311
- return offset <= lineEnd ? offset : undefined
312
- }
313
-
314
295
  function sourceDefinitionExtractor(source) {
315
- const offsets = typeof source === 'string' ? lineOffsets(source) : undefined
296
+ const offsets = typeof source === 'string' ? sourceLineStarts(source) : undefined
316
297
  const extracted = new Map()
317
298
  return span => {
318
299
  if (offsets === undefined || span === null || typeof span !== 'object') return undefined
319
- const start = offsetAt(offsets, source.length, span)
320
- const end = offsetAt(offsets, source.length, span.end)
300
+ const start = sourceOffsetAtPosition(source, span, offsets)
301
+ const end = sourceOffsetAtPosition(source, span.end, offsets)
321
302
  if (start === undefined || end === undefined || end <= start) return undefined
322
303
  const key = `${start}:${end}`
323
304
  if (extracted.has(key)) return extracted.get(key)
324
- const length = end - start
325
- const bounded = length <= MAX_DEFINITION_SOURCE_LENGTH
326
- ? source.slice(start, end)
327
- : `${source.slice(start, start + MAX_DEFINITION_SOURCE_LENGTH - 3)}...`
305
+ const bounded = boundedDefinitionSource(source.slice(start, end))
328
306
  if (bounded.length === 0) return undefined
329
307
  const definition = Object.freeze({ source: bounded, line: span.line, column: span.column })
330
308
  extracted.set(key, definition)
@@ -1,3 +1,55 @@
1
+ /**
2
+ * Whether the installed settings generation owns the plugin's exported `Config`
3
+ * document.
4
+ *
5
+ * A generation that still attaches through the provider section installer — on
6
+ * its settings service, or on the package helper an older release published —
7
+ * validates the plugin's own fields and has no volatile contract. The
8
+ * generation that serves the exported `Config` itself, and commits only fields
9
+ * marked volatile, publishes neither installer. Both probes read the installed
10
+ * public surface, so no release identity selects the shape.
11
+ *
12
+ * @param settingsModule - the installed public settings package surface.
13
+ * @returns whether that generation serves the plugin's Config document itself.
14
+ */
15
+ export function hostOwnsSettingsDocument(settingsModule) {
16
+ return typeof settingsModule?.installSettingsSection !== 'function'
17
+ && typeof settingsModule?.SettingsProvider?.prototype?.installSection !== 'function'
18
+ }
19
+
20
+ /**
21
+ * Build one exported `Config` field for the installed settings generation.
22
+ *
23
+ * A generation that serves the document itself keeps a form mounted and commits
24
+ * new values into the retained handle only for fields whose nearest schema
25
+ * ancestor is marked volatile, so the marker is applied there. A builder that
26
+ * does not publish that capability causes a dependency error during import; silently
27
+ * emitting ordinary fields would hide the form and every Client feature gated
28
+ * by its settings snapshot. The generation that installs its own section
29
+ * validates the same fields itself
30
+ * and must not receive the marker, because its validated defaults would become
31
+ * live handles its document path never commits.
32
+ *
33
+ * @param options.Schema - the schema builder the plugin imports.
34
+ * @param options.field - one field definition from `internal/config-spec.js`.
35
+ * @param options.settingsModule - the installed public settings package surface.
36
+ * @returns the Cordis schema field for that definition.
37
+ */
38
+ export function configFieldSchema({ Schema, field, settingsModule }) {
39
+ const base = field.type === 'boolean'
40
+ ? Schema.boolean().default(field.default)
41
+ : field.type === 'enum'
42
+ // Keep omission visible until resolveConfig migrates legacy policies.
43
+ ? Schema.union(field.options.map(option => Schema.const(option)))
44
+ : Schema.number().step(1).min(field.min).max(field.max).default(field.default)
45
+ const described = base.description(field.description)
46
+ if (!hostOwnsSettingsDocument(settingsModule)) return described
47
+ if (typeof described.volatile !== 'function') {
48
+ throw new Error('ptc-plus: the host settings contract requires @deepseek-ai/schemastery with volatile(); reinstall the plugin to update its dependencies')
49
+ }
50
+ return described.volatile()
51
+ }
52
+
1
53
  /** Install an optional settings section across current and legacy DSH settings APIs. */
2
54
  export function installSettingsSectionCompat({
3
55
  ctx,
@@ -6,6 +58,7 @@ export function installSettingsSectionCompat({
6
58
  schema,
7
59
  entry,
8
60
  hooks,
61
+ ownerFiber,
9
62
  onProvider,
10
63
  }) {
11
64
  ctx.inject(['settings'], settingsContext => {
@@ -16,7 +69,19 @@ export function installSettingsSectionCompat({
16
69
  return
17
70
  }
18
71
  if (typeof settingsModule?.installSettingsSection !== 'function') {
19
- throw new Error('ptc-plus: incompatible settings service; expected installSection or installSettingsSection')
72
+ // The newest settings generation owns its document and card from the
73
+ // plugin's exported Config, so it exposes no section installer to call.
74
+ // Treat that shape as a host-owned surface instead of a load failure;
75
+ // the host commits volatile-only changes into the retained handles and
76
+ // notifies this fiber through `loader/volatile-update`.
77
+ if (typeof provider?.configure === 'function') {
78
+ // The host records the own-page policy against the profile entry's own
79
+ // fiber, so the owner is the plugin's entry context rather than the
80
+ // scope this installation happens to run in.
81
+ const dispose = provider.configure({ auto: false }, ownerFiber ?? ctx.fiber)
82
+ ctx.effect?.(() => dispose, 'ptc-plus settings page policy')
83
+ }
84
+ return
20
85
  }
21
86
  const mountedOwner = new Proxy(ctx, {
22
87
  get(target, property) {
@@ -287,6 +287,25 @@ export function sourceLineStarts(source) {
287
287
  return starts
288
288
  }
289
289
 
290
+ /** Convert one-based JavaScript source coordinates without admitting a
291
+ * position inside a line terminator. End coordinates may point immediately
292
+ * after the final content character. */
293
+ export function sourceOffsetAtPosition(source, position, starts) {
294
+ if (typeof source !== 'string' || position === null || typeof position !== 'object'
295
+ || !Number.isSafeInteger(position.line) || position.line < 1
296
+ || !Number.isSafeInteger(position.column) || position.column < 1) return undefined
297
+ const lineStarts = starts ?? sourceLineStarts(source)
298
+ if (!Array.isArray(lineStarts) || position.line > lineStarts.length) return undefined
299
+ const lineStart = lineStarts[position.line - 1]
300
+ const nextLineStart = lineStarts[position.line]
301
+ let lineEnd = source.length
302
+ if (nextLineStart !== undefined) {
303
+ lineEnd = nextLineStart - (source[nextLineStart - 2] === '\r' && source[nextLineStart - 1] === '\n' ? 2 : 1)
304
+ }
305
+ const offset = lineStart + position.column - 1
306
+ return offset <= lineEnd ? offset : undefined
307
+ }
308
+
290
309
  /** Visit maintained emitter coordinates without re-encoding available raw maps.
291
310
  * All line numbers at this boundary are zero-based; absent origins stay absent. */
292
311
  export function visitSourceMappings(transformed, visit) {
@@ -16,6 +16,12 @@ import { USER_BINDING_TRANSFORM } from './module-transform-contract.js'
16
16
  import { moduleRuntimeIntrinsics } from './compiler-intrinsics.js'
17
17
  import { stringifyCompilerData } from './compiler-data.js'
18
18
  import { createCellCompletionObserver } from './cell-completion.js'
19
+ import {
20
+ WORKER_REALM_MUTATION,
21
+ assignWorkerRealmProperty,
22
+ defineWorkerRealmProperty,
23
+ deleteWorkerRealmProperty,
24
+ } from './worker-realm-surfaces.js'
19
25
  import { WORKER_SHUTDOWN_ACKNOWLEDGEMENT, WORKER_SHUTDOWN_REQUEST } from './worker-shutdown.js'
20
26
  import { WORKER_REPL_OPTIONS, createWorkerControlPromise, createWorkerReplErrorHandler,
21
27
  disableWorkerReplDomain,
@@ -31,10 +37,10 @@ const ControlPromise = createWorkerControlPromise()
31
37
  const evaluations = createPrivateAsyncLocalStorage()
32
38
  const context = globalThis
33
39
  const completionObserver = createCellCompletionObserver(runInWorkerReplRealm('Function'))
34
- context.console = console
40
+ assignWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'console', context, 'console', console)
35
41
  let retainedEvaluator
36
42
  const completionKey = `__ptc_console_${randomUUID().replaceAll('-', '')}`
37
- Object.defineProperty(context, completionKey, { value: value => {
43
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined, context, completionKey, { value: value => {
38
44
  evaluations.getStore().completed = true
39
45
  return value
40
46
  } })
@@ -65,7 +71,8 @@ const moduleCompilation = createUserModuleCompilationHooks({
65
71
  transformForParent: parent => parent === replParent || parent === parentUrl ? USER_BINDING_TRANSFORM : undefined,
66
72
  })
67
73
  const providedRequire = managedRequire(parentUrl, createRequire(parentUrl))
68
- Object.defineProperty(context, 'require', { configurable: true, value: providedRequire })
74
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'cell module metadata',
75
+ context, 'require', { configurable: true, value: providedRequire })
69
76
  const importCanary = 'data:text/javascript,export default 1'
70
77
  registerHooks({ resolve(specifier, context, nextResolve) {
71
78
  if (specifier === importCanary) replParent = context.parentURL
@@ -86,10 +93,12 @@ process.on('uncaughtException', error => {
86
93
  function evaluate(source) {
87
94
  const prepared = prepareConsoleProgram(source, { languageSemantics: 'stateful-v1', knownBindings })
88
95
  const helpers = [prepared.rootRuntimeName, prepared.returnSignal]
89
- Object.defineProperty(context, prepared.rootRuntimeName, {
96
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
97
+ context, prepared.rootRuntimeName, {
90
98
  configurable: true, value: roots.begin({ ...prepared.rootBindings, committed: () => {} }),
91
99
  })
92
- Object.defineProperty(context, prepared.returnSignal, { configurable: true, value: ConsoleReturn })
100
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
101
+ context, prepared.returnSignal, { configurable: true, value: ConsoleReturn })
93
102
  if (prepared.moduleLoads.length === 0) {
94
103
  const operation = evaluatePrepared(prepared.code, prepared.asyncCompletion)
95
104
  return new ControlPromise((resolve, reject) => operation.then(
@@ -101,7 +110,9 @@ function evaluate(source) {
101
110
  }
102
111
 
103
112
  function removeHelpers(helpers) {
104
- for (let index = 0; index < helpers.length; index++) delete context[helpers[index]]
113
+ for (let index = 0; index < helpers.length; index++) {
114
+ deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, helpers[index])
115
+ }
105
116
  }
106
117
 
107
118
  async function evaluateWithModules(prepared, helpers) {
@@ -132,7 +143,8 @@ async function evaluateWithModules(prepared, helpers) {
132
143
  } finally { setDelete(adapterParents, adapter) }
133
144
  if (load.global !== undefined) {
134
145
  appendArray(helpers, load.global)
135
- Object.defineProperty(context, load.global, { configurable: true, value: namespace })
146
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
147
+ context, load.global, { configurable: true, value: namespace })
136
148
  }
137
149
  }
138
150
  return await evaluatePrepared(prepared.code, prepared.asyncCompletion)
@@ -152,7 +164,8 @@ function evaluatePrepared(javascript, asyncCompletion = false) {
152
164
  protectWorkerReplAsyncContext(server)
153
165
  workerReplContext(server)
154
166
  restoreWorkerReplGlobals(workerGlobalBaseline, context)
155
- Object.defineProperty(context, 'require', { configurable: true, value: providedRequire })
167
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'cell module metadata',
168
+ context, 'require', { configurable: true, value: providedRequire })
156
169
  const errorDomain = disableWorkerReplDomain(server.eval.domain ?? createDomain())
157
170
  const evaluator = { server, input, output, errorDomain }
158
171
  retainedEvaluator = evaluator
@@ -205,7 +218,8 @@ try {
205
218
  for (let index = 0; index < names.length; index++) {
206
219
  const name = names[index]
207
220
  setAdd(knownBindings, name)
208
- Object.defineProperty(context, name, { configurable: true, enumerable: true, get: () => namespace[name] })
221
+ defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined,
222
+ context, name, { configurable: true, enumerable: true, get: () => namespace[name] })
209
223
  }
210
224
  parentPort.on('message', (message) => {
211
225
  if (message?.type === WORKER_SHUTDOWN_REQUEST) {
@@ -1,3 +1,4 @@
1
+ import { isPtcMessageSource, PTC_MESSAGE_SOURCE_KIND } from './message-sources.js'
1
2
  import { isRecord } from './record-utils.js'
2
3
  import { normalizeBindingModelContext } from './user-binding-model-context.js'
3
4
 
@@ -51,13 +52,13 @@ function normalizeAction(value) {
51
52
 
52
53
  export function bindingActionNotice(action) {
53
54
  return {
54
- source: { kind: 'plugin', plugin: 'ptc-plus', form: 'notice', summary: 'Global User Binding action' },
55
+ source: { kind: PTC_MESSAGE_SOURCE_KIND, form: 'notice', summary: 'Global User Binding action' },
55
56
  content: [{ type: 'text', text: ACTION_PREFIX + JSON.stringify(normalizeAction(action)) }],
56
57
  }
57
58
  }
58
59
 
59
60
  export function readBindingAction(message) {
60
- if (message?.source?.kind !== 'plugin' || message.source.plugin !== 'ptc-plus'
61
+ if (!isPtcMessageSource(message?.source)
61
62
  || message.source.form !== 'notice' || message.source.summary !== 'Global User Binding action'
62
63
  || message.content?.length !== 1 || message.content[0]?.type !== 'text') return undefined
63
64
  try {
@@ -1,3 +1,4 @@
1
+ import { PTC_MESSAGE_SOURCE_KIND } from './message-sources.js'
1
2
  import { RPC_CONTRACTS } from './rpc-contract.js'
2
3
  import { randomUUID } from 'node:crypto'
3
4
  import { dirname } from 'node:path'
@@ -87,7 +88,7 @@ function authoringTask(command, current) {
87
88
  function taskMessage(text) {
88
89
  return createUserMessage({
89
90
  content: [{ type: 'text', text }],
90
- source: { kind: 'plugin', plugin: 'ptc-plus', form: 'instructions' },
91
+ source: { kind: PTC_MESSAGE_SOURCE_KIND, form: 'instructions' },
91
92
  })
92
93
  }
93
94
 
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto'
2
2
  import { identifier, exportedSymbols, sourceDurability } from './compiler-service.js'
3
3
  import { assertOwnFields, isRecord } from './record-utils.js'
4
+ import { boundedDefinitionSource } from './repl-memory-projection.js'
4
5
  import { bindingModelPreferences, normalizeBindingModelContext } from './user-binding-model-context.js'
5
6
  import { supportedUserBindingTransform, USER_BINDING_TRANSFORM } from './module-transform-contract.js'
6
7
 
@@ -296,7 +297,11 @@ export function userBindingCatalogEntries(snapshot) {
296
297
  kind: binding.kind,
297
298
  entryId: entry.id,
298
299
  fingerprint: entry.fingerprint,
299
- definition: Object.freeze({ source: binding.declaration, line: 1, column: 1 }),
300
+ definition: Object.freeze({
301
+ source: boundedDefinitionSource(binding.declaration),
302
+ line: 1,
303
+ column: 1,
304
+ }),
300
305
  })))
301
306
  }
302
307
 
@@ -1,6 +1,10 @@
1
1
  import { EXPECTED_TARGET_CALL_SEQ, editRejectedCell } from './rejected-cell-editor.js'
2
2
 
3
3
  const CLOSING_DELIMITERS = Object.freeze(['}', ')', ']'])
4
+ const CLOSING_SUFFIXES = Object.freeze([
5
+ ...CLOSING_DELIMITERS,
6
+ ...CLOSING_DELIMITERS.flatMap(first => CLOSING_DELIMITERS.map(second => first + second)),
7
+ ])
4
8
  const LINE_TERMINATORS = /\r\n|[\n\r\u2028\u2029]/gu
5
9
  export const MAX_PARSE_REPAIR_ANCHOR_CODE_UNITS = 256
6
10
 
@@ -52,14 +56,14 @@ function renderInvocation(argumentsValue) {
52
56
  return `edit_run_code(${json})`
53
57
  }
54
58
 
55
- /** Returns a source-exact edit only when one bounded EOF-closing candidate is valid. */
59
+ /** Requires exactly one valid suffix across all one- and two-token EOF closures. */
56
60
  export function validatedEofClosureRepair({ source, position, prepare, targetCallSeq }) {
57
61
  if (!Number.isSafeInteger(targetCallSeq) || targetCallSeq < 0
58
62
  || !isSourceEnd(source, position)) return undefined
59
63
  const anchor = boundedUniqueSuffix(source)
60
64
  if (anchor === undefined) return undefined
61
65
  const candidates = []
62
- for (const delimiter of CLOSING_DELIMITERS) {
66
+ for (const delimiter of CLOSING_SUFFIXES) {
63
67
  const code = source + delimiter
64
68
  let prepared
65
69
  try {