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.
- package/docs/openapi.json +1 -1
- package/package.json +2 -2
- package/public/assets/MessageBubble-CUddRP1E.js +3 -0
- package/public/assets/{MessageBubble-BORB9AKu.js.map → MessageBubble-CUddRP1E.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-BwEOOqdj.js → PlanGraphPanel-CpIBMvnu.js} +2 -2
- package/public/assets/{PlanGraphPanel-BwEOOqdj.js.map → PlanGraphPanel-CpIBMvnu.js.map} +1 -1
- package/public/assets/{activity-DD9bl5uf.js → activity-B6WPWNsX.js} +2 -2
- package/public/assets/{activity-DD9bl5uf.js.map → activity-B6WPWNsX.js.map} +1 -1
- package/public/assets/{agents-DJgqCIqi.js → agents-BebF-yQr.js} +2 -2
- package/public/assets/{agents-DJgqCIqi.js.map → agents-BebF-yQr.js.map} +1 -1
- package/public/assets/{automation-Vbei8P9W.js → automation-CyPXC1A8.js} +2 -2
- package/public/assets/{automation-Vbei8P9W.js.map → automation-CyPXC1A8.js.map} +1 -1
- package/public/assets/{chat-Dw5xpc5W.js → chat-DfqC3kFF.js} +2 -2
- package/public/assets/{chat-Dw5xpc5W.js.map → chat-DfqC3kFF.js.map} +1 -1
- package/public/assets/display-CWMHll1J.js.map +1 -1
- package/public/assets/{formatted-body-impl-BisZ0FtO.js → formatted-body-impl-CrE-hFvy.js} +2 -2
- package/public/assets/{formatted-body-impl-BisZ0FtO.js.map → formatted-body-impl-CrE-hFvy.js.map} +1 -1
- package/public/assets/{index-Bsi2NH0v.js → index-CdDX5NR1.js} +5 -5
- package/public/assets/{index-Bsi2NH0v.js.map → index-CdDX5NR1.js.map} +1 -1
- package/public/assets/{insights-D9ytYZyE.js → insights-BribQHXe.js} +2 -2
- package/public/assets/{insights-D9ytYZyE.js.map → insights-BribQHXe.js.map} +1 -1
- package/public/assets/{maintenance-BA93FvLR.js → maintenance-Cjol3fS_.js} +2 -2
- package/public/assets/{maintenance-BA93FvLR.js.map → maintenance-Cjol3fS_.js.map} +1 -1
- package/public/assets/{managed-agents-DGwdEIzA.js → managed-agents-1lZwQNHl.js} +2 -2
- package/public/assets/{managed-agents-DGwdEIzA.js.map → managed-agents-1lZwQNHl.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-C7FDXM5c.js → markdown-preview-impl-C2WeUNFR.js} +2 -2
- package/public/assets/{markdown-preview-impl-C7FDXM5c.js.map → markdown-preview-impl-C2WeUNFR.js.map} +1 -1
- package/public/assets/{memory-CHzkRJqf.js → memory-B7mcELEW.js} +2 -2
- package/public/assets/{memory-CHzkRJqf.js.map → memory-B7mcELEW.js.map} +1 -1
- package/public/assets/{messages-B7CJ5tgT.js → messages-B8jF9p2l.js} +2 -2
- package/public/assets/{messages-B7CJ5tgT.js.map → messages-B8jF9p2l.js.map} +1 -1
- package/public/assets/{orchestrators-DOy9xIx2.js → orchestrators-CQjvBXDx.js} +2 -2
- package/public/assets/{orchestrators-DOy9xIx2.js.map → orchestrators-CQjvBXDx.js.map} +1 -1
- package/public/assets/{overview-DLbv1BWa.js → overview-CgDUJOG-.js} +2 -2
- package/public/assets/{overview-DLbv1BWa.js.map → overview-CgDUJOG-.js.map} +1 -1
- package/public/assets/{pairs-BDQo-k3F.js → pairs-BdPKQyzE.js} +2 -2
- package/public/assets/{pairs-BDQo-k3F.js.map → pairs-BdPKQyzE.js.map} +1 -1
- package/public/assets/{security-9WrCcrWs.js → security-BlZQ0ECI.js} +2 -2
- package/public/assets/{security-9WrCcrWs.js.map → security-BlZQ0ECI.js.map} +1 -1
- package/public/assets/{select-CRjeGZ9a.js → select-CjpI0JtN.js} +2 -2
- package/public/assets/{select-CRjeGZ9a.js.map → select-CjpI0JtN.js.map} +1 -1
- package/public/assets/{settings-Durch6xW.js → settings-CMgqpUY-.js} +2 -2
- package/public/assets/{settings-Durch6xW.js.map → settings-CMgqpUY-.js.map} +1 -1
- package/public/assets/{tasks-86St2vpn.js → tasks-BcHWptn4.js} +2 -2
- package/public/assets/{tasks-86St2vpn.js.map → tasks-BcHWptn4.js.map} +1 -1
- package/public/assets/{teams-C9WUxzud.js → teams-BGL-VpqZ.js} +2 -2
- package/public/assets/{teams-C9WUxzud.js.map → teams-BGL-VpqZ.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-yXWyt2fm.js → terminal-viewer-impl-D8bnx7Z7.js} +2 -2
- package/public/assets/{terminal-viewer-impl-yXWyt2fm.js.map → terminal-viewer-impl-D8bnx7Z7.js.map} +1 -1
- package/public/assets/{user-avatar-tomKla2H.js → user-avatar-DZ1g4X7l.js} +2 -2
- package/public/assets/{user-avatar-tomKla2H.js.map → user-avatar-DZ1g4X7l.js.map} +1 -1
- package/public/assets/{work-queue-BtLMzf0g.js → work-queue-CIy6fe6y.js} +2 -2
- package/public/assets/{work-queue-BtLMzf0g.js.map → work-queue-CIy6fe6y.js.map} +1 -1
- package/public/index.html +1 -1
- package/src/bus.ts +9 -2
- package/src/config.ts +1 -0
- 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/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/tools-spawn.ts +30 -2
- package/src/routes/agents-spawn.ts +15 -2
- package/src/routes/agents.ts +17 -2
- package/src/routes/commands.ts +5 -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/spawn-agent.ts +62 -2
- package/src/spawn-command.ts +10 -0
- package/src/validation.ts +7 -2
- package/src/workspace-auto-merge.ts +53 -9
- package/public/assets/MessageBubble-BORB9AKu.js +0 -3
package/src/mcp/tools-spawn.ts
CHANGED
|
@@ -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,
|
package/src/routes/agents.ts
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
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;
|
package/src/routes/commands.ts
CHANGED
|
@@ -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,
|
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/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
|