dsh-ptc-plus 0.4.10 → 0.5.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.
- package/README.en.md +65 -127
- package/README.md +65 -127
- 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 +42702 -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 +4 -4
- 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/0044-coexist-with-the-official-persistent-repl.md +35 -0
- package/docs/adr/README.md +8 -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 +33 -10
- package/docs/program-data-plane.md +5 -3
- package/docs/publishing.md +7 -7
- 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 +53 -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 +146 -20
- package/internal/binding-toggle-receipt.js +24 -0
- package/internal/capability-command-receipt.js +31 -0
- package/internal/capability-environment.js +1253 -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 +214 -0
- package/internal/config-spec.js +89 -10
- package/internal/direct-surface-owner.js +27 -29
- package/internal/dynamic-environment-runtime.js +9 -9
- package/internal/edit-transport-owner.js +195 -140
- package/internal/execution-provider-owner.js +40 -12
- package/internal/execution-seam-compat.js +26 -8
- package/internal/kernel-worker.js +104 -32
- package/internal/native-root-dynamic.js +12 -13
- package/internal/official-repl-priority.js +51 -0
- 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 +275 -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 +19 -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 +49 -30
- package/src/client-activity.js +3 -2
- 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 +1555 -0
- package/src/client-capability-sort.js +29 -0
- package/src/client-copy.js +923 -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 +112 -15
- package/src/client-tool-hover.js +185 -0
- package/src/client-tool-view.js +45 -9
- 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 } 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'
|
|
@@ -378,13 +379,29 @@ export function createUserBindingsOwner(ctx, options = {}) {
|
|
|
378
379
|
}
|
|
379
380
|
// Fixed endpoint tables grouped by domain; the dispatcher only looks up the
|
|
380
381
|
// operation, rejects unknown endpoints and hands over the validated input.
|
|
382
|
+
const setEnabled = async (input, requested) => {
|
|
383
|
+
const next = await store.setEnabled(input.id, requested, input.expectedRevision)
|
|
384
|
+
const entry = next.entries.find(entry => entry.id === input.id)
|
|
385
|
+
if (entry === undefined) throw new Error('saved binding missing from confirmed catalog')
|
|
386
|
+
const sessions = new Set((ctx.agents?.list?.() ?? []).map(agent => agent.session).filter(Boolean))
|
|
387
|
+
for (const session of sessions) {
|
|
388
|
+
try {
|
|
389
|
+
// Before the first accepted step these are initial settings, not changes
|
|
390
|
+
// to an environment the model has received. The catalog owns first delivery.
|
|
391
|
+
if (session.deriveMessages()[0]?.role !== 'system') continue
|
|
392
|
+
const message = createUserMessage(bindingToggleMessage(entry, next.revision))
|
|
393
|
+
session.append('user/message', message, { surfaceOp: 'append' })
|
|
394
|
+
} catch (error) { reportContainedFailure('ptc-plus: binding configuration saved but its session receipt could not be appended', error) }
|
|
395
|
+
}
|
|
396
|
+
return next
|
|
397
|
+
}
|
|
381
398
|
const storageEndpoints = Object.freeze({
|
|
382
399
|
list: () => store.list(),
|
|
383
400
|
load: input => store.entry(input.id),
|
|
384
401
|
reload: () => store.reload(),
|
|
385
402
|
save: saveEntry,
|
|
386
|
-
enable: input =>
|
|
387
|
-
disable: input =>
|
|
403
|
+
enable: input => setEnabled(input, true),
|
|
404
|
+
disable: input => setEnabled(input, false),
|
|
388
405
|
remove: input => store.remove(input.id, input.expectedRevision),
|
|
389
406
|
import: input => store.importFile(input.path, input.expectedRevision, input.options),
|
|
390
407
|
validate: input => validateEntry(input.entry),
|
|
@@ -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. */
|
package/internal/value-wire.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { types } from 'node:util'
|
|
1
2
|
import { assertFields, isRecord } from './record-utils.js'
|
|
2
3
|
import { runtimeIntrinsics as internal } from './runtime-intrinsics.js'
|
|
3
4
|
import {
|
|
@@ -23,6 +24,9 @@ const intrinsicFunctionToString = Function.prototype.toString
|
|
|
23
24
|
const reflectApply = Reflect.apply
|
|
24
25
|
const bufferByteLength = internal.bufferByteLength
|
|
25
26
|
const jsonStringify = JSON.stringify
|
|
27
|
+
const { isProxy, isDate, isMap, isSet, isRegExp, isAnyArrayBuffer, isTypedArray,
|
|
28
|
+
isDataView, isNativeError, isPromise, isWeakMap, isWeakSet } = types
|
|
29
|
+
const encodingFailures = new WeakMap()
|
|
26
30
|
|
|
27
31
|
function limitsOf(options = {}) {
|
|
28
32
|
const limits = { ...DEFAULT_VALUE_LIMITS, ...options }
|
|
@@ -36,19 +40,16 @@ function limitsOf(options = {}) {
|
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
function hasIntrinsicConstructor(prototype, name) {
|
|
43
|
+
if (isProxy(prototype)) return false
|
|
39
44
|
const constructor = Object.getOwnPropertyDescriptor(prototype, 'constructor')?.value
|
|
40
|
-
if (typeof constructor !== 'function') return false
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
&& reflectApply(intrinsicFunctionToString, constructor, []) === `function ${name}() { [native code] }`
|
|
45
|
-
} catch {
|
|
46
|
-
return false
|
|
47
|
-
}
|
|
45
|
+
if (typeof constructor !== 'function' || isProxy(constructor)) return false
|
|
46
|
+
return Object.getOwnPropertyDescriptor(constructor, 'name')?.value === name
|
|
47
|
+
&& Object.getOwnPropertyDescriptor(constructor, 'prototype')?.value === prototype
|
|
48
|
+
&& reflectApply(intrinsicFunctionToString, constructor, []) === `function ${name}() { [native code] }`
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
function isIntrinsicObjectPrototype(value) {
|
|
51
|
-
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, 'Object')
|
|
52
|
+
return !isProxy(value) && Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, 'Object')
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
function plainObjectPrototype(value) {
|
|
@@ -66,8 +67,53 @@ function hasPlainArrayPrototype(value) {
|
|
|
66
67
|
&& isIntrinsicObjectPrototype(objectPrototype)
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
function invalid(path, detail) {
|
|
70
|
-
|
|
70
|
+
function invalid(path, detail, kind = 'shape', legacyDetail = detail, legacyMessage = `value at ${path} is not PTC Value V1: ${legacyDetail}`) {
|
|
71
|
+
const error = new TypeError(`value at ${path} is not PTC Value V1: ${detail}`)
|
|
72
|
+
weakMapSet(encodingFailures, error, { kind, path, detail, legacyMessage })
|
|
73
|
+
throw error
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Brand checks do not read constructor, toStringTag, getters, or Proxy traps. */
|
|
77
|
+
function unsupportedObject(value) {
|
|
78
|
+
if (isProxy(value)) return 'Proxy'
|
|
79
|
+
if (isDate(value)) return 'Date'
|
|
80
|
+
if (isMap(value)) return 'Map'
|
|
81
|
+
if (isSet(value)) return 'Set'
|
|
82
|
+
if (isRegExp(value)) return 'RegExp'
|
|
83
|
+
if (isAnyArrayBuffer(value)) return 'ArrayBuffer/SharedArrayBuffer'
|
|
84
|
+
if (isTypedArray(value)) return 'TypedArray/Buffer'
|
|
85
|
+
if (isDataView(value)) return 'DataView'
|
|
86
|
+
if (isNativeError(value)) return 'Error'
|
|
87
|
+
if (isPromise(value)) return 'Promise'
|
|
88
|
+
if (isWeakMap(value)) return 'WeakMap'
|
|
89
|
+
if (isWeakSet(value)) return 'WeakSet'
|
|
90
|
+
return 'unknown prototype'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function valueEncodingFailure(error) {
|
|
94
|
+
return weakMapGet(encodingFailures, error)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function valueEncodingHelp(failure) {
|
|
98
|
+
return failure?.kind === 'budget'
|
|
99
|
+
? 'reduce the transferred graph to fit the reported value budget'
|
|
100
|
+
: 'select needed data explicitly as supported primitives, plain objects or arrays; keep unsupported live values in bindings (Date: explicit ISO text or timestamp; Map/Set: explicit entries or values); do not JSON.stringify a graph containing BigInt'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Only codec-owned failures acquire boundary facts; owner exceptions remain untouched. */
|
|
104
|
+
export function encodeValueAtBoundary(value, options, boundary) {
|
|
105
|
+
try {
|
|
106
|
+
return encodeValue(value, options)
|
|
107
|
+
} catch (error) {
|
|
108
|
+
const failure = valueEncodingFailure(error)
|
|
109
|
+
if (failure === undefined) throw error
|
|
110
|
+
const fact = boundary === 'parameter'
|
|
111
|
+
? 'program binding parameter was not dispatched'
|
|
112
|
+
: 'program binding call completed, but its result could not be transferred; external effects may already have occurred; do not repeat the operation solely to obtain its result'
|
|
113
|
+
const wrapped = new TypeError(`${fact}: ${error.message}; ${valueEncodingHelp(failure)}`)
|
|
114
|
+
weakMapSet(encodingFailures, wrapped, failure)
|
|
115
|
+
throw wrapped
|
|
116
|
+
}
|
|
71
117
|
}
|
|
72
118
|
|
|
73
119
|
function stringBytes(value) {
|
|
@@ -86,7 +132,7 @@ export function encodeValue(value, options = {}) {
|
|
|
86
132
|
|
|
87
133
|
const accountText = (text, path) => {
|
|
88
134
|
textBytes += stringBytes(text)
|
|
89
|
-
if (textBytes > limits.maxStringBytes) invalid(path, `string budget exceeds ${limits.maxStringBytes} bytes
|
|
135
|
+
if (textBytes > limits.maxStringBytes) invalid(path, `string budget exceeds ${limits.maxStringBytes} bytes`, 'budget')
|
|
90
136
|
}
|
|
91
137
|
|
|
92
138
|
const atom = (current, path) => {
|
|
@@ -106,14 +152,14 @@ export function encodeValue(value, options = {}) {
|
|
|
106
152
|
if (typeof current === 'bigint') {
|
|
107
153
|
const text = toString(current)
|
|
108
154
|
const digits = text[0] === '-' ? text.length - 1 : text.length
|
|
109
|
-
if (digits > limits.maxBigIntDigits) invalid(path, `BigInt exceeds ${limits.maxBigIntDigits} digits
|
|
155
|
+
if (digits > limits.maxBigIntDigits) invalid(path, `BigInt exceeds ${limits.maxBigIntDigits} digits`, 'budget')
|
|
110
156
|
accountText(text, path)
|
|
111
157
|
return { tag: 'bigint', value: text }
|
|
112
158
|
}
|
|
113
|
-
if (typeof current !== 'object') invalid(path, typeof current)
|
|
159
|
+
if (typeof current !== 'object') invalid(path, typeof current, 'unsupported')
|
|
114
160
|
const prior = weakMapGet(seen, current)
|
|
115
161
|
if (prior !== undefined) return { tag: 'reference', index: prior }
|
|
116
|
-
if (nodes.length >= limits.maxNodes) invalid(path, `node budget exceeds ${limits.maxNodes}
|
|
162
|
+
if (nodes.length >= limits.maxNodes) invalid(path, `node budget exceeds ${limits.maxNodes}`, 'budget')
|
|
117
163
|
const index = nodes.length
|
|
118
164
|
weakMapSet(seen, current, index)
|
|
119
165
|
appendArray(nodes, undefined)
|
|
@@ -127,10 +173,10 @@ export function encodeValue(value, options = {}) {
|
|
|
127
173
|
const current = item.value
|
|
128
174
|
if (isArray(current)) {
|
|
129
175
|
if (!hasPlainArrayPrototype(current)) invalid(item.path, 'non-plain array')
|
|
130
|
-
if (current.length > limits.maxArrayLength) invalid(item.path, `array length exceeds ${limits.maxArrayLength}
|
|
176
|
+
if (current.length > limits.maxArrayLength) invalid(item.path, `array length exceeds ${limits.maxArrayLength}`, 'budget')
|
|
131
177
|
arraySlotCount += current.length
|
|
132
178
|
if (arraySlotCount > limits.maxArrayLength) {
|
|
133
|
-
invalid(item.path, `array slot budget exceeds ${limits.maxArrayLength}
|
|
179
|
+
invalid(item.path, `array slot budget exceeds ${limits.maxArrayLength}`, 'budget')
|
|
134
180
|
}
|
|
135
181
|
const entries = []
|
|
136
182
|
const keys = Reflect.ownKeys(current)
|
|
@@ -147,7 +193,7 @@ export function encodeValue(value, options = {}) {
|
|
|
147
193
|
invalid(`${item.path}[${key}]`, 'array index must be an enumerable data property')
|
|
148
194
|
}
|
|
149
195
|
edgeCount += 1
|
|
150
|
-
if (edgeCount > limits.maxEdges) invalid(item.path, `edge budget exceeds ${limits.maxEdges}
|
|
196
|
+
if (edgeCount > limits.maxEdges) invalid(item.path, `edge budget exceeds ${limits.maxEdges}`, 'budget')
|
|
151
197
|
appendArray(entries, [index, atom(descriptor.value, `${item.path}[${key}]`)])
|
|
152
198
|
}
|
|
153
199
|
nodes[item.index] = { type: 'array', length: current.length, entries }
|
|
@@ -155,7 +201,7 @@ export function encodeValue(value, options = {}) {
|
|
|
155
201
|
}
|
|
156
202
|
|
|
157
203
|
const prototype = plainObjectPrototype(current)
|
|
158
|
-
if (prototype === undefined) invalid(item.path, 'non-plain object')
|
|
204
|
+
if (prototype === undefined) invalid(item.path, `non-plain object (${unsupportedObject(current)})`, 'unsupported', 'non-plain object')
|
|
159
205
|
const entries = []
|
|
160
206
|
const keys = Reflect.ownKeys(current)
|
|
161
207
|
for (let keyIndex = 0; keyIndex < keys.length; keyIndex += 1) {
|
|
@@ -167,7 +213,7 @@ export function encodeValue(value, options = {}) {
|
|
|
167
213
|
}
|
|
168
214
|
accountText(key, `${item.path}.${key}`)
|
|
169
215
|
edgeCount += 1
|
|
170
|
-
if (edgeCount > limits.maxEdges) invalid(item.path, `edge budget exceeds ${limits.maxEdges}
|
|
216
|
+
if (edgeCount > limits.maxEdges) invalid(item.path, `edge budget exceeds ${limits.maxEdges}`, 'budget')
|
|
171
217
|
appendArray(entries, [key, atom(descriptor.value, `${item.path}.${key}`)])
|
|
172
218
|
}
|
|
173
219
|
nodes[item.index] = { type: 'object', prototype, entries }
|
|
@@ -442,7 +488,8 @@ function renderCanonicalValue(normalized, limits) {
|
|
|
442
488
|
let bytes = 0
|
|
443
489
|
const append = (text) => {
|
|
444
490
|
bytes += stringBytes(text)
|
|
445
|
-
if (bytes > limits.maxStringBytes)
|
|
491
|
+
if (bytes > limits.maxStringBytes) invalid('$', `rendered PTC value exceeds ${limits.maxStringBytes} bytes`, 'budget', undefined,
|
|
492
|
+
`rendered PTC value exceeds ${limits.maxStringBytes} bytes`)
|
|
446
493
|
appendArray(chunks, text)
|
|
447
494
|
}
|
|
448
495
|
const emitted = new Set()
|