agent-relay-server 0.106.0 → 0.108.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/docs/openapi.json +503 -1
  2. package/package.json +6 -5
  3. package/public/assets/{MessageBubble-CPYQQLDI.js → MessageBubble-DYJezSjU.js} +2 -2
  4. package/public/assets/{MessageBubble-CPYQQLDI.js.map → MessageBubble-DYJezSjU.js.map} +1 -1
  5. package/public/assets/{PlanGraphPanel-B7xZt5DI.js → PlanGraphPanel-MWcVw38W.js} +2 -2
  6. package/public/assets/{PlanGraphPanel-B7xZt5DI.js.map → PlanGraphPanel-MWcVw38W.js.map} +1 -1
  7. package/public/assets/{activity-UKQrQ8_0.js → activity-Db3X3dg8.js} +2 -2
  8. package/public/assets/{activity-UKQrQ8_0.js.map → activity-Db3X3dg8.js.map} +1 -1
  9. package/public/assets/{agent-profiles-Zr_5nNEJ.js → agent-profiles-Di2w5U1o.js} +2 -2
  10. package/public/assets/{agent-profiles-Zr_5nNEJ.js.map → agent-profiles-Di2w5U1o.js.map} +1 -1
  11. package/public/assets/{agents-FdrfmJsj.js → agents-GSJXvmU1.js} +2 -2
  12. package/public/assets/{agents-FdrfmJsj.js.map → agents-GSJXvmU1.js.map} +1 -1
  13. package/public/assets/{analytics-VximqXQ9.js → analytics-B9USQLl5.js} +18 -18
  14. package/public/assets/{analytics-VximqXQ9.js.map → analytics-B9USQLl5.js.map} +1 -1
  15. package/public/assets/{automation-DlEcJvgu.js → automation-XRePs1dT.js} +2 -2
  16. package/public/assets/{automation-DlEcJvgu.js.map → automation-XRePs1dT.js.map} +1 -1
  17. package/public/assets/{branch-state-badge-CcLZ_u86.js → branch-state-badge-C_VNyER3.js} +2 -2
  18. package/public/assets/{branch-state-badge-CcLZ_u86.js.map → branch-state-badge-C_VNyER3.js.map} +1 -1
  19. package/public/assets/{channels-CcSnLzCG.js → channels-DACvNoC2.js} +2 -2
  20. package/public/assets/{channels-CcSnLzCG.js.map → channels-DACvNoC2.js.map} +1 -1
  21. package/public/assets/{chat-D1bunuI-.js → chat-D_0uHgB6.js} +2 -2
  22. package/public/assets/{chat-D1bunuI-.js.map → chat-D_0uHgB6.js.map} +1 -1
  23. package/public/assets/{connectors-DbPOlQhL.js → connectors-C0B38LUK.js} +2 -2
  24. package/public/assets/{connectors-DbPOlQhL.js.map → connectors-C0B38LUK.js.map} +1 -1
  25. package/public/assets/display-1J__Ti1K.js +3 -0
  26. package/public/assets/display-1J__Ti1K.js.map +1 -0
  27. package/public/assets/{formatted-body-impl-4v9SkUD1.js → formatted-body-impl-BKWCAjBd.js} +2 -2
  28. package/public/assets/{formatted-body-impl-4v9SkUD1.js.map → formatted-body-impl-BKWCAjBd.js.map} +1 -1
  29. package/public/assets/index-DlBSuzd2.js +23 -0
  30. package/public/assets/index-DlBSuzd2.js.map +1 -0
  31. package/public/assets/{insights-CntgHcTp.js → insights-BzNoYvDe.js} +2 -2
  32. package/public/assets/{insights-CntgHcTp.js.map → insights-BzNoYvDe.js.map} +1 -1
  33. package/public/assets/{integrations-hkSW8CzM.js → integrations-DLzJpt9G.js} +2 -2
  34. package/public/assets/{integrations-hkSW8CzM.js.map → integrations-DLzJpt9G.js.map} +1 -1
  35. package/public/assets/{maintenance-Dw7frQtu.js → maintenance-B6_0-ECm.js} +2 -2
  36. package/public/assets/{maintenance-Dw7frQtu.js.map → maintenance-B6_0-ECm.js.map} +1 -1
  37. package/public/assets/{managed-agents-Bj--bFmZ.js → managed-agents-CpzKXDtV.js} +2 -2
  38. package/public/assets/{managed-agents-Bj--bFmZ.js.map → managed-agents-CpzKXDtV.js.map} +1 -1
  39. package/public/assets/{markdown-preview-impl-DGLCocU8.js → markdown-preview-impl-DxBnlJ2k.js} +2 -2
  40. package/public/assets/{markdown-preview-impl-DGLCocU8.js.map → markdown-preview-impl-DxBnlJ2k.js.map} +1 -1
  41. package/public/assets/{memory-yj2MIxUA.js → memory-Bnm7ILWL.js} +2 -2
  42. package/public/assets/{memory-yj2MIxUA.js.map → memory-Bnm7ILWL.js.map} +1 -1
  43. package/public/assets/{messages-BT5mL7iz.js → messages-D9LkocTc.js} +2 -2
  44. package/public/assets/{messages-BT5mL7iz.js.map → messages-D9LkocTc.js.map} +1 -1
  45. package/public/assets/{orchestrators-BVDAhrPk.js → orchestrators-C__JfrNr.js} +2 -2
  46. package/public/assets/{orchestrators-BVDAhrPk.js.map → orchestrators-C__JfrNr.js.map} +1 -1
  47. package/public/assets/{overview-DlqJIqYK.js → overview-BQ0b0V7Q.js} +2 -2
  48. package/public/assets/{overview-DlqJIqYK.js.map → overview-BQ0b0V7Q.js.map} +1 -1
  49. package/public/assets/{pairs-DQ4tNCo3.js → pairs-ChyeBs0Z.js} +2 -2
  50. package/public/assets/{pairs-DQ4tNCo3.js.map → pairs-ChyeBs0Z.js.map} +1 -1
  51. package/public/assets/{projects-vt63bwHN.js → projects-v5Kp1Oaq.js} +2 -2
  52. package/public/assets/{projects-vt63bwHN.js.map → projects-v5Kp1Oaq.js.map} +1 -1
  53. package/public/assets/{prompt-settings-DlsqOP0C.js → prompt-settings-YhAOlYc6.js} +2 -2
  54. package/public/assets/{prompt-settings-DlsqOP0C.js.map → prompt-settings-YhAOlYc6.js.map} +1 -1
  55. package/public/assets/{registry-Cw2fdWrn.js → registry-DZoAfrzx.js} +2 -2
  56. package/public/assets/{registry-Cw2fdWrn.js.map → registry-DZoAfrzx.js.map} +1 -1
  57. package/public/assets/{security-ucr7IT1V.js → security-DRCj00eA.js} +2 -2
  58. package/public/assets/{security-ucr7IT1V.js.map → security-DRCj00eA.js.map} +1 -1
  59. package/public/assets/{select-D8UWCtYZ.js → select-ZOpLw2Fd.js} +2 -2
  60. package/public/assets/{select-D8UWCtYZ.js.map → select-ZOpLw2Fd.js.map} +1 -1
  61. package/public/assets/settings-DGgwr9WH.js +6 -0
  62. package/public/assets/settings-DGgwr9WH.js.map +1 -0
  63. package/public/assets/{store-DUfrGvOE.js → store-Bnag1kY-.js} +2 -2
  64. package/public/assets/{store-DUfrGvOE.js.map → store-Bnag1kY-.js.map} +1 -1
  65. package/public/assets/{tasks-jRUvxUd5.js → tasks-DUTu8ZhA.js} +2 -2
  66. package/public/assets/{tasks-jRUvxUd5.js.map → tasks-DUTu8ZhA.js.map} +1 -1
  67. package/public/assets/{teams-Dn0yggD-.js → teams-BJpaoG2m.js} +2 -2
  68. package/public/assets/{teams-Dn0yggD-.js.map → teams-BJpaoG2m.js.map} +1 -1
  69. package/public/assets/{terminal-viewer-impl-fLly20IP.js → terminal-viewer-impl-CQhh6oXa.js} +2 -2
  70. package/public/assets/{terminal-viewer-impl-fLly20IP.js.map → terminal-viewer-impl-CQhh6oXa.js.map} +1 -1
  71. package/public/assets/types-BiJFuTiL.js +2 -0
  72. package/public/assets/types-BiJFuTiL.js.map +1 -0
  73. package/public/assets/{user-avatar-BWLUHt2e.js → user-avatar-BYGrUx1U.js} +2 -2
  74. package/public/assets/{user-avatar-BWLUHt2e.js.map → user-avatar-BYGrUx1U.js.map} +1 -1
  75. package/public/assets/{work-queue-BAnNY0xE.js → work-queue-BH2-mARc.js} +2 -2
  76. package/public/assets/{work-queue-BAnNY0xE.js.map → work-queue-BH2-mARc.js.map} +1 -1
  77. package/public/assets/{workspaces-CIsQqQby.js → workspaces-DldYyXZR.js} +2 -2
  78. package/public/assets/{workspaces-CIsQqQby.js.map → workspaces-DldYyXZR.js.map} +1 -1
  79. package/public/index.html +4 -4
  80. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  81. package/runner/plugins/claude/monitors/relay-monitor.provisioned.mjs +6 -0
  82. package/src/branch-landed.ts +93 -4
  83. package/src/config-store.ts +12 -1
  84. package/src/db/agents.ts +3 -2
  85. package/src/db/continuations.ts +13 -3
  86. package/src/db/index.ts +3 -0
  87. package/src/db/issue-lifecycle.ts +12 -0
  88. package/src/db/mappers.ts +17 -0
  89. package/src/db/messages.ts +9 -4
  90. package/src/db/migrations.ts +91 -1
  91. package/src/db/notification-subscriptions.ts +1 -1
  92. package/src/db/prompt-templates.ts +19 -0
  93. package/src/db/provider-quotas.ts +6 -0
  94. package/src/db/schema.ts +67 -1
  95. package/src/db/stats.ts +3 -2
  96. package/src/db/task-comment-through.ts +112 -0
  97. package/src/db/task-core.ts +14 -1
  98. package/src/db/task-entity.ts +629 -0
  99. package/src/db/task-maintenance.ts +4 -3
  100. package/src/db/task-memory.ts +180 -0
  101. package/src/db/tasks.ts +24 -10
  102. package/src/issue-tracker/adapter.ts +15 -0
  103. package/src/issue-tracker/github.ts +40 -2
  104. package/src/issue-tracker/index.ts +1 -0
  105. package/src/mcp/index.ts +12 -1
  106. package/src/mcp/tools-spawn.ts +4 -0
  107. package/src/mcp-dispatch-tools.ts +164 -0
  108. package/src/mcp-notification-subscription-tools.ts +62 -4
  109. package/src/mcp-plan-tools.ts +30 -3
  110. package/src/mcp-release-tools.ts +33 -0
  111. package/src/mcp-task-memory-tools.ts +132 -0
  112. package/src/mcp-task-tools.ts +232 -0
  113. package/src/mcp-task-weave-tools.ts +66 -0
  114. package/src/notification-router.ts +24 -2
  115. package/src/notification-rules.ts +4 -0
  116. package/src/notification-types.ts +25 -0
  117. package/src/prompt-resolver.ts +66 -3
  118. package/src/routes/agents-spawn.ts +10 -0
  119. package/src/routes/commands.ts +17 -1
  120. package/src/routes/index.ts +16 -2
  121. package/src/routes/plans.ts +10 -2
  122. package/src/routes/provider-quotas.ts +13 -1
  123. package/src/routes/tasks.ts +276 -2
  124. package/src/routing-policy-store.ts +147 -2
  125. package/src/security.ts +2 -0
  126. package/src/self-resume.ts +13 -2
  127. package/src/services/issue-lifecycle.ts +9 -1
  128. package/src/services/plan-graph.ts +107 -9
  129. package/src/services/release-readiness.ts +142 -0
  130. package/src/services/send-message.ts +31 -0
  131. package/src/services/spawn-agent.ts +81 -4
  132. package/src/services/task-context-hydration.ts +361 -0
  133. package/src/services/task-entity.ts +149 -0
  134. package/src/services/task-issue-weave.ts +558 -0
  135. package/src/services/task-lifecycle.ts +465 -0
  136. package/src/services/task-plan-view.ts +178 -0
  137. package/src/services/task-semantic-events.ts +137 -0
  138. package/src/services/transient-agent-reaper.ts +71 -23
  139. package/src/spawn-targets.ts +27 -2
  140. package/src/task-dispatcher.ts +461 -0
  141. package/src/upgrade-install-verify.ts +20 -1
  142. package/public/assets/display-BWi8DQCp.js +0 -3
  143. package/public/assets/display-BWi8DQCp.js.map +0 -1
  144. package/public/assets/index-Vmhw442d.js +0 -23
  145. package/public/assets/index-Vmhw442d.js.map +0 -1
  146. package/public/assets/settings-Dvf_ps1B.js +0 -6
  147. package/public/assets/settings-Dvf_ps1B.js.map +0 -1
  148. package/public/assets/types-DorDDvEb.js +0 -2
  149. package/public/assets/types-DorDDvEb.js.map +0 -1
