@dylanrussell/agent-router 2.1.0 → 2.1.2
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/CHANGELOG.md +12 -0
- package/README.md +2 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/plugin.js +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/tui.d.ts +24 -0
- package/dist/tui.js +89 -9
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
package/dist/tui.d.ts
CHANGED
|
@@ -26,6 +26,17 @@ interface SidebarTheme {
|
|
|
26
26
|
readonly warning?: unknown;
|
|
27
27
|
readonly success?: unknown;
|
|
28
28
|
}
|
|
29
|
+
/** Live model selection observed for one agent in a session. */
|
|
30
|
+
interface LiveSelection extends ModelAssignment {
|
|
31
|
+
readonly agent: string;
|
|
32
|
+
}
|
|
33
|
+
/** Routing mode of the viewed session, as reported by the status service. */
|
|
34
|
+
interface RoutingStatus {
|
|
35
|
+
readonly mode: "automatic" | "pinned";
|
|
36
|
+
readonly reason?: string | undefined;
|
|
37
|
+
/** Epoch ms of the last quota evaluation behind the current mode, if tracked. */
|
|
38
|
+
readonly checkedAt?: number | null | undefined;
|
|
39
|
+
}
|
|
29
40
|
interface SidebarContext {
|
|
30
41
|
/** Active stack when the TUI booted — differing means a restart is due. */
|
|
31
42
|
readonly bootActive: string | null;
|
|
@@ -34,6 +45,14 @@ interface SidebarContext {
|
|
|
34
45
|
readonly current?: (ModelAssignment & {
|
|
35
46
|
readonly agent: string;
|
|
36
47
|
}) | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Selections of running direct children of the viewed session, at its location.
|
|
50
|
+
* Conflicting models are all retained. The viewed session takes precedence.
|
|
51
|
+
*/
|
|
52
|
+
readonly live?: readonly LiveSelection[] | undefined;
|
|
53
|
+
readonly defaults?: readonly LiveSelection[] | undefined;
|
|
54
|
+
/** Routing mode of the viewed session; omitted when no status source answers. */
|
|
55
|
+
readonly routing?: RoutingStatus | undefined;
|
|
37
56
|
}
|
|
38
57
|
|
|
39
58
|
/**
|
|
@@ -115,6 +134,11 @@ interface RouterTuiApi {
|
|
|
115
134
|
readonly provider?: unknown;
|
|
116
135
|
};
|
|
117
136
|
readonly currentModel?: (sessionID: string) => SidebarContext["current"];
|
|
137
|
+
readonly configuredModels?: () => readonly LiveSelection[];
|
|
138
|
+
/** Running direct-child selections at the viewed parent's location; retain conflicts. */
|
|
139
|
+
readonly liveSelections?: ((sessionID: string) => readonly LiveSelection[]) | undefined;
|
|
140
|
+
/** Read-only routing-status query; undefined means no status source is available. */
|
|
141
|
+
readonly routingStatus?: ((sessionID: string) => Promise<RoutingStatus | undefined>) | undefined;
|
|
118
142
|
}
|
|
119
143
|
|
|
120
144
|
/**
|
package/dist/tui.js
CHANGED
|
@@ -15872,6 +15872,9 @@ function normalizeVariant(variant) {
|
|
|
15872
15872
|
function sameModel(a, b) {
|
|
15873
15873
|
return a.model === b.model && normalizeVariant(a.variant) === normalizeVariant(b.variant);
|
|
15874
15874
|
}
|
|
15875
|
+
function quotaAge(checkedAt, now = Date.now()) {
|
|
15876
|
+
return `${Math.max(0, Math.round((now - checkedAt) / 1e3))}s ago`;
|
|
15877
|
+
}
|
|
15875
15878
|
function buildSidebarNodes(snapshot, ctx) {
|
|
15876
15879
|
const theme = ctx.theme ?? {};
|
|
15877
15880
|
const nodes = [text("Agent Stacks", { fg: theme.text, attributes: TEXT_ATTR_BOLD })];
|
|
@@ -15894,17 +15897,32 @@ function buildSidebarNodes(snapshot, ctx) {
|
|
|
15894
15897
|
marginTop: 1
|
|
15895
15898
|
})
|
|
15896
15899
|
);
|
|
15900
|
+
if (ctx.routing) {
|
|
15901
|
+
const parts = [`Routing \xB7 ${ctx.routing.mode === "pinned" ? "Pinned" : "Automatic"}`];
|
|
15902
|
+
if (ctx.routing.reason) parts.push(ctx.routing.reason);
|
|
15903
|
+
parts.push(
|
|
15904
|
+
ctx.routing.checkedAt != null ? `quota ${quotaAge(ctx.routing.checkedAt)}` : "quota freshness unknown"
|
|
15905
|
+
);
|
|
15906
|
+
nodes.push(text(parts.join(" \xB7 "), { fg: theme.textMuted }));
|
|
15907
|
+
} else {
|
|
15908
|
+
nodes.push(text("Routing \xB7 unknown \xB7 freshness unknown", { fg: theme.textMuted }));
|
|
15909
|
+
}
|
|
15897
15910
|
if (snapshot.agents.length === 0) {
|
|
15898
15911
|
nodes.push(text("\u2022 (none)", { fg: theme.textMuted }));
|
|
15899
15912
|
} else {
|
|
15900
|
-
nodes.push(text("Precedence \u2193 \xB7 \u25CF
|
|
15913
|
+
nodes.push(text("Precedence \u2193 \xB7 \u25CF selected", { fg: theme.textMuted }));
|
|
15901
15914
|
for (const assignment of snapshot.agents) {
|
|
15902
15915
|
const chain = [assignment, ...assignment.fallbacks ?? []];
|
|
15903
15916
|
const current = ctx.current?.agent === assignment.agent ? ctx.current : void 0;
|
|
15904
|
-
const
|
|
15905
|
-
const
|
|
15906
|
-
|
|
15907
|
-
|
|
15917
|
+
const children = (ctx.live ?? []).filter((entry) => entry.agent === assignment.agent);
|
|
15918
|
+
const configured = ctx.defaults?.find((entry) => entry.agent === assignment.agent);
|
|
15919
|
+
const selections = current ? [current] : children.length ? children : [configured ?? assignment];
|
|
15920
|
+
const unique = selections.filter(
|
|
15921
|
+
(entry, index) => selections.findIndex((other) => sameModel(entry, other)) === index
|
|
15922
|
+
);
|
|
15923
|
+
const modelRow = (model, active) => text(`${active ? "\u25CF " : " "}${modelLabel(model)}`, {
|
|
15924
|
+
fg: active ? theme.warning : theme.textMuted,
|
|
15925
|
+
attributes: 0,
|
|
15908
15926
|
wrapMode: "char",
|
|
15909
15927
|
width: "100%"
|
|
15910
15928
|
});
|
|
@@ -15917,8 +15935,13 @@ function buildSidebarNodes(snapshot, ctx) {
|
|
|
15917
15935
|
kind: "box",
|
|
15918
15936
|
props: { flexDirection: "column", paddingLeft: 2 },
|
|
15919
15937
|
children: [
|
|
15920
|
-
...chain.map(
|
|
15921
|
-
|
|
15938
|
+
...chain.map(
|
|
15939
|
+
(model) => modelRow(
|
|
15940
|
+
model,
|
|
15941
|
+
unique.some((selected) => sameModel(model, selected))
|
|
15942
|
+
)
|
|
15943
|
+
),
|
|
15944
|
+
...unique.filter((selected) => !chain.some((model) => sameModel(model, selected))).map((selected) => modelRow(selected, true))
|
|
15922
15945
|
]
|
|
15923
15946
|
}
|
|
15924
15947
|
]
|
|
@@ -15933,6 +15956,7 @@ function buildSidebarNodes(snapshot, ctx) {
|
|
|
15933
15956
|
|
|
15934
15957
|
// src/tui/index.ts
|
|
15935
15958
|
var POLL_INTERVAL_MS = 1500;
|
|
15959
|
+
var ROUTING_INTERVAL_MS = 3e3;
|
|
15936
15960
|
var SIDEBAR_ORDER = 850;
|
|
15937
15961
|
function debugLog(message) {
|
|
15938
15962
|
const target = process.env.AGENT_ROUTER_TUI_DEBUG ?? process.env.OMO_TUI_DEBUG;
|
|
@@ -16018,16 +16042,25 @@ var tui = async (api) => {
|
|
|
16018
16042
|
if (!paths) return;
|
|
16019
16043
|
let snapshot = await readStackSnapshot(paths);
|
|
16020
16044
|
const bootActive = snapshot.active;
|
|
16045
|
+
let routing;
|
|
16046
|
+
let routingSessionID;
|
|
16047
|
+
let lastSessionID;
|
|
16021
16048
|
const viewContext = (sessionID) => ({
|
|
16022
16049
|
bootActive,
|
|
16023
16050
|
theme: api.theme?.current,
|
|
16024
|
-
current: sessionID ? api.currentModel?.(sessionID) : void 0
|
|
16051
|
+
current: sessionID ? api.currentModel?.(sessionID) : void 0,
|
|
16052
|
+
live: sessionID ? api.liveSelections?.(sessionID) : void 0,
|
|
16053
|
+
defaults: api.configuredModels?.(),
|
|
16054
|
+
routing: sessionID === routingSessionID ? routing : void 0
|
|
16025
16055
|
});
|
|
16026
16056
|
try {
|
|
16027
16057
|
api.slots.register({
|
|
16028
16058
|
order: SIDEBAR_ORDER,
|
|
16029
16059
|
slots: {
|
|
16030
|
-
sidebar_content: (sessionID) =>
|
|
16060
|
+
sidebar_content: (sessionID) => {
|
|
16061
|
+
if (sessionID) lastSessionID = sessionID;
|
|
16062
|
+
return materialize(buildSidebarNodes(snapshot, viewContext(sessionID)), solid);
|
|
16063
|
+
}
|
|
16031
16064
|
}
|
|
16032
16065
|
});
|
|
16033
16066
|
debugLog("slots.register ok");
|
|
@@ -16060,6 +16093,20 @@ var tui = async (api) => {
|
|
|
16060
16093
|
}
|
|
16061
16094
|
}
|
|
16062
16095
|
});
|
|
16096
|
+
const stopRouting = api.routingStatus ? createSidebarPoller({
|
|
16097
|
+
read: async () => {
|
|
16098
|
+
const sessionID = lastSessionID;
|
|
16099
|
+
const status2 = sessionID ? await api.routingStatus?.(sessionID).catch(() => void 0) : void 0;
|
|
16100
|
+
return { routing: status2, sessionID, key: JSON.stringify([sessionID, status2 ?? null]) };
|
|
16101
|
+
},
|
|
16102
|
+
intervalMs: ROUTING_INTERVAL_MS,
|
|
16103
|
+
initial: { routing: void 0, sessionID: void 0, key: "boot" },
|
|
16104
|
+
onChange: (next) => {
|
|
16105
|
+
routing = next.routing;
|
|
16106
|
+
routingSessionID = next.sessionID;
|
|
16107
|
+
api.renderer.requestRender();
|
|
16108
|
+
}
|
|
16109
|
+
}) : void 0;
|
|
16063
16110
|
const deps = { api, paths, refresh };
|
|
16064
16111
|
const status = () => api.ui.toast({
|
|
16065
16112
|
title: "agent-router",
|
|
@@ -16073,6 +16120,7 @@ var tui = async (api) => {
|
|
|
16073
16120
|
debugLog(`command.register FAILED: ${e.message}`);
|
|
16074
16121
|
}
|
|
16075
16122
|
api.lifecycle.onDispose(stopPolling);
|
|
16123
|
+
if (stopRouting) api.lifecycle.onDispose(stopRouting);
|
|
16076
16124
|
debugLog(`tui() init complete \u2014 active=${snapshot.active ?? "(none)"}`);
|
|
16077
16125
|
};
|
|
16078
16126
|
var agentRouterTui = {
|
|
@@ -16195,6 +16243,38 @@ var agentRouterTui = {
|
|
|
16195
16243
|
variant: session.model.variant ?? null
|
|
16196
16244
|
};
|
|
16197
16245
|
},
|
|
16246
|
+
liveSelections: (sessionID) => {
|
|
16247
|
+
const sessions = ctx.data.session;
|
|
16248
|
+
if (typeof sessions.list !== "function" || typeof sessions.status !== "function") return [];
|
|
16249
|
+
try {
|
|
16250
|
+
const parent = sessions.get(sessionID);
|
|
16251
|
+
if (!parent?.location) return [];
|
|
16252
|
+
const selections = [];
|
|
16253
|
+
for (const session of sessions.list()) {
|
|
16254
|
+
if (session.parentID !== sessionID || !session.agent || !session.model) continue;
|
|
16255
|
+
if (session.location?.directory !== parent.location.directory) continue;
|
|
16256
|
+
if (sessions.status(session.id) !== "running") continue;
|
|
16257
|
+
const { agent, model } = session;
|
|
16258
|
+
selections.push({
|
|
16259
|
+
agent,
|
|
16260
|
+
model: `${model.providerID}/${model.id}`,
|
|
16261
|
+
variant: model.variant ?? null
|
|
16262
|
+
});
|
|
16263
|
+
}
|
|
16264
|
+
return selections;
|
|
16265
|
+
} catch {
|
|
16266
|
+
return [];
|
|
16267
|
+
}
|
|
16268
|
+
},
|
|
16269
|
+
configuredModels: () => (ctx.data.location.agent?.list(ctx.location) ?? []).flatMap(
|
|
16270
|
+
(agent) => agent.model ? [
|
|
16271
|
+
{
|
|
16272
|
+
agent: agent.name,
|
|
16273
|
+
model: `${agent.model.providerID}/${agent.model.id}`,
|
|
16274
|
+
variant: agent.model.variant ?? null
|
|
16275
|
+
}
|
|
16276
|
+
] : []
|
|
16277
|
+
),
|
|
16198
16278
|
state: {
|
|
16199
16279
|
get provider() {
|
|
16200
16280
|
const models = ctx.data.location.model.list(ctx.location) ?? [];
|