agent-relay-server 0.91.4 → 0.92.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 (76) hide show
  1. package/docs/openapi.json +1 -1
  2. package/package.json +2 -2
  3. package/public/assets/MessageBubble-CUddRP1E.js +3 -0
  4. package/public/assets/{MessageBubble-BORB9AKu.js.map → MessageBubble-CUddRP1E.js.map} +1 -1
  5. package/public/assets/{PlanGraphPanel-BwEOOqdj.js → PlanGraphPanel-CpIBMvnu.js} +2 -2
  6. package/public/assets/{PlanGraphPanel-BwEOOqdj.js.map → PlanGraphPanel-CpIBMvnu.js.map} +1 -1
  7. package/public/assets/{activity-DD9bl5uf.js → activity-B6WPWNsX.js} +2 -2
  8. package/public/assets/{activity-DD9bl5uf.js.map → activity-B6WPWNsX.js.map} +1 -1
  9. package/public/assets/{agents-DJgqCIqi.js → agents-BebF-yQr.js} +2 -2
  10. package/public/assets/{agents-DJgqCIqi.js.map → agents-BebF-yQr.js.map} +1 -1
  11. package/public/assets/{automation-Vbei8P9W.js → automation-CyPXC1A8.js} +2 -2
  12. package/public/assets/{automation-Vbei8P9W.js.map → automation-CyPXC1A8.js.map} +1 -1
  13. package/public/assets/{chat-Dw5xpc5W.js → chat-DfqC3kFF.js} +2 -2
  14. package/public/assets/{chat-Dw5xpc5W.js.map → chat-DfqC3kFF.js.map} +1 -1
  15. package/public/assets/display-CWMHll1J.js.map +1 -1
  16. package/public/assets/{formatted-body-impl-BisZ0FtO.js → formatted-body-impl-CrE-hFvy.js} +2 -2
  17. package/public/assets/{formatted-body-impl-BisZ0FtO.js.map → formatted-body-impl-CrE-hFvy.js.map} +1 -1
  18. package/public/assets/{index-Bsi2NH0v.js → index-CdDX5NR1.js} +5 -5
  19. package/public/assets/{index-Bsi2NH0v.js.map → index-CdDX5NR1.js.map} +1 -1
  20. package/public/assets/{insights-D9ytYZyE.js → insights-BribQHXe.js} +2 -2
  21. package/public/assets/{insights-D9ytYZyE.js.map → insights-BribQHXe.js.map} +1 -1
  22. package/public/assets/{maintenance-BA93FvLR.js → maintenance-Cjol3fS_.js} +2 -2
  23. package/public/assets/{maintenance-BA93FvLR.js.map → maintenance-Cjol3fS_.js.map} +1 -1
  24. package/public/assets/{managed-agents-DGwdEIzA.js → managed-agents-1lZwQNHl.js} +2 -2
  25. package/public/assets/{managed-agents-DGwdEIzA.js.map → managed-agents-1lZwQNHl.js.map} +1 -1
  26. package/public/assets/{markdown-preview-impl-C7FDXM5c.js → markdown-preview-impl-C2WeUNFR.js} +2 -2
  27. package/public/assets/{markdown-preview-impl-C7FDXM5c.js.map → markdown-preview-impl-C2WeUNFR.js.map} +1 -1
  28. package/public/assets/{memory-CHzkRJqf.js → memory-B7mcELEW.js} +2 -2
  29. package/public/assets/{memory-CHzkRJqf.js.map → memory-B7mcELEW.js.map} +1 -1
  30. package/public/assets/{messages-B7CJ5tgT.js → messages-B8jF9p2l.js} +2 -2
  31. package/public/assets/{messages-B7CJ5tgT.js.map → messages-B8jF9p2l.js.map} +1 -1
  32. package/public/assets/{orchestrators-DOy9xIx2.js → orchestrators-CQjvBXDx.js} +2 -2
  33. package/public/assets/{orchestrators-DOy9xIx2.js.map → orchestrators-CQjvBXDx.js.map} +1 -1
  34. package/public/assets/{overview-DLbv1BWa.js → overview-CgDUJOG-.js} +2 -2
  35. package/public/assets/{overview-DLbv1BWa.js.map → overview-CgDUJOG-.js.map} +1 -1
  36. package/public/assets/{pairs-BDQo-k3F.js → pairs-BdPKQyzE.js} +2 -2
  37. package/public/assets/{pairs-BDQo-k3F.js.map → pairs-BdPKQyzE.js.map} +1 -1
  38. package/public/assets/{security-9WrCcrWs.js → security-BlZQ0ECI.js} +2 -2
  39. package/public/assets/{security-9WrCcrWs.js.map → security-BlZQ0ECI.js.map} +1 -1
  40. package/public/assets/{select-CRjeGZ9a.js → select-CjpI0JtN.js} +2 -2
  41. package/public/assets/{select-CRjeGZ9a.js.map → select-CjpI0JtN.js.map} +1 -1
  42. package/public/assets/{settings-Durch6xW.js → settings-CMgqpUY-.js} +2 -2
  43. package/public/assets/{settings-Durch6xW.js.map → settings-CMgqpUY-.js.map} +1 -1
  44. package/public/assets/{tasks-86St2vpn.js → tasks-BcHWptn4.js} +2 -2
  45. package/public/assets/{tasks-86St2vpn.js.map → tasks-BcHWptn4.js.map} +1 -1
  46. package/public/assets/{teams-C9WUxzud.js → teams-BGL-VpqZ.js} +2 -2
  47. package/public/assets/{teams-C9WUxzud.js.map → teams-BGL-VpqZ.js.map} +1 -1
  48. package/public/assets/{terminal-viewer-impl-yXWyt2fm.js → terminal-viewer-impl-D8bnx7Z7.js} +2 -2
  49. package/public/assets/{terminal-viewer-impl-yXWyt2fm.js.map → terminal-viewer-impl-D8bnx7Z7.js.map} +1 -1
  50. package/public/assets/{user-avatar-tomKla2H.js → user-avatar-DZ1g4X7l.js} +2 -2
  51. package/public/assets/{user-avatar-tomKla2H.js.map → user-avatar-DZ1g4X7l.js.map} +1 -1
  52. package/public/assets/{work-queue-BtLMzf0g.js → work-queue-CIy6fe6y.js} +2 -2
  53. package/public/assets/{work-queue-BtLMzf0g.js.map → work-queue-CIy6fe6y.js.map} +1 -1
  54. package/public/index.html +1 -1
  55. package/src/bus.ts +9 -2
  56. package/src/config.ts +1 -0
  57. package/src/db/agent-predicates.ts +21 -0
  58. package/src/db/agents.ts +3 -3
  59. package/src/db/delivery.ts +38 -0
  60. package/src/db/messages.ts +18 -1
  61. package/src/db/workspaces.ts +44 -2
  62. package/src/maintenance/constants.ts +2 -0
  63. package/src/maintenance/jobs.ts +21 -8
  64. package/src/maintenance/workspaces.ts +76 -0
  65. package/src/mcp/tools-spawn.ts +30 -2
  66. package/src/routes/agents-spawn.ts +15 -2
  67. package/src/routes/agents.ts +17 -2
  68. package/src/routes/commands.ts +5 -0
  69. package/src/services/managed-running.ts +18 -1
  70. package/src/services/rate-limit-resume.ts +3 -0
  71. package/src/services/register-agent.ts +7 -1
  72. package/src/services/spawn-agent.ts +62 -2
  73. package/src/spawn-command.ts +10 -0
  74. package/src/validation.ts +7 -2
  75. package/src/workspace-auto-merge.ts +53 -9
  76. package/public/assets/MessageBubble-BORB9AKu.js +0 -3