@@ -0,0 +1,461 @@
1
+ // #839 — the declarative task dispatcher.
2
+ //
3
+ // A pure function from (Task work-shape + live system state) → a concrete spawn
4
+ // (provider / model / effort) plus a human-readable rationale. The work-shape is the
5
+ // small, stable taxonomy a coordinator declares on the Task's `dispatch` field
6
+ // ({complexity, type, contextFootprint, criticality}); the routing heuristics that map
7
+ // it onto an agent live as INSPECTABLE, EDITABLE policy (RoutingPolicy.dispatch, with a
8
+ // built-in default) — never hardcoded here, never tribal coordinator knowledge.
9
+ //
10
+ // Guarantees this module is built to keep (the #839 GUARD):
11
+ // 1. Every decision reports WHY (rationale string + structured factors).
12
+ // 2. Every decision is overridable by an explicit model/provider pin.
13
+ // 3. The function is PURE — no DB writes, no spawning. The MCP entrypoint
14
+ // (mcp-dispatch-tools.ts) consumes the decision; #838 (spawn-with-task) will
15
+ // hydrate + actually spawn. Auto-review surfaces as a PLAN the caller may enact.
16
+ import type {
17
+ DispatchAutoReview,
18
+ DispatchMatch,
19
+ DispatchModelTarget,
20
+ DispatchPolicy,
21
+ DispatchRule,
22
+ SpawnProvider,
23
+ TaskDispatch,
24
+ } from "agent-relay-sdk";
25
+ import type { ProviderCatalogEntry, ProviderModelCatalogEntry } from "agent-relay-sdk/provider-catalog";
26
+ import { effectiveProviderCatalogList } from "./provider-catalog-store";
27
+ import { getDispatchPolicy } from "./routing-policy-store";
28
+ import { providerQuotaBands, type ProviderQuotaBand, type QuotaBand } from "./spawn-targets";
29
+
30
+ /** An explicit per-case override. Any field set wins over the policy-derived target. */
31
+ export interface DispatchPin {
32
+ provider?: SpawnProvider;
33
+ model?: string;
34
+ effort?: string;
35
+ }
36
+
37
+ /**
38
+ * Live-state inputs. All optional + injectable so the dispatcher is unit-testable without a
39
+ * running fleet; each defaults to the real source (policy store / quota bands / catalog).
40
+ */
41
+ export interface DispatchContext {
42
+ policy?: DispatchPolicy;
43
+ bands?: ProviderQuotaBand[];
44
+ catalog?: ProviderCatalogEntry[];
45
+ }
46
+
47
+ export interface DispatchTarget {
48
+ provider: SpawnProvider;
49
+ model?: string;
50
+ effort?: string;
51
+ }
52
+
53
+ /** Structured trace of the inputs that produced the decision (the machine-readable rationale). */
54
+ export interface DispatchFactors {
55
+ complexity?: string;
56
+ type?: string;
57
+ contextFootprint?: string;
58
+ criticality?: string;
59
+ /** Label of the matched rule, or a "fallback"/"pin" marker. */
60
+ matchedRule: string;
61
+ /** The final provider's live quota band. */
62
+ band: QuotaBand;
63
+ /** Context window of the chosen model, when known. */
64
+ contextWindowTokens?: number;
65
+ /** Model was escalated to a bigger-window family member for a large footprint. */
66
+ escalatedForFootprint?: boolean;
67
+ /** Provider was rerouted away from a scarce/blocked pool; original provider recorded here. */
68
+ reroutedFromProvider?: SpawnProvider;
69
+ /** Which target fields were set by an explicit pin. */
70
+ pinnedFields?: string[];
71
+ }
72
+
73
+ /** A dependent review Task the caller may create (high criticality / complex work). */
74
+ export interface ReviewPlan {
75
+ /** Work-shape for the review Task. */
76
+ dispatch: TaskDispatch;
77
+ target: DispatchTarget;
78
+ rationale: string;
79
+ /** Why the review was triggered (e.g. "criticality=high"). */
80
+ reason: string;
81
+ }
82
+
83
+ export interface DispatchDecision {
84
+ target: DispatchTarget;
85
+ /** True when any target field came from an explicit pin. */
86
+ pinned: boolean;
87
+ /** Human-readable explanation, e.g. `opus-4.8 high: type=build, complexity=complex; claude band=normal`. */
88
+ rationale: string;
89
+ factors: DispatchFactors;
90
+ /** Non-fatal advisories (e.g. scarce pool, no model meets the footprint). */
91
+ warnings: string[];
92
+ /** Present when the policy's auto-review threshold is met. */
93
+ review?: ReviewPlan;
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Public entrypoint
98
+ // ---------------------------------------------------------------------------
99
+
100
+ /**
101
+ * Map a work-shape + live state onto a concrete spawn. `pin` overrides any field per-case;
102
+ * the rationale always explains the choice (including why a pin or reroute happened).
103
+ */
104
+ export function dispatchTask(shape: TaskDispatch | undefined, pin: DispatchPin = {}, ctx: DispatchContext = {}): DispatchDecision {
105
+ const policy = ctx.policy ?? getDispatchPolicy();
106
+ const catalog = ctx.catalog ?? effectiveProviderCatalogList();
107
+ const bands = ctx.bands ?? providerQuotaBands();
108
+ const work: TaskDispatch = shape ?? {};
109
+ const warnings: string[] = [];
110
+
111
+ const matched = resolvePolicyTarget(work, policy);
112
+ let target: DispatchTarget = { ...matched.target };
113
+ const factors: DispatchFactors = {
114
+ ...(work.complexity ? { complexity: work.complexity } : {}),
115
+ ...(work.type ? { type: work.type } : {}),
116
+ ...(work.contextFootprint ? { contextFootprint: work.contextFootprint } : {}),
117
+ ...(work.criticality ? { criticality: work.criticality } : {}),
118
+ matchedRule: matched.label,
119
+ band: "normal",
120
+ };
121
+
122
+ const pinnedFields = applyPin(target, pin);
123
+ const providerPinned = pinnedFields.includes("provider");
124
+ const modelPinned = pinnedFields.includes("model");
125
+ const effortPinned = pinnedFields.includes("effort");
126
+ if (pinnedFields.length) factors.pinnedFields = pinnedFields;
127
+
128
+ // Footprint escalation (skip when the model is explicitly pinned — respect the human choice).
129
+ if (!modelPinned) escalateForFootprint(target, work, policy, catalog, factors, warnings);
130
+
131
+ // Band-aware rerouting (skip when the provider is explicitly pinned). Protects scarce pools
132
+ // and load-shifts large/blocked work onto an abundant one. Re-validates footprint + a pinned
133
+ // effort on the rerouted provider so neither is silently dropped.
134
+ if (!providerPinned) rerouteForBand(target, work, policy, bands, catalog, factors, warnings, { effortPinned });
135
+
136
+ // Record the FINAL provider's band + the chosen model's window for transparency.
137
+ factors.band = bandFor(target.provider, bands);
138
+ const finalWindow = contextWindowOf(catalog, target.provider, target.model);
139
+ if (finalWindow !== undefined) factors.contextWindowTokens = finalWindow;
140
+ collectBandWarnings(target.provider, work, bands, providerPinned, warnings);
141
+
142
+ const review = planAutoReview(work, policy, bands, catalog);
143
+
144
+ return {
145
+ target,
146
+ pinned: pinnedFields.length > 0,
147
+ rationale: buildRationale(target, factors, warnings),
148
+ factors,
149
+ warnings,
150
+ ...(review ? { review } : {}),
151
+ };
152
+ }
153
+
154
+ // ---------------------------------------------------------------------------
155
+ // Policy rule matching
156
+ // ---------------------------------------------------------------------------
157
+
158
+ function resolvePolicyTarget(work: TaskDispatch, policy: DispatchPolicy): { target: DispatchModelTarget; label: string } {
159
+ for (const rule of policy.rules) {
160
+ if (rule.enabled === false) continue;
161
+ if (matchesShape(rule.match, work)) {
162
+ return { target: rule.target, label: rule.label ?? "rule" };
163
+ }
164
+ }
165
+ return { target: policy.fallback, label: "fallback (no rule matched)" };
166
+ }
167
+
168
+ function matchesShape(match: DispatchMatch, work: TaskDispatch): boolean {
169
+ return (
170
+ dimensionMatches(match.complexity, work.complexity) &&
171
+ dimensionMatches(match.type, work.type) &&
172
+ dimensionMatches(match.contextFootprint, work.contextFootprint) &&
173
+ dimensionMatches(match.criticality, work.criticality)
174
+ );
175
+ }
176
+
177
+ // An unset criterion is a wildcard. A set criterion needs a present work value that is one of
178
+ // the (possibly array) allowed values.
179
+ function dimensionMatches(criterion: string | string[] | undefined, value: string | undefined): boolean {
180
+ if (criterion === undefined) return true;
181
+ if (value === undefined) return false;
182
+ const allowed = Array.isArray(criterion) ? criterion : [criterion];
183
+ return allowed.includes(value);
184
+ }
185
+
186
+ // ---------------------------------------------------------------------------
187
+ // Pin application
188
+ // ---------------------------------------------------------------------------
189
+
190
+ function applyPin(target: DispatchTarget, pin: DispatchPin): string[] {
191
+ const fields: string[] = [];
192
+ if (pin.provider) {
193
+ // A provider change invalidates a provider-specific policy model/effort unless the pin
194
+ // also restates them; clear them so we fall back to that provider's defaults.
195
+ if (pin.provider !== target.provider) {
196
+ if (!pin.model) target.model = undefined;
197
+ if (!pin.effort) target.effort = undefined;
198
+ }
199
+ target.provider = pin.provider;
200
+ fields.push("provider");
201
+ }
202
+ if (pin.model) {
203
+ target.model = pin.model;
204
+ fields.push("model");
205
+ }
206
+ if (pin.effort) {
207
+ target.effort = pin.effort;
208
+ fields.push("effort");
209
+ }
210
+ return fields;
211
+ }
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // Footprint → context-window escalation
215
+ // ---------------------------------------------------------------------------
216
+
217
+ function escalateForFootprint(
218
+ target: DispatchTarget,
219
+ work: TaskDispatch,
220
+ policy: DispatchPolicy,
221
+ catalog: ProviderCatalogEntry[],
222
+ factors: DispatchFactors,
223
+ warnings: string[],
224
+ ): void {
225
+ const footprint = work.contextFootprint;
226
+ if (!footprint) return;
227
+ const minTokens = policy.footprintMinContextTokens?.[footprint];
228
+ if (!minTokens) return;
229
+ const entry = catalogEntry(catalog, target.provider);
230
+ if (!entry) return;
231
+ const alias = target.model ?? entry.defaultModel;
232
+ const current = alias ? contextWindowFromEntry(entry, alias) : undefined;
233
+ if (current !== undefined && current >= minTokens) return; // already big enough
234
+
235
+ // Prefer widening the SAME model family (opus-4.8 → opus-4.8[1m]); fall back to any other
236
+ // sufficient model on this provider before giving up (e.g. after a reroute reset us to a small
237
+ // default). Smallest sufficient window wins so we don't over-allocate.
238
+ const pick = biggestSufficient(entry, minTokens, alias);
239
+ if (pick && pick.alias !== alias) {
240
+ target.model = pick.alias;
241
+ factors.escalatedForFootprint = true;
242
+ return;
243
+ }
244
+ if (!pick && current === undefined) return; // unknown window AND no sufficient alternative — can't assert a gap
245
+ // Nothing on this provider satisfies the footprint — surface it rather than silently downgrade.
246
+ warnings.push(
247
+ `contextFootprint=${footprint} wants ≥${minTokens} tokens but ${target.provider}/${alias ?? "default"} offers ${current ?? "unknown"}; no model on ${target.provider} meets it — review the pick.`,
248
+ );
249
+ }
250
+
251
+ // The smallest-window model on this provider whose window meets the requirement. Prefers the
252
+ // same model family as `alias` (e.g. opus-4.8 → opus-4.8[1m]) when one qualifies, else any family.
253
+ function biggestSufficient(
254
+ entry: ProviderCatalogEntry,
255
+ minTokens: number,
256
+ alias: string | undefined,
257
+ ): ProviderModelCatalogEntry | undefined {
258
+ const sufficient = entry.models
259
+ .filter((m) => !m.disabled && !m.unavailable)
260
+ .map((m) => ({ model: m, window: m.limits?.contextWindowTokens?.value }))
261
+ .filter((c): c is { model: ProviderModelCatalogEntry; window: number } => typeof c.window === "number" && c.window >= minTokens)
262
+ .sort((a, b) => a.window - b.window);
263
+ const base = alias ? familyBase(alias) : undefined;
264
+ const sameFamily = base ? sufficient.find((c) => familyBase(c.model.alias) === base) : undefined;
265
+ return (sameFamily ?? sufficient[0])?.model;
266
+ }
267
+
268
+ function familyBase(alias: string): string {
269
+ return alias.trim().toLowerCase().replace(/\[[^\]]+\]$/, "");
270
+ }
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // Quota-band aware rerouting
274
+ // ---------------------------------------------------------------------------
275
+
276
+ function rerouteForBand(
277
+ target: DispatchTarget,
278
+ work: TaskDispatch,
279
+ policy: DispatchPolicy,
280
+ bands: ProviderQuotaBand[],
281
+ catalog: ProviderCatalogEntry[],
282
+ factors: DispatchFactors,
283
+ warnings: string[],
284
+ opts: { effortPinned: boolean },
285
+ ): void {
286
+ const band = bandFor(target.provider, bands);
287
+ const blocked = isBlocked(target.provider, bands);
288
+ const isLarge = work.contextFootprint === "large";
289
+ // Reroute when the pool is at capacity / hard-avoid, or a large job lands on an avoid-large pool.
290
+ const mustMove = blocked || band === "hard-avoid" || (isLarge && band === "avoid-large");
291
+ if (!mustMove) return;
292
+
293
+ const alt = healthiestAlternative(target.provider, bands, catalog);
294
+ if (!alt) {
295
+ warnings.push(`${target.provider} band=${band}${blocked ? " (at capacity)" : ""} and no healthier provider is available — keeping it; expect contention.`);
296
+ return;
297
+ }
298
+ const altEntry = catalogEntry(catalog, alt.provider)!;
299
+ factors.reroutedFromProvider = target.provider;
300
+ warnings.push(`Rerouted ${target.provider} (band=${band}${blocked ? ", at capacity" : ""}) → ${alt.provider} (band=${alt.band}) to ${alt.band === "prefer" ? "use abundant quota" : "avoid the scarce pool"}.`);
301
+ const prevEffort = target.effort;
302
+ target.provider = alt.provider;
303
+ // Provider changed → model is provider-specific; reset to the new provider's default, then
304
+ // RE-VALIDATE the footprint on the new provider (it may widen to a 1M model, or warn if it
305
+ // cannot meet the footprint) so a large job is never silently downgraded onto a small window.
306
+ target.model = altEntry.defaultModel;
307
+ // Re-derive escalation against the NEW provider — the prior provider's escalation flag is stale.
308
+ delete factors.escalatedForFootprint;
309
+ escalateForFootprint(target, work, policy, catalog, factors, warnings);
310
+ // Effort: a pinned effort is authoritative and survives the reroute (warn if the rerouted model
311
+ // doesn't list it, rather than silently dropping it). An unpinned effort is kept only if supported.
312
+ if (opts.effortPinned) {
313
+ target.effort = prevEffort;
314
+ if (prevEffort && effortIfSupported(altEntry, target.model, prevEffort) === undefined) {
315
+ warnings.push(`Pinned effort '${prevEffort}' isn't listed for ${alt.provider}/${target.model ?? "default"}; honoring the pin anyway.`);
316
+ }
317
+ } else {
318
+ target.effort = effortIfSupported(altEntry, target.model, prevEffort);
319
+ }
320
+ }
321
+
322
+ // Best healthy provider other than `avoid`, considering EVERY spawnable provider in the live
323
+ // catalog — not only those with a quota advisory. A provider with no quota record defaults to the
324
+ // "normal" band (via bandFor), so a healthy catalog provider is never skipped just because it has
325
+ // no burn data yet. Prefer "prefer" band, then "normal"; never a blocked one.
326
+ function healthiestAlternative(
327
+ avoid: SpawnProvider,
328
+ bands: ProviderQuotaBand[],
329
+ catalog: ProviderCatalogEntry[],
330
+ ): { provider: SpawnProvider; band: QuotaBand } | undefined {
331
+ const ranked = catalog
332
+ .map((c) => c.provider)
333
+ .filter((provider) => provider !== avoid)
334
+ .map((provider) => ({ provider, band: bandFor(provider, bands), blockedNow: isBlocked(provider, bands) }))
335
+ .filter((c) => !c.blockedNow && (c.band === "prefer" || c.band === "normal"))
336
+ .sort((a, b) => bandRank(a.band) - bandRank(b.band));
337
+ return ranked[0];
338
+ }
339
+
340
+ function collectBandWarnings(provider: SpawnProvider, work: TaskDispatch, bands: ProviderQuotaBand[], providerPinned: boolean, warnings: string[]): void {
341
+ if (!providerPinned) return; // non-pinned scarce pools were already handled by reroute
342
+ const band = bandFor(provider, bands);
343
+ if (band === "hard-avoid" || isBlocked(provider, bands)) {
344
+ warnings.push(`Pinned provider ${provider} is band=${band}${isBlocked(provider, bands) ? " (at capacity)" : ""}; honoring the pin but it may be slow or blocked.`);
345
+ } else if (band === "avoid-large" && work.contextFootprint === "large") {
346
+ warnings.push(`Pinned provider ${provider} is band=avoid-large for a large job; honoring the pin.`);
347
+ }
348
+ }
349
+
350
+ // ---------------------------------------------------------------------------
351
+ // Auto-review planning
352
+ // ---------------------------------------------------------------------------
353
+
354
+ function planAutoReview(
355
+ work: TaskDispatch,
356
+ policy: DispatchPolicy,
357
+ bands: ProviderQuotaBand[],
358
+ catalog: ProviderCatalogEntry[],
359
+ ): ReviewPlan | undefined {
360
+ const cfg = policy.autoReview;
361
+ if (!cfg) return undefined;
362
+ const reason = autoReviewReason(work, cfg);
363
+ if (!reason) return undefined;
364
+ // The review Task's own work-shape: a review, inheriting the parent's criticality. Its target
365
+ // is resolved through the SAME policy (so review routing stays single-source) — either the
366
+ // explicit autoReview.target or whatever the review-type rules pick. No pin, no recursion into
367
+ // auto-review (a review of a review isn't wanted).
368
+ const reviewShape: TaskDispatch = {
369
+ type: "review",
370
+ ...(work.criticality ? { criticality: work.criticality } : {}),
371
+ ...(work.complexity ? { complexity: work.complexity } : {}),
372
+ };
373
+ const reviewPolicy: DispatchPolicy = cfg.target
374
+ ? { ...policy, rules: [{ label: "auto-review target", match: { type: "review" }, target: cfg.target }, ...policy.rules], autoReview: undefined }
375
+ : { ...policy, autoReview: undefined };
376
+ const decision = dispatchTask(reviewShape, {}, { policy: reviewPolicy, bands, catalog });
377
+ return {
378
+ dispatch: reviewShape,
379
+ target: decision.target,
380
+ rationale: decision.rationale,
381
+ reason,
382
+ };
383
+ }
384
+
385
+ function autoReviewReason(work: TaskDispatch, cfg: DispatchAutoReview): string | undefined {
386
+ if (work.criticality && cfg.whenCriticality?.includes(work.criticality)) return `criticality=${work.criticality}`;
387
+ if (work.complexity && cfg.whenComplexity?.includes(work.complexity)) return `complexity=${work.complexity}`;
388
+ return undefined;
389
+ }
390
+
391
+ // ---------------------------------------------------------------------------
392
+ // Rationale rendering
393
+ // ---------------------------------------------------------------------------
394
+
395
+ function buildRationale(target: DispatchTarget, factors: DispatchFactors, warnings: string[]): string {
396
+ const head = `${target.model ?? `${target.provider} default`}${target.effort ? ` ${target.effort}` : ""}`;
397
+ const shape = [
398
+ factors.type ? `type=${factors.type}` : undefined,
399
+ factors.complexity ? `complexity=${factors.complexity}` : undefined,
400
+ factors.contextFootprint ? `contextFootprint=${factors.contextFootprint}` : undefined,
401
+ factors.criticality ? `criticality=${factors.criticality}` : undefined,
402
+ ].filter(Boolean).join(", ");
403
+ const parts = [`${head}:`];
404
+ if (shape) parts.push(shape + ";");
405
+ parts.push(`matched ${factors.matchedRule};`);
406
+ parts.push(`${capitalize(target.provider)} band=${factors.band}`);
407
+ if (factors.escalatedForFootprint) parts.push(`(escalated to ${target.model} for footprint)`);
408
+ if (factors.reroutedFromProvider) parts.push(`(rerouted from ${factors.reroutedFromProvider})`);
409
+ if (factors.pinnedFields?.length) parts.push(`(pinned: ${factors.pinnedFields.join(", ")})`);
410
+ return parts.join(" ");
411
+ }
412
+
413
+ function capitalize(value: string): string {
414
+ return value ? value[0]!.toUpperCase() + value.slice(1) : value;
415
+ }
416
+
417
+ // ---------------------------------------------------------------------------
418
+ // Catalog / band helpers
419
+ // ---------------------------------------------------------------------------
420
+
421
+ function catalogEntry(catalog: ProviderCatalogEntry[], provider: SpawnProvider): ProviderCatalogEntry | undefined {
422
+ return catalog.find((c) => c.provider === provider);
423
+ }
424
+
425
+ function contextWindowOf(catalog: ProviderCatalogEntry[], provider: SpawnProvider, model: string | undefined): number | undefined {
426
+ const entry = catalogEntry(catalog, provider);
427
+ if (!entry) return undefined;
428
+ const alias = model ?? entry.defaultModel;
429
+ return alias ? contextWindowFromEntry(entry, alias) : undefined;
430
+ }
431
+
432
+ function contextWindowFromEntry(entry: ProviderCatalogEntry, alias: string): number | undefined {
433
+ const model = entry.models.find((m) => m.alias === alias) ?? entry.models.find((m) => m.providerModel === alias);
434
+ return model?.limits?.contextWindowTokens?.value;
435
+ }
436
+
437
+ function effortIfSupported(entry: ProviderCatalogEntry, alias: string | undefined, effort: string | undefined): string | undefined {
438
+ if (!effort || !alias) return undefined;
439
+ const model = entry.models.find((m) => m.alias === alias);
440
+ return model && model.efforts.includes(effort) ? effort : undefined;
441
+ }
442
+
443
+ function bandFor(provider: SpawnProvider, bands: ProviderQuotaBand[]): QuotaBand {
444
+ return bands.find((b) => b.provider === provider)?.band ?? "normal";
445
+ }
446
+
447
+ function isBlocked(provider: SpawnProvider, bands: ProviderQuotaBand[]): boolean {
448
+ return bands.find((b) => b.provider === provider)?.blockedNow ?? false;
449
+ }
450
+
451
+ // Lower = healthier. Mirrors the bandRank ordering used elsewhere (prefer is the best state).
452
+ function bandRank(band: QuotaBand): number {
453
+ switch (band) {
454
+ case "prefer": return 0;
455
+ case "normal": return 1;
456
+ case "caution": return 2;
457
+ case "avoid-large": return 3;
458
+ case "hard-avoid": return 4;
459
+ default: return 1;
460
+ }
461
+ }
@@ -1,4 +1,4 @@
1
- import { chmodSync, existsSync, readFileSync, rmSync } from "node:fs";
1
+ import { chmodSync, existsSync, readFileSync, realpathSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
4
  /**
@@ -143,10 +143,29 @@ export const defaultInstallIo: InstallIo = {
143
143
  : bin && typeof bin === "object"
144
144
  ? Object.values(bin as Record<string, unknown>).filter((v): v is string => typeof v === "string")
145
145
  : [];
146
+ // Resolve the real node_modules path once so we can guard against workspace
147
+ // symlinks that point back into the repo working tree (#825).
148
+ let realNodeModules: string;
149
+ try {
150
+ realNodeModules = realpathSync(join(prefix, "node_modules"));
151
+ } catch {
152
+ return [];
153
+ }
146
154
  const restored: string[] = [];
147
155
  for (const rel of targets) {
148
156
  const file = join(pkgDir, rel);
149
157
  if (!existsSync(file)) continue;
158
+ // Skip if the realpath resolves outside node_modules (e.g. a workspace symlink
159
+ // node_modules/pkg → ../pkg points into the tracked source tree). Chmodding
160
+ // through such a symlink flips the source file's mode and makes the working
161
+ // tree appear dirty, mis-routing the landed path into the WIP-preserve path.
162
+ let realFile: string;
163
+ try {
164
+ realFile = realpathSync(file);
165
+ } catch {
166
+ continue;
167
+ }
168
+ if (!realFile.startsWith(realNodeModules + "/")) continue;
150
169
  try {
151
170
  chmodSync(file, 0o755);
152
171
  restored.push(file);
@@ -1,3 +0,0 @@
1
- import{c as e,d as t,n,o as r,t as i,u as a}from"./types-DorDDvEb.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(e().map(e=>[e,`Bot`]))}),{...ne,...Object.fromEntries(e().map(e=>[e,`${a(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(t){if(t?.id===`user`)return`user`;if(t?.id===`system`)return`system`;if(S(t))return`channel`;let n=e(),r=typeof t?.meta?.provider==`string`?t.meta.provider.toLowerCase():void 0;if(r&&n.includes(r))return r;let i=[...t?.tags||[],t?.meta?.client,t?.meta?.runtime,t?.meta?.agentType].filter(e=>typeof e==`string`).map(e=>e.toLowerCase());for(let e of n)if(i.some(t=>t.includes(e)))return e;return`agent`}function E(e,t){if(!t?.lastSeen||t.status===`offline`||t.id===`user`||t.id===`system`)return!1;let n=t.meta?.transport;if(r(n)&&n.connected===!0)return!1;let i=new Date(t.lastSeen).getTime();return Number.isFinite(i)?e-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 r=n(e);return r?t[r]: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=n(e)||``,r=0;for(let e=0;e<t.length;e++)r=r*31+t.charCodeAt(e)>>>0;return L[r%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 i(e.from)&&e.to?e.to:i(e.to)&&e.from?e.from:``}function K(e){return i(e.to)&&!i(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 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(e){let t=e?.meta?.providerState;return!r(t)||typeof t.state!=`string`?null:{state:t.state,reason:typeof t.reason==`string`?t.reason:void 0,label:typeof t.label==`string`&&t.label?t.label:t.state,recommendedAction:typeof t.recommendedAction==`string`?t.recommendedAction:void 0,source:typeof t.source==`string`?t.source:void 0,raw:t.raw,updatedAt:typeof t.updatedAt==`number`?t.updatedAt:void 0,pendingApproval:ge(t.pendingApproval)}}function ge(e){if(!r(e)||typeof e.id!=`string`)return;let t=Array.isArray(e.choices)?e.choices.filter(r).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):[],n=Array.isArray(e.questions)?e.questions.filter(r).map(e=>({question:typeof e.question==`string`?e.question:``,header:typeof e.header==`string`?e.header:void 0,multiSelect:e.multiSelect===!0,options:Array.isArray(e.options)?e.options.filter(r).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:e.id,provider:typeof e.provider==`string`?e.provider:void 0,kind:typeof e.kind==`string`?e.kind:void 0,title:typeof e.title==`string`&&e.title?e.title:`Permission request`,body:typeof e.body==`string`?e.body:``,choices:t,...n&&n.length?{questions:n}:{}}}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(e){let t=e?.meta?.activeSubagents;if(!Array.isArray(t)){let t=typeof e?.meta?.activeSubagentCount==`number`?e.meta.activeSubagentCount:0;return t>0?Array.from({length:t},(e,t)=>({id:`subagent-${t+1}`,label:`Subagent ${t+1}`})):[]}return t.filter(r).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(e){let t=e?.meta?.activeWork;if(!Array.isArray(t))return null;let n=t.find(e=>r(e)&&e.kind===`background-script`);return!n||!r(n)?null:typeof n.label==`string`&&n.label?n.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-BWi8DQCp.js.map