dsh-ptc-plus 0.3.2 → 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 (199) hide show
  1. package/README.en.md +65 -78
  2. package/README.md +65 -78
  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 +30596 -742
  12. package/compiler-amaro.cjs +5 -0
  13. package/compiler-amaro.cjs.map +6 -0
  14. package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
  15. package/compiler-assets.json +3 -0
  16. package/compiler-core.cjs +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 +5 -3
  25. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +1 -1
  26. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
  27. package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
  28. package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
  29. package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
  30. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +3 -1
  31. package/docs/adr/0014-persist-cell-rewrite-policy.md +6 -2
  32. package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
  33. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  34. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
  35. package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
  36. package/docs/adr/0019-plugin-settings-and-kill-switch.md +5 -3
  37. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  38. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +12 -0
  39. package/docs/adr/0023-global-user-bindings.md +104 -0
  40. package/docs/adr/0024-repl-console-observation.md +33 -0
  41. package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
  42. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
  43. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
  44. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
  45. package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
  46. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
  47. package/docs/architecture.md +73 -21
  48. package/docs/binding-review-placement-design.md +200 -0
  49. package/docs/capability-projection.md +1 -1
  50. package/docs/client-ui.md +95 -19
  51. package/docs/durability-design.md +25 -10
  52. package/docs/evaluation.md +78 -6
  53. package/docs/installation.md +41 -6
  54. package/docs/publishing.md +5 -3
  55. package/docs/runtime-reference.md +72 -25
  56. package/docs/semantic-validation.md +29 -0
  57. package/docs/stateful-binding-updates-proposal.md +755 -0
  58. package/docs/user-bindings.en.md +64 -0
  59. package/docs/user-bindings.md +64 -0
  60. package/docs/value-wire.md +3 -3
  61. package/docs/verification.md +57 -0
  62. package/index.js +369 -92
  63. package/internal/ast-traversal.js +5 -2
  64. package/internal/binding-descriptors.js +1 -0
  65. package/internal/binding-pattern-traversal.js +25 -0
  66. package/internal/binding-pattern.js +26 -29
  67. package/internal/binding-source-analysis.js +391 -0
  68. package/internal/binding-update-policy.js +66 -0
  69. package/internal/bounded-worker-round.js +56 -0
  70. package/internal/callable-definition-environment.js +42 -0
  71. package/internal/callable-source-catalog.js +129 -0
  72. package/internal/callable-source-encoding.js +47 -0
  73. package/internal/callable-source-facts.js +459 -0
  74. package/internal/callable-source-format.js +2 -0
  75. package/internal/callable-source-lowering.js +123 -0
  76. package/internal/cell-analysis-contract.js +25 -0
  77. package/internal/cell-analysis.js +240 -290
  78. package/internal/cell-completion.js +15 -0
  79. package/internal/cell-error.js +7 -0
  80. package/internal/cell-parser.js +66 -0
  81. package/internal/cell-rewriter.js +3 -677
  82. package/internal/commonjs-export-evidence.js +63 -0
  83. package/internal/commonjs-source-evidence.js +72 -0
  84. package/internal/compiler-amaro.cjs +2 -0
  85. package/internal/compiler-compression.js +12 -0
  86. package/internal/compiler-data.js +58 -0
  87. package/internal/compiler-descriptors.js +54 -0
  88. package/internal/compiler-dynamic-cache.js +48 -0
  89. package/internal/compiler-entry.js +35 -0
  90. package/internal/compiler-generated-names.js +17 -0
  91. package/internal/compiler-intrinsics.js +84 -0
  92. package/internal/compiler-module-links.js +34 -0
  93. package/internal/compiler-operations.js +61 -0
  94. package/internal/compiler-platform-factory.js +84 -0
  95. package/internal/compiler-platform.js +136 -0
  96. package/internal/compiler-record-roles.js +53 -0
  97. package/internal/compiler-region-output.js +259 -0
  98. package/internal/compiler-resource-lowering.js +103 -0
  99. package/internal/compiler-scope-facts.js +115 -0
  100. package/internal/compiler-service.js +109 -0
  101. package/internal/compiler-source-cache.js +32 -0
  102. package/internal/compiler-source-pieces.js +93 -0
  103. package/internal/compiler-source-regions.js +132 -0
  104. package/internal/compiler-storage-source.js +54 -0
  105. package/internal/compiler-text.js +15 -0
  106. package/internal/compiler-typescript.cjs +2 -0
  107. package/internal/config-spec.js +83 -27
  108. package/internal/cordis-tools-owner.js +130 -21
  109. package/internal/diagnostic.js +27 -1
  110. package/internal/direct-surface-owner.js +76 -23
  111. package/internal/dynamic-binding-evidence.js +16 -0
  112. package/internal/dynamic-environment-compiler.js +779 -0
  113. package/internal/dynamic-environment-integration.js +87 -0
  114. package/internal/dynamic-environment-runtime.js +362 -0
  115. package/internal/dynamic-native-calls.js +150 -0
  116. package/internal/dynamic-native-runtime.js +248 -0
  117. package/internal/dynamic-scope-analysis.js +90 -0
  118. package/internal/edit-transport-owner.js +27 -2
  119. package/internal/execution-seam-compat.js +193 -0
  120. package/internal/failure-reporting.js +118 -18
  121. package/internal/host-rpc.js +66 -0
  122. package/internal/isolated-worker.js +548 -0
  123. package/internal/kernel-child.js +139 -0
  124. package/internal/kernel-worker.js +904 -120
  125. package/internal/language-semantics.js +14 -0
  126. package/internal/legacy-binding-catalog.js +48 -0
  127. package/internal/legacy-cell-analysis.js +600 -0
  128. package/internal/legacy-cell-parser.js +50 -0
  129. package/internal/legacy-cell-rewriter.js +631 -0
  130. package/internal/legacy-repl-convenience.js +291 -0
  131. package/internal/managed-module-operations.js +170 -0
  132. package/internal/module-compilation.js +321 -0
  133. package/internal/module-runtime-platform.js +19 -0
  134. package/internal/module-transform-contract.js +12 -0
  135. package/internal/native-await-declarations.js +68 -0
  136. package/internal/native-root-compilation.js +54 -0
  137. package/internal/native-root-dynamic.js +67 -0
  138. package/internal/recovery-tips.js +27 -15
  139. package/internal/rejected-cell-editor.js +13 -8
  140. package/internal/repl-convenience.js +2 -289
  141. package/internal/repl-memory-projection.js +81 -12
  142. package/internal/repl-observation-interest.js +70 -0
  143. package/internal/repl-rewrite-contract.js +6 -0
  144. package/internal/repl-scope-normalizer.js +2091 -0
  145. package/internal/repl-value-observer.js +114 -0
  146. package/internal/rpc-contract.js +52 -0
  147. package/internal/runtime-bridge-owner.js +172 -28
  148. package/internal/runtime-config.js +48 -52
  149. package/internal/runtime-contexts.js +82 -10
  150. package/internal/runtime-messages.js +111 -0
  151. package/internal/session-cell-executor.js +401 -87
  152. package/internal/session-journal-recovery.js +684 -0
  153. package/internal/session-journal-schema.js +106 -0
  154. package/internal/session-journal.js +184 -654
  155. package/internal/session-log-view.js +42 -9
  156. package/internal/session-runtime.js +256 -95
  157. package/internal/session-state.js +247 -71
  158. package/internal/source-position-map.js +211 -71
  159. package/internal/stateful-module-compiler.js +154 -0
  160. package/internal/stateful-module-runtime.js +303 -0
  161. package/internal/stateful-root-compiler.js +598 -0
  162. package/internal/stateful-root-runtime.js +340 -0
  163. package/internal/typert-codec-compat.js +28 -0
  164. package/internal/typescript-transform.js +29 -0
  165. package/internal/user-binding-authoring.js +70 -0
  166. package/internal/user-binding-console-worker.js +184 -0
  167. package/internal/user-binding-console.js +148 -0
  168. package/internal/user-binding-draft-projection.js +335 -0
  169. package/internal/user-binding-model-context.js +31 -0
  170. package/internal/user-binding-runner.js +56 -0
  171. package/internal/user-bindings-owner.js +1065 -0
  172. package/internal/user-bindings-store.js +293 -0
  173. package/internal/user-bindings.js +353 -0
  174. package/internal/value-wire-schema.js +28 -0
  175. package/internal/value-wire.js +29 -20
  176. package/internal/worker-client.js +57 -67
  177. package/internal/worker-environment.js +45 -0
  178. package/internal/worker-output-relay.js +29 -0
  179. package/internal/worker-shutdown.js +14 -0
  180. package/package.json +75 -7
  181. package/src/client-activity.js +88 -53
  182. package/src/client-authoring-view.js +580 -0
  183. package/src/client-binding-review.js +230 -0
  184. package/src/client-bindings-data.js +29 -0
  185. package/src/client-catalog.js +127 -0
  186. package/src/client-code-editor.js +84 -0
  187. package/src/client-console.js +112 -0
  188. package/src/client-copy.js +370 -0
  189. package/src/client-feature-gates.js +62 -0
  190. package/src/client-host-compat.js +150 -0
  191. package/src/client-indicator-view.js +267 -0
  192. package/src/client-repl-view.js +292 -0
  193. package/src/client-rpc.js +20 -0
  194. package/src/client-settings-view.js +221 -0
  195. package/src/client-styles.js +106 -0
  196. package/src/client-tool-view.js +108 -0
  197. package/src/client-transport.js +159 -0
  198. package/src/client-workbench.js +521 -0
  199. package/src/client.js +233 -615
