dsh-ptc-plus 0.3.1 → 0.4.1

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 (200) hide show
  1. package/README.en.md +66 -73
  2. package/README.md +66 -73
  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 +30610 -722
  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 +554 -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 +6 -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 +6 -4
  25. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +15 -3
  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 +9 -3
  31. package/docs/adr/0014-persist-cell-rewrite-policy.md +11 -5
  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 +8 -6
  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 +17 -3
  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 +82 -22
  48. package/docs/binding-review-placement-design.md +200 -0
  49. package/docs/capability-projection.md +1 -1
  50. package/docs/client-ui.md +96 -20
  51. package/docs/durability-design.md +61 -24
  52. package/docs/evaluation.md +78 -6
  53. package/docs/installation.md +41 -6
  54. package/docs/program-data-plane.md +6 -0
  55. package/docs/publishing.md +5 -3
  56. package/docs/runtime-reference.md +79 -25
  57. package/docs/semantic-validation.md +29 -0
  58. package/docs/stateful-binding-updates-proposal.md +755 -0
  59. package/docs/user-bindings.en.md +64 -0
  60. package/docs/user-bindings.md +64 -0
  61. package/docs/value-wire.md +3 -3
  62. package/docs/verification.md +57 -0
  63. package/index.js +374 -92
  64. package/internal/ast-traversal.js +5 -2
  65. package/internal/binding-descriptors.js +1 -0
  66. package/internal/binding-pattern-traversal.js +25 -0
  67. package/internal/binding-pattern.js +26 -29
  68. package/internal/binding-source-analysis.js +391 -0
  69. package/internal/binding-update-policy.js +66 -0
  70. package/internal/bounded-worker-round.js +56 -0
  71. package/internal/callable-definition-environment.js +42 -0
  72. package/internal/callable-source-catalog.js +129 -0
  73. package/internal/callable-source-encoding.js +47 -0
  74. package/internal/callable-source-facts.js +459 -0
  75. package/internal/callable-source-format.js +2 -0
  76. package/internal/callable-source-lowering.js +123 -0
  77. package/internal/cell-analysis-contract.js +25 -0
  78. package/internal/cell-analysis.js +252 -242
  79. package/internal/cell-completion.js +15 -0
  80. package/internal/cell-error.js +7 -0
  81. package/internal/cell-parser.js +66 -0
  82. package/internal/cell-rewriter.js +3 -553
  83. package/internal/commonjs-export-evidence.js +63 -0
  84. package/internal/commonjs-source-evidence.js +72 -0
  85. package/internal/compiler-amaro.cjs +2 -0
  86. package/internal/compiler-compression.js +12 -0
  87. package/internal/compiler-data.js +58 -0
  88. package/internal/compiler-descriptors.js +54 -0
  89. package/internal/compiler-dynamic-cache.js +48 -0
  90. package/internal/compiler-entry.js +35 -0
  91. package/internal/compiler-generated-names.js +17 -0
  92. package/internal/compiler-intrinsics.js +84 -0
  93. package/internal/compiler-module-links.js +34 -0
  94. package/internal/compiler-operations.js +61 -0
  95. package/internal/compiler-platform-factory.js +84 -0
  96. package/internal/compiler-platform.js +136 -0
  97. package/internal/compiler-record-roles.js +53 -0
  98. package/internal/compiler-region-output.js +259 -0
  99. package/internal/compiler-resource-lowering.js +103 -0
  100. package/internal/compiler-scope-facts.js +115 -0
  101. package/internal/compiler-service.js +109 -0
  102. package/internal/compiler-source-cache.js +32 -0
  103. package/internal/compiler-source-pieces.js +93 -0
  104. package/internal/compiler-source-regions.js +132 -0
  105. package/internal/compiler-storage-source.js +54 -0
  106. package/internal/compiler-text.js +15 -0
  107. package/internal/compiler-typescript.cjs +2 -0
  108. package/internal/config-spec.js +91 -25
  109. package/internal/cordis-tools-owner.js +130 -21
  110. package/internal/diagnostic.js +27 -1
  111. package/internal/direct-surface-owner.js +76 -23
  112. package/internal/dynamic-binding-evidence.js +16 -0
  113. package/internal/dynamic-environment-compiler.js +779 -0
  114. package/internal/dynamic-environment-integration.js +87 -0
  115. package/internal/dynamic-environment-runtime.js +362 -0
  116. package/internal/dynamic-native-calls.js +150 -0
  117. package/internal/dynamic-native-runtime.js +248 -0
  118. package/internal/dynamic-scope-analysis.js +90 -0
  119. package/internal/edit-transport-owner.js +28 -3
  120. package/internal/execution-seam-compat.js +193 -0
  121. package/internal/failure-reporting.js +118 -18
  122. package/internal/host-rpc.js +66 -0
  123. package/internal/isolated-worker.js +548 -0
  124. package/internal/kernel-child.js +139 -0
  125. package/internal/kernel-worker.js +911 -116
  126. package/internal/language-semantics.js +14 -0
  127. package/internal/legacy-binding-catalog.js +48 -0
  128. package/internal/legacy-cell-analysis.js +600 -0
  129. package/internal/legacy-cell-parser.js +50 -0
  130. package/internal/legacy-cell-rewriter.js +631 -0
  131. package/internal/legacy-repl-convenience.js +291 -0
  132. package/internal/managed-module-operations.js +170 -0
  133. package/internal/module-compilation.js +321 -0
  134. package/internal/module-runtime-platform.js +19 -0
  135. package/internal/module-transform-contract.js +12 -0
  136. package/internal/native-await-declarations.js +68 -0
  137. package/internal/native-root-compilation.js +54 -0
  138. package/internal/native-root-dynamic.js +67 -0
  139. package/internal/recovery-tips.js +27 -15
  140. package/internal/rejected-cell-editor.js +13 -8
  141. package/internal/repl-convenience.js +2 -201
  142. package/internal/repl-memory-projection.js +81 -12
  143. package/internal/repl-observation-interest.js +70 -0
  144. package/internal/repl-rewrite-contract.js +21 -0
  145. package/internal/repl-scope-normalizer.js +2091 -0
  146. package/internal/repl-value-observer.js +114 -0
  147. package/internal/rpc-contract.js +52 -0
  148. package/internal/runtime-bridge-owner.js +172 -28
  149. package/internal/runtime-config.js +48 -51
  150. package/internal/runtime-contexts.js +82 -10
  151. package/internal/runtime-messages.js +111 -0
  152. package/internal/session-cell-executor.js +455 -91
  153. package/internal/session-journal-recovery.js +684 -0
  154. package/internal/session-journal-schema.js +106 -0
  155. package/internal/session-journal.js +237 -440
  156. package/internal/session-log-view.js +42 -9
  157. package/internal/session-runtime.js +325 -80
  158. package/internal/session-state.js +249 -40
  159. package/internal/source-position-map.js +211 -71
  160. package/internal/stateful-module-compiler.js +154 -0
  161. package/internal/stateful-module-runtime.js +303 -0
  162. package/internal/stateful-root-compiler.js +598 -0
  163. package/internal/stateful-root-runtime.js +340 -0
  164. package/internal/typert-codec-compat.js +28 -0
  165. package/internal/typescript-transform.js +29 -0
  166. package/internal/user-binding-authoring.js +70 -0
  167. package/internal/user-binding-console-worker.js +184 -0
  168. package/internal/user-binding-console.js +148 -0
  169. package/internal/user-binding-draft-projection.js +335 -0
  170. package/internal/user-binding-model-context.js +31 -0
  171. package/internal/user-binding-runner.js +56 -0
  172. package/internal/user-bindings-owner.js +1065 -0
  173. package/internal/user-bindings-store.js +293 -0
  174. package/internal/user-bindings.js +353 -0
  175. package/internal/value-wire-schema.js +28 -0
  176. package/internal/value-wire.js +29 -20
  177. package/internal/worker-client.js +57 -67
  178. package/internal/worker-environment.js +45 -0
  179. package/internal/worker-output-relay.js +29 -0
  180. package/internal/worker-shutdown.js +14 -0
  181. package/package.json +81 -11
  182. package/src/client-activity.js +109 -45
  183. package/src/client-authoring-view.js +580 -0
  184. package/src/client-binding-review.js +230 -0
  185. package/src/client-bindings-data.js +29 -0
  186. package/src/client-catalog.js +127 -0
  187. package/src/client-code-editor.js +84 -0
  188. package/src/client-console.js +112 -0
  189. package/src/client-copy.js +370 -0
  190. package/src/client-feature-gates.js +62 -0
  191. package/src/client-host-compat.js +150 -0
  192. package/src/client-indicator-view.js +267 -0
  193. package/src/client-repl-view.js +292 -0
  194. package/src/client-rpc.js +20 -0
  195. package/src/client-settings-view.js +221 -0
  196. package/src/client-styles.js +106 -0
  197. package/src/client-tool-view.js +108 -0
  198. package/src/client-transport.js +159 -0
  199. package/src/client-workbench.js +521 -0
  200. package/src/client.js +233 -615
