@omercnet/paseo-omp 0.3.0 → 0.4.0-next.114.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.md +19 -5
- package/client/omp-config-surface.tsx +243 -24
- package/client/omp-config-views.ts +24 -0
- package/client/omp-model-picker-state.ts +145 -0
- package/client/omp-model-picker.tsx +282 -0
- package/client/omp-routing-editor.tsx +307 -0
- package/client/support-diagnostics-state.ts +45 -0
- package/index.server.ts +27 -2
- package/package.json +2 -8
- package/paseo-plugin.json +1 -1
- package/server/omp-models.ts +59 -0
- package/server/omp-settings.ts +30 -20
- package/server/operational-failure-diagnostics.ts +76 -0
- package/server/package-version.ts +2 -0
- package/server/protocol-violation-diagnostics.ts +169 -0
- package/server/provider/catalog.ts +39 -10
- package/server/provider/connection.ts +60 -8
- package/server/provider/host-tools.ts +284 -34
- package/server/provider/mcp-transport.ts +2 -1
- package/server/provider/omp-rpc.ts +1011 -107
- package/server/provider/profile-providers.ts +7 -2
- package/server/provider/registration.ts +12 -2
- package/server/provider/security.ts +8 -10
- package/server/provider/session-descriptors.ts +45 -11
- package/server/provider/session.ts +200 -58
- package/server/provider/subsessions.ts +311 -73
- package/server/provider/timeline-projector.ts +34 -11
- package/server/support-diagnostics.ts +284 -0
- package/shared/omp-models.ts +49 -0
- package/shared/omp-settings.ts +227 -3
- package/shared/support-diagnostics.ts +32 -0
- package/CHANGELOG.md +0 -113
- package/SUPPORT.md +0 -44
- package/TESTING.md +0 -150
- package/docs/alpha-release-checklist.md +0 -68
- package/docs/configuration.md +0 -126
- package/docs/core-provider-issue-audit.md +0 -109
- package/docs/images/mcp-authorization-compact.png +0 -0
- package/docs/images/mcp-controls-wide.png +0 -0
- package/docs/images/plugin-manager.png +0 -0
- package/docs/images/workspace-settings.png +0 -0
- package/docs/installation.md +0 -89
- package/tsconfig.json +0 -16
|
@@ -21,13 +21,15 @@ import { OmpTimelineProjector, type OmpTimelineScheduler } from "./timeline-proj
|
|
|
21
21
|
|
|
22
22
|
const MAX_CHILDREN = 1_024;
|
|
23
23
|
const MAX_TASK_DISPATCHES = 4_096;
|
|
24
|
-
const MAX_BUFFERED_EVENTS =
|
|
24
|
+
const MAX_BUFFERED_EVENTS = MAX_CHILDREN * 3;
|
|
25
25
|
const MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
26
26
|
const MAX_CHILD_MESSAGE_IDENTITIES = 2_048;
|
|
27
27
|
const MAX_REPLAY_MESSAGES = 100_000;
|
|
28
28
|
const MAX_REPLAY_BYTES = 64 * 1024 * 1024;
|
|
29
29
|
const MAX_REPLAY_NODES = 400_000;
|
|
30
30
|
const MAX_REPLAY_DEPTH = 16;
|
|
31
|
+
const CHILD_REPLAY_UNAVAILABLE = "OMP subagent history is unavailable or incomplete";
|
|
32
|
+
type BufferedSubagentEvent = { event: OmpSubagentEvent; bytes: number };
|
|
31
33
|
|
|
32
34
|
type Emit = (event: ProviderEvent) => void;
|
|
33
35
|
type ChildTerminalStatus = "completed" | "failed" | "canceled";
|
|
@@ -53,6 +55,7 @@ type ChildState = {
|
|
|
53
55
|
status: ChildStatus;
|
|
54
56
|
terminalRequested?: ChildTerminalStatus;
|
|
55
57
|
sessionClosed: boolean;
|
|
58
|
+
replayUnavailable?: boolean;
|
|
56
59
|
seenInSnapshot: boolean;
|
|
57
60
|
seenAssistantIdentities: BoundedStringSet;
|
|
58
61
|
projector: OmpTimelineProjector;
|
|
@@ -63,6 +66,7 @@ type TaskDispatch = {
|
|
|
63
66
|
childSessionIds: Set<string>;
|
|
64
67
|
acknowledged: boolean;
|
|
65
68
|
};
|
|
69
|
+
type ReplayHistory = { sessionFile: string; messages: OmpMessage[] };
|
|
66
70
|
type ReplayBudget = { messages: number; bytes: number; nodes: number };
|
|
67
71
|
const TaskArgsSchema = z.object({
|
|
68
72
|
tasks: z.array(z.unknown()).max(MAX_CHILDREN).optional(),
|
|
@@ -94,6 +98,18 @@ const TaskResultDetailsSchema = z.object({
|
|
|
94
98
|
exitCode: z.number().optional(),
|
|
95
99
|
error: z.unknown().optional(),
|
|
96
100
|
aborted: z.boolean().optional(),
|
|
101
|
+
status: z
|
|
102
|
+
.enum([
|
|
103
|
+
"pending",
|
|
104
|
+
"running",
|
|
105
|
+
"completed",
|
|
106
|
+
"failed",
|
|
107
|
+
"error",
|
|
108
|
+
"aborted",
|
|
109
|
+
"canceled",
|
|
110
|
+
"cancelled",
|
|
111
|
+
])
|
|
112
|
+
.optional(),
|
|
97
113
|
}),
|
|
98
114
|
)
|
|
99
115
|
.max(MAX_CHILDREN),
|
|
@@ -192,8 +208,15 @@ function replayChildren(messages: readonly OmpMessage[]): ReplayChildRef[] {
|
|
|
192
208
|
const details = taskResultDetails(message);
|
|
193
209
|
const results = details?.results ?? [];
|
|
194
210
|
for (const result of results) {
|
|
211
|
+
const canceled =
|
|
212
|
+
result.aborted === true ||
|
|
213
|
+
result.status === "aborted" ||
|
|
214
|
+
result.status === "canceled" ||
|
|
215
|
+
result.status === "cancelled";
|
|
195
216
|
const failed =
|
|
196
217
|
message.isError === true ||
|
|
218
|
+
result.status === "failed" ||
|
|
219
|
+
result.status === "error" ||
|
|
197
220
|
Boolean(result.error) ||
|
|
198
221
|
(typeof result.exitCode === "number" && result.exitCode !== 0);
|
|
199
222
|
children.push({
|
|
@@ -201,7 +224,7 @@ function replayChildren(messages: readonly OmpMessage[]): ReplayChildRef[] {
|
|
|
201
224
|
agent: result.agent ?? call?.title,
|
|
202
225
|
description: call?.description,
|
|
203
226
|
parentToolCallId: message.toolCallId,
|
|
204
|
-
status:
|
|
227
|
+
status: canceled ? "canceled" : failed ? "failed" : "completed",
|
|
205
228
|
});
|
|
206
229
|
}
|
|
207
230
|
const resultIds = new Set(results.map((result) => result.id));
|
|
@@ -283,12 +306,18 @@ function terminalStatus(status: string): ChildTerminalStatus | undefined {
|
|
|
283
306
|
return;
|
|
284
307
|
}
|
|
285
308
|
|
|
309
|
+
function bufferedNativeId(event: OmpSubagentEvent): string {
|
|
310
|
+
return event.type === "subagent_progress" ? event.payload.progress.id : event.payload.id;
|
|
311
|
+
}
|
|
312
|
+
|
|
286
313
|
export class OmpSubsessionProjector {
|
|
287
314
|
private readonly children = new Map<string, ChildState>();
|
|
288
315
|
private readonly sessionIdByNativeId = new Map<string, string>();
|
|
289
316
|
private readonly toolOwners = new Map<string, string>();
|
|
290
317
|
private readonly dispatches = new Map<string, TaskDispatch>();
|
|
291
|
-
private readonly bufferedEvents:
|
|
318
|
+
private readonly bufferedEvents: BufferedSubagentEvent[] = [];
|
|
319
|
+
// null means every child observed during this replay is omitted after tombstone saturation.
|
|
320
|
+
private omittedBufferedChildren: Set<string> | null = new Set();
|
|
292
321
|
private bufferedBytes = 0;
|
|
293
322
|
private replaying = false;
|
|
294
323
|
private closed = false;
|
|
@@ -355,15 +384,7 @@ export class OmpSubsessionProjector {
|
|
|
355
384
|
handle(event: OmpSubagentEvent): void {
|
|
356
385
|
if (this.closed) return;
|
|
357
386
|
if (this.replaying) {
|
|
358
|
-
|
|
359
|
-
throw new OmpPublicError("OMP subagent replay event limit reached");
|
|
360
|
-
}
|
|
361
|
-
const bytes = boundedJsonBytes(event, MAX_BUFFERED_BYTES, 1_024, MAX_BUFFERED_BYTES, 4_096);
|
|
362
|
-
if (bytes === Number.POSITIVE_INFINITY || this.bufferedBytes + bytes > MAX_BUFFERED_BYTES) {
|
|
363
|
-
throw new OmpPublicError("OMP subagent replay event limit reached");
|
|
364
|
-
}
|
|
365
|
-
this.bufferedEvents.push(event);
|
|
366
|
-
this.bufferedBytes += bytes;
|
|
387
|
+
this.bufferEvent(event);
|
|
367
388
|
return;
|
|
368
389
|
}
|
|
369
390
|
this.apply(event);
|
|
@@ -390,23 +411,48 @@ export class OmpSubsessionProjector {
|
|
|
390
411
|
signal,
|
|
391
412
|
0,
|
|
392
413
|
);
|
|
393
|
-
|
|
414
|
+
let snapshots: OmpSubagentSnapshot[] = [];
|
|
415
|
+
try {
|
|
416
|
+
snapshots = await waitForReplay(runtimeSession.getSubagents(), signal);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
if (signal.aborted) throw error;
|
|
419
|
+
}
|
|
394
420
|
await this.replaySnapshots(snapshots, runtimeSession, runtime, visited, budget, signal);
|
|
395
421
|
signal.throwIfAborted();
|
|
396
422
|
this.reconcileSnapshots(snapshots);
|
|
397
423
|
completed = true;
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (signal.aborted) throw error;
|
|
426
|
+
this.terminalize("failed");
|
|
427
|
+
completed = true;
|
|
398
428
|
} finally {
|
|
399
429
|
this.replaying = false;
|
|
400
430
|
const buffered = this.bufferedEvents.splice(0);
|
|
401
431
|
this.bufferedBytes = 0;
|
|
402
432
|
if (completed && !signal.aborted) {
|
|
403
|
-
for (const event of buffered)
|
|
433
|
+
for (const { event } of buffered) {
|
|
434
|
+
if (
|
|
435
|
+
event.type === "subagent_progress" &&
|
|
436
|
+
!terminalStatus(event.payload.progress.status)
|
|
437
|
+
) {
|
|
438
|
+
const sessionId = this.sessionIdByNativeId.get(event.payload.progress.id);
|
|
439
|
+
const child = sessionId ? this.children.get(sessionId) : undefined;
|
|
440
|
+
if (child && child.status !== "running") continue;
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
this.apply(event);
|
|
444
|
+
} catch {
|
|
445
|
+
this.terminalize("failed");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
404
448
|
} else {
|
|
449
|
+
this.terminalize("failed");
|
|
405
450
|
this.closed = true;
|
|
406
451
|
for (const child of this.children.values()) child.projector.close();
|
|
407
452
|
this.dispatches.clear();
|
|
408
453
|
this.toolOwners.clear();
|
|
409
454
|
}
|
|
455
|
+
this.omittedBufferedChildren = new Set();
|
|
410
456
|
}
|
|
411
457
|
}
|
|
412
458
|
|
|
@@ -441,6 +487,126 @@ export class OmpSubsessionProjector {
|
|
|
441
487
|
}
|
|
442
488
|
this.bufferedEvents.length = 0;
|
|
443
489
|
this.bufferedBytes = 0;
|
|
490
|
+
this.omittedBufferedChildren = new Set();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
private bufferEvent(event: OmpSubagentEvent): void {
|
|
494
|
+
const isProgress = event.type === "subagent_progress";
|
|
495
|
+
const progressTerminal = isProgress ? terminalStatus(event.payload.progress.status) : undefined;
|
|
496
|
+
const isAdvisoryProgress = isProgress && !progressTerminal;
|
|
497
|
+
const bufferedEvent: OmpSubagentEvent =
|
|
498
|
+
isProgress && progressTerminal
|
|
499
|
+
? {
|
|
500
|
+
type: "subagent_lifecycle",
|
|
501
|
+
payload: {
|
|
502
|
+
id: event.payload.progress.id,
|
|
503
|
+
agent: event.payload.agent,
|
|
504
|
+
status: event.payload.progress.status,
|
|
505
|
+
index: event.payload.index,
|
|
506
|
+
...(event.payload.agentSource ? { agentSource: event.payload.agentSource } : {}),
|
|
507
|
+
...(event.payload.parentToolCallId
|
|
508
|
+
? { parentToolCallId: event.payload.parentToolCallId }
|
|
509
|
+
: {}),
|
|
510
|
+
...(event.payload.detached !== undefined ? { detached: event.payload.detached } : {}),
|
|
511
|
+
},
|
|
512
|
+
}
|
|
513
|
+
: event;
|
|
514
|
+
const incomingId = bufferedNativeId(bufferedEvent);
|
|
515
|
+
if (this.isBufferedChildOmitted(incomingId)) return;
|
|
516
|
+
const bytes = boundedJsonBytes(
|
|
517
|
+
bufferedEvent,
|
|
518
|
+
MAX_BUFFERED_BYTES,
|
|
519
|
+
1_024,
|
|
520
|
+
MAX_BUFFERED_BYTES,
|
|
521
|
+
4_096,
|
|
522
|
+
);
|
|
523
|
+
if (bytes === Number.POSITIVE_INFINITY) {
|
|
524
|
+
if (!isAdvisoryProgress) this.omitBufferedChild(incomingId);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (isAdvisoryProgress) {
|
|
528
|
+
for (let index = this.bufferedEvents.length - 1; index >= 0; index -= 1) {
|
|
529
|
+
const queued = this.bufferedEvents[index]?.event;
|
|
530
|
+
if (!queued) continue;
|
|
531
|
+
if (
|
|
532
|
+
queued.type === "subagent_lifecycle" &&
|
|
533
|
+
queued.payload.id === incomingId &&
|
|
534
|
+
terminalStatus(queued.payload.status)
|
|
535
|
+
) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (queued.type !== "subagent_progress" || queued.payload.progress.id !== incomingId) {
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (terminalStatus(queued.payload.progress.status)) return;
|
|
542
|
+
const [removed] = this.bufferedEvents.splice(index, 1);
|
|
543
|
+
this.bufferedBytes -= removed?.bytes ?? 0;
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const bufferedIds = new Set<string>();
|
|
549
|
+
for (const child of this.children.values()) bufferedIds.add(child.nativeId);
|
|
550
|
+
for (const { event: queued } of this.bufferedEvents) bufferedIds.add(bufferedNativeId(queued));
|
|
551
|
+
if (!bufferedIds.has(incomingId) && bufferedIds.size >= MAX_CHILDREN) {
|
|
552
|
+
if (isAdvisoryProgress) return;
|
|
553
|
+
const advisory = this.bufferedEvents.find(
|
|
554
|
+
({ event: queued }) =>
|
|
555
|
+
queued.type === "subagent_progress" && !terminalStatus(queued.payload.progress.status),
|
|
556
|
+
);
|
|
557
|
+
if (!advisory) {
|
|
558
|
+
this.omitBufferedChild(incomingId);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const evictedId = bufferedNativeId(advisory.event);
|
|
562
|
+
this.omitBufferedChild(evictedId);
|
|
563
|
+
if (this.isBufferedChildOmitted(incomingId)) return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
while (
|
|
567
|
+
this.bufferedEvents.length >= MAX_BUFFERED_EVENTS ||
|
|
568
|
+
this.bufferedBytes + bytes > MAX_BUFFERED_BYTES
|
|
569
|
+
) {
|
|
570
|
+
const advisoryIndex = this.bufferedEvents.findIndex(
|
|
571
|
+
({ event: queued }) =>
|
|
572
|
+
queued.type === "subagent_progress" && !terminalStatus(queued.payload.progress.status),
|
|
573
|
+
);
|
|
574
|
+
if (advisoryIndex < 0) {
|
|
575
|
+
if (!isAdvisoryProgress) this.omitBufferedChild(incomingId);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const [removed] = this.bufferedEvents.splice(advisoryIndex, 1);
|
|
579
|
+
this.bufferedBytes -= removed?.bytes ?? 0;
|
|
580
|
+
}
|
|
581
|
+
this.bufferedEvents.push({ event: bufferedEvent, bytes });
|
|
582
|
+
this.bufferedBytes += bytes;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
private isBufferedChildOmitted(nativeId: string): boolean {
|
|
586
|
+
return this.omittedBufferedChildren === null || this.omittedBufferedChildren.has(nativeId);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
private omitBufferedChild(nativeId: string): void {
|
|
590
|
+
const omitted = this.omittedBufferedChildren;
|
|
591
|
+
if (!omitted || omitted.has(nativeId)) return;
|
|
592
|
+
if (omitted.size >= MAX_CHILDREN) {
|
|
593
|
+
this.omittedBufferedChildren = null;
|
|
594
|
+
this.bufferedEvents.length = 0;
|
|
595
|
+
this.bufferedBytes = 0;
|
|
596
|
+
for (const child of this.children.values()) child.replayUnavailable = true;
|
|
597
|
+
this.terminalize("failed");
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
omitted.add(nativeId);
|
|
601
|
+
for (let index = this.bufferedEvents.length - 1; index >= 0; index -= 1) {
|
|
602
|
+
const queued = this.bufferedEvents[index];
|
|
603
|
+
if (!queued || bufferedNativeId(queued.event) !== nativeId) continue;
|
|
604
|
+
const [removed] = this.bufferedEvents.splice(index, 1);
|
|
605
|
+
this.bufferedBytes -= removed?.bytes ?? 0;
|
|
606
|
+
}
|
|
607
|
+
const sessionId = this.sessionIdByNativeId.get(nativeId);
|
|
608
|
+
const child = sessionId ? this.children.get(sessionId) : undefined;
|
|
609
|
+
if (child?.status === "running") this.failReplayChild(child);
|
|
444
610
|
}
|
|
445
611
|
|
|
446
612
|
private apply(event: OmpSubagentEvent): void {
|
|
@@ -555,6 +721,7 @@ export class OmpSubsessionProjector {
|
|
|
555
721
|
type: "session.opened",
|
|
556
722
|
sessionId,
|
|
557
723
|
parentSessionId,
|
|
724
|
+
toolCallId: ref.parentToolCallId,
|
|
558
725
|
capabilities: ["session.subsession"],
|
|
559
726
|
restoration: "parent",
|
|
560
727
|
cwd: this.cwd,
|
|
@@ -567,6 +734,7 @@ export class OmpSubsessionProjector {
|
|
|
567
734
|
}
|
|
568
735
|
|
|
569
736
|
private restartChild(child: ChildState): void {
|
|
737
|
+
child.replayUnavailable = false;
|
|
570
738
|
if (child.status === "running") return;
|
|
571
739
|
child.status = "running";
|
|
572
740
|
child.terminalRequested = undefined;
|
|
@@ -587,7 +755,12 @@ export class OmpSubsessionProjector {
|
|
|
587
755
|
if (!this.hasDirectActivity(child.sessionId)) this.finishChild(child, status);
|
|
588
756
|
}
|
|
589
757
|
|
|
590
|
-
private finishChild(
|
|
758
|
+
private finishChild(
|
|
759
|
+
child: ChildState,
|
|
760
|
+
status: ChildTerminalStatus,
|
|
761
|
+
force = false,
|
|
762
|
+
errorMessage = "OMP subagent failed",
|
|
763
|
+
): void {
|
|
591
764
|
if (child.status !== "running") return;
|
|
592
765
|
if (!force && this.hasDirectActivity(child.sessionId)) {
|
|
593
766
|
child.terminalRequested = status;
|
|
@@ -600,7 +773,7 @@ export class OmpSubsessionProjector {
|
|
|
600
773
|
sessionId: child.sessionId,
|
|
601
774
|
turnId: child.turnId,
|
|
602
775
|
state: status,
|
|
603
|
-
...(status === "failed" ? { error: { message:
|
|
776
|
+
...(status === "failed" ? { error: { message: errorMessage } } : {}),
|
|
604
777
|
});
|
|
605
778
|
for (const [toolCallId, dispatch] of this.dispatches) {
|
|
606
779
|
if (dispatch.childSessionIds.has(child.sessionId)) this.settleDispatch(toolCallId, dispatch);
|
|
@@ -611,6 +784,10 @@ export class OmpSubsessionProjector {
|
|
|
611
784
|
}
|
|
612
785
|
this.onActivityChange();
|
|
613
786
|
}
|
|
787
|
+
private failReplayChild(child: ChildState): void {
|
|
788
|
+
child.replayUnavailable = true;
|
|
789
|
+
this.finishChild(child, "failed", true, CHILD_REPLAY_UNAVAILABLE);
|
|
790
|
+
}
|
|
614
791
|
|
|
615
792
|
private hasDirectActivity(ownerSessionId: string): boolean {
|
|
616
793
|
for (const child of this.children.values()) {
|
|
@@ -671,6 +848,10 @@ export class OmpSubsessionProjector {
|
|
|
671
848
|
this.resolveParent(snapshot.parentToolCallId, snapshot.sessionFile),
|
|
672
849
|
);
|
|
673
850
|
present.add(child.nativeId);
|
|
851
|
+
if (child.replayUnavailable) {
|
|
852
|
+
child.seenInSnapshot = true;
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
674
855
|
const terminal = terminalStatus(snapshot.status);
|
|
675
856
|
if (terminal) this.requestTerminal(child, terminal);
|
|
676
857
|
else this.restartChild(child);
|
|
@@ -692,36 +873,75 @@ export class OmpSubsessionProjector {
|
|
|
692
873
|
budget: ReplayBudget,
|
|
693
874
|
signal: AbortSignal,
|
|
694
875
|
): Promise<void> {
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
876
|
+
const collected: Array<{
|
|
877
|
+
snapshot: OmpSubagentSnapshot;
|
|
878
|
+
sessionFile?: string;
|
|
879
|
+
messages?: OmpMessage[];
|
|
880
|
+
}> = [];
|
|
881
|
+
for (const snapshot of snapshots) {
|
|
700
882
|
signal.throwIfAborted();
|
|
701
883
|
if (this.sessionIdByNativeId.has(snapshot.id)) continue;
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
884
|
+
try {
|
|
885
|
+
const history = await waitForReplay(
|
|
886
|
+
runtimeSession.getSubagentMessages({ subagentId: snapshot.id }),
|
|
887
|
+
signal,
|
|
888
|
+
);
|
|
889
|
+
try {
|
|
890
|
+
this.accountReplay(history.messages, budget, signal);
|
|
891
|
+
collected.push({
|
|
892
|
+
snapshot,
|
|
893
|
+
sessionFile: history.sessionFile,
|
|
894
|
+
messages: history.messages,
|
|
895
|
+
});
|
|
896
|
+
} catch (error) {
|
|
897
|
+
if (signal.aborted) throw error;
|
|
898
|
+
collected.push({ snapshot, sessionFile: history.sessionFile });
|
|
899
|
+
}
|
|
900
|
+
} catch (error) {
|
|
901
|
+
if (signal.aborted) throw error;
|
|
902
|
+
collected.push({ snapshot });
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
collected.sort((left, right) => {
|
|
906
|
+
const leftDepth = left.sessionFile?.split("/").length ?? Number.MAX_SAFE_INTEGER;
|
|
907
|
+
const rightDepth = right.sessionFile?.split("/").length ?? Number.MAX_SAFE_INTEGER;
|
|
908
|
+
return leftDepth - rightDepth;
|
|
909
|
+
});
|
|
910
|
+
const snapshotIds = new Set(collected.map(({ snapshot }) => snapshot.id));
|
|
911
|
+
for (const { snapshot, sessionFile, messages } of collected) {
|
|
707
912
|
signal.throwIfAborted();
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
913
|
+
if (this.sessionIdByNativeId.has(snapshot.id)) continue;
|
|
914
|
+
let child: ChildState | undefined;
|
|
915
|
+
try {
|
|
916
|
+
child = this.ensureChild(
|
|
917
|
+
{ ...snapshot, sessionFile },
|
|
918
|
+
this.resolveParent(snapshot.parentToolCallId, sessionFile),
|
|
919
|
+
);
|
|
920
|
+
if (!messages || this.isBufferedChildOmitted(snapshot.id)) {
|
|
921
|
+
this.failReplayChild(child);
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
this.projectReplay(child, messages, signal);
|
|
925
|
+
if (!sessionFile) {
|
|
926
|
+
this.failReplayChild(child);
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
visited.add(`${sessionFile}\0${snapshot.id}`);
|
|
930
|
+
await this.replayChildren(
|
|
931
|
+
child.sessionId,
|
|
932
|
+
sessionFile,
|
|
933
|
+
messages,
|
|
934
|
+
runtime,
|
|
935
|
+
visited,
|
|
936
|
+
budget,
|
|
937
|
+
signal,
|
|
938
|
+
1,
|
|
939
|
+
snapshotIds,
|
|
940
|
+
);
|
|
941
|
+
} catch (error) {
|
|
942
|
+
if (signal.aborted) throw error;
|
|
943
|
+
if (child) this.failReplayChild(child);
|
|
944
|
+
}
|
|
725
945
|
}
|
|
726
946
|
const activeToolCallIds = new Set(
|
|
727
947
|
snapshots.flatMap((snapshot) =>
|
|
@@ -744,46 +964,64 @@ export class OmpSubsessionProjector {
|
|
|
744
964
|
budget: ReplayBudget,
|
|
745
965
|
signal: AbortSignal,
|
|
746
966
|
depth: number,
|
|
967
|
+
snapshotIds?: ReadonlySet<string>,
|
|
747
968
|
): Promise<void> {
|
|
748
969
|
signal.throwIfAborted();
|
|
749
970
|
if (depth > MAX_REPLAY_DEPTH) throw new OmpPublicError("OMP subagent history is too deep");
|
|
750
971
|
this.indexTaskCalls(parentSessionId, messages);
|
|
751
972
|
for (const ref of replayChildren(messages)) {
|
|
752
973
|
signal.throwIfAborted();
|
|
753
|
-
if (!parentSessionFile)
|
|
754
|
-
throw new OmpPublicError("OMP parent transcript identity is unavailable");
|
|
755
|
-
}
|
|
974
|
+
if (!parentSessionFile || snapshotIds?.has(ref.id)) continue;
|
|
756
975
|
const visitKey = `${parentSessionFile}\0${ref.id}`;
|
|
757
976
|
if (visited.has(visitKey)) continue;
|
|
758
977
|
visited.add(visitKey);
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
978
|
+
let child: ChildState | undefined;
|
|
979
|
+
let history: ReplayHistory | undefined;
|
|
980
|
+
try {
|
|
981
|
+
const loaded = await waitForReplay(
|
|
982
|
+
runtime.readPersistedSubagentTranscript({
|
|
983
|
+
parentSessionFile,
|
|
984
|
+
childTranscriptId: ref.id,
|
|
985
|
+
cwd: this.cwd,
|
|
986
|
+
signal,
|
|
987
|
+
}),
|
|
764
988
|
signal,
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
989
|
+
);
|
|
990
|
+
history = loaded;
|
|
991
|
+
child = this.ensureChild({ ...ref, sessionFile: loaded.sessionFile }, parentSessionId);
|
|
992
|
+
this.accountReplay(loaded.messages, budget, signal);
|
|
993
|
+
signal.throwIfAborted();
|
|
994
|
+
this.projectReplay(child, loaded.messages, signal);
|
|
995
|
+
await this.replayChildren(
|
|
996
|
+
child.sessionId,
|
|
997
|
+
history.sessionFile,
|
|
998
|
+
history.messages,
|
|
999
|
+
runtime,
|
|
1000
|
+
visited,
|
|
1001
|
+
budget,
|
|
1002
|
+
signal,
|
|
1003
|
+
depth + 1,
|
|
1004
|
+
snapshotIds,
|
|
1005
|
+
);
|
|
1006
|
+
signal.throwIfAborted();
|
|
1007
|
+
this.requestTerminal(
|
|
1008
|
+
child,
|
|
1009
|
+
ref.status === "derive" ? replayTerminalStatus(history.messages) : ref.status,
|
|
1010
|
+
);
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
if (signal.aborted) throw error;
|
|
1013
|
+
if (!child) {
|
|
1014
|
+
try {
|
|
1015
|
+
child = this.ensureChild(
|
|
1016
|
+
{ ...ref, sessionFile: history?.sessionFile },
|
|
1017
|
+
parentSessionId,
|
|
1018
|
+
);
|
|
1019
|
+
} catch {
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
this.failReplayChild(child);
|
|
1024
|
+
}
|
|
787
1025
|
}
|
|
788
1026
|
}
|
|
789
1027
|
|
|
@@ -12,11 +12,14 @@ import {
|
|
|
12
12
|
type JsonValue,
|
|
13
13
|
OmpPublicDataSerializer,
|
|
14
14
|
OmpPublicError,
|
|
15
|
+
truncateUtf8,
|
|
15
16
|
utf8Bytes,
|
|
16
17
|
} from "./security";
|
|
17
18
|
|
|
18
19
|
const STREAM_FRAME_MS = 32;
|
|
19
20
|
const MAX_STREAM_TEXT_LENGTH = 4 * 1024 * 1024;
|
|
21
|
+
const MAX_RAW_STREAM_TEXT_LENGTH = 8 * 1024 * 1024;
|
|
22
|
+
const MAX_REDACTED_STREAM_TEXT_LENGTH = MAX_RAW_STREAM_TEXT_LENGTH * 3;
|
|
20
23
|
const MAX_ACTIVE_TOOLS = 64;
|
|
21
24
|
const MAX_TODOS = 256;
|
|
22
25
|
const MAX_TURN_NATIVE_IDENTITIES = 1_024;
|
|
@@ -908,10 +911,7 @@ export class OmpTimelineProjector {
|
|
|
908
911
|
return;
|
|
909
912
|
}
|
|
910
913
|
if (message.role === "bashExecution") {
|
|
911
|
-
|
|
912
|
-
? `$ ${message.command}\n${message.output}`
|
|
913
|
-
: `$ ${message.command}`;
|
|
914
|
-
this.project({ type: "command_output", text }, this.replayTurnId);
|
|
914
|
+
this.publishCustomMessage(message);
|
|
915
915
|
this.finishTurn(this.replayTurnId);
|
|
916
916
|
this.replayTurnId = null;
|
|
917
917
|
}
|
|
@@ -1007,13 +1007,28 @@ export class OmpTimelineProjector {
|
|
|
1007
1007
|
if (!this.stream || this.closed || this.stream.dirtyBlocks.size === 0) return;
|
|
1008
1008
|
const stream = this.stream;
|
|
1009
1009
|
if (!stream.nativeIdentity && !finalizeFallback) return;
|
|
1010
|
-
const indexes = [...stream.
|
|
1010
|
+
const indexes = [...stream.blocks.keys()].sort((left, right) => left - right);
|
|
1011
|
+
let remainingTextBytes = MAX_STREAM_TEXT_LENGTH;
|
|
1011
1012
|
stream.dirtyBlocks.clear();
|
|
1012
1013
|
for (const contentIndex of indexes) {
|
|
1013
1014
|
const block = stream.blocks.get(contentIndex);
|
|
1014
|
-
if (!block
|
|
1015
|
-
const publicText =
|
|
1016
|
-
|
|
1015
|
+
if (!block) continue;
|
|
1016
|
+
const publicText =
|
|
1017
|
+
block.kind === "image"
|
|
1018
|
+
? block.text
|
|
1019
|
+
: truncateUtf8(
|
|
1020
|
+
this.dataFilter.text(block.text, MAX_REDACTED_STREAM_TEXT_LENGTH),
|
|
1021
|
+
remainingTextBytes,
|
|
1022
|
+
);
|
|
1023
|
+
if (block.kind !== "image") {
|
|
1024
|
+
remainingTextBytes = Math.max(0, remainingTextBytes - utf8Bytes(publicText));
|
|
1025
|
+
}
|
|
1026
|
+
if (
|
|
1027
|
+
block.publishedText === publicText ||
|
|
1028
|
+
(!publicText && block.publishedText === undefined)
|
|
1029
|
+
) {
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1017
1032
|
const nextPublishedBytes = utf8Bytes(publicText);
|
|
1018
1033
|
if (
|
|
1019
1034
|
stream.retainedBytes + stream.publishedBytes + nextPublishedBytes >
|
|
@@ -1267,7 +1282,7 @@ export class OmpTimelineProjector {
|
|
|
1267
1282
|
(snapshot.kind === "image" ? 0 : snapshotBytes);
|
|
1268
1283
|
if (
|
|
1269
1284
|
retainedBytes + stream.publishedBytes > MAX_STREAM_TOTAL_BYTES ||
|
|
1270
|
-
textBytes >
|
|
1285
|
+
textBytes > MAX_RAW_STREAM_TEXT_LENGTH
|
|
1271
1286
|
) {
|
|
1272
1287
|
return;
|
|
1273
1288
|
}
|
|
@@ -1291,7 +1306,7 @@ export class OmpTimelineProjector {
|
|
|
1291
1306
|
|
|
1292
1307
|
private publishCommand(turnId: string): void {
|
|
1293
1308
|
if (!this.commandText) return;
|
|
1294
|
-
const publicText = this.dataFilter.text(this.commandText);
|
|
1309
|
+
const publicText = this.dataFilter.text(this.commandText, MAX_STREAM_TEXT_LENGTH);
|
|
1295
1310
|
if (!publicText || publicText === this.commandPublishedText) return;
|
|
1296
1311
|
this.commandPublishedText = publicText;
|
|
1297
1312
|
this.publish({
|
|
@@ -1377,13 +1392,21 @@ export class OmpTimelineProjector {
|
|
|
1377
1392
|
type: "shell",
|
|
1378
1393
|
command,
|
|
1379
1394
|
...(firstString(details, "cwd") ? { cwd: firstString(details, "cwd") } : {}),
|
|
1380
|
-
...(output ? { output: this.dataFilter.text(output) } : {}),
|
|
1395
|
+
...(output ? { output: this.dataFilter.text(output, MAX_STREAM_TEXT_LENGTH) } : {}),
|
|
1381
1396
|
...(typeof message.exitCode === "number" || message.exitCode === null
|
|
1382
1397
|
? { exitCode: message.exitCode }
|
|
1383
1398
|
: typeof details?.exitCode === "number"
|
|
1384
1399
|
? { exitCode: details.exitCode }
|
|
1385
1400
|
: {}),
|
|
1386
1401
|
},
|
|
1402
|
+
...(message.cancelled !== undefined || message.truncated !== undefined
|
|
1403
|
+
? {
|
|
1404
|
+
metadata: {
|
|
1405
|
+
...(message.cancelled !== undefined ? { cancelled: message.cancelled } : {}),
|
|
1406
|
+
...(message.truncated !== undefined ? { truncated: message.truncated } : {}),
|
|
1407
|
+
},
|
|
1408
|
+
}
|
|
1409
|
+
: {}),
|
|
1387
1410
|
status: message.cancelled ? "canceled" : "completed",
|
|
1388
1411
|
error: null,
|
|
1389
1412
|
});
|