@@ -12,13 +12,15 @@ import { countLiveSpawnedAgents, listAgents, listWorkspaces, ValidationError } f
12
12
  import { listCommands } from "../commands-db";
13
13
  import { DEFAULT_SPAWN_PROFILE_NAME } from "../config-store";
14
14
  import { optionalEnum } from "../validation";
15
- import { APPROVAL_MODES, SPAWN_PROVIDERS, stringValue, VALID_AGENT_LIFECYCLES, VALID_EFFORTS, VALID_WORKSPACE_MODES } from "agent-relay-sdk";
15
+ import { APPROVAL_MODES, AUTO_MERGE_POLICIES, SPAWN_PROVIDERS, stringValue, VALID_AGENT_LIFECYCLES, VALID_EFFORTS, VALID_WORKSPACE_MODES } from "agent-relay-sdk";
16
16
  import type { ProviderEffort } from "agent-relay-sdk/provider-catalog";
17
- import type { AgentCard, AgentLifecycle, Command, SpawnApprovalMode, SpawnProvider, WorkspaceMode, WorkspaceRecord } from "../types";
17
+ import type { AgentCard, AgentLifecycle, Command, SpawnApprovalMode, SpawnProvider, WorkspaceAutoMergePolicy, WorkspaceLandingPolicy, WorkspaceMode, WorkspaceRecord } from "../types";
18
18
  import type { McpAuthContext, ToolDefinition } from "./types";
