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
@@ -0,0 +1,114 @@
1
+ import { types } from 'node:util'
2
+ import { runInContext } from 'node:vm'
3
+ import { parse } from 'acorn'
4
+ import { randomUUID } from 'node:crypto'
5
+ import { walkAst, SKIP_AST_CHILDREN } from './ast-traversal.js'
6
+ import { bindingNodes } from './binding-pattern-traversal.js'
7
+
8
+ const MAX_ENTRIES = 128
9
+ const MAX_TEXT = 512
10
+ const MAX_PROPERTIES = 5
11
+ const ownDescriptor = Object.getOwnPropertyDescriptor
12
+ const isArray = Array.isArray
13
+ const isProxy = types.isProxy
14
+ const stringify = JSON.stringify
15
+ const now = Date.now
16
+ const bigintText = BigInt.prototype.toString
17
+ const BIGINT_LIMIT = 10n ** 128n
18
+ const unreadable = () => ({ status: 'unreadable', text: '', truncated: false })
19
+
20
+ function primitiveText(value) {
21
+ if (typeof value === 'string') return stringify(value.slice(0, MAX_TEXT))
22
+ if (typeof value === 'bigint') return value > -BIGINT_LIMIT && value < BIGINT_LIMIT
23
+ ? `${bigintText.call(value)}n` : '[bigint: more than 128 digits]'
24
+ if (typeof value === 'symbol' || typeof value === 'function') return `[${typeof value}: unreadable]`
25
+ if (value === null) return 'null'
26
+ if (typeof value === 'object') return isProxy(value) ? '[proxy: unreadable]' : '[object]'
27
+ return Object.is(value, -0) ? '-0' : String(value)
28
+ }
29
+
30
+ /** Inspect own descriptors only, rejecting proxies before any reflective operation. */
31
+ export function previewBindingValue(value) {
32
+ if (['function', 'symbol'].includes(typeof value)) return unreadable()
33
+ if (value === null || typeof value !== 'object') {
34
+ const text = primitiveText(value)
35
+ return { status: 'readable', text: text.slice(0, MAX_TEXT), truncated: text.length > MAX_TEXT
36
+ || (typeof value === 'bigint' && (value <= -BIGINT_LIMIT || value >= BIGINT_LIMIT)) }
37
+ }
38
+ // Only arrays provide a finite slot range without enumerating the whole object.
39
+ if (isProxy(value) || !isArray(value)) return unreadable()
40
+ const keys = Array.from({ length: Math.min(ownDescriptor(value, 'length').value, MAX_PROPERTIES) }, (_value, index) => String(index))
41
+ const properties = keys.map(key => {
42
+ const descriptor = ownDescriptor(value, key)
43
+ const text = descriptor === undefined ? '[empty]'
44
+ : Object.hasOwn(descriptor, 'value') ? primitiveText(descriptor.value) : '[accessor: unreadable]'
45
+ return `${stringify(key)}: ${text}`
46
+ })
47
+ const text = `array { ${properties.join(', ')} }`
48
+ // Uninspected own properties remain unknown even when all indexed slots fit.
49
+ return { status: 'readable', text: text.slice(0, MAX_TEXT), truncated: true }
50
+ }
51
+
52
+ /** Probe the actual evaluator without consulting a Node version or any session value. */
53
+ export async function supportsAwaitLexicals(context, evaluate) {
54
+ const prefix = `__ptc_observation_${randomUUID().replaceAll('-', '')}`
55
+ const names = [`${prefix}_let`, `${prefix}_const`]
56
+ try {
57
+ await evaluate(`let ${names[0]} = await Promise.resolve(41); const ${names[1]} = 42;`)
58
+ return names.every((name, index) => ownDescriptor(context, name) === undefined
59
+ && runInContext(name, context, { timeout: 25 }) === index + 41)
60
+ } catch {
61
+ return false
62
+ } finally {
63
+ // Lexical probe names are private and never enter the binding inventory.
64
+ for (const name of names) delete context[name]
65
+ }
66
+ }
67
+
68
+ /** Successful programs prove storage; await lexical reads additionally require a runtime probe. */
69
+ export function createReplValueObserver(context, { awaitLexicals = false } = {}) {
70
+ const lexicals = new Set()
71
+ const globals = new Set()
72
+ return {
73
+ record(program) {
74
+ try {
75
+ const tree = parse(program, { ecmaVersion: 'latest', allowAwaitOutsideFunction: true })
76
+ let awaits = false
77
+ walkAst(tree, node => {
78
+ if (['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'].includes(node.type)) return SKIP_AST_CHILDREN
79
+ if (node.type === 'AwaitExpression' || node.await === true) awaits = true
80
+ })
81
+ if (awaits && !awaitLexicals) return
82
+ for (const statement of tree.body) {
83
+ if (statement.type !== 'VariableDeclaration') continue
84
+ for (const declaration of statement.declarations) {
85
+ for (const binding of bindingNodes(declaration.id)) {
86
+ (statement.kind === 'var' ? globals : lexicals).add(binding.name)
87
+ }
88
+ }
89
+ }
90
+ } catch {}
91
+ },
92
+ observe(names) {
93
+ const at = now()
94
+ const entries = names.slice(0, MAX_ENTRIES).map(name => {
95
+ let preview = unreadable()
96
+ try {
97
+ if (lexicals.has(name)) {
98
+ // Lexical storage shadows global properties, even while uninitialized.
99
+ if (/^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u.test(name) && now() - at < 100) {
100
+ preview = previewBindingValue(runInContext(name, context, { timeout: 25, displayErrors: false }))
101
+ }
102
+ } else if (globals.has(name)) {
103
+ const descriptor = ownDescriptor(context, name)
104
+ if (descriptor !== undefined && Object.hasOwn(descriptor, 'value')) {
105
+ preview = previewBindingValue(descriptor.value)
106
+ }
107
+ }
108
+ } catch {}
109
+ return { name, ...preview }
110
+ })
111
+ return { at, entries }
112
+ },
113
+ }
114
+ }
@@ -0,0 +1,52 @@
1
+ import Schema from '@deepseek-ai/schemastery'
2
+ import { strictCodec } from './typert-codec-compat.js'
3
+
4
+ const resultSchema = Schema.union([
5
+ Schema.object({ ok: Schema.const(true).required(), value: Schema.any() }),
6
+ Schema.object({
7
+ ok: Schema.const(false).required(),
8
+ error: Schema.object({
9
+ code: Schema.string().required(),
10
+ message: Schema.string().required(),
11
+ details: Schema.any().required(),
12
+ }).required(),
13
+ }),
14
+ ]).required()
15
+
16
+ /** Shared executable wire definitions; business payload validation stays with each owner. */
17
+ export const RPC_CONTRACTS = Object.freeze({
18
+ bindings: Object.freeze({ service: 'ptcPlusBindings' }),
19
+ repl: Object.freeze({ service: 'ptcPlusRepl' }),
20
+ })
21
+
22
+ export function rpcDescriptor({ service }) {
23
+ return {
24
+ id: `dsh-ptc-plus#${service}/invoke`,
25
+ service,
26
+ namespace: service,
27
+ method: 'invoke',
28
+ invocation: { kind: 'direct' },
29
+ parameters: [
30
+ { name: 'operation', wire: 'operation', source: 'json',
31
+ codec: strictCodec({
32
+ typeSymbol: 'dsh-ptc-plus#RpcOperation',
33
+ schema: Schema.string().required(),
34
+ }) },
35
+ { name: 'payload', wire: 'payload', source: 'json',
36
+ codec: strictCodec({
37
+ typeSymbol: 'dsh-ptc-plus#RpcPayload',
38
+ schema: Schema.any(),
39
+ }) },
40
+ ],
41
+ cancellation: { parameter: 'signal' },
42
+ result: strictCodec({
43
+ typeSymbol: 'dsh-ptc-plus#RpcResult',
44
+ schema: resultSchema,
45
+ }),
46
+ }
47
+ }
48
+
49
+ export const RPC_REMOTE = Object.freeze({
50
+ package: 'dsh-ptc-plus',
51
+ descriptors: Object.values(RPC_CONTRACTS).map(rpcDescriptor),
52
+ })
@@ -1,5 +1,6 @@
1
- /** Own the CodeRuntime patch, per-cell lease, journal projection, and session settlement. */
1
+ /** Own the execution-seam takeover, per-cell lease, journal projection, and session settlement. */
2
2
  import { AsyncLocalStorage } from 'node:async_hooks'
