dsh-ptc-plus 0.4.1 → 0.4.3
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 +164 -78
- package/README.md +165 -79
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
- package/assets/ptc-plus-icon.svg +5 -0
- package/client.js +854 -336
- package/compiler-core.cjs +161 -157
- package/compiler-core.cjs.map +3 -3
- package/docs/README.md +4 -1
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +8 -7
- package/docs/adr/0008-define-agent-operating-contract.md +2 -2
- package/docs/adr/0010-session-log-derived-recovery-tips.md +7 -0
- package/docs/adr/0016-archive-resolved-review-findings.md +3 -3
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +3 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -0
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +14 -3
- package/docs/adr/0023-global-user-bindings.md +7 -7
- package/docs/adr/0025-use-versioned-logical-binding-identities.md +3 -3
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +23 -7
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +9 -5
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +26 -6
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +29 -0
- package/docs/adr/README.md +4 -4
- package/docs/architecture.md +15 -10
- package/docs/binding-review-placement-design.md +1 -1
- package/docs/client-ui.md +9 -5
- package/docs/durability-design.md +12 -9
- package/docs/installation.md +27 -9
- package/docs/runtime-reference.md +23 -12
- package/docs/semantic-validation.md +7 -3
- package/docs/stateful-binding-updates-proposal.md +8 -4
- package/docs/user-bindings.en.md +10 -7
- package/docs/user-bindings.md +9 -6
- package/docs/value-wire.md +1 -1
- package/docs/verification-optimization-work.md +41 -0
- package/docs/verification.md +53 -2
- package/index.js +39 -34
- package/internal/async-local-storage-intrinsics.js +43 -0
- package/internal/binding-declaration-contract.js +8 -0
- package/internal/binding-source-analysis.js +381 -64
- package/internal/binding-update-policy.js +7 -1
- package/internal/callable-source-facts.js +37 -1
- package/internal/cell-analysis.js +85 -17
- package/internal/compiler-intrinsics.js +9 -43
- package/internal/compiler-module-links.js +7 -4
- package/internal/config-spec.js +1 -1
- package/internal/cordis-tools-owner.js +20 -10
- package/internal/dynamic-environment-integration.js +8 -4
- package/internal/dynamic-environment-runtime.js +24 -3
- package/internal/dynamic-native-calls.js +4 -1
- package/internal/dynamic-native-runtime.js +15 -1
- package/internal/edit-transport-owner.js +80 -20
- package/internal/execution-seam-compat.js +192 -30
- package/internal/failure-reporting.js +38 -30
- package/internal/isolated-worker.js +17 -10
- package/internal/kernel-worker.js +645 -393
- package/internal/message-sources.js +14 -0
- package/internal/module-compilation.js +7 -5
- package/internal/module-transform-contract.js +32 -2
- package/internal/record-utils.js +24 -15
- package/internal/repl-memory-projection.js +10 -0
- package/internal/repl-scope-normalizer.js +72 -35
- package/internal/repl-value-observer.js +145 -46
- package/internal/runtime-config.js +62 -2
- package/internal/runtime-intrinsics.js +104 -0
- package/internal/runtime-messages.js +6 -4
- package/internal/session-cell-executor.js +26 -2
- package/internal/session-journal-recovery.js +267 -67
- package/internal/session-journal-schema.js +30 -13
- package/internal/session-journal.js +485 -44
- package/internal/session-log-view.js +5 -4
- package/internal/session-runtime.js +46 -21
- package/internal/session-state.js +7 -29
- package/internal/settings-compat.js +66 -1
- package/internal/source-position-map.js +38 -0
- package/internal/stateful-root-compiler.js +3 -1
- package/internal/typescript-transform.js +4 -2
- package/internal/user-binding-console-worker.js +115 -41
- package/internal/user-binding-draft-projection.js +3 -2
- package/internal/user-bindings-owner.js +2 -1
- package/internal/user-bindings.js +16 -3
- package/internal/validated-parse-repair.js +6 -2
- package/internal/value-wire.js +189 -94
- package/internal/worker-client.js +99 -8
- package/internal/worker-cwd-virtualization.js +82 -54
- package/internal/worker-environment.js +20 -0
- package/internal/worker-output-capture.js +187 -0
- package/internal/worker-output-fence.js +26 -0
- package/internal/worker-realm-surfaces.js +197 -0
- package/internal/worker-repl-realm.js +140 -0
- package/package.json +37 -4
- package/src/client-activity.js +12 -2
- package/src/client-authoring-view.js +121 -62
- package/src/client-console.js +4 -2
- package/src/client-copy.js +4 -2
- package/src/client-feature-gates.js +7 -9
- package/src/client-host-compat.js +104 -27
- package/src/client-repl-view.js +3 -1
- package/src/client-styles.js +12 -1
- package/src/client-tool-view.js +3 -1
- package/src/client-transport.js +2 -2
- package/src/client-workbench.js +4 -3
- package/src/client.js +56 -17
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util'
|
|
1
2
|
import { decodeValue, encodeValue, normalizeValueWire } from './value-wire.js'
|
|
2
3
|
import { normalizeDiagnostic } from './diagnostic.js'
|
|
3
4
|
import { assertOwnFields, isRecord } from './record-utils.js'
|
|
4
5
|
import { sessionEvents } from './session-events.js'
|
|
5
6
|
import { LEGACY_LANGUAGE_SEMANTICS, normalizeLanguageSemantics } from './language-semantics.js'
|
|
7
|
+
import { LEGACY_USER_BINDING_TRANSFORM, historicalModuleTransformForLanguage,
|
|
8
|
+
moduleTransformForLanguage, normalizeModuleTransform } from './module-transform-contract.js'
|
|
6
9
|
import {
|
|
7
10
|
LEGACY_DEFAULT_EXPORT_BINDING,
|
|
8
11
|
LEGACY_IMPORT_EXPRESSION_BOUNDARY,
|
|
@@ -16,6 +19,8 @@ import {
|
|
|
16
19
|
REWRITES_KEY,
|
|
17
20
|
RECOVERY_BOUNDARY_KEY,
|
|
18
21
|
JOURNAL_VERSION,
|
|
22
|
+
LANGUAGE_SEMANTICS_JOURNAL_VERSION,
|
|
23
|
+
LANGUAGE_SEMANTICS_JOURNAL_FIELDS,
|
|
19
24
|
IMPORT_BOUNDARY_JOURNAL_VERSION,
|
|
20
25
|
PER_NAME_USER_BINDINGS_JOURNAL_VERSION,
|
|
21
26
|
PER_NAME_JOURNAL_FIELDS,
|
|
@@ -54,8 +59,10 @@ import {
|
|
|
54
59
|
JOURNAL_VERSIONS,
|
|
55
60
|
USER_BINDINGS_REUSE_POLICIES,
|
|
56
61
|
RECOVERY_BOUNDARY_FIELDS,
|
|
62
|
+
REPL_TOOL_NAMES,
|
|
57
63
|
REWRITE_FIELDS,
|
|
58
64
|
REWRITE_KINDS,
|
|
65
|
+
usesCallSequenceConfirms,
|
|
59
66
|
} from './session-journal-schema.js'
|
|
60
67
|
|
|
61
68
|
export {
|
|
@@ -78,12 +85,16 @@ function validName(name) {
|
|
|
78
85
|
return typeof name === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(name)
|
|
79
86
|
}
|
|
80
87
|
|
|
88
|
+
export function isCanonicalSequence(value) {
|
|
89
|
+
return Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0)
|
|
90
|
+
}
|
|
91
|
+
|
|
81
92
|
function normalizeCalls(value) {
|
|
82
93
|
if (!Array.isArray(value)) throw new Error('invalid dsh-ptc-plus journal calls')
|
|
83
94
|
const calls = value.map((call, index) => {
|
|
84
95
|
if (!isRecord(call) || typeof call.global !== 'string' || typeof call.member !== 'string'
|
|
85
96
|
|| !Object.hasOwn(call, 'args') || (call.ok !== true && call.ok !== false)
|
|
86
|
-
|| !
|
|
97
|
+
|| !isCanonicalSequence(call.settle)) {
|
|
87
98
|
throw new Error(`invalid dsh-ptc-plus journal call at index ${index}`)
|
|
88
99
|
}
|
|
89
100
|
assertOwnFields(call, call.ok ? CALL_SUCCESS_FIELDS : CALL_ERROR_FIELDS, `journal call at index ${index}`)
|
|
@@ -166,7 +177,7 @@ function normalizeLegacyConfirms(value, resolveLegacyConfirm) {
|
|
|
166
177
|
throw new Error('legacy dsh-ptc-plus confirmed no-op calls require session call identity')
|
|
167
178
|
}
|
|
168
179
|
const confirms = value.map(callId => resolveLegacyConfirm(callId))
|
|
169
|
-
if (confirms.some(callSeq => !
|
|
180
|
+
if (confirms.some(callSeq => !isCanonicalSequence(callSeq))) {
|
|
170
181
|
throw new Error('legacy dsh-ptc-plus confirmed no-op call is not uniquely persisted')
|
|
171
182
|
}
|
|
172
183
|
if (new Set(confirms).size !== confirms.length) {
|
|
@@ -177,7 +188,7 @@ function normalizeLegacyConfirms(value, resolveLegacyConfirm) {
|
|
|
177
188
|
|
|
178
189
|
function normalizeConfirms(value) {
|
|
179
190
|
if (value === undefined) return []
|
|
180
|
-
if (!Array.isArray(value) || value.some(callSeq => !
|
|
191
|
+
if (!Array.isArray(value) || value.some(callSeq => !isCanonicalSequence(callSeq))) {
|
|
181
192
|
throw new Error('invalid dsh-ptc-plus confirmed no-op calls')
|
|
182
193
|
}
|
|
183
194
|
const confirms = [...new Set(value)]
|
|
@@ -251,12 +262,18 @@ function normalizeUserBindingsFingerprint(value) {
|
|
|
251
262
|
|
|
252
263
|
function migrateJournal(value, resolveLegacyConfirm) {
|
|
253
264
|
if (value.version === JOURNAL_VERSION) return value
|
|
265
|
+
if (value.version === LANGUAGE_SEMANTICS_JOURNAL_VERSION) {
|
|
266
|
+
assertOwnFields(value, LANGUAGE_SEMANTICS_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
|
|
267
|
+
const languageSemantics = normalizeLanguageSemantics(value.languageSemantics)
|
|
268
|
+
return { ...value, version: JOURNAL_VERSION, moduleTransform: historicalModuleTransformForLanguage(languageSemantics) }
|
|
269
|
+
}
|
|
254
270
|
if (value.version === PER_NAME_USER_BINDINGS_JOURNAL_VERSION) {
|
|
255
271
|
assertOwnFields(value, PER_NAME_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
|
|
256
|
-
return { ...value, version: JOURNAL_VERSION, languageSemantics: LEGACY_LANGUAGE_SEMANTICS
|
|
272
|
+
return { ...value, version: JOURNAL_VERSION, languageSemantics: LEGACY_LANGUAGE_SEMANTICS,
|
|
273
|
+
moduleTransform: LEGACY_USER_BINDING_TRANSFORM }
|
|
257
274
|
}
|
|
258
275
|
const shadow = { userBindingsShadowPolicy: LEGACY_USER_BINDINGS_SHADOW_POLICY, userBindingNames: null,
|
|
259
|
-
languageSemantics: LEGACY_LANGUAGE_SEMANTICS }
|
|
276
|
+
languageSemantics: LEGACY_LANGUAGE_SEMANTICS, moduleTransform: LEGACY_USER_BINDING_TRANSFORM }
|
|
260
277
|
if (value.version === IMPORT_BOUNDARY_JOURNAL_VERSION) {
|
|
261
278
|
assertOwnFields(value, WHOLE_ENTRY_JOURNAL_FIELDS, 'dsh-ptc-plus journal')
|
|
262
279
|
return { ...value, version: JOURNAL_VERSION, ...shadow }
|
|
@@ -325,6 +342,7 @@ export function normalizeJournal(value, options = {}) {
|
|
|
325
342
|
const migrated = migrateJournal(value, options.resolveLegacyConfirm)
|
|
326
343
|
assertOwnFields(migrated, JOURNAL_FIELDS, 'dsh-ptc-plus journal')
|
|
327
344
|
const languageSemantics = normalizeLanguageSemantics(migrated.languageSemantics)
|
|
345
|
+
const moduleTransform = normalizeModuleTransform(migrated.moduleTransform, languageSemantics)
|
|
328
346
|
const bindingPolicy = normalizeBindingPolicy(migrated.bindingPolicy)
|
|
329
347
|
const rewritePolicy = normalizeRewritePolicy(migrated.rewritePolicy)
|
|
330
348
|
const moduleSemantics = normalizeModuleSemantics(migrated.moduleSemantics)
|
|
@@ -355,6 +373,7 @@ export function normalizeJournal(value, options = {}) {
|
|
|
355
373
|
return Object.freeze({
|
|
356
374
|
version: JOURNAL_VERSION,
|
|
357
375
|
languageSemantics,
|
|
376
|
+
moduleTransform,
|
|
358
377
|
bindingPolicy,
|
|
359
378
|
rewritePolicy,
|
|
360
379
|
moduleSemantics,
|
|
@@ -374,7 +393,7 @@ export function normalizeJournal(value, options = {}) {
|
|
|
374
393
|
|
|
375
394
|
/** Validate the required persisted relation that makes one edit result executable history. */
|
|
376
395
|
export function normalizeDerivedEditResult(meta, expectedTargetCallSeq) {
|
|
377
|
-
if (!
|
|
396
|
+
if (!isCanonicalSequence(expectedTargetCallSeq)) {
|
|
378
397
|
throw new Error('derived edit does not identify an eligible target call')
|
|
379
398
|
}
|
|
380
399
|
if (!isRecord(meta)) throw new Error('invalid dsh-ptc-plus derived edit metadata')
|
|
@@ -382,7 +401,8 @@ export function normalizeDerivedEditResult(meta, expectedTargetCallSeq) {
|
|
|
382
401
|
const derived = meta[DERIVED_RUN_KEY]
|
|
383
402
|
if (!isRecord(target)) throw new Error('invalid dsh-ptc-plus edit target metadata')
|
|
384
403
|
assertOwnFields(target, EDIT_TARGET_FIELDS, 'edit target metadata')
|
|
385
|
-
if (target.targetCallSeq
|
|
404
|
+
if (!isCanonicalSequence(target.targetCallSeq)
|
|
405
|
+
|| target.targetCallSeq !== expectedTargetCallSeq) {
|
|
386
406
|
throw new Error('derived edit target does not match the eligible target call')
|
|
387
407
|
}
|
|
388
408
|
if (!isRecord(derived) || typeof derived.code !== 'string' || typeof derived.description !== 'string') {
|
|
@@ -476,13 +496,13 @@ export function recoveryBoundariesEqual(left, right) {
|
|
|
476
496
|
|
|
477
497
|
function normalizeRecoveryBoundaryValue(value, eventSeq = undefined) {
|
|
478
498
|
if (!isRecord(value)
|
|
479
|
-
|| !
|
|
499
|
+
|| !isCanonicalSequence(value.failedCallSeq)
|
|
480
500
|
|| (value.frontierCallSeq !== null
|
|
481
|
-
&&
|
|
501
|
+
&& !isCanonicalSequence(value.frontierCallSeq))) {
|
|
482
502
|
throw new Error('invalid dsh-ptc-plus recovery boundary')
|
|
483
503
|
}
|
|
484
504
|
assertOwnFields(value, RECOVERY_BOUNDARY_FIELDS, 'recovery boundary')
|
|
485
|
-
if (eventSeq !== undefined &&
|
|
505
|
+
if (eventSeq !== undefined && !isCanonicalSequence(eventSeq)) {
|
|
486
506
|
throw new Error('invalid dsh-ptc-plus recovery boundary event sequence')
|
|
487
507
|
}
|
|
488
508
|
return {
|
|
@@ -507,30 +527,399 @@ export function withRecoveryBoundaries(meta, boundaries) {
|
|
|
507
527
|
return base
|
|
508
528
|
}
|
|
509
529
|
|
|
530
|
+
const HOST_SEQUENCE_RELATION_POLICY_V0 = new Map([
|
|
531
|
+
['agent-preset/selected', null],
|
|
532
|
+
['agent/inbox/spliced', null],
|
|
533
|
+
['approval/asked', null],
|
|
534
|
+
['approval/decided', null],
|
|
535
|
+
['approval/policy', null],
|
|
536
|
+
['assistant/chunk', null],
|
|
537
|
+
['assistant/message', null],
|
|
538
|
+
['command/done', 'command-source'],
|
|
539
|
+
['command/run', null],
|
|
540
|
+
// Released format 0 also wrote the legacy spellings of the compaction events
|
|
541
|
+
// and a steering message that its own v0-to-v1 stage normalizes to
|
|
542
|
+
// `compaction/*` and `user/message`. They belong to the same frozen
|
|
543
|
+
// vocabulary, so the retired-boundary converter accepts them with their
|
|
544
|
+
// normalized relation semantics. `request/header-delta` and `mode/set` stay
|
|
545
|
+
// unsupported because that stage refuses them as well.
|
|
546
|
+
['compact/end', null],
|
|
547
|
+
['compact/prune', 'compaction-shadow'],
|
|
548
|
+
['compact/start', null],
|
|
549
|
+
['compact/summary', 'compaction-shadow'],
|
|
550
|
+
['compaction/end', null],
|
|
551
|
+
['compaction/prune', 'compaction-shadow'],
|
|
552
|
+
['compaction/start', null],
|
|
553
|
+
['compaction/summary', 'compaction-shadow'],
|
|
554
|
+
['feedback/record', null],
|
|
555
|
+
['goal/change', null],
|
|
556
|
+
['hook/invoked', null],
|
|
557
|
+
['hook/result', null],
|
|
558
|
+
['llm/retry', null],
|
|
559
|
+
['llm/retry-started', null],
|
|
560
|
+
['model/selection', null],
|
|
561
|
+
['permission/preset', null],
|
|
562
|
+
['plan/mode', null],
|
|
563
|
+
['request/context', null],
|
|
564
|
+
['request/header', null],
|
|
565
|
+
['sandbox/mode', null],
|
|
566
|
+
['schedule/change', null],
|
|
567
|
+
['session-log-deepseek/delivery-accepted', 'delivery-through'],
|
|
568
|
+
['session/end-seed', null],
|
|
569
|
+
['session/title', 'title-messages'],
|
|
570
|
+
['session/title-llm-request', 'title-request-messages'],
|
|
571
|
+
['step/end', null],
|
|
572
|
+
['step/start', null],
|
|
573
|
+
['steering/message', null],
|
|
574
|
+
['subagent/descriptor', null],
|
|
575
|
+
['subagent/model-selection-policy', null],
|
|
576
|
+
['team/member', null],
|
|
577
|
+
['team/message/delivered', null],
|
|
578
|
+
['team/message/queued', null],
|
|
579
|
+
['team/task', null],
|
|
580
|
+
['todo/write', null],
|
|
581
|
+
['tool-workflow/agent-end', null],
|
|
582
|
+
['tool-workflow/agent-start', null],
|
|
583
|
+
['tool-workflow/run-end', null],
|
|
584
|
+
['tool-workflow/run-start', null],
|
|
585
|
+
['tool/call', null],
|
|
586
|
+
['tool/code-dispatch', null],
|
|
587
|
+
['tool/code-dispatch-start', null],
|
|
588
|
+
['tool/result', null],
|
|
589
|
+
['turn/end', null],
|
|
590
|
+
['turn/start', null],
|
|
591
|
+
['user/message', null],
|
|
592
|
+
['web/deepseek-search-llm-request', null],
|
|
593
|
+
])
|
|
594
|
+
|
|
595
|
+
const V0_SURFACE_EVENT_TYPES = new Set([
|
|
596
|
+
'user/message', 'steering/message', 'assistant/message', 'tool/result',
|
|
597
|
+
])
|
|
598
|
+
const HOST_DATA_SEQUENCE_RELATIONS_V0 = new Map([
|
|
599
|
+
['command/done', new Set(['sourceEventSeq'])],
|
|
600
|
+
['compact/prune', new Set(['shadowedSeqs'])],
|
|
601
|
+
['compact/summary', new Set(['shadowedSeqs'])],
|
|
602
|
+
['compaction/prune', new Set(['shadowedSeqs'])],
|
|
603
|
+
['compaction/summary', new Set(['shadowedSeqs'])],
|
|
604
|
+
['session-log-deepseek/delivery-accepted', new Set(['throughSeq'])],
|
|
605
|
+
['session/title', new Set(['messageSeqs'])],
|
|
606
|
+
['session/title-llm-request', new Set(['messageSeqs'])],
|
|
607
|
+
])
|
|
608
|
+
const SEQUENCE_RELATION_FIELD = /Seqs?$/u
|
|
609
|
+
|
|
610
|
+
function validateKnownDataSequenceRelations(event) {
|
|
611
|
+
if (!isRecord(event.data)) return
|
|
612
|
+
const allowed = HOST_DATA_SEQUENCE_RELATIONS_V0.get(event.type) ?? new Set()
|
|
613
|
+
for (const field of Reflect.ownKeys(event.data)) {
|
|
614
|
+
if (typeof field === 'string' && SEQUENCE_RELATION_FIELD.test(field) && !allowed.has(field)) {
|
|
615
|
+
throw new Error(`unsupported ${event.type} data sequence relation ${JSON.stringify(field)}`)
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function validateEarlierEventReference(events, event, seq, label) {
|
|
621
|
+
if (!isCanonicalSequence(seq) || seq >= event.seq || events[seq]?.seq !== seq) {
|
|
622
|
+
throw new Error(`invalid ${label} event reference ${JSON.stringify(seq)} at event ${event.seq}`)
|
|
623
|
+
}
|
|
624
|
+
return events[seq]
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function validateEarlierEventReferences(events, event, seqs, label) {
|
|
628
|
+
if (!Array.isArray(seqs)) throw new Error(`invalid ${label} event references`)
|
|
629
|
+
const seen = new Set()
|
|
630
|
+
return seqs.map(seq => {
|
|
631
|
+
if (seen.has(seq)) throw new Error(`duplicate ${label} event reference ${JSON.stringify(seq)} at event ${event.seq}`)
|
|
632
|
+
seen.add(seq)
|
|
633
|
+
return validateEarlierEventReference(events, event, seq, label)
|
|
634
|
+
})
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function sameSequences(left, right) {
|
|
638
|
+
return left.length === right.length && left.every((seq, index) => seq === right[index])
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function validateSurfaceRelation(events, event, surface) {
|
|
642
|
+
const eligible = V0_SURFACE_EVENT_TYPES.has(event.type)
|
|
643
|
+
if (!eligible) {
|
|
644
|
+
if (event.surfaceOp !== undefined || event.sourceEventSeqs !== undefined) {
|
|
645
|
+
throw new Error(`session event ${JSON.stringify(event.type)} is not surface-eligible`)
|
|
646
|
+
}
|
|
647
|
+
return
|
|
648
|
+
}
|
|
649
|
+
if (event.sourceEventSeqs !== undefined) {
|
|
650
|
+
validateEarlierEventReferences(events, event, event.sourceEventSeqs, 'source')
|
|
651
|
+
}
|
|
652
|
+
if (event.sourceEventSeqs?.length === 0 && event.type !== 'assistant/message') {
|
|
653
|
+
throw new Error('source event references must not be empty except on assistant/message')
|
|
654
|
+
}
|
|
655
|
+
const op = event.surfaceOp
|
|
656
|
+
// Historical host formats predate the mandatory append marker.
|
|
657
|
+
if (op === undefined || op === 'append') {
|
|
658
|
+
surface.push(event.seq)
|
|
659
|
+
return
|
|
660
|
+
}
|
|
661
|
+
if (!isRecord(op) || op.op !== 'replace'
|
|
662
|
+
|| Reflect.ownKeys(op).length !== 3
|
|
663
|
+
|| !isCanonicalSequence(op.start)
|
|
664
|
+
|| !isCanonicalSequence(op.end)) {
|
|
665
|
+
throw new Error('invalid surface operation during recovery-boundary migration')
|
|
666
|
+
}
|
|
667
|
+
validateEarlierEventReference(events, event, op.start, 'surface replacement start')
|
|
668
|
+
validateEarlierEventReference(events, event, op.end, 'surface replacement end')
|
|
669
|
+
const start = surface.indexOf(op.start)
|
|
670
|
+
const end = surface.indexOf(op.end)
|
|
671
|
+
if (start < 0 || end < start) {
|
|
672
|
+
throw new Error(`surface replacement at event ${event.seq} does not identify a current ordered surface range`)
|
|
673
|
+
}
|
|
674
|
+
const shadowed = surface.slice(start, end + 1)
|
|
675
|
+
const provenance = new Set(event.sourceEventSeqs ?? [])
|
|
676
|
+
if (shadowed.some(seq => !provenance.has(seq))) {
|
|
677
|
+
throw new Error(`surface replacement at event ${event.seq} does not cite every replaced surface node`)
|
|
678
|
+
}
|
|
679
|
+
if (event.type === 'tool/result'
|
|
680
|
+
&& (shadowed.length !== 1 || events[shadowed[0]]?.type !== 'tool/result')) {
|
|
681
|
+
throw new Error('tool/result surface replacement must target one current tool/result')
|
|
682
|
+
}
|
|
683
|
+
if (event.type === 'tool/result') {
|
|
684
|
+
const withoutContent = candidate => {
|
|
685
|
+
const data = candidate?.data
|
|
686
|
+
const message = data?.message
|
|
687
|
+
const content = message?.content
|
|
688
|
+
if (!isRecord(data) || !isRecord(message) || !Array.isArray(content)
|
|
689
|
+
|| content.length !== 1 || !isRecord(content[0])) {
|
|
690
|
+
throw new Error('tool/result surface replacement has invalid message content')
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
...data,
|
|
694
|
+
message: { ...message, content: [{ ...content[0], content: null }] },
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (!isDeepStrictEqual(withoutContent(events[shadowed[0]]), withoutContent(event))) {
|
|
698
|
+
throw new Error('tool/result surface replacement may change only content')
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
surface.splice(start, shadowed.length, event.seq)
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function validateCommandSource(events, event) {
|
|
705
|
+
if (!isRecord(event.data)) {
|
|
706
|
+
throw new Error(`command/done event ${event.seq} has invalid data`)
|
|
707
|
+
}
|
|
708
|
+
if (event.data.sourceEventSeq === undefined) return
|
|
709
|
+
if (!isRecord(event.data) || event.data.kind !== 'success') {
|
|
710
|
+
throw new Error(`command/done event ${event.seq} has a source outside a successful result`)
|
|
711
|
+
}
|
|
712
|
+
const source = validateEarlierEventReference(events, event, event.data.sourceEventSeq, 'command source')
|
|
713
|
+
if (source.type === 'command/run' || source.type === 'command/done') {
|
|
714
|
+
throw new Error(`command/done event ${event.seq} cites a command lifecycle event as its source`)
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function validateTitleRequestSources(events, event) {
|
|
719
|
+
if (!isRecord(event.data) || !Array.isArray(event.data.messageSeqs)) {
|
|
720
|
+
throw new Error('invalid session/title-llm-request message references')
|
|
721
|
+
}
|
|
722
|
+
const sources = validateEarlierEventReferences(
|
|
723
|
+
events, event, event.data.messageSeqs, 'session title request message',
|
|
724
|
+
)
|
|
725
|
+
if (sources.some(source => source.type !== 'user/message')) {
|
|
726
|
+
throw new Error(`session/title-llm-request event ${event.seq} must cite earlier user/message events`)
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function validateDeliveryThrough(events, event) {
|
|
731
|
+
if (!isRecord(event.data)) {
|
|
732
|
+
throw new Error(`session-log-deepseek/delivery-accepted event ${event.seq} has invalid data`)
|
|
733
|
+
}
|
|
734
|
+
validateEarlierEventReference(events, event, event.data.throughSeq, 'session delivery through')
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function validateTitleSources(events, event) {
|
|
738
|
+
if (!isRecord(event.data) || !Array.isArray(event.data.messageSeqs)) {
|
|
739
|
+
throw new Error('invalid session/title message references')
|
|
740
|
+
}
|
|
741
|
+
if ((event.data.messageSeqs.length === 0) !== (event.data.source?.kind === 'user')) {
|
|
742
|
+
const requirement = event.data.source?.kind === 'user'
|
|
743
|
+
? 'cite no message seqs'
|
|
744
|
+
: 'cite at least one message seq'
|
|
745
|
+
throw new Error(`session/title event ${event.seq} must ${requirement}`)
|
|
746
|
+
}
|
|
747
|
+
const sources = validateEarlierEventReferences(events, event, event.data.messageSeqs, 'session title message')
|
|
748
|
+
if (sources.some(source => source.type !== 'user/message' || source.data?.source?.kind !== 'user')) {
|
|
749
|
+
throw new Error(`session/title event ${event.seq} must cite earlier human user/message events`)
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function validateCompactionSources(events, event, surface) {
|
|
754
|
+
if (!isRecord(event.data) || !Array.isArray(event.data.shadowedSeqs)) {
|
|
755
|
+
throw new Error(`invalid ${event.type} shadow references`)
|
|
756
|
+
}
|
|
757
|
+
const shadowed = event.data.shadowedSeqs
|
|
758
|
+
validateEarlierEventReferences(events, event, shadowed, 'compaction shadow')
|
|
759
|
+
if (event.data.shadowedRange === undefined) {
|
|
760
|
+
if (shadowed.length === 0) return
|
|
761
|
+
const start = surface.indexOf(shadowed[0])
|
|
762
|
+
if (start < 0 || !sameSequences(surface.slice(start, start + shadowed.length), shadowed)) {
|
|
763
|
+
throw new Error(`${event.type} event ${event.seq} shadow list does not match the current surface`)
|
|
764
|
+
}
|
|
765
|
+
return
|
|
766
|
+
}
|
|
767
|
+
const range = event.data.shadowedRange
|
|
768
|
+
if (!isRecord(range)) throw new Error(`invalid ${event.type} shadow range`)
|
|
769
|
+
validateEarlierEventReference(events, event, range.start, 'compaction range start')
|
|
770
|
+
validateEarlierEventReference(events, event, range.end, 'compaction range end')
|
|
771
|
+
const start = surface.indexOf(range.start)
|
|
772
|
+
const end = surface.indexOf(range.end)
|
|
773
|
+
if (start < 0 || end < start
|
|
774
|
+
|| !sameSequences(surface.slice(start, end + 1), shadowed)) {
|
|
775
|
+
throw new Error(`${event.type} event ${event.seq} shadow range does not match the current surface`)
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function validateHostSequenceRelations(events) {
|
|
780
|
+
const surface = []
|
|
781
|
+
for (const event of events) {
|
|
782
|
+
if (event.type === RECOVERY_BOUNDARY_EVENT) continue
|
|
783
|
+
if (!HOST_SEQUENCE_RELATION_POLICY_V0.has(event.type)) {
|
|
784
|
+
throw new Error(`unsupported session event type ${JSON.stringify(event.type)} during recovery-boundary migration`)
|
|
785
|
+
}
|
|
786
|
+
validateKnownDataSequenceRelations(event)
|
|
787
|
+
validateSurfaceRelation(events, event, surface)
|
|
788
|
+
const relation = HOST_SEQUENCE_RELATION_POLICY_V0.get(event.type)
|
|
789
|
+
if (relation === 'command-source') validateCommandSource(events, event)
|
|
790
|
+
else if (relation === 'title-messages') validateTitleSources(events, event)
|
|
791
|
+
else if (relation === 'title-request-messages') validateTitleRequestSources(events, event)
|
|
792
|
+
else if (relation === 'delivery-through') validateDeliveryThrough(events, event)
|
|
793
|
+
else if (relation === 'compaction-shadow') validateCompactionSources(events, event, surface)
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function rewriteHostSequenceRelations(event, mapEvent) {
|
|
798
|
+
const relation = HOST_SEQUENCE_RELATION_POLICY_V0.get(event.type)
|
|
799
|
+
if (Object.hasOwn(event, 'sourceEventSeqs')) {
|
|
800
|
+
event.sourceEventSeqs = event.sourceEventSeqs.map(seq => mapEvent(seq, 'source'))
|
|
801
|
+
}
|
|
802
|
+
if (event.surfaceOp !== undefined && event.surfaceOp !== 'append') {
|
|
803
|
+
event.surfaceOp = {
|
|
804
|
+
...event.surfaceOp,
|
|
805
|
+
start: mapEvent(event.surfaceOp.start, 'surface replacement start'),
|
|
806
|
+
end: mapEvent(event.surfaceOp.end, 'surface replacement end'),
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (relation === 'command-source') {
|
|
810
|
+
if (event.data.sourceEventSeq !== undefined) {
|
|
811
|
+
event.data = {
|
|
812
|
+
...event.data,
|
|
813
|
+
sourceEventSeq: mapEvent(event.data.sourceEventSeq, 'command source'),
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
} else if (relation === 'title-messages') {
|
|
817
|
+
event.data = {
|
|
818
|
+
...event.data,
|
|
819
|
+
messageSeqs: event.data.messageSeqs.map(seq => mapEvent(seq, 'session title message')),
|
|
820
|
+
}
|
|
821
|
+
} else if (relation === 'title-request-messages') {
|
|
822
|
+
const mapped = event.data.messageSeqs.map(seq => mapEvent(seq, 'session title request message'))
|
|
823
|
+
if (mapped.some((seq, index) => seq !== event.data.messageSeqs[index])) {
|
|
824
|
+
throw new Error('session/title-llm-request messageSeqs cannot be preserved across removed events')
|
|
825
|
+
}
|
|
826
|
+
} else if (relation === 'delivery-through') {
|
|
827
|
+
event.data = {
|
|
828
|
+
...event.data,
|
|
829
|
+
throughSeq: mapEvent(event.data.throughSeq, 'session delivery through'),
|
|
830
|
+
}
|
|
831
|
+
} else if (relation === 'compaction-shadow') {
|
|
832
|
+
const data = {
|
|
833
|
+
...event.data,
|
|
834
|
+
shadowedSeqs: event.data.shadowedSeqs.map(seq => mapEvent(seq, 'compaction shadow')),
|
|
835
|
+
}
|
|
836
|
+
if (event.data.shadowedRange !== undefined) {
|
|
837
|
+
data.shadowedRange = {
|
|
838
|
+
...event.data.shadowedRange,
|
|
839
|
+
start: mapEvent(event.data.shadowedRange.start, 'compaction range start'),
|
|
840
|
+
end: mapEvent(event.data.shadowedRange.end, 'compaction range end'),
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
event.data = data
|
|
844
|
+
}
|
|
845
|
+
return event
|
|
846
|
+
}
|
|
847
|
+
|
|
510
848
|
/**
|
|
511
849
|
* Convert the retired custom boundary event in a raw log before DSH restores it.
|
|
512
850
|
* The returned log is a detached, renumbered copy; the input is never mutated.
|
|
513
851
|
*/
|
|
514
852
|
export function migrateRecoveryBoundaryEvents(events) {
|
|
515
853
|
if (!Array.isArray(events)) throw new TypeError('recovery-boundary migration expects an event array')
|
|
854
|
+
const sequenceMap = new Map()
|
|
855
|
+
let nextSeq = 0
|
|
856
|
+
for (const [index, event] of events.entries()) {
|
|
857
|
+
if (!isCanonicalSequence(event?.seq)) {
|
|
858
|
+
throw new Error('invalid session event sequence during recovery-boundary migration')
|
|
859
|
+
}
|
|
860
|
+
if (sequenceMap.has(event.seq)) {
|
|
861
|
+
throw new Error(`duplicate session event sequence ${event.seq} during recovery-boundary migration`)
|
|
862
|
+
}
|
|
863
|
+
if (event.seq !== index) {
|
|
864
|
+
throw new Error(`session event sequence ${event.seq} at index ${index} is not contiguous from zero`)
|
|
865
|
+
}
|
|
866
|
+
const retainedSeq = event.type === RECOVERY_BOUNDARY_EVENT ? undefined : nextSeq++
|
|
867
|
+
sequenceMap.set(event.seq, { event, retainedSeq })
|
|
868
|
+
}
|
|
869
|
+
validateHostSequenceRelations(events)
|
|
870
|
+
const mapEvent = (seq, label) => {
|
|
871
|
+
if (!isCanonicalSequence(seq)) throw new Error(`invalid ${label} event reference`)
|
|
872
|
+
const target = sequenceMap.get(seq)
|
|
873
|
+
if (target === undefined || target.retainedSeq === undefined) {
|
|
874
|
+
throw new Error(`unmapped ${label} event reference ${seq}`)
|
|
875
|
+
}
|
|
876
|
+
return target.retainedSeq
|
|
877
|
+
}
|
|
878
|
+
const mapCall = (seq, resultSeq, label) => {
|
|
879
|
+
const target = sequenceMap.get(seq)
|
|
880
|
+
if (!isCanonicalSequence(seq) || target === undefined
|
|
881
|
+
|| target.retainedSeq === undefined || target.event?.type !== 'tool/call'
|
|
882
|
+
|| !REPL_TOOL_NAMES.has(target.event.data?.name) || seq >= resultSeq) {
|
|
883
|
+
throw new Error(`unproved ${label} call reference ${JSON.stringify(seq)} at event ${resultSeq}`)
|
|
884
|
+
}
|
|
885
|
+
return target.retainedSeq
|
|
886
|
+
}
|
|
887
|
+
const mapBoundary = (boundary, resultSeq) => ({
|
|
888
|
+
failedCallSeq: mapCall(boundary.failedCallSeq, resultSeq, 'recovery-boundary failure'),
|
|
889
|
+
frontierCallSeq: boundary.frontierCallSeq === null ? null : (() => {
|
|
890
|
+
if (boundary.frontierCallSeq >= boundary.failedCallSeq) {
|
|
891
|
+
throw new Error('recovery-boundary frontier must precede its failed call')
|
|
892
|
+
}
|
|
893
|
+
return mapCall(boundary.frontierCallSeq, resultSeq, 'recovery-boundary frontier')
|
|
894
|
+
})(),
|
|
895
|
+
})
|
|
516
896
|
const migrated = []
|
|
517
897
|
const pending = []
|
|
898
|
+
const boundaryOrigins = new Map()
|
|
518
899
|
for (const event of events) {
|
|
519
900
|
if (event?.type === RECOVERY_BOUNDARY_EVENT) {
|
|
520
|
-
|
|
521
|
-
throw new Error('invalid dsh-ptc-plus recovery boundary event sequence')
|
|
522
|
-
}
|
|
523
|
-
pending.push(normalizeRecoveryBoundaryValue(event.data))
|
|
901
|
+
pending.push({ boundary: normalizeRecoveryBoundaryValue(event.data), eventSeq: event.seq })
|
|
524
902
|
continue
|
|
525
903
|
}
|
|
526
904
|
const detached = cloneJson(event)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
905
|
+
const sourceSeqs = detached?.sourceEventSeqs
|
|
906
|
+
const source = Array.isArray(sourceSeqs) && sourceSeqs.length === 1
|
|
907
|
+
? sequenceMap.get(sourceSeqs[0]) : undefined
|
|
908
|
+
const carriesJournal = detached?.type === 'tool/result'
|
|
909
|
+
&& isRecord(detached.data?.meta) && Object.hasOwn(detached.data.meta, JOURNAL_KEY)
|
|
910
|
+
&& source?.retainedSeq !== undefined && source.event?.type === 'tool/call'
|
|
911
|
+
&& REPL_TOOL_NAMES.has(source.event.data?.name) && source.event.seq < event.seq
|
|
912
|
+
if (pending.length > 0 && carriesJournal) {
|
|
913
|
+
const data = { ...detached.data }
|
|
914
|
+
const meta = { ...data.meta }
|
|
530
915
|
const existing = meta[RECOVERY_BOUNDARY_KEY] === undefined
|
|
531
916
|
? []
|
|
532
917
|
: normalizeRecoveryBoundaries(meta[RECOVERY_BOUNDARY_KEY])
|
|
533
|
-
meta[RECOVERY_BOUNDARY_KEY] = [...
|
|
918
|
+
meta[RECOVERY_BOUNDARY_KEY] = [...pending.map(item => item.boundary), ...existing]
|
|
919
|
+
boundaryOrigins.set(event.seq, {
|
|
920
|
+
pendingCount: pending.length,
|
|
921
|
+
pendingEventSeqs: pending.map(item => item.eventSeq),
|
|
922
|
+
})
|
|
534
923
|
data.meta = meta
|
|
535
924
|
detached.data = data
|
|
536
925
|
pending.length = 0
|
|
@@ -538,19 +927,53 @@ export function migrateRecoveryBoundaryEvents(events) {
|
|
|
538
927
|
migrated.push(detached)
|
|
539
928
|
}
|
|
540
929
|
if (pending.length > 0) {
|
|
541
|
-
throw new Error('recovery boundary has no later
|
|
930
|
+
throw new Error('recovery boundary has no later PTC journal result for migration')
|
|
542
931
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
932
|
+
return Object.freeze(migrated.map((event) => {
|
|
933
|
+
const oldSeq = event.seq
|
|
934
|
+
rewriteHostSequenceRelations(event, mapEvent)
|
|
935
|
+
if (event.type === 'tool/result' && isRecord(event.data?.meta)) {
|
|
936
|
+
const meta = event.data.meta
|
|
937
|
+
const journal = meta[JOURNAL_KEY]
|
|
938
|
+
if (usesCallSequenceConfirms(journal) && journal.confirms !== undefined) {
|
|
939
|
+
if (!Array.isArray(journal.confirms)) throw new Error('invalid dsh-ptc-plus confirmed no-op calls')
|
|
940
|
+
journal.confirms = journal.confirms.map(seq => mapCall(seq, oldSeq, 'journal confirmation'))
|
|
941
|
+
}
|
|
942
|
+
if (meta[EDIT_TARGET_KEY] !== undefined) {
|
|
943
|
+
if (!isRecord(meta[EDIT_TARGET_KEY])) throw new Error('invalid dsh-ptc-plus edit target')
|
|
944
|
+
meta[EDIT_TARGET_KEY].targetCallSeq = mapCall(
|
|
945
|
+
meta[EDIT_TARGET_KEY].targetCallSeq, oldSeq, 'edit target',
|
|
946
|
+
)
|
|
947
|
+
}
|
|
948
|
+
if (meta[RECOVERY_BOUNDARY_KEY] !== undefined) {
|
|
949
|
+
const origins = boundaryOrigins.get(oldSeq)
|
|
950
|
+
meta[RECOVERY_BOUNDARY_KEY] = normalizeRecoveryBoundaries(meta[RECOVERY_BOUNDARY_KEY])
|
|
951
|
+
.map((boundary, index) => mapBoundary(boundary,
|
|
952
|
+
origins !== undefined && index < origins.pendingCount
|
|
953
|
+
? origins.pendingEventSeqs[index]
|
|
954
|
+
: oldSeq))
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
event.seq = sequenceMap.get(oldSeq).retainedSeq
|
|
958
|
+
return Object.freeze(event)
|
|
959
|
+
}))
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function currentAssistantCallSeq(events, candidates, callId, toolName) {
|
|
963
|
+
let assistantIndex = events.length - 1
|
|
964
|
+
while (assistantIndex >= 0 && events[assistantIndex]?.type !== 'assistant/message') {
|
|
965
|
+
assistantIndex -= 1
|
|
546
966
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
967
|
+
if (assistantIndex < 0) return undefined
|
|
968
|
+
const content = events[assistantIndex].data?.message?.content
|
|
969
|
+
if (!Array.isArray(content)) return undefined
|
|
970
|
+
const blocks = content.filter(part => part?.type === 'tool-call'
|
|
971
|
+
&& part.id === callId && part.name === toolName)
|
|
972
|
+
if (blocks.length !== 1) return undefined
|
|
973
|
+
const eventIndexes = new Map(events.map((event, index) => [event, index]))
|
|
974
|
+
const current = candidates.filter(event => eventIndexes.get(event) > assistantIndex
|
|
975
|
+
&& event.data.arguments === blocks[0].arguments)
|
|
976
|
+
return current.length === 1 ? current[0].seq : undefined
|
|
554
977
|
}
|
|
555
978
|
|
|
556
979
|
/** Resolve the persisted event identity for one named tool call being dispatched. */
|
|
@@ -559,31 +982,47 @@ export function liveToolCallSeq(session, callId, toolName) {
|
|
|
559
982
|
if (!Array.isArray(events) || typeof callId !== 'string' || callId.length === 0
|
|
560
983
|
|| typeof toolName !== 'string' || toolName.length === 0) return undefined
|
|
561
984
|
|
|
562
|
-
const
|
|
985
|
+
const pendingCalls = new Map()
|
|
563
986
|
for (const event of events) {
|
|
564
|
-
if (event?.type
|
|
565
|
-
|
|
566
|
-
|
|
987
|
+
if (event?.type === 'tool/call' && event.data?.callId === callId) {
|
|
988
|
+
if (event.data.name === toolName && !isCanonicalSequence(event.seq)) {
|
|
989
|
+
throw new Error(`current ${toolName} call has an invalid session event sequence`)
|
|
990
|
+
}
|
|
991
|
+
if (isCanonicalSequence(event.seq)) pendingCalls.set(event.seq, event)
|
|
992
|
+
continue
|
|
993
|
+
}
|
|
994
|
+
if (event?.type !== 'tool/result') continue
|
|
995
|
+
const sourceRelation = event.sourceEventSeqs
|
|
996
|
+
const canonicalSourceRelation = Array.isArray(sourceRelation)
|
|
997
|
+
&& sourceRelation.length === 1
|
|
998
|
+
&& isCanonicalSequence(sourceRelation[0])
|
|
999
|
+
if (canonicalSourceRelation) {
|
|
1000
|
+
pendingCalls.delete(sourceRelation[0])
|
|
1001
|
+
continue
|
|
1002
|
+
}
|
|
1003
|
+
// A damaged result cannot recover state, but its ordered call identity can
|
|
1004
|
+
// still prove that the sole preceding same-id call is no longer live.
|
|
1005
|
+
if (event.data?.message?.source?.callId === callId
|
|
1006
|
+
&& pendingCalls.size === 1) {
|
|
1007
|
+
pendingCalls.clear()
|
|
567
1008
|
}
|
|
568
1009
|
}
|
|
569
1010
|
|
|
570
|
-
const candidates = []
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
if (
|
|
575
|
-
throw new Error(`current ${toolName} call has an invalid session event sequence`)
|
|
576
|
-
}
|
|
577
|
-
candidates.push(event.seq)
|
|
1011
|
+
const candidates = [...pendingCalls.values()]
|
|
1012
|
+
.filter(event => event.data?.name === toolName)
|
|
1013
|
+
if (candidates.length > 1) {
|
|
1014
|
+
const current = currentAssistantCallSeq(events, candidates, callId, toolName)
|
|
1015
|
+
if (current !== undefined) return current
|
|
578
1016
|
}
|
|
579
1017
|
if (candidates.length > 1) {
|
|
580
1018
|
throw new Error(`session log contains multiple unpaired ${toolName} calls for callId ${JSON.stringify(callId)}`)
|
|
581
1019
|
}
|
|
582
|
-
return candidates[0]
|
|
1020
|
+
return candidates[0]?.seq
|
|
583
1021
|
}
|
|
584
1022
|
|
|
585
1023
|
/** Start a mutable journal for one live cell. */
|
|
586
|
-
export function createJournal(confirms = [], bindingPolicy, rewritePolicy, languageSemantics = LEGACY_LANGUAGE_SEMANTICS
|
|
1024
|
+
export function createJournal(confirms = [], bindingPolicy, rewritePolicy, languageSemantics = LEGACY_LANGUAGE_SEMANTICS,
|
|
1025
|
+
moduleTransform = moduleTransformForLanguage(languageSemantics)) {
|
|
587
1026
|
if (typeof bindingPolicy === 'string') {
|
|
588
1027
|
if (!BINDING_MODES.has(bindingPolicy)) throw new TypeError('invalid dsh-ptc-plus journal binding mode')
|
|
589
1028
|
bindingPolicy = {
|
|
@@ -591,9 +1030,11 @@ export function createJournal(confirms = [], bindingPolicy, rewritePolicy, langu
|
|
|
591
1030
|
functionClassRedeclarations: false,
|
|
592
1031
|
}
|
|
593
1032
|
}
|
|
1033
|
+
languageSemantics = normalizeLanguageSemantics(languageSemantics)
|
|
594
1034
|
return {
|
|
595
1035
|
version: JOURNAL_VERSION,
|
|
596
|
-
languageSemantics
|
|
1036
|
+
languageSemantics,
|
|
1037
|
+
moduleTransform: normalizeModuleTransform(moduleTransform, languageSemantics),
|
|
597
1038
|
bindingPolicy: normalizeBindingPolicy(bindingPolicy),
|
|
598
1039
|
rewritePolicy: normalizeRewritePolicy(rewritePolicy),
|
|
599
1040
|
moduleSemantics: normalizeModuleSemantics(LIVE_MODULE_SEMANTICS),
|