agent-relay-server 0.121.5 → 0.122.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 +1 -1
- package/package.json +5 -4
- package/public/assets/{MessageBubble-CLqxCG9b.js → MessageBubble-DRdUD-kW.js} +2 -2
- package/public/assets/{MessageBubble-CLqxCG9b.js.map → MessageBubble-DRdUD-kW.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-BkkToFEb.js → PlanGraphPanel-siKMPCln.js} +2 -2
- package/public/assets/{PlanGraphPanel-BkkToFEb.js.map → PlanGraphPanel-siKMPCln.js.map} +1 -1
- package/public/assets/{activity-D-ocGQGs.js → activity-D3MNcSaV.js} +2 -2
- package/public/assets/{activity-D-ocGQGs.js.map → activity-D3MNcSaV.js.map} +1 -1
- package/public/assets/{agent-profiles-CRPgYTyb.js → agent-profiles-CKzttq6G.js} +2 -2
- package/public/assets/{agent-profiles-CRPgYTyb.js.map → agent-profiles-CKzttq6G.js.map} +1 -1
- package/public/assets/{agent-type-icon-CeEoMouY.js → agent-type-icon-0ZJRP1Vy.js} +2 -2
- package/public/assets/{agent-type-icon-CeEoMouY.js.map → agent-type-icon-0ZJRP1Vy.js.map} +1 -1
- package/public/assets/{agents-Chw5E_2q.js → agents-CuVj_rfG.js} +2 -2
- package/public/assets/{agents-Chw5E_2q.js.map → agents-CuVj_rfG.js.map} +1 -1
- package/public/assets/{analytics-BmNDG0hR.js → analytics-iob6C27d.js} +2 -2
- package/public/assets/{analytics-BmNDG0hR.js.map → analytics-iob6C27d.js.map} +1 -1
- package/public/assets/{automation-D7g90kTv.js → automation-B2ixxs4q.js} +2 -2
- package/public/assets/{automation-D7g90kTv.js.map → automation-B2ixxs4q.js.map} +1 -1
- package/public/assets/{branch-state-badge-Bv7DhLBZ.js → branch-state-badge-CafBJWKo.js} +2 -2
- package/public/assets/{branch-state-badge-Bv7DhLBZ.js.map → branch-state-badge-CafBJWKo.js.map} +1 -1
- package/public/assets/{channels-CLwPeEPi.js → channels-C-WsbL2H.js} +2 -2
- package/public/assets/{channels-CLwPeEPi.js.map → channels-C-WsbL2H.js.map} +1 -1
- package/public/assets/{chat-Cgj7VKzc.js → chat-DeoVTVob.js} +2 -2
- package/public/assets/{chat-Cgj7VKzc.js.map → chat-DeoVTVob.js.map} +1 -1
- package/public/assets/{connectors-Br6fiTny.js → connectors-C2Ac03LJ.js} +2 -2
- package/public/assets/{connectors-Br6fiTny.js.map → connectors-C2Ac03LJ.js.map} +1 -1
- package/public/assets/display-CJzSoTMq.js +3 -0
- package/public/assets/display-CJzSoTMq.js.map +1 -0
- package/public/assets/{formatted-body-CmkuD23M.js → formatted-body-35XBOY5L.js} +3 -3
- package/public/assets/{formatted-body-CmkuD23M.js.map → formatted-body-35XBOY5L.js.map} +1 -1
- package/public/assets/{formatted-body-impl-DbdFmbwW.js → formatted-body-impl-RmDxI5Ux.js} +2 -2
- package/public/assets/{formatted-body-impl-DbdFmbwW.js.map → formatted-body-impl-RmDxI5Ux.js.map} +1 -1
- package/public/assets/index-D0xMQQb_.js +25 -0
- package/public/assets/{index-D9OogaB5.js.map → index-D0xMQQb_.js.map} +1 -1
- package/public/assets/{insights-qvaPqWCV.js → insights-B643SFvm.js} +2 -2
- package/public/assets/{insights-qvaPqWCV.js.map → insights-B643SFvm.js.map} +1 -1
- package/public/assets/{integrations-DYEGSqq_.js → integrations-hFIHeCF4.js} +2 -2
- package/public/assets/{integrations-DYEGSqq_.js.map → integrations-hFIHeCF4.js.map} +1 -1
- package/public/assets/{maintenance-C0HIEB-J.js → maintenance-CURvdp_Z.js} +2 -2
- package/public/assets/{maintenance-C0HIEB-J.js.map → maintenance-CURvdp_Z.js.map} +1 -1
- package/public/assets/{managed-agents-DdS7aI38.js → managed-agents-DrZtdlSn.js} +2 -2
- package/public/assets/{managed-agents-DdS7aI38.js.map → managed-agents-DrZtdlSn.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-BnXMH1z1.js → markdown-preview-impl-CVDO--ek.js} +2 -2
- package/public/assets/{markdown-preview-impl-BnXMH1z1.js.map → markdown-preview-impl-CVDO--ek.js.map} +1 -1
- package/public/assets/{memory-CSwx7bz8.js → memory-Bm7dt_Qf.js} +2 -2
- package/public/assets/{memory-CSwx7bz8.js.map → memory-Bm7dt_Qf.js.map} +1 -1
- package/public/assets/{messages-Be9CmvDv.js → messages-DTq4i2KZ.js} +2 -2
- package/public/assets/{messages-Be9CmvDv.js.map → messages-DTq4i2KZ.js.map} +1 -1
- package/public/assets/{orchestrators-BgtvvHvo.js → orchestrators-Dra318Ap.js} +2 -2
- package/public/assets/{orchestrators-BgtvvHvo.js.map → orchestrators-Dra318Ap.js.map} +1 -1
- package/public/assets/{overview-BvhbPWtF.js → overview-yr1Vmk64.js} +2 -2
- package/public/assets/{overview-BvhbPWtF.js.map → overview-yr1Vmk64.js.map} +1 -1
- package/public/assets/{pairs-CpXSOMx0.js → pairs-mnlKDBGp.js} +2 -2
- package/public/assets/{pairs-CpXSOMx0.js.map → pairs-mnlKDBGp.js.map} +1 -1
- package/public/assets/projects-Dzdf-gkK.js +2 -0
- package/public/assets/{projects-DdPNPFJI.js.map → projects-Dzdf-gkK.js.map} +1 -1
- package/public/assets/prompt-settings-ByL8SNCi.js +2 -0
- package/public/assets/{prompt-settings-BpZse7-X.js.map → prompt-settings-ByL8SNCi.js.map} +1 -1
- package/public/assets/{registry-pb1gTZEg.js → registry-CXWyOtpr.js} +2 -2
- package/public/assets/{registry-pb1gTZEg.js.map → registry-CXWyOtpr.js.map} +1 -1
- package/public/assets/{security-BgcX1n9D.js → security-0SBsc_4W.js} +2 -2
- package/public/assets/{security-BgcX1n9D.js.map → security-0SBsc_4W.js.map} +1 -1
- package/public/assets/{select-DZPVpKPv.js → select-Bw5z2KJ_.js} +2 -2
- package/public/assets/{select-DZPVpKPv.js.map → select-Bw5z2KJ_.js.map} +1 -1
- package/public/assets/{settings-hd5kzdRB.js → settings-78Cyq_tA.js} +4 -4
- package/public/assets/{settings-hd5kzdRB.js.map → settings-78Cyq_tA.js.map} +1 -1
- package/public/assets/{store-DKTLubBT.js → store-BPrVuKiv.js} +4 -4
- package/public/assets/store-BPrVuKiv.js.map +1 -0
- package/public/assets/{tasks-C5d2LU5E.js → tasks-Cg2MMeri.js} +2 -2
- package/public/assets/{tasks-C5d2LU5E.js.map → tasks-Cg2MMeri.js.map} +1 -1
- package/public/assets/teams-DWv6UwFV.js +2 -0
- package/public/assets/{teams-B8f2pGJe.js.map → teams-DWv6UwFV.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-CxAscH0U.js → terminal-viewer-impl-DBLDjbIg.js} +2 -2
- package/public/assets/{terminal-viewer-impl-CxAscH0U.js.map → terminal-viewer-impl-DBLDjbIg.js.map} +1 -1
- package/public/assets/{types-uVOXgvMT.js → types-BeJUSfDj.js} +2 -2
- package/public/assets/types-BeJUSfDj.js.map +1 -0
- package/public/assets/{user-avatar-Dw6mzWhv.js → user-avatar-BsOLyVJj.js} +2 -2
- package/public/assets/{user-avatar-Dw6mzWhv.js.map → user-avatar-BsOLyVJj.js.map} +1 -1
- package/public/assets/{work-queue-9imc7aNK.js → work-queue-DmwvnF6F.js} +2 -2
- package/public/assets/{work-queue-9imc7aNK.js.map → work-queue-DmwvnF6F.js.map} +1 -1
- package/public/assets/{workspaces-CLYc3yF3.js → workspaces-dIBivJN8.js} +2 -2
- package/public/assets/{workspaces-CLYc3yF3.js.map → workspaces-dIBivJN8.js.map} +1 -1
- package/public/index.html +6 -6
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/plugins/claude/monitors/relay-monitor.provisioned.mjs +44 -938
- package/runner/src/adapter.ts +53 -18
- package/runner/src/config.ts +1 -4
- package/src/agent-card-projection.ts +5 -1
- package/src/agent-command-activity.ts +39 -0
- package/src/automations/reconcile.ts +2 -2
- package/src/cli/workspace.ts +3 -1
- package/src/config.ts +10 -1
- package/src/db/agents.ts +118 -37
- package/src/db/message-reads.ts +50 -0
- package/src/db/messages.ts +48 -9
- package/src/db/migrations.ts +54 -0
- package/src/db/provider-quota-burn.ts +96 -67
- package/src/db/provider-quotas.ts +29 -7
- package/src/db/scheduled-timers.ts +21 -0
- package/src/db/schema.ts +1 -0
- package/src/db/tasks.ts +33 -3
- package/src/db/work-evidence.ts +10 -7
- package/src/event-loop-lag.ts +15 -0
- package/src/gate-resolver.ts +24 -1
- package/src/maintenance/jobs.ts +23 -6
- package/src/managed-policy.ts +5 -12
- package/src/mcp-plan-tools.ts +13 -1
- package/src/project-instructions.ts +36 -2
- package/src/quota-advisory.ts +6 -4
- package/src/quota-band-transition.ts +23 -14
- package/src/routes/agent-sessions.ts +39 -7
- package/src/routes/agents.ts +3 -2
- package/src/routes/tasks.ts +16 -4
- package/src/routes/workspaces.ts +35 -10
- package/src/services/plan-graph.ts +27 -9
- package/src/services/register-agent.ts +12 -12
- package/src/services/send-message.ts +43 -15
- package/src/services/spawn-agent.ts +3 -0
- package/src/spawn-command.ts +8 -0
- package/src/spawn-targets.ts +19 -34
- package/src/sse.ts +25 -3
- package/src/workspace-merge.ts +25 -5
- package/src/workspace-orphans.ts +13 -6
- package/public/assets/display-exQDWum3.js +0 -3
- package/public/assets/display-exQDWum3.js.map +0 -1
- package/public/assets/index-D9OogaB5.js +0 -25
- package/public/assets/projects-DdPNPFJI.js +0 -2
- package/public/assets/prompt-settings-BpZse7-X.js +0 -2
- package/public/assets/store-DKTLubBT.js.map +0 -1
- package/public/assets/teams-B8f2pGJe.js +0 -2
- package/public/assets/types-uVOXgvMT.js.map +0 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// status broadcast, the single timeline note, the spawned-child parent-wake, and
|
|
11
11
|
// the first-registration audit row.
|
|
12
12
|
import { errMessage, isReservedAgentId } from "agent-relay-sdk";
|
|
13
|
-
import { ValidationError, createActivityEvent, deliverableTaskIdsBySpawnRequestId, getAgent, getTaskDetail, mergeAgentMeta, readSpawnReportVerbosity,
|
|
13
|
+
import { ValidationError, createActivityEvent, deliverableTaskIdsBySpawnRequestId, getAgent, getTaskDetail, mergeAgentMeta, readSpawnReportVerbosity, seedSpawnPromptMirror, seedSpawnReplyObligation, upsertAgent } from "../db";
|
|
14
14
|
import { emitAgentStatus, emitNewMessage } from "../sse";
|
|
15
15
|
import { noteAgentTimelineEvent } from "../compaction-watch";
|
|
16
16
|
import { agentProviderTimelineEvent } from "../provider-timeline-event";
|
|
@@ -91,18 +91,18 @@ export function registerAgent(input: RegisterAgentInput, ctx: AuthContext): Agen
|
|
|
91
91
|
// Registration labels are spawn defaults. Runtime /label changes remain authoritative.
|
|
92
92
|
const registrationInput = existing ? { ...input } : input;
|
|
93
93
|
if (existing) delete registrationInput.label;
|
|
94
|
-
const agent = upsertAgent(registrationInput);
|
|
95
|
-
|
|
96
94
|
// #1051/#484 — instance takeover is where the restart-strand invariant fires now that a
|
|
97
|
-
// heartbeat lapse no longer revokes anything. When a FRESH runner process re-registers
|
|
98
|
-
//
|
|
99
|
-
// tokens "stale":
|
|
100
|
-
//
|
|
101
|
-
// is
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
// heartbeat lapse no longer revokes anything. When a FRESH runner process re-registers under
|
|
96
|
+
// this agent id, the epoch bump inside upsertAgent atomically revokes the PRIOR instance's
|
|
97
|
+
// runtime tokens "stale": refused for auth yet re-mintable via the orchestrator (#484). A
|
|
98
|
+
// same-instance reconnect keeps its instanceId, so the epoch is unchanged and the live token
|
|
99
|
+
// is left valid — the whole point of #1051 (in-band recovery after lag without a re-mint).
|
|
100
|
+
// #1057(3) — the ONLY jti spared from that revoke is server-verified: the token that
|
|
101
|
+
// authenticated THIS registration, and only when it resolves to this very agent (a
|
|
102
|
+
// same-credential instance rotation, e.g. a respawn reusing its unexpired env token).
|
|
103
|
+
// Client-sent meta.auth can no longer nominate jtis to keep.
|
|
104
|
+
const verifiedKeepJti = ctx.callerAgentId === input.id ? ctx.component?.jti : undefined;
|
|
105
|
+
const agent = upsertAgent(registrationInput, verifiedKeepJti ? { verifiedKeepJtis: [verifiedKeepJti] } : undefined);
|
|
106
106
|
|
|
107
107
|
// Managed came-up-running reconcile — no-op for non-managed agents (the guard lives
|
|
108
108
|
// in markManagedAgentRunning). Clears backoff, flushes the policy queue, transitions
|
|
@@ -21,12 +21,13 @@
|
|
|
21
21
|
//
|
|
22
22
|
// OUT OF SCOPE (transport-edge, excluded from the parity facet): automatic memory injection
|
|
23
23
|
// (needs request-derived context) and per-transport audit (HTTP domain row vs MCP tool-call row).
|
|
24
|
-
import { canonicalHumanAgentId, isHumanAgentId, isMechanicalMessageKind, isRecord, isReservedAgentId } from "agent-relay-sdk";
|
|
24
|
+
import { canonicalHumanAgentId, isHumanAgentId, isMechanicalMessageKind, isRecord, isReservedAgentId, isTurnFinalSession } from "agent-relay-sdk";
|
|
25
25
|
import {
|
|
26
26
|
ValidationError,
|
|
27
27
|
flushDeferredLineageReport,
|
|
28
28
|
getAgent,
|
|
29
29
|
getMessage,
|
|
30
|
+
latestInboundMessage,
|
|
30
31
|
listAgents,
|
|
31
32
|
listRecentFinalResponseCaptures,
|
|
32
33
|
promoteLineageCapturedResponse,
|
|
@@ -287,6 +288,14 @@ function emitSendResults(results: DbSendMessageResult[]): void {
|
|
|
287
288
|
emitNewMessages(liveMessages);
|
|
288
289
|
}
|
|
289
290
|
|
|
291
|
+
// #976 — a spawned child (results-only profile) whose turn-final response Relay already promotes to
|
|
292
|
+
// its spawn-parent does not also need to fire a bare ack/report via relay_send_message. Codex workers
|
|
293
|
+
// do this routinely ("Noted.", "Cleared.", "Going forward I'll…", or a duplicate of the final report),
|
|
294
|
+
// and each redundant message wakes the coordinator and burns the context coordinators exist to
|
|
295
|
+
// protect. This ONE home suppresses such acks on BOTH transports (HTTP + MCP) and BOTH providers
|
|
296
|
+
// (codex + claude): a reply-path ack collapses onto its parent; a standalone self→parent ack (no
|
|
297
|
+
// replyTo — the codex relay_send_message leak) collapses onto the latest inbound it answers. A
|
|
298
|
+
// genuine result (final capture, a session-final step, or non-ack body) always passes through.
|
|
290
299
|
function suppressResultsOnlyAck(input: SendMessageInput): Message | null {
|
|
291
300
|
if (input.kind && input.kind !== "chat" && input.kind !== "session") return null;
|
|
292
301
|
const sender = getAgent(input.from);
|
|
@@ -296,21 +305,40 @@ function suppressResultsOnlyAck(input: SendMessageInput): Message | null {
|
|
|
296
305
|
if (!isPassiveAck(input.body)) return null;
|
|
297
306
|
if (isRecord(input.payload?.responseCapture)) return null;
|
|
298
307
|
const session = isRecord(input.payload?.session) ? input.payload.session : undefined;
|
|
299
|
-
if (session
|
|
300
|
-
|
|
308
|
+
if (isTurnFinalSession(session)) return null;
|
|
309
|
+
// Reply-path: collapse onto the parent message being acked.
|
|
310
|
+
if (input.replyTo) return getMessage(input.replyTo);
|
|
311
|
+
// Standalone self→parent path (#976): only a spawned child acking its OWN spawn-parent. Anchor to
|
|
312
|
+
// the latest inbound from that parent so the drop mirrors the reply-path collapse; if there is no
|
|
313
|
+
// prior inbound to anchor to, deliver (never drop a message we can't attribute to a conversation).
|
|
314
|
+
if (sender?.spawnedBy && sender.spawnedBy === input.to) {
|
|
315
|
+
return latestInboundMessage(input.from, sender.spawnedBy);
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
301
318
|
}
|
|
302
319
|
|
|
320
|
+
// Exact-match ceremonial acks (after normalization). Kept an explicit set — high precision, so a
|
|
321
|
+
// real short status ("Cleared the queue", "PASS") never matches, only the bare acknowledgement.
|
|
322
|
+
const PASSIVE_ACK_PHRASES = new Set([
|
|
323
|
+
"noted", "noted standing by",
|
|
324
|
+
"acknowledged", "acknowledged standing by",
|
|
325
|
+
"standing by", "ok standing by", "okay standing by",
|
|
326
|
+
"understood", "understood standing by",
|
|
327
|
+
"cleared", "will do", "will do standing by", "will comply",
|
|
328
|
+
"on it", "got it", "roger", "roger that", "copy", "copy that",
|
|
329
|
+
"sounds good", "ok", "okay", "okay will do", "thanks", "thank you",
|
|
330
|
+
"no problem", "no worries",
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
// A short SELF-REFERENTIAL acknowledgement of an instruction about the agent's OWN reporting
|
|
334
|
+
// behavior ("Going forward, I'll only use Relay mid-task…"). Bounded length + it must name the
|
|
335
|
+
// messaging/reporting channel, so a genuine forward-looking REPORT that happens to open with
|
|
336
|
+
// "going forward" ("going forward the build fails unless we bump node") still passes through.
|
|
337
|
+
const REPORTING_META_ACK = /^(going forward|from now on)\b.{0,160}\b(relay|messages?|acks?|reports?|reporting|pings?|notif\w*)\b/;
|
|
338
|
+
|
|
303
339
|
function isPassiveAck(body: string): boolean {
|
|
304
|
-
const text = body.toLowerCase().replace(/[
|
|
305
|
-
return
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
"acknowledged",
|
|
309
|
-
"acknowledged standing by",
|
|
310
|
-
"standing by",
|
|
311
|
-
"ok standing by",
|
|
312
|
-
"okay standing by",
|
|
313
|
-
"understood",
|
|
314
|
-
"understood standing by",
|
|
315
|
-
].includes(text);
|
|
340
|
+
const text = body.toLowerCase().replace(/[.!,…]+/g, " ").replace(/\s+/g, " ").trim();
|
|
341
|
+
if (!text) return false;
|
|
342
|
+
if (PASSIVE_ACK_PHRASES.has(text)) return true;
|
|
343
|
+
return text.length <= 160 && REPORTING_META_ACK.test(text);
|
|
316
344
|
}
|
|
@@ -647,6 +647,9 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
647
647
|
providerArgs: input.providerArgs ?? [],
|
|
648
648
|
prompt: resolvedPrompt,
|
|
649
649
|
systemPromptAppend,
|
|
650
|
+
// #1062 — stash the RAW caller brief (pre-composition) so a dashboard (non-policy) restart can
|
|
651
|
+
// re-thread it as callerSystemPromptAppend and stay fresh-spawn-identical instead of dropping it.
|
|
652
|
+
...(resolvedSystemPromptAppend ? { callerSystemPromptAppend: resolvedSystemPromptAppend } : {}),
|
|
650
653
|
relayInjectionEvents: composedInstructions.relayInjectionEvents,
|
|
651
654
|
landStrategy: input.landStrategy,
|
|
652
655
|
autoMerge: input.autoMerge,
|
package/src/spawn-command.ts
CHANGED
|
@@ -96,6 +96,13 @@ interface BuildSpawnCommandOptions {
|
|
|
96
96
|
providerArgs?: string[];
|
|
97
97
|
prompt?: string;
|
|
98
98
|
systemPromptAppend?: string;
|
|
99
|
+
/**
|
|
100
|
+
* #1062 — the RAW caller/parent brief (before project/profile/memory composition), persisted
|
|
101
|
+
* alongside the composed `systemPromptAppend` so a dashboard (non-policy) restart can re-thread it
|
|
102
|
+
* as `callerSystemPromptAppend` and re-project the rest fresh — otherwise the restart re-composes
|
|
103
|
+
* project/profile/memory but silently drops the parent's original task brief.
|
|
104
|
+
*/
|
|
105
|
+
callerSystemPromptAppend?: string;
|
|
99
106
|
relayInjectionEvents?: unknown[];
|
|
100
107
|
landStrategy?: WorkspaceLandingPolicy;
|
|
101
108
|
autoMerge?: WorkspaceAutoMergePolicy;
|
|
@@ -170,6 +177,7 @@ export function buildSpawnCommand(opts: BuildSpawnCommandOptions): Record<string
|
|
|
170
177
|
...def(opts.providerArgs, "providerArgs"),
|
|
171
178
|
...def(opts.prompt, "prompt"),
|
|
172
179
|
...def(opts.systemPromptAppend, "systemPromptAppend"),
|
|
180
|
+
...def(opts.callerSystemPromptAppend, "callerSystemPromptAppend"),
|
|
173
181
|
...def(opts.relayInjectionEvents, "relayInjectionEvents"),
|
|
174
182
|
...def(opts.landStrategy, "landStrategy"),
|
|
175
183
|
...def(opts.autoMerge, "autoMerge"),
|
package/src/spawn-targets.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { renderTemplate } from "./prompt-resolver";
|
|
|
6
6
|
import { getRoutingPolicy } from "./routing-policy-store";
|
|
7
7
|
import { isPathWithinBase } from "./utils";
|
|
8
8
|
import { McpNotFoundError } from "./mcp-errors";
|
|
9
|
-
import { DEFAULT_QUOTA_PREFER_SLACK_AT, DEFAULT_QUOTA_TARGET_UTILIZATION, quotaPaceMetrics } from "agent-relay-sdk/provider-quota";
|
|
9
|
+
import { DEFAULT_QUOTA_PREFER_SLACK_AT, DEFAULT_QUOTA_TARGET_UTILIZATION, quotaOvershootPenalty, quotaPaceMetrics } from "agent-relay-sdk/provider-quota";
|
|
10
10
|
import { isSpawnProvider, type ProviderQuotaRoutingConfig } from "agent-relay-sdk";
|
|
11
11
|
import type { Orchestrator, ProviderCatalogSummary, ProviderQuotaBurnRole, ProviderQuotaBurnWindow, ProviderQuotaRecord, QuotaState, SpawnProvider } from "./types";
|
|
12
12
|
|
|
@@ -82,13 +82,11 @@ interface SpawnQuotaBurnWindow {
|
|
|
82
82
|
timeToWallMs: number | null;
|
|
83
83
|
projectedWallAt: number | null;
|
|
84
84
|
earlyWallMs: number | null;
|
|
85
|
-
earlyFraction: number | null;
|
|
86
85
|
// #760 — symmetric underburn signal (see ProviderQuotaBurnWindow.slack), re-derived against
|
|
87
86
|
// the per-provider target, plus the "prefer" flag for this window.
|
|
88
87
|
projectedUtilAtReset: number | null;
|
|
89
88
|
slack: number | null;
|
|
90
89
|
prefer: boolean;
|
|
91
|
-
weight: 1 | 3;
|
|
92
90
|
penalty: number;
|
|
93
91
|
blockedNow: boolean;
|
|
94
92
|
}
|
|
@@ -272,45 +270,34 @@ function spawnQuotaWindow(
|
|
|
272
270
|
): SpawnQuotaBurnWindow | undefined {
|
|
273
271
|
if (!quotaWindow) return undefined;
|
|
274
272
|
const timeToResetMs = typeof quotaWindow.resetsAt === "number" ? Math.max(0, quotaWindow.resetsAt - now) : null;
|
|
275
|
-
// earlyWallMs may be negative (#760 symmetric); the over-pace penalty only sees the positive half.
|
|
276
|
-
const earlyFraction = burnWindow.earlyWallMs !== null && timeToResetMs && timeToResetMs > 0
|
|
277
|
-
? Math.max(0, burnWindow.earlyWallMs) / timeToResetMs
|
|
278
|
-
: null;
|
|
279
|
-
const weight = burnWindow.role === "7d" ? 3 : 1;
|
|
280
273
|
const hardBackstop = providerCfg?.hardBackstopUtilization;
|
|
281
|
-
// #760 — floor default is now 0 (off). The projection
|
|
282
|
-
//
|
|
283
|
-
//
|
|
274
|
+
// #760 — floor default is now 0 (off). The evidence-capped projection replaces the blunt
|
|
275
|
+
// utilization floor (which hid a legitimate low-util-high-burst alarm); operators may still opt
|
|
276
|
+
// back in to raw-utilization suppression by setting a positive floor.
|
|
284
277
|
const utilizationFloor = providerCfg?.burnPenaltyUtilizationFloor ?? 0;
|
|
285
|
-
const pacePenalty = (earlyFraction ?? 0) * weight;
|
|
286
278
|
|
|
287
|
-
// #
|
|
288
|
-
//
|
|
289
|
-
//
|
|
279
|
+
// #1072 — the band projection is evidence-capped at INGEST off the slow rate and is
|
|
280
|
+
// target-independent, so keep it as-is and re-derive only the per-provider-target slack + penalty
|
|
281
|
+
// from it. burnRatio (display) re-paces the same slow rate against the per-provider target.
|
|
290
282
|
const target = providerCfg?.targetUtilization ?? DEFAULT_QUOTA_TARGET_UTILIZATION;
|
|
291
283
|
const preferSlackAt = providerCfg?.preferSlackAt ?? DEFAULT_QUOTA_PREFER_SLACK_AT;
|
|
292
284
|
const blockedNow = hardBackstop === 0 ? false : quotaWindow.utilization >= (hardBackstop ?? 0.99);
|
|
285
|
+
const projectedUtilAtReset = burnWindow.projectedUtilAtReset;
|
|
293
286
|
let burnRatio = burnWindow.burnRatio;
|
|
294
|
-
let projectedUtilAtReset = burnWindow.projectedUtilAtReset;
|
|
295
|
-
let slack = burnWindow.slack;
|
|
296
287
|
if (burnWindow.observedRatePerMs !== null && timeToResetMs !== null && timeToResetMs > 0) {
|
|
297
|
-
|
|
298
|
-
burnRatio = pace.burnRatio;
|
|
299
|
-
projectedUtilAtReset = pace.projectedUtilAtReset;
|
|
300
|
-
slack = pace.slack;
|
|
288
|
+
burnRatio = quotaPaceMetrics({ utilization: quotaWindow.utilization, observedRatePerMs: burnWindow.observedRatePerMs, timeToResetMs, target }).burnRatio;
|
|
301
289
|
}
|
|
302
290
|
|
|
303
|
-
// #
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const rawPenalty = Math.max(pacePenalty, positionPenalty);
|
|
291
|
+
// #1072 — ONE unified overshoot penalty (replaces #760 earlyFraction, the #1071 7d ×3 weight,
|
|
292
|
+
// and the #798 idle position penalty). The measured current overshoot counts at full weight; the
|
|
293
|
+
// projected trend overshoot is scaled by the continuous confidence, so thin evidence cannot
|
|
294
|
+
// hard-escalate on an extrapolated slope. Idle and active share this single formula — an idle
|
|
295
|
+
// over-target provider is held by the current-overshoot term (confidence-independent, so the
|
|
296
|
+
// band is flat tick-to-tick), an active climb escalates as the confidence-weighted trend crosses.
|
|
297
|
+
const confidence = burnWindow.confidenceScore ?? 0;
|
|
298
|
+
const { slack, penalty: rawPenalty } = projectedUtilAtReset !== null
|
|
299
|
+
? quotaOvershootPenalty(projectedUtilAtReset, quotaWindow.utilization, target, confidence)
|
|
300
|
+
: { slack: burnWindow.slack, penalty: 0 };
|
|
314
301
|
const penalty = quotaWindow.utilization < utilizationFloor ? 0 : rawPenalty;
|
|
315
302
|
const prefer = !blockedNow && penalty === 0
|
|
316
303
|
&& quotaPenaltyBandFor(penalty) === "normal"
|
|
@@ -327,11 +314,9 @@ function spawnQuotaWindow(
|
|
|
327
314
|
timeToWallMs: burnWindow.timeToWallMs,
|
|
328
315
|
projectedWallAt: burnWindow.projectedWallAt,
|
|
329
316
|
earlyWallMs: burnWindow.earlyWallMs,
|
|
330
|
-
earlyFraction,
|
|
331
317
|
projectedUtilAtReset,
|
|
332
318
|
slack,
|
|
333
319
|
prefer,
|
|
334
|
-
weight,
|
|
335
320
|
penalty,
|
|
336
321
|
blockedNow,
|
|
337
322
|
};
|
package/src/sse.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { projectAgentStatusData } from "./agent-card-projection";
|
|
|
4
4
|
import { emitRelayEvent, subscribeRelayEvents, type RelayEvent } from "./events";
|
|
5
5
|
import { messageMatchesAgent, targetMatchesAgent } from "./agent-ref";
|
|
6
6
|
import type { ActivityEvent, AgentCard, Message, RelayInjectionCategory, RelayNotification, Task } from "./types";
|
|
7
|
-
import { isRecord } from "agent-relay-sdk";
|
|
7
|
+
import { isRecord, isWorkFinalMessagePayload } from "agent-relay-sdk";
|
|
8
8
|
import { agentProviderState } from "./provider-state";
|
|
9
9
|
|
|
10
10
|
interface Connection {
|
|
@@ -165,6 +165,10 @@ function fanout(event: RelayEvent): void {
|
|
|
165
165
|
sendNewMessages(batchMessages(event.data), event.seq);
|
|
166
166
|
return;
|
|
167
167
|
}
|
|
168
|
+
if (event.type === "message.finalized") {
|
|
169
|
+
sendMessageUpdate("message.finalized", event.data as unknown as Message, event.seq);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
168
172
|
if (event.type === "task.updated" || event.type === "task.created" || event.type === "task.claimed" || event.type === "task.status") {
|
|
169
173
|
sendTaskChanged(event.data as unknown as Task, event.type, event.seq);
|
|
170
174
|
return;
|
|
@@ -203,6 +207,15 @@ function sendNewMessages(messages: Message[], seq?: number): void {
|
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
209
|
|
|
210
|
+
function sendMessageUpdate(eventName: string, msg: Message, seq?: number): void {
|
|
211
|
+
const chunk = sseFrame(eventName, msg, seq);
|
|
212
|
+
const agentCache = new Map<string, AgentCard | null>();
|
|
213
|
+
for (const conn of connections.values()) {
|
|
214
|
+
if (conn.agentId && !messageMatchesAgent(msg, conn.agentId, fanoutAgent(conn.agentId, agentCache))) continue;
|
|
215
|
+
sendEncoded(conn, chunk);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
206
219
|
function batchMessages(data: Record<string, unknown>): Message[] {
|
|
207
220
|
const messages = (data as { messages?: unknown }).messages;
|
|
208
221
|
return Array.isArray(messages) ? messages as Message[] : [];
|
|
@@ -329,6 +342,10 @@ export function emitMessageReactionUpdated(msg: Message) {
|
|
|
329
342
|
emitRelayEvent({ type: "message.reaction_updated", source: "server", subject: String(msg.id), data: msg as unknown as Record<string, unknown> });
|
|
330
343
|
}
|
|
331
344
|
|
|
345
|
+
export function emitMessageFinalized(msg: Message) {
|
|
346
|
+
emitRelayEvent({ type: "message.finalized", source: "server", subject: String(msg.id), data: msg as unknown as Record<string, unknown> });
|
|
347
|
+
}
|
|
348
|
+
|
|
332
349
|
function sendTaskChanged(task: Task, eventType = "task.updated", seq?: number): void {
|
|
333
350
|
const chunk = sseFrame(eventType, task, seq);
|
|
334
351
|
const agentCache = new Map<string, AgentCard | null>();
|
|
@@ -387,6 +404,12 @@ function sendReplayEvent(conn: Connection, event: RelayEvent, agentCache: Map<st
|
|
|
387
404
|
send(conn, "message.new", msg, event.seq);
|
|
388
405
|
return;
|
|
389
406
|
}
|
|
407
|
+
if (event.type === "message.finalized" && conn.agentId) {
|
|
408
|
+
const msg = event.data as unknown as Message;
|
|
409
|
+
if (!messageMatchesAgent(msg, conn.agentId, fanoutAgent(conn.agentId, agentCache))) return;
|
|
410
|
+
send(conn, "message.finalized", msg, event.seq);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
390
413
|
if (event.type.startsWith("task.") && conn.agentId) {
|
|
391
414
|
const task = event.data as unknown as Task;
|
|
392
415
|
if (!targetMatchesAgent(task.target, conn.agentId, fanoutAgent(conn.agentId, agentCache))) return;
|
|
@@ -481,8 +504,7 @@ function isInboundChannelMessage(msg: Message): boolean {
|
|
|
481
504
|
|
|
482
505
|
function isBadgeWorthyMessage(msg: Message): boolean {
|
|
483
506
|
if (msg.kind !== "session") return false;
|
|
484
|
-
|
|
485
|
-
return session?.final === true;
|
|
507
|
+
return isWorkFinalMessagePayload(msg.payload);
|
|
486
508
|
}
|
|
487
509
|
|
|
488
510
|
function notificationForMessage(msg: Message): RelayNotification | undefined {
|
package/src/workspace-merge.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { isRecord, stringValue } from "agent-relay-sdk";
|
|
|
3
3
|
import { createCommand } from "./commands-db";
|
|
4
4
|
import {
|
|
5
5
|
acquireMergeLease,
|
|
6
|
+
exitCertificateMatchesInstance,
|
|
6
7
|
getActiveMergePause,
|
|
7
8
|
getAgent,
|
|
8
9
|
listAgents,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
setMergeLeaseCommand,
|
|
12
13
|
updateWorkspaceStatus,
|
|
13
14
|
} from "./db";
|
|
15
|
+
import { OFFLINE_PRUNE_MS } from "./config";
|
|
14
16
|
import { getLandingConfig } from "./config-store";
|
|
15
17
|
import type {
|
|
16
18
|
Command,
|
|
@@ -149,15 +151,33 @@ export function withOwnerOnline<T extends { ownerAgentId?: string }>(workspace:
|
|
|
149
151
|
// the 2026-07-04 cascade). A genuine process exit is only known when the owning
|
|
150
152
|
// orchestrator REPORTS it: it stamps `meta.lastExit` (routes/orchestrator.ts). So
|
|
151
153
|
// require both offline AND a reported exit before treating the owner as gone — never
|
|
152
|
-
// nuke/land a worktree on a heartbeat gap alone.
|
|
153
|
-
//
|
|
154
|
-
//
|
|
154
|
+
// nuke/land a worktree on a heartbeat gap alone.
|
|
155
|
+
//
|
|
156
|
+
// The confirmed-gone signal is the reconciling primitive between two opposing failures:
|
|
157
|
+
// • false POSITIVE (#1054/#1059) — reaping a live-but-lagging owner. Guarded by requiring
|
|
158
|
+
// a reported exit AND that the exit certificate belongs to the CURRENT instance: a stale
|
|
159
|
+
// report for a superseded session carries a different `spawnRequestId` and is rejected.
|
|
160
|
+
// • false NEGATIVE (#1060) — leaking a genuinely-gone owner's worktree. A MISSING row (pruned
|
|
161
|
+
// after OFFLINE_PRUNE_MS offline, or an explicit deleteAgent) can't be a live process under
|
|
162
|
+
// relay management, so it is gone; likewise an owner offline past the prune horizon is about
|
|
163
|
+
// to be pruned anyway and can't be a mere heartbeat-lagging owner (that window is bounded by
|
|
164
|
+
// OFFLINE_PRUNE_MS). Both restore the pre-#1054 isOwnerAlive reapability the #1054 gate lost.
|
|
155
165
|
export function isOwnerProcessConfirmedGone(ownerAgentId: string | undefined): boolean {
|
|
156
166
|
if (!ownerAgentId) return false;
|
|
157
167
|
const agent = getAgent(ownerAgentId);
|
|
158
|
-
|
|
168
|
+
// #1060 — the owner row is gone (24h-offline prune or deleteAgent): genuinely gone, reapable.
|
|
169
|
+
if (!agent) return true;
|
|
170
|
+
if (agent.status !== "offline") return false;
|
|
171
|
+
// #1060(3) — offline past the prune horizon: can't be a live-but-lagging owner and is about to
|
|
172
|
+
// become a missing row anyway. Covers an unmanaged owner that never gets meta.lastExit.
|
|
173
|
+
if (Date.now() - agent.lastSeen >= OFFLINE_PRUNE_MS) return true;
|
|
159
174
|
const meta = isRecord(agent.meta) ? agent.meta : undefined;
|
|
160
|
-
|
|
175
|
+
if (!meta) return false;
|
|
176
|
+
const lastExit = isRecord(meta.lastExit) ? meta.lastExit : undefined;
|
|
177
|
+
if (!lastExit) return false;
|
|
178
|
+
// #1059 — the reported exit must be for THIS instance, not a stale certificate stamped by a
|
|
179
|
+
// late report for a superseded predecessor (see exitCertificateMatchesInstance).
|
|
180
|
+
return exitCertificateMatchesInstance(meta, lastExit);
|
|
161
181
|
}
|
|
162
182
|
|
|
163
183
|
function workspaceOwnerHostKeys(workspace: WorkspaceRecord): Set<string> {
|
package/src/workspace-orphans.ts
CHANGED
|
@@ -26,7 +26,7 @@ import { createCommand } from "./commands-db";
|
|
|
26
26
|
import { emitRelayEvent } from "./events";
|
|
27
27
|
import { isPathWithinBase } from "./utils";
|
|
28
28
|
import { TERMINAL_WORKSPACE_STATUSES, worktreeReapable, type WorktreeReapState } from "./workspace-phase";
|
|
29
|
-
import { isOwnerAlive, liveAgentCwds, worktreeInUseByLiveAgent } from "./workspace-merge";
|
|
29
|
+
import { isOwnerAlive, isOwnerProcessConfirmedGone, liveAgentCwds, worktreeInUseByLiveAgent } from "./workspace-merge";
|
|
30
30
|
import { applyWorkspaceAction } from "./workspace-actions";
|
|
31
31
|
import { workspaceOwnerOrchestrator } from "./workspace-host";
|
|
32
32
|
import { UNLANDED_FLAG_COOLDOWN_MS, orphanGraceMs, orphanWorktreeReapEnabled, relayToken } from "./config";
|
|
@@ -606,14 +606,21 @@ export async function reapOrphanedWorktrees(): Promise<Record<string, unknown>>
|
|
|
606
606
|
const key = `dead-owner:${resolve(workspace.worktreePath)}`;
|
|
607
607
|
liveDeadOwnerKeys.add(key);
|
|
608
608
|
|
|
609
|
-
|
|
610
|
-
|
|
609
|
+
// #1054 — gate on PROCESS-confirmed death, not heartbeat-only liveness. A live
|
|
610
|
+
// owner that is falsely marked `offline` (sustained relay lag / network partition
|
|
611
|
+
// >30min) has no orchestrator-reported `meta.lastExit`; isOwnerAlive alone would
|
|
612
|
+
// treat it as reap-eligible and destroy its landed+clean worktree — killing the
|
|
613
|
+
// live session's cwd. worktreeInUseByLiveAgent can't protect it (offline owners are
|
|
614
|
+
// excluded from liveCwds). Require offline AND a reported exit before the grace clock
|
|
615
|
+
// even starts, so only a genuinely-gone owner's worktree is ever reaped.
|
|
616
|
+
if (!isOwnerProcessConfirmedGone(workspace.ownerAgentId)) {
|
|
617
|
+
deadOwnerTracker.delete(key); // alive, or falsely-offline live — reset the grace window (#1054/#278)
|
|
611
618
|
continue;
|
|
612
619
|
}
|
|
613
620
|
|
|
614
|
-
// Owner
|
|
615
|
-
// reconnecting agent isn't raced; the in-memory tracker re-arms on
|
|
616
|
-
// grace of 0 reaps on the first
|
|
621
|
+
// Owner process confirmed gone. Require continuous confirmed-gone-ness for the grace
|
|
622
|
+
// window so a reconnecting agent isn't raced; the in-memory tracker re-arms on
|
|
623
|
+
// restart. A grace of 0 reaps on the first confirmed-gone observation (env escape hatch).
|
|
617
624
|
const tracked = deadOwnerTracker.get(key);
|
|
618
625
|
const firstSeenDeadAt = tracked?.firstSeenDeadAt ?? now;
|
|
619
626
|
if (!tracked) deadOwnerTracker.set(key, { firstSeenDeadAt });
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{d as e,g as t,h as n,o as r,p as i,s as a}from"./types-uVOXgvMT.js";var o=`user`,s=o,c=5e3,l=new Set([`done`,`failed`,`canceled`]),u=new Set([`open`,`blocked`]),d={online:0,idle:0,busy:2,offline:3},f={idle:0,online:0,busy:2,offline:4},p={from:``,to:``,body:``,channel:``,subject:``,claimable:!1},m={toMode:`agent`,to:``,body:``,channel:``,subject:``,claimable:!1},h={pairId:``,from:``,body:``,subject:``},ee={requesterId:``,targetId:``,objective:``},te={user:`User`,system:`Server`,channel:`MessagesSquare`,agent:`Bot`},ne={user:`Human operator`,system:`System`,channel:`Channel`,agent:`Agent`};({...te,...Object.fromEntries(i().map(e=>[e,`Bot`]))}),{...ne,...Object.fromEntries(i().map(e=>[e,`${n(e)} agent`]))};var re=[{key:`overview`,label:`Overview`,icon:`LayoutDashboard`},{key:`chat`,label:`Chat`,icon:`MessageCircle`},{key:`agents`,label:`Agents`,icon:`Bot`},{key:`managed`,label:`Managed Agents`,icon:`Workflow`},{key:`teams`,label:`Teams`,icon:`Users`},{key:`profiles`,label:`Agent Profiles`,icon:`UserCog`},{key:`orchestrators`,label:`Orchestrators`,icon:`Server`},{key:`workspaces`,label:`Workspaces`,icon:`GitBranch`},{key:`files`,label:`Files`,icon:`FolderTree`},{key:`channels`,label:`Channels`,icon:`MessagesSquare`},{key:`connectors`,label:`Connectors`,icon:`Plug`},{key:`integrations`,label:`Integrations`,icon:`PlugZap`},{key:`security`,label:`Security`,icon:`Shield`},{key:`memory`,label:`Memory`,icon:`BrainCircuit`},{key:`activity`,label:`Activity`,icon:`Activity`},{key:`pairs`,label:`Pairs`,icon:`Link`},{key:`messages`,label:`Messages`,icon:`Mail`},{key:`work`,label:`Work Queue`,icon:`ListChecks`},{key:`tasks`,label:`Tasks`,icon:`ClipboardList`},{key:`automation`,label:`Automation`,icon:`CalendarClock`},{key:`analytics`,label:`Analytics`,icon:`AreaChart`},{key:`insights`,label:`Insights`,icon:`Lightbulb`},{key:`projects`,label:`Projects`,icon:`Boxes`},{key:`prompts`,label:`Prompts`,icon:`FileText`},{key:`registry`,label:`Registry`,icon:`PackagePlus`},{key:`maintenance`,label:`Maintenance`,icon:`Wrench`},{key:`settings`,label:`Settings`,icon:`Settings`}],ie={critical:`text-red-400 bg-red-500/10`,warning:`text-yellow-400 bg-yellow-500/10`,info:`text-blue-400 bg-blue-500/10`},ae={active:`bg-emerald-500/20 text-emerald-400`,pending:`bg-yellow-500/20 text-yellow-400`,ended:`bg-zinc-500/20 text-zinc-400`,rejected:`bg-red-500/20 text-red-400`,expired:`bg-red-500/20 text-red-400`};function g(e){let t=typeof e==`number`?e:new Date(e||0).getTime();return Number.isFinite(t)?t:0}var _=[`lifecycle.`,`commands.`,`capabilities.`],v={"+1":`👍`,":+1:":`👍`,thumbsup:`👍`,":thumbsup:":`👍`,"thumbs-up":`👍`,":thumbs-up:":`👍`,thumbs_up:`👍`,":thumbs_up:":`👍`,thumb_up:`👍`,":thumb_up:":`👍`,like:`👍`,":like:":`👍`,heart:`❤️`,":heart:":`❤️`,redheart:`❤️`,red_heart:`❤️`,":red_heart:":`❤️`,check:`✅`,":check:":`✅`,checkmark:`✅`,":checkmark:":`✅`,white_check_mark:`✅`,":white_check_mark:":`✅`,eyes:`👀`,":eyes:":`👀`};function y(e){let t=e.trim();return v[t.toLowerCase()]??t}function oe(e){return e.filter(e=>!_.some(t=>e.startsWith(t)))}function se(e,t=2){if(!e)return``;let n=e.replace(/\/+$/,``).split(`/`);return n.length<=t?e:n.slice(-t).join(`/`)}function b(e){return e.trim().replace(/\\/g,`/`).replace(/\/+$/,``)||`/`}function ce(e,t){if(!e?.trim()||!t?.trim())return!1;let n=b(e),r=b(t);return r===`/`?n.startsWith(`/`):n===`/`?!1:n===r||n.startsWith(r+`/`)}function x(e){return e?.meta?.builtin===!0||e?.id===`user`||e?.id===`system`}function S(e){let t=e?.tags||[],n=e?.capabilities||[],r=typeof e?.meta?.policyName==`string`?e.meta.policyName:``;return e?.kind===`channel`||e?.meta?.kind===`channel`||t.includes(`channel`)||t.some(e=>e.startsWith(`channel:`))||n.includes(`channel`)||n.some(e=>e.startsWith(`channel:`))||t.includes(`telegram`)||n.includes(`telegram`)||r===`telegram-main`}function C(e){return!!(typeof e?.meta?.automationId==`string`||typeof e?.meta?.automationRunId==`string`||e?.tags?.includes(`automation`))}function w(e){return x(e)||S(e)||C(e)}function T(e){if(e?.id===`user`)return`user`;if(e?.id===`system`)return`system`;if(S(e))return`channel`;let t=i(),n=typeof e?.meta?.provider==`string`?e.meta.provider.toLowerCase():void 0;if(n&&t.includes(n))return n;let r=[...e?.tags||[],e?.meta?.client,e?.meta?.runtime,e?.meta?.agentType].filter(e=>typeof e==`string`).map(e=>e.toLowerCase());for(let e of t)if(r.some(t=>t.includes(e)))return e;return`agent`}function E(t,n){if(!n?.lastSeen||n.status===`offline`||n.id===`user`||n.id===`system`)return!1;let r=n.meta?.transport;if(e(r)&&r.connected===!0)return!1;let i=new Date(n.lastSeen).getTime();return Number.isFinite(i)?t-i>6e4:!1}function D(e,t){if(!e||x(e)||S(e)||t===`resume`)return!1;if(t===`interrupt`)return e.providerCapabilities?.liveSession?.interrupt===!0&&e.status===`busy`;if(k(e)&&(t===`restart`||t===`shutdown`||t===`compact`||t===`clearContext`)&&!O(e))return!1;let n=e.providerCapabilities?.lifecycle;if(n){if(t===`restart`)return n.restartHard===!0;if(t===`shutdown`)return n.shutdownHard===!0;if(t===`reconnect`)return n.reconnect===!0}return t===`compact`?e.providerCapabilities?.context?.compact===!0:t===`clearContext`?e.providerCapabilities?.context?.clear===!0:e.meta?.runnerManaged===!0&&(t===`restart`||t===`shutdown`)}function O(e){return typeof e.meta?.tmuxSession==`string`&&e.meta.tmuxSession.trim().length>0}function k(e){return(typeof e?.meta?.provider==`string`?t(e.meta.provider):void 0)?.capabilityDefaults?.terminal?.mode===`live`}function A(e){return D(e,`restart`)||D(e,`shutdown`)||D(e,`reconnect`)||D(e,`compact`)||D(e,`clearContext`)}function j(e){return!!(e&&!x(e)&&!S(e)&&!A(e)&&(e.status===`offline`||e.status===`stale`))}function M(e,t){if(e.status!==`offline`)return!1;let n=typeof e.meta?.policyName==`string`?e.meta.policyName:``;if(!n)return!1;let r=t.find(e=>e.policy.name===n);return!!(r?.state.agentId&&r.state.agentId!==e.id)}function N(e,t,n=[]){let r=e.filter(e=>!M(e,n));return t?r:r.filter(e=>!w(e))}function P(e){return e?e.label||e.name||e.id.slice(-12):`?`}function F(e,t){let n=a(e);return n?t[n]:void 0}function I(e,t){let n=F(e,t);return n?.displayName?.trim()||n?.handle?.trim()||`User`}var L=[`text-sky-300`,`text-amber-300`,`text-emerald-300`,`text-violet-300`,`text-rose-300`,`text-cyan-300`];function R(e){let t=a(e)||``,n=0;for(let e=0;e<t.length;e++)n=n*31+t.charCodeAt(e)>>>0;return L[n%L.length]}function z(e,t){if(!e)return`?`;if(e===`broadcast`)return`broadcast`;if(e.startsWith(`tag:`))return`#`+e.slice(4);if(e.startsWith(`cap:`))return e.slice(4);if(e.startsWith(`label:`))return e.slice(6);if(e.startsWith(`team:`)){let n=e.slice(5),r=Object.values(t).find(e=>e.teamId===n);return r?`team:${P(r)}`:`team:${n.slice(-8)}`}let n=t[e];return n?P(n):e.slice(-8)}function B(e){if(!e)return``;let t=e.payload||{},n=t.message;if(n&&typeof n==`object`&&typeof n.text==`string`&&n.text.trim())return n.text;let r=t.interaction;if(r&&typeof r==`object`){let e=typeof r.title==`string`?r.title.trim():``,t=typeof r.description==`string`?r.description.trim():``;if(e&&t)return e+`
|
|
2
|
-
`+t;if(e)return e;if(t)return t}let i=t.reaction;if(i&&typeof i==`object`){let e=typeof i.name==`string`?i.name:``,t=typeof i.emoji==`string`?i.emoji:``,n=typeof i.value==`string`?i.value:``;return[`Reaction`,y(t||e||n)].filter(Boolean).join(`: `)}let a=t.activity;return a&&typeof a==`object`?[typeof a.kind==`string`?a.kind:`activity`,typeof a.state==`string`?a.state:``].filter(Boolean).join(` `):typeof t.text==`string`&&t.text.trim()?t.text:typeof t.message==`string`&&t.message.trim()?t.message:U(e.body||``)??(e.body||``)}function V(e){if(!e?.payload)return!1;let t=e.payload.event;return e.payload.reactionNotification===!0||t?.type===`message.reaction`}function H(e){if(!e)return null;let t=e.trim();if(!t.startsWith(`{`))return null;try{let e=JSON.parse(t);if(typeof e!=`object`||!e||Array.isArray(e))return null;let n=typeof e.text==`string`?e.text:typeof e.message==`string`?e.message:null;if(!n?.trim())return null;let r=new Set([`text`,`message`]);return{text:n,meta:Object.entries(e).filter(([e,t])=>!r.has(e)&&(typeof t==`string`||typeof t==`number`||typeof t==`boolean`)).map(([e,t])=>[e,String(t)]),raw:e}}catch{return null}}function U(e){return H(e)?.text??null}function W(e){let t=e?.subject||B(e)||``;return t.length>90?t.slice(0,90)+`...`:t}function G(e){return r(e.from)&&e.to?e.to:r(e.to)&&e.from?e.from:``}function K(e){return r(e.to)&&!r(e.from)}function le(e){if(e.kind!==`session`)return!1;let t=e.payload?.session?.type;return t===`reasoning`||t===`tool`}function q(e){return e.kind===`session`&&e.payload?.session?.final===!0}function ue(e,t){let n=e.trim(),r=t.trim();return r?n===r?!0:n.startsWith(`${r}\n\n`)||n.endsWith(`\n\n${r}`)||n.includes(`\n\n${r}\n\n`):!1}function de(e,t,n){if(!K(n)||!q(n)||(n.readBy||[]).includes(`user`))return!1;let r=Number(e[t]||0);return n.id>(Number.isFinite(r)?r:0)}function J(e){return(e.kind??`claim`)===`claim`&&u.has(e.status)&&!e.claimedBy}function fe(e){return!!(e.claimable&&!e.claimedBy&&!(e.kind===`task`&&Number.isSafeInteger(e.payload?.taskId)))}function pe(e,t){return!e||!t?!1:e===`broadcast`||e===t.id?!0:e.startsWith(`tag:`)?(t.tags||[]).includes(e.slice(4)):e.startsWith(`cap:`)?(t.capabilities||[]).includes(e.slice(4)):e.startsWith(`label:`)?t.label===e.slice(6):e.startsWith(`team:`)?t.teamId===e.slice(5):!1}function me(e,t){return e?t.from===e||t.to===e||t.resolvedToAgent===e:!1}function he(e,t){if(!e||!t)return!1;let n=[t.id,t.type,...t.topicChannels||[]].filter(Boolean);if(e.channel&&n.includes(e.channel))return!0;let r=e.payload?.channel;if(r&&typeof r==`object`){let e=[r.agentId,r.provider,r.accountId].filter(Boolean);if(e.includes(t.id)||e.includes(t.type)||t.accountId&&e.includes(t.accountId))return!0}return!!(t.agentId&&(e.from===t.agentId||e.to===t.agentId))}function Y(t){let n=t?.meta?.providerState;return!e(n)||typeof n.state!=`string`?null:{state:n.state,reason:typeof n.reason==`string`?n.reason:void 0,label:typeof n.label==`string`&&n.label?n.label:n.state,recommendedAction:typeof n.recommendedAction==`string`?n.recommendedAction:void 0,source:typeof n.source==`string`?n.source:void 0,raw:n.raw,updatedAt:typeof n.updatedAt==`number`?n.updatedAt:void 0,pendingApproval:ge(n.pendingApproval)}}function ge(t){if(!e(t)||typeof t.id!=`string`)return;let n=Array.isArray(t.choices)?t.choices.filter(e).map(e=>({id:e.id,label:typeof e.label==`string`?e.label:String(e.id||``)})).filter(e=>(e.id===`approve`||e.id===`approve-session`||e.id===`deny`||e.id===`abort`)&&!!e.label):[],r=Array.isArray(t.questions)?t.questions.filter(e).map(t=>({question:typeof t.question==`string`?t.question:``,header:typeof t.header==`string`?t.header:void 0,multiSelect:t.multiSelect===!0,options:Array.isArray(t.options)?t.options.filter(e).map(e=>({label:typeof e.label==`string`?e.label:String(e.label??``),description:typeof e.description==`string`?e.description:void 0})).filter(e=>!!e.label):[]})).filter(e=>!!e.question&&e.options.length>0):void 0;return{id:t.id,provider:typeof t.provider==`string`?t.provider:void 0,kind:typeof t.kind==`string`?t.kind:void 0,title:typeof t.title==`string`&&t.title?t.title:`Permission request`,body:typeof t.body==`string`?t.body:``,choices:n,...r&&r.length?{questions:r}:{}}}function X(e){let t=Y(e);return t?.state===`blocked`?t:null}function _e(e){return!!e?.id}function ve(e){return _e(X(e)?.pendingApproval)}function Z(t){let n=t?.meta?.activeSubagents;if(!Array.isArray(n)){let e=typeof t?.meta?.activeSubagentCount==`number`?t.meta.activeSubagentCount:0;return e>0?Array.from({length:e},(e,t)=>({id:`subagent-${t+1}`,label:`Subagent ${t+1}`})):[]}return n.filter(e).map((e,t)=>{let n=typeof e.id==`string`&&e.id?e.id:`subagent-${t+1}`,r=typeof e.role==`string`&&e.role?e.role:void 0;return{id:n,label:typeof e.label==`string`&&e.label?e.label:r||n,role:r,startedAt:typeof e.startedAt==`number`?e.startedAt:void 0}})}function Q(t){let n=t?.meta?.activeWork;if(!Array.isArray(n))return null;let r=n.find(t=>e(t)&&t.kind===`background-script`);return!r||!e(r)?null:typeof r.label==`string`&&r.label?r.label:`background script`}function ye(e,t,n,r){let i=typeof t?.meta?.lifecycleAction==`string`?t.meta.lifecycleAction:``,a=E(e,t),o=t?.status!==`offline`&&!t?.ready&&a,s=t?.status!==`offline`&&!t?.ready&&!a,c=n.unread>0&&t?.status!==`busy`,l=X(t),u=be(t,n,r);if(t?.status===`offline`)return{label:`offline`,tone:`secondary`,icon:`PlugZap`,stale:!1,reconnecting:!1,badges:u};if(i.startsWith(`finalizing-`))return{label:`wrapping up (${i.slice(11)})`,tone:`warning`,icon:`Hourglass`,stale:a,reconnecting:o,badges:u};if(i===`shutting-down`)return{label:`shutting down`,tone:`warning`,icon:`Power`,stale:a,reconnecting:o,badges:u};if(i===`killing`)return{label:`killing`,tone:`danger`,icon:`Power`,stale:a,reconnecting:o,badges:u};if(i===`restarting`)return{label:`restarting`,tone:`warning`,icon:`RefreshCw`,stale:a,reconnecting:o,badges:u};if(t?.status===`stale`)return{label:`stale`,tone:`danger`,icon:`RefreshCw`,stale:!0,reconnecting:!0,badges:u};if(o)return{label:`reconnecting`,tone:`danger`,icon:`RefreshCw`,stale:a,reconnecting:o,badges:u};if(s)return{label:`online, not ready`,tone:`warning`,icon:`Loader`,stale:a,reconnecting:o,badges:u};if(l)return{label:`blocked: ${l.label}`,tone:`danger`,icon:`AlertCircle`,stale:a,reconnecting:o,badges:u};if(t?.status===`busy`){let e=Array.isArray(t?.meta?.busyReasons)?t.meta.busyReasons:[];return Q(t)&&!e.includes(`provider-turn`)&&!e.includes(`subagent`)?{label:`running background work`,tone:`warning`,icon:`Play`,stale:a,reconnecting:o,badges:u}:{label:`busy in turn`,tone:`warning`,icon:`Play`,stale:a,reconnecting:o,badges:u}}return r?.status===`active`?{label:`paired`,tone:`success`,icon:`Link`,stale:a,reconnecting:o,badges:u}:c?{label:`idle, unread`,tone:`danger`,icon:`Bell`,stale:a,reconnecting:o,badges:u}:{label:t?.status===`idle`?`idle`:`ready`,tone:`success`,icon:`CircleCheck`,stale:a,reconnecting:o,badges:u}}function be(e,t,n){let r=[],i=X(e),a=Z(e).length;i&&r.push({label:i.reason?`blocked: ${i.reason}`:`blocked`,className:`bg-red-500/20 text-red-400`}),a&&r.push({label:a===1?`1 subagent`:a+` subagents`,className:`bg-cyan-500/20 text-cyan-300`});let o=Q(e);return o&&r.push({label:o,className:`bg-indigo-500/20 text-indigo-300`}),n?.status===`active`&&r.push({label:`paired`,className:`bg-emerald-500/20 text-emerald-400`}),n?.status===`pending`&&r.push({label:`pair invite`,className:`bg-yellow-500/20 text-yellow-400`}),t.needsHumanResponse&&r.push({label:`needs response`,className:`bg-amber-500/20 text-amber-300`}),t.unread&&r.push({label:t.unread+` unread`,className:`bg-red-500/20 text-red-400`}),t.claimableTasks&&r.push({label:t.claimableTasks+` claimable`,className:`bg-orange-500/20 text-orange-400`}),r}function xe(){return{unread:0,needsHumanResponse:!1,pendingPairInvite:!1,claimableTasks:0,total:0,score:0}}function $(e){return!e||e.status===`offline`?!1:e.targetHealth?.status===`ok`?!0:e.targetHealth?.status===`error`||e.targetHealth?.status===`warning`?!1:e.ready}function Se(e){return e?e.targetHealth?.status===`error`?{label:`target broken`,tone:`danger`,icon:`AlertTriangle`,badges:[]}:e.targetHealth?.status===`warning`?{label:`target warning`,tone:`warning`,icon:`AlertCircle`,badges:[]}:e.status===`offline`?{label:`offline`,tone:`secondary`,icon:`PlugZap`,badges:[]}:e.status===`busy`?{label:`busy`,tone:`warning`,icon:`Activity`,badges:[]}:$(e)?{label:`ready`,tone:`success`,icon:`CircleCheck`,badges:[]}:{label:`not ready`,tone:`warning`,icon:`Loader`,badges:[]}:{label:`unknown`,tone:`secondary`,icon:`PlugZap`,badges:[]}}function Ce(e){let t=e?.runtime||{};return t.status===`error`?{label:`error`,tone:`danger`,icon:`AlertTriangle`,badges:[]}:t.status===`warn`?{label:`warning`,tone:`warning`,icon:`AlertCircle`,badges:[]}:t.running?{label:`running`,tone:`success`,icon:`CircleCheck`,badges:[]}:t.enabled===!1?{label:`disabled`,tone:`secondary`,icon:`Pause`,badges:[]}:t.status===`ok`?{label:`ok`,tone:`success`,icon:`CircleCheck`,badges:[]}:{label:`unknown`,tone:`secondary`,icon:`HelpCircle`,badges:[]}}function we(e){let t=e?.taskStats||{};return(t.waitingTasks||0)>0?{label:`waiting`,tone:`warning`,icon:`AlertCircle`,badges:[]}:(t.openTasks||0)>0?{label:`active`,tone:`info`,icon:`Activity`,badges:[]}:e?.configured?e.observed?{label:`quiet`,tone:`success`,icon:`CircleCheck`,badges:[]}:{label:`configured`,tone:`primary`,icon:`PlugZap`,badges:[]}:{label:`observed only`,tone:`secondary`,icon:`Eye`,badges:[]}}function Te(e){let t=e?.health;return!t||t.status===`ok`?e?.version||e?.package?.version?`compatible`:`unknown version`:t.status===`upgrade-required`?`upgrade required`:t.status===`restart-required`?`restart required`:t.status===`unknown`?`unknown contract`:(t.issues||[]).some(e=>e.code===`protocol-mismatch`)?`protocol mismatch`:(t.issues||[]).some(e=>e.code===`package-drift`)?`package drift`:t.restartRequired?`restart required`:t.status}function Ee(e){let t=e?.health?.status;return t===`upgrade-required`?`text-red-400 bg-red-500/10`:t===`warn`||t===`restart-required`?`text-yellow-400 bg-yellow-500/10`:t===`unknown`?`text-zinc-400 bg-zinc-500/10`:`text-emerald-400 bg-emerald-500/10`}function De(e,t){if(!t)return``;let n=new Date(t).getTime();if(!Number.isFinite(n))return``;let r=Math.max(0,(e-n)/1e3);return r<60?Math.floor(r)+`s ago`:r<3600?Math.floor(r/60)+`m ago`:r<86400?Math.floor(r/3600)+`h ago`:Math.floor(r/86400)+`d ago`}function Oe(e){return e?new Date(e).toLocaleString():``}function ke(e){return String(e||`export`).replace(/[^a-z0-9._-]+/gi,`-`).replace(/^-+|-+$/g,``).slice(0,80)||`export`}function Ae(e){return e.filter(e=>e.status!==`ok`).map(e=>{let t={name:e.name,status:e.status,detail:e.detail||e.name,impact:je(e),subjects:e.subjects||[],actions:[{label:`Inspect logs`,icon:`FileSearch`,copy:`agent-relay daemon logs`},{label:`Restart daemon`,icon:`RefreshCw`,copy:`agent-relay daemon restart`},{label:`Copy env`,icon:`Copy`,copy:`agent-relay doctor`}]};return e.name===`stale-live-agents`?t.actions.unshift({label:`Run reaper`,icon:`Eraser`,api:`POST`,path:`/system/reap`},{label:`Show stale`,icon:`Filter`,view:`agents`,preset:`offline_stale`}):e.name===`channel-delivery-targets`?t.actions.unshift({label:`Open channels`,icon:`MessagesSquare`,view:`channels`},{label:`Run reaper`,icon:`Eraser`,api:`POST`,path:`/system/reap`}):[`expired-message-claims`,`expired-task-claims`,`offline-claimed-tasks`].includes(e.name)&&t.actions.unshift({label:`Run reaper`,icon:`Eraser`,api:`POST`,path:`/system/reap`},{label:`Open work`,icon:`ListChecks`,view:`work`}),t})}function je(e){return{database:`Relay persistence is unavailable; messages, state, and audit writes may fail.`,"stale-live-agents":`Agents may look online even though their heartbeat has stopped.`,"expired-message-claims":`Claimable messages may be stuck until the reaper releases expired claims.`,"expired-task-claims":`Tasks can appear owned by agents that no longer hold a live lease.`,"offline-claimed-tasks":`Offline agents are still shown as owners for active work.`,"channel-delivery-targets":`Inbound channel messages may be accepted but routed to no live delivery agent.`}[e.name]||`Relay health is degraded for this check.`}function Me(e){return{success:`text-emerald-400`,warning:`text-yellow-400`,danger:`text-red-400`,info:`text-blue-400`,primary:`text-blue-400`,secondary:`text-zinc-400`}[e]||`text-zinc-400`}function Ne(e,t){return!e||e.status===`offline`?`bg-zinc-500 opacity-50`:typeof e.meta?.lifecycleAction==`string`&&e.meta.lifecycleAction.startsWith(`finalizing-`)?`bg-amber-500 animate-pulse`:e.meta?.lifecycleAction===`shutting-down`||e.meta?.lifecycleAction===`restarting`?`bg-yellow-500 animate-pulse`:e.meta?.lifecycleAction===`killing`||X(e)?`bg-red-500 animate-pulse`:e.status===`busy`?`bg-yellow-500`:t&&E(t,e)?`bg-red-500 animate-pulse`:e.ready?`bg-emerald-500 shadow-[0_0_6px_theme(colors.emerald.500)]`:`bg-emerald-500 animate-pulse`}function Pe(e,t,n){if(typeof document>`u`||typeof URL>`u`||typeof Blob>`u`)return;let r=URL.createObjectURL(new Blob([t],{type:n})),i=document.createElement(`a`);i.href=r,i.download=e,i.click(),URL.revokeObjectURL(r)}export{p as $,me as A,Y as B,fe as C,le as D,V as E,Ee as F,Ne as G,ue as H,Te as I,Me as J,De as K,H as L,W as M,b as N,de as O,y as P,l as Q,ce as R,q as S,K as T,ke as U,F as V,se as W,N as X,oe as Y,f as Z,R as _,D as a,c as at,we as b,Se as c,ie as ct,P as d,m as et,z as f,Ae as g,Oe as h,ye as i,s as it,he as j,B as k,Ce as l,d as lt,xe as m,j as n,h as nt,T as o,re as ot,Pe as p,g as q,ve as r,o as rt,$ as s,ae as st,Z as t,ee as tt,pe as u,I as v,J as w,E as x,G as y,X as z};
|
|
3
|
-
//# sourceMappingURL=display-exQDWum3.js.map
|