@omercnet/paseo-omp 2.0.0 → 2.0.1-next.255.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/npm-shrinkwrap.json +2 -2
- package/package.json +4 -4
- package/server/hub.ts +18 -9
- package/server/operational-failure-diagnostics.ts +1 -0
- package/server/package-version.ts +1 -1
- package/server/provider/host-tools.ts +160 -71
- package/server/provider/omp-rpc-protocol.ts +32 -0
- package/server/provider/session.ts +27 -2
- package/server/provider/timeline-projector.ts +3 -0
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-omp",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@omercnet/paseo-omp",
|
|
9
|
-
"version": "2.0.
|
|
9
|
+
"version": "2.0.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@getpaseo/protocol": "0.11.1",
|
|
12
12
|
"@modelcontextprotocol/sdk": "1.30.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-omp",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1-next.255.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -63,16 +63,16 @@
|
|
|
63
63
|
"@getpaseo/client": "0.11.1",
|
|
64
64
|
"@getpaseo/plugin": "0.11.1",
|
|
65
65
|
"@getpaseo/server": "0.11.1",
|
|
66
|
-
"@oh-my-pi/pi-agent-core": "18.
|
|
67
|
-
"@oh-my-pi/pi-coding-agent": "18.
|
|
66
|
+
"@oh-my-pi/pi-agent-core": "18.8.4",
|
|
67
|
+
"@oh-my-pi/pi-coding-agent": "18.8.4",
|
|
68
68
|
"@tanstack/react-query": "^5.102.3",
|
|
69
69
|
"@types/node": "^24.10.1",
|
|
70
70
|
"@types/react": "^19.2.18",
|
|
71
71
|
"@vitest/coverage-v8": "^5.0.0",
|
|
72
72
|
"esbuild": "0.28.2",
|
|
73
|
+
"pino": "10.3.1",
|
|
73
74
|
"react": "19.1.0",
|
|
74
75
|
"react-native": "0.81.5",
|
|
75
|
-
"pino": "10.3.1",
|
|
76
76
|
"tsx": "^4.20.6",
|
|
77
77
|
"typescript": "^7.0.2",
|
|
78
78
|
"vitest": "^5.0.0",
|
package/server/hub.ts
CHANGED
|
@@ -8,6 +8,13 @@ import type { HubProcess, listHubProcesses, tailHubLog } from "../shared/hub";
|
|
|
8
8
|
const MAX_LOG_BYTES = 64 * 1024;
|
|
9
9
|
|
|
10
10
|
const ScopeFileSchema = z.object({ projectDir: z.string() });
|
|
11
|
+
// omp 18.8 moved the spec from meta.json to a sibling spec.json; older releases embed it in meta.json.
|
|
12
|
+
const SpecSchema = z.object({
|
|
13
|
+
application: z.string(),
|
|
14
|
+
args: z.array(z.string()).optional(),
|
|
15
|
+
cwd: z.string().optional(),
|
|
16
|
+
});
|
|
17
|
+
|
|
11
18
|
const MetaFileSchema = z.object({
|
|
12
19
|
daemon: z.object({
|
|
13
20
|
state: z.string(),
|
|
@@ -21,11 +28,7 @@ const MetaFileSchema = z.object({
|
|
|
21
28
|
exitedAt: z.number().optional(),
|
|
22
29
|
exitCode: z.number().optional(),
|
|
23
30
|
}),
|
|
24
|
-
spec:
|
|
25
|
-
application: z.string(),
|
|
26
|
-
args: z.array(z.string()).optional(),
|
|
27
|
-
cwd: z.string().optional(),
|
|
28
|
-
}),
|
|
31
|
+
spec: SpecSchema.optional(),
|
|
29
32
|
});
|
|
30
33
|
|
|
31
34
|
/**
|
|
@@ -71,10 +74,12 @@ async function findProjectDaemonRoots(root: string, cwd: string): Promise<string
|
|
|
71
74
|
return matches;
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
function toHubProcess(name: string, value: unknown): HubProcess | undefined {
|
|
77
|
+
function toHubProcess(name: string, value: unknown, specFile: unknown): HubProcess | undefined {
|
|
75
78
|
const parsed = MetaFileSchema.safeParse(value);
|
|
76
79
|
if (!parsed.success) return undefined;
|
|
77
|
-
const { daemon
|
|
80
|
+
const { daemon } = parsed.data;
|
|
81
|
+
const spec = parsed.data.spec ?? SpecSchema.safeParse(specFile).data;
|
|
82
|
+
if (!spec) return undefined;
|
|
78
83
|
return {
|
|
79
84
|
name,
|
|
80
85
|
application: spec.application,
|
|
@@ -101,8 +106,12 @@ export async function listHubProcessesFrom(root: string, cwd: string): Promise<H
|
|
|
101
106
|
const names = await listDirNames(join(projectRoot, "daemons"));
|
|
102
107
|
await Promise.all(
|
|
103
108
|
names.map(async (name) => {
|
|
104
|
-
const
|
|
105
|
-
const
|
|
109
|
+
const dir = join(projectRoot, "daemons", name);
|
|
110
|
+
const [meta, spec] = await Promise.all([
|
|
111
|
+
readJsonFile(join(dir, "meta.json")),
|
|
112
|
+
readJsonFile(join(dir, "spec.json")),
|
|
113
|
+
]);
|
|
114
|
+
const process = toHubProcess(name, meta, spec);
|
|
106
115
|
if (process) processes.push(process);
|
|
107
116
|
}),
|
|
108
117
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const OMP_OPERATIONAL_FAILURES = [
|
|
2
2
|
{ category: "session-open", stage: "startup" },
|
|
3
3
|
{ category: "session-open", stage: "catalog" },
|
|
4
|
+
{ category: "session-open", stage: "optional-mcp" },
|
|
4
5
|
{ category: "replay-recovery", stage: "persisted-replay" },
|
|
5
6
|
{ category: "replay-recovery", stage: "runtime-recovery" },
|
|
6
7
|
{ category: "replay-recovery", stage: "rewind" },
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Release Please keeps this bundle-safe constant aligned with package.json. */
|
|
2
|
-
export const PASEO_OMP_PACKAGE_VERSION = "2.0.
|
|
2
|
+
export const PASEO_OMP_PACKAGE_VERSION = "2.0.1-next.255.1"; // x-release-please-version
|
|
@@ -58,6 +58,10 @@ const RESULT_TRUNCATED_NOTICE = "[MCP content truncated; details omitted]";
|
|
|
58
58
|
const MAX_PENDING_HOST_TOOL_CALLS = 64;
|
|
59
59
|
const MAX_PENDING_HOST_TOOL_BYTES = 8 * 1024 * 1024;
|
|
60
60
|
const DEFAULT_INITIALIZATION_TIMEOUT_MS = 20_000;
|
|
61
|
+
// Cold npx-style servers can take several seconds; 10 s leaves headroom under the 20 s ceiling.
|
|
62
|
+
const OPTIONAL_MCP_SERVER_TIMEOUT_MS = 10_000;
|
|
63
|
+
const MAX_DEGRADED_NOTICE_NAMES = 5;
|
|
64
|
+
const MAX_DEGRADED_NAME_BYTES = 64;
|
|
61
65
|
const DEFAULT_MCP_CALL_LIFETIME_MS = 5 * 60 * 1000;
|
|
62
66
|
const MAX_DIRECT_BROWSER_CALLS = 4;
|
|
63
67
|
const BROWSER_CALL_TIMEOUT_MS = 20_000;
|
|
@@ -85,6 +89,8 @@ const DEFAULT_CALL_SCHEDULER: OmpHostToolScheduler = {
|
|
|
85
89
|
|
|
86
90
|
export interface OmpHostToolsOpenOptions {
|
|
87
91
|
connectMcp?: OmpMcpConnector;
|
|
92
|
+
/** Per-server cap for non-Paseo servers. Defaults to 10 s. */
|
|
93
|
+
optionalServerTimeoutMs?: number;
|
|
88
94
|
signal?: AbortSignal;
|
|
89
95
|
initializationTimeoutMs?: number;
|
|
90
96
|
callTimeoutMs?: number;
|
|
@@ -92,6 +98,33 @@ export interface OmpHostToolsOpenOptions {
|
|
|
92
98
|
reportOperationalFailure?: OmpOperationalFailureReporter;
|
|
93
99
|
}
|
|
94
100
|
|
|
101
|
+
export type OmpDegradedMcpServer = {
|
|
102
|
+
name: string;
|
|
103
|
+
reason: "unavailable" | "timed-out" | "invalid-tools";
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const DEGRADED_REASON_TEXT: Record<OmpDegradedMcpServer["reason"], string> = {
|
|
107
|
+
unavailable: "unavailable",
|
|
108
|
+
"timed-out": "timed out",
|
|
109
|
+
"invalid-tools": "invalid tool list",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Fixed reason vocabulary and bounded names only: never raw error text. */
|
|
113
|
+
export function describeDegradedMcpServers(degraded: readonly OmpDegradedMcpServer[]): string {
|
|
114
|
+
const shown = degraded.slice(0, MAX_DEGRADED_NOTICE_NAMES).map((server) => {
|
|
115
|
+
const name = boundedText(
|
|
116
|
+
server.name.replace(/[^\p{L}\p{N}_.:@/ -]/gu, ""),
|
|
117
|
+
MAX_DEGRADED_NAME_BYTES,
|
|
118
|
+
"server",
|
|
119
|
+
);
|
|
120
|
+
return `${name} (${DEGRADED_REASON_TEXT[server.reason]})`;
|
|
121
|
+
});
|
|
122
|
+
const more = degraded.length - shown.length;
|
|
123
|
+
const list = more > 0 ? `${shown.join(", ")}, and ${more} more` : shown.join(", ");
|
|
124
|
+
const noun = degraded.length === 1 ? "server was" : "servers were";
|
|
125
|
+
return `${degraded.length} MCP ${noun} skipped: ${list}. Their tools are missing from this session. Reopen the agent after fixing them.`;
|
|
126
|
+
}
|
|
127
|
+
|
|
95
128
|
type ClassifiedServer = {
|
|
96
129
|
name: string;
|
|
97
130
|
config: ProviderMcpServerConfig;
|
|
@@ -508,6 +541,7 @@ export class OmpHostToolsBridge {
|
|
|
508
541
|
private readonly callTimeoutMs: number,
|
|
509
542
|
private readonly callScheduler: OmpHostToolScheduler,
|
|
510
543
|
private readonly reportOperationalFailure: OmpOperationalFailureReporter,
|
|
544
|
+
readonly degraded: readonly OmpDegradedMcpServer[],
|
|
511
545
|
) {
|
|
512
546
|
this.labels = new Map(definitions.map(({ name, label }) => [name, label ?? name]));
|
|
513
547
|
}
|
|
@@ -535,89 +569,143 @@ export class OmpHostToolsBridge {
|
|
|
535
569
|
const connections: OmpMcpConnection[] = [];
|
|
536
570
|
const definitions: OmpHostToolDefinition[] = [];
|
|
537
571
|
const targets = new Map<string, ToolTarget>();
|
|
572
|
+
const degraded: OmpDegradedMcpServer[] = [];
|
|
573
|
+
const report = (failure: OmpOperationalFailure) => {
|
|
574
|
+
try {
|
|
575
|
+
options.reportOperationalFailure?.(failure);
|
|
576
|
+
} catch {
|
|
577
|
+
// Diagnostics must never affect session open.
|
|
578
|
+
}
|
|
579
|
+
};
|
|
538
580
|
try {
|
|
539
581
|
for (const server of servers) {
|
|
540
582
|
initialization.signal.throwIfAborted();
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
583
|
+
// Only Paseo-owned servers are required. Any other server that fails is skipped whole.
|
|
584
|
+
const optional = !server.internal;
|
|
585
|
+
const timeout = optional ? new AbortController() : undefined;
|
|
586
|
+
const timer = timeout
|
|
587
|
+
? setTimeout(
|
|
588
|
+
() => timeout.abort(),
|
|
589
|
+
options.optionalServerTimeoutMs ?? OPTIONAL_MCP_SERVER_TIMEOUT_MS,
|
|
590
|
+
)
|
|
591
|
+
: undefined;
|
|
592
|
+
const signal = timeout
|
|
593
|
+
? AbortSignal.any([initialization.signal, timeout.signal])
|
|
594
|
+
: initialization.signal;
|
|
595
|
+
const connectionCount = connections.length;
|
|
596
|
+
const definitionCount = definitions.length;
|
|
597
|
+
const added: string[] = [];
|
|
598
|
+
let reason: OmpDegradedMcpServer["reason"] = "unavailable";
|
|
548
599
|
try {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
600
|
+
const connecting = connectMcp(server.name, server.config, config.cwd, signal);
|
|
601
|
+
let connection: OmpMcpConnection;
|
|
602
|
+
try {
|
|
603
|
+
connection =
|
|
604
|
+
connectMcp === connectMcpServer
|
|
605
|
+
? await connecting
|
|
606
|
+
: await abortable(connecting, signal);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
if (connectMcp !== connectMcpServer && signal.aborted) {
|
|
609
|
+
const cleanup = connecting.then(
|
|
610
|
+
(lateConnection) => lateConnection.close(),
|
|
611
|
+
(rejection) =>
|
|
612
|
+
rejection instanceof OmpCleanupFailure ? rejection.cleanup : undefined,
|
|
613
|
+
);
|
|
614
|
+
throw new OmpCleanupFailure(
|
|
615
|
+
"OMP MCP connection initialization was interrupted",
|
|
616
|
+
cleanup,
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
throw error;
|
|
620
|
+
}
|
|
621
|
+
connections.push(connection);
|
|
622
|
+
const tools = (await discoverMcpTools(connection, signal)).sort((left, right) =>
|
|
623
|
+
left.name.localeCompare(right.name),
|
|
624
|
+
);
|
|
625
|
+
reason = "invalid-tools";
|
|
626
|
+
const namespace = namespaces.get(server.name);
|
|
627
|
+
if (!namespace) throw new Error("MCP server namespace is unavailable");
|
|
628
|
+
for (const tool of tools) {
|
|
629
|
+
if (
|
|
630
|
+
!tool.name ||
|
|
631
|
+
utf8Bytes(tool.name) > MAX_HOST_TOOL_NAME_BYTES ||
|
|
632
|
+
utf8Bytes(tool.description ?? "") > MAX_HOST_TOOL_DESCRIPTION_BYTES ||
|
|
633
|
+
!tool.inputSchema ||
|
|
634
|
+
typeof tool.inputSchema !== "object" ||
|
|
635
|
+
Array.isArray(tool.inputSchema) ||
|
|
636
|
+
boundedJsonBytes(tool.inputSchema, MAX_HOST_TOOL_SCHEMA_BYTES) ===
|
|
637
|
+
Number.POSITIVE_INFINITY
|
|
638
|
+
) {
|
|
639
|
+
throw new Error("MCP server exposed an invalid host tool definition");
|
|
640
|
+
}
|
|
641
|
+
if (definitions.length >= MAX_HOST_TOOLS) {
|
|
642
|
+
throw new Error("MCP servers exposed too many host tools");
|
|
643
|
+
}
|
|
644
|
+
let name = exposedToolName(server, namespace, tool.name);
|
|
645
|
+
if (targets.has(name)) {
|
|
646
|
+
const suffix = digest(`${server.name}\0${tool.name}`);
|
|
647
|
+
name = `${name.slice(0, MAX_HOST_TOOL_NAME_BYTES - suffix.length - 1)}_${suffix}`;
|
|
648
|
+
}
|
|
649
|
+
if (targets.has(name)) throw new Error("MCP host tool names collide");
|
|
650
|
+
const normalizedServerName = safeName(server.name, "server");
|
|
651
|
+
const normalizedToolName = safeName(tool.name, "tool");
|
|
652
|
+
const friendlyToolName = normalizedToolName.startsWith(`${normalizedServerName}_`)
|
|
653
|
+
? normalizedToolName.slice(normalizedServerName.length + 1)
|
|
654
|
+
: tool.name;
|
|
655
|
+
const fallbackLabel = `${humanizeName(server.name)}: ${humanizeName(friendlyToolName)}`;
|
|
656
|
+
definitions.push({
|
|
657
|
+
name,
|
|
658
|
+
label: boundedText(tool.title ?? fallbackLabel, MAX_HOST_TOOL_LABEL_BYTES, name),
|
|
659
|
+
description: boundedText(
|
|
660
|
+
tool.description ?? `MCP tool from ${server.name}`,
|
|
661
|
+
MAX_HOST_TOOL_DESCRIPTION_BYTES,
|
|
662
|
+
"MCP tool",
|
|
663
|
+
),
|
|
664
|
+
loadMode:
|
|
665
|
+
server.internal || server.config.alwaysLoad === true ? "essential" : "discoverable",
|
|
666
|
+
parameters: structuredClone(tool.inputSchema),
|
|
667
|
+
});
|
|
668
|
+
targets.set(name, { toolName: tool.name, connection });
|
|
669
|
+
added.push(name);
|
|
563
670
|
}
|
|
564
|
-
throw error;
|
|
565
|
-
}
|
|
566
|
-
connections.push(connection);
|
|
567
|
-
const tools = (await discoverMcpTools(connection, initialization.signal)).sort(
|
|
568
|
-
(left, right) => left.name.localeCompare(right.name),
|
|
569
|
-
);
|
|
570
|
-
const namespace = namespaces.get(server.name);
|
|
571
|
-
if (!namespace) throw new Error("MCP server namespace is unavailable");
|
|
572
|
-
for (const tool of tools) {
|
|
573
671
|
if (
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
boundedJsonBytes(tool.inputSchema, MAX_HOST_TOOL_SCHEMA_BYTES) ===
|
|
581
|
-
Number.POSITIVE_INFINITY
|
|
672
|
+
boundedJsonBytes(
|
|
673
|
+
definitions,
|
|
674
|
+
MAX_HOST_TOOL_CATALOG_BYTES,
|
|
675
|
+
MAX_HOST_TOOLS,
|
|
676
|
+
64 * 1024,
|
|
677
|
+
) === Number.POSITIVE_INFINITY
|
|
582
678
|
) {
|
|
583
|
-
throw new
|
|
584
|
-
}
|
|
585
|
-
if (definitions.length >= MAX_HOST_TOOLS) {
|
|
586
|
-
throw new Error("MCP servers exposed too many host tools");
|
|
679
|
+
throw new OmpPublicError("OMP host tool catalog exceeds the RPC frame limit");
|
|
587
680
|
}
|
|
588
|
-
|
|
589
|
-
if (
|
|
590
|
-
|
|
591
|
-
|
|
681
|
+
} catch (error) {
|
|
682
|
+
if (!optional || initialization.signal.aborted) throw error;
|
|
683
|
+
if (error instanceof OmpCleanupFailure) {
|
|
684
|
+
try {
|
|
685
|
+
await abortable(error.cleanup, initialization.signal);
|
|
686
|
+
} catch {
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
} else if (connections.length > connectionCount) {
|
|
690
|
+
const failed = connections.pop() as OmpMcpConnection;
|
|
691
|
+
const closing = Promise.resolve().then(() => failed.close());
|
|
692
|
+
try {
|
|
693
|
+
await abortable(closing, initialization.signal);
|
|
694
|
+
} catch {
|
|
695
|
+
throw new OmpCleanupFailure("OMP MCP host tool cleanup failed", closing);
|
|
696
|
+
}
|
|
592
697
|
}
|
|
593
|
-
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
: tool.name;
|
|
599
|
-
const fallbackLabel = `${humanizeName(server.name)}: ${humanizeName(friendlyToolName)}`;
|
|
600
|
-
definitions.push({
|
|
601
|
-
name,
|
|
602
|
-
label: boundedText(tool.title ?? fallbackLabel, MAX_HOST_TOOL_LABEL_BYTES, name),
|
|
603
|
-
description: boundedText(
|
|
604
|
-
tool.description ?? `MCP tool from ${server.name}`,
|
|
605
|
-
MAX_HOST_TOOL_DESCRIPTION_BYTES,
|
|
606
|
-
"MCP tool",
|
|
607
|
-
),
|
|
608
|
-
loadMode:
|
|
609
|
-
server.internal || server.config.alwaysLoad === true ? "essential" : "discoverable",
|
|
610
|
-
parameters: structuredClone(tool.inputSchema),
|
|
698
|
+
definitions.length = definitionCount;
|
|
699
|
+
for (const name of added) targets.delete(name);
|
|
700
|
+
degraded.push({
|
|
701
|
+
name: server.name,
|
|
702
|
+
reason: timeout?.signal.aborted ? "timed-out" : reason,
|
|
611
703
|
});
|
|
612
|
-
|
|
704
|
+
report({ category: "session-open", stage: "optional-mcp" });
|
|
705
|
+
} finally {
|
|
706
|
+
clearTimeout(timer);
|
|
613
707
|
}
|
|
614
708
|
}
|
|
615
|
-
if (
|
|
616
|
-
boundedJsonBytes(definitions, MAX_HOST_TOOL_CATALOG_BYTES, MAX_HOST_TOOLS, 64 * 1024) ===
|
|
617
|
-
Number.POSITIVE_INFINITY
|
|
618
|
-
) {
|
|
619
|
-
throw new OmpPublicError("OMP host tool catalog exceeds the RPC frame limit");
|
|
620
|
-
}
|
|
621
709
|
return new OmpHostToolsBridge(
|
|
622
710
|
connections,
|
|
623
711
|
definitions,
|
|
@@ -625,6 +713,7 @@ export class OmpHostToolsBridge {
|
|
|
625
713
|
callTimeoutMs,
|
|
626
714
|
callScheduler,
|
|
627
715
|
options.reportOperationalFailure ?? (() => {}),
|
|
716
|
+
degraded,
|
|
628
717
|
);
|
|
629
718
|
} catch (error) {
|
|
630
719
|
const cleanupTasks = [
|
|
@@ -80,6 +80,9 @@ export const OMP_SESSION_EVENT_POLICIES = {
|
|
|
80
80
|
advisor_cost_changed: "ignore",
|
|
81
81
|
advisor_yielded: "normalize",
|
|
82
82
|
ttsr_triggered: "ignore",
|
|
83
|
+
cache_warming_start: "ignore",
|
|
84
|
+
cache_warming_end: "ignore",
|
|
85
|
+
queue_update: "ignore",
|
|
83
86
|
todo_reminder: "normalize",
|
|
84
87
|
todo_auto_clear: "normalize",
|
|
85
88
|
irc_message: "normalize",
|
|
@@ -136,6 +139,26 @@ export const OMP_RPC_COMMAND_POLICIES = {
|
|
|
136
139
|
get_messages_page: "implemented",
|
|
137
140
|
get_login_providers: "unsupported",
|
|
138
141
|
login: "unsupported",
|
|
142
|
+
abort_and_restore_queue: "unsupported",
|
|
143
|
+
btw: "unsupported",
|
|
144
|
+
btw_cancel: "unsupported",
|
|
145
|
+
cancel_subagent: "unsupported",
|
|
146
|
+
fork: "unsupported",
|
|
147
|
+
get_btw_history: "unsupported",
|
|
148
|
+
get_logout_accounts: "unsupported",
|
|
149
|
+
goal: "unsupported",
|
|
150
|
+
live_mute: "unsupported",
|
|
151
|
+
live_start: "unsupported",
|
|
152
|
+
live_stop: "unsupported",
|
|
153
|
+
logout: "unsupported",
|
|
154
|
+
predict_word: "unsupported",
|
|
155
|
+
predict_word_feedback: "unsupported",
|
|
156
|
+
promote_queued_message: "unsupported",
|
|
157
|
+
remove_queued_message: "unsupported",
|
|
158
|
+
set_ask_dialog: "unsupported",
|
|
159
|
+
set_cache_warming: "unsupported",
|
|
160
|
+
set_slow_mode: "unsupported",
|
|
161
|
+
steer_subagent: "unsupported",
|
|
139
162
|
} as const;
|
|
140
163
|
export const OMP_RPC_RESPONSE_POLICIES = OMP_RPC_COMMAND_POLICIES;
|
|
141
164
|
export const OMP_PROTOCOL_VIOLATION_CATEGORIES = [
|
|
@@ -218,6 +241,9 @@ export const OMP_PROTOCOL_EVENT_TYPES = [
|
|
|
218
241
|
"config_warnings_changed",
|
|
219
242
|
"advisor_cost_changed",
|
|
220
243
|
"ttsr_triggered",
|
|
244
|
+
"cache_warming_start",
|
|
245
|
+
"cache_warming_end",
|
|
246
|
+
"queue_update",
|
|
221
247
|
"irc_message",
|
|
222
248
|
"thinking_level_changed",
|
|
223
249
|
"goal_updated",
|
|
@@ -374,6 +400,9 @@ function eventSchemaClassification(
|
|
|
374
400
|
case "config_warnings_changed":
|
|
375
401
|
case "advisor_cost_changed":
|
|
376
402
|
case "ttsr_triggered":
|
|
403
|
+
case "cache_warming_start":
|
|
404
|
+
case "cache_warming_end":
|
|
405
|
+
case "queue_update":
|
|
377
406
|
return { reason: "configuration-event-schema", expected: "valid-configuration-event" };
|
|
378
407
|
case "extension_ui_request":
|
|
379
408
|
return { reason: "extension-ui-schema", expected: "valid-extension-ui-event" };
|
|
@@ -1522,6 +1551,9 @@ const OmpAgentSessionEventSchema = z.discriminatedUnion("type", [
|
|
|
1522
1551
|
...OmpCoreAgentEventSchema.options,
|
|
1523
1552
|
z.object({ type: z.literal("config_warnings_changed") }),
|
|
1524
1553
|
z.object({ type: z.literal("advisor_cost_changed") }),
|
|
1554
|
+
z.object({ type: z.literal("cache_warming_start") }),
|
|
1555
|
+
z.object({ type: z.literal("cache_warming_end") }),
|
|
1556
|
+
z.object({ type: z.literal("queue_update") }),
|
|
1525
1557
|
z.object({ type: z.literal("advisor_yielded") }),
|
|
1526
1558
|
z.object({
|
|
1527
1559
|
type: z.literal("ttsr_triggered"),
|
|
@@ -24,7 +24,13 @@ import {
|
|
|
24
24
|
type OmpRecoveryOptions,
|
|
25
25
|
withCommittedOmpSelection,
|
|
26
26
|
} from "./config-normalization";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
describeDegradedMcpServers,
|
|
29
|
+
type OmpDegradedMcpServer,
|
|
30
|
+
OmpHostToolsBridge,
|
|
31
|
+
type OmpMcpConnector,
|
|
32
|
+
validateOmpHostToolConfig,
|
|
33
|
+
} from "./host-tools";
|
|
28
34
|
import { isOmpImageMimeType, OmpImageMaterializer } from "./image";
|
|
29
35
|
import type { OmpRuntime, OmpRuntimeSession } from "./omp-rpc";
|
|
30
36
|
import { buildOmpSpawnRequest, type OmpStartOptions } from "./omp-rpc-environment";
|
|
@@ -331,6 +337,7 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
331
337
|
private readonly permissions: OmpSessionPermissions;
|
|
332
338
|
private unsupportedThinkingNoticePending = false;
|
|
333
339
|
private unsupportedThinkingNoticePublished = false;
|
|
340
|
+
private degradedMcpServers: readonly OmpDegradedMcpServer[] = [];
|
|
334
341
|
|
|
335
342
|
private constructor(
|
|
336
343
|
id: string,
|
|
@@ -685,6 +692,7 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
685
692
|
scheduler,
|
|
686
693
|
);
|
|
687
694
|
session.unsupportedThinkingNoticePending = unsupportedThinkingLevel;
|
|
695
|
+
session.degradedMcpServers = hostTools.degraded;
|
|
688
696
|
|
|
689
697
|
if (bootstrapConfigRevision !== reconciledConfigRevision) {
|
|
690
698
|
session.configRefreshDirty = true;
|
|
@@ -744,6 +752,7 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
744
752
|
...(this.config.title ? { title: this.dataFilter.text(this.config.title, 256) } : {}),
|
|
745
753
|
});
|
|
746
754
|
this.publishUnsupportedThinkingNotice();
|
|
755
|
+
this.publishDegradedMcpNotice();
|
|
747
756
|
this.emit({ type: "session.config", sessionId: this.id, config: this.configState });
|
|
748
757
|
if (this.replayHistoryOnOpen) await this.replayHistory(true);
|
|
749
758
|
this.publishCommands(this.commandCatalog);
|
|
@@ -767,6 +776,19 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
767
776
|
},
|
|
768
777
|
});
|
|
769
778
|
}
|
|
779
|
+
private publishDegradedMcpNotice(): void {
|
|
780
|
+
if (this.degradedMcpServers.length === 0) return;
|
|
781
|
+
this.emit({
|
|
782
|
+
type: "session.notice",
|
|
783
|
+
sessionId: this.id,
|
|
784
|
+
notice: {
|
|
785
|
+
id: "omp:mcp-servers-unavailable",
|
|
786
|
+
severity: "warning",
|
|
787
|
+
title: "MCP servers unavailable",
|
|
788
|
+
description: describeDegradedMcpServers(this.degradedMcpServers),
|
|
789
|
+
},
|
|
790
|
+
});
|
|
791
|
+
}
|
|
770
792
|
|
|
771
793
|
private async replayHistory(preferPersistedTranscript = false): Promise<void> {
|
|
772
794
|
if (!this.runtime.canReplayHistory) {
|
|
@@ -2274,7 +2296,10 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
2274
2296
|
if (
|
|
2275
2297
|
event.type === "config_warnings_changed" ||
|
|
2276
2298
|
event.type === "advisor_cost_changed" ||
|
|
2277
|
-
event.type === "ttsr_triggered"
|
|
2299
|
+
event.type === "ttsr_triggered" ||
|
|
2300
|
+
event.type === "cache_warming_start" ||
|
|
2301
|
+
event.type === "cache_warming_end" ||
|
|
2302
|
+
event.type === "queue_update"
|
|
2278
2303
|
) {
|
|
2279
2304
|
return;
|
|
2280
2305
|
}
|
|
@@ -463,6 +463,9 @@ export class OmpTimelineProjector {
|
|
|
463
463
|
event.type === "config_warnings_changed" ||
|
|
464
464
|
event.type === "advisor_cost_changed" ||
|
|
465
465
|
event.type === "ttsr_triggered" ||
|
|
466
|
+
event.type === "cache_warming_start" ||
|
|
467
|
+
event.type === "cache_warming_end" ||
|
|
468
|
+
event.type === "queue_update" ||
|
|
466
469
|
event.type === "irc_message" ||
|
|
467
470
|
event.type === "model_changed" ||
|
|
468
471
|
event.type === "thinking_level_changed"
|