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
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
reduceStateOperations,
|
|
11
11
|
} from './session-journal.js'
|
|
12
12
|
import {
|
|
13
|
+
foldSessionTimeline,
|
|
13
14
|
pathToHead,
|
|
14
15
|
recoveryBoundaryForHistory,
|
|
15
16
|
recoverJournal,
|
|
16
17
|
visibleExecutableCallSeqs,
|
|
17
18
|
} from './session-journal-recovery.js'
|
|
18
19
|
import { valueLimitsFromConfig } from './value-wire-schema.js'
|
|
20
|
+
import { encodeValueAtBoundary, valueWiresEqual } from './value-wire.js'
|
|
19
21
|
import { normalizeBindingDescriptors } from './binding-descriptors.js'
|
|
20
22
|
import { resolveConfig } from './runtime-config.js'
|
|
21
23
|
import { WorkerClient } from './worker-client.js'
|
|
@@ -26,6 +28,7 @@ import { executionPolicies } from './binding-update-policy.js'
|
|
|
26
28
|
import {
|
|
27
29
|
createReplMemorySnapshot,
|
|
28
30
|
normalizeReplMemorySnapshot,
|
|
31
|
+
REPL_MEMORY_META_KEY,
|
|
29
32
|
unavailableReplMemorySnapshot,
|
|
30
33
|
} from './repl-memory-projection.js'
|
|
31
34
|
|
|
@@ -34,8 +37,12 @@ const UNAVAILABLE_SURFACE_GENERATION = Symbol('unavailableSurfaceGeneration')
|
|
|
34
37
|
|
|
35
38
|
function surfaceGeneration(session, provenanceRequired = true) {
|
|
36
39
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
const surface = session?.surface
|
|
41
|
+
const generation = surface?.replaceGeneration
|
|
42
|
+
if (generation === undefined) return provenanceRequired ? UNAVAILABLE_SURFACE_GENERATION : undefined
|
|
43
|
+
return surface.contentGeneration === undefined
|
|
44
|
+
? generation
|
|
45
|
+
: `${generation}:${surface.contentGeneration}`
|
|
39
46
|
} catch {
|
|
40
47
|
return provenanceRequired ? UNAVAILABLE_SURFACE_GENERATION : undefined
|
|
41
48
|
}
|
|
@@ -96,6 +103,7 @@ class SessionKernel {
|
|
|
96
103
|
this.recoveryNotice = history.volatileSuffix.length === 0
|
|
97
104
|
? undefined
|
|
98
105
|
: recoveryDiagnostic(history.volatileSuffix.length)
|
|
106
|
+
this.recoveryNoticeShown = false
|
|
99
107
|
this.active = undefined
|
|
100
108
|
this.sequence = 0
|
|
101
109
|
this.tail = Promise.resolve()
|
|
@@ -144,17 +152,25 @@ class SessionKernel {
|
|
|
144
152
|
} catch (error) {
|
|
145
153
|
record.failed = true
|
|
146
154
|
record.reason = error
|
|
147
|
-
return
|
|
155
|
+
return Promise.resolve(record)
|
|
148
156
|
}
|
|
149
157
|
const attempt = Promise.resolve(reset).then(
|
|
150
|
-
() => { this.reclamationRecords.delete(sequence) },
|
|
158
|
+
() => { this.reclamationRecords.delete(sequence); return record },
|
|
151
159
|
error => {
|
|
152
160
|
record.failed = true
|
|
153
161
|
record.reason = error
|
|
162
|
+
return record
|
|
154
163
|
},
|
|
155
164
|
)
|
|
156
165
|
this.reclamationAttempts.add(attempt)
|
|
157
166
|
attempt.then(() => { this.reclamationAttempts.delete(attempt) })
|
|
167
|
+
return attempt
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async prepareWorker() {
|
|
171
|
+
await Promise.all([...this.reclamationAttempts])
|
|
172
|
+
const failures = [...this.reclamationRecords.values()].filter(record => record.failed).map(record => record.reason)
|
|
173
|
+
if (failures.length) throw new AggregateError(failures, 'previous computation helper reclamation failed; dispose the session before continuing')
|
|
158
174
|
}
|
|
159
175
|
|
|
160
176
|
valueLimits(config = this.config) {
|
|
@@ -333,14 +349,12 @@ class SessionKernel {
|
|
|
333
349
|
}
|
|
334
350
|
if (skipped > 0) this.recoveryNotice = recoveryDiagnostic(skipped)
|
|
335
351
|
}
|
|
336
|
-
const leadingDiagnostics = this.recoveryNotice === undefined
|
|
352
|
+
const leadingDiagnostics = this.recoveryNotice === undefined || this.recoveryNoticeShown
|
|
353
|
+
? [] : [this.recoveryNotice]
|
|
354
|
+
this.recoveryNoticeShown = this.recoveryNotice !== undefined
|
|
337
355
|
this.recoveryNotice = undefined
|
|
338
356
|
if (request.journal !== undefined) request.journal.diagnostics.push(...leadingDiagnostics)
|
|
339
357
|
const result = await this.cellExecutor.executeCell(request, undefined, config)
|
|
340
|
-
if (leadingDiagnostics.length > 0) {
|
|
341
|
-
const rendered = leadingDiagnostics.map(item => renderDiagnostic(item, request.program))
|
|
342
|
-
result.logs = [...rendered, ...result.logs]
|
|
343
|
-
}
|
|
344
358
|
if (result.error === undefined) {
|
|
345
359
|
this.failures.reset()
|
|
346
360
|
} else {
|
|
@@ -351,17 +365,29 @@ class SessionKernel {
|
|
|
351
365
|
if (request.journal !== undefined) request.journal.diagnostics.push(hint)
|
|
352
366
|
}
|
|
353
367
|
}
|
|
368
|
+
if (leadingDiagnostics.length > 0) {
|
|
369
|
+
const rendered = leadingDiagnostics.map(item => renderDiagnostic(item, request.program))
|
|
370
|
+
if (result.error === undefined) result.logs = [...rendered, ...result.logs]
|
|
371
|
+
else result.error = { ...result.error, message: [result.error.message, ...rendered].join('\n') }
|
|
372
|
+
}
|
|
354
373
|
return finishResult(result)
|
|
355
374
|
}
|
|
356
375
|
|
|
357
376
|
async replayHistory(request, config) {
|
|
358
377
|
this.bindingCatalog = new BindingCatalog()
|
|
378
|
+
const limits = this.valueLimits(config)
|
|
379
|
+
const emptyArgsWire = encodeValueAtBoundary({}, limits, 'parameter')
|
|
359
380
|
const path = pathToHead(this.history)
|
|
360
381
|
for (const node of path) {
|
|
382
|
+
const bindings = replayBindingNamespaces(request.bindings, node.journal, limits, emptyArgsWire)
|
|
383
|
+
const bindingDescriptors = bindings === request.bindings
|
|
384
|
+
? request.bindingDescriptors : normalizeBindingDescriptors(bindings)
|
|
361
385
|
try {
|
|
362
386
|
const result = await this.cellExecutor.executeCell(
|
|
363
387
|
{
|
|
364
388
|
...request,
|
|
389
|
+
bindings: bindingDescriptors.namespaces,
|
|
390
|
+
bindingDescriptors,
|
|
365
391
|
program: node.code,
|
|
366
392
|
journal: undefined,
|
|
367
393
|
...(node.userBindings === undefined ? { userBindings: undefined } : { userBindings: node.userBindings }),
|
|
@@ -476,7 +502,17 @@ class SessionKernel {
|
|
|
476
502
|
this.workerObservation = undefined
|
|
477
503
|
// A refused reclamation stays recorded on this kernel instead of becoming
|
|
478
504
|
// an unhandled rejection; the kernel keeps the worker until dispose.
|
|
479
|
-
this.resetWorker(worker)
|
|
505
|
+
void this.resetWorker(worker).then(record => {
|
|
506
|
+
if (record.failed) {
|
|
507
|
+
result = { ...result, error: { ...result.error,
|
|
508
|
+
message: `${result.error?.message ?? 'cell terminated'}; helper reclamation failed: ${String(record.reason?.message ?? record.reason)}` } }
|
|
509
|
+
if (journal !== undefined && replay === undefined) {
|
|
510
|
+
this.completeJournal(journal, 'discarded', result, journal.volatileReason, active.diagnostics)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
active.finish(result)
|
|
514
|
+
})
|
|
515
|
+
return
|
|
480
516
|
}
|
|
481
517
|
if (!terminate && active.observing && replay === undefined) {
|
|
482
518
|
// Computation and journal settlement are complete. Observation cannot change either.
|
|
@@ -621,11 +657,47 @@ function sessionOf(sessionContext) {
|
|
|
621
657
|
session,
|
|
622
658
|
callId: sessionContext.callId,
|
|
623
659
|
persistedCallSeq: sessionContext.persistedCallSeq,
|
|
660
|
+
sourceCallSeq: sessionContext.sourceCallSeq,
|
|
624
661
|
cwd: typeof session?.header?.cwd === 'string' ? session.header.cwd : undefined,
|
|
625
662
|
surfaceProvenanceRequired: true,
|
|
626
663
|
}
|
|
627
664
|
}
|
|
628
665
|
|
|
666
|
+
/**
|
|
667
|
+
* Recorded-value replay settles a host call from its journal entry without
|
|
668
|
+
* dispatching it. A member the Host no longer supplies must therefore stay
|
|
669
|
+
* visible to the replayed cell as a replay-only stub; otherwise a routine
|
|
670
|
+
* capability removal would make an otherwise provable cell unreconstructable
|
|
671
|
+
* and contract the durable frontier. Zero-argument calls keep their recorded
|
|
672
|
+
* empty-object form so the args wire still matches the journal.
|
|
673
|
+
*/
|
|
674
|
+
function replayBindingNamespaces(namespaces, journal, limits, emptyArgsWire) {
|
|
675
|
+
const calls = journal?.calls ?? []
|
|
676
|
+
let changed = false
|
|
677
|
+
const next = namespaces.map(namespace => {
|
|
678
|
+
const recorded = calls.filter(call => call.global === namespace.global)
|
|
679
|
+
const missing = [...new Set(recorded.map(call => call.member))]
|
|
680
|
+
.filter(member => !namespace.members.includes(member))
|
|
681
|
+
if (missing.length === 0) return namespace
|
|
682
|
+
changed = true
|
|
683
|
+
const functions = { ...namespace.functions }
|
|
684
|
+
const emptyObjectMembers = [...(namespace.emptyObjectMembers ?? [])]
|
|
685
|
+
for (const member of missing) {
|
|
686
|
+
// The replay executor settles or rejects every host-binding call from the
|
|
687
|
+
// recorded journal, so the placeholder keeps the member visible without
|
|
688
|
+
// ever running; the throw only guards a dispatch path that would bypass
|
|
689
|
+
// recorded-value interception.
|
|
690
|
+
/* c8 ignore next -- replay interception precedes invocation */
|
|
691
|
+
functions[member] = () => { throw new Error(`replay-only member ${namespace.global}.${member}`) }
|
|
692
|
+
if (recorded.some(call => call.member === member && valueWiresEqual(call.args, emptyArgsWire, limits))) {
|
|
693
|
+
emptyObjectMembers.push(member)
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return { ...namespace, functions, emptyObjectMembers }
|
|
697
|
+
})
|
|
698
|
+
return changed ? next : namespaces
|
|
699
|
+
}
|
|
700
|
+
|
|
629
701
|
export class SessionRuntime {
|
|
630
702
|
constructor(config = {}, options = {}) {
|
|
631
703
|
this.config = resolvedRuntimeConfig(config)
|
|
@@ -642,6 +714,7 @@ export class SessionRuntime {
|
|
|
642
714
|
this.withInitiator = typeof options.withInitiator === 'function' ? options.withInitiator : undefined
|
|
643
715
|
this.observeSession = options.observeSession ?? (() => false)
|
|
644
716
|
this.readSessionLog = options.readSessionLog ?? (async session => session)
|
|
717
|
+
this.sessionForId = options.sessionForId
|
|
645
718
|
this.disposal = undefined
|
|
646
719
|
}
|
|
647
720
|
|
|
@@ -686,10 +759,32 @@ export class SessionRuntime {
|
|
|
686
759
|
if (this.disposed || !this.config.replViewEnabled) return undefined
|
|
687
760
|
const { observation: _observation, ...expected } = normalizeReplMemorySnapshot(memory)
|
|
688
761
|
if (!expected.available || expected.entries.length === 0) return undefined
|
|
689
|
-
|
|
762
|
+
const kernel = this.kernels.get(sessionId)
|
|
763
|
+
const session = kernel?.session ?? this.sessionForId?.(sessionId)
|
|
764
|
+
if (session !== undefined && kernel?.surfaceProvenanceRequired !== false) {
|
|
765
|
+
const observed = await this.readSessionLog(session, signal)
|
|
766
|
+
signal?.throwIfAborted()
|
|
767
|
+
const visible = visibleExecutableCallSeqs(observed)
|
|
768
|
+
if (kernel !== undefined) {
|
|
769
|
+
if ([...kernel.liveCallSeqs].some(seq => !visible.has(seq))) return unavailableReplMemorySnapshot()
|
|
770
|
+
kernel.surfaceGeneration = surfaceGeneration(session)
|
|
771
|
+
} else {
|
|
772
|
+
const timeline = foldSessionTimeline(observed.events)
|
|
773
|
+
const history = recoverJournal(observed, undefined, { visibleCallSeqs: visible })
|
|
774
|
+
const head = history.nodes[history.head]
|
|
775
|
+
if (head === undefined) return unavailableReplMemorySnapshot()
|
|
776
|
+
// A cold inventory can describe a proved recorded frontier without
|
|
777
|
+
// instantiating a worker. It cannot describe a discarded suffix.
|
|
778
|
+
const resultSeq = timeline.results.get(head.callSeq)?.eventSeq
|
|
779
|
+
const result = observed.events.find(event => event.type === 'tool/result' && event.seq === resultSeq)
|
|
780
|
+
const recorded = result?.data?.meta?.[REPL_MEMORY_META_KEY]?.memory
|
|
781
|
+
if (!isDeepStrictEqual(recorded, memory)) return unavailableReplMemorySnapshot()
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return kernel?.observe(expected, signal)
|
|
690
785
|
}
|
|
691
786
|
|
|
692
|
-
async runTentative(sessionContext, request) {
|
|
787
|
+
async runTentative(sessionContext, request, { programCompletion = false } = {}) {
|
|
693
788
|
const completed = result => Object.freeze({ result, settlement: undefined })
|
|
694
789
|
if (this.disposed) return completed({ logs: [], error: { kind: 'abort', message: 'PTC runtime disposed' } })
|
|
695
790
|
let bindingDescriptors
|
|
@@ -699,11 +794,15 @@ export class SessionRuntime {
|
|
|
699
794
|
return completed({ logs: [], error: { kind: 'exception', message: messageOf(error) } })
|
|
700
795
|
}
|
|
701
796
|
request = { ...request, bindings: bindingDescriptors.namespaces, bindingDescriptors }
|
|
797
|
+
if (request.cwd !== undefined && (typeof request.cwd !== 'string' || !isAbsolute(request.cwd))) {
|
|
798
|
+
return completed({ logs: [], error: { kind: 'exception', message: 'PTC execution cwd must be an absolute path' } })
|
|
799
|
+
}
|
|
702
800
|
const {
|
|
703
801
|
id: sessionId,
|
|
704
802
|
session,
|
|
705
803
|
callId,
|
|
706
804
|
persistedCallSeq,
|
|
805
|
+
sourceCallSeq: provedSourceCallSeq,
|
|
707
806
|
cwd,
|
|
708
807
|
surfaceProvenanceRequired,
|
|
709
808
|
} = sessionOf(sessionContext)
|
|
@@ -726,11 +825,15 @@ export class SessionRuntime {
|
|
|
726
825
|
&& !isCanonicalSequence(persistedCallSeq)) {
|
|
727
826
|
throw new Error('persisted tool call sequence must be a non-negative safe integer')
|
|
728
827
|
}
|
|
828
|
+
if (provedSourceCallSeq !== undefined
|
|
829
|
+
&& !isCanonicalSequence(provedSourceCallSeq)) {
|
|
830
|
+
throw new Error('source tool call sequence must be a non-negative safe integer')
|
|
831
|
+
}
|
|
729
832
|
if (cellConfig.durableReplay) {
|
|
730
|
-
sourceCallSeq = liveToolCallSeq(sessionLog, callId, 'run_code')
|
|
833
|
+
sourceCallSeq = provedSourceCallSeq ?? liveToolCallSeq(sessionLog, callId, 'run_code')
|
|
731
834
|
callSeq = persistedCallSeq ?? sourceCallSeq
|
|
732
835
|
} else {
|
|
733
|
-
sourceCallSeq = persistedCallSeq
|
|
836
|
+
sourceCallSeq = provedSourceCallSeq ?? persistedCallSeq
|
|
734
837
|
if (sourceCallSeq === undefined) {
|
|
735
838
|
try {
|
|
736
839
|
sourceCallSeq = liveToolCallSeq(sessionLog, callId, 'run_code')
|
|
@@ -779,11 +882,12 @@ export class SessionRuntime {
|
|
|
779
882
|
policies.rewritesEnabled,
|
|
780
883
|
policies.languageSemantics,
|
|
781
884
|
policies.moduleTransform,
|
|
885
|
+
request.cwd ?? cwd ?? null,
|
|
782
886
|
)
|
|
783
887
|
const workerReservation = kernel.reserveWorkerConfiguration(cellConfig)
|
|
784
888
|
let result
|
|
785
889
|
try {
|
|
786
|
-
result = await kernel.run({ ...request, journal, callSeq, sourceCallSeq, sessionLog }, cellConfig)
|
|
890
|
+
result = await kernel.run({ ...request, journal, callSeq, sourceCallSeq, sessionLog, programCompletion }, cellConfig)
|
|
787
891
|
} finally {
|
|
788
892
|
kernel.releaseWorkerConfiguration(workerReservation)
|
|
789
893
|
}
|
|
@@ -36,12 +36,16 @@ export function hostOwnsSettingsDocument(settingsModule) {
|
|
|
36
36
|
* @returns the Cordis schema field for that definition.
|
|
37
37
|
*/
|
|
38
38
|
export function configFieldSchema({ Schema, field, settingsModule }) {
|
|
39
|
-
const
|
|
40
|
-
? Schema.boolean()
|
|
39
|
+
const schema = field.type === 'boolean'
|
|
40
|
+
? Schema.boolean()
|
|
41
|
+
: field.type === 'string'
|
|
42
|
+
? Schema.string()
|
|
41
43
|
: field.type === 'enum'
|
|
42
|
-
// Keep omission visible until resolveConfig migrates legacy policies.
|
|
43
44
|
? Schema.union(field.options.map(option => Schema.const(option)))
|
|
44
|
-
: Schema.number().step(1).min(field.min).max(field.max)
|
|
45
|
+
: Schema.number().step(1).min(field.min).max(field.max)
|
|
46
|
+
// Migration must distinguish omitted legacy inputs from explicit new choices.
|
|
47
|
+
// Runtime defaults belong to resolveConfig; the Host must preserve omission.
|
|
48
|
+
const base = field.preserveOmission || field.type === 'enum' ? schema : schema.default(field.default)
|
|
45
49
|
const described = base.description(field.description)
|
|
46
50
|
if (!hostOwnsSettingsDocument(settingsModule)) return described
|
|
47
51
|
if (typeof described.volatile !== 'function') {
|
|
@@ -68,7 +72,10 @@ export function installSettingsSectionCompat({
|
|
|
68
72
|
const writeNamespace = sectionInstalled ? namespace : (ownerFiber ?? ctx.fiber)?.entry?.options?.id
|
|
69
73
|
const writer = typeof provider?.update === 'function'
|
|
70
74
|
&& typeof writeNamespace === 'string' && writeNamespace.length > 0
|
|
71
|
-
? Object.freeze({
|
|
75
|
+
? Object.freeze({
|
|
76
|
+
update: (patch, expectedRevision) => provider.update(writeNamespace, patch, expectedRevision),
|
|
77
|
+
read: () => provider.describe?.().find(descriptor => descriptor.ns === writeNamespace),
|
|
78
|
+
}) : undefined
|
|
72
79
|
const unbind = onProvider(provider, writer)
|
|
73
80
|
if (typeof unbind === 'function') settingsContext.effect(() => unbind, 'ptc-plus settings writer')
|
|
74
81
|
if (typeof provider?.installSection === 'function') {
|
|
@@ -96,9 +96,9 @@ export function createUserModuleCompilationHooks({ transformForParent = () => un
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
-
const recordResolution = (parentUrl, specifier, importAttributes, result
|
|
99
|
+
const recordResolution = (parentUrl, specifier, importAttributes, result,
|
|
100
|
+
transform = mapGet(modules, parentUrl) ?? transformForParent(parentUrl)) => {
|
|
100
101
|
recordStatefulModuleResolution(parentUrl, specifier, importAttributes, result.url)
|
|
101
|
-
const transform = mapGet(modules, parentUrl) ?? transformForParent(parentUrl)
|
|
102
102
|
// Runtime links are outside the user compilation graph. Skipping only
|
|
103
103
|
// their load would still propagate user transforms to their dependencies.
|
|
104
104
|
if (transform !== undefined && !startsWith(result.url, 'node:')
|
|
@@ -106,6 +106,7 @@ export function createUserModuleCompilationHooks({ transformForParent = () => un
|
|
|
106
106
|
}
|
|
107
107
|
return {
|
|
108
108
|
mark,
|
|
109
|
+
recordResolution,
|
|
109
110
|
resolve(specifier, context, nextResolve) {
|
|
110
111
|
const evidence = commonJsEvidence.resolve(specifier, context, recordResolution)
|
|
111
112
|
if (evidence !== undefined) return evidence
|
|
@@ -198,7 +198,7 @@ const nativeModuleRealm = {
|
|
|
198
198
|
importModule: (source, options) => import(source, options),
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
export function managedModuleImport(parentUrl, specifier, options, realm = nativeModuleRealm) {
|
|
201
|
+
export function managedModuleImport(parentUrl, specifier, options, realm = nativeModuleRealm, recordResolution) {
|
|
202
202
|
return new realm.Promise((resolve, reject) => {
|
|
203
203
|
// Template conversion follows native import's ToString (including Symbols).
|
|
204
204
|
const source = realm.stringify(specifier)
|
|
@@ -216,6 +216,7 @@ export function managedModuleImport(parentUrl, specifier, options, realm = nativ
|
|
|
216
216
|
if (request !== url) return nextResolve(request, context)
|
|
217
217
|
const resolution = nextResolve(source, { ...context, parentURL: parentUrl })
|
|
218
218
|
const attributes = resolution.importAttributes ?? context.importAttributes
|
|
219
|
+
recordResolution?.(parentUrl, source, attributes, resolution)
|
|
219
220
|
return { url: resolveManagedModuleAdapter(resolution, attributes), format: 'module',
|
|
220
221
|
importAttributes: attributes, shortCircuit: true }
|
|
221
222
|
})
|
|
@@ -259,13 +260,16 @@ export function loadManagedModuleAdapter(url) {
|
|
|
259
260
|
}
|
|
260
261
|
|
|
261
262
|
/** Only PTC-provided require paths decode formally compiled ESM returns. */
|
|
262
|
-
export function managedRequire(parentUrl, nativeRequire) {
|
|
263
|
+
export function managedRequire(parentUrl, nativeRequire, recordResolution) {
|
|
263
264
|
const acquire = (target, receiver, args) => {
|
|
264
265
|
let url
|
|
265
266
|
let value
|
|
266
267
|
const dispose = registerModuleResolutionHook((source, context, nextResolve) => {
|
|
267
268
|
const result = nextResolve(source, context)
|
|
268
|
-
if (source === args[0] && url === undefined)
|
|
269
|
+
if (source === args[0] && url === undefined) {
|
|
270
|
+
url = result.url
|
|
271
|
+
recordResolution?.(parentUrl, source, context.importAttributes, result)
|
|
272
|
+
}
|
|
269
273
|
return result
|
|
270
274
|
})
|
|
271
275
|
try {
|
|
@@ -13,7 +13,7 @@ export function createStatefulRootRuntime({ readAmbient, typeofAmbient, writeAmb
|
|
|
13
13
|
const entries = new Map()
|
|
14
14
|
const intrinsics = captureCompilerIntrinsics(dynamicIntrinsics?.realmFunction)
|
|
15
15
|
errors ??= intrinsics
|
|
16
|
-
const
|
|
16
|
+
const defaultDynamicRuntime = createDynamicEnvironmentRuntime({ ...dynamicIntrinsics, importModule })
|
|
17
17
|
const moduleRuntime = dynamicIntrinsics === undefined ? undefined : createDynamicEnvironmentRuntime()
|
|
18
18
|
let commit
|
|
19
19
|
const read = name => {
|
|
@@ -59,7 +59,11 @@ export function createStatefulRootRuntime({ readAmbient, typeofAmbient, writeAmb
|
|
|
59
59
|
return value
|
|
60
60
|
}
|
|
61
61
|
const begin = ({ declared = [], known = [], readOnly = [], legacyImports = [], legacyWritable = [], legacyLexicals = [], legacyObjects = [],
|
|
62
|
-
declarationKinds = [], declaredKinds = [], callableSources = [], languageSemantics = 'stateful-v1', committed
|
|
62
|
+
declarationKinds = [], declaredKinds = [], callableSources = [], languageSemantics = 'stateful-v1', committed,
|
|
63
|
+
importModule: cellImportModule = importModule }) => {
|
|
64
|
+
// Each cell owns dynamic-source resolution; shared logical roots remain live.
|
|
65
|
+
const dynamicRuntime = createDynamicEnvironmentRuntime({ ...dynamicIntrinsics,
|
|
66
|
+
importModule: cellImportModule }).installIntrinsics()
|
|
63
67
|
dynamicRuntime.registerSources(callableSources)
|
|
64
68
|
commit = committed
|
|
65
69
|
const kinds = new Map()
|
|
@@ -127,7 +131,7 @@ export function createStatefulRootRuntime({ readAmbient, typeofAmbient, writeAmb
|
|
|
127
131
|
const result = {
|
|
128
132
|
values,
|
|
129
133
|
intrinsics,
|
|
130
|
-
importModule,
|
|
134
|
+
importModule: cellImportModule,
|
|
131
135
|
dynamic({ realm = false } = {}) {
|
|
132
136
|
const pendingKinds = new Map()
|
|
133
137
|
for (let index = 0; index < declaredKinds.length; index++) mapSet(pendingKinds, declaredKinds[index][0], declaredKinds[index][1])
|
|
@@ -155,6 +159,7 @@ export function createStatefulRootRuntime({ readAmbient, typeofAmbient, writeAmb
|
|
|
155
159
|
const varFrame = { has: name => variableKind(kindFor(name)), get: name => varFrame.has(name) ? descriptor(name, 'var') : undefined,
|
|
156
160
|
set() {} }
|
|
157
161
|
return dynamicRuntime.environment({
|
|
162
|
+
importModule: cellImportModule,
|
|
158
163
|
varFrame, frames: [{ kind: 'lexical', bindings: lexicalFrame }],
|
|
159
164
|
declareVars: realm ? (names, functionNames) => {
|
|
160
165
|
const globals = []
|
|
@@ -288,7 +293,7 @@ export function createStatefulRootRuntime({ readAmbient, typeofAmbient, writeAmb
|
|
|
288
293
|
// Only new-generation environments select intrinsic interfaces. Native
|
|
289
294
|
// global/prototype properties and saved legacy identities remain intact.
|
|
290
295
|
if (dynamicIntrinsics !== undefined) {
|
|
291
|
-
|
|
296
|
+
defaultDynamicRuntime.installIntrinsics()
|
|
292
297
|
moduleRuntime.installIntrinsics()
|
|
293
298
|
}
|
|
294
299
|
return result
|
|
@@ -37,14 +37,9 @@ export const BINDING_SUBMISSION = deepFreeze({
|
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
export const BINDING_AUTHORING_SDK = `
|
|
40
|
-
|
|
40
|
+
/binding only: copy its requestId; submission does not save, enable or run the draft.
|
|
41
41
|
\`\`\`ts
|
|
42
|
-
declare namespace code {
|
|
43
|
-
function submitBindingDraft(args: {
|
|
44
|
-
requestId: string;
|
|
45
|
-
entry: { id: string; name: string; scope: "namespace" | "top-level"; symbols?: string[]; purpose: string; source: string; modelContext?: { includeDeclaration?: boolean; instructions?: string } };
|
|
46
|
-
}): Promise<{ accepted: true; id: string; requestId: string }>;
|
|
47
|
-
}
|
|
42
|
+
declare namespace code { function submitBindingDraft(args: { requestId: string; entry: { id: string; name: string; scope: "namespace" | "top-level"; symbols?: string[]; purpose: string; source: string; modelContext?: { includeDeclaration?: boolean; instructions?: string } } }): Promise<{ accepted: true; id: string; requestId: string }> }
|
|
48
43
|
\`\`\``
|
|
49
44
|
|
|
50
45
|
export function bindingAuthoringInstructions(requestId, cwd) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PTC_MESSAGE_SOURCE_KIND } from './message-sources.js'
|
|
2
|
+
import { bindingToggleMessage, readBindingToggle } from './binding-toggle-receipt.js'
|
|
2
3
|
import { RPC_CONTRACTS } from './rpc-contract.js'
|
|
3
4
|
import { randomUUID } from 'node:crypto'
|
|
4
5
|
import { dirname } from 'node:path'
|
|
@@ -185,6 +186,7 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
185
186
|
valueLimits: options.valueLimits,
|
|
186
187
|
}
|
|
187
188
|
let disposed = false
|
|
189
|
+
const pendingConfigurationReceipts = new WeakMap()
|
|
188
190
|
const codeConsole = new UserBindingConsole(currentOptions)
|
|
189
191
|
const commandRegistrations = new Map()
|
|
190
192
|
const sessionAgents = new Map()
|
|
@@ -378,13 +380,33 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
378
380
|
}
|
|
379
381
|
// Fixed endpoint tables grouped by domain; the dispatcher only looks up the
|
|
380
382
|
// operation, rejects unknown endpoints and hands over the validated input.
|
|
383
|
+
const setEnabled = async (input, requested) => {
|
|
384
|
+
const next = await store.setEnabled(input.id, requested, input.expectedRevision)
|
|
385
|
+
const entry = next.entries.find(entry => entry.id === input.id)
|
|
386
|
+
if (entry === undefined) throw new Error('saved binding missing from confirmed catalog')
|
|
387
|
+
const sessions = new Set((ctx.agents?.list?.() ?? []).map(agent => agent.session).filter(Boolean))
|
|
388
|
+
for (const session of sessions) {
|
|
389
|
+
try {
|
|
390
|
+
const message = createUserMessage(bindingToggleMessage(entry, next.revision))
|
|
391
|
+
// The Host alone establishes the first system head. Before that accepted
|
|
392
|
+
// boundary, keep UI configuration facts pending without waking the Agent.
|
|
393
|
+
if (session.deriveMessages()[0]?.role === 'system') session.append('user/message', message, { surfaceOp: 'append' })
|
|
394
|
+
else {
|
|
395
|
+
const pending = pendingConfigurationReceipts.get(session) ?? []
|
|
396
|
+
pending.push(message)
|
|
397
|
+
pendingConfigurationReceipts.set(session, pending)
|
|
398
|
+
}
|
|
399
|
+
} catch (error) { reportContainedFailure('ptc-plus: binding configuration saved but its session receipt could not be appended', error) }
|
|
400
|
+
}
|
|
401
|
+
return next
|
|
402
|
+
}
|
|
381
403
|
const storageEndpoints = Object.freeze({
|
|
382
404
|
list: () => store.list(),
|
|
383
405
|
load: input => store.entry(input.id),
|
|
384
406
|
reload: () => store.reload(),
|
|
385
407
|
save: saveEntry,
|
|
386
|
-
enable: input =>
|
|
387
|
-
disable: input =>
|
|
408
|
+
enable: input => setEnabled(input, true),
|
|
409
|
+
disable: input => setEnabled(input, false),
|
|
388
410
|
remove: input => store.remove(input.id, input.expectedRevision),
|
|
389
411
|
import: input => store.importFile(input.path, input.expectedRevision, input.options),
|
|
390
412
|
validate: input => validateEntry(input.entry),
|
|
@@ -894,6 +916,23 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
894
916
|
mountAuthoring()
|
|
895
917
|
|
|
896
918
|
return Object.freeze({
|
|
919
|
+
committedEvent(session, event) {
|
|
920
|
+
if (event.type !== 'user/message' || !readBindingToggle(event.data)) return
|
|
921
|
+
const pending = pendingConfigurationReceipts.get(session)
|
|
922
|
+
if (pending) pendingConfigurationReceipts.set(session, pending.filter(message => message.id !== event.data.id))
|
|
923
|
+
},
|
|
924
|
+
async preStep(payload, next) {
|
|
925
|
+
const decision = await next()
|
|
926
|
+
if (disposed || decision?.kind !== 'enter' || payload.signal?.aborted || payload.agent?.signal?.aborted) return decision
|
|
927
|
+
const session = payload.agent?.session
|
|
928
|
+
const pending = pendingConfigurationReceipts.get(session)
|
|
929
|
+
if (!pending?.length) return decision
|
|
930
|
+
const visible = new Set(session.deriveMessages().map(message => message.id))
|
|
931
|
+
const remaining = pending.filter(message => !visible.has(message.id))
|
|
932
|
+
pendingConfigurationReceipts.set(session, remaining)
|
|
933
|
+
const proposed = new Set((decision.messages ?? []).map(message => message.id))
|
|
934
|
+
return { ...decision, messages: [...(decision.messages ?? []), ...remaining.filter(message => !proposed.has(message.id))] }
|
|
935
|
+
},
|
|
897
936
|
path: store.filename,
|
|
898
937
|
cwd: bindingsCwd,
|
|
899
938
|
async snapshot() {
|
|
@@ -53,7 +53,7 @@ function renderInvocation(argumentsValue) {
|
|
|
53
53
|
const json = JSON.stringify(argumentsValue)
|
|
54
54
|
.replaceAll('\u2028', '\\u2028')
|
|
55
55
|
.replaceAll('\u2029', '\\u2029')
|
|
56
|
-
return `edit_run_code(${json})`
|
|
56
|
+
return `await tools.edit_run_code(${json})`
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/** Requires exactly one valid suffix across all one- and two-token EOF closures. */
|