@omercnet/paseo-omp 0.3.0-next.108.1 → 0.3.0-next.111.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 +163 -14
- package/client/omp-config-views.ts +24 -0
- package/client/support-diagnostics-state.ts +45 -0
- package/index.server.ts +24 -2
- package/package.json +2 -8
- package/paseo-plugin.json +1 -1
- 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/connection.ts +25 -0
- package/server/provider/host-tools.ts +63 -10
- package/server/provider/omp-rpc.ts +469 -44
- package/server/provider/profile-providers.ts +7 -2
- package/server/provider/registration.ts +12 -2
- package/server/provider/session.ts +70 -3
- package/server/support-diagnostics.ts +284 -0
- 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/model-role-picker-compact.webp +0 -0
- package/docs/images/model-role-picker-desktop.webp +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
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OMP_PROTOCOL_VIOLATION_CATEGORIES,
|
|
3
|
+
OMP_PROTOCOL_VIOLATION_REASONS,
|
|
4
|
+
type OmpProtocolViolationCategory,
|
|
5
|
+
type OmpProtocolViolationDiagnostic,
|
|
6
|
+
type OmpProtocolViolationReason,
|
|
7
|
+
} from "./provider/omp-rpc";
|
|
8
|
+
|
|
9
|
+
export interface OmpProtocolViolationSummary {
|
|
10
|
+
category: OmpProtocolViolationCategory;
|
|
11
|
+
occurrenceCount: number;
|
|
12
|
+
batchCount: number;
|
|
13
|
+
maxOccurrenceCount: number;
|
|
14
|
+
firstAt: string | null;
|
|
15
|
+
lastAt: string | null;
|
|
16
|
+
reasonCounts: Record<OmpProtocolViolationReason, number>;
|
|
17
|
+
latestReason: OmpProtocolViolationDiagnostic["reason"] | null;
|
|
18
|
+
latestPhase: OmpProtocolViolationDiagnostic["phase"] | null;
|
|
19
|
+
latestEventType: OmpProtocolViolationDiagnostic["eventType"] | null;
|
|
20
|
+
latestFrameType: OmpProtocolViolationDiagnostic["frameType"] | null;
|
|
21
|
+
latestField: OmpProtocolViolationDiagnostic["field"] | null;
|
|
22
|
+
latestExpected: OmpProtocolViolationDiagnostic["expected"] | null;
|
|
23
|
+
latestActualType: OmpProtocolViolationDiagnostic["actualType"] | null;
|
|
24
|
+
maxByteSize: number | null;
|
|
25
|
+
latestLimitBytes: number | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type MutableSummary = OmpProtocolViolationSummary;
|
|
29
|
+
|
|
30
|
+
const FRAME_TYPES: Record<NonNullable<OmpProtocolViolationDiagnostic["frameType"]>, true> = {
|
|
31
|
+
ready: true,
|
|
32
|
+
response: true,
|
|
33
|
+
rpc_chunk: true,
|
|
34
|
+
rpc_frame_error: true,
|
|
35
|
+
notice: true,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function boundedCount(value: unknown): number {
|
|
39
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return 0;
|
|
40
|
+
return Math.min(value, Number.MAX_SAFE_INTEGER);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function boundedAdd(left: number, right: number): number {
|
|
44
|
+
return Math.min(Number.MAX_SAFE_INTEGER, left + right);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function safeTimestamp(now: () => Date): string | null {
|
|
48
|
+
try {
|
|
49
|
+
const value = now();
|
|
50
|
+
return Number.isFinite(value.getTime()) ? value.toISOString() : null;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function logProtocolViolation(message: string, diagnostic: OmpProtocolViolationDiagnostic): void {
|
|
57
|
+
const fields = [
|
|
58
|
+
`category: ${diagnostic.category}`,
|
|
59
|
+
`reason: ${diagnostic.reason}`,
|
|
60
|
+
`occurrenceCount: ${diagnostic.occurrenceCount}`,
|
|
61
|
+
`phase: ${diagnostic.phase}`,
|
|
62
|
+
...(diagnostic.eventType ? [`eventType: ${diagnostic.eventType}`] : []),
|
|
63
|
+
...(diagnostic.frameType ? [`frameType: ${diagnostic.frameType}`] : []),
|
|
64
|
+
...(diagnostic.field ? [`field: ${diagnostic.field}`] : []),
|
|
65
|
+
...(diagnostic.expected ? [`expected: ${diagnostic.expected}`] : []),
|
|
66
|
+
...(diagnostic.actualType ? [`actualType: ${diagnostic.actualType}`] : []),
|
|
67
|
+
...(diagnostic.maxByteSize ? [`maxByteSize: ${diagnostic.maxByteSize}`] : []),
|
|
68
|
+
...(diagnostic.limitBytes ? [`limitBytes: ${diagnostic.limitBytes}`] : []),
|
|
69
|
+
];
|
|
70
|
+
console.error(`${message} { ${fields.join(", ")} }`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Fixed-category, saturating in-process aggregation. A new instance is created on each reload. */
|
|
74
|
+
export class OmpProtocolViolationCollector {
|
|
75
|
+
private readonly summaries = Object.fromEntries(
|
|
76
|
+
OMP_PROTOCOL_VIOLATION_CATEGORIES.map((category) => [
|
|
77
|
+
category,
|
|
78
|
+
{
|
|
79
|
+
category,
|
|
80
|
+
occurrenceCount: 0,
|
|
81
|
+
batchCount: 0,
|
|
82
|
+
maxOccurrenceCount: 0,
|
|
83
|
+
firstAt: null,
|
|
84
|
+
lastAt: null,
|
|
85
|
+
reasonCounts: Object.fromEntries(
|
|
86
|
+
OMP_PROTOCOL_VIOLATION_REASONS.map((reason) => [reason, 0]),
|
|
87
|
+
),
|
|
88
|
+
latestReason: null,
|
|
89
|
+
latestPhase: null,
|
|
90
|
+
latestEventType: null,
|
|
91
|
+
latestFrameType: null,
|
|
92
|
+
latestField: null,
|
|
93
|
+
latestExpected: null,
|
|
94
|
+
latestActualType: null,
|
|
95
|
+
maxByteSize: null,
|
|
96
|
+
latestLimitBytes: null,
|
|
97
|
+
},
|
|
98
|
+
]),
|
|
99
|
+
) as Record<OmpProtocolViolationCategory, MutableSummary>;
|
|
100
|
+
|
|
101
|
+
constructor(
|
|
102
|
+
private readonly now: () => Date = () => new Date(),
|
|
103
|
+
private readonly log: (
|
|
104
|
+
message: string,
|
|
105
|
+
diagnostic: OmpProtocolViolationDiagnostic,
|
|
106
|
+
) => void | PromiseLike<void> = logProtocolViolation,
|
|
107
|
+
) {}
|
|
108
|
+
|
|
109
|
+
report = (diagnostic: OmpProtocolViolationDiagnostic): void => {
|
|
110
|
+
try {
|
|
111
|
+
const summary = this.summaries[diagnostic.category];
|
|
112
|
+
if (!summary) return;
|
|
113
|
+
const occurrenceCount = boundedCount(diagnostic.occurrenceCount);
|
|
114
|
+
const timestamp = safeTimestamp(this.now);
|
|
115
|
+
summary.occurrenceCount = boundedAdd(summary.occurrenceCount, occurrenceCount);
|
|
116
|
+
summary.batchCount = boundedAdd(summary.batchCount, 1);
|
|
117
|
+
summary.maxOccurrenceCount = Math.max(summary.maxOccurrenceCount, occurrenceCount);
|
|
118
|
+
if (timestamp) {
|
|
119
|
+
summary.firstAt ??= timestamp;
|
|
120
|
+
summary.lastAt = timestamp;
|
|
121
|
+
}
|
|
122
|
+
summary.reasonCounts[diagnostic.reason] = boundedAdd(
|
|
123
|
+
summary.reasonCounts[diagnostic.reason],
|
|
124
|
+
occurrenceCount,
|
|
125
|
+
);
|
|
126
|
+
summary.latestReason = diagnostic.reason;
|
|
127
|
+
summary.latestPhase = diagnostic.phase;
|
|
128
|
+
summary.latestEventType = diagnostic.eventType ?? null;
|
|
129
|
+
summary.latestFrameType =
|
|
130
|
+
diagnostic.frameType && FRAME_TYPES[diagnostic.frameType] ? diagnostic.frameType : null;
|
|
131
|
+
summary.latestField = diagnostic.field ?? null;
|
|
132
|
+
summary.latestExpected = diagnostic.expected ?? null;
|
|
133
|
+
summary.latestActualType = diagnostic.actualType ?? null;
|
|
134
|
+
const byteSize = boundedCount(diagnostic.maxByteSize);
|
|
135
|
+
if (byteSize > 0) summary.maxByteSize = Math.max(summary.maxByteSize ?? 0, byteSize);
|
|
136
|
+
const limitBytes = boundedCount(diagnostic.limitBytes);
|
|
137
|
+
summary.latestLimitBytes = limitBytes || null;
|
|
138
|
+
|
|
139
|
+
const safeDiagnostic: OmpProtocolViolationDiagnostic = {
|
|
140
|
+
category: diagnostic.category,
|
|
141
|
+
reason: diagnostic.reason,
|
|
142
|
+
phase: diagnostic.phase,
|
|
143
|
+
occurrenceCount,
|
|
144
|
+
...(summary.latestEventType ? { eventType: summary.latestEventType } : {}),
|
|
145
|
+
...(summary.latestFrameType ? { frameType: summary.latestFrameType } : {}),
|
|
146
|
+
...(summary.latestField ? { field: summary.latestField } : {}),
|
|
147
|
+
...(summary.latestExpected ? { expected: summary.latestExpected } : {}),
|
|
148
|
+
...(summary.latestActualType ? { actualType: summary.latestActualType } : {}),
|
|
149
|
+
...(byteSize > 0 ? { maxByteSize: byteSize } : {}),
|
|
150
|
+
...(limitBytes > 0 ? { limitBytes } : {}),
|
|
151
|
+
};
|
|
152
|
+
try {
|
|
153
|
+
const logging = this.log("OMP protocol violation", safeDiagnostic);
|
|
154
|
+
if (logging) void Promise.resolve(logging).catch(() => undefined);
|
|
155
|
+
} catch {
|
|
156
|
+
// Diagnostics must never affect transport or provider flow.
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
// Diagnostics must never affect transport or provider flow.
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
snapshot(): OmpProtocolViolationSummary[] {
|
|
164
|
+
return OMP_PROTOCOL_VIOLATION_CATEGORIES.map((category) => ({
|
|
165
|
+
...this.summaries[category],
|
|
166
|
+
reasonCounts: { ...this.summaries[category].reasonCounts },
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
requireProviderCapabilities,
|
|
10
10
|
} from "@getpaseo/plugin/server/provider";
|
|
11
11
|
import type { OmpBrowserAuthorizationRegistry } from "../mcp-browser";
|
|
12
|
+
import type {
|
|
13
|
+
OmpOperationalFailure,
|
|
14
|
+
OmpOperationalFailureReporter,
|
|
15
|
+
} from "../operational-failure-diagnostics";
|
|
12
16
|
import { discoverOmpCatalog } from "./catalog";
|
|
13
17
|
import { normalizeOmpCatalogOptions } from "./config-normalization";
|
|
14
18
|
import type { OmpMcpConnector } from "./host-tools";
|
|
@@ -683,6 +687,7 @@ export function createOmpConnection(
|
|
|
683
687
|
browserAuthorizationRegistry?: OmpBrowserAuthorizationRegistry,
|
|
684
688
|
reportDiagnostic: (diagnostic: OmpConnectionDiagnostic) => void = (diagnostic) =>
|
|
685
689
|
console.error("OMP provider failure", diagnostic),
|
|
690
|
+
reportOperationalFailure: OmpOperationalFailureReporter = () => {},
|
|
686
691
|
): ProviderConnection {
|
|
687
692
|
const errorDetails = (error: unknown, fallback: string): { message: string } => {
|
|
688
693
|
if (isOmpPublicError(error)) return { message: error.message };
|
|
@@ -696,6 +701,13 @@ export function createOmpConnection(
|
|
|
696
701
|
}
|
|
697
702
|
return { message: `${fallback} (diagnostic ${diagnosticId})` };
|
|
698
703
|
};
|
|
704
|
+
const recordOperationalFailure = (failure: OmpOperationalFailure) => {
|
|
705
|
+
try {
|
|
706
|
+
reportOperationalFailure(failure);
|
|
707
|
+
} catch {
|
|
708
|
+
// Diagnostics must never affect provider requests or cleanup.
|
|
709
|
+
}
|
|
710
|
+
};
|
|
699
711
|
const safeCapabilities = [...new Set(capabilities)].filter(
|
|
700
712
|
(capability) =>
|
|
701
713
|
SUPPORTED_CAPABILITIES[capability] &&
|
|
@@ -802,6 +814,9 @@ export function createOmpConnection(
|
|
|
802
814
|
},
|
|
803
815
|
});
|
|
804
816
|
} catch (error) {
|
|
817
|
+
if (!closing) {
|
|
818
|
+
recordOperationalFailure({ category: "session-open", stage: "catalog" });
|
|
819
|
+
}
|
|
805
820
|
if (isOmpCleanupFailure(error)) catalogCleanup = error.cleanup;
|
|
806
821
|
if (!closing) requestFailure(input.requestId, error, "OMP catalog discovery failed");
|
|
807
822
|
}
|
|
@@ -925,6 +940,7 @@ export function createOmpConnection(
|
|
|
925
940
|
environment,
|
|
926
941
|
mcpConnector,
|
|
927
942
|
mcpInitializationTimeoutMs,
|
|
943
|
+
reportOperationalFailure,
|
|
928
944
|
);
|
|
929
945
|
const discoveredNativeSessionId = session.persistenceSessionId;
|
|
930
946
|
if (discoveredNativeSessionId) nativeSessionId = discoveredNativeSessionId;
|
|
@@ -977,6 +993,15 @@ export function createOmpConnection(
|
|
|
977
993
|
nativeReservations.release(nativeSessionId, token);
|
|
978
994
|
}
|
|
979
995
|
} catch (error) {
|
|
996
|
+
const openingCancelled =
|
|
997
|
+
closing || controller.signal.aborted || opening.get(input.sessionId)?.token !== token;
|
|
998
|
+
if (!openingCancelled) {
|
|
999
|
+
recordOperationalFailure(
|
|
1000
|
+
input.history === "replay" || nativeSessionId
|
|
1001
|
+
? { category: "replay-recovery", stage: "persisted-replay" }
|
|
1002
|
+
: { category: "session-open", stage: "startup" },
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
980
1005
|
deleteSession(input.sessionId, token);
|
|
981
1006
|
let cleanupError: unknown;
|
|
982
1007
|
if (session) {
|
|
@@ -3,6 +3,10 @@ import type {
|
|
|
3
3
|
ProviderMcpServerConfig,
|
|
4
4
|
ProviderSessionConfig,
|
|
5
5
|
} from "@getpaseo/plugin/server/provider";
|
|
6
|
+
import type {
|
|
7
|
+
OmpOperationalFailure,
|
|
8
|
+
OmpOperationalFailureReporter,
|
|
9
|
+
} from "../operational-failure-diagnostics";
|
|
6
10
|
import { isValidImagePayload } from "./image";
|
|
7
11
|
import {
|
|
8
12
|
type ConnectedMcpClient,
|
|
@@ -26,6 +30,11 @@ import {
|
|
|
26
30
|
utf8Bytes,
|
|
27
31
|
} from "./security";
|
|
28
32
|
|
|
33
|
+
type HostToolFailureStage = Extract<
|
|
34
|
+
OmpOperationalFailure,
|
|
35
|
+
{ category: "tool-projector"; stage: `host-tool-${string}` }
|
|
36
|
+
>["stage"];
|
|
37
|
+
|
|
29
38
|
const INTERNAL_PASEO_MCP_PATH = "/mcp/agents";
|
|
30
39
|
const RESERVED_PASEO_NAMESPACE = "paseo";
|
|
31
40
|
const MAX_MCP_SERVERS = 32;
|
|
@@ -79,6 +88,7 @@ export interface OmpHostToolsOpenOptions {
|
|
|
79
88
|
initializationTimeoutMs?: number;
|
|
80
89
|
callTimeoutMs?: number;
|
|
81
90
|
callScheduler?: OmpHostToolScheduler;
|
|
91
|
+
reportOperationalFailure?: OmpOperationalFailureReporter;
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
type ClassifiedServer = {
|
|
@@ -496,6 +506,7 @@ export class OmpHostToolsBridge {
|
|
|
496
506
|
private readonly targets: ReadonlyMap<string, ToolTarget>,
|
|
497
507
|
private readonly callTimeoutMs: number,
|
|
498
508
|
private readonly callScheduler: OmpHostToolScheduler,
|
|
509
|
+
private readonly reportOperationalFailure: OmpOperationalFailureReporter,
|
|
499
510
|
) {
|
|
500
511
|
this.labels = new Map(definitions.map(({ name, label }) => [name, label ?? name]));
|
|
501
512
|
}
|
|
@@ -612,6 +623,7 @@ export class OmpHostToolsBridge {
|
|
|
612
623
|
targets,
|
|
613
624
|
callTimeoutMs,
|
|
614
625
|
callScheduler,
|
|
626
|
+
options.reportOperationalFailure ?? (() => {}),
|
|
615
627
|
);
|
|
616
628
|
} catch (error) {
|
|
617
629
|
const cleanupTasks = [
|
|
@@ -740,7 +752,11 @@ export class OmpHostToolsBridge {
|
|
|
740
752
|
if (!runtime) return true;
|
|
741
753
|
const target = this.targets.get(event.toolName);
|
|
742
754
|
if (!target) {
|
|
743
|
-
this.
|
|
755
|
+
this.sendOperationalError(
|
|
756
|
+
"host-tool-unknown",
|
|
757
|
+
runtime,
|
|
758
|
+
errorResult(event.id, "Unknown OMP host tool"),
|
|
759
|
+
);
|
|
744
760
|
return true;
|
|
745
761
|
}
|
|
746
762
|
const retainedBytes = boundedJsonBytes(
|
|
@@ -756,7 +772,11 @@ export class OmpHostToolsBridge {
|
|
|
756
772
|
retainedBytes === Number.POSITIVE_INFINITY ||
|
|
757
773
|
this.pendingBytes + retainedBytes > MAX_PENDING_HOST_TOOL_BYTES
|
|
758
774
|
) {
|
|
759
|
-
this.
|
|
775
|
+
this.sendOperationalError(
|
|
776
|
+
"host-tool-capacity",
|
|
777
|
+
runtime,
|
|
778
|
+
errorResult(event.id, "OMP host tool bridge is at capacity"),
|
|
779
|
+
);
|
|
760
780
|
return true;
|
|
761
781
|
}
|
|
762
782
|
const pending: PendingCall = {
|
|
@@ -794,23 +814,30 @@ export class OmpHostToolsBridge {
|
|
|
794
814
|
})
|
|
795
815
|
.then((result) => {
|
|
796
816
|
if (!this.isCurrent(event.id, pending)) return;
|
|
797
|
-
let terminal: OmpHostToolResult;
|
|
798
817
|
try {
|
|
799
818
|
const normalized = normalizeResult(result);
|
|
800
|
-
terminal = {
|
|
819
|
+
const terminal: OmpHostToolResult = {
|
|
801
820
|
type: "host_tool_result",
|
|
802
821
|
id: event.id,
|
|
803
822
|
result: normalized,
|
|
804
823
|
...(normalized.isError !== undefined ? { isError: normalized.isError } : {}),
|
|
805
824
|
};
|
|
825
|
+
if (normalized.isError) {
|
|
826
|
+
this.sendOperationalError("host-tool-call", runtime, terminal, pending);
|
|
827
|
+
} else this.sendTerminal(runtime, terminal, pending);
|
|
806
828
|
} catch {
|
|
807
|
-
|
|
829
|
+
this.sendOperationalError(
|
|
830
|
+
"host-tool-normalization",
|
|
831
|
+
runtime,
|
|
832
|
+
errorResult(event.id, "MCP host tool execution failed"),
|
|
833
|
+
pending,
|
|
834
|
+
);
|
|
808
835
|
}
|
|
809
|
-
this.sendTerminal(runtime, terminal, pending);
|
|
810
836
|
})
|
|
811
837
|
.catch(() => {
|
|
812
838
|
if (!this.isCurrent(event.id, pending)) return;
|
|
813
|
-
this.
|
|
839
|
+
this.sendOperationalError(
|
|
840
|
+
"host-tool-call",
|
|
814
841
|
runtime,
|
|
815
842
|
errorResult(event.id, "MCP host tool execution failed"),
|
|
816
843
|
pending,
|
|
@@ -909,22 +936,44 @@ export class OmpHostToolsBridge {
|
|
|
909
936
|
return fits(bounded) ? bounded : errorResult(result.id, OMP_HOST_TOOL_FRAME_LIMIT_ERROR);
|
|
910
937
|
}
|
|
911
938
|
|
|
912
|
-
private
|
|
939
|
+
private recordOperationalFailure(failure: OmpOperationalFailure): void {
|
|
940
|
+
try {
|
|
941
|
+
this.reportOperationalFailure(failure);
|
|
942
|
+
} catch {
|
|
943
|
+
// Diagnostics must never affect host tool execution or result delivery.
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
private sendOperationalError(
|
|
948
|
+
stage: HostToolFailureStage,
|
|
913
949
|
runtime: OmpRuntimeSession,
|
|
914
950
|
result: OmpHostToolResult,
|
|
915
951
|
pending?: PendingCall,
|
|
916
952
|
): void {
|
|
953
|
+
if (this.sendTerminal(runtime, result, pending)) {
|
|
954
|
+
this.recordOperationalFailure({ category: "tool-projector", stage });
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
private sendTerminal(
|
|
959
|
+
runtime: OmpRuntimeSession,
|
|
960
|
+
result: OmpHostToolResult,
|
|
961
|
+
pending?: PendingCall,
|
|
962
|
+
): boolean {
|
|
917
963
|
const bounded = this.boundedTerminal(runtime, result);
|
|
918
|
-
if (pending && !this.isCurrent(bounded.id, pending)) return;
|
|
964
|
+
if (pending && !this.isCurrent(bounded.id, pending)) return false;
|
|
919
965
|
try {
|
|
920
966
|
runtime.sendHostToolResult(bounded);
|
|
967
|
+
return true;
|
|
921
968
|
} catch (error) {
|
|
922
969
|
this.failRuntime(runtime, error);
|
|
970
|
+
return false;
|
|
923
971
|
}
|
|
924
972
|
}
|
|
925
973
|
|
|
926
974
|
private failRuntime(runtime: OmpRuntimeSession, error: unknown): void {
|
|
927
975
|
if (this.runtime !== runtime) return;
|
|
976
|
+
this.recordOperationalFailure({ category: "tool-projector", stage: "host-tool-delivery" });
|
|
928
977
|
const failure =
|
|
929
978
|
error instanceof Error ? error : new Error("OMP host tool result delivery failed");
|
|
930
979
|
this.detach();
|
|
@@ -945,7 +994,11 @@ export class OmpHostToolsBridge {
|
|
|
945
994
|
if (!this.isCurrent(id, pending)) return;
|
|
946
995
|
this.releasePending(id, pending);
|
|
947
996
|
pending.controller.abort(new Error("OMP MCP host tool call timed out"));
|
|
948
|
-
this.
|
|
997
|
+
this.sendOperationalError(
|
|
998
|
+
"host-tool-timeout",
|
|
999
|
+
pending.runtime,
|
|
1000
|
+
errorResult(id, "OMP MCP host tool call timed out"),
|
|
1001
|
+
);
|
|
949
1002
|
}
|
|
950
1003
|
|
|
951
1004
|
private releasePending(id: string, pending: PendingCall): void {
|