19
19
  import { callerAgentId } from "./identity";
20
20
  import { enumField, optionalBoolean, optionalNonNegativeInt, optionalPositiveInt, optionalString, optionalStringArray } from "./validation";
21
21
 
22
+ const SPAWN_LAND_STRATEGIES = ["direct", "pr"] as const;
23
+
22
24
  export const SPAWN_TOOLS: ToolDefinition[] = [
23
25
  {
24
26
  name: "relay_spawn_agent",
@@ -48,6 +50,9 @@ export const SPAWN_TOOLS: ToolDefinition[] = [
48
50
  ],
49
51
  description: "#689 — shared host-listener MCP. Host-MCP profiles default ON: the worker's NON-IDENTITY MCP (tokenlean, github) is routed through one orchestrator-owned shared `callmux` listener via a per-agent `callmux bridge --cwd \"$WORKTREE\"`, so the MCP session survives listener/downstream restart and keeps isolated worktree cwd routing. Profiles that suppress host MCP stay OFF unless explicitly opted in. Pass `false` as a per-spawn kill-switch to force direct/per-agent MCP injection. The identity-bearing relay endpoint stays per-agent (#215), never on the shared listener.",
50
52
  },
53
+ landStrategy: { type: "string", enum: SPAWN_LAND_STRATEGIES, description: "#421 — per-spawn workspace land strategy. Omit to use the instance landing.strategy. `direct` keeps deterministic rebase/fast-forward landing; `pr` opens a PR." },
54
+ autoMerge: { type: "string", enum: AUTO_MERGE_POLICIES, description: "#421/#305 — PR auto-merge policy when landStrategy resolves to `pr`. `on-green` auto-merges after checks, `on-approval` waits for reviewer approval, `manual` opens the PR only." },
55
+ reviewer: { type: "string", description: "#421/#305 — optional reviewer handle or policy target for reviewer-first PR lands, used with landStrategy `pr` and autoMerge `on-approval`." },
51
56
  profile: { type: "string", description: `Agent profile name to apply (env, instructions, permissions, MCP/skills, spawn quota). Omit to use \`${DEFAULT_SPAWN_PROFILE_NAME}\`.` },
52
57
  tags: { type: "array", items: { type: "string" } },
53
58
  capabilities: { type: "array", items: { type: "string" } },
@@ -56,6 +61,16 @@ export const SPAWN_TOOLS: ToolDefinition[] = [
56
61
  spawnRequestId: { type: "string" },
57
62
  lifecycle: { type: "string", enum: VALID_AGENT_LIFECYCLES, description: "Lifecycle mode. `persistent` keeps the agent until explicitly stopped. `transient` lets Relay auto-shutdown the child after it is idle and, for isolated workers, after its branch has landed. Default: persistent." },
58
63
  workspaceMode: { type: "string", enum: VALID_WORKSPACE_MODES, description: "Git workspace for the worker. `isolated` = its own worktree on an `agent/session-*` branch off main that auto-lands when ready (delegate a unit of work, get a branch worker). `shared` = edits your live checkout (a sidecar). `inherit` = let the orchestrator decide. DEFAULT for agent-initiated spawns is `isolated` so a delegated worker lands to main instead of editing your tree; pass `shared`/`inherit` to override." },
64
+ resumeWorkspace: {
65
+ type: "object",
66
+ description: "#635 — attach a new worker to an EXISTING managed worktree/branch instead of always creating a fresh one. `attach`: reuse the worktree as-is (crash recovery — WIP is preserved); `branch-from`: fresh worktree off the existing branch HEAD (phased work, sequential hand-off). Guard: `attach` is rejected when the original owner is still alive. The resumed worker's branch auto-lands normally on completion.",
67
+ properties: {
68
+ branch: { type: "string", description: "The existing agent branch to attach to or branch from (e.g. `agent/build-389/.../...`)." },
69
+ mode: { type: "string", enum: ["attach", "branch-from"], description: "`attach` = reuse the existing worktree directory (WIP preserved). `branch-from` = fresh worktree off the branch HEAD (committed history carried, WIP not)." },
70
+ },
71
+ required: ["branch", "mode"],
72
+ additionalProperties: false,
73
+ },
59
74
  waitForRegistrationMs: { type: "integer", minimum: 0, maximum: 30000, description: "How long to wait for the spawned agent to register before returning, so the response carries its resolved agent id (default 8000; 0 = return immediately with just spawnRequestId)." },
60
75
  },
61
76
  required: ["provider"],
@@ -105,6 +120,15 @@ export const SPAWN_TOOLS: ToolDefinition[] = [
105
120
  },
106
121
  ];
