@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
|
@@ -43,7 +43,7 @@ function profileDirectory(environment: NodeJS.ProcessEnv): string {
|
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/**
|
|
46
|
+
/** Contribution registration is synchronous; inspect directory names only. */
|
|
47
47
|
export function discoverOmpProfilesSync(environment: NodeJS.ProcessEnv = process.env): string[] {
|
|
48
48
|
let directory: Dir;
|
|
49
49
|
try {
|
|
@@ -151,7 +151,12 @@ export function createProfileOmpProvider(profile: string, options: OmpProviderOp
|
|
|
151
151
|
const id = profileProviderId(profile);
|
|
152
152
|
const environment = fixedEnvironment(profile, options.environment ?? process.env);
|
|
153
153
|
const sessionDir = resolve(ompSessionDir(environment));
|
|
154
|
-
const runtime =
|
|
154
|
+
const runtime =
|
|
155
|
+
options.runtime ??
|
|
156
|
+
new OmpRpcRuntime({
|
|
157
|
+
environment,
|
|
158
|
+
reportProtocolViolation: options.reportProtocolViolation,
|
|
159
|
+
});
|
|
155
160
|
const readPersistedSessionTranscript = runtime.readPersistedSessionTranscript?.bind(runtime);
|
|
156
161
|
const assertSessionDir = (requested?: string) => {
|
|
157
162
|
if (requested !== undefined && resolve(requested) !== sessionDir) {
|
|
@@ -3,10 +3,11 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import type { ProviderRegistration } from "@getpaseo/plugin/server/provider";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import type { OmpBrowserAuthorizationRegistry } from "../mcp-browser";
|
|
6
|
+
import type { OmpOperationalFailureReporter } from "../operational-failure-diagnostics";
|
|
6
7
|
import { probeOmpAvailability } from "../provider-diagnostics";
|
|
7
8
|
import { createOmpConnection, OmpNativeSessionReservations } from "./connection";
|
|
8
9
|
import type { OmpMcpConnector } from "./host-tools";
|
|
9
|
-
import { OmpRpcRuntime, type OmpRuntime } from "./omp-rpc";
|
|
10
|
+
import { type OmpProtocolViolationDiagnostic, OmpRpcRuntime, type OmpRuntime } from "./omp-rpc";
|
|
10
11
|
import { parseOmpProviderOptions } from "./provider-options";
|
|
11
12
|
import { boundedJsonBytes } from "./security";
|
|
12
13
|
import { OmpProviderOptionsSchema } from "./settings";
|
|
@@ -66,6 +67,8 @@ export interface OmpProviderOptions {
|
|
|
66
67
|
mcpInitializationTimeoutMs?: number;
|
|
67
68
|
mcpConnector?: OmpMcpConnector;
|
|
68
69
|
browserAuthorizationRegistry?: OmpBrowserAuthorizationRegistry;
|
|
70
|
+
reportProtocolViolation?: (diagnostic: OmpProtocolViolationDiagnostic) => void | Promise<void>;
|
|
71
|
+
reportOperationalFailure?: OmpOperationalFailureReporter;
|
|
69
72
|
availabilityProbe?: (
|
|
70
73
|
options: ProviderCatalogOptionsCompat,
|
|
71
74
|
timeoutMs: number | undefined,
|
|
@@ -85,7 +88,12 @@ function stableJson(value: unknown): string {
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
export function createOmpProvider(options: OmpProviderOptions = {}): ProviderRegistrationCompat {
|
|
88
|
-
const runtime =
|
|
91
|
+
const runtime =
|
|
92
|
+
options.runtime ??
|
|
93
|
+
new OmpRpcRuntime({
|
|
94
|
+
environment: options.environment,
|
|
95
|
+
reportProtocolViolation: options.reportProtocolViolation,
|
|
96
|
+
});
|
|
89
97
|
const nativeReservations = new OmpNativeSessionReservations();
|
|
90
98
|
return {
|
|
91
99
|
id: "omp-plugin",
|
|
@@ -156,6 +164,8 @@ export function createOmpProvider(options: OmpProviderOptions = {}): ProviderReg
|
|
|
156
164
|
options.mcpInitializationTimeoutMs,
|
|
157
165
|
options.replayTimeoutMs,
|
|
158
166
|
options.browserAuthorizationRegistry,
|
|
167
|
+
undefined,
|
|
168
|
+
options.reportOperationalFailure,
|
|
159
169
|
);
|
|
160
170
|
},
|
|
161
171
|
};
|
|
@@ -11,6 +11,10 @@ import type {
|
|
|
11
11
|
ProviderUsage,
|
|
12
12
|
} from "@getpaseo/plugin/server/provider";
|
|
13
13
|
import { getForgeDefinitionOrNeutral } from "@getpaseo/protocol/forge-manifest";
|
|
14
|
+
import type {
|
|
15
|
+
OmpOperationalFailure,
|
|
16
|
+
OmpOperationalFailureReporter,
|
|
17
|
+
} from "../operational-failure-diagnostics";
|
|
14
18
|
import {
|
|
15
19
|
mapOmpModels,
|
|
16
20
|
nativeOmpModelId,
|
|
@@ -292,6 +296,7 @@ type ActiveTurn = {
|
|
|
292
296
|
terminalizing: boolean;
|
|
293
297
|
terminalization?: Promise<void>;
|
|
294
298
|
terminalOutcome?: TurnOutcome;
|
|
299
|
+
operationalTerminalStage?: "failed" | "unresolved";
|
|
295
300
|
terminalWake?: VoidDeferred;
|
|
296
301
|
steerReady: VoidDeferred;
|
|
297
302
|
steersInFlight: number;
|
|
@@ -973,6 +978,7 @@ export class OmpProviderSession {
|
|
|
973
978
|
private readonly transitionNativeSession: NativeSessionTransition,
|
|
974
979
|
private readonly quarantineRewindCleanup: RewindCleanupQuarantine,
|
|
975
980
|
private readonly retireRewindSession: RewindSessionRetirement,
|
|
981
|
+
private readonly reportOperationalFailure: OmpOperationalFailureReporter,
|
|
976
982
|
scheduler: OmpTimelineScheduler = defaultOmpTimelineScheduler,
|
|
977
983
|
) {
|
|
978
984
|
this.id = id;
|
|
@@ -1006,6 +1012,13 @@ export class OmpProviderSession {
|
|
|
1006
1012
|
: null;
|
|
1007
1013
|
this.bindRuntime(runtime);
|
|
1008
1014
|
}
|
|
1015
|
+
private recordOperationalFailure(failure: OmpOperationalFailure): void {
|
|
1016
|
+
try {
|
|
1017
|
+
this.reportOperationalFailure(failure);
|
|
1018
|
+
} catch {
|
|
1019
|
+
// Diagnostics must never affect provider or session flow.
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1009
1022
|
get persistenceSessionId(): string | undefined {
|
|
1010
1023
|
return this.persistSession ? this.nativeSessionId : undefined;
|
|
1011
1024
|
}
|
|
@@ -1032,6 +1045,7 @@ export class OmpProviderSession {
|
|
|
1032
1045
|
environment?: NodeJS.ProcessEnv,
|
|
1033
1046
|
mcpConnector?: OmpMcpConnector,
|
|
1034
1047
|
mcpInitializationTimeoutMs?: number,
|
|
1048
|
+
reportOperationalFailure: OmpOperationalFailureReporter = () => {},
|
|
1035
1049
|
): Promise<OmpProviderSession> {
|
|
1036
1050
|
const resumeSessionId = ompPersistenceSessionId(input);
|
|
1037
1051
|
if (resumeSessionId && !input.config.persist) {
|
|
@@ -1078,6 +1092,7 @@ export class OmpProviderSession {
|
|
|
1078
1092
|
connectMcp: mcpConnector,
|
|
1079
1093
|
signal,
|
|
1080
1094
|
initializationTimeoutMs: mcpInitializationTimeoutMs,
|
|
1095
|
+
reportOperationalFailure,
|
|
1081
1096
|
});
|
|
1082
1097
|
let native: OmpRuntimeSession | undefined;
|
|
1083
1098
|
let cleanupNativeSessionId: string | undefined;
|
|
@@ -1236,6 +1251,7 @@ export class OmpProviderSession {
|
|
|
1236
1251
|
transitionNativeSession,
|
|
1237
1252
|
quarantineRewindCleanup,
|
|
1238
1253
|
retireRewindSession,
|
|
1254
|
+
reportOperationalFailure,
|
|
1239
1255
|
scheduler,
|
|
1240
1256
|
);
|
|
1241
1257
|
session.unsupportedThinkingNoticePending = unsupportedThinkingLevel;
|
|
@@ -1653,6 +1669,10 @@ export class OmpProviderSession {
|
|
|
1653
1669
|
}
|
|
1654
1670
|
} catch (error) {
|
|
1655
1671
|
if (replay.signal.aborted) throw error;
|
|
1672
|
+
this.recordOperationalFailure({
|
|
1673
|
+
category: "replay-recovery",
|
|
1674
|
+
stage: "persisted-replay",
|
|
1675
|
+
});
|
|
1656
1676
|
this.emit({
|
|
1657
1677
|
type: "timeline.item",
|
|
1658
1678
|
sessionId: this.id,
|
|
@@ -1807,6 +1827,7 @@ export class OmpProviderSession {
|
|
|
1807
1827
|
this.publishCommittedConfig(state, runtime, generation);
|
|
1808
1828
|
this.emit({ type: "request.completed", requestId: input.requestId });
|
|
1809
1829
|
} catch (error) {
|
|
1830
|
+
this.recordOperationalFailure({ category: "replay-recovery", stage: "rewind" });
|
|
1810
1831
|
const failure = branchMutationPossible
|
|
1811
1832
|
? { message: "OMP conversation rewind left native state indeterminate" }
|
|
1812
1833
|
: providerError(error, "OMP conversation rewind failed");
|
|
@@ -2924,7 +2945,13 @@ export class OmpProviderSession {
|
|
|
2924
2945
|
|
|
2925
2946
|
private async recoverRuntime(): Promise<void> {
|
|
2926
2947
|
if (!this.runtimeDead) return;
|
|
2927
|
-
this.recoveryPromise ??= this.startRecovery()
|
|
2948
|
+
this.recoveryPromise ??= this.startRecovery().catch((error) => {
|
|
2949
|
+
this.recordOperationalFailure({
|
|
2950
|
+
category: "replay-recovery",
|
|
2951
|
+
stage: "runtime-recovery",
|
|
2952
|
+
});
|
|
2953
|
+
throw error;
|
|
2954
|
+
});
|
|
2928
2955
|
try {
|
|
2929
2956
|
await this.recoveryPromise;
|
|
2930
2957
|
} finally {
|
|
@@ -3147,6 +3174,10 @@ export class OmpProviderSession {
|
|
|
3147
3174
|
try {
|
|
3148
3175
|
this.subsessions?.handle(event);
|
|
3149
3176
|
} catch {
|
|
3177
|
+
this.recordOperationalFailure({
|
|
3178
|
+
category: "tool-projector",
|
|
3179
|
+
stage: "subsession-projector",
|
|
3180
|
+
});
|
|
3150
3181
|
this.handleRuntimeFailure("OMP subagent event processing failed");
|
|
3151
3182
|
}
|
|
3152
3183
|
return;
|
|
@@ -3381,6 +3412,10 @@ export class OmpProviderSession {
|
|
|
3381
3412
|
try {
|
|
3382
3413
|
this.subsessions?.observeSessionEvent(this.id, event);
|
|
3383
3414
|
} catch {
|
|
3415
|
+
this.recordOperationalFailure({
|
|
3416
|
+
category: "tool-projector",
|
|
3417
|
+
stage: "subsession-projector",
|
|
3418
|
+
});
|
|
3384
3419
|
this.handleRuntimeFailure("OMP subagent dispatch tracking failed");
|
|
3385
3420
|
return;
|
|
3386
3421
|
}
|
|
@@ -3437,7 +3472,15 @@ export class OmpProviderSession {
|
|
|
3437
3472
|
}
|
|
3438
3473
|
}
|
|
3439
3474
|
if (isNativeTurnActivity(event)) this.markAgentEvidence(turn);
|
|
3440
|
-
|
|
3475
|
+
try {
|
|
3476
|
+
this.projector.project(event, turn.turnId);
|
|
3477
|
+
} catch (error) {
|
|
3478
|
+
this.recordOperationalFailure({
|
|
3479
|
+
category: "tool-projector",
|
|
3480
|
+
stage: "timeline-projector",
|
|
3481
|
+
});
|
|
3482
|
+
throw error;
|
|
3483
|
+
}
|
|
3441
3484
|
if (event.type === "tool_execution_end") this.resumeDeferredAgentEnd();
|
|
3442
3485
|
}
|
|
3443
3486
|
|
|
@@ -4506,11 +4549,13 @@ export class OmpProviderSession {
|
|
|
4506
4549
|
const state = await this.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
4507
4550
|
if (turn.terminal || this.activeTurn !== turn || turn.deferredAgentEnd !== candidate) return;
|
|
4508
4551
|
if (!state) {
|
|
4552
|
+
turn.operationalTerminalStage = "unresolved";
|
|
4509
4553
|
this.handleRuntimeFailure("OMP agent_end state could not be confirmed");
|
|
4510
4554
|
return;
|
|
4511
4555
|
}
|
|
4512
4556
|
turn.deferredAgentEnd = undefined;
|
|
4513
4557
|
if (state.isStreaming || state.isCompacting) return;
|
|
4558
|
+
turn.operationalTerminalStage = "unresolved";
|
|
4514
4559
|
await this.finishTurn(turn, "failed", {
|
|
4515
4560
|
message: "OMP unkeyed agent_end could not be correlated to the current prompt",
|
|
4516
4561
|
});
|
|
@@ -4591,6 +4636,7 @@ export class OmpProviderSession {
|
|
|
4591
4636
|
) {
|
|
4592
4637
|
void this.completeAgentEnd(turn, candidate.event);
|
|
4593
4638
|
} else {
|
|
4639
|
+
turn.operationalTerminalStage = "unresolved";
|
|
4594
4640
|
this.handleRuntimeFailure("OMP agent_end state could not be confirmed");
|
|
4595
4641
|
}
|
|
4596
4642
|
}, AGENT_END_SETTLE_MS);
|
|
@@ -4615,6 +4661,10 @@ export class OmpProviderSession {
|
|
|
4615
4661
|
turn.terminalizing = false;
|
|
4616
4662
|
turn.deferredAgentEnd = candidate;
|
|
4617
4663
|
void this.subsessions.reconcile(this.runtime).catch(() => {
|
|
4664
|
+
this.recordOperationalFailure({
|
|
4665
|
+
category: "tool-projector",
|
|
4666
|
+
stage: "subsession-projector",
|
|
4667
|
+
});
|
|
4618
4668
|
if (!turn.terminal && this.activeTurn === turn) {
|
|
4619
4669
|
this.subsessions?.terminalize("failed");
|
|
4620
4670
|
this.resumeDeferredAgentEnd();
|
|
@@ -4700,6 +4750,7 @@ export class OmpProviderSession {
|
|
|
4700
4750
|
return;
|
|
4701
4751
|
}
|
|
4702
4752
|
if (turn.userEchoObserved) {
|
|
4753
|
+
turn.operationalTerminalStage = "unresolved";
|
|
4703
4754
|
this.handleRuntimeFailure("OMP agent_end state could not be confirmed");
|
|
4704
4755
|
return;
|
|
4705
4756
|
}
|
|
@@ -4759,6 +4810,7 @@ export class OmpProviderSession {
|
|
|
4759
4810
|
await this.finishTurn(turn, "completed");
|
|
4760
4811
|
return;
|
|
4761
4812
|
}
|
|
4813
|
+
turn.operationalTerminalStage = outcome === "failed" ? "failed" : "unresolved";
|
|
4762
4814
|
const error =
|
|
4763
4815
|
outcome === "failed" ? "OMP assistant turn failed" : unknownTerminalOutcomeError(event, turn);
|
|
4764
4816
|
this.subsessions?.terminalize("failed");
|
|
@@ -4901,6 +4953,12 @@ export class OmpProviderSession {
|
|
|
4901
4953
|
usageSampled: true,
|
|
4902
4954
|
};
|
|
4903
4955
|
}
|
|
4956
|
+
if (outcome.state === "failed") {
|
|
4957
|
+
this.recordOperationalFailure({
|
|
4958
|
+
category: "terminal-outcome",
|
|
4959
|
+
stage: turn.operationalTerminalStage ?? "failed",
|
|
4960
|
+
});
|
|
4961
|
+
}
|
|
4904
4962
|
this.imageMaterializer.clear();
|
|
4905
4963
|
turn.terminal = true;
|
|
4906
4964
|
if (this.usageSample?.turn === turn) this.usageSample = null;
|
|
@@ -4914,7 +4972,15 @@ export class OmpProviderSession {
|
|
|
4914
4972
|
}
|
|
4915
4973
|
this.publishPendingUsers(turn);
|
|
4916
4974
|
this.resolveTurnPermissions(turn.turnId);
|
|
4917
|
-
|
|
4975
|
+
try {
|
|
4976
|
+
this.projector.finishTurn(turn.turnId, preserveCompactions);
|
|
4977
|
+
} catch (error) {
|
|
4978
|
+
this.recordOperationalFailure({
|
|
4979
|
+
category: "tool-projector",
|
|
4980
|
+
stage: "timeline-projector",
|
|
4981
|
+
});
|
|
4982
|
+
throw error;
|
|
4983
|
+
}
|
|
4918
4984
|
this.unclaimedBranchEntries.length = 0;
|
|
4919
4985
|
this.emit({
|
|
4920
4986
|
type: "session.turn",
|
|
@@ -4972,6 +5038,7 @@ export class OmpProviderSession {
|
|
|
4972
5038
|
this.publishPromptResult(turn, { type: "failed", error: { message } });
|
|
4973
5039
|
if (turn.started) void this.finishTurn(turn, "failed", { message }, true, true);
|
|
4974
5040
|
else {
|
|
5041
|
+
this.recordOperationalFailure({ category: "terminal-outcome", stage: "failed" });
|
|
4975
5042
|
turn.steerReady.resolve();
|
|
4976
5043
|
turn.terminal = true;
|
|
4977
5044
|
this.projector.finishTurn(turn.turnId);
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { arch, platform } from "node:os";
|
|
2
|
+
import type { RpcInput } from "@getpaseo/plugin";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import type { OmpProviderHealth, OmpVersion } from "../shared/provider-diagnostics";
|
|
5
|
+
import {
|
|
6
|
+
type getOmpSupportReport,
|
|
7
|
+
OMP_SUPPORT_REPORT_MAX_BYTES,
|
|
8
|
+
OMP_SUPPORT_REPORT_SCHEMA_VERSION,
|
|
9
|
+
supportReportByteLength,
|
|
10
|
+
} from "../shared/support-diagnostics";
|
|
11
|
+
import type {
|
|
12
|
+
OmpOperationalFailureCollector,
|
|
13
|
+
OmpOperationalFailureSummary,
|
|
14
|
+
} from "./operational-failure-diagnostics";
|
|
15
|
+
import { PASEO_OMP_PACKAGE_VERSION } from "./package-version";
|
|
16
|
+
import type {
|
|
17
|
+
OmpProtocolViolationCollector,
|
|
18
|
+
OmpProtocolViolationSummary,
|
|
19
|
+
} from "./protocol-violation-diagnostics";
|
|
20
|
+
import { resolveGetOmpProviderHealth } from "./provider-diagnostics";
|
|
21
|
+
|
|
22
|
+
const PACKAGE_VERSION = z
|
|
23
|
+
.string()
|
|
24
|
+
.regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,48})?$/u)
|
|
25
|
+
.max(64);
|
|
26
|
+
const NODE_VERSION = z
|
|
27
|
+
.string()
|
|
28
|
+
.regex(/^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,48})?$/u)
|
|
29
|
+
.max(64);
|
|
30
|
+
const KNOWN_PLATFORMS: Record<string, true> = {
|
|
31
|
+
aix: true,
|
|
32
|
+
android: true,
|
|
33
|
+
darwin: true,
|
|
34
|
+
freebsd: true,
|
|
35
|
+
linux: true,
|
|
36
|
+
openbsd: true,
|
|
37
|
+
sunos: true,
|
|
38
|
+
win32: true,
|
|
39
|
+
};
|
|
40
|
+
const KNOWN_ARCHITECTURES: Record<string, true> = {
|
|
41
|
+
arm: true,
|
|
42
|
+
arm64: true,
|
|
43
|
+
ia32: true,
|
|
44
|
+
loong64: true,
|
|
45
|
+
mips: true,
|
|
46
|
+
mipsel: true,
|
|
47
|
+
ppc: true,
|
|
48
|
+
ppc64: true,
|
|
49
|
+
riscv64: true,
|
|
50
|
+
s390: true,
|
|
51
|
+
s390x: true,
|
|
52
|
+
x64: true,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export interface OmpSupportReportData {
|
|
56
|
+
collectedAt: string;
|
|
57
|
+
pluginVersion: string;
|
|
58
|
+
platform: string;
|
|
59
|
+
architecture: string;
|
|
60
|
+
nodeVersion: string;
|
|
61
|
+
scope: "global" | "workspace";
|
|
62
|
+
store: "default" | "named-profile" | "custom-directory";
|
|
63
|
+
health: OmpProviderHealth | null;
|
|
64
|
+
violations: readonly OmpProtocolViolationSummary[];
|
|
65
|
+
operationalFailures: readonly OmpOperationalFailureSummary[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const EXPECTATION_LABELS: Record<
|
|
69
|
+
NonNullable<OmpProtocolViolationSummary["latestExpected"]>,
|
|
70
|
+
string
|
|
71
|
+
> = {
|
|
72
|
+
"complete-json-line": "newline-terminated JSON frame",
|
|
73
|
+
"within-byte-limit": "byte length within negotiated limit",
|
|
74
|
+
"valid-json": "valid UTF-8 JSON",
|
|
75
|
+
"object-envelope": "JSON object envelope",
|
|
76
|
+
"bounded-frame-type": "frame type string up to 64 bytes",
|
|
77
|
+
"valid-response-frame": "valid response envelope",
|
|
78
|
+
"valid-ready-frame": "valid ready handshake",
|
|
79
|
+
"single-ready-frame": "one ready handshake",
|
|
80
|
+
"valid-event-frame": "valid event payload",
|
|
81
|
+
"valid-event-state-transition": "event valid for current stream state",
|
|
82
|
+
"valid-message-event": "valid message event payload",
|
|
83
|
+
"valid-tool-event": "valid tool event payload",
|
|
84
|
+
"valid-lifecycle-event": "valid lifecycle event payload",
|
|
85
|
+
"valid-subagent-event": "valid subagent event payload",
|
|
86
|
+
"valid-configuration-event": "valid configuration event payload",
|
|
87
|
+
"valid-extension-ui-event": "valid extension UI event payload",
|
|
88
|
+
"known-event-type": "supported event type",
|
|
89
|
+
"notice-level-enum": "info, warning, or error",
|
|
90
|
+
"notice-message-string": "bounded string",
|
|
91
|
+
"valid-chunk-frame": "valid chunk metadata",
|
|
92
|
+
"valid-base64-chunk": "valid base64 chunk",
|
|
93
|
+
"first-chunk-index-zero": "first chunk index 0",
|
|
94
|
+
"contiguous-chunk-sequence": "contiguous chunk sequence",
|
|
95
|
+
"declared-chunk-byte-count": "decoded bytes equal declared byte length",
|
|
96
|
+
"chunk-before-deadline": "next chunk before timeout",
|
|
97
|
+
"no-interleaved-frame": "no interleaved frame during chunk assembly",
|
|
98
|
+
"no-remote-frame-error": "no remote frame error",
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
function finiteCount(value: number | null | undefined, maximum = Number.MAX_SAFE_INTEGER): string {
|
|
102
|
+
return value !== null && value !== undefined && Number.isSafeInteger(value) && value >= 0
|
|
103
|
+
? String(Math.min(value, maximum))
|
|
104
|
+
: "unknown";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function usefulReportLine(line: string): boolean {
|
|
108
|
+
return !line.endsWith(": unavailable") && !line.endsWith(": unknown") && !line.endsWith(": 0");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function formatVersion(version: OmpVersion | null): string {
|
|
112
|
+
if (!version) return "unavailable";
|
|
113
|
+
const core = `${version.major}.${version.minor}.${version.patch}`;
|
|
114
|
+
return version.prerelease ? `${core}-${version.prerelease}` : core;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function formatHealth(health: OmpProviderHealth | null): string[] {
|
|
118
|
+
if (!health) return ["collection.provider_health: failed"];
|
|
119
|
+
return [
|
|
120
|
+
"collection.provider_health: complete",
|
|
121
|
+
`omp.installed: ${health.binary.installed}`,
|
|
122
|
+
`omp.version: ${formatVersion(health.binary.version)}`,
|
|
123
|
+
`omp.version_probe: ${health.binary.versionStatus}`,
|
|
124
|
+
`omp.process_cleanup: ${health.binary.processCleanupFailed ? "failed" : "ok"}`,
|
|
125
|
+
`compatibility.rpc_ui: ${!health.rpcUi.checked || health.rpcUi.supported === null ? "unknown" : health.rpcUi.supported ? "supported" : "not-advertised"}`,
|
|
126
|
+
`compatibility.lsp: ${health.lsp.status}`,
|
|
127
|
+
`mcp.status: ${health.mcp.status}`,
|
|
128
|
+
`mcp.server_count: ${finiteCount(health.mcp.serverCount, 4_096)}`,
|
|
129
|
+
`mcp.reason: ${health.mcp.reason ?? "unknown"}`,
|
|
130
|
+
`storage.agent_root: ${health.roots.agentRootState}`,
|
|
131
|
+
`storage.config: ${health.roots.configState}`,
|
|
132
|
+
`storage.session_root: ${health.roots.sessionRootState}`,
|
|
133
|
+
`storage.agent_database: ${health.databases.agentDbState}`,
|
|
134
|
+
`storage.history_database: ${health.databases.historyDbState}`,
|
|
135
|
+
`storage.memory_backend: ${health.memoryBackend ?? "unknown"}`,
|
|
136
|
+
`hub.status: ${health.process.status}`,
|
|
137
|
+
`hub.tracked_count: ${finiteCount(health.process.trackedCount, 100_000)}`,
|
|
138
|
+
`hub.active_count: ${finiteCount(health.process.activeCount, 100_000)}`,
|
|
139
|
+
`hub.historical_count: ${finiteCount(health.process.historicalCount, 100_000)}`,
|
|
140
|
+
`hub.unknown_count: ${finiteCount(health.process.unknownCount, 100_000)}`,
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Stable line ordering is part of the pasted support-report contract. */
|
|
145
|
+
export function formatOmpSupportReport(data: OmpSupportReportData): string {
|
|
146
|
+
const lines = [
|
|
147
|
+
"OMP support diagnostics",
|
|
148
|
+
`schema_version: ${OMP_SUPPORT_REPORT_SCHEMA_VERSION}`,
|
|
149
|
+
`collected_at_utc: ${data.collectedAt}`,
|
|
150
|
+
`paseo_omp.version: ${data.pluginVersion}`,
|
|
151
|
+
`runtime.platform: ${data.platform}`,
|
|
152
|
+
`runtime.architecture: ${data.architecture}`,
|
|
153
|
+
`runtime.node: ${data.nodeVersion}`,
|
|
154
|
+
`selection.scope: ${data.scope}`,
|
|
155
|
+
`selection.store: ${data.store}`,
|
|
156
|
+
"selection.applies_to: provider-health",
|
|
157
|
+
"diagnostic_counters.scope: plugin-process-all-stores-workspaces",
|
|
158
|
+
"diagnostic_counters.lifetime: since-plugin-load",
|
|
159
|
+
...formatHealth(data.health),
|
|
160
|
+
];
|
|
161
|
+
for (const violation of data.violations) {
|
|
162
|
+
if (violation.occurrenceCount === 0) continue;
|
|
163
|
+
const prefix = `protocol.${violation.category}`;
|
|
164
|
+
lines.push(
|
|
165
|
+
`${prefix}.occurrence_count: ${finiteCount(violation.occurrenceCount)}`,
|
|
166
|
+
`${prefix}.batch_count: ${finiteCount(violation.batchCount)}`,
|
|
167
|
+
`${prefix}.max_batch_count: ${finiteCount(violation.maxOccurrenceCount)}`,
|
|
168
|
+
);
|
|
169
|
+
for (const [reason, count] of Object.entries(violation.reasonCounts)) {
|
|
170
|
+
if (count > 0) lines.push(`${prefix}.reason.${reason}.occurrence_count: ${count}`);
|
|
171
|
+
}
|
|
172
|
+
lines.push(
|
|
173
|
+
`${prefix}.latest_reason: ${violation.latestReason ?? "unknown"}`,
|
|
174
|
+
`${prefix}.latest_phase: ${violation.latestPhase ?? "unknown"}`,
|
|
175
|
+
`${prefix}.latest_event_type: ${violation.latestEventType ?? "unknown"}`,
|
|
176
|
+
`${prefix}.first_at_utc: ${violation.firstAt ?? "unavailable"}`,
|
|
177
|
+
`${prefix}.last_at_utc: ${violation.lastAt ?? "unavailable"}`,
|
|
178
|
+
`${prefix}.latest_frame_type: ${violation.latestFrameType ?? "unknown"}`,
|
|
179
|
+
`${prefix}.latest_field: ${violation.latestField ?? "unknown"}`,
|
|
180
|
+
`${prefix}.latest_expected: ${violation.latestExpected ? EXPECTATION_LABELS[violation.latestExpected] : "unknown"}`,
|
|
181
|
+
`${prefix}.latest_actual_type: ${violation.latestActualType ?? "unknown"}`,
|
|
182
|
+
`${prefix}.max_byte_size: ${finiteCount(violation.maxByteSize)}`,
|
|
183
|
+
`${prefix}.latest_limit_bytes: ${finiteCount(violation.latestLimitBytes)}`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
for (const failure of data.operationalFailures) {
|
|
187
|
+
if (failure.occurrenceCount === 0) continue;
|
|
188
|
+
const prefix = `operational.${failure.category}.${failure.stage}`;
|
|
189
|
+
lines.push(
|
|
190
|
+
`${prefix}.occurrence_count: ${finiteCount(failure.occurrenceCount)}`,
|
|
191
|
+
`${prefix}.first_at_utc: ${failure.firstAt ?? "unavailable"}`,
|
|
192
|
+
`${prefix}.last_at_utc: ${failure.lastAt ?? "unavailable"}`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const report = `${lines.filter(usefulReportLine).join("\n")}\n`;
|
|
196
|
+
if (supportReportByteLength(report) > OMP_SUPPORT_REPORT_MAX_BYTES) {
|
|
197
|
+
return [
|
|
198
|
+
"OMP support diagnostics",
|
|
199
|
+
`schema_version: ${OMP_SUPPORT_REPORT_SCHEMA_VERSION}`,
|
|
200
|
+
`collected_at_utc: ${data.collectedAt}`,
|
|
201
|
+
"collection_status: failed",
|
|
202
|
+
"collection_error: report-size-limit",
|
|
203
|
+
"",
|
|
204
|
+
].join("\n");
|
|
205
|
+
}
|
|
206
|
+
return report;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function collectedAt(now: () => Date): string {
|
|
210
|
+
try {
|
|
211
|
+
const value = now();
|
|
212
|
+
return Number.isFinite(value.getTime()) ? value.toISOString() : "unavailable";
|
|
213
|
+
} catch {
|
|
214
|
+
return "unavailable";
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function resolveGetOmpSupportReport(
|
|
219
|
+
input: RpcInput<typeof getOmpSupportReport>,
|
|
220
|
+
violations: OmpProtocolViolationCollector,
|
|
221
|
+
operationalFailures: OmpOperationalFailureCollector,
|
|
222
|
+
dependencies: {
|
|
223
|
+
loadHealth?: typeof resolveGetOmpProviderHealth;
|
|
224
|
+
loadPluginVersion?: () => Promise<string>;
|
|
225
|
+
now?: () => Date;
|
|
226
|
+
platform?: () => string;
|
|
227
|
+
architecture?: () => string;
|
|
228
|
+
nodeVersion?: string;
|
|
229
|
+
} = {},
|
|
230
|
+
): Promise<{ report: string }> {
|
|
231
|
+
const timestamp = collectedAt(dependencies.now ?? (() => new Date()));
|
|
232
|
+
try {
|
|
233
|
+
const [healthResult, pluginVersionResult] = await Promise.allSettled([
|
|
234
|
+
(dependencies.loadHealth ?? resolveGetOmpProviderHealth)(input),
|
|
235
|
+
(dependencies.loadPluginVersion ?? (() => Promise.resolve(PASEO_OMP_PACKAGE_VERSION)))(),
|
|
236
|
+
]);
|
|
237
|
+
const platformValue = (dependencies.platform ?? platform)();
|
|
238
|
+
const architectureValue = (dependencies.architecture ?? arch)();
|
|
239
|
+
const nodeVersionValue = dependencies.nodeVersion ?? process.version;
|
|
240
|
+
return {
|
|
241
|
+
report: formatOmpSupportReport({
|
|
242
|
+
collectedAt: timestamp,
|
|
243
|
+
pluginVersion:
|
|
244
|
+
pluginVersionResult.status === "fulfilled" &&
|
|
245
|
+
PACKAGE_VERSION.safeParse(pluginVersionResult.value).success
|
|
246
|
+
? pluginVersionResult.value
|
|
247
|
+
: "unavailable",
|
|
248
|
+
platform: KNOWN_PLATFORMS[platformValue] ? platformValue : "unknown",
|
|
249
|
+
architecture: KNOWN_ARCHITECTURES[architectureValue] ? architectureValue : "unknown",
|
|
250
|
+
nodeVersion: NODE_VERSION.safeParse(nodeVersionValue).success
|
|
251
|
+
? nodeVersionValue
|
|
252
|
+
: "unavailable",
|
|
253
|
+
scope: input.cwd ? "workspace" : "global",
|
|
254
|
+
store: input.store?.profile
|
|
255
|
+
? "named-profile"
|
|
256
|
+
: input.store?.agentDir
|
|
257
|
+
? "custom-directory"
|
|
258
|
+
: "default",
|
|
259
|
+
health: healthResult.status === "fulfilled" ? healthResult.value : null,
|
|
260
|
+
violations: violations.snapshot(),
|
|
261
|
+
operationalFailures: operationalFailures.snapshot(),
|
|
262
|
+
}),
|
|
263
|
+
};
|
|
264
|
+
} catch {
|
|
265
|
+
return {
|
|
266
|
+
report: formatOmpSupportReport({
|
|
267
|
+
collectedAt: timestamp,
|
|
268
|
+
pluginVersion: "unavailable",
|
|
269
|
+
platform: "unknown",
|
|
270
|
+
architecture: "unknown",
|
|
271
|
+
nodeVersion: "unavailable",
|
|
272
|
+
scope: input.cwd ? "workspace" : "global",
|
|
273
|
+
store: input.store?.profile
|
|
274
|
+
? "named-profile"
|
|
275
|
+
: input.store?.agentDir
|
|
276
|
+
? "custom-directory"
|
|
277
|
+
: "default",
|
|
278
|
+
health: null,
|
|
279
|
+
violations: violations.snapshot(),
|
|
280
|
+
operationalFailures: operationalFailures.snapshot(),
|
|
281
|
+
}),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { OmpWorkspaceCwdSchema } from "./hub";
|
|
4
|
+
import { OmpStoreSchema } from "./omp-store";
|
|
5
|
+
|
|
6
|
+
export const OMP_SUPPORT_REPORT_SCHEMA_VERSION = 1 as const;
|
|
7
|
+
export const OMP_SUPPORT_REPORT_MAX_BYTES = 64 * 1024;
|
|
8
|
+
export const OMP_SUPPORT_ISSUE_URL =
|
|
9
|
+
"https://github.com/omercnet/paseo-plugins/issues/new?template=omp-plugin.yml";
|
|
10
|
+
|
|
11
|
+
export function supportReportByteLength(value: string): number {
|
|
12
|
+
return new TextEncoder().encode(value).byteLength;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const OmpSupportReportTextSchema = z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1)
|
|
18
|
+
.refine((value) => supportReportByteLength(value) <= OMP_SUPPORT_REPORT_MAX_BYTES, {
|
|
19
|
+
message: "OMP support report exceeds 64 KiB",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const getOmpSupportReport = defineRpc({
|
|
23
|
+
name: "paseo-omp.get-support-report",
|
|
24
|
+
input: z
|
|
25
|
+
.object({
|
|
26
|
+
store: OmpStoreSchema.optional(),
|
|
27
|
+
force: z.boolean().optional(),
|
|
28
|
+
cwd: OmpWorkspaceCwdSchema.optional(),
|
|
29
|
+
})
|
|
30
|
+
.strict(),
|
|
31
|
+
output: z.object({ report: OmpSupportReportTextSchema }).strict(),
|
|
32
|
+
});
|