dsh-ptc-plus 0.4.10 → 0.5.0
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.
- package/README.en.md +60 -130
- package/README.md +60 -130
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
- package/assets/ptc-plus-repl-workspace-en.png +0 -0
- package/assets/ptc-plus-repl-workspace-zh.png +0 -0
- package/assets/ptc-plus-settings-en.png +0 -0
- package/assets/ptc-plus-settings-zh.png +0 -0
- package/client.cjs +42336 -0
- package/cordis.patch.yml +1 -0
- package/docs/README.md +6 -2
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +31 -198
- package/docs/adr/0008-define-agent-operating-contract.md +5 -1
- package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -2
- package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
- package/docs/adr/0017-track-the-latest-dsh-public-surface.md +2 -2
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +1 -1
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +1 -1
- package/docs/adr/0022-stage-npm-releases-with-oidc.md +4 -2
- package/docs/adr/0023-global-user-bindings.md +2 -0
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +2 -0
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +25 -23
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +1 -1
- package/docs/adr/0034-keep-program-values-separate-from-output-projections.md +29 -0
- package/docs/adr/0035-run-coverage-only-before-a-new-release.md +27 -0
- package/docs/adr/0036-separate-focused-development-from-delivery-verification.md +29 -0
- package/docs/adr/0037-keep-tool-hover-previews-passive.md +52 -0
- package/docs/adr/0038-freeze-session-capability-plans.md +71 -0
- package/docs/adr/0039-verify-documentation-without-runtime-suites.md +29 -0
- package/docs/adr/0040-separate-capability-configurations-from-session-observations.md +39 -0
- package/docs/adr/0041-record-cell-execution-directories.md +65 -0
- package/docs/adr/0042-persist-provider-availability-and-handback.md +41 -0
- package/docs/adr/0043-compose-capability-and-context-policies.md +37 -0
- package/docs/adr/README.md +6 -0
- package/docs/architecture.md +23 -25
- package/docs/capability-planning-components.md +93 -0
- package/docs/capability-projection.md +7 -0
- package/docs/client-ui.md +70 -23
- package/docs/client-visual-contract.md +26 -9
- package/docs/context-management.md +22 -0
- package/docs/context-optimization-plan.md +353 -0
- package/docs/context-public-interface-coverage.md +25 -0
- package/docs/durability-design.md +20 -15
- package/docs/evaluation.md +41 -6
- package/docs/evaluations/command-goat-deepseek-v4.1-flash-20261006.md +126 -0
- package/docs/evaluations/command-goat-deepseek-v4.1-flash.md +281 -0
- package/docs/installation.md +31 -10
- package/docs/program-data-plane.md +5 -3
- package/docs/publishing.md +5 -5
- package/docs/release-coverage.md +73 -0
- package/docs/release-notes.md +1 -1
- package/docs/review-procedure.md +56 -0
- package/docs/runtime-reference.md +48 -16
- package/docs/semantic-validation.md +14 -2
- package/docs/stateful-binding-updates-proposal.md +2 -2
- package/docs/user-bindings.md +1 -1
- package/docs/value-wire.md +11 -1
- package/docs/verification-optimization-work.md +26 -15
- package/docs/verification.md +30 -88
- package/index.js +123 -20
- package/internal/binding-toggle-receipt.js +24 -0
- package/internal/capability-command-receipt.js +29 -0
- package/internal/capability-environment.js +1247 -0
- package/internal/capability-plan.js +285 -0
- package/internal/capability-policy.js +96 -0
- package/internal/capability-statistics-store.js +180 -0
- package/internal/capability-statistics.js +210 -0
- package/internal/config-spec.js +79 -9
- package/internal/direct-surface-owner.js +27 -29
- package/internal/dynamic-environment-runtime.js +9 -9
- package/internal/edit-transport-owner.js +180 -139
- package/internal/execution-provider-owner.js +15 -12
- package/internal/execution-seam-compat.js +8 -8
- package/internal/kernel-worker.js +104 -32
- package/internal/native-root-dynamic.js +12 -13
- package/internal/program-bindings.js +190 -0
- package/internal/recovery-tips.js +3 -2
- package/internal/rejected-cell-editor.js +8 -8
- package/internal/repl-observation-interest.js +11 -3
- package/internal/rpc-contract.js +1 -0
- package/internal/runtime-bridge-owner.js +272 -34
- package/internal/runtime-config.js +58 -0
- package/internal/runtime-contexts.js +74 -5
- package/internal/runtime-intrinsics.js +1 -0
- package/internal/runtime-messages.js +41 -0
- package/internal/sdk-projection.js +10 -82
- package/internal/session-cell-executor.js +42 -19
- package/internal/session-events.js +34 -0
- package/internal/session-journal-recovery.js +234 -11
- package/internal/session-journal-schema.js +6 -3
- package/internal/session-journal.js +31 -2
- package/internal/session-log-view.js +1 -1
- package/internal/session-runtime.js +119 -15
- package/internal/settings-compat.js +12 -5
- package/internal/stateful-module-compiler.js +3 -2
- package/internal/stateful-module-runtime.js +7 -3
- package/internal/stateful-root-runtime.js +9 -4
- package/internal/user-binding-authoring.js +2 -7
- package/internal/user-bindings-owner.js +41 -2
- package/internal/validated-parse-repair.js +1 -1
- package/internal/value-wire.js +68 -21
- package/internal/worker-cwd-virtualization.js +20 -18
- package/internal/worker-realm-surfaces.js +3 -3
- package/package.json +47 -29
- package/src/client-authoring-view.js +67 -30
- package/src/client-binding-review.js +2 -1
- package/src/client-binding-toggle.js +18 -0
- package/src/client-capabilities-view.js +1341 -0
- package/src/client-capability-sort.js +29 -0
- package/src/client-copy.js +871 -8
- package/src/client-feature-gates.js +3 -0
- package/src/client-indicator-view.js +4 -6
- package/src/client-primitives.js +33 -2
- package/src/client-repl-view.js +20 -13
- package/src/client-settings-view.js +411 -41
- package/src/client-styles.js +108 -15
- package/src/client-tool-hover.js +185 -0
- package/src/client-tool-view.js +37 -8
- package/src/client-transport.js +44 -9
- package/src/client-workbench.js +6 -7
- package/src/client.js +76 -22
- package/client.js +0 -32479
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** Own the execution-seam takeover, per-cell lease, journal projection, and session settlement. */
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
3
|
+
import { deepEqualJson, snapshotJsonValue } from '@deepseek-ai/dsh-util-values'
|
|
4
|
+
import { decodeValue, renderValueWire } from './value-wire.js'
|
|
5
|
+
import { valueLimitsFromConfig } from './value-wire-schema.js'
|
|
3
6
|
import { executionPolicies } from './binding-update-policy.js'
|
|
4
7
|
import { assertObjectJsonSchema, validateJsonSchemaValue } from '@deepseek-ai/dsh-tools'
|
|
5
8
|
import { normalizeBindingDescriptors } from './binding-descriptors.js'
|
|
@@ -17,11 +20,13 @@ import {
|
|
|
17
20
|
capabilityFind,
|
|
18
21
|
capabilityInspect,
|
|
19
22
|
capabilityTree,
|
|
23
|
+
createCapabilitySearcher,
|
|
20
24
|
toolCapabilityMetadata,
|
|
21
25
|
} from './program-bindings.js'
|
|
22
26
|
import { deepFreeze } from './record-utils.js'
|
|
23
|
-
import { withReplMemorySnapshot } from './repl-memory-projection.js'
|
|
27
|
+
import { REPL_MEMORY_META_KEY, withReplMemorySnapshot } from './repl-memory-projection.js'
|
|
24
28
|
import { withUserBindingDraftCapability } from './user-binding-draft-projection.js'
|
|
29
|
+
import { POST_EDIT_KEY } from './session-journal-schema.js'
|
|
25
30
|
import { generatedRunCodeDescriptionMeta } from './run-code-description.js'
|
|
26
31
|
import { BINDING_SUBMISSION } from './user-binding-authoring.js'
|
|
27
32
|
import {
|
|
@@ -71,7 +76,7 @@ const PROGRAM_CAPABILITY_METADATA = deepFreeze([
|
|
|
71
76
|
namespace: 'repl',
|
|
72
77
|
members: [{
|
|
73
78
|
name: 'state',
|
|
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
|
|
79
|
+
description: 'Manage named REPL checkpoints. list returns checkpoint names, not variable names. Save requires a durable cell; a volatile save rejection leaves live bindings usable. 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 discards bindings beyond the selected durable state for subsequent cells; an omitted name selects the prior durable head, or an empty REPL if none exists. Restore does not undo external effects or change enabled binding configuration; subsequent cells may remain volatile.',
|
|
75
80
|
parameters: {
|
|
76
81
|
type: 'object',
|
|
77
82
|
properties: {
|
|
@@ -128,7 +133,7 @@ const PROGRAM_CAPABILITY_METADATA = deepFreeze([
|
|
|
128
133
|
description: 'Child console output in emission order.',
|
|
129
134
|
},
|
|
130
135
|
result: {
|
|
131
|
-
description: '
|
|
136
|
+
description: 'Plugin-owned isolated execution preserves supported PTC Value V1 types and reference relationships; omitted for no return, present for explicit undefined. Native delegation and legacy upstream execution retain their original result contracts.',
|
|
132
137
|
},
|
|
133
138
|
},
|
|
134
139
|
required: ['logs'],
|
|
@@ -154,6 +159,7 @@ export function createRuntimeBridgeOwner({
|
|
|
154
159
|
toolSchemasForAgent,
|
|
155
160
|
userBindingDraftForAgent,
|
|
156
161
|
bindingSubmissionForAgent,
|
|
162
|
+
capabilityEnvironment,
|
|
157
163
|
}) {
|
|
158
164
|
const scope = new AsyncLocalStorage()
|
|
159
165
|
const childScope = new AsyncLocalStorage()
|
|
@@ -173,22 +179,27 @@ export function createRuntimeBridgeOwner({
|
|
|
173
179
|
const withInitiator = ctx.agents === undefined || typeof ctx.agents.withInitiator !== 'function'
|
|
174
180
|
? undefined
|
|
175
181
|
: (agent, operation) => ctx.agents.withInitiator(agent, operation)
|
|
176
|
-
const sessions = new SessionRuntime(sessionConfig, {
|
|
182
|
+
const sessions = new SessionRuntime(sessionConfig, {
|
|
183
|
+
withInitiator, userBindingsCwd, observeSession, readSessionLog,
|
|
184
|
+
sessionForId: id => ctx.get('sessions')?.get(id),
|
|
185
|
+
})
|
|
177
186
|
let currentConfig = sessions.config
|
|
178
187
|
const patchedDefinitions = new Map()
|
|
179
188
|
const pending = new WeakMap()
|
|
180
189
|
const nativeRunCodeHandoffs = new Set()
|
|
181
190
|
let active = true
|
|
182
|
-
const runIsolated = async (request, executionToken, depth, cellConfig, inheritedTools) => {
|
|
191
|
+
const runIsolated = async (request, executionToken, depth, cellConfig, inheritedTools, programCompletion = false) => {
|
|
183
192
|
const child = new SessionRuntime({ ...cellConfig, durableReplay: false, userBindingsEnabled: false,
|
|
184
193
|
replViewEnabled: false }, { withInitiator, userBindingsCwd })
|
|
185
194
|
childRuntimes.add(child)
|
|
186
195
|
const projected = projectBindings(request, depth, executionToken, inheritedTools, cellConfig)
|
|
187
196
|
childFinalizers.add(child)
|
|
188
197
|
try {
|
|
189
|
-
|
|
190
|
-
cwd: executionToken?.session?.header?.cwd,
|
|
191
|
-
} } }, { ...projected.request, executionToken })
|
|
198
|
+
const execution = await child.runTentative({ id: 'isolated-cell', session: { header: {
|
|
199
|
+
cwd: request.cwd ?? executionToken?.session?.header?.cwd,
|
|
200
|
+
} } }, { ...projected.request, executionToken }, { programCompletion })
|
|
201
|
+
if (execution.settlement !== undefined) child.finalize(execution.settlement, true)
|
|
202
|
+
return { result: execution.result, completion: execution.settlement?.journal.completion }
|
|
192
203
|
} finally {
|
|
193
204
|
projected.release()
|
|
194
205
|
try {
|
|
@@ -263,11 +274,12 @@ export function createRuntimeBridgeOwner({
|
|
|
263
274
|
}
|
|
264
275
|
if (languageSemantics !== 'legacy-v1') {
|
|
265
276
|
const child = await runIsolated({ ...request, program: args.code }, executionToken,
|
|
266
|
-
depth + 1, cellConfig, functions)
|
|
267
|
-
if (child.error !== undefined) {
|
|
268
|
-
throw new Error(`nested run_code failed (${child.error.kind}): ${child.error.message}`)
|
|
277
|
+
depth + 1, cellConfig, functions, true)
|
|
278
|
+
if (child.result.error !== undefined) {
|
|
279
|
+
throw new Error(`nested run_code failed (${child.result.error.kind}): ${child.result.error.message}`)
|
|
269
280
|
}
|
|
270
|
-
return { logs: child.logs, ...(child.
|
|
281
|
+
return { logs: child.result.logs, ...(child.completion.hasValue
|
|
282
|
+
? { result: decodeValue(child.completion.value, valueLimitsFromConfig(cellConfig)) } : {}) }
|
|
271
283
|
}
|
|
272
284
|
const childProjected = projectBindings(
|
|
273
285
|
{ ...request, program: args.code }, depth + 1, executionToken, functions, cellConfig,
|
|
@@ -284,7 +296,8 @@ export function createRuntimeBridgeOwner({
|
|
|
284
296
|
return { logs: child.logs, ...(child.value === undefined ? {} : { result: child.value }) }
|
|
285
297
|
}
|
|
286
298
|
|
|
287
|
-
const
|
|
299
|
+
const nativeSchemas = toolSchemasForAgent(executionToken?.agent)
|
|
300
|
+
const schemas = (capabilityEnvironment?.visibleSchemas(executionToken?.agent, nativeSchemas) ?? nativeSchemas)
|
|
288
301
|
.filter(schema => schema?.name === RUN_CODE
|
|
289
302
|
|| (schema?.name !== EDIT_RUN_CODE && typeof functions[schema?.name] === 'function'))
|
|
290
303
|
const emptyObjectTools = new Set(schemas.flatMap(schema => (
|
|
@@ -294,20 +307,30 @@ export function createRuntimeBridgeOwner({
|
|
|
294
307
|
)))
|
|
295
308
|
const projected = bindingDescriptors.namespaces.map((binding) => {
|
|
296
309
|
const wrapped = Object.create(null)
|
|
297
|
-
|
|
310
|
+
const members = binding.global === 'tools'
|
|
311
|
+
? capabilityEnvironment?.projectMembers(executionToken?.agent, binding.members) ?? binding.members
|
|
312
|
+
: binding.members
|
|
313
|
+
for (const key of members) {
|
|
314
|
+
const original = (...args) => binding.functions[key](...args)
|
|
315
|
+
const invoke = binding.global === 'tools'
|
|
316
|
+
? capabilityEnvironment?.adaptFunction(executionToken?.agent, key, original) ?? original
|
|
317
|
+
: original
|
|
298
318
|
Object.defineProperty(wrapped, key, {
|
|
299
319
|
enumerable: true,
|
|
300
320
|
value: async (...args) => {
|
|
301
321
|
ensureLease()
|
|
302
|
-
return binding.
|
|
322
|
+
return binding.global === 'tools' && key === EDIT_RUN_CODE
|
|
323
|
+
? scope.run(executionToken, () => invoke(...args))
|
|
324
|
+
: invoke(...args)
|
|
303
325
|
},
|
|
304
326
|
})
|
|
305
327
|
}
|
|
306
328
|
const emptyObjectMembers = binding.global === 'tools'
|
|
307
|
-
?
|
|
329
|
+
? members.filter(member => emptyObjectTools.has(member))
|
|
308
330
|
: []
|
|
309
331
|
return {
|
|
310
332
|
...binding,
|
|
333
|
+
members,
|
|
311
334
|
functions: wrapped,
|
|
312
335
|
...(binding.global === 'tools' ? { emptyObjectMembers } : {}),
|
|
313
336
|
}
|
|
@@ -318,12 +341,14 @@ export function createRuntimeBridgeOwner({
|
|
|
318
341
|
: []
|
|
319
342
|
)))
|
|
320
343
|
const metadata = Object.freeze([
|
|
321
|
-
...toolCapabilityMetadata(schemas, annotations),
|
|
344
|
+
...(capabilityEnvironment?.toolMetadata(executionToken?.agent, toolCapabilityMetadata(schemas, annotations), schemas) ?? toolCapabilityMetadata(schemas, annotations)),
|
|
345
|
+
...(capabilityEnvironment?.metadata(executionToken?.agent) ?? []),
|
|
322
346
|
...PROGRAM_CAPABILITY_METADATA.map(entry => entry.namespace === 'code'
|
|
323
347
|
&& cellConfig.userBindingsEnabled === true
|
|
324
348
|
? { ...entry, members: [...entry.members, BINDING_SUBMISSION] }
|
|
325
349
|
: entry),
|
|
326
350
|
])
|
|
351
|
+
let searchCapabilities
|
|
327
352
|
projected.push(namespace('capabilities', {
|
|
328
353
|
tree: async value => {
|
|
329
354
|
if (value !== undefined) throw new TypeError('capabilities.tree does not accept arguments')
|
|
@@ -337,11 +362,24 @@ export function createRuntimeBridgeOwner({
|
|
|
337
362
|
},
|
|
338
363
|
inspect: async value => {
|
|
339
364
|
ensureLease()
|
|
340
|
-
if (value === undefined)
|
|
365
|
+
if (value === undefined) {
|
|
366
|
+
const result = capabilityInspect(metadata)
|
|
367
|
+
capabilityEnvironment?.explored(executionToken?.agent, result)
|
|
368
|
+
return result
|
|
369
|
+
}
|
|
341
370
|
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
342
371
|
throw new TypeError('capabilities.inspect expects an object')
|
|
343
372
|
}
|
|
344
|
-
|
|
373
|
+
const result = capabilityInspect(metadata, value.symbols, value.budget)
|
|
374
|
+
capabilityEnvironment?.explored(executionToken?.agent, result)
|
|
375
|
+
return result
|
|
376
|
+
},
|
|
377
|
+
search: async value => {
|
|
378
|
+
ensureLease()
|
|
379
|
+
searchCapabilities ??= createCapabilitySearcher(metadata)
|
|
380
|
+
const result = searchCapabilities(value)
|
|
381
|
+
capabilityEnvironment?.explored(executionToken?.agent, result)
|
|
382
|
+
return result
|
|
345
383
|
},
|
|
346
384
|
}, 'CapabilityExplorationError', 'operation'))
|
|
347
385
|
projected.push(namespace('code', {
|
|
@@ -367,6 +405,10 @@ export function createRuntimeBridgeOwner({
|
|
|
367
405
|
const output = definition.output
|
|
368
406
|
const ownPresentationMeta = Object.getOwnPropertyDescriptor(output, 'presentationMeta')
|
|
369
407
|
const patchedPresentationMeta = (args, value) => {
|
|
408
|
+
const projectedCurrent = scope.getStore()
|
|
409
|
+
if (projectedCurrent !== undefined) {
|
|
410
|
+
projectedCurrent.presentationMetaCalls = (projectedCurrent.presentationMetaCalls ?? 0) + 1
|
|
411
|
+
}
|
|
370
412
|
const prototype = Object.getPrototypeOf(output)
|
|
371
413
|
const originalPresentationMeta = ownPresentationMeta === undefined
|
|
372
414
|
? prototype === null ? undefined : Reflect.get(prototype, 'presentationMeta', output)
|
|
@@ -374,8 +416,15 @@ export function createRuntimeBridgeOwner({
|
|
|
374
416
|
? ownPresentationMeta.value
|
|
375
417
|
: ownPresentationMeta.get?.call(output)
|
|
376
418
|
const base = originalPresentationMeta === undefined
|
|
377
|
-
|
|
378
|
-
|
|
419
|
+
// Installing a projector replaces an optional Host callback with a
|
|
420
|
+
// required JSON-producing callback, including policy reprojection
|
|
421
|
+
// after the execution scope has ended. An empty object conveys no
|
|
422
|
+
// computation evidence; a stale settlement must never fill that gap.
|
|
423
|
+
? {}
|
|
424
|
+
: snapshotJsonValue(originalPresentationMeta.call(output, args, value))
|
|
425
|
+
// An invalid upstream projection is still the Host's output failure;
|
|
426
|
+
// adding plugin metadata must not make it appear valid.
|
|
427
|
+
if (base === undefined) return undefined
|
|
379
428
|
if (!active) return base
|
|
380
429
|
const current = scope.getStore()
|
|
381
430
|
const settlement = current?.settlement
|
|
@@ -388,9 +437,50 @@ export function createRuntimeBridgeOwner({
|
|
|
388
437
|
if (settlement.userBindings !== undefined) {
|
|
389
438
|
meta = withUserBindingsSnapshot(meta, settlement.userBindings)
|
|
390
439
|
}
|
|
440
|
+
if (current.postEdit?.provenance !== undefined) {
|
|
441
|
+
meta = { ...meta, [POST_EDIT_KEY]: current.postEdit.provenance }
|
|
442
|
+
}
|
|
391
443
|
meta = generatedRunCodeDescriptionMeta(args, meta)
|
|
392
444
|
meta = withReplMemorySnapshot(meta, settlement.replMemory, presentationGeneration)
|
|
393
|
-
|
|
445
|
+
if (current.postEdit?.provenance?.meta?.[REPL_MEMORY_META_KEY] !== undefined) {
|
|
446
|
+
meta = { ...meta,
|
|
447
|
+
[REPL_MEMORY_META_KEY]: current.postEdit.provenance.meta[REPL_MEMORY_META_KEY] }
|
|
448
|
+
}
|
|
449
|
+
meta = withDraftCapability(meta, current)
|
|
450
|
+
return capabilityEnvironment?.complete(current, { meta }).meta ?? meta
|
|
451
|
+
}
|
|
452
|
+
const ownProjectContent = Object.getOwnPropertyDescriptor(definition, 'projectContent')
|
|
453
|
+
const patchedProjectContent = (exec, result) => {
|
|
454
|
+
const prototype = Object.getPrototypeOf(definition)
|
|
455
|
+
const originalProjectContent = ownProjectContent === undefined
|
|
456
|
+
? prototype === null ? undefined : Reflect.get(prototype, 'projectContent', definition)
|
|
457
|
+
: Object.hasOwn(ownProjectContent, 'value')
|
|
458
|
+
? ownProjectContent.value : ownProjectContent.get?.call(definition)
|
|
459
|
+
const original = originalProjectContent?.call(definition, exec, result)
|
|
460
|
+
const current = active ? pending.get(exec) : undefined
|
|
461
|
+
const derived = current?.derivedExecution
|
|
462
|
+
// Some public Host implementations omit the metadata projector for a
|
|
463
|
+
// nested success. Capture its canonical value before policy; the private
|
|
464
|
+
// relation is admitted only if policy retains that same frozen value.
|
|
465
|
+
if (current?.deferredSettlement?.current === current && derived !== undefined
|
|
466
|
+
&& current.exec === exec && exec.token !== undefined && exec.parent !== undefined
|
|
467
|
+
&& current.id === sessionId(exec.agent) && derived.agent === exec.agent
|
|
468
|
+
&& derived.callId === exec.callId && derived.rootCallId === exec.rootCallId
|
|
469
|
+
&& derived.parent === exec.parent && deepEqualJson(derived.arguments, exec.arguments)
|
|
470
|
+
&& current.presentationMetaCalls === undefined && result.isError === false
|
|
471
|
+
&& result.value !== null && typeof result.value === 'object'
|
|
472
|
+
&& Object.isFrozen(result.value) && result.meta?.[JOURNAL_KEY] === undefined
|
|
473
|
+
&& current.settlement !== undefined) {
|
|
474
|
+
const metadata = scope.run(current, () => patchedPresentationMeta(exec.arguments, result.value))
|
|
475
|
+
if (metadata?.[JOURNAL_KEY] !== undefined) {
|
|
476
|
+
current.deferredSettlement.projection = { exec, token: exec.token,
|
|
477
|
+
value: result.value, meta: snapshotJsonValue(result.meta), metadata,
|
|
478
|
+
presentationMetaCalls: current.presentationMetaCalls }
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const presentation = active ? pending.get(exec)?.postEdit?.presentation : undefined
|
|
482
|
+
if (presentation === undefined) return original
|
|
483
|
+
return [...original ?? result.content, ...presentation.content]
|
|
394
484
|
}
|
|
395
485
|
const ownExecute = Object.getOwnPropertyDescriptor(definition, 'execute')
|
|
396
486
|
const originalExecute = definition.execute
|
|
@@ -409,6 +499,10 @@ export function createRuntimeBridgeOwner({
|
|
|
409
499
|
value: patchedPresentationMeta,
|
|
410
500
|
})
|
|
411
501
|
presentationPatched = true
|
|
502
|
+
Object.defineProperty(definition, 'projectContent', {
|
|
503
|
+
configurable: true, enumerable: ownProjectContent?.enumerable ?? true,
|
|
504
|
+
writable: true, value: patchedProjectContent,
|
|
505
|
+
})
|
|
412
506
|
if (patchedExecute !== undefined) {
|
|
413
507
|
Object.defineProperty(definition, 'execute', {
|
|
414
508
|
configurable: true,
|
|
@@ -422,10 +516,20 @@ export function createRuntimeBridgeOwner({
|
|
|
422
516
|
output,
|
|
423
517
|
ownPresentationMeta,
|
|
424
518
|
patchedPresentationMeta,
|
|
519
|
+
ownProjectContent,
|
|
520
|
+
patchedProjectContent,
|
|
425
521
|
ownExecute,
|
|
426
522
|
patchedExecute,
|
|
427
523
|
})
|
|
428
524
|
} catch (error) {
|
|
525
|
+
if (definition.projectContent === patchedProjectContent) {
|
|
526
|
+
if (ownProjectContent === undefined) delete definition.projectContent
|
|
527
|
+
else Object.defineProperty(definition, 'projectContent', ownProjectContent)
|
|
528
|
+
}
|
|
529
|
+
if (patchedExecute !== undefined && definition.execute === patchedExecute) {
|
|
530
|
+
if (ownExecute === undefined) delete definition.execute
|
|
531
|
+
else Object.defineProperty(definition, 'execute', ownExecute)
|
|
532
|
+
}
|
|
429
533
|
if (presentationPatched && output.presentationMeta === patchedPresentationMeta) {
|
|
430
534
|
if (ownPresentationMeta === undefined) delete output.presentationMeta
|
|
431
535
|
else Object.defineProperty(output, 'presentationMeta', ownPresentationMeta)
|
|
@@ -440,6 +544,7 @@ export function createRuntimeBridgeOwner({
|
|
|
440
544
|
if (!active || !child.active) return Promise.reject(new Error('PTC execution lease expired'))
|
|
441
545
|
if (executionPolicies(child.cellConfig).languageSemantics !== 'legacy-v1') {
|
|
442
546
|
return runIsolated(request, child.executionToken, child.depth, child.cellConfig, child.functions)
|
|
547
|
+
.then(execution => execution.result)
|
|
443
548
|
}
|
|
444
549
|
}
|
|
445
550
|
if (!active) return seam.runUpstream(request)
|
|
@@ -451,22 +556,90 @@ export function createRuntimeBridgeOwner({
|
|
|
451
556
|
executionToken: current,
|
|
452
557
|
...(current.userBindings === undefined ? {} : { userBindings: current.userBindings }),
|
|
453
558
|
})
|
|
454
|
-
.then((execution) => {
|
|
559
|
+
.then(async (execution) => {
|
|
455
560
|
current.settlement = execution.settlement
|
|
456
|
-
|
|
561
|
+
const pendingEdit = current.pendingEdit
|
|
562
|
+
if (pendingEdit === undefined) return execution.result
|
|
563
|
+
if (!pendingEdit.confirmed || current.signal.aborted) {
|
|
564
|
+
pendingEdit.finish(false)
|
|
565
|
+
return execution.result
|
|
566
|
+
}
|
|
567
|
+
try {
|
|
568
|
+
const postEdit = await pendingEdit.dispatch(current.signal, current.exec)
|
|
569
|
+
current.postEdit = postEdit
|
|
570
|
+
for (const context of postEdit.presentation?.additionalContexts ?? []) {
|
|
571
|
+
current.exec.deferContext?.(context)
|
|
572
|
+
}
|
|
573
|
+
if (postEdit.presentation?.concludesTurn === true && execution.result.error === undefined) {
|
|
574
|
+
current.exec.concludeTurn?.()
|
|
575
|
+
}
|
|
576
|
+
const outcome = postEdit.outcome
|
|
577
|
+
const value = postEdit.provenance?.meta?.[JOURNAL_KEY]?.completion
|
|
578
|
+
const hasRenderedContent = postEdit.presentation?.content?.length > 0
|
|
579
|
+
let valueText = ''
|
|
580
|
+
if (!hasRenderedContent && value?.kind === 'return' && value.hasValue) {
|
|
581
|
+
try {
|
|
582
|
+
valueText = `; value: ${renderValueWire(value.value, {
|
|
583
|
+
...valueLimitsFromConfig(current.submission?.config ?? currentConfig),
|
|
584
|
+
maxStringBytes: 2048,
|
|
585
|
+
})}`
|
|
586
|
+
} catch {
|
|
587
|
+
valueText = '; value omitted from preview'
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const status = outcome.edited
|
|
591
|
+
? `edit_run_code settled${outcome.error === undefined ? '' : ' with execution error'}`
|
|
592
|
+
: 'edit_run_code did not execute the accepted revision'
|
|
593
|
+
const detail = [
|
|
594
|
+
...(hasRenderedContent || !Array.isArray(outcome.logs) ? [] : outcome.logs),
|
|
595
|
+
...(!hasRenderedContent && typeof outcome.error === 'string' ? [outcome.error] : []),
|
|
596
|
+
...(!hasRenderedContent && typeof outcome.reason === 'string' ? [outcome.reason] : []),
|
|
597
|
+
].filter(item => typeof item === 'string').join('\n')
|
|
598
|
+
return { ...execution.result,
|
|
599
|
+
logs: [...execution.result.logs, `${status}${valueText}${detail ? `: ${detail}` : ''}`] }
|
|
600
|
+
} catch (error) {
|
|
601
|
+
pendingEdit.finish(false)
|
|
602
|
+
return { ...execution.result,
|
|
603
|
+
logs: [...execution.result.logs,
|
|
604
|
+
`edit_run_code failed after acceptance: ${error instanceof Error ? error.message : String(error)}`] }
|
|
605
|
+
}
|
|
457
606
|
})
|
|
458
607
|
.finally(projected.release)
|
|
459
608
|
}
|
|
460
609
|
|
|
461
610
|
const releaseSeam = seam.installExecute(patchedRun)
|
|
462
611
|
|
|
612
|
+
const ownedDerivedScope = (exec, executionArguments = exec.arguments) => {
|
|
613
|
+
const inherited = scope.getStore()
|
|
614
|
+
const derived = inherited?.derivedExecution
|
|
615
|
+
if (inherited?.deferredSettlement === undefined
|
|
616
|
+
|| inherited.id !== sessionId(exec.agent) || derived?.agent !== exec.agent
|
|
617
|
+
|| derived.callId !== exec.callId || derived.rootCallId !== exec.rootCallId
|
|
618
|
+
|| derived.parent !== exec.parent || exec.name !== RUN_CODE
|
|
619
|
+
|| !deepEqualJson(derived.arguments, executionArguments)) return undefined
|
|
620
|
+
return inherited
|
|
621
|
+
}
|
|
622
|
+
|
|
463
623
|
const withSubmission = async (exec, operation) => {
|
|
464
624
|
const id = sessionId(exec.agent)
|
|
465
625
|
const inherited = scope.getStore()
|
|
466
|
-
|
|
626
|
+
const ownsDerived = ownedDerivedScope(exec) !== undefined
|
|
627
|
+
if ((exec.parent !== undefined && !ownsDerived) || id === undefined
|
|
628
|
+
|| inherited?.submission?.sessionId === id) return operation()
|
|
467
629
|
const submission = sessions.beginSubmission({ id }, exec.signal)
|
|
630
|
+
const composite = inherited?.deferredSettlement !== undefined && inherited.id === id
|
|
631
|
+
? {
|
|
632
|
+
persistedCallSeq: inherited.persistedCallSeq,
|
|
633
|
+
sourceCallSeq: inherited.sourceCallSeq,
|
|
634
|
+
sessionLog: inherited.sessionLog,
|
|
635
|
+
deferredSettlement: inherited.deferredSettlement,
|
|
636
|
+
derivedExecution: inherited.derivedExecution,
|
|
637
|
+
}
|
|
638
|
+
: {}
|
|
468
639
|
try {
|
|
469
|
-
|
|
640
|
+
// Derived edits retain their proved source under a fresh admission for
|
|
641
|
+
// the same session. Delegated agents own a fresh log and no child handoff.
|
|
642
|
+
return await scope.run({ ...composite, submission }, () => childScope.run(undefined, operation))
|
|
470
643
|
} finally {
|
|
471
644
|
sessions.releaseSubmission(submission)
|
|
472
645
|
}
|
|
@@ -479,6 +652,25 @@ export function createRuntimeBridgeOwner({
|
|
|
479
652
|
return sessionLog
|
|
480
653
|
}
|
|
481
654
|
|
|
655
|
+
const readNestedDispatchLog = async (session, signal) => {
|
|
656
|
+
const inherited = scope.getStore()
|
|
657
|
+
if (inherited === undefined || inherited.session !== session
|
|
658
|
+
|| inherited.signal.aborted || signal?.aborted) {
|
|
659
|
+
throw new Error('ptc-plus: nested edit has no active root run_code cell')
|
|
660
|
+
}
|
|
661
|
+
return readSessionLog(session, signal)
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const rootEditScope = exec => {
|
|
665
|
+
const current = scope.getStore()
|
|
666
|
+
if (!active || current === undefined || childScope.getStore() !== undefined
|
|
667
|
+
|| current.deferredSettlement !== undefined
|
|
668
|
+
|| current.callId !== exec.rootCallId || current.agent !== exec.agent
|
|
669
|
+
|| current.session !== exec.agent?.session || current.signal.aborted
|
|
670
|
+
|| exec.signal?.aborted) return undefined
|
|
671
|
+
return current
|
|
672
|
+
}
|
|
673
|
+
|
|
482
674
|
return Object.freeze({
|
|
483
675
|
config: sessions.config,
|
|
484
676
|
reconfigure(nextConfig) {
|
|
@@ -488,8 +680,18 @@ export function createRuntimeBridgeOwner({
|
|
|
488
680
|
observeRepl(id, memory, signal) { return sessions.observe(id, memory, signal) },
|
|
489
681
|
withSubmission,
|
|
490
682
|
readSubmissionLog,
|
|
683
|
+
readNestedDispatchLog,
|
|
684
|
+
prepareAgent(agent) { patchRunCodeDefinition(agent) },
|
|
685
|
+
canScheduleEdit(exec) { return rootEditScope(exec) !== undefined },
|
|
686
|
+
scheduleEdit(exec, pendingEdit) {
|
|
687
|
+
const current = rootEditScope(exec)
|
|
688
|
+
if (current === undefined || current.pendingEdit !== undefined) return false
|
|
689
|
+
current.pendingEdit = pendingEdit
|
|
690
|
+
return true
|
|
691
|
+
},
|
|
491
692
|
// A composite tool's outer result owns the final durability decision.
|
|
492
|
-
async executeTentative(callSeq, operation, sessionLog) {
|
|
693
|
+
async executeTentative(callSeq, sourceCallSeq, operation, sessionLog, derivedExecution) {
|
|
694
|
+
const id = scope.getStore()?.id
|
|
493
695
|
const settlement = {
|
|
494
696
|
current: undefined,
|
|
495
697
|
innerConfirmed: false,
|
|
@@ -504,22 +706,27 @@ export function createRuntimeBridgeOwner({
|
|
|
504
706
|
}
|
|
505
707
|
try {
|
|
506
708
|
const result = await scope.run({
|
|
507
|
-
|
|
709
|
+
id,
|
|
508
710
|
...(callSeq === undefined ? {} : { persistedCallSeq: callSeq }),
|
|
711
|
+
...(sourceCallSeq === undefined ? {} : { sourceCallSeq }),
|
|
509
712
|
...(sessionLog === undefined ? {} : { sessionLog }),
|
|
510
713
|
deferredSettlement: settlement,
|
|
714
|
+
derivedExecution,
|
|
511
715
|
}, operation)
|
|
512
|
-
return Object.freeze({ result, finalize
|
|
716
|
+
return Object.freeze({ result, finalize,
|
|
717
|
+
...(settlement.metadata === undefined ? {} : { metadata: settlement.metadata }) })
|
|
513
718
|
} catch (error) {
|
|
514
719
|
finalize(false)
|
|
515
720
|
throw error
|
|
516
721
|
}
|
|
517
722
|
},
|
|
518
723
|
async handleExecute(exec, next, executionArguments = exec.arguments, userBindings = undefined) {
|
|
519
|
-
|
|
724
|
+
const ownsDerived = ownedDerivedScope(exec, executionArguments) !== undefined
|
|
725
|
+
if (exec.parent !== undefined && !ownsDerived) return scope.run(undefined, next)
|
|
520
726
|
return withSubmission(exec, async () => {
|
|
521
727
|
const id = sessionId(exec.agent)
|
|
522
728
|
if (id === undefined) return next()
|
|
729
|
+
await capabilityEnvironment?.prepare(exec.agent)
|
|
523
730
|
patchRunCodeDefinition(exec.agent)
|
|
524
731
|
const inherited = scope.getStore()
|
|
525
732
|
const persistedCallSeq = inherited?.persistedCallSeq
|
|
@@ -528,12 +735,16 @@ export function createRuntimeBridgeOwner({
|
|
|
528
735
|
submission: inherited?.submission,
|
|
529
736
|
callId: String(exec.callId),
|
|
530
737
|
...(persistedCallSeq === undefined ? {} : { persistedCallSeq }),
|
|
738
|
+
...(inherited?.sourceCallSeq === undefined ? {} : { sourceCallSeq: inherited.sourceCallSeq }),
|
|
531
739
|
...(inherited?.deferredSettlement === undefined
|
|
532
740
|
? {}
|
|
533
|
-
: { deferredSettlement: inherited.deferredSettlement
|
|
741
|
+
: { deferredSettlement: inherited.deferredSettlement,
|
|
742
|
+
derivedExecution: inherited.derivedExecution }),
|
|
534
743
|
session: exec.agent?.session,
|
|
744
|
+
signal: exec.signal,
|
|
535
745
|
sessionLog: await readSubmissionLog(exec.agent?.session, exec.signal),
|
|
536
746
|
agent: exec.agent,
|
|
747
|
+
exec,
|
|
537
748
|
executionArguments,
|
|
538
749
|
...(userBindings === undefined ? {} : { userBindings }),
|
|
539
750
|
}
|
|
@@ -554,26 +765,47 @@ export function createRuntimeBridgeOwner({
|
|
|
554
765
|
if (settlement.userBindings !== undefined) {
|
|
555
766
|
meta = withUserBindingsSnapshot(meta, settlement.userBindings)
|
|
556
767
|
}
|
|
768
|
+
if (current.postEdit?.provenance !== undefined) {
|
|
769
|
+
meta = { ...meta, [POST_EDIT_KEY]: current.postEdit.provenance }
|
|
770
|
+
}
|
|
557
771
|
meta = withReplMemorySnapshot(meta, settlement.replMemory, presentationGeneration)
|
|
772
|
+
if (current.postEdit?.provenance?.meta?.[REPL_MEMORY_META_KEY] !== undefined) {
|
|
773
|
+
meta = { ...meta,
|
|
774
|
+
[REPL_MEMORY_META_KEY]: current.postEdit.provenance.meta[REPL_MEMORY_META_KEY] }
|
|
775
|
+
}
|
|
558
776
|
meta = withDraftCapability(meta, current)
|
|
559
|
-
return { ...result, meta }
|
|
777
|
+
return capabilityEnvironment?.complete(current, { ...result, meta }) ?? { ...result, meta }
|
|
560
778
|
}
|
|
561
779
|
return result
|
|
562
780
|
})
|
|
563
781
|
})
|
|
564
782
|
},
|
|
565
783
|
handleResult(exec, result) {
|
|
566
|
-
if (exec.parent !== undefined) return
|
|
784
|
+
if (exec.parent !== undefined && !pending.has(exec)) return
|
|
567
785
|
const id = sessionId(exec.agent)
|
|
568
786
|
if (id === undefined) return
|
|
569
787
|
const current = pending.get(exec)
|
|
570
788
|
pending.delete(exec)
|
|
571
789
|
const settlement = current?.settlement
|
|
572
790
|
if (settlement === undefined) {
|
|
791
|
+
current?.pendingEdit?.finish(false)
|
|
573
792
|
sessions.noteNoop(id, current?.sessionLog, exec.callId)
|
|
574
793
|
return
|
|
575
794
|
}
|
|
576
|
-
const
|
|
795
|
+
const deferred = current.deferredSettlement
|
|
796
|
+
const projection = deferred?.projection
|
|
797
|
+
const derived = current.derivedExecution
|
|
798
|
+
const privateMetadata = projection !== undefined && projection.exec === exec
|
|
799
|
+
&& deferred.current === current && current.exec === exec && current.id === id
|
|
800
|
+
&& derived?.agent === exec.agent && derived.callId === exec.callId
|
|
801
|
+
&& derived.rootCallId === exec.rootCallId && derived.parent === exec.parent
|
|
802
|
+
&& deepEqualJson(derived.arguments, exec.arguments)
|
|
803
|
+
&& projection.token === exec.token && projection.value === result?.value
|
|
804
|
+
&& result.isError === false && deepEqualJson(projection.meta, result.meta)
|
|
805
|
+
&& projection.presentationMetaCalls === current.presentationMetaCalls
|
|
806
|
+
? projection.metadata : undefined
|
|
807
|
+
if (deferred !== undefined) deferred.metadata = privateMetadata
|
|
808
|
+
const meta = privateMetadata ?? result?.meta
|
|
577
809
|
const confirmed = meta !== null && typeof meta === 'object' && !Array.isArray(meta)
|
|
578
810
|
&& Object.hasOwn(meta, JOURNAL_KEY)
|
|
579
811
|
&& journalsEqual(meta[JOURNAL_KEY], settlement.journal)
|
|
@@ -592,6 +824,8 @@ export function createRuntimeBridgeOwner({
|
|
|
592
824
|
return
|
|
593
825
|
}
|
|
594
826
|
sessions.finalize(settlement, confirmed)
|
|
827
|
+
current.pendingEdit?.finish(confirmed && current.postEdit?.provenance !== undefined
|
|
828
|
+
&& deepEqualJson(meta?.[POST_EDIT_KEY], current.postEdit.provenance))
|
|
595
829
|
},
|
|
596
830
|
disposeAgent(agent) {
|
|
597
831
|
return sessions.disposeSession(sessionId(agent) ?? String(agent.id))
|
|
@@ -620,6 +854,10 @@ export function createRuntimeBridgeOwner({
|
|
|
620
854
|
if (patched.ownPresentationMeta === undefined) delete patched.output.presentationMeta
|
|
621
855
|
else Object.defineProperty(patched.output, 'presentationMeta', patched.ownPresentationMeta)
|
|
622
856
|
}
|
|
857
|
+
if (patched.definition.projectContent === patched.patchedProjectContent) {
|
|
858
|
+
if (patched.ownProjectContent === undefined) delete patched.definition.projectContent
|
|
859
|
+
else Object.defineProperty(patched.definition, 'projectContent', patched.ownProjectContent)
|
|
860
|
+
}
|
|
623
861
|
if (patched.patchedExecute !== undefined
|
|
624
862
|
&& patched.definition.execute === patched.patchedExecute) {
|
|
625
863
|
if (patched.ownExecute === undefined) delete patched.definition.execute
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { capabilityPolicyForConfig } from './capability-policy.js'
|
|
1
2
|
import {
|
|
2
3
|
CONFIG_DEFAULTS,
|
|
3
4
|
CONFIG_FIELDS,
|
|
@@ -6,6 +7,43 @@ import {
|
|
|
6
7
|
|
|
7
8
|
export { MAX_TIMER_DELAY_MS }
|
|
8
9
|
|
|
10
|
+
/** Reusable configurations contain capability settings, never session evidence. */
|
|
11
|
+
export const CAPABILITY_CONFIGURATION_KEYS = Object.freeze(CONFIG_FIELDS
|
|
12
|
+
.filter(field => (field.key.startsWith('capability') && field.key !== 'capabilityProfiles') || ['modelPreviewTokens', 'modelPreviewEnabled'].includes(field.key))
|
|
13
|
+
.map(field => field.key))
|
|
14
|
+
|
|
15
|
+
export function capabilityProfileConfiguration(value, { allowInvalidStatistics = false } = {}) {
|
|
16
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
17
|
+
|| Object.keys(value).some(key => !CAPABILITY_CONFIGURATION_KEYS.includes(key))) {
|
|
18
|
+
throw new TypeError('ptc-plus: invalid capability configuration fields')
|
|
19
|
+
}
|
|
20
|
+
const resolved = resolveConfig(value)
|
|
21
|
+
let statistics
|
|
22
|
+
try { statistics = JSON.parse(resolved.capabilityStatistics) } catch {}
|
|
23
|
+
if (!allowInvalidStatistics && (!statistics || typeof statistics.enabled !== 'boolean'
|
|
24
|
+
|| !Number.isSafeInteger(statistics.windowSize) || statistics.windowSize < 1
|
|
25
|
+
|| !Number.isSafeInteger(statistics.minimumSamples) || statistics.minimumSamples < 1 || statistics.minimumSamples > statistics.windowSize
|
|
26
|
+
|| !Number.isFinite(statistics.fullThreshold) || statistics.fullThreshold < 0 || statistics.fullThreshold > 1)) {
|
|
27
|
+
throw new TypeError('ptc-plus: invalid saved capability statistics settings')
|
|
28
|
+
}
|
|
29
|
+
return Object.fromEntries(CAPABILITY_CONFIGURATION_KEYS.map(key => [key, resolved[key]]))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function validateCapabilityProfiles(value) {
|
|
33
|
+
if (!value || value.schema !== 'ptc-capability-profiles/v1' || !Array.isArray(value.profiles) || value.profiles.length > 100) {
|
|
34
|
+
throw new TypeError('ptc-plus: invalid capability profiles document')
|
|
35
|
+
}
|
|
36
|
+
const ids = new Set()
|
|
37
|
+
const names = new Set()
|
|
38
|
+
return { schema: value.schema, profiles: value.profiles.map(profile => {
|
|
39
|
+
if (!profile || typeof profile.id !== 'string' || !profile.id.trim() || profile.id.length > 100
|
|
40
|
+
|| typeof profile.name !== 'string' || !profile.name.trim() || profile.name.length > 80
|
|
41
|
+
|| ids.has(profile.id) || names.has(profile.name.trim())) throw new TypeError('ptc-plus: invalid or duplicate capability profile')
|
|
42
|
+
ids.add(profile.id); names.add(profile.name.trim())
|
|
43
|
+
return { id: profile.id, name: profile.name.trim(), configuration: capabilityProfileConfiguration(profile.configuration) }
|
|
44
|
+
}) }
|
|
45
|
+
}
|
|
46
|
+
|
|
9
47
|
/** The wall-clock ceiling field carries the platform timer limit as its maximum. */
|
|
10
48
|
const MAX_WALL_MS_FIELD = CONFIG_FIELDS.find(field => field.key === 'maxWallMs')
|
|
11
49
|
|
|
@@ -21,6 +59,10 @@ function assertIntegerField(field, value) {
|
|
|
21
59
|
|
|
22
60
|
/** Validate one field against its CONFIG_FIELDS declaration. */
|
|
23
61
|
function assertField(field, value) {
|
|
62
|
+
if (field.type === 'string') {
|
|
63
|
+
if (typeof value !== 'string') throw new TypeError(`ptc-plus: ${field.key} must be a string`)
|
|
64
|
+
return
|
|
65
|
+
}
|
|
24
66
|
if (field.type === 'boolean') {
|
|
25
67
|
if (typeof value !== 'boolean') {
|
|
26
68
|
throw new TypeError(`ptc-plus: ${field.key} must be a boolean`)
|
|
@@ -74,6 +116,22 @@ export function resolveConfig(config = {}) {
|
|
|
74
116
|
resolved[field.key] = liveConfigValue(resolved[field.key], CONFIG_DEFAULTS[field.key])
|
|
75
117
|
}
|
|
76
118
|
for (const field of CONFIG_FIELDS) assertField(field, resolved[field.key])
|
|
119
|
+
validateCapabilityProfiles(JSON.parse(resolved.capabilityProfiles))
|
|
120
|
+
for (const [key, array] of [['capabilityBaseline', true], ['capabilityOverrides', false]]) {
|
|
121
|
+
let value
|
|
122
|
+
try { value = JSON.parse(resolved[key]) } catch { throw new TypeError(`ptc-plus: ${key} must contain valid JSON`) }
|
|
123
|
+
if (array ? !Array.isArray(value) || value.some(id => typeof id !== 'string' || !id.trim())
|
|
124
|
+
: value === null || typeof value !== 'object' || Array.isArray(value)
|
|
125
|
+
|| Object.entries(value).some(([id, selection]) => !id.trim() || !['full', 'on-demand', 'closed'].includes(selection)
|
|
126
|
+
|| (id.startsWith('mcpServers.') && (id === 'mcpServers.' || selection !== 'closed')))) {
|
|
127
|
+
throw new TypeError(`ptc-plus: invalid ${key}`)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
resolved.capabilityPolicy = JSON.stringify(capabilityPolicyForConfig(provided.has('capabilityPolicy') ? resolved : { ...resolved, capabilityPolicy: undefined }))
|
|
131
|
+
if (!provided.has('capabilityPolicy') && resolved.capabilityManagementEnabled) {
|
|
132
|
+
if (!provided.has('capabilityRecoveryEnabled')) resolved.capabilityRecoveryEnabled = true
|
|
133
|
+
if (!provided.has('modelPreviewEnabled')) resolved.modelPreviewEnabled = true
|
|
134
|
+
}
|
|
77
135
|
// Migrate complete old policies; mixed choices retain their explicit
|
|
78
136
|
// compatibility state until the user selects the unified language.
|
|
79
137
|
const legacyKeys = [
|