@@ -7,6 +7,7 @@ import { RUN_CODE } from './runtime-bridge-owner.js'
7
7
  const CORDIS_PRESET_ID = 'cordis'
8
8
  const CORDIS_SKILL_NAME = 'cordis-plugin-development'
9
9
  const CORDIS_SKILL_PROVIDER = 'ptc-plus-cordis'
10
+ const COMPANION_SKILL_TOOL = 'skill'
10
11
 
11
12
  function exactCompanionCandidates(observation) {
12
13
  if (Array.isArray(observation)) {
@@ -80,6 +81,40 @@ function requireFiberServices(agent, fiber, owner) {
80
81
  }
81
82
  }
82
83
 
84
+ /**
85
+ * Companion surfaces the agent scope decides for itself.
86
+ *
87
+ * A scope composes its own tools, preset service and Skill service, so one that
88
+ * omits any of them is not a scope this plugin publishes in yet: the host keeps
89
+ * authority over the composition, and the missing member can appear later (a
90
+ * tool registered after this owner loaded, a service provided by a plugin that
91
+ * loads later). Such an agent therefore stays deferred instead of becoming a
92
+ * failure, which also keeps this plugin's unavailability out of the host
93
+ * operation that created it. `undefined` reports a scope without the public
94
+ * `Context.get` API, whose contradiction the activation guard owns.
95
+ *
96
+ * @param {object} agent - the DSH agent scope.
97
+ * @returns {string[]|undefined} the missing companion surfaces, if any.
98
+ */
99
+ function absentCompanionSurfaces(agent) {
100
+ if (typeof agent?.ctx?.get !== 'function') return undefined
101
+ const missing = []
102
+ if (agent.ctx.tools?.get?.(RUN_CODE, agent) === undefined) missing.push(`the ${RUN_CODE} tool`)
103
+ if (agent.ctx.tools?.get?.(COMPANION_SKILL_TOOL, agent) === undefined) {
104
+ missing.push(`the ${COMPANION_SKILL_TOOL} tool`)
105
+ }
106
+ if (agent.ctx.get('agentPresets') === undefined) missing.push('the agentPresets service')
107
+ if (agent.ctx.get('skills') === undefined) missing.push('the skills service')
108
+ return missing
109
+ }
110
+
111
+ /**
112
+ * Read the services the mount publishes through, and reject a scope whose
113
+ * exposed service contradicts their public contract. Whether a companion surface
114
+ * exists at all is decided before the mount ([`absentCompanionSurfaces`]): this
115
+ * guard owns the shapes, so a service that is present but does not expose the
116
+ * API the mount calls is a host contradiction rather than a deferred surface.
117
+ */
83
118
  function requireCompanionServices(agent) {
84
119
  if (typeof agent?.ctx?.get !== 'function') {
85
120
  throw new Error('ptc-plus: cordisToolsEnabled requires the DSH agent Context.get API')
@@ -93,9 +128,6 @@ function requireCompanionServices(agent) {
93
128
  || typeof skills.list !== 'function' || typeof skills.get !== 'function') {
94
129
  throw new Error('ptc-plus: cordisToolsEnabled requires the DSH skills registerProvider/list/get APIs')
95
130
  }
96
- if (agent.ctx.tools?.get?.('skill', agent) === undefined) {
97
- throw new Error('ptc-plus: cordisToolsEnabled requires the DSH skill tool in the PTC agent scope')
98
- }
99
131
  return { agentPresets, skills }
100
132
  }
101
133
 
@@ -234,6 +266,8 @@ export function createCordisToolsOwner(
234
266
  }
235
267
  const mounts = new Map()
236
268
  const pending = new Set()
269
+ // Agent scopes this owner withdrew from, keyed by the failure it reported.
270
+ const withdrawn = new Map()
237
271
  const cordisInspectLeases = createCordisInspectLeases(ctx)
238
272
  const scopedCordisPlugin = cordisInspectLeases.plugin(cordisPlugin)
239
273
  const scopedSkillPlugin = exactCompanionSkillPlugin(skillFilesystemPlugin)
@@ -251,19 +285,34 @@ export function createCordisToolsOwner(
251
285
  const disposeMount = (agent, mount) => {
252
286
  mount.disposed = true
253
287
  if (mount.disposal === undefined) {
254
- mount.disposal = (async () => {
288
+ const operation = (async () => {
255
289
  const failures = []
256
- for (const fiber of [...mount.fibers].reverse()) {
290
+ const fibers = [...mount.fibers].reverse()
291
+ const results = await Promise.allSettled(fibers.map(fiber => {
257
292
  try {
258
- await fiber.dispose()
293
+ return Promise.resolve(fiber.dispose())
259
294
  } catch (error) {
260
- failures.push(error)
295
+ return Promise.reject(error)
296
+ }
297
+ }))
298
+ for (let index = 0; index < results.length; index += 1) {
299
+ const result = results[index]
300
+ const fiber = fibers[index]
301
+ if (result.status === 'rejected') failures.push(result.reason)
302
+ else {
303
+ const ownedIndex = mount.fibers.indexOf(fiber)
304
+ if (ownedIndex !== -1) mount.fibers.splice(ownedIndex, 1)
261
305
  }
262
306
  }
263
307
  if (failures.length > 0) {
264
308
  throw new AggregateError(failures, 'ptc-plus Cordis agent mount disposal failed')
265
309
  }
266
310
  })()
311
+ mount.disposal = operation
312
+ operation.then(
313
+ () => { if (mount.disposal === operation) mount.disposal = undefined },
314
+ () => { if (mount.disposal === operation) mount.disposal = undefined },
315
+ )
267
316
  }
268
317
  return mount.disposal.then(() => {
269
318
  if (mounts.get(agent) === mount) mounts.delete(agent)
@@ -272,6 +321,7 @@ export function createCordisToolsOwner(
272
321
 
273
322
  const disposeAgent = async (agent) => {
274
323
  pending.delete(agent)
324
+ withdrawn.delete(agent)
275
325
  const mount = mounts.get(agent)
276
326
  if (mount === undefined) return
277
327
  await disposeMount(agent, mount)
@@ -280,8 +330,11 @@ export function createCordisToolsOwner(
280
330
  const installAgent = (agent) => {
281
331
  if (disposed) return Promise.resolve()
282
332
  const mounted = mounts.get(agent)
283
- if (mounted !== undefined) return mounted.activation
284
- if (agent?.ctx?.tools?.get?.(RUN_CODE, agent) === undefined) {
333
+ if (mounted !== undefined) {
334
+ return mounted.withdrawn === true ? Promise.resolve() : mounted.activation
335
+ }
336
+ const absent = absentCompanionSurfaces(agent)
337
+ if (absent !== undefined && absent.length > 0) {
285
338
  pending.add(agent)
286
339
  return Promise.resolve()
287
340
  }
@@ -295,6 +348,7 @@ export function createCordisToolsOwner(
295
348
  disposal: undefined,
296
349
  activated: false,
297
350
  disposed: false,
351
+ withdrawn: false,
298
352
  }
299
353
  mounts.set(agent, mount)
300
354
  mount.activation = Promise.resolve().then(async () => {
@@ -340,10 +394,51 @@ export function createCordisToolsOwner(
340
394
  return mount.activation
341
395
  }
342
396
 
397
+ /**
398
+ * Report one agent scope this owner cannot serve and stop retrying it.
399
+ *
400
+ * A scope whose exposed preset, services, provider or fiber activation
401
+ * contradicts the DSH contract is not repaired by a later tool registration,
402
+ * and DSH sessions create many scopes, so the same failure is reported once
403
+ * per scope instead of once per turn. The mount is marked so later calls
404
+ * resolve immediately rather than repeating the attempt and its diagnostic.
405
+ */
406
+ const withdraw = (agent, error) => {
407
+ const mount = mounts.get(agent)
408
+ if (mount !== undefined) mount.withdrawn = true
409
+ pending.delete(agent)
410
+ const message = error instanceof Error ? error.message : String(error)
411
+ if (withdrawn.get(agent) === message) return
412
+ withdrawn.set(agent, message)
413
+ ctx.logger?.warn?.('ptc-plus: Cordis companion tooling withdrew from an agent scope', error)
414
+ }
415
+
416
+ /**
417
+ * Install for a call the host makes on its own behalf.
418
+ *
419
+ * DSH 0.1.6 announces `agent/created` serially and rejects `agents.create()`
420
+ * when a listener rejects, so a scope this owner cannot serve must withdraw
421
+ * instead of failing an operation another plugin asked for. Install-time
422
+ * enumeration stays strict: there the plugin speaks for what it claims to
423
+ * have published, and `ready` reports the same failure.
424
+ */
425
+ const installForHost = (agent) => {
426
+ let activation
427
+ try {
428
+ activation = installAgent(agent)
429
+ } catch (error) {
430
+ withdraw(agent, error)
431
+ return Promise.resolve()
432
+ }
433
+ return Promise.resolve(activation).catch(error => {
434
+ withdraw(agent, error)
435
+ })
436
+ }
437
+
343
438
  const promptAssembly = async (_assembly, context, next) => {
344
439
  const agent = context?.scope
345
440
  if (agent === undefined || mounts.get(agent)?.activated === true) return next()
346
- await installAgent(agent)
441
+ await installForHost(agent)
347
442
  if (disposed || mounts.get(agent)?.activated !== true) return next()
348
443
  if (typeof ctx.systemPrompt?.assemble !== 'function') {
349
444
  throw new Error('ptc-plus: cordisToolsEnabled requires the DSH systemPrompt.assemble API')
@@ -369,7 +464,7 @@ export function createCordisToolsOwner(
369
464
  let initialActivations
370
465
  try {
371
466
  stopPromptAssembly = ctx.on('system-prompt/assemble', promptAssembly, { prepend: true })
372
- stopCreated = ctx.on('agent/created', ({ agent }) => installAgent(agent))
467
+ stopCreated = ctx.on('agent/created', ({ agent }) => installForHost(agent))
373
468
  stopDisposed = ctx.on('agent/disposed', ({ agent }) => disposeAgent(agent))
374
469
  stopToolsChange = ctx.on('tools/change', retryPending)
375
470
  initialActivations = ctx.agents.list().map(installAgent)
@@ -387,26 +482,40 @@ export function createCordisToolsOwner(
387
482
  }
388
483
 
389
484
  let disposal
485
+ let leasesDisposed = false
390
486
  const dispose = () => {
391
487
  if (disposal !== undefined) return disposal
392
488
  disposed = true
393
- stopToolsChange()
394
- stopDisposed()
395
- stopCreated()
396
- stopPromptAssembly()
489
+ stopToolsChange?.()
490
+ stopToolsChange = undefined
491
+ stopDisposed?.()
492
+ stopDisposed = undefined
493
+ stopCreated?.()
494
+ stopCreated = undefined
495
+ stopPromptAssembly?.()
496
+ stopPromptAssembly = undefined
397
497
  pending.clear()
498
+ withdrawn.clear()
398
499
  const owned = [...mounts.entries()]
399
500
  const attempts = owned.map(([agent, mount]) => disposeMount(agent, mount))
400
- disposal = Promise.allSettled(attempts).then(results => {
501
+ const operation = Promise.allSettled(attempts).then(results => {
401
502
  const failures = results.filter(result => result.status === 'rejected').map(result => result.reason)
402
- try {
403
- cordisInspectLeases.dispose()
404
- } catch (error) {
405
- failures.push(error)
503
+ if (!leasesDisposed) {
504
+ try {
505
+ cordisInspectLeases.dispose()
506
+ leasesDisposed = true
507
+ } catch (error) {
508
+ failures.push(error)
509
+ }
406
510
  }
407
511
  if (failures.length > 0) throw new AggregateError(failures, 'ptc-plus Cordis disposal failed')
408
512
  })
409
- return disposal
513
+ disposal = operation
514
+ operation.then(
515
+ () => { if (disposal === operation) disposal = undefined },
516
+ () => { if (disposal === operation) disposal = undefined },
517
+ )
518
+ return operation
410
519
  }
411
520
  const ready = Promise.all(initialActivations).then(() => undefined).catch(async error => {
412
521
  try {
@@ -3,9 +3,10 @@ import { assertOwnFields, isRecord } from './record-utils.js'
3
3
 
4
4
  const DIAGNOSTIC_FIELDS = new Set([
5
5
  'code', 'severity', 'phase', 'message', 'stateEffect', 'dispatchState',
6
- 'source', 'cause', 'help',
6
+ 'source', 'cause', 'help', 'collisions',
7
7
  ])
8
8
  const SOURCE_FIELDS = new Set(['cell', 'start', 'end'])
9
+ const COLLISION_FIELDS = new Set(['name', 'kind', 'reason', 'start', 'end'])
9
10
  const POSITION_FIELDS = new Set(['line', 'column'])
10
11
  const CAUSE_FIELDS = new Set(['code', 'message'])
11
12
  const SEVERITIES = new Set(['error', 'warning', 'note'])
@@ -26,6 +27,23 @@ function normalizePosition(value, label) {
26
27
  return Object.freeze({ line: value.line, column: value.column })
27
28
  }
28
29
 
30
+ /** One collision record as its compiler owner determined it: the colliding name, its
31
+ * declaration kind, the reason the producer rejected it, and the original source span. */
32
+ function normalizeCollision(value) {
33
+ if (!isRecord(value)) throw new Error('invalid dsh-ptc-plus diagnostic collision')
34
+ assertOwnFields(value, COLLISION_FIELDS, 'diagnostic collision')
35
+ if (!isLine(value.name) || !isLine(value.kind) || !isLine(value.reason)) {
36
+ throw new Error('invalid dsh-ptc-plus diagnostic collision')
37
+ }
38
+ return Object.freeze({
39
+ name: value.name,
40
+ kind: value.kind,
41
+ reason: value.reason,
42
+ start: normalizePosition(value.start, 'collision start'),
43
+ ...(value.end === undefined ? {} : { end: normalizePosition(value.end, 'collision end') }),
44
+ })
45
+ }
46
+
29
47
  function normalizeCause(value, depth) {
30
48
  if (depth > 16) throw new Error('diagnostic cause chain is too deep')
31
49
  if (!isRecord(value)) throw new Error('invalid diagnostic cause')
@@ -77,6 +95,13 @@ export function normalizeDiagnostic(value, depth = 0) {
77
95
  }
78
96
  let cause
79
97
  if (value.cause !== undefined) cause = normalizeCause(value.cause, depth)
98
+ let collisions
99
+ if (value.collisions !== undefined) {
100
+ if (!Array.isArray(value.collisions) || value.collisions.length === 0) {
101
+ throw new Error('invalid dsh-ptc-plus diagnostic collisions')
102
+ }
103
+ collisions = Object.freeze(value.collisions.map(collision => normalizeCollision(collision)))
104
+ }
80
105
  let help
81
106
  if (value.help !== undefined) {
82
107
  if (!Array.isArray(value.help) || value.help.length > 3
@@ -94,6 +119,7 @@ export function normalizeDiagnostic(value, depth = 0) {
94
119
  ...(value.dispatchState === undefined ? {} : { dispatchState: value.dispatchState }),
95
120
  ...(source === undefined ? {} : { source }),
96
121
  ...(cause === undefined ? {} : { cause }),
122
+ ...(collisions === undefined ? {} : { collisions }),
97
123
  ...(help === undefined ? {} : { help }),
98
124
  })
99
125
  }
@@ -11,8 +11,10 @@ import {
11
11
  generatedRunCodeExecutionArguments,
12
12
  generatedRunCodeDescriptionMeta,
13
13
  } from './run-code-description.js'
14
+ import { userBindingsConfiguredContext } from './user-bindings.js'
15
+ import { BINDING_AUTHORING_SDK } from './user-binding-authoring.js'
14
16
 
15
- const RUN_CODE_TOOL_DESCRIPTION = 'Evaluate the next TypeScript cell in this session-bound persistent REPL. Earlier top-level bindings remain available, so this call extends the current environment instead of creating a fresh one. Use `code` for the async-function body and `description` for its short UI summary. Successful image-bearing subtool results are attached after the cell.'
17
+ const RUN_CODE_TOOL_DESCRIPTION = 'Evaluate the next TypeScript cell in this session-bound persistent REPL, reusing earlier top-level bindings. Use `code` for the async-function body and `description` for its short UI summary. Successful image-bearing subtool results are attached after the cell.'
16
18
  const RUN_CODE_CODE_DESCRIPTION = 'Code for the next REPL cell, parsed as the body of an async TypeScript function.'
17
19
  const RUN_CODE_DESCRIPTION_DESCRIPTION = 'Short active-voice summary of what this cell does, 5-10 words (shown in the UI).'
18
20
  const CODE_TRANSPORT_INSTRUCTION = '`run_code` and `edit_run_code` are the only tools callable directly. Call every native tool declared by the SDK from inside a program.'
@@ -42,11 +44,13 @@ function adaptRunCodeSchema(tool) {
42
44
  }
43
45
  }
44
46
 
45
- function capabilitySdk(nativeSdk) {
47
+ function capabilitySdk(nativeSdk, userBindingsEnabled) {
46
48
  return `${projectProgramSdk(nativeSdk)}
47
49
 
48
50
  ## PTC Plus program capabilities
49
51
 
52
+ Discover program APIs without calling them. \`tree()\` lists namespaces and members; \`inspect()\` describes selected \`namespace.member\` symbols. \`find()\` matches case-insensitive symbols or contiguous words, separating CamelCase and punctuation. Use short queries such as \`"read"\`; if none match, shorten the query or check \`tree()\`. Missing metadata is unknown.
53
+
50
54
  \`\`\`ts
51
55
  declare class CapabilityExplorationError extends Error { readonly operation: "tree" | "find" | "inspect" }
52
56
  declare const capabilities: {
@@ -54,7 +58,7 @@ declare const capabilities: {
54
58
  find(query: string): Promise<Array<{ symbol: string; description?: string; completeness: string; effect: string; replay: string }>>
55
59
  inspect(args?: { symbols?: string[]; budget?: number }): Promise<{ symbols: unknown[]; omitted: number; unknown: string[]; budget: number }>
56
60
  }
57
- \`\`\``
61
+ \`\`\`${userBindingsEnabled ? `\n${BINDING_AUTHORING_SDK}` : ''}`
58
62
  }
59
63
 
60
64
  function rejection(message) {
@@ -141,6 +145,8 @@ export function createDirectSurfaceOwner({
141
145
  canonicalizeToolCalls,
142
146
  sessionId,
143
147
  toolSchemasForAgent,
148
+ userBindingsForAgent = async () => undefined,
149
+ setAgentPresentation = async () => {},
144
150
  }) {
145
151
  // Composition is anchored to Agent identity because DSH selects presentation
146
152
  // once per composed agent. Request signals bind the exact assembly to stream
@@ -151,6 +157,7 @@ export function createDirectSurfaceOwner({
151
157
  const canonicalRequests = new WeakMap()
152
158
  const sessions = new Map()
153
159
  const cordisRecovery = createCordisRecoveryPolicy(runtimeConfig.cordisToolsEnabled)
160
+ let disposed = false
154
161
  let currentCanonicalizeToolCalls = canonicalizeToolCalls
155
162
  let currentAutoDescribeRunCode = runtimeConfig.autoDescribeRunCode
156
163
  const tipConfig = {
@@ -193,9 +200,31 @@ export function createDirectSurfaceOwner({
193
200
  return undefined
194
201
  }
195
202
 
196
- const rememberRequest = (id, signal, presentation, nativeSchemas, autoDescribeRunCode) => {
203
+ // One dispatch resolves the policy captured for its exact call, then the
204
+ // request bound to its signal, then the session's latest request.
205
+ const dispatchPolicy = (exec) => {
206
+ const id = sessionId(exec.agent)
207
+ return executionPolicy(id, exec.callId) ?? requestPolicy(exec?.signal, id)
208
+ }
209
+
210
+ const rememberRequest = (
211
+ id,
212
+ signal,
213
+ presentation,
214
+ nativeSchemas,
215
+ autoDescribeRunCode,
216
+ userBindings,
217
+ contexts = [],
218
+ ) => {
197
219
  const owner = sessionOwner(id)
198
- const request = { presentation, nativeSchemas, autoDescribeRunCode, owner }
220
+ const request = {
221
+ presentation,
222
+ nativeSchemas,
223
+ autoDescribeRunCode,
224
+ owner,
225
+ contexts,
226
+ ...(userBindings === undefined ? {} : { userBindings }),
227
+ }
199
228
  owner.latestRequest = request
200
229
  if (signal !== undefined) canonicalRequests.set(signal, request)
201
230
  }
@@ -228,6 +257,9 @@ export function createDirectSurfaceOwner({
228
257
  const autoDescribeRunCode = currentAutoDescribeRunCode
229
258
  const agent = context?.agent
230
259
  const id = sessionId(agent)
260
+ const requestOwner = id === undefined ? undefined : sessionOwner(id)
261
+ const isCurrent = () => !disposed && (requestOwner === undefined
262
+ || (requestOwner.active && sessions.get(id) === requestOwner))
231
263
  const initialState = presentationState(initialAssembly)
232
264
  let composition = compositions.get(agent)
233
265
  if (composition === undefined && id !== undefined
@@ -239,6 +271,7 @@ export function createDirectSurfaceOwner({
239
271
  }
240
272
 
241
273
  const assembly = await next()
274
+ if (!isCurrent()) return assembly
242
275
  const tools = assembly.tools
243
276
  if (!Array.isArray(tools)) {
244
277
  throw new Error('ptc-plus: incompatible prompt assembly; expected a tools array')
@@ -260,16 +293,22 @@ export function createDirectSurfaceOwner({
260
293
  if (presentation !== 'native') {
261
294
  throw new Error(`ptc-plus: ${presentation} agent composition assembled without run_code`)
262
295
  }
296
+ await setAgentPresentation(agent, presentation)
297
+ if (!isCurrent()) return assembly
263
298
  if (id !== undefined) {
264
- rememberRequest(id, requestSignal, 'native', new Map(), autoDescribeRunCode)
299
+ rememberRequest(id, requestSignal, 'native', new Map(), autoDescribeRunCode, undefined)
265
300
  }
266
301
  return assembly
267
302
  }
268
303
  if (presentation === 'native') {
269
304
  throw new Error('ptc-plus: native agent composition assembled with run_code')
270
305
  }
306
+ await setAgentPresentation(agent, presentation)
307
+ if (!isCurrent()) return assembly
271
308
  const sessionPtc = presentation !== 'native' && id !== undefined
272
309
  const sessionPtcProjection = presentation === 'ptc' && id !== undefined
310
+ const userBindings = sessionPtc ? await userBindingsForAgent(agent) : undefined
311
+ if (!isCurrent()) return assembly
273
312
  const runCode = tools.find(tool => tool?.name === RUN_CODE)
274
313
  let directTools = tools
275
314
  if (sessionPtcProjection) {
@@ -283,17 +322,18 @@ export function createDirectSurfaceOwner({
283
322
  },
284
323
  })
285
324
  : { contexts: [] }
286
- const contexts = sessionPtc && Array.isArray(assembly.contexts)
287
- && runtimeContexts.contexts.length > 0
288
- ? [...assembly.contexts, ...runtimeContexts.contexts]
289
- : assembly.contexts
325
+ const bindingDefaults = userBindings === undefined ? undefined : userBindingsConfiguredContext(userBindings)
326
+ const contexts = [
327
+ ...runtimeContexts.contexts,
328
+ ...(bindingDefaults === undefined ? [] : [bindingDefaults]),
329
+ ]
290
330
  const projectsSections = presentation !== 'native' && Array.isArray(assembly.sections)
291
331
  && assembly.sections.some(section => section?.name === 'tools:sdk'
292
332
  || (sessionPtcProjection && section?.name === completedState.collapseSectionName))
293
333
  const sections = projectsSections
294
334
  ? assembly.sections.map(section => {
295
335
  if (section?.name === 'tools:sdk') {
296
- return { ...section, text: capabilitySdk(section.text) }
336
+ return { ...section, text: capabilitySdk(section.text, userBindings !== undefined) }
297
337
  }
298
338
  if (sessionPtcProjection && section?.name === completedState.collapseSectionName) {
299
339
  return { ...section, text: CODE_TRANSPORT_INSTRUCTION }
@@ -301,7 +341,6 @@ export function createDirectSurfaceOwner({
301
341
  return section
302
342
  })
303
343
  : assembly.sections
304
-
305
344
  if (id !== undefined) {
306
345
  const nativeSchemas = presentation === 'ptc'
307
346
  ? new Map(toolSchemasForAgent(agent)
@@ -309,7 +348,15 @@ export function createDirectSurfaceOwner({
309
348
  && schema.name !== RUN_CODE && schema.name !== EDIT_RUN_CODE)
310
349
  .map(schema => [schema.name, schema]))
311
350
  : new Map()
312
- rememberRequest(id, requestSignal, presentation, nativeSchemas, autoDescribeRunCode)
351
+ rememberRequest(
352
+ id,
353
+ requestSignal,
354
+ presentation,
355
+ nativeSchemas,
356
+ autoDescribeRunCode,
357
+ userBindings,
358
+ contexts,
359
+ )
313
360
  }
314
361
 
315
362
  return {
@@ -320,9 +367,11 @@ export function createDirectSurfaceOwner({
320
367
  ? adaptRunCodeSchema(tool)
321
368
  : tool),
322
369
  sections,
323
- contexts,
324
370
  }
325
371
  },
372
+ contextsForRequest(context) {
373
+ return requestPolicy(context.signal, sessionId(context.agent))?.contexts ?? []
374
+ },
326
375
  stream(options, next) {
327
376
  const optionSessionId = options.sessionId === undefined ? undefined : String(options.sessionId)
328
377
  const policy = requestPolicy(options?.signal, optionSessionId)
@@ -340,9 +389,7 @@ export function createDirectSurfaceOwner({
340
389
  if (exec.name === EDIT_RUN_CODE && exec.parent !== undefined) {
341
390
  return rejection(`tool ${EDIT_RUN_CODE} is only callable directly in PTC mode; call native tools from inside run_code`)
342
391
  }
343
- const id = sessionId(exec.agent)
344
- const policy = executionPolicy(id, exec.callId)
345
- ?? requestPolicy(exec?.signal, id)
392
+ const policy = dispatchPolicy(exec)
346
393
  if (exec.name === EDIT_RUN_CODE) {
347
394
  return policy?.presentation === 'ptc'
348
395
  ? undefined
@@ -355,9 +402,7 @@ export function createDirectSurfaceOwner({
355
402
  return rejection(`tool ${exec.name} is not a direct PTC tool; use run_code or edit_run_code directly, and call native tools from inside run_code`)
356
403
  },
357
404
  executionArguments(exec) {
358
- const id = sessionId(exec.agent)
359
- const policy = executionPolicy(id, exec.callId)
360
- ?? requestPolicy(exec?.signal, id)
405
+ const policy = dispatchPolicy(exec)
361
406
  const originalArguments = exec.arguments
362
407
  if (exec.name !== RUN_CODE || exec.parent !== undefined
363
408
  || policy?.autoDescribeRunCode !== true || policy.presentation === 'native'
@@ -367,6 +412,10 @@ export function createDirectSurfaceOwner({
367
412
  }
368
413
  return generatedRunCodeExecutionArguments(originalArguments)
369
414
  },
415
+ executionUserBindings(exec) {
416
+ const policy = dispatchPolicy(exec)
417
+ return policy?.presentation === 'native' ? undefined : policy?.userBindings
418
+ },
370
419
  argumentDiagnostic(exec, result) {
371
420
  // DSH uses success-result identity to avoid re-running output projectors
372
421
  // after this middleware's settlement scope has ended.
@@ -377,9 +426,7 @@ export function createDirectSurfaceOwner({
377
426
  : { ...result, ...(meta === undefined ? {} : { meta }) }
378
427
  const missingPath = missingDescriptionPath(diagnosed.error)
379
428
  if (missingPath === undefined) return diagnosed
380
- const id = sessionId(exec.agent)
381
- const policy = executionPolicy(id, exec.callId)
382
- ?? requestPolicy(exec?.signal, id)
429
+ const policy = dispatchPolicy(exec)
383
430
  const outer = missingPath === 'description'
384
431
  && policy?.autoDescribeRunCode !== true
385
432
  && !(isRecord(exec.arguments) && typeof exec.arguments.description === 'string')
@@ -410,7 +457,13 @@ export function createDirectSurfaceOwner({
410
457
  clearCompositionsForSession(id)
411
458
  clearSession(id)
412
459
  },
460
+ resetSessionComposition(session) {
461
+ const id = String(session.id ?? session)
462
+ clearCompositionsForSession(id)
463
+ clearSession(id)
464
+ },
413
465
  dispose() {
466
+ disposed = true
414
467
  compositions.clear()
415
468
  for (const owner of sessions.values()) owner.active = false
416
469
  sessions.clear()
@@ -0,0 +1,16 @@
1
+ import { parseExpression } from '@babel/parser'
2
+
3
+ /** Dynamic names require both a native identifier and their exact source origin. */
4
+ export function dynamicBindingOrigin(target, name, origins) {
5
+ if (typeof target !== 'string' || typeof name !== 'string') return undefined
6
+ const suffix = `:${JSON.stringify(name)}`
7
+ if (!target.endsWith(suffix)) return undefined
8
+ const origin = target.slice(0, -suffix.length)
9
+ if (!origins.has(origin)) return undefined
10
+ try {
11
+ const identifier = parseExpression(name)
12
+ return identifier.type === 'Identifier' && identifier.name === name ? origin : undefined
13
+ } catch {
14
+ return undefined
15
+ }
16
+ }