agent-relay-server 0.96.0 → 0.98.0
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/docs/openapi.json +55 -1
- package/package.json +3 -3
- package/public/assets/MessageBubble-C4AeEHdz.js +4 -0
- package/public/assets/{MessageBubble-BO9eaLce.js.map → MessageBubble-C4AeEHdz.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-BK2GoOql.js → PlanGraphPanel-cwPUGFXA.js} +2 -2
- package/public/assets/{PlanGraphPanel-BK2GoOql.js.map → PlanGraphPanel-cwPUGFXA.js.map} +1 -1
- package/public/assets/{activity-FPNsylmj.js → activity-BWGwnSg5.js} +2 -2
- package/public/assets/{activity-FPNsylmj.js.map → activity-BWGwnSg5.js.map} +1 -1
- package/public/assets/agent-profiles-C6N9cTJg.js +2 -0
- package/public/assets/agent-profiles-C6N9cTJg.js.map +1 -0
- package/public/assets/agents-FUBxhCEU.js +2 -0
- package/public/assets/{agents-DwXYbjeZ.js.map → agents-FUBxhCEU.js.map} +1 -1
- package/public/assets/{analytics-BiYHZABE.js → analytics-CUJbpoA7.js} +2 -2
- package/public/assets/{analytics-BiYHZABE.js.map → analytics-CUJbpoA7.js.map} +1 -1
- package/public/assets/{automation-deDG3zs5.js → automation-CMketzfB.js} +2 -2
- package/public/assets/{automation-deDG3zs5.js.map → automation-CMketzfB.js.map} +1 -1
- package/public/assets/{badge-BbfOskRI.js → badge-BLar6FjP.js} +2 -2
- package/public/assets/{badge-BbfOskRI.js.map → badge-BLar6FjP.js.map} +1 -1
- package/public/assets/branch-state-badge-CNko2_BL.js +2 -0
- package/public/assets/{branch-state-badge-ClWlvQks.js.map → branch-state-badge-CNko2_BL.js.map} +1 -1
- package/public/assets/{button-DWiw--lT.js → button-BrIB69qd.js} +2 -2
- package/public/assets/{button-DWiw--lT.js.map → button-BrIB69qd.js.map} +1 -1
- package/public/assets/card-lhnFnBoz.js +2 -0
- package/public/assets/{card-BeqvuYTT.js.map → card-lhnFnBoz.js.map} +1 -1
- package/public/assets/{channels-ln-_oQlI.js → channels-Tg8l4n5J.js} +2 -2
- package/public/assets/{channels-ln-_oQlI.js.map → channels-Tg8l4n5J.js.map} +1 -1
- package/public/assets/chat-BV_pnRhk.js +2 -0
- package/public/assets/{chat-DH8vR-mf.js.map → chat-BV_pnRhk.js.map} +1 -1
- package/public/assets/{connectors-CmxpixdH.js → connectors-B5wbjyUd.js} +2 -2
- package/public/assets/{connectors-CmxpixdH.js.map → connectors-B5wbjyUd.js.map} +1 -1
- package/public/assets/copy-button-Cd-LbSbg.js +2 -0
- package/public/assets/{copy-button-CIXy8ks6.js.map → copy-button-Cd-LbSbg.js.map} +1 -1
- package/public/assets/dist-30weaXto.js +2 -0
- package/public/assets/{dist-LFuRypZd.js.map → dist-30weaXto.js.map} +1 -1
- package/public/assets/{dist-CtuS_3Jl.js → dist-B5VvlkhI.js} +2 -2
- package/public/assets/{dist-CtuS_3Jl.js.map → dist-B5VvlkhI.js.map} +1 -1
- package/public/assets/dist-BEzpMqLy.js +2 -0
- package/public/assets/{dist-Q6OOS84U.js.map → dist-BEzpMqLy.js.map} +1 -1
- package/public/assets/{dist-CbxR2pzD.js → dist-BjOBkHHY.js} +2 -2
- package/public/assets/{dist-CbxR2pzD.js.map → dist-BjOBkHHY.js.map} +1 -1
- package/public/assets/{dist-zTw85jD8.js → dist-BnbGIUmi.js} +2 -2
- package/public/assets/{dist-zTw85jD8.js.map → dist-BnbGIUmi.js.map} +1 -1
- package/public/assets/{dist-BufB2Xy_.js → dist-Bp10H3oR.js} +2 -2
- package/public/assets/{dist-BufB2Xy_.js.map → dist-Bp10H3oR.js.map} +1 -1
- package/public/assets/{dist-Du_bcq-z.js → dist-CQF3Jiqg.js} +2 -2
- package/public/assets/{dist-Du_bcq-z.js.map → dist-CQF3Jiqg.js.map} +1 -1
- package/public/assets/dist-Cgy0FmP-.js +2 -0
- package/public/assets/{dist-CXkmW1Od.js.map → dist-Cgy0FmP-.js.map} +1 -1
- package/public/assets/{dist-KxiSP9h9.js → dist-DAl28ejX.js} +2 -2
- package/public/assets/{dist-KxiSP9h9.js.map → dist-DAl28ejX.js.map} +1 -1
- package/public/assets/{dist-eG_93I3z.js → dist-DDUGmmoi.js} +2 -2
- package/public/assets/{dist-eG_93I3z.js.map → dist-DDUGmmoi.js.map} +1 -1
- package/public/assets/{dist-BQXmO19g.js → dist-DZ97OosY.js} +2 -2
- package/public/assets/{dist-BQXmO19g.js.map → dist-DZ97OosY.js.map} +1 -1
- package/public/assets/{es2015-ibwCE_JR.js → es2015-B8Z9p8oQ.js} +2 -2
- package/public/assets/{es2015-ibwCE_JR.js.map → es2015-B8Z9p8oQ.js.map} +1 -1
- package/public/assets/{formatted-body-impl-l_m1i28m.js → formatted-body-impl-DzHQTBmn.js} +2 -2
- package/public/assets/{formatted-body-impl-l_m1i28m.js.map → formatted-body-impl-DzHQTBmn.js.map} +1 -1
- package/public/assets/index-CYLrIuJT.css +2 -0
- package/public/assets/index-CdEAo41I.js +23 -0
- package/public/assets/index-CdEAo41I.js.map +1 -0
- package/public/assets/{input-DUdtS3nG.js → input-B_fUHtLD.js} +2 -2
- package/public/assets/{input-DUdtS3nG.js.map → input-B_fUHtLD.js.map} +1 -1
- package/public/assets/{insights-DpNVkiVq.js → insights-C_pJNcFD.js} +2 -2
- package/public/assets/{insights-DpNVkiVq.js.map → insights-C_pJNcFD.js.map} +1 -1
- package/public/assets/{integrations-DscgKx2I.js → integrations-sYKtupOq.js} +2 -2
- package/public/assets/{integrations-DscgKx2I.js.map → integrations-sYKtupOq.js.map} +1 -1
- package/public/assets/{lib-Gp69YB9e.js → lib-ng6WZYjE.js} +2 -2
- package/public/assets/{lib-Gp69YB9e.js.map → lib-ng6WZYjE.js.map} +1 -1
- package/public/assets/{lucide-react-uFIMC7W7.js → lucide-react-BYZuAhQs.js} +2 -2
- package/public/assets/{lucide-react-uFIMC7W7.js.map → lucide-react-BYZuAhQs.js.map} +1 -1
- package/public/assets/maintenance-Ckz5BAor.js +2 -0
- package/public/assets/{maintenance-BhAxK-Fd.js.map → maintenance-Ckz5BAor.js.map} +1 -1
- package/public/assets/{managed-agents-Bg4aG1a9.js → managed-agents-BbZclnqP.js} +4 -4
- package/public/assets/{managed-agents-Bg4aG1a9.js.map → managed-agents-BbZclnqP.js.map} +1 -1
- package/public/assets/markdown-preview-impl-CtuHw6Mm.js +2 -0
- package/public/assets/{markdown-preview-impl-BJHjAo1H.js.map → markdown-preview-impl-CtuHw6Mm.js.map} +1 -1
- package/public/assets/memory-C5DK3rhW.js +2 -0
- package/public/assets/{memory-WwrerNC0.js.map → memory-C5DK3rhW.js.map} +1 -1
- package/public/assets/{messages-BY6mboBf.js → messages-CsjtiFEb.js} +2 -2
- package/public/assets/{messages-BY6mboBf.js.map → messages-CsjtiFEb.js.map} +1 -1
- package/public/assets/{orchestrators-ClNqQHnw.js → orchestrators-D9fMiBRy.js} +2 -2
- package/public/assets/{orchestrators-ClNqQHnw.js.map → orchestrators-D9fMiBRy.js.map} +1 -1
- package/public/assets/{overview-Bk91s8bA.js → overview-BLWoCnNQ.js} +2 -2
- package/public/assets/{overview-Bk91s8bA.js.map → overview-BLWoCnNQ.js.map} +1 -1
- package/public/assets/{pairs-IxkuPIfN.js → pairs-HnvpLz4C.js} +2 -2
- package/public/assets/{pairs-IxkuPIfN.js.map → pairs-HnvpLz4C.js.map} +1 -1
- package/public/assets/{projects-D7WJtj7Y.js → projects-BPI8z55X.js} +2 -2
- package/public/assets/{projects-D7WJtj7Y.js.map → projects-BPI8z55X.js.map} +1 -1
- package/public/assets/{react-dom-DF2cuNaf.js → react-dom-Dbhs84ug.js} +2 -2
- package/public/assets/{react-dom-DF2cuNaf.js.map → react-dom-Dbhs84ug.js.map} +1 -1
- package/public/assets/{registry-DUzVG_Oi.js → registry-BqAS3ADV.js} +2 -2
- package/public/assets/{registry-DUzVG_Oi.js.map → registry-BqAS3ADV.js.map} +1 -1
- package/public/assets/{security-D9ru8DHd.js → security-CWgtcQ2X.js} +2 -2
- package/public/assets/{security-D9ru8DHd.js.map → security-CWgtcQ2X.js.map} +1 -1
- package/public/assets/{select-Bu-gipoC.js → select-UdIYiKFs.js} +2 -2
- package/public/assets/{select-Bu-gipoC.js.map → select-UdIYiKFs.js.map} +1 -1
- package/public/assets/settings-ruDxPuza.js +6 -0
- package/public/assets/settings-ruDxPuza.js.map +1 -0
- package/public/assets/{store-DGM2s9L1.js → store-pKbf_WYd.js} +5 -5
- package/public/assets/{store-DGM2s9L1.js.map → store-pKbf_WYd.js.map} +1 -1
- package/public/assets/{tasks-C94dSpvI.js → tasks-DomQFwZP.js} +2 -2
- package/public/assets/{tasks-C94dSpvI.js.map → tasks-DomQFwZP.js.map} +1 -1
- package/public/assets/{teams-Cq7HWQe9.js → teams-CXNp0U3B.js} +2 -2
- package/public/assets/{teams-Cq7HWQe9.js.map → teams-CXNp0U3B.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-BQr-VK32.js → terminal-viewer-impl-DiWBtzvZ.js} +3 -3
- package/public/assets/{terminal-viewer-impl-BQr-VK32.js.map → terminal-viewer-impl-DiWBtzvZ.js.map} +1 -1
- package/public/assets/{use-keyboard-viewport-CeLM-PeW.js → use-keyboard-viewport-D8_NYDkh.js} +2 -2
- package/public/assets/{use-keyboard-viewport-CeLM-PeW.js.map → use-keyboard-viewport-D8_NYDkh.js.map} +1 -1
- package/public/assets/user-avatar-DBs9Koqp.js +2 -0
- package/public/assets/{user-avatar-aCd8Fb5v.js.map → user-avatar-DBs9Koqp.js.map} +1 -1
- package/public/assets/{work-queue-BuyQhedx.js → work-queue-BD29QuLd.js} +2 -2
- package/public/assets/{work-queue-BuyQhedx.js.map → work-queue-BD29QuLd.js.map} +1 -1
- package/public/assets/workspaces-BolccWZy.js +3 -0
- package/public/assets/{workspaces-DXTrd2or.js.map → workspaces-BolccWZy.js.map} +1 -1
- package/public/index.html +21 -21
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/src/adapter.ts +9 -1
- package/src/agent-busy-health.ts +63 -62
- package/src/codex-wham-quota.ts +5 -76
- package/src/compaction-watch.ts +7 -6
- package/src/db/provider-quota-burn.ts +6 -18
- package/src/db/provider-quotas.ts +5 -6
- package/src/lifecycle-manager.ts +4 -2
- package/src/maintenance/jobs.ts +1 -1
- package/src/mcp/tools-spawn.ts +13 -2
- package/src/mcp-team-template-tools.ts +8 -1
- package/src/notification-types.ts +3 -1
- package/src/routes/agents-spawn.ts +4 -2
- package/src/routes/index.ts +3 -0
- package/src/routes/routing-policy.ts +25 -0
- package/src/routing-policy-push.ts +82 -0
- package/src/routing-policy-store.ts +148 -0
- package/src/routing-rule-resolver.ts +87 -0
- package/src/security.ts +6 -0
- package/src/services/spawn-agent.ts +46 -1
- package/src/spawn-targets.ts +44 -12
- package/public/assets/MessageBubble-BO9eaLce.js +0 -4
- package/public/assets/agent-profiles-lEZqu7Gm.js +0 -2
- package/public/assets/agent-profiles-lEZqu7Gm.js.map +0 -1
- package/public/assets/agents-DwXYbjeZ.js +0 -2
- package/public/assets/branch-state-badge-ClWlvQks.js +0 -2
- package/public/assets/card-BeqvuYTT.js +0 -2
- package/public/assets/chat-DH8vR-mf.js +0 -2
- package/public/assets/copy-button-CIXy8ks6.js +0 -2
- package/public/assets/dist-CXkmW1Od.js +0 -2
- package/public/assets/dist-LFuRypZd.js +0 -2
- package/public/assets/dist-Q6OOS84U.js +0 -2
- package/public/assets/index-BETon8ID.css +0 -2
- package/public/assets/index-D1GX2b-m.js +0 -23
- package/public/assets/index-D1GX2b-m.js.map +0 -1
- package/public/assets/maintenance-BhAxK-Fd.js +0 -2
- package/public/assets/markdown-preview-impl-BJHjAo1H.js +0 -2
- package/public/assets/memory-WwrerNC0.js +0 -2
- package/public/assets/settings-CoOySnqS.js +0 -6
- package/public/assets/settings-CoOySnqS.js.map +0 -1
- package/public/assets/user-avatar-aCd8Fb5v.js +0 -2
- package/public/assets/workspaces-DXTrd2or.js +0 -3
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Live delta push for routing-policy saves (#732). When the policy is updated via
|
|
2
|
+
// PUT /api/routing-policy, all live spawn-capable agents (coordinators) receive an
|
|
3
|
+
// in-band routing.policy.changed notification so the change takes effect mid-session
|
|
4
|
+
// without respawn.
|
|
5
|
+
//
|
|
6
|
+
// Delivery mechanism: same emitRelayEvent + routeNotification path as agent.ready /
|
|
7
|
+
// agent.exited (#308/#239). emitRelayEvent writes to the durable bus outbox (so bus
|
|
8
|
+
// clients subscribed to routing.* receive it immediately); routeNotification sends a
|
|
9
|
+
// message.new push to each spawn-capable agent's inbox so connected coordinators
|
|
10
|
+
// that are subscribed only to message.* are also woken with the delta in-band.
|
|
11
|
+
//
|
|
12
|
+
// Spawn-capable = live token carries command:spawn (or command:* / admin:* / *).
|
|
13
|
+
// The same token-scope check used by quota-advisory.ts (isSpawnCapableAgent).
|
|
14
|
+
import { getDb } from "./db";
|
|
15
|
+
import { emitRelayEvent } from "./events";
|
|
16
|
+
import { routeNotification } from "./notification-router";
|
|
17
|
+
import { parseJson } from "./utils";
|
|
18
|
+
import { isRecord } from "agent-relay-sdk";
|
|
19
|
+
import type { RoutingPolicy } from "agent-relay-sdk";
|
|
20
|
+
|
|
21
|
+
const PUSH_TTL_MS = 15 * 60 * 1000;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Emit a durable `routing.policy.changed` event and push the delta to every
|
|
25
|
+
* live spawn-capable agent so coordinators ingest the new policy mid-session.
|
|
26
|
+
* Called by setRoutingPolicy on every successful save.
|
|
27
|
+
*/
|
|
28
|
+
export function notifyRoutingPolicyChanged(policy: RoutingPolicy, opts: { updatedBy?: string } = {}): void {
|
|
29
|
+
emitRelayEvent({
|
|
30
|
+
type: "routing.policy.changed",
|
|
31
|
+
source: "server",
|
|
32
|
+
subject: "routing-policy:default",
|
|
33
|
+
data: { policy: policy as unknown as Record<string, unknown>, updatedBy: opts.updatedBy ?? null },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const recipients = getSpawnCapableAgentIds();
|
|
37
|
+
if (recipients.length === 0) return;
|
|
38
|
+
|
|
39
|
+
routeNotification({
|
|
40
|
+
type: "routing.policy.changed",
|
|
41
|
+
scope: {},
|
|
42
|
+
recipients,
|
|
43
|
+
ttlMs: PUSH_TTL_MS,
|
|
44
|
+
message: {
|
|
45
|
+
subject: "Routing policy updated",
|
|
46
|
+
body: `🗺 Routing policy updated${opts.updatedBy ? ` by ${opts.updatedBy}` : ""}. Ingest this delta now — spawn routing decisions take effect immediately without respawn. Re-read relay_spawn_targets for live provider/model options.`,
|
|
47
|
+
payload: { kind: "routing.policy.changed", policy: policy as unknown as Record<string, unknown>, updatedBy: opts.updatedBy ?? null },
|
|
48
|
+
replyExpected: false,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type AgentRow = { id: string; meta: string | null };
|
|
54
|
+
|
|
55
|
+
function getSpawnCapableAgentIds(): string[] {
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
const candidates = getDb().query(`
|
|
58
|
+
SELECT id, meta FROM agents
|
|
59
|
+
WHERE ready = 1 AND status NOT IN ('offline', 'stale')
|
|
60
|
+
`).all() as AgentRow[];
|
|
61
|
+
return candidates.filter((agent) => isSpawnCapable(agent, now)).map((a) => a.id);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isSpawnCapable(agent: AgentRow, now: number): boolean {
|
|
65
|
+
const meta = parseJson<Record<string, unknown>>(agent.meta ?? "{}", {});
|
|
66
|
+
const auth = isRecord(meta.auth) ? meta.auth : undefined;
|
|
67
|
+
const jti = typeof auth?.jti === "string" ? auth.jti : undefined;
|
|
68
|
+
if (!jti) return false;
|
|
69
|
+
const row = getDb().query(
|
|
70
|
+
"SELECT scope, revoked_at, expires_at FROM tokens WHERE jti = ?",
|
|
71
|
+
).get(jti) as { scope: string; revoked_at?: number | null; expires_at?: number | null } | undefined;
|
|
72
|
+
if (!row || row.revoked_at) return false;
|
|
73
|
+
const nowSeconds = Math.floor(now / 1000);
|
|
74
|
+
if (row.expires_at !== null && row.expires_at !== undefined && row.expires_at <= nowSeconds) return false;
|
|
75
|
+
const scopes = parseJson<string[]>(row.scope, []);
|
|
76
|
+
return (
|
|
77
|
+
scopes.includes("command:spawn") ||
|
|
78
|
+
scopes.includes("command:*") ||
|
|
79
|
+
scopes.includes("admin:*") ||
|
|
80
|
+
scopes.includes("*")
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Routing-policy storage (#728). One relay-owned policy document per instance,
|
|
2
|
+
// backed by the existing `config` table (namespace "routing-policy", key "default")
|
|
3
|
+
// so history + config.changed SSE come for free at no schema cost.
|
|
4
|
+
//
|
|
5
|
+
// The policy covers routing rules (task-type/capability → provider/model/effort)
|
|
6
|
+
// and quota tuning (band thresholds + per-provider hardBackstopUtilization /
|
|
7
|
+
// burnPenaltyUtilizationFloor). #730 will wire quota fields into spawn-targets.ts;
|
|
8
|
+
// this module only stores and retrieves them.
|
|
9
|
+
import { ValidationError } from "./db";
|
|
10
|
+
import { getConfig, setConfig } from "./config-store";
|
|
11
|
+
import { notifyRoutingPolicyChanged } from "./routing-policy-push";
|
|
12
|
+
import { isRecord, SPAWN_PROVIDERS } from "agent-relay-sdk";
|
|
13
|
+
import type { ProviderQuotaRoutingConfig, QuotaBandThresholds, RoutingPolicy, RoutingRule } from "agent-relay-sdk";
|
|
14
|
+
|
|
15
|
+
export const ROUTING_POLICY_NAMESPACE = "routing-policy";
|
|
16
|
+
export const ROUTING_POLICY_KEY = "default";
|
|
17
|
+
|
|
18
|
+
/** Default policy — preserves current spawn-targets.ts behaviour when unset. */
|
|
19
|
+
export const DEFAULT_ROUTING_POLICY: RoutingPolicy = {
|
|
20
|
+
schemaVersion: 1,
|
|
21
|
+
rules: [],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Retrieve the stored routing policy, falling back to the default when absent. */
|
|
25
|
+
export function getRoutingPolicy(): RoutingPolicy {
|
|
26
|
+
const entry = getConfig<RoutingPolicy>(ROUTING_POLICY_NAMESPACE, ROUTING_POLICY_KEY);
|
|
27
|
+
return entry ? normalizeRoutingPolicy(entry.value) : { ...DEFAULT_ROUTING_POLICY };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Persist the routing policy. The incoming value is normalized so partial bodies
|
|
32
|
+
* still produce a valid document. Emits `config.changed` via `setConfig` so the
|
|
33
|
+
* dashboard + any live listeners pick it up immediately.
|
|
34
|
+
*/
|
|
35
|
+
export function setRoutingPolicy(value: unknown, opts: { updatedBy?: string } = {}): RoutingPolicy {
|
|
36
|
+
const normalized = normalizeRoutingPolicy(value);
|
|
37
|
+
setConfig(ROUTING_POLICY_NAMESPACE, ROUTING_POLICY_KEY, normalized, opts.updatedBy);
|
|
38
|
+
notifyRoutingPolicyChanged(normalized, opts);
|
|
39
|
+
return normalized;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Normalizer — coerces arbitrary input into a valid RoutingPolicy, defaulting
|
|
44
|
+
// missing fields so a partial PUT is safe and a missing policy is a no-op.
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
export function normalizeRoutingPolicy(value: unknown): RoutingPolicy {
|
|
48
|
+
const input = isRecord(value) ? value : {};
|
|
49
|
+
return {
|
|
50
|
+
schemaVersion: 1,
|
|
51
|
+
rules: normalizeRules(input.rules),
|
|
52
|
+
...(input.quotaBands !== undefined ? { quotaBands: normalizeQuotaBands(input.quotaBands) } : {}),
|
|
53
|
+
...(input.providerQuota !== undefined ? { providerQuota: normalizeProviderQuota(input.providerQuota) } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeRules(raw: unknown): RoutingRule[] {
|
|
58
|
+
if (!Array.isArray(raw)) return [];
|
|
59
|
+
const rules: RoutingRule[] = [];
|
|
60
|
+
for (let i = 0; i < raw.length; i++) {
|
|
61
|
+
rules.push(normalizeRule(raw[i], i));
|
|
62
|
+
}
|
|
63
|
+
return rules;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function normalizeRule(raw: unknown, idx: number): RoutingRule {
|
|
67
|
+
if (!isRecord(raw)) throw new ValidationError(`rules[${idx}] must be an object`);
|
|
68
|
+
const match = isRecord(raw.match) ? raw.match : {};
|
|
69
|
+
if (!isRecord(raw.match)) throw new ValidationError(`rules[${idx}].match must be an object`);
|
|
70
|
+
if (match.taskType === undefined && match.capability === undefined) {
|
|
71
|
+
throw new ValidationError(`rules[${idx}].match must set at least one of: taskType, capability`);
|
|
72
|
+
}
|
|
73
|
+
const target = isRecord(raw.target) ? raw.target : {};
|
|
74
|
+
if (!isRecord(raw.target)) throw new ValidationError(`rules[${idx}].target must be an object`);
|
|
75
|
+
if (typeof target.provider !== "string" || !(SPAWN_PROVIDERS as readonly string[]).includes(target.provider)) {
|
|
76
|
+
throw new ValidationError(`rules[${idx}].target.provider must be one of: ${SPAWN_PROVIDERS.join(", ")}`);
|
|
77
|
+
}
|
|
78
|
+
const rule: RoutingRule = {
|
|
79
|
+
match: {
|
|
80
|
+
...(typeof match.taskType === "string" && match.taskType ? { taskType: match.taskType } : {}),
|
|
81
|
+
...(typeof match.capability === "string" && match.capability ? { capability: match.capability } : {}),
|
|
82
|
+
},
|
|
83
|
+
target: {
|
|
84
|
+
provider: target.provider as RoutingRule["target"]["provider"],
|
|
85
|
+
...(typeof target.model === "string" && target.model ? { model: target.model } : {}),
|
|
86
|
+
...(typeof target.effort === "string" && target.effort ? { effort: target.effort } : {}),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
if (typeof raw.label === "string" && raw.label) rule.label = raw.label;
|
|
90
|
+
if (typeof raw.enabled === "boolean") rule.enabled = raw.enabled;
|
|
91
|
+
return rule;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeQuotaBands(raw: unknown): Partial<QuotaBandThresholds> {
|
|
95
|
+
if (!isRecord(raw)) throw new ValidationError("quotaBands must be an object");
|
|
96
|
+
const result: Partial<QuotaBandThresholds> = {};
|
|
97
|
+
if (raw.cautionAt !== undefined) result.cautionAt = cleanQuotaFraction(raw.cautionAt, "quotaBands.cautionAt");
|
|
98
|
+
if (raw.avoidLargeAt !== undefined) result.avoidLargeAt = cleanQuotaFraction(raw.avoidLargeAt, "quotaBands.avoidLargeAt");
|
|
99
|
+
if (raw.hardAvoidAt !== undefined) result.hardAvoidAt = cleanQuotaFraction(raw.hardAvoidAt, "quotaBands.hardAvoidAt");
|
|
100
|
+
const orderedPairs: Array<[string, number]> = [];
|
|
101
|
+
if (result.cautionAt !== undefined) orderedPairs.push(["cautionAt", result.cautionAt]);
|
|
102
|
+
if (result.avoidLargeAt !== undefined) orderedPairs.push(["avoidLargeAt", result.avoidLargeAt]);
|
|
103
|
+
if (result.hardAvoidAt !== undefined) orderedPairs.push(["hardAvoidAt", result.hardAvoidAt]);
|
|
104
|
+
for (let i = 0; i < orderedPairs.length - 1; i++) {
|
|
105
|
+
const cur = orderedPairs[i]!;
|
|
106
|
+
const next = orderedPairs[i + 1]!;
|
|
107
|
+
if (cur[1] >= next[1]) {
|
|
108
|
+
throw new ValidationError(`quotaBands.${cur[0]} must be less than quotaBands.${next[0]}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function normalizeProviderQuota(raw: unknown): Partial<Record<string, ProviderQuotaRoutingConfig>> {
|
|
115
|
+
if (!isRecord(raw)) throw new ValidationError("providerQuota must be an object");
|
|
116
|
+
const result: Partial<Record<string, ProviderQuotaRoutingConfig>> = {};
|
|
117
|
+
for (const provider of SPAWN_PROVIDERS) {
|
|
118
|
+
if (!(provider in raw)) continue;
|
|
119
|
+
result[provider] = normalizeProviderQuotaConfig(raw[provider], provider);
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function normalizeProviderQuotaConfig(raw: unknown, provider: string): ProviderQuotaRoutingConfig {
|
|
125
|
+
if (!isRecord(raw)) throw new ValidationError(`providerQuota.${provider} must be an object`);
|
|
126
|
+
const cfg: ProviderQuotaRoutingConfig = {};
|
|
127
|
+
if (raw.hardBackstopUtilization !== undefined) {
|
|
128
|
+
cfg.hardBackstopUtilization = cleanFraction(raw.hardBackstopUtilization, `providerQuota.${provider}.hardBackstopUtilization`);
|
|
129
|
+
}
|
|
130
|
+
if (raw.burnPenaltyUtilizationFloor !== undefined) {
|
|
131
|
+
cfg.burnPenaltyUtilizationFloor = cleanFraction(raw.burnPenaltyUtilizationFloor, `providerQuota.${provider}.burnPenaltyUtilizationFloor`);
|
|
132
|
+
}
|
|
133
|
+
return cfg;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function cleanQuotaFraction(value: unknown, field: string): number {
|
|
137
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
138
|
+
throw new ValidationError(`${field} must be a non-negative finite number`);
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function cleanFraction(value: unknown, field: string): number {
|
|
144
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
|
|
145
|
+
throw new ValidationError(`${field} must be a number between 0 and 1`);
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Declarative routing-rules engine (#729).
|
|
2
|
+
//
|
|
3
|
+
// Resolves the first matching enabled rule from RoutingPolicy.rules, given a task-type
|
|
4
|
+
// and/or capability. Rules are ADVISORY — they surface a recommended target to the
|
|
5
|
+
// coordinator; they never override an explicit per-spawn argument or block a spawn.
|
|
6
|
+
//
|
|
7
|
+
// Precedence (highest → lowest):
|
|
8
|
+
// 1. Explicit per-spawn args (provider/model/effort in relay_spawn_agent / SpawnAgentInput).
|
|
9
|
+
// 2. This resolver — the recommendation when no explicit arg is given.
|
|
10
|
+
// 3. Profile default — fallback when no rule matches.
|
|
11
|
+
//
|
|
12
|
+
// The quota band / gate is a parallel SAFETY layer that composes on top of the resolved
|
|
13
|
+
// target (it may advise a cheaper effort or healthier provider; a hard block still blocks).
|
|
14
|
+
// It is NOT part of rule resolution and is NOT consulted here.
|
|
15
|
+
import type { RoutingRule } from "agent-relay-sdk";
|
|
16
|
+
|
|
17
|
+
/** Input criteria for rule matching. At least one field should be set. */
|
|
18
|
+
export interface RoutingMatchInput {
|
|
19
|
+
/** Task-type to match against rule.match.taskType (prefix semantics). */
|
|
20
|
+
taskType?: string;
|
|
21
|
+
/** Capability tag to match against rule.match.capability (exact match). */
|
|
22
|
+
capability?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The resolved target recommendation from a matching rule.
|
|
27
|
+
* Mirrors RoutingRule["target"] so callers never import the full rule.
|
|
28
|
+
*/
|
|
29
|
+
export interface RoutingResolution {
|
|
30
|
+
/** Recommended provider. */
|
|
31
|
+
provider: RoutingRule["target"]["provider"];
|
|
32
|
+
/** Recommended model alias, if the rule specifies one. */
|
|
33
|
+
model?: string;
|
|
34
|
+
/** Recommended effort level, if the rule specifies one. */
|
|
35
|
+
effort?: string;
|
|
36
|
+
/** Human-readable label from the matched rule, for logging/display. */
|
|
37
|
+
matchedRuleLabel?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Evaluate the rules array in order and return the first matching enabled rule's target.
|
|
42
|
+
*
|
|
43
|
+
* Match semantics:
|
|
44
|
+
* - `taskType`: the rule's taskType is a prefix of the input (e.g. rule "gui" matches
|
|
45
|
+
* input "gui" and "gui/ux", but not "gui2" or "xgui"). Case-sensitive.
|
|
46
|
+
* - `capability`: exact match. Case-sensitive.
|
|
47
|
+
* - When a rule sets both taskType AND capability, both must match (AND semantics).
|
|
48
|
+
* - A rule with `enabled: false` is skipped.
|
|
49
|
+
*
|
|
50
|
+
* Returns null when no rule matches or the rules array is empty.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveRoutingRule(
|
|
53
|
+
rules: RoutingRule[],
|
|
54
|
+
input: RoutingMatchInput,
|
|
55
|
+
): RoutingResolution | null {
|
|
56
|
+
for (const rule of rules) {
|
|
57
|
+
if (rule.enabled === false) continue;
|
|
58
|
+
if (!matchesCriteria(rule.match, input)) continue;
|
|
59
|
+
return {
|
|
60
|
+
provider: rule.target.provider,
|
|
61
|
+
...(rule.target.model ? { model: rule.target.model } : {}),
|
|
62
|
+
...(rule.target.effort ? { effort: rule.target.effort } : {}),
|
|
63
|
+
...(rule.label ? { matchedRuleLabel: rule.label } : {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function matchesCriteria(criteria: RoutingRule["match"], input: RoutingMatchInput): boolean {
|
|
70
|
+
if (!criteria.taskType && !criteria.capability) return false;
|
|
71
|
+
if (criteria.taskType !== undefined) {
|
|
72
|
+
if (!input.taskType) return false;
|
|
73
|
+
if (!isPrefixMatch(input.taskType, criteria.taskType)) return false;
|
|
74
|
+
}
|
|
75
|
+
if (criteria.capability !== undefined) {
|
|
76
|
+
if (input.capability !== criteria.capability) return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* True if `value` equals `prefix` or starts with `prefix + "/"`.
|
|
83
|
+
* Guards against substring-only matches (e.g. "gui" must not match "gui2").
|
|
84
|
+
*/
|
|
85
|
+
function isPrefixMatch(value: string, prefix: string): boolean {
|
|
86
|
+
return value === prefix || value.startsWith(prefix + "/");
|
|
87
|
+
}
|
package/src/security.ts
CHANGED
|
@@ -199,6 +199,7 @@ export function requiredScopeFor(method: string, pathname: string): string | nul
|
|
|
199
199
|
if (pathname.startsWith("/api/pairs")) return method === "GET" ? "pairs:read" : "pairs:write";
|
|
200
200
|
if (pathname === "/api/provider-quota-leases/acquire" || pathname === "/api/provider-quota-leases/release") return "quota:write";
|
|
201
201
|
if (pathname === "/api/provider-quotas") return method === "GET" ? "agent:read" : "quota:write";
|
|
202
|
+
if (pathname === "/api/routing-policy") return method === "GET" ? "agent:read" : "settings:write:routing";
|
|
202
203
|
if (pathname === "/api/teams/config") return method === "GET" ? "teams:read" : "settings:write:teams";
|
|
203
204
|
if (pathname.startsWith("/api/teams")) return method === "GET" ? "teams:read" : "teams:write";
|
|
204
205
|
if (pathname.startsWith("/api/blackboard")) return method === "GET" ? "blackboard:read" : "blackboard:write";
|
|
@@ -288,6 +289,11 @@ export function requiredComponentScopeFor(method: string, pathname: string): str
|
|
|
288
289
|
if (pathname === "/api/providers/quota-config" || pathname.match(/^\/api\/providers\/[^/]+\/quota-config$/)) {
|
|
289
290
|
return method === "GET" ? "agent:read" : "settings:write:provider";
|
|
290
291
|
}
|
|
292
|
+
// Routing policy (#728): reads are agent:read (coordinators read to apply rules);
|
|
293
|
+
// writes are settings:write:routing (admin-scoped).
|
|
294
|
+
if (pathname === "/api/routing-policy") {
|
|
295
|
+
return method === "GET" ? "agent:read" : "settings:write:routing";
|
|
296
|
+
}
|
|
291
297
|
if (pathname === "/api/teams/config") return method === "GET" ? "teams:read" : "settings:write:teams";
|
|
292
298
|
if (pathname.startsWith("/api/messages") || pathname.startsWith("/api/inbox")) return method === "GET" ? "message:read" : "message:send";
|
|
293
299
|
if (pathname.startsWith("/api/agent-profiles")) return method === "GET" ? "agent:read" : "agent:write";
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
// them through `spawnAgent(systemCtx)` is a clean belt-and-suspenders follow-up (#342),
|
|
25
25
|
// deliberately out of scope here — this slice converges the two external TRANSPORTS.
|
|
26
26
|
import type { SpawnProvider } from "agent-relay-sdk";
|
|
27
|
-
import { DEFAULT_USER_ID, errMessage, stringValue } from "agent-relay-sdk";
|
|
27
|
+
import { DEFAULT_USER_ID, errMessage, isRecord, stringValue } from "agent-relay-sdk";
|
|
28
28
|
import { countLiveSpawnedAgents, createActivityEvent, ensureTeam, getAgent, listAgents, listWorkspaces, resolveAgentOwnership, resolveProjectForCwd, resolveScopedAnchor, setAgentTeam, ValidationError, withResolvedProvisioning } from "../db";
|
|
29
29
|
import { createCommand } from "../commands-db";
|
|
30
30
|
import { emitCommandEvent } from "../command-events";
|
|
@@ -446,6 +446,51 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
446
446
|
};
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
/**
|
|
450
|
+
* #734 — strip the heavy resolved-provisioning file CONTENTS from a spawn command before
|
|
451
|
+
* echoing it back to the CALLER. `withResolvedProvisioning` inlines every bundled plugin/skill
|
|
452
|
+
* file under `params.agentProfile.provisioning.{skills,plugins}[].files[].content` (~90KB for a
|
|
453
|
+
* bundled-plugin profile like default-relay), which blows the MCP result token cap and hard-errors
|
|
454
|
+
* the call. The ORCHESTRATOR-bound command — dispatched on the command bus inside `spawnAgent`
|
|
455
|
+
* (the row in `listCommands`, not this returned object) — keeps the full bytes so it can still
|
|
456
|
+
* materialize the plugins on the target host. This returns a structural summary (paths + byte
|
|
457
|
+
* counts, contents dropped) leaving the dispatched command untouched (the mutated path is
|
|
458
|
+
* shallow-cloned, never mutated in place).
|
|
459
|
+
*/
|
|
460
|
+
export function summarizeSpawnCommandForCaller(command: Command): Command {
|
|
461
|
+
const profile = command.params.agentProfile;
|
|
462
|
+
if (!isRecord(profile) || !isRecord(profile.provisioning)) return command;
|
|
463
|
+
const provisioning = profile.provisioning;
|
|
464
|
+
const stripContents = (assets: unknown): unknown => {
|
|
465
|
+
if (!Array.isArray(assets)) return assets;
|
|
466
|
+
return assets.map((asset) => {
|
|
467
|
+
if (!isRecord(asset) || !Array.isArray(asset.files)) return asset;
|
|
468
|
+
return {
|
|
469
|
+
...asset,
|
|
470
|
+
files: asset.files.map((file) =>
|
|
471
|
+
isRecord(file) && typeof file.content === "string"
|
|
472
|
+
? { path: file.path, contentBytes: file.content.length }
|
|
473
|
+
: file,
|
|
474
|
+
),
|
|
475
|
+
};
|
|
476
|
+
});
|
|
477
|
+
};
|
|
478
|
+
return {
|
|
479
|
+
...command,
|
|
480
|
+
params: {
|
|
481
|
+
...command.params,
|
|
482
|
+
agentProfile: {
|
|
483
|
+
...profile,
|
|
484
|
+
provisioning: {
|
|
485
|
+
...provisioning,
|
|
486
|
+
skills: stripContents(provisioning.skills),
|
|
487
|
+
plugins: stripContents(provisioning.plugins),
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
449
494
|
/** Poll the agents table for the child that registers with this spawnRequestId (#255). */
|
|
450
495
|
async function waitForSpawnedAgent(spawnRequestId: string, timeoutMs: number, pollMs = 300): Promise<string | null> {
|
|
451
496
|
const deadline = Date.now() + timeoutMs;
|
package/src/spawn-targets.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { countLiveSpawnedAgents, getAgent, getOrchestrator, listOrchestrators, listProviderQuotas, ValidationError } from "./db";
|
|
2
2
|
import { DEFAULT_SPAWN_PROFILE_NAME, listAgentProfiles } from "./config-store";
|
|
3
3
|
import { effectiveProviderCatalogList } from "./provider-catalog-store";
|
|
4
|
+
import { getRoutingPolicy } from "./routing-policy-store";
|
|
4
5
|
import { isPathWithinBase } from "./utils";
|
|
5
6
|
import { McpNotFoundError } from "./mcp-errors";
|
|
7
|
+
import type { ProviderQuotaRoutingConfig } from "agent-relay-sdk";
|
|
6
8
|
import type { Orchestrator, ProviderCatalogSummary, ProviderQuotaBurnRole, ProviderQuotaBurnWindow, ProviderQuotaRecord, QuotaState, SpawnProvider } from "./types";
|
|
7
9
|
|
|
8
10
|
// #308 — spawn discovery + target selection. Home for the live capability matrix that
|
|
@@ -22,6 +24,8 @@ export function buildSpawnTargets(input: { callerId?: string; quota: number }):
|
|
|
22
24
|
const self = preferHost ? orchestrators.find((o) => o.hostname === preferHost || o.id === preferHost) : undefined;
|
|
23
25
|
const liveChildren = input.callerId ? countLiveSpawnedAgents(input.callerId) : 0;
|
|
24
26
|
const quotaAdvisories = providerQuotaAdvisories();
|
|
27
|
+
const policy = getRoutingPolicy();
|
|
28
|
+
const enabledRules = policy.rules.filter((r) => r.enabled !== false);
|
|
25
29
|
|
|
26
30
|
return {
|
|
27
31
|
self: {
|
|
@@ -39,6 +43,14 @@ export function buildSpawnTargets(input: { callerId?: string; quota: number }):
|
|
|
39
43
|
providers: providerMatrixFor(o, quotaAdvisories),
|
|
40
44
|
})),
|
|
41
45
|
profiles: spawnProfilesSummary(),
|
|
46
|
+
// #729 — declarative routing rules advisory. Rules are evaluated in array order;
|
|
47
|
+
// first matching enabled rule wins. Precedence: explicit per-spawn args > matching rule >
|
|
48
|
+
// profile default. Quota gate (blockedNow / quotaPenaltyBand above) is a parallel safety
|
|
49
|
+
// layer applied AFTER target resolution — it advises on the resolved target but does not
|
|
50
|
+
// pick it. Resolve programmatically with resolveRoutingRule() from routing-rule-resolver.ts.
|
|
51
|
+
routing: {
|
|
52
|
+
rules: enabledRules,
|
|
53
|
+
},
|
|
42
54
|
guidance:
|
|
43
55
|
`Default to your own host (isSelf:true); only set orchestratorId to spawn onto another host that runs a provider/model yours doesn't. Omitting profile applies ${DEFAULT_SPAWN_PROFILE_NAME}; omitting model/effort uses the provider default (marked below). Prefer a named profile over assembling raw knobs; set spawnRequestId for idempotency. After spawning you'll be pushed agent.ready / agent.exited / agent.spawn_failed — don't block-poll.`,
|
|
44
56
|
};
|
|
@@ -132,19 +144,28 @@ function providerMatrixFor(o: Orchestrator, quotaAdvisories: Map<SpawnProvider,
|
|
|
132
144
|
}
|
|
133
145
|
|
|
134
146
|
function providerQuotaAdvisories(now = Date.now()): Map<SpawnProvider, SpawnQuotaAdvisory> {
|
|
147
|
+
const policy = getRoutingPolicy();
|
|
135
148
|
const byProvider = new Map<SpawnProvider, ProviderQuotaRecord[]>();
|
|
136
149
|
for (const record of listProviderQuotas({ historyLimit: 0, now }).quotas) {
|
|
137
150
|
if (record.provider !== "claude" && record.provider !== "codex") continue;
|
|
138
151
|
const provider = record.provider;
|
|
139
152
|
byProvider.set(provider, [...(byProvider.get(provider) ?? []), record]);
|
|
140
153
|
}
|
|
141
|
-
return new Map([...byProvider.entries()].map(([provider, records]) => [
|
|
154
|
+
return new Map([...byProvider.entries()].map(([provider, records]) => [
|
|
155
|
+
provider,
|
|
156
|
+
quotaAdvisoryFor(records, now, policy.quotaBands, policy.providerQuota?.[provider]),
|
|
157
|
+
]));
|
|
142
158
|
}
|
|
143
159
|
|
|
144
|
-
function quotaAdvisoryFor(
|
|
160
|
+
function quotaAdvisoryFor(
|
|
161
|
+
records: ProviderQuotaRecord[],
|
|
162
|
+
now: number,
|
|
163
|
+
quotaBands?: Partial<{ cautionAt: number; avoidLargeAt: number; hardAvoidAt: number }>,
|
|
164
|
+
providerCfg?: ProviderQuotaRoutingConfig,
|
|
165
|
+
): SpawnQuotaAdvisory {
|
|
145
166
|
const selected = new Map<ProviderQuotaBurnRole, SpawnQuotaBurnWindow>();
|
|
146
167
|
for (const record of records) {
|
|
147
|
-
for (const window of quotaWindowsFor(record, now)) {
|
|
168
|
+
for (const window of quotaWindowsFor(record, now, providerCfg)) {
|
|
148
169
|
const existing = selected.get(window.role);
|
|
149
170
|
if (!existing || compareQuotaWindows(window, existing) > 0) selected.set(window.role, window);
|
|
150
171
|
}
|
|
@@ -152,7 +173,7 @@ function quotaAdvisoryFor(records: ProviderQuotaRecord[], now: number): SpawnQuo
|
|
|
152
173
|
const windows = [...selected.values()].sort((a, b) => roleSort(a.role) - roleSort(b.role));
|
|
153
174
|
const quotaPenalty = Math.max(0, ...windows.map((window) => window.penalty));
|
|
154
175
|
const blockedNow = windows.some((window) => window.blockedNow);
|
|
155
|
-
const quotaPenaltyBand = quotaPenaltyBandFor(quotaPenalty);
|
|
176
|
+
const quotaPenaltyBand = quotaPenaltyBandFor(quotaPenalty, quotaBands);
|
|
156
177
|
return {
|
|
157
178
|
quotaBurn: { windows },
|
|
158
179
|
quotaPenalty,
|
|
@@ -162,10 +183,10 @@ function quotaAdvisoryFor(records: ProviderQuotaRecord[], now: number): SpawnQuo
|
|
|
162
183
|
};
|
|
163
184
|
}
|
|
164
185
|
|
|
165
|
-
function quotaWindowsFor(record: ProviderQuotaRecord, now: number): SpawnQuotaBurnWindow[] {
|
|
186
|
+
function quotaWindowsFor(record: ProviderQuotaRecord, now: number, providerCfg?: ProviderQuotaRoutingConfig): SpawnQuotaBurnWindow[] {
|
|
166
187
|
const quotaByName = new Map((record.quota?.windows ?? []).map((window) => [window.name, window]));
|
|
167
188
|
return (record.burn?.windows ?? [])
|
|
168
|
-
.map((burnWindow) => spawnQuotaWindow(record, burnWindow, quotaByName.get(burnWindow.name), now))
|
|
189
|
+
.map((burnWindow) => spawnQuotaWindow(record, burnWindow, quotaByName.get(burnWindow.name), now, providerCfg))
|
|
169
190
|
.filter((window): window is SpawnQuotaBurnWindow => Boolean(window));
|
|
170
191
|
}
|
|
171
192
|
|
|
@@ -174,6 +195,7 @@ function spawnQuotaWindow(
|
|
|
174
195
|
burnWindow: ProviderQuotaBurnWindow,
|
|
175
196
|
quotaWindow: QuotaState["windows"][number] | undefined,
|
|
176
197
|
now: number,
|
|
198
|
+
providerCfg?: ProviderQuotaRoutingConfig,
|
|
177
199
|
): SpawnQuotaBurnWindow | undefined {
|
|
178
200
|
if (!quotaWindow) return undefined;
|
|
179
201
|
const timeToResetMs = typeof quotaWindow.resetsAt === "number" ? Math.max(0, quotaWindow.resetsAt - now) : null;
|
|
@@ -181,6 +203,10 @@ function spawnQuotaWindow(
|
|
|
181
203
|
? Math.max(0, burnWindow.earlyWallMs) / timeToResetMs
|
|
182
204
|
: null;
|
|
183
205
|
const weight = burnWindow.role === "7d" ? 3 : 1;
|
|
206
|
+
const hardBackstop = providerCfg?.hardBackstopUtilization ?? 0.99;
|
|
207
|
+
const utilizationFloor = providerCfg?.burnPenaltyUtilizationFloor ?? 0.5;
|
|
208
|
+
const rawPenalty = (earlyFraction ?? 0) * weight;
|
|
209
|
+
const penalty = quotaWindow.utilization < utilizationFloor ? 0 : rawPenalty;
|
|
184
210
|
return {
|
|
185
211
|
role: burnWindow.role,
|
|
186
212
|
name: burnWindow.name,
|
|
@@ -195,8 +221,8 @@ function spawnQuotaWindow(
|
|
|
195
221
|
earlyWallMs: burnWindow.earlyWallMs,
|
|
196
222
|
earlyFraction,
|
|
197
223
|
weight,
|
|
198
|
-
penalty
|
|
199
|
-
blockedNow: quotaWindow.utilization >=
|
|
224
|
+
penalty,
|
|
225
|
+
blockedNow: quotaWindow.utilization >= hardBackstop,
|
|
200
226
|
};
|
|
201
227
|
}
|
|
202
228
|
|
|
@@ -206,10 +232,16 @@ function compareQuotaWindows(a: SpawnQuotaBurnWindow, b: SpawnQuotaBurnWindow):
|
|
|
206
232
|
return a.utilization - b.utilization;
|
|
207
233
|
}
|
|
208
234
|
|
|
209
|
-
function quotaPenaltyBandFor(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
235
|
+
function quotaPenaltyBandFor(
|
|
236
|
+
quotaPenalty: number,
|
|
237
|
+
quotaBands?: Partial<{ cautionAt: number; avoidLargeAt: number; hardAvoidAt: number }>,
|
|
238
|
+
): SpawnQuotaBurnBand {
|
|
239
|
+
const hardAvoidAt = quotaBands?.hardAvoidAt ?? 1.0;
|
|
240
|
+
const avoidLargeAt = quotaBands?.avoidLargeAt ?? 0.5;
|
|
241
|
+
const cautionAt = quotaBands?.cautionAt ?? 0.15;
|
|
242
|
+
if (quotaPenalty >= hardAvoidAt) return "hard-avoid";
|
|
243
|
+
if (quotaPenalty >= avoidLargeAt) return "avoid-large";
|
|
244
|
+
if (quotaPenalty >= cautionAt) return "caution";
|
|
213
245
|
return "normal";
|
|
214
246
|
}
|
|
215
247
|
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./chunk-CilyBKbf.js";import{B as t,Bn as n,Bt as r,Dt as i,Et as a,Jn as o,L as s,Nn as c,On as l,Qn as u,V as d,Vn as f,Xn as p,Xt as m,Zt as h,_n as g,a as _,b as v,bn as y,c as ee,cn as te,ft as b,g as x,gn as S,hn as C,i as ne,in as re,lr as w,lt as T,m as E,pn as D,r as O,sn as ie,x as k,xn as A,yn as ae}from"./lucide-react-uFIMC7W7.js";import{g as j,r as M,t as N}from"./store-DGM2s9L1.js";import{E as P,H as oe,N as F,P as I,R as se,S as ce,V as le,_ as ue,d as de,f as fe,k as L,q as R,rt as z,v as pe}from"./display-DRiGi1zj.js";import{t as B}from"./types-Tv67voFu.js";import{a as me,i as he,o as ge,t as V,w as H}from"./index-D1GX2b-m.js";import{t as _e}from"./user-avatar-aCd8Fb5v.js";var U=e(w(),1),ve={online:`came online`,offline:`went offline`,shutdown:`shut down`,restarted:`restarted`,"compaction-requested":`compaction requested`,compacting:`compaction started`,compacted:`context compacted`,"compaction-stalled":`compaction stalled — may need manual intervention`,"clear-requested":`clear requested`,"clearing-context":`clear started`,"context-cleared":`context cleared`,"clear-stalled":`context clear stalled — may need manual intervention`,"shutting-down":`shutting down`},ye=new Set(Object.keys(ve)),be=3e3;function W(e){return new Date(e.occurredAt??e.createdAt).getTime()}function xe(e){if(e.kind!==`session`)return null;let t=e.payload?.session;return!t||t.type!==`narration`&&t.type!==`reasoning`&&t.type!==`tool`?null:{id:e.id,kind:t.type,label:typeof t.label==`string`?t.label:void 0,text:e.body,ts:W(e),turnId:typeof t.turnId==`string`?t.turnId:void 0}}function Se(e){if(e.kind!==`session`)return null;let t=e.payload?.session;if(t?.type!==`notice`)return null;let n=typeof t.label==`string`&&t.label?t.label:`compacted`;return{id:`notice-${e.id}`,status:n,timestamp:W(e)}}function Ce(e){if(e.kind!==`session`)return null;let t=e.payload?.session;if(t?.type!==`relay-injection`)return null;let n=t.relayInjection&&typeof t.relayInjection==`object`&&!Array.isArray(t.relayInjection)?t.relayInjection:{},r=typeof n.category==`string`&&[`memory`,`instructions`,`tools`,`comms`,`knowledge`,`context`].includes(n.category)?n.category:`context`,i=typeof n.timestamp==`number`&&Number.isFinite(n.timestamp)?n.timestamp:W(e),a=n.detail&&typeof n.detail==`object`&&!Array.isArray(n.detail)?n.detail:{};return{id:e.id,category:r,summary:typeof n.summary==`string`&&n.summary.trim()?n.summary:e.body,detail:a,agentId:typeof n.agentId==`string`&&n.agentId?n.agentId:e.from,timestamp:i}}function we(e){if(e.kind!==`session`)return null;let t=e.payload?.session;if(t?.type!==`prompt-resolution`)return null;let n=t.promptResolution&&typeof t.promptResolution==`object`&&!Array.isArray(t.promptResolution)?t.promptResolution:{},r=n.kind===`questions`||n.kind===`plan`||n.kind===`command`||n.kind===`tool`?n.kind:`tool`,i=typeof n.title==`string`&&n.title.trim()?n.title:`Prompt resolved`,a=typeof n.decisionLabel==`string`&&n.decisionLabel.trim()?n.decisionLabel:typeof t.label==`string`&&t.label.trim()?t.label:`Resolved`,o=n.answers&&typeof n.answers==`object`&&!Array.isArray(n.answers)?n.answers:{};return{id:e.id,title:i,kind:r,body:typeof n.promptBody==`string`?n.promptBody:typeof n.body==`string`?n.body:e.body,toolName:typeof n.toolName==`string`&&n.toolName.trim()?n.toolName:i,decisionLabel:a,questions:Te(n.questions,o,a),timestamp:W(e)}}function Te(e,t,n){let r=(Array.isArray(e)?e:[]).map((e,r)=>{let i=e&&typeof e==`object`&&!Array.isArray(e)?e:{},a=typeof i.question==`string`&&i.question.trim()?i.question.trim():typeof i.header==`string`&&i.header.trim()?i.header.trim():`Question ${r+1}`,o=t[a]??t[String(i.question??``)]??t[String(i.header??``)];return{question:a,answer:typeof o==`string`&&o.trim()?o.trim():n}});return r.length?r:Object.entries(t).map(([e,t])=>({question:e,answer:t}))}function Ee(e,t){let n=e.trim();for(let e of t){let t=e.trim();if(t){if(n===t)break;n.startsWith(`${t}\n\n`)&&(n=n.slice(t.length+2).trimStart())}}return n.trim()||e.trim()}function De(e){return e.kind===`system`&&e.from===`system`}var Oe=new Set([`agent.ready`,`agent.exited`,`agent.spawn_failed`]);function G(e){let t=new Date(e);return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,`0`)}-${String(t.getDate()).padStart(2,`0`)}`}function ke(e){let t=e.filter(e=>ye.has(e.status)).slice().sort((e,t)=>e.timestamp-t.timestamp),n=[];for(let e of t)n.some(t=>t.id===e.id)||n.some(t=>t.status===e.status&&Math.abs(t.timestamp-e.timestamp)<=be)||n.push(e);return n}function Ae(e,t,n,r=[],i){let a=[];for(let e of r){let t=e.entries.reduce((e,t)=>Math.min(e,R(t.originalCreatedAt)),1/0),n=Number.isFinite(t)?t-1:e.importedAt;a.push({ts:n,entry:{type:`import-boundary`,history:e,timestamp:n}});for(let t of e.entries)a.push({ts:R(t.originalCreatedAt),entry:{type:`imported-message`,history:e,entry:t}})}if(n){let e=new Date(n).getTime();Number.isFinite(e)&&a.push({ts:e,entry:{type:`created`,timestamp:e}})}if(i?.spawnedBy){let e=n?new Date(n).getTime():0,t=Number.isFinite(e)?e:0;a.push({ts:t,entry:{type:`agent-action`,direction:`spawned-by`,agentId:i.spawnedBy.agentId,provider:i.spawnedBy.provider,status:`ready`,timestamp:t}})}let o=i?.spawnedBy?.agentId,s=null,c=1/0;for(let t of e){if(t.kind===`session`||De(t)||P(t)||!(t.from===`user`||o!==void 0&&t.from===o)||!L(t).trim())continue;let e=W(t);e<c&&(c=e,s=t.id)}let l=new Map,u=new Map,d=new Map;for(let t of e){if(t.kind!==`session`)continue;let e=t.payload?.session;if(e?.type===`narration`&&typeof e.turnId==`string`){let n=u.get(e.turnId)??[];n.push({id:t.id,body:t.body.trim()}),u.set(e.turnId,n)}else if(e?.type===`response`&&typeof e.turnId==`string`){let n=l.get(e.turnId)??[];n.push(t.body.trim()),l.set(e.turnId,n)}}let f=new Map;for(let[e,t]of l){let n=(u.get(e)??[]).map(e=>e.body);f.set(e,t.map(e=>Ee(e,n)))}for(let t of e){if(t.kind!==`session`)continue;let e=t.payload?.session;if(e?.type!==`response`||typeof e.turnId!=`string`)continue;let n=Ee(t.body,(u.get(e.turnId)??[]).map(e=>e.body));n&&n!==t.body&&d.set(t.id,n)}let p=new Set;for(let[e,t]of f){let n=u.get(e)??[];for(let e of t)for(let t=n.length-1;t>=0;t--){let r=n[t];if(!p.has(r.id)&&oe(e,r.body)){p.add(r.id);break}}}let m=new Map,h=[];for(let t of e){if(De(t)){let e=typeof t.payload?.kind==`string`?t.payload.kind:``;if(Oe.has(e)){let n=t.payload,r=typeof n.agentId==`string`?n.agentId:``,i=W(t);if(!r){a.push({ts:i,entry:{type:`notice`,msg:t}});continue}let o=e===`agent.ready`?`ready`:e===`agent.exited`?`exited`:`failed`,s=m.get(r);if(s)s.entry.status=o,typeof n.reason==`string`&&(s.entry.reason=n.reason),s.entry.timestamp=i,s.ts=i;else{let e={ts:i,entry:{type:`agent-action`,direction:`spawned-child`,agentId:r,provider:typeof n.provider==`string`?n.provider:void 0,status:o,reason:typeof n.reason==`string`?n.reason:void 0,timestamp:i}};m.set(r,e),a.push(e)}continue}a.push({ts:W(t),entry:{type:`notice`,msg:t}});continue}let e=Se(t);if(e){h.push(e);continue}let n=xe(t);if(n){if(n.kind===`narration`&&p.has(n.id))continue;a.push({ts:n.ts,entry:{type:`activity`,steps:[n],timestamp:n.ts}});continue}let r=Ce(t);if(r){a.push({ts:r.timestamp,entry:{type:`relay-injection`,event:r}});continue}let i=we(t);if(i){a.push({ts:i.timestamp,entry:{type:`prompt-resolution`,event:i}});continue}if(t.kind===`session`){let e=t.payload?.session;if(e?.type===`response`&&typeof e.turnId==`string`){let e=d.get(t.id);if(e&&e!==t.body){a.push({ts:W(t),entry:{type:`message`,msg:{...t,body:e}}});continue}}}let o=t.id===s?`initial-prompt`:`message`;a.push({ts:W(t),entry:{type:o,msg:t}})}for(let e of ke([...t,...h]))a.push({ts:e.timestamp,entry:{type:`status`,event:e}});let g={created:0,date:1,"import-boundary":2,"imported-message":3,"initial-prompt":4,message:4,activity:4,notice:4,"agent-action":4,"relay-injection":4,"prompt-resolution":4,status:5};a.sort((e,t)=>{let n=e.ts-t.ts;return Math.abs(n)<3e3&&g[e.entry.type]-g[t.entry.type]||n});let _=[],v=``;for(let{ts:e,entry:t}of a){if(t.type===`created`){_.push(t);continue}let n=G(e);if(n!==v&&(_.push({type:`date`,date:n,timestamp:e}),v=n),t.type===`activity`){let e=_[_.length-1];if(e&&e.type===`activity`&&Ne(e,t)){e.steps.push(...t.steps);continue}}_.push(t)}return _}function je(e){return e.type===`message`?`msg-${e.msg.id}`:e.type===`initial-prompt`?`initprompt-${e.msg.id}`:e.type===`notice`?`notice-${e.msg.id}`:e.type===`relay-injection`?`relay-injection-${e.event.id}`:e.type===`prompt-resolution`?`prompt-resolution-${e.event.id}`:e.type===`agent-action`?`agent-action-${e.direction}-${e.agentId}`:e.type===`activity`?`act-${e.steps[0]?.id??e.timestamp}`:e.type===`import-boundary`?`import-${e.history.id}`:e.type===`imported-message`?`import-msg-${e.history.id}-${e.entry.originalMessageId}-${e.entry.position}`:e.type===`status`?`status-${e.event.id}`:e.type===`date`?`date-${e.date}`:`created-${e.timestamp}`}function Me(e){return e.type===`message`||e.type===`initial-prompt`||e.type===`notice`?(e.msg.subject?`${e.msg.subject} `:``)+L(e.msg):e.type===`imported-message`?L(e.entry.message):e.type===`activity`?e.steps.map(e=>[e.label,e.text].filter(Boolean).join(` `)).join(`
|
|
2
|
-
`):e.type===`relay-injection`?`${e.event.category} ${e.event.summary}\n${JSON.stringify(e.event.detail)}`:e.type===`prompt-resolution`?[e.event.title,e.event.decisionLabel,e.event.body,...e.event.questions.map(e=>`${e.question} ${e.answer}`)].filter(Boolean).join(`
|
|
3
|
-
`):``}function Ne(e,t){let n=e.steps[e.steps.length-1]?.turnId,r=t.steps[0]?.turnId;return n&&r?n===r:!n&&!r}function Pe(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/1024/1024).toFixed(1)} MB`}function Fe(e,t){if(t.startsWith(`/`))return t;let n=`${e.replace(/\/+$/,``)}/${t}`.split(`/`),r=[];for(let e of n)!e||e===`.`||(e===`..`?r.pop():r.push(e));return`/`+r.join(`/`)}function Ie(e){let t=e.replace(/\/+$/,``),n=t.lastIndexOf(`/`);return n>0?t.slice(0,n):`/`}function Le(e,t){return e.filter(e=>e.status===`online`&&se(t,e.baseDir)).sort((e,t)=>F(t.baseDir).length-F(e.baseDir).length)[0]||e.find(e=>e.status===`online`)}function Re(){return typeof window<`u`&&window.matchMedia(`(min-width: 1280px)`).matches}function ze(){return typeof window<`u`&&!!window.getSelection()?.toString()}function Be(e){let t=e.payload?.attachments;return Array.isArray(t)?t.filter(e=>!!(e&&typeof e==`object`&&typeof e.artifactId==`string`)):[]}var Ve=[`👍`,`❤️`,`✅`,`👀`];function He(e){let t=new Map;for(let n of e.reactions||[]){let e=I(n.emoji),r=t.get(e)??{emoji:e,count:0,mine:!1,actors:[]};r.count+=1,r.mine=r.mine||n.actorId===`user`,r.actors.push(n.actorId),t.set(e,r)}return[...t.values()].sort((e,t)=>t.count-e.count||e.emoji.localeCompare(t.emoji))}function K(e){return P(e)}function Ue(e,t){let n=e.readBy||[],r=e.resolvedToAgent||t||e.to,i=r?n.includes(r):!1,a=n.some(e=>e!==z);if(i||a){let e=i?r:n.find(e=>e!==z);return{label:`read`,title:e?`Read by ${e}`:`Read by agent`,tone:`ok`,icon:A}}if(e.deliveryStatus===`delivered`)return{label:`delivered`,title:`Delivered to agent inbox`,tone:`ok`,icon:A};if(e.deliveryStatus===`queued`)return{label:`queued`,title:`Queued until the target agent is available`,tone:`warn`,icon:re};if(e.deliveryStatus===`failed`||e.deliveryStatus===`dead`){let t=e.deliveryPoisonReason||e.deliveryLastError||e.deliveryStatus;return{label:e.deliveryStatus,title:t,tone:`danger`,icon:C}}return{label:`sent`,title:`Accepted by Relay`,tone:`muted`,icon:y}}function We(e){let t=R(e.createdAt);return(e.reactions||[]).reduce((e,t)=>Math.max(e,R(t.updatedAt||t.createdAt)),t)}function Ge(e){return e?e.messages.reduce((e,t)=>Math.max(e,We(t)),0):0}function Ke(e,t,n){return(e-t)*n}function qe(e){if(!e)return 0;let t=0;for(let n of e.messages)ce(n)&&(t=Math.max(t,R(n.createdAt)));return t}function q(e){return new Date(e).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`})}function Je(e){for(let t of e)t.previewUrl&&URL.revokeObjectURL(t.previewUrl)}function Ye(e,t,n,r){if(e?.teamId)return{teamId:e.teamId};let i=[...t];n&&!i.some(e=>e.id===n.id)&&i.push(n);let a=i.filter(e=>e.threadId).reverse(),o=a.find(e=>!!r[M(e.threadId,e.channel)])??a[0];return o?.threadId?{threadId:o.threadId,...o.channel?{channel:o.channel}:{}}:null}var J=p(),Xe={online:`text-emerald-400`,offline:`text-zinc-400`,shutdown:`text-zinc-400`,restarted:`text-blue-400`,"compaction-requested":`text-zinc-400`,compacting:`text-amber-400`,compacted:`text-amber-400`,"compaction-stalled":`text-red-400`,"clear-requested":`text-zinc-400`,"clearing-context":`text-amber-400`,"context-cleared":`text-amber-400`,"clear-stalled":`text-red-400`,"shutting-down":`text-zinc-400`};function Ze(e){let t=new Date,[n,r,i]=e.split(`-`).map(Number),a=new Date(n,r-1,i);if(e===G(t.getTime()))return`Today`;let o=new Date(t);return o.setDate(o.getDate()-1),e===G(o.getTime())?`Yesterday`:a.toLocaleDateString(void 0,{weekday:`short`,month:`short`,day:`numeric`,year:a.getFullYear()===t.getFullYear()?void 0:`numeric`})}function Qe({event:e}){return(0,J.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,J.jsxs)(`div`,{className:`flex items-center gap-1.5 text-xs text-muted-foreground`,children:[(0,J.jsx)(ie,{className:o(`w-2 h-2 fill-current`,Xe[e.status]||`text-zinc-400`)}),(0,J.jsx)(`span`,{children:ve[e.status]||e.status}),(0,J.jsx)(`span`,{className:`text-muted-foreground/60`,children:q(e.timestamp)})]}),(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}var $e={"branch.landed":a,"context.warning":E,"agent.context_threshold":E,"agent.resume_budget_exhausted":E,"agent.rate_limit_resume":d},et={"branch.landed":`branch landed`,"context.warning":`context warning`,"agent.context_threshold":`context threshold reached`};function tt(e){return e&&(e.includes(`.`)?e.slice(e.indexOf(`.`)+1):e).replace(/[._-]+/g,` `).trim()||`system signal`}function Y({agentId:e,fallbackLabel:t,className:n,noIcon:r}){let i=N(t=>t.agentsById[e])||null,a=N(e=>e.openInboxThread),s=N(e=>e.switchView),c=i?de(i):t||e;return(0,J.jsxs)(`button`,{type:`button`,className:o(`inline-flex max-w-full items-center gap-1 rounded px-1 text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground`,n),onClick:t=>{t.stopPropagation(),a(e),s(`chat`)},title:`Open chat for ${c}`,children:[!r&&(0,J.jsx)(H,{agent:i,className:`w-4 h-4 text-[10px]`}),(0,J.jsx)(`span`,{className:`max-w-[160px] truncate`,children:c})]})}function nt({msg:e}){let t=e.payload??{},n=it(e),r=typeof t.sha==`string`?t.sha:``,i=r?r.slice(0,7):``,o=typeof t.author==`string`?t.author:``,s=typeof t.branch==`string`?t.branch:``,c=typeof t.base==`string`?t.base:``,l=t.pushed;return(0,J.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,J.jsxs)(`div`,{className:`flex max-w-full flex-wrap items-center justify-center gap-x-1.5 gap-y-0.5 rounded-full border border-emerald-600/35 bg-emerald-500/10 px-2.5 py-1 text-xs text-emerald-700 dark:border-emerald-500/25 dark:text-emerald-300/90`,children:[(0,J.jsx)(a,{className:`h-3 w-3 shrink-0 text-emerald-600 dark:text-emerald-400`}),(0,J.jsx)(`span`,{className:`text-emerald-700 dark:text-emerald-300`,children:`branch landed`}),s&&c&&(0,J.jsxs)(`span`,{className:`font-mono text-emerald-700/80 dark:text-emerald-200/70`,title:`${s} → ${c}`,children:[s,(0,J.jsx)(`span`,{className:`opacity-50`,children:` → `}),c]}),i&&(0,J.jsx)(`span`,{className:`rounded bg-emerald-500/15 px-1 font-mono text-emerald-800 dark:text-emerald-200`,title:r,children:i}),o&&(0,J.jsxs)(`span`,{className:`inline-flex items-center gap-1 text-emerald-700/80 dark:text-emerald-300/80`,children:[(0,J.jsx)(`span`,{className:`opacity-70`,children:`by`}),(0,J.jsx)(Y,{agentId:o,className:`text-emerald-700 hover:text-emerald-900 dark:text-emerald-300/90 dark:hover:text-emerald-100`})]}),l===!1&&(0,J.jsx)(`span`,{className:`text-amber-600 dark:text-amber-300/80`,title:`Local-only land — not pushed to origin`,children:`· local only`}),n&&(0,J.jsxs)(`span`,{className:`max-w-[220px] truncate text-emerald-700/70 dark:text-emerald-300/70`,title:n,children:[`· because `,n]}),(0,J.jsx)(`span`,{className:`text-emerald-700/60 dark:text-emerald-300/50`,children:q(e.occurredAt??e.createdAt)})]}),(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function rt({msg:e}){let t=typeof e.payload?.kind==`string`?e.payload.kind:``;if(t===`branch.landed`)return(0,J.jsx)(nt,{msg:e});let n=$e[t]??s,r=et[t]??tt(t),i=it(e);return(0,J.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,J.jsxs)(`div`,{className:`flex items-center gap-1.5 rounded-full border border-border bg-muted/30 px-2.5 py-1 text-xs text-muted-foreground`,children:[(0,J.jsx)(n,{className:`h-3 w-3`}),(0,J.jsx)(`span`,{children:r}),i&&(0,J.jsxs)(`span`,{className:`max-w-[220px] truncate text-muted-foreground/80`,title:i,children:[`· because `,i]}),(0,J.jsx)(`span`,{className:`text-muted-foreground/60`,children:q(e.occurredAt??e.createdAt)})]}),(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function it(e){let t=e.payload?.routing;if(!t||typeof t!=`object`||Array.isArray(t))return``;let n=t.reason;return typeof n==`string`?n:``}var at={"spawned-child":{ready:`spawned`,exited:`spawned · exited`,failed:`spawn failed`},"spawned-by":{ready:`spawned by`,exited:`spawned by`,failed:`spawned by`}},ot={"spawned-child-ready":t,"spawned-child-exited":T,"spawned-child-failed":C,"spawned-by-ready":f,"spawned-by-exited":f,"spawned-by-failed":f},st={"spawned-child-ready":`text-emerald-400`,"spawned-child-exited":`text-zinc-400`,"spawned-child-failed":`text-red-400`,"spawned-by-ready":`text-blue-400`,"spawned-by-exited":`text-blue-400`,"spawned-by-failed":`text-blue-400`};function ct({entry:e}){let t=`${e.direction}-${e.status}`,n=at[e.direction]?.[e.status]??e.direction,r=ot[t]??s,i=st[t]??`text-muted-foreground`,a=e.status===`failed`;return(0,J.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,J.jsxs)(`div`,{className:o(`flex items-center gap-1.5 rounded-full border bg-muted/30 px-2.5 py-1 text-xs`,a?`border-red-500/30`:`border-border`),children:[(0,J.jsx)(r,{className:o(`h-3 w-3 shrink-0`,i)}),(0,J.jsx)(`span`,{className:`text-muted-foreground`,children:n}),(0,J.jsx)(Y,{agentId:e.agentId}),e.reason&&a&&(0,J.jsxs)(`span`,{className:`text-red-400/80 max-w-[120px] truncate`,title:e.reason,children:[`— `,e.reason]}),(0,J.jsx)(`span`,{className:`text-muted-foreground/60 shrink-0`,children:q(e.timestamp)})]}),(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}var lt={memory:l,instructions:r,tools:_,comms:s,knowledge:c,context:h},ut={memory:`border-emerald-500/25 bg-emerald-500/10 text-emerald-300`,instructions:`border-sky-500/25 bg-sky-500/10 text-sky-300`,tools:`border-amber-500/25 bg-amber-500/10 text-amber-300`,comms:`border-violet-500/25 bg-violet-500/10 text-violet-300`,knowledge:`border-cyan-500/25 bg-cyan-500/10 text-cyan-300`,context:`border-zinc-500/25 bg-zinc-500/10 text-zinc-300`};function dt(e){try{return JSON.stringify(e,null,2)}catch{return String(e)}}function ft({entry:e}){let[t,n]=(0,U.useState)(!1),r=e.event,i=lt[r.category]??v;return(0,J.jsx)(`div`,{className:`flex justify-center py-2 my-1`,children:(0,J.jsxs)(`div`,{className:o(`w-full max-w-[85%] md:max-w-[75%] rounded-lg border px-2.5 py-1.5 text-xs shadow-sm`,ut[r.category]??ut.context),children:[(0,J.jsxs)(`button`,{type:`button`,className:`flex w-full min-w-0 items-center gap-1.5 text-left`,onClick:()=>n(e=>!e),children:[(0,J.jsx)(i,{className:`h-3.5 w-3.5 shrink-0`}),(0,J.jsx)(`span`,{className:`shrink-0 font-medium`,children:r.category}),(0,J.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-foreground/80`,children:r.summary}),(0,J.jsx)(`span`,{className:`shrink-0 text-muted-foreground/60`,children:q(r.timestamp)}),(0,J.jsx)(g,{className:o(`h-3.5 w-3.5 shrink-0 transition-transform`,t&&`rotate-90`)})]}),t&&(0,J.jsx)(`pre`,{className:`mt-2 max-h-72 overflow-auto rounded-md border border-border/60 bg-background/75 p-2 font-mono text-[11px] leading-relaxed text-muted-foreground whitespace-pre-wrap break-words`,children:dt(r.detail)})]})})}function pt({blockedLabel:e,onInterrupt:t}){return e?(0,J.jsx)(`div`,{className:`flex justify-start mb-3 animate-in fade-in slide-in-from-bottom-1 duration-150`,children:(0,J.jsxs)(`div`,{className:`flex h-9 items-center gap-2 rounded-2xl rounded-bl-sm bg-card ring-1 ring-red-500/30 px-4`,children:[(0,J.jsx)(C,{className:`w-3.5 h-3.5 text-red-400`}),(0,J.jsxs)(`span`,{className:`text-xs text-red-300`,children:[`blocked: `,e]})]})}):(0,J.jsx)(`div`,{className:`flex justify-start mb-3 animate-in fade-in slide-in-from-bottom-1 duration-150`,children:(0,J.jsxs)(`div`,{className:`flex h-9 items-center gap-2 rounded-2xl rounded-bl-sm bg-card ring-1 ring-foreground/10 px-4`,children:[(0,J.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,J.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_infinite]`}),(0,J.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_0.2s_infinite]`}),(0,J.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_0.4s_infinite]`})]}),(0,J.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`working`}),t&&(0,J.jsxs)(`button`,{onClick:t,title:`Interrupt`,className:`ml-1 flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs text-muted-foreground hover:text-red-300 hover:bg-red-500/10 transition-colors`,children:[(0,J.jsx)(te,{className:`w-3.5 h-3.5`}),`Stop`]})]})})}var X=`agent-relay:chat-hide-tools`,Z=(()=>{let e=new Set,t=(()=>{try{return window.localStorage.getItem(X)===`on`}catch{return!1}})(),n=()=>e.forEach(e=>e());return typeof window<`u`&&window.addEventListener(`storage`,e=>{e.key===X&&(t=e.newValue===`on`,n())}),{subscribe(t){return e.add(t),()=>e.delete(t)},get:()=>t,toggle(){t=!t;try{window.localStorage.setItem(X,t?`on`:`off`)}catch{}n()}}})();function mt(){return[(0,U.useSyncExternalStore)(Z.subscribe,Z.get,()=>!1),Z.toggle]}function ht({steps:e,showReasoning:t,issueRepo:n}){let[r,i]=mt(),a=t?e:e.filter(e=>e.kind!==`reasoning`);if(!a.length)return null;let s=a.filter(e=>e.kind===`tool`).length;return(0,J.jsx)(`div`,{className:`flex justify-start mb-2`,children:(0,J.jsxs)(`div`,{className:`max-w-[85%] md:max-w-[75%] min-w-0 space-y-1.5`,children:[a.map(e=>e.kind===`narration`?(0,J.jsx)(V,{text:e.text,issueRepo:n,className:`text-sm leading-relaxed break-words text-foreground/90 min-w-0`},e.id):e.kind===`reasoning`?(0,J.jsxs)(`div`,{className:`flex items-start gap-1.5 text-xs leading-relaxed text-muted-foreground/80`,children:[(0,J.jsx)(l,{className:`w-3.5 h-3.5 mt-0.5 shrink-0 opacity-70`}),(0,J.jsx)(V,{text:e.text,issueRepo:n,className:`italic break-words min-w-0`})]},e.id):r?null:(0,J.jsxs)(`div`,{className:`flex items-start gap-1.5 text-xs leading-relaxed text-muted-foreground`,children:[(0,J.jsx)(x,{className:`w-3 h-3 mt-0.5 shrink-0 opacity-70`}),(0,J.jsxs)(`span`,{className:`min-w-0 break-words`,children:[(0,J.jsx)(`span`,{className:`font-medium`,children:e.label||`tool`}),e.text&&e.text.trim()&&e.text.trim()!==(e.label??``).trim()?(0,J.jsxs)(`span`,{className:`opacity-70`,children:[` — `,e.text]}):null]})]},e.id)),s>0&&(0,J.jsxs)(`button`,{onClick:i,className:`flex items-center gap-1 text-[11px] text-muted-foreground/50 hover:text-muted-foreground transition-colors text-left`,children:[(0,J.jsx)(g,{className:o(`w-3 h-3 shrink-0 transition-transform`,!r&&`rotate-90`)}),(0,J.jsx)(`span`,{children:r?`show ${s} tool step${s===1?``:`s`}`:`hide tool steps`})]})]})})}function gt({entry:e}){let{event:t}=e,n=t.kind===`questions`,r=n?t.title:t.kind===`plan`?`Plan prompt`:`${t.toolName} permission`;return(0,J.jsx)(`div`,{className:`flex justify-center py-1.5`,children:(0,J.jsxs)(`div`,{className:`w-full max-w-2xl rounded-md border border-border bg-muted/20 px-3 py-2 text-sm`,children:[(0,J.jsxs)(`div`,{className:`flex items-center gap-2 text-xs text-muted-foreground`,children:[(0,J.jsx)(D,{className:`h-3.5 w-3.5 text-emerald-500`}),(0,J.jsx)(`span`,{className:`font-medium text-foreground`,children:r}),(0,J.jsx)(`span`,{children:t.decisionLabel}),(0,J.jsx)(`span`,{className:`ml-auto shrink-0 text-muted-foreground/60`,children:q(t.timestamp)})]}),n&&t.questions.length>0?(0,J.jsx)(`div`,{className:`mt-2 space-y-1.5`,children:t.questions.map((e,t)=>(0,J.jsxs)(`div`,{className:`grid gap-0.5`,children:[(0,J.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:e.question}),(0,J.jsx)(`div`,{className:`text-sm text-foreground`,children:e.answer})]},`${e.question}-${t}`))}):t.body.trim()?(0,J.jsx)(`div`,{className:`mt-2 max-h-48 overflow-auto rounded border border-border/70 bg-background/40 px-2 py-1.5 text-sm`,children:(0,J.jsx)(V,{text:t.body})}):null]})})}function _t({date:e}){return(0,J.jsxs)(`div`,{className:`flex items-center justify-center gap-3 py-3 my-1`,children:[(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,J.jsx)(`span`,{className:`text-xs font-medium text-muted-foreground`,children:Ze(e)}),(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function vt({timestamp:e}){let t=new Date(e).toLocaleString(void 0,{month:`short`,day:`numeric`,year:`numeric`,hour:`2-digit`,minute:`2-digit`});return(0,J.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,J.jsxs)(`span`,{className:`text-xs text-muted-foreground/60`,children:[`created `,t]}),(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function yt({history:e}){let t=e.sourceAgentLabel||e.sourcePeerId,n=e.entries.length;return(0,J.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,J.jsxs)(`div`,{className:`flex items-center gap-1.5 rounded-full border border-dashed border-border bg-muted/30 px-2 py-1 text-[11px] text-muted-foreground`,children:[(0,J.jsx)(i,{className:`h-3 w-3`}),(0,J.jsxs)(`span`,{children:[`forked history from `,t]}),(0,J.jsxs)(`span`,{className:`text-muted-foreground/60`,children:[n,` `,n===1?`entry`:`entries`]})]}),(0,J.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function bt({entry:e,sourceLabel:t,issueRepo:n}){let r=e.message,i=L(r),a=e.originalFrom===z;return(0,J.jsx)(`div`,{className:o(`flex mb-3 opacity-80`,a?`justify-end`:`justify-start`),children:(0,J.jsx)(`div`,{className:`max-w-[85%] md:max-w-[75%]`,children:(0,J.jsxs)(`div`,{className:o(`rounded-2xl border border-dashed px-3.5 py-2 text-sm select-text`,a?`border-primary/25 bg-primary/10 text-foreground rounded-br-sm`:`border-border bg-card/70 rounded-bl-sm`),children:[(0,J.jsxs)(`div`,{className:o(`mb-1 text-[10px] uppercase tracking-normal`,a?`text-primary/70`:`text-muted-foreground`),children:[`forked history · `,a?`you`:t||e.originalFrom]}),(0,J.jsx)(V,{text:i,rawBody:r.body,issueRepo:n,className:`leading-relaxed`}),(0,J.jsxs)(`p`,{className:o(`mt-1 flex items-center justify-end gap-1.5 text-xs`,a?`text-primary/60`:`text-muted-foreground`),children:[(0,J.jsxs)(`span`,{className:`opacity-40`,children:[`#`,e.originalMessageId]}),(0,J.jsx)(`span`,{children:q(e.originalCreatedAt)})]})]})})})}function Q({msg:e,issueRepo:t}){let n=L(e),r=q(e.occurredAt??e.createdAt);return(0,J.jsx)(`div`,{"data-msg-id":e.id,className:`mb-3`,children:(0,J.jsxs)(`div`,{className:`rounded-xl border border-primary/30 bg-primary/[0.06] px-3.5 py-2.5 shadow-sm`,children:[(0,J.jsxs)(`div`,{className:`mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-primary/80`,children:[(0,J.jsx)(v,{className:`h-3.5 w-3.5`}),(0,J.jsx)(`span`,{children:`Initial prompt`}),(0,J.jsx)(`span`,{className:`ml-auto text-[10px] font-normal normal-case text-muted-foreground/60`,children:r})]}),(0,J.jsx)(V,{text:n,rawBody:e.body,issueRepo:t,className:`text-sm leading-relaxed text-foreground`})]})})}function xt({scrollEl:e,timeline:t}){let[n,r]=(0,U.useState)(!0),[i,a]=(0,U.useState)(null),[s,c]=(0,U.useState)(null),[l,u]=(0,U.useState)(!1),d=(0,U.useMemo)(()=>{for(let e=t.length-1;e>=0;e--){let n=t[e];if(!(!n||n.type!==`message`&&n.type!==`initial-prompt`)&&n.msg.from===`user`)return{id:n.msg.id,body:L(n.msg)}}return null},[t]);if((0,U.useEffect)(()=>{if(!d||!e){r(!0);return}let t=null,n=null,i=()=>{let i=e.querySelector(`[data-msg-id="${d.id}"]`);if(!i){t?.disconnect(),t=null,n=null,r(!1);return}let a=i.getBoundingClientRect(),o=e.getBoundingClientRect();u(a.top<o.top),i!==n&&(t?.disconnect(),n=i,t=new IntersectionObserver(([e])=>{e&&r(e.isIntersecting)},{root:e,threshold:0}),t.observe(i))};return i(),e.addEventListener(`scroll`,i,{passive:!0}),()=>{t?.disconnect(),e.removeEventListener(`scroll`,i)}},[d?.id,e]),n||!d||!d.body.trim())return null;let f=i?.id===d.id?i.value:!1,p=s?.id===d.id?s.value:!1;function m(){!e||!d||e.querySelector(`[data-msg-id="${d.id}"]`)?.scrollIntoView({behavior:`smooth`,block:`center`})}let h=l?`↑`:`↓`;return p?(0,J.jsx)(`div`,{className:`flex shrink-0 bg-card/45 border-b border-border`,children:(0,J.jsxs)(`button`,{type:`button`,className:`w-full flex items-center justify-center gap-1.5 py-1 text-[11px] text-primary/60 hover:text-primary transition-colors`,onClick:()=>c({id:d.id,value:!1}),children:[(0,J.jsx)(`span`,{children:`Your prompt`}),(0,J.jsx)(ae,{className:`w-3 h-3`})]})}):(0,J.jsxs)(`section`,{className:`flex shrink-0 flex-col border-b border-border bg-card/45 px-3 py-2`,children:[(0,J.jsxs)(`div`,{className:`flex items-center gap-2 text-[11px] text-muted-foreground mb-0.5`,children:[(0,J.jsx)(`span`,{className:`font-medium text-primary/70`,children:`Your prompt`}),(0,J.jsxs)(`button`,{type:`button`,className:`ml-auto hover:text-foreground transition-colors`,onClick:m,children:[h,` scroll to original`]}),(0,J.jsx)(`button`,{type:`button`,className:`hover:text-foreground transition-colors`,onClick:()=>c({id:d.id,value:!0}),title:`Minimize`,children:(0,J.jsx)(S,{className:`w-3 h-3`})})]}),(0,J.jsx)(`p`,{className:o(`text-sm text-foreground leading-snug cursor-pointer`,!f&&`line-clamp-2`),onClick:()=>a({id:d.id,value:!f}),children:d.body})]})}function St(){return(0,U.useSyncExternalStore)(e=>j.subscribe(e),()=>j.getPlayingKey(),()=>null)}function Ct({msg:e,peer:t}){return(0,J.jsx)(Q,{msg:e,issueRepo:N(e=>e.agentsById[t]?.issueRepo)})}function wt({agent:e,onClick:t}){let n=!!(t&&e);return(0,J.jsx)(`button`,{type:`button`,title:e?e.label||e.name||e.id:void 0,onClick:t,disabled:!n,className:o(`shrink-0 self-end transition-opacity`,n?`cursor-pointer hover:opacity-80`:`cursor-default opacity-60`),children:(0,J.jsx)(H,{agent:e})})}function Tt({agentId:e,onOpen:t}){let n=N(t=>t.agentsById[e]??null);return(0,J.jsx)(wt,{agent:n,onClick:n?()=>t(n):void 0})}function Et({attachment:e,outbound:t}){let n=e.kind===`image`,i=e.title||e.artifactId,[a,s]=(0,U.useState)(null),[c,l]=(0,U.useState)(null),[d,f]=(0,U.useState)(!1),[p,h]=(0,U.useState)(!1);(0,U.useEffect)(()=>{let t=!1,n=null;async function r(){f(!0),l(null),s(null);try{let r=await u(`/artifacts/${encodeURIComponent(e.artifactId)}/content`);if(t)return;n=URL.createObjectURL(r),s(n)}catch(e){t||l(e.message)}finally{t||f(!1)}}return r(),()=>{t=!0,n&&URL.revokeObjectURL(n)}},[e.artifactId]);function g(){if(!a)return;let e=document.createElement(`a`);e.href=a,e.download=i,document.body.appendChild(e),e.click(),e.remove()}return n?(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`button`,{type:`button`,onClick:()=>a&&h(!0),disabled:!a,className:`group/img mt-2 block overflow-hidden rounded-md border border-foreground/10 bg-black/10 text-left disabled:cursor-default relative`,children:a?(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(`img`,{src:a,alt:i,loading:`lazy`,className:`block max-h-72 max-w-full object-contain`}),(0,J.jsx)(`span`,{className:`absolute inset-0 flex items-center justify-center bg-black/0 group-hover/img:bg-black/30 transition-colors`,children:(0,J.jsx)(O,{className:`w-6 h-6 text-white opacity-0 group-hover/img:opacity-100 transition-opacity drop-shadow-md`})})]}):(0,J.jsxs)(`span`,{className:o(`flex min-h-20 items-center gap-2 px-3 py-2 text-xs`,c?`text-red-400`:`text-muted-foreground`),children:[d?(0,J.jsx)(b,{className:`h-4 w-4 animate-spin`}):(0,J.jsx)(C,{className:`h-4 w-4`}),c||`Loading image`]})}),a&&(0,J.jsx)(he,{open:p,onOpenChange:h,children:(0,J.jsxs)(me,{className:`max-w-[95vw] max-h-[95vh] w-auto p-0 bg-black/95 border-zinc-800 gap-0 overflow-hidden`,showCloseButton:!1,children:[(0,J.jsx)(ge,{className:`sr-only`,children:i}),(0,J.jsxs)(`div`,{className:`relative flex items-center justify-center min-h-[200px]`,children:[(0,J.jsx)(`img`,{src:a,alt:i,className:`max-w-[93vw] max-h-[90vh] object-contain`}),(0,J.jsxs)(`div`,{className:`absolute top-2 right-2 flex gap-1`,children:[(0,J.jsx)(`button`,{type:`button`,onClick:g,className:`rounded-md bg-black/60 hover:bg-black/80 p-2 text-white transition-colors`,title:`Download`,children:(0,J.jsx)(m,{className:`w-4 h-4`})}),(0,J.jsx)(`button`,{type:`button`,onClick:()=>h(!1),className:`rounded-md bg-black/60 hover:bg-black/80 p-2 text-white transition-colors`,title:`Close`,children:(0,J.jsx)(ne,{className:`w-4 h-4`})})]})]})]})})]}):(0,J.jsxs)(`button`,{type:`button`,onClick:g,disabled:!a,className:o(`mt-2 flex w-full items-center gap-2 rounded-md border px-2.5 py-2 text-left text-xs transition-colors disabled:cursor-default disabled:opacity-70`,t?`border-primary-foreground/20 bg-primary-foreground/10 hover:bg-primary-foreground/15`:`border-border bg-background hover:bg-muted`),children:[d?(0,J.jsx)(b,{className:`h-4 w-4 shrink-0 animate-spin`}):c?(0,J.jsx)(C,{className:`h-4 w-4 shrink-0 text-red-400`}):(0,J.jsx)(r,{className:`h-4 w-4 shrink-0`}),(0,J.jsx)(`span`,{className:o(`min-w-0 flex-1 truncate`,c&&`text-red-400`),children:c||i}),(0,J.jsx)(m,{className:`h-3.5 w-3.5 shrink-0`})]})}function $({open:e,onToggle:t,onReact:n}){return(0,J.jsxs)(`div`,{className:`relative shrink-0`,children:[(0,J.jsx)(`button`,{type:`button`,title:`Add reaction`,onClick:t,className:o(`inline-flex h-5 w-6 items-center justify-center rounded-full border border-border bg-popover text-muted-foreground shadow-sm transition hover:bg-muted hover:text-foreground`,e?`opacity-100`:`opacity-0 pointer-events-none md:group-hover/msg:opacity-100 md:group-hover/msg:pointer-events-auto`),children:(0,J.jsx)(k,{className:`h-3 w-3`})}),e&&(0,J.jsx)(`div`,{className:`absolute bottom-full left-0 z-30 mb-1 flex gap-0.5 rounded-full border border-border bg-popover p-0.5 shadow-md`,children:Ve.map(e=>(0,J.jsx)(`button`,{type:`button`,title:`React ${e}`,onClick:()=>n(e),className:`inline-flex h-6 w-7 items-center justify-center rounded-full text-xs transition-colors hover:bg-muted`,children:e},e))})]})}function Dt({msg:e,peer:t}){let[r,i]=(0,U.useState)(!1),a=N(e=>e.agentsById),s=N(e=>e.openAgentDetail),c=e.from===t,l=(c?e.to:e.from)||``,u=a[c?t:l]?.issueRepo??a[t]?.issueRepo,d=!l||l===`broadcast`||l.includes(`:`),p=L(e),m=q(e.occurredAt??e.createdAt),h=c?n:f,_=a[e.from??``]??null;function v(e){e.stopPropagation(),_&&s(_)}return(0,J.jsx)(`div`,{"data-msg-id":e.id,className:`flex justify-center mb-1.5`,children:(0,J.jsxs)(`div`,{className:`w-full max-w-[94%] rounded-md border border-border/50 bg-muted/15 px-2.5 py-1`,children:[(0,J.jsxs)(`div`,{className:`flex items-center gap-1.5 text-xs cursor-pointer select-none`,onClick:()=>i(e=>!e),children:[(0,J.jsx)(g,{className:o(`h-3 w-3 shrink-0 text-muted-foreground/60 transition-transform`,r&&`rotate-90`)}),(0,J.jsx)(`button`,{type:`button`,title:_?_.label||_.name||_.id:void 0,disabled:!_,onClick:v,className:o(`shrink-0 transition-opacity`,_?`cursor-pointer hover:opacity-80`:`cursor-default opacity-60`),children:(0,J.jsx)(H,{agent:_,className:`!w-4 !h-4 !rounded-sm opacity-70`})}),(0,J.jsx)(h,{className:`h-3 w-3 shrink-0 text-muted-foreground/70`}),(0,J.jsxs)(`span`,{className:`shrink-0 text-muted-foreground`,children:[`Message `,c?`to`:`from`]}),d?(0,J.jsx)(`span`,{className:`shrink-0 font-medium text-muted-foreground`,children:fe(l,a)}):(0,J.jsx)(Y,{agentId:l,className:`shrink-0 px-0.5 text-foreground/80`,noIcon:!0}),!r&&(0,J.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-muted-foreground/50`,children:p}),(0,J.jsx)(`span`,{className:`ml-auto shrink-0 text-muted-foreground/40`,children:m})]}),r&&(0,J.jsx)(`div`,{className:`mt-1.5 pl-5`,children:(0,J.jsx)(V,{text:p,rawBody:e.body,issueRepo:u,className:`text-sm leading-relaxed text-foreground/90`})})]})})}var Ot=(0,U.memo)(function({msg:e,peer:t,showAvatar:n=!1,onOpenReferencedPath:r,onPreviewReferencedPath:i,onPreviewReferencedPathEnd:a}){if(!B(e.from)&&!B(e.to))return(0,J.jsx)(Dt,{msg:e,peer:t});let s=e.kind===`session`?e.payload?.session:void 0;if(s?.type===`prompt`&&s?.origin===`provider`)return(0,J.jsx)(Ct,{msg:e,peer:t});let c=B(e.from),l=N(e=>e.reactToMessage),u=N(e=>e.voiceTtsEnabled),d=St(),f=N(e=>{let n=e.agentsById[t]?.meta?.cwd;return typeof n==`string`?n:``}),p=N(e=>e.agentsById[t]?.issueRepo),m=N(e=>e.usersById),h=N(e=>e.users.length>1),g=c?le(e.from,m):void 0,_=N(e=>e.openAgentDetail),[v,y]=(0,U.useState)(!1),b=(0,U.useRef)(null),x=(0,U.useRef)(null),S=(0,U.useRef)(!1),C=L(e),ne=q(e.occurredAt??e.createdAt),re=Be(e),w=He(e),T=c?Ue(e,t):null,E=T?.icon,D=d===String(e.id),O=u&&!c&&!K(e)&&C.trim().length>0;function ie(t){t.stopPropagation(),D?j.bargeIn():j.speak(e.body,String(e.id))}(0,U.useEffect)(()=>{if(!v)return;function e(e){ze()||b.current&&!b.current.contains(e.target)&&y(!1)}return document.addEventListener(`mousedown`,e),document.addEventListener(`touchstart`,e),()=>{document.removeEventListener(`mousedown`,e),document.removeEventListener(`touchstart`,e)}},[v]);function k(e){return Fe(f||`/`,e)}function A(e,t){let n=k(e);r?.(n,t)}function ae(e,t,n){let r=k(e);i?.(r,t,n)}function M(t){l(e,t),y(!1)}function P(e){e.button===0&&(x.current={x:e.clientX,y:e.clientY},S.current=!1)}function oe(e){let t=x.current;t&&(Math.abs(e.clientX-t.x)>3||Math.abs(e.clientY-t.y)>3)&&(S.current=!0)}function F(t){if(!(K(e)||c)&&!t.target.closest(`a,button,input,textarea,select,[role="button"]`)){if(S.current||ze()){S.current=!1;return}y(e=>!e)}}let I=n&&!K(e);return(0,J.jsxs)(`div`,{"data-msg-id":e.id,className:o(`group/msg flex mb-3 items-end`,c?`justify-end`:`justify-start`,I&&`gap-1.5`),children:[!c&&I&&(0,J.jsx)(Tt,{agentId:t,onOpen:_}),(0,J.jsxs)(`div`,{ref:b,className:o(`relative`,I?`max-w-[calc(85%-2rem)] md:max-w-[calc(75%-2rem)]`:`max-w-[85%] md:max-w-[75%]`),children:[O&&(0,J.jsx)(`button`,{type:`button`,title:D?`Stop playback`:`Play aloud`,onClick:ie,className:o(`absolute -top-2 -right-2 z-20 inline-flex h-6 w-6 items-center justify-center rounded-full border bg-popover shadow-sm transition`,D?`border-primary/50 text-primary opacity-100`:`border-border text-muted-foreground opacity-70 hover:bg-muted hover:text-foreground hover:opacity-100 [@media(hover:hover)]:md:opacity-0 [@media(hover:hover)]:md:group-hover/msg:opacity-100`),children:D?(0,J.jsx)(te,{className:`h-3.5 w-3.5 animate-pulse`}):(0,J.jsx)(ee,{className:`h-3.5 w-3.5`})}),(0,J.jsxs)(`div`,{className:o(`rounded-2xl px-3.5 py-2 text-sm select-text`,!c&&!K(e)&&`cursor-pointer`,c?`bg-primary text-primary-foreground rounded-br-sm`:`bg-card ring-1 ring-foreground/10 rounded-bl-sm`),onPointerDown:P,onPointerMove:oe,onClick:F,children:[c&&h&&(0,J.jsx)(`p`,{className:o(`text-[11px] font-semibold mb-0.5 text-right`,ue(e.from)),children:pe(e.from,m)}),e.subject&&(0,J.jsx)(`p`,{className:o(`text-xs font-semibold mb-1`,c?`text-primary-foreground/70`:`text-muted-foreground`),children:e.subject}),(0,J.jsx)(V,{text:C,rawBody:e.body,issueRepo:p,className:`leading-relaxed`,onPrimary:c,onOpenPath:A,onPreviewPath:ae,onPreviewPathEnd:a,resolvePathTitle:(e,t)=>`${k(e)}${t?`:${t}`:``}`}),re.map(t=>(0,J.jsx)(Et,{attachment:t,outbound:c},`${e.id}-${t.artifactId}-${t.role||``}`)),(0,J.jsxs)(`p`,{className:o(`text-xs mt-1 flex items-center gap-1.5`,c?`justify-end text-primary-foreground/60`:`justify-end text-muted-foreground`),children:[(0,J.jsxs)(`span`,{className:`opacity-40`,children:[`#`,e.id]}),(0,J.jsx)(`span`,{children:ne}),T&&E&&(0,J.jsxs)(`span`,{className:o(`inline-flex items-center gap-1`,T.tone===`ok`&&`text-emerald-300`,T.tone===`warn`&&`text-amber-200`,T.tone===`danger`&&`text-red-200`),title:T.title,children:[(0,J.jsx)(E,{className:`h-3 w-3`}),T.label]})]})]}),!K(e)&&w.length>0&&(0,J.jsxs)(`div`,{className:o(`flex flex-wrap items-center gap-0.5 -mt-1 relative z-10`,c?`justify-end pr-2`:`justify-start pl-2`),children:[!c&&(0,J.jsx)($,{open:v,onToggle:()=>y(e=>!e),onReact:M}),w.map(e=>(0,J.jsxs)(`button`,{type:`button`,title:e.actors.join(`, `),onClick:()=>M(e.emoji),className:o(`inline-flex h-5 items-center gap-0.5 rounded-full border px-1.5 text-[11px] shadow-sm transition-colors`,e.mine?`border-primary/40 bg-primary/10 text-primary`:`border-border bg-background hover:bg-muted text-foreground`),children:[(0,J.jsx)(`span`,{children:e.emoji}),e.count>1&&(0,J.jsx)(`span`,{children:e.count})]},e.emoji))]}),!K(e)&&!c&&w.length===0&&(0,J.jsx)(`div`,{className:`absolute -bottom-2.5 left-1 z-20`,children:(0,J.jsx)($,{open:v,onToggle:()=>y(e=>!e),onReact:M})})]}),c&&I&&(0,J.jsx)(`span`,{className:`shrink-0 self-end opacity-60`,children:(0,J.jsx)(_e,{user:g,address:e.from,distinct:h})})]})});export{Ge as C,Me as E,Re as S,je as T,qe as _,vt as a,Ye as b,bt as c,gt as d,ft as f,Pe as g,Ke as h,pt as i,Q as l,xt as m,ht as n,_t as o,Qe as p,ct as r,yt as s,Ot as t,rt as u,Le as v,Ae as w,Je as x,Ie as y};
|
|
4
|
-
//# sourceMappingURL=MessageBubble-BO9eaLce.js.map
|