@omercnet/paseo-omp 5.1.1 → 5.2.0-next.362.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/OVERVIEW.md +1 -0
- package/README.md +2 -2
- package/client/provider-image.tsx +127 -14
- package/client/provider-launch-settings.tsx +46 -2
- package/client/subagents-state.ts +108 -0
- package/client/subagents-strip.tsx +391 -0
- package/index.client.tsx +9 -0
- package/index.server.ts +25 -0
- package/npm-shrinkwrap.json +9 -9
- package/package.json +6 -9
- package/scripts/prepare-dependencies.mjs +7 -10
- package/server/mcp-browser.ts +3 -1
- package/server/package-version.ts +1 -1
- package/server/provider/connection.ts +43 -8
- package/server/provider/omp-rpc-process.ts +9 -9
- package/server/provider/omp-rpc-protocol.ts +40 -2
- package/server/provider/omp-rpc-transport.ts +5 -2
- package/server/provider/omp-rpc.ts +25 -0
- package/server/provider/registration.ts +18 -0
- package/server/provider/session-compaction.ts +29 -25
- package/server/provider/session-descriptors.ts +36 -30
- package/server/provider/session-events.ts +1 -0
- package/server/provider/session-history.ts +2 -0
- package/server/provider/session-limit.ts +32 -0
- package/server/provider/session-terminal.ts +52 -1
- package/server/provider/session.ts +255 -67
- package/server/provider/subsessions.ts +109 -7
- package/server/provider/timeline-projector.ts +41 -26
- package/server/subagents.ts +97 -0
- package/shared/provider-launch-settings.ts +4 -0
- package/shared/subagents.ts +60 -0
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
OmpOperationalFailure,
|
|
14
14
|
OmpOperationalFailureReporter,
|
|
15
15
|
} from "../operational-failure-diagnostics";
|
|
16
|
+
import type { OmpSubagentControlRegistry } from "../subagents";
|
|
16
17
|
import { discoverOmpCatalog } from "./catalog";
|
|
17
18
|
import { normalizeOmpCatalogOptions } from "./config-normalization";
|
|
18
19
|
import type { OmpMcpConnector } from "./host-tools";
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
} from "./security";
|
|
35
36
|
import { type OmpOpenedSession, OmpProviderSession, ompPersistenceSessionId } from "./session";
|
|
36
37
|
import { OmpHistorySession } from "./session-history";
|
|
38
|
+
import { OmpSessionLimit } from "./session-limit";
|
|
37
39
|
import { isOmpHistoryOnlyOpen } from "./session-purpose";
|
|
38
40
|
|
|
39
41
|
type ProviderConfigurationCompat = {
|
|
@@ -69,7 +71,6 @@ const SUPPORTED_INPUTS: Readonly<Record<string, true>> = {
|
|
|
69
71
|
"session.can_stop_runtime": true,
|
|
70
72
|
"session.close": true,
|
|
71
73
|
};
|
|
72
|
-
const MAX_CONNECTION_SESSIONS = 32;
|
|
73
74
|
const MAX_ACTIVE_OPERATIONS = 128;
|
|
74
75
|
const MAX_PROVIDER_INPUT_BYTES = 2 * 1024 * 1024;
|
|
75
76
|
const MAX_NESTED_OPTION_BYTES = 256 * 1024;
|
|
@@ -723,6 +724,8 @@ export function createOmpConnection(
|
|
|
723
724
|
console.error("OMP provider failure", diagnostic),
|
|
724
725
|
reportOperationalFailure: OmpOperationalFailureReporter = () => {},
|
|
725
726
|
resolveHostInheritEnv?: () => Promise<readonly string[]>,
|
|
727
|
+
sessionLimit = new OmpSessionLimit(),
|
|
728
|
+
subagentControlRegistry?: OmpSubagentControlRegistry,
|
|
726
729
|
): ProviderConnection {
|
|
727
730
|
const errorDetails = (error: unknown, fallback: string): { message: string } => {
|
|
728
731
|
if (isOmpPublicError(error)) return { message: error.message };
|
|
@@ -757,6 +760,7 @@ export function createOmpConnection(
|
|
|
757
760
|
session: OmpOpenedSession;
|
|
758
761
|
nativeSessionId?: string;
|
|
759
762
|
removeBrowserAuthorization?: () => void;
|
|
763
|
+
removeSubagentControls?: () => void;
|
|
760
764
|
}
|
|
761
765
|
>();
|
|
762
766
|
const opening = new Map<
|
|
@@ -767,6 +771,13 @@ export function createOmpConnection(
|
|
|
767
771
|
string,
|
|
768
772
|
{ token: symbol; nativeSessionId?: string; cleanup?: Promise<void> }
|
|
769
773
|
>();
|
|
774
|
+
// One session id is held by `sessions`, `opening` and `failedCleanup` at different moments, and
|
|
775
|
+
// by two of them at once while it opens or fails. After a failed close only unresolved cleanup
|
|
776
|
+
// stays counted, until it drains.
|
|
777
|
+
const untrackSessions = sessionLimit.track(() => {
|
|
778
|
+
if (closed) return failedCleanup.size;
|
|
779
|
+
return new Set([...sessions.keys(), ...opening.keys(), ...failedCleanup.keys()]).size;
|
|
780
|
+
});
|
|
770
781
|
const shutdown = new AbortController();
|
|
771
782
|
let catalogCleanup: Promise<void> | null = null;
|
|
772
783
|
const activeOperations = new Set<Promise<void>>();
|
|
@@ -783,6 +794,7 @@ export function createOmpConnection(
|
|
|
783
794
|
if (slot?.token !== token) return false;
|
|
784
795
|
sessions.delete(sessionId);
|
|
785
796
|
slot.removeBrowserAuthorization?.();
|
|
797
|
+
slot.removeSubagentControls?.();
|
|
786
798
|
return true;
|
|
787
799
|
};
|
|
788
800
|
|
|
@@ -809,7 +821,9 @@ export function createOmpConnection(
|
|
|
809
821
|
...(quarantinedNativeSessionId ? { nativeSessionId: quarantinedNativeSessionId } : {}),
|
|
810
822
|
});
|
|
811
823
|
nativeReservations.quarantine(quarantinedNativeSessionId, token, cleanup, () => {
|
|
812
|
-
if (failedCleanup.get(sessionId)?.token
|
|
824
|
+
if (failedCleanup.get(sessionId)?.token !== token) return;
|
|
825
|
+
failedCleanup.delete(sessionId);
|
|
826
|
+
if (closed && failedCleanup.size === 0) untrackSessions();
|
|
813
827
|
});
|
|
814
828
|
};
|
|
815
829
|
|
|
@@ -879,6 +893,7 @@ export function createOmpConnection(
|
|
|
879
893
|
query: input.query,
|
|
880
894
|
limit: input.limit,
|
|
881
895
|
sessionDir: listingConfig.sessionDir,
|
|
896
|
+
signal: shutdown.signal,
|
|
882
897
|
})
|
|
883
898
|
).map((session) => ({
|
|
884
899
|
persistence: { version: 1, data: { sessionId: session.id } },
|
|
@@ -906,8 +921,13 @@ export function createOmpConnection(
|
|
|
906
921
|
requestFailure(input.requestId, new OmpPublicError("OMP session already exists"));
|
|
907
922
|
return;
|
|
908
923
|
}
|
|
909
|
-
if (
|
|
910
|
-
requestFailure(
|
|
924
|
+
if (sessionLimit.openSessions >= sessionLimit.maxSessions) {
|
|
925
|
+
requestFailure(
|
|
926
|
+
input.requestId,
|
|
927
|
+
new OmpPublicError(
|
|
928
|
+
`OMP session limit reached (${sessionLimit.maxSessions} open sessions). Archive idle agents or raise the session limit in OMP → Plugin.`,
|
|
929
|
+
),
|
|
930
|
+
);
|
|
911
931
|
return;
|
|
912
932
|
}
|
|
913
933
|
const token = Symbol(input.sessionId);
|
|
@@ -978,6 +998,7 @@ export function createOmpConnection(
|
|
|
978
998
|
environment,
|
|
979
999
|
hostInheritEnv,
|
|
980
1000
|
scheduler,
|
|
1001
|
+
controller.signal,
|
|
981
1002
|
)
|
|
982
1003
|
: await OmpProviderSession.open(
|
|
983
1004
|
input,
|
|
@@ -1018,12 +1039,12 @@ export function createOmpConnection(
|
|
|
1018
1039
|
nativeReservations.release(nativeSessionId, token);
|
|
1019
1040
|
return;
|
|
1020
1041
|
}
|
|
1021
|
-
const
|
|
1042
|
+
const agentId = input.config.env.PASEO_AGENT_ID?.trim() || input.sessionId;
|
|
1022
1043
|
// A history session has no live Paseo browser tools to authorize.
|
|
1023
1044
|
const browserAuthorization = historyOnly
|
|
1024
1045
|
? undefined
|
|
1025
1046
|
: browserAuthorizationRegistry?.register(
|
|
1026
|
-
|
|
1047
|
+
agentId,
|
|
1027
1048
|
session.openPaseoBrowser.bind(session),
|
|
1028
1049
|
);
|
|
1029
1050
|
session.setBrowserAuthorizationIssuer(browserAuthorization?.issue ?? null);
|
|
@@ -1033,11 +1054,17 @@ export function createOmpConnection(
|
|
|
1033
1054
|
browserAuthorization.remove();
|
|
1034
1055
|
}
|
|
1035
1056
|
: undefined;
|
|
1057
|
+
// A history session has no runtime, so it has no subagents to control.
|
|
1058
|
+
const removeSubagentControls =
|
|
1059
|
+
session instanceof OmpProviderSession
|
|
1060
|
+
? subagentControlRegistry?.register(agentId, session)
|
|
1061
|
+
: undefined;
|
|
1036
1062
|
sessions.set(input.sessionId, {
|
|
1037
1063
|
token,
|
|
1038
1064
|
session,
|
|
1039
1065
|
nativeSessionId,
|
|
1040
1066
|
...(removeBrowserAuthorization ? { removeBrowserAuthorization } : {}),
|
|
1067
|
+
...(removeSubagentControls ? { removeSubagentControls } : {}),
|
|
1041
1068
|
});
|
|
1042
1069
|
await session.publishOpened(input.requestId);
|
|
1043
1070
|
if (
|
|
@@ -1189,7 +1216,10 @@ export function createOmpConnection(
|
|
|
1189
1216
|
shutdown.abort(shutdownReason);
|
|
1190
1217
|
for (const { controller } of opening.values()) controller.abort(shutdownReason);
|
|
1191
1218
|
for (const { session } of sessions.values()) session.beginConnectionShutdown();
|
|
1192
|
-
for (const slot of sessions.values())
|
|
1219
|
+
for (const slot of sessions.values()) {
|
|
1220
|
+
slot.removeBrowserAuthorization?.();
|
|
1221
|
+
slot.removeSubagentControls?.();
|
|
1222
|
+
}
|
|
1193
1223
|
const failures: unknown[] = [];
|
|
1194
1224
|
const operationBatch = [...activeOperations];
|
|
1195
1225
|
const operationResults = await Promise.allSettled(operationBatch);
|
|
@@ -1313,7 +1343,12 @@ export function createOmpConnection(
|
|
|
1313
1343
|
return () => listeners.delete(listener);
|
|
1314
1344
|
},
|
|
1315
1345
|
close() {
|
|
1316
|
-
closePromise ??= Promise.resolve()
|
|
1346
|
+
closePromise ??= Promise.resolve()
|
|
1347
|
+
.then(disposeConnection)
|
|
1348
|
+
.then(untrackSessions, (error: unknown) => {
|
|
1349
|
+
if (failedCleanup.size === 0) untrackSessions();
|
|
1350
|
+
throw error;
|
|
1351
|
+
});
|
|
1317
1352
|
return closePromise;
|
|
1318
1353
|
},
|
|
1319
1354
|
};
|
|
@@ -64,12 +64,15 @@ export async function terminatePosixProcessTree(
|
|
|
64
64
|
|
|
65
65
|
export type ProcessTreeCleanup = "verified" | "uncertain" | "failed";
|
|
66
66
|
|
|
67
|
-
export async function stopWindowsTree(
|
|
67
|
+
export async function stopWindowsTree(
|
|
68
|
+
pid: number,
|
|
69
|
+
spawnTaskkill: typeof spawn = spawn,
|
|
70
|
+
): Promise<ProcessTreeCleanup> {
|
|
68
71
|
const result = Promise.withResolvers<ProcessTreeCleanup>();
|
|
69
72
|
const systemRoot = process.env.SystemRoot ?? WINDOWS_DEFAULT_SYSTEM_ROOT;
|
|
70
73
|
let taskkill: ChildProcessWithoutNullStreams;
|
|
71
74
|
try {
|
|
72
|
-
taskkill =
|
|
75
|
+
taskkill = spawnTaskkill(
|
|
73
76
|
join(systemRoot, "System32", "taskkill.exe"),
|
|
74
77
|
["/PID", String(pid), "/T", "/F"],
|
|
75
78
|
{
|
|
@@ -99,13 +102,10 @@ export async function stopWindowsTree(pid: number): Promise<ProcessTreeCleanup>
|
|
|
99
102
|
}, PROCESS_STOP_TIMEOUT_MS);
|
|
100
103
|
taskkill.once("error", () => finish("failed"));
|
|
101
104
|
taskkill.once("close", (code, signal) => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
? "uncertain"
|
|
107
|
-
: "failed",
|
|
108
|
-
);
|
|
105
|
+
// 128 means taskkill found no such process because it already exited; retrying only repeats
|
|
106
|
+
// 128. Callers still wait for the root OMP process's own exit, so a live root cannot pass.
|
|
107
|
+
// Descendants of an already exited root cannot be enumerated by taskkill and are not checked.
|
|
108
|
+
finish((code === 0 || code === 128) && signal === null ? "verified" : "failed");
|
|
109
109
|
});
|
|
110
110
|
return result.promise;
|
|
111
111
|
}
|
|
@@ -71,6 +71,8 @@ export const OMP_SESSION_EVENT_POLICIES = {
|
|
|
71
71
|
tool_execution_end: "normalize",
|
|
72
72
|
auto_compaction_start: "normalize",
|
|
73
73
|
auto_compaction_end: "normalize",
|
|
74
|
+
manual_compaction_start: "normalize",
|
|
75
|
+
manual_compaction_end: "normalize",
|
|
74
76
|
auto_retry_start: "normalize",
|
|
75
77
|
auto_retry_end: "normalize",
|
|
76
78
|
retry_fallback_applied: "normalize",
|
|
@@ -128,6 +130,7 @@ export const OMP_RPC_COMMAND_POLICIES = {
|
|
|
128
130
|
bash: "unsupported",
|
|
129
131
|
abort_bash: "unsupported",
|
|
130
132
|
get_session_stats: "implemented",
|
|
133
|
+
get_session_lease: "unsupported",
|
|
131
134
|
export_html: "unsupported",
|
|
132
135
|
switch_session: "unsupported",
|
|
133
136
|
branch: "implemented",
|
|
@@ -142,15 +145,21 @@ export const OMP_RPC_COMMAND_POLICIES = {
|
|
|
142
145
|
abort_and_restore_queue: "unsupported",
|
|
143
146
|
btw: "unsupported",
|
|
144
147
|
btw_cancel: "unsupported",
|
|
145
|
-
cancel_subagent: "
|
|
148
|
+
cancel_subagent: "implemented",
|
|
146
149
|
fork: "unsupported",
|
|
147
150
|
get_btw_history: "unsupported",
|
|
148
151
|
get_logout_accounts: "unsupported",
|
|
149
152
|
goal: "unsupported",
|
|
150
153
|
live_mute: "unsupported",
|
|
154
|
+
live_connect: "unsupported",
|
|
151
155
|
live_start: "unsupported",
|
|
152
156
|
live_stop: "unsupported",
|
|
153
157
|
logout: "unsupported",
|
|
158
|
+
navigate_tree: "unsupported",
|
|
159
|
+
abort_branch_summary: "unsupported",
|
|
160
|
+
set_label: "unsupported",
|
|
161
|
+
dictation_start: "unsupported",
|
|
162
|
+
dictation_stop: "unsupported",
|
|
154
163
|
predict_word: "unsupported",
|
|
155
164
|
predict_word_feedback: "unsupported",
|
|
156
165
|
promote_queued_message: "unsupported",
|
|
@@ -158,7 +167,7 @@ export const OMP_RPC_COMMAND_POLICIES = {
|
|
|
158
167
|
set_ask_dialog: "implemented",
|
|
159
168
|
set_cache_warming: "unsupported",
|
|
160
169
|
set_slow_mode: "unsupported",
|
|
161
|
-
steer_subagent: "
|
|
170
|
+
steer_subagent: "implemented",
|
|
162
171
|
} as const;
|
|
163
172
|
export const OMP_RPC_RESPONSE_POLICIES = OMP_RPC_COMMAND_POLICIES;
|
|
164
173
|
export const OMP_PROTOCOL_VIOLATION_CATEGORIES = [
|
|
@@ -254,9 +263,14 @@ export const OMP_PROTOCOL_EVENT_TYPES = [
|
|
|
254
263
|
"todo_auto_clear",
|
|
255
264
|
"auto_compaction_start",
|
|
256
265
|
"auto_compaction_end",
|
|
266
|
+
"manual_compaction_start",
|
|
267
|
+
"manual_compaction_end",
|
|
257
268
|
"available_commands_update",
|
|
258
269
|
"notice",
|
|
259
270
|
"command_output",
|
|
271
|
+
"extension_error",
|
|
272
|
+
"session_info_update",
|
|
273
|
+
"config_update",
|
|
260
274
|
"extension_ui_request",
|
|
261
275
|
"prompt_result",
|
|
262
276
|
"host_tool_call",
|
|
@@ -403,6 +417,8 @@ function eventSchemaClassification(
|
|
|
403
417
|
case "cache_warming_start":
|
|
404
418
|
case "cache_warming_end":
|
|
405
419
|
case "queue_update":
|
|
420
|
+
case "session_info_update":
|
|
421
|
+
case "config_update":
|
|
406
422
|
return { reason: "configuration-event-schema", expected: "valid-configuration-event" };
|
|
407
423
|
case "extension_ui_request":
|
|
408
424
|
return { reason: "extension-ui-schema", expected: "valid-extension-ui-event" };
|
|
@@ -1606,6 +1622,17 @@ const OmpAgentSessionEventSchema = z.discriminatedUnion("type", [
|
|
|
1606
1622
|
errorMessage: boundedString(64 * 1024).optional(),
|
|
1607
1623
|
skipped: z.boolean().optional(),
|
|
1608
1624
|
}),
|
|
1625
|
+
z.object({
|
|
1626
|
+
type: z.literal("manual_compaction_start"),
|
|
1627
|
+
mode: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES).optional(),
|
|
1628
|
+
}),
|
|
1629
|
+
z.object({
|
|
1630
|
+
type: z.literal("manual_compaction_end"),
|
|
1631
|
+
result: OmpCompactionResultSchema.optional(),
|
|
1632
|
+
aborted: z.boolean(),
|
|
1633
|
+
skipped: z.boolean().optional(),
|
|
1634
|
+
errorMessage: boundedString(64 * 1024).optional(),
|
|
1635
|
+
}),
|
|
1609
1636
|
z.object({
|
|
1610
1637
|
type: z.literal("notice"),
|
|
1611
1638
|
id: IDENTIFIER.optional(),
|
|
@@ -1799,6 +1826,14 @@ export const OmpRuntimeEventSchema = z.discriminatedUnion("type", [
|
|
|
1799
1826
|
commands: z.array(OmpAvailableCommandSchema).max(MAX_ARRAY_ITEMS),
|
|
1800
1827
|
}),
|
|
1801
1828
|
z.object({ type: z.literal("command_output"), text: TEXT.optional() }),
|
|
1829
|
+
z.object({
|
|
1830
|
+
type: z.literal("extension_error"),
|
|
1831
|
+
extensionPath: boundedString(MAX_PATH_LENGTH),
|
|
1832
|
+
event: NAME,
|
|
1833
|
+
error: boundedString(64 * 1024),
|
|
1834
|
+
}),
|
|
1835
|
+
z.object({ type: z.literal("session_info_update") }),
|
|
1836
|
+
z.object({ type: z.literal("config_update") }),
|
|
1802
1837
|
OmpExtensionUiRequestSchema,
|
|
1803
1838
|
z.object({
|
|
1804
1839
|
type: z.literal("prompt_result"),
|
|
@@ -1967,6 +2002,8 @@ export const OmpSubagentsResultSchema = z.object({
|
|
|
1967
2002
|
)
|
|
1968
2003
|
.max(1_024),
|
|
1969
2004
|
});
|
|
2005
|
+
export const OmpCancelSubagentResultSchema = z.object({ cancelled: z.boolean() });
|
|
2006
|
+
export const OmpSteerSubagentResultSchema = z.object({}).strict().optional();
|
|
1970
2007
|
export const OmpSubagentMessagesResultSchema = z.object({
|
|
1971
2008
|
sessionFile: boundedString(MAX_PATH_LENGTH),
|
|
1972
2009
|
fromByte: z.number().int().nonnegative(),
|
|
@@ -1997,6 +2034,7 @@ export function parseOmpHostToolAgentResult(value: unknown): OmpHostToolResult["
|
|
|
1997
2034
|
export type OmpRpcEvent =
|
|
1998
2035
|
| z.infer<typeof OmpRuntimeEventSchema>
|
|
1999
2036
|
| { type: "prompt_error"; id: string; error: string; code?: string }
|
|
2037
|
+
| { type: "session_settled" }
|
|
2000
2038
|
| { type: "process_exit"; error: string };
|
|
2001
2039
|
export type OmpAgentSessionEvent = z.infer<typeof OmpAgentSessionEventSchema>;
|
|
2002
2040
|
export type OmpSubagentSnapshot = z.infer<typeof OmpSubagentsResultSchema>["subagents"][number];
|
|
@@ -1090,8 +1090,11 @@ export class OmpRpcProcess {
|
|
|
1090
1090
|
});
|
|
1091
1091
|
return;
|
|
1092
1092
|
}
|
|
1093
|
-
// Emitted
|
|
1094
|
-
if (type === "session_settled")
|
|
1093
|
+
// Emitted when the session is quiet: nothing live, queued, or pending in the background.
|
|
1094
|
+
if (type === "session_settled") {
|
|
1095
|
+
this.emit({ type: "session_settled" });
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1095
1098
|
if (type === "ready") {
|
|
1096
1099
|
if (this.readyReceived) {
|
|
1097
1100
|
this.recordProtocolViolation("duplicate-ready", {
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
OmpBranchMessagesResultSchema,
|
|
17
17
|
type OmpBranchResult,
|
|
18
18
|
OmpBranchResultSchema,
|
|
19
|
+
OmpCancelSubagentResultSchema,
|
|
19
20
|
type OmpCompactionResult,
|
|
20
21
|
OmpCompactionResultSchema,
|
|
21
22
|
type OmpExtensionUiResponse,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
OmpSessionStateSchema,
|
|
42
43
|
type OmpSessionStats,
|
|
43
44
|
OmpSessionStatsSchema,
|
|
45
|
+
OmpSteerSubagentResultSchema,
|
|
44
46
|
type OmpSubagentMessagesResult,
|
|
45
47
|
OmpSubagentMessagesResultSchema,
|
|
46
48
|
type OmpSubagentSnapshot,
|
|
@@ -105,6 +107,9 @@ export interface OmpRuntimeSession {
|
|
|
105
107
|
subagentId?: string;
|
|
106
108
|
sessionFile?: string;
|
|
107
109
|
}): Promise<OmpSubagentMessagesResult>;
|
|
110
|
+
/** Resolves `false` when OMP no longer lists the subagent as running for this session. */
|
|
111
|
+
cancelSubagent(subagentId: string): Promise<boolean>;
|
|
112
|
+
steerSubagent(subagentId: string, message: string): Promise<void>;
|
|
108
113
|
prompt(
|
|
109
114
|
message: string,
|
|
110
115
|
images?: readonly OmpImage[],
|
|
@@ -466,6 +471,26 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
466
471
|
);
|
|
467
472
|
}
|
|
468
473
|
|
|
474
|
+
async cancelSubagent(subagentId: string): Promise<boolean> {
|
|
475
|
+
const safeId = validateBoundedText(subagentId, "subagent identifier", MAX_ID_LENGTH);
|
|
476
|
+
return OmpCancelSubagentResultSchema.parse(
|
|
477
|
+
await this.process.request({ type: "cancel_subagent", subagentId: safeId }),
|
|
478
|
+
).cancelled;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async steerSubagent(subagentId: string, message: string): Promise<void> {
|
|
482
|
+
const safeId = validateBoundedText(subagentId, "subagent identifier", MAX_ID_LENGTH);
|
|
483
|
+
const safeMessage = validateBoundedText(message, "subagent steering message", MAX_TEXT_LENGTH);
|
|
484
|
+
if (!safeMessage.trim()) throw new Error("Invalid OMP subagent steering message");
|
|
485
|
+
OmpSteerSubagentResultSchema.parse(
|
|
486
|
+
await this.process.request({
|
|
487
|
+
type: "steer_subagent",
|
|
488
|
+
subagentId: safeId,
|
|
489
|
+
message: safeMessage,
|
|
490
|
+
}),
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
469
494
|
async getBranchMessages(): Promise<Array<{ entryId: string; text: string }>> {
|
|
470
495
|
const result = OmpBranchMessagesResultSchema.parse(
|
|
471
496
|
await this.process.request({ type: "get_branch_messages" }),
|
|
@@ -5,6 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
import type { OmpBrowserAuthorizationRegistry } from "../mcp-browser";
|
|
6
6
|
import type { OmpOperationalFailureReporter } from "../operational-failure-diagnostics";
|
|
7
7
|
import { probeOmpAvailability } from "../provider-diagnostics";
|
|
8
|
+
import type { OmpSubagentControlRegistry } from "../subagents";
|
|
8
9
|
import { mergeOmpInheritedEnvironmentNames } from "./config-normalization";
|
|
9
10
|
import { createOmpConnection, OmpNativeSessionReservations } from "./connection";
|
|
10
11
|
import type { OmpMcpConnector } from "./host-tools";
|
|
@@ -12,6 +13,7 @@ import { OmpRpcRuntime, type OmpRuntime } from "./omp-rpc";
|
|
|
12
13
|
import type { OmpProtocolViolationDiagnostic } from "./omp-rpc-protocol";
|
|
13
14
|
import { parseOmpProviderOptions } from "./provider-options";
|
|
14
15
|
import { boundedJsonBytes } from "./security";
|
|
16
|
+
import { OmpSessionLimit } from "./session-limit";
|
|
15
17
|
import { OmpProviderOptionsSchema } from "./settings";
|
|
16
18
|
import type { OmpTimelineScheduler } from "./timeline-projector";
|
|
17
19
|
|
|
@@ -116,11 +118,14 @@ export interface OmpProviderOptions {
|
|
|
116
118
|
timelineScheduler?: OmpTimelineScheduler;
|
|
117
119
|
replayTimeoutMs?: number;
|
|
118
120
|
environment?: NodeJS.ProcessEnv;
|
|
121
|
+
platform?: NodeJS.Platform;
|
|
119
122
|
/** Fixed, non-secret profile/store identity for providers whose configuration is server-owned. */
|
|
120
123
|
catalogIdentity?: Readonly<Record<string, string>>;
|
|
121
124
|
mcpInitializationTimeoutMs?: number;
|
|
122
125
|
mcpConnector?: OmpMcpConnector;
|
|
123
126
|
browserAuthorizationRegistry?: OmpBrowserAuthorizationRegistry;
|
|
127
|
+
/** Shared by every provider of one plugin instance; each provider has its own otherwise. */
|
|
128
|
+
sessionLimit?: OmpSessionLimit;
|
|
124
129
|
reportProtocolViolation?: (diagnostic: OmpProtocolViolationDiagnostic) => void | Promise<void>;
|
|
125
130
|
reportOperationalFailure?: OmpOperationalFailureReporter;
|
|
126
131
|
availabilityProbe?: (
|
|
@@ -128,6 +133,7 @@ export interface OmpProviderOptions {
|
|
|
128
133
|
timeoutMs: number | undefined,
|
|
129
134
|
) => Promise<ProviderAvailabilityCompat>;
|
|
130
135
|
resolveHostInheritEnv?: () => Promise<readonly string[]>;
|
|
136
|
+
subagentControlRegistry?: OmpSubagentControlRegistry;
|
|
131
137
|
}
|
|
132
138
|
|
|
133
139
|
function stableJson(value: unknown): string {
|
|
@@ -150,6 +156,8 @@ export function createOmpProvider(options: OmpProviderOptions = {}): ProviderReg
|
|
|
150
156
|
reportProtocolViolation: options.reportProtocolViolation,
|
|
151
157
|
});
|
|
152
158
|
const nativeReservations = new OmpNativeSessionReservations();
|
|
159
|
+
const sessionLimit = options.sessionLimit ?? new OmpSessionLimit();
|
|
160
|
+
const platform = options.platform ?? process.platform;
|
|
153
161
|
const checkAvailability: NonNullable<ProviderRegistrationCompat["checkAvailability"]> = async (
|
|
154
162
|
catalogOptions,
|
|
155
163
|
context,
|
|
@@ -159,6 +167,12 @@ export function createOmpProvider(options: OmpProviderOptions = {}): ProviderReg
|
|
|
159
167
|
}
|
|
160
168
|
const providerOptions = parseOmpProviderOptions(catalogOptions.providerOptions);
|
|
161
169
|
const environment = { ...(options.environment ?? process.env), ...providerOptions.env };
|
|
170
|
+
// Windows env names are case-insensitive, but a copy keeps only the literal key (often
|
|
171
|
+
// `Path`), so `environment.PATH` would miss it and OMP would look not installed.
|
|
172
|
+
if (platform === "win32" && environment.PATH === undefined) {
|
|
173
|
+
const pathKey = Object.keys(environment).find((name) => name.toUpperCase() === "PATH");
|
|
174
|
+
if (pathKey !== undefined) environment.PATH = environment[pathKey];
|
|
175
|
+
}
|
|
162
176
|
const configuredCommand: readonly [string, ...string[]] = providerOptions.command?.[0]
|
|
163
177
|
? [providerOptions.command[0], ...providerOptions.command.slice(1)]
|
|
164
178
|
: [environment.OMP_COMMAND || "omp"];
|
|
@@ -167,6 +181,7 @@ export function createOmpProvider(options: OmpProviderOptions = {}): ProviderReg
|
|
|
167
181
|
cwd:
|
|
168
182
|
catalogOptions.scope === "workspace" && catalogOptions.cwd ? catalogOptions.cwd : homedir(),
|
|
169
183
|
environment,
|
|
184
|
+
platform,
|
|
170
185
|
timeoutMs: context?.timeoutMs,
|
|
171
186
|
});
|
|
172
187
|
};
|
|
@@ -216,6 +231,7 @@ export function createOmpProvider(options: OmpProviderOptions = {}): ProviderReg
|
|
|
216
231
|
const capabilities = CAPABILITIES.filter((capability) =>
|
|
217
232
|
requestedCapabilities.has(capability),
|
|
218
233
|
);
|
|
234
|
+
await sessionLimit.refresh();
|
|
219
235
|
return createOmpConnection(
|
|
220
236
|
runtime,
|
|
221
237
|
capabilities,
|
|
@@ -229,6 +245,8 @@ export function createOmpProvider(options: OmpProviderOptions = {}): ProviderReg
|
|
|
229
245
|
undefined,
|
|
230
246
|
options.reportOperationalFailure,
|
|
231
247
|
options.resolveHostInheritEnv,
|
|
248
|
+
sessionLimit,
|
|
249
|
+
options.subagentControlRegistry,
|
|
232
250
|
);
|
|
233
251
|
},
|
|
234
252
|
};
|
|
@@ -7,6 +7,7 @@ import type { OmpSessionUsage } from "./session-usage";
|
|
|
7
7
|
import type { OmpTimelineProjector } from "./timeline-projector";
|
|
8
8
|
|
|
9
9
|
type Emit = (event: ProviderEvent) => void;
|
|
10
|
+
export type CompactionOutcome = "completed" | "failed" | "canceled" | "skipped";
|
|
10
11
|
|
|
11
12
|
export interface ActiveCompaction {
|
|
12
13
|
id: string;
|
|
@@ -73,51 +74,54 @@ export class OmpSessionCompaction {
|
|
|
73
74
|
});
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
handleEnd(
|
|
77
78
|
turn: ActiveTurn,
|
|
78
|
-
event: Extract<OmpRpcEvent, { type: "auto_compaction_end" }>,
|
|
79
|
-
):
|
|
79
|
+
event: Extract<OmpRpcEvent, { type: "auto_compaction_end" | "manual_compaction_end" }>,
|
|
80
|
+
): CompactionOutcome | undefined {
|
|
80
81
|
if (this.discardedEnds > 0) {
|
|
81
82
|
this.discardedEnds -= 1;
|
|
82
|
-
return
|
|
83
|
+
return undefined;
|
|
83
84
|
}
|
|
84
85
|
const operation = this.operation;
|
|
86
|
+
const trigger = event.type === "auto_compaction_end" ? "auto" : "manual";
|
|
85
87
|
if (
|
|
86
|
-
operation?.trigger !==
|
|
88
|
+
operation?.trigger !== trigger ||
|
|
87
89
|
operation.turnId !== turn.turnId ||
|
|
88
90
|
operation.generation !== turn.generation
|
|
89
91
|
) {
|
|
90
|
-
return
|
|
92
|
+
return undefined;
|
|
91
93
|
}
|
|
92
|
-
if (event.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
operation.retrying
|
|
103
|
-
|
|
94
|
+
if (event.type === "auto_compaction_end") {
|
|
95
|
+
if (event.action !== undefined && operation.action !== event.action) {
|
|
96
|
+
this.retire("OMP emitted overlapping compactions");
|
|
97
|
+
this.discardedEnds = 1;
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
if (event.willRetry) {
|
|
101
|
+
operation.retrying = true;
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
if (operation.retrying) {
|
|
105
|
+
operation.retrying = false;
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
104
108
|
}
|
|
105
|
-
const state = event.aborted
|
|
109
|
+
const state: CompactionOutcome = event.aborted
|
|
106
110
|
? "canceled"
|
|
107
|
-
: event.
|
|
108
|
-
? "
|
|
109
|
-
: event.
|
|
110
|
-
? "
|
|
111
|
+
: event.skipped
|
|
112
|
+
? "skipped"
|
|
113
|
+
: event.errorMessage
|
|
114
|
+
? "failed"
|
|
111
115
|
: "completed";
|
|
112
116
|
this.finish(state, {
|
|
113
117
|
tokensBefore: event.result?.tokensBefore ?? event.result?.preTokens,
|
|
114
118
|
message: event.errorMessage,
|
|
115
119
|
});
|
|
116
|
-
return
|
|
120
|
+
return state;
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
finish(
|
|
120
|
-
state:
|
|
124
|
+
state: CompactionOutcome,
|
|
121
125
|
options: { tokensBefore?: number | null; message?: string } = {},
|
|
122
126
|
): void {
|
|
123
127
|
const operation = this.operation;
|