@omercnet/paseo-omp 0.3.0 → 0.4.0-next.119.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -5
- package/client/omp-config-surface.tsx +243 -24
- package/client/omp-config-views.ts +24 -0
- package/client/omp-model-picker-state.ts +145 -0
- package/client/omp-model-picker.tsx +282 -0
- package/client/omp-routing-editor.tsx +307 -0
- package/client/support-diagnostics-state.ts +45 -0
- package/index.server.ts +27 -2
- package/package.json +4 -8
- package/paseo-plugin.json +1 -1
- package/server/omp-models.ts +59 -0
- package/server/omp-settings.ts +30 -20
- package/server/operational-failure-diagnostics.ts +76 -0
- package/server/package-version.ts +2 -0
- package/server/protocol-violation-diagnostics.ts +169 -0
- package/server/provider/catalog.ts +39 -10
- package/server/provider/connection.ts +88 -9
- package/server/provider/host-tools.ts +284 -34
- package/server/provider/mcp-transport.ts +2 -1
- package/server/provider/omp-rpc.ts +1785 -267
- package/server/provider/profile-providers.ts +7 -2
- package/server/provider/registration.ts +12 -2
- package/server/provider/security.ts +8 -10
- package/server/provider/session-descriptors.ts +97 -29
- package/server/provider/session.ts +212 -59
- package/server/provider/subsessions.ts +334 -74
- package/server/provider/timeline-projector.ts +125 -32
- package/server/support-diagnostics.ts +284 -0
- package/shared/omp-models.ts +49 -0
- package/shared/omp-settings.ts +227 -3
- package/shared/support-diagnostics.ts +32 -0
- package/CHANGELOG.md +0 -113
- package/SUPPORT.md +0 -44
- package/TESTING.md +0 -150
- package/docs/alpha-release-checklist.md +0 -68
- package/docs/configuration.md +0 -126
- package/docs/core-provider-issue-audit.md +0 -109
- package/docs/images/mcp-authorization-compact.png +0 -0
- package/docs/images/mcp-controls-wide.png +0 -0
- package/docs/images/plugin-manager.png +0 -0
- package/docs/images/workspace-settings.png +0 -0
- package/docs/installation.md +0 -89
- package/tsconfig.json +0 -16
|
@@ -11,7 +11,19 @@ import type {
|
|
|
11
11
|
ProviderUsage,
|
|
12
12
|
} from "@getpaseo/plugin/server/provider";
|
|
13
13
|
import { getForgeDefinitionOrNeutral } from "@getpaseo/protocol/forge-manifest";
|
|
14
|
-
import {
|
|
14
|
+
import type {
|
|
15
|
+
OmpOperationalFailure,
|
|
16
|
+
OmpOperationalFailureReporter,
|
|
17
|
+
} from "../operational-failure-diagnostics";
|
|
18
|
+
import {
|
|
19
|
+
mapOmpModels,
|
|
20
|
+
nativeOmpModelId,
|
|
21
|
+
OMP_MODES,
|
|
22
|
+
ompModelId,
|
|
23
|
+
selectOmpModels,
|
|
24
|
+
thinkingForModel,
|
|
25
|
+
validateOmpModelIdentities,
|
|
26
|
+
} from "./catalog";
|
|
15
27
|
import {
|
|
16
28
|
normalizeOmpSessionConfig,
|
|
17
29
|
type OmpRecoveryOptions,
|
|
@@ -21,6 +33,7 @@ import { OmpHostToolsBridge, type OmpMcpConnector, validateOmpHostToolConfig } f
|
|
|
21
33
|
import { isOmpImageMimeType, isValidImagePayload, OmpImageMaterializer } from "./image";
|
|
22
34
|
import type {
|
|
23
35
|
OmpAvailableCommand,
|
|
36
|
+
OmpBranchResult,
|
|
24
37
|
OmpCompactionResult,
|
|
25
38
|
OmpExtensionUiResponse,
|
|
26
39
|
OmpImage,
|
|
@@ -35,7 +48,7 @@ import type {
|
|
|
35
48
|
OmpToolApprovalCancel,
|
|
36
49
|
OmpToolApprovalRequest,
|
|
37
50
|
} from "./omp-rpc";
|
|
38
|
-
import { buildOmpSpawnRequest } from "./omp-rpc";
|
|
51
|
+
import { buildOmpSpawnRequest, OmpRpcRequestRejectedError } from "./omp-rpc";
|
|
39
52
|
import {
|
|
40
53
|
BoundedStringSet,
|
|
41
54
|
boundedJsonBytes,
|
|
@@ -283,6 +296,7 @@ type ActiveTurn = {
|
|
|
283
296
|
terminalizing: boolean;
|
|
284
297
|
terminalization?: Promise<void>;
|
|
285
298
|
terminalOutcome?: TurnOutcome;
|
|
299
|
+
operationalTerminalStage?: "failed" | "unresolved";
|
|
286
300
|
terminalWake?: VoidDeferred;
|
|
287
301
|
steerReady: VoidDeferred;
|
|
288
302
|
steersInFlight: number;
|
|
@@ -938,6 +952,8 @@ export class OmpProviderSession {
|
|
|
938
952
|
private readonly pendingToolPermissions = new Map<string, PendingToolPermission>();
|
|
939
953
|
private readonly inFlightToolPermissions = new Map<string, PendingToolPermission>();
|
|
940
954
|
private readonly resolvedToolApprovalIds = new BoundedStringSet(MAX_TRACKED_ENTRY_IDS);
|
|
955
|
+
private unsupportedThinkingNoticePending = false;
|
|
956
|
+
private unsupportedThinkingNoticePublished = false;
|
|
941
957
|
|
|
942
958
|
private constructor(
|
|
943
959
|
id: string,
|
|
@@ -962,6 +978,7 @@ export class OmpProviderSession {
|
|
|
962
978
|
private readonly transitionNativeSession: NativeSessionTransition,
|
|
963
979
|
private readonly quarantineRewindCleanup: RewindCleanupQuarantine,
|
|
964
980
|
private readonly retireRewindSession: RewindSessionRetirement,
|
|
981
|
+
private readonly reportOperationalFailure: OmpOperationalFailureReporter,
|
|
965
982
|
scheduler: OmpTimelineScheduler = defaultOmpTimelineScheduler,
|
|
966
983
|
) {
|
|
967
984
|
this.id = id;
|
|
@@ -995,6 +1012,13 @@ export class OmpProviderSession {
|
|
|
995
1012
|
: null;
|
|
996
1013
|
this.bindRuntime(runtime);
|
|
997
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
|
+
}
|
|
998
1022
|
get persistenceSessionId(): string | undefined {
|
|
999
1023
|
return this.persistSession ? this.nativeSessionId : undefined;
|
|
1000
1024
|
}
|
|
@@ -1021,6 +1045,7 @@ export class OmpProviderSession {
|
|
|
1021
1045
|
environment?: NodeJS.ProcessEnv,
|
|
1022
1046
|
mcpConnector?: OmpMcpConnector,
|
|
1023
1047
|
mcpInitializationTimeoutMs?: number,
|
|
1048
|
+
reportOperationalFailure: OmpOperationalFailureReporter = () => {},
|
|
1024
1049
|
): Promise<OmpProviderSession> {
|
|
1025
1050
|
const resumeSessionId = ompPersistenceSessionId(input);
|
|
1026
1051
|
if (resumeSessionId && !input.config.persist) {
|
|
@@ -1056,10 +1081,9 @@ export class OmpProviderSession {
|
|
|
1056
1081
|
}
|
|
1057
1082
|
const startOptions: OmpStartOptions = {
|
|
1058
1083
|
...normalizedConfig,
|
|
1059
|
-
//
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
: {}),
|
|
1084
|
+
// Thinking is authorized only after this runtime reports its exact model catalog.
|
|
1085
|
+
thinkingOption: undefined,
|
|
1086
|
+
...(resumeSessionId ? { systemPrompt: undefined, resumeSessionId } : {}),
|
|
1063
1087
|
signal,
|
|
1064
1088
|
environment,
|
|
1065
1089
|
};
|
|
@@ -1068,6 +1092,7 @@ export class OmpProviderSession {
|
|
|
1068
1092
|
connectMcp: mcpConnector,
|
|
1069
1093
|
signal,
|
|
1070
1094
|
initializationTimeoutMs: mcpInitializationTimeoutMs,
|
|
1095
|
+
reportOperationalFailure,
|
|
1071
1096
|
});
|
|
1072
1097
|
let native: OmpRuntimeSession | undefined;
|
|
1073
1098
|
let cleanupNativeSessionId: string | undefined;
|
|
@@ -1095,6 +1120,7 @@ export class OmpProviderSession {
|
|
|
1095
1120
|
() => ({ available: false, commands: [] }),
|
|
1096
1121
|
),
|
|
1097
1122
|
]);
|
|
1123
|
+
validateOmpModelIdentities(nativeModels);
|
|
1098
1124
|
if (effectiveConfig.persist && !native.canReplayHistory) {
|
|
1099
1125
|
throw new OmpPublicError("OMP session persistence requires negotiated RPC protocol v2");
|
|
1100
1126
|
}
|
|
@@ -1108,12 +1134,10 @@ export class OmpProviderSession {
|
|
|
1108
1134
|
if (resumeSessionId && initialState.sessionId !== resumeSessionId) {
|
|
1109
1135
|
throw new OmpPublicError("OMP resumed a different native session");
|
|
1110
1136
|
}
|
|
1111
|
-
const models = mapOmpModels(nativeModels, new OmpPublicDataSerializer(outputRedactionValues));
|
|
1112
|
-
const nativeModelsByPublicId = new Map(
|
|
1113
|
-
nativeModels.map((model) => [ompModelId(model), model] as const),
|
|
1114
|
-
);
|
|
1115
1137
|
if (!resumeSessionId && input.config.model) {
|
|
1116
|
-
const selected =
|
|
1138
|
+
const selected = selectOmpModels(nativeModels, state.model).find(
|
|
1139
|
+
(model) => ompModelId(model) === input.config.model,
|
|
1140
|
+
);
|
|
1117
1141
|
if (!selected) {
|
|
1118
1142
|
throw new OmpPublicError("OMP model is not advertised by the configured session runtime");
|
|
1119
1143
|
}
|
|
@@ -1122,9 +1146,29 @@ export class OmpProviderSession {
|
|
|
1122
1146
|
state = await native.getState();
|
|
1123
1147
|
}
|
|
1124
1148
|
}
|
|
1149
|
+
let currentModel = state.model
|
|
1150
|
+
? nativeModels.find(
|
|
1151
|
+
(model) => model.provider === state.model?.provider && model.id === state.model.id,
|
|
1152
|
+
)
|
|
1153
|
+
: undefined;
|
|
1154
|
+
if (state.model && !currentModel) {
|
|
1155
|
+
throw new OmpPublicError("OMP runtime selected an unadvertised model");
|
|
1156
|
+
}
|
|
1157
|
+
if (!resumeSessionId && input.config.thinkingOption !== undefined) {
|
|
1158
|
+
if (
|
|
1159
|
+
!thinkingForModel(currentModel).some(
|
|
1160
|
+
(option) => option.id === input.config.thinkingOption,
|
|
1161
|
+
)
|
|
1162
|
+
) {
|
|
1163
|
+
throw new OmpPublicError("OMP thinking level is unavailable for the selected model");
|
|
1164
|
+
}
|
|
1165
|
+
if (state.thinkingLevel !== input.config.thinkingOption) {
|
|
1166
|
+
await native.setThinkingLevel(input.config.thinkingOption);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1125
1169
|
const reconciledConfigRevision = bootstrapConfigRevision;
|
|
1126
1170
|
state = await native.getState();
|
|
1127
|
-
|
|
1171
|
+
currentModel = state.model
|
|
1128
1172
|
? nativeModels.find(
|
|
1129
1173
|
(model) => model.provider === state.model?.provider && model.id === state.model.id,
|
|
1130
1174
|
)
|
|
@@ -1132,21 +1176,21 @@ export class OmpProviderSession {
|
|
|
1132
1176
|
if (state.model && !currentModel) {
|
|
1133
1177
|
throw new OmpPublicError("OMP runtime selected an unadvertised model");
|
|
1134
1178
|
}
|
|
1179
|
+
const selectedNativeModels = selectOmpModels(nativeModels, state.model);
|
|
1180
|
+
const models = mapOmpModels(
|
|
1181
|
+
selectedNativeModels,
|
|
1182
|
+
new OmpPublicDataSerializer(outputRedactionValues),
|
|
1183
|
+
);
|
|
1184
|
+
const nativeModelsByPublicId = new Map(
|
|
1185
|
+
nativeModels.map((model) => [ompModelId(model), model] as const),
|
|
1186
|
+
);
|
|
1135
1187
|
const thinkingOptions = thinkingForModel(currentModel);
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
throw new OmpPublicError("OMP thinking level is unavailable for the selected model");
|
|
1143
|
-
}
|
|
1144
|
-
if (
|
|
1145
|
-
committedThinkingLevel &&
|
|
1146
|
-
!thinkingOptions.some((option) => option.id === committedThinkingLevel)
|
|
1147
|
-
) {
|
|
1148
|
-
throw new OmpPublicError("OMP runtime selected an unsupported thinking level");
|
|
1149
|
-
}
|
|
1188
|
+
const applicableLevel = applicableThinkingLevel(currentModel, state.thinkingLevel);
|
|
1189
|
+
const committedThinkingLevel = thinkingOptions.some((option) => option.id === applicableLevel)
|
|
1190
|
+
? applicableLevel
|
|
1191
|
+
: undefined;
|
|
1192
|
+
const unsupportedThinkingLevel =
|
|
1193
|
+
applicableLevel !== undefined && committedThinkingLevel === undefined;
|
|
1150
1194
|
const configState: ProviderConfigState = {
|
|
1151
1195
|
...(state.model ? { model: ompModelId(state.model) } : {}),
|
|
1152
1196
|
mode: normalizedConfig.mode ?? "full",
|
|
@@ -1207,8 +1251,10 @@ export class OmpProviderSession {
|
|
|
1207
1251
|
transitionNativeSession,
|
|
1208
1252
|
quarantineRewindCleanup,
|
|
1209
1253
|
retireRewindSession,
|
|
1254
|
+
reportOperationalFailure,
|
|
1210
1255
|
scheduler,
|
|
1211
1256
|
);
|
|
1257
|
+
session.unsupportedThinkingNoticePending = unsupportedThinkingLevel;
|
|
1212
1258
|
|
|
1213
1259
|
if (bootstrapConfigRevision !== reconciledConfigRevision) {
|
|
1214
1260
|
session.configRefreshDirty = true;
|
|
@@ -1267,6 +1313,7 @@ export class OmpProviderSession {
|
|
|
1267
1313
|
: {}),
|
|
1268
1314
|
...(this.config.title ? { title: this.dataFilter.text(this.config.title, 256) } : {}),
|
|
1269
1315
|
});
|
|
1316
|
+
this.publishUnsupportedThinkingNotice();
|
|
1270
1317
|
this.emit({ type: "session.config", sessionId: this.id, config: this.configState });
|
|
1271
1318
|
if (this.replayHistoryOnOpen) await this.replayHistory(true);
|
|
1272
1319
|
this.publishCommands(this.commandCatalog);
|
|
@@ -1274,6 +1321,22 @@ export class OmpProviderSession {
|
|
|
1274
1321
|
this.readyPublished = true;
|
|
1275
1322
|
if (this.configRefreshDirty) this.scheduleCommittedConfigRefresh();
|
|
1276
1323
|
}
|
|
1324
|
+
private publishUnsupportedThinkingNotice(): void {
|
|
1325
|
+
if (!this.unsupportedThinkingNoticePending || this.unsupportedThinkingNoticePublished) return;
|
|
1326
|
+
this.unsupportedThinkingNoticePending = false;
|
|
1327
|
+
this.unsupportedThinkingNoticePublished = true;
|
|
1328
|
+
this.emit({
|
|
1329
|
+
type: "session.notice",
|
|
1330
|
+
sessionId: this.id,
|
|
1331
|
+
notice: {
|
|
1332
|
+
id: "omp:unsupported-thinking-level",
|
|
1333
|
+
severity: "warning",
|
|
1334
|
+
title: "OMP thinking level unavailable",
|
|
1335
|
+
description:
|
|
1336
|
+
"OMP reported a thinking level that the active model does not advertise. Paseo omitted it from the session configuration.",
|
|
1337
|
+
},
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1277
1340
|
|
|
1278
1341
|
private usageFrom(
|
|
1279
1342
|
state: OmpSessionState | undefined,
|
|
@@ -1592,8 +1655,24 @@ export class OmpProviderSession {
|
|
|
1592
1655
|
replay.signal,
|
|
1593
1656
|
);
|
|
1594
1657
|
messages = transcript.messages;
|
|
1658
|
+
if (transcript.imageReplayWarning) {
|
|
1659
|
+
this.emit({
|
|
1660
|
+
type: "timeline.item",
|
|
1661
|
+
sessionId: this.id,
|
|
1662
|
+
item: {
|
|
1663
|
+
id: "omp:replay-image-unavailable",
|
|
1664
|
+
type: "notification",
|
|
1665
|
+
level: "warning",
|
|
1666
|
+
message: "OMP skipped one or more unavailable images while replaying this session.",
|
|
1667
|
+
},
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1595
1670
|
} catch (error) {
|
|
1596
1671
|
if (replay.signal.aborted) throw error;
|
|
1672
|
+
this.recordOperationalFailure({
|
|
1673
|
+
category: "replay-recovery",
|
|
1674
|
+
stage: "persisted-replay",
|
|
1675
|
+
});
|
|
1597
1676
|
this.emit({
|
|
1598
1677
|
type: "timeline.item",
|
|
1599
1678
|
sessionId: this.id,
|
|
@@ -1606,7 +1685,7 @@ export class OmpProviderSession {
|
|
|
1606
1685
|
});
|
|
1607
1686
|
}
|
|
1608
1687
|
}
|
|
1609
|
-
messages ??= await waitForReplay(this.runtime.getMessages(), replay.signal);
|
|
1688
|
+
messages ??= await waitForReplay(this.runtime.getMessages(replay.signal), replay.signal);
|
|
1610
1689
|
this.quarantineBranchEntries();
|
|
1611
1690
|
for (const message of messages) {
|
|
1612
1691
|
replay.signal.throwIfAborted();
|
|
@@ -1667,19 +1746,33 @@ export class OmpProviderSession {
|
|
|
1667
1746
|
throw new OmpPublicError("OMP conversation rewind requires negotiated RPC protocol v2");
|
|
1668
1747
|
}
|
|
1669
1748
|
const entryId = this.projector.resolveRevertToken(input.token);
|
|
1670
|
-
const
|
|
1671
|
-
runtime.getBranchMessages(),
|
|
1672
|
-
runtime.getState(),
|
|
1673
|
-
]);
|
|
1749
|
+
const beforeState = await runtime.getState();
|
|
1674
1750
|
this.requireCurrentRuntime(runtime, generation);
|
|
1675
1751
|
if (this.activeTurn || beforeState.isStreaming || beforeState.isCompacting) {
|
|
1676
1752
|
throw new OmpPublicError("Cannot rewind the OMP conversation while a turn is active");
|
|
1677
1753
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1754
|
+
const beforeAdvertisedModel = beforeState.model
|
|
1755
|
+
? this.nativeModelsByPublicId.get(ompModelId(beforeState.model))
|
|
1756
|
+
: undefined;
|
|
1757
|
+
if (beforeState.model && !beforeAdvertisedModel) {
|
|
1758
|
+
throw new OmpPublicError("OMP runtime selected an unadvertised model");
|
|
1680
1759
|
}
|
|
1760
|
+
const restorableThinkingLevel = thinkingForModel(beforeAdvertisedModel).some(
|
|
1761
|
+
(option) => option.id === beforeState.thinkingLevel,
|
|
1762
|
+
)
|
|
1763
|
+
? beforeState.thinkingLevel
|
|
1764
|
+
: undefined;
|
|
1681
1765
|
branchMutationPossible = true;
|
|
1682
|
-
|
|
1766
|
+
let result: OmpBranchResult;
|
|
1767
|
+
try {
|
|
1768
|
+
result = await runtime.branch(entryId);
|
|
1769
|
+
} catch (error) {
|
|
1770
|
+
if (error instanceof OmpRpcRequestRejectedError) {
|
|
1771
|
+
branchMutationPossible = false;
|
|
1772
|
+
throw new OmpPublicError("OMP conversation rewind token is stale");
|
|
1773
|
+
}
|
|
1774
|
+
throw error;
|
|
1775
|
+
}
|
|
1683
1776
|
this.requireCurrentRuntime(runtime, generation);
|
|
1684
1777
|
if (result.cancelled) {
|
|
1685
1778
|
branchMutationPossible = false;
|
|
@@ -1711,13 +1804,10 @@ export class OmpProviderSession {
|
|
|
1711
1804
|
}
|
|
1712
1805
|
await runtime.setModel(beforeState.model.provider, beforeState.model.id);
|
|
1713
1806
|
}
|
|
1714
|
-
if (thinkingChanged) {
|
|
1715
|
-
|
|
1716
|
-
throw new OmpPublicError("OMP changed thinking level while rewinding the conversation");
|
|
1717
|
-
}
|
|
1718
|
-
await runtime.setThinkingLevel(beforeState.thinkingLevel);
|
|
1807
|
+
if (thinkingChanged && restorableThinkingLevel) {
|
|
1808
|
+
await runtime.setThinkingLevel(restorableThinkingLevel);
|
|
1719
1809
|
}
|
|
1720
|
-
if (modelChanged || thinkingChanged) {
|
|
1810
|
+
if (modelChanged || (thinkingChanged && restorableThinkingLevel)) {
|
|
1721
1811
|
state = await runtime.getState();
|
|
1722
1812
|
this.requireCurrentRuntime(runtime, generation);
|
|
1723
1813
|
}
|
|
@@ -1725,7 +1815,7 @@ export class OmpProviderSession {
|
|
|
1725
1815
|
state.sessionId !== this.nativeSessionId ||
|
|
1726
1816
|
state.model?.provider !== beforeState.model?.provider ||
|
|
1727
1817
|
state.model?.id !== beforeState.model?.id ||
|
|
1728
|
-
state.thinkingLevel !==
|
|
1818
|
+
(restorableThinkingLevel !== undefined && state.thinkingLevel !== restorableThinkingLevel)
|
|
1729
1819
|
) {
|
|
1730
1820
|
throw new OmpPublicError("OMP did not preserve session configuration while rewinding");
|
|
1731
1821
|
}
|
|
@@ -1737,6 +1827,7 @@ export class OmpProviderSession {
|
|
|
1737
1827
|
this.publishCommittedConfig(state, runtime, generation);
|
|
1738
1828
|
this.emit({ type: "request.completed", requestId: input.requestId });
|
|
1739
1829
|
} catch (error) {
|
|
1830
|
+
this.recordOperationalFailure({ category: "replay-recovery", stage: "rewind" });
|
|
1740
1831
|
const failure = branchMutationPossible
|
|
1741
1832
|
? { message: "OMP conversation rewind left native state indeterminate" }
|
|
1742
1833
|
: providerError(error, "OMP conversation rewind failed");
|
|
@@ -2325,7 +2416,10 @@ export class OmpProviderSession {
|
|
|
2325
2416
|
const targetModel = targetModelId
|
|
2326
2417
|
? this.nativeModelsByPublicId.get(targetModelId)
|
|
2327
2418
|
: undefined;
|
|
2328
|
-
|
|
2419
|
+
const targetModelPublished = this.configState.models.some(
|
|
2420
|
+
(model) => model.id === targetModelId,
|
|
2421
|
+
);
|
|
2422
|
+
if (input.changes.model !== undefined && (!targetModel || !targetModelPublished)) {
|
|
2329
2423
|
throw new OmpPublicError("OMP model selection is unavailable");
|
|
2330
2424
|
}
|
|
2331
2425
|
if (
|
|
@@ -2572,13 +2666,18 @@ export class OmpProviderSession {
|
|
|
2572
2666
|
throw new OmpCatalogEscape("OMP runtime selected an unadvertised model");
|
|
2573
2667
|
}
|
|
2574
2668
|
const thinkingOptions = thinkingForModel(advertisedModel);
|
|
2575
|
-
const
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
) {
|
|
2580
|
-
|
|
2669
|
+
const applicableLevel = applicableThinkingLevel(advertisedModel, state.thinkingLevel);
|
|
2670
|
+
const committedThinkingLevel = thinkingOptions.some((option) => option.id === applicableLevel)
|
|
2671
|
+
? applicableLevel
|
|
2672
|
+
: undefined;
|
|
2673
|
+
if (applicableLevel !== undefined && committedThinkingLevel === undefined) {
|
|
2674
|
+
this.unsupportedThinkingNoticePending = true;
|
|
2675
|
+
this.publishUnsupportedThinkingNotice();
|
|
2581
2676
|
}
|
|
2677
|
+
const models = mapOmpModels(
|
|
2678
|
+
selectOmpModels([...this.nativeModelsByPublicId.values()], state.model),
|
|
2679
|
+
this.dataFilter,
|
|
2680
|
+
);
|
|
2582
2681
|
this.configRefreshAttempts = 0;
|
|
2583
2682
|
this.cancelConfigRefreshRetry();
|
|
2584
2683
|
const nextConfig: ProviderConfigState = {
|
|
@@ -2587,12 +2686,15 @@ export class OmpProviderSession {
|
|
|
2587
2686
|
...(committedThinkingLevel
|
|
2588
2687
|
? { thinkingOption: committedThinkingLevel }
|
|
2589
2688
|
: { thinkingOption: undefined }),
|
|
2689
|
+
models,
|
|
2590
2690
|
thinkingOptions,
|
|
2591
2691
|
};
|
|
2592
2692
|
const changed =
|
|
2593
2693
|
force ||
|
|
2594
2694
|
nextConfig.model !== this.configState.model ||
|
|
2595
2695
|
nextConfig.thinkingOption !== this.configState.thinkingOption ||
|
|
2696
|
+
nextConfig.models.some((model, index) => model.id !== this.configState.models[index]?.id) ||
|
|
2697
|
+
this.configState.models.length !== nextConfig.models.length ||
|
|
2596
2698
|
nextConfig.thinkingOptions.length !== this.configState.thinkingOptions.length ||
|
|
2597
2699
|
nextConfig.thinkingOptions.some(
|
|
2598
2700
|
(option, index) =>
|
|
@@ -2843,7 +2945,13 @@ export class OmpProviderSession {
|
|
|
2843
2945
|
|
|
2844
2946
|
private async recoverRuntime(): Promise<void> {
|
|
2845
2947
|
if (!this.runtimeDead) return;
|
|
2846
|
-
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
|
+
});
|
|
2847
2955
|
try {
|
|
2848
2956
|
await this.recoveryPromise;
|
|
2849
2957
|
} finally {
|
|
@@ -2913,13 +3021,6 @@ export class OmpProviderSession {
|
|
|
2913
3021
|
if (state.model && !advertisedModel) {
|
|
2914
3022
|
throw new Error("OMP recovered with an unadvertised model");
|
|
2915
3023
|
}
|
|
2916
|
-
const recoveredThinkingLevel = applicableThinkingLevel(advertisedModel, state.thinkingLevel);
|
|
2917
|
-
if (
|
|
2918
|
-
recoveredThinkingLevel &&
|
|
2919
|
-
!thinkingForModel(advertisedModel).some((option) => option.id === recoveredThinkingLevel)
|
|
2920
|
-
) {
|
|
2921
|
-
throw new Error("OMP recovered with an unsupported thinking level");
|
|
2922
|
-
}
|
|
2923
3024
|
if (this.closed) throw new Error("OMP session closed while runtime recovery was pending");
|
|
2924
3025
|
if (!this.hostTools.isBoundTo(recovered)) {
|
|
2925
3026
|
throw new Error("OMP host tool bridge detached during recovery");
|
|
@@ -3073,6 +3174,10 @@ export class OmpProviderSession {
|
|
|
3073
3174
|
try {
|
|
3074
3175
|
this.subsessions?.handle(event);
|
|
3075
3176
|
} catch {
|
|
3177
|
+
this.recordOperationalFailure({
|
|
3178
|
+
category: "tool-projector",
|
|
3179
|
+
stage: "subsession-projector",
|
|
3180
|
+
});
|
|
3076
3181
|
this.handleRuntimeFailure("OMP subagent event processing failed");
|
|
3077
3182
|
}
|
|
3078
3183
|
return;
|
|
@@ -3129,6 +3234,17 @@ export class OmpProviderSession {
|
|
|
3129
3234
|
this.handleRuntimeFailure();
|
|
3130
3235
|
return;
|
|
3131
3236
|
}
|
|
3237
|
+
if (
|
|
3238
|
+
event.type === "config_warnings_changed" ||
|
|
3239
|
+
event.type === "advisor_cost_changed" ||
|
|
3240
|
+
event.type === "ttsr_triggered"
|
|
3241
|
+
) {
|
|
3242
|
+
return;
|
|
3243
|
+
}
|
|
3244
|
+
if (event.type === "irc_message") {
|
|
3245
|
+
this.projector.projectPassive(event);
|
|
3246
|
+
return;
|
|
3247
|
+
}
|
|
3132
3248
|
if (
|
|
3133
3249
|
event.type === "notice" ||
|
|
3134
3250
|
event.type === "todo_reminder" ||
|
|
@@ -3307,6 +3423,10 @@ export class OmpProviderSession {
|
|
|
3307
3423
|
try {
|
|
3308
3424
|
this.subsessions?.observeSessionEvent(this.id, event);
|
|
3309
3425
|
} catch {
|
|
3426
|
+
this.recordOperationalFailure({
|
|
3427
|
+
category: "tool-projector",
|
|
3428
|
+
stage: "subsession-projector",
|
|
3429
|
+
});
|
|
3310
3430
|
this.handleRuntimeFailure("OMP subagent dispatch tracking failed");
|
|
3311
3431
|
return;
|
|
3312
3432
|
}
|
|
@@ -3363,7 +3483,15 @@ export class OmpProviderSession {
|
|
|
3363
3483
|
}
|
|
3364
3484
|
}
|
|
3365
3485
|
if (isNativeTurnActivity(event)) this.markAgentEvidence(turn);
|
|
3366
|
-
|
|
3486
|
+
try {
|
|
3487
|
+
this.projector.project(event, turn.turnId);
|
|
3488
|
+
} catch (error) {
|
|
3489
|
+
this.recordOperationalFailure({
|
|
3490
|
+
category: "tool-projector",
|
|
3491
|
+
stage: "timeline-projector",
|
|
3492
|
+
});
|
|
3493
|
+
throw error;
|
|
3494
|
+
}
|
|
3367
3495
|
if (event.type === "tool_execution_end") this.resumeDeferredAgentEnd();
|
|
3368
3496
|
}
|
|
3369
3497
|
|
|
@@ -4432,11 +4560,13 @@ export class OmpProviderSession {
|
|
|
4432
4560
|
const state = await this.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
4433
4561
|
if (turn.terminal || this.activeTurn !== turn || turn.deferredAgentEnd !== candidate) return;
|
|
4434
4562
|
if (!state) {
|
|
4563
|
+
turn.operationalTerminalStage = "unresolved";
|
|
4435
4564
|
this.handleRuntimeFailure("OMP agent_end state could not be confirmed");
|
|
4436
4565
|
return;
|
|
4437
4566
|
}
|
|
4438
4567
|
turn.deferredAgentEnd = undefined;
|
|
4439
4568
|
if (state.isStreaming || state.isCompacting) return;
|
|
4569
|
+
turn.operationalTerminalStage = "unresolved";
|
|
4440
4570
|
await this.finishTurn(turn, "failed", {
|
|
4441
4571
|
message: "OMP unkeyed agent_end could not be correlated to the current prompt",
|
|
4442
4572
|
});
|
|
@@ -4517,6 +4647,7 @@ export class OmpProviderSession {
|
|
|
4517
4647
|
) {
|
|
4518
4648
|
void this.completeAgentEnd(turn, candidate.event);
|
|
4519
4649
|
} else {
|
|
4650
|
+
turn.operationalTerminalStage = "unresolved";
|
|
4520
4651
|
this.handleRuntimeFailure("OMP agent_end state could not be confirmed");
|
|
4521
4652
|
}
|
|
4522
4653
|
}, AGENT_END_SETTLE_MS);
|
|
@@ -4541,8 +4672,13 @@ export class OmpProviderSession {
|
|
|
4541
4672
|
turn.terminalizing = false;
|
|
4542
4673
|
turn.deferredAgentEnd = candidate;
|
|
4543
4674
|
void this.subsessions.reconcile(this.runtime).catch(() => {
|
|
4675
|
+
this.recordOperationalFailure({
|
|
4676
|
+
category: "tool-projector",
|
|
4677
|
+
stage: "subsession-projector",
|
|
4678
|
+
});
|
|
4544
4679
|
if (!turn.terminal && this.activeTurn === turn) {
|
|
4545
|
-
this.
|
|
4680
|
+
this.subsessions?.terminalize("failed");
|
|
4681
|
+
this.resumeDeferredAgentEnd();
|
|
4546
4682
|
}
|
|
4547
4683
|
});
|
|
4548
4684
|
return true;
|
|
@@ -4625,6 +4761,7 @@ export class OmpProviderSession {
|
|
|
4625
4761
|
return;
|
|
4626
4762
|
}
|
|
4627
4763
|
if (turn.userEchoObserved) {
|
|
4764
|
+
turn.operationalTerminalStage = "unresolved";
|
|
4628
4765
|
this.handleRuntimeFailure("OMP agent_end state could not be confirmed");
|
|
4629
4766
|
return;
|
|
4630
4767
|
}
|
|
@@ -4684,6 +4821,7 @@ export class OmpProviderSession {
|
|
|
4684
4821
|
await this.finishTurn(turn, "completed");
|
|
4685
4822
|
return;
|
|
4686
4823
|
}
|
|
4824
|
+
turn.operationalTerminalStage = outcome === "failed" ? "failed" : "unresolved";
|
|
4687
4825
|
const error =
|
|
4688
4826
|
outcome === "failed" ? "OMP assistant turn failed" : unknownTerminalOutcomeError(event, turn);
|
|
4689
4827
|
this.subsessions?.terminalize("failed");
|
|
@@ -4826,6 +4964,12 @@ export class OmpProviderSession {
|
|
|
4826
4964
|
usageSampled: true,
|
|
4827
4965
|
};
|
|
4828
4966
|
}
|
|
4967
|
+
if (outcome.state === "failed") {
|
|
4968
|
+
this.recordOperationalFailure({
|
|
4969
|
+
category: "terminal-outcome",
|
|
4970
|
+
stage: turn.operationalTerminalStage ?? "failed",
|
|
4971
|
+
});
|
|
4972
|
+
}
|
|
4829
4973
|
this.imageMaterializer.clear();
|
|
4830
4974
|
turn.terminal = true;
|
|
4831
4975
|
if (this.usageSample?.turn === turn) this.usageSample = null;
|
|
@@ -4839,7 +4983,15 @@ export class OmpProviderSession {
|
|
|
4839
4983
|
}
|
|
4840
4984
|
this.publishPendingUsers(turn);
|
|
4841
4985
|
this.resolveTurnPermissions(turn.turnId);
|
|
4842
|
-
|
|
4986
|
+
try {
|
|
4987
|
+
this.projector.finishTurn(turn.turnId, preserveCompactions);
|
|
4988
|
+
} catch (error) {
|
|
4989
|
+
this.recordOperationalFailure({
|
|
4990
|
+
category: "tool-projector",
|
|
4991
|
+
stage: "timeline-projector",
|
|
4992
|
+
});
|
|
4993
|
+
throw error;
|
|
4994
|
+
}
|
|
4843
4995
|
this.unclaimedBranchEntries.length = 0;
|
|
4844
4996
|
this.emit({
|
|
4845
4997
|
type: "session.turn",
|
|
@@ -4897,6 +5049,7 @@ export class OmpProviderSession {
|
|
|
4897
5049
|
this.publishPromptResult(turn, { type: "failed", error: { message } });
|
|
4898
5050
|
if (turn.started) void this.finishTurn(turn, "failed", { message }, true, true);
|
|
4899
5051
|
else {
|
|
5052
|
+
this.recordOperationalFailure({ category: "terminal-outcome", stage: "failed" });
|
|
4900
5053
|
turn.steerReady.resolve();
|
|
4901
5054
|
turn.terminal = true;
|
|
4902
5055
|
this.projector.finishTurn(turn.turnId);
|