@omercnet/paseo-omp 0.4.0-next.114.1 → 0.4.0-next.120.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/client/omp-config-styles.ts +284 -0
- package/client/omp-config-surface.tsx +1 -279
- package/client/omp-routing-editor.tsx +1 -1
- package/package.json +3 -1
- package/server/package-version.ts +1 -1
- package/server/provider/config-normalization.ts +1 -1
- package/server/provider/connection.ts +28 -1
- package/server/provider/mcp-transport.ts +1 -1
- package/server/provider/omp-rpc-environment.ts +368 -0
- package/server/provider/omp-rpc-process.ts +119 -0
- package/server/provider/omp-rpc-values.ts +31 -0
- package/server/provider/omp-rpc.ts +814 -670
- package/server/provider/profile-providers.ts +2 -1
- package/server/provider/prompt-payload.ts +247 -0
- package/server/provider/session-descriptors.ts +52 -18
- package/server/provider/session.ts +23 -244
- package/server/provider/subsessions.ts +28 -6
- package/server/provider/timeline-projector.ts +94 -24
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import type {
|
|
3
3
|
ProviderConfigState,
|
|
4
|
-
ProviderContent,
|
|
5
4
|
ProviderError,
|
|
6
5
|
ProviderEvent,
|
|
7
6
|
ProviderInput,
|
|
@@ -10,7 +9,6 @@ import type {
|
|
|
10
9
|
ProviderToolCallDetail,
|
|
11
10
|
ProviderUsage,
|
|
12
11
|
} from "@getpaseo/plugin/server/provider";
|
|
13
|
-
import { getForgeDefinitionOrNeutral } from "@getpaseo/protocol/forge-manifest";
|
|
14
12
|
import type {
|
|
15
13
|
OmpOperationalFailure,
|
|
16
14
|
OmpOperationalFailureReporter,
|
|
@@ -30,13 +28,12 @@ import {
|
|
|
30
28
|
withCommittedOmpSelection,
|
|
31
29
|
} from "./config-normalization";
|
|
32
30
|
import { OmpHostToolsBridge, type OmpMcpConnector, validateOmpHostToolConfig } from "./host-tools";
|
|
33
|
-
import { isOmpImageMimeType,
|
|
31
|
+
import { isOmpImageMimeType, OmpImageMaterializer } from "./image";
|
|
34
32
|
import type {
|
|
35
33
|
OmpAvailableCommand,
|
|
36
34
|
OmpBranchResult,
|
|
37
35
|
OmpCompactionResult,
|
|
38
36
|
OmpExtensionUiResponse,
|
|
39
|
-
OmpImage,
|
|
40
37
|
OmpMessage,
|
|
41
38
|
OmpModel,
|
|
42
39
|
OmpRpcEvent,
|
|
@@ -44,11 +41,19 @@ import type {
|
|
|
44
41
|
OmpRuntimeSession,
|
|
45
42
|
OmpSessionState,
|
|
46
43
|
OmpSessionStats,
|
|
47
|
-
OmpStartOptions,
|
|
48
44
|
OmpToolApprovalCancel,
|
|
49
45
|
OmpToolApprovalRequest,
|
|
50
46
|
} from "./omp-rpc";
|
|
51
|
-
import {
|
|
47
|
+
import { OmpRpcRequestRejectedError } from "./omp-rpc";
|
|
48
|
+
import { buildOmpSpawnRequest, type OmpStartOptions } from "./omp-rpc-environment";
|
|
49
|
+
import {
|
|
50
|
+
inlinePromptFrameBytes,
|
|
51
|
+
isSafeCommandName,
|
|
52
|
+
MAX_PROMPT_TEXT_LENGTH,
|
|
53
|
+
type OmpPromptPayload,
|
|
54
|
+
promptPayload,
|
|
55
|
+
slashCommandName,
|
|
56
|
+
} from "./prompt-payload";
|
|
52
57
|
import {
|
|
53
58
|
BoundedStringSet,
|
|
54
59
|
boundedJsonBytes,
|
|
@@ -94,9 +99,6 @@ const USAGE_REFRESH_MS = 100;
|
|
|
94
99
|
const FINAL_USAGE_WAIT_MS = 250;
|
|
95
100
|
const COMPACTION_MAX_WAIT_MS = 5 * 60_000;
|
|
96
101
|
const AGENT_END_SETTLE_MS = 5_000;
|
|
97
|
-
const MAX_PROMPT_PARTS = 64;
|
|
98
|
-
const MAX_PROMPT_TEXT_LENGTH = 1024 * 1024;
|
|
99
|
-
const RPC_REQUEST_ID_BYTES = 36;
|
|
100
102
|
const MAX_AGENT_END_CORRELATION_MESSAGES = 512;
|
|
101
103
|
const MAX_TRACKED_ENTRY_IDS = 1_024;
|
|
102
104
|
const MAX_UNCLAIMED_BRANCH_ENTRIES = 1_024;
|
|
@@ -407,240 +409,6 @@ async function settleSessionCleanup(promises: readonly Promise<void>[]): Promise
|
|
|
407
409
|
throw new AggregateError(failures, "OMP session initialization cleanup failed");
|
|
408
410
|
}
|
|
409
411
|
}
|
|
410
|
-
function isSafeCommandName(name: string): boolean {
|
|
411
|
-
return /^[A-Za-z0-9][A-Za-z0-9._-]*(?::[A-Za-z0-9][A-Za-z0-9._-]*)*$/u.test(name);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
type OmpPromptPayload = { text: string; images: OmpImage[]; commandName?: string };
|
|
415
|
-
|
|
416
|
-
const REVIEW_LINE_MARKERS = { add: "+", remove: "-", context: " " } as const;
|
|
417
|
-
|
|
418
|
-
function renderPromptAttachmentAsText(part: Exclude<ProviderContent, { type: "image" }>): string {
|
|
419
|
-
switch (part.type) {
|
|
420
|
-
case "forge_change_request": {
|
|
421
|
-
return renderChangeRequestAttachment({
|
|
422
|
-
forge: part.forge ?? "github",
|
|
423
|
-
number: part.number,
|
|
424
|
-
title: part.title,
|
|
425
|
-
url: part.url,
|
|
426
|
-
body: part.body,
|
|
427
|
-
projectPath: part.projectPath,
|
|
428
|
-
baseRefName: part.baseRefName,
|
|
429
|
-
headRefName: part.headRefName,
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
case "github_pr": {
|
|
433
|
-
return renderChangeRequestAttachment({
|
|
434
|
-
forge: "github",
|
|
435
|
-
number: part.number,
|
|
436
|
-
title: part.title,
|
|
437
|
-
url: part.url,
|
|
438
|
-
body: part.body,
|
|
439
|
-
baseRefName: part.baseRefName,
|
|
440
|
-
headRefName: part.headRefName,
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
case "forge_issue": {
|
|
444
|
-
return renderIssueAttachment({
|
|
445
|
-
forge: part.forge ?? "github",
|
|
446
|
-
number: part.number,
|
|
447
|
-
title: part.title,
|
|
448
|
-
url: part.url,
|
|
449
|
-
body: part.body,
|
|
450
|
-
projectPath: part.projectPath,
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
case "github_issue": {
|
|
454
|
-
return renderIssueAttachment({
|
|
455
|
-
forge: "github",
|
|
456
|
-
number: part.number,
|
|
457
|
-
title: part.title,
|
|
458
|
-
url: part.url,
|
|
459
|
-
body: part.body,
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
case "text": {
|
|
463
|
-
return part.text;
|
|
464
|
-
}
|
|
465
|
-
case "review": {
|
|
466
|
-
const lines = [`Paseo review attachment (${part.mode})`, `CWD: ${part.cwd}`];
|
|
467
|
-
if (part.baseRef) {
|
|
468
|
-
lines.push(`Base: ${part.baseRef}`);
|
|
469
|
-
}
|
|
470
|
-
part.comments.forEach((comment, index) => {
|
|
471
|
-
lines.push(
|
|
472
|
-
"",
|
|
473
|
-
`Comment ${index + 1}: ${comment.filePath}:${comment.side}:${comment.lineNumber}`,
|
|
474
|
-
comment.body,
|
|
475
|
-
comment.context.hunkHeader,
|
|
476
|
-
);
|
|
477
|
-
const target = comment.context.targetLine;
|
|
478
|
-
for (const line of comment.context.lines) {
|
|
479
|
-
const isTarget =
|
|
480
|
-
line.oldLineNumber === target.oldLineNumber &&
|
|
481
|
-
line.newLineNumber === target.newLineNumber &&
|
|
482
|
-
line.type === target.type &&
|
|
483
|
-
line.content === target.content;
|
|
484
|
-
const prefix = isTarget ? "> " : " ";
|
|
485
|
-
const oldLn = padLineNumber(line.oldLineNumber);
|
|
486
|
-
const newLn = padLineNumber(line.newLineNumber);
|
|
487
|
-
lines.push(`${prefix}${oldLn} ${newLn} ${REVIEW_LINE_MARKERS[line.type]}${line.content}`);
|
|
488
|
-
}
|
|
489
|
-
});
|
|
490
|
-
return lines.join("\n");
|
|
491
|
-
}
|
|
492
|
-
case "uploaded_file": {
|
|
493
|
-
return [
|
|
494
|
-
`Uploaded file: ${part.fileName}`,
|
|
495
|
-
`Path: ${part.path}`,
|
|
496
|
-
`MIME: ${part.mimeType}`,
|
|
497
|
-
`Size: ${part.size} bytes`,
|
|
498
|
-
].join("\n");
|
|
499
|
-
}
|
|
500
|
-
default:
|
|
501
|
-
throw new Error("unreachable");
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function renderChangeRequestAttachment(input: {
|
|
506
|
-
forge: string;
|
|
507
|
-
number: number;
|
|
508
|
-
title: string;
|
|
509
|
-
url: string;
|
|
510
|
-
body?: string | null;
|
|
511
|
-
projectPath?: string;
|
|
512
|
-
baseRefName?: string | null;
|
|
513
|
-
headRefName?: string | null;
|
|
514
|
-
}): string {
|
|
515
|
-
const lines = [
|
|
516
|
-
`${formatForgeLabel(input.forge)} ${formatChangeRequestAbbrev(input.forge)} ${formatChangeRequestNumber(input.forge, input.number)}: ${input.title}`,
|
|
517
|
-
input.url,
|
|
518
|
-
];
|
|
519
|
-
if (input.projectPath) {
|
|
520
|
-
lines.push(`Project: ${input.projectPath}`);
|
|
521
|
-
}
|
|
522
|
-
if (input.baseRefName) {
|
|
523
|
-
lines.push(`Base: ${input.baseRefName}`);
|
|
524
|
-
}
|
|
525
|
-
if (input.headRefName) {
|
|
526
|
-
lines.push(`Head: ${input.headRefName}`);
|
|
527
|
-
}
|
|
528
|
-
if (input.body) {
|
|
529
|
-
lines.push("", input.body);
|
|
530
|
-
}
|
|
531
|
-
return lines.join("\n");
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function renderIssueAttachment(input: {
|
|
535
|
-
forge: string;
|
|
536
|
-
number: number;
|
|
537
|
-
title: string;
|
|
538
|
-
url: string;
|
|
539
|
-
body?: string | null;
|
|
540
|
-
projectPath?: string;
|
|
541
|
-
}): string {
|
|
542
|
-
const lines = [
|
|
543
|
-
`${formatForgeLabel(input.forge)} Issue ${formatIssueNumber(input.forge, input.number)}: ${input.title}`,
|
|
544
|
-
input.url,
|
|
545
|
-
];
|
|
546
|
-
if (input.projectPath) {
|
|
547
|
-
lines.push(`Project: ${input.projectPath}`);
|
|
548
|
-
}
|
|
549
|
-
if (input.body) {
|
|
550
|
-
lines.push("", input.body);
|
|
551
|
-
}
|
|
552
|
-
return lines.join("\n");
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function formatForgeLabel(forge: string): string {
|
|
556
|
-
return getForgeDefinitionOrNeutral(forge).displayName;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function formatChangeRequestAbbrev(forge: string): string {
|
|
560
|
-
return getForgeDefinitionOrNeutral(forge).changeRequestAbbrev;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
function formatChangeRequestNumber(forge: string, number: number): string {
|
|
564
|
-
return `${getForgeDefinitionOrNeutral(forge).changeRequestNumberPrefix}${number}`;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
function formatIssueNumber(forge: string, number: number): string {
|
|
568
|
-
return `${getForgeDefinitionOrNeutral(forge).issueNumberPrefix}${number}`;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
function padLineNumber(lineNumber: number | null): string {
|
|
572
|
-
return (lineNumber?.toString() ?? "-").padStart(2);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function promptPayload(input: SessionPromptInput): OmpPromptPayload {
|
|
576
|
-
if (input.prompt.outputSchema !== undefined || input.prompt.clearPendingPermissions) {
|
|
577
|
-
throw new OmpPublicError("OMP does not support structured output or permission controls");
|
|
578
|
-
}
|
|
579
|
-
if (input.prompt.input.type === "command") {
|
|
580
|
-
const name = input.prompt.input.name.trim();
|
|
581
|
-
if (!isSafeCommandName(name)) throw new OmpPublicError("Invalid OMP command name");
|
|
582
|
-
const argumentsText = input.prompt.input.arguments.trim();
|
|
583
|
-
const text = `/${name}${argumentsText ? ` ${argumentsText}` : ""}`;
|
|
584
|
-
if (utf8Bytes(text) > MAX_PROMPT_TEXT_LENGTH)
|
|
585
|
-
throw new OmpPublicError("OMP command is too large");
|
|
586
|
-
return { text, images: [], commandName: name };
|
|
587
|
-
}
|
|
588
|
-
if (input.prompt.input.content.length > MAX_PROMPT_PARTS) {
|
|
589
|
-
throw new OmpPublicError("OMP prompt has too many content parts");
|
|
590
|
-
}
|
|
591
|
-
const parts: string[] = [];
|
|
592
|
-
const images: OmpImage[] = [];
|
|
593
|
-
let length = 0;
|
|
594
|
-
const appendText = (text: string) => {
|
|
595
|
-
length += utf8Bytes(text) + (parts.length > 0 ? 2 : 0);
|
|
596
|
-
if (length > MAX_PROMPT_TEXT_LENGTH) throw new OmpPublicError("OMP prompt is too large");
|
|
597
|
-
parts.push(text);
|
|
598
|
-
};
|
|
599
|
-
for (const part of input.prompt.input.content) {
|
|
600
|
-
if (part.type === "text") {
|
|
601
|
-
appendText(part.text);
|
|
602
|
-
continue;
|
|
603
|
-
}
|
|
604
|
-
if (part.type === "image") {
|
|
605
|
-
if (!isValidImagePayload(part.data, part.mimeType, 8 * 1024 * 1024)) {
|
|
606
|
-
throw new OmpPublicError("OMP prompt image is invalid");
|
|
607
|
-
}
|
|
608
|
-
images.push({ type: "image", data: part.data, mimeType: part.mimeType });
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
appendText(renderPromptAttachmentAsText(part));
|
|
612
|
-
}
|
|
613
|
-
const text = parts.join("\n\n").trim();
|
|
614
|
-
if (!text && images.length === 0) throw new OmpPublicError("OMP prompt cannot be empty");
|
|
615
|
-
return { text, images };
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
function inlinePromptFrameBytes(payload: OmpPromptPayload, delivery: "prompt" | "steer"): number {
|
|
619
|
-
const images = payload.images.map(({ type, mimeType }) => ({ type, data: "", mimeType }));
|
|
620
|
-
const frame = {
|
|
621
|
-
type: delivery,
|
|
622
|
-
message: payload.text,
|
|
623
|
-
...(images.length > 0 ? { images } : {}),
|
|
624
|
-
...(delivery === "prompt" ? { id: "" } : {}),
|
|
625
|
-
};
|
|
626
|
-
const requestIdBytes = delivery === "prompt" ? RPC_REQUEST_ID_BYTES : 0;
|
|
627
|
-
return (
|
|
628
|
-
utf8Bytes(JSON.stringify(frame)) +
|
|
629
|
-
requestIdBytes +
|
|
630
|
-
payload.images.reduce((total, image) => total + image.data.length, 0) +
|
|
631
|
-
1
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
function slashCommandName(text: string): string | undefined {
|
|
636
|
-
if (!text.startsWith("/")) return undefined;
|
|
637
|
-
const body = text.slice(1);
|
|
638
|
-
if (!body) return undefined;
|
|
639
|
-
const firstWhitespace = body.search(/\s/u);
|
|
640
|
-
const name = firstWhitespace === -1 ? body : body.slice(0, firstWhitespace);
|
|
641
|
-
return name || undefined;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
412
|
function nativeEntryId(message: OmpMessage): string | undefined {
|
|
645
413
|
return message.entryId;
|
|
646
414
|
}
|
|
@@ -1685,7 +1453,7 @@ export class OmpProviderSession {
|
|
|
1685
1453
|
});
|
|
1686
1454
|
}
|
|
1687
1455
|
}
|
|
1688
|
-
messages ??= await waitForReplay(this.runtime.getMessages(), replay.signal);
|
|
1456
|
+
messages ??= await waitForReplay(this.runtime.getMessages(replay.signal), replay.signal);
|
|
1689
1457
|
this.quarantineBranchEntries();
|
|
1690
1458
|
for (const message of messages) {
|
|
1691
1459
|
replay.signal.throwIfAborted();
|
|
@@ -3234,6 +3002,17 @@ export class OmpProviderSession {
|
|
|
3234
3002
|
this.handleRuntimeFailure();
|
|
3235
3003
|
return;
|
|
3236
3004
|
}
|
|
3005
|
+
if (
|
|
3006
|
+
event.type === "config_warnings_changed" ||
|
|
3007
|
+
event.type === "advisor_cost_changed" ||
|
|
3008
|
+
event.type === "ttsr_triggered"
|
|
3009
|
+
) {
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
if (event.type === "irc_message") {
|
|
3013
|
+
this.projector.projectPassive(event);
|
|
3014
|
+
return;
|
|
3015
|
+
}
|
|
3237
3016
|
if (
|
|
3238
3017
|
event.type === "notice" ||
|
|
3239
3018
|
event.type === "todo_reminder" ||
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { basename, extname } from "node:path";
|
|
2
|
+
import { basename, extname, isAbsolute } from "node:path";
|
|
3
3
|
import type { ProviderEvent } from "@getpaseo/plugin/server/provider";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import type {
|
|
@@ -882,10 +882,32 @@ export class OmpSubsessionProjector {
|
|
|
882
882
|
signal.throwIfAborted();
|
|
883
883
|
if (this.sessionIdByNativeId.has(snapshot.id)) continue;
|
|
884
884
|
try {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
885
|
+
let history: ReplayHistory;
|
|
886
|
+
if (snapshot.sessionFile && isAbsolute(snapshot.sessionFile) && this.rootSessionFile) {
|
|
887
|
+
try {
|
|
888
|
+
history = await waitForReplay(
|
|
889
|
+
runtime.readPersistedSubagentTranscript({
|
|
890
|
+
parentSessionFile: this.rootSessionFile,
|
|
891
|
+
childTranscriptId: snapshot.id,
|
|
892
|
+
sessionFile: snapshot.sessionFile,
|
|
893
|
+
cwd: this.cwd,
|
|
894
|
+
signal,
|
|
895
|
+
}),
|
|
896
|
+
signal,
|
|
897
|
+
);
|
|
898
|
+
} catch (error) {
|
|
899
|
+
if (signal.aborted) throw error;
|
|
900
|
+
history = await waitForReplay(
|
|
901
|
+
runtimeSession.getSubagentMessages({ subagentId: snapshot.id }),
|
|
902
|
+
signal,
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
} else {
|
|
906
|
+
history = await waitForReplay(
|
|
907
|
+
runtimeSession.getSubagentMessages({ subagentId: snapshot.id }),
|
|
908
|
+
signal,
|
|
909
|
+
);
|
|
910
|
+
}
|
|
889
911
|
try {
|
|
890
912
|
this.accountReplay(history.messages, budget, signal);
|
|
891
913
|
collected.push({
|
|
@@ -899,7 +921,7 @@ export class OmpSubsessionProjector {
|
|
|
899
921
|
}
|
|
900
922
|
} catch (error) {
|
|
901
923
|
if (signal.aborted) throw error;
|
|
902
|
-
collected.push({ snapshot });
|
|
924
|
+
collected.push({ snapshot, sessionFile: snapshot.sessionFile });
|
|
903
925
|
}
|
|
904
926
|
}
|
|
905
927
|
collected.sort((left, right) => {
|
|
@@ -6,7 +6,12 @@ import type {
|
|
|
6
6
|
} from "@getpaseo/plugin/server/provider";
|
|
7
7
|
import { OMP_MCP_AUTH_TIMELINE_KIND } from "../../shared/mcp";
|
|
8
8
|
import { isOmpImageMimeType, isValidImagePayload, type OmpImageMimeType } from "./image";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
OMP_MAX_CONTENT_PARTS,
|
|
11
|
+
OMP_MESSAGE_REPLAY_POLICIES,
|
|
12
|
+
type OmpMessage,
|
|
13
|
+
type OmpRpcEvent,
|
|
14
|
+
} from "./omp-rpc";
|
|
10
15
|
import {
|
|
11
16
|
boundedJsonBytes,
|
|
12
17
|
type JsonValue,
|
|
@@ -134,6 +139,10 @@ function imageBlock(data: string, mimeType: string): StreamBlockSnapshot | undef
|
|
|
134
139
|
}
|
|
135
140
|
|
|
136
141
|
type OmpAssistantMessage = Extract<OmpMessage, { role: "assistant" }>;
|
|
142
|
+
type OmpCustomDisplayMessage = Extract<
|
|
143
|
+
OmpMessage,
|
|
144
|
+
{ role: "bashExecution" | "pythonExecution" | "custom" | "hookMessage" }
|
|
145
|
+
>;
|
|
137
146
|
|
|
138
147
|
function blockText(
|
|
139
148
|
message: OmpAssistantMessage,
|
|
@@ -417,16 +426,35 @@ export class OmpTimelineProjector {
|
|
|
417
426
|
return;
|
|
418
427
|
}
|
|
419
428
|
}
|
|
429
|
+
if (
|
|
430
|
+
(event.type === "message_start" ||
|
|
431
|
+
event.type === "message_update" ||
|
|
432
|
+
event.type === "message_end") &&
|
|
433
|
+
OMP_MESSAGE_REPLAY_POLICIES[event.message.role] === "ignore"
|
|
434
|
+
) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
420
437
|
if (
|
|
421
438
|
event.type === "todo_reminder" ||
|
|
422
439
|
event.type === "todo_auto_clear" ||
|
|
440
|
+
event.type === "goal_updated" ||
|
|
441
|
+
event.type === "auto_retry_start" ||
|
|
442
|
+
event.type === "auto_retry_end" ||
|
|
443
|
+
event.type === "retry_fallback_applied" ||
|
|
444
|
+
event.type === "retry_fallback_succeeded" ||
|
|
423
445
|
event.type === "notice" ||
|
|
424
446
|
event.type === "extension_ui_request" ||
|
|
425
447
|
event.type === "auto_compaction_start" ||
|
|
426
448
|
event.type === "auto_compaction_end" ||
|
|
427
449
|
event.type === "compaction_start" ||
|
|
428
450
|
event.type === "compaction_end" ||
|
|
429
|
-
event.type === "advisor_yielded"
|
|
451
|
+
event.type === "advisor_yielded" ||
|
|
452
|
+
event.type === "config_warnings_changed" ||
|
|
453
|
+
event.type === "advisor_cost_changed" ||
|
|
454
|
+
event.type === "ttsr_triggered" ||
|
|
455
|
+
event.type === "irc_message" ||
|
|
456
|
+
event.type === "model_changed" ||
|
|
457
|
+
event.type === "thinking_level_changed"
|
|
430
458
|
) {
|
|
431
459
|
this.projectPassive(event);
|
|
432
460
|
return;
|
|
@@ -449,7 +477,12 @@ export class OmpTimelineProjector {
|
|
|
449
477
|
this.scheduleFlush();
|
|
450
478
|
return;
|
|
451
479
|
case "message_end":
|
|
452
|
-
if (
|
|
480
|
+
if (
|
|
481
|
+
event.message.role === "custom" ||
|
|
482
|
+
event.message.role === "hookMessage" ||
|
|
483
|
+
event.message.role === "bashExecution" ||
|
|
484
|
+
event.message.role === "pythonExecution"
|
|
485
|
+
) {
|
|
453
486
|
this.publishCustomMessage(event.message);
|
|
454
487
|
return;
|
|
455
488
|
}
|
|
@@ -499,12 +532,13 @@ export class OmpTimelineProjector {
|
|
|
499
532
|
if (!snapshot.silent) this.publishTool(snapshot, "running");
|
|
500
533
|
return;
|
|
501
534
|
}
|
|
502
|
-
case "tool_execution_update":
|
|
535
|
+
case "tool_execution_update":
|
|
536
|
+
case "tool_stream_update": {
|
|
503
537
|
const previous = this.tools.get(event.toolCallId);
|
|
504
538
|
if (!previous) return;
|
|
505
539
|
if (previous.turnId !== turnId || previous.generation !== this.runtimeGeneration) return;
|
|
506
540
|
const output = this.dataFilter.json(
|
|
507
|
-
event.partialResult,
|
|
541
|
+
event.type === "tool_execution_update" ? event.partialResult : event.update,
|
|
508
542
|
MAX_PUBLIC_TOOL_PAYLOAD_BYTES,
|
|
509
543
|
MAX_PUBLIC_TOOL_PAYLOAD_BYTES,
|
|
510
544
|
);
|
|
@@ -600,6 +634,10 @@ export class OmpTimelineProjector {
|
|
|
600
634
|
this.publishRetryFallback(event);
|
|
601
635
|
return;
|
|
602
636
|
}
|
|
637
|
+
if (event.type === "irc_message") {
|
|
638
|
+
this.publishCustomMessage(event.message as OmpCustomDisplayMessage);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
603
641
|
if (event.type === "notice") {
|
|
604
642
|
this.noticeSequence += 1;
|
|
605
643
|
this.publish({
|
|
@@ -839,8 +877,9 @@ export class OmpTimelineProjector {
|
|
|
839
877
|
|
|
840
878
|
projectReplayMessage(message: OmpMessage): void {
|
|
841
879
|
if (this.closed) return;
|
|
842
|
-
const nativeIdentity = assistantIdentity(message);
|
|
843
880
|
this.replaySequence += 1;
|
|
881
|
+
if (OMP_MESSAGE_REPLAY_POLICIES[message.role] === "ignore") return;
|
|
882
|
+
|
|
844
883
|
if (message.role === "user") {
|
|
845
884
|
this.replayToolCalls.clear();
|
|
846
885
|
if (this.replayTurnId) this.finishTurn(this.replayTurnId);
|
|
@@ -861,7 +900,9 @@ export class OmpTimelineProjector {
|
|
|
861
900
|
}
|
|
862
901
|
return;
|
|
863
902
|
}
|
|
903
|
+
|
|
864
904
|
if (message.role === "assistant") {
|
|
905
|
+
const nativeIdentity = assistantIdentity(message);
|
|
865
906
|
this.replayTurnId ??= `omp:replay-turn:${this.replaySequence}`;
|
|
866
907
|
this.projectingReplay = true;
|
|
867
908
|
try {
|
|
@@ -880,6 +921,7 @@ export class OmpTimelineProjector {
|
|
|
880
921
|
if (nativeIdentity) this.rememberReplayOccurrence(nativeIdentity, message);
|
|
881
922
|
return;
|
|
882
923
|
}
|
|
924
|
+
|
|
883
925
|
this.replayTurnId ??= `omp:replay-turn:${this.replaySequence}`;
|
|
884
926
|
if (message.role === "toolResult") {
|
|
885
927
|
const replayCall = this.replayToolCalls.get(message.toolCallId);
|
|
@@ -910,10 +952,18 @@ export class OmpTimelineProjector {
|
|
|
910
952
|
);
|
|
911
953
|
return;
|
|
912
954
|
}
|
|
913
|
-
|
|
955
|
+
|
|
956
|
+
if (
|
|
957
|
+
message.role === "bashExecution" ||
|
|
958
|
+
message.role === "pythonExecution" ||
|
|
959
|
+
message.role === "custom" ||
|
|
960
|
+
message.role === "hookMessage"
|
|
961
|
+
) {
|
|
914
962
|
this.publishCustomMessage(message);
|
|
915
|
-
|
|
916
|
-
|
|
963
|
+
if (message.role === "bashExecution" || message.role === "pythonExecution") {
|
|
964
|
+
this.finishTurn(this.replayTurnId);
|
|
965
|
+
this.replayTurnId = null;
|
|
966
|
+
}
|
|
917
967
|
}
|
|
918
968
|
}
|
|
919
969
|
|
|
@@ -1350,9 +1400,17 @@ export class OmpTimelineProjector {
|
|
|
1350
1400
|
return undefined;
|
|
1351
1401
|
}
|
|
1352
1402
|
|
|
1353
|
-
private publishCustomMessage(message:
|
|
1354
|
-
if (
|
|
1355
|
-
|
|
1403
|
+
private publishCustomMessage(message: OmpCustomDisplayMessage): void {
|
|
1404
|
+
if (
|
|
1405
|
+
(message.role === "custom" || message.role === "hookMessage") &&
|
|
1406
|
+
message.display === false
|
|
1407
|
+
) {
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
const rawType =
|
|
1411
|
+
message.role === "custom" || message.role === "hookMessage"
|
|
1412
|
+
? (message.customType ?? message.role)
|
|
1413
|
+
: message.role;
|
|
1356
1414
|
const publicType = this.dataFilter.text(rawType, 256);
|
|
1357
1415
|
const lowerType = rawType.toLowerCase();
|
|
1358
1416
|
const details = jsonRecord(this.dataFilter.json(message.details ?? null));
|
|
@@ -1361,7 +1419,9 @@ export class OmpTimelineProjector {
|
|
|
1361
1419
|
const id = nativeIdentity
|
|
1362
1420
|
? `omp:custom:${createHash("sha256").update(nativeIdentity).digest("base64url").slice(0, 12)}`
|
|
1363
1421
|
: `omp:custom:${this.customSequence}`;
|
|
1364
|
-
const
|
|
1422
|
+
const messageContent =
|
|
1423
|
+
message.role === "custom" || message.role === "hookMessage" ? message.content : undefined;
|
|
1424
|
+
const contentParts = Array.isArray(messageContent) ? messageContent : [];
|
|
1365
1425
|
const imageResult = nativeImageResult(
|
|
1366
1426
|
{
|
|
1367
1427
|
content:
|
|
@@ -1373,16 +1433,26 @@ export class OmpTimelineProjector {
|
|
|
1373
1433
|
this.dataFilter,
|
|
1374
1434
|
);
|
|
1375
1435
|
const content =
|
|
1376
|
-
typeof
|
|
1377
|
-
?
|
|
1436
|
+
typeof messageContent === "string"
|
|
1437
|
+
? messageContent
|
|
1378
1438
|
: contentParts
|
|
1379
1439
|
.flatMap((part) => (part.type === "text" && part.text ? [part.text] : []))
|
|
1380
1440
|
.join("\n\n");
|
|
1381
|
-
|
|
1441
|
+
const execution = message.role === "bashExecution" || message.role === "pythonExecution";
|
|
1442
|
+
if (execution || /bash|shell|python/u.test(lowerType)) {
|
|
1443
|
+
const nativeCommand =
|
|
1444
|
+
message.role === "bashExecution"
|
|
1445
|
+
? message.command
|
|
1446
|
+
: message.role === "pythonExecution"
|
|
1447
|
+
? message.code
|
|
1448
|
+
: undefined;
|
|
1382
1449
|
const command = this.dataFilter.text(
|
|
1383
|
-
|
|
1450
|
+
nativeCommand ?? firstString(details, "command", "input") ?? publicType,
|
|
1384
1451
|
);
|
|
1385
|
-
const output = message.output ?? content;
|
|
1452
|
+
const output = execution ? (message.output ?? content) : content;
|
|
1453
|
+
const exitCode = execution ? message.exitCode : undefined;
|
|
1454
|
+
const cancelled = execution ? message.cancelled : undefined;
|
|
1455
|
+
const truncated = execution ? message.truncated : undefined;
|
|
1386
1456
|
this.publish({
|
|
1387
1457
|
type: "tool_call",
|
|
1388
1458
|
id,
|
|
@@ -1393,21 +1463,21 @@ export class OmpTimelineProjector {
|
|
|
1393
1463
|
command,
|
|
1394
1464
|
...(firstString(details, "cwd") ? { cwd: firstString(details, "cwd") } : {}),
|
|
1395
1465
|
...(output ? { output: this.dataFilter.text(output, MAX_STREAM_TEXT_LENGTH) } : {}),
|
|
1396
|
-
...(typeof
|
|
1397
|
-
? { exitCode
|
|
1466
|
+
...(typeof exitCode === "number" || exitCode === null
|
|
1467
|
+
? { exitCode }
|
|
1398
1468
|
: typeof details?.exitCode === "number"
|
|
1399
1469
|
? { exitCode: details.exitCode }
|
|
1400
1470
|
: {}),
|
|
1401
1471
|
},
|
|
1402
|
-
...(
|
|
1472
|
+
...(cancelled !== undefined || truncated !== undefined
|
|
1403
1473
|
? {
|
|
1404
1474
|
metadata: {
|
|
1405
|
-
...(
|
|
1406
|
-
...(
|
|
1475
|
+
...(cancelled !== undefined ? { cancelled } : {}),
|
|
1476
|
+
...(truncated !== undefined ? { truncated } : {}),
|
|
1407
1477
|
},
|
|
1408
1478
|
}
|
|
1409
1479
|
: {}),
|
|
1410
|
-
status:
|
|
1480
|
+
status: cancelled ? "canceled" : "completed",
|
|
1411
1481
|
error: null,
|
|
1412
1482
|
});
|
|
1413
1483
|
if (imageResult) this.publishImages(id, publicType, imageResult.image);
|