package/index.js CHANGED
@@ -1,14 +1,16 @@
1
1
  /**
2
2
  * Session-bound REPL for DeepSeek Harness PTC mode.
3
3
  *
4
- * DSH's run_code bridge does not pass session identity to CodeRuntime.run().
5
- * The tools/execute around-hook carries that identity into the runtime bridge,
6
- * which redirects only those runs to a persistent per-session kernel.
4
+ * DSH's run_code bridge does not pass session identity to the execution seam it
5
+ * calls. The tools/execute around-hook carries that identity into the runtime
6
+ * bridge, which redirects only those runs to a persistent per-session kernel.
7
7
  */
8
8
 
9
9
  import Schema from '@deepseek-ai/schemastery'
10
10
  import { randomUUID } from 'node:crypto'
11
11
  import { createDirectSurfaceOwner } from './internal/direct-surface-owner.js'
12
+ import { createRuntimeMessageOwner } from './internal/runtime-contexts.js'
13
+ import { PTC_DELIVERY_CONTEXT } from './internal/runtime-messages.js'
12
14
  import { createCordisToolsOwner } from './internal/cordis-tools-owner.js'
13
15
  import { createEditTransportOwner, EDIT_RUN_CODE } from './internal/edit-transport-owner.js'
14
16
  import { createRuntimeBridgeOwner, RUN_CODE } from './internal/runtime-bridge-owner.js'
@@ -18,7 +20,14 @@ import {
18
20
  SETTINGS_NAMESPACE,
19
21
  } from './internal/config-spec.js'
20
22
  import { installSettingsSectionCompat } from './internal/settings-compat.js'
23
+ import { installExecutionSeam } from './internal/execution-seam-compat.js'
24
+ import { valueLimitsFromConfig } from './internal/value-wire-schema.js'
21
25
  import { createReplMemoryProjection } from './internal/repl-memory-projection.js'
26
+ import { createUserBindingDraftProjection } from './internal/user-binding-draft-projection.js'
27
+ import { createUserBindingsOwner } from './internal/user-bindings-owner.js'
28
+ import { createHostRpc } from './internal/host-rpc.js'
29
+ import { createReplObservationInterest } from './internal/repl-observation-interest.js'
30
+ import { guidancePolicies } from './internal/binding-update-policy.js'
22
31
  import * as dshSettings from '@deepseek-ai/dsh-settings'
23
32
 
24
33
  const INSTALL_CLEANUP = Symbol('ptc-plus install cleanup')
