agent-relay-server 0.91.4 → 0.93.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/openapi.json +29 -1
- package/package.json +2 -2
- package/public/assets/MessageBubble-DX16Hbb5.js +3 -0
- package/public/assets/MessageBubble-DX16Hbb5.js.map +1 -0
- package/public/assets/{PlanGraphPanel-BwEOOqdj.js → PlanGraphPanel-BuWerKhs.js} +2 -2
- package/public/assets/{PlanGraphPanel-BwEOOqdj.js.map → PlanGraphPanel-BuWerKhs.js.map} +1 -1
- package/public/assets/activity-tuTEn3vn.js +2 -0
- package/public/assets/{activity-DD9bl5uf.js.map → activity-tuTEn3vn.js.map} +1 -1
- package/public/assets/{agent-profiles-CLJNeVxc.js → agent-profiles-CAwmjdDx.js} +2 -2
- package/public/assets/{agent-profiles-CLJNeVxc.js.map → agent-profiles-CAwmjdDx.js.map} +1 -1
- package/public/assets/agents-B-BSBrM5.js +2 -0
- package/public/assets/{agents-DJgqCIqi.js.map → agents-B-BSBrM5.js.map} +1 -1
- package/public/assets/{analytics-C1F28oqr.js → analytics-aI_oGHtR.js} +3 -3
- package/public/assets/{analytics-C1F28oqr.js.map → analytics-aI_oGHtR.js.map} +1 -1
- package/public/assets/{automation-Vbei8P9W.js → automation-BAQM1dBh.js} +2 -2
- package/public/assets/{automation-Vbei8P9W.js.map → automation-BAQM1dBh.js.map} +1 -1
- package/public/assets/{badge-CK6ejiOh.js → badge-fjSgjnDc.js} +2 -2
- package/public/assets/{badge-CK6ejiOh.js.map → badge-fjSgjnDc.js.map} +1 -1
- package/public/assets/branch-state-badge-DaafW7sN.js +2 -0
- package/public/assets/{branch-state-badge-Q4W68jxj.js.map → branch-state-badge-DaafW7sN.js.map} +1 -1
- package/public/assets/{button-BAwKuRIq.js → button-DidMOp8n.js} +2 -2
- package/public/assets/{button-BAwKuRIq.js.map → button-DidMOp8n.js.map} +1 -1
- package/public/assets/card-CpLnx1JJ.js +2 -0
- package/public/assets/{card-DDxxo-Fo.js.map → card-CpLnx1JJ.js.map} +1 -1
- package/public/assets/{channels-BQbNtrtF.js → channels-DHjTCcE8.js} +2 -2
- package/public/assets/{channels-BQbNtrtF.js.map → channels-DHjTCcE8.js.map} +1 -1
- package/public/assets/chat-uWKl3MpS.js +2 -0
- package/public/assets/{chat-Dw5xpc5W.js.map → chat-uWKl3MpS.js.map} +1 -1
- package/public/assets/{connectors-CmARSKoV.js → connectors-CI97PYOl.js} +2 -2
- package/public/assets/{connectors-CmARSKoV.js.map → connectors-CI97PYOl.js.map} +1 -1
- package/public/assets/copy-button-CKAwTG9g.js +2 -0
- package/public/assets/{copy-button-D3B_s7J7.js.map → copy-button-CKAwTG9g.js.map} +1 -1
- package/public/assets/display-CWMHll1J.js.map +1 -1
- package/public/assets/{dist-Dmiv1tSO.js → dist-BEpGArlE.js} +2 -2
- package/public/assets/{dist-Dmiv1tSO.js.map → dist-BEpGArlE.js.map} +1 -1
- package/public/assets/dist-BZ-CpDXy.js +2 -0
- package/public/assets/{dist-CIbvPDQV.js.map → dist-BZ-CpDXy.js.map} +1 -1
- package/public/assets/{dist-DLsSqk6h.js → dist-BaDWyAAG.js} +2 -2
- package/public/assets/{dist-DLsSqk6h.js.map → dist-BaDWyAAG.js.map} +1 -1
- package/public/assets/{dist-CAFUEpK2.js → dist-BivRSJg7.js} +2 -2
- package/public/assets/{dist-CAFUEpK2.js.map → dist-BivRSJg7.js.map} +1 -1
- package/public/assets/{dist-CL2npVDe.js → dist-C9ntDbUp.js} +2 -2
- package/public/assets/{dist-CL2npVDe.js.map → dist-C9ntDbUp.js.map} +1 -1
- package/public/assets/dist-CEb4q1gZ.js +2 -0
- package/public/assets/{dist-DyqEeyBI.js.map → dist-CEb4q1gZ.js.map} +1 -1
- package/public/assets/dist-DBDYIgS5.js +2 -0
- package/public/assets/{dist-CoRABSO3.js.map → dist-DBDYIgS5.js.map} +1 -1
- package/public/assets/{dist-ERYvDMdt.js → dist-DNxK5Ibj.js} +2 -2
- package/public/assets/{dist-ERYvDMdt.js.map → dist-DNxK5Ibj.js.map} +1 -1
- package/public/assets/{dist-YjP8Gd1C.js → dist-DayffsX1.js} +2 -2
- package/public/assets/{dist-YjP8Gd1C.js.map → dist-DayffsX1.js.map} +1 -1
- package/public/assets/{dist-YQVQrtnc.js → dist-L3vQoV3T.js} +2 -2
- package/public/assets/{dist-YQVQrtnc.js.map → dist-L3vQoV3T.js.map} +1 -1
- package/public/assets/{dist-CnRv0y2x.js → dist-uFOP-AfS.js} +2 -2
- package/public/assets/{dist-CnRv0y2x.js.map → dist-uFOP-AfS.js.map} +1 -1
- package/public/assets/{es2015-Yto-4EBw.js → es2015-CJr6bLcU.js} +2 -2
- package/public/assets/{es2015-Yto-4EBw.js.map → es2015-CJr6bLcU.js.map} +1 -1
- package/public/assets/{formatted-body-impl-BisZ0FtO.js → formatted-body-impl-DuqZunxL.js} +2 -2
- package/public/assets/{formatted-body-impl-BisZ0FtO.js.map → formatted-body-impl-DuqZunxL.js.map} +1 -1
- package/public/assets/index-DLjkMB-b.css +2 -0
- package/public/assets/index-dMB8klzy.js +23 -0
- package/public/assets/index-dMB8klzy.js.map +1 -0
- package/public/assets/{input-CLW2gDUM.js → input-BbH2Jq-f.js} +2 -2
- package/public/assets/{input-CLW2gDUM.js.map → input-BbH2Jq-f.js.map} +1 -1
- package/public/assets/{insights-D9ytYZyE.js → insights-C0lZ7n8D.js} +2 -2
- package/public/assets/{insights-D9ytYZyE.js.map → insights-C0lZ7n8D.js.map} +1 -1
- package/public/assets/integrations-DXh00Ss-.js +2 -0
- package/public/assets/{integrations-D9Cx-7ma.js.map → integrations-DXh00Ss-.js.map} +1 -1
- package/public/assets/{lib-DdCM7GfN.js → lib-B0r9alsu.js} +2 -2
- package/public/assets/{lib-DdCM7GfN.js.map → lib-B0r9alsu.js.map} +1 -1
- package/public/assets/{lucide-react-CsPXvXMC.js → lucide-react-BCarrGVa.js} +2 -2
- package/public/assets/{lucide-react-CsPXvXMC.js.map → lucide-react-BCarrGVa.js.map} +1 -1
- package/public/assets/{maintenance-BA93FvLR.js → maintenance-Dh9nzjYV.js} +2 -2
- package/public/assets/{maintenance-BA93FvLR.js.map → maintenance-Dh9nzjYV.js.map} +1 -1
- package/public/assets/{managed-agents-DGwdEIzA.js → managed-agents-Bsveljn4.js} +4 -4
- package/public/assets/{managed-agents-DGwdEIzA.js.map → managed-agents-Bsveljn4.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-C7FDXM5c.js → markdown-preview-impl-CYpMFis9.js} +2 -2
- package/public/assets/{markdown-preview-impl-C7FDXM5c.js.map → markdown-preview-impl-CYpMFis9.js.map} +1 -1
- package/public/assets/{memory-CHzkRJqf.js → memory-DMFQMXrp.js} +2 -2
- package/public/assets/{memory-CHzkRJqf.js.map → memory-DMFQMXrp.js.map} +1 -1
- package/public/assets/messages-Cnzc7j50.js +2 -0
- package/public/assets/{messages-B7CJ5tgT.js.map → messages-Cnzc7j50.js.map} +1 -1
- package/public/assets/{orchestrators-DOy9xIx2.js → orchestrators-DSoDPNVS.js} +2 -2
- package/public/assets/{orchestrators-DOy9xIx2.js.map → orchestrators-DSoDPNVS.js.map} +1 -1
- package/public/assets/{overview-DLbv1BWa.js → overview-OsRxo5XQ.js} +2 -2
- package/public/assets/{overview-DLbv1BWa.js.map → overview-OsRxo5XQ.js.map} +1 -1
- package/public/assets/{pairs-BDQo-k3F.js → pairs-D4UiUhyW.js} +2 -2
- package/public/assets/{pairs-BDQo-k3F.js.map → pairs-D4UiUhyW.js.map} +1 -1
- package/public/assets/{projects-hU5KGW_T.js → projects-CYzU-Cf3.js} +2 -2
- package/public/assets/{projects-hU5KGW_T.js.map → projects-CYzU-Cf3.js.map} +1 -1
- package/public/assets/{react-dom-CEl58oxP.js → react-dom-CYpnckci.js} +2 -2
- package/public/assets/{react-dom-CEl58oxP.js.map → react-dom-CYpnckci.js.map} +1 -1
- package/public/assets/{security-9WrCcrWs.js → security-CC2F6xdd.js} +2 -2
- package/public/assets/{security-9WrCcrWs.js.map → security-CC2F6xdd.js.map} +1 -1
- package/public/assets/{select-CRjeGZ9a.js → select-CD38f1WQ.js} +2 -2
- package/public/assets/{select-CRjeGZ9a.js.map → select-CD38f1WQ.js.map} +1 -1
- package/public/assets/settings-T3B40Amb.js +6 -0
- package/public/assets/settings-T3B40Amb.js.map +1 -0
- package/public/assets/{store-DsqUEZOf.js → store-CJeafbP1.js} +3 -3
- package/public/assets/{store-DsqUEZOf.js.map → store-CJeafbP1.js.map} +1 -1
- package/public/assets/{tasks-86St2vpn.js → tasks-BkrhWMw7.js} +2 -2
- package/public/assets/{tasks-86St2vpn.js.map → tasks-BkrhWMw7.js.map} +1 -1
- package/public/assets/{teams-C9WUxzud.js → teams-DyzaREAg.js} +2 -2
- package/public/assets/{teams-C9WUxzud.js.map → teams-DyzaREAg.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-yXWyt2fm.js → terminal-viewer-impl-B5JeHof3.js} +3 -3
- package/public/assets/{terminal-viewer-impl-yXWyt2fm.js.map → terminal-viewer-impl-B5JeHof3.js.map} +1 -1
- package/public/assets/{use-keyboard-viewport-DABeIMEJ.js → use-keyboard-viewport-BYCJkZCN.js} +2 -2
- package/public/assets/{use-keyboard-viewport-DABeIMEJ.js.map → use-keyboard-viewport-BYCJkZCN.js.map} +1 -1
- package/public/assets/user-avatar-C7i11_ju.js +2 -0
- package/public/assets/{user-avatar-tomKla2H.js.map → user-avatar-C7i11_ju.js.map} +1 -1
- package/public/assets/{work-queue-BtLMzf0g.js → work-queue-DHp4p1Zk.js} +2 -2
- package/public/assets/{work-queue-BtLMzf0g.js.map → work-queue-DHp4p1Zk.js.map} +1 -1
- package/public/assets/workspaces-BzwZvbsq.js +3 -0
- package/public/assets/{workspaces-C740m1ys.js.map → workspaces-BzwZvbsq.js.map} +1 -1
- package/public/index.html +21 -21
- package/src/agent-lifecycle-events.ts +0 -10
- package/src/branch-landed.ts +17 -29
- package/src/bus.ts +9 -2
- package/src/config-store.ts +13 -26
- package/src/config.ts +1 -0
- package/src/context-advisory.ts +20 -10
- package/src/db/agent-predicates.ts +21 -0
- package/src/db/agents.ts +3 -3
- package/src/db/delivery.ts +38 -0
- package/src/db/messages.ts +18 -1
- package/src/db/migrations.ts +17 -0
- package/src/db/scheduled-timers.ts +33 -12
- package/src/db/schema.ts +4 -0
- package/src/db/workspaces.ts +44 -2
- package/src/maintenance/constants.ts +2 -0
- package/src/maintenance/jobs.ts +21 -8
- package/src/maintenance/workspaces.ts +76 -0
- package/src/mcp/index.ts +1 -1
- package/src/mcp/tools-messaging.ts +13 -3
- package/src/mcp/tools-spawn.ts +30 -2
- package/src/mcp-notification-subscription-tools.ts +55 -2
- package/src/notification-router.ts +163 -9
- package/src/notification-rules.ts +7 -1
- package/src/notification-settings-store.ts +47 -0
- package/src/notification-settings.ts +155 -0
- package/src/notification-types.ts +2 -0
- package/src/quota-advisory.ts +0 -3
- package/src/routes/agents-spawn.ts +15 -2
- package/src/routes/agents.ts +17 -2
- package/src/routes/commands.ts +5 -0
- package/src/routes/index.ts +2 -0
- package/src/routes/notifications.ts +36 -0
- package/src/routes/settings.ts +6 -0
- package/src/security.ts +1 -0
- package/src/services/managed-running.ts +18 -1
- package/src/services/rate-limit-resume.ts +3 -0
- package/src/services/register-agent.ts +7 -1
- package/src/services/scheduler.ts +29 -1
- package/src/services/spawn-agent.ts +62 -2
- package/src/settings/registry.ts +30 -44
- package/src/spawn-command.ts +10 -0
- package/src/sse.ts +1 -0
- package/src/validation.ts +7 -2
- package/src/workspace-auto-merge.ts +53 -9
- package/public/assets/MessageBubble-BORB9AKu.js +0 -3
- package/public/assets/MessageBubble-BORB9AKu.js.map +0 -1
- package/public/assets/activity-DD9bl5uf.js +0 -2
- package/public/assets/agents-DJgqCIqi.js +0 -2
- package/public/assets/branch-state-badge-Q4W68jxj.js +0 -2
- package/public/assets/card-DDxxo-Fo.js +0 -2
- package/public/assets/chat-Dw5xpc5W.js +0 -2
- package/public/assets/copy-button-D3B_s7J7.js +0 -2
- package/public/assets/dist-CIbvPDQV.js +0 -2
- package/public/assets/dist-CoRABSO3.js +0 -2
- package/public/assets/dist-DyqEeyBI.js +0 -2
- package/public/assets/index-Bsi2NH0v.js +0 -23
- package/public/assets/index-Bsi2NH0v.js.map +0 -1
- package/public/assets/index-umexz3cs.css +0 -2
- package/public/assets/integrations-D9Cx-7ma.js +0 -2
- package/public/assets/messages-B7CJ5tgT.js +0 -2
- package/public/assets/settings-Durch6xW.js +0 -4
- package/public/assets/settings-Durch6xW.js.map +0 -1
- package/public/assets/user-avatar-tomKla2H.js +0 -2
- package/public/assets/workspaces-C740m1ys.js +0 -3
package/src/security.ts
CHANGED
|
@@ -151,6 +151,7 @@ export function requiredScopeFor(method: string, pathname: string): string | nul
|
|
|
151
151
|
if (pathname === "/api/health") return "health:read";
|
|
152
152
|
if (pathname === "/api/events") return "events:read";
|
|
153
153
|
if (pathname === "/api/route") return "agent:read";
|
|
154
|
+
if (pathname === "/api/notifications/preview") return "notifications:read";
|
|
154
155
|
if (pathname === "/api/mcp") return "mcp:use";
|
|
155
156
|
if (pathname === "/api/orchestrators/bootstrap/exchange") return "token:write";
|
|
156
157
|
if (pathname === "/api/orchestrators/bootstrap") return "token:write";
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// Composed by `registerAgent` (HTTP + bus registration) and called directly by the
|
|
13
13
|
// orchestrator-report projection in lifecycle-manager. It does NOT upsert the agent
|
|
14
14
|
// row — the agent registers itself over HTTP/bus; this only reconciles managed state.
|
|
15
|
-
import { createActivityEvent, resolveQueuedPolicyMessages } from "../db";
|
|
15
|
+
import { createActivityEvent, resolveQueuedDirectMessages, resolveQueuedPolicyMessages } from "../db";
|
|
16
16
|
import { getManagedAgentState, updateManagedAgentState } from "../config-store";
|
|
17
17
|
import { emitRelayEvent } from "../events";
|
|
18
18
|
import { emitMessageAvailable, emitMessageDeliveryUpdated, emitNewMessage } from "../sse";
|
|
@@ -129,3 +129,20 @@ function recordManagedTransition(
|
|
|
129
129
|
},
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Flush queued messages addressed directly to `agentId` (#416). Called when a registered agent
|
|
135
|
+
* transitions back to available: heartbeat stale→idle, re-registration after restart/compact, or
|
|
136
|
+
* rate-limit resume. Emits the same `message.available` / `message.new` / `message.delivery_updated`
|
|
137
|
+
* events that the policy flush emits so SSE subscribers and the dashboard react identically.
|
|
138
|
+
* No-op when there are no queued direct messages.
|
|
139
|
+
*/
|
|
140
|
+
export function flushQueuedDirectMessages(agentId: string): void {
|
|
141
|
+
const available = resolveQueuedDirectMessages(agentId);
|
|
142
|
+
if (!available.length) return;
|
|
143
|
+
emitMessageAvailable("direct", agentId, available);
|
|
144
|
+
for (const message of available) {
|
|
145
|
+
emitNewMessage(message);
|
|
146
|
+
emitMessageDeliveryUpdated(message);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createActivityEvent, listAgents, mergeAgentMeta } from "../db";
|
|
2
2
|
import { routeNotification } from "../notification-router";
|
|
3
|
+
import { flushQueuedDirectMessages } from "./managed-running";
|
|
3
4
|
import {
|
|
4
5
|
TRANSIENT_RATE_LIMIT_BACKOFF_MS,
|
|
5
6
|
agentProviderState,
|
|
@@ -134,6 +135,8 @@ export function resumeRateLimitedAgents(): Record<string, unknown> {
|
|
|
134
135
|
|
|
135
136
|
if (transient) mergeAgentMeta(agent.id, { providerState: nextTransientState(state as Record<string, unknown>, now, attempts) });
|
|
136
137
|
|
|
138
|
+
// #416 — flush messages queued while the agent was rate-limited.
|
|
139
|
+
flushQueuedDirectMessages(agent.id);
|
|
137
140
|
resumed.push(agent.id);
|
|
138
141
|
createActivityEvent({
|
|
139
142
|
clientId: `rate-limit-resume-${agent.id}-${now}`,
|
|
@@ -15,7 +15,8 @@ import { emitAgentStatus, emitNewMessage } from "../sse";
|
|
|
15
15
|
import { noteAgentTimelineEvent } from "../compaction-watch";
|
|
16
16
|
import { notifyAgentReady } from "../agent-lifecycle-events";
|
|
17
17
|
import { resolveSpawnLineage, resolveTeamLineage } from "../security";
|
|
18
|
-
import { markManagedAgentRunning } from "./managed-running";
|
|
18
|
+
import { flushQueuedDirectMessages, markManagedAgentRunning } from "./managed-running";
|
|
19
|
+
import { isAgentUnavailable } from "../db/agent-predicates";
|
|
19
20
|
import type { AuthContext } from "./auth-context";
|
|
20
21
|
import type { AgentCard, RegisterAgentInput } from "../types";
|
|
21
22
|
|
|
@@ -71,6 +72,11 @@ export function registerAgent(input: RegisterAgentInput, ctx: AuthContext): Agen
|
|
|
71
72
|
// transport. notifyAgentReady no-ops for non-spawned agents and dedups repeats.
|
|
72
73
|
if (agent.ready && !existing?.ready) notifyAgentReady(agent.id);
|
|
73
74
|
|
|
75
|
+
// #416 — flush queued direct messages when a re-registering agent was previously unavailable
|
|
76
|
+
// (stale, compacting, blocked) or a new agent becomes ready. No-op for brand-new agents
|
|
77
|
+
// (no queued messages yet) and when no queued messages exist.
|
|
78
|
+
if (existing && isAgentUnavailable(existing) && agent.ready) flushQueuedDirectMessages(agent.id);
|
|
79
|
+
|
|
74
80
|
if (!existing) {
|
|
75
81
|
// #413 — a spawned worker with a report-back contract gets a pending reply obligation to its
|
|
76
82
|
// spawner so it can't idle with its verdict undelivered (vent #121). First-registration only.
|
|
@@ -39,9 +39,13 @@ export interface ScheduleTimerInput {
|
|
|
39
39
|
everyMs?: number;
|
|
40
40
|
rrule?: string;
|
|
41
41
|
timezone?: string;
|
|
42
|
+
kind?: SendMessageInput["kind"];
|
|
43
|
+
channel?: string;
|
|
42
44
|
subject?: string;
|
|
43
45
|
body: string;
|
|
46
|
+
claimable?: boolean;
|
|
44
47
|
payload?: Record<string, unknown>;
|
|
48
|
+
meta?: Record<string, unknown>;
|
|
45
49
|
maxRuns?: number;
|
|
46
50
|
expiresAt?: string | number;
|
|
47
51
|
idempotencyKey?: string;
|
|
@@ -78,9 +82,13 @@ export function scheduleTimer(input: ScheduleTimerInput, ctx: AuthContext): Sche
|
|
|
78
82
|
const deliveryFrom = ctx.callerAgentId ?? "system";
|
|
79
83
|
const body = cleanBody(input.body);
|
|
80
84
|
const payload = cleanPayload(input.payload);
|
|
85
|
+
const meta = cleanPayload(input.meta);
|
|
81
86
|
const maxRuns = cleanPositiveInt(input.maxRuns, "maxRuns");
|
|
82
87
|
const expiresAt = cleanExpiresAt(input.expiresAt, now);
|
|
83
88
|
const idempotencyKey = cleanOptionalString(input.idempotencyKey, "idempotencyKey", 240);
|
|
89
|
+
const messageKind = cleanOptionalString(input.kind, "kind", 40) as SendMessageInput["kind"] | undefined;
|
|
90
|
+
const channel = cleanOptionalString(input.channel, "channel", 120);
|
|
91
|
+
const claimable = cleanOptionalBoolean(input.claimable, "claimable");
|
|
84
92
|
|
|
85
93
|
if (schedule.kind === "interval") {
|
|
86
94
|
if ((schedule.def.everyMs ?? 0) < SCHEDULER_MIN_RECURRING_INTERVAL_MS) {
|
|
@@ -100,9 +108,13 @@ export function scheduleTimer(input: ScheduleTimerInput, ctx: AuthContext): Sche
|
|
|
100
108
|
validateSendMessageAuthorization({
|
|
101
109
|
from: deliveryFrom,
|
|
102
110
|
to: target,
|
|
111
|
+
kind: messageKind,
|
|
112
|
+
channel,
|
|
103
113
|
body,
|
|
104
114
|
subject: cleanOptionalString(input.subject, "subject", 200),
|
|
115
|
+
claimable,
|
|
105
116
|
payload,
|
|
117
|
+
meta,
|
|
106
118
|
idempotencyKey: idempotencyKey ? `${idempotencyKey}:probe` : undefined,
|
|
107
119
|
}, ctx);
|
|
108
120
|
|
|
@@ -116,9 +128,13 @@ export function scheduleTimer(input: ScheduleTimerInput, ctx: AuthContext): Sche
|
|
|
116
128
|
nextRunAt: schedule.nextRunAt,
|
|
117
129
|
scheduleKind: schedule.kind,
|
|
118
130
|
scheduleDef: schedule.def,
|
|
131
|
+
messageKind,
|
|
132
|
+
channel,
|
|
119
133
|
subject: cleanOptionalString(input.subject, "subject", 200),
|
|
120
134
|
body,
|
|
135
|
+
claimable,
|
|
121
136
|
payload,
|
|
137
|
+
meta,
|
|
122
138
|
maxRuns,
|
|
123
139
|
expiresAt,
|
|
124
140
|
idempotencyKey,
|
|
@@ -176,8 +192,11 @@ function fireClaimedTimer(timer: ScheduledTimer, now: number): { status: "fired"
|
|
|
176
192
|
const result = sendMessageService({
|
|
177
193
|
from: timer.deliveryFrom,
|
|
178
194
|
to: timer.target,
|
|
195
|
+
kind: timer.messageKind as SendMessageInput["kind"] | undefined,
|
|
196
|
+
channel: timer.channel,
|
|
179
197
|
subject: timer.subject,
|
|
180
198
|
body: timer.body,
|
|
199
|
+
claimable: timer.claimable,
|
|
181
200
|
payload: {
|
|
182
201
|
...timer.payload,
|
|
183
202
|
scheduler: {
|
|
@@ -187,6 +206,7 @@ function fireClaimedTimer(timer: ScheduledTimer, now: number): { status: "fired"
|
|
|
187
206
|
},
|
|
188
207
|
},
|
|
189
208
|
meta: {
|
|
209
|
+
...timer.meta,
|
|
190
210
|
scheduler: {
|
|
191
211
|
timerId: timer.id,
|
|
192
212
|
correlationId: timer.correlationId,
|
|
@@ -199,7 +219,7 @@ function fireClaimedTimer(timer: ScheduledTimer, now: number): { status: "fired"
|
|
|
199
219
|
return { status: "fired", messageId: result.message.id };
|
|
200
220
|
} catch (error) {
|
|
201
221
|
const updated = recordScheduledTimerFailure({
|
|
202
|
-
|
|
222
|
+
timer,
|
|
203
223
|
error: errMessage(error),
|
|
204
224
|
now,
|
|
205
225
|
retryMs: SCHEDULER_RETRY_MS,
|
|
@@ -215,6 +235,8 @@ function timerRunIdempotencyKey(timer: ScheduledTimer, runNumber: number): strin
|
|
|
215
235
|
}
|
|
216
236
|
|
|
217
237
|
function persistedAuthContext(timer: ScheduledTimer): AuthContext {
|
|
238
|
+
// Timers intentionally fire with the create-time component scope snapshot. That keeps durable
|
|
239
|
+
// sends restart-safe; a stricter future model can revalidate a live token at fire time.
|
|
218
240
|
const component: ComponentToken = {
|
|
219
241
|
sub: timer.createdBy,
|
|
220
242
|
role: timer.createdByKind,
|
|
@@ -287,6 +309,12 @@ function cleanPositiveInt(value: unknown, field: string): number | undefined {
|
|
|
287
309
|
return value;
|
|
288
310
|
}
|
|
289
311
|
|
|
312
|
+
function cleanOptionalBoolean(value: unknown, field: string): boolean | undefined {
|
|
313
|
+
if (value === undefined || value === null) return undefined;
|
|
314
|
+
if (typeof value !== "boolean") throw new ValidationError(`${field} must be a boolean`);
|
|
315
|
+
return value;
|
|
316
|
+
}
|
|
317
|
+
|
|
290
318
|
function cleanExpiresAt(value: unknown, now: number): number | undefined {
|
|
291
319
|
if (value === undefined || value === null) return undefined;
|
|
292
320
|
const ms = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : NaN;
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
// deliberately out of scope here — this slice converges the two external TRANSPORTS.
|
|
26
26
|
import type { SpawnProvider } from "agent-relay-sdk";
|
|
27
27
|
import { errMessage, stringValue } from "agent-relay-sdk";
|
|
28
|
-
import { countLiveSpawnedAgents, createActivityEvent, ensureTeam, getAgent, listAgents, resolveProjectForCwd, setAgentTeam, ValidationError, withResolvedProvisioning } from "../db";
|
|
28
|
+
import { countLiveSpawnedAgents, createActivityEvent, ensureTeam, getAgent, listAgents, listWorkspaces, resolveProjectForCwd, setAgentTeam, ValidationError, withResolvedProvisioning } from "../db";
|
|
29
29
|
import { createCommand } from "../commands-db";
|
|
30
30
|
import { emitCommandEvent } from "../command-events";
|
|
31
31
|
import { buildSpawnCommand, generateSpawnRequestId, resolveSpawnModelParams } from "../spawn-command";
|
|
@@ -42,7 +42,9 @@ import { hasComponentScope, isComponentAuthorizedFor } from "../security";
|
|
|
42
42
|
import { isPathWithinBase } from "../utils";
|
|
43
43
|
import { McpAuthError, McpNotFoundError } from "../mcp-errors";
|
|
44
44
|
import type { AuthContext } from "./auth-context";
|
|
45
|
-
import type { AgentCard, AgentLifecycle, Command, SpawnApprovalMode, WorkspaceMode } from "../types";
|
|
45
|
+
import type { AgentCard, AgentLifecycle, Command, SpawnApprovalMode, WorkspaceAutoMergePolicy, WorkspaceLandingPolicy, WorkspaceMode } from "../types";
|
|
46
|
+
import { isOwnerAlive, liveAgentCwds, worktreeInUseByLiveAgent } from "../workspace-merge";
|
|
47
|
+
import { TERMINAL_WORKSPACE_STATUSES } from "../workspace-phase";
|
|
46
48
|
|
|
47
49
|
/** The spawn scope every transport must hold (admin `*` / `admin:*` pass; `system` bypasses). */
|
|
48
50
|
const SPAWN_SCOPE = "command:spawn";
|
|
@@ -99,6 +101,9 @@ export interface SpawnAgentInput {
|
|
|
99
101
|
* The identity-bearing relay endpoint stays per-agent (#215), never on the shared listener.
|
|
100
102
|
*/
|
|
101
103
|
sharedMcp?: SharedMcpSpawnOverride;
|
|
104
|
+
landStrategy?: WorkspaceLandingPolicy;
|
|
105
|
+
autoMerge?: WorkspaceAutoMergePolicy;
|
|
106
|
+
reviewer?: string;
|
|
102
107
|
/**
|
|
103
108
|
* Legacy report-back contract flag. Spawned children now report up by lineage: their
|
|
104
109
|
* turn-final response is delivered to the spawner automatically, without a reply obligation.
|
|
@@ -115,6 +120,23 @@ export interface SpawnAgentInput {
|
|
|
115
120
|
requestedBy?: string;
|
|
116
121
|
/** Poll for the spawned child's registration this long (ms). 0 = fire-and-forget (HTTP). */
|
|
117
122
|
waitForRegistrationMs?: number;
|
|
123
|
+
/**
|
|
124
|
+
* #635 — attach to or branch off an existing managed worktree. Relay validates existence
|
|
125
|
+
* + live-agent containment before enriching with the resolved worktreePath/workspaceId and
|
|
126
|
+
* threading to the orchestrator. User provides only `branch` + `mode`; relay fills the rest.
|
|
127
|
+
*/
|
|
128
|
+
resumeWorkspace?: {
|
|
129
|
+
branch: string;
|
|
130
|
+
mode: "attach" | "branch-from";
|
|
131
|
+
/** Relay-enriched: resolved worktree path from the DB (set during validation, not user input). */
|
|
132
|
+
worktreePath?: string;
|
|
133
|
+
/** Relay-enriched: existing workspace id so the record is updated, not duplicated (attach only). */
|
|
134
|
+
workspaceId?: string;
|
|
135
|
+
/** Relay-enriched: existing baseRef. */
|
|
136
|
+
baseRef?: string;
|
|
137
|
+
/** Relay-enriched: existing baseSha. */
|
|
138
|
+
baseSha?: string;
|
|
139
|
+
};
|
|
118
140
|
}
|
|
119
141
|
|
|
120
142
|
export interface SpawnAgentResult {
|
|
@@ -236,6 +258,40 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
236
258
|
// outside its cwd prefix, and MCP now honors the same bound.
|
|
237
259
|
assertResourceAllowed(ctx, { scope: "agent:write", resource: { orchestratorId: orchestrator.id, cwd: resolvedCwd } });
|
|
238
260
|
|
|
261
|
+
// #635 — validate and enrich resumeWorkspace before dispatching to the orchestrator.
|
|
262
|
+
// The relay has the workspace DB records; the orchestrator has disk access. We do the
|
|
263
|
+
// "is this a real managed workspace and is it free?" check here, the orchestrator
|
|
264
|
+
// does the "does the worktree actually exist on disk?" check in resolveSpawnWorkspace.
|
|
265
|
+
if (input.resumeWorkspace) {
|
|
266
|
+
const { branch, mode } = input.resumeWorkspace;
|
|
267
|
+
const existingWs = listWorkspaces().find(
|
|
268
|
+
(ws) => ws.branch === branch && ws.mode === "isolated" && !TERMINAL_WORKSPACE_STATUSES.has(ws.status),
|
|
269
|
+
);
|
|
270
|
+
if (!existingWs || !existingWs.worktreePath) {
|
|
271
|
+
throw new ValidationError(`resumeWorkspace: no active isolated workspace found for branch "${branch}"`);
|
|
272
|
+
}
|
|
273
|
+
if (!isPathWithinBase(existingWs.worktreePath, orchestrator.baseDir)) {
|
|
274
|
+
throw new ValidationError(`resumeWorkspace: worktree "${existingWs.worktreePath}" is outside orchestrator base dir "${orchestrator.baseDir}"`);
|
|
275
|
+
}
|
|
276
|
+
if (mode === "attach") {
|
|
277
|
+
if (isOwnerAlive(existingWs.ownerAgentId)) {
|
|
278
|
+
throw new ValidationError(`resumeWorkspace: workspace owner "${existingWs.ownerAgentId}" is still alive — cannot double-attach`);
|
|
279
|
+
}
|
|
280
|
+
if (worktreeInUseByLiveAgent(existingWs.worktreePath, liveAgentCwds())) {
|
|
281
|
+
throw new ValidationError(`resumeWorkspace: worktree "${existingWs.worktreePath}" is still in use by a live agent`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Enrich with relay-resolved fields the orchestrator needs.
|
|
285
|
+
input.resumeWorkspace = {
|
|
286
|
+
branch,
|
|
287
|
+
mode,
|
|
288
|
+
worktreePath: existingWs.worktreePath,
|
|
289
|
+
workspaceId: existingWs.id,
|
|
290
|
+
baseRef: existingWs.baseRef,
|
|
291
|
+
baseSha: existingWs.baseSha,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
239
295
|
// Provenance actor: the resolved caller agent when known, else the transport's label, else
|
|
240
296
|
// the raw token actor. Recorded in the command payload, the token mint, and the audit row.
|
|
241
297
|
const requestedBy = callerId ?? input.requestedBy ?? ctx.actor.id;
|
|
@@ -321,6 +377,10 @@ export async function spawnAgent(input: SpawnAgentInput, ctx: AuthContext): Prom
|
|
|
321
377
|
prompt: input.prompt,
|
|
322
378
|
systemPromptAppend,
|
|
323
379
|
relayInjectionEvents: composedInstructions.relayInjectionEvents,
|
|
380
|
+
landStrategy: input.landStrategy,
|
|
381
|
+
autoMerge: input.autoMerge,
|
|
382
|
+
reviewer: input.reviewer,
|
|
383
|
+
resumeWorkspace: input.resumeWorkspace,
|
|
324
384
|
...(expectReply ? { expectReply } : {}),
|
|
325
385
|
policyName: input.policyName,
|
|
326
386
|
spawnRequestId,
|
package/src/settings/registry.ts
CHANGED
|
@@ -28,6 +28,13 @@ import {
|
|
|
28
28
|
type SettingScope,
|
|
29
29
|
} from "agent-relay-sdk";
|
|
30
30
|
import { DEFAULT_TEAMS_CONFIG } from "agent-relay-sdk/teams-config";
|
|
31
|
+
import {
|
|
32
|
+
NOTIFICATIONS_CONFIG_DEFAULTS,
|
|
33
|
+
NOTIFICATIONS_DEFAULT_KEY,
|
|
34
|
+
NOTIFICATIONS_NAMESPACE,
|
|
35
|
+
NOTIFICATIONS_SETTINGS_SCHEMA,
|
|
36
|
+
parseNotificationSettingsKey,
|
|
37
|
+
} from "../notification-settings";
|
|
31
38
|
|
|
32
39
|
/** Fail-closed default scope — a descriptor that omits scope requires admin to
|
|
33
40
|
* read and write (#385 owns the scope grammar; this layer only declares it). */
|
|
@@ -177,34 +184,6 @@ const INSIGHTS_SCHEMA: JSONSchema = {
|
|
|
177
184
|
},
|
|
178
185
|
};
|
|
179
186
|
|
|
180
|
-
const CONTEXT_THRESHOLD_SCHEMA: JSONSchema = {
|
|
181
|
-
type: "object",
|
|
182
|
-
additionalProperties: false,
|
|
183
|
-
required: ["enabled", "advisoryUtilization", "hardBackstopUtilization", "bandSize"],
|
|
184
|
-
properties: {
|
|
185
|
-
enabled: { type: "boolean" },
|
|
186
|
-
advisoryUtilization: { type: "number", minimum: 0, maximum: 1 },
|
|
187
|
-
selfCompactUtilization: { type: "number", minimum: 0, maximum: 1 },
|
|
188
|
-
warnUtilization: { type: "number", minimum: 0, maximum: 1 },
|
|
189
|
-
hardBackstopUtilization: { type: "number", minimum: 0, maximum: 1 },
|
|
190
|
-
bandSize: { type: "number", minimum: 0, maximum: 1 },
|
|
191
|
-
},
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const NOTIFICATIONS_SCHEMA: JSONSchema = {
|
|
195
|
-
type: "object",
|
|
196
|
-
additionalProperties: false,
|
|
197
|
-
required: ["enabled", "branchLanded", "agentReady", "agentExited", "agentSpawnFailed", "contextThreshold"],
|
|
198
|
-
properties: {
|
|
199
|
-
enabled: { type: "boolean" },
|
|
200
|
-
branchLanded: { type: "boolean" },
|
|
201
|
-
agentReady: { type: "boolean" },
|
|
202
|
-
agentExited: { type: "boolean" },
|
|
203
|
-
agentSpawnFailed: { type: "boolean" },
|
|
204
|
-
contextThreshold: CONTEXT_THRESHOLD_SCHEMA,
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
|
|
208
187
|
const WORKSPACE_SCHEMA: JSONSchema = {
|
|
209
188
|
type: "object",
|
|
210
189
|
additionalProperties: false,
|
|
@@ -362,22 +341,7 @@ function seedBuiltInSettings(): void {
|
|
|
362
341
|
display: { group: "Insights", label: "Insights", description: "Continuous self-improvement instrumentation toggles.", order: 20 },
|
|
363
342
|
});
|
|
364
343
|
|
|
365
|
-
|
|
366
|
-
namespace: "notifications",
|
|
367
|
-
key: "default",
|
|
368
|
-
schema: NOTIFICATIONS_SCHEMA,
|
|
369
|
-
defaults: {
|
|
370
|
-
enabled: true,
|
|
371
|
-
branchLanded: true,
|
|
372
|
-
agentReady: true,
|
|
373
|
-
agentExited: true,
|
|
374
|
-
agentSpawnFailed: true,
|
|
375
|
-
contextThreshold: { enabled: true, advisoryUtilization: 0.7, hardBackstopUtilization: 0.9, bandSize: 0.1 },
|
|
376
|
-
},
|
|
377
|
-
scope: { read: "settings:read:notifications", write: "settings:write:notifications" },
|
|
378
|
-
tier: "server-runtime",
|
|
379
|
-
display: { group: "Notifications", label: "Lifecycle notifications", description: "Relay-driven lifecycle push notifications (#239).", order: 30 },
|
|
380
|
-
});
|
|
344
|
+
registerNotificationSettingDescriptor(NOTIFICATIONS_DEFAULT_KEY);
|
|
381
345
|
|
|
382
346
|
registerSetting({
|
|
383
347
|
namespace: "workspace",
|
|
@@ -481,6 +445,28 @@ export function registerProviderConfigSetting(input: {
|
|
|
481
445
|
});
|
|
482
446
|
}
|
|
483
447
|
|
|
448
|
+
export function registerNotificationSettingDescriptor(key: string = NOTIFICATIONS_DEFAULT_KEY): SettingDescriptor {
|
|
449
|
+
const parsed = parseNotificationSettingsKey(key);
|
|
450
|
+
if (!parsed) throw new Error(`invalid notification setting key "${key}"`);
|
|
451
|
+
const label = parsed.kind === "default"
|
|
452
|
+
? "Lifecycle notifications"
|
|
453
|
+
: parsed.kind === "team"
|
|
454
|
+
? `Team notifications: ${parsed.id}`
|
|
455
|
+
: `Agent notifications: ${parsed.id}`;
|
|
456
|
+
const description = parsed.kind === "default"
|
|
457
|
+
? "Default relay-driven lifecycle push notification policy."
|
|
458
|
+
: "Scoped notification override; unset fields inherit from the less-specific layer.";
|
|
459
|
+
return registerSetting({
|
|
460
|
+
namespace: NOTIFICATIONS_NAMESPACE,
|
|
461
|
+
key,
|
|
462
|
+
schema: NOTIFICATIONS_SETTINGS_SCHEMA,
|
|
463
|
+
defaults: parsed.kind === "default" ? NOTIFICATIONS_CONFIG_DEFAULTS : {},
|
|
464
|
+
scope: { read: "settings:read:notifications", write: "settings:write:notifications" },
|
|
465
|
+
tier: "server-runtime",
|
|
466
|
+
display: { group: "Notifications", label, description, order: parsed.kind === "default" ? 30 : 31 },
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
484
470
|
function ensureSeeded(): void {
|
|
485
471
|
if (!seeded) seedBuiltInSettings();
|
|
486
472
|
}
|
package/src/spawn-command.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { resolveProviderSelection, type ProviderEffort } from "agent-relay-sdk/p
|
|
|
5
5
|
import { workspaceSpawnParams } from "./config-store";
|
|
6
6
|
import { ValidationError } from "./db";
|
|
7
7
|
import { effectiveProviderCatalogList } from "./provider-catalog-store";
|
|
8
|
+
import type { WorkspaceAutoMergePolicy, WorkspaceLandingPolicy } from "./types";
|
|
8
9
|
|
|
9
10
|
/** Resolved provider/model/effort triple — the output shape of every model-resolution wrapper. */
|
|
10
11
|
interface SpawnModelParams {
|
|
@@ -96,6 +97,9 @@ interface BuildSpawnCommandOptions {
|
|
|
96
97
|
prompt?: string;
|
|
97
98
|
systemPromptAppend?: string;
|
|
98
99
|
relayInjectionEvents?: unknown[];
|
|
100
|
+
landStrategy?: WorkspaceLandingPolicy;
|
|
101
|
+
autoMerge?: WorkspaceAutoMergePolicy;
|
|
102
|
+
reviewer?: string;
|
|
99
103
|
/**
|
|
100
104
|
* #413 — when true, the child registering with this spawnRequestId gets a pending reply
|
|
101
105
|
* obligation back to its spawner, so the Stop hook refuses to let it idle until it delivers
|
|
@@ -113,6 +117,8 @@ interface BuildSpawnCommandOptions {
|
|
|
113
117
|
automationRunId?: string;
|
|
114
118
|
orchestratorId?: string;
|
|
115
119
|
requestedVia?: string;
|
|
120
|
+
/** #635 — relay-enriched resume-workspace target. Relayed verbatim to the orchestrator. */
|
|
121
|
+
resumeWorkspace?: { branch: string; mode: "attach" | "branch-from"; worktreePath?: string; workspaceId?: string; baseRef?: string; baseSha?: string };
|
|
116
122
|
|
|
117
123
|
/** Extra params merged last (e.g. Claude-resume fields). */
|
|
118
124
|
extra?: Record<string, unknown>;
|
|
@@ -151,6 +157,9 @@ export function buildSpawnCommand(opts: BuildSpawnCommandOptions): Record<string
|
|
|
151
157
|
...def(opts.prompt, "prompt"),
|
|
152
158
|
...def(opts.systemPromptAppend, "systemPromptAppend"),
|
|
153
159
|
...def(opts.relayInjectionEvents, "relayInjectionEvents"),
|
|
160
|
+
...def(opts.landStrategy, "landStrategy"),
|
|
161
|
+
...def(opts.autoMerge, "autoMerge"),
|
|
162
|
+
...def(opts.reviewer, "reviewer"),
|
|
154
163
|
...def(opts.expectReply, "expectReply"),
|
|
155
164
|
...def(opts.headless, "headless"),
|
|
156
165
|
...def(opts.policyName, "policyName"),
|
|
@@ -159,6 +168,7 @@ export function buildSpawnCommand(opts: BuildSpawnCommandOptions): Record<string
|
|
|
159
168
|
...def(opts.orchestratorId, "orchestratorId"),
|
|
160
169
|
...def(opts.requestedVia, "requestedVia"),
|
|
161
170
|
...def(opts.spawnRequestId, "spawnRequestId"),
|
|
171
|
+
...def(opts.resumeWorkspace, "resumeWorkspace"),
|
|
162
172
|
env: opts.env,
|
|
163
173
|
requestedBy: opts.requestedBy,
|
|
164
174
|
requestedAt: opts.requestedAt ?? Date.now(),
|
package/src/sse.ts
CHANGED
|
@@ -310,6 +310,7 @@ function notificationForMessage(msg: Message): RelayNotification | undefined {
|
|
|
310
310
|
agentId: msg.from,
|
|
311
311
|
threadPeer: msg.from,
|
|
312
312
|
view: "chat",
|
|
313
|
+
...(isRecord(msg.payload?.routing) ? { routing: msg.payload.routing as unknown as RelayNotification["routing"] } : {}),
|
|
313
314
|
createdAt: Date.now(),
|
|
314
315
|
};
|
|
315
316
|
}
|
package/src/validation.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ValidationError } from "./db";
|
|
2
|
-
import { type TokenConstraints, type WorkspaceMetadata, type WorkspaceMode, type WorkspaceProbe, type WorkspaceStatus } from "./types";
|
|
3
|
-
import { VALID_WORKSPACE_MODES, isRecord } from "agent-relay-sdk";
|
|
2
|
+
import { type TokenConstraints, type WorkspaceAutoMergePolicy, type WorkspaceLandingPolicy, type WorkspaceMetadata, type WorkspaceMode, type WorkspaceProbe, type WorkspaceStatus } from "./types";
|
|
3
|
+
import { AUTO_MERGE_POLICIES, VALID_WORKSPACE_MODES, isRecord } from "agent-relay-sdk";
|
|
4
|
+
|
|
5
|
+
const WORKSPACE_LAND_STRATEGIES = ["direct", "pr"] as const;
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Trim + validate an optional string input. Throws `ValidationError` when the
|
|
@@ -164,6 +166,9 @@ export function cleanWorkspaceMetadata(value: unknown, field: string): Workspace
|
|
|
164
166
|
baseSha: cleanString(value.baseSha, `${field}.baseSha`, { max: 80 }),
|
|
165
167
|
status: optionalEnum(value.status, `${field}.status`, VALID_WORKSPACE_STATUSES) as WorkspaceStatus | undefined,
|
|
166
168
|
stewardAgentId: cleanString(value.stewardAgentId, `${field}.stewardAgentId`, { max: 240 }),
|
|
169
|
+
landStrategy: optionalEnum(value.landStrategy, `${field}.landStrategy`, WORKSPACE_LAND_STRATEGIES) as WorkspaceLandingPolicy | undefined,
|
|
170
|
+
autoMerge: optionalEnum(value.autoMerge, `${field}.autoMerge`, AUTO_MERGE_POLICIES) as WorkspaceAutoMergePolicy | undefined,
|
|
171
|
+
reviewer: cleanString(value.reviewer, `${field}.reviewer`, { max: 240 }),
|
|
167
172
|
probe: isRecord(value.probe) ? value.probe as unknown as WorkspaceProbe : undefined,
|
|
168
173
|
};
|
|
169
174
|
}
|
|
@@ -37,9 +37,56 @@ import { requestWorkspaceMerge } from "./workspace-merge";
|
|
|
37
37
|
import { workspaceActiveClaim } from "./workspace-claim";
|
|
38
38
|
import { DIRTY_WORKTREE_LAND_SKIP_REASON, READY_TO_LAND_STATUSES } from "./workspace-phase";
|
|
39
39
|
import { fetchHostMergePreview, workspacePathWithinBase } from "./workspace-probe";
|
|
40
|
-
import type { WorkspaceMergePreview, WorkspaceRecord } from "./types";
|
|
40
|
+
import type { WorkspaceAutoMergePolicy, WorkspaceLandingPolicy, WorkspaceMergePreview, WorkspaceMergeStrategy, WorkspaceRecord } from "./types";
|
|
41
41
|
import { STEWARD_WAKE_COOLDOWN_MS, stewardFallbackTarget, workspaceAutoMergeEnabled } from "./config";
|
|
42
42
|
|
|
43
|
+
const WORKSPACE_LAND_STRATEGIES = ["direct", "pr"] as const;
|
|
44
|
+
const WORKSPACE_AUTO_MERGE_POLICIES = ["on-green", "on-approval", "manual"] as const;
|
|
45
|
+
|
|
46
|
+
type AutoMergeRequestOptions = {
|
|
47
|
+
requestedBy: "auto-merge";
|
|
48
|
+
strategy?: WorkspaceMergeStrategy;
|
|
49
|
+
autoMerge?: WorkspaceAutoMergePolicy;
|
|
50
|
+
reviewer?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function workspaceLandStrategy(metadata: Record<string, unknown>): WorkspaceLandingPolicy | undefined {
|
|
54
|
+
const value = metadata.landStrategy;
|
|
55
|
+
return typeof value === "string" && (WORKSPACE_LAND_STRATEGIES as readonly string[]).includes(value)
|
|
56
|
+
? value as WorkspaceLandingPolicy
|
|
57
|
+
: undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function workspaceAutoMergePolicy(metadata: Record<string, unknown>): WorkspaceAutoMergePolicy | undefined {
|
|
61
|
+
const value = metadata.autoMerge;
|
|
62
|
+
return typeof value === "string" && (WORKSPACE_AUTO_MERGE_POLICIES as readonly string[]).includes(value)
|
|
63
|
+
? value as WorkspaceAutoMergePolicy
|
|
64
|
+
: undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function workspaceReviewer(metadata: Record<string, unknown>): string | undefined {
|
|
68
|
+
const value = metadata.reviewer;
|
|
69
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function autoMergeRequestOptions(ws: WorkspaceRecord): AutoMergeRequestOptions {
|
|
73
|
+
const landStrategy = workspaceLandStrategy(ws.metadata);
|
|
74
|
+
const autoMerge = workspaceAutoMergePolicy(ws.metadata);
|
|
75
|
+
const reviewer = workspaceReviewer(ws.metadata);
|
|
76
|
+
if (landStrategy === "pr") {
|
|
77
|
+
return {
|
|
78
|
+
requestedBy: "auto-merge",
|
|
79
|
+
strategy: "pr",
|
|
80
|
+
...(autoMerge ? { autoMerge } : {}),
|
|
81
|
+
...(reviewer ? { reviewer } : {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (landStrategy === "direct") return { requestedBy: "auto-merge", strategy: "rebase-ff" };
|
|
85
|
+
return getLandingConfig().strategy === "pr"
|
|
86
|
+
? { requestedBy: "auto-merge", ...(autoMerge ? { autoMerge } : {}), ...(reviewer ? { reviewer } : {}) }
|
|
87
|
+
: { requestedBy: "auto-merge", strategy: "rebase-ff" };
|
|
88
|
+
}
|
|
89
|
+
|
|
43
90
|
// Don't re-wake the managed steward for the same workspace more than once per this
|
|
44
91
|
// window — a persistent conflict/behind row would otherwise re-ping every sweep.
|
|
45
92
|
// Wake the managed per-repo steward (issue #167) for a workspace it should handle:
|
|
@@ -246,14 +293,11 @@ async function landCandidates(
|
|
|
246
293
|
continue;
|
|
247
294
|
}
|
|
248
295
|
|
|
249
|
-
// Honor
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
// requestWorkspaceMerge resolves it from the config and defaults autoMerge to "on-green";
|
|
255
|
-
// the default "direct" keeps the historical deterministic rebase-ff (no behavior change).
|
|
256
|
-
const result = requestWorkspaceMerge(ws, getLandingConfig().strategy === "pr" ? { requestedBy: "auto-merge" } : { strategy: "rebase-ff", requestedBy: "auto-merge" });
|
|
296
|
+
// Honor per-spawn land metadata first (#421), then the operator's instance
|
|
297
|
+
// landing.strategy (#419-followup). A spawn override of `pr` becomes an explicit PR land
|
|
298
|
+
// and may carry autoMerge/reviewer; omitted strategy falls back to instance config.
|
|
299
|
+
// The default/direct path keeps the historical deterministic rebase-ff behavior.
|
|
300
|
+
const result = requestWorkspaceMerge(ws, autoMergeRequestOptions(ws));
|
|
257
301
|
if (!result.ok) {
|
|
258
302
|
// 409 = another merge holds this repo's lease this tick; the on-settle re-trigger
|
|
259
303
|
// (routes/commands.ts → scheduleRepoMerge) drains it once the lease frees.
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{r as e}from"./chunk-CilyBKbf.js";import{Cn as t,Ct as n,F as r,Fn as i,Hn as a,It as o,Kn as s,Kt as c,On as l,Pn as u,R as d,Wn as f,a as p,b as m,c as h,dn as g,en as _,fn as v,g as ee,gn as y,hn as b,i as x,in as te,lt as S,m as C,mn as w,ot as ne,qt as re,r as T,rn as E,rr as D,un as O,wt as ie,y as k,z as A}from"./lucide-react-CsPXvXMC.js";import{g as j,r as ae,t as M}from"./store-DsqUEZOf.js";import{E as N,H as P,N as F,P as I,R as L,S as R,V as oe,Z as z,_ as se,d as ce,f as le,k as B,lt as V,q as H,v as ue}from"./display-CWMHll1J.js";import{a as de,i as fe,o as pe,t as U,w as W}from"./index-Bsi2NH0v.js";import{t as me}from"./user-avatar-tomKla2H.js";var G=e(D(),1),he={online:`came online`,offline:`went offline`,shutdown:`shut down`,restarted:`restarted`,"compaction-requested":`compaction requested`,compacting:`compaction started`,compacted:`context compacted`,"compaction-stalled":`compaction stalled — may need manual intervention`,"clear-requested":`clear requested`,"clearing-context":`clear started`,"context-cleared":`context cleared`,"clear-stalled":`context clear stalled — may need manual intervention`,"shutting-down":`shutting down`},ge=new Set(Object.keys(he)),_e=3e3;function K(e){return new Date(e.occurredAt??e.createdAt).getTime()}function ve(e){if(e.kind!==`session`)return null;let t=e.payload?.session;return!t||t.type!==`narration`&&t.type!==`reasoning`&&t.type!==`tool`?null:{id:e.id,kind:t.type,label:typeof t.label==`string`?t.label:void 0,text:e.body,ts:K(e),turnId:typeof t.turnId==`string`?t.turnId:void 0}}function ye(e){if(e.kind!==`session`)return null;let t=e.payload?.session;if(t?.type!==`notice`)return null;let n=typeof t.label==`string`&&t.label?t.label:`compacted`;return{id:`notice-${e.id}`,status:n,timestamp:K(e)}}function be(e){if(e.kind!==`session`)return null;let t=e.payload?.session;if(t?.type!==`relay-injection`)return null;let n=t.relayInjection&&typeof t.relayInjection==`object`&&!Array.isArray(t.relayInjection)?t.relayInjection:{},r=typeof n.category==`string`&&[`memory`,`instructions`,`tools`,`comms`,`knowledge`,`context`].includes(n.category)?n.category:`context`,i=typeof n.timestamp==`number`&&Number.isFinite(n.timestamp)?n.timestamp:K(e),a=n.detail&&typeof n.detail==`object`&&!Array.isArray(n.detail)?n.detail:{};return{id:e.id,category:r,summary:typeof n.summary==`string`&&n.summary.trim()?n.summary:e.body,detail:a,agentId:typeof n.agentId==`string`&&n.agentId?n.agentId:e.from,timestamp:i}}function xe(e,t){let n=e.trim();for(let e of t){let t=e.trim();if(t){if(n===t)break;n.startsWith(`${t}\n\n`)&&(n=n.slice(t.length+2).trimStart())}}return n.trim()||e.trim()}function Se(e){return e.kind===`system`&&e.from===`system`}var Ce=new Set([`agent.ready`,`agent.exited`,`agent.spawn_failed`]);function q(e){let t=new Date(e);return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,`0`)}-${String(t.getDate()).padStart(2,`0`)}`}function we(e){let t=e.filter(e=>ge.has(e.status)).slice().sort((e,t)=>e.timestamp-t.timestamp),n=[];for(let e of t)n.some(t=>t.id===e.id)||n.some(t=>t.status===e.status&&Math.abs(t.timestamp-e.timestamp)<=_e)||n.push(e);return n}function Te(e,t,n,r=[],i){let a=[];for(let e of r){let t=e.entries.reduce((e,t)=>Math.min(e,H(t.originalCreatedAt)),1/0),n=Number.isFinite(t)?t-1:e.importedAt;a.push({ts:n,entry:{type:`import-boundary`,history:e,timestamp:n}});for(let t of e.entries)a.push({ts:H(t.originalCreatedAt),entry:{type:`imported-message`,history:e,entry:t}})}if(n){let e=new Date(n).getTime();Number.isFinite(e)&&a.push({ts:e,entry:{type:`created`,timestamp:e}})}if(i?.spawnedBy){let e=n?new Date(n).getTime():0,t=Number.isFinite(e)?e:0;a.push({ts:t,entry:{type:`agent-action`,direction:`spawned-by`,agentId:i.spawnedBy.agentId,provider:i.spawnedBy.provider,status:`ready`,timestamp:t}})}let o=i?.spawnedBy?.agentId,s=null,c=1/0;for(let t of e){if(t.kind===`session`||Se(t)||N(t)||!(t.from===`user`||o!==void 0&&t.from===o)||!B(t).trim())continue;let e=K(t);e<c&&(c=e,s=t.id)}let l=new Map,u=new Map,d=new Map;for(let t of e){if(t.kind!==`session`)continue;let e=t.payload?.session;if(e?.type===`narration`&&typeof e.turnId==`string`){let n=u.get(e.turnId)??[];n.push({id:t.id,body:t.body.trim()}),u.set(e.turnId,n)}else if(e?.type===`response`&&typeof e.turnId==`string`){let n=l.get(e.turnId)??[];n.push(t.body.trim()),l.set(e.turnId,n)}}let f=new Map;for(let[e,t]of l){let n=(u.get(e)??[]).map(e=>e.body);f.set(e,t.map(e=>xe(e,n)))}for(let t of e){if(t.kind!==`session`)continue;let e=t.payload?.session;if(e?.type!==`response`||typeof e.turnId!=`string`)continue;let n=xe(t.body,(u.get(e.turnId)??[]).map(e=>e.body));n&&n!==t.body&&d.set(t.id,n)}let p=new Set;for(let[e,t]of f){let n=u.get(e)??[];for(let e of t)for(let t=n.length-1;t>=0;t--){let r=n[t];if(!p.has(r.id)&&P(e,r.body)){p.add(r.id);break}}}let m=new Map,h=[];for(let t of e){if(Se(t)){let e=typeof t.payload?.kind==`string`?t.payload.kind:``;if(Ce.has(e)){let n=t.payload,r=typeof n.agentId==`string`?n.agentId:``,i=K(t);if(!r){a.push({ts:i,entry:{type:`notice`,msg:t}});continue}let o=e===`agent.ready`?`ready`:e===`agent.exited`?`exited`:`failed`,s=m.get(r);if(s)s.entry.status=o,typeof n.reason==`string`&&(s.entry.reason=n.reason),s.entry.timestamp=i,s.ts=i;else{let e={ts:i,entry:{type:`agent-action`,direction:`spawned-child`,agentId:r,provider:typeof n.provider==`string`?n.provider:void 0,status:o,reason:typeof n.reason==`string`?n.reason:void 0,timestamp:i}};m.set(r,e),a.push(e)}continue}a.push({ts:K(t),entry:{type:`notice`,msg:t}});continue}let e=ye(t);if(e){h.push(e);continue}let n=ve(t);if(n){if(n.kind===`narration`&&p.has(n.id))continue;a.push({ts:n.ts,entry:{type:`activity`,steps:[n],timestamp:n.ts}});continue}let r=be(t);if(r){a.push({ts:r.timestamp,entry:{type:`relay-injection`,event:r}});continue}if(t.kind===`session`){let e=t.payload?.session;if(e?.type===`response`&&typeof e.turnId==`string`){let e=d.get(t.id);if(e&&e!==t.body){a.push({ts:K(t),entry:{type:`message`,msg:{...t,body:e}}});continue}}}let i=t.id===s?`initial-prompt`:`message`;a.push({ts:K(t),entry:{type:i,msg:t}})}for(let e of we([...t,...h]))a.push({ts:e.timestamp,entry:{type:`status`,event:e}});let g={created:0,date:1,"import-boundary":2,"imported-message":3,"initial-prompt":4,message:4,activity:4,notice:4,"agent-action":4,"relay-injection":4,status:5};a.sort((e,t)=>{let n=e.ts-t.ts;return Math.abs(n)<3e3&&g[e.entry.type]-g[t.entry.type]||n});let _=[],v=``;for(let{ts:e,entry:t}of a){if(t.type===`created`){_.push(t);continue}let n=q(e);if(n!==v&&(_.push({type:`date`,date:n,timestamp:e}),v=n),t.type===`activity`){let e=_[_.length-1];if(e&&e.type===`activity`&&Oe(e,t)){e.steps.push(...t.steps);continue}}_.push(t)}return _}function Ee(e){return e.type===`message`?`msg-${e.msg.id}`:e.type===`initial-prompt`?`initprompt-${e.msg.id}`:e.type===`notice`?`notice-${e.msg.id}`:e.type===`relay-injection`?`relay-injection-${e.event.id}`:e.type===`agent-action`?`agent-action-${e.direction}-${e.agentId}`:e.type===`activity`?`act-${e.steps[0]?.id??e.timestamp}`:e.type===`import-boundary`?`import-${e.history.id}`:e.type===`imported-message`?`import-msg-${e.history.id}-${e.entry.originalMessageId}-${e.entry.position}`:e.type===`status`?`status-${e.event.id}`:e.type===`date`?`date-${e.date}`:`created-${e.timestamp}`}function De(e){return e.type===`message`||e.type===`initial-prompt`||e.type===`notice`?(e.msg.subject?`${e.msg.subject} `:``)+B(e.msg):e.type===`imported-message`?B(e.entry.message):e.type===`activity`?e.steps.map(e=>[e.label,e.text].filter(Boolean).join(` `)).join(`
|
|
2
|
-
`):e.type===`relay-injection`?`${e.event.category} ${e.event.summary}\n${JSON.stringify(e.event.detail)}`:``}function Oe(e,t){let n=e.steps[e.steps.length-1]?.turnId,r=t.steps[0]?.turnId;return n&&r?n===r:!n&&!r}function ke(e){return e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:`${(e/1024/1024).toFixed(1)} MB`}function Ae(e,t){if(t.startsWith(`/`))return t;let n=`${e.replace(/\/+$/,``)}/${t}`.split(`/`),r=[];for(let e of n)!e||e===`.`||(e===`..`?r.pop():r.push(e));return`/`+r.join(`/`)}function je(e){let t=e.replace(/\/+$/,``),n=t.lastIndexOf(`/`);return n>0?t.slice(0,n):`/`}function Me(e,t){return e.filter(e=>e.status===`online`&&L(t,e.baseDir)).sort((e,t)=>F(t.baseDir).length-F(e.baseDir).length)[0]||e.find(e=>e.status===`online`)}function Ne(){return typeof window<`u`&&window.matchMedia(`(min-width: 1280px)`).matches}function Pe(){return typeof window<`u`&&!!window.getSelection()?.toString()}function Fe(e){let t=e.payload?.attachments;return Array.isArray(t)?t.filter(e=>!!(e&&typeof e==`object`&&typeof e.artifactId==`string`)):[]}var Ie=[`👍`,`❤️`,`✅`,`👀`];function Le(e){let t=new Map;for(let n of e.reactions||[]){let e=I(n.emoji),r=t.get(e)??{emoji:e,count:0,mine:!1,actors:[]};r.count+=1,r.mine=r.mine||n.actorId===`user`,r.actors.push(n.actorId),t.set(e,r)}return[...t.values()].sort((e,t)=>t.count-e.count||e.emoji.localeCompare(t.emoji))}function J(e){return N(e)}function Re(e,t){let n=e.readBy||[],r=e.resolvedToAgent||t||e.to,i=r?n.includes(r):!1,a=n.some(e=>e!==V);if(i||a){let e=i?r:n.find(e=>e!==V);return{label:`read`,title:e?`Read by ${e}`:`Read by agent`,tone:`ok`,icon:y}}if(e.deliveryStatus===`delivered`)return{label:`delivered`,title:`Delivered to agent inbox`,tone:`ok`,icon:y};if(e.deliveryStatus===`queued`)return{label:`queued`,title:`Queued until the target agent is available`,tone:`warn`,icon:_};if(e.deliveryStatus===`failed`||e.deliveryStatus===`dead`){let t=e.deliveryPoisonReason||e.deliveryLastError||e.deliveryStatus;return{label:e.deliveryStatus,title:t,tone:`danger`,icon:O}}return{label:`sent`,title:`Accepted by Relay`,tone:`muted`,icon:b}}function ze(e){let t=H(e.createdAt);return(e.reactions||[]).reduce((e,t)=>Math.max(e,H(t.updatedAt||t.createdAt)),t)}function Be(e){return e?e.messages.reduce((e,t)=>Math.max(e,ze(t)),0):0}function Ve(e,t,n){return(e-t)*n}function He(e){if(!e)return 0;let t=0;for(let n of e.messages)R(n)&&(t=Math.max(t,H(n.createdAt)));return t}function Y(e){return new Date(e).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`})}function Ue(e){for(let t of e)t.previewUrl&&URL.revokeObjectURL(t.previewUrl)}function We(e,t,n,r){if(e?.teamId)return{teamId:e.teamId};let i=[...t];n&&!i.some(e=>e.id===n.id)&&i.push(n);let a=i.filter(e=>e.threadId).reverse(),o=a.find(e=>!!r[ae(e.threadId,e.channel)])??a[0];return o?.threadId?{threadId:o.threadId,...o.channel?{channel:o.channel}:{}}:null}var X=f(),Ge={online:`text-emerald-400`,offline:`text-zinc-400`,shutdown:`text-zinc-400`,restarted:`text-blue-400`,"compaction-requested":`text-zinc-400`,compacting:`text-amber-400`,compacted:`text-amber-400`,"compaction-stalled":`text-red-400`,"clear-requested":`text-zinc-400`,"clearing-context":`text-amber-400`,"context-cleared":`text-amber-400`,"clear-stalled":`text-red-400`,"shutting-down":`text-zinc-400`};function Ke(e){let t=new Date,[n,r,i]=e.split(`-`).map(Number),a=new Date(n,r-1,i);if(e===q(t.getTime()))return`Today`;let o=new Date(t);return o.setDate(o.getDate()-1),e===q(o.getTime())?`Yesterday`:a.toLocaleDateString(void 0,{weekday:`short`,month:`short`,day:`numeric`,year:a.getFullYear()===t.getFullYear()?void 0:`numeric`})}function qe({event:e}){return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsxs)(`div`,{className:`flex items-center gap-1.5 text-xs text-muted-foreground`,children:[(0,X.jsx)(E,{className:a(`w-2 h-2 fill-current`,Ge[e.status]||`text-zinc-400`)}),(0,X.jsx)(`span`,{children:he[e.status]||e.status}),(0,X.jsx)(`span`,{className:`text-muted-foreground/60`,children:Y(e.timestamp)})]}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}var Je={"branch.landed":n,"context.warning":C,"agent.context_threshold":C,"agent.resume_budget_exhausted":C,"agent.rate_limit_resume":A},Ye={"branch.landed":`branch landed`,"context.warning":`context warning`,"agent.context_threshold":`context threshold reached`};function Xe(e){return e&&(e.includes(`.`)?e.slice(e.indexOf(`.`)+1):e).replace(/[._-]+/g,` `).trim()||`system signal`}function Z({agentId:e,fallbackLabel:t,className:n,noIcon:r}){let i=M(t=>t.agentsById[e])||null,o=M(e=>e.openInboxThread),s=M(e=>e.switchView),c=i?ce(i):t||e;return(0,X.jsxs)(`button`,{type:`button`,className:a(`inline-flex max-w-full items-center gap-1 rounded px-1 text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground`,n),onClick:t=>{t.stopPropagation(),o(e),s(`chat`)},title:`Open chat for ${c}`,children:[!r&&(0,X.jsx)(W,{agent:i,className:`w-4 h-4 text-[10px]`}),(0,X.jsx)(`span`,{className:`max-w-[160px] truncate`,children:c})]})}function Ze({msg:e}){let t=e.payload??{},r=typeof t.sha==`string`?t.sha:``,i=r?r.slice(0,7):``,a=typeof t.author==`string`?t.author:``,o=typeof t.branch==`string`?t.branch:``,s=typeof t.base==`string`?t.base:``,c=t.pushed;return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsxs)(`div`,{className:`flex max-w-full flex-wrap items-center justify-center gap-x-1.5 gap-y-0.5 rounded-full border border-emerald-600/35 bg-emerald-500/10 px-2.5 py-1 text-xs text-emerald-700 dark:border-emerald-500/25 dark:text-emerald-300/90`,children:[(0,X.jsx)(n,{className:`h-3 w-3 shrink-0 text-emerald-600 dark:text-emerald-400`}),(0,X.jsx)(`span`,{className:`text-emerald-700 dark:text-emerald-300`,children:`branch landed`}),o&&s&&(0,X.jsxs)(`span`,{className:`font-mono text-emerald-700/80 dark:text-emerald-200/70`,title:`${o} → ${s}`,children:[o,(0,X.jsx)(`span`,{className:`opacity-50`,children:` → `}),s]}),i&&(0,X.jsx)(`span`,{className:`rounded bg-emerald-500/15 px-1 font-mono text-emerald-800 dark:text-emerald-200`,title:r,children:i}),a&&(0,X.jsxs)(`span`,{className:`inline-flex items-center gap-1 text-emerald-700/80 dark:text-emerald-300/80`,children:[(0,X.jsx)(`span`,{className:`opacity-70`,children:`by`}),(0,X.jsx)(Z,{agentId:a,className:`text-emerald-700 hover:text-emerald-900 dark:text-emerald-300/90 dark:hover:text-emerald-100`})]}),c===!1&&(0,X.jsx)(`span`,{className:`text-amber-600 dark:text-amber-300/80`,title:`Local-only land — not pushed to origin`,children:`· local only`}),(0,X.jsx)(`span`,{className:`text-emerald-700/60 dark:text-emerald-300/50`,children:Y(e.occurredAt??e.createdAt)})]}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function Qe({msg:e}){let t=typeof e.payload?.kind==`string`?e.payload.kind:``;if(t===`branch.landed`)return(0,X.jsx)(Ze,{msg:e});let n=Je[t]??r,i=Ye[t]??Xe(t);return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsxs)(`div`,{className:`flex items-center gap-1.5 rounded-full border border-border bg-muted/30 px-2.5 py-1 text-xs text-muted-foreground`,children:[(0,X.jsx)(n,{className:`h-3 w-3`}),(0,X.jsx)(`span`,{children:i}),(0,X.jsx)(`span`,{className:`text-muted-foreground/60`,children:Y(e.occurredAt??e.createdAt)})]}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}var $e={"spawned-child":{ready:`spawned`,exited:`spawned · exited`,failed:`spawn failed`},"spawned-by":{ready:`spawned by`,exited:`spawned by`,failed:`spawned by`}},et={"spawned-child-ready":d,"spawned-child-exited":ne,"spawned-child-failed":O,"spawned-by-ready":i,"spawned-by-exited":i,"spawned-by-failed":i},tt={"spawned-child-ready":`text-emerald-400`,"spawned-child-exited":`text-zinc-400`,"spawned-child-failed":`text-red-400`,"spawned-by-ready":`text-blue-400`,"spawned-by-exited":`text-blue-400`,"spawned-by-failed":`text-blue-400`};function nt({entry:e}){let t=`${e.direction}-${e.status}`,n=$e[e.direction]?.[e.status]??e.direction,i=et[t]??r,o=tt[t]??`text-muted-foreground`,s=e.status===`failed`;return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsxs)(`div`,{className:a(`flex items-center gap-1.5 rounded-full border bg-muted/30 px-2.5 py-1 text-xs`,s?`border-red-500/30`:`border-border`),children:[(0,X.jsx)(i,{className:a(`h-3 w-3 shrink-0`,o)}),(0,X.jsx)(`span`,{className:`text-muted-foreground`,children:n}),(0,X.jsx)(Z,{agentId:e.agentId}),e.reason&&s&&(0,X.jsxs)(`span`,{className:`text-red-400/80 max-w-[120px] truncate`,title:e.reason,children:[`— `,e.reason]}),(0,X.jsx)(`span`,{className:`text-muted-foreground/60 shrink-0`,children:Y(e.timestamp)})]}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}var rt={memory:t,instructions:o,tools:p,comms:r,knowledge:l,context:re},it={memory:`border-emerald-500/25 bg-emerald-500/10 text-emerald-300`,instructions:`border-sky-500/25 bg-sky-500/10 text-sky-300`,tools:`border-amber-500/25 bg-amber-500/10 text-amber-300`,comms:`border-violet-500/25 bg-violet-500/10 text-violet-300`,knowledge:`border-cyan-500/25 bg-cyan-500/10 text-cyan-300`,context:`border-zinc-500/25 bg-zinc-500/10 text-zinc-300`};function at(e){try{return JSON.stringify(e,null,2)}catch{return String(e)}}function ot({entry:e}){let[t,n]=(0,G.useState)(!1),r=e.event,i=rt[r.category]??k;return(0,X.jsx)(`div`,{className:`flex justify-center py-2 my-1`,children:(0,X.jsxs)(`div`,{className:a(`w-full max-w-[85%] md:max-w-[75%] rounded-lg border px-2.5 py-1.5 text-xs shadow-sm`,it[r.category]??it.context),children:[(0,X.jsxs)(`button`,{type:`button`,className:`flex w-full min-w-0 items-center gap-1.5 text-left`,onClick:()=>n(e=>!e),children:[(0,X.jsx)(i,{className:`h-3.5 w-3.5 shrink-0`}),(0,X.jsx)(`span`,{className:`shrink-0 font-medium`,children:r.category}),(0,X.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-foreground/80`,children:r.summary}),(0,X.jsx)(`span`,{className:`shrink-0 text-muted-foreground/60`,children:Y(r.timestamp)}),(0,X.jsx)(v,{className:a(`h-3.5 w-3.5 shrink-0 transition-transform`,t&&`rotate-90`)})]}),t&&(0,X.jsx)(`pre`,{className:`mt-2 max-h-72 overflow-auto rounded-md border border-border/60 bg-background/75 p-2 font-mono text-[11px] leading-relaxed text-muted-foreground whitespace-pre-wrap break-words`,children:at(r.detail)})]})})}function st({blockedLabel:e,onInterrupt:t}){return e?(0,X.jsx)(`div`,{className:`flex justify-start mb-3 animate-in fade-in slide-in-from-bottom-1 duration-150`,children:(0,X.jsxs)(`div`,{className:`flex h-9 items-center gap-2 rounded-2xl rounded-bl-sm bg-card ring-1 ring-red-500/30 px-4`,children:[(0,X.jsx)(O,{className:`w-3.5 h-3.5 text-red-400`}),(0,X.jsxs)(`span`,{className:`text-xs text-red-300`,children:[`blocked: `,e]})]})}):(0,X.jsx)(`div`,{className:`flex justify-start mb-3 animate-in fade-in slide-in-from-bottom-1 duration-150`,children:(0,X.jsxs)(`div`,{className:`flex h-9 items-center gap-2 rounded-2xl rounded-bl-sm bg-card ring-1 ring-foreground/10 px-4`,children:[(0,X.jsxs)(`div`,{className:`flex items-center gap-1`,children:[(0,X.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_infinite]`}),(0,X.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_0.2s_infinite]`}),(0,X.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_0.4s_infinite]`})]}),(0,X.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`working`}),t&&(0,X.jsxs)(`button`,{onClick:t,title:`Interrupt`,className:`ml-1 flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs text-muted-foreground hover:text-red-300 hover:bg-red-500/10 transition-colors`,children:[(0,X.jsx)(te,{className:`w-3.5 h-3.5`}),`Stop`]})]})})}var Q=`agent-relay:chat-hide-tools`,$=(()=>{let e=new Set,t=(()=>{try{return window.localStorage.getItem(Q)===`on`}catch{return!1}})(),n=()=>e.forEach(e=>e());return typeof window<`u`&&window.addEventListener(`storage`,e=>{e.key===Q&&(t=e.newValue===`on`,n())}),{subscribe(t){return e.add(t),()=>e.delete(t)},get:()=>t,toggle(){t=!t;try{window.localStorage.setItem(Q,t?`on`:`off`)}catch{}n()}}})();function ct(){return[(0,G.useSyncExternalStore)($.subscribe,$.get,()=>!1),$.toggle]}function lt({steps:e,showReasoning:n,issueRepo:r}){let[i,o]=ct(),s=n?e:e.filter(e=>e.kind!==`reasoning`);if(!s.length)return null;let c=s.filter(e=>e.kind===`tool`).length;return(0,X.jsx)(`div`,{className:`flex justify-start mb-2`,children:(0,X.jsxs)(`div`,{className:`max-w-[85%] md:max-w-[75%] min-w-0 space-y-1.5`,children:[s.map(e=>e.kind===`narration`?(0,X.jsx)(U,{text:e.text,issueRepo:r,className:`text-sm leading-relaxed break-words text-foreground/90 min-w-0`},e.id):e.kind===`reasoning`?(0,X.jsxs)(`div`,{className:`flex items-start gap-1.5 text-xs leading-relaxed text-muted-foreground/80`,children:[(0,X.jsx)(t,{className:`w-3.5 h-3.5 mt-0.5 shrink-0 opacity-70`}),(0,X.jsx)(U,{text:e.text,issueRepo:r,className:`italic break-words min-w-0`})]},e.id):i?null:(0,X.jsxs)(`div`,{className:`flex items-start gap-1.5 text-xs leading-relaxed text-muted-foreground`,children:[(0,X.jsx)(ee,{className:`w-3 h-3 mt-0.5 shrink-0 opacity-70`}),(0,X.jsxs)(`span`,{className:`min-w-0 break-words`,children:[(0,X.jsx)(`span`,{className:`font-medium`,children:e.label||`tool`}),e.text&&e.text.trim()&&e.text.trim()!==(e.label??``).trim()?(0,X.jsxs)(`span`,{className:`opacity-70`,children:[` — `,e.text]}):null]})]},e.id)),c>0&&(0,X.jsxs)(`button`,{onClick:o,className:`flex items-center gap-1 text-[11px] text-muted-foreground/50 hover:text-muted-foreground transition-colors text-left`,children:[(0,X.jsx)(v,{className:a(`w-3 h-3 shrink-0 transition-transform`,!i&&`rotate-90`)}),(0,X.jsx)(`span`,{children:i?`show ${c} tool step${c===1?``:`s`}`:`hide tool steps`})]})]})})}function ut({date:e}){return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-3 py-3 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsx)(`span`,{className:`text-xs font-medium text-muted-foreground`,children:Ke(e)}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function dt({timestamp:e}){let t=new Date(e).toLocaleString(void 0,{month:`short`,day:`numeric`,year:`numeric`,hour:`2-digit`,minute:`2-digit`});return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsxs)(`span`,{className:`text-xs text-muted-foreground/60`,children:[`created `,t]}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function ft({history:e}){let t=e.sourceAgentLabel||e.sourcePeerId,n=e.entries.length;return(0,X.jsxs)(`div`,{className:`flex items-center justify-center gap-2 py-2 my-1`,children:[(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`}),(0,X.jsxs)(`div`,{className:`flex items-center gap-1.5 rounded-full border border-dashed border-border bg-muted/30 px-2 py-1 text-[11px] text-muted-foreground`,children:[(0,X.jsx)(ie,{className:`h-3 w-3`}),(0,X.jsxs)(`span`,{children:[`forked history from `,t]}),(0,X.jsxs)(`span`,{className:`text-muted-foreground/60`,children:[n,` `,n===1?`entry`:`entries`]})]}),(0,X.jsx)(`div`,{className:`h-px flex-1 bg-border`})]})}function pt({entry:e,sourceLabel:t,issueRepo:n}){let r=e.message,i=B(r),o=e.originalFrom===V;return(0,X.jsx)(`div`,{className:a(`flex mb-3 opacity-80`,o?`justify-end`:`justify-start`),children:(0,X.jsx)(`div`,{className:`max-w-[85%] md:max-w-[75%]`,children:(0,X.jsxs)(`div`,{className:a(`rounded-2xl border border-dashed px-3.5 py-2 text-sm select-text`,o?`border-primary/25 bg-primary/10 text-foreground rounded-br-sm`:`border-border bg-card/70 rounded-bl-sm`),children:[(0,X.jsxs)(`div`,{className:a(`mb-1 text-[10px] uppercase tracking-normal`,o?`text-primary/70`:`text-muted-foreground`),children:[`forked history · `,o?`you`:t||e.originalFrom]}),(0,X.jsx)(U,{text:i,rawBody:r.body,issueRepo:n,className:`leading-relaxed`}),(0,X.jsxs)(`p`,{className:a(`mt-1 flex items-center justify-end gap-1.5 text-xs`,o?`text-primary/60`:`text-muted-foreground`),children:[(0,X.jsxs)(`span`,{className:`opacity-40`,children:[`#`,e.originalMessageId]}),(0,X.jsx)(`span`,{children:Y(e.originalCreatedAt)})]})]})})})}function mt({msg:e,issueRepo:t}){let n=B(e),r=Y(e.occurredAt??e.createdAt);return(0,X.jsx)(`div`,{"data-msg-id":e.id,className:`mb-3`,children:(0,X.jsxs)(`div`,{className:`rounded-xl border border-primary/30 bg-primary/[0.06] px-3.5 py-2.5 shadow-sm`,children:[(0,X.jsxs)(`div`,{className:`mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-primary/80`,children:[(0,X.jsx)(k,{className:`h-3.5 w-3.5`}),(0,X.jsx)(`span`,{children:`Initial prompt`}),(0,X.jsx)(`span`,{className:`ml-auto text-[10px] font-normal normal-case text-muted-foreground/60`,children:r})]}),(0,X.jsx)(U,{text:n,rawBody:e.body,issueRepo:t,className:`text-sm leading-relaxed text-foreground`})]})})}function ht({scrollEl:e,timeline:t}){let[n,r]=(0,G.useState)(!0),[i,o]=(0,G.useState)(null),[s,c]=(0,G.useState)(null),[l,u]=(0,G.useState)(!1),d=(0,G.useMemo)(()=>{for(let e=t.length-1;e>=0;e--){let n=t[e];if(!(!n||n.type!==`message`&&n.type!==`initial-prompt`)&&n.msg.from===`user`)return{id:n.msg.id,body:B(n.msg)}}return null},[t]);if((0,G.useEffect)(()=>{if(!d||!e){r(!0);return}let t=null,n=null,i=()=>{let i=e.querySelector(`[data-msg-id="${d.id}"]`);if(!i){t?.disconnect(),t=null,n=null,r(!1);return}let a=i.getBoundingClientRect(),o=e.getBoundingClientRect();u(a.top<o.top),i!==n&&(t?.disconnect(),n=i,t=new IntersectionObserver(([e])=>{e&&r(e.isIntersecting)},{root:e,threshold:0}),t.observe(i))};return i(),e.addEventListener(`scroll`,i,{passive:!0}),()=>{t?.disconnect(),e.removeEventListener(`scroll`,i)}},[d?.id,e]),n||!d||!d.body.trim())return null;let f=i?.id===d.id?i.value:!1,p=s?.id===d.id?s.value:!1;function m(){!e||!d||e.querySelector(`[data-msg-id="${d.id}"]`)?.scrollIntoView({behavior:`smooth`,block:`center`})}let h=l?`↑`:`↓`;return p?(0,X.jsx)(`div`,{className:`flex shrink-0 bg-card/45 border-b border-border`,children:(0,X.jsxs)(`button`,{type:`button`,className:`w-full flex items-center justify-center gap-1.5 py-1 text-[11px] text-primary/60 hover:text-primary transition-colors`,onClick:()=>c({id:d.id,value:!1}),children:[(0,X.jsx)(`span`,{children:`Your prompt`}),(0,X.jsx)(w,{className:`w-3 h-3`})]})}):(0,X.jsxs)(`section`,{className:`flex shrink-0 flex-col border-b border-border bg-card/45 px-3 py-2`,children:[(0,X.jsxs)(`div`,{className:`flex items-center gap-2 text-[11px] text-muted-foreground mb-0.5`,children:[(0,X.jsx)(`span`,{className:`font-medium text-primary/70`,children:`Your prompt`}),(0,X.jsxs)(`button`,{type:`button`,className:`ml-auto hover:text-foreground transition-colors`,onClick:m,children:[h,` scroll to original`]}),(0,X.jsx)(`button`,{type:`button`,className:`hover:text-foreground transition-colors`,onClick:()=>c({id:d.id,value:!0}),title:`Minimize`,children:(0,X.jsx)(g,{className:`w-3 h-3`})})]}),(0,X.jsx)(`p`,{className:a(`text-sm text-foreground leading-snug cursor-pointer`,!f&&`line-clamp-2`),onClick:()=>o({id:d.id,value:!f}),children:d.body})]})}function gt(){return(0,G.useSyncExternalStore)(e=>j.subscribe(e),()=>j.getPlayingKey(),()=>null)}function _t({msg:e,peer:t}){return(0,X.jsx)(mt,{msg:e,issueRepo:M(e=>e.agentsById[t]?.issueRepo)})}function vt({agent:e,onClick:t}){let n=!!(t&&e);return(0,X.jsx)(`button`,{type:`button`,title:e?e.label||e.name||e.id:void 0,onClick:t,disabled:!n,className:a(`shrink-0 self-end transition-opacity`,n?`cursor-pointer hover:opacity-80`:`cursor-default opacity-60`),children:(0,X.jsx)(W,{agent:e})})}function yt({attachment:e,outbound:t}){let n=e.kind===`image`,r=e.title||e.artifactId,[i,l]=(0,G.useState)(null),[u,d]=(0,G.useState)(null),[f,p]=(0,G.useState)(!1),[m,h]=(0,G.useState)(!1);(0,G.useEffect)(()=>{let t=!1,n=null;async function r(){p(!0),d(null),l(null);try{let r=await s(`/artifacts/${encodeURIComponent(e.artifactId)}/content`);if(t)return;n=URL.createObjectURL(r),l(n)}catch(e){t||d(e.message)}finally{t||p(!1)}}return r(),()=>{t=!0,n&&URL.revokeObjectURL(n)}},[e.artifactId]);function g(){if(!i)return;let e=document.createElement(`a`);e.href=i,e.download=r,document.body.appendChild(e),e.click(),e.remove()}return n?(0,X.jsxs)(X.Fragment,{children:[(0,X.jsx)(`button`,{type:`button`,onClick:()=>i&&h(!0),disabled:!i,className:`group/img mt-2 block overflow-hidden rounded-md border border-foreground/10 bg-black/10 text-left disabled:cursor-default relative`,children:i?(0,X.jsxs)(X.Fragment,{children:[(0,X.jsx)(`img`,{src:i,alt:r,loading:`lazy`,className:`block max-h-72 max-w-full object-contain`}),(0,X.jsx)(`span`,{className:`absolute inset-0 flex items-center justify-center bg-black/0 group-hover/img:bg-black/30 transition-colors`,children:(0,X.jsx)(T,{className:`w-6 h-6 text-white opacity-0 group-hover/img:opacity-100 transition-opacity drop-shadow-md`})})]}):(0,X.jsxs)(`span`,{className:a(`flex min-h-20 items-center gap-2 px-3 py-2 text-xs`,u?`text-red-400`:`text-muted-foreground`),children:[f?(0,X.jsx)(S,{className:`h-4 w-4 animate-spin`}):(0,X.jsx)(O,{className:`h-4 w-4`}),u||`Loading image`]})}),i&&(0,X.jsx)(fe,{open:m,onOpenChange:h,children:(0,X.jsxs)(de,{className:`max-w-[95vw] max-h-[95vh] w-auto p-0 bg-black/95 border-zinc-800 gap-0 overflow-hidden`,showCloseButton:!1,children:[(0,X.jsx)(pe,{className:`sr-only`,children:r}),(0,X.jsxs)(`div`,{className:`relative flex items-center justify-center min-h-[200px]`,children:[(0,X.jsx)(`img`,{src:i,alt:r,className:`max-w-[93vw] max-h-[90vh] object-contain`}),(0,X.jsxs)(`div`,{className:`absolute top-2 right-2 flex gap-1`,children:[(0,X.jsx)(`button`,{type:`button`,onClick:g,className:`rounded-md bg-black/60 hover:bg-black/80 p-2 text-white transition-colors`,title:`Download`,children:(0,X.jsx)(c,{className:`w-4 h-4`})}),(0,X.jsx)(`button`,{type:`button`,onClick:()=>h(!1),className:`rounded-md bg-black/60 hover:bg-black/80 p-2 text-white transition-colors`,title:`Close`,children:(0,X.jsx)(x,{className:`w-4 h-4`})})]})]})]})})]}):(0,X.jsxs)(`button`,{type:`button`,onClick:g,disabled:!i,className:a(`mt-2 flex w-full items-center gap-2 rounded-md border px-2.5 py-2 text-left text-xs transition-colors disabled:cursor-default disabled:opacity-70`,t?`border-primary-foreground/20 bg-primary-foreground/10 hover:bg-primary-foreground/15`:`border-border bg-background hover:bg-muted`),children:[f?(0,X.jsx)(S,{className:`h-4 w-4 shrink-0 animate-spin`}):u?(0,X.jsx)(O,{className:`h-4 w-4 shrink-0 text-red-400`}):(0,X.jsx)(o,{className:`h-4 w-4 shrink-0`}),(0,X.jsx)(`span`,{className:a(`min-w-0 flex-1 truncate`,u&&`text-red-400`),children:u||r}),(0,X.jsx)(c,{className:`h-3.5 w-3.5 shrink-0`})]})}function bt({open:e,onToggle:t,onReact:n}){return(0,X.jsxs)(`div`,{className:`relative shrink-0`,children:[(0,X.jsx)(`button`,{type:`button`,title:`Add reaction`,onClick:t,className:a(`inline-flex h-5 w-6 items-center justify-center rounded-full border border-border bg-popover text-muted-foreground shadow-sm transition hover:bg-muted hover:text-foreground`,e?`opacity-100`:`opacity-0 pointer-events-none md:group-hover/msg:opacity-100 md:group-hover/msg:pointer-events-auto`),children:(0,X.jsx)(m,{className:`h-3 w-3`})}),e&&(0,X.jsx)(`div`,{className:`absolute bottom-full left-0 z-30 mb-1 flex gap-0.5 rounded-full border border-border bg-popover p-0.5 shadow-md`,children:Ie.map(e=>(0,X.jsx)(`button`,{type:`button`,title:`React ${e}`,onClick:()=>n(e),className:`inline-flex h-6 w-7 items-center justify-center rounded-full text-xs transition-colors hover:bg-muted`,children:e},e))})]})}function xt({msg:e,peer:t}){let[n,r]=(0,G.useState)(!1),o=M(e=>e.agentsById),s=M(e=>e.openAgentDetail),c=e.from===t,l=(c?e.to:e.from)||``,d=o[c?t:l]?.issueRepo??o[t]?.issueRepo,f=!l||l===`broadcast`||l.includes(`:`),p=B(e),m=Y(e.occurredAt??e.createdAt),h=c?u:i,g=o[e.from??``]??null;function _(e){e.stopPropagation(),g&&s(g)}return(0,X.jsx)(`div`,{"data-msg-id":e.id,className:`flex justify-center mb-1.5`,children:(0,X.jsxs)(`div`,{className:`w-full max-w-[94%] rounded-md border border-border/50 bg-muted/15 px-2.5 py-1`,children:[(0,X.jsxs)(`div`,{className:`flex items-center gap-1.5 text-xs cursor-pointer select-none`,onClick:()=>r(e=>!e),children:[(0,X.jsx)(v,{className:a(`h-3 w-3 shrink-0 text-muted-foreground/60 transition-transform`,n&&`rotate-90`)}),(0,X.jsx)(`button`,{type:`button`,title:g?g.label||g.name||g.id:void 0,disabled:!g,onClick:_,className:a(`shrink-0 transition-opacity`,g?`cursor-pointer hover:opacity-80`:`cursor-default opacity-60`),children:(0,X.jsx)(W,{agent:g,className:`!w-4 !h-4 !rounded-sm opacity-70`})}),(0,X.jsx)(h,{className:`h-3 w-3 shrink-0 text-muted-foreground/70`}),(0,X.jsxs)(`span`,{className:`shrink-0 text-muted-foreground`,children:[`Message `,c?`to`:`from`]}),f?(0,X.jsx)(`span`,{className:`shrink-0 font-medium text-muted-foreground`,children:le(l,o)}):(0,X.jsx)(Z,{agentId:l,className:`shrink-0 px-0.5 text-foreground/80`,noIcon:!0}),!n&&(0,X.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-muted-foreground/50`,children:p}),(0,X.jsx)(`span`,{className:`ml-auto shrink-0 text-muted-foreground/40`,children:m})]}),n&&(0,X.jsx)(`div`,{className:`mt-1.5 pl-5`,children:(0,X.jsx)(U,{text:p,rawBody:e.body,issueRepo:d,className:`text-sm leading-relaxed text-foreground/90`})})]})})}var St=(0,G.memo)(function({msg:e,peer:t,showAvatar:n=!1,onOpenReferencedPath:r,onPreviewReferencedPath:i,onPreviewReferencedPathEnd:o}){if(!z(e.from)&&!z(e.to))return(0,X.jsx)(xt,{msg:e,peer:t});let s=e.kind===`session`?e.payload?.session:void 0;if(s?.type===`prompt`&&s?.origin===`provider`)return(0,X.jsx)(_t,{msg:e,peer:t});let c=z(e.from),l=M(e=>e.reactToMessage),u=M(e=>e.voiceTtsEnabled),d=gt(),f=M(e=>{let n=e.agentsById[t]?.meta?.cwd;return typeof n==`string`?n:``}),p=M(e=>e.agentsById[t]?.issueRepo),m=M(e=>e.agentsById[t]??null),g=M(e=>e.usersById),_=M(e=>e.users.length>1),v=c?oe(e.from,g):void 0,ee=M(e=>e.openAgentDetail),[y,b]=(0,G.useState)(!1),x=(0,G.useRef)(null),S=(0,G.useRef)(null),C=(0,G.useRef)(!1),w=B(e),ne=Y(e.occurredAt??e.createdAt),re=Fe(e),T=Le(e),E=c?Re(e,t):null,D=E?.icon,O=d===String(e.id),ie=u&&!c&&!J(e)&&w.trim().length>0;function k(t){t.stopPropagation(),O?j.bargeIn():j.speak(e.body,String(e.id))}(0,G.useEffect)(()=>{if(!y)return;function e(e){Pe()||x.current&&!x.current.contains(e.target)&&b(!1)}return document.addEventListener(`mousedown`,e),document.addEventListener(`touchstart`,e),()=>{document.removeEventListener(`mousedown`,e),document.removeEventListener(`touchstart`,e)}},[y]);function A(e){return Ae(f||`/`,e)}function ae(e,t){let n=A(e);r?.(n,t)}function N(e,t,n){let r=A(e);i?.(r,t,n)}function P(t){l(e,t),b(!1)}function F(e){e.button===0&&(S.current={x:e.clientX,y:e.clientY},C.current=!1)}function I(e){let t=S.current;t&&(Math.abs(e.clientX-t.x)>3||Math.abs(e.clientY-t.y)>3)&&(C.current=!0)}function L(t){if(!(J(e)||c)&&!t.target.closest(`a,button,input,textarea,select,[role="button"]`)){if(C.current||Pe()){C.current=!1;return}b(e=>!e)}}let R=n&&!J(e);function ce(){!c&&m&&ee(m)}return(0,X.jsxs)(`div`,{"data-msg-id":e.id,className:a(`group/msg flex mb-3 items-end`,c?`justify-end`:`justify-start`,R&&`gap-1.5`),children:[!c&&R&&(0,X.jsx)(vt,{agent:m,onClick:ce}),(0,X.jsxs)(`div`,{ref:x,className:a(`relative`,R?`max-w-[calc(85%-2rem)] md:max-w-[calc(75%-2rem)]`:`max-w-[85%] md:max-w-[75%]`),children:[ie&&(0,X.jsx)(`button`,{type:`button`,title:O?`Stop playback`:`Play aloud`,onClick:k,className:a(`absolute -top-2 -right-2 z-20 inline-flex h-6 w-6 items-center justify-center rounded-full border bg-popover shadow-sm transition`,O?`border-primary/50 text-primary opacity-100`:`border-border text-muted-foreground opacity-70 hover:bg-muted hover:text-foreground hover:opacity-100 [@media(hover:hover)]:md:opacity-0 [@media(hover:hover)]:md:group-hover/msg:opacity-100`),children:O?(0,X.jsx)(te,{className:`h-3.5 w-3.5 animate-pulse`}):(0,X.jsx)(h,{className:`h-3.5 w-3.5`})}),(0,X.jsxs)(`div`,{className:a(`rounded-2xl px-3.5 py-2 text-sm select-text`,!c&&!J(e)&&`cursor-pointer`,c?`bg-primary text-primary-foreground rounded-br-sm`:`bg-card ring-1 ring-foreground/10 rounded-bl-sm`),onPointerDown:F,onPointerMove:I,onClick:L,children:[c&&_&&(0,X.jsx)(`p`,{className:a(`text-[11px] font-semibold mb-0.5 text-right`,se(e.from)),children:ue(e.from,g)}),e.subject&&(0,X.jsx)(`p`,{className:a(`text-xs font-semibold mb-1`,c?`text-primary-foreground/70`:`text-muted-foreground`),children:e.subject}),(0,X.jsx)(U,{text:w,rawBody:e.body,issueRepo:p,className:`leading-relaxed`,onPrimary:c,onOpenPath:ae,onPreviewPath:N,onPreviewPathEnd:o,resolvePathTitle:(e,t)=>`${A(e)}${t?`:${t}`:``}`}),re.map(t=>(0,X.jsx)(yt,{attachment:t,outbound:c},`${e.id}-${t.artifactId}-${t.role||``}`)),(0,X.jsxs)(`p`,{className:a(`text-xs mt-1 flex items-center gap-1.5`,c?`justify-end text-primary-foreground/60`:`justify-end text-muted-foreground`),children:[(0,X.jsxs)(`span`,{className:`opacity-40`,children:[`#`,e.id]}),(0,X.jsx)(`span`,{children:ne}),E&&D&&(0,X.jsxs)(`span`,{className:a(`inline-flex items-center gap-1`,E.tone===`ok`&&`text-emerald-300`,E.tone===`warn`&&`text-amber-200`,E.tone===`danger`&&`text-red-200`),title:E.title,children:[(0,X.jsx)(D,{className:`h-3 w-3`}),E.label]})]})]}),!J(e)&&T.length>0&&(0,X.jsxs)(`div`,{className:a(`flex flex-wrap items-center gap-0.5 -mt-1 relative z-10`,c?`justify-end pr-2`:`justify-start pl-2`),children:[!c&&(0,X.jsx)(bt,{open:y,onToggle:()=>b(e=>!e),onReact:P}),T.map(e=>(0,X.jsxs)(`button`,{type:`button`,title:e.actors.join(`, `),onClick:()=>P(e.emoji),className:a(`inline-flex h-5 items-center gap-0.5 rounded-full border px-1.5 text-[11px] shadow-sm transition-colors`,e.mine?`border-primary/40 bg-primary/10 text-primary`:`border-border bg-background hover:bg-muted text-foreground`),children:[(0,X.jsx)(`span`,{children:e.emoji}),e.count>1&&(0,X.jsx)(`span`,{children:e.count})]},e.emoji))]}),!J(e)&&!c&&T.length===0&&(0,X.jsx)(`div`,{className:`absolute -bottom-2.5 left-1 z-20`,children:(0,X.jsx)(bt,{open:y,onToggle:()=>b(e=>!e),onReact:P})})]}),c&&R&&(0,X.jsx)(`span`,{className:`shrink-0 self-end opacity-60`,children:(0,X.jsx)(me,{user:v,address:e.from,distinct:_})})]})});export{Te as C,Be as S,De as T,Me as _,dt as a,Ue as b,pt as c,ot as d,qe as f,He as g,ke as h,st as i,mt as l,Ve as m,lt as n,ut as o,ht as p,nt as r,ft as s,St as t,Qe as u,je as v,Ee as w,Ne as x,We as y};
|
|
3
|
-
//# sourceMappingURL=MessageBubble-BORB9AKu.js.map
|