agent-relay-server 0.106.0 → 0.108.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/openapi.json +503 -1
- package/package.json +6 -5
- package/public/assets/{MessageBubble-CPYQQLDI.js → MessageBubble-DYJezSjU.js} +2 -2
- package/public/assets/{MessageBubble-CPYQQLDI.js.map → MessageBubble-DYJezSjU.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-B7xZt5DI.js → PlanGraphPanel-MWcVw38W.js} +2 -2
- package/public/assets/{PlanGraphPanel-B7xZt5DI.js.map → PlanGraphPanel-MWcVw38W.js.map} +1 -1
- package/public/assets/{activity-UKQrQ8_0.js → activity-Db3X3dg8.js} +2 -2
- package/public/assets/{activity-UKQrQ8_0.js.map → activity-Db3X3dg8.js.map} +1 -1
- package/public/assets/{agent-profiles-Zr_5nNEJ.js → agent-profiles-Di2w5U1o.js} +2 -2
- package/public/assets/{agent-profiles-Zr_5nNEJ.js.map → agent-profiles-Di2w5U1o.js.map} +1 -1
- package/public/assets/{agents-FdrfmJsj.js → agents-GSJXvmU1.js} +2 -2
- package/public/assets/{agents-FdrfmJsj.js.map → agents-GSJXvmU1.js.map} +1 -1
- package/public/assets/{analytics-VximqXQ9.js → analytics-B9USQLl5.js} +18 -18
- package/public/assets/{analytics-VximqXQ9.js.map → analytics-B9USQLl5.js.map} +1 -1
- package/public/assets/{automation-DlEcJvgu.js → automation-XRePs1dT.js} +2 -2
- package/public/assets/{automation-DlEcJvgu.js.map → automation-XRePs1dT.js.map} +1 -1
- package/public/assets/{branch-state-badge-CcLZ_u86.js → branch-state-badge-C_VNyER3.js} +2 -2
- package/public/assets/{branch-state-badge-CcLZ_u86.js.map → branch-state-badge-C_VNyER3.js.map} +1 -1
- package/public/assets/{channels-CcSnLzCG.js → channels-DACvNoC2.js} +2 -2
- package/public/assets/{channels-CcSnLzCG.js.map → channels-DACvNoC2.js.map} +1 -1
- package/public/assets/{chat-D1bunuI-.js → chat-D_0uHgB6.js} +2 -2
- package/public/assets/{chat-D1bunuI-.js.map → chat-D_0uHgB6.js.map} +1 -1
- package/public/assets/{connectors-DbPOlQhL.js → connectors-C0B38LUK.js} +2 -2
- package/public/assets/{connectors-DbPOlQhL.js.map → connectors-C0B38LUK.js.map} +1 -1
- package/public/assets/display-1J__Ti1K.js +3 -0
- package/public/assets/display-1J__Ti1K.js.map +1 -0
- package/public/assets/{formatted-body-impl-4v9SkUD1.js → formatted-body-impl-BKWCAjBd.js} +2 -2
- package/public/assets/{formatted-body-impl-4v9SkUD1.js.map → formatted-body-impl-BKWCAjBd.js.map} +1 -1
- package/public/assets/index-DlBSuzd2.js +23 -0
- package/public/assets/index-DlBSuzd2.js.map +1 -0
- package/public/assets/{insights-CntgHcTp.js → insights-BzNoYvDe.js} +2 -2
- package/public/assets/{insights-CntgHcTp.js.map → insights-BzNoYvDe.js.map} +1 -1
- package/public/assets/{integrations-hkSW8CzM.js → integrations-DLzJpt9G.js} +2 -2
- package/public/assets/{integrations-hkSW8CzM.js.map → integrations-DLzJpt9G.js.map} +1 -1
- package/public/assets/{maintenance-Dw7frQtu.js → maintenance-B6_0-ECm.js} +2 -2
- package/public/assets/{maintenance-Dw7frQtu.js.map → maintenance-B6_0-ECm.js.map} +1 -1
- package/public/assets/{managed-agents-Bj--bFmZ.js → managed-agents-CpzKXDtV.js} +2 -2
- package/public/assets/{managed-agents-Bj--bFmZ.js.map → managed-agents-CpzKXDtV.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-DGLCocU8.js → markdown-preview-impl-DxBnlJ2k.js} +2 -2
- package/public/assets/{markdown-preview-impl-DGLCocU8.js.map → markdown-preview-impl-DxBnlJ2k.js.map} +1 -1
- package/public/assets/{memory-yj2MIxUA.js → memory-Bnm7ILWL.js} +2 -2
- package/public/assets/{memory-yj2MIxUA.js.map → memory-Bnm7ILWL.js.map} +1 -1
- package/public/assets/{messages-BT5mL7iz.js → messages-D9LkocTc.js} +2 -2
- package/public/assets/{messages-BT5mL7iz.js.map → messages-D9LkocTc.js.map} +1 -1
- package/public/assets/{orchestrators-BVDAhrPk.js → orchestrators-C__JfrNr.js} +2 -2
- package/public/assets/{orchestrators-BVDAhrPk.js.map → orchestrators-C__JfrNr.js.map} +1 -1
- package/public/assets/{overview-DlqJIqYK.js → overview-BQ0b0V7Q.js} +2 -2
- package/public/assets/{overview-DlqJIqYK.js.map → overview-BQ0b0V7Q.js.map} +1 -1
- package/public/assets/{pairs-DQ4tNCo3.js → pairs-ChyeBs0Z.js} +2 -2
- package/public/assets/{pairs-DQ4tNCo3.js.map → pairs-ChyeBs0Z.js.map} +1 -1
- package/public/assets/{projects-vt63bwHN.js → projects-v5Kp1Oaq.js} +2 -2
- package/public/assets/{projects-vt63bwHN.js.map → projects-v5Kp1Oaq.js.map} +1 -1
- package/public/assets/{prompt-settings-DlsqOP0C.js → prompt-settings-YhAOlYc6.js} +2 -2
- package/public/assets/{prompt-settings-DlsqOP0C.js.map → prompt-settings-YhAOlYc6.js.map} +1 -1
- package/public/assets/{registry-Cw2fdWrn.js → registry-DZoAfrzx.js} +2 -2
- package/public/assets/{registry-Cw2fdWrn.js.map → registry-DZoAfrzx.js.map} +1 -1
- package/public/assets/{security-ucr7IT1V.js → security-DRCj00eA.js} +2 -2
- package/public/assets/{security-ucr7IT1V.js.map → security-DRCj00eA.js.map} +1 -1
- package/public/assets/{select-D8UWCtYZ.js → select-ZOpLw2Fd.js} +2 -2
- package/public/assets/{select-D8UWCtYZ.js.map → select-ZOpLw2Fd.js.map} +1 -1
- package/public/assets/settings-DGgwr9WH.js +6 -0
- package/public/assets/settings-DGgwr9WH.js.map +1 -0
- package/public/assets/{store-DUfrGvOE.js → store-Bnag1kY-.js} +2 -2
- package/public/assets/{store-DUfrGvOE.js.map → store-Bnag1kY-.js.map} +1 -1
- package/public/assets/{tasks-jRUvxUd5.js → tasks-DUTu8ZhA.js} +2 -2
- package/public/assets/{tasks-jRUvxUd5.js.map → tasks-DUTu8ZhA.js.map} +1 -1
- package/public/assets/{teams-Dn0yggD-.js → teams-BJpaoG2m.js} +2 -2
- package/public/assets/{teams-Dn0yggD-.js.map → teams-BJpaoG2m.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-fLly20IP.js → terminal-viewer-impl-CQhh6oXa.js} +2 -2
- package/public/assets/{terminal-viewer-impl-fLly20IP.js.map → terminal-viewer-impl-CQhh6oXa.js.map} +1 -1
- package/public/assets/types-BiJFuTiL.js +2 -0
- package/public/assets/types-BiJFuTiL.js.map +1 -0
- package/public/assets/{user-avatar-BWLUHt2e.js → user-avatar-BYGrUx1U.js} +2 -2
- package/public/assets/{user-avatar-BWLUHt2e.js.map → user-avatar-BYGrUx1U.js.map} +1 -1
- package/public/assets/{work-queue-BAnNY0xE.js → work-queue-BH2-mARc.js} +2 -2
- package/public/assets/{work-queue-BAnNY0xE.js.map → work-queue-BH2-mARc.js.map} +1 -1
- package/public/assets/{workspaces-CIsQqQby.js → workspaces-DldYyXZR.js} +2 -2
- package/public/assets/{workspaces-CIsQqQby.js.map → workspaces-DldYyXZR.js.map} +1 -1
- package/public/index.html +4 -4
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/plugins/claude/monitors/relay-monitor.provisioned.mjs +6 -0
- package/src/branch-landed.ts +93 -4
- package/src/config-store.ts +12 -1
- package/src/db/agents.ts +3 -2
- package/src/db/continuations.ts +13 -3
- package/src/db/index.ts +3 -0
- package/src/db/issue-lifecycle.ts +12 -0
- package/src/db/mappers.ts +17 -0
- package/src/db/messages.ts +9 -4
- package/src/db/migrations.ts +91 -1
- package/src/db/notification-subscriptions.ts +1 -1
- package/src/db/prompt-templates.ts +19 -0
- package/src/db/provider-quotas.ts +6 -0
- package/src/db/schema.ts +67 -1
- package/src/db/stats.ts +3 -2
- package/src/db/task-comment-through.ts +112 -0
- package/src/db/task-core.ts +14 -1
- package/src/db/task-entity.ts +629 -0
- package/src/db/task-maintenance.ts +4 -3
- package/src/db/task-memory.ts +180 -0
- package/src/db/tasks.ts +24 -10
- package/src/issue-tracker/adapter.ts +15 -0
- package/src/issue-tracker/github.ts +40 -2
- package/src/issue-tracker/index.ts +1 -0
- package/src/mcp/index.ts +12 -1
- package/src/mcp/tools-spawn.ts +4 -0
- package/src/mcp-dispatch-tools.ts +164 -0
- package/src/mcp-notification-subscription-tools.ts +62 -4
- package/src/mcp-plan-tools.ts +30 -3
- package/src/mcp-release-tools.ts +33 -0
- package/src/mcp-task-memory-tools.ts +132 -0
- package/src/mcp-task-tools.ts +232 -0
- package/src/mcp-task-weave-tools.ts +66 -0
- package/src/notification-router.ts +24 -2
- package/src/notification-rules.ts +4 -0
- package/src/notification-types.ts +25 -0
- package/src/prompt-resolver.ts +66 -3
- package/src/routes/agents-spawn.ts +10 -0
- package/src/routes/commands.ts +17 -1
- package/src/routes/index.ts +16 -2
- package/src/routes/plans.ts +10 -2
- package/src/routes/provider-quotas.ts +13 -1
- package/src/routes/tasks.ts +276 -2
- package/src/routing-policy-store.ts +147 -2
- package/src/security.ts +2 -0
- package/src/self-resume.ts +13 -2
- package/src/services/issue-lifecycle.ts +9 -1
- package/src/services/plan-graph.ts +107 -9
- package/src/services/release-readiness.ts +142 -0
- package/src/services/send-message.ts +31 -0
- package/src/services/spawn-agent.ts +81 -4
- package/src/services/task-context-hydration.ts +361 -0
- package/src/services/task-entity.ts +149 -0
- package/src/services/task-issue-weave.ts +558 -0
- package/src/services/task-lifecycle.ts +465 -0
- package/src/services/task-plan-view.ts +178 -0
- package/src/services/task-semantic-events.ts +137 -0
- package/src/services/transient-agent-reaper.ts +71 -23
- package/src/spawn-targets.ts +27 -2
- package/src/task-dispatcher.ts +461 -0
- package/src/upgrade-install-verify.ts +20 -1
- package/public/assets/display-BWi8DQCp.js +0 -3
- package/public/assets/display-BWi8DQCp.js.map +0 -1
- package/public/assets/index-Vmhw442d.js +0 -23
- package/public/assets/index-Vmhw442d.js.map +0 -1
- package/public/assets/settings-Dvf_ps1B.js +0 -6
- package/public/assets/settings-Dvf_ps1B.js.map +0 -1
- package/public/assets/types-DorDDvEb.js +0 -2
- package/public/assets/types-DorDDvEb.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspaces-CIsQqQby.js","names":[],"sources":["../../dashboard/src/components/views/workspaces-list.tsx","../../dashboard/src/components/views/workspaces.tsx"],"sourcesContent":["import { useEffect, type ReactNode } from 'react'\nimport { FolderOpen, PackageOpen, RefreshCw } from 'lucide-react'\nimport { TERMINAL_WORKSPACE_STATUS_VALUES } from 'agent-relay-sdk/types'\nimport { useRelayStore } from '@/store'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'\nimport type { WorkspaceOrphan, WorkspaceRecord, WorkspaceStatus } from '@/types'\n\nexport const LIVE_WORKSPACE_STATUSES = new Set<WorkspaceStatus>(['active', 'ready', 'conflict', 'review_requested', 'merge_planned', 'cleanup_requested'])\nconst TERMINAL_STATUSES = new Set<WorkspaceStatus>(TERMINAL_WORKSPACE_STATUS_VALUES)\n\nexport type WorkspaceFilter = 'active' | 'cleaned' | 'all'\nexport type WorkspaceProjectFilter = string\n\nexport const ALL_PROJECTS = '__all__'\nexport const UNASSIGNED_PROJECTS = '__unassigned__'\n\ntype RepoWorkspaceGroup = {\n repoRoot: string\n workspaces: WorkspaceRecord[]\n}\n\nexport type WorkspaceProjectGroup = {\n key: string\n title: string\n projectId?: string\n projectSlug?: string\n unassigned: boolean\n repoGroups: RepoWorkspaceGroup[]\n}\n\nfunction repoWorkspaceGroups(workspaces: WorkspaceRecord[], workspaceSort: (a: WorkspaceRecord, b: WorkspaceRecord) => number): RepoWorkspaceGroup[] {\n const grouped = new Map<string, WorkspaceRecord[]>()\n for (const workspace of workspaces) {\n const key = workspace.repoRoot || workspace.sourceCwd || 'unknown repo'\n grouped.set(key, [...(grouped.get(key) || []), workspace])\n }\n return [...grouped.entries()]\n .map(([repoRoot, items]) => ({ repoRoot, workspaces: items.sort(workspaceSort) }))\n .sort((a, b) => a.repoRoot.localeCompare(b.repoRoot))\n}\n\nexport function groupWorkspaces(workspaces: WorkspaceRecord[], workspaceSort: (a: WorkspaceRecord, b: WorkspaceRecord) => number): WorkspaceProjectGroup[] {\n const grouped = new Map<string, WorkspaceRecord[]>()\n for (const workspace of workspaces) {\n const key = workspace.projectSlug || workspace.repoRoot || workspace.sourceCwd || 'unassigned'\n grouped.set(key, [...(grouped.get(key) || []), workspace])\n }\n return [...grouped.entries()]\n .map(([key, items]) => {\n const sample = items[0]!\n const unassigned = !sample.projectId\n const title = unassigned ? (sample.repoRoot || sample.sourceCwd || 'unknown repo') : (sample.projectTitle || sample.projectSlug || sample.repoRoot)\n return {\n key,\n title,\n ...(sample.projectId ? { projectId: sample.projectId } : {}),\n ...(sample.projectSlug ? { projectSlug: sample.projectSlug } : {}),\n unassigned,\n repoGroups: repoWorkspaceGroups(items, workspaceSort),\n }\n })\n .sort((a, b) => {\n const liveA = a.repoGroups.some((group) => group.workspaces.some((item) => LIVE_WORKSPACE_STATUSES.has(item.status)))\n const liveB = b.repoGroups.some((group) => group.workspaces.some((item) => LIVE_WORKSPACE_STATUSES.has(item.status)))\n if (liveA !== liveB) return liveA ? -1 : 1\n return a.title.localeCompare(b.title)\n })\n}\n\nexport function filterWorkspaces(workspaces: WorkspaceRecord[], filter: WorkspaceFilter, projectFilter: WorkspaceProjectFilter): WorkspaceRecord[] {\n const byStatus = filter === 'all'\n ? workspaces\n : filter === 'cleaned'\n ? workspaces.filter((workspace) => TERMINAL_STATUSES.has(workspace.status))\n : workspaces.filter((workspace) => !TERMINAL_STATUSES.has(workspace.status))\n if (projectFilter === ALL_PROJECTS) return byStatus\n if (projectFilter === UNASSIGNED_PROJECTS) return byStatus.filter((workspace) => !workspace.projectId)\n return byStatus.filter((workspace) => workspace.projectId === projectFilter)\n}\n\nexport function ProjectGroup({\n group,\n renderWorkspaceRow,\n}: {\n group: WorkspaceProjectGroup\n renderWorkspaceRow: (workspace: WorkspaceRecord) => ReactNode\n}) {\n const workspaces = group.repoGroups.flatMap((repoGroup) => repoGroup.workspaces)\n const liveCount = workspaces.filter((item) => LIVE_WORKSPACE_STATUSES.has(item.status)).length\n const readyCount = workspaces.filter((item) => item.status === 'ready' || item.status === 'review_requested' || item.status === 'merge_planned').length\n const conflictCount = workspaces.filter((item) => item.status === 'conflict').length\n const showRepoSubgroups = group.repoGroups.length > 1\n\n return (\n <Card>\n <CardHeader className=\"px-4 py-3\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <CardTitle className=\"truncate text-sm font-medium\" title={group.title}>{group.title}</CardTitle>\n {group.unassigned && <Badge variant=\"outline\" className=\"text-[10px]\">Unassigned</Badge>}\n </div>\n <div className=\"mt-1 flex flex-wrap gap-1.5\">\n <Badge variant=\"outline\" className=\"text-[10px]\">{workspaces.length} total</Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">{liveCount} live</Badge>\n {readyCount > 0 && <Badge variant=\"outline\" className=\"border-emerald-500/20 text-emerald-400 text-[10px]\">{readyCount} merge signal</Badge>}\n {conflictCount > 0 && <Badge variant=\"outline\" className=\"border-red-500/20 text-red-400 text-[10px]\">{conflictCount} conflict</Badge>}\n </div>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"p-0\">\n {group.repoGroups.map((repoGroup) => (\n <div key={repoGroup.repoRoot} className=\"border-t border-border first:border-t-0\">\n {showRepoSubgroups && (\n <div className=\"flex items-center gap-2 border-b border-dashed border-border/80 px-3 py-2\">\n <FolderOpen className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate font-mono text-[11px] text-muted-foreground\" title={repoGroup.repoRoot}>{repoGroup.repoRoot}</span>\n </div>\n )}\n {repoGroup.workspaces.map((workspace) => renderWorkspaceRow(workspace))}\n </div>\n ))}\n </CardContent>\n </Card>\n )\n}\n\nfunction shortPath(path: string): string {\n const parts = path.split('/').filter(Boolean)\n if (parts.length <= 3) return path || '-'\n return `/${parts.slice(-3).join('/')}`\n}\n\nexport function OrphanPanel() {\n const orphans = useRelayStore((s) => s.workspaceOrphans)\n const fetchWorkspaceOrphans = useRelayStore((s) => s.fetchWorkspaceOrphans)\n const reclaimWorkspaceOrphan = useRelayStore((s) => s.reclaimWorkspaceOrphan)\n\n useEffect(() => {\n void fetchWorkspaceOrphans()\n }, [fetchWorkspaceOrphans])\n\n if (!orphans.length) return null\n\n const renderOrphan = (orphan: WorkspaceOrphan) => (\n <div key={orphan.worktreePath} className=\"flex items-center gap-3 border-t border-border px-4 py-2 text-xs\">\n <FolderOpen className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate font-medium\" title={orphan.branch}>{orphan.branch || '(detached)'}</span>\n <span className=\"truncate font-mono text-muted-foreground\" title={orphan.worktreePath}>{shortPath(orphan.worktreePath)}</span>\n {orphan.hadTerminalRow && <Badge variant=\"outline\" className=\"text-[10px]\">cleanup failed</Badge>}\n <Button size=\"sm\" variant=\"outline\" className=\"ml-auto h-7 text-xs\" onClick={() => void reclaimWorkspaceOrphan(orphan)}>\n <PackageOpen className=\"mr-1 h-3.5 w-3.5\" />Reclaim\n </Button>\n </div>\n )\n\n return (\n <Card className=\"border-amber-500/30\">\n <CardHeader className=\"px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <PackageOpen className=\"h-4 w-4 text-amber-400\" />\n <CardTitle className=\"text-sm font-medium\">Orphaned worktrees</CardTitle>\n <Badge variant=\"outline\" className=\"border-amber-500/30 text-amber-400 text-[10px]\">{orphans.length}</Badge>\n <Button size=\"icon-sm\" variant=\"ghost\" className=\"ml-auto h-6 w-6\" title=\"Rescan\" onClick={() => void fetchWorkspaceOrphans()}>\n <RefreshCw className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\n <p className=\"mt-1 text-xs text-muted-foreground\">Agent worktrees on disk with no live workspace. Reclaim removes them from the host.</p>\n </CardHeader>\n <CardContent className=\"p-0\">{orphans.map(renderOrphan)}</CardContent>\n </Card>\n )\n}\n","import { useEffect, useMemo, useRef, useState } from 'react'\nimport { ArrowDown, ArrowUp, Check, ChevronDown, ChevronRight, Clock, Eye, FileDiff, FilePen, Flag, FolderOpen, GitBranch, GitCommitHorizontal, GitMerge, RefreshCw, TriangleAlert, Trash2, X } from 'lucide-react'\nimport { api } from '@/lib/api'\nimport { useRelayStore, useNow } from '@/store'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { CopyButton } from '@/components/shared/copy-button'\nimport { displayName, timeAgo } from '@/lib/display'\nimport { ALL_PROJECTS, filterWorkspaces, groupWorkspaces, LIVE_WORKSPACE_STATUSES, OrphanPanel, ProjectGroup, UNASSIGNED_PROJECTS, type WorkspaceFilter, type WorkspaceProjectFilter } from './workspaces-list'\nimport type { Agent, Project, WorkspaceRecord, WorkspaceStatus } from '@/types'\n\nconst GIT_STATE_STATUSES = new Set<WorkspaceStatus>(['active', 'ready', 'conflict', 'review_requested', 'merge_planned'])\nconst MERGEABLE_STATUSES = new Set<WorkspaceStatus>(['active', 'ready', 'review_requested', 'merge_planned', 'conflict'])\n\nconst STATUS_CLASS: Record<WorkspaceStatus, string> = {\n active: 'bg-blue-500/10 text-blue-400 border-blue-500/20',\n ready: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n conflict: 'bg-red-500/10 text-red-400 border-red-500/20',\n review_requested: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n merge_planned: 'bg-indigo-500/10 text-indigo-400 border-indigo-500/20',\n merged: 'bg-violet-500/10 text-violet-400 border-violet-500/20',\n abandoned: 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20',\n cleanup_requested: 'bg-orange-500/10 text-orange-400 border-orange-500/20',\n cleaned: 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20',\n}\n\nconst STATUS_LABEL: Record<WorkspaceStatus, string> = {\n active: 'active',\n ready: 'ready',\n conflict: 'conflict',\n review_requested: 'review',\n merge_planned: 'merge planned',\n merged: 'merged',\n abandoned: 'abandoned',\n cleanup_requested: 'cleanup',\n cleaned: 'cleaned',\n}\n\nconst STATUS_ORDER: Record<WorkspaceStatus, number> = {\n conflict: 0,\n ready: 1,\n review_requested: 2,\n merge_planned: 3,\n active: 4,\n cleanup_requested: 5,\n merged: 6,\n abandoned: 7,\n cleaned: 8,\n}\n\nfunction agentLabel(agent: Agent | undefined, fallback: string | undefined): string {\n if (agent) return displayName(agent)\n return fallback || '-'\n}\n\nfunction shortPath(path: string): string {\n const parts = path.split('/').filter(Boolean)\n if (parts.length <= 3) return path || '-'\n return `/${parts.slice(-3).join('/')}`\n}\n\nfunction statusTone(status: WorkspaceStatus): string {\n return STATUS_CLASS[status] || 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n}\n\nfunction workspaceSort(a: WorkspaceRecord, b: WorkspaceRecord): number {\n const status = (STATUS_ORDER[a.status] ?? 99) - (STATUS_ORDER[b.status] ?? 99)\n if (status !== 0) return status\n return Number(b.updatedAt || 0) - Number(a.updatedAt || 0)\n}\n\nfunction WorkspaceActions({ workspace, expanded, onToggleDetails }: { workspace: WorkspaceRecord; expanded: boolean; onToggleDetails: () => void }) {\n const workspaceAction = useRelayStore((s) => s.workspaceAction)\n const purgeWorkspace = useRelayStore((s) => s.purgeWorkspace)\n const fetchWorkspaceMergePreview = useRelayStore((s) => s.fetchWorkspaceMergePreview)\n const openFilesAt = useRelayStore((s) => s.openFilesAt)\n const terminal = workspace.status === 'cleaned' || workspace.status === 'merged' || workspace.status === 'abandoned'\n const disabled = terminal || workspace.status === 'cleanup_requested'\n const openPath = workspace.worktreePath || workspace.sourceCwd || workspace.repoRoot\n // Already-landed work (squash/cherry-pick) has nothing left to merge — a merge\n // would open a duplicate/no-op PR, so disable the button.\n const gitState = useRelayStore((s) => s.workspaceGitState[workspace.id])\n const landed = !!gitState && gitState.available !== false && gitState.landed === true\n const mergeable = workspace.mode === 'isolated' && Boolean(workspace.worktreePath) && MERGEABLE_STATUSES.has(workspace.status) && !landed\n\n // Refresh the preview so the confirm dialog states the strategy/outcome, then\n // dispatch the real merge command.\n async function merge() {\n await fetchWorkspaceMergePreview(workspace.id)\n await workspaceAction(workspace.id, 'merge')\n }\n\n return (\n <div className=\"flex flex-wrap justify-end gap-1.5\">\n <Button size=\"icon-sm\" variant=\"ghost\" title={expanded ? 'Hide details' : 'Show diff & timeline'} onClick={onToggleDetails}>\n {expanded ? <ChevronDown className=\"h-3.5 w-3.5\" /> : <ChevronRight className=\"h-3.5 w-3.5\" />}\n </Button>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n title=\"Open workspace\"\n disabled={!openPath || workspace.status === 'cleaned'}\n onClick={() => void openFilesAt({ path: openPath })}\n >\n <FolderOpen className=\"h-3.5 w-3.5\" />\n </Button>\n <CopyButton value={openPath} label=\"Copy path\" copiedLabel=\"Copied path\" size=\"icon-sm\" variant=\"ghost\" disabled={!openPath} />\n <Button size=\"icon-sm\" variant=\"outline\" title=\"Mark ready\" disabled={disabled || workspace.status === 'ready'} onClick={() => void workspaceAction(workspace.id, 'ready')}>\n <Check className=\"h-3.5 w-3.5\" />\n </Button>\n <Button size=\"icon-sm\" variant=\"outline\" title=\"Request review\" disabled={disabled || workspace.status === 'review_requested'} onClick={() => void workspaceAction(workspace.id, 'request-review')}>\n <Eye className=\"h-3.5 w-3.5\" />\n </Button>\n <Button size=\"icon-sm\" variant=\"outline\" title=\"Mark merge planned\" disabled={disabled || workspace.status === 'merge_planned'} onClick={() => void workspaceAction(workspace.id, 'merge-plan')}>\n <Flag className=\"h-3.5 w-3.5\" />\n </Button>\n <Button size=\"icon-sm\" variant=\"default\" title={landed ? 'Already merged into base — nothing to land' : 'Merge & land work'} disabled={!mergeable} onClick={() => void merge()}>\n <GitMerge className=\"h-3.5 w-3.5\" />\n </Button>\n <Button size=\"icon-sm\" variant=\"outline\" title=\"Request cleanup (removes the worktree on the host)\" disabled={workspace.status === 'cleaned' || workspace.status === 'cleanup_requested'} onClick={() => void workspaceAction(workspace.id, 'cleanup')}>\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n {terminal && (\n <Button size=\"icon-sm\" variant=\"ghost\" title=\"Purge record (clears the row, does not touch disk)\" onClick={() => void purgeWorkspace(workspace.id)}>\n <X className=\"h-3.5 w-3.5\" />\n </Button>\n )}\n </div>\n )\n}\n\n// Pre-merge conflict hint, shown next to git state when the worktree has work to\n// land. Lazily fetched from the owning host (an external sync), so an effect is\n// the right tool. Reports clean-ff vs would-conflict before the user merges.\nfunction MergePreviewHint({ workspace, unmerged }: { workspace: WorkspaceRecord; unmerged: number }) {\n const preview = useRelayStore((s) => s.workspaceMergePreview[workspace.id])\n const fetchWorkspaceMergePreview = useRelayStore((s) => s.fetchWorkspaceMergePreview)\n const eligible = unmerged > 0 && MERGEABLE_STATUSES.has(workspace.status)\n\n useEffect(() => {\n if (eligible && preview === undefined) void fetchWorkspaceMergePreview(workspace.id)\n }, [eligible, preview, workspace.id, fetchWorkspaceMergePreview])\n\n if (!eligible || preview === undefined || preview.available === false) return null\n if (preview.conflict) {\n return (\n <span className=\"flex items-center gap-0.5 text-amber-400\" title={`Merging into ${preview.baseRef || 'base'} would conflict — resolve before merging`}>\n <TriangleAlert className=\"h-3 w-3\" />conflict\n </span>\n )\n }\n if (preview.conflict === false) {\n return (\n <span className=\"flex items-center gap-0.5 text-emerald-400\" title={`Clean to merge via ${preview.strategy === 'pr' ? 'PR' : 'rebase + fast-forward'} into ${preview.baseRef || 'base'}`}>\n <Check className=\"h-3 w-3\" />{preview.strategy === 'pr' ? 'PR ready' : 'clean'}\n </span>\n )\n }\n return null\n}\n\n// Live git state of the worktree, fetched lazily from the owning host. This is\n// an external-system sync (host git over the orchestrator proxy), so an effect\n// is the right tool — it does not derive state from props.\nfunction GitState({ workspace }: { workspace: WorkspaceRecord }) {\n const now = useNow()\n const gitState = useRelayStore((s) => s.workspaceGitState[workspace.id])\n const fetchWorkspaceGitState = useRelayStore((s) => s.fetchWorkspaceGitState)\n const eligible = workspace.mode === 'isolated' && Boolean(workspace.worktreePath) && GIT_STATE_STATUSES.has(workspace.status)\n\n useEffect(() => {\n if (eligible && gitState === undefined) void fetchWorkspaceGitState(workspace.id)\n }, [eligible, gitState, workspace.id, fetchWorkspaceGitState])\n\n if (!eligible) return null\n\n const refresh = (\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n className=\"h-5 w-5\"\n title=\"Refresh git state\"\n onClick={() => void fetchWorkspaceGitState(workspace.id)}\n >\n <RefreshCw className=\"h-3 w-3\" />\n </Button>\n )\n\n if (gitState === undefined) {\n return <div className=\"flex items-center gap-1 text-[11px] text-muted-foreground\">Loading git state…</div>\n }\n if (gitState.available === false) {\n return (\n <div className=\"flex items-center gap-1 text-[11px] text-muted-foreground\" title={gitState.reason}>\n <span>git state unavailable</span>\n {refresh}\n </div>\n )\n }\n if (gitState.missing) {\n return <div className=\"flex items-center gap-1 text-[11px] text-muted-foreground\">worktree gone {refresh}</div>\n }\n if (gitState.error) {\n return (\n <div className=\"flex items-center gap-1 text-[11px] text-red-400\" title={gitState.error}>\n <span>git error</span>\n {refresh}\n </div>\n )\n }\n\n const ahead = gitState.ahead ?? 0\n // Work squash/cherry-pick-merged into base leaves `ahead` positive but is\n // effectively landed — surface the unmerged count, not the raw ahead.\n const landed = gitState.landed === true\n const unmerged = landed ? 0 : (gitState.unmergedAhead ?? ahead)\n const behind = gitState.behind ?? 0\n const dirty = gitState.dirtyCount ?? 0\n const clean = unmerged === 0 && dirty === 0 && !landed\n const commit = gitState.lastCommit\n\n return (\n <div className=\"space-y-0.5\">\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[11px]\">\n {clean ? (\n <span className=\"text-muted-foreground\">no unmerged work</span>\n ) : (\n <>\n {landed ? (\n <span className=\"flex items-center gap-0.5 text-emerald-400\" title={`Work already landed in ${gitState.baseRef || 'base'} via squash/cherry-pick — safe to clean up`}>\n <Check className=\"h-3 w-3\" />landed\n </span>\n ) : (\n <span className={unmerged > 0 ? 'flex items-center text-emerald-400' : 'flex items-center text-muted-foreground'} title={ahead !== unmerged ? `${unmerged} unmerged commit(s) (${ahead} ahead of base)` : `${unmerged} commit(s) ahead of base`}>\n <ArrowUp className=\"h-3 w-3\" />{unmerged}\n </span>\n )}\n <span className={behind > 0 ? 'flex items-center text-amber-400' : 'flex items-center text-muted-foreground'} title={`${behind} commit(s) behind base`}>\n <ArrowDown className=\"h-3 w-3\" />{behind}\n </span>\n {dirty > 0 && (\n <span className=\"flex items-center text-orange-400\" title={`${dirty} uncommitted change(s)`}>\n <FilePen className=\"h-3 w-3\" />{dirty}\n </span>\n )}\n {!landed && <MergePreviewHint workspace={workspace} unmerged={unmerged} />}\n </>\n )}\n {refresh}\n </div>\n {commit && (\n <div className=\"flex min-w-0 items-center gap-1 text-[11px] text-muted-foreground\" title={`${commit.sha.slice(0, 8)} — ${commit.message}`}>\n <GitCommitHorizontal className=\"h-3 w-3 shrink-0\" />\n <span className=\"truncate\">{commit.message || commit.sha.slice(0, 8)}</span>\n {commit.at ? <span className=\"shrink-0 opacity-70\">· {timeAgo(now, commit.at)}</span> : null}\n </div>\n )}\n </div>\n )\n}\n\n// Lifecycle timeline from the timestamps the relay already records:\n// created → ready → merged → cleaned. Surfaces history without a drawer.\nfunction WorkspaceTimeline({ workspace }: { workspace: WorkspaceRecord }) {\n const now = useNow()\n const mergedAt = typeof workspace.metadata?.mergedAt === 'number' ? (workspace.metadata.mergedAt as number) : undefined\n const steps = [\n { label: 'created', at: workspace.createdAt },\n { label: 'ready', at: workspace.readyAt },\n { label: 'merged', at: mergedAt },\n { label: 'cleaned', at: workspace.cleanedAt },\n ].filter((step): step is { label: string; at: number } => typeof step.at === 'number' && step.at > 0)\n\n return (\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-muted-foreground\">\n <span className=\"flex items-center gap-1 font-medium text-foreground\"><Clock className=\"h-3 w-3\" />Timeline</span>\n {steps.map((step, i) => (\n <span key={step.label} className=\"flex items-center gap-1\">\n {i > 0 && <span className=\"opacity-40\">→</span>}\n <span className=\"text-foreground\">{step.label}</span>\n <span title={new Date(step.at).toLocaleString()}>{timeAgo(now, step.at)}</span>\n </span>\n ))}\n </div>\n )\n}\n\nfunction diffLineClass(line: string): string {\n if (line.startsWith('+') && !line.startsWith('+++')) return 'text-emerald-400'\n if (line.startsWith('-') && !line.startsWith('---')) return 'text-red-400'\n if (line.startsWith('@@')) return 'text-cyan-400'\n if (line.startsWith('diff ') || line.startsWith('index ') || line.startsWith('+++') || line.startsWith('---')) return 'text-muted-foreground'\n return ''\n}\n\n// Diff of committed work against base, lazily fetched from the owning host.\nfunction WorkspaceDiffView({ workspace }: { workspace: WorkspaceRecord }) {\n const diff = useRelayStore((s) => s.workspaceDiff[workspace.id])\n const fetchWorkspaceDiff = useRelayStore((s) => s.fetchWorkspaceDiff)\n const eligible = workspace.mode === 'isolated' && Boolean(workspace.worktreePath)\n\n useEffect(() => {\n if (eligible && diff === undefined) void fetchWorkspaceDiff(workspace.id)\n }, [eligible, diff, workspace.id, fetchWorkspaceDiff])\n\n if (!eligible) return null\n if (diff === undefined) return <div className=\"text-[11px] text-muted-foreground\">Loading diff…</div>\n if (diff.available === false) return <div className=\"text-[11px] text-muted-foreground\">Diff unavailable{diff.reason ? `: ${diff.reason}` : ''}</div>\n if (diff.missing) return <div className=\"text-[11px] text-muted-foreground\">Worktree no longer on disk</div>\n if (diff.error) return <div className=\"text-[11px] text-red-400\">Diff error: {diff.error}</div>\n if (!diff.files.length) return <div className=\"text-[11px] text-muted-foreground\">No committed changes against {diff.baseRef || 'base'}</div>\n\n const refresh = (\n <Button size=\"icon-sm\" variant=\"ghost\" className=\"h-5 w-5\" title=\"Refresh diff\" onClick={() => void fetchWorkspaceDiff(workspace.id)}>\n <RefreshCw className=\"h-3 w-3\" />\n </Button>\n )\n\n return (\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2 text-[11px] font-medium\">\n <FileDiff className=\"h-3 w-3\" />\n <span>{diff.files.length} file{diff.files.length === 1 ? '' : 's'} vs {diff.baseRef || 'base'}</span>\n {refresh}\n </div>\n <ul className=\"space-y-0.5 text-[11px]\">\n {diff.files.map((file) => (\n <li key={file.path} className=\"flex items-center gap-2 font-mono\">\n <span className=\"truncate\" title={file.path}>{file.path}</span>\n {file.binary ? (\n <span className=\"shrink-0 text-muted-foreground\">binary</span>\n ) : (\n <span className=\"shrink-0\">\n <span className=\"text-emerald-400\">+{file.additions ?? 0}</span>{' '}\n <span className=\"text-red-400\">−{file.deletions ?? 0}</span>\n </span>\n )}\n </li>\n ))}\n </ul>\n {diff.patch && (\n <pre className=\"max-h-80 overflow-auto rounded border border-border bg-muted/30 p-2 text-[10.5px] leading-relaxed\">\n {diff.patch.split('\\n').map((line, i) => (\n <div key={i} className={diffLineClass(line)}>{line || ' '}</div>\n ))}\n {diff.truncated && <div className=\"mt-1 text-muted-foreground\">… patch truncated</div>}\n </pre>\n )}\n </div>\n )\n}\n\nfunction WorkspaceDetails({ workspace }: { workspace: WorkspaceRecord }) {\n return (\n <div className=\"space-y-3 border-t border-dashed border-border bg-muted/20 px-3 py-3\">\n <WorkspaceTimeline workspace={workspace} />\n <WorkspaceDiffView workspace={workspace} />\n </div>\n )\n}\n\n// A `pr`-strategy merge lands as an opened PR, not a local fast-forward, so the\n// outcome only shows up here — surface the PR link and any error the orchestrator\n// returned (which the command layer keeps in result even when status=succeeded).\nfunction prLabel(url: string): string {\n const match = url.match(/\\/pull\\/(\\d+)/)\n return match ? `PR #${match[1]}` : 'View PR'\n}\n\nfunction MergeOutcome({ workspace }: { workspace: WorkspaceRecord }) {\n const result = workspace.metadata?.mergeResult as { prUrl?: string; error?: string } | undefined\n const prUrl = result?.prUrl\n const error = result?.error || (workspace.metadata?.mergeError as string | undefined)\n if (!prUrl && !error) return null\n return (\n <>\n {prUrl && (\n <a\n href={prUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n title={prUrl}\n className=\"inline-flex items-center gap-0.5 rounded border border-violet-500/30 px-1.5 py-0 text-[10px] text-violet-400 hover:text-violet-300\"\n >\n <GitMerge className=\"h-2.5 w-2.5\" />{prLabel(prUrl)}\n </a>\n )}\n {error && (\n <Badge variant=\"outline\" className=\"max-w-[16rem] border-amber-500/30 text-amber-400 text-[10px]\" title={error}>\n <TriangleAlert className=\"mr-0.5 h-2.5 w-2.5 shrink-0\" /><span className=\"truncate\">merge: {error}</span>\n </Badge>\n )}\n </>\n )\n}\n\nfunction WorkspaceRow({ workspace }: { workspace: WorkspaceRecord }) {\n const now = useNow()\n const [expanded, setExpanded] = useState(false)\n const agentsById = useRelayStore((s) => s.agentsById)\n const focused = useRelayStore((s) => s.workspaceFocusId === workspace.id)\n const owner = workspace.ownerAgentId ? agentsById[workspace.ownerAgentId] : undefined\n const steward = workspace.stewardAgentId ? agentsById[workspace.stewardAgentId] : undefined\n const path = workspace.worktreePath || workspace.sourceCwd || workspace.repoRoot\n const rowRef = useRef<HTMLDivElement>(null)\n\n // Click-through landing (#236): scroll the targeted row into view once it mounts.\n useEffect(() => {\n if (focused) rowRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' })\n }, [focused])\n\n return (\n <div ref={rowRef} className={`border-t border-border ${focused ? 'bg-primary/5 ring-1 ring-inset ring-primary/40' : ''}`}>\n <div className=\"grid gap-3 px-3 py-3 text-sm lg:grid-cols-[minmax(180px,1.2fr)_minmax(220px,1.5fr)_minmax(160px,1fr)_minmax(170px,auto)]\">\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <GitBranch className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate font-medium\" title={workspace.branch || workspace.id}>{workspace.branch || workspace.id}</span>\n </div>\n <div className=\"flex flex-wrap gap-1\">\n <Badge variant=\"outline\" className={`text-[10px] ${workspace.mode === 'isolated' ? 'border-sky-500/30 text-sky-400' : 'border-zinc-500/30 text-zinc-400'}`}>\n {workspace.mode}\n </Badge>\n {workspace.requestedMode && workspace.requestedMode !== workspace.mode && (\n <Badge variant=\"outline\" className=\"text-[10px]\">requested {workspace.requestedMode}</Badge>\n )}\n <Badge variant=\"outline\" className={`text-[10px] ${statusTone(workspace.status)}`}>{STATUS_LABEL[workspace.status] || workspace.status}</Badge>\n <MergeOutcome workspace={workspace} />\n </div>\n </div>\n\n <div className=\"min-w-0 space-y-1\">\n <div className=\"truncate font-mono text-xs\" title={path}>{shortPath(path)}</div>\n <div className=\"truncate text-xs text-muted-foreground\" title={workspace.sourceCwd}>{workspace.sourceCwd}</div>\n <GitState workspace={workspace} />\n </div>\n\n <div className=\"grid grid-cols-[4.5rem_minmax(0,1fr)] gap-x-2 gap-y-1 text-xs\">\n <span className=\"text-muted-foreground\">Owner</span>\n <span className=\"truncate\" title={workspace.ownerAgentId}>{agentLabel(owner, workspace.ownerPolicyName || workspace.ownerAgentId)}</span>\n <span className=\"text-muted-foreground\">Steward</span>\n <span className=\"truncate\" title={workspace.stewardAgentId}>{agentLabel(steward, workspace.stewardAgentId)}</span>\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{timeAgo(now, workspace.updatedAt)}</span>\n {workspace.baseRef && (\n <>\n <span className=\"text-muted-foreground\">Base</span>\n <span className=\"truncate\" title={workspace.baseSha}>{workspace.baseRef}</span>\n </>\n )}\n </div>\n\n <WorkspaceActions workspace={workspace} expanded={expanded} onToggleDetails={() => setExpanded((value) => !value)} />\n </div>\n {expanded && <WorkspaceDetails workspace={workspace} />}\n </div>\n )\n}\n\nexport function WorkspacesView() {\n const [filter, setFilter] = useState<WorkspaceFilter>('active')\n const [projectFilter, setProjectFilter] = useState<WorkspaceProjectFilter>(ALL_PROJECTS)\n const [projects, setProjects] = useState<Project[]>([])\n const workspaces = useRelayStore((s) => s.workspaces)\n const set = useRelayStore((s) => s.set)\n const workspaceFocusId = useRelayStore((s) => s.workspaceFocusId)\n\n // Badge click-through (#236): a focused workspace may be in any state, so widen\n // the filter to 'all' to guarantee its row renders. Clear the focus when leaving\n // the panel so the highlight doesn't linger on the next visit.\n useEffect(() => {\n if (workspaceFocusId) {\n setFilter('all')\n setProjectFilter(ALL_PROJECTS)\n }\n }, [workspaceFocusId])\n useEffect(() => () => { set({ workspaceFocusId: null }) }, [set])\n useEffect(() => {\n let cancelled = false\n api<{ projects: Project[] }>('GET', '/projects')\n .then((res) => { if (!cancelled) setProjects(res.projects ?? []) })\n .catch(() => { if (!cancelled) setProjects([]) })\n return () => { cancelled = true }\n }, [])\n\n const visibleWorkspaces = useMemo(() => filterWorkspaces(workspaces, filter, projectFilter), [workspaces, filter, projectFilter])\n const grouped = useMemo(() => groupWorkspaces(visibleWorkspaces, workspaceSort), [visibleWorkspaces])\n const liveCount = workspaces.filter((item) => LIVE_WORKSPACE_STATUSES.has(item.status)).length\n const isolatedCount = workspaces.filter((item) => item.mode === 'isolated').length\n const readyCount = workspaces.filter((item) => item.status === 'ready' || item.status === 'review_requested' || item.status === 'merge_planned').length\n const conflictCount = workspaces.filter((item) => item.status === 'conflict').length\n const doneCount = workspaces.filter((item) => item.status === 'merged' || item.status === 'abandoned' || item.status === 'cleaned').length\n const activeCount = workspaces.length - doneCount\n const filters: Array<{ key: WorkspaceFilter; label: string; count: number }> = [\n { key: 'active', label: 'Active', count: activeCount },\n { key: 'cleaned', label: 'Done', count: doneCount },\n { key: 'all', label: 'All', count: workspaces.length },\n ]\n const projectOptions = useMemo(() => {\n const options = [{ value: ALL_PROJECTS, label: 'All Projects' }]\n for (const project of projects) {\n options.push({ value: project.id, label: project.title || project.slug })\n }\n options.push({ value: UNASSIGNED_PROJECTS, label: 'Unassigned' })\n return options\n }, [projects])\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Workspaces</h1>\n <p className=\"text-sm text-muted-foreground\">Agent worktrees, ownership, and merge readiness.</p>\n </div>\n <div className=\"ml-auto flex flex-wrap gap-1.5\">\n <Badge variant=\"outline\">{isolatedCount} isolated</Badge>\n <Badge variant=\"outline\">{liveCount} live</Badge>\n <Badge variant=\"outline\">{readyCount} ready</Badge>\n {conflictCount > 0 && <Badge variant=\"outline\" className=\"border-red-500/20 text-red-400\">{conflictCount} conflict</Badge>}\n </div>\n </div>\n\n <OrphanPanel />\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"flex rounded-md border border-border bg-card p-1\">\n {filters.map((item) => (\n <Button\n key={item.key}\n size=\"sm\"\n variant={filter === item.key ? 'secondary' : 'ghost'}\n className=\"h-7 text-xs\"\n onClick={() => setFilter(item.key)}\n >\n {item.label}\n <span className=\"ml-1 text-muted-foreground\">{item.count}</span>\n </Button>\n ))}\n </div>\n <select\n className=\"h-8 min-w-[12rem] rounded-md border border-border bg-card px-2.5 text-sm text-foreground\"\n value={projectFilter}\n onChange={(e) => setProjectFilter(e.target.value)}\n >\n {projectOptions.map((option) => (\n <option key={option.value} value={option.value}>{option.label}</option>\n ))}\n </select>\n </div>\n\n {grouped.length === 0 ? (\n <div className=\"rounded-md border border-border px-3 py-12 text-center text-sm text-muted-foreground\">\n No {filter === 'all' ? '' : filter} workspaces{projectFilter !== ALL_PROJECTS ? ' in this project filter' : ''}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {grouped.map((group) => (\n <ProjectGroup key={group.key} group={group} renderWorkspaceRow={(workspace) => <WorkspaceRow key={workspace.id} workspace={workspace} />} />\n ))}\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"workspaces-DldYyXZR.js","names":[],"sources":["../../dashboard/src/components/views/workspaces-list.tsx","../../dashboard/src/components/views/workspaces.tsx"],"sourcesContent":["import { useEffect, type ReactNode } from 'react'\nimport { FolderOpen, PackageOpen, RefreshCw } from 'lucide-react'\nimport { TERMINAL_WORKSPACE_STATUS_VALUES } from 'agent-relay-sdk/types'\nimport { useRelayStore } from '@/store'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'\nimport type { WorkspaceOrphan, WorkspaceRecord, WorkspaceStatus } from '@/types'\n\nexport const LIVE_WORKSPACE_STATUSES = new Set<WorkspaceStatus>(['active', 'ready', 'conflict', 'review_requested', 'merge_planned', 'cleanup_requested'])\nconst TERMINAL_STATUSES = new Set<WorkspaceStatus>(TERMINAL_WORKSPACE_STATUS_VALUES)\n\nexport type WorkspaceFilter = 'active' | 'cleaned' | 'all'\nexport type WorkspaceProjectFilter = string\n\nexport const ALL_PROJECTS = '__all__'\nexport const UNASSIGNED_PROJECTS = '__unassigned__'\n\ntype RepoWorkspaceGroup = {\n repoRoot: string\n workspaces: WorkspaceRecord[]\n}\n\nexport type WorkspaceProjectGroup = {\n key: string\n title: string\n projectId?: string\n projectSlug?: string\n unassigned: boolean\n repoGroups: RepoWorkspaceGroup[]\n}\n\nfunction repoWorkspaceGroups(workspaces: WorkspaceRecord[], workspaceSort: (a: WorkspaceRecord, b: WorkspaceRecord) => number): RepoWorkspaceGroup[] {\n const grouped = new Map<string, WorkspaceRecord[]>()\n for (const workspace of workspaces) {\n const key = workspace.repoRoot || workspace.sourceCwd || 'unknown repo'\n grouped.set(key, [...(grouped.get(key) || []), workspace])\n }\n return [...grouped.entries()]\n .map(([repoRoot, items]) => ({ repoRoot, workspaces: items.sort(workspaceSort) }))\n .sort((a, b) => a.repoRoot.localeCompare(b.repoRoot))\n}\n\nexport function groupWorkspaces(workspaces: WorkspaceRecord[], workspaceSort: (a: WorkspaceRecord, b: WorkspaceRecord) => number): WorkspaceProjectGroup[] {\n const grouped = new Map<string, WorkspaceRecord[]>()\n for (const workspace of workspaces) {\n const key = workspace.projectSlug || workspace.repoRoot || workspace.sourceCwd || 'unassigned'\n grouped.set(key, [...(grouped.get(key) || []), workspace])\n }\n return [...grouped.entries()]\n .map(([key, items]) => {\n const sample = items[0]!\n const unassigned = !sample.projectId\n const title = unassigned ? (sample.repoRoot || sample.sourceCwd || 'unknown repo') : (sample.projectTitle || sample.projectSlug || sample.repoRoot)\n return {\n key,\n title,\n ...(sample.projectId ? { projectId: sample.projectId } : {}),\n ...(sample.projectSlug ? { projectSlug: sample.projectSlug } : {}),\n unassigned,\n repoGroups: repoWorkspaceGroups(items, workspaceSort),\n }\n })\n .sort((a, b) => {\n const liveA = a.repoGroups.some((group) => group.workspaces.some((item) => LIVE_WORKSPACE_STATUSES.has(item.status)))\n const liveB = b.repoGroups.some((group) => group.workspaces.some((item) => LIVE_WORKSPACE_STATUSES.has(item.status)))\n if (liveA !== liveB) return liveA ? -1 : 1\n return a.title.localeCompare(b.title)\n })\n}\n\nexport function filterWorkspaces(workspaces: WorkspaceRecord[], filter: WorkspaceFilter, projectFilter: WorkspaceProjectFilter): WorkspaceRecord[] {\n const byStatus = filter === 'all'\n ? workspaces\n : filter === 'cleaned'\n ? workspaces.filter((workspace) => TERMINAL_STATUSES.has(workspace.status))\n : workspaces.filter((workspace) => !TERMINAL_STATUSES.has(workspace.status))\n if (projectFilter === ALL_PROJECTS) return byStatus\n if (projectFilter === UNASSIGNED_PROJECTS) return byStatus.filter((workspace) => !workspace.projectId)\n return byStatus.filter((workspace) => workspace.projectId === projectFilter)\n}\n\nexport function ProjectGroup({\n group,\n renderWorkspaceRow,\n}: {\n group: WorkspaceProjectGroup\n renderWorkspaceRow: (workspace: WorkspaceRecord) => ReactNode\n}) {\n const workspaces = group.repoGroups.flatMap((repoGroup) => repoGroup.workspaces)\n const liveCount = workspaces.filter((item) => LIVE_WORKSPACE_STATUSES.has(item.status)).length\n const readyCount = workspaces.filter((item) => item.status === 'ready' || item.status === 'review_requested' || item.status === 'merge_planned').length\n const conflictCount = workspaces.filter((item) => item.status === 'conflict').length\n const showRepoSubgroups = group.repoGroups.length > 1\n\n return (\n <Card>\n <CardHeader className=\"px-4 py-3\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <CardTitle className=\"truncate text-sm font-medium\" title={group.title}>{group.title}</CardTitle>\n {group.unassigned && <Badge variant=\"outline\" className=\"text-[10px]\">Unassigned</Badge>}\n </div>\n <div className=\"mt-1 flex flex-wrap gap-1.5\">\n <Badge variant=\"outline\" className=\"text-[10px]\">{workspaces.length} total</Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">{liveCount} live</Badge>\n {readyCount > 0 && <Badge variant=\"outline\" className=\"border-emerald-500/20 text-emerald-400 text-[10px]\">{readyCount} merge signal</Badge>}\n {conflictCount > 0 && <Badge variant=\"outline\" className=\"border-red-500/20 text-red-400 text-[10px]\">{conflictCount} conflict</Badge>}\n </div>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"p-0\">\n {group.repoGroups.map((repoGroup) => (\n <div key={repoGroup.repoRoot} className=\"border-t border-border first:border-t-0\">\n {showRepoSubgroups && (\n <div className=\"flex items-center gap-2 border-b border-dashed border-border/80 px-3 py-2\">\n <FolderOpen className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate font-mono text-[11px] text-muted-foreground\" title={repoGroup.repoRoot}>{repoGroup.repoRoot}</span>\n </div>\n )}\n {repoGroup.workspaces.map((workspace) => renderWorkspaceRow(workspace))}\n </div>\n ))}\n </CardContent>\n </Card>\n )\n}\n\nfunction shortPath(path: string): string {\n const parts = path.split('/').filter(Boolean)\n if (parts.length <= 3) return path || '-'\n return `/${parts.slice(-3).join('/')}`\n}\n\nexport function OrphanPanel() {\n const orphans = useRelayStore((s) => s.workspaceOrphans)\n const fetchWorkspaceOrphans = useRelayStore((s) => s.fetchWorkspaceOrphans)\n const reclaimWorkspaceOrphan = useRelayStore((s) => s.reclaimWorkspaceOrphan)\n\n useEffect(() => {\n void fetchWorkspaceOrphans()\n }, [fetchWorkspaceOrphans])\n\n if (!orphans.length) return null\n\n const renderOrphan = (orphan: WorkspaceOrphan) => (\n <div key={orphan.worktreePath} className=\"flex items-center gap-3 border-t border-border px-4 py-2 text-xs\">\n <FolderOpen className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate font-medium\" title={orphan.branch}>{orphan.branch || '(detached)'}</span>\n <span className=\"truncate font-mono text-muted-foreground\" title={orphan.worktreePath}>{shortPath(orphan.worktreePath)}</span>\n {orphan.hadTerminalRow && <Badge variant=\"outline\" className=\"text-[10px]\">cleanup failed</Badge>}\n <Button size=\"sm\" variant=\"outline\" className=\"ml-auto h-7 text-xs\" onClick={() => void reclaimWorkspaceOrphan(orphan)}>\n <PackageOpen className=\"mr-1 h-3.5 w-3.5\" />Reclaim\n </Button>\n </div>\n )\n\n return (\n <Card className=\"border-amber-500/30\">\n <CardHeader className=\"px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <PackageOpen className=\"h-4 w-4 text-amber-400\" />\n <CardTitle className=\"text-sm font-medium\">Orphaned worktrees</CardTitle>\n <Badge variant=\"outline\" className=\"border-amber-500/30 text-amber-400 text-[10px]\">{orphans.length}</Badge>\n <Button size=\"icon-sm\" variant=\"ghost\" className=\"ml-auto h-6 w-6\" title=\"Rescan\" onClick={() => void fetchWorkspaceOrphans()}>\n <RefreshCw className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\n <p className=\"mt-1 text-xs text-muted-foreground\">Agent worktrees on disk with no live workspace. Reclaim removes them from the host.</p>\n </CardHeader>\n <CardContent className=\"p-0\">{orphans.map(renderOrphan)}</CardContent>\n </Card>\n )\n}\n","import { useEffect, useMemo, useRef, useState } from 'react'\nimport { ArrowDown, ArrowUp, Check, ChevronDown, ChevronRight, Clock, Eye, FileDiff, FilePen, Flag, FolderOpen, GitBranch, GitCommitHorizontal, GitMerge, RefreshCw, TriangleAlert, Trash2, X } from 'lucide-react'\nimport { api } from '@/lib/api'\nimport { useRelayStore, useNow } from '@/store'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { CopyButton } from '@/components/shared/copy-button'\nimport { displayName, timeAgo } from '@/lib/display'\nimport { ALL_PROJECTS, filterWorkspaces, groupWorkspaces, LIVE_WORKSPACE_STATUSES, OrphanPanel, ProjectGroup, UNASSIGNED_PROJECTS, type WorkspaceFilter, type WorkspaceProjectFilter } from './workspaces-list'\nimport type { Agent, Project, WorkspaceRecord, WorkspaceStatus } from '@/types'\n\nconst GIT_STATE_STATUSES = new Set<WorkspaceStatus>(['active', 'ready', 'conflict', 'review_requested', 'merge_planned'])\nconst MERGEABLE_STATUSES = new Set<WorkspaceStatus>(['active', 'ready', 'review_requested', 'merge_planned', 'conflict'])\n\nconst STATUS_CLASS: Record<WorkspaceStatus, string> = {\n active: 'bg-blue-500/10 text-blue-400 border-blue-500/20',\n ready: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n conflict: 'bg-red-500/10 text-red-400 border-red-500/20',\n review_requested: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n merge_planned: 'bg-indigo-500/10 text-indigo-400 border-indigo-500/20',\n merged: 'bg-violet-500/10 text-violet-400 border-violet-500/20',\n abandoned: 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20',\n cleanup_requested: 'bg-orange-500/10 text-orange-400 border-orange-500/20',\n cleaned: 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20',\n}\n\nconst STATUS_LABEL: Record<WorkspaceStatus, string> = {\n active: 'active',\n ready: 'ready',\n conflict: 'conflict',\n review_requested: 'review',\n merge_planned: 'merge planned',\n merged: 'merged',\n abandoned: 'abandoned',\n cleanup_requested: 'cleanup',\n cleaned: 'cleaned',\n}\n\nconst STATUS_ORDER: Record<WorkspaceStatus, number> = {\n conflict: 0,\n ready: 1,\n review_requested: 2,\n merge_planned: 3,\n active: 4,\n cleanup_requested: 5,\n merged: 6,\n abandoned: 7,\n cleaned: 8,\n}\n\nfunction agentLabel(agent: Agent | undefined, fallback: string | undefined): string {\n if (agent) return displayName(agent)\n return fallback || '-'\n}\n\nfunction shortPath(path: string): string {\n const parts = path.split('/').filter(Boolean)\n if (parts.length <= 3) return path || '-'\n return `/${parts.slice(-3).join('/')}`\n}\n\nfunction statusTone(status: WorkspaceStatus): string {\n return STATUS_CLASS[status] || 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n}\n\nfunction workspaceSort(a: WorkspaceRecord, b: WorkspaceRecord): number {\n const status = (STATUS_ORDER[a.status] ?? 99) - (STATUS_ORDER[b.status] ?? 99)\n if (status !== 0) return status\n return Number(b.updatedAt || 0) - Number(a.updatedAt || 0)\n}\n\nfunction WorkspaceActions({ workspace, expanded, onToggleDetails }: { workspace: WorkspaceRecord; expanded: boolean; onToggleDetails: () => void }) {\n const workspaceAction = useRelayStore((s) => s.workspaceAction)\n const purgeWorkspace = useRelayStore((s) => s.purgeWorkspace)\n const fetchWorkspaceMergePreview = useRelayStore((s) => s.fetchWorkspaceMergePreview)\n const openFilesAt = useRelayStore((s) => s.openFilesAt)\n const terminal = workspace.status === 'cleaned' || workspace.status === 'merged' || workspace.status === 'abandoned'\n const disabled = terminal || workspace.status === 'cleanup_requested'\n const openPath = workspace.worktreePath || workspace.sourceCwd || workspace.repoRoot\n // Already-landed work (squash/cherry-pick) has nothing left to merge — a merge\n // would open a duplicate/no-op PR, so disable the button.\n const gitState = useRelayStore((s) => s.workspaceGitState[workspace.id])\n const landed = !!gitState && gitState.available !== false && gitState.landed === true\n const mergeable = workspace.mode === 'isolated' && Boolean(workspace.worktreePath) && MERGEABLE_STATUSES.has(workspace.status) && !landed\n\n // Refresh the preview so the confirm dialog states the strategy/outcome, then\n // dispatch the real merge command.\n async function merge() {\n await fetchWorkspaceMergePreview(workspace.id)\n await workspaceAction(workspace.id, 'merge')\n }\n\n return (\n <div className=\"flex flex-wrap justify-end gap-1.5\">\n <Button size=\"icon-sm\" variant=\"ghost\" title={expanded ? 'Hide details' : 'Show diff & timeline'} onClick={onToggleDetails}>\n {expanded ? <ChevronDown className=\"h-3.5 w-3.5\" /> : <ChevronRight className=\"h-3.5 w-3.5\" />}\n </Button>\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n title=\"Open workspace\"\n disabled={!openPath || workspace.status === 'cleaned'}\n onClick={() => void openFilesAt({ path: openPath })}\n >\n <FolderOpen className=\"h-3.5 w-3.5\" />\n </Button>\n <CopyButton value={openPath} label=\"Copy path\" copiedLabel=\"Copied path\" size=\"icon-sm\" variant=\"ghost\" disabled={!openPath} />\n <Button size=\"icon-sm\" variant=\"outline\" title=\"Mark ready\" disabled={disabled || workspace.status === 'ready'} onClick={() => void workspaceAction(workspace.id, 'ready')}>\n <Check className=\"h-3.5 w-3.5\" />\n </Button>\n <Button size=\"icon-sm\" variant=\"outline\" title=\"Request review\" disabled={disabled || workspace.status === 'review_requested'} onClick={() => void workspaceAction(workspace.id, 'request-review')}>\n <Eye className=\"h-3.5 w-3.5\" />\n </Button>\n <Button size=\"icon-sm\" variant=\"outline\" title=\"Mark merge planned\" disabled={disabled || workspace.status === 'merge_planned'} onClick={() => void workspaceAction(workspace.id, 'merge-plan')}>\n <Flag className=\"h-3.5 w-3.5\" />\n </Button>\n <Button size=\"icon-sm\" variant=\"default\" title={landed ? 'Already merged into base — nothing to land' : 'Merge & land work'} disabled={!mergeable} onClick={() => void merge()}>\n <GitMerge className=\"h-3.5 w-3.5\" />\n </Button>\n <Button size=\"icon-sm\" variant=\"outline\" title=\"Request cleanup (removes the worktree on the host)\" disabled={workspace.status === 'cleaned' || workspace.status === 'cleanup_requested'} onClick={() => void workspaceAction(workspace.id, 'cleanup')}>\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n {terminal && (\n <Button size=\"icon-sm\" variant=\"ghost\" title=\"Purge record (clears the row, does not touch disk)\" onClick={() => void purgeWorkspace(workspace.id)}>\n <X className=\"h-3.5 w-3.5\" />\n </Button>\n )}\n </div>\n )\n}\n\n// Pre-merge conflict hint, shown next to git state when the worktree has work to\n// land. Lazily fetched from the owning host (an external sync), so an effect is\n// the right tool. Reports clean-ff vs would-conflict before the user merges.\nfunction MergePreviewHint({ workspace, unmerged }: { workspace: WorkspaceRecord; unmerged: number }) {\n const preview = useRelayStore((s) => s.workspaceMergePreview[workspace.id])\n const fetchWorkspaceMergePreview = useRelayStore((s) => s.fetchWorkspaceMergePreview)\n const eligible = unmerged > 0 && MERGEABLE_STATUSES.has(workspace.status)\n\n useEffect(() => {\n if (eligible && preview === undefined) void fetchWorkspaceMergePreview(workspace.id)\n }, [eligible, preview, workspace.id, fetchWorkspaceMergePreview])\n\n if (!eligible || preview === undefined || preview.available === false) return null\n if (preview.conflict) {\n return (\n <span className=\"flex items-center gap-0.5 text-amber-400\" title={`Merging into ${preview.baseRef || 'base'} would conflict — resolve before merging`}>\n <TriangleAlert className=\"h-3 w-3\" />conflict\n </span>\n )\n }\n if (preview.conflict === false) {\n return (\n <span className=\"flex items-center gap-0.5 text-emerald-400\" title={`Clean to merge via ${preview.strategy === 'pr' ? 'PR' : 'rebase + fast-forward'} into ${preview.baseRef || 'base'}`}>\n <Check className=\"h-3 w-3\" />{preview.strategy === 'pr' ? 'PR ready' : 'clean'}\n </span>\n )\n }\n return null\n}\n\n// Live git state of the worktree, fetched lazily from the owning host. This is\n// an external-system sync (host git over the orchestrator proxy), so an effect\n// is the right tool — it does not derive state from props.\nfunction GitState({ workspace }: { workspace: WorkspaceRecord }) {\n const now = useNow()\n const gitState = useRelayStore((s) => s.workspaceGitState[workspace.id])\n const fetchWorkspaceGitState = useRelayStore((s) => s.fetchWorkspaceGitState)\n const eligible = workspace.mode === 'isolated' && Boolean(workspace.worktreePath) && GIT_STATE_STATUSES.has(workspace.status)\n\n useEffect(() => {\n if (eligible && gitState === undefined) void fetchWorkspaceGitState(workspace.id)\n }, [eligible, gitState, workspace.id, fetchWorkspaceGitState])\n\n if (!eligible) return null\n\n const refresh = (\n <Button\n size=\"icon-sm\"\n variant=\"ghost\"\n className=\"h-5 w-5\"\n title=\"Refresh git state\"\n onClick={() => void fetchWorkspaceGitState(workspace.id)}\n >\n <RefreshCw className=\"h-3 w-3\" />\n </Button>\n )\n\n if (gitState === undefined) {\n return <div className=\"flex items-center gap-1 text-[11px] text-muted-foreground\">Loading git state…</div>\n }\n if (gitState.available === false) {\n return (\n <div className=\"flex items-center gap-1 text-[11px] text-muted-foreground\" title={gitState.reason}>\n <span>git state unavailable</span>\n {refresh}\n </div>\n )\n }\n if (gitState.missing) {\n return <div className=\"flex items-center gap-1 text-[11px] text-muted-foreground\">worktree gone {refresh}</div>\n }\n if (gitState.error) {\n return (\n <div className=\"flex items-center gap-1 text-[11px] text-red-400\" title={gitState.error}>\n <span>git error</span>\n {refresh}\n </div>\n )\n }\n\n const ahead = gitState.ahead ?? 0\n // Work squash/cherry-pick-merged into base leaves `ahead` positive but is\n // effectively landed — surface the unmerged count, not the raw ahead.\n const landed = gitState.landed === true\n const unmerged = landed ? 0 : (gitState.unmergedAhead ?? ahead)\n const behind = gitState.behind ?? 0\n const dirty = gitState.dirtyCount ?? 0\n const clean = unmerged === 0 && dirty === 0 && !landed\n const commit = gitState.lastCommit\n\n return (\n <div className=\"space-y-0.5\">\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[11px]\">\n {clean ? (\n <span className=\"text-muted-foreground\">no unmerged work</span>\n ) : (\n <>\n {landed ? (\n <span className=\"flex items-center gap-0.5 text-emerald-400\" title={`Work already landed in ${gitState.baseRef || 'base'} via squash/cherry-pick — safe to clean up`}>\n <Check className=\"h-3 w-3\" />landed\n </span>\n ) : (\n <span className={unmerged > 0 ? 'flex items-center text-emerald-400' : 'flex items-center text-muted-foreground'} title={ahead !== unmerged ? `${unmerged} unmerged commit(s) (${ahead} ahead of base)` : `${unmerged} commit(s) ahead of base`}>\n <ArrowUp className=\"h-3 w-3\" />{unmerged}\n </span>\n )}\n <span className={behind > 0 ? 'flex items-center text-amber-400' : 'flex items-center text-muted-foreground'} title={`${behind} commit(s) behind base`}>\n <ArrowDown className=\"h-3 w-3\" />{behind}\n </span>\n {dirty > 0 && (\n <span className=\"flex items-center text-orange-400\" title={`${dirty} uncommitted change(s)`}>\n <FilePen className=\"h-3 w-3\" />{dirty}\n </span>\n )}\n {!landed && <MergePreviewHint workspace={workspace} unmerged={unmerged} />}\n </>\n )}\n {refresh}\n </div>\n {commit && (\n <div className=\"flex min-w-0 items-center gap-1 text-[11px] text-muted-foreground\" title={`${commit.sha.slice(0, 8)} — ${commit.message}`}>\n <GitCommitHorizontal className=\"h-3 w-3 shrink-0\" />\n <span className=\"truncate\">{commit.message || commit.sha.slice(0, 8)}</span>\n {commit.at ? <span className=\"shrink-0 opacity-70\">· {timeAgo(now, commit.at)}</span> : null}\n </div>\n )}\n </div>\n )\n}\n\n// Lifecycle timeline from the timestamps the relay already records:\n// created → ready → merged → cleaned. Surfaces history without a drawer.\nfunction WorkspaceTimeline({ workspace }: { workspace: WorkspaceRecord }) {\n const now = useNow()\n const mergedAt = typeof workspace.metadata?.mergedAt === 'number' ? (workspace.metadata.mergedAt as number) : undefined\n const steps = [\n { label: 'created', at: workspace.createdAt },\n { label: 'ready', at: workspace.readyAt },\n { label: 'merged', at: mergedAt },\n { label: 'cleaned', at: workspace.cleanedAt },\n ].filter((step): step is { label: string; at: number } => typeof step.at === 'number' && step.at > 0)\n\n return (\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-muted-foreground\">\n <span className=\"flex items-center gap-1 font-medium text-foreground\"><Clock className=\"h-3 w-3\" />Timeline</span>\n {steps.map((step, i) => (\n <span key={step.label} className=\"flex items-center gap-1\">\n {i > 0 && <span className=\"opacity-40\">→</span>}\n <span className=\"text-foreground\">{step.label}</span>\n <span title={new Date(step.at).toLocaleString()}>{timeAgo(now, step.at)}</span>\n </span>\n ))}\n </div>\n )\n}\n\nfunction diffLineClass(line: string): string {\n if (line.startsWith('+') && !line.startsWith('+++')) return 'text-emerald-400'\n if (line.startsWith('-') && !line.startsWith('---')) return 'text-red-400'\n if (line.startsWith('@@')) return 'text-cyan-400'\n if (line.startsWith('diff ') || line.startsWith('index ') || line.startsWith('+++') || line.startsWith('---')) return 'text-muted-foreground'\n return ''\n}\n\n// Diff of committed work against base, lazily fetched from the owning host.\nfunction WorkspaceDiffView({ workspace }: { workspace: WorkspaceRecord }) {\n const diff = useRelayStore((s) => s.workspaceDiff[workspace.id])\n const fetchWorkspaceDiff = useRelayStore((s) => s.fetchWorkspaceDiff)\n const eligible = workspace.mode === 'isolated' && Boolean(workspace.worktreePath)\n\n useEffect(() => {\n if (eligible && diff === undefined) void fetchWorkspaceDiff(workspace.id)\n }, [eligible, diff, workspace.id, fetchWorkspaceDiff])\n\n if (!eligible) return null\n if (diff === undefined) return <div className=\"text-[11px] text-muted-foreground\">Loading diff…</div>\n if (diff.available === false) return <div className=\"text-[11px] text-muted-foreground\">Diff unavailable{diff.reason ? `: ${diff.reason}` : ''}</div>\n if (diff.missing) return <div className=\"text-[11px] text-muted-foreground\">Worktree no longer on disk</div>\n if (diff.error) return <div className=\"text-[11px] text-red-400\">Diff error: {diff.error}</div>\n if (!diff.files.length) return <div className=\"text-[11px] text-muted-foreground\">No committed changes against {diff.baseRef || 'base'}</div>\n\n const refresh = (\n <Button size=\"icon-sm\" variant=\"ghost\" className=\"h-5 w-5\" title=\"Refresh diff\" onClick={() => void fetchWorkspaceDiff(workspace.id)}>\n <RefreshCw className=\"h-3 w-3\" />\n </Button>\n )\n\n return (\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2 text-[11px] font-medium\">\n <FileDiff className=\"h-3 w-3\" />\n <span>{diff.files.length} file{diff.files.length === 1 ? '' : 's'} vs {diff.baseRef || 'base'}</span>\n {refresh}\n </div>\n <ul className=\"space-y-0.5 text-[11px]\">\n {diff.files.map((file) => (\n <li key={file.path} className=\"flex items-center gap-2 font-mono\">\n <span className=\"truncate\" title={file.path}>{file.path}</span>\n {file.binary ? (\n <span className=\"shrink-0 text-muted-foreground\">binary</span>\n ) : (\n <span className=\"shrink-0\">\n <span className=\"text-emerald-400\">+{file.additions ?? 0}</span>{' '}\n <span className=\"text-red-400\">−{file.deletions ?? 0}</span>\n </span>\n )}\n </li>\n ))}\n </ul>\n {diff.patch && (\n <pre className=\"max-h-80 overflow-auto rounded border border-border bg-muted/30 p-2 text-[10.5px] leading-relaxed\">\n {diff.patch.split('\\n').map((line, i) => (\n <div key={i} className={diffLineClass(line)}>{line || ' '}</div>\n ))}\n {diff.truncated && <div className=\"mt-1 text-muted-foreground\">… patch truncated</div>}\n </pre>\n )}\n </div>\n )\n}\n\nfunction WorkspaceDetails({ workspace }: { workspace: WorkspaceRecord }) {\n return (\n <div className=\"space-y-3 border-t border-dashed border-border bg-muted/20 px-3 py-3\">\n <WorkspaceTimeline workspace={workspace} />\n <WorkspaceDiffView workspace={workspace} />\n </div>\n )\n}\n\n// A `pr`-strategy merge lands as an opened PR, not a local fast-forward, so the\n// outcome only shows up here — surface the PR link and any error the orchestrator\n// returned (which the command layer keeps in result even when status=succeeded).\nfunction prLabel(url: string): string {\n const match = url.match(/\\/pull\\/(\\d+)/)\n return match ? `PR #${match[1]}` : 'View PR'\n}\n\nfunction MergeOutcome({ workspace }: { workspace: WorkspaceRecord }) {\n const result = workspace.metadata?.mergeResult as { prUrl?: string; error?: string } | undefined\n const prUrl = result?.prUrl\n const error = result?.error || (workspace.metadata?.mergeError as string | undefined)\n if (!prUrl && !error) return null\n return (\n <>\n {prUrl && (\n <a\n href={prUrl}\n target=\"_blank\"\n rel=\"noreferrer\"\n title={prUrl}\n className=\"inline-flex items-center gap-0.5 rounded border border-violet-500/30 px-1.5 py-0 text-[10px] text-violet-400 hover:text-violet-300\"\n >\n <GitMerge className=\"h-2.5 w-2.5\" />{prLabel(prUrl)}\n </a>\n )}\n {error && (\n <Badge variant=\"outline\" className=\"max-w-[16rem] border-amber-500/30 text-amber-400 text-[10px]\" title={error}>\n <TriangleAlert className=\"mr-0.5 h-2.5 w-2.5 shrink-0\" /><span className=\"truncate\">merge: {error}</span>\n </Badge>\n )}\n </>\n )\n}\n\nfunction WorkspaceRow({ workspace }: { workspace: WorkspaceRecord }) {\n const now = useNow()\n const [expanded, setExpanded] = useState(false)\n const agentsById = useRelayStore((s) => s.agentsById)\n const focused = useRelayStore((s) => s.workspaceFocusId === workspace.id)\n const owner = workspace.ownerAgentId ? agentsById[workspace.ownerAgentId] : undefined\n const steward = workspace.stewardAgentId ? agentsById[workspace.stewardAgentId] : undefined\n const path = workspace.worktreePath || workspace.sourceCwd || workspace.repoRoot\n const rowRef = useRef<HTMLDivElement>(null)\n\n // Click-through landing (#236): scroll the targeted row into view once it mounts.\n useEffect(() => {\n if (focused) rowRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' })\n }, [focused])\n\n return (\n <div ref={rowRef} className={`border-t border-border ${focused ? 'bg-primary/5 ring-1 ring-inset ring-primary/40' : ''}`}>\n <div className=\"grid gap-3 px-3 py-3 text-sm lg:grid-cols-[minmax(180px,1.2fr)_minmax(220px,1.5fr)_minmax(160px,1fr)_minmax(170px,auto)]\">\n <div className=\"min-w-0 space-y-1\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <GitBranch className=\"h-3.5 w-3.5 shrink-0 text-muted-foreground\" />\n <span className=\"truncate font-medium\" title={workspace.branch || workspace.id}>{workspace.branch || workspace.id}</span>\n </div>\n <div className=\"flex flex-wrap gap-1\">\n <Badge variant=\"outline\" className={`text-[10px] ${workspace.mode === 'isolated' ? 'border-sky-500/30 text-sky-400' : 'border-zinc-500/30 text-zinc-400'}`}>\n {workspace.mode}\n </Badge>\n {workspace.requestedMode && workspace.requestedMode !== workspace.mode && (\n <Badge variant=\"outline\" className=\"text-[10px]\">requested {workspace.requestedMode}</Badge>\n )}\n <Badge variant=\"outline\" className={`text-[10px] ${statusTone(workspace.status)}`}>{STATUS_LABEL[workspace.status] || workspace.status}</Badge>\n <MergeOutcome workspace={workspace} />\n </div>\n </div>\n\n <div className=\"min-w-0 space-y-1\">\n <div className=\"truncate font-mono text-xs\" title={path}>{shortPath(path)}</div>\n <div className=\"truncate text-xs text-muted-foreground\" title={workspace.sourceCwd}>{workspace.sourceCwd}</div>\n <GitState workspace={workspace} />\n </div>\n\n <div className=\"grid grid-cols-[4.5rem_minmax(0,1fr)] gap-x-2 gap-y-1 text-xs\">\n <span className=\"text-muted-foreground\">Owner</span>\n <span className=\"truncate\" title={workspace.ownerAgentId}>{agentLabel(owner, workspace.ownerPolicyName || workspace.ownerAgentId)}</span>\n <span className=\"text-muted-foreground\">Steward</span>\n <span className=\"truncate\" title={workspace.stewardAgentId}>{agentLabel(steward, workspace.stewardAgentId)}</span>\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{timeAgo(now, workspace.updatedAt)}</span>\n {workspace.baseRef && (\n <>\n <span className=\"text-muted-foreground\">Base</span>\n <span className=\"truncate\" title={workspace.baseSha}>{workspace.baseRef}</span>\n </>\n )}\n </div>\n\n <WorkspaceActions workspace={workspace} expanded={expanded} onToggleDetails={() => setExpanded((value) => !value)} />\n </div>\n {expanded && <WorkspaceDetails workspace={workspace} />}\n </div>\n )\n}\n\nexport function WorkspacesView() {\n const [filter, setFilter] = useState<WorkspaceFilter>('active')\n const [projectFilter, setProjectFilter] = useState<WorkspaceProjectFilter>(ALL_PROJECTS)\n const [projects, setProjects] = useState<Project[]>([])\n const workspaces = useRelayStore((s) => s.workspaces)\n const set = useRelayStore((s) => s.set)\n const workspaceFocusId = useRelayStore((s) => s.workspaceFocusId)\n\n // Badge click-through (#236): a focused workspace may be in any state, so widen\n // the filter to 'all' to guarantee its row renders. Clear the focus when leaving\n // the panel so the highlight doesn't linger on the next visit.\n useEffect(() => {\n if (workspaceFocusId) {\n setFilter('all')\n setProjectFilter(ALL_PROJECTS)\n }\n }, [workspaceFocusId])\n useEffect(() => () => { set({ workspaceFocusId: null }) }, [set])\n useEffect(() => {\n let cancelled = false\n api<{ projects: Project[] }>('GET', '/projects')\n .then((res) => { if (!cancelled) setProjects(res.projects ?? []) })\n .catch(() => { if (!cancelled) setProjects([]) })\n return () => { cancelled = true }\n }, [])\n\n const visibleWorkspaces = useMemo(() => filterWorkspaces(workspaces, filter, projectFilter), [workspaces, filter, projectFilter])\n const grouped = useMemo(() => groupWorkspaces(visibleWorkspaces, workspaceSort), [visibleWorkspaces])\n const liveCount = workspaces.filter((item) => LIVE_WORKSPACE_STATUSES.has(item.status)).length\n const isolatedCount = workspaces.filter((item) => item.mode === 'isolated').length\n const readyCount = workspaces.filter((item) => item.status === 'ready' || item.status === 'review_requested' || item.status === 'merge_planned').length\n const conflictCount = workspaces.filter((item) => item.status === 'conflict').length\n const doneCount = workspaces.filter((item) => item.status === 'merged' || item.status === 'abandoned' || item.status === 'cleaned').length\n const activeCount = workspaces.length - doneCount\n const filters: Array<{ key: WorkspaceFilter; label: string; count: number }> = [\n { key: 'active', label: 'Active', count: activeCount },\n { key: 'cleaned', label: 'Done', count: doneCount },\n { key: 'all', label: 'All', count: workspaces.length },\n ]\n const projectOptions = useMemo(() => {\n const options = [{ value: ALL_PROJECTS, label: 'All Projects' }]\n for (const project of projects) {\n options.push({ value: project.id, label: project.title || project.slug })\n }\n options.push({ value: UNASSIGNED_PROJECTS, label: 'Unassigned' })\n return options\n }, [projects])\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold\">Workspaces</h1>\n <p className=\"text-sm text-muted-foreground\">Agent worktrees, ownership, and merge readiness.</p>\n </div>\n <div className=\"ml-auto flex flex-wrap gap-1.5\">\n <Badge variant=\"outline\">{isolatedCount} isolated</Badge>\n <Badge variant=\"outline\">{liveCount} live</Badge>\n <Badge variant=\"outline\">{readyCount} ready</Badge>\n {conflictCount > 0 && <Badge variant=\"outline\" className=\"border-red-500/20 text-red-400\">{conflictCount} conflict</Badge>}\n </div>\n </div>\n\n <OrphanPanel />\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"flex rounded-md border border-border bg-card p-1\">\n {filters.map((item) => (\n <Button\n key={item.key}\n size=\"sm\"\n variant={filter === item.key ? 'secondary' : 'ghost'}\n className=\"h-7 text-xs\"\n onClick={() => setFilter(item.key)}\n >\n {item.label}\n <span className=\"ml-1 text-muted-foreground\">{item.count}</span>\n </Button>\n ))}\n </div>\n <select\n className=\"h-8 min-w-[12rem] rounded-md border border-border bg-card px-2.5 text-sm text-foreground\"\n value={projectFilter}\n onChange={(e) => setProjectFilter(e.target.value)}\n >\n {projectOptions.map((option) => (\n <option key={option.value} value={option.value}>{option.label}</option>\n ))}\n </select>\n </div>\n\n {grouped.length === 0 ? (\n <div className=\"rounded-md border border-border px-3 py-12 text-center text-sm text-muted-foreground\">\n No {filter === 'all' ? '' : filter} workspaces{projectFilter !== ALL_PROJECTS ? ' in this project filter' : ''}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {grouped.map((group) => (\n <ProjectGroup key={group.key} group={group} renderWorkspaceRow={(workspace) => <WorkspaceRow key={workspace.id} workspace={workspace} />} />\n ))}\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
package/public/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%2309090b'/%3E%3Ccircle cx='16' cy='16' r='4.5' fill='%2358a6ff'/%3E%3Ccircle cx='6' cy='8' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='26' cy='8' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='6' cy='24' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='26' cy='24' r='2.5' fill='%233fb950'/%3E%3Cline x1='8' y1='9.5' x2='13' y2='14' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='24' y1='9.5' x2='19' y2='14' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='8' y1='22.5' x2='13' y2='18' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='24' y1='22.5' x2='19' y2='18' stroke='%2330363d' stroke-width='1.5'/%3E%3C/svg%3E">
|
|
13
13
|
<link rel="manifest" href="manifest.webmanifest">
|
|
14
14
|
<link rel="apple-touch-icon" href="icons/agent-relay-192.png">
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-DlBSuzd2.js"></script>
|
|
16
16
|
<link rel="modulepreload" crossorigin href="./assets/chunk-CilyBKbf.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="./assets/preload-helper-C_JMBXql.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="./assets/lucide-react-C4eXYCwx.js">
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
<link rel="modulepreload" crossorigin href="./assets/dist-CgqfdHSt.js">
|
|
31
31
|
<link rel="modulepreload" crossorigin href="./assets/dist-BwwlmyBq.js">
|
|
32
32
|
<link rel="modulepreload" crossorigin href="./assets/dist-DC-TqTz2.js">
|
|
33
|
-
<link rel="modulepreload" crossorigin href="./assets/types-
|
|
34
|
-
<link rel="modulepreload" crossorigin href="./assets/display-
|
|
35
|
-
<link rel="modulepreload" crossorigin href="./assets/store-
|
|
33
|
+
<link rel="modulepreload" crossorigin href="./assets/types-BiJFuTiL.js">
|
|
34
|
+
<link rel="modulepreload" crossorigin href="./assets/display-1J__Ti1K.js">
|
|
35
|
+
<link rel="modulepreload" crossorigin href="./assets/store-Bnag1kY-.js">
|
|
36
36
|
<link rel="modulepreload" crossorigin href="./assets/button-KASZcscA.js">
|
|
37
37
|
<link rel="modulepreload" crossorigin href="./assets/copy-button-d9DFNX9v.js">
|
|
38
38
|
<link rel="modulepreload" crossorigin href="./assets/badge-D8fABzMs.js">
|
|
@@ -65,6 +65,9 @@ var claudeProviderManifest = {
|
|
|
65
65
|
launch: {
|
|
66
66
|
managedPolicyPrompt: {
|
|
67
67
|
alwaysOn: "append-system-prompt"
|
|
68
|
+
},
|
|
69
|
+
taskHydration: {
|
|
70
|
+
channel: "append-system-prompt"
|
|
68
71
|
}
|
|
69
72
|
},
|
|
70
73
|
catalog: {
|
|
@@ -188,6 +191,9 @@ var codexProviderManifest = {
|
|
|
188
191
|
launch: {
|
|
189
192
|
managedPolicyPrompt: {
|
|
190
193
|
alwaysOn: "prompt"
|
|
194
|
+
},
|
|
195
|
+
taskHydration: {
|
|
196
|
+
channel: "prompt"
|
|
191
197
|
}
|
|
192
198
|
},
|
|
193
199
|
catalog: {
|
package/src/branch-landed.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { errMessage } from "agent-relay-sdk";
|
|
1
|
+
import { errMessage, type BaseWorktreeSyncResult } from "agent-relay-sdk";
|
|
2
2
|
import { emitRelayEvent } from "./events";
|
|
3
3
|
import { routeNotification, INFORMATIONAL_TTL_MS } from "./notification-router";
|
|
4
4
|
import { registerDefaultAudienceRules, audiencePredicates } from "./notification-rules";
|
|
5
5
|
import { createActivityEvent, getAgent, updateWorkspaceStatus } from "./db";
|
|
6
6
|
import { renderTemplate } from "./prompt-resolver";
|
|
7
|
+
import { resolveCoordinatorAgentId } from "./steward-identity";
|
|
8
|
+
import { stewardFallbackTarget } from "./config";
|
|
7
9
|
import { handleLandedIssueLifecycle } from "./services/issue-lifecycle";
|
|
10
|
+
import { handleLandedTaskLifecycle } from "./services/task-lifecycle";
|
|
8
11
|
import type { WorkspaceMergePreview, WorkspaceRecord } from "./types";
|
|
9
12
|
|
|
10
13
|
// #444 — branch.landed is the first rule-driven type: the on-main "merged" fan-out audience comes
|
|
@@ -131,9 +134,22 @@ export function notifyBranchLanded(input: BranchLandedInput): void {
|
|
|
131
134
|
notification: "landed-success",
|
|
132
135
|
},
|
|
133
136
|
});
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
// #482/#833 issue close FIRST, then the #831 Task drive: the Task's close PROJECTION
|
|
138
|
+
// (taskCloseBridge) reads the canonical close ledger the issue hook writes, so chaining makes the
|
|
139
|
+
// receipt accurate. Both are best-effort + isolated — a throw in either can never fail the merge
|
|
140
|
+
// (it already landed before notifyBranchLanded runs). A non-task branch makes the Task drive a no-op.
|
|
141
|
+
void (async () => {
|
|
142
|
+
try {
|
|
143
|
+
await handleLandedIssueLifecycle({ ...input, commitSubjects }, { log: lifecycleLog });
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.warn(`issue lifecycle land hook failed for ${workspace.id}: ${errMessage(error)}`);
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
await handleLandedTaskLifecycle({ ...input, commitSubjects }, { log: lifecycleLog });
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.warn(`task lifecycle land hook failed for ${workspace.id}: ${errMessage(error)}`);
|
|
151
|
+
}
|
|
152
|
+
})();
|
|
137
153
|
|
|
138
154
|
const author = workspace.ownerAgentId;
|
|
139
155
|
const shaLabel = shortSha ? ` as \`${shortSha}\`` : "";
|
|
@@ -207,6 +223,79 @@ export function notifyBranchLanded(input: BranchLandedInput): void {
|
|
|
207
223
|
});
|
|
208
224
|
}
|
|
209
225
|
|
|
226
|
+
/**
|
|
227
|
+
* #824 — a dirty base checkout left in a MIXED state after a land (HEAD advanced via ref
|
|
228
|
+
* plumbing, but the shared/primary working files did NOT catch up for some landed paths).
|
|
229
|
+
* The land itself SUCCEEDED — this is not a land failure — but the shared checkout now serves
|
|
230
|
+
* STALE content for those paths, which previously shipped a reverted file into a bootstrap
|
|
231
|
+
* publish (#823). The old behavior was a swallowed host-side `console.error`; this raises a
|
|
232
|
+
* LOUD, operator-visible signal (activity alert + a durable push to the owner and its
|
|
233
|
+
* coordinator) so the mixed state is reconciled before anything reads/builds/publishes from
|
|
234
|
+
* the checkout. Human WIP is never auto-clobbered; `preservedWipPaths` flags paths to merge by
|
|
235
|
+
* hand rather than blind-restore.
|
|
236
|
+
*/
|
|
237
|
+
export function notifyBaseWorktreeStrand(input: {
|
|
238
|
+
workspace: Pick<WorkspaceRecord, "id" | "repoRoot" | "branch" | "baseRef" | "ownerAgentId">;
|
|
239
|
+
sync: BaseWorktreeSyncResult;
|
|
240
|
+
}): void {
|
|
241
|
+
const { workspace, sync } = input;
|
|
242
|
+
if (sync.reconciled) return;
|
|
243
|
+
const base = workspace.baseRef ?? "base";
|
|
244
|
+
const owner = workspace.ownerAgentId;
|
|
245
|
+
const coordinator = resolveCoordinatorAgentId(owner ?? undefined);
|
|
246
|
+
const fallback = stewardFallbackTarget();
|
|
247
|
+
const unsynced = sync.unsyncedPaths ?? [];
|
|
248
|
+
const preservedWip = sync.preservedWipPaths ?? [];
|
|
249
|
+
const pathLabel = unsynced.length ? unsynced.map((p) => `\`${p}\``).join(", ") : "(unknown paths)";
|
|
250
|
+
const wipLabel = preservedWip.length
|
|
251
|
+
? ` Some carry uncommitted human WIP and were preserved (merge by hand, do NOT blind-restore): ${preservedWip.map((p) => `\`${p}\``).join(", ")}.`
|
|
252
|
+
: "";
|
|
253
|
+
const body = `⚠️ The shared \`${base}\` checkout under \`${workspace.repoRoot}\` is in a MIXED state after a land: HEAD advanced but these working files did NOT catch up — ${pathLabel}. Reads/builds/publishes from this checkout will see STALE content until reconciled (\`git -C ${workspace.repoRoot} restore --staged --worktree -- <path>\` for non-WIP paths).${wipLabel}`;
|
|
254
|
+
|
|
255
|
+
const payload = {
|
|
256
|
+
kind: "workspace.base-checkout-strand",
|
|
257
|
+
workspaceId: workspace.id,
|
|
258
|
+
repoRoot: workspace.repoRoot,
|
|
259
|
+
branch: workspace.branch,
|
|
260
|
+
base,
|
|
261
|
+
author: owner,
|
|
262
|
+
unsyncedPaths: unsynced,
|
|
263
|
+
preservedWipPaths: preservedWip,
|
|
264
|
+
reconciled: false,
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
emitRelayEvent({ type: "workspace.base_checkout_strand", source: "server", subject: workspace.id, data: payload });
|
|
268
|
+
|
|
269
|
+
createActivityEvent({
|
|
270
|
+
clientId: `workspace-base-strand-${workspace.id}-${Date.now()}`,
|
|
271
|
+
kind: "state",
|
|
272
|
+
title: "Base checkout stranded after land (mixed state)",
|
|
273
|
+
body: `${base} (${workspace.repoRoot}) — ${unsynced.length} landed path(s) out of sync with HEAD${preservedWip.length ? `, ${preservedWip.length} with human WIP` : ""}: ${unsynced.join(", ")}`,
|
|
274
|
+
meta: workspace.branch ?? workspace.id,
|
|
275
|
+
icon: "ti-alert-octagon",
|
|
276
|
+
view: "orchestrators",
|
|
277
|
+
metadata: { source: "server", maintenanceJobId: "workspace-base-strand", workspaceId: workspace.id, repoRoot: workspace.repoRoot, unsyncedPaths: unsynced, preservedWipPaths: preservedWip },
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Durable (ttl null) push to the owner and its coordinator — whoever works in the shared
|
|
281
|
+
// checkout must learn it's stale before acting on it. Fallback steward covers a teamless land.
|
|
282
|
+
const recipients = [...new Set([owner, coordinator, fallback].filter((r): r is string => Boolean(r)))];
|
|
283
|
+
if (recipients.length) {
|
|
284
|
+
routeNotification({
|
|
285
|
+
type: "workspace.base_checkout_strand",
|
|
286
|
+
scope: { author: owner, parent: coordinator ?? undefined, repoRoot: workspace.repoRoot, workspaceId: workspace.id },
|
|
287
|
+
recipients,
|
|
288
|
+
ttlMs: null,
|
|
289
|
+
message: {
|
|
290
|
+
subject: "Base checkout left stale after land — reconcile before publishing",
|
|
291
|
+
body,
|
|
292
|
+
payload: { ...payload, notification: "base-checkout-strand" },
|
|
293
|
+
replyExpected: false,
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
210
299
|
/**
|
|
211
300
|
* Fatal land failure wake-up for the branch owner (#538). This is intentionally separate from
|
|
212
301
|
* steward-task/conflict pings: it tells the worker that its post-ready idle wait ended without
|
package/src/config-store.ts
CHANGED
|
@@ -73,7 +73,7 @@ function builtInAgentProfiles(): AgentProfile[] {
|
|
|
73
73
|
agentProfileDefaults({ name: "vanilla", icon: "🫥", description: "Zero host bleed (#552): isolated provider home with host plugins, skills, hooks, MCP, and the CLAUDE.md/AGENTS.md cascade (repo + global) all suppressed at launch. Relay provisions any surface explicitly per profile.", base: "vanilla" }),
|
|
74
74
|
agentProfileDefaults({ name: "lean-worker", icon: "🪶", description: "Lean worker profile (#570): vanilla host-dependency drop with only Relay context/MCP restored and Project knowledge injected from the spawn cwd.", base: "vanilla", relay: { context: true, skills: false, plugins: false, statusLine: false, mcp: true, tools: { groups: WORKER_TOOL_GROUPS } }, projectInstructions: { policy: "all" } }),
|
|
75
75
|
agentProfileDefaults({ name: "coordinator", icon: "🧭", description: "Relay-aware coordination lead for spawning worker teams, maintaining shared state, and driving review and release.", base: "host", instructions: { append: [renderTemplate("profile.coordinator.charter")], repoInstructions: "allow", globalInstructions: "allow" }, maxSpawnedAgents: 8 }),
|
|
76
|
-
agentProfileDefaults({ name: "ops-assistant", icon: "🛠️", description: "Relay-aware ops lieutenant for a coordinator: runs tests, releases, smoke probes, gates, and host checks, then reports crisp outcomes back up.", provider: "codex", base: "host", instructions: { append: [renderTemplate("profile.ops-assistant.brief")], repoInstructions: "allow", globalInstructions: "allow" }, permissions: { mode: "open", filesystem: "host" } }),
|
|
76
|
+
agentProfileDefaults({ name: "ops-assistant", icon: "🛠️", description: "Relay-aware ops lieutenant for a coordinator: runs tests, releases, smoke probes, gates, and host checks, then reports crisp outcomes back up.", provider: "codex", base: "host", instructions: { append: [renderTemplate("profile.ops-assistant.brief")], repoInstructions: "allow", globalInstructions: "allow" }, notifications: { responseMode: "results-only" }, permissions: { mode: "open", filesystem: "host" } }),
|
|
77
77
|
agentProfileDefaults({ name: "worker", icon: "🔨", description: "Relay-aware focused implementer with open approval posture and no delegated spawn capability.", base: "host", instructions: { system: renderTemplate("profile.worker.system"), append: [], repoInstructions: "allow", globalInstructions: "allow" }, mcp: { mode: "none" }, permissions: { mode: "open", filesystem: "host" }, relay: { tools: { groups: WORKER_TOOL_GROUPS } } }),
|
|
78
78
|
agentProfileDefaults({ name: "reviewer", icon: "🔍", description: "Relay-aware read-only reviewer for audit and review passes without file writes.", base: "host", mcp: { mode: "none" }, permissions: { mode: "read-only", filesystem: "host" }, relay: { tools: { groups: WORKER_TOOL_GROUPS } } }),
|
|
79
79
|
].map((profile) => ({ ...profile, builtIn: true }));
|
|
@@ -272,6 +272,15 @@ function cleanAgentProfileSharedMcp(value: unknown): AgentProfile["sharedMcp"] {
|
|
|
272
272
|
return { policy: cleanEnum(policy, "sharedMcp.policy", SHARED_MCP_POLICY_NAMES) as NonNullable<AgentProfile["sharedMcp"]>["policy"] };
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
function cleanAgentProfileNotifications(value: unknown): AgentProfile["notifications"] {
|
|
276
|
+
if (value === undefined || value === null) return undefined;
|
|
277
|
+
if (!isRecord(value)) throw new ValidationError("notifications must be an object");
|
|
278
|
+
const responseMode = value.responseMode === undefined || value.responseMode === null
|
|
279
|
+
? undefined
|
|
280
|
+
: cleanEnum(value.responseMode, "notifications.responseMode", ["normal", "results-only"] as const);
|
|
281
|
+
return responseMode ? { responseMode } : {};
|
|
282
|
+
}
|
|
283
|
+
|
|
275
284
|
function agentProfileDefaults(input: Pick<AgentProfile, "name" | "base"> & Omit<Partial<AgentProfile>, "relay"> & { relay?: Partial<AgentProfile["relay"]> }): AgentProfile {
|
|
276
285
|
const isolated = input.base !== "host";
|
|
277
286
|
// "vanilla" (#552) additionally suppresses the REPO instruction cascade
|
|
@@ -297,6 +306,7 @@ function agentProfileDefaults(input: Pick<AgentProfile, "name" | "base"> & Omit<
|
|
|
297
306
|
...(input.projectSkills ? { projectSkills: input.projectSkills } : {}),
|
|
298
307
|
...(input.projectSettings ? { projectSettings: input.projectSettings } : {}),
|
|
299
308
|
...(input.sharedMcp ? { sharedMcp: input.sharedMcp } : {}),
|
|
309
|
+
...(input.notifications ? { notifications: input.notifications } : {}),
|
|
300
310
|
relay: {
|
|
301
311
|
context: input.relay?.context ?? !isolated,
|
|
302
312
|
skills: input.relay?.skills ?? !isolated,
|
|
@@ -379,6 +389,7 @@ function validateAgentProfile(key: string, value: unknown, opts: { allowBuiltIn?
|
|
|
379
389
|
projectSkills: cleanAgentProfileProjectSkills(value.projectSkills),
|
|
380
390
|
projectSettings: cleanAgentProfileProjectSettings(value.projectSettings),
|
|
381
391
|
sharedMcp: cleanAgentProfileSharedMcp(value.sharedMcp),
|
|
392
|
+
notifications: cleanAgentProfileNotifications(value.notifications),
|
|
382
393
|
relay: {
|
|
383
394
|
context: relay.context === undefined ? defaults.relay.context : cleanBoolean(relay.context, "relay.context"),
|
|
384
395
|
skills: relay.skills === undefined ? defaults.relay.skills : cleanBoolean(relay.skills, "relay.skills"),
|
package/src/db/agents.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { closeOpenPairsForAgent } from "./pair-maintenance.ts";
|
|
|
24
24
|
import { parseStringArray } from "./shared.ts";
|
|
25
25
|
import { listAgentQuotaSnapshotsFromProviderStore } from "./provider-quotas.ts";
|
|
26
26
|
import { settleSingleTargetOnDemandTasks } from "./task-maintenance.ts";
|
|
27
|
+
import { claimTaskWhere } from "./task-core.ts";
|
|
27
28
|
import { electWorkspaceStewards, electWorkspaceStewardsForAgent } from "./workspaces.ts";
|
|
28
29
|
import { DEFAULT_SELF_RESUME_CONFIG } from "../self-resume-config";
|
|
29
30
|
import type { TokenRevokeReason } from "../security";
|
|
@@ -535,7 +536,7 @@ export function pruneOfflineAgents(maxOfflineMs: number = DAY_MS): string[] {
|
|
|
535
536
|
|
|
536
537
|
getDb()
|
|
537
538
|
.query(
|
|
538
|
-
`UPDATE tasks SET status = 'open', claimed_by = NULL, claimed_at = NULL, claim_expires_at = NULL, updated_at = ? WHERE ${offlineClaimCondition} AND status IN ('claimed', 'in_progress', 'blocked')`
|
|
539
|
+
`UPDATE tasks SET status = 'open', claimed_by = NULL, claimed_at = NULL, claim_expires_at = NULL, updated_at = ? WHERE ${claimTaskWhere()} AND ${offlineClaimCondition} AND status IN ('claimed', 'in_progress', 'blocked')`
|
|
539
540
|
)
|
|
540
541
|
.run(now, cutoff);
|
|
541
542
|
|
|
@@ -566,7 +567,7 @@ export function deleteAgent(id: string): { ok: boolean; error?: string } {
|
|
|
566
567
|
const now = Date.now();
|
|
567
568
|
getDb().query(`UPDATE messages SET claimed_by = NULL, claimed_at = NULL, claim_expires_at = NULL WHERE claimed_by = ? AND NOT ${messageTaskClosed("messages.id")}`).run(id);
|
|
568
569
|
settleSingleTargetOnDemandTasks("claimed_by = ?", [id], now, "agent-removed");
|
|
569
|
-
getDb().query(
|
|
570
|
+
getDb().query(`UPDATE tasks SET status = 'open', claimed_by = NULL, claimed_at = NULL, claim_expires_at = NULL, updated_at = ? WHERE ${claimTaskWhere()} AND claimed_by = ? AND status IN ('claimed', 'in_progress', 'blocked')`).run(now, id);
|
|
570
571
|
revokeRuntimeTokensForAgent(id, now);
|
|
571
572
|
closeOpenPairsForAgent(id, now);
|
|
572
573
|
return getDb().query("DELETE FROM agents WHERE id = ?").run(id).changes > 0;
|
package/src/db/continuations.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isRecord } from "agent-relay-sdk";
|
|
2
2
|
import { parseJson } from "../utils";
|
|
3
3
|
import { getDb, ValidationError } from "./connection.ts";
|
|
4
|
-
import type { ContinuationBudget, ContinuationEnvelope, ContinuationLedgerEntry, SelfResumeConfig } from "../types";
|
|
4
|
+
import type { ContinuationBudget, ContinuationDecisionEntry, ContinuationEnvelope, ContinuationLedgerEntry, SelfResumeConfig } from "../types";
|
|
5
5
|
|
|
6
6
|
interface ContinuationRow {
|
|
7
7
|
agent_id: string;
|
|
@@ -113,7 +113,8 @@ function rowToEnvelope(row: ContinuationRow): ContinuationEnvelope {
|
|
|
113
113
|
agentId: row.agent_id,
|
|
114
114
|
objective: row.objective,
|
|
115
115
|
generation: row.generation,
|
|
116
|
-
ledger: parseJson<ContinuationLedgerEntry[]>(row.ledger, []).filter(
|
|
116
|
+
ledger: parseJson<ContinuationLedgerEntry[]>(row.ledger, []).filter(isContinuationLedgerEntry),
|
|
117
|
+
decisions: [],
|
|
117
118
|
workingState: row.working_state,
|
|
118
119
|
archiveRefs: parseJson<string[]>(row.archive_refs, []).filter((value): value is string => typeof value === "string"),
|
|
119
120
|
budget: parseBudget(row.budget),
|
|
@@ -140,7 +141,7 @@ function parseBudget(raw: string): ContinuationBudget {
|
|
|
140
141
|
};
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
function
|
|
144
|
+
export function isContinuationLedgerEntry(value: unknown): value is ContinuationLedgerEntry {
|
|
144
145
|
return isRecord(value)
|
|
145
146
|
&& typeof value.gen === "number"
|
|
146
147
|
&& typeof value.ruledOut === "string"
|
|
@@ -148,6 +149,15 @@ function isLedgerEntry(value: unknown): value is ContinuationLedgerEntry {
|
|
|
148
149
|
&& typeof value.at === "number";
|
|
149
150
|
}
|
|
150
151
|
|
|
152
|
+
export function isContinuationDecisionEntry(value: unknown): value is ContinuationDecisionEntry {
|
|
153
|
+
return isRecord(value)
|
|
154
|
+
&& typeof value.gen === "number"
|
|
155
|
+
&& typeof value.decision === "string"
|
|
156
|
+
&& typeof value.rationale === "string"
|
|
157
|
+
&& (value.by === undefined || typeof value.by === "string")
|
|
158
|
+
&& typeof value.at === "number";
|
|
159
|
+
}
|
|
160
|
+
|
|
151
161
|
function numberValue(value: unknown): number {
|
|
152
162
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
153
163
|
}
|
package/src/db/index.ts
CHANGED
|
@@ -9,6 +9,9 @@ export * from "./channels.ts";
|
|
|
9
9
|
export * from "./agents.ts";
|
|
10
10
|
export * from "./agent-search.ts";
|
|
11
11
|
export * from "./tasks.ts";
|
|
12
|
+
export * from "./task-entity.ts";
|
|
13
|
+
export * from "./task-memory.ts";
|
|
14
|
+
export * from "./task-comment-through.ts";
|
|
12
15
|
export * from "./integrations.ts";
|
|
13
16
|
export * from "./pairs.ts";
|
|
14
17
|
export * from "./artifacts.ts";
|