@@ -30,7 +39,10 @@ export const name = 'ptc-plus'
30
39
  function configSchemaField(field) {
31
40
  const base = field.type === 'boolean'
32
41
  ? Schema.boolean().default(field.default)
33
- : Schema.number().step(1).min(field.min).max(field.max).default(field.default)
42
+ : field.type === 'enum'
43
+ // Keep omission visible until resolveConfig migrates legacy policies.
44
+ ? Schema.union(field.options.map(option => Schema.const(option)))
45
+ : Schema.number().step(1).min(field.min).max(field.max).default(field.default)
34
46
  return base.description(field.description)
35
47
  }
36
48
 
@@ -38,18 +50,20 @@ export const Config = Schema.object(Object.fromEntries(
38
50
  CONFIG_FIELDS.map(field => [field.key, configSchemaField(field)]),
39
51
  ))
40
52
 
41
- /** Services required by the plugin. */
42
- export const inject = ['tools', 'codeRuntime', 'systemPrompt', 'agents', 'llm']
53
+ /**
54
+ * Core services required by the plugin. Optional authoring services are
55
+ * injected on demand. The execution seam is not listed here: the host registers
56
+ * it under the service name of one generation, so `installExecutionSeam`
57
+ * selects the live one.
58
+ */
59
+ export const inject = ['tools', 'systemPrompt', 'agents', 'llm']
43
60
 
