agent-relay-server 0.121.5 → 0.122.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/openapi.json +1 -1
- package/package.json +6 -6
- 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/adapters/claude-delivery.ts +127 -0
- package/runner/src/adapters/claude-quota-harvest.ts +92 -0
- package/runner/src/adapters/claude-session-probe.ts +225 -0
- package/runner/src/adapters/claude-status-detectors.ts +147 -0
- package/runner/src/adapters/claude-transcript-tail.ts +128 -0
- package/runner/src/adapters/claude-transcript.ts +414 -0
- package/runner/src/adapters/claude.ts +672 -0
- package/runner/src/adapters/codex-agent-message-capture.ts +139 -0
- package/runner/src/adapters/codex-client.ts +279 -0
- package/runner/src/adapters/codex.ts +1345 -0
- package/runner/src/attachment-cache.ts +189 -0
- package/runner/src/busy-reconciler.ts +102 -0
- package/runner/src/claim-tracker.ts +140 -0
- package/runner/src/claude-prompt-gates.ts +268 -0
- package/runner/src/config.ts +1 -4
- package/runner/src/context-probe-state.ts +6 -0
- package/runner/src/continuation-archive.ts +179 -0
- package/runner/src/control-server.ts +639 -0
- package/runner/src/index.ts +396 -0
- package/runner/src/launch-assembly.ts +508 -0
- package/runner/src/liveness.ts +50 -0
- package/runner/src/logger.ts +99 -0
- package/runner/src/mcp-outbox.ts +120 -0
- package/runner/src/message-body-config/config.ts +3 -0
- package/runner/src/message-body-config.ts +1 -0
- package/runner/src/native-self-resume.ts +202 -0
- package/runner/src/outbox.ts +342 -0
- package/runner/src/process-meta.ts +9 -0
- package/runner/src/profile-home.ts +260 -0
- package/runner/src/profile-projection.ts +305 -0
- package/runner/src/providers.ts +20 -0
- package/runner/src/provisioning.ts +314 -0
- package/runner/src/quota.ts +40 -0
- package/runner/src/rate-limit.ts +189 -0
- package/runner/src/relay-injection-events.ts +102 -0
- package/runner/src/relay-instructions.ts +60 -0
- package/runner/src/relay-mcp-proxy.ts +383 -0
- package/runner/src/relay-mcp.ts +156 -0
- package/runner/src/reply-obligation-cache.ts +136 -0
- package/runner/src/response-capture-report.ts +63 -0
- package/runner/src/runner-core.ts +2409 -0
- package/runner/src/runner-helpers.ts +435 -0
- package/runner/src/runner-insights.ts +105 -0
- package/runner/src/runner.ts +14 -0
- package/runner/src/session-destroy.ts +22 -0
- package/runner/src/session-insights.ts +118 -0
- package/runner/src/session-scratch.ts +271 -0
- package/runner/src/template-resolver.ts +29 -0
- package/runner/src/version.ts +43 -0
- 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 +19 -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
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"display-exQDWum3.js","names":[],"sources":["../../dashboard/src/lib/constants.ts","../../dashboard/src/lib/display.ts"],"sourcesContent":["import type { ComposeState, InboxComposeState, PairMessageState, PairInviteState, ViewName } from '@/types'\n\nconst PREF_PREFIX = 'ar-'\nexport const HUMAN_AGENT_ID = 'user'\nexport const INBOX_OPERATOR_ID = HUMAN_AGENT_ID\nexport const LIVE_REFRESH_MS = 5_000\n// Max live messages retained in memory. Must be >= the largest fetch limit\n// (chat view fetches 300) so SSE appends never trim already-loaded history.\nexport const MESSAGE_BUFFER_CAP = 500\n\nexport const CLOSED_TASK_STATUSES = new Set(['done', 'failed', 'canceled'])\nexport const WAITING_TASK_STATUSES = new Set(['open', 'blocked'])\n\nexport const STATUS_SORT_ORDER: Record<string, number> = { online: 0, idle: 0, busy: 2, offline: 3 }\nexport const CHAT_STATUS_SORT_ORDER: Record<string, number> = { idle: 0, online: 0, busy: 2, offline: 4 }\n\nexport const DEFAULT_COMPOSE: ComposeState = { from: '', to: '', body: '', channel: '', subject: '', claimable: false }\nexport const DEFAULT_INBOX_COMPOSE: InboxComposeState = { toMode: 'agent', to: '', body: '', channel: '', subject: '', claimable: false }\nexport const DEFAULT_PAIR_MESSAGE: PairMessageState = { pairId: '', from: '', body: '', subject: '' }\nexport const DEFAULT_PAIR_INVITE: PairInviteState = { requesterId: '', targetId: '', objective: '' }\n\n\nimport { getKnownProviderIds, getProviderLabel } from './provider-display'\n\nconst SYSTEM_TYPE_ICONS: Record<string, string> = {\n user: 'User',\n system: 'Server',\n channel: 'MessagesSquare',\n agent: 'Bot',\n}\n\nconst SYSTEM_TYPE_TITLES: Record<string, string> = {\n user: 'Human operator',\n system: 'System',\n channel: 'Channel',\n agent: 'Agent',\n}\n\nexport const AGENT_TYPE_ICONS: Record<string, string> = {\n ...SYSTEM_TYPE_ICONS,\n ...Object.fromEntries(getKnownProviderIds().map((id) => [id, 'Bot'])),\n}\n\nexport const AGENT_TYPE_TITLES: Record<string, string> = {\n ...SYSTEM_TYPE_TITLES,\n ...Object.fromEntries(getKnownProviderIds().map((id) => [id, `${getProviderLabel(id)} agent`])),\n}\n\nexport const NAV_ITEMS: Array<{ key: ViewName; label: string; icon: string }> = [\n { key: 'overview', label: 'Overview', icon: 'LayoutDashboard' },\n { key: 'chat', label: 'Chat', icon: 'MessageCircle' },\n { key: 'agents', label: 'Agents', icon: 'Bot' },\n { key: 'managed', label: 'Managed Agents', icon: 'Workflow' },\n { key: 'teams', label: 'Teams', icon: 'Users' },\n { key: 'profiles', label: 'Agent Profiles', icon: 'UserCog' },\n { key: 'orchestrators', label: 'Orchestrators', icon: 'Server' },\n { key: 'workspaces', label: 'Workspaces', icon: 'GitBranch' },\n { key: 'files', label: 'Files', icon: 'FolderTree' },\n { key: 'channels', label: 'Channels', icon: 'MessagesSquare' },\n { key: 'connectors', label: 'Connectors', icon: 'Plug' },\n { key: 'integrations', label: 'Integrations', icon: 'PlugZap' },\n { key: 'security', label: 'Security', icon: 'Shield' },\n { key: 'memory', label: 'Memory', icon: 'BrainCircuit' },\n { key: 'activity', label: 'Activity', icon: 'Activity' },\n { key: 'pairs', label: 'Pairs', icon: 'Link' },\n { key: 'messages', label: 'Messages', icon: 'Mail' },\n { key: 'work', label: 'Work Queue', icon: 'ListChecks' },\n { key: 'tasks', label: 'Tasks', icon: 'ClipboardList' },\n { key: 'automation', label: 'Automation', icon: 'CalendarClock' },\n { key: 'analytics', label: 'Analytics', icon: 'AreaChart' },\n { key: 'insights', label: 'Insights', icon: 'Lightbulb' },\n { key: 'projects', label: 'Projects', icon: 'Boxes' },\n { key: 'prompts', label: 'Prompts', icon: 'FileText' },\n { key: 'registry', label: 'Registry', icon: 'PackagePlus' },\n { key: 'maintenance', label: 'Maintenance', icon: 'Wrench' },\n { key: 'settings', label: 'Settings', icon: 'Settings' },\n]\n\nexport const SEVERITY_COLORS: Record<string, string> = {\n critical: 'text-red-400 bg-red-500/10',\n warning: 'text-yellow-400 bg-yellow-500/10',\n info: 'text-blue-400 bg-blue-500/10',\n}\n\nexport const PAIR_STATUS_COLORS: Record<string, string> = {\n active: 'bg-emerald-500/20 text-emerald-400',\n pending: 'bg-yellow-500/20 text-yellow-400',\n ended: 'bg-zinc-500/20 text-zinc-400',\n rejected: 'bg-red-500/20 text-red-400',\n expired: 'bg-red-500/20 text-red-400',\n}\n","import type {\n Agent, Message, PairSession, Task, ChannelSummary, ConnectorSummary,\n IntegrationSummary, Orchestrator, AgentType, PresenceInfo, PresenceBadge,\n AttentionInfo, InboxThread, HealthCheck, HealthDiagnostic, ManagedPolicyHealth,\n} from '@/types'\nimport type { User } from '@/types'\nimport { HUMAN_AGENT_ID, AGENT_TYPE_ICONS, AGENT_TYPE_TITLES, WAITING_TASK_STATUSES } from './constants'\nimport { getKnownProviderIds, getProviderManifest } from './provider-display'\nimport { isRecord, isHumanAgentId, userIdFromAddress } from 'agent-relay-sdk/types'\n\nexport function toTimestamp(value: unknown): number {\n const ts = typeof value === 'number' ? value : new Date((value as string) || 0).getTime()\n return Number.isFinite(ts) ? ts : 0\n}\n\nconst INTERNAL_CAP_PREFIXES = ['lifecycle.', 'commands.', 'capabilities.']\n\nconst REACTION_EMOJI_ALIASES: Record<string, string> = {\n '+1': '👍',\n ':+1:': '👍',\n thumbsup: '👍',\n ':thumbsup:': '👍',\n 'thumbs-up': '👍',\n ':thumbs-up:': '👍',\n thumbs_up: '👍',\n ':thumbs_up:': '👍',\n thumb_up: '👍',\n ':thumb_up:': '👍',\n like: '👍',\n ':like:': '👍',\n heart: '❤️',\n ':heart:': '❤️',\n redheart: '❤️',\n red_heart: '❤️',\n ':red_heart:': '❤️',\n check: '✅',\n ':check:': '✅',\n checkmark: '✅',\n ':checkmark:': '✅',\n white_check_mark: '✅',\n ':white_check_mark:': '✅',\n eyes: '👀',\n ':eyes:': '👀',\n}\n\nexport function normalizeReactionEmoji(value: string): string {\n const trimmed = value.trim()\n return REACTION_EMOJI_ALIASES[trimmed.toLowerCase()] ?? trimmed\n}\n\nexport function userFacingCapabilities(caps: string[]): string[] {\n return caps.filter((c) => !INTERNAL_CAP_PREFIXES.some((p) => c.startsWith(p)))\n}\n\nexport function shortPath(cwd: string | undefined | null, segments = 2): string {\n if (!cwd) return ''\n const parts = cwd.replace(/\\/+$/, '').split('/')\n return parts.length <= segments ? cwd : parts.slice(-segments).join('/')\n}\n\nexport function normalizePathForCompare(path: string): string {\n return path.trim().replace(/\\\\/g, '/').replace(/\\/+$/, '') || '/'\n}\n\nexport function pathWithinBase(path: string | undefined | null, baseDir: string | undefined | null): boolean {\n if (!path?.trim() || !baseDir?.trim()) return false\n const normalizedPath = normalizePathForCompare(path)\n const normalizedBase = normalizePathForCompare(baseDir)\n if (normalizedBase === '/') return normalizedPath.startsWith('/')\n if (normalizedPath === '/') return false\n return normalizedPath === normalizedBase || normalizedPath.startsWith(normalizedBase + '/')\n}\n\nfunction isBuiltInAgent(agent: Agent | null | undefined): boolean {\n return agent?.meta?.builtin === true || agent?.id === HUMAN_AGENT_ID || agent?.id === 'system'\n}\n\nfunction isChannelAgent(agent: Agent | null | undefined): boolean {\n const tags = agent?.tags || []\n const caps = agent?.capabilities || []\n const policyName = typeof agent?.meta?.policyName === 'string' ? agent.meta.policyName : ''\n return agent?.kind === 'channel' ||\n agent?.meta?.kind === 'channel' ||\n tags.includes('channel') ||\n tags.some((tag) => tag.startsWith('channel:')) ||\n caps.includes('channel') ||\n caps.some((cap) => cap.startsWith('channel:')) ||\n tags.includes('telegram') ||\n caps.includes('telegram') ||\n policyName === 'telegram-main'\n}\n\nfunction isAutomationAgent(agent: Agent | null | undefined): boolean {\n return Boolean(\n typeof agent?.meta?.automationId === 'string' ||\n typeof agent?.meta?.automationRunId === 'string' ||\n agent?.tags?.includes('automation')\n )\n}\n\nfunction isSystemAgent(agent: Agent | null | undefined): boolean {\n return isBuiltInAgent(agent) || isChannelAgent(agent) || isAutomationAgent(agent)\n}\n\nexport function agentType(agent: Agent | null | undefined): AgentType {\n if (agent?.id === HUMAN_AGENT_ID) return 'user'\n if (agent?.id === 'system') return 'system'\n if (isChannelAgent(agent)) return 'channel'\n\n const knownProviders = getKnownProviderIds()\n\n // #417: check the authoritative provider field first (exact match) so a name or id\n // containing a provider name as a substring can't mislabel the agent.\n const provider = typeof agent?.meta?.provider === 'string' ? agent.meta.provider.toLowerCase() : undefined\n if (provider && knownProviders.includes(provider)) return provider as AgentType\n\n // Fallback heuristic for agents registered without meta.provider. Only scan tags\n // and meta fields — not id or name, which are free-text and cause false positives.\n const values = [\n ...(agent?.tags || []),\n agent?.meta?.client as string,\n agent?.meta?.runtime as string,\n agent?.meta?.agentType as string,\n ]\n .filter((v): v is string => typeof v === 'string')\n .map((v) => v.toLowerCase())\n\n for (const pid of knownProviders) {\n if (values.some((v) => v.includes(pid))) return pid as AgentType\n }\n return 'agent'\n}\n\nfunction agentTypeIcon(agent: Agent | null | undefined): string {\n return AGENT_TYPE_ICONS[agentType(agent)] ?? AGENT_TYPE_ICONS.agent ?? 'ti-robot'\n}\n\nfunction agentTypeTitle(agent: Agent | null | undefined): string {\n return AGENT_TYPE_TITLES[agentType(agent)] ?? AGENT_TYPE_TITLES.agent ?? 'Agent'\n}\n\nexport function isAgentStale(now: number, agent: Agent | null | undefined): boolean {\n if (!agent?.lastSeen || agent.status === 'offline') return false\n if (agent.id === 'user' || agent.id === 'system') return false\n // A live bus connection is authoritative liveness: an idle-but-connected agent\n // is reachable, not stale, even if lastSeen has aged (e.g. heartbeat keepalive\n // gaps while idle). Genuine death flips transport.connected to false (or the\n // server reaps the agent offline), at which point the lastSeen check applies.\n const transport = agent.meta?.transport\n if (isRecord(transport) && transport.connected === true) return false\n const lastSeenMs = new Date(agent.lastSeen as string).getTime()\n if (!Number.isFinite(lastSeenMs)) return false\n return now - lastSeenMs > 60_000\n}\n\nexport type AgentControlAction = 'restart' | 'shutdown' | 'reconnect' | 'compact' | 'clearContext' | 'resume' | 'interrupt'\n\nexport function agentSupportsControlAction(agent: Agent | null | undefined, action: AgentControlAction): boolean {\n if (!agent || isBuiltInAgent(agent) || isChannelAgent(agent)) return false\n if (action === 'resume') return false\n // Interrupt is provider-independent and only meaningful while the agent is actually mid-turn.\n if (action === 'interrupt') return agent.providerCapabilities?.liveSession?.interrupt === true && agent.status === 'busy'\n const needsTmux = providerUsesLiveTerminal(agent) && (action === 'restart' || action === 'shutdown' || action === 'compact' || action === 'clearContext')\n if (needsTmux && !agentHasTmuxSession(agent)) return false\n const lifecycle = agent.providerCapabilities?.lifecycle\n if (lifecycle) {\n if (action === 'restart') return lifecycle.restartHard === true\n if (action === 'shutdown') return lifecycle.shutdownHard === true\n if (action === 'reconnect') return lifecycle.reconnect === true\n }\n if (action === 'compact') return agent.providerCapabilities?.context?.compact === true\n if (action === 'clearContext') return agent.providerCapabilities?.context?.clear === true\n return agent.meta?.runnerManaged === true && (action === 'restart' || action === 'shutdown')\n}\n\nfunction agentHasTmuxSession(agent: Agent): boolean {\n return typeof agent.meta?.tmuxSession === 'string' && agent.meta.tmuxSession.trim().length > 0\n}\n\nfunction providerUsesLiveTerminal(agent: Agent | null | undefined): boolean {\n const manifest = typeof agent?.meta?.provider === 'string' ? getProviderManifest(agent.meta.provider as string) : undefined\n return manifest?.capabilityDefaults?.terminal?.mode === 'live'\n}\n\nfunction agentSupportsControlActions(agent: Agent | null | undefined): boolean {\n return agentSupportsControlAction(agent, 'restart') ||\n agentSupportsControlAction(agent, 'shutdown') ||\n agentSupportsControlAction(agent, 'reconnect') ||\n agentSupportsControlAction(agent, 'compact') ||\n agentSupportsControlAction(agent, 'clearContext')\n}\n\nexport function agentCanBeForgotten(agent: Agent | null | undefined): boolean {\n return Boolean(agent && !isBuiltInAgent(agent) && !isChannelAgent(agent) && !agentSupportsControlActions(agent) && (agent.status === 'offline' || agent.status === 'stale'))\n}\n\nfunction isRetiredManagedRuntimeAgent(agent: Agent, managedPolicies: ManagedPolicyHealth[]): boolean {\n if (agent.status !== 'offline') return false\n const policyName = typeof agent.meta?.policyName === 'string' ? agent.meta.policyName : ''\n if (!policyName) return false\n const policy = managedPolicies.find((item) => item.policy.name === policyName)\n return Boolean(policy?.state.agentId && policy.state.agentId !== agent.id)\n}\n\nexport function visibleAgents(agents: Agent[], showBuiltIns: boolean, managedPolicies: ManagedPolicyHealth[] = []): Agent[] {\n const activeRuntimeAgents = agents.filter((a) => !isRetiredManagedRuntimeAgent(a, managedPolicies))\n return showBuiltIns ? activeRuntimeAgents : activeRuntimeAgents.filter((a) => !isSystemAgent(a))\n}\n\nexport function displayName(agent: Agent | null | undefined): string {\n if (!agent) return '?'\n return agent.label || agent.name || agent.id.slice(-12)\n}\n\n// Human attribution (#390). Resolve a human message address (`user` / `user:<id>`) to its\n// users-table row, or undefined for a non-human address / unknown id. The single bridge from\n// a wire address → a user record on the client, mirroring the SDK resolver on the server.\nexport function resolveUser(address: string | undefined, usersById: Record<string, User>): User | undefined {\n const userId = userIdFromAddress(address)\n return userId ? usersById[userId] : undefined\n}\n\n// Name to show for a human address. Prefers the user's displayName, then handle, then a\n// generic 'User' so a not-yet-loaded directory (or the seeded default) still reads sensibly —\n// which keeps today's single operator rendering exactly as before.\nexport function humanDisplayName(address: string | undefined, usersById: Record<string, User>): string {\n const user = resolveUser(address, usersById)\n return user?.displayName?.trim() || user?.handle?.trim() || 'User'\n}\n\n// Deterministic accent color per human so multiple operators are visually distinguishable in\n// one thread. Stable for a given user id; the single default operator never needs it (callers\n// only apply this once more than one human exists, preserving back-compat styling).\nconst HUMAN_ACCENT_CLASSES = [\n 'text-sky-300', 'text-amber-300', 'text-emerald-300', 'text-violet-300', 'text-rose-300', 'text-cyan-300',\n]\nexport function humanAccentClass(address: string | undefined): string {\n const userId = userIdFromAddress(address) || ''\n let hash = 0\n for (let i = 0; i < userId.length; i++) hash = (hash * 31 + userId.charCodeAt(i)) >>> 0\n return HUMAN_ACCENT_CLASSES[hash % HUMAN_ACCENT_CLASSES.length]!\n}\n\nexport function displayTarget(target: string, agentsById: Record<string, Agent>): string {\n if (!target) return '?'\n if (target === 'broadcast') return 'broadcast'\n if (target.startsWith('tag:')) return '#' + target.slice(4)\n if (target.startsWith('cap:')) return target.slice(4)\n if (target.startsWith('label:')) return target.slice(6)\n if (target.startsWith('team:')) {\n const teamId = target.slice(5)\n const member = Object.values(agentsById).find((a) => a.teamId === teamId)\n return member ? `team:${displayName(member)}` : `team:${teamId.slice(-8)}`\n }\n const agent = agentsById[target]\n return agent ? displayName(agent) : target.slice(-8)\n}\n\nexport function messageBody(msg: Message | null | undefined): string {\n if (!msg) return ''\n const payload = msg.payload || {}\n const channelMessage = payload.message as Record<string, unknown> | undefined\n if (channelMessage && typeof channelMessage === 'object' && typeof channelMessage.text === 'string' && (channelMessage.text as string).trim()) {\n return channelMessage.text as string\n }\n const interaction = payload.interaction as Record<string, unknown> | undefined\n if (interaction && typeof interaction === 'object') {\n const title = typeof interaction.title === 'string' ? interaction.title.trim() : ''\n const description = typeof interaction.description === 'string' ? interaction.description.trim() : ''\n if (title && description) return title + '\\n' + description\n if (title) return title\n if (description) return description\n }\n const reaction = payload.reaction as Record<string, unknown> | undefined\n if (reaction && typeof reaction === 'object') {\n const name = typeof reaction.name === 'string' ? reaction.name : ''\n const emoji = typeof reaction.emoji === 'string' ? reaction.emoji : ''\n const value = typeof reaction.value === 'string' ? reaction.value : ''\n const displayEmoji = normalizeReactionEmoji(emoji || name || value)\n return ['Reaction', displayEmoji].filter(Boolean).join(': ')\n }\n const activity = payload.activity as Record<string, unknown> | undefined\n if (activity && typeof activity === 'object') {\n const kind = typeof activity.kind === 'string' ? activity.kind : 'activity'\n const state = typeof activity.state === 'string' ? activity.state : ''\n return [kind, state].filter(Boolean).join(' ')\n }\n if (typeof payload.text === 'string' && (payload.text as string).trim()) return payload.text as string\n if (typeof payload.message === 'string' && (payload.message as string).trim()) return payload.message as string\n return extractTextFromJson(msg.body || '') ?? (msg.body || '')\n}\n\nexport function isReactionEventMessage(msg: Message | null | undefined): boolean {\n if (!msg?.payload) return false\n const event = msg.payload.event as Record<string, unknown> | undefined\n return msg.payload.reactionNotification === true || event?.type === 'message.reaction'\n}\n\ninterface ParsedJsonBody {\n text: string\n meta: Array<[string, string]>\n raw: Record<string, unknown>\n}\n\nexport function parseJsonBody(body: string): ParsedJsonBody | null {\n if (!body) return null\n const trimmed = body.trim()\n if (!trimmed.startsWith('{')) return null\n try {\n const obj = JSON.parse(trimmed)\n if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) return null\n const text = typeof obj.text === 'string' ? obj.text\n : typeof obj.message === 'string' ? obj.message\n : null\n if (!text?.trim()) return null\n const skipKeys = new Set(['text', 'message'])\n const meta = Object.entries(obj)\n .filter(([k, v]) => !skipKeys.has(k) && (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean'))\n .map(([k, v]) => [k, String(v)] as [string, string])\n return { text, meta, raw: obj }\n } catch {\n return null\n }\n}\n\nfunction extractTextFromJson(body: string): string | null {\n const parsed = parseJsonBody(body)\n return parsed?.text ?? null\n}\n\nexport function messagePreview(msg: Message | null | undefined): string {\n const text = msg?.subject || messageBody(msg) || ''\n return text.length > 90 ? text.slice(0, 90) + '...' : text\n}\n\n// De-singletoned human addressing (#393): a thread peer is the non-human side of any message\n// whose other side is a human address — the legacy bare `'user'` sink OR any `user:<id>` in the\n// namespace (`user:default` included). Today's single operator is still `'user'`, so this is a\n// no-op for the live deployment; per-user inbox scoping lands with auth/sessions (#389).\nexport function inboxPeer(msg: Message): string {\n if (isHumanAgentId(msg.from) && msg.to) return msg.to\n if (isHumanAgentId(msg.to) && msg.from) return msg.from\n return ''\n}\n\nexport function isHumanInboundMessage(msg: Message): boolean {\n return isHumanAgentId(msg.to) && !isHumanAgentId(msg.from)\n}\n\nexport function isSessionActivityStep(msg: Message): boolean {\n if (msg.kind !== 'session') return false\n const type = (msg.payload?.session as { type?: string } | undefined)?.type\n return type === 'reasoning' || type === 'tool'\n}\n\nexport function isBadgeWorthy(msg: Message): boolean {\n return msg.kind === 'session' && (msg.payload?.session as { final?: boolean } | undefined)?.final === true\n}\n\n// True when `block` appears as a whole \\n\\n-delimited segment of a response body —\n// the same boundary extractLastAssistantTurn joins narration on. Matches the entire\n// body (chatCaptureMode \"final\") or any internal block (\"full\") without naive-substring\n// false positives, and is robust to narration blocks that contain their own blank lines.\n// Used to suppress a narration trace step that the closing response bubble already repeats.\nexport function responseBodyContainsBlock(body: string, block: string): boolean {\n const b = body.trim()\n const t = block.trim()\n if (!t) return false\n if (b === t) return true\n return b.startsWith(`${t}\\n\\n`) || b.endsWith(`\\n\\n${t}`) || b.includes(`\\n\\n${t}\\n\\n`)\n}\n\nexport function isUnreadHumanMessage(readCursors: Record<string, number>, peer: string, msg: Message): boolean {\n if (!isHumanInboundMessage(msg)) return false\n if (!isBadgeWorthy(msg)) return false\n if ((msg.readBy || []).includes(HUMAN_AGENT_ID)) return false\n const cursor = Number(readCursors[peer] || 0)\n return msg.id > (Number.isFinite(cursor) ? cursor : 0)\n}\n\nexport function isClaimableTaskWaiting(task: Task): boolean {\n // #834 — only claim-lane tasks are claimable; deliverable Tasks are never claimed.\n return (task.kind ?? 'claim') === 'claim' && WAITING_TASK_STATUSES.has(task.status) && !task.claimedBy\n}\n\nexport function isClaimableMessageWaiting(msg: Message): boolean {\n return Boolean(msg.claimable && !msg.claimedBy && !(msg.kind === 'task' && Number.isSafeInteger((msg.payload as Record<string, unknown>)?.taskId)))\n}\n\nexport function dashboardTargetMatchesAgent(target: string, agent: Agent): boolean {\n if (!target || !agent) return false\n if (target === 'broadcast' || target === agent.id) return true\n if (target.startsWith('tag:')) return (agent.tags || []).includes(target.slice(4))\n if (target.startsWith('cap:')) return (agent.capabilities || []).includes(target.slice(4))\n if (target.startsWith('label:')) return agent.label === target.slice(6)\n if (target.startsWith('team:')) return agent.teamId === target.slice(5)\n return false\n}\n\nexport function messageMatchesAgentChat(agentId: string, message: Message): boolean {\n if (!agentId) return false\n if (message.from === agentId || message.to === agentId) return true\n if (message.resolvedToAgent === agentId) return true\n return false\n}\n\nexport function messageMatchesChannel(message: Message, channel: ChannelSummary): boolean {\n if (!message || !channel) return false\n const channelKeys = [channel.id, channel.type, ...(channel.topicChannels || [])].filter(Boolean)\n if (message.channel && channelKeys.includes(message.channel)) return true\n const payloadChannel = message.payload?.channel as Record<string, unknown> | undefined\n if (payloadChannel && typeof payloadChannel === 'object') {\n const keys = [payloadChannel.agentId, payloadChannel.provider, payloadChannel.accountId].filter(Boolean) as string[]\n if (keys.includes(channel.id) || keys.includes(channel.type) || (channel.accountId && keys.includes(channel.accountId))) return true\n }\n if (channel.agentId && (message.from === channel.agentId || message.to === channel.agentId)) return true\n return false\n}\n\ninterface ActiveSubagentInfo {\n id: string\n label: string\n role?: string\n startedAt?: number\n}\n\ninterface ProviderRuntimeState {\n state: string\n reason?: string\n label: string\n recommendedAction?: string\n source?: string\n raw?: unknown\n updatedAt?: number\n pendingApproval?: ProviderPendingApproval\n}\n\ninterface ProviderPendingApprovalChoice {\n id: 'approve' | 'approve-session' | 'deny' | 'abort'\n label: string\n}\n\ninterface ProviderQuestionOption {\n label: string\n description?: string\n}\n\nexport interface ProviderQuestion {\n question: string\n header?: string\n options: ProviderQuestionOption[]\n multiSelect?: boolean\n}\n\nexport interface ProviderPendingApproval {\n id: string\n provider?: string\n kind?: string\n title: string\n body: string\n choices: ProviderPendingApprovalChoice[]\n // Present when kind === 'questions' (Claude AskUserQuestion). The dashboard\n // renders these as a form and submits an 'answer' decision.\n questions?: ProviderQuestion[]\n}\n\nexport function providerRuntimeState(agent: Agent | null | undefined): ProviderRuntimeState | null {\n const raw = agent?.meta?.providerState\n if (!isRecord(raw) || typeof raw.state !== 'string') return null\n return {\n state: raw.state,\n reason: typeof raw.reason === 'string' ? raw.reason : undefined,\n label: typeof raw.label === 'string' && raw.label ? raw.label : raw.state,\n recommendedAction: typeof raw.recommendedAction === 'string' ? raw.recommendedAction : undefined,\n source: typeof raw.source === 'string' ? raw.source : undefined,\n raw: raw.raw,\n updatedAt: typeof raw.updatedAt === 'number' ? raw.updatedAt : undefined,\n pendingApproval: providerPendingApproval(raw.pendingApproval),\n }\n}\n\nfunction providerPendingApproval(value: unknown): ProviderPendingApproval | undefined {\n if (!isRecord(value) || typeof value.id !== 'string') return undefined\n const choices = Array.isArray(value.choices)\n ? value.choices.filter(isRecord).map((choice) => ({\n id: choice.id,\n label: typeof choice.label === 'string' ? choice.label : String(choice.id || ''),\n })).filter((choice): choice is ProviderPendingApprovalChoice =>\n (choice.id === 'approve' || choice.id === 'approve-session' || choice.id === 'deny' || choice.id === 'abort') && Boolean(choice.label)\n )\n : []\n const questions = Array.isArray(value.questions)\n ? value.questions.filter(isRecord).map((q): ProviderQuestion => ({\n question: typeof q.question === 'string' ? q.question : '',\n header: typeof q.header === 'string' ? q.header : undefined,\n multiSelect: q.multiSelect === true,\n options: Array.isArray(q.options)\n ? q.options.filter(isRecord).map((o) => ({\n label: typeof o.label === 'string' ? o.label : String(o.label ?? ''),\n description: typeof o.description === 'string' ? o.description : undefined,\n })).filter((o) => Boolean(o.label))\n : [],\n })).filter((q) => Boolean(q.question) && q.options.length > 0)\n : undefined\n return {\n id: value.id,\n provider: typeof value.provider === 'string' ? value.provider : undefined,\n kind: typeof value.kind === 'string' ? value.kind : undefined,\n title: typeof value.title === 'string' && value.title ? value.title : 'Permission request',\n body: typeof value.body === 'string' ? value.body : '',\n choices,\n ...(questions && questions.length ? { questions } : {}),\n }\n}\n\nexport function providerBlockedState(agent: Agent | null | undefined): ProviderRuntimeState | null {\n const state = providerRuntimeState(agent)\n return state?.state === 'blocked' ? state : null\n}\n\nexport function isPendingApprovalBadgeWorthy(approval: ProviderPendingApproval | null | undefined): boolean { return Boolean(approval?.id) }\n\nexport function agentNeedsHumanResponse(agent: Agent | null | undefined): boolean { return isPendingApprovalBadgeWorthy(providerBlockedState(agent)?.pendingApproval) }\n\nexport function activeSubagents(agent: Agent | null | undefined): ActiveSubagentInfo[] {\n const raw = agent?.meta?.activeSubagents\n if (!Array.isArray(raw)) {\n const count = typeof agent?.meta?.activeSubagentCount === 'number' ? agent.meta.activeSubagentCount : 0\n return count > 0 ? Array.from({ length: count }, (_, index) => ({ id: `subagent-${index + 1}`, label: `Subagent ${index + 1}` })) : []\n }\n return raw\n .filter(isRecord)\n .map((item, index) => {\n const id = typeof item.id === 'string' && item.id ? item.id : `subagent-${index + 1}`\n const role = typeof item.role === 'string' && item.role ? item.role : undefined\n const label = typeof item.label === 'string' && item.label ? item.label : role || id\n const startedAt = typeof item.startedAt === 'number' ? item.startedAt : undefined\n return { id, label, role, startedAt }\n })\n}\n\n// #502: a background shell (run_in_background) still running for this agent keeps\n// it busy even though its turn ended. The runner surfaces it as a `background-script`\n// work item in meta.activeWork carrying a human label (\"N background tasks\"); return\n// that label for a badge, or null when none are running.\nexport function activeBackgroundScriptLabel(agent: Agent | null | undefined): string | null {\n const raw = agent?.meta?.activeWork\n if (!Array.isArray(raw)) return null\n const item = raw.find((work) => isRecord(work) && work.kind === 'background-script')\n if (!item || !isRecord(item)) return null\n return typeof item.label === 'string' && item.label ? item.label : 'background script'\n}\n\nexport function agentPresence(\n now: number, agent: Agent | null | undefined, attention: AttentionInfo, pair: PairSession | null\n): PresenceInfo {\n const lifecycleAction = typeof agent?.meta?.lifecycleAction === 'string' ? agent.meta.lifecycleAction : ''\n const stale = isAgentStale(now, agent)\n const reconnecting = agent?.status !== 'offline' && !agent?.ready && stale\n const starting = agent?.status !== 'offline' && !agent?.ready && !stale\n const unreadIdle = attention.unread > 0 && agent?.status !== 'busy'\n const blocked = providerBlockedState(agent)\n const badges = presenceBadges(agent, attention, pair)\n\n if (agent?.status === 'offline') return { label: 'offline', tone: 'secondary', icon: 'PlugZap', stale: false, reconnecting: false, badges }\n // Pre-session-destroy window (#183): the runner is running end-of-session work before a\n // compact/clear/restart/shutdown. Distinct from plain \"busy\" and non-addressable.\n if (lifecycleAction.startsWith('finalizing-')) {\n return { label: `wrapping up (${lifecycleAction.slice('finalizing-'.length)})`, tone: 'warning', icon: 'Hourglass', stale, reconnecting, badges }\n }\n if (lifecycleAction === 'shutting-down') return { label: 'shutting down', tone: 'warning', icon: 'Power', stale, reconnecting, badges }\n if (lifecycleAction === 'killing') return { label: 'killing', tone: 'danger', icon: 'Power', stale, reconnecting, badges }\n if (lifecycleAction === 'restarting') return { label: 'restarting', tone: 'warning', icon: 'RefreshCw', stale, reconnecting, badges }\n if (agent?.status === 'stale') return { label: 'stale', tone: 'danger', icon: 'RefreshCw', stale: true, reconnecting: true, badges }\n if (reconnecting) return { label: 'reconnecting', tone: 'danger', icon: 'RefreshCw', stale, reconnecting, badges }\n if (starting) return { label: 'online, not ready', tone: 'warning', icon: 'Loader', stale, reconnecting, badges }\n if (blocked) return { label: `blocked: ${blocked.label}`, tone: 'danger', icon: 'AlertCircle', stale, reconnecting, badges }\n if (agent?.status === 'busy') {\n // #502: busy purely because a background shell outlived the turn (no active\n // provider turn / subagent) reads more truthfully as background work.\n const reasons = Array.isArray(agent?.meta?.busyReasons) ? agent.meta.busyReasons : []\n const backgroundOnly = activeBackgroundScriptLabel(agent) && !reasons.includes('provider-turn') && !reasons.includes('subagent')\n if (backgroundOnly) return { label: 'running background work', tone: 'warning', icon: 'Play', stale, reconnecting, badges }\n return { label: 'busy in turn', tone: 'warning', icon: 'Play', stale, reconnecting, badges }\n }\n if (pair?.status === 'active') return { label: 'paired', tone: 'success', icon: 'Link', stale, reconnecting, badges }\n if (unreadIdle) return { label: 'idle, unread', tone: 'danger', icon: 'Bell', stale, reconnecting, badges }\n return { label: agent?.status === 'idle' ? 'idle' : 'ready', tone: 'success', icon: 'CircleCheck', stale, reconnecting, badges }\n}\n\nfunction presenceBadges(agent: Agent | null | undefined, attention: AttentionInfo, pair: PairSession | null): PresenceBadge[] {\n const badges: PresenceBadge[] = []\n const blocked = providerBlockedState(agent)\n const subagentCount = activeSubagents(agent).length\n if (blocked) badges.push({ label: blocked.reason ? `blocked: ${blocked.reason}` : 'blocked', className: 'bg-red-500/20 text-red-400' })\n if (subagentCount) badges.push({ label: subagentCount === 1 ? '1 subagent' : subagentCount + ' subagents', className: 'bg-cyan-500/20 text-cyan-300' })\n const backgroundScriptLabel = activeBackgroundScriptLabel(agent)\n if (backgroundScriptLabel) badges.push({ label: backgroundScriptLabel, className: 'bg-indigo-500/20 text-indigo-300' })\n if (pair?.status === 'active') badges.push({ label: 'paired', className: 'bg-emerald-500/20 text-emerald-400' })\n if (pair?.status === 'pending') badges.push({ label: 'pair invite', className: 'bg-yellow-500/20 text-yellow-400' })\n if (attention.needsHumanResponse) badges.push({ label: 'needs response', className: 'bg-amber-500/20 text-amber-300' })\n if (attention.unread) badges.push({ label: attention.unread + ' unread', className: 'bg-red-500/20 text-red-400' })\n if (attention.claimableTasks) badges.push({ label: attention.claimableTasks + ' claimable', className: 'bg-orange-500/20 text-orange-400' })\n return badges\n}\n\nexport function emptyAttention(): AttentionInfo {\n return { unread: 0, needsHumanResponse: false, pendingPairInvite: false, claimableTasks: 0, total: 0, score: 0 }\n}\n\n\nexport function channelIsReady(channel: ChannelSummary | null): boolean {\n if (!channel || channel.status === 'offline') return false\n if (channel.targetHealth?.status === 'ok') return true\n if (channel.targetHealth?.status === 'error' || channel.targetHealth?.status === 'warning') return false\n return channel.ready\n}\n\nexport function channelPresence(channel: ChannelSummary | null): PresenceInfo {\n if (!channel) return { label: 'unknown', tone: 'secondary', icon: 'PlugZap', badges: [] }\n if (channel.targetHealth?.status === 'error') return { label: 'target broken', tone: 'danger', icon: 'AlertTriangle', badges: [] }\n if (channel.targetHealth?.status === 'warning') return { label: 'target warning', tone: 'warning', icon: 'AlertCircle', badges: [] }\n if (channel.status === 'offline') return { label: 'offline', tone: 'secondary', icon: 'PlugZap', badges: [] }\n if (channel.status === 'busy') return { label: 'busy', tone: 'warning', icon: 'Activity', badges: [] }\n if (!channelIsReady(channel)) return { label: 'not ready', tone: 'warning', icon: 'Loader', badges: [] }\n return { label: 'ready', tone: 'success', icon: 'CircleCheck', badges: [] }\n}\n\nexport function connectorPresence(connector: ConnectorSummary): PresenceInfo {\n const runtime = connector?.runtime || {}\n if (runtime.status === 'error') return { label: 'error', tone: 'danger', icon: 'AlertTriangle', badges: [] }\n if (runtime.status === 'warn') return { label: 'warning', tone: 'warning', icon: 'AlertCircle', badges: [] }\n if (runtime.running) return { label: 'running', tone: 'success', icon: 'CircleCheck', badges: [] }\n if (runtime.enabled === false) return { label: 'disabled', tone: 'secondary', icon: 'Pause', badges: [] }\n if (runtime.status === 'ok') return { label: 'ok', tone: 'success', icon: 'CircleCheck', badges: [] }\n return { label: 'unknown', tone: 'secondary', icon: 'HelpCircle', badges: [] }\n}\n\nexport function integrationPresence(integration: IntegrationSummary): PresenceInfo {\n const stats = integration?.taskStats || {}\n if ((stats.waitingTasks || 0) > 0) return { label: 'waiting', tone: 'warning', icon: 'AlertCircle', badges: [] }\n if ((stats.openTasks || 0) > 0) return { label: 'active', tone: 'info', icon: 'Activity', badges: [] }\n if (!integration?.configured) return { label: 'observed only', tone: 'secondary', icon: 'Eye', badges: [] }\n if (integration.observed) return { label: 'quiet', tone: 'success', icon: 'CircleCheck', badges: [] }\n return { label: 'configured', tone: 'primary', icon: 'PlugZap', badges: [] }\n}\n\nexport function orchestratorHealthLabel(orch: Orchestrator): string {\n const health = orch?.health\n if (!health || health.status === 'ok') return orch?.version || orch?.package?.version ? 'compatible' : 'unknown version'\n if (health.status === 'upgrade-required') return 'upgrade required'\n if (health.status === 'restart-required') return 'restart required'\n if (health.status === 'unknown') return 'unknown contract'\n if ((health.issues || []).some((i) => i.code === 'protocol-mismatch')) return 'protocol mismatch'\n if ((health.issues || []).some((i) => i.code === 'package-drift')) return 'package drift'\n if (health.restartRequired) return 'restart required'\n return health.status\n}\n\nexport function orchestratorHealthClass(orch: Orchestrator): string {\n const status = orch?.health?.status\n if (status === 'upgrade-required') return 'text-red-400 bg-red-500/10'\n if (status === 'warn' || status === 'restart-required') return 'text-yellow-400 bg-yellow-500/10'\n if (status === 'unknown') return 'text-zinc-400 bg-zinc-500/10'\n return 'text-emerald-400 bg-emerald-500/10'\n}\n\nexport function timeAgo(now: number, iso: string | number | null | undefined): string {\n if (!iso) return ''\n const ts = new Date(iso as string).getTime()\n if (!Number.isFinite(ts)) return ''\n const diff = Math.max(0, (now - ts) / 1000)\n if (diff < 60) return Math.floor(diff) + 's ago'\n if (diff < 3600) return Math.floor(diff / 60) + 'm ago'\n if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'\n return Math.floor(diff / 86400) + 'd ago'\n}\n\nexport function fmtTime(iso: string | number | null | undefined): string {\n if (!iso) return ''\n return new Date(iso as string).toLocaleString()\n}\n\nexport function safeFilename(value: string): string {\n return String(value || 'export').replace(/[^a-z0-9._-]+/gi, '-').replace(/^-+|-+$/g, '').slice(0, 80) || 'export'\n}\n\nexport function healthDiagnostics(checks: HealthCheck[]): HealthDiagnostic[] {\n return checks.filter((c) => c.status !== 'ok').map((check) => {\n const base: HealthDiagnostic = {\n name: check.name,\n status: check.status,\n detail: check.detail || check.name,\n impact: healthImpact(check),\n subjects: check.subjects || [],\n actions: [\n { label: 'Inspect logs', icon: 'FileSearch', copy: 'agent-relay daemon logs' },\n { label: 'Restart daemon', icon: 'RefreshCw', copy: 'agent-relay daemon restart' },\n { label: 'Copy env', icon: 'Copy', copy: 'agent-relay doctor' },\n ],\n }\n if (check.name === 'stale-live-agents') {\n base.actions.unshift(\n { label: 'Run reaper', icon: 'Eraser', api: 'POST', path: '/system/reap' },\n { label: 'Show stale', icon: 'Filter', view: 'agents', preset: 'offline_stale' },\n )\n } else if (check.name === 'channel-delivery-targets') {\n base.actions.unshift(\n { label: 'Open channels', icon: 'MessagesSquare', view: 'channels' },\n { label: 'Run reaper', icon: 'Eraser', api: 'POST', path: '/system/reap' },\n )\n } else if (['expired-message-claims', 'expired-task-claims', 'offline-claimed-tasks'].includes(check.name)) {\n base.actions.unshift(\n { label: 'Run reaper', icon: 'Eraser', api: 'POST', path: '/system/reap' },\n { label: 'Open work', icon: 'ListChecks', view: 'work' },\n )\n }\n return base\n })\n}\n\nfunction healthImpact(check: HealthCheck): string {\n const impacts: Record<string, string> = {\n 'database': 'Relay persistence is unavailable; messages, state, and audit writes may fail.',\n 'stale-live-agents': 'Agents may look online even though their heartbeat has stopped.',\n 'expired-message-claims': 'Claimable messages may be stuck until the reaper releases expired claims.',\n 'expired-task-claims': 'Tasks can appear owned by agents that no longer hold a live lease.',\n 'offline-claimed-tasks': 'Offline agents are still shown as owners for active work.',\n 'channel-delivery-targets': 'Inbound channel messages may be accepted but routed to no live delivery agent.',\n }\n return impacts[check.name] || 'Relay health is degraded for this check.'\n}\n\nfunction toneToBadgeVariant(tone: string): 'default' | 'secondary' | 'destructive' | 'outline' {\n if (tone === 'danger' || tone === 'destructive') return 'destructive'\n if (tone === 'secondary') return 'secondary'\n return 'default'\n}\n\nexport function toneToColor(tone: string): string {\n const map: Record<string, string> = {\n success: 'text-emerald-400',\n warning: 'text-yellow-400',\n danger: 'text-red-400',\n info: 'text-blue-400',\n primary: 'text-blue-400',\n secondary: 'text-zinc-400',\n }\n return map[tone] || 'text-zinc-400'\n}\n\nfunction toneToBg(tone: string): string {\n const map: Record<string, string> = {\n success: 'bg-emerald-500',\n warning: 'bg-yellow-500',\n danger: 'bg-red-500',\n info: 'bg-blue-500',\n primary: 'bg-blue-500',\n secondary: 'bg-zinc-500',\n }\n return map[tone] || 'bg-zinc-500'\n}\n\nexport function statusDotColor(agent: Agent | null, now?: number): string {\n if (!agent) return 'bg-zinc-500 opacity-50'\n if (agent.status === 'offline') return 'bg-zinc-500 opacity-50'\n if (typeof agent.meta?.lifecycleAction === 'string' && agent.meta.lifecycleAction.startsWith('finalizing-')) return 'bg-amber-500 animate-pulse'\n if (agent.meta?.lifecycleAction === 'shutting-down' || agent.meta?.lifecycleAction === 'restarting') return 'bg-yellow-500 animate-pulse'\n if (agent.meta?.lifecycleAction === 'killing') return 'bg-red-500 animate-pulse'\n if (providerBlockedState(agent)) return 'bg-red-500 animate-pulse'\n if (agent.status === 'busy') return 'bg-yellow-500'\n if (now && isAgentStale(now, agent)) return 'bg-red-500 animate-pulse'\n if (!agent.ready) return 'bg-emerald-500 animate-pulse'\n return 'bg-emerald-500 shadow-[0_0_6px_theme(colors.emerald.500)]'\n}\n\nexport function downloadText(filename: string, text: string, type: string) {\n if (typeof document === 'undefined' || typeof URL === 'undefined' || typeof Blob === 'undefined') return\n const url = URL.createObjectURL(new Blob([text], { type }))\n const link = document.createElement('a')\n link.href = url\n link.download = filename\n link.click()\n URL.revokeObjectURL(url)\n}\n"],"mappings":"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"}
|