agent-relay-server 0.91.4 → 0.93.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/openapi.json +29 -1
- package/package.json +2 -2
- package/public/assets/MessageBubble-DX16Hbb5.js +3 -0
- package/public/assets/MessageBubble-DX16Hbb5.js.map +1 -0
- package/public/assets/{PlanGraphPanel-BwEOOqdj.js → PlanGraphPanel-BuWerKhs.js} +2 -2
- package/public/assets/{PlanGraphPanel-BwEOOqdj.js.map → PlanGraphPanel-BuWerKhs.js.map} +1 -1
- package/public/assets/activity-tuTEn3vn.js +2 -0
- package/public/assets/{activity-DD9bl5uf.js.map → activity-tuTEn3vn.js.map} +1 -1
- package/public/assets/{agent-profiles-CLJNeVxc.js → agent-profiles-CAwmjdDx.js} +2 -2
- package/public/assets/{agent-profiles-CLJNeVxc.js.map → agent-profiles-CAwmjdDx.js.map} +1 -1
- package/public/assets/agents-B-BSBrM5.js +2 -0
- package/public/assets/{agents-DJgqCIqi.js.map → agents-B-BSBrM5.js.map} +1 -1
- package/public/assets/{analytics-C1F28oqr.js → analytics-aI_oGHtR.js} +3 -3
- package/public/assets/{analytics-C1F28oqr.js.map → analytics-aI_oGHtR.js.map} +1 -1
- package/public/assets/{automation-Vbei8P9W.js → automation-BAQM1dBh.js} +2 -2
- package/public/assets/{automation-Vbei8P9W.js.map → automation-BAQM1dBh.js.map} +1 -1
- package/public/assets/{badge-CK6ejiOh.js → badge-fjSgjnDc.js} +2 -2
- package/public/assets/{badge-CK6ejiOh.js.map → badge-fjSgjnDc.js.map} +1 -1
- package/public/assets/branch-state-badge-DaafW7sN.js +2 -0
- package/public/assets/{branch-state-badge-Q4W68jxj.js.map → branch-state-badge-DaafW7sN.js.map} +1 -1
- package/public/assets/{button-BAwKuRIq.js → button-DidMOp8n.js} +2 -2
- package/public/assets/{button-BAwKuRIq.js.map → button-DidMOp8n.js.map} +1 -1
- package/public/assets/card-CpLnx1JJ.js +2 -0
- package/public/assets/{card-DDxxo-Fo.js.map → card-CpLnx1JJ.js.map} +1 -1
- package/public/assets/{channels-BQbNtrtF.js → channels-DHjTCcE8.js} +2 -2
- package/public/assets/{channels-BQbNtrtF.js.map → channels-DHjTCcE8.js.map} +1 -1
- package/public/assets/chat-uWKl3MpS.js +2 -0
- package/public/assets/{chat-Dw5xpc5W.js.map → chat-uWKl3MpS.js.map} +1 -1
- package/public/assets/{connectors-CmARSKoV.js → connectors-CI97PYOl.js} +2 -2
- package/public/assets/{connectors-CmARSKoV.js.map → connectors-CI97PYOl.js.map} +1 -1
- package/public/assets/copy-button-CKAwTG9g.js +2 -0
- package/public/assets/{copy-button-D3B_s7J7.js.map → copy-button-CKAwTG9g.js.map} +1 -1
- package/public/assets/display-CWMHll1J.js.map +1 -1
- package/public/assets/{dist-Dmiv1tSO.js → dist-BEpGArlE.js} +2 -2
- package/public/assets/{dist-Dmiv1tSO.js.map → dist-BEpGArlE.js.map} +1 -1
- package/public/assets/dist-BZ-CpDXy.js +2 -0
- package/public/assets/{dist-CIbvPDQV.js.map → dist-BZ-CpDXy.js.map} +1 -1
- package/public/assets/{dist-DLsSqk6h.js → dist-BaDWyAAG.js} +2 -2
- package/public/assets/{dist-DLsSqk6h.js.map → dist-BaDWyAAG.js.map} +1 -1
- package/public/assets/{dist-CAFUEpK2.js → dist-BivRSJg7.js} +2 -2
- package/public/assets/{dist-CAFUEpK2.js.map → dist-BivRSJg7.js.map} +1 -1
- package/public/assets/{dist-CL2npVDe.js → dist-C9ntDbUp.js} +2 -2
- package/public/assets/{dist-CL2npVDe.js.map → dist-C9ntDbUp.js.map} +1 -1
- package/public/assets/dist-CEb4q1gZ.js +2 -0
- package/public/assets/{dist-DyqEeyBI.js.map → dist-CEb4q1gZ.js.map} +1 -1
- package/public/assets/dist-DBDYIgS5.js +2 -0
- package/public/assets/{dist-CoRABSO3.js.map → dist-DBDYIgS5.js.map} +1 -1
- package/public/assets/{dist-ERYvDMdt.js → dist-DNxK5Ibj.js} +2 -2
- package/public/assets/{dist-ERYvDMdt.js.map → dist-DNxK5Ibj.js.map} +1 -1
- package/public/assets/{dist-YjP8Gd1C.js → dist-DayffsX1.js} +2 -2
- package/public/assets/{dist-YjP8Gd1C.js.map → dist-DayffsX1.js.map} +1 -1
- package/public/assets/{dist-YQVQrtnc.js → dist-L3vQoV3T.js} +2 -2
- package/public/assets/{dist-YQVQrtnc.js.map → dist-L3vQoV3T.js.map} +1 -1
- package/public/assets/{dist-CnRv0y2x.js → dist-uFOP-AfS.js} +2 -2
- package/public/assets/{dist-CnRv0y2x.js.map → dist-uFOP-AfS.js.map} +1 -1
- package/public/assets/{es2015-Yto-4EBw.js → es2015-CJr6bLcU.js} +2 -2
- package/public/assets/{es2015-Yto-4EBw.js.map → es2015-CJr6bLcU.js.map} +1 -1
- package/public/assets/{formatted-body-impl-BisZ0FtO.js → formatted-body-impl-DuqZunxL.js} +2 -2
- package/public/assets/{formatted-body-impl-BisZ0FtO.js.map → formatted-body-impl-DuqZunxL.js.map} +1 -1
- package/public/assets/index-DLjkMB-b.css +2 -0
- package/public/assets/index-dMB8klzy.js +23 -0
- package/public/assets/index-dMB8klzy.js.map +1 -0
- package/public/assets/{input-CLW2gDUM.js → input-BbH2Jq-f.js} +2 -2
- package/public/assets/{input-CLW2gDUM.js.map → input-BbH2Jq-f.js.map} +1 -1
- package/public/assets/{insights-D9ytYZyE.js → insights-C0lZ7n8D.js} +2 -2
- package/public/assets/{insights-D9ytYZyE.js.map → insights-C0lZ7n8D.js.map} +1 -1
- package/public/assets/integrations-DXh00Ss-.js +2 -0
- package/public/assets/{integrations-D9Cx-7ma.js.map → integrations-DXh00Ss-.js.map} +1 -1
- package/public/assets/{lib-DdCM7GfN.js → lib-B0r9alsu.js} +2 -2
- package/public/assets/{lib-DdCM7GfN.js.map → lib-B0r9alsu.js.map} +1 -1
- package/public/assets/{lucide-react-CsPXvXMC.js → lucide-react-BCarrGVa.js} +2 -2
- package/public/assets/{lucide-react-CsPXvXMC.js.map → lucide-react-BCarrGVa.js.map} +1 -1
- package/public/assets/{maintenance-BA93FvLR.js → maintenance-Dh9nzjYV.js} +2 -2
- package/public/assets/{maintenance-BA93FvLR.js.map → maintenance-Dh9nzjYV.js.map} +1 -1
- package/public/assets/{managed-agents-DGwdEIzA.js → managed-agents-Bsveljn4.js} +4 -4
- package/public/assets/{managed-agents-DGwdEIzA.js.map → managed-agents-Bsveljn4.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-C7FDXM5c.js → markdown-preview-impl-CYpMFis9.js} +2 -2
- package/public/assets/{markdown-preview-impl-C7FDXM5c.js.map → markdown-preview-impl-CYpMFis9.js.map} +1 -1
- package/public/assets/{memory-CHzkRJqf.js → memory-DMFQMXrp.js} +2 -2
- package/public/assets/{memory-CHzkRJqf.js.map → memory-DMFQMXrp.js.map} +1 -1
- package/public/assets/messages-Cnzc7j50.js +2 -0
- package/public/assets/{messages-B7CJ5tgT.js.map → messages-Cnzc7j50.js.map} +1 -1
- package/public/assets/{orchestrators-DOy9xIx2.js → orchestrators-DSoDPNVS.js} +2 -2
- package/public/assets/{orchestrators-DOy9xIx2.js.map → orchestrators-DSoDPNVS.js.map} +1 -1
- package/public/assets/{overview-DLbv1BWa.js → overview-OsRxo5XQ.js} +2 -2
- package/public/assets/{overview-DLbv1BWa.js.map → overview-OsRxo5XQ.js.map} +1 -1
- package/public/assets/{pairs-BDQo-k3F.js → pairs-D4UiUhyW.js} +2 -2
- package/public/assets/{pairs-BDQo-k3F.js.map → pairs-D4UiUhyW.js.map} +1 -1
- package/public/assets/{projects-hU5KGW_T.js → projects-CYzU-Cf3.js} +2 -2
- package/public/assets/{projects-hU5KGW_T.js.map → projects-CYzU-Cf3.js.map} +1 -1
- package/public/assets/{react-dom-CEl58oxP.js → react-dom-CYpnckci.js} +2 -2
- package/public/assets/{react-dom-CEl58oxP.js.map → react-dom-CYpnckci.js.map} +1 -1
- package/public/assets/{security-9WrCcrWs.js → security-CC2F6xdd.js} +2 -2
- package/public/assets/{security-9WrCcrWs.js.map → security-CC2F6xdd.js.map} +1 -1
- package/public/assets/{select-CRjeGZ9a.js → select-CD38f1WQ.js} +2 -2
- package/public/assets/{select-CRjeGZ9a.js.map → select-CD38f1WQ.js.map} +1 -1
- package/public/assets/settings-T3B40Amb.js +6 -0
- package/public/assets/settings-T3B40Amb.js.map +1 -0
- package/public/assets/{store-DsqUEZOf.js → store-CJeafbP1.js} +3 -3
- package/public/assets/{store-DsqUEZOf.js.map → store-CJeafbP1.js.map} +1 -1
- package/public/assets/{tasks-86St2vpn.js → tasks-BkrhWMw7.js} +2 -2
- package/public/assets/{tasks-86St2vpn.js.map → tasks-BkrhWMw7.js.map} +1 -1
- package/public/assets/{teams-C9WUxzud.js → teams-DyzaREAg.js} +2 -2
- package/public/assets/{teams-C9WUxzud.js.map → teams-DyzaREAg.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-yXWyt2fm.js → terminal-viewer-impl-B5JeHof3.js} +3 -3
- package/public/assets/{terminal-viewer-impl-yXWyt2fm.js.map → terminal-viewer-impl-B5JeHof3.js.map} +1 -1
- package/public/assets/{use-keyboard-viewport-DABeIMEJ.js → use-keyboard-viewport-BYCJkZCN.js} +2 -2
- package/public/assets/{use-keyboard-viewport-DABeIMEJ.js.map → use-keyboard-viewport-BYCJkZCN.js.map} +1 -1
- package/public/assets/user-avatar-C7i11_ju.js +2 -0
- package/public/assets/{user-avatar-tomKla2H.js.map → user-avatar-C7i11_ju.js.map} +1 -1
- package/public/assets/{work-queue-BtLMzf0g.js → work-queue-DHp4p1Zk.js} +2 -2
- package/public/assets/{work-queue-BtLMzf0g.js.map → work-queue-DHp4p1Zk.js.map} +1 -1
- package/public/assets/workspaces-BzwZvbsq.js +3 -0
- package/public/assets/{workspaces-C740m1ys.js.map → workspaces-BzwZvbsq.js.map} +1 -1
- package/public/index.html +21 -21
- package/src/agent-lifecycle-events.ts +0 -10
- package/src/branch-landed.ts +17 -29
- package/src/bus.ts +9 -2
- package/src/config-store.ts +13 -26
- package/src/config.ts +1 -0
- package/src/context-advisory.ts +20 -10
- package/src/db/agent-predicates.ts +21 -0
- package/src/db/agents.ts +3 -3
- package/src/db/delivery.ts +38 -0
- package/src/db/messages.ts +18 -1
- package/src/db/migrations.ts +17 -0
- package/src/db/scheduled-timers.ts +33 -12
- package/src/db/schema.ts +4 -0
- package/src/db/workspaces.ts +44 -2
- package/src/maintenance/constants.ts +2 -0
- package/src/maintenance/jobs.ts +21 -8
- package/src/maintenance/workspaces.ts +76 -0
- package/src/mcp/index.ts +1 -1
- package/src/mcp/tools-messaging.ts +13 -3
- package/src/mcp/tools-spawn.ts +30 -2
- package/src/mcp-notification-subscription-tools.ts +55 -2
- package/src/notification-router.ts +163 -9
- package/src/notification-rules.ts +7 -1
- package/src/notification-settings-store.ts +47 -0
- package/src/notification-settings.ts +155 -0
- package/src/notification-types.ts +2 -0
- package/src/quota-advisory.ts +0 -3
- package/src/routes/agents-spawn.ts +15 -2
- package/src/routes/agents.ts +17 -2
- package/src/routes/commands.ts +5 -0
- package/src/routes/index.ts +2 -0
- package/src/routes/notifications.ts +36 -0
- package/src/routes/settings.ts +6 -0
- package/src/security.ts +1 -0
- package/src/services/managed-running.ts +18 -1
- package/src/services/rate-limit-resume.ts +3 -0
- package/src/services/register-agent.ts +7 -1
- package/src/services/scheduler.ts +29 -1
- package/src/services/spawn-agent.ts +62 -2
- package/src/settings/registry.ts +30 -44
- package/src/spawn-command.ts +10 -0
- package/src/sse.ts +1 -0
- package/src/validation.ts +7 -2
- package/src/workspace-auto-merge.ts +53 -9
- package/public/assets/MessageBubble-BORB9AKu.js +0 -3
- package/public/assets/MessageBubble-BORB9AKu.js.map +0 -1
- package/public/assets/activity-DD9bl5uf.js +0 -2
- package/public/assets/agents-DJgqCIqi.js +0 -2
- package/public/assets/branch-state-badge-Q4W68jxj.js +0 -2
- package/public/assets/card-DDxxo-Fo.js +0 -2
- package/public/assets/chat-Dw5xpc5W.js +0 -2
- package/public/assets/copy-button-D3B_s7J7.js +0 -2
- package/public/assets/dist-CIbvPDQV.js +0 -2
- package/public/assets/dist-CoRABSO3.js +0 -2
- package/public/assets/dist-DyqEeyBI.js +0 -2
- package/public/assets/index-Bsi2NH0v.js +0 -23
- package/public/assets/index-Bsi2NH0v.js.map +0 -1
- package/public/assets/index-umexz3cs.css +0 -2
- package/public/assets/integrations-D9Cx-7ma.js +0 -2
- package/public/assets/messages-B7CJ5tgT.js +0 -2
- package/public/assets/settings-Durch6xW.js +0 -4
- package/public/assets/settings-Durch6xW.js.map +0 -1
- package/public/assets/user-avatar-tomKla2H.js +0 -2
- package/public/assets/workspaces-C740m1ys.js +0 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"display-CWMHll1J.js","names":[],"sources":["../../dashboard/src/lib/constants.ts","../../sdk/src/types/guards.ts","../../sdk/src/types/constants.ts","../../sdk/src/types/workspaces.ts","../../sdk/src/types/tokens.ts","../../sdk/src/types/users.ts","../../dashboard/src/lib/display.ts"],"sourcesContent":["import type { ComposeState, InboxComposeState, PairMessageState, PairInviteState, ViewName } from '@/types'\n\nconst PREF_PREFIX = 'ar-'\nexport const HUMAN_AGENT_ID = 'user'\nexport const INBOX_OPERATOR_ID = HUMAN_AGENT_ID\nexport const LIVE_REFRESH_MS = 5_000\n// Max live messages retained in memory. Must be >= the largest fetch limit\n// (chat view fetches 300) so SSE appends never trim already-loaded history.\nexport const MESSAGE_BUFFER_CAP = 500\n\nexport const CLOSED_TASK_STATUSES = new Set(['done', 'failed', 'canceled'])\nexport const WAITING_TASK_STATUSES = new Set(['open', 'blocked'])\n\nexport const STATUS_SORT_ORDER: Record<string, number> = { online: 0, idle: 0, busy: 2, offline: 3 }\nexport const CHAT_STATUS_SORT_ORDER: Record<string, number> = { idle: 0, online: 0, busy: 2, offline: 4 }\n\nexport const DEFAULT_COMPOSE: ComposeState = { from: '', to: '', body: '', channel: '', subject: '', claimable: false }\nexport const DEFAULT_INBOX_COMPOSE: InboxComposeState = { toMode: 'agent', to: '', body: '', channel: '', subject: '', claimable: false }\nexport const DEFAULT_PAIR_MESSAGE: PairMessageState = { pairId: '', from: '', body: '', subject: '' }\nexport const DEFAULT_PAIR_INVITE: PairInviteState = { requesterId: '', targetId: '', objective: '' }\n\n\nexport const AGENT_TYPE_ICONS: Record<string, string> = {\n codex: 'Terminal',\n claude: 'ClaudeLogo',\n user: 'User',\n system: 'Server',\n channel: 'MessagesSquare',\n agent: 'Bot',\n}\n\nexport const AGENT_TYPE_TITLES: Record<string, string> = {\n codex: 'Codex agent',\n claude: 'Claude agent',\n user: 'Human operator',\n system: 'System',\n channel: 'Channel',\n agent: 'Agent',\n}\n\nexport const NAV_ITEMS: Array<{ key: ViewName; label: string; icon: string }> = [\n { key: 'overview', label: 'Overview', icon: 'LayoutDashboard' },\n { key: 'chat', label: 'Chat', icon: 'MessageCircle' },\n { key: 'agents', label: 'Agents', icon: 'Bot' },\n { key: 'managed', label: 'Managed Agents', icon: 'Workflow' },\n { key: 'teams', label: 'Teams', icon: 'Users' },\n { key: 'profiles', label: 'Agent Profiles', icon: 'UserCog' },\n { key: 'orchestrators', label: 'Orchestrators', icon: 'Server' },\n { key: 'workspaces', label: 'Workspaces', icon: 'GitBranch' },\n { key: 'files', label: 'Files', icon: 'FolderTree' },\n { key: 'channels', label: 'Channels', icon: 'MessagesSquare' },\n { key: 'connectors', label: 'Connectors', icon: 'Plug' },\n { key: 'integrations', label: 'Integrations', icon: 'PlugZap' },\n { key: 'security', label: 'Security', icon: 'Shield' },\n { key: 'memory', label: 'Memory', icon: 'BrainCircuit' },\n { key: 'activity', label: 'Activity', icon: 'Activity' },\n { key: 'pairs', label: 'Pairs', icon: 'Link' },\n { key: 'messages', label: 'Messages', icon: 'Mail' },\n { key: 'work', label: 'Work Queue', icon: 'ListChecks' },\n { key: 'tasks', label: 'Tasks', icon: 'ClipboardList' },\n { key: 'automation', label: 'Automation', icon: 'CalendarClock' },\n { key: 'analytics', label: 'Analytics', icon: 'AreaChart' },\n { key: 'insights', label: 'Insights', icon: 'Lightbulb' },\n { key: 'projects', label: 'Projects', icon: 'Boxes' },\n { key: 'maintenance', label: 'Maintenance', icon: 'Wrench' },\n { key: 'settings', label: 'Settings', icon: 'Settings' },\n]\n\nexport const SEVERITY_COLORS: Record<string, string> = {\n critical: 'text-red-400 bg-red-500/10',\n warning: 'text-yellow-400 bg-yellow-500/10',\n info: 'text-blue-400 bg-blue-500/10',\n}\n\nexport const PAIR_STATUS_COLORS: Record<string, string> = {\n active: 'bg-emerald-500/20 text-emerald-400',\n pending: 'bg-yellow-500/20 text-yellow-400',\n ended: 'bg-zinc-500/20 text-zinc-400',\n rejected: 'bg-red-500/20 text-red-400',\n expired: 'bg-red-500/20 text-red-400',\n}\n","// --- Shared micro-helpers (single source of truth; do not re-declare per file) ---\n\n/** True for a non-null, non-array object. The canonical type guard for the whole repo. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Narrow `unknown` to a non-empty trimmed string, else `undefined`.\n * Settled semantics: whitespace-only is treated as empty (returns `undefined`).\n */\nexport function stringValue(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\n/** Extract a human-readable message from any thrown value. */\nexport function errMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n","// --- Relay connection defaults ---\n\n/** Default port the relay server listens on. */\nconst DEFAULT_RELAY_PORT = 4850;\n\n/** Default relay base URL. Loopback spelling settled on `127.0.0.1` (not `localhost`). */\nexport const DEFAULT_RELAY_URL = `http://127.0.0.1:${DEFAULT_RELAY_PORT}`;\n","/** Workspace modes — runtime tuple + derived type + guard. */\nexport const VALID_WORKSPACE_MODES = [\"isolated\", \"shared\", \"inherit\"] as const;\nexport type WorkspaceMode = (typeof VALID_WORKSPACE_MODES)[number];\nexport function isWorkspaceMode(value: unknown): value is WorkspaceMode {\n return typeof value === \"string\" && (VALID_WORKSPACE_MODES as readonly string[]).includes(value);\n}\n/** Narrow an unknown to a WorkspaceMode, or undefined if it isn't one. */\nexport function normalizeWorkspaceMode(value: unknown): WorkspaceMode | undefined {\n return isWorkspaceMode(value) ? value : undefined;\n}\nexport type WorkspaceStatus = \"active\" | \"ready\" | \"conflict\" | \"review_requested\" | \"merge_planned\" | \"merged\" | \"abandoned\" | \"cleanup_requested\" | \"cleaned\";\n/** Terminal workspace statuses shared across server + dashboard filters. */\nexport const TERMINAL_WORKSPACE_STATUS_VALUES = [\"cleaned\", \"merged\", \"abandoned\"] as const satisfies readonly WorkspaceStatus[];\n\n/** How a workspace's work is integrated back into its base branch. */\nexport type WorkspaceMergeStrategy = \"pr\" | \"rebase-ff\" | \"auto\";\n\n/** Policy words accepted as the user-facing landing strategy (normalised to WorkspaceMergeStrategy). */\nexport type WorkspaceLandingPolicy = \"direct\" | \"pr\";\n\n/**\n * When to auto-merge a PR that was opened by a `pr`-strategy land.\n * `on-green` — merge once all required status checks pass (default for pr lands).\n * `on-approval` — merge once a reviewer has approved.\n * `manual` — open the PR but never auto-merge; the human hits the button.\n */\nexport const AUTO_MERGE_POLICIES = [\"on-green\", \"on-approval\", \"manual\"] as const;\nexport type WorkspaceAutoMergePolicy = (typeof AUTO_MERGE_POLICIES)[number];\n\n/** Instance-wide landing configuration (stored in config-store under namespace \"landing\"). */\nexport interface LandingConfig {\n /** Default strategy for all workspace lands. \"direct\" → \"auto\" (host picks rebase-ff/pr); \"pr\" → always open a PR. */\n strategy: WorkspaceLandingPolicy;\n}\n\nexport interface WorkspaceProbeWorktree {\n path: string;\n branch?: string;\n headSha?: string;\n bare?: boolean;\n detached?: boolean;\n prunable?: boolean;\n locked?: boolean;\n reason?: string;\n}\n\nexport interface WorkspaceProbe {\n path: string;\n isGitRepo: boolean;\n repoRoot?: string;\n branch?: string;\n headSha?: string;\n dirty?: boolean;\n detached?: boolean;\n worktrees?: WorkspaceProbeWorktree[];\n error?: string;\n}\n\ninterface WorkspaceGitCommit {\n sha: string;\n message: string;\n at?: number;\n}\n\n/**\n * Live git state of a workspace worktree, computed on the host by the\n * orchestrator. Used to surface whether a worktree contains actual work\n * (commits ahead of base, dirty files) so the dashboard isn't a black box and\n * so exit-time reconcile can decide cleanup-vs-flag.\n */\nexport interface WorkspaceGitState {\n /** Commits on the agent branch not yet in base (raw `rev-list` count). */\n ahead?: number;\n /** Commits still genuinely unmerged after discounting work already present in\n * base via a squash/cherry-pick (git-cherry '+' count). Equals `ahead` for a\n * normal branch; 0 when the work landed but left `ahead` positive. */\n unmergedAhead?: number;\n /** True when `ahead` > 0 but the work is already in base (squash/cherry-pick\n * merge) — the worktree is effectively merged and safe to clean up. */\n landed?: boolean;\n /** Commits in base not in the agent branch (how far base moved on). */\n behind?: number;\n /** True if the worktree has uncommitted/untracked changes. */\n dirty?: boolean;\n /** Number of dirty (porcelain) entries. */\n dirtyCount?: number;\n /** Most recent commit in the worktree. */\n lastCommit?: WorkspaceGitCommit;\n /** Ref/sha the ahead/behind counts were computed against. */\n baseRef?: string;\n /** Branch currently checked out in the worktree (for recorded-vs-live mismatch). */\n branch?: string;\n /** Set when the worktree path no longer exists on disk. */\n missing?: boolean;\n /** Populated when git interrogation failed. */\n error?: string;\n}\n\n/**\n * Pre-flight check for integrating a workspace's work, computed on the host.\n * Reports whether the merge would be clean, conflict, or is a no-op, and which\n * strategy `auto` would pick — so the dashboard can warn before the user acts.\n */\nexport interface WorkspaceMergePreview {\n /** Strategy `auto` would resolve to given the repo's remote/gh state. */\n strategy: \"pr\" | \"rebase-ff\";\n /** Commits on the agent branch not yet in base (raw count). */\n ahead?: number;\n /** Genuinely-unmerged commits after discounting squash/cherry-pick landings. */\n unmergedAhead?: number;\n /** True when the work already landed in base via squash/cherry-pick. */\n landed?: boolean;\n /** Merged-PR state (ground truth when local git can't see a squash landing because\n * base moved on). Populated only with `checkPr` (a `gh` round-trip). */\n prState?: \"merged\" | \"open\";\n /** True when the branch's PR is merged on the remote. `prMergeSha` is its merge commit,\n * recorded as the landed SHA when the relay finalizes an out-of-band PR merge (#304). */\n prMerged?: boolean;\n prMergeSha?: string;\n /** Subject (PR title) of the merged PR, carried into the `branch.landed` notice so it\n * reads with the landed change's title, not just a bare SHA (#423). */\n prMergeSubject?: string;\n /** URL of the branch's PR, when one was found via `gh`. */\n prUrl?: string; prNumber?: number; reviewDecision?: \"APPROVED\" | \"CHANGES_REQUESTED\" | \"REVIEW_REQUIRED\"; statusCheckRollup?: unknown[]; mergeable?: string; mergeStateStatus?: string;\n /** Commits in base not in the agent branch. */\n behind?: number;\n /** Uncommitted/untracked entries in the worktree. */\n dirtyCount?: number;\n /** True if the three-way merge of base and the branch would conflict. */\n conflict?: boolean;\n /** True if base can fast-forward to the branch with no rebase needed. */\n cleanFastForward?: boolean;\n /** Whether an `origin` remote is configured. */\n hasRemote?: boolean;\n /** Whether the `gh` CLI is available on the host. */\n ghAvailable?: boolean;\n /** Resolved base branch name the work would land on. */\n baseRef?: string;\n /** Human-readable reason a merge can't proceed (no work, dirty, missing). */\n reason?: string;\n /** True when there is nothing to land: ahead=0 with a clean worktree (branch tree\n * already in base — never diverged, or already landed via squash/cherry-pick/PR).\n * A no-op land resolves the workspace to a terminal state instead of parking it in\n * the steward queue (#230). Distinct from a dirty-worktree refusal, which also sets\n * `reason` but is not a no-op. */\n noop?: boolean;\n /** Set when the worktree path no longer exists. */\n missing?: boolean;\n /** Populated when git interrogation failed. */\n error?: string;\n}\n\n/** Outcome of a `workspace.merge` command executed on the host. */\nexport interface WorkspaceMergeResult {\n workspaceId?: string;\n /** Strategy actually used. */\n strategy: \"pr\" | \"rebase-ff\";\n /** True when work was landed locally (rebase-ff into base). */\n merged: boolean;\n /** True when the land was a no-op — nothing to merge (ahead=0, clean worktree),\n * resolved to a terminal `merged` status to clear the steward queue (#230). The\n * worktree/branch are reclaimed too when the owner is gone (`deleteBranch`). */\n noop?: boolean;\n /** True when a merge was prevented by conflicts. */\n conflict?: boolean;\n /** PR URL when the pr strategy opened one. */\n prUrl?: string;\n branch?: string;\n baseRef?: string;\n /** SHA of the landed commit — the agent branch's tip, preserved verbatim (#287).\n * This is the SHA reported in the `branch.landed` notice, so it exists on base\n * exactly as the agent produced it (no rebase rewrite). On a clean fast-forward\n * it equals {@link baseSha}; on a no-ff merge it's the merge's second parent. */\n mergedSha?: string;\n /** SHA base points at after the land (#287): equals {@link mergedSha} on a\n * fast-forward, or the merge commit on a no-ff merge when base had advanced. The\n * relay records this as the recycled workspace's new base_sha. Absent on older\n * orchestrators — consumers fall back to {@link mergedSha}. */\n baseSha?: string;\n /** Subject line of the landed commit (git log -1 --format=%s of mergedSha), for\n * the `branch.landed` notification body (#239). Best-effort; absent on older\n * orchestrators or when the subject can't be read. */\n subject?: string;\n branchDeleted?: boolean;\n worktreeRemoved?: boolean;\n /** Fresh branch the worktree was recycled onto after a land-and-continue merge\n * (#206) — the relay repoints the workspace row at it. Absent for terminal lands. */\n newBranch?: string;\n /** True when the landed base branch was pushed to its upstream (origin). */\n pushed?: boolean;\n /** Resulting workspace status the relay should record. */\n status: WorkspaceStatus;\n /** Deps re-provisioned after a land-and-continue recycle when the advanced base\n * brought new dependencies (issue #51). Absent when nothing was stale. */\n depsRefresh?: WorkspaceDepsRefreshResult;\n /** True when GitHub auto-merge was successfully armed after PR creation (#305).\n * False when arming was attempted but the repo has auto-merge disabled. */\n autoMergeArmed?: boolean;\n /** True when the PR was opened but auto-merge was NOT armed because the\n * autoMerge policy is \"on-approval\" — a reviewer pipeline arms it later (#305). */\n awaitingApproval?: boolean;\n /** Populated when the merge could not complete. */\n error?: string;\n}\n\n/**\n * Joined steward briefing for one workspace (#208): the row, owner + orchestrator\n * liveness, live git state, recorded-vs-live branch mismatch, any active steward\n * claim, and a recommended next action — so a steward (or release agent) doesn't\n * reconstruct it from ad-hoc shell + API calls.\n */\nexport interface WorkspaceDiagnostics {\n workspaceId: string;\n status: WorkspaceStatus;\n mode: WorkspaceMode;\n repoRoot: string;\n worktreePath?: string;\n recordedBranch?: string;\n liveBranch?: string;\n baseRef?: string;\n branchMismatch?: boolean;\n owner?: { id?: string; status?: string; online: boolean };\n /**\n * The branch agent's coordinator — the owner's `spawnedBy` lineage resolved to a live\n * agent (#640). `id` is null when no coordinator resolves (owner unknown or not spawned\n * by anyone); steward escalation targets it before the configured fallback.\n */\n coordinator?: { id: string | null; online: boolean };\n orchestrator?: { id?: string; online: boolean };\n /** Active claim that auto-merge yields to (#208), if held and unexpired. */\n claim?: { by?: string; purpose?: string; expiresAt?: number };\n /** Live worktree git state (proxied from the host); absent when unavailable. */\n gitState?: WorkspaceGitState;\n /** Why git state is absent (host offline, no worktree, …). */\n gitStateUnavailable?: string;\n recommendation: { action: \"merge\" | \"rebase\" | \"cleanup\" | \"review\" | \"wait\" | \"none\"; confidence: \"high\" | \"medium\" | \"low\"; reason: string };\n}\n\n/** One changed file in a workspace diff against its base. */\nexport interface WorkspaceDiffFile {\n path: string;\n /** Lines added (undefined for binary files). */\n additions?: number;\n /** Lines removed (undefined for binary files). */\n deletions?: number;\n binary?: boolean;\n}\n\n/** Diff of a workspace's committed work against its base, computed on the host. */\nexport interface WorkspaceDiff {\n baseRef?: string;\n ahead?: number;\n files: WorkspaceDiffFile[];\n /** Unified patch text (may be truncated). */\n patch?: string;\n /** True when the patch was capped at the size limit. */\n truncated?: boolean;\n /** Uncommitted entries in the worktree, not part of the committed diff. */\n dirtyCount?: number;\n missing?: boolean;\n error?: string;\n}\n\n/** A worktree found on disk with no matching active workspace row. */\nexport interface WorkspaceOrphan {\n worktreePath: string;\n repoRoot: string;\n branch?: string;\n /** Base ref used to prove the orphan branch is landed before branch-ref cleanup. */\n baseRef?: string;\n /** Base SHA recorded for the workspace when available. */\n baseSha?: string;\n headSha?: string;\n /** True if a DB row exists for this path but is already terminal (cleaned/merged). */\n hadTerminalRow?: boolean;\n /** Work already merged into base (squash/cherry/PR). Set from the host probe. */\n landed?: boolean;\n /** Raw commits ahead of base (a squash-landed branch still shows >0). */\n ahead?: number;\n /** Commits ahead whose patch isn't already in base — the squash-aware count. */\n unmergedAhead?: number;\n /** Uncommitted working-tree changes in the worktree. */\n dirty?: boolean;\n /**\n * Safe to remove with no loss: clean tree AND (nothing ahead OR already landed).\n * `false` means the worktree holds un-landed work and must be flagged, not reaped.\n * `undefined` means the host couldn't be probed — treat as not safe.\n */\n safeToReap?: boolean;\n}\n\n/** Host-local view of an agent/* branch that may need recovery review. */\nexport interface WorkspaceRecoveryBranch {\n repoRoot: string;\n branch: string;\n headSha?: string;\n baseRef?: string;\n baseSha?: string;\n ahead?: number;\n unmergedAhead?: number;\n landed?: boolean;\n lastCommit?: WorkspaceGitCommit;\n ageMs?: number;\n /** True when the branch has no unique unlanded work relative to base. */\n safeToDelete?: boolean;\n /** Why safety could not be proven or why work must be reviewed first. */\n preserveReason?: string;\n /** Server-side enrichment when a workspace row still records this branch. */\n workspaceId?: string;\n ownerAgentId?: string;\n ownerStatus?: string;\n cleanupCommandId?: string;\n cleanupPreservedReason?: string;\n preservedByCleanup?: boolean;\n activeWorkspace?: boolean;\n}\n\nexport interface WorkspaceRecoveryBranchList {\n repoRoot?: string;\n branches: WorkspaceRecoveryBranch[];\n scannedRepos?: string[];\n reason?: string;\n}\n\nexport interface WorkspaceRecoveryBranchDiscardResult {\n repoRoot: string;\n branch: string;\n branchDeleted: boolean;\n forced?: boolean;\n preserveReason?: string;\n headSha?: string;\n}\n\n/**\n * Outcome of provisioning node_modules into a freshly created isolated worktree.\n * Git worktrees do not share gitignored/untracked files, so a new worktree starts\n * with no installed deps — typecheck/test/build would see thousands of phantom\n * missing-module errors. We either symlink the source checkout's node_modules\n * (fast, default) or run a package install (when the source has none to borrow).\n */\nexport interface WorkspaceDepsProvision {\n mode: \"symlink\" | \"install\" | \"none\";\n /** Relative dirs (from repo root) whose node_modules were symlinked. */\n linked?: string[];\n /** Relative dirs where a package install ran successfully. */\n installed?: string[];\n packageManager?: string;\n error?: string;\n}\n\n/** Result of symlinking configured untracked files/dirs from main into an isolated worktree. */\nexport interface WorkspaceSymlinkProvision {\n /** Relative paths (from repo root) that were symlinked from the main checkout. */\n linked: string[];\n /** Per-pattern failures, if any (best-effort: a failure never blocks the spawn). */\n errors?: string[];\n}\n\n/** One project dir's outcome in a workspace deps refresh (issue #51). */\nexport interface WorkspaceDepsRefreshDir {\n /** Relative dir from repo root (`.` for root, e.g. `dashboard`). */\n dir: string;\n /** ok — declared deps already present; stale — missing (check-only, not installed);\n * installed — re-installed in isolation; failed — install errored. */\n status: \"ok\" | \"stale\" | \"installed\" | \"failed\";\n /** Declared deps (dependencies + devDependencies) missing from node_modules, capped sample. */\n missing?: string[];\n /** True when the dir's node_modules was a shared symlink replaced with a real install. */\n wasSymlink?: boolean;\n packageManager?: string;\n error?: string;\n}\n\n/**\n * Outcome of refreshing an isolated worktree's deps (issue #51). The default symlink\n * provisioning shares the source checkout's node_modules, so a dep added to the base\n * AFTER worktree creation is missing in the worktree. Refresh promotes each stale dir\n * to a real, isolated install — never mutating the shared source node_modules.\n */\nexport interface WorkspaceDepsRefreshResult {\n /** True when at least one dir was actually (re)installed. */\n refreshed: boolean;\n /** True (check-only) when any dir is stale, or (refresh) when any install left deps missing. */\n stale?: boolean;\n dirs: WorkspaceDepsRefreshDir[];\n error?: string;\n}\n\nexport interface WorkspaceMetadata {\n id?: string;\n mode: WorkspaceMode;\n requestedMode?: WorkspaceMode;\n repoRoot?: string;\n sourceCwd?: string;\n worktreePath?: string;\n branch?: string;\n baseRef?: string;\n baseSha?: string;\n status?: WorkspaceStatus;\n stewardAgentId?: string;\n deps?: WorkspaceDepsProvision;\n /** Configured untracked files/dirs symlinked from main (see WorkspaceConfig.symlinkPaths). */\n symlinks?: WorkspaceSymlinkProvision;\n probe?: WorkspaceProbe;\n}\n\nexport interface WorkspaceRecord {\n id: string;\n repoRoot: string;\n sourceCwd: string;\n worktreePath: string;\n branch?: string;\n baseRef?: string;\n baseSha?: string;\n mode: WorkspaceMode;\n requestedMode?: WorkspaceMode;\n status: WorkspaceStatus;\n ownerAgentId?: string;\n /** Server-derived from ownerAgentId via the authoritative owner liveness helper. */\n ownerOnline?: boolean;\n ownerPolicyName?: string;\n ownerAutomationRunId?: string;\n stewardAgentId?: string;\n metadata: Record<string, unknown>;\n createdAt: number;\n updatedAt: number;\n readyAt?: number;\n cleanedAt?: number;\n}\n","// ──────────────────────────────────────────────────────────────────────────\n// Settings descriptors (epic #380, #382) — the self-describing contract any\n// surface (core relay, provider, channel, connector) registers so the API\n// (#383), scopes (#385), module wiring (#386), and UI (#384) all build on one\n// abstraction. This is the *declaration* layer over the existing `config`\n// table — NOT a storage change. The registry home is `src/settings/registry.ts`.\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * A typed JSON Schema (draft-07-compatible). Common keywords are spelled out so\n * descriptors get type-checked field shapes; the `[key: string]: unknown` tail\n * keeps the door open for any keyword `ajv` understands (the registry compiles\n * each schema with `ajv`, so anything ajv accepts is valid). Declared here (not\n * as `Record<string, unknown>`) so the descriptor's `schema` field is typed —\n * the sdk stays dependency-free; only the server imports `ajv` to compile it.\n */\nexport type JSONSchemaTypeName =\n | \"string\"\n | \"number\"\n | \"integer\"\n | \"boolean\"\n | \"object\"\n | \"array\"\n | \"null\";\n\nexport interface JSONSchema {\n $schema?: string;\n $id?: string;\n $ref?: string;\n $defs?: Record<string, JSONSchema>;\n definitions?: Record<string, JSONSchema>;\n title?: string;\n description?: string;\n type?: JSONSchemaTypeName | JSONSchemaTypeName[];\n enum?: unknown[];\n const?: unknown;\n default?: unknown;\n examples?: unknown[];\n // numeric\n minimum?: number;\n maximum?: number;\n exclusiveMinimum?: number;\n exclusiveMaximum?: number;\n multipleOf?: number;\n // string\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n format?: string;\n // array\n items?: JSONSchema | JSONSchema[];\n minItems?: number;\n maxItems?: number;\n uniqueItems?: boolean;\n // object\n properties?: Record<string, JSONSchema>;\n required?: string[];\n additionalProperties?: boolean | JSONSchema;\n patternProperties?: Record<string, JSONSchema>;\n // composition\n allOf?: JSONSchema[];\n anyOf?: JSONSchema[];\n oneOf?: JSONSchema[];\n not?: JSONSchema;\n // escape hatch for any other JSON Schema keyword\n [key: string]: unknown;\n}\n\n/**\n * Where a setting lives and how it can be changed:\n * - `server-runtime` — instance-global; stored in the `config` table, mutable at runtime via the API.\n * - `bootstrap` — sourced from env/file at boot; surfaced read-only (the API exposes it but cannot write it).\n * - `browser` — a client-only preference; the server never persists it (UI/local-storage owns it).\n * - `user` — per-human preference (#391); stored in `user_settings` keyed by the resolved `users.id`,\n * mutable at runtime. The same `(namespace, key)` descriptor address resolves to a per-user value,\n * so a pref follows the human across devices. Defaults to the seeded `user:default` (#393) until\n * sessions (#389) map a request to its user.\n */\nexport type SettingTier = \"server-runtime\" | \"bootstrap\" | \"browser\" | \"user\";\n\n/**\n * The scopes (#385) a caller must hold to read or write a setting. Carried as\n * opaque scope-token strings — #385 owns the grammar and `hasScope` matching;\n * this layer only declares the requirement. Registration defaults both to the\n * fail-closed admin scope when a descriptor omits them.\n */\nexport interface SettingScope {\n read: string;\n write: string;\n}\n\n/** Display metadata for the Settings UI (#384) — grouping, labelling, ordering. */\nexport interface SettingDisplay {\n /** Grouping bucket the UI renders this setting under (e.g. \"Agents\", \"Notifications\"). */\n group: string;\n /** Human-readable label. */\n label: string;\n /** Optional longer description / help text. */\n description?: string;\n /** Sort order within the group (ascending; unset sorts last). */\n order?: number;\n}\n\n/**\n * A self-describing setting. Any surface registers one of these; the registry is\n * the single source of truth for which namespaces exist, their schema, defaults,\n * required scope, tier, and display. `scope` is always populated on a registered\n * descriptor (the registry fills the fail-closed admin default) so consumers\n * never null-check it. `namespace`/`key` map directly onto the `config` table's\n * composite PK.\n */\nexport interface SettingDescriptor<T = unknown> {\n /** Maps onto the `config` table namespace (e.g. \"steward\", \"landing\"). */\n namespace: string;\n /** Maps onto the `config` table key within the namespace (e.g. \"default\"). */\n key: string;\n /** Full JSON Schema for the value — ajv-compiled at registration (#382/#383). */\n schema: JSONSchema;\n /** Fallback value used when the config row is unset. */\n defaults: T;\n /** Read/write scopes required to access this setting (#385). */\n scope: SettingScope;\n /** Mutability/source tier. */\n tier: SettingTier;\n /** Display metadata for the Settings UI (#384). */\n display: SettingDisplay;\n}\n\n/** Runtime settings API entry. `version: 0` means descriptor defaults only; no config row is stored. */\nexport interface SettingEntry<T = unknown> extends SettingDescriptor<T> {\n value: T;\n version: number;\n updatedAt: string;\n updatedBy?: string;\n readable: boolean;\n writable: boolean;\n}\n\nconst SCOPE_ALIASES: Record<string, string> = {\n \"admin:*\": \"system:admin\",\n \"system:write\": \"system:admin\",\n \"agents:read\": \"agent:read\",\n \"agents:write\": \"agent:write\",\n \"messages:read\": \"message:read\",\n \"messages:write\": \"message:send\",\n \"tasks:read\": \"task:read\",\n \"tasks:write\": \"task:write\",\n \"commands:read\": \"command:read\",\n \"commands:write\": \"command:write\",\n \"channels:read\": \"channel:read\",\n \"channels:write\": \"channel:write\",\n \"integrations:read\": \"integration:read\",\n \"integrations:write\": \"integration:write\",\n \"connectors:read\": \"integration:read\",\n \"connectors:write\": \"integration:write\",\n \"events:create\": \"integration:write\",\n \"tasks:create\": \"task:write\",\n};\n\nexport function normalizeScope(scope: string): string {\n return SCOPE_ALIASES[scope] ?? scope;\n}\n\nexport function hasScope(scopes: readonly string[] | undefined, requiredScope: string): boolean {\n const required = normalizeScope(requiredScope);\n const requiredParts = parseScopeParts(required);\n if (!requiredParts) return false;\n if (scopes?.includes(\"*\")) return true;\n for (const rawScope of scopes ?? []) {\n const scope = normalizeScope(rawScope);\n const scopeParts = parseScopeParts(scope);\n if (!scopeParts) continue;\n if (scope === \"system:admin\") return true;\n if (scope === required) return true;\n if (scope === `${requiredParts.category}:*`) return true;\n if (\n requiredParts.category === \"settings\" &&\n requiredParts.action &&\n requiredParts.namespace &&\n scopeParts.category === \"settings\" &&\n scopeParts.action === requiredParts.action &&\n scopeParts.namespace === undefined\n ) return true;\n }\n return false;\n}\n\nfunction parseScopeParts(scope: string): { category: string; action?: string; namespace?: string } | null {\n if (scope === \"*\") return { category: \"*\" };\n const parts = scope.split(\":\");\n if (parts.some((part) => !part)) return null;\n if (parts.length === 2) {\n const [category, action] = parts as [string, string];\n return { category, action };\n }\n if (parts.length === 3) {\n const [category, action, namespace] = parts as [string, string, string];\n if (category !== \"settings\") return null;\n if (action !== \"read\" && action !== \"write\") return null;\n if (namespace === \"*\") return null;\n return { category, action, namespace };\n }\n return null;\n}\n\nexport type PairStatus = \"pending\" | \"active\" | \"ended\" | \"rejected\" | \"expired\";\n\nexport interface PairSession {\n id: string;\n requesterId: string;\n targetId: string;\n status: PairStatus;\n objective?: string;\n createdAt: number;\n updatedAt: number;\n expiresAt: number;\n acceptedAt?: number;\n endedAt?: number;\n endedBy?: string;\n lastMessageAt?: number;\n meta: Record<string, unknown>;\n}\n\nexport interface CreatePairInput {\n from: string;\n target: string;\n objective?: string;\n ttlMs?: number;\n meta?: Record<string, unknown>;\n}\n\nexport interface PairActionInput {\n agentId: string;\n reason?: string;\n}\n\nexport interface PairMessageInput {\n from: string;\n body: string;\n subject?: string;\n}\n\nexport interface PollQuery {\n for: string; // agent-id\n since?: number; // unix ms (createdAt cursor)\n sinceId?: number; // monotonic message id cursor (preferred — avoids same-ms collisions)\n unread?: boolean;\n channel?: string;\n limit?: number;\n}\n\nexport interface ComponentToken {\n sub: string;\n role: \"provider\" | \"channel\" | \"orchestrator\" | \"admin\" | \"dashboard\" | string;\n scope: string[];\n constraints?: TokenConstraints;\n iat: number;\n exp?: number;\n jti?: string;\n}\n\nexport interface TokenRecord {\n jti: string;\n sub: string;\n role: string;\n scope: string[];\n constraints?: TokenConstraints;\n profileId?: string;\n issuedAt: number;\n expiresAt?: number;\n revokedAt?: number;\n /** Why the token was revoked (#484): \"stale\" | \"rotation\" | \"admin\" | \"agent-removed\" | \"pruned\". Drives self-heal eligibility. */\n revokedReason?: string;\n createdBy?: string;\n}\n\nexport interface TokenProfile {\n id: string;\n name: string;\n description?: string;\n role: string;\n scope: string[];\n constraints?: TokenConstraints;\n ttlSeconds?: number;\n builtIn: boolean;\n createdAt: number;\n updatedAt: number;\n createdBy?: string;\n}\n\nexport interface CreateTokenProfileInput {\n id?: string;\n name: string;\n description?: string;\n role: string;\n scope: string[];\n constraints?: TokenConstraints;\n ttlSeconds?: number;\n createdBy?: string;\n}\n\nexport type UpdateTokenProfileInput = Partial<Omit<CreateTokenProfileInput, \"id\">>;\n\nexport interface TokenConstraints {\n agents?: string[];\n policies?: string[];\n parentAgents?: string[];\n /** Parent agent id stamped on a child's runner token at spawn; read at registration to\n * set the child agent card's `spawnedBy` authoritatively (the child can't forge it). */\n spawnedBy?: string;\n /** Durable team id stamped on runner tokens; identity-only grouping, never an authorization gate. */\n teamId?: string;\n /** Durable project id resolved from the spawn cwd (containment match against project_roots)\n * and stamped on runner tokens; identity-only grouping (callerProject reads it), never an\n * authorization gate. Absent when the cwd is inside no registered root (#405). */\n projectId?: string;\n /** Tenant id baked into a human session token at login (#389), copied from the user's\n * `users.tenant_id`. Identity-only today: it carries the SaaS multi-tenant seam into the token\n * so isolation becomes an additive change later, but enforces NOTHING — no auth path branches on\n * it yet. Absent for single-tenant users with a null `tenant_id`. */\n tenantId?: string;\n /** Spawn quota (max concurrent live children) baked into a spawn-capable agent's token,\n * resolved from its profile's `maxSpawnedAgents`. The runtime quota check reads it here. */\n maxSpawnedAgents?: number;\n targets?: string[];\n channels?: string[];\n orchestrators?: string[];\n hosts?: string[];\n cwd?: string;\n cwdPrefixes?: string[];\n taskIds?: string[];\n memoryScopes?: string[];\n integrationNames?: string[];\n spawnRequestIds?: string[];\n terminalAttach?: boolean;\n logsRead?: boolean;\n canDelegate?: boolean;\n}\n\ntype TokenScope =\n | \"system:admin\"\n | \"token:read\"\n | \"token:write\"\n | \"agent:read\"\n | \"agent:write\"\n | \"message:read\"\n | \"message:send\"\n | \"schedule:write\"\n | \"schedule:persistent\"\n | \"task:read\"\n | \"task:write\"\n | \"command:read\"\n | \"command:write\"\n | \"command:*\"\n | \"artifact:read\"\n | \"artifact:write\"\n | \"artifact:admin\"\n | \"memory:read\"\n | \"memory:write\"\n | \"memory:admin\"\n | \"mcp:use\"\n | \"teams:read\"\n | \"teams:write\"\n | \"terminal:attach\"\n | \"logs:read\"\n | \"integration:read\"\n | \"integration:write\"\n | \"channel:read\"\n | \"channel:write\"\n | \"stats:read\"\n | \"health:read\"\n | \"events:read\"\n | \"command:spawn\"\n | \"command:shutdown\"\n | \"recipe:start\"\n | \"recipe:stop\"\n | \"admin:*\";\n\nexport interface ConfigEntry<T = unknown> {\n namespace: string;\n key: string;\n value: T;\n version: number;\n updatedAt: string;\n updatedBy?: string;\n}\n\nexport interface ConfigHistoryEntry<T = unknown> {\n id: number;\n namespace: string;\n key: string;\n value: T;\n version: number;\n changedAt: string;\n changedBy?: string;\n}\n\nexport interface MaintenanceJob {\n id: string;\n title: string;\n description?: string;\n intervalMs: number;\n timeoutMs: number;\n enabled: boolean;\n runOnStart: boolean;\n lastRunAt?: number;\n nextRunAt?: number;\n lastDurationMs?: number;\n lastStatus: \"idle\" | \"running\" | \"succeeded\" | \"failed\" | \"disabled\";\n lastError?: string;\n lastResult?: Record<string, unknown>;\n consecutiveFailures: number;\n running: boolean;\n leaseUntil?: number;\n updatedAt: number;\n}\n\nexport interface MaintenanceJobRun {\n id: string;\n status: \"succeeded\" | \"failed\" | \"skipped\";\n startedAt: number;\n finishedAt: number;\n durationMs: number;\n result?: Record<string, unknown>;\n error?: string;\n}\n\nexport interface HealthCheck {\n name: string;\n status: \"ok\" | \"warn\" | \"error\";\n detail?: string;\n count?: number;\n subjects?: Array<{ id: string; label?: string; status?: string; detail?: string }>;\n}\n\nexport interface HealthReport {\n status: \"ok\" | \"degraded\" | \"error\";\n version: string;\n generatedAt: number;\n checks: HealthCheck[];\n}\n","// First-class human users (#388 / keystone #393). Promotes the human from the hardcoded\n// `'user'` sink to a real identity record. Addressing is per-user (`user:<id>`); the bare\n// `'user'` sink is the back-compat alias of the seeded `user:default` admin.\n//\n// SINGLE RESOLVER HOME (duplication ratchet): the `'user'` ≡ `user:default` ≡\n// `users.id='default'` mapping lives ONLY here. Every funnel site and every later #388 wave\n// calls these helpers rather than re-deriving the namespace — one place owns human addressing.\n\n/** Addressing prefix for the per-user human namespace: `user:<id>`. */\nexport const USER_NAMESPACE_PREFIX = \"user:\";\n\n/** Stable relational id of the admin user seeded on first boot, mapped to today's single human. */\nexport const DEFAULT_USER_ID = \"default\";\n\n/** Legacy bare human sink, retained as a back-compat alias of `user:default`. */\nexport const LEGACY_HUMAN_AGENT_ID = \"user\";\n\n/** Canonical addressing form for a human user: `user:<id>`. */\nexport function userSinkId(userId: string): string {\n return `${USER_NAMESPACE_PREFIX}${userId}`;\n}\n\n/**\n * True for any human address: the legacy bare `'user'` sink or any `user:<id>` in the\n * namespace. The human stopped being a singleton in #393 — prefer this over a literal\n * `id === 'user'` comparison so per-user sinks are recognized everywhere the human is.\n */\nexport function isHumanAgentId(id: string | undefined): boolean {\n return id === LEGACY_HUMAN_AGENT_ID || (typeof id === \"string\" && id.startsWith(USER_NAMESPACE_PREFIX));\n}\n\n/**\n * The relational `users.id` behind any human address, or `undefined` for a non-human id.\n * Both the legacy bare `'user'` and the `user:default` alias resolve to {@link DEFAULT_USER_ID};\n * `user:<id>` yields `<id>`. This is the bridge from a wire/message address to a `users` row.\n */\nexport function userIdFromAddress(id: string | undefined): string | undefined {\n if (id === LEGACY_HUMAN_AGENT_ID) return DEFAULT_USER_ID;\n if (typeof id === \"string\" && id.startsWith(USER_NAMESPACE_PREFIX)) {\n return id.slice(USER_NAMESPACE_PREFIX.length) || DEFAULT_USER_ID;\n }\n return undefined;\n}\n\n/**\n * The stored/canonical wire id for a human address, used for routing + storage so legacy bytes\n * never move (#393 Option A). The default user's canonical stored id is the legacy bare `'user'`,\n * so the working `user:default` alias collapses to it and matches every pre-existing\n * message/`operator_id`/inbox row. Every other `user:<id>` is already canonical. Non-human ids\n * pass through unchanged.\n */\nexport function canonicalHumanAgentId(id: string): string {\n // The degenerate empty address `user:` collapses to the default sink too, so it never lands\n // between resolvers: `userIdFromAddress('user:')` already yields DEFAULT_USER_ID, and without\n // this `canonicalHumanAgentId('user:')` returned `'user:'` — one helper said \"default human\",\n // the other routed to a `user:` agent a provider could squat (#393). Both now agree.\n if (id === LEGACY_HUMAN_AGENT_ID || id === userSinkId(DEFAULT_USER_ID) || id === USER_NAMESPACE_PREFIX) {\n return LEGACY_HUMAN_AGENT_ID;\n }\n return id;\n}\n\n/** Role gate for a user. Enforcement (ownership/authz) lands in #392 — this carries the value. */\nexport type UserRole = \"admin\" | \"operator\" | \"viewer\";\n\n/** Account lifecycle. `active` is the only state a fresh/seeded user takes today. */\nexport type UserStatus = \"active\" | \"suspended\" | \"deleted\";\n\nexport interface User {\n /** Stable id; the addressing form is `user:<id>` (see {@link userSinkId}). Others FK to this. */\n id: string;\n /** Short login-ish handle, unique within a tenant. */\n handle: string;\n displayName: string;\n role: UserRole;\n status: UserStatus;\n /**\n * SaaS multi-tenant seam (#393): nullable, defaults to the seeded `tenant:default`. The relay\n * is single-tenant today; the column existing now makes multi-tenant an additive migration\n * rather than a second identity refactor. Do NOT key isolation on it yet.\n */\n tenantId: string | null;\n /**\n * Content-addressed avatar via the existing artifact stack (#395/#630): nullable FK →\n * `artifacts.id`. This sub-issue adds the column only — upload/serve is out of scope.\n */\n avatarArtifactId: string | null;\n /** Authenticated API-relative image path for the current avatar, or null when unset. */\n avatarUrl: string | null;\n createdAt: number;\n updatedAt: number;\n}\n\nexport interface CreateUserInput {\n id: string;\n handle: string;\n displayName: string;\n role?: UserRole;\n status?: UserStatus;\n tenantId?: string | null;\n avatarArtifactId?: string | null;\n}\n","import type {\n Agent, Message, PairSession, Task, ChannelSummary, ConnectorSummary,\n IntegrationSummary, Orchestrator, AgentType, PresenceInfo, PresenceBadge,\n AttentionInfo, InboxThread, HealthCheck, HealthDiagnostic, ManagedPolicyHealth,\n} from '@/types'\nimport type { User } from '@/types'\nimport { HUMAN_AGENT_ID, AGENT_TYPE_ICONS, AGENT_TYPE_TITLES, WAITING_TASK_STATUSES } from './constants'\nimport { isRecord, isHumanAgentId, userIdFromAddress } from 'agent-relay-sdk/types'\n\nexport function toTimestamp(value: unknown): number {\n const ts = typeof value === 'number' ? value : new Date((value as string) || 0).getTime()\n return Number.isFinite(ts) ? ts : 0\n}\n\nconst INTERNAL_CAP_PREFIXES = ['lifecycle.', 'commands.', 'capabilities.']\n\nconst REACTION_EMOJI_ALIASES: Record<string, string> = {\n '+1': '👍',\n ':+1:': '👍',\n thumbsup: '👍',\n ':thumbsup:': '👍',\n 'thumbs-up': '👍',\n ':thumbs-up:': '👍',\n thumbs_up: '👍',\n ':thumbs_up:': '👍',\n thumb_up: '👍',\n ':thumb_up:': '👍',\n like: '👍',\n ':like:': '👍',\n heart: '❤️',\n ':heart:': '❤️',\n redheart: '❤️',\n red_heart: '❤️',\n ':red_heart:': '❤️',\n check: '✅',\n ':check:': '✅',\n checkmark: '✅',\n ':checkmark:': '✅',\n white_check_mark: '✅',\n ':white_check_mark:': '✅',\n eyes: '👀',\n ':eyes:': '👀',\n}\n\nexport function normalizeReactionEmoji(value: string): string {\n const trimmed = value.trim()\n return REACTION_EMOJI_ALIASES[trimmed.toLowerCase()] ?? trimmed\n}\n\nexport function userFacingCapabilities(caps: string[]): string[] {\n return caps.filter((c) => !INTERNAL_CAP_PREFIXES.some((p) => c.startsWith(p)))\n}\n\nexport function shortPath(cwd: string | undefined | null, segments = 2): string {\n if (!cwd) return ''\n const parts = cwd.replace(/\\/+$/, '').split('/')\n return parts.length <= segments ? cwd : parts.slice(-segments).join('/')\n}\n\nexport function normalizePathForCompare(path: string): string {\n return path.trim().replace(/\\\\/g, '/').replace(/\\/+$/, '') || '/'\n}\n\nexport function pathWithinBase(path: string | undefined | null, baseDir: string | undefined | null): boolean {\n if (!path?.trim() || !baseDir?.trim()) return false\n const normalizedPath = normalizePathForCompare(path)\n const normalizedBase = normalizePathForCompare(baseDir)\n if (normalizedBase === '/') return normalizedPath.startsWith('/')\n if (normalizedPath === '/') return false\n return normalizedPath === normalizedBase || normalizedPath.startsWith(normalizedBase + '/')\n}\n\nfunction isBuiltInAgent(agent: Agent | null | undefined): boolean {\n return agent?.meta?.builtin === true || agent?.id === HUMAN_AGENT_ID || agent?.id === 'system'\n}\n\nfunction isChannelAgent(agent: Agent | null | undefined): boolean {\n const tags = agent?.tags || []\n const caps = agent?.capabilities || []\n const policyName = typeof agent?.meta?.policyName === 'string' ? agent.meta.policyName : ''\n return agent?.kind === 'channel' ||\n agent?.meta?.kind === 'channel' ||\n tags.includes('channel') ||\n tags.some((tag) => tag.startsWith('channel:')) ||\n caps.includes('channel') ||\n caps.some((cap) => cap.startsWith('channel:')) ||\n tags.includes('telegram') ||\n caps.includes('telegram') ||\n policyName === 'telegram-main'\n}\n\nfunction isAutomationAgent(agent: Agent | null | undefined): boolean {\n return Boolean(\n typeof agent?.meta?.automationId === 'string' ||\n typeof agent?.meta?.automationRunId === 'string' ||\n agent?.tags?.includes('automation')\n )\n}\n\nfunction isSystemAgent(agent: Agent | null | undefined): boolean {\n return isBuiltInAgent(agent) || isChannelAgent(agent) || isAutomationAgent(agent)\n}\n\nexport function agentType(agent: Agent | null | undefined): AgentType {\n if (agent?.id === HUMAN_AGENT_ID) return 'user'\n if (agent?.id === 'system') return 'system'\n if (isChannelAgent(agent)) return 'channel'\n\n // #417: check the authoritative provider field first (exact match) so a name or id\n // containing \"codex\"/\"claude\" as a substring can't mislabel the agent.\n const provider = typeof agent?.meta?.provider === 'string' ? agent.meta.provider.toLowerCase() : undefined\n if (provider === 'codex') return 'codex'\n if (provider === 'claude') return 'claude'\n\n // Fallback heuristic for agents registered without meta.provider. Only scan tags\n // and meta fields — not id or name, which are free-text and cause false positives.\n const values = [\n ...(agent?.tags || []),\n agent?.meta?.client as string,\n agent?.meta?.runtime as string,\n agent?.meta?.agentType as string,\n ]\n .filter((v): v is string => typeof v === 'string')\n .map((v) => v.toLowerCase())\n\n if (values.some((v) => v.includes('codex'))) return 'codex'\n if (values.some((v) => v.includes('claude'))) return 'claude'\n return 'agent'\n}\n\nfunction agentTypeIcon(agent: Agent | null | undefined): string {\n return AGENT_TYPE_ICONS[agentType(agent)] ?? AGENT_TYPE_ICONS.agent ?? 'ti-robot'\n}\n\nfunction agentTypeTitle(agent: Agent | null | undefined): string {\n return AGENT_TYPE_TITLES[agentType(agent)] ?? AGENT_TYPE_TITLES.agent ?? 'Agent'\n}\n\nexport function isAgentStale(now: number, agent: Agent | null | undefined): boolean {\n if (!agent?.lastSeen || agent.status === 'offline') return false\n if (agent.id === 'user' || agent.id === 'system') return false\n // A live bus connection is authoritative liveness: an idle-but-connected agent\n // is reachable, not stale, even if lastSeen has aged (e.g. heartbeat keepalive\n // gaps while idle). Genuine death flips transport.connected to false (or the\n // server reaps the agent offline), at which point the lastSeen check applies.\n const transport = agent.meta?.transport\n if (isRecord(transport) && transport.connected === true) return false\n const lastSeenMs = new Date(agent.lastSeen as string).getTime()\n if (!Number.isFinite(lastSeenMs)) return false\n return now - lastSeenMs > 60_000\n}\n\nexport type AgentControlAction = 'restart' | 'shutdown' | 'reconnect' | 'compact' | 'clearContext' | 'resume' | 'interrupt'\n\nexport function agentSupportsControlAction(agent: Agent | null | undefined, action: AgentControlAction): boolean {\n if (!agent || isBuiltInAgent(agent) || isChannelAgent(agent)) return false\n if (action === 'resume') return false\n // Interrupt is provider-independent (Claude ESC, Codex turn-interrupt) and only\n // meaningful while the agent is actually mid-turn.\n if (action === 'interrupt') return agent.providerCapabilities?.liveSession?.interrupt === true && agent.status === 'busy'\n const needsClaudeTmux = agentType(agent) === 'claude' && (action === 'restart' || action === 'shutdown' || action === 'compact' || action === 'clearContext')\n if (needsClaudeTmux && !agentHasTmuxSession(agent)) return false\n const lifecycle = agent.providerCapabilities?.lifecycle\n if (lifecycle) {\n if (action === 'restart') return lifecycle.restartHard === true\n if (action === 'shutdown') return lifecycle.shutdownHard === true\n if (action === 'reconnect') return lifecycle.reconnect === true\n }\n if (action === 'compact') return agent.providerCapabilities?.context?.compact === true\n if (action === 'clearContext') return agent.providerCapabilities?.context?.clear === true\n return agent.meta?.runnerManaged === true && (action === 'restart' || action === 'shutdown')\n}\n\nfunction agentHasTmuxSession(agent: Agent): boolean {\n return typeof agent.meta?.tmuxSession === 'string' && agent.meta.tmuxSession.trim().length > 0\n}\n\nfunction agentSupportsControlActions(agent: Agent | null | undefined): boolean {\n return agentSupportsControlAction(agent, 'restart') ||\n agentSupportsControlAction(agent, 'shutdown') ||\n agentSupportsControlAction(agent, 'reconnect') ||\n agentSupportsControlAction(agent, 'compact') ||\n agentSupportsControlAction(agent, 'clearContext')\n}\n\nexport function agentCanBeForgotten(agent: Agent | null | undefined): boolean {\n return Boolean(agent && !isBuiltInAgent(agent) && !isChannelAgent(agent) && !agentSupportsControlActions(agent) && (agent.status === 'offline' || agent.status === 'stale'))\n}\n\nfunction isRetiredManagedRuntimeAgent(agent: Agent, managedPolicies: ManagedPolicyHealth[]): boolean {\n if (agent.status !== 'offline') return false\n const policyName = typeof agent.meta?.policyName === 'string' ? agent.meta.policyName : ''\n if (!policyName) return false\n const policy = managedPolicies.find((item) => item.policy.name === policyName)\n return Boolean(policy?.state.agentId && policy.state.agentId !== agent.id)\n}\n\nexport function visibleAgents(agents: Agent[], showBuiltIns: boolean, managedPolicies: ManagedPolicyHealth[] = []): Agent[] {\n const activeRuntimeAgents = agents.filter((a) => !isRetiredManagedRuntimeAgent(a, managedPolicies))\n return showBuiltIns ? activeRuntimeAgents : activeRuntimeAgents.filter((a) => !isSystemAgent(a))\n}\n\nexport function displayName(agent: Agent | null | undefined): string {\n if (!agent) return '?'\n return agent.label || agent.name || agent.id.slice(-12)\n}\n\n// Human attribution (#390). Resolve a human message address (`user` / `user:<id>`) to its\n// users-table row, or undefined for a non-human address / unknown id. The single bridge from\n// a wire address → a user record on the client, mirroring the SDK resolver on the server.\nexport function resolveUser(address: string | undefined, usersById: Record<string, User>): User | undefined {\n const userId = userIdFromAddress(address)\n return userId ? usersById[userId] : undefined\n}\n\n// Name to show for a human address. Prefers the user's displayName, then handle, then a\n// generic 'User' so a not-yet-loaded directory (or the seeded default) still reads sensibly —\n// which keeps today's single operator rendering exactly as before.\nexport function humanDisplayName(address: string | undefined, usersById: Record<string, User>): string {\n const user = resolveUser(address, usersById)\n return user?.displayName?.trim() || user?.handle?.trim() || 'User'\n}\n\n// Deterministic accent color per human so multiple operators are visually distinguishable in\n// one thread. Stable for a given user id; the single default operator never needs it (callers\n// only apply this once more than one human exists, preserving back-compat styling).\nconst HUMAN_ACCENT_CLASSES = [\n 'text-sky-300', 'text-amber-300', 'text-emerald-300', 'text-violet-300', 'text-rose-300', 'text-cyan-300',\n]\nexport function humanAccentClass(address: string | undefined): string {\n const userId = userIdFromAddress(address) || ''\n let hash = 0\n for (let i = 0; i < userId.length; i++) hash = (hash * 31 + userId.charCodeAt(i)) >>> 0\n return HUMAN_ACCENT_CLASSES[hash % HUMAN_ACCENT_CLASSES.length]!\n}\n\nexport function displayTarget(target: string, agentsById: Record<string, Agent>): string {\n if (!target) return '?'\n if (target === 'broadcast') return 'broadcast'\n if (target.startsWith('tag:')) return '#' + target.slice(4)\n if (target.startsWith('cap:')) return target.slice(4)\n if (target.startsWith('label:')) return target.slice(6)\n if (target.startsWith('team:')) {\n const teamId = target.slice(5)\n const member = Object.values(agentsById).find((a) => a.teamId === teamId)\n return member ? `team:${displayName(member)}` : `team:${teamId.slice(-8)}`\n }\n const agent = agentsById[target]\n return agent ? displayName(agent) : target.slice(-8)\n}\n\nexport function messageBody(msg: Message | null | undefined): string {\n if (!msg) return ''\n const payload = msg.payload || {}\n const channelMessage = payload.message as Record<string, unknown> | undefined\n if (channelMessage && typeof channelMessage === 'object' && typeof channelMessage.text === 'string' && (channelMessage.text as string).trim()) {\n return channelMessage.text as string\n }\n const interaction = payload.interaction as Record<string, unknown> | undefined\n if (interaction && typeof interaction === 'object') {\n const title = typeof interaction.title === 'string' ? interaction.title.trim() : ''\n const description = typeof interaction.description === 'string' ? interaction.description.trim() : ''\n if (title && description) return title + '\\n' + description\n if (title) return title\n if (description) return description\n }\n const reaction = payload.reaction as Record<string, unknown> | undefined\n if (reaction && typeof reaction === 'object') {\n const name = typeof reaction.name === 'string' ? reaction.name : ''\n const emoji = typeof reaction.emoji === 'string' ? reaction.emoji : ''\n const value = typeof reaction.value === 'string' ? reaction.value : ''\n const displayEmoji = normalizeReactionEmoji(emoji || name || value)\n return ['Reaction', displayEmoji].filter(Boolean).join(': ')\n }\n const activity = payload.activity as Record<string, unknown> | undefined\n if (activity && typeof activity === 'object') {\n const kind = typeof activity.kind === 'string' ? activity.kind : 'activity'\n const state = typeof activity.state === 'string' ? activity.state : ''\n return [kind, state].filter(Boolean).join(' ')\n }\n if (typeof payload.text === 'string' && (payload.text as string).trim()) return payload.text as string\n if (typeof payload.message === 'string' && (payload.message as string).trim()) return payload.message as string\n return extractTextFromJson(msg.body || '') ?? (msg.body || '')\n}\n\nexport function isReactionEventMessage(msg: Message | null | undefined): boolean {\n if (!msg?.payload) return false\n const event = msg.payload.event as Record<string, unknown> | undefined\n return msg.payload.reactionNotification === true || event?.type === 'message.reaction'\n}\n\ninterface ParsedJsonBody {\n text: string\n meta: Array<[string, string]>\n raw: Record<string, unknown>\n}\n\nexport function parseJsonBody(body: string): ParsedJsonBody | null {\n if (!body) return null\n const trimmed = body.trim()\n if (!trimmed.startsWith('{')) return null\n try {\n const obj = JSON.parse(trimmed)\n if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) return null\n const text = typeof obj.text === 'string' ? obj.text\n : typeof obj.message === 'string' ? obj.message\n : null\n if (!text?.trim()) return null\n const skipKeys = new Set(['text', 'message'])\n const meta = Object.entries(obj)\n .filter(([k, v]) => !skipKeys.has(k) && (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean'))\n .map(([k, v]) => [k, String(v)] as [string, string])\n return { text, meta, raw: obj }\n } catch {\n return null\n }\n}\n\nfunction extractTextFromJson(body: string): string | null {\n const parsed = parseJsonBody(body)\n return parsed?.text ?? null\n}\n\nexport function messagePreview(msg: Message | null | undefined): string {\n const text = msg?.subject || messageBody(msg) || ''\n return text.length > 90 ? text.slice(0, 90) + '...' : text\n}\n\n// De-singletoned human addressing (#393): a thread peer is the non-human side of any message\n// whose other side is a human address — the legacy bare `'user'` sink OR any `user:<id>` in the\n// namespace (`user:default` included). Today's single operator is still `'user'`, so this is a\n// no-op for the live deployment; per-user inbox scoping lands with auth/sessions (#389).\nexport function inboxPeer(msg: Message): string {\n if (isHumanAgentId(msg.from) && msg.to) return msg.to\n if (isHumanAgentId(msg.to) && msg.from) return msg.from\n return ''\n}\n\nexport function isHumanInboundMessage(msg: Message): boolean {\n return isHumanAgentId(msg.to) && !isHumanAgentId(msg.from)\n}\n\nexport function isSessionActivityStep(msg: Message): boolean {\n if (msg.kind !== 'session') return false\n const type = (msg.payload?.session as { type?: string } | undefined)?.type\n return type === 'reasoning' || type === 'tool'\n}\n\nexport function isBadgeWorthy(msg: Message): boolean {\n return msg.kind === 'session' && (msg.payload?.session as { final?: boolean } | undefined)?.final === true\n}\n\n// True when `block` appears as a whole \\n\\n-delimited segment of a response body —\n// the same boundary extractLastAssistantTurn joins narration on. Matches the entire\n// body (chatCaptureMode \"final\") or any internal block (\"full\") without naive-substring\n// false positives, and is robust to narration blocks that contain their own blank lines.\n// Used to suppress a narration trace step that the closing response bubble already repeats.\nexport function responseBodyContainsBlock(body: string, block: string): boolean {\n const b = body.trim()\n const t = block.trim()\n if (!t) return false\n if (b === t) return true\n return b.startsWith(`${t}\\n\\n`) || b.endsWith(`\\n\\n${t}`) || b.includes(`\\n\\n${t}\\n\\n`)\n}\n\nexport function isUnreadHumanMessage(readCursors: Record<string, number>, peer: string, msg: Message): boolean {\n if (!isHumanInboundMessage(msg)) return false\n if (!isBadgeWorthy(msg)) return false\n if ((msg.readBy || []).includes(HUMAN_AGENT_ID)) return false\n const cursor = Number(readCursors[peer] || 0)\n return msg.id > (Number.isFinite(cursor) ? cursor : 0)\n}\n\nexport function isClaimableTaskWaiting(task: Task): boolean {\n return WAITING_TASK_STATUSES.has(task.status) && !task.claimedBy\n}\n\nexport function isClaimableMessageWaiting(msg: Message): boolean {\n return Boolean(msg.claimable && !msg.claimedBy && !(msg.kind === 'task' && Number.isSafeInteger((msg.payload as Record<string, unknown>)?.taskId)))\n}\n\nexport function dashboardTargetMatchesAgent(target: string, agent: Agent): boolean {\n if (!target || !agent) return false\n if (target === 'broadcast' || target === agent.id) return true\n if (target.startsWith('tag:')) return (agent.tags || []).includes(target.slice(4))\n if (target.startsWith('cap:')) return (agent.capabilities || []).includes(target.slice(4))\n if (target.startsWith('label:')) return agent.label === target.slice(6)\n if (target.startsWith('team:')) return agent.teamId === target.slice(5)\n return false\n}\n\nexport function messageMatchesAgentChat(agentId: string, message: Message): boolean {\n if (!agentId) return false\n if (message.from === agentId || message.to === agentId) return true\n if (message.resolvedToAgent === agentId) return true\n return false\n}\n\nexport function messageMatchesChannel(message: Message, channel: ChannelSummary): boolean {\n if (!message || !channel) return false\n const channelKeys = [channel.id, channel.type, ...(channel.topicChannels || [])].filter(Boolean)\n if (message.channel && channelKeys.includes(message.channel)) return true\n const payloadChannel = message.payload?.channel as Record<string, unknown> | undefined\n if (payloadChannel && typeof payloadChannel === 'object') {\n const keys = [payloadChannel.agentId, payloadChannel.provider, payloadChannel.accountId].filter(Boolean) as string[]\n if (keys.includes(channel.id) || keys.includes(channel.type) || (channel.accountId && keys.includes(channel.accountId))) return true\n }\n if (channel.agentId && (message.from === channel.agentId || message.to === channel.agentId)) return true\n return false\n}\n\ninterface ActiveSubagentInfo {\n id: string\n label: string\n role?: string\n startedAt?: number\n}\n\ninterface ProviderRuntimeState {\n state: string\n reason?: string\n label: string\n recommendedAction?: string\n source?: string\n raw?: unknown\n updatedAt?: number\n pendingApproval?: ProviderPendingApproval\n}\n\ninterface ProviderPendingApprovalChoice {\n id: 'approve' | 'approve-session' | 'deny' | 'abort'\n label: string\n}\n\ninterface ProviderQuestionOption {\n label: string\n description?: string\n}\n\nexport interface ProviderQuestion {\n question: string\n header?: string\n options: ProviderQuestionOption[]\n multiSelect?: boolean\n}\n\nexport interface ProviderPendingApproval {\n id: string\n provider?: string\n kind?: string\n title: string\n body: string\n choices: ProviderPendingApprovalChoice[]\n // Present when kind === 'questions' (Claude AskUserQuestion). The dashboard\n // renders these as a form and submits an 'answer' decision.\n questions?: ProviderQuestion[]\n}\n\nexport function providerRuntimeState(agent: Agent | null | undefined): ProviderRuntimeState | null {\n const raw = agent?.meta?.providerState\n if (!isRecord(raw) || typeof raw.state !== 'string') return null\n return {\n state: raw.state,\n reason: typeof raw.reason === 'string' ? raw.reason : undefined,\n label: typeof raw.label === 'string' && raw.label ? raw.label : raw.state,\n recommendedAction: typeof raw.recommendedAction === 'string' ? raw.recommendedAction : undefined,\n source: typeof raw.source === 'string' ? raw.source : undefined,\n raw: raw.raw,\n updatedAt: typeof raw.updatedAt === 'number' ? raw.updatedAt : undefined,\n pendingApproval: providerPendingApproval(raw.pendingApproval),\n }\n}\n\nfunction providerPendingApproval(value: unknown): ProviderPendingApproval | undefined {\n if (!isRecord(value) || typeof value.id !== 'string') return undefined\n const choices = Array.isArray(value.choices)\n ? value.choices.filter(isRecord).map((choice) => ({\n id: choice.id,\n label: typeof choice.label === 'string' ? choice.label : String(choice.id || ''),\n })).filter((choice): choice is ProviderPendingApprovalChoice =>\n (choice.id === 'approve' || choice.id === 'approve-session' || choice.id === 'deny' || choice.id === 'abort') && Boolean(choice.label)\n )\n : []\n const questions = Array.isArray(value.questions)\n ? value.questions.filter(isRecord).map((q): ProviderQuestion => ({\n question: typeof q.question === 'string' ? q.question : '',\n header: typeof q.header === 'string' ? q.header : undefined,\n multiSelect: q.multiSelect === true,\n options: Array.isArray(q.options)\n ? q.options.filter(isRecord).map((o) => ({\n label: typeof o.label === 'string' ? o.label : String(o.label ?? ''),\n description: typeof o.description === 'string' ? o.description : undefined,\n })).filter((o) => Boolean(o.label))\n : [],\n })).filter((q) => Boolean(q.question) && q.options.length > 0)\n : undefined\n return {\n id: value.id,\n provider: typeof value.provider === 'string' ? value.provider : undefined,\n kind: typeof value.kind === 'string' ? value.kind : undefined,\n title: typeof value.title === 'string' && value.title ? value.title : 'Permission request',\n body: typeof value.body === 'string' ? value.body : '',\n choices,\n ...(questions && questions.length ? { questions } : {}),\n }\n}\n\nexport function providerBlockedState(agent: Agent | null | undefined): ProviderRuntimeState | null {\n const state = providerRuntimeState(agent)\n return state?.state === 'blocked' ? state : null\n}\n\nexport function isPendingApprovalBadgeWorthy(approval: ProviderPendingApproval | null | undefined): boolean { return Boolean(approval?.id) }\n\nexport function agentNeedsHumanResponse(agent: Agent | null | undefined): boolean { return isPendingApprovalBadgeWorthy(providerBlockedState(agent)?.pendingApproval) }\n\nexport function activeSubagents(agent: Agent | null | undefined): ActiveSubagentInfo[] {\n const raw = agent?.meta?.activeSubagents\n if (!Array.isArray(raw)) {\n const count = typeof agent?.meta?.activeSubagentCount === 'number' ? agent.meta.activeSubagentCount : 0\n return count > 0 ? Array.from({ length: count }, (_, index) => ({ id: `subagent-${index + 1}`, label: `Subagent ${index + 1}` })) : []\n }\n return raw\n .filter(isRecord)\n .map((item, index) => {\n const id = typeof item.id === 'string' && item.id ? item.id : `subagent-${index + 1}`\n const role = typeof item.role === 'string' && item.role ? item.role : undefined\n const label = typeof item.label === 'string' && item.label ? item.label : role || id\n const startedAt = typeof item.startedAt === 'number' ? item.startedAt : undefined\n return { id, label, role, startedAt }\n })\n}\n\n// #502: a background shell (run_in_background) still running for this agent keeps\n// it busy even though its turn ended. The runner surfaces it as a `background-script`\n// work item in meta.activeWork carrying a human label (\"N background tasks\"); return\n// that label for a badge, or null when none are running.\nexport function activeBackgroundScriptLabel(agent: Agent | null | undefined): string | null {\n const raw = agent?.meta?.activeWork\n if (!Array.isArray(raw)) return null\n const item = raw.find((work) => isRecord(work) && work.kind === 'background-script')\n if (!item || !isRecord(item)) return null\n return typeof item.label === 'string' && item.label ? item.label : 'background script'\n}\n\nexport function agentPresence(\n now: number, agent: Agent | null | undefined, attention: AttentionInfo, pair: PairSession | null\n): PresenceInfo {\n const lifecycleAction = typeof agent?.meta?.lifecycleAction === 'string' ? agent.meta.lifecycleAction : ''\n const stale = isAgentStale(now, agent)\n const reconnecting = agent?.status !== 'offline' && !agent?.ready && stale\n const starting = agent?.status !== 'offline' && !agent?.ready && !stale\n const unreadIdle = attention.unread > 0 && agent?.status !== 'busy'\n const blocked = providerBlockedState(agent)\n const badges = presenceBadges(agent, attention, pair)\n\n if (agent?.status === 'offline') return { label: 'offline', tone: 'secondary', icon: 'PlugZap', stale: false, reconnecting: false, badges }\n // Pre-session-destroy window (#183): the runner is running end-of-session work before a\n // compact/clear/restart/shutdown. Distinct from plain \"busy\" and non-addressable.\n if (lifecycleAction.startsWith('finalizing-')) {\n return { label: `wrapping up (${lifecycleAction.slice('finalizing-'.length)})`, tone: 'warning', icon: 'Hourglass', stale, reconnecting, badges }\n }\n if (lifecycleAction === 'shutting-down') return { label: 'shutting down', tone: 'warning', icon: 'Power', stale, reconnecting, badges }\n if (lifecycleAction === 'killing') return { label: 'killing', tone: 'danger', icon: 'Power', stale, reconnecting, badges }\n if (lifecycleAction === 'restarting') return { label: 'restarting', tone: 'warning', icon: 'RefreshCw', stale, reconnecting, badges }\n if (agent?.status === 'stale') return { label: 'stale', tone: 'danger', icon: 'RefreshCw', stale: true, reconnecting: true, badges }\n if (reconnecting) return { label: 'reconnecting', tone: 'danger', icon: 'RefreshCw', stale, reconnecting, badges }\n if (starting) return { label: 'online, not ready', tone: 'warning', icon: 'Loader', stale, reconnecting, badges }\n if (blocked) return { label: `blocked: ${blocked.label}`, tone: 'danger', icon: 'AlertCircle', stale, reconnecting, badges }\n if (agent?.status === 'busy') {\n // #502: busy purely because a background shell outlived the turn (no active\n // provider turn / subagent) reads more truthfully as background work.\n const reasons = Array.isArray(agent?.meta?.busyReasons) ? agent.meta.busyReasons : []\n const backgroundOnly = activeBackgroundScriptLabel(agent) && !reasons.includes('provider-turn') && !reasons.includes('subagent')\n if (backgroundOnly) return { label: 'running background work', tone: 'warning', icon: 'Play', stale, reconnecting, badges }\n return { label: 'busy in turn', tone: 'warning', icon: 'Play', stale, reconnecting, badges }\n }\n if (pair?.status === 'active') return { label: 'paired', tone: 'success', icon: 'Link', stale, reconnecting, badges }\n if (unreadIdle) return { label: 'idle, unread', tone: 'danger', icon: 'Bell', stale, reconnecting, badges }\n return { label: agent?.status === 'idle' ? 'idle' : 'ready', tone: 'success', icon: 'CircleCheck', stale, reconnecting, badges }\n}\n\nfunction presenceBadges(agent: Agent | null | undefined, attention: AttentionInfo, pair: PairSession | null): PresenceBadge[] {\n const badges: PresenceBadge[] = []\n const blocked = providerBlockedState(agent)\n const subagentCount = activeSubagents(agent).length\n if (blocked) badges.push({ label: blocked.reason ? `blocked: ${blocked.reason}` : 'blocked', className: 'bg-red-500/20 text-red-400' })\n if (subagentCount) badges.push({ label: subagentCount === 1 ? '1 subagent' : subagentCount + ' subagents', className: 'bg-cyan-500/20 text-cyan-300' })\n const backgroundScriptLabel = activeBackgroundScriptLabel(agent)\n if (backgroundScriptLabel) badges.push({ label: backgroundScriptLabel, className: 'bg-indigo-500/20 text-indigo-300' })\n if (pair?.status === 'active') badges.push({ label: 'paired', className: 'bg-emerald-500/20 text-emerald-400' })\n if (pair?.status === 'pending') badges.push({ label: 'pair invite', className: 'bg-yellow-500/20 text-yellow-400' })\n if (attention.needsHumanResponse) badges.push({ label: 'needs response', className: 'bg-amber-500/20 text-amber-300' })\n if (attention.unread) badges.push({ label: attention.unread + ' unread', className: 'bg-red-500/20 text-red-400' })\n if (attention.claimableTasks) badges.push({ label: attention.claimableTasks + ' claimable', className: 'bg-orange-500/20 text-orange-400' })\n return badges\n}\n\nexport function emptyAttention(): AttentionInfo {\n return { unread: 0, needsHumanResponse: false, pendingPairInvite: false, claimableTasks: 0, total: 0, score: 0 }\n}\n\nexport function channelIsReady(channel: ChannelSummary | null): boolean {\n if (!channel || channel.status === 'offline') return false\n if (channel.targetHealth?.status === 'ok') return true\n if (channel.targetHealth?.status === 'error' || channel.targetHealth?.status === 'warning') return false\n return channel.ready\n}\n\nexport function channelPresence(channel: ChannelSummary | null): PresenceInfo {\n if (!channel) return { label: 'unknown', tone: 'secondary', icon: 'PlugZap', badges: [] }\n if (channel.targetHealth?.status === 'error') return { label: 'target broken', tone: 'danger', icon: 'AlertTriangle', badges: [] }\n if (channel.targetHealth?.status === 'warning') return { label: 'target warning', tone: 'warning', icon: 'AlertCircle', badges: [] }\n if (channel.status === 'offline') return { label: 'offline', tone: 'secondary', icon: 'PlugZap', badges: [] }\n if (channel.status === 'busy') return { label: 'busy', tone: 'warning', icon: 'Activity', badges: [] }\n if (!channelIsReady(channel)) return { label: 'not ready', tone: 'warning', icon: 'Loader', badges: [] }\n return { label: 'ready', tone: 'success', icon: 'CircleCheck', badges: [] }\n}\n\nexport function connectorPresence(connector: ConnectorSummary): PresenceInfo {\n const runtime = connector?.runtime || {}\n if (runtime.status === 'error') return { label: 'error', tone: 'danger', icon: 'AlertTriangle', badges: [] }\n if (runtime.status === 'warn') return { label: 'warning', tone: 'warning', icon: 'AlertCircle', badges: [] }\n if (runtime.running) return { label: 'running', tone: 'success', icon: 'CircleCheck', badges: [] }\n if (runtime.enabled === false) return { label: 'disabled', tone: 'secondary', icon: 'Pause', badges: [] }\n if (runtime.status === 'ok') return { label: 'ok', tone: 'success', icon: 'CircleCheck', badges: [] }\n return { label: 'unknown', tone: 'secondary', icon: 'HelpCircle', badges: [] }\n}\n\nexport function integrationPresence(integration: IntegrationSummary): PresenceInfo {\n const stats = integration?.taskStats || {}\n if ((stats.waitingTasks || 0) > 0) return { label: 'waiting', tone: 'warning', icon: 'AlertCircle', badges: [] }\n if ((stats.openTasks || 0) > 0) return { label: 'active', tone: 'info', icon: 'Activity', badges: [] }\n if (!integration?.configured) return { label: 'observed only', tone: 'secondary', icon: 'Eye', badges: [] }\n if (integration.observed) return { label: 'quiet', tone: 'success', icon: 'CircleCheck', badges: [] }\n return { label: 'configured', tone: 'primary', icon: 'PlugZap', badges: [] }\n}\n\nexport function orchestratorHealthLabel(orch: Orchestrator): string {\n const health = orch?.health\n if (!health || health.status === 'ok') return orch?.version || orch?.package?.version ? 'compatible' : 'unknown version'\n if (health.status === 'upgrade-required') return 'upgrade required'\n if (health.status === 'restart-required') return 'restart required'\n if (health.status === 'unknown') return 'unknown contract'\n if ((health.issues || []).some((i) => i.code === 'protocol-mismatch')) return 'protocol mismatch'\n if ((health.issues || []).some((i) => i.code === 'package-drift')) return 'package drift'\n if (health.restartRequired) return 'restart required'\n return health.status\n}\n\nexport function orchestratorHealthClass(orch: Orchestrator): string {\n const status = orch?.health?.status\n if (status === 'upgrade-required') return 'text-red-400 bg-red-500/10'\n if (status === 'warn' || status === 'restart-required') return 'text-yellow-400 bg-yellow-500/10'\n if (status === 'unknown') return 'text-zinc-400 bg-zinc-500/10'\n return 'text-emerald-400 bg-emerald-500/10'\n}\n\nexport function timeAgo(now: number, iso: string | number | null | undefined): string {\n if (!iso) return ''\n const ts = new Date(iso as string).getTime()\n if (!Number.isFinite(ts)) return ''\n const diff = Math.max(0, (now - ts) / 1000)\n if (diff < 60) return Math.floor(diff) + 's ago'\n if (diff < 3600) return Math.floor(diff / 60) + 'm ago'\n if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'\n return Math.floor(diff / 86400) + 'd ago'\n}\n\nexport function fmtTime(iso: string | number | null | undefined): string {\n if (!iso) return ''\n return new Date(iso as string).toLocaleString()\n}\n\nexport function safeFilename(value: string): string {\n return String(value || 'export').replace(/[^a-z0-9._-]+/gi, '-').replace(/^-+|-+$/g, '').slice(0, 80) || 'export'\n}\n\nexport function healthDiagnostics(checks: HealthCheck[]): HealthDiagnostic[] {\n return checks.filter((c) => c.status !== 'ok').map((check) => {\n const base: HealthDiagnostic = {\n name: check.name,\n status: check.status,\n detail: check.detail || check.name,\n impact: healthImpact(check),\n subjects: check.subjects || [],\n actions: [\n { label: 'Inspect logs', icon: 'FileSearch', copy: 'agent-relay daemon logs' },\n { label: 'Restart daemon', icon: 'RefreshCw', copy: 'agent-relay daemon restart' },\n { label: 'Copy env', icon: 'Copy', copy: 'agent-relay doctor' },\n ],\n }\n if (check.name === 'stale-live-agents') {\n base.actions.unshift(\n { label: 'Run reaper', icon: 'Eraser', api: 'POST', path: '/system/reap' },\n { label: 'Show stale', icon: 'Filter', view: 'agents', preset: 'offline_stale' },\n )\n } else if (check.name === 'channel-delivery-targets') {\n base.actions.unshift(\n { label: 'Open channels', icon: 'MessagesSquare', view: 'channels' },\n { label: 'Run reaper', icon: 'Eraser', api: 'POST', path: '/system/reap' },\n )\n } else if (['expired-message-claims', 'expired-task-claims', 'offline-claimed-tasks'].includes(check.name)) {\n base.actions.unshift(\n { label: 'Run reaper', icon: 'Eraser', api: 'POST', path: '/system/reap' },\n { label: 'Open work', icon: 'ListChecks', view: 'work' },\n )\n }\n return base\n })\n}\n\nfunction healthImpact(check: HealthCheck): string {\n const impacts: Record<string, string> = {\n 'database': 'Relay persistence is unavailable; messages, state, and audit writes may fail.',\n 'stale-live-agents': 'Agents may look online even though their heartbeat has stopped.',\n 'expired-message-claims': 'Claimable messages may be stuck until the reaper releases expired claims.',\n 'expired-task-claims': 'Tasks can appear owned by agents that no longer hold a live lease.',\n 'offline-claimed-tasks': 'Offline agents are still shown as owners for active work.',\n 'channel-delivery-targets': 'Inbound channel messages may be accepted but routed to no live delivery agent.',\n }\n return impacts[check.name] || 'Relay health is degraded for this check.'\n}\n\nfunction toneToBadgeVariant(tone: string): 'default' | 'secondary' | 'destructive' | 'outline' {\n if (tone === 'danger' || tone === 'destructive') return 'destructive'\n if (tone === 'secondary') return 'secondary'\n return 'default'\n}\n\nexport function toneToColor(tone: string): string {\n const map: Record<string, string> = {\n success: 'text-emerald-400',\n warning: 'text-yellow-400',\n danger: 'text-red-400',\n info: 'text-blue-400',\n primary: 'text-blue-400',\n secondary: 'text-zinc-400',\n }\n return map[tone] || 'text-zinc-400'\n}\n\nfunction toneToBg(tone: string): string {\n const map: Record<string, string> = {\n success: 'bg-emerald-500',\n warning: 'bg-yellow-500',\n danger: 'bg-red-500',\n info: 'bg-blue-500',\n primary: 'bg-blue-500',\n secondary: 'bg-zinc-500',\n }\n return map[tone] || 'bg-zinc-500'\n}\n\nexport function statusDotColor(agent: Agent | null, now?: number): string {\n if (!agent) return 'bg-zinc-500 opacity-50'\n if (agent.status === 'offline') return 'bg-zinc-500 opacity-50'\n if (typeof agent.meta?.lifecycleAction === 'string' && agent.meta.lifecycleAction.startsWith('finalizing-')) return 'bg-amber-500 animate-pulse'\n if (agent.meta?.lifecycleAction === 'shutting-down' || agent.meta?.lifecycleAction === 'restarting') return 'bg-yellow-500 animate-pulse'\n if (agent.meta?.lifecycleAction === 'killing') return 'bg-red-500 animate-pulse'\n if (providerBlockedState(agent)) return 'bg-red-500 animate-pulse'\n if (agent.status === 'busy') return 'bg-yellow-500'\n if (now && isAgentStale(now, agent)) return 'bg-red-500 animate-pulse'\n if (!agent.ready) return 'bg-emerald-500 animate-pulse'\n return 'bg-emerald-500 shadow-[0_0_6px_theme(colors.emerald.500)]'\n}\n\nexport function downloadText(filename: string, text: string, type: string) {\n if (typeof document === 'undefined' || typeof URL === 'undefined' || typeof Blob === 'undefined') return\n const url = URL.createObjectURL(new Blob([text], { type }))\n const link = document.createElement('a')\n link.href = url\n link.download = filename\n link.click()\n URL.revokeObjectURL(url)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"display-CWMHll1J.js","names":[],"sources":["../../dashboard/src/lib/constants.ts","../../sdk/src/types/guards.ts","../../sdk/src/types/constants.ts","../../sdk/src/types/workspaces.ts","../../sdk/src/types/tokens.ts","../../sdk/src/types/users.ts","../../dashboard/src/lib/display.ts"],"sourcesContent":["import type { ComposeState, InboxComposeState, PairMessageState, PairInviteState, ViewName } from '@/types'\n\nconst PREF_PREFIX = 'ar-'\nexport const HUMAN_AGENT_ID = 'user'\nexport const INBOX_OPERATOR_ID = HUMAN_AGENT_ID\nexport const LIVE_REFRESH_MS = 5_000\n// Max live messages retained in memory. Must be >= the largest fetch limit\n// (chat view fetches 300) so SSE appends never trim already-loaded history.\nexport const MESSAGE_BUFFER_CAP = 500\n\nexport const CLOSED_TASK_STATUSES = new Set(['done', 'failed', 'canceled'])\nexport const WAITING_TASK_STATUSES = new Set(['open', 'blocked'])\n\nexport const STATUS_SORT_ORDER: Record<string, number> = { online: 0, idle: 0, busy: 2, offline: 3 }\nexport const CHAT_STATUS_SORT_ORDER: Record<string, number> = { idle: 0, online: 0, busy: 2, offline: 4 }\n\nexport const DEFAULT_COMPOSE: ComposeState = { from: '', to: '', body: '', channel: '', subject: '', claimable: false }\nexport const DEFAULT_INBOX_COMPOSE: InboxComposeState = { toMode: 'agent', to: '', body: '', channel: '', subject: '', claimable: false }\nexport const DEFAULT_PAIR_MESSAGE: PairMessageState = { pairId: '', from: '', body: '', subject: '' }\nexport const DEFAULT_PAIR_INVITE: PairInviteState = { requesterId: '', targetId: '', objective: '' }\n\n\nexport const AGENT_TYPE_ICONS: Record<string, string> = {\n codex: 'Terminal',\n claude: 'ClaudeLogo',\n user: 'User',\n system: 'Server',\n channel: 'MessagesSquare',\n agent: 'Bot',\n}\n\nexport const AGENT_TYPE_TITLES: Record<string, string> = {\n codex: 'Codex agent',\n claude: 'Claude agent',\n user: 'Human operator',\n system: 'System',\n channel: 'Channel',\n agent: 'Agent',\n}\n\nexport const NAV_ITEMS: Array<{ key: ViewName; label: string; icon: string }> = [\n { key: 'overview', label: 'Overview', icon: 'LayoutDashboard' },\n { key: 'chat', label: 'Chat', icon: 'MessageCircle' },\n { key: 'agents', label: 'Agents', icon: 'Bot' },\n { key: 'managed', label: 'Managed Agents', icon: 'Workflow' },\n { key: 'teams', label: 'Teams', icon: 'Users' },\n { key: 'profiles', label: 'Agent Profiles', icon: 'UserCog' },\n { key: 'orchestrators', label: 'Orchestrators', icon: 'Server' },\n { key: 'workspaces', label: 'Workspaces', icon: 'GitBranch' },\n { key: 'files', label: 'Files', icon: 'FolderTree' },\n { key: 'channels', label: 'Channels', icon: 'MessagesSquare' },\n { key: 'connectors', label: 'Connectors', icon: 'Plug' },\n { key: 'integrations', label: 'Integrations', icon: 'PlugZap' },\n { key: 'security', label: 'Security', icon: 'Shield' },\n { key: 'memory', label: 'Memory', icon: 'BrainCircuit' },\n { key: 'activity', label: 'Activity', icon: 'Activity' },\n { key: 'pairs', label: 'Pairs', icon: 'Link' },\n { key: 'messages', label: 'Messages', icon: 'Mail' },\n { key: 'work', label: 'Work Queue', icon: 'ListChecks' },\n { key: 'tasks', label: 'Tasks', icon: 'ClipboardList' },\n { key: 'automation', label: 'Automation', icon: 'CalendarClock' },\n { key: 'analytics', label: 'Analytics', icon: 'AreaChart' },\n { key: 'insights', label: 'Insights', icon: 'Lightbulb' },\n { key: 'projects', label: 'Projects', icon: 'Boxes' },\n { key: 'maintenance', label: 'Maintenance', icon: 'Wrench' },\n { key: 'settings', label: 'Settings', icon: 'Settings' },\n]\n\nexport const SEVERITY_COLORS: Record<string, string> = {\n critical: 'text-red-400 bg-red-500/10',\n warning: 'text-yellow-400 bg-yellow-500/10',\n info: 'text-blue-400 bg-blue-500/10',\n}\n\nexport const PAIR_STATUS_COLORS: Record<string, string> = {\n active: 'bg-emerald-500/20 text-emerald-400',\n pending: 'bg-yellow-500/20 text-yellow-400',\n ended: 'bg-zinc-500/20 text-zinc-400',\n rejected: 'bg-red-500/20 text-red-400',\n expired: 'bg-red-500/20 text-red-400',\n}\n","// --- Shared micro-helpers (single source of truth; do not re-declare per file) ---\n\n/** True for a non-null, non-array object. The canonical type guard for the whole repo. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Narrow `unknown` to a non-empty trimmed string, else `undefined`.\n * Settled semantics: whitespace-only is treated as empty (returns `undefined`).\n */\nexport function stringValue(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\n/** Extract a human-readable message from any thrown value. */\nexport function errMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n","// --- Relay connection defaults ---\n\n/** Default port the relay server listens on. */\nconst DEFAULT_RELAY_PORT = 4850;\n\n/** Default relay base URL. Loopback spelling settled on `127.0.0.1` (not `localhost`). */\nexport const DEFAULT_RELAY_URL = `http://127.0.0.1:${DEFAULT_RELAY_PORT}`;\n","/** Workspace modes — runtime tuple + derived type + guard. */\nexport const VALID_WORKSPACE_MODES = [\"isolated\", \"shared\", \"inherit\"] as const;\nexport type WorkspaceMode = (typeof VALID_WORKSPACE_MODES)[number];\nexport function isWorkspaceMode(value: unknown): value is WorkspaceMode {\n return typeof value === \"string\" && (VALID_WORKSPACE_MODES as readonly string[]).includes(value);\n}\n/** Narrow an unknown to a WorkspaceMode, or undefined if it isn't one. */\nexport function normalizeWorkspaceMode(value: unknown): WorkspaceMode | undefined {\n return isWorkspaceMode(value) ? value : undefined;\n}\nexport type WorkspaceStatus = \"active\" | \"ready\" | \"conflict\" | \"review_requested\" | \"merge_planned\" | \"merged\" | \"abandoned\" | \"cleanup_requested\" | \"cleaned\";\n/** Terminal workspace statuses shared across server + dashboard filters. */\nexport const TERMINAL_WORKSPACE_STATUS_VALUES = [\"cleaned\", \"merged\", \"abandoned\"] as const satisfies readonly WorkspaceStatus[];\n\n/** How a workspace's work is integrated back into its base branch. */\nexport type WorkspaceMergeStrategy = \"pr\" | \"rebase-ff\" | \"auto\";\n\n/** Policy words accepted as the user-facing landing strategy (normalised to WorkspaceMergeStrategy). */\nexport type WorkspaceLandingPolicy = \"direct\" | \"pr\";\n\n/**\n * When to auto-merge a PR that was opened by a `pr`-strategy land.\n * `on-green` — merge once all required status checks pass (default for pr lands).\n * `on-approval` — merge once a reviewer has approved.\n * `manual` — open the PR but never auto-merge; the human hits the button.\n */\nexport const AUTO_MERGE_POLICIES = [\"on-green\", \"on-approval\", \"manual\"] as const;\nexport type WorkspaceAutoMergePolicy = (typeof AUTO_MERGE_POLICIES)[number];\n\n/** Instance-wide landing configuration (stored in config-store under namespace \"landing\"). */\nexport interface LandingConfig {\n /** Default strategy for all workspace lands. \"direct\" → \"auto\" (host picks rebase-ff/pr); \"pr\" → always open a PR. */\n strategy: WorkspaceLandingPolicy;\n}\n\nexport interface WorkspaceProbeWorktree {\n path: string;\n branch?: string;\n headSha?: string;\n bare?: boolean;\n detached?: boolean;\n prunable?: boolean;\n locked?: boolean;\n reason?: string;\n}\n\nexport interface WorkspaceProbe {\n path: string;\n isGitRepo: boolean;\n repoRoot?: string;\n branch?: string;\n headSha?: string;\n dirty?: boolean;\n detached?: boolean;\n worktrees?: WorkspaceProbeWorktree[];\n error?: string;\n}\n\ninterface WorkspaceGitCommit {\n sha: string;\n message: string;\n at?: number;\n}\n\n/**\n * Live git state of a workspace worktree, computed on the host by the\n * orchestrator. Used to surface whether a worktree contains actual work\n * (commits ahead of base, dirty files) so the dashboard isn't a black box and\n * so exit-time reconcile can decide cleanup-vs-flag.\n */\nexport interface WorkspaceGitState {\n /** Commits on the agent branch not yet in base (raw `rev-list` count). */\n ahead?: number;\n /** Commits still genuinely unmerged after discounting work already present in\n * base via a squash/cherry-pick (git-cherry '+' count). Equals `ahead` for a\n * normal branch; 0 when the work landed but left `ahead` positive. */\n unmergedAhead?: number;\n /** True when `ahead` > 0 but the work is already in base (squash/cherry-pick\n * merge) — the worktree is effectively merged and safe to clean up. */\n landed?: boolean;\n /** Commits in base not in the agent branch (how far base moved on). */\n behind?: number;\n /** True if the worktree has uncommitted/untracked changes. */\n dirty?: boolean;\n /** Number of dirty (porcelain) entries. */\n dirtyCount?: number;\n /** Most recent commit in the worktree. */\n lastCommit?: WorkspaceGitCommit;\n /** Ref/sha the ahead/behind counts were computed against. */\n baseRef?: string;\n /** Branch currently checked out in the worktree (for recorded-vs-live mismatch). */\n branch?: string;\n /** Set when the worktree path no longer exists on disk. */\n missing?: boolean;\n /** Populated when git interrogation failed. */\n error?: string;\n}\n\n/**\n * Pre-flight check for integrating a workspace's work, computed on the host.\n * Reports whether the merge would be clean, conflict, or is a no-op, and which\n * strategy `auto` would pick — so the dashboard can warn before the user acts.\n */\nexport interface WorkspaceMergePreview {\n /** Strategy `auto` would resolve to given the repo's remote/gh state. */\n strategy: \"pr\" | \"rebase-ff\";\n /** Commits on the agent branch not yet in base (raw count). */\n ahead?: number;\n /** Genuinely-unmerged commits after discounting squash/cherry-pick landings. */\n unmergedAhead?: number;\n /** True when the work already landed in base via squash/cherry-pick. */\n landed?: boolean;\n /** Merged-PR state (ground truth when local git can't see a squash landing because\n * base moved on). Populated only with `checkPr` (a `gh` round-trip). */\n prState?: \"merged\" | \"open\";\n /** True when the branch's PR is merged on the remote. `prMergeSha` is its merge commit,\n * recorded as the landed SHA when the relay finalizes an out-of-band PR merge (#304). */\n prMerged?: boolean;\n prMergeSha?: string;\n /** Subject (PR title) of the merged PR, carried into the `branch.landed` notice so it\n * reads with the landed change's title, not just a bare SHA (#423). */\n prMergeSubject?: string;\n /** URL of the branch's PR, when one was found via `gh`. */\n prUrl?: string; prNumber?: number; reviewDecision?: \"APPROVED\" | \"CHANGES_REQUESTED\" | \"REVIEW_REQUIRED\"; statusCheckRollup?: unknown[]; mergeable?: string; mergeStateStatus?: string;\n /** Commits in base not in the agent branch. */\n behind?: number;\n /** Uncommitted/untracked entries in the worktree. */\n dirtyCount?: number;\n /** True if the three-way merge of base and the branch would conflict. */\n conflict?: boolean;\n /** True if base can fast-forward to the branch with no rebase needed. */\n cleanFastForward?: boolean;\n /** Whether an `origin` remote is configured. */\n hasRemote?: boolean;\n /** Whether the `gh` CLI is available on the host. */\n ghAvailable?: boolean;\n /** Resolved base branch name the work would land on. */\n baseRef?: string;\n /** Human-readable reason a merge can't proceed (no work, dirty, missing). */\n reason?: string;\n /** True when there is nothing to land: ahead=0 with a clean worktree (branch tree\n * already in base — never diverged, or already landed via squash/cherry-pick/PR).\n * A no-op land resolves the workspace to a terminal state instead of parking it in\n * the steward queue (#230). Distinct from a dirty-worktree refusal, which also sets\n * `reason` but is not a no-op. */\n noop?: boolean;\n /** Set when the worktree path no longer exists. */\n missing?: boolean;\n /** Populated when git interrogation failed. */\n error?: string;\n}\n\n/** Outcome of a `workspace.merge` command executed on the host. */\nexport interface WorkspaceMergeResult {\n workspaceId?: string;\n /** Strategy actually used. */\n strategy: \"pr\" | \"rebase-ff\";\n /** True when work was landed locally (rebase-ff into base). */\n merged: boolean;\n /** True when the land was a no-op — nothing to merge (ahead=0, clean worktree),\n * resolved to a terminal `merged` status to clear the steward queue (#230). The\n * worktree/branch are reclaimed too when the owner is gone (`deleteBranch`). */\n noop?: boolean;\n /** True when a merge was prevented by conflicts. */\n conflict?: boolean;\n /** PR URL when the pr strategy opened one. */\n prUrl?: string;\n branch?: string;\n baseRef?: string;\n /** SHA of the landed commit — the agent branch's tip, preserved verbatim (#287).\n * This is the SHA reported in the `branch.landed` notice, so it exists on base\n * exactly as the agent produced it (no rebase rewrite). On a clean fast-forward\n * it equals {@link baseSha}; on a no-ff merge it's the merge's second parent. */\n mergedSha?: string;\n /** SHA base points at after the land (#287): equals {@link mergedSha} on a\n * fast-forward, or the merge commit on a no-ff merge when base had advanced. The\n * relay records this as the recycled workspace's new base_sha. Absent on older\n * orchestrators — consumers fall back to {@link mergedSha}. */\n baseSha?: string;\n /** Subject line of the landed commit (git log -1 --format=%s of mergedSha), for\n * the `branch.landed` notification body (#239). Best-effort; absent on older\n * orchestrators or when the subject can't be read. */\n subject?: string;\n branchDeleted?: boolean;\n worktreeRemoved?: boolean;\n /** Fresh branch the worktree was recycled onto after a land-and-continue merge\n * (#206) — the relay repoints the workspace row at it. Absent for terminal lands. */\n newBranch?: string;\n /** True when the landed base branch was pushed to its upstream (origin). */\n pushed?: boolean;\n /** Resulting workspace status the relay should record. */\n status: WorkspaceStatus;\n /** Deps re-provisioned after a land-and-continue recycle when the advanced base\n * brought new dependencies (issue #51). Absent when nothing was stale. */\n depsRefresh?: WorkspaceDepsRefreshResult;\n /** True when GitHub auto-merge was successfully armed after PR creation (#305).\n * False when arming was attempted but the repo has auto-merge disabled. */\n autoMergeArmed?: boolean;\n /** True when the PR was opened but auto-merge was NOT armed because the\n * autoMerge policy is \"on-approval\" — a reviewer pipeline arms it later (#305). */\n awaitingApproval?: boolean;\n /** Populated when the merge could not complete. */\n error?: string;\n}\n\n/**\n * Joined steward briefing for one workspace (#208): the row, owner + orchestrator\n * liveness, live git state, recorded-vs-live branch mismatch, any active steward\n * claim, and a recommended next action — so a steward (or release agent) doesn't\n * reconstruct it from ad-hoc shell + API calls.\n */\nexport interface WorkspaceDiagnostics {\n workspaceId: string;\n status: WorkspaceStatus;\n mode: WorkspaceMode;\n repoRoot: string;\n worktreePath?: string;\n recordedBranch?: string;\n liveBranch?: string;\n baseRef?: string;\n branchMismatch?: boolean;\n owner?: { id?: string; status?: string; online: boolean };\n /**\n * The branch agent's coordinator — the owner's `spawnedBy` lineage resolved to a live\n * agent (#640). `id` is null when no coordinator resolves (owner unknown or not spawned\n * by anyone); steward escalation targets it before the configured fallback.\n */\n coordinator?: { id: string | null; online: boolean };\n orchestrator?: { id?: string; online: boolean };\n /** Active claim that auto-merge yields to (#208), if held and unexpired. */\n claim?: { by?: string; purpose?: string; expiresAt?: number };\n /** Live worktree git state (proxied from the host); absent when unavailable. */\n gitState?: WorkspaceGitState;\n /** Why git state is absent (host offline, no worktree, …). */\n gitStateUnavailable?: string;\n recommendation: { action: \"merge\" | \"rebase\" | \"cleanup\" | \"review\" | \"wait\" | \"none\"; confidence: \"high\" | \"medium\" | \"low\"; reason: string };\n}\n\n/** One changed file in a workspace diff against its base. */\nexport interface WorkspaceDiffFile {\n path: string;\n /** Lines added (undefined for binary files). */\n additions?: number;\n /** Lines removed (undefined for binary files). */\n deletions?: number;\n binary?: boolean;\n}\n\n/** Diff of a workspace's committed work against its base, computed on the host. */\nexport interface WorkspaceDiff {\n baseRef?: string;\n ahead?: number;\n files: WorkspaceDiffFile[];\n /** Unified patch text (may be truncated). */\n patch?: string;\n /** True when the patch was capped at the size limit. */\n truncated?: boolean;\n /** Uncommitted entries in the worktree, not part of the committed diff. */\n dirtyCount?: number;\n missing?: boolean;\n error?: string;\n}\n\n/** A worktree found on disk with no matching active workspace row. */\nexport interface WorkspaceOrphan {\n worktreePath: string;\n repoRoot: string;\n branch?: string;\n /** Base ref used to prove the orphan branch is landed before branch-ref cleanup. */\n baseRef?: string;\n /** Base SHA recorded for the workspace when available. */\n baseSha?: string;\n headSha?: string;\n /** True if a DB row exists for this path but is already terminal (cleaned/merged). */\n hadTerminalRow?: boolean;\n /** Work already merged into base (squash/cherry/PR). Set from the host probe. */\n landed?: boolean;\n /** Raw commits ahead of base (a squash-landed branch still shows >0). */\n ahead?: number;\n /** Commits ahead whose patch isn't already in base — the squash-aware count. */\n unmergedAhead?: number;\n /** Uncommitted working-tree changes in the worktree. */\n dirty?: boolean;\n /**\n * Safe to remove with no loss: clean tree AND (nothing ahead OR already landed).\n * `false` means the worktree holds un-landed work and must be flagged, not reaped.\n * `undefined` means the host couldn't be probed — treat as not safe.\n */\n safeToReap?: boolean;\n}\n\n/** Host-local view of an agent/* branch that may need recovery review. */\nexport interface WorkspaceRecoveryBranch {\n repoRoot: string;\n branch: string;\n headSha?: string;\n baseRef?: string;\n baseSha?: string;\n ahead?: number;\n unmergedAhead?: number;\n landed?: boolean;\n lastCommit?: WorkspaceGitCommit;\n ageMs?: number;\n /** True when the branch has no unique unlanded work relative to base. */\n safeToDelete?: boolean;\n /** Why safety could not be proven or why work must be reviewed first. */\n preserveReason?: string;\n /** Server-side enrichment when a workspace row still records this branch. */\n workspaceId?: string;\n ownerAgentId?: string;\n ownerStatus?: string;\n cleanupCommandId?: string;\n cleanupPreservedReason?: string;\n preservedByCleanup?: boolean;\n activeWorkspace?: boolean;\n}\n\nexport interface WorkspaceRecoveryBranchList {\n repoRoot?: string;\n branches: WorkspaceRecoveryBranch[];\n scannedRepos?: string[];\n reason?: string;\n}\n\nexport interface WorkspaceRecoveryBranchDiscardResult {\n repoRoot: string;\n branch: string;\n branchDeleted: boolean;\n forced?: boolean;\n preserveReason?: string;\n headSha?: string;\n}\n\n/**\n * Outcome of provisioning node_modules into a freshly created isolated worktree.\n * Git worktrees do not share gitignored/untracked files, so a new worktree starts\n * with no installed deps — typecheck/test/build would see thousands of phantom\n * missing-module errors. We either symlink the source checkout's node_modules\n * (fast, default) or run a package install (when the source has none to borrow).\n */\nexport interface WorkspaceDepsProvision {\n mode: \"symlink\" | \"install\" | \"none\";\n /** Relative dirs (from repo root) whose node_modules were symlinked. */\n linked?: string[];\n /** Relative dirs where a package install ran successfully. */\n installed?: string[];\n packageManager?: string;\n error?: string;\n}\n\n/** Result of symlinking configured untracked files/dirs from main into an isolated worktree. */\nexport interface WorkspaceSymlinkProvision {\n /** Relative paths (from repo root) that were symlinked from the main checkout. */\n linked: string[];\n /** Per-pattern failures, if any (best-effort: a failure never blocks the spawn). */\n errors?: string[];\n}\n\n/** One project dir's outcome in a workspace deps refresh (issue #51). */\nexport interface WorkspaceDepsRefreshDir {\n /** Relative dir from repo root (`.` for root, e.g. `dashboard`). */\n dir: string;\n /** ok — declared deps already present; stale — missing (check-only, not installed);\n * installed — re-installed in isolation; failed — install errored. */\n status: \"ok\" | \"stale\" | \"installed\" | \"failed\";\n /** Declared deps (dependencies + devDependencies) missing from node_modules, capped sample. */\n missing?: string[];\n /** True when the dir's node_modules was a shared symlink replaced with a real install. */\n wasSymlink?: boolean;\n packageManager?: string;\n error?: string;\n}\n\n/**\n * Outcome of refreshing an isolated worktree's deps (issue #51). The default symlink\n * provisioning shares the source checkout's node_modules, so a dep added to the base\n * AFTER worktree creation is missing in the worktree. Refresh promotes each stale dir\n * to a real, isolated install — never mutating the shared source node_modules.\n */\nexport interface WorkspaceDepsRefreshResult {\n /** True when at least one dir was actually (re)installed. */\n refreshed: boolean;\n /** True (check-only) when any dir is stale, or (refresh) when any install left deps missing. */\n stale?: boolean;\n dirs: WorkspaceDepsRefreshDir[];\n error?: string;\n}\n\nexport interface WorkspaceMetadata {\n id?: string;\n mode: WorkspaceMode;\n requestedMode?: WorkspaceMode;\n repoRoot?: string;\n sourceCwd?: string;\n worktreePath?: string;\n branch?: string;\n baseRef?: string;\n baseSha?: string;\n status?: WorkspaceStatus;\n stewardAgentId?: string;\n /** Per-spawn override for autonomous workspace landing. Omit to use instance landing.strategy. */\n landStrategy?: WorkspaceLandingPolicy;\n /** PR auto-merge policy used when landStrategy resolves to `pr`. */\n autoMerge?: WorkspaceAutoMergePolicy;\n /** Optional reviewer handle or policy target for reviewer-gated PR lands. */\n reviewer?: string;\n deps?: WorkspaceDepsProvision;\n /** Configured untracked files/dirs symlinked from main (see WorkspaceConfig.symlinkPaths). */\n symlinks?: WorkspaceSymlinkProvision;\n probe?: WorkspaceProbe;\n}\n\nexport interface WorkspaceRecord {\n id: string;\n repoRoot: string;\n sourceCwd: string;\n worktreePath: string;\n branch?: string;\n baseRef?: string;\n baseSha?: string;\n mode: WorkspaceMode;\n requestedMode?: WorkspaceMode;\n status: WorkspaceStatus;\n ownerAgentId?: string;\n /** Server-derived from ownerAgentId via the authoritative owner liveness helper. */\n ownerOnline?: boolean;\n ownerPolicyName?: string;\n ownerAutomationRunId?: string;\n stewardAgentId?: string;\n metadata: Record<string, unknown>;\n createdAt: number;\n updatedAt: number;\n readyAt?: number;\n cleanedAt?: number;\n}\n","// ──────────────────────────────────────────────────────────────────────────\n// Settings descriptors (epic #380, #382) — the self-describing contract any\n// surface (core relay, provider, channel, connector) registers so the API\n// (#383), scopes (#385), module wiring (#386), and UI (#384) all build on one\n// abstraction. This is the *declaration* layer over the existing `config`\n// table — NOT a storage change. The registry home is `src/settings/registry.ts`.\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * A typed JSON Schema (draft-07-compatible). Common keywords are spelled out so\n * descriptors get type-checked field shapes; the `[key: string]: unknown` tail\n * keeps the door open for any keyword `ajv` understands (the registry compiles\n * each schema with `ajv`, so anything ajv accepts is valid). Declared here (not\n * as `Record<string, unknown>`) so the descriptor's `schema` field is typed —\n * the sdk stays dependency-free; only the server imports `ajv` to compile it.\n */\nexport type JSONSchemaTypeName =\n | \"string\"\n | \"number\"\n | \"integer\"\n | \"boolean\"\n | \"object\"\n | \"array\"\n | \"null\";\n\nexport interface JSONSchema {\n $schema?: string;\n $id?: string;\n $ref?: string;\n $defs?: Record<string, JSONSchema>;\n definitions?: Record<string, JSONSchema>;\n title?: string;\n description?: string;\n type?: JSONSchemaTypeName | JSONSchemaTypeName[];\n enum?: unknown[];\n const?: unknown;\n default?: unknown;\n examples?: unknown[];\n // numeric\n minimum?: number;\n maximum?: number;\n exclusiveMinimum?: number;\n exclusiveMaximum?: number;\n multipleOf?: number;\n // string\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n format?: string;\n // array\n items?: JSONSchema | JSONSchema[];\n minItems?: number;\n maxItems?: number;\n uniqueItems?: boolean;\n // object\n properties?: Record<string, JSONSchema>;\n required?: string[];\n additionalProperties?: boolean | JSONSchema;\n patternProperties?: Record<string, JSONSchema>;\n // composition\n allOf?: JSONSchema[];\n anyOf?: JSONSchema[];\n oneOf?: JSONSchema[];\n not?: JSONSchema;\n // escape hatch for any other JSON Schema keyword\n [key: string]: unknown;\n}\n\n/**\n * Where a setting lives and how it can be changed:\n * - `server-runtime` — instance-global; stored in the `config` table, mutable at runtime via the API.\n * - `bootstrap` — sourced from env/file at boot; surfaced read-only (the API exposes it but cannot write it).\n * - `browser` — a client-only preference; the server never persists it (UI/local-storage owns it).\n * - `user` — per-human preference (#391); stored in `user_settings` keyed by the resolved `users.id`,\n * mutable at runtime. The same `(namespace, key)` descriptor address resolves to a per-user value,\n * so a pref follows the human across devices. Defaults to the seeded `user:default` (#393) until\n * sessions (#389) map a request to its user.\n */\nexport type SettingTier = \"server-runtime\" | \"bootstrap\" | \"browser\" | \"user\";\n\n/**\n * The scopes (#385) a caller must hold to read or write a setting. Carried as\n * opaque scope-token strings — #385 owns the grammar and `hasScope` matching;\n * this layer only declares the requirement. Registration defaults both to the\n * fail-closed admin scope when a descriptor omits them.\n */\nexport interface SettingScope {\n read: string;\n write: string;\n}\n\n/** Display metadata for the Settings UI (#384) — grouping, labelling, ordering. */\nexport interface SettingDisplay {\n /** Grouping bucket the UI renders this setting under (e.g. \"Agents\", \"Notifications\"). */\n group: string;\n /** Human-readable label. */\n label: string;\n /** Optional longer description / help text. */\n description?: string;\n /** Sort order within the group (ascending; unset sorts last). */\n order?: number;\n}\n\n/**\n * A self-describing setting. Any surface registers one of these; the registry is\n * the single source of truth for which namespaces exist, their schema, defaults,\n * required scope, tier, and display. `scope` is always populated on a registered\n * descriptor (the registry fills the fail-closed admin default) so consumers\n * never null-check it. `namespace`/`key` map directly onto the `config` table's\n * composite PK.\n */\nexport interface SettingDescriptor<T = unknown> {\n /** Maps onto the `config` table namespace (e.g. \"steward\", \"landing\"). */\n namespace: string;\n /** Maps onto the `config` table key within the namespace (e.g. \"default\"). */\n key: string;\n /** Full JSON Schema for the value — ajv-compiled at registration (#382/#383). */\n schema: JSONSchema;\n /** Fallback value used when the config row is unset. */\n defaults: T;\n /** Read/write scopes required to access this setting (#385). */\n scope: SettingScope;\n /** Mutability/source tier. */\n tier: SettingTier;\n /** Display metadata for the Settings UI (#384). */\n display: SettingDisplay;\n}\n\n/** Runtime settings API entry. `version: 0` means descriptor defaults only; no config row is stored. */\nexport interface SettingEntry<T = unknown> extends SettingDescriptor<T> {\n value: T;\n version: number;\n updatedAt: string;\n updatedBy?: string;\n readable: boolean;\n writable: boolean;\n}\n\nconst SCOPE_ALIASES: Record<string, string> = {\n \"admin:*\": \"system:admin\",\n \"system:write\": \"system:admin\",\n \"agents:read\": \"agent:read\",\n \"agents:write\": \"agent:write\",\n \"messages:read\": \"message:read\",\n \"messages:write\": \"message:send\",\n \"tasks:read\": \"task:read\",\n \"tasks:write\": \"task:write\",\n \"commands:read\": \"command:read\",\n \"commands:write\": \"command:write\",\n \"channels:read\": \"channel:read\",\n \"channels:write\": \"channel:write\",\n \"integrations:read\": \"integration:read\",\n \"integrations:write\": \"integration:write\",\n \"connectors:read\": \"integration:read\",\n \"connectors:write\": \"integration:write\",\n \"events:create\": \"integration:write\",\n \"tasks:create\": \"task:write\",\n};\n\nexport function normalizeScope(scope: string): string {\n return SCOPE_ALIASES[scope] ?? scope;\n}\n\nexport function hasScope(scopes: readonly string[] | undefined, requiredScope: string): boolean {\n const required = normalizeScope(requiredScope);\n const requiredParts = parseScopeParts(required);\n if (!requiredParts) return false;\n if (scopes?.includes(\"*\")) return true;\n for (const rawScope of scopes ?? []) {\n const scope = normalizeScope(rawScope);\n const scopeParts = parseScopeParts(scope);\n if (!scopeParts) continue;\n if (scope === \"system:admin\") return true;\n if (scope === required) return true;\n if (scope === `${requiredParts.category}:*`) return true;\n if (\n requiredParts.category === \"settings\" &&\n requiredParts.action &&\n requiredParts.namespace &&\n scopeParts.category === \"settings\" &&\n scopeParts.action === requiredParts.action &&\n scopeParts.namespace === undefined\n ) return true;\n }\n return false;\n}\n\nfunction parseScopeParts(scope: string): { category: string; action?: string; namespace?: string } | null {\n if (scope === \"*\") return { category: \"*\" };\n const parts = scope.split(\":\");\n if (parts.some((part) => !part)) return null;\n if (parts.length === 2) {\n const [category, action] = parts as [string, string];\n return { category, action };\n }\n if (parts.length === 3) {\n const [category, action, namespace] = parts as [string, string, string];\n if (category !== \"settings\") return null;\n if (action !== \"read\" && action !== \"write\") return null;\n if (namespace === \"*\") return null;\n return { category, action, namespace };\n }\n return null;\n}\n\nexport type PairStatus = \"pending\" | \"active\" | \"ended\" | \"rejected\" | \"expired\";\n\nexport interface PairSession {\n id: string;\n requesterId: string;\n targetId: string;\n status: PairStatus;\n objective?: string;\n createdAt: number;\n updatedAt: number;\n expiresAt: number;\n acceptedAt?: number;\n endedAt?: number;\n endedBy?: string;\n lastMessageAt?: number;\n meta: Record<string, unknown>;\n}\n\nexport interface CreatePairInput {\n from: string;\n target: string;\n objective?: string;\n ttlMs?: number;\n meta?: Record<string, unknown>;\n}\n\nexport interface PairActionInput {\n agentId: string;\n reason?: string;\n}\n\nexport interface PairMessageInput {\n from: string;\n body: string;\n subject?: string;\n}\n\nexport interface PollQuery {\n for: string; // agent-id\n since?: number; // unix ms (createdAt cursor)\n sinceId?: number; // monotonic message id cursor (preferred — avoids same-ms collisions)\n unread?: boolean;\n channel?: string;\n limit?: number;\n}\n\nexport interface ComponentToken {\n sub: string;\n role: \"provider\" | \"channel\" | \"orchestrator\" | \"admin\" | \"dashboard\" | string;\n scope: string[];\n constraints?: TokenConstraints;\n iat: number;\n exp?: number;\n jti?: string;\n}\n\nexport interface TokenRecord {\n jti: string;\n sub: string;\n role: string;\n scope: string[];\n constraints?: TokenConstraints;\n profileId?: string;\n issuedAt: number;\n expiresAt?: number;\n revokedAt?: number;\n /** Why the token was revoked (#484): \"stale\" | \"rotation\" | \"admin\" | \"agent-removed\" | \"pruned\". Drives self-heal eligibility. */\n revokedReason?: string;\n createdBy?: string;\n}\n\nexport interface TokenProfile {\n id: string;\n name: string;\n description?: string;\n role: string;\n scope: string[];\n constraints?: TokenConstraints;\n ttlSeconds?: number;\n builtIn: boolean;\n createdAt: number;\n updatedAt: number;\n createdBy?: string;\n}\n\nexport interface CreateTokenProfileInput {\n id?: string;\n name: string;\n description?: string;\n role: string;\n scope: string[];\n constraints?: TokenConstraints;\n ttlSeconds?: number;\n createdBy?: string;\n}\n\nexport type UpdateTokenProfileInput = Partial<Omit<CreateTokenProfileInput, \"id\">>;\n\nexport interface TokenConstraints {\n agents?: string[];\n policies?: string[];\n parentAgents?: string[];\n /** Parent agent id stamped on a child's runner token at spawn; read at registration to\n * set the child agent card's `spawnedBy` authoritatively (the child can't forge it). */\n spawnedBy?: string;\n /** Durable team id stamped on runner tokens; identity-only grouping, never an authorization gate. */\n teamId?: string;\n /** Durable project id resolved from the spawn cwd (containment match against project_roots)\n * and stamped on runner tokens; identity-only grouping (callerProject reads it), never an\n * authorization gate. Absent when the cwd is inside no registered root (#405). */\n projectId?: string;\n /** Tenant id baked into a human session token at login (#389), copied from the user's\n * `users.tenant_id`. Identity-only today: it carries the SaaS multi-tenant seam into the token\n * so isolation becomes an additive change later, but enforces NOTHING — no auth path branches on\n * it yet. Absent for single-tenant users with a null `tenant_id`. */\n tenantId?: string;\n /** Spawn quota (max concurrent live children) baked into a spawn-capable agent's token,\n * resolved from its profile's `maxSpawnedAgents`. The runtime quota check reads it here. */\n maxSpawnedAgents?: number;\n targets?: string[];\n channels?: string[];\n orchestrators?: string[];\n hosts?: string[];\n cwd?: string;\n cwdPrefixes?: string[];\n taskIds?: string[];\n memoryScopes?: string[];\n integrationNames?: string[];\n spawnRequestIds?: string[];\n terminalAttach?: boolean;\n logsRead?: boolean;\n canDelegate?: boolean;\n}\n\ntype TokenScope =\n | \"system:admin\"\n | \"token:read\"\n | \"token:write\"\n | \"agent:read\"\n | \"agent:write\"\n | \"message:read\"\n | \"message:send\"\n | \"schedule:write\"\n | \"schedule:persistent\"\n | \"task:read\"\n | \"task:write\"\n | \"command:read\"\n | \"command:write\"\n | \"command:*\"\n | \"artifact:read\"\n | \"artifact:write\"\n | \"artifact:admin\"\n | \"memory:read\"\n | \"memory:write\"\n | \"memory:admin\"\n | \"mcp:use\"\n | \"teams:read\"\n | \"teams:write\"\n | \"terminal:attach\"\n | \"logs:read\"\n | \"integration:read\"\n | \"integration:write\"\n | \"channel:read\"\n | \"channel:write\"\n | \"stats:read\"\n | \"health:read\"\n | \"events:read\"\n | \"command:spawn\"\n | \"command:shutdown\"\n | \"recipe:start\"\n | \"recipe:stop\"\n | \"admin:*\";\n\nexport interface ConfigEntry<T = unknown> {\n namespace: string;\n key: string;\n value: T;\n version: number;\n updatedAt: string;\n updatedBy?: string;\n}\n\nexport interface ConfigHistoryEntry<T = unknown> {\n id: number;\n namespace: string;\n key: string;\n value: T;\n version: number;\n changedAt: string;\n changedBy?: string;\n}\n\nexport interface MaintenanceJob {\n id: string;\n title: string;\n description?: string;\n intervalMs: number;\n timeoutMs: number;\n enabled: boolean;\n runOnStart: boolean;\n lastRunAt?: number;\n nextRunAt?: number;\n lastDurationMs?: number;\n lastStatus: \"idle\" | \"running\" | \"succeeded\" | \"failed\" | \"disabled\";\n lastError?: string;\n lastResult?: Record<string, unknown>;\n consecutiveFailures: number;\n running: boolean;\n leaseUntil?: number;\n updatedAt: number;\n}\n\nexport interface MaintenanceJobRun {\n id: string;\n status: \"succeeded\" | \"failed\" | \"skipped\";\n startedAt: number;\n finishedAt: number;\n durationMs: number;\n result?: Record<string, unknown>;\n error?: string;\n}\n\nexport interface HealthCheck {\n name: string;\n status: \"ok\" | \"warn\" | \"error\";\n detail?: string;\n count?: number;\n subjects?: Array<{ id: string; label?: string; status?: string; detail?: string }>;\n}\n\nexport interface HealthReport {\n status: \"ok\" | \"degraded\" | \"error\";\n version: string;\n generatedAt: number;\n checks: HealthCheck[];\n}\n","// First-class human users (#388 / keystone #393). Promotes the human from the hardcoded\n// `'user'` sink to a real identity record. Addressing is per-user (`user:<id>`); the bare\n// `'user'` sink is the back-compat alias of the seeded `user:default` admin.\n//\n// SINGLE RESOLVER HOME (duplication ratchet): the `'user'` ≡ `user:default` ≡\n// `users.id='default'` mapping lives ONLY here. Every funnel site and every later #388 wave\n// calls these helpers rather than re-deriving the namespace — one place owns human addressing.\n\n/** Addressing prefix for the per-user human namespace: `user:<id>`. */\nexport const USER_NAMESPACE_PREFIX = \"user:\";\n\n/** Stable relational id of the admin user seeded on first boot, mapped to today's single human. */\nexport const DEFAULT_USER_ID = \"default\";\n\n/** Legacy bare human sink, retained as a back-compat alias of `user:default`. */\nexport const LEGACY_HUMAN_AGENT_ID = \"user\";\n\n/** Canonical addressing form for a human user: `user:<id>`. */\nexport function userSinkId(userId: string): string {\n return `${USER_NAMESPACE_PREFIX}${userId}`;\n}\n\n/**\n * True for any human address: the legacy bare `'user'` sink or any `user:<id>` in the\n * namespace. The human stopped being a singleton in #393 — prefer this over a literal\n * `id === 'user'` comparison so per-user sinks are recognized everywhere the human is.\n */\nexport function isHumanAgentId(id: string | undefined): boolean {\n return id === LEGACY_HUMAN_AGENT_ID || (typeof id === \"string\" && id.startsWith(USER_NAMESPACE_PREFIX));\n}\n\n/**\n * The relational `users.id` behind any human address, or `undefined` for a non-human id.\n * Both the legacy bare `'user'` and the `user:default` alias resolve to {@link DEFAULT_USER_ID};\n * `user:<id>` yields `<id>`. This is the bridge from a wire/message address to a `users` row.\n */\nexport function userIdFromAddress(id: string | undefined): string | undefined {\n if (id === LEGACY_HUMAN_AGENT_ID) return DEFAULT_USER_ID;\n if (typeof id === \"string\" && id.startsWith(USER_NAMESPACE_PREFIX)) {\n return id.slice(USER_NAMESPACE_PREFIX.length) || DEFAULT_USER_ID;\n }\n return undefined;\n}\n\n/**\n * The stored/canonical wire id for a human address, used for routing + storage so legacy bytes\n * never move (#393 Option A). The default user's canonical stored id is the legacy bare `'user'`,\n * so the working `user:default` alias collapses to it and matches every pre-existing\n * message/`operator_id`/inbox row. Every other `user:<id>` is already canonical. Non-human ids\n * pass through unchanged.\n */\nexport function canonicalHumanAgentId(id: string): string {\n // The degenerate empty address `user:` collapses to the default sink too, so it never lands\n // between resolvers: `userIdFromAddress('user:')` already yields DEFAULT_USER_ID, and without\n // this `canonicalHumanAgentId('user:')` returned `'user:'` — one helper said \"default human\",\n // the other routed to a `user:` agent a provider could squat (#393). Both now agree.\n if (id === LEGACY_HUMAN_AGENT_ID || id === userSinkId(DEFAULT_USER_ID) || id === USER_NAMESPACE_PREFIX) {\n return LEGACY_HUMAN_AGENT_ID;\n }\n return id;\n}\n\n/** Role gate for a user. Enforcement (ownership/authz) lands in #392 — this carries the value. */\nexport type UserRole = \"admin\" | \"operator\" | \"viewer\";\n\n/** Account lifecycle. `active` is the only state a fresh/seeded user takes today. */\nexport type UserStatus = \"active\" | \"suspended\" | \"deleted\";\n\nexport interface User {\n /** Stable id; the addressing form is `user:<id>` (see {@link userSinkId}). Others FK to this. */\n id: string;\n /** Short login-ish handle, unique within a tenant. */\n handle: string;\n displayName: string;\n role: UserRole;\n status: UserStatus;\n /**\n * SaaS multi-tenant seam (#393): nullable, defaults to the seeded `tenant:default`. The relay\n * is single-tenant today; the column existing now makes multi-tenant an additive migration\n * rather than a second identity refactor. Do NOT key isolation on it yet.\n */\n tenantId: string | null;\n /**\n * Content-addressed avatar via the existing artifact stack (#395/#630): nullable FK →\n * `artifacts.id`. This sub-issue adds the column only — upload/serve is out of scope.\n */\n avatarArtifactId: string | null;\n /** Authenticated API-relative image path for the current avatar, or null when unset. */\n avatarUrl: string | null;\n createdAt: number;\n updatedAt: number;\n}\n\nexport interface CreateUserInput {\n id: string;\n handle: string;\n displayName: string;\n role?: UserRole;\n status?: UserStatus;\n tenantId?: string | null;\n avatarArtifactId?: string | null;\n}\n","import type {\n Agent, Message, PairSession, Task, ChannelSummary, ConnectorSummary,\n IntegrationSummary, Orchestrator, AgentType, PresenceInfo, PresenceBadge,\n AttentionInfo, InboxThread, HealthCheck, HealthDiagnostic, ManagedPolicyHealth,\n} from '@/types'\nimport type { User } from '@/types'\nimport { HUMAN_AGENT_ID, AGENT_TYPE_ICONS, AGENT_TYPE_TITLES, WAITING_TASK_STATUSES } from './constants'\nimport { isRecord, isHumanAgentId, userIdFromAddress } from 'agent-relay-sdk/types'\n\nexport function toTimestamp(value: unknown): number {\n const ts = typeof value === 'number' ? value : new Date((value as string) || 0).getTime()\n return Number.isFinite(ts) ? ts : 0\n}\n\nconst INTERNAL_CAP_PREFIXES = ['lifecycle.', 'commands.', 'capabilities.']\n\nconst REACTION_EMOJI_ALIASES: Record<string, string> = {\n '+1': '👍',\n ':+1:': '👍',\n thumbsup: '👍',\n ':thumbsup:': '👍',\n 'thumbs-up': '👍',\n ':thumbs-up:': '👍',\n thumbs_up: '👍',\n ':thumbs_up:': '👍',\n thumb_up: '👍',\n ':thumb_up:': '👍',\n like: '👍',\n ':like:': '👍',\n heart: '❤️',\n ':heart:': '❤️',\n redheart: '❤️',\n red_heart: '❤️',\n ':red_heart:': '❤️',\n check: '✅',\n ':check:': '✅',\n checkmark: '✅',\n ':checkmark:': '✅',\n white_check_mark: '✅',\n ':white_check_mark:': '✅',\n eyes: '👀',\n ':eyes:': '👀',\n}\n\nexport function normalizeReactionEmoji(value: string): string {\n const trimmed = value.trim()\n return REACTION_EMOJI_ALIASES[trimmed.toLowerCase()] ?? trimmed\n}\n\nexport function userFacingCapabilities(caps: string[]): string[] {\n return caps.filter((c) => !INTERNAL_CAP_PREFIXES.some((p) => c.startsWith(p)))\n}\n\nexport function shortPath(cwd: string | undefined | null, segments = 2): string {\n if (!cwd) return ''\n const parts = cwd.replace(/\\/+$/, '').split('/')\n return parts.length <= segments ? cwd : parts.slice(-segments).join('/')\n}\n\nexport function normalizePathForCompare(path: string): string {\n return path.trim().replace(/\\\\/g, '/').replace(/\\/+$/, '') || '/'\n}\n\nexport function pathWithinBase(path: string | undefined | null, baseDir: string | undefined | null): boolean {\n if (!path?.trim() || !baseDir?.trim()) return false\n const normalizedPath = normalizePathForCompare(path)\n const normalizedBase = normalizePathForCompare(baseDir)\n if (normalizedBase === '/') return normalizedPath.startsWith('/')\n if (normalizedPath === '/') return false\n return normalizedPath === normalizedBase || normalizedPath.startsWith(normalizedBase + '/')\n}\n\nfunction isBuiltInAgent(agent: Agent | null | undefined): boolean {\n return agent?.meta?.builtin === true || agent?.id === HUMAN_AGENT_ID || agent?.id === 'system'\n}\n\nfunction isChannelAgent(agent: Agent | null | undefined): boolean {\n const tags = agent?.tags || []\n const caps = agent?.capabilities || []\n const policyName = typeof agent?.meta?.policyName === 'string' ? agent.meta.policyName : ''\n return agent?.kind === 'channel' ||\n agent?.meta?.kind === 'channel' ||\n tags.includes('channel') ||\n tags.some((tag) => tag.startsWith('channel:')) ||\n caps.includes('channel') ||\n caps.some((cap) => cap.startsWith('channel:')) ||\n tags.includes('telegram') ||\n caps.includes('telegram') ||\n policyName === 'telegram-main'\n}\n\nfunction isAutomationAgent(agent: Agent | null | undefined): boolean {\n return Boolean(\n typeof agent?.meta?.automationId === 'string' ||\n typeof agent?.meta?.automationRunId === 'string' ||\n agent?.tags?.includes('automation')\n )\n}\n\nfunction isSystemAgent(agent: Agent | null | undefined): boolean {\n return isBuiltInAgent(agent) || isChannelAgent(agent) || isAutomationAgent(agent)\n}\n\nexport function agentType(agent: Agent | null | undefined): AgentType {\n if (agent?.id === HUMAN_AGENT_ID) return 'user'\n if (agent?.id === 'system') return 'system'\n if (isChannelAgent(agent)) return 'channel'\n\n // #417: check the authoritative provider field first (exact match) so a name or id\n // containing \"codex\"/\"claude\" as a substring can't mislabel the agent.\n const provider = typeof agent?.meta?.provider === 'string' ? agent.meta.provider.toLowerCase() : undefined\n if (provider === 'codex') return 'codex'\n if (provider === 'claude') return 'claude'\n\n // Fallback heuristic for agents registered without meta.provider. Only scan tags\n // and meta fields — not id or name, which are free-text and cause false positives.\n const values = [\n ...(agent?.tags || []),\n agent?.meta?.client as string,\n agent?.meta?.runtime as string,\n agent?.meta?.agentType as string,\n ]\n .filter((v): v is string => typeof v === 'string')\n .map((v) => v.toLowerCase())\n\n if (values.some((v) => v.includes('codex'))) return 'codex'\n if (values.some((v) => v.includes('claude'))) return 'claude'\n return 'agent'\n}\n\nfunction agentTypeIcon(agent: Agent | null | undefined): string {\n return AGENT_TYPE_ICONS[agentType(agent)] ?? AGENT_TYPE_ICONS.agent ?? 'ti-robot'\n}\n\nfunction agentTypeTitle(agent: Agent | null | undefined): string {\n return AGENT_TYPE_TITLES[agentType(agent)] ?? AGENT_TYPE_TITLES.agent ?? 'Agent'\n}\n\nexport function isAgentStale(now: number, agent: Agent | null | undefined): boolean {\n if (!agent?.lastSeen || agent.status === 'offline') return false\n if (agent.id === 'user' || agent.id === 'system') return false\n // A live bus connection is authoritative liveness: an idle-but-connected agent\n // is reachable, not stale, even if lastSeen has aged (e.g. heartbeat keepalive\n // gaps while idle). Genuine death flips transport.connected to false (or the\n // server reaps the agent offline), at which point the lastSeen check applies.\n const transport = agent.meta?.transport\n if (isRecord(transport) && transport.connected === true) return false\n const lastSeenMs = new Date(agent.lastSeen as string).getTime()\n if (!Number.isFinite(lastSeenMs)) return false\n return now - lastSeenMs > 60_000\n}\n\nexport type AgentControlAction = 'restart' | 'shutdown' | 'reconnect' | 'compact' | 'clearContext' | 'resume' | 'interrupt'\n\nexport function agentSupportsControlAction(agent: Agent | null | undefined, action: AgentControlAction): boolean {\n if (!agent || isBuiltInAgent(agent) || isChannelAgent(agent)) return false\n if (action === 'resume') return false\n // Interrupt is provider-independent (Claude ESC, Codex turn-interrupt) and only\n // meaningful while the agent is actually mid-turn.\n if (action === 'interrupt') return agent.providerCapabilities?.liveSession?.interrupt === true && agent.status === 'busy'\n const needsClaudeTmux = agentType(agent) === 'claude' && (action === 'restart' || action === 'shutdown' || action === 'compact' || action === 'clearContext')\n if (needsClaudeTmux && !agentHasTmuxSession(agent)) return false\n const lifecycle = agent.providerCapabilities?.lifecycle\n if (lifecycle) {\n if (action === 'restart') return lifecycle.restartHard === true\n if (action === 'shutdown') return lifecycle.shutdownHard === true\n if (action === 'reconnect') return lifecycle.reconnect === true\n }\n if (action === 'compact') return agent.providerCapabilities?.context?.compact === true\n if (action === 'clearContext') return agent.providerCapabilities?.context?.clear === true\n return agent.meta?.runnerManaged === true && (action === 'restart' || action === 'shutdown')\n}\n\nfunction agentHasTmuxSession(agent: Agent): boolean {\n return typeof agent.meta?.tmuxSession === 'string' && agent.meta.tmuxSession.trim().length > 0\n}\n\nfunction agentSupportsControlActions(agent: Agent | null | undefined): boolean {\n return agentSupportsControlAction(agent, 'restart') ||\n agentSupportsControlAction(agent, 'shutdown') ||\n agentSupportsControlAction(agent, 'reconnect') ||\n agentSupportsControlAction(agent, 'compact') ||\n agentSupportsControlAction(agent, 'clearContext')\n}\n\nexport function agentCanBeForgotten(agent: Agent | null | undefined): boolean {\n return Boolean(agent && !isBuiltInAgent(agent) && !isChannelAgent(agent) && !agentSupportsControlActions(agent) && (agent.status === 'offline' || agent.status === 'stale'))\n}\n\nfunction isRetiredManagedRuntimeAgent(agent: Agent, managedPolicies: ManagedPolicyHealth[]): boolean {\n if (agent.status !== 'offline') return false\n const policyName = typeof agent.meta?.policyName === 'string' ? agent.meta.policyName : ''\n if (!policyName) return false\n const policy = managedPolicies.find((item) => item.policy.name === policyName)\n return Boolean(policy?.state.agentId && policy.state.agentId !== agent.id)\n}\n\nexport function visibleAgents(agents: Agent[], showBuiltIns: boolean, managedPolicies: ManagedPolicyHealth[] = []): Agent[] {\n const activeRuntimeAgents = agents.filter((a) => !isRetiredManagedRuntimeAgent(a, managedPolicies))\n return showBuiltIns ? activeRuntimeAgents : activeRuntimeAgents.filter((a) => !isSystemAgent(a))\n}\n\nexport function displayName(agent: Agent | null | undefined): string {\n if (!agent) return '?'\n return agent.label || agent.name || agent.id.slice(-12)\n}\n\n// Human attribution (#390). Resolve a human message address (`user` / `user:<id>`) to its\n// users-table row, or undefined for a non-human address / unknown id. The single bridge from\n// a wire address → a user record on the client, mirroring the SDK resolver on the server.\nexport function resolveUser(address: string | undefined, usersById: Record<string, User>): User | undefined {\n const userId = userIdFromAddress(address)\n return userId ? usersById[userId] : undefined\n}\n\n// Name to show for a human address. Prefers the user's displayName, then handle, then a\n// generic 'User' so a not-yet-loaded directory (or the seeded default) still reads sensibly —\n// which keeps today's single operator rendering exactly as before.\nexport function humanDisplayName(address: string | undefined, usersById: Record<string, User>): string {\n const user = resolveUser(address, usersById)\n return user?.displayName?.trim() || user?.handle?.trim() || 'User'\n}\n\n// Deterministic accent color per human so multiple operators are visually distinguishable in\n// one thread. Stable for a given user id; the single default operator never needs it (callers\n// only apply this once more than one human exists, preserving back-compat styling).\nconst HUMAN_ACCENT_CLASSES = [\n 'text-sky-300', 'text-amber-300', 'text-emerald-300', 'text-violet-300', 'text-rose-300', 'text-cyan-300',\n]\nexport function humanAccentClass(address: string | undefined): string {\n const userId = userIdFromAddress(address) || ''\n let hash = 0\n for (let i = 0; i < userId.length; i++) hash = (hash * 31 + userId.charCodeAt(i)) >>> 0\n return HUMAN_ACCENT_CLASSES[hash % HUMAN_ACCENT_CLASSES.length]!\n}\n\nexport function displayTarget(target: string, agentsById: Record<string, Agent>): string {\n if (!target) return '?'\n if (target === 'broadcast') return 'broadcast'\n if (target.startsWith('tag:')) return '#' + target.slice(4)\n if (target.startsWith('cap:')) return target.slice(4)\n if (target.startsWith('label:')) return target.slice(6)\n if (target.startsWith('team:')) {\n const teamId = target.slice(5)\n const member = Object.values(agentsById).find((a) => a.teamId === teamId)\n return member ? `team:${displayName(member)}` : `team:${teamId.slice(-8)}`\n }\n const agent = agentsById[target]\n return agent ? displayName(agent) : target.slice(-8)\n}\n\nexport function messageBody(msg: Message | null | undefined): string {\n if (!msg) return ''\n const payload = msg.payload || {}\n const channelMessage = payload.message as Record<string, unknown> | undefined\n if (channelMessage && typeof channelMessage === 'object' && typeof channelMessage.text === 'string' && (channelMessage.text as string).trim()) {\n return channelMessage.text as string\n }\n const interaction = payload.interaction as Record<string, unknown> | undefined\n if (interaction && typeof interaction === 'object') {\n const title = typeof interaction.title === 'string' ? interaction.title.trim() : ''\n const description = typeof interaction.description === 'string' ? interaction.description.trim() : ''\n if (title && description) return title + '\\n' + description\n if (title) return title\n if (description) return description\n }\n const reaction = payload.reaction as Record<string, unknown> | undefined\n if (reaction && typeof reaction === 'object') {\n const name = typeof reaction.name === 'string' ? reaction.name : ''\n const emoji = typeof reaction.emoji === 'string' ? reaction.emoji : ''\n const value = typeof reaction.value === 'string' ? reaction.value : ''\n const displayEmoji = normalizeReactionEmoji(emoji || name || value)\n return ['Reaction', displayEmoji].filter(Boolean).join(': ')\n }\n const activity = payload.activity as Record<string, unknown> | undefined\n if (activity && typeof activity === 'object') {\n const kind = typeof activity.kind === 'string' ? activity.kind : 'activity'\n const state = typeof activity.state === 'string' ? activity.state : ''\n return [kind, state].filter(Boolean).join(' ')\n }\n if (typeof payload.text === 'string' && (payload.text as string).trim()) return payload.text as string\n if (typeof payload.message === 'string' && (payload.message as string).trim()) return payload.message as string\n return extractTextFromJson(msg.body || '') ?? (msg.body || '')\n}\n\nexport function isReactionEventMessage(msg: Message | null | undefined): boolean {\n if (!msg?.payload) return false\n const event = msg.payload.event as Record<string, unknown> | undefined\n return msg.payload.reactionNotification === true || event?.type === 'message.reaction'\n}\n\ninterface ParsedJsonBody {\n text: string\n meta: Array<[string, string]>\n raw: Record<string, unknown>\n}\n\nexport function parseJsonBody(body: string): ParsedJsonBody | null {\n if (!body) return null\n const trimmed = body.trim()\n if (!trimmed.startsWith('{')) return null\n try {\n const obj = JSON.parse(trimmed)\n if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) return null\n const text = typeof obj.text === 'string' ? obj.text\n : typeof obj.message === 'string' ? obj.message\n : null\n if (!text?.trim()) return null\n const skipKeys = new Set(['text', 'message'])\n const meta = Object.entries(obj)\n .filter(([k, v]) => !skipKeys.has(k) && (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean'))\n .map(([k, v]) => [k, String(v)] as [string, string])\n return { text, meta, raw: obj }\n } catch {\n return null\n }\n}\n\nfunction extractTextFromJson(body: string): string | null {\n const parsed = parseJsonBody(body)\n return parsed?.text ?? null\n}\n\nexport function messagePreview(msg: Message | null | undefined): string {\n const text = msg?.subject || messageBody(msg) || ''\n return text.length > 90 ? text.slice(0, 90) + '...' : text\n}\n\n// De-singletoned human addressing (#393): a thread peer is the non-human side of any message\n// whose other side is a human address — the legacy bare `'user'` sink OR any `user:<id>` in the\n// namespace (`user:default` included). Today's single operator is still `'user'`, so this is a\n// no-op for the live deployment; per-user inbox scoping lands with auth/sessions (#389).\nexport function inboxPeer(msg: Message): string {\n if (isHumanAgentId(msg.from) && msg.to) return msg.to\n if (isHumanAgentId(msg.to) && msg.from) return msg.from\n return ''\n}\n\nexport function isHumanInboundMessage(msg: Message): boolean {\n return isHumanAgentId(msg.to) && !isHumanAgentId(msg.from)\n}\n\nexport function isSessionActivityStep(msg: Message): boolean {\n if (msg.kind !== 'session') return false\n const type = (msg.payload?.session as { type?: string } | undefined)?.type\n return type === 'reasoning' || type === 'tool'\n}\n\nexport function isBadgeWorthy(msg: Message): boolean {\n return msg.kind === 'session' && (msg.payload?.session as { final?: boolean } | undefined)?.final === true\n}\n\n// True when `block` appears as a whole \\n\\n-delimited segment of a response body —\n// the same boundary extractLastAssistantTurn joins narration on. Matches the entire\n// body (chatCaptureMode \"final\") or any internal block (\"full\") without naive-substring\n// false positives, and is robust to narration blocks that contain their own blank lines.\n// Used to suppress a narration trace step that the closing response bubble already repeats.\nexport function responseBodyContainsBlock(body: string, block: string): boolean {\n const b = body.trim()\n const t = block.trim()\n if (!t) return false\n if (b === t) return true\n return b.startsWith(`${t}\\n\\n`) || b.endsWith(`\\n\\n${t}`) || b.includes(`\\n\\n${t}\\n\\n`)\n}\n\nexport function isUnreadHumanMessage(readCursors: Record<string, number>, peer: string, msg: Message): boolean {\n if (!isHumanInboundMessage(msg)) return false\n if (!isBadgeWorthy(msg)) return false\n if ((msg.readBy || []).includes(HUMAN_AGENT_ID)) return false\n const cursor = Number(readCursors[peer] || 0)\n return msg.id > (Number.isFinite(cursor) ? cursor : 0)\n}\n\nexport function isClaimableTaskWaiting(task: Task): boolean {\n return WAITING_TASK_STATUSES.has(task.status) && !task.claimedBy\n}\n\nexport function isClaimableMessageWaiting(msg: Message): boolean {\n return Boolean(msg.claimable && !msg.claimedBy && !(msg.kind === 'task' && Number.isSafeInteger((msg.payload as Record<string, unknown>)?.taskId)))\n}\n\nexport function dashboardTargetMatchesAgent(target: string, agent: Agent): boolean {\n if (!target || !agent) return false\n if (target === 'broadcast' || target === agent.id) return true\n if (target.startsWith('tag:')) return (agent.tags || []).includes(target.slice(4))\n if (target.startsWith('cap:')) return (agent.capabilities || []).includes(target.slice(4))\n if (target.startsWith('label:')) return agent.label === target.slice(6)\n if (target.startsWith('team:')) return agent.teamId === target.slice(5)\n return false\n}\n\nexport function messageMatchesAgentChat(agentId: string, message: Message): boolean {\n if (!agentId) return false\n if (message.from === agentId || message.to === agentId) return true\n if (message.resolvedToAgent === agentId) return true\n return false\n}\n\nexport function messageMatchesChannel(message: Message, channel: ChannelSummary): boolean {\n if (!message || !channel) return false\n const channelKeys = [channel.id, channel.type, ...(channel.topicChannels || [])].filter(Boolean)\n if (message.channel && channelKeys.includes(message.channel)) return true\n const payloadChannel = message.payload?.channel as Record<string, unknown> | undefined\n if (payloadChannel && typeof payloadChannel === 'object') {\n const keys = [payloadChannel.agentId, payloadChannel.provider, payloadChannel.accountId].filter(Boolean) as string[]\n if (keys.includes(channel.id) || keys.includes(channel.type) || (channel.accountId && keys.includes(channel.accountId))) return true\n }\n if (channel.agentId && (message.from === channel.agentId || message.to === channel.agentId)) return true\n return false\n}\n\ninterface ActiveSubagentInfo {\n id: string\n label: string\n role?: string\n startedAt?: number\n}\n\ninterface ProviderRuntimeState {\n state: string\n reason?: string\n label: string\n recommendedAction?: string\n source?: string\n raw?: unknown\n updatedAt?: number\n pendingApproval?: ProviderPendingApproval\n}\n\ninterface ProviderPendingApprovalChoice {\n id: 'approve' | 'approve-session' | 'deny' | 'abort'\n label: string\n}\n\ninterface ProviderQuestionOption {\n label: string\n description?: string\n}\n\nexport interface ProviderQuestion {\n question: string\n header?: string\n options: ProviderQuestionOption[]\n multiSelect?: boolean\n}\n\nexport interface ProviderPendingApproval {\n id: string\n provider?: string\n kind?: string\n title: string\n body: string\n choices: ProviderPendingApprovalChoice[]\n // Present when kind === 'questions' (Claude AskUserQuestion). The dashboard\n // renders these as a form and submits an 'answer' decision.\n questions?: ProviderQuestion[]\n}\n\nexport function providerRuntimeState(agent: Agent | null | undefined): ProviderRuntimeState | null {\n const raw = agent?.meta?.providerState\n if (!isRecord(raw) || typeof raw.state !== 'string') return null\n return {\n state: raw.state,\n reason: typeof raw.reason === 'string' ? raw.reason : undefined,\n label: typeof raw.label === 'string' && raw.label ? raw.label : raw.state,\n recommendedAction: typeof raw.recommendedAction === 'string' ? raw.recommendedAction : undefined,\n source: typeof raw.source === 'string' ? raw.source : undefined,\n raw: raw.raw,\n updatedAt: typeof raw.updatedAt === 'number' ? raw.updatedAt : undefined,\n pendingApproval: providerPendingApproval(raw.pendingApproval),\n }\n}\n\nfunction providerPendingApproval(value: unknown): ProviderPendingApproval | undefined {\n if (!isRecord(value) || typeof value.id !== 'string') return undefined\n const choices = Array.isArray(value.choices)\n ? value.choices.filter(isRecord).map((choice) => ({\n id: choice.id,\n label: typeof choice.label === 'string' ? choice.label : String(choice.id || ''),\n })).filter((choice): choice is ProviderPendingApprovalChoice =>\n (choice.id === 'approve' || choice.id === 'approve-session' || choice.id === 'deny' || choice.id === 'abort') && Boolean(choice.label)\n )\n : []\n const questions = Array.isArray(value.questions)\n ? value.questions.filter(isRecord).map((q): ProviderQuestion => ({\n question: typeof q.question === 'string' ? q.question : '',\n header: typeof q.header === 'string' ? q.header : undefined,\n multiSelect: q.multiSelect === true,\n options: Array.isArray(q.options)\n ? q.options.filter(isRecord).map((o) => ({\n label: typeof o.label === 'string' ? o.label : String(o.label ?? ''),\n description: typeof o.description === 'string' ? o.description : undefined,\n })).filter((o) => Boolean(o.label))\n : [],\n })).filter((q) => Boolean(q.question) && q.options.length > 0)\n : undefined\n return {\n id: value.id,\n provider: typeof value.provider === 'string' ? value.provider : undefined,\n kind: typeof value.kind === 'string' ? value.kind : undefined,\n title: typeof value.title === 'string' && value.title ? value.title : 'Permission request',\n body: typeof value.body === 'string' ? value.body : '',\n choices,\n ...(questions && questions.length ? { questions } : {}),\n }\n}\n\nexport function providerBlockedState(agent: Agent | null | undefined): ProviderRuntimeState | null {\n const state = providerRuntimeState(agent)\n return state?.state === 'blocked' ? state : null\n}\n\nexport function isPendingApprovalBadgeWorthy(approval: ProviderPendingApproval | null | undefined): boolean { return Boolean(approval?.id) }\n\nexport function agentNeedsHumanResponse(agent: Agent | null | undefined): boolean { return isPendingApprovalBadgeWorthy(providerBlockedState(agent)?.pendingApproval) }\n\nexport function activeSubagents(agent: Agent | null | undefined): ActiveSubagentInfo[] {\n const raw = agent?.meta?.activeSubagents\n if (!Array.isArray(raw)) {\n const count = typeof agent?.meta?.activeSubagentCount === 'number' ? agent.meta.activeSubagentCount : 0\n return count > 0 ? Array.from({ length: count }, (_, index) => ({ id: `subagent-${index + 1}`, label: `Subagent ${index + 1}` })) : []\n }\n return raw\n .filter(isRecord)\n .map((item, index) => {\n const id = typeof item.id === 'string' && item.id ? item.id : `subagent-${index + 1}`\n const role = typeof item.role === 'string' && item.role ? item.role : undefined\n const label = typeof item.label === 'string' && item.label ? item.label : role || id\n const startedAt = typeof item.startedAt === 'number' ? item.startedAt : undefined\n return { id, label, role, startedAt }\n })\n}\n\n// #502: a background shell (run_in_background) still running for this agent keeps\n// it busy even though its turn ended. The runner surfaces it as a `background-script`\n// work item in meta.activeWork carrying a human label (\"N background tasks\"); return\n// that label for a badge, or null when none are running.\nexport function activeBackgroundScriptLabel(agent: Agent | null | undefined): string | null {\n const raw = agent?.meta?.activeWork\n if (!Array.isArray(raw)) return null\n const item = raw.find((work) => isRecord(work) && work.kind === 'background-script')\n if (!item || !isRecord(item)) return null\n return typeof item.label === 'string' && item.label ? item.label : 'background script'\n}\n\nexport function agentPresence(\n now: number, agent: Agent | null | undefined, attention: AttentionInfo, pair: PairSession | null\n): PresenceInfo {\n const lifecycleAction = typeof agent?.meta?.lifecycleAction === 'string' ? agent.meta.lifecycleAction : ''\n const stale = isAgentStale(now, agent)\n const reconnecting = agent?.status !== 'offline' && !agent?.ready && stale\n const starting = agent?.status !== 'offline' && !agent?.ready && !stale\n const unreadIdle = attention.unread > 0 && agent?.status !== 'busy'\n const blocked = providerBlockedState(agent)\n const badges = presenceBadges(agent, attention, pair)\n\n if (agent?.status === 'offline') return { label: 'offline', tone: 'secondary', icon: 'PlugZap', stale: false, reconnecting: false, badges }\n // Pre-session-destroy window (#183): the runner is running end-of-session work before a\n // compact/clear/restart/shutdown. Distinct from plain \"busy\" and non-addressable.\n if (lifecycleAction.startsWith('finalizing-')) {\n return { label: `wrapping up (${lifecycleAction.slice('finalizing-'.length)})`, tone: 'warning', icon: 'Hourglass', stale, reconnecting, badges }\n }\n if (lifecycleAction === 'shutting-down') return { label: 'shutting down', tone: 'warning', icon: 'Power', stale, reconnecting, badges }\n if (lifecycleAction === 'killing') return { label: 'killing', tone: 'danger', icon: 'Power', stale, reconnecting, badges }\n if (lifecycleAction === 'restarting') return { label: 'restarting', tone: 'warning', icon: 'RefreshCw', stale, reconnecting, badges }\n if (agent?.status === 'stale') return { label: 'stale', tone: 'danger', icon: 'RefreshCw', stale: true, reconnecting: true, badges }\n if (reconnecting) return { label: 'reconnecting', tone: 'danger', icon: 'RefreshCw', stale, reconnecting, badges }\n if (starting) return { label: 'online, not ready', tone: 'warning', icon: 'Loader', stale, reconnecting, badges }\n if (blocked) return { label: `blocked: ${blocked.label}`, tone: 'danger', icon: 'AlertCircle', stale, reconnecting, badges }\n if (agent?.status === 'busy') {\n // #502: busy purely because a background shell outlived the turn (no active\n // provider turn / subagent) reads more truthfully as background work.\n const reasons = Array.isArray(agent?.meta?.busyReasons) ? agent.meta.busyReasons : []\n const backgroundOnly = activeBackgroundScriptLabel(agent) && !reasons.includes('provider-turn') && !reasons.includes('subagent')\n if (backgroundOnly) return { label: 'running background work', tone: 'warning', icon: 'Play', stale, reconnecting, badges }\n return { label: 'busy in turn', tone: 'warning', icon: 'Play', stale, reconnecting, badges }\n }\n if (pair?.status === 'active') return { label: 'paired', tone: 'success', icon: 'Link', stale, reconnecting, badges }\n if (unreadIdle) return { label: 'idle, unread', tone: 'danger', icon: 'Bell', stale, reconnecting, badges }\n return { label: agent?.status === 'idle' ? 'idle' : 'ready', tone: 'success', icon: 'CircleCheck', stale, reconnecting, badges }\n}\n\nfunction presenceBadges(agent: Agent | null | undefined, attention: AttentionInfo, pair: PairSession | null): PresenceBadge[] {\n const badges: PresenceBadge[] = []\n const blocked = providerBlockedState(agent)\n const subagentCount = activeSubagents(agent).length\n if (blocked) badges.push({ label: blocked.reason ? `blocked: ${blocked.reason}` : 'blocked', className: 'bg-red-500/20 text-red-400' })\n if (subagentCount) badges.push({ label: subagentCount === 1 ? '1 subagent' : subagentCount + ' subagents', className: 'bg-cyan-500/20 text-cyan-300' })\n const backgroundScriptLabel = activeBackgroundScriptLabel(agent)\n if (backgroundScriptLabel) badges.push({ label: backgroundScriptLabel, className: 'bg-indigo-500/20 text-indigo-300' })\n if (pair?.status === 'active') badges.push({ label: 'paired', className: 'bg-emerald-500/20 text-emerald-400' })\n if (pair?.status === 'pending') badges.push({ label: 'pair invite', className: 'bg-yellow-500/20 text-yellow-400' })\n if (attention.needsHumanResponse) badges.push({ label: 'needs response', className: 'bg-amber-500/20 text-amber-300' })\n if (attention.unread) badges.push({ label: attention.unread + ' unread', className: 'bg-red-500/20 text-red-400' })\n if (attention.claimableTasks) badges.push({ label: attention.claimableTasks + ' claimable', className: 'bg-orange-500/20 text-orange-400' })\n return badges\n}\n\nexport function emptyAttention(): AttentionInfo {\n return { unread: 0, needsHumanResponse: false, pendingPairInvite: false, claimableTasks: 0, total: 0, score: 0 }\n}\n\nexport function channelIsReady(channel: ChannelSummary | null): boolean {\n if (!channel || channel.status === 'offline') return false\n if (channel.targetHealth?.status === 'ok') return true\n if (channel.targetHealth?.status === 'error' || channel.targetHealth?.status === 'warning') return false\n return channel.ready\n}\n\nexport function channelPresence(channel: ChannelSummary | null): PresenceInfo {\n if (!channel) return { label: 'unknown', tone: 'secondary', icon: 'PlugZap', badges: [] }\n if (channel.targetHealth?.status === 'error') return { label: 'target broken', tone: 'danger', icon: 'AlertTriangle', badges: [] }\n if (channel.targetHealth?.status === 'warning') return { label: 'target warning', tone: 'warning', icon: 'AlertCircle', badges: [] }\n if (channel.status === 'offline') return { label: 'offline', tone: 'secondary', icon: 'PlugZap', badges: [] }\n if (channel.status === 'busy') return { label: 'busy', tone: 'warning', icon: 'Activity', badges: [] }\n if (!channelIsReady(channel)) return { label: 'not ready', tone: 'warning', icon: 'Loader', badges: [] }\n return { label: 'ready', tone: 'success', icon: 'CircleCheck', badges: [] }\n}\n\nexport function connectorPresence(connector: ConnectorSummary): PresenceInfo {\n const runtime = connector?.runtime || {}\n if (runtime.status === 'error') return { label: 'error', tone: 'danger', icon: 'AlertTriangle', badges: [] }\n if (runtime.status === 'warn') return { label: 'warning', tone: 'warning', icon: 'AlertCircle', badges: [] }\n if (runtime.running) return { label: 'running', tone: 'success', icon: 'CircleCheck', badges: [] }\n if (runtime.enabled === false) return { label: 'disabled', tone: 'secondary', icon: 'Pause', badges: [] }\n if (runtime.status === 'ok') return { label: 'ok', tone: 'success', icon: 'CircleCheck', badges: [] }\n return { label: 'unknown', tone: 'secondary', icon: 'HelpCircle', badges: [] }\n}\n\nexport function integrationPresence(integration: IntegrationSummary): PresenceInfo {\n const stats = integration?.taskStats || {}\n if ((stats.waitingTasks || 0) > 0) return { label: 'waiting', tone: 'warning', icon: 'AlertCircle', badges: [] }\n if ((stats.openTasks || 0) > 0) return { label: 'active', tone: 'info', icon: 'Activity', badges: [] }\n if (!integration?.configured) return { label: 'observed only', tone: 'secondary', icon: 'Eye', badges: [] }\n if (integration.observed) return { label: 'quiet', tone: 'success', icon: 'CircleCheck', badges: [] }\n return { label: 'configured', tone: 'primary', icon: 'PlugZap', badges: [] }\n}\n\nexport function orchestratorHealthLabel(orch: Orchestrator): string {\n const health = orch?.health\n if (!health || health.status === 'ok') return orch?.version || orch?.package?.version ? 'compatible' : 'unknown version'\n if (health.status === 'upgrade-required') return 'upgrade required'\n if (health.status === 'restart-required') return 'restart required'\n if (health.status === 'unknown') return 'unknown contract'\n if ((health.issues || []).some((i) => i.code === 'protocol-mismatch')) return 'protocol mismatch'\n if ((health.issues || []).some((i) => i.code === 'package-drift')) return 'package drift'\n if (health.restartRequired) return 'restart required'\n return health.status\n}\n\nexport function orchestratorHealthClass(orch: Orchestrator): string {\n const status = orch?.health?.status\n if (status === 'upgrade-required') return 'text-red-400 bg-red-500/10'\n if (status === 'warn' || status === 'restart-required') return 'text-yellow-400 bg-yellow-500/10'\n if (status === 'unknown') return 'text-zinc-400 bg-zinc-500/10'\n return 'text-emerald-400 bg-emerald-500/10'\n}\n\nexport function timeAgo(now: number, iso: string | number | null | undefined): string {\n if (!iso) return ''\n const ts = new Date(iso as string).getTime()\n if (!Number.isFinite(ts)) return ''\n const diff = Math.max(0, (now - ts) / 1000)\n if (diff < 60) return Math.floor(diff) + 's ago'\n if (diff < 3600) return Math.floor(diff / 60) + 'm ago'\n if (diff < 86400) return Math.floor(diff / 3600) + 'h ago'\n return Math.floor(diff / 86400) + 'd ago'\n}\n\nexport function fmtTime(iso: string | number | null | undefined): string {\n if (!iso) return ''\n return new Date(iso as string).toLocaleString()\n}\n\nexport function safeFilename(value: string): string {\n return String(value || 'export').replace(/[^a-z0-9._-]+/gi, '-').replace(/^-+|-+$/g, '').slice(0, 80) || 'export'\n}\n\nexport function healthDiagnostics(checks: HealthCheck[]): HealthDiagnostic[] {\n return checks.filter((c) => c.status !== 'ok').map((check) => {\n const base: HealthDiagnostic = {\n name: check.name,\n status: check.status,\n detail: check.detail || check.name,\n impact: healthImpact(check),\n subjects: check.subjects || [],\n actions: [\n { label: 'Inspect logs', icon: 'FileSearch', copy: 'agent-relay daemon logs' },\n { label: 'Restart daemon', icon: 'RefreshCw', copy: 'agent-relay daemon restart' },\n { label: 'Copy env', icon: 'Copy', copy: 'agent-relay doctor' },\n ],\n }\n if (check.name === 'stale-live-agents') {\n base.actions.unshift(\n { label: 'Run reaper', icon: 'Eraser', api: 'POST', path: '/system/reap' },\n { label: 'Show stale', icon: 'Filter', view: 'agents', preset: 'offline_stale' },\n )\n } else if (check.name === 'channel-delivery-targets') {\n base.actions.unshift(\n { label: 'Open channels', icon: 'MessagesSquare', view: 'channels' },\n { label: 'Run reaper', icon: 'Eraser', api: 'POST', path: '/system/reap' },\n )\n } else if (['expired-message-claims', 'expired-task-claims', 'offline-claimed-tasks'].includes(check.name)) {\n base.actions.unshift(\n { label: 'Run reaper', icon: 'Eraser', api: 'POST', path: '/system/reap' },\n { label: 'Open work', icon: 'ListChecks', view: 'work' },\n )\n }\n return base\n })\n}\n\nfunction healthImpact(check: HealthCheck): string {\n const impacts: Record<string, string> = {\n 'database': 'Relay persistence is unavailable; messages, state, and audit writes may fail.',\n 'stale-live-agents': 'Agents may look online even though their heartbeat has stopped.',\n 'expired-message-claims': 'Claimable messages may be stuck until the reaper releases expired claims.',\n 'expired-task-claims': 'Tasks can appear owned by agents that no longer hold a live lease.',\n 'offline-claimed-tasks': 'Offline agents are still shown as owners for active work.',\n 'channel-delivery-targets': 'Inbound channel messages may be accepted but routed to no live delivery agent.',\n }\n return impacts[check.name] || 'Relay health is degraded for this check.'\n}\n\nfunction toneToBadgeVariant(tone: string): 'default' | 'secondary' | 'destructive' | 'outline' {\n if (tone === 'danger' || tone === 'destructive') return 'destructive'\n if (tone === 'secondary') return 'secondary'\n return 'default'\n}\n\nexport function toneToColor(tone: string): string {\n const map: Record<string, string> = {\n success: 'text-emerald-400',\n warning: 'text-yellow-400',\n danger: 'text-red-400',\n info: 'text-blue-400',\n primary: 'text-blue-400',\n secondary: 'text-zinc-400',\n }\n return map[tone] || 'text-zinc-400'\n}\n\nfunction toneToBg(tone: string): string {\n const map: Record<string, string> = {\n success: 'bg-emerald-500',\n warning: 'bg-yellow-500',\n danger: 'bg-red-500',\n info: 'bg-blue-500',\n primary: 'bg-blue-500',\n secondary: 'bg-zinc-500',\n }\n return map[tone] || 'bg-zinc-500'\n}\n\nexport function statusDotColor(agent: Agent | null, now?: number): string {\n if (!agent) return 'bg-zinc-500 opacity-50'\n if (agent.status === 'offline') return 'bg-zinc-500 opacity-50'\n if (typeof agent.meta?.lifecycleAction === 'string' && agent.meta.lifecycleAction.startsWith('finalizing-')) return 'bg-amber-500 animate-pulse'\n if (agent.meta?.lifecycleAction === 'shutting-down' || agent.meta?.lifecycleAction === 'restarting') return 'bg-yellow-500 animate-pulse'\n if (agent.meta?.lifecycleAction === 'killing') return 'bg-red-500 animate-pulse'\n if (providerBlockedState(agent)) return 'bg-red-500 animate-pulse'\n if (agent.status === 'busy') return 'bg-yellow-500'\n if (now && isAgentStale(now, agent)) return 'bg-red-500 animate-pulse'\n if (!agent.ready) return 'bg-emerald-500 animate-pulse'\n return 'bg-emerald-500 shadow-[0_0_6px_theme(colors.emerald.500)]'\n}\n\nexport function downloadText(filename: string, text: string, type: string) {\n if (typeof document === 'undefined' || typeof URL === 'undefined' || typeof Blob === 'undefined') return\n const url = URL.createObjectURL(new Blob([text], { type }))\n const link = document.createElement('a')\n link.href = url\n link.download = filename\n link.click()\n URL.revokeObjectURL(url)\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as e}from"./chunk-CilyBKbf.js";import{
|
|
2
|
-
//# sourceMappingURL=dist-
|
|
1
|
+
import{r as e}from"./chunk-CilyBKbf.js";import{ir as t}from"./lucide-react-BCarrGVa.js";import{t as n}from"./dist-DNxK5Ibj.js";var r=e(t(),1),i=r.useInsertionEffect||n;function a({prop:e,defaultProp:t,onChange:n=()=>{},caller:i}){let[a,c,l]=o({defaultProp:t,onChange:n}),u=e!==void 0,d=u?e:a;{let t=r.useRef(e!==void 0);r.useEffect(()=>{let e=t.current;e!==u&&console.warn(`${i} is changing from ${e?`controlled`:`uncontrolled`} to ${u?`controlled`:`uncontrolled`}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`),t.current=u},[u,i])}return[d,r.useCallback(t=>{if(u){let n=s(t)?t(e):t;n!==e&&l.current?.(n)}else c(t)},[u,e,c,l])]}function o({defaultProp:e,onChange:t}){let[n,a]=r.useState(e),o=r.useRef(n),s=r.useRef(t);return i(()=>{s.current=t},[t]),r.useEffect(()=>{o.current!==n&&(s.current?.(n),o.current=n)},[n,o]),[n,a,s]}function s(e){return typeof e==`function`}export{a as t};
|
|
2
|
+
//# sourceMappingURL=dist-BEpGArlE.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dist-
|
|
1
|
+
{"version":3,"file":"dist-BEpGArlE.js","names":["React","useLayoutEffect"],"sources":["../../dashboard/node_modules/@radix-ui/react-use-controllable-state/dist/index.mjs"],"sourcesContent":["// src/use-controllable-state.tsx\nimport * as React from \"react\";\nimport { useLayoutEffect } from \"@radix-ui/react-use-layout-effect\";\nvar useInsertionEffect = React[\" useInsertionEffect \".trim().toString()] || useLayoutEffect;\nfunction useControllableState({\n prop,\n defaultProp,\n onChange = () => {\n },\n caller\n}) {\n const [uncontrolledProp, setUncontrolledProp, onChangeRef] = useUncontrolledState({\n defaultProp,\n onChange\n });\n const isControlled = prop !== void 0;\n const value = isControlled ? prop : uncontrolledProp;\n if (true) {\n const isControlledRef = React.useRef(prop !== void 0);\n React.useEffect(() => {\n const wasControlled = isControlledRef.current;\n if (wasControlled !== isControlled) {\n const from = wasControlled ? \"controlled\" : \"uncontrolled\";\n const to = isControlled ? \"controlled\" : \"uncontrolled\";\n console.warn(\n `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`\n );\n }\n isControlledRef.current = isControlled;\n }, [isControlled, caller]);\n }\n const setValue = React.useCallback(\n (nextValue) => {\n if (isControlled) {\n const value2 = isFunction(nextValue) ? nextValue(prop) : nextValue;\n if (value2 !== prop) {\n onChangeRef.current?.(value2);\n }\n } else {\n setUncontrolledProp(nextValue);\n }\n },\n [isControlled, prop, setUncontrolledProp, onChangeRef]\n );\n return [value, setValue];\n}\nfunction useUncontrolledState({\n defaultProp,\n onChange\n}) {\n const [value, setValue] = React.useState(defaultProp);\n const prevValueRef = React.useRef(value);\n const onChangeRef = React.useRef(onChange);\n useInsertionEffect(() => {\n onChangeRef.current = onChange;\n }, [onChange]);\n React.useEffect(() => {\n if (prevValueRef.current !== value) {\n onChangeRef.current?.(value);\n prevValueRef.current = value;\n }\n }, [value, prevValueRef]);\n return [value, setValue, onChangeRef];\n}\nfunction isFunction(value) {\n return typeof value === \"function\";\n}\n\n// src/use-controllable-state-reducer.tsx\nimport * as React2 from \"react\";\nimport { useEffectEvent } from \"@radix-ui/react-use-effect-event\";\nvar SYNC_STATE = Symbol(\"RADIX:SYNC_STATE\");\nfunction useControllableStateReducer(reducer, userArgs, initialArg, init) {\n const { prop: controlledState, defaultProp, onChange: onChangeProp, caller } = userArgs;\n const isControlled = controlledState !== void 0;\n const onChange = useEffectEvent(onChangeProp);\n if (true) {\n const isControlledRef = React2.useRef(controlledState !== void 0);\n React2.useEffect(() => {\n const wasControlled = isControlledRef.current;\n if (wasControlled !== isControlled) {\n const from = wasControlled ? \"controlled\" : \"uncontrolled\";\n const to = isControlled ? \"controlled\" : \"uncontrolled\";\n console.warn(\n `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`\n );\n }\n isControlledRef.current = isControlled;\n }, [isControlled, caller]);\n }\n const args = [{ ...initialArg, state: defaultProp }];\n if (init) {\n args.push(init);\n }\n const [internalState, dispatch] = React2.useReducer(\n (state2, action) => {\n if (action.type === SYNC_STATE) {\n return { ...state2, state: action.state };\n }\n const next = reducer(state2, action);\n if (isControlled && !Object.is(next.state, state2.state)) {\n onChange(next.state);\n }\n return next;\n },\n ...args\n );\n const uncontrolledState = internalState.state;\n const prevValueRef = React2.useRef(uncontrolledState);\n React2.useEffect(() => {\n if (prevValueRef.current !== uncontrolledState) {\n prevValueRef.current = uncontrolledState;\n if (!isControlled) {\n onChange(uncontrolledState);\n }\n }\n }, [onChange, uncontrolledState, prevValueRef, isControlled]);\n const state = React2.useMemo(() => {\n const isControlled2 = controlledState !== void 0;\n if (isControlled2) {\n return { ...internalState, state: controlledState };\n }\n return internalState;\n }, [internalState, controlledState]);\n React2.useEffect(() => {\n if (isControlled && !Object.is(controlledState, internalState.state)) {\n dispatch({ type: SYNC_STATE, state: controlledState });\n }\n }, [controlledState, internalState.state, isControlled]);\n return [state, dispatch];\n}\nexport {\n useControllableState,\n useControllableStateReducer\n};\n//# sourceMappingURL=index.mjs.map\n"],"x_google_ignoreList":[0],"mappings":"8IAGI,EAAqBA,EAAM,oBAA6CC,EAC5E,SAAS,EAAqB,CAC5B,OACA,cACA,eAAiB,GAEjB,UACC,CACD,GAAM,CAAC,EAAkB,EAAqB,GAAe,EAAqB,CAChF,cACA,WACD,CAAC,CACI,EAAe,IAAS,IAAK,GAC7B,EAAQ,EAAe,EAAO,EAC1B,CACR,IAAM,EAAA,EAAwB,OAAO,IAAS,IAAK,GAAE,CACrD,EAAM,cAAgB,CACpB,IAAM,EAAgB,EAAgB,QAClC,IAAkB,GAGpB,QAAQ,KACN,GAAG,EAAO,oBAHC,EAAgB,aAAe,eAGP,MAF1B,EAAe,aAAe,eAEK,4KAC7C,CAEH,EAAgB,QAAU,GACzB,CAAC,EAAc,EAAO,CAAC,CAe5B,MAAO,CAAC,EAAA,EAbe,YACpB,GAAc,CACb,GAAI,EAAc,CAChB,IAAM,EAAS,EAAW,EAAU,CAAG,EAAU,EAAK,CAAG,EACrD,IAAW,GACb,EAAY,UAAU,EAAO,MAG/B,EAAoB,EAAU,EAGlC,CAAC,EAAc,EAAM,EAAqB,EAAY,CAEjC,CAAC,CAE1B,SAAS,EAAqB,CAC5B,cACA,YACC,CACD,GAAM,CAAC,EAAO,GAAA,EAAkB,SAAS,EAAY,CAC/C,EAAA,EAAqB,OAAO,EAAM,CAClC,EAAA,EAAoB,OAAO,EAAS,CAU1C,OATA,MAAyB,CACvB,EAAY,QAAU,GACrB,CAAC,EAAS,CAAC,CACd,EAAM,cAAgB,CAChB,EAAa,UAAY,IAC3B,EAAY,UAAU,EAAM,CAC5B,EAAa,QAAU,IAExB,CAAC,EAAO,EAAa,CAAC,CAClB,CAAC,EAAO,EAAU,EAAY,CAEvC,SAAS,EAAW,EAAO,CACzB,OAAO,OAAO,GAAU"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{r as e}from"./chunk-CilyBKbf.js";import{ir as t}from"./lucide-react-BCarrGVa.js";var n=e(t(),1);function r(e){let t=n.useRef({value:e,previous:e});return n.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}export{r as t};
|
|
2
|
+
//# sourceMappingURL=dist-BZ-CpDXy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dist-
|
|
1
|
+
{"version":3,"file":"dist-BZ-CpDXy.js","names":[],"sources":["../../dashboard/node_modules/@radix-ui/react-use-previous/dist/index.mjs"],"sourcesContent":["// packages/react/use-previous/src/use-previous.tsx\nimport * as React from \"react\";\nfunction usePrevious(value) {\n const ref = React.useRef({ value, previous: value });\n return React.useMemo(() => {\n if (ref.current.value !== value) {\n ref.current.previous = ref.current.value;\n ref.current.value = value;\n }\n return ref.current.previous;\n }, [value]);\n}\nexport {\n usePrevious\n};\n//# sourceMappingURL=index.mjs.map\n"],"x_google_ignoreList":[0],"mappings":"uGAEA,SAAS,EAAY,EAAO,CAC1B,IAAM,EAAA,EAAY,OAAO,CAAE,QAAO,SAAU,EAAO,CAAC,CACpD,OAAA,EAAa,aACP,EAAI,QAAQ,QAAU,IACxB,EAAI,QAAQ,SAAW,EAAI,QAAQ,MACnC,EAAI,QAAQ,MAAQ,GAEf,EAAI,QAAQ,UAClB,CAAC,EAAM,CAAC"}
|