44
- function replGuidance(
45
- looseTopLevelRedeclarations,
46
- looseTopLevelFunctionClassRedeclarations,
47
- durableReplay,
48
- autoRewriteImports,
49
- autoStripExports,
50
- autoSplitRedeclarations,
51
- cordisToolsEnabled,
52
- ) {
61
+ function replGuidance({ bindingPolicy, rewritesEnabled, languageSemantics, durableReplay, cordisToolsEnabled }) {
62
+ const looseTopLevelRedeclarations = bindingPolicy.variableRedeclarations
63
+ const looseTopLevelFunctionClassRedeclarations = bindingPolicy.functionClassRedeclarations
64
+ const autoRewriteImports = rewritesEnabled.autoRewriteImports
65
+ const autoStripExports = rewritesEnabled.autoStripExports
66
+ const autoSplitRedeclarations = rewritesEnabled.autoSplitRedeclarations
53
67
  const variableRedeclaration = looseTopLevelRedeclarations
54
68
  ? 'Repeated top-level `const`/`let` declarations replace existing bindings.'
55
69
  : 'Repeated top-level variable declarations fail before execution, so reuse existing bindings or place one-off declarations inside a block.'
@@ -66,72 +80,133 @@ function replGuidance(
66
80
  const splitSyntax = autoSplitRedeclarations
67
81
  ? 'Mixed new/existing top-level destructuring is split automatically while preserving assignment semantics.'
68
82
  : 'Mixed new/existing top-level destructuring remains unsupported; separate the declaration from the assignment.'
83
+ const cellConventions = languageSemantics === 'legacy-v1'
84
+ ? `Cells are async function bodies; ${moduleSyntax} Use dynamic import or require explicitly when static module syntax is unsupported. ${variableRedeclaration} ${functionClassRedeclaration} ${splitSyntax}`
85
+ : `Cells support TypeScript, top-level await, return, static import and top-level export. ${languageSemantics === 'stateful-v1'
86
+ ? 'Declarations and assignments update the same logical binding within its scope, including const, functions, classes and imports. A bare declaration preserves an existing value. A declaration publishes its bindings after its whole pattern initializes; explicit assignments preserve actual partial writes. Imports follow their module until locally overwritten; importing again changes that same binding source. Existing closures observe later updates, while saved values retain their identity.'
87
+ : 'Redeclaration protection is enabled: existing session names cannot be redeclared, and newly created const and import bindings reject assignment. Other declarations follow native scope rules. Previously writable bindings stay writable.'}`
69
88
  const recovery = durableReplay
70
89
  ? 'Direct Node/OS access remains live but is not replayed after a kernel restart.'
71
90
  : 'Durable replay is disabled for this profile. Bindings remain reusable only in the current process; a new kernel starts empty.'
72
91
  const cordisRecovery = cordisToolsEnabled
73
- ? 'When using Cordis tools, keep large host or client source in a top-level binding before the Cordis call. A Cordis parse or validation error is a runtime failure: bindings assigned before that failure remain live, so retry only the Cordis call with the existing binding instead of resending the source. Treat that binding as the read-only input for the retry.'
92
+ ? 'Keep large Cordis plugin source in a binding before the tool call so it can be reused after an error. A Cordis parse or validation failure can follow external effects; repeat the call only when its retry rules and current results establish that it is safe.'
74
93
  : ''
75
94
  return `\`run_code\` continues one persistent PTC REPL. Ordinary top-level bindings remain available to later cells, so reuse them instead of resending setup code. Choose the smallest cell that answers the request and return only the value the next step needs.
76
95
 
77
- The host may append a bounded recovery context after a qualifying failure. Treat that context as a session-log-derived diagnostic: use \`edit_run_code\` only when it explicitly proves a complete-cell rerun is safe; otherwise inspect live state in a new short \`run_code\` cell.
96
+ After an execution error, earlier statements may have taken effect. Inspect relevant values in a short cell and continue from them; repeat external operations only when their retry rules and current results establish that it is safe. \`edit_run_code\` edits and reruns a complete cell.
78
97
 
79
98
  ## Cell conventions
80
- Expressions that are neither returned nor printed produce no output. Keep large inspection results in bindings or reduce them to targeted excerpts: \`tools.read\` is bounded inspection, not a lossless whole-file reader. Cells are async function bodies; ${moduleSyntax} Use dynamic import or require explicitly when static module syntax is unsupported. ${variableRedeclaration} ${functionClassRedeclaration} ${splitSyntax}
99
+ Use \`return\` for an explicit result and \`console\` for logs. Keep large inspection results in bindings or reduce them to targeted excerpts: \`tools.read\` is bounded inspection, not a lossless whole-file reader. ${cellConventions}
81
100
 
82
101
  ## Available capabilities
83
- Use \`capabilities.tree()\`, \`capabilities.find()\`, and \`capabilities.inspect()\` to discover the current request's live \`tools.*\` members before calling an unfamiliar binding. Prefer direct current-cell work; reserve \`code.run\` for source already held as data.
102
+ Use \`capabilities.tree()\`, \`capabilities.find()\`, and \`capabilities.inspect()\` for unfamiliar program APIs. Prefer direct current-cell work; reserve \`code.run\` for isolated execution of source held as data.
103
+ Program API references expire when their cell ends. Reusable helpers must read the current namespace when called, for example \`async function readNow(args) { return tools.read(args) }\`, rather than retaining an earlier \`tools\` object or method. Background callbacks from completed cells cannot call program APIs.
84
104
 
85
105
  Native tool availability, executable names, shells, and path syntax depend on the current DSH profile and execution world; inspect them instead of assuming Windows, WSL, POSIX, or a particular shell. ${recovery}${cordisToolsEnabled ? ` ${cordisRecovery}` : ''}`
86
106
  }
87
107
 
88
108
  /** Register the session-bound REPL runtime. */
89
- function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId) {
109
+ function installPtCRuntime(ctx, resolvedConfig, seam, toolSchemasForAgent, sessionId) {
90
110
  const presentationGeneration = randomUUID()
91
111
  const replMemoryProjection = createReplMemoryProjection(presentationGeneration)
112
+ const userBindingDraftProjection = createUserBindingDraftProjection(presentationGeneration)
92
113
  let activeConfig = resolvedConfig
93
114
  let cordisTools
115
+ let cordisToolsReady = false
116
+ const cordisOwners = new Set()
94
117
  let runtimeBridge
95
118
  let editTransport
96
119
  let directSurface
120
+ let userBindings
121
+ let observationInterest
97
122
  let ready
98
123
  let pendingCordisActivation
124
+ let draftProjectionRegistration
125
+ let draftProjectionAvailable = false
126
+ let projectionService
99
127
  const disposers = []
128
+ const disposedOwners = new Set()
100
129
  let disposed = false
101
- async function dispose() {
102
- if (disposed) return
130
+ let disposal
131
+ function dispose() {
103
132
  disposed = true
104
133
  pendingCordisActivation?.cancel()
105
- const failures = []
106
- for (const dispose of [...disposers].reverse()) {
107
- if (typeof dispose !== 'function') continue
108
- try {
109
- await dispose()
110
- } catch (error) {
111
- failures.push(error)
112
- }
134
+ if (disposal !== undefined) return disposal
135
+ const tasks = []
136
+ for (let index = disposers.length - 1; index >= 0; index -= 1) {
137
+ const cleanup = disposers[index]
138
+ if (typeof cleanup !== 'function') continue
139
+ tasks.push({
140
+ run: cleanup,
141
+ complete: () => { if (disposers[index] === cleanup) disposers[index] = undefined },
142
+ })
143
+ }
144
+ for (const owner of [directSurface, editTransport, runtimeBridge, userBindings]) {
145
+ if (typeof owner?.dispose !== 'function' || disposedOwners.has(owner)) continue
146
+ tasks.push({ run: () => owner.dispose(), complete: () => disposedOwners.add(owner) })
113
147
  }
114
- for (const owner of [directSurface, editTransport, runtimeBridge, cordisTools]) {
148
+ for (const owner of cordisOwners) {
149
+ tasks.push({
150
+ run: () => owner.dispose(),
151
+ complete: () => {
152
+ cordisOwners.delete(owner)
153
+ if (cordisTools === owner) {
154
+ cordisTools = undefined
155
+ cordisToolsReady = false
156
+ }
157
+ },
158
+ })
159
+ }
160
+ const attempts = tasks.map(task => {
115
161
  try {
116
- await owner?.dispose()
162
+ return Promise.resolve(task.run())
117
163
  } catch (error) {
118
- failures.push(error)
164
+ return Promise.reject(error)
119
165
  }
166
+ })
167
+ const operation = Promise.allSettled(attempts)
168
+ .then(results => {
169
+ const failures = []
170
+ for (let index = 0; index < results.length; index += 1) {
171
+ const result = results[index]
172
+ if (result.status === 'fulfilled') tasks[index].complete()
173
+ else failures.push(result.reason)
174
+ }
175
+ if (failures.length > 0) throw new AggregateError(failures, 'ptc-plus runtime disposal failed')
176
+ })
177
+ disposal = operation
178
+ operation.then(
179
+ () => { if (disposal === operation) disposal = undefined },
180
+ () => { if (disposal === operation) disposal = undefined },
181
+ )
182
+ return operation
183
+ }
184
+ const disposeCordisToolsOwner = async (owner) => {
185
+ await owner.dispose()
186
+ cordisOwners.delete(owner)
187
+ if (cordisTools === owner) {
188
+ cordisTools = undefined
189
+ cordisToolsReady = false
120
190
  }
121
- if (failures.length > 0) throw new AggregateError(failures, 'ptc-plus runtime disposal failed')
191
+ }
192
+ const disposeRetainedCordisTools = async () => {
193
+ const owners = [...cordisOwners]
194
+ const results = await Promise.allSettled(owners.map(owner => disposeCordisToolsOwner(owner)))
195
+ const failures = results.filter(result => result.status === 'rejected').map(result => result.reason)
196
+ if (failures.length > 0) throw new AggregateError(failures, 'ptc-plus: retained Cordis disposal failed')
122
197
  }
123
198
  const awaitCordisToolsOwner = async (owner, cancelled) => {
124
199
  try {
125
200
  await Promise.race([owner.ready, cancelled])
201
+ if (cordisTools === owner) cordisToolsReady = true
126
202
  return owner
127
203
  } catch (error) {
128
204
  let rollbackError
129
205
  try {
130
- await owner.dispose()
206
+ await disposeCordisToolsOwner(owner)
131
207
  } catch (caught) {
132
208
  rollbackError = caught
133
209
  }
134
- if (cordisTools === owner) cordisTools = undefined
135
210
  if (rollbackError !== undefined) {
136
211
  throw new AggregateError(
137
212
  [error, rollbackError],
@@ -143,13 +218,17 @@ function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId)
143
218
  }
144
219
  }
145
220
  const activateCordisToolsOwner = async () => {
221
+ await disposeRetainedCordisTools()
222
+ if (disposed) throw new Error('ptc-plus: Cordis activation cancelled')
146
223
  const owner = createCordisToolsOwner(ctx)
224
+ cordisOwners.add(owner)
147
225
  let cancel
148
226
  const cancelled = new Promise((_resolve, reject) => {
149
227
  cancel = () => reject(new Error('ptc-plus: Cordis activation cancelled'))
150
228
  })
151
229
  const activation = { owner, cancel }
152
230
  cordisTools = owner
231
+ cordisToolsReady = false
153
232
  pendingCordisActivation = activation
154
233
  try {
155
234
  return await awaitCordisToolsOwner(owner, cancelled)
@@ -158,19 +237,92 @@ function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId)
158
237
  }
159
238
  }
160
239
  const cancelCordisActivation = () => pendingCordisActivation?.cancel()
240
+ const registerProjection = (projection, label) => {
241
+ try {
242
+ const unregister = projectionService?.register?.(projection)
243
+ if (typeof unregister !== 'function') {
244
+ throw new Error('ptc-plus: sessionProjections.register did not return a disposer')
245
+ }
246
+ return unregister
247
+ } catch (error) {
248
+ ctx.logger?.warn?.(`ptc-plus: ${label} projection unavailable`, error)
249
+ return undefined
250
+ }
251
+ }
252
+ const setDraftProjectionEnabled = async (enabled) => {
253
+ if (!enabled) {
254
+ const unregister = draftProjectionRegistration
255
+ draftProjectionAvailable = false
256
+ const results = await Promise.allSettled([
257
+ Promise.resolve().then(() => userBindings?.setDraftProjectionAvailable(false)),
258
+ Promise.resolve().then(() => unregister?.()),
259
+ ])
260
+ const failures = results.filter(result => result.status === 'rejected').map(result => result.reason)
261
+ if (failures.length > 0) {
262
+ throw new AggregateError(failures, 'ptc-plus: binding draft projection cleanup failed')
263
+ }
264
+ return
265
+ }
266
+ if (draftProjectionRegistration !== undefined) {
267
+ if (draftProjectionAvailable) return
268
+ await draftProjectionRegistration()
269
+ }
270
+ if (projectionService === undefined) return
271
+ const unregister = registerProjection(userBindingDraftProjection, 'binding draft')
272
+ if (unregister === undefined) return
273
+ let active = true
274
+ let releasing
275
+ const release = () => {
276
+ if (!active) return Promise.resolve()
277
+ if (releasing !== undefined) return releasing
278
+ const operation = Promise.resolve().then(unregister)
279
+ releasing = operation
280
+ operation.then(
281
+ () => {
282
+ active = false
283
+ if (draftProjectionRegistration === release) draftProjectionRegistration = undefined
284
+ if (releasing === operation) releasing = undefined
285
+ },
286
+ () => { if (releasing === operation) releasing = undefined },
287
+ )
288
+ return operation
289
+ }
290
+ draftProjectionRegistration = release
291
+ disposers.push(release)
292
+ draftProjectionAvailable = true
293
+ try {
294
+ await userBindings?.setDraftProjectionAvailable(true)
295
+ } catch (error) {
296
+ draftProjectionAvailable = false
297
+ await Promise.allSettled([
298
+ Promise.resolve().then(() => userBindings?.setDraftProjectionAvailable(false)),
299
+ release(),
300
+ ])
301
+ throw error
302
+ }
303
+ }
161
304
  try {
305
+ const hostRpc = createHostRpc(ctx)
306
+ disposers.push(() => hostRpc.dispose())
307
+ observationInterest = createReplObservationInterest(ctx, activeConfig.replViewEnabled,
308
+ (id, memory, signal) => runtimeBridge?.observeRepl(id, memory, signal))
309
+ disposers.push(() => observationInterest.dispose())
162
310
  if (typeof ctx.inject === 'function') {
163
311
  const projectionInjection = ctx.inject(['sessionProjections'], (scope) => {
164
312
  if (disposed) return
165
- try {
166
- const unregister = scope.sessionProjections?.register?.(replMemoryProjection)
167
- if (typeof unregister !== 'function') {
168
- throw new Error('ptc-plus: sessionProjections.register did not return a disposer')
169
- }
170
- disposers.push(unregister)
171
- } catch (error) {
172
- ctx.logger?.warn?.('ptc-plus: REPL memory projection unavailable', error)
173
- }
313
+ const service = scope.sessionProjections
314
+ projectionService = service
315
+ draftProjectionAvailable = false
316
+ const unregisterMemory = registerProjection(replMemoryProjection, 'REPL memory')
317
+ if (unregisterMemory !== undefined) disposers.push(unregisterMemory)
318
+ void setDraftProjectionEnabled(activeConfig.userBindingsEnabled).catch(error => {
319
+ ctx.logger?.warn?.('ptc-plus: binding draft projection unavailable', error)
320
+ })
321
+ scope.effect?.(() => async () => {
322
+ if (projectionService !== service) return
323
+ projectionService = undefined
324
+ await setDraftProjectionEnabled(false)
325
+ }, 'ptc-plus: binding draft projection availability')
174
326
  })
175
327
  if (typeof projectionInjection === 'function') {
176
328
  disposers.push(projectionInjection)
@@ -178,23 +330,32 @@ function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId)
178
330
  disposers.push(() => projectionInjection.dispose())
179
331
  }
180
332
  }
181
- cordisTools = activeConfig.cordisToolsEnabled
182
- ? createCordisToolsOwner(ctx)
183
- : undefined
333
+ cordisTools = activeConfig.cordisToolsEnabled ? createCordisToolsOwner(ctx) : undefined
184
334
  if (cordisTools !== undefined) {
185
335
  const initialCordisTools = cordisTools
186
- ready = awaitCordisToolsOwner(initialCordisTools, new Promise(() => {})).catch(error => {
187
- if (cordisTools === initialCordisTools) cordisTools = undefined
188
- throw error
189
- })
336
+ cordisOwners.add(initialCordisTools)
337
+ ready = awaitCordisToolsOwner(initialCordisTools, new Promise(() => {}))
190
338
  }
339
+ userBindings = createUserBindingsOwner(ctx, {
340
+ enabled: activeConfig.userBindingsEnabled,
341
+ draftProjectionAvailable,
342
+ maxWallMs: activeConfig.maxWallMs,
343
+ maxOutputBytes: activeConfig.maxOutputBytes,
344
+ maxOldGenerationSizeMb: activeConfig.maxOldGenerationSizeMb,
345
+ valueLimits: valueLimitsFromConfig(activeConfig),
346
+ })
191
347
  runtimeBridge = createRuntimeBridgeOwner({
192
348
  ctx,
349
+ seam,
350
+ observeSession: id => observationInterest.has(id),
193
351
  sessionConfig: activeConfig,
352
+ userBindingsCwd: userBindings.cwd,
194
353
  maxNestedRunCodeDepth: activeConfig.maxNestedRunCodeDepth,
195
354
  presentationGeneration,
196
355
  sessionId,
197
356
  toolSchemasForAgent,
357
+ userBindingDraftForAgent: agent => userBindings.draftCapabilityForAgent(agent),
358
+ bindingSubmissionForAgent: (agent, ensureLease, onAccepted) => userBindings.submissionForAgent(agent, ensureLease, onAccepted),
198
359
  })
199
360
  editTransport = createEditTransportOwner(ctx, {
200
361
  durableReplay: activeConfig.durableReplay,
@@ -209,21 +370,17 @@ function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId)
209
370
  canonicalizeToolCalls: activeConfig.canonicalizeToolCalls,
210
371
  sessionId,
211
372
  toolSchemasForAgent,
373
+ userBindingsForAgent: () => userBindings.snapshot(),
374
+ setAgentPresentation: (agent, presentation) => (
375
+ userBindings.setAgentPresentation(agent, presentation)
376
+ ),
212
377
  })
213
378
  disposers.push(ctx.systemPrompt.section({
214
379
  name: 'tools:ptc-plus-repl',
215
380
  order: 98,
216
381
  text: context => {
217
382
  if (ctx.tools.get(RUN_CODE, context?.scope) === undefined) return ''
218
- return replGuidance(
219
- activeConfig.looseTopLevelRedeclarations,
220
- activeConfig.looseTopLevelFunctionClassRedeclarations,
221
- activeConfig.durableReplay,
222
- activeConfig.autoRewriteImports,
223
- activeConfig.autoStripExports,
224
- activeConfig.autoSplitRedeclarations,
225
- activeConfig.cordisToolsEnabled,
226
- )
383
+ return replGuidance(guidancePolicies(activeConfig))
227
384
  },
228
385
  }))
229
386
  disposers.push(ctx.on('system-prompt/assemble', (assembly, context, next) => (
@@ -235,7 +392,13 @@ function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId)
235
392
  if (rejected !== undefined) return rejected
236
393
  if (exec.name === RUN_CODE) {
237
394
  const executionArguments = directSurface.executionArguments(exec)
238
- return Promise.resolve(runtimeBridge.handleExecute(exec, next, executionArguments))
395
+ const requestUserBindings = directSurface.executionUserBindings(exec)
396
+ return Promise.resolve(runtimeBridge.handleExecute(
397
+ exec,
398
+ next,
399
+ executionArguments,
400
+ requestUserBindings,
401
+ ))
239
402
  .then(result => directSurface.argumentDiagnostic(exec, result))
240
403
  }
241
404
  return next()
@@ -245,22 +408,48 @@ function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId)
245
408
  if (exec.name === EDIT_RUN_CODE) return editTransport.handleResult(exec, result)
246
409
  if (exec.name === RUN_CODE) return runtimeBridge.handleResult(exec, result)
247
410
  }))
248
- disposers.push(ctx.on('agent/disposed', ({ agent }) => {
411
+ disposers.push(ctx.on('agent/disposed', async ({ agent }) => {
249
412
  directSurface.disposeAgent(agent)
250
413
  editTransport.disposeAgent(agent)
251
- return runtimeBridge.disposeAgent(agent)
414
+ await Promise.all([
415
+ runtimeBridge.disposeAgent(agent),
416
+ userBindings.clearAgentPresentation(agent),
417
+ ])
252
418
  }))
253
- disposers.push(ctx.on('session/disposed', (session) => {
419
+ disposers.push(ctx.on('session/disposed', async (session) => {
254
420
  directSurface.disposeSession(session)
255
421
  editTransport.disposeSession(session)
256
- return runtimeBridge.disposeSession(session)
422
+ await Promise.all([
423
+ runtimeBridge.disposeSession(session),
424
+ userBindings.clearSessionPresentation(session?.id ?? session),
425
+ ])
426
+ }))
427
+ disposers.push(ctx.on('agent-preset/selected', (sessionId) => {
428
+ directSurface.resetSessionComposition(sessionId)
429
+ const agent = ctx.agents?.get?.(String(sessionId))
430
+ if (agent !== undefined) editTransport.disposeAgent(agent)
431
+ const cleanup = agent === undefined
432
+ ? userBindings.clearSessionPresentation(sessionId)
433
+ : userBindings.setAgentPresentation(agent)
434
+ const report = ctx.logger?.warn?.bind(
435
+ ctx.logger,
436
+ 'ptc-plus: failed to reconcile Global User Bindings authoring after preset selection',
437
+ ) ?? console.warn
438
+ void cleanup.catch(report)
257
439
  }))
258
440
  } catch (error) {
259
- const cleanup = dispose()
260
- if (error !== null && typeof error === 'object') {
261
- Object.defineProperty(error, INSTALL_CLEANUP, { value: cleanup })
441
+ const failedRuntime = Object.freeze({ dispose })
442
+ let failure = error
443
+ if (failure === null || (typeof failure !== 'object' && typeof failure !== 'function')) {
444
+ failure = new Error(String(error), { cause: error })
262
445
  }
263
- throw error
446
+ try {
447
+ Object.defineProperty(failure, INSTALL_CLEANUP, { value: failedRuntime })
448
+ } catch {
449
+ failure = new Error(failure.message ?? String(failure), { cause: failure })
450
+ Object.defineProperty(failure, INSTALL_CLEANUP, { value: failedRuntime })
451
+ }
452
+ throw failure
264
453
  }
265
454
  async function reconfigure(nextConfig) {
266
455
  if (disposed) return
@@ -273,26 +462,34 @@ function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId)
273
462
  rollbacks.push(() => editTransport.reconfigure(previousConfig))
274
463
  directSurface.reconfigure(nextConfig)
275
464
  rollbacks.push(() => directSurface.reconfigure(previousConfig))
465
+ await userBindings.reconfigure(nextConfig)
466
+ rollbacks.push(() => userBindings.reconfigure(previousConfig))
467
+ await setDraftProjectionEnabled(nextConfig.userBindingsEnabled)
468
+ rollbacks.push(() => setDraftProjectionEnabled(previousConfig.userBindingsEnabled))
276
469
 
470
+ if (nextConfig.cordisToolsEnabled && cordisTools !== undefined && !cordisToolsReady) {
471
+ await disposeRetainedCordisTools()
472
+ }
277
473
  if (nextConfig.cordisToolsEnabled && cordisTools === undefined) {
278
474
  await activateCordisToolsOwner()
279
475
  } else if (!nextConfig.cordisToolsEnabled && cordisTools !== undefined) {
280
476
  const currentCordis = cordisTools
477
+ cordisToolsReady = false
281
478
  try {
282
- await currentCordis.dispose()
283
- cordisTools = undefined
479
+ await disposeCordisToolsOwner(currentCordis)
284
480
  } catch (error) {
285
481
  try {
482
+ await disposeCordisToolsOwner(currentCordis)
286
483
  await activateCordisToolsOwner()
287
484
  /* c8 ignore next */
288
485
  } catch (rollbackError) {
289
- cordisTools = currentCordis
290
486
  throw new AggregateError([error, rollbackError], 'ptc-plus: Cordis reconfiguration and rollback failed', { cause: error })
291
487
  }
292
488
  throw error
293
489
  }
294
490
  }
295
491
  activeConfig = nextConfig
492
+ observationInterest.reconfigure(nextConfig.replViewEnabled)
296
493
  } catch (error) {
297
494
  const rollbackFailures = []
298
495
  /* c8 ignore next -- the rollback loop's rejection branch is host-specific. */
@@ -308,12 +505,25 @@ function installPtCRuntime(ctx, resolvedConfig, toolSchemasForAgent, sessionId)
308
505
  }
309
506
  }
310
507
 
311
- return Object.freeze({ cancelCordisActivation, dispose, reconfigure, ready })
508
+ return Object.freeze({
509
+ cancelCordisActivation, dispose, reconfigure, ready,
510
+ contextsForRequest: context => directSurface.contextsForRequest(context),
511
+ })
312
512
  }
313
513
 
314
514
  /** Register the session-bound REPL runtime. */
315
515
  export function apply(ctx, config = {}) {
516
+ // Configuration is validated before the plugin waits for the host's seam, so
517
+ // an unusable configuration fails at the load call rather than later.
316
518
  const resolvedConfig = resolveConfig(config)
519
+ return installExecutionSeam(ctx, {
520
+ services: inject,
521
+ attach: (scope, seam) => applyWithExecutionSeam(scope, resolvedConfig, seam),
522
+ })
523
+ }
524
+
525
+ /** Install the plugin into the scope of the seam the host registered. */
526
+ function applyWithExecutionSeam(ctx, resolvedConfig, seam) {
317
527
  const toolSchemasForAgent = agent => typeof ctx.tools.schemas === 'function'
318
528
  ? ctx.tools.schemas(agent)
319
529
  : []
@@ -323,7 +533,9 @@ export function apply(ctx, config = {}) {
323
533
  }
324
534
  let committed
325
535
  let activating
536
+ const retained = new Set()
326
537
  let disposed = false
538
+ let controllerDisposal
327
539
  let transitionTail = Promise.resolve()
328
540
  let pendingTransitions = 0
329
541
  const trackTransition = operation => {
@@ -348,9 +560,26 @@ export function apply(ctx, config = {}) {
348
560
  return { promise, resolve, reject }
349
561
  }
350
562
  const disposeRuntime = record => {
563
+ if (record?.disposed === true) return Promise.resolve()
351
564
  if (record?.disposal !== undefined) return record.disposal
352
- record.disposal = record.runtime.dispose()
353
- return record.disposal
565
+ retained.add(record)
566
+ const operation = Promise.resolve().then(() => record.runtime.dispose())
567
+ record.disposal = operation
568
+ operation.then(
569
+ () => {
570
+ record.disposed = true
571
+ retained.delete(record)
572
+ if (record.disposal === operation) record.disposal = undefined
573
+ },
574
+ () => { if (record.disposal === operation) record.disposal = undefined },
575
+ )
576
+ return operation
577
+ }
578
+ const disposeRetainedRuntimes = async () => {
579
+ const records = [...retained].filter(record => record.runtime !== undefined)
580
+ const results = await Promise.allSettled(records.map(record => disposeRuntime(record)))
581
+ const failures = results.filter(result => result.status === 'rejected').map(result => result.reason)
582
+ if (failures.length > 0) throw new AggregateError(failures, 'ptc-plus retained runtime disposal failed')
354
583
  }
355
584
  const activationError = (error, cleanupError) => new AggregateError(
356
585
  [error, cleanupError],
@@ -376,17 +605,18 @@ export function apply(ctx, config = {}) {
376
605
  record.completion.resolve({ status: 'superseded' })
377
606
  return
378
607
  }
379
- if (ctx.codeRuntime.language !== 'typescript') {
380
- throw new Error('ptc-plus: unsupported code runtime language ' + JSON.stringify(ctx.codeRuntime.language) + '; only "typescript" is supported')
608
+ if (seam.language !== 'typescript') {
609
+ throw new Error('ptc-plus: unsupported code runtime language ' + JSON.stringify(seam.language) + '; only "typescript" is supported')
381
610
  }
382
611
  let candidate
383
612
  try {
384
- candidate = installPtCRuntime(ctx, record.config, toolSchemasForAgent, sessionId)
613
+ candidate = installPtCRuntime(ctx, record.config, seam, toolSchemasForAgent, sessionId)
385
614
  record.runtime = candidate
386
615
  } catch (error) {
387
- const cleanup = error?.[INSTALL_CLEANUP]
388
- if (cleanup === undefined) throw error
389
- const trackedCleanup = trackTransition(cleanup)
616
+ const failedRuntime = error?.[INSTALL_CLEANUP]
617
+ if (failedRuntime === undefined) throw error
618
+ record.runtime = failedRuntime
619
+ const trackedCleanup = trackTransition(disposeRuntime(record))
390
620
  void trackedCleanup.then(
391
621
  () => record.completion.reject(error),
392
622
  cleanupError => record.completion.reject(activationError(error, cleanupError)),
@@ -427,6 +657,8 @@ export function apply(ctx, config = {}) {
427
657
  generation,
428
658
  runtime: undefined,
429
659
  disposal: undefined,
660
+ disposed: false,
661
+ retiring: false,
430
662
  trackedDisposal: undefined,
431
663
  cancelled: false,
432
664
  completion,
@@ -455,10 +687,22 @@ export function apply(ctx, config = {}) {
455
687
  apply(nextConfig, generation) {
456
688
  if (disposed) return Promise.resolve({ status: 'superseded' })
457
689
  if (activating !== undefined) {
458
- const cleanup = cancelActivation(activating)
690
+ cancelActivation(activating)
691
+ const cleanup = disposeRetainedRuntimes()
692
+ return beginActivation(nextConfig, generation, cleanup)
693
+ }
694
+ if (committed === undefined) {
695
+ const cleanup = retained.size === 0 ? undefined : disposeRetainedRuntimes()
696
+ return beginActivation(nextConfig, generation, cleanup)
697
+ }
698
+ if (committed.retiring) {
699
+ const current = committed
700
+ void disposeRuntime(current)
701
+ const cleanup = disposeRetainedRuntimes().then(() => {
702
+ if (committed === current) committed = undefined
703
+ })
459
704
  return beginActivation(nextConfig, generation, cleanup)
460
705
  }
461
- if (committed === undefined) return beginActivation(nextConfig, generation)
462
706
  const current = committed
463
707
  if (!nextConfig.cordisToolsEnabled) current.runtime.cancelCordisActivation()
464
708
  return enqueueTransition(async () => {
@@ -469,27 +713,60 @@ export function apply(ctx, config = {}) {
469
713
  return { status: 'applied' }
470
714
  })
471
715
  },
472
- uninstall() {
473
- const activationCleanup = activating === undefined
716
+ async uninstall() {
717
+ const active = activating
718
+ const activationCleanup = active === undefined
474
719
  ? Promise.resolve()
475
- : cancelActivation(activating)
720
+ : cancelActivation(active)
476
721
  const current = committed
477
- committed = undefined
722
+ if (current !== undefined) current.retiring = true
478
723
  const committedCleanup = current === undefined
479
724
  ? Promise.resolve()
480
725
  : trackTransition(disposeRuntime(current))
481
- return Promise.all([activationCleanup, committedCleanup]).then(() => undefined)
726
+ const records = [...retained].filter(record => (
727
+ record !== current && record !== active && record.runtime !== undefined
728
+ ))
729
+ const results = await Promise.allSettled([
730
+ activationCleanup,
731
+ committedCleanup,
732
+ ...records.map(record => disposeRuntime(record)),
733
+ ])
734
+ const failures = results.filter(result => result.status === 'rejected').map(result => result.reason)
735
+ if (current?.disposed === true && committed === current) committed = undefined
736
+ if (failures.length > 0) throw new AggregateError(failures, 'ptc-plus runtime uninstall failed')
482
737
  },
483
738
  committedConfig() {
484
- return committed?.config
739
+ return committed?.retiring ? undefined : committed?.config
485
740
  },
486
- async dispose() {
741
+ dispose() {
487
742
  disposed = true
488
- await controller.uninstall()
743
+ if (controllerDisposal !== undefined) return controllerDisposal
744
+ const operation = controller.uninstall()
745
+ controllerDisposal = operation
746
+ operation.then(
747
+ () => { if (controllerDisposal === operation) controllerDisposal = undefined },
748
+ () => { if (controllerDisposal === operation) controllerDisposal = undefined },
749
+ )
750
+ return operation
489
751
  },
490
752
  }
491
753
  ctx.effect(() => async () => controller.dispose(), 'ptc-plus runtime lifecycle')
492
754
 
755
+ const messageOwner = createRuntimeMessageOwner(context => (
756
+ committed?.retiring ? [] : committed?.runtime.contextsForRequest(context) ?? []
757
+ ))
758
+ ctx.effect(() => ctx.systemPrompt.context({
759
+ name: PTC_DELIVERY_CONTEXT, order: 98, text: '',
760
+ }), 'ptc-plus dynamic message delivery witness')
761
+ ctx.effect(() => ctx.on('system-prompt/assemble', (assembly, context, next) => (
762
+ messageOwner.assemble(assembly, context, next)
763
+ )), 'ptc-plus message assembly')
764
+ ctx.effect(() => ctx.on('agent/pre-step', (payload, next) => (
765
+ messageOwner.preStep(payload, next)
766
+ )), 'ptc-plus accepted messages')
767
+ ctx.effect(() => ctx.on('agent/disposed', ({ agent }) => messageOwner.disposeAgent(agent)), 'ptc-plus message agent disposal')
768
+ ctx.effect(() => () => messageOwner.dispose(), 'ptc-plus dynamic message lifecycle')
769
+
493
770
  let configSource = () => resolvedConfig
494
771
  let configurationGeneration = 0
495
772
  let settingsWriter