107
122
 
123
+ function parseResumeWorkspace(value: unknown): { branch: string; mode: "attach" | "branch-from" } | undefined {
124
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
125
+ const obj = value as Record<string, unknown>;
126
+ const branch = optionalString(obj.branch, "resumeWorkspace.branch", 500);
127
+ const mode = obj.mode === "attach" || obj.mode === "branch-from" ? obj.mode : undefined;
128
+ if (!branch || !mode) return undefined;
129
+ return { branch, mode };
130
+ }
131
+
108
132
  export async function relaySpawnAgent(auth: McpAuthContext, args: Record<string, unknown>): Promise<Record<string, unknown>> {
109
133
  const input: SpawnAgentInput = {
110
134
  provider: enumField(args.provider, "provider", SPAWN_PROVIDERS) as SpawnProvider,
@@ -126,12 +150,16 @@ export async function relaySpawnAgent(auth: McpAuthContext, args: Record<string,
126
150
  projectSkills: parseProjectSkills(args.projectSkills, (m) => new ValidationError(m)),
127
151
  projectSettings: parseProjectSettings(args.projectSettings, (m) => new ValidationError(m)),
128
152
  sharedMcp: parseSharedMcp(args.sharedMcp, (m) => new ValidationError(m)),
153
+ landStrategy: optionalEnum(args.landStrategy, "landStrategy", SPAWN_LAND_STRATEGIES) as WorkspaceLandingPolicy | undefined,
154
+ autoMerge: optionalEnum(args.autoMerge, "autoMerge", AUTO_MERGE_POLICIES) as WorkspaceAutoMergePolicy | undefined,
155
+ reviewer: optionalString(args.reviewer, "reviewer", 240),
129
156
  tags: optionalStringArray(args.tags, "tags"),
130
157
  capabilities: optionalStringArray(args.capabilities, "capabilities"),
131
158
  providerArgs: optionalStringArray(args.providerArgs, "providerArgs"),
132
159
  spawnRequestId: optionalString(args.spawnRequestId, "spawnRequestId", 160),
133
160
  requestedVia: "mcp",
134
161
  waitForRegistrationMs: optionalNonNegativeInt(args.waitForRegistrationMs, "waitForRegistrationMs") ?? 8000,
162
+ resumeWorkspace: parseResumeWorkspace(args.resumeWorkspace),
135
163
  };
136
164
  const result = await spawnAgent(input, authContextFromMcp(auth));
137
165
  return { ...result };
@@ -1,5 +1,5 @@
1
1
  // Auto-split from routes.ts (#299). Domain: agents-spawn.
2
- import { APPROVAL_MODES, SPAWN_PROVIDERS, VALID_WORKSPACE_MODES, isRecord } from "agent-relay-sdk";
2
+ import { APPROVAL_MODES, AUTO_MERGE_POLICIES, SPAWN_PROVIDERS, VALID_WORKSPACE_MODES, isRecord } from "agent-relay-sdk";
3
3
  import { McpAuthError, McpNotFoundError } from "../mcp-errors";
4
4
  import { VALID_AGENT_STATUSES, error, json, parseBody, type Handler } from "./_shared";
5
5
  import { ValidationError, listAgents } from "../db";
@@ -15,10 +15,19 @@ import { authContextFromRequest } from "../services/auth-context";
15
15
  import { listHostDirectories } from "../agent-spawn";
16
16
  import { rankRouteCandidates, type RouteAdvisorInput } from "../context-router";
17
17
  import { quotaStateFromUnknown } from "../quota";
18
- import { type AgentKind, type RegisterAgentInput, type SpawnApprovalMode, type SpawnProvider, type WorkspaceMode } from "../types";
18
+ import { type AgentKind, type RegisterAgentInput, type SpawnApprovalMode, type SpawnProvider, type WorkspaceAutoMergePolicy, type WorkspaceLandingPolicy, type WorkspaceMode } from "../types";
19
19
  import { type ProviderEffort } from "agent-relay-sdk/provider-catalog";
20
20
 
21
21
  const VALID_AGENT_KINDS = ["provider", "channel", "orchestrator", "system", "user"] as const;
22
+ const SPAWN_LAND_STRATEGIES = ["direct", "pr"] as const;
23
+
24
+ function parseResumeWorkspaceHttp(value: unknown): { branch: string; mode: "attach" | "branch-from" } | undefined {
25
+ if (!value || !isRecord(value)) return undefined;
26
+ const branch = cleanString(value.branch, "resumeWorkspace.branch", { max: 500 });
27
+ const mode = value.mode === "attach" || value.mode === "branch-from" ? value.mode : undefined;
28
+ if (!branch || !mode) return undefined;
29
+ return { branch, mode };
30
+ }
22
31
 
23
32
  function normalizeAgentInput(body: unknown): RegisterAgentInput {
24
33
  if (!isRecord(body)) throw new ValidationError("JSON object body required");
@@ -129,12 +138,16 @@ export const postAgentSpawn: Handler = async (req) => {
129
138
  projectSkills: parseProjectSkills(parsed.body.projectSkills, (m) => new ValidationError(m)),
130
139
  projectSettings: parseProjectSettings(parsed.body.projectSettings, (m) => new ValidationError(m)),
131
140
  sharedMcp: parseSharedMcp(parsed.body.sharedMcp, (m) => new ValidationError(m)),
141
+ landStrategy: optionalEnum(parsed.body.landStrategy, "landStrategy", SPAWN_LAND_STRATEGIES) as WorkspaceLandingPolicy | undefined,
142
+ autoMerge: optionalEnum(parsed.body.autoMerge, "autoMerge", AUTO_MERGE_POLICIES) as WorkspaceAutoMergePolicy | undefined,
143
+ reviewer: cleanString(parsed.body.reviewer, "reviewer", { max: 240 }),
132
144
  tags: cleanStringArray(parsed.body.tags, "tags", { itemMax: 80, maxItems: 50 }),
133
145
  capabilities: cleanStringArray(parsed.body.capabilities, "capabilities", { itemMax: 80, maxItems: 50 }),
134
146
  providerArgs: cleanStringArray(parsed.body.providerArgs, "providerArgs", { itemMax: 80, maxItems: 50 }),
135
147
  spawnRequestId: cleanString(parsed.body.spawnRequestId, "spawnRequestId", { max: 160 }),
136
148
  requestedVia: "dashboard",
137
149
  requestedBy: "dashboard",
150
+ resumeWorkspace: parseResumeWorkspaceHttp(parsed.body.resumeWorkspace),
138
151
  // HTTP is fire-and-forget (202): the dashboard polls registration separately via the
139
152
  // returned spawnRequestId.
140
153
  waitForRegistrationMs: 0,
@@ -7,6 +7,8 @@ import { cleanStringArray } from "../validation";
7
7
  import { clearActiveMemories, memoryBroker } from "../memory-service";
8
8
  import { emitAgentRemoved, emitAgentStatus } from "../sse";
9
9
  import { notifyAgentReady } from "../agent-lifecycle-events";
10
+ import { flushQueuedDirectMessages } from "../services/managed-running";
11
+ import { isAgentUnavailable } from "../db/agent-predicates";
10
12
  import { getCompactionWatch } from "../compaction-watch";
11
13
  import { isRecord } from "agent-relay-sdk";
12
14
  import { type AgentCard } from "../types";
@@ -210,7 +212,15 @@ export const postHeartbeat: Handler = async (req, params) => {
210
212
  const guard = normalizeAgentSessionGuard(req, parsed.body);
211
213
  const session = validateAgentSession(params.id!, guard);
212
214
  if (!session.ok) return error(session.error!, agentSessionStatus(session.error));
213
- return heartbeat(params.id!, guard) ? json({ ok: true }) : error("agent not found", 404);
215
+ // #416 capture unavailability before heartbeat to detect the transition.
216
+ const beforeHeartbeat = getAgent(params.id!);
217
+ const wasUnavailable = beforeHeartbeat ? isAgentUnavailable(beforeHeartbeat) : false;
218
+ if (!heartbeat(params.id!, guard)) return error("agent not found", 404);
219
+ if (wasUnavailable) {
220
+ const after = getAgent(params.id!);
221
+ if (after && !isAgentUnavailable(after)) flushQueuedDirectMessages(params.id!);
222
+ }
223
+ return json({ ok: true });
214
224
  } catch (e) {
215
225
  if (e instanceof ValidationError) return error(e.message, 400);
216
226
  throw e;
@@ -287,7 +297,12 @@ export const patchAgentReady: Handler = async (req, params) => {
287
297
  // #308 — a spawned child flipping false→true ready is "genuinely ready" (past onboarding):
288
298
  // wake its parent so it can stop block-polling. notifyAgentReady no-ops for non-spawned
289
299
  // agents and dedups repeat readies.
290
- if (body.ready && !before?.ready) notifyAgentReady(params.id!);
300
+ if (body.ready && !before?.ready) {
301
+ notifyAgentReady(params.id!);
302
+ // #416 — also flush queued direct messages: a not-ready agent that becomes ready
303
+ // may have queued messages waiting (e.g. starting up, clearing context).
304
+ flushQueuedDirectMessages(params.id!);
305
+ }
291
306
  } catch (e) {
292
307
  if (e instanceof ValidationError) return error(e.message, 400);
293
308
  throw e;
@@ -13,6 +13,7 @@ import { authContextFromRequest } from "../services/auth-context";
13
13
  import { commandAuthorizationResource as dispatchCommandAuthorizationResource, dispatchCommand } from "../services/dispatch-command";
14
14
  import { ServiceAuthError } from "../services/errors";
15
15
  import { notifyBranchLandFailed, notifyBranchLanded } from "../branch-landed";
16
+ import { applyIdleRefreshResult } from "../maintenance/workspaces";
16
17
  import { seedProviderConfigs } from "../provider-config-store";
17
18
  import { notifyAgentSpawnFailed } from "../agent-lifecycle-events";
18
19
  import { handleReviewerMergeResult } from "../reviewer-pipeline";
@@ -186,6 +187,10 @@ export const patchCommand: Handler = async (req, params) => {
186
187
  });
187
188
  }
188
189
  }
190
+ if (command.type === "workspace.idle-refresh" && command.status === "succeeded" && isRecord(command.result)) {
191
+ const workspaceId = cleanString(command.result.workspaceId, "result.workspaceId", { max: 160 });
192
+ if (workspaceId) applyIdleRefreshResult(workspaceId, command.result);
193
+ }
189
194
  if (command.type === "workspace.merge") {
190
195
  // Merge settled (either way) — free the per-repo merge lease so the next
191
196
  // base merge can proceed (issue #157).
@@ -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.
@@ -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,
@@ -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/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 the operator's instance landing.strategy (#419-followup): when PR mode is on
250
- // (`landing.strategy = "pr"`), autonomous worker lands must open a PR too not silently
251
- // direct-merge. Passing an explicit "rebase-ff" here SHADOWS the instance config inside
252
- // requestWorkspaceMerge (explicit wins over instanceStrategy), which is the bug that made
253
- // PR mode never engage for spawned workers. For "pr" we OMIT the explicit strategy so
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