3
+ import { executionPolicies } from './binding-update-policy.js'
3
4
  import { assertObjectJsonSchema, validateJsonSchemaValue } from '@deepseek-ai/dsh-tools'
4
5
  import { normalizeBindingDescriptors } from './binding-descriptors.js'
5
6
  import { SessionRuntime } from './session-runtime.js'
@@ -20,7 +21,14 @@ import {
20
21
  } from './program-bindings.js'
21
22
  import { deepFreeze } from './record-utils.js'
22
23
  import { withReplMemorySnapshot } from './repl-memory-projection.js'
24
+ import { withUserBindingDraftCapability } from './user-binding-draft-projection.js'
23
25
  import { generatedRunCodeDescriptionMeta } from './run-code-description.js'
26
+ import { BINDING_SUBMISSION } from './user-binding-authoring.js'
27
+ import {
28
+ USER_BINDINGS_META_KEY,
29
+ userBindingsSnapshotsEqual,
30
+ withUserBindingsSnapshot,
31
+ } from './user-bindings.js'
24
32
 
25
33
  export const RUN_CODE = 'run_code'
26
34
 
@@ -63,12 +71,12 @@ const PROGRAM_CAPABILITY_METADATA = deepFreeze([
63
71
  namespace: 'repl',
64
72
  members: [{
65
73
  name: 'state',
66
- description: 'List, save, restore, or delete named durable REPL states.',
74
+ description: 'Manage named REPL checkpoints. list returns checkpoint names, not variable names. Save captures the final durable state of this cell, including subsequent mutations; it is lost if the cell becomes volatile or is discarded. Save, restore and delete take effect after cell settlement and journal confirmation, not when the receipt is awaited. Restore changes bindings for subsequent cells and does not undo external effects.',
67
75
  parameters: {
68
76
  type: 'object',
69
77
  properties: {
70
- action: { enum: ['list', 'save', 'restore', 'delete'] },
71
- name: { type: 'string' },
78
+ action: { enum: ['list', 'save', 'restore', 'delete'], description: 'list reads the current cell control view, including tentative names; mutations are accepted for settlement.' },
79
+ name: { type: 'string', description: 'Required for save/delete; omit for restore to select the durable head.' },
72
80
  },
73
81
  oneOf: [
74
82
  { required: ['action'], properties: { action: { const: 'list' } } },
@@ -77,7 +85,19 @@ const PROGRAM_CAPABILITY_METADATA = deepFreeze([
77
85
  { required: ['action', 'name'], properties: { action: { const: 'delete' } } },
78
86
  ],
79
87
  },
80
- returns: { type: 'object' },
88
+ returns: {
89
+ type: 'object',
90
+ properties: {
91
+ action: { enum: ['save', 'restore', 'delete'] },
92
+ name: { type: 'string' },
93
+ saved: { const: true, description: 'Save accepted for the final durable cell boundary, subject to settlement and confirmation.' },
94
+ restored: { const: true, description: 'Restore accepted; use a later cell to compute from restored bindings.' },
95
+ deleted: { const: true, description: 'Deletion accepted for settlement; removes the checkpoint name, not external effects.' },
96
+ names: { type: 'array', items: { type: 'string' }, description: 'Sorted checkpoint names in the current cell control view, including tentative operations.' },
97
+ mode: { enum: ['durable', 'volatile'] },
98
+ volatileReason: { type: 'string' },
99
+ },
100
+ },
81
101
  effect: 'ptc-state',
82
102
  authority: 'ptc-plus-program-binding',
83
103
  completeness: 'complete',
@@ -123,26 +143,71 @@ const PROGRAM_CAPABILITY_METADATA = deepFreeze([
123
143
 
124
144
  export function createRuntimeBridgeOwner({
125
145
  ctx,
146
+ seam,
126
147
  sessionConfig,
148
+ userBindingsCwd,
149
+ observeSession,
127
150
  maxNestedRunCodeDepth,
128
151
  presentationGeneration,
129
152
  sessionId,
130
153
  toolSchemasForAgent,
154
+ userBindingDraftForAgent,
155
+ bindingSubmissionForAgent,
131
156
  }) {
132
157
  const scope = new AsyncLocalStorage()
158
+ const childScope = new AsyncLocalStorage()
159
+ const childRuntimes = new Set()
160
+ const childDisposals = new WeakMap()
161
+ const childFinalizers = new WeakSet()
162
+ const disposeChildRuntime = (child) => {
163
+ let disposal = childDisposals.get(child)
164
+ if (disposal === undefined) {
165
+ disposal = child.dispose()
166
+ childDisposals.set(child, disposal)
167
+ }
168
+ return disposal
169
+ }
133
170
  // AgentRegistry.withInitiator is AsyncLocalStorage-based. The arrow preserves
134
171
  // its receiver when a worker callback re-enters the host tool pipeline.
135
172
  const withInitiator = ctx.agents === undefined || typeof ctx.agents.withInitiator !== 'function'
136
173
  ? undefined
137
174
  : (agent, operation) => ctx.agents.withInitiator(agent, operation)
138
- const sessions = new SessionRuntime(sessionConfig, { withInitiator })
175
+ const sessions = new SessionRuntime(sessionConfig, { withInitiator, userBindingsCwd, observeSession })
139
176
  let currentConfig = sessions.config
140
- const runtime = ctx.codeRuntime
141
- const ownRun = Object.getOwnPropertyDescriptor(runtime, 'run')
142
- const upstreamRun = runtime.run
143
177
  const patchedDefinitions = new Map()
144
178
  const pending = new WeakMap()
179
+ const nativeRunCodeHandoffs = new Set()
145
180
  let active = true
181
+ const runIsolated = async (request, executionToken, depth, cellConfig, inheritedTools) => {
182
+ const child = new SessionRuntime({ ...cellConfig, durableReplay: false, userBindingsEnabled: false,
183
+ replViewEnabled: false }, { withInitiator, userBindingsCwd })
184
+ childRuntimes.add(child)
185
+ const projected = projectBindings(request, depth, executionToken, inheritedTools, cellConfig)
186
+ childFinalizers.add(child)
187
+ try {
188
+ return await child.run({ id: 'isolated-cell', session: { header: {
189
+ cwd: executionToken?.session?.header?.cwd,
190
+ } } }, { ...projected.request, executionToken })
191
+ } finally {
192
+ projected.release()
193
+ try {
194
+ await disposeChildRuntime(child)
195
+ childRuntimes.delete(child)
196
+ } finally {
197
+ childDisposals.delete(child)
198
+ childFinalizers.delete(child)
199
+ }
200
+ }
201
+ }
202
+ const withDraftCapability = (meta, current) => currentConfig.userBindingsEnabled !== true
203
+ || typeof userBindingDraftForAgent !== 'function'
204
+ ? meta
205
+ : withUserBindingDraftCapability(
206
+ meta,
207
+ userBindingDraftForAgent(current.agent),
208
+ presentationGeneration,
209
+ current.bindingCandidate ?? null,
210
+ )
146
211
 
147
212
  const projectBindings = (
148
213
  request,
@@ -165,7 +230,7 @@ export function createRuntimeBridgeOwner({
165
230
  toolsNamespace === undefined ? Object.create(null) : toolsNamespace.functions
166
231
  )
167
232
  const ensureLease = () => {
168
- if (!lease.active) throw new Error('PTC execution lease expired')
233
+ if (!active || !lease.active) throw new Error('PTC execution lease expired')
169
234
  }
170
235
  const runCode = async (value) => {
171
236
  ensureLease()
@@ -173,13 +238,42 @@ export function createRuntimeBridgeOwner({
173
238
  if (depth >= cellConfig.maxNestedRunCodeDepth) {
174
239
  throw new RangeError(`code.run recursion depth exceeds configured maximum ${cellConfig.maxNestedRunCodeDepth}`)
175
240
  }
176
- if (typeof functions[RUN_CODE] === 'function') return functions[RUN_CODE](args)
241
+ const languageSemantics = executionPolicies(cellConfig).languageSemantics
242
+ const nativeRunCode = functions[RUN_CODE]
243
+ if (typeof nativeRunCode === 'function') {
244
+ ensureLease()
245
+ let settleHandoff
246
+ const handoff = {
247
+ active: true,
248
+ executionToken,
249
+ depth: depth + 1,
250
+ cellConfig,
251
+ functions,
252
+ settled: new Promise(resolve => { settleHandoff = resolve }),
253
+ }
254
+ nativeRunCodeHandoffs.add(handoff)
255
+ try {
256
+ return await childScope.run(handoff, () => nativeRunCode.call(functions, args))
257
+ } finally {
258
+ handoff.active = false
259
+ nativeRunCodeHandoffs.delete(handoff)
260
+ settleHandoff()
261
+ }
262
+ }
263
+ if (languageSemantics !== 'legacy-v1') {
264
+ const child = await runIsolated({ ...request, program: args.code }, executionToken,
265
+ depth + 1, cellConfig, functions)
266
+ if (child.error !== undefined) {
267
+ throw new Error(`nested run_code failed (${child.error.kind}): ${child.error.message}`)
268
+ }
269
+ return { logs: child.logs, ...(child.value === undefined ? {} : { result: child.value }) }
270
+ }
177
271
  const childProjected = projectBindings(
178
272
  { ...request, program: args.code }, depth + 1, executionToken, functions, cellConfig,
179
273
  )
180
274
  let child
181
275
  try {
182
- child = await upstreamRun.call(runtime, childProjected.request)
276
+ child = await seam.invokeUpstream(childProjected.request)
183
277
  } finally {
184
278
  childProjected.release()
185
279
  }
@@ -224,7 +318,10 @@ export function createRuntimeBridgeOwner({
224
318
  )))
225
319
  const metadata = Object.freeze([
226
320
  ...toolCapabilityMetadata(schemas, annotations),
227
- ...PROGRAM_CAPABILITY_METADATA,
321
+ ...PROGRAM_CAPABILITY_METADATA.map(entry => entry.namespace === 'code'
322
+ && cellConfig.userBindingsEnabled === true
323
+ ? { ...entry, members: [...entry.members, BINDING_SUBMISSION] }
324
+ : entry),
228
325
  ])
229
326
  projected.push(namespace('capabilities', {
230
327
  tree: async value => {
@@ -246,7 +343,14 @@ export function createRuntimeBridgeOwner({
246
343
  return capabilityInspect(metadata, value.symbols, value.budget)
247
344
  },
248
345
  }, 'CapabilityExplorationError', 'operation'))
249
- projected.push(namespace('code', { run: runCode }, 'CodeExecutionError', 'operation'))
346
+ projected.push(namespace('code', {
347
+ run: runCode,
348
+ ...(cellConfig.userBindingsEnabled === true ? {
349
+ submitBindingDraft: bindingSubmissionForAgent(executionToken?.agent, ensureLease, candidate => {
350
+ executionToken.bindingCandidate = candidate
351
+ }),
352
+ } : {}),
353
+ }, 'CodeExecutionError', 'operation'))
250
354
  return { request: { ...request, bindings: projected }, release }
251
355
  }
252
356
 
@@ -278,8 +382,12 @@ export function createRuntimeBridgeOwner({
278
382
  meta = withRecoveryBoundaries(meta, settlement.recoveryBoundaries)
279
383
  }
280
384
  if (settlement.rewrites !== undefined) meta = withRewrites(meta, settlement.rewrites)
385
+ if (settlement.userBindings !== undefined) {
386
+ meta = withUserBindingsSnapshot(meta, settlement.userBindings)
387
+ }
281
388
  meta = generatedRunCodeDescriptionMeta(args, meta)
282
- return withReplMemorySnapshot(meta, settlement.replMemory, presentationGeneration)
389
+ meta = withReplMemorySnapshot(meta, settlement.replMemory, presentationGeneration)
390
+ return withDraftCapability(meta, current)
283
391
  }
284
392
  const ownExecute = Object.getOwnPropertyDescriptor(definition, 'execute')
285
393
  const originalExecute = definition.execute
@@ -324,11 +432,22 @@ export function createRuntimeBridgeOwner({
324
432
  }
325
433
 
326
434
  const patchedRun = function (request) {
327
- if (!active) return upstreamRun.call(runtime, request)
435
+ const child = childScope.getStore()
436
+ if (child !== undefined) {
437
+ if (!active || !child.active) return Promise.reject(new Error('PTC execution lease expired'))
438
+ if (executionPolicies(child.cellConfig).languageSemantics !== 'legacy-v1') {
439
+ return runIsolated(request, child.executionToken, child.depth, child.cellConfig, child.functions)
440
+ }
441
+ }
442
+ if (!active) return seam.invokeUpstream(request)
328
443
  const current = scope.getStore()
329
- if (current === undefined) return upstreamRun.call(runtime, request)
444
+ if (current === undefined) return seam.invokeUpstream(request)
330
445
  const projected = projectBindings(request, 0, current)
331
- return sessions.runTentative(current, { ...projected.request, executionToken: current })
446
+ return sessions.runTentative(current, {
447
+ ...projected.request,
448
+ executionToken: current,
449
+ ...(current.userBindings === undefined ? {} : { userBindings: current.userBindings }),
450
+ })
332
451
  .then((execution) => {
333
452
  current.settlement = execution.settlement
334
453
  return execution.result
@@ -336,11 +455,7 @@ export function createRuntimeBridgeOwner({
336
455
  .finally(projected.release)
337
456
  }
338
457
 
339
- Object.defineProperty(runtime, 'run', {
340
- configurable: true,
341
- writable: true,
342
- value: patchedRun,
343
- })
458
+ const releaseSeam = seam.takeOver(patchedRun)
344
459
 
345
460
  return Object.freeze({
346
461
  config: sessions.config,
@@ -348,6 +463,7 @@ export function createRuntimeBridgeOwner({
348
463
  sessions.reconfigure(nextConfig)
349
464
  currentConfig = sessions.config
350
465
  },
466
+ observeRepl(id, memory, signal) { return sessions.observe(id, memory, signal) },
351
467
  // A composite tool's outer result owns the final durability decision.
352
468
  async executeTentative(callSeq, operation) {
353
469
  const settlement = {
@@ -373,7 +489,7 @@ export function createRuntimeBridgeOwner({
373
489
  throw error
374
490
  }
375
491
  },
376
- handleExecute(exec, next, executionArguments = exec.arguments) {
492
+ handleExecute(exec, next, executionArguments = exec.arguments, userBindings = undefined) {
377
493
  if (exec.parent !== undefined) return scope.run(undefined, next)
378
494
  const id = sessionId(exec.agent)
379
495
  if (id === undefined) return next()
@@ -390,6 +506,7 @@ export function createRuntimeBridgeOwner({
390
506
  session: exec.agent?.session,
391
507
  agent: exec.agent,
392
508
  executionArguments,
509
+ ...(userBindings === undefined ? {} : { userBindings }),
393
510
  }
394
511
  if (current.deferredSettlement !== undefined) {
395
512
  current.deferredSettlement.current = current
@@ -404,7 +521,11 @@ export function createRuntimeBridgeOwner({
404
521
  meta = withRecoveryBoundaries(meta, settlement.recoveryBoundaries)
405
522
  }
406
523
  if (settlement.rewrites !== undefined) meta = withRewrites(meta, settlement.rewrites)
524
+ if (settlement.userBindings !== undefined) {
525
+ meta = withUserBindingsSnapshot(meta, settlement.userBindings)
526
+ }
407
527
  meta = withReplMemorySnapshot(meta, settlement.replMemory, presentationGeneration)
528
+ meta = withDraftCapability(meta, current)
408
529
  return { ...result, meta }
409
530
  }
410
531
  return result
@@ -429,6 +550,12 @@ export function createRuntimeBridgeOwner({
429
550
  Object.hasOwn(meta, RECOVERY_BOUNDARY_KEY) ? meta[RECOVERY_BOUNDARY_KEY] : undefined,
430
551
  settlement.recoveryBoundaries,
431
552
  )
553
+ && userBindingsSnapshotsEqual(
554
+ Object.hasOwn(meta, USER_BINDINGS_META_KEY)
555
+ ? meta[USER_BINDINGS_META_KEY]
556
+ : undefined,
557
+ settlement.userBindings,
558
+ )
432
559
  if (current.deferredSettlement !== undefined) {
433
560
  current.deferredSettlement.innerConfirmed = confirmed
434
561
  return
@@ -443,10 +570,20 @@ export function createRuntimeBridgeOwner({
443
570
  },
444
571
  async dispose() {
445
572
  active = false
446
- if (runtime.run === patchedRun) {
447
- if (ownRun === undefined) delete runtime.run
448
- else Object.defineProperty(runtime, 'run', ownRun)
573
+ const failures = []
574
+ const children = [...childRuntimes]
575
+ const childResultsPromise = Promise.allSettled(
576
+ children.map(child => disposeChildRuntime(child)),
577
+ )
578
+ await Promise.all([...nativeRunCodeHandoffs].map(handoff => handoff.settled))
579
+ const childResults = await childResultsPromise
580
+ for (const [index, result] of childResults.entries()) {
581
+ const child = children[index]
582
+ if (!childFinalizers.has(child)) childDisposals.delete(child)
583
+ if (result.status === 'rejected') failures.push(result.reason)
584
+ else childRuntimes.delete(child)
449
585
  }
586
+ releaseSeam()
450
587
  for (const patched of patchedDefinitions.values()) {
451
588
  if (patched.output.presentationMeta === patched.patchedPresentationMeta) {
452
589
  if (patched.originalPresentationMeta === undefined) delete patched.output.presentationMeta
@@ -459,7 +596,14 @@ export function createRuntimeBridgeOwner({
459
596
  }
460
597
  }
461
598
  patchedDefinitions.clear()
462
- await sessions.dispose()
599
+ try {
600
+ await sessions.dispose()
601
+ } catch (error) {
602
+ failures.push(error)
603
+ }
604
+ if (failures.length > 0) {
605
+ throw new AggregateError(failures, 'ptc-plus runtime bridge disposal failed')
606
+ }
463
607
  },
464
608
  })
465
609
  }
@@ -1,65 +1,66 @@
1
1
  import {
2
2
  CONFIG_DEFAULTS,
3
+ CONFIG_FIELDS,
3
4
  MAX_TIMER_DELAY_MS,
4
5
  } from './config-spec.js'
5
6
 
6
7
  export { MAX_TIMER_DELAY_MS }
7
8
 
8
- /** Positive-safe-integer limits applied to runtime budget fields. */
9
- const BUDGET_KEYS = Object.freeze([
10
- 'computeMs',
11
- 'maxOutputBytes',
12
- 'maxOldGenerationSizeMb',
13
- 'maxValueNodes',
14
- 'maxValueEdges',
15
- 'maxValueArrayLength',
16
- 'maxValueBigIntDigits',
17
- ])
9
+ /** The wall-clock ceiling field carries the platform timer limit as its maximum. */
10
+ const MAX_WALL_MS_FIELD = CONFIG_FIELDS.find(field => field.key === 'maxWallMs')
18
11
 
19
- /** Boolean runtime behavior switches. */
20
- const BOOLEAN_KEYS = Object.freeze([
21
- 'enhancedToolView',
22
- 'autoDescribeRunCode',
23
- 'cordisToolsEnabled',
24
- 'canonicalizeToolCalls',
25
- 'looseTopLevelRedeclarations',
26
- 'looseTopLevelFunctionClassRedeclarations',
27
- 'durableReplay',
28
- 'autoRewriteImports',
29
- 'autoStripExports',
30
- 'autoSplitRedeclarations',
31
- 'tipsEnabled',
32
- ])
12
+ /** Validate one integer field against the bounds declared in CONFIG_FIELDS. */
13
+ function assertIntegerField(field, value) {
14
+ if (!Number.isSafeInteger(value) || value < field.min) {
15
+ throw new TypeError(`ptc-plus: ${field.key} must be a positive safe integer`)
16
+ }
17
+ if (value > field.max) {
18
+ throw new TypeError(`ptc-plus: ${field.key} must not exceed ${field.max}`)
19
+ }
20
+ }
33
21
 
34
- /** Positive-safe-integer field groups that validate like budgets. */
35
- const POSITIVE_KEYS = Object.freeze([
36
- 'maxNestedRunCodeDepth',
37
- 'tipCooldownMessages',
38
- 'tipEscalationFailures',
39
- ])
22
+ /** Validate one field against its CONFIG_FIELDS declaration. */
23
+ function assertField(field, value) {
24
+ if (field.type === 'boolean') {
25
+ if (typeof value !== 'boolean') {
26
+ throw new TypeError(`ptc-plus: ${field.key} must be a boolean`)
27
+ }
28
+ return
29
+ }
30
+ if (field.type === 'enum') {
31
+ if (!field.options.includes(value)) {
32
+ throw new TypeError(`ptc-plus: ${field.key} must be one of ${field.options.join(', ')}`)
33
+ }
34
+ return
35
+ }
36
+ assertIntegerField(field, value)
37
+ }
40
38
 
41
39
  /**
42
40
  * Resolve plugin/runtime config from raw input, applying the shared defaults
43
- * and the same validation the Host Config schema exposes.
44
- * @param config - raw config object; unknown keys are preserved for forwards compatibility.
41
+ * and validating every declared field with the rules the Host Config schema
42
+ * exposes. Unknown keys are preserved for forwards compatibility.
43
+ * @param config - raw config object.
45
44
  * @returns a resolved config object.
46
45
  */
47
46
  export function resolveConfig(config = {}) {
48
47
  const resolved = { ...CONFIG_DEFAULTS, ...config }
49
- if (typeof resolved.enabled !== 'boolean') {
50
- throw new TypeError('ptc-plus: enabled must be a boolean')
51
- }
52
- for (const key of [...BUDGET_KEYS, ...POSITIVE_KEYS]) {
53
- const value = resolved[key]
54
- if (!Number.isSafeInteger(value) || value < 1) {
55
- throw new TypeError(`ptc-plus: ${key} must be a positive safe integer`)
56
- }
57
- }
58
- validateMaxWallMs(resolved.maxWallMs)
59
- for (const key of BOOLEAN_KEYS) {
60
- if (typeof resolved[key] !== 'boolean') {
61
- throw new TypeError(`ptc-plus: ${key} must be a boolean`)
62
- }
48
+ for (const field of CONFIG_FIELDS) assertField(field, resolved[field.key])
49
+ // Migrate complete old policies; mixed choices retain their explicit
50
+ // compatibility state until the user selects the unified language.
51
+ const legacyKeys = [
52
+ 'looseTopLevelRedeclarations', 'looseTopLevelFunctionClassRedeclarations',
53
+ 'autoRewriteImports', 'autoStripExports', 'autoSplitRedeclarations',
54
+ ]
55
+ if (!Object.hasOwn(config, 'bindingUpdates')
56
+ && legacyKeys.some(key => Object.hasOwn(config, key))) {
57
+ if (legacyKeys.every(key => resolved[key] === true)) {
58
+ resolved.bindingUpdates = 'stateful'
59
+ } else if (legacyKeys.slice(0, 2).every(key => resolved[key] === false)
60
+ && resolved.autoSplitRedeclarations === false
61
+ && resolved.autoRewriteImports && resolved.autoStripExports) {
62
+ resolved.bindingUpdates = 'protected'
63
+ } else resolved.legacyBindingSettings = true
63
64
  }
64
65
  return resolved
65
66
  }
@@ -70,11 +71,6 @@ export function resolveConfig(config = {}) {
70
71
  * @returns the validated value.
71
72
  */
72
73
  export function validateMaxWallMs(value) {
73
- if (!Number.isSafeInteger(value) || value < 1) {
74
- throw new TypeError('ptc-plus: maxWallMs must be a positive safe integer')
75
- }
76
- if (value > MAX_TIMER_DELAY_MS) {
77
- throw new TypeError(`ptc-plus: maxWallMs must not exceed ${MAX_TIMER_DELAY_MS}`)
78
- }
74
+ assertIntegerField(MAX_WALL_MS_FIELD, value)
79
75
  return value
80
76
  }