@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
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type PluginHostProps,
|
|
3
|
+
type PluginTimelineItemProps,
|
|
4
|
+
useRpc,
|
|
5
|
+
} from "@getpaseo/plugin/client";
|
|
6
|
+
import { Icon, TextInput, useToast } from "@getpaseo/plugin/client/ui";
|
|
7
|
+
import { useEffect, useMemo, useReducer, useState } from "react";
|
|
8
|
+
import { Pressable, Text, View } from "react-native";
|
|
9
|
+
import {
|
|
10
|
+
cancelOmpSubagent,
|
|
11
|
+
OMP_SUBAGENT_STEER_MAX_LENGTH,
|
|
12
|
+
type OmpSubagentStrip as OmpSubagentStripData,
|
|
13
|
+
steerOmpSubagent,
|
|
14
|
+
} from "../shared/subagents";
|
|
15
|
+
import {
|
|
16
|
+
activeSubagent,
|
|
17
|
+
formatElapsed,
|
|
18
|
+
INITIAL_SUBAGENT_STRIP_STATE,
|
|
19
|
+
isSendableDraft,
|
|
20
|
+
isSubagentRequestPending,
|
|
21
|
+
type OmpSubagentRow,
|
|
22
|
+
requestedSubmission,
|
|
23
|
+
type SubagentStripAction,
|
|
24
|
+
type SubagentStripState,
|
|
25
|
+
stopOutcomeMessage,
|
|
26
|
+
subagentRequestError,
|
|
27
|
+
subagentStripReducer,
|
|
28
|
+
} from "./subagents-state";
|
|
29
|
+
|
|
30
|
+
const ROW_HEIGHT = 28;
|
|
31
|
+
|
|
32
|
+
function createStyles(theme: PluginHostProps["theme"], compact: boolean) {
|
|
33
|
+
return {
|
|
34
|
+
root: { gap: 2, paddingVertical: 2 },
|
|
35
|
+
row: {
|
|
36
|
+
flexDirection: "row" as const,
|
|
37
|
+
alignItems: "center" as const,
|
|
38
|
+
gap: 8,
|
|
39
|
+
height: ROW_HEIGHT,
|
|
40
|
+
paddingLeft: compact ? 2 : 4,
|
|
41
|
+
},
|
|
42
|
+
dot: { width: 6, height: 6, borderRadius: 3, backgroundColor: theme.colors.accent },
|
|
43
|
+
dotFinished: { backgroundColor: theme.colors.foregroundMuted },
|
|
44
|
+
agent: { color: theme.colors.foreground, fontSize: 13, flexShrink: 0 },
|
|
45
|
+
description: { color: theme.colors.foregroundMuted, fontSize: 13, flexShrink: 1, minWidth: 0 },
|
|
46
|
+
elapsed: {
|
|
47
|
+
color: theme.colors.foregroundMuted,
|
|
48
|
+
fontSize: 12,
|
|
49
|
+
fontVariant: ["tabular-nums" as const],
|
|
50
|
+
},
|
|
51
|
+
actions: { flexDirection: "row" as const, alignItems: "center" as const, gap: 2 },
|
|
52
|
+
iconButton: {
|
|
53
|
+
width: 24,
|
|
54
|
+
height: 24,
|
|
55
|
+
borderRadius: 6,
|
|
56
|
+
alignItems: "center" as const,
|
|
57
|
+
justifyContent: "center" as const,
|
|
58
|
+
},
|
|
59
|
+
textButton: {
|
|
60
|
+
height: 24,
|
|
61
|
+
paddingHorizontal: 8,
|
|
62
|
+
borderRadius: 6,
|
|
63
|
+
justifyContent: "center" as const,
|
|
64
|
+
},
|
|
65
|
+
hovered: { backgroundColor: theme.colors.surface2 },
|
|
66
|
+
disabled: { opacity: 0.5 },
|
|
67
|
+
stopLabel: { color: theme.colors.statusDanger, fontSize: 12 },
|
|
68
|
+
keepLabel: { color: theme.colors.foregroundMuted, fontSize: 12 },
|
|
69
|
+
tooltip: {
|
|
70
|
+
position: "absolute" as const,
|
|
71
|
+
bottom: 28,
|
|
72
|
+
right: 0,
|
|
73
|
+
paddingHorizontal: 6,
|
|
74
|
+
paddingVertical: 2,
|
|
75
|
+
borderRadius: 6,
|
|
76
|
+
backgroundColor: theme.colors.surface2,
|
|
77
|
+
borderWidth: 1,
|
|
78
|
+
borderColor: theme.colors.border,
|
|
79
|
+
},
|
|
80
|
+
tooltipText: { color: theme.colors.foreground, fontSize: 11 },
|
|
81
|
+
input: {
|
|
82
|
+
flexBasis: 360,
|
|
83
|
+
flexShrink: 1,
|
|
84
|
+
minWidth: 0,
|
|
85
|
+
height: 24,
|
|
86
|
+
paddingHorizontal: 8,
|
|
87
|
+
borderRadius: 6,
|
|
88
|
+
borderWidth: 1,
|
|
89
|
+
borderColor: theme.colors.border,
|
|
90
|
+
backgroundColor: theme.colors.surface1,
|
|
91
|
+
color: theme.colors.foreground,
|
|
92
|
+
fontSize: 13,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
type Styles = ReturnType<typeof createStyles>;
|
|
98
|
+
|
|
99
|
+
/** Re-renders once per second while `active`, for elapsed times. */
|
|
100
|
+
function useSecondTick(active: boolean): number {
|
|
101
|
+
const [now, setNow] = useState(() => Date.now());
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (!active) return;
|
|
104
|
+
const timer = setInterval(() => setNow(Date.now()), 1_000);
|
|
105
|
+
return () => clearInterval(timer);
|
|
106
|
+
}, [active]);
|
|
107
|
+
return now;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Running OMP subagents of one `task` call, published by the provider below its tool card. Paseo
|
|
112
|
+
* shows provider subagents read-only, so the plugin adds steer and stop here.
|
|
113
|
+
*/
|
|
114
|
+
export function OmpSubagentStrip({
|
|
115
|
+
agentId,
|
|
116
|
+
item,
|
|
117
|
+
theme,
|
|
118
|
+
layout,
|
|
119
|
+
}: PluginTimelineItemProps<OmpSubagentStripData>) {
|
|
120
|
+
const cancel = useRpc(cancelOmpSubagent);
|
|
121
|
+
const steer = useRpc(steerOmpSubagent);
|
|
122
|
+
const toast = useToast();
|
|
123
|
+
const styles = useMemo(() => createStyles(theme, layout.compact), [theme, layout.compact]);
|
|
124
|
+
const [state, dispatch] = useReducer(subagentStripReducer, INITIAL_SUBAGENT_STRIP_STATE);
|
|
125
|
+
const listed = item.data.subagents;
|
|
126
|
+
const active = activeSubagent(state);
|
|
127
|
+
const unlisted = active && !listed.some((row) => row.id === active.id) ? active : null;
|
|
128
|
+
const now = useSecondTick(listed.length > 0);
|
|
129
|
+
|
|
130
|
+
async function submit(): Promise<void> {
|
|
131
|
+
const request = requestedSubmission(state);
|
|
132
|
+
dispatch({ type: "submit" });
|
|
133
|
+
if (!request) return;
|
|
134
|
+
try {
|
|
135
|
+
if (request.kind === "stopping") {
|
|
136
|
+
const result = await cancel({ agentId, subagentId: request.subagent.id });
|
|
137
|
+
toast.show(stopOutcomeMessage(result.cancelled), {
|
|
138
|
+
variant: result.cancelled ? "success" : "default",
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
await steer({ agentId, subagentId: request.subagent.id, message: request.draft });
|
|
142
|
+
toast.show("Message sent to the subagent", { variant: "success" });
|
|
143
|
+
}
|
|
144
|
+
dispatch({ type: "settled" });
|
|
145
|
+
} catch (cause) {
|
|
146
|
+
toast.error(subagentRequestError(cause));
|
|
147
|
+
dispatch({ type: "failed" });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (listed.length === 0 && !unlisted) return null;
|
|
152
|
+
return (
|
|
153
|
+
<View style={styles.root}>
|
|
154
|
+
{listed.map((row) => (
|
|
155
|
+
<SubagentRow
|
|
156
|
+
key={row.id}
|
|
157
|
+
row={row}
|
|
158
|
+
listed
|
|
159
|
+
now={now}
|
|
160
|
+
state={state}
|
|
161
|
+
styles={styles}
|
|
162
|
+
theme={theme}
|
|
163
|
+
dispatch={dispatch}
|
|
164
|
+
submit={submit}
|
|
165
|
+
/>
|
|
166
|
+
))}
|
|
167
|
+
{unlisted ? (
|
|
168
|
+
<SubagentRow
|
|
169
|
+
row={unlisted}
|
|
170
|
+
listed={false}
|
|
171
|
+
now={now}
|
|
172
|
+
state={state}
|
|
173
|
+
styles={styles}
|
|
174
|
+
theme={theme}
|
|
175
|
+
dispatch={dispatch}
|
|
176
|
+
submit={submit}
|
|
177
|
+
/>
|
|
178
|
+
) : null}
|
|
179
|
+
</View>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface RowProps {
|
|
184
|
+
row: OmpSubagentRow;
|
|
185
|
+
/** False keeps the row that owns a draft visible after its subagent leaves the strip. */
|
|
186
|
+
listed: boolean;
|
|
187
|
+
now: number;
|
|
188
|
+
state: SubagentStripState;
|
|
189
|
+
styles: Styles;
|
|
190
|
+
theme: PluginHostProps["theme"];
|
|
191
|
+
dispatch: (action: SubagentStripAction) => void;
|
|
192
|
+
submit(): Promise<void>;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function SubagentRow(props: RowProps) {
|
|
196
|
+
const { row, listed, now, state, styles } = props;
|
|
197
|
+
const owner = activeSubagent(state)?.id === row.id ? state : null;
|
|
198
|
+
const steering = owner?.kind === "steering" || owner?.kind === "sending";
|
|
199
|
+
return (
|
|
200
|
+
<View style={styles.row}>
|
|
201
|
+
<View style={[styles.dot, listed ? null : styles.dotFinished]} />
|
|
202
|
+
<Text numberOfLines={1} style={styles.agent}>
|
|
203
|
+
{row.agent}
|
|
204
|
+
</Text>
|
|
205
|
+
{steering ? (
|
|
206
|
+
<SteerControls {...props} />
|
|
207
|
+
) : (
|
|
208
|
+
<>
|
|
209
|
+
<Text numberOfLines={1} style={styles.description}>
|
|
210
|
+
{row.description ?? ""}
|
|
211
|
+
</Text>
|
|
212
|
+
{owner?.kind === "confirming-stop" || owner?.kind === "stopping" ? (
|
|
213
|
+
<StopConfirmation {...props} />
|
|
214
|
+
) : (
|
|
215
|
+
<>
|
|
216
|
+
<Text style={styles.elapsed}>
|
|
217
|
+
{listed ? formatElapsed(now - row.runningSince) : "finished"}
|
|
218
|
+
</Text>
|
|
219
|
+
{listed ? <RowActions {...props} /> : null}
|
|
220
|
+
</>
|
|
221
|
+
)}
|
|
222
|
+
</>
|
|
223
|
+
)}
|
|
224
|
+
</View>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function RowActions({ row, state, styles, theme, dispatch }: RowProps) {
|
|
229
|
+
const pending = isSubagentRequestPending(state);
|
|
230
|
+
return (
|
|
231
|
+
<View style={styles.actions}>
|
|
232
|
+
<IconButton
|
|
233
|
+
icon="MessageSquare"
|
|
234
|
+
label="Steer: send this subagent a message at its next step"
|
|
235
|
+
disabled={pending}
|
|
236
|
+
styles={styles}
|
|
237
|
+
theme={theme}
|
|
238
|
+
onPress={() => dispatch({ type: "start-steer", subagent: row })}
|
|
239
|
+
/>
|
|
240
|
+
<IconButton
|
|
241
|
+
icon="CircleStop"
|
|
242
|
+
label="Stop this subagent and its nested subagents; the parent keeps running"
|
|
243
|
+
disabled={pending}
|
|
244
|
+
styles={styles}
|
|
245
|
+
theme={theme}
|
|
246
|
+
onPress={() => dispatch({ type: "request-stop", subagent: row })}
|
|
247
|
+
/>
|
|
248
|
+
</View>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function StopConfirmation({ state, styles, dispatch, submit }: RowProps) {
|
|
253
|
+
const stopping = state.kind === "stopping";
|
|
254
|
+
return (
|
|
255
|
+
<View style={styles.actions}>
|
|
256
|
+
<TextButton
|
|
257
|
+
label={stopping ? "Stopping..." : "Stop"}
|
|
258
|
+
accessibilityLabel="Confirm stopping this subagent"
|
|
259
|
+
labelStyle={styles.stopLabel}
|
|
260
|
+
disabled={stopping}
|
|
261
|
+
styles={styles}
|
|
262
|
+
onPress={() => void submit()}
|
|
263
|
+
/>
|
|
264
|
+
<TextButton
|
|
265
|
+
label="Keep"
|
|
266
|
+
accessibilityLabel="Keep this subagent running"
|
|
267
|
+
labelStyle={styles.keepLabel}
|
|
268
|
+
disabled={stopping}
|
|
269
|
+
styles={styles}
|
|
270
|
+
onPress={() => dispatch({ type: "dismiss" })}
|
|
271
|
+
/>
|
|
272
|
+
</View>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function SteerControls({ row, state, styles, theme, dispatch, submit }: RowProps) {
|
|
277
|
+
const sending = state.kind === "sending";
|
|
278
|
+
const draft = state.kind === "steering" || state.kind === "sending" ? state.draft : "";
|
|
279
|
+
return (
|
|
280
|
+
<>
|
|
281
|
+
<TextInput
|
|
282
|
+
accessibilityLabel={`Message for ${row.agent}`}
|
|
283
|
+
autoFocus
|
|
284
|
+
editable={!sending}
|
|
285
|
+
maxLength={OMP_SUBAGENT_STEER_MAX_LENGTH}
|
|
286
|
+
onChangeText={(text) => dispatch({ type: "edit-draft", draft: text })}
|
|
287
|
+
onKeyPress={(event) => {
|
|
288
|
+
if (event.nativeEvent.key === "Escape") dispatch({ type: "dismiss" });
|
|
289
|
+
}}
|
|
290
|
+
onSubmitEditing={() => void submit()}
|
|
291
|
+
placeholder="Message the subagent"
|
|
292
|
+
placeholderTextColor={theme.colors.foregroundMuted}
|
|
293
|
+
style={styles.input}
|
|
294
|
+
value={draft}
|
|
295
|
+
/>
|
|
296
|
+
<View style={styles.actions}>
|
|
297
|
+
<IconButton
|
|
298
|
+
icon="SendHorizontal"
|
|
299
|
+
label="Send to the subagent (Enter)"
|
|
300
|
+
disabled={sending || !isSendableDraft(draft)}
|
|
301
|
+
styles={styles}
|
|
302
|
+
theme={theme}
|
|
303
|
+
onPress={() => void submit()}
|
|
304
|
+
/>
|
|
305
|
+
<IconButton
|
|
306
|
+
icon="X"
|
|
307
|
+
label="Close (Esc)"
|
|
308
|
+
disabled={sending}
|
|
309
|
+
styles={styles}
|
|
310
|
+
theme={theme}
|
|
311
|
+
onPress={() => dispatch({ type: "dismiss" })}
|
|
312
|
+
/>
|
|
313
|
+
</View>
|
|
314
|
+
</>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function IconButton({
|
|
319
|
+
icon,
|
|
320
|
+
label,
|
|
321
|
+
disabled,
|
|
322
|
+
styles,
|
|
323
|
+
theme,
|
|
324
|
+
onPress,
|
|
325
|
+
}: {
|
|
326
|
+
icon: string;
|
|
327
|
+
label: string;
|
|
328
|
+
disabled: boolean;
|
|
329
|
+
styles: Styles;
|
|
330
|
+
theme: PluginHostProps["theme"];
|
|
331
|
+
onPress(): void;
|
|
332
|
+
}) {
|
|
333
|
+
const [hovered, setHovered] = useState(false);
|
|
334
|
+
return (
|
|
335
|
+
<View>
|
|
336
|
+
<Pressable
|
|
337
|
+
accessibilityRole="button"
|
|
338
|
+
accessibilityLabel={label}
|
|
339
|
+
disabled={disabled}
|
|
340
|
+
onHoverIn={() => setHovered(true)}
|
|
341
|
+
onHoverOut={() => setHovered(false)}
|
|
342
|
+
onPress={onPress}
|
|
343
|
+
style={[
|
|
344
|
+
styles.iconButton,
|
|
345
|
+
hovered && !disabled ? styles.hovered : null,
|
|
346
|
+
disabled ? styles.disabled : null,
|
|
347
|
+
]}
|
|
348
|
+
>
|
|
349
|
+
<Icon name={icon} size={14} color={theme.colors.foregroundMuted} />
|
|
350
|
+
</Pressable>
|
|
351
|
+
{hovered && !disabled ? (
|
|
352
|
+
<View pointerEvents="none" style={styles.tooltip}>
|
|
353
|
+
<Text numberOfLines={1} style={styles.tooltipText}>
|
|
354
|
+
{label}
|
|
355
|
+
</Text>
|
|
356
|
+
</View>
|
|
357
|
+
) : null}
|
|
358
|
+
</View>
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function TextButton({
|
|
363
|
+
label,
|
|
364
|
+
accessibilityLabel,
|
|
365
|
+
labelStyle,
|
|
366
|
+
disabled,
|
|
367
|
+
styles,
|
|
368
|
+
onPress,
|
|
369
|
+
}: {
|
|
370
|
+
label: string;
|
|
371
|
+
accessibilityLabel: string;
|
|
372
|
+
labelStyle: Styles["stopLabel"];
|
|
373
|
+
disabled: boolean;
|
|
374
|
+
styles: Styles;
|
|
375
|
+
onPress(): void;
|
|
376
|
+
}) {
|
|
377
|
+
const [hovered, setHovered] = useState(false);
|
|
378
|
+
return (
|
|
379
|
+
<Pressable
|
|
380
|
+
accessibilityRole="button"
|
|
381
|
+
accessibilityLabel={accessibilityLabel}
|
|
382
|
+
disabled={disabled}
|
|
383
|
+
onHoverIn={() => setHovered(true)}
|
|
384
|
+
onHoverOut={() => setHovered(false)}
|
|
385
|
+
onPress={onPress}
|
|
386
|
+
style={[styles.textButton, hovered && !disabled ? styles.hovered : null]}
|
|
387
|
+
>
|
|
388
|
+
<Text style={labelStyle}>{label}</Text>
|
|
389
|
+
</Pressable>
|
|
390
|
+
);
|
|
391
|
+
}
|
package/index.client.tsx
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
HUB_SIDEBAR_ITEM_ID,
|
|
39
39
|
loadHubSnapshot,
|
|
40
40
|
} from "./client/sidebar-compat";
|
|
41
|
+
import { OmpSubagentStrip } from "./client/subagents-strip";
|
|
41
42
|
import {
|
|
42
43
|
type ComposerPillSettings,
|
|
43
44
|
composerPillSettings,
|
|
@@ -48,6 +49,7 @@ import { OMP_MCP_AUTH_TIMELINE_KIND, ompMcpAuthorizationTimelineSchema } from ".
|
|
|
48
49
|
import { type OmpStore, storeForProvider } from "./shared/omp-store";
|
|
49
50
|
import { ompImageTimelineSchema, transformOmpImageToolItem } from "./shared/provider-image";
|
|
50
51
|
import { listOmpQuotas } from "./shared/quota";
|
|
52
|
+
import { OMP_SUBAGENT_STRIP_KIND, ompSubagentStripSchema } from "./shared/subagents";
|
|
51
53
|
|
|
52
54
|
const PAGE_LIMIT = 200;
|
|
53
55
|
const MAX_PAGES = 10;
|
|
@@ -237,6 +239,12 @@ export default function contribute(client: PluginClientContext) {
|
|
|
237
239
|
schema: ompMcpAuthorizationTimelineSchema,
|
|
238
240
|
Component: OmpMcpAuthorizationCard,
|
|
239
241
|
});
|
|
242
|
+
const removeSubagentStripRenderer = client.addTimelineRenderer({
|
|
243
|
+
kind: OMP_SUBAGENT_STRIP_KIND,
|
|
244
|
+
version: 1,
|
|
245
|
+
schema: ompSubagentStripSchema,
|
|
246
|
+
Component: OmpSubagentStrip,
|
|
247
|
+
});
|
|
240
248
|
const removeImageTransformer = client.addTimelineTransformer({
|
|
241
249
|
id: "omp-images",
|
|
242
250
|
query: { itemType: "tool_call" },
|
|
@@ -544,6 +552,7 @@ export default function contribute(client: PluginClientContext) {
|
|
|
544
552
|
pills.clear();
|
|
545
553
|
removeImageRenderer();
|
|
546
554
|
removeMcpAuthorizationRenderer();
|
|
555
|
+
removeSubagentStripRenderer();
|
|
547
556
|
removeImageTransformer();
|
|
548
557
|
removeOpenConfig();
|
|
549
558
|
removeConfigEntry();
|
package/index.server.ts
CHANGED
|
@@ -25,9 +25,15 @@ import {
|
|
|
25
25
|
} from "./server/provider/profile-providers";
|
|
26
26
|
import { createOmpProvider } from "./server/provider/registration";
|
|
27
27
|
import { OmpPublicError } from "./server/provider/security";
|
|
28
|
+
import { OmpSessionLimit } from "./server/provider/session-limit";
|
|
28
29
|
import { resolveGetOmpProviderHealth } from "./server/provider-diagnostics";
|
|
29
30
|
import { resolveListOmpQuotas } from "./server/quota";
|
|
30
31
|
import { resolveListOmpSessions } from "./server/sessions";
|
|
32
|
+
import {
|
|
33
|
+
OmpSubagentControlRegistry,
|
|
34
|
+
resolveCancelOmpSubagent,
|
|
35
|
+
resolveSteerOmpSubagent,
|
|
36
|
+
} from "./server/subagents";
|
|
31
37
|
import { resolveGetOmpSupportReport } from "./server/support-diagnostics";
|
|
32
38
|
import { composerPillSettings } from "./shared/composer-pill-settings";
|
|
33
39
|
import { listHubProcesses, tailHubLog } from "./shared/hub";
|
|
@@ -47,6 +53,7 @@ import { getOmpProviderHealth } from "./shared/provider-diagnostics";
|
|
|
47
53
|
import { providerLaunchSettings } from "./shared/provider-launch-settings";
|
|
48
54
|
import { listOmpQuotas } from "./shared/quota";
|
|
49
55
|
import { listOmpSessions } from "./shared/sessions";
|
|
56
|
+
import { cancelOmpSubagent, steerOmpSubagent } from "./shared/subagents";
|
|
50
57
|
import { getOmpSupportReport } from "./shared/support-diagnostics";
|
|
51
58
|
|
|
52
59
|
function scoped<T extends { store?: OmpStore }, R>(handler: (input: T) => R) {
|
|
@@ -62,6 +69,12 @@ export default function contribute(server: PluginServerContext) {
|
|
|
62
69
|
throw new OmpPublicError("OMP host launch settings are unavailable");
|
|
63
70
|
};
|
|
64
71
|
const browserAuthorizationRegistry = new OmpBrowserAuthorizationRegistry();
|
|
72
|
+
const sessionLimit = new OmpSessionLimit(async () => {
|
|
73
|
+
const current = await launchSettings.read();
|
|
74
|
+
return current.status === "ready" ? current.values.maxSessions : undefined;
|
|
75
|
+
});
|
|
76
|
+
const removeSessionLimitListener = launchSettings.subscribe(() => sessionLimit.refresh());
|
|
77
|
+
const subagentControlRegistry = new OmpSubagentControlRegistry();
|
|
65
78
|
const protocolViolations = new OmpProtocolViolationCollector();
|
|
66
79
|
const operationalFailures = new OmpOperationalFailureCollector();
|
|
67
80
|
const profiles = discoverOmpProfilesSync();
|
|
@@ -70,6 +83,8 @@ export default function contribute(server: PluginServerContext) {
|
|
|
70
83
|
server.registerProvider(
|
|
71
84
|
createProfileOmpProvider(profile, {
|
|
72
85
|
browserAuthorizationRegistry,
|
|
86
|
+
sessionLimit,
|
|
87
|
+
subagentControlRegistry,
|
|
73
88
|
reportProtocolViolation: protocolViolations.report,
|
|
74
89
|
reportOperationalFailure: operationalFailures.report,
|
|
75
90
|
resolveHostInheritEnv,
|
|
@@ -97,6 +112,12 @@ export default function contribute(server: PluginServerContext) {
|
|
|
97
112
|
server.handle(openOmpMcpAuthorizationInPaseoBrowser, (input) =>
|
|
98
113
|
resolveOpenOmpMcpAuthorizationInPaseoBrowser(input, browserAuthorizationRegistry),
|
|
99
114
|
);
|
|
115
|
+
server.handle(cancelOmpSubagent, (input) =>
|
|
116
|
+
resolveCancelOmpSubagent(input, subagentControlRegistry),
|
|
117
|
+
);
|
|
118
|
+
server.handle(steerOmpSubagent, (input) =>
|
|
119
|
+
resolveSteerOmpSubagent(input, subagentControlRegistry),
|
|
120
|
+
);
|
|
100
121
|
const removeSessionOpenHook = server.before("agent.session_open", ({ request }) => {
|
|
101
122
|
if (request.provider !== "omp-plugin" && !request.provider.startsWith("omp-plugin-")) return;
|
|
102
123
|
return withOmpSessionOpenEnv(request);
|
|
@@ -104,6 +125,8 @@ export default function contribute(server: PluginServerContext) {
|
|
|
104
125
|
server.registerProvider(
|
|
105
126
|
createOmpProvider({
|
|
106
127
|
browserAuthorizationRegistry,
|
|
128
|
+
sessionLimit,
|
|
129
|
+
subagentControlRegistry,
|
|
107
130
|
reportProtocolViolation: protocolViolations.report,
|
|
108
131
|
reportOperationalFailure: operationalFailures.report,
|
|
109
132
|
resolveHostInheritEnv,
|
|
@@ -111,6 +134,8 @@ export default function contribute(server: PluginServerContext) {
|
|
|
111
134
|
);
|
|
112
135
|
return () => {
|
|
113
136
|
browserAuthorizationRegistry.clear();
|
|
137
|
+
removeSessionLimitListener();
|
|
138
|
+
subagentControlRegistry.clear();
|
|
114
139
|
removeSessionOpenHook();
|
|
115
140
|
};
|
|
116
141
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-omp",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@omercnet/paseo-omp",
|
|
9
|
-
"version": "5.
|
|
9
|
+
"version": "5.2.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@getpaseo/protocol": "0.14.0",
|
|
12
|
-
"@modelcontextprotocol/sdk": "1.
|
|
12
|
+
"@modelcontextprotocol/sdk": "1.32.0",
|
|
13
13
|
"yaml": "^2.9.0"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"node_modules/@modelcontextprotocol/sdk": {
|
|
38
|
-
"version": "1.
|
|
39
|
-
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.
|
|
40
|
-
"integrity": "sha512-
|
|
38
|
+
"version": "1.32.0",
|
|
39
|
+
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.0.tgz",
|
|
40
|
+
"integrity": "sha512-8BviX/hK4Gd2eL1KdTwm0gfl4d3wdoErW0Jd00h6nomUAtk6IJk5vwQJc6kfWtJTzN5OEV/lgIiJKI8fiDGAlA==",
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@hono/node-server": "^1.19.9 || ^2.0.5",
|
|
@@ -435,9 +435,9 @@
|
|
|
435
435
|
}
|
|
436
436
|
},
|
|
437
437
|
"node_modules/express-rate-limit": {
|
|
438
|
-
"version": "8.7.
|
|
439
|
-
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.
|
|
440
|
-
"integrity": "sha512-
|
|
438
|
+
"version": "8.7.2",
|
|
439
|
+
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.2.tgz",
|
|
440
|
+
"integrity": "sha512-U1ScocxDD9WI09LaBVypvNkOtNR5yWAmZLkuKJdAzIchA4YKFYF6GT5IKER7epG58oXtjvGlV0/6ii0F83EyZg==",
|
|
441
441
|
"license": "MIT",
|
|
442
442
|
"dependencies": {
|
|
443
443
|
"debug": "^4.4.3",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-omp",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0-next.362.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@getpaseo/protocol": "0.14.0",
|
|
58
|
-
"@modelcontextprotocol/sdk": "1.
|
|
58
|
+
"@modelcontextprotocol/sdk": "1.32.0",
|
|
59
59
|
"yaml": "^2.9.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
@@ -64,22 +64,19 @@
|
|
|
64
64
|
"@getpaseo/client": "0.14.0",
|
|
65
65
|
"@getpaseo/plugin": "0.14.0",
|
|
66
66
|
"@getpaseo/server": "0.14.0",
|
|
67
|
-
"@oh-my-pi/pi-agent-core": "18.
|
|
68
|
-
"@oh-my-pi/pi-coding-agent": "18.
|
|
67
|
+
"@oh-my-pi/pi-agent-core": "18.14.0",
|
|
68
|
+
"@oh-my-pi/pi-coding-agent": "18.14.0",
|
|
69
69
|
"@tanstack/react-query": "^5.102.3",
|
|
70
70
|
"@types/node": "^24.10.1",
|
|
71
71
|
"@types/react": "^19.2.18",
|
|
72
72
|
"@vitest/coverage-v8": "^5.0.0",
|
|
73
73
|
"esbuild": "0.28.2",
|
|
74
|
+
"pino": "10.4.0",
|
|
74
75
|
"react": "19.1.0",
|
|
75
76
|
"react-native": "0.81.5",
|
|
76
|
-
"pino": "10.3.1",
|
|
77
77
|
"tsx": "^4.20.6",
|
|
78
78
|
"typescript": "^7.0.2",
|
|
79
79
|
"vitest": "^5.0.0",
|
|
80
|
-
"zod": "4.
|
|
81
|
-
},
|
|
82
|
-
"overrides": {
|
|
83
|
-
"qs": "6.16.1"
|
|
80
|
+
"zod": "4.6.7"
|
|
84
81
|
}
|
|
85
82
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
|
|
6
6
|
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
7
|
+
// Run through the shell so Windows resolves npm.cmd: Node refuses to spawn .cmd files without one.
|
|
8
|
+
const INSTALL_COMMAND = "npm ci --omit=dev --ignore-scripts --workspaces=false";
|
|
7
9
|
|
|
8
10
|
function loadRootCatalog(root) {
|
|
9
11
|
for (let directory = root; ; directory = dirname(directory)) {
|
|
@@ -27,6 +29,8 @@ function stageManifest(root, catalog, install) {
|
|
|
27
29
|
manifest.dependencies[name] = catalog[name];
|
|
28
30
|
}
|
|
29
31
|
delete manifest.devDependencies;
|
|
32
|
+
// npm-shrinkwrap.json is generated without overrides, so npm ci rejects a manifest that keeps them.
|
|
33
|
+
delete manifest.overrides;
|
|
30
34
|
|
|
31
35
|
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
32
36
|
try {
|
|
@@ -36,18 +40,11 @@ function stageManifest(root, catalog, install) {
|
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
export function prepareDependencies(root = projectRoot, execute =
|
|
43
|
+
export function prepareDependencies(root = projectRoot, execute = execSync) {
|
|
40
44
|
const catalog = loadRootCatalog(root);
|
|
41
45
|
if (!catalog) return false;
|
|
42
46
|
stageManifest(root, catalog, () => {
|
|
43
|
-
execute(
|
|
44
|
-
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
45
|
-
["ci", "--omit=dev", "--ignore-scripts", "--workspaces=false"],
|
|
46
|
-
{
|
|
47
|
-
cwd: root,
|
|
48
|
-
stdio: "inherit",
|
|
49
|
-
},
|
|
50
|
-
);
|
|
47
|
+
execute(INSTALL_COMMAND, { cwd: root, stdio: "inherit" });
|
|
51
48
|
});
|
|
52
49
|
return true;
|
|
53
50
|
}
|
package/server/mcp-browser.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { RpcInput } from "@getpaseo/plugin";
|
|
3
3
|
import type { openOmpMcpAuthorizationInPaseoBrowser } from "../shared/mcp";
|
|
4
|
+
import { MAX_SESSIONS_UPPER_BOUND } from "../shared/provider-launch-settings";
|
|
4
5
|
import { OmpPublicError } from "./provider/security";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
// One registration per live session. The shared session limit refuses sessions first.
|
|
8
|
+
const MAX_REGISTERED_SESSIONS = MAX_SESSIONS_UPPER_BOUND;
|
|
7
9
|
const MAX_AUTHORIZATIONS_PER_SESSION = 16;
|
|
8
10
|
|
|
9
11
|
type BrowserAuthorizationOpener = (url: string) => Promise<void>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Release Please keeps this bundle-safe constant aligned with package.json. */
|
|
2
|
-
export const PASEO_OMP_PACKAGE_VERSION = "5.
|
|
2
|
+
export const PASEO_OMP_PACKAGE_VERSION = "5.2.0-next.362.1"; // x-release-please-version
|