agent-relay-server 0.102.2 → 0.104.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/docs/openapi.json +1 -1
  2. package/package.json +4 -2
  3. package/public/assets/MessageBubble-BpiTbbnp.js +4 -0
  4. package/public/assets/{MessageBubble-Dppo7pil.js.map → MessageBubble-BpiTbbnp.js.map} +1 -1
  5. package/public/assets/{PlanGraphPanel-DO5OY2qa.js → PlanGraphPanel-k8Aa98g5.js} +2 -2
  6. package/public/assets/{PlanGraphPanel-DO5OY2qa.js.map → PlanGraphPanel-k8Aa98g5.js.map} +1 -1
  7. package/public/assets/{activity-BhuOs5ha.js → activity-CxKi5Erb.js} +2 -2
  8. package/public/assets/{activity-BhuOs5ha.js.map → activity-CxKi5Erb.js.map} +1 -1
  9. package/public/assets/agent-profiles-DMZXdN6K.js +2 -0
  10. package/public/assets/agent-profiles-DMZXdN6K.js.map +1 -0
  11. package/public/assets/agents-BrvpH3gn.js +2 -0
  12. package/public/assets/agents-BrvpH3gn.js.map +1 -0
  13. package/public/assets/{analytics-BxGRljQc.js → analytics-1fvh-9ok.js} +2 -2
  14. package/public/assets/{analytics-BxGRljQc.js.map → analytics-1fvh-9ok.js.map} +1 -1
  15. package/public/assets/automation-BCgX5WOH.js +2 -0
  16. package/public/assets/automation-BCgX5WOH.js.map +1 -0
  17. package/public/assets/{branch-state-badge-olhanssg.js → branch-state-badge-DYA7bU0i.js} +2 -2
  18. package/public/assets/{branch-state-badge-olhanssg.js.map → branch-state-badge-DYA7bU0i.js.map} +1 -1
  19. package/public/assets/{channels-yHkm1dux.js → channels-CK80oJYG.js} +2 -2
  20. package/public/assets/{channels-yHkm1dux.js.map → channels-CK80oJYG.js.map} +1 -1
  21. package/public/assets/chat-D_2PEz06.js +2 -0
  22. package/public/assets/chat-D_2PEz06.js.map +1 -0
  23. package/public/assets/{connectors-DpR0V2hy.js → connectors-ChGyht5m.js} +2 -2
  24. package/public/assets/{connectors-DpR0V2hy.js.map → connectors-ChGyht5m.js.map} +1 -1
  25. package/public/assets/display-D1xB8NJZ.js +3 -0
  26. package/public/assets/display-D1xB8NJZ.js.map +1 -0
  27. package/public/assets/{formatted-body-impl-BTeCfK4I.js → formatted-body-impl-BJ4WCF5J.js} +2 -2
  28. package/public/assets/{formatted-body-impl-BTeCfK4I.js.map → formatted-body-impl-BJ4WCF5J.js.map} +1 -1
  29. package/public/assets/index-BMO_Sblt.css +2 -0
  30. package/public/assets/index-CN7QZlMb.js +23 -0
  31. package/public/assets/index-CN7QZlMb.js.map +1 -0
  32. package/public/assets/{insights-C4aYlgsY.js → insights-BCZRV0hX.js} +2 -2
  33. package/public/assets/{insights-C4aYlgsY.js.map → insights-BCZRV0hX.js.map} +1 -1
  34. package/public/assets/{integrations-CEmYa5mW.js → integrations-C7UGs00U.js} +2 -2
  35. package/public/assets/{integrations-CEmYa5mW.js.map → integrations-C7UGs00U.js.map} +1 -1
  36. package/public/assets/maintenance-ChYno3Fm.js +2 -0
  37. package/public/assets/{maintenance-DVx3SiC_.js.map → maintenance-ChYno3Fm.js.map} +1 -1
  38. package/public/assets/{managed-agents-DUyOTdz1.js → managed-agents-DjxZQNIR.js} +4 -4
  39. package/public/assets/managed-agents-DjxZQNIR.js.map +1 -0
  40. package/public/assets/{markdown-preview-impl-D3hYfzGD.js → markdown-preview-impl-DFa5HF1H.js} +2 -2
  41. package/public/assets/{markdown-preview-impl-D3hYfzGD.js.map → markdown-preview-impl-DFa5HF1H.js.map} +1 -1
  42. package/public/assets/{memory-2IfPgcnN.js → memory-CTw6kadT.js} +2 -2
  43. package/public/assets/{memory-2IfPgcnN.js.map → memory-CTw6kadT.js.map} +1 -1
  44. package/public/assets/{messages-CkOwABhw.js → messages-Cjr816D_.js} +2 -2
  45. package/public/assets/{messages-CkOwABhw.js.map → messages-Cjr816D_.js.map} +1 -1
  46. package/public/assets/{orchestrators-CEKO3eQ1.js → orchestrators-CdmFKA7K.js} +2 -2
  47. package/public/assets/{orchestrators-CEKO3eQ1.js.map → orchestrators-CdmFKA7K.js.map} +1 -1
  48. package/public/assets/overview-DBOnjfpP.js +2 -0
  49. package/public/assets/{overview-D9BxevAq.js.map → overview-DBOnjfpP.js.map} +1 -1
  50. package/public/assets/{pairs-CqT4D1uL.js → pairs-BkcdKdF7.js} +2 -2
  51. package/public/assets/{pairs-CqT4D1uL.js.map → pairs-BkcdKdF7.js.map} +1 -1
  52. package/public/assets/{projects-C_zwR-Fn.js → projects-ClKbDQB1.js} +2 -2
  53. package/public/assets/{projects-C_zwR-Fn.js.map → projects-ClKbDQB1.js.map} +1 -1
  54. package/public/assets/registry-Bsf3RtOO.js +2 -0
  55. package/public/assets/registry-Bsf3RtOO.js.map +1 -0
  56. package/public/assets/{security-8a5_4U3S.js → security-Iyu5bhJw.js} +2 -2
  57. package/public/assets/{security-8a5_4U3S.js.map → security-Iyu5bhJw.js.map} +1 -1
  58. package/public/assets/{select-s4BE-T62.js → select-Bi6tNJDx.js} +2 -2
  59. package/public/assets/{select-s4BE-T62.js.map → select-Bi6tNJDx.js.map} +1 -1
  60. package/public/assets/settings-CbHp2F9r.js +6 -0
  61. package/public/assets/settings-CbHp2F9r.js.map +1 -0
  62. package/public/assets/store-zeyDiSKE.js +9 -0
  63. package/public/assets/store-zeyDiSKE.js.map +1 -0
  64. package/public/assets/{tasks-Bcr17zR3.js → tasks-CPlf-5Tr.js} +2 -2
  65. package/public/assets/{tasks-Bcr17zR3.js.map → tasks-CPlf-5Tr.js.map} +1 -1
  66. package/public/assets/teams-D2uUNhlY.js +2 -0
  67. package/public/assets/{teams-B_HSkU9T.js.map → teams-D2uUNhlY.js.map} +1 -1
  68. package/public/assets/{terminal-viewer-impl-Ctp4csJ3.js → terminal-viewer-impl-uRzbrlXy.js} +2 -2
  69. package/public/assets/{terminal-viewer-impl-Ctp4csJ3.js.map → terminal-viewer-impl-uRzbrlXy.js.map} +1 -1
  70. package/public/assets/types-DorDDvEb.js +2 -0
  71. package/public/assets/types-DorDDvEb.js.map +1 -0
  72. package/public/assets/{user-avatar-RliaJcaG.js → user-avatar-GFWMuAGK.js} +2 -2
  73. package/public/assets/{user-avatar-RliaJcaG.js.map → user-avatar-GFWMuAGK.js.map} +1 -1
  74. package/public/assets/{work-queue-C0hpmlv8.js → work-queue-B30SNmuq.js} +2 -2
  75. package/public/assets/{work-queue-C0hpmlv8.js.map → work-queue-B30SNmuq.js.map} +1 -1
  76. package/public/assets/{workspaces-QXbQx_AG.js → workspaces-CSpmgbPT.js} +3 -3
  77. package/public/assets/{workspaces-QXbQx_AG.js.map → workspaces-CSpmgbPT.js.map} +1 -1
  78. package/public/index.html +5 -5
  79. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  80. package/runner/plugins/claude/monitors/relay-monitor.provisioned.mjs +238 -16
  81. package/runner/src/config.ts +8 -3
  82. package/src/automations/validation.ts +3 -3
  83. package/src/cli/orchestrator.ts +7 -5
  84. package/src/cli/provider.ts +26 -10
  85. package/src/cli/upgrade.ts +5 -4
  86. package/src/commands-db.ts +1 -1
  87. package/src/config-store.ts +3 -3
  88. package/src/config.ts +3 -3
  89. package/src/context-advisory.ts +3 -3
  90. package/src/db/provider-quotas.ts +11 -2
  91. package/src/db/provisioning-registry.ts +49 -61
  92. package/src/managed-policy.ts +7 -2
  93. package/src/mcp/tools-spawn.ts +2 -2
  94. package/src/mcp-team-template-tools.ts +2 -2
  95. package/src/provider-catalog-store.ts +1 -1
  96. package/src/recipe-validator.ts +4 -2
  97. package/src/routes/agent-profiles.ts +2 -2
  98. package/src/routes/agent-sessions.ts +18 -23
  99. package/src/routes/provider-config.ts +1 -1
  100. package/src/spawn-targets.ts +2 -2
  101. package/src/upgrade.ts +120 -44
  102. package/src/workspace-phase.ts +1 -0
  103. package/public/assets/MessageBubble-Dppo7pil.js +0 -4
  104. package/public/assets/agent-profiles-CT-vbfzd.js +0 -2
  105. package/public/assets/agent-profiles-CT-vbfzd.js.map +0 -1
  106. package/public/assets/agents-CLcgi8AD.js +0 -2
  107. package/public/assets/agents-CLcgi8AD.js.map +0 -1
  108. package/public/assets/automation-CE1i1zpw.js +0 -2
  109. package/public/assets/automation-CE1i1zpw.js.map +0 -1
  110. package/public/assets/chat-DHdFZvAg.js +0 -2
  111. package/public/assets/chat-DHdFZvAg.js.map +0 -1
  112. package/public/assets/display-DRiGi1zj.js +0 -3
  113. package/public/assets/display-DRiGi1zj.js.map +0 -1
  114. package/public/assets/index-CKhh9XRZ.css +0 -2
  115. package/public/assets/index-DeDosfVE.js +0 -23
  116. package/public/assets/index-DeDosfVE.js.map +0 -1
  117. package/public/assets/maintenance-DVx3SiC_.js +0 -2
  118. package/public/assets/managed-agents-DUyOTdz1.js.map +0 -1
  119. package/public/assets/overview-D9BxevAq.js +0 -2
  120. package/public/assets/registry-BLjwVXCW.js +0 -2
  121. package/public/assets/registry-BLjwVXCW.js.map +0 -1
  122. package/public/assets/settings-BR30UpbJ.js +0 -6
  123. package/public/assets/settings-BR30UpbJ.js.map +0 -1
  124. package/public/assets/store-DNbkrqbU.js +0 -9
  125. package/public/assets/store-DNbkrqbU.js.map +0 -1
  126. package/public/assets/teams-B_HSkU9T.js +0 -2
  127. package/public/assets/types-Tv67voFu.js +0 -2
  128. package/public/assets/types-Tv67voFu.js.map +0 -1
@@ -0,0 +1,2 @@
1
+ var e=/^[a-z][a-z0-9-]*$/,t=new Map;function n(n){if(!e.test(n.id))throw Error(`invalid provider id: ${n.id}`);if(!n.label.trim())throw Error(`provider ${n.id} is missing a label`);if(!n.icon.trim())throw Error(`provider ${n.id} is missing an icon`);if(t.has(n.id))throw Error(`provider already registered: ${n.id}`);t.set(n.id,n)}function r(e){return t.get(e)}function i(){return[...t.values()]}var a=[`low`,`medium`,`high`,`max`],o=[`low`,`medium`,`high`,`xhigh`,`max`],s={value:2e5,source:`catalog`,confidence:`declared`},c={value:1e6,source:`catalog`,confidence:`declared`},l={modalities:{input:{text:!0,image:!0},output:{text:!0}},tools:{code:!0,review:!0,debug:!0,refactor:!0},source:`catalog`,confidence:`declared`},u=[{name:`five_hour`,unit:`%`,reset:!0},{name:`seven_day`,unit:`%`,reset:!0}];function d(e){return{...e?{limits:e}:{},capabilities:l}}n({id:`claude`,label:`Claude`,icon:`🟠`,launch:{managedPolicyPrompt:{alwaysOn:`append-system-prompt`}},catalog:{defaultModel:`sonnet-4.6`,models:[{alias:`fable-5`,label:`Fable 5`,providerModel:`claude-fable-5`,efforts:o,defaultEffort:`medium`,unavailable:`suspended by export-control directive, 2026-06-12`,...d({contextWindowTokens:c})},{alias:`opus-4.8`,label:`Opus 4.8`,providerModel:`claude-opus-4-8`,efforts:o,defaultEffort:`medium`,...d({contextWindowTokens:s})},{alias:`opus-4.8[1m]`,label:`Opus 4.8 1M`,providerModel:`claude-opus-4-8[1m]`,efforts:o,defaultEffort:`medium`,...d({contextWindowTokens:c})},{alias:`opus-4.7`,label:`Opus 4.7`,providerModel:`claude-opus-4-7`,efforts:o,defaultEffort:`medium`,...d({contextWindowTokens:s})},{alias:`opus-4.7[1m]`,label:`Opus 4.7 1M`,providerModel:`claude-opus-4-7[1m]`,efforts:o,defaultEffort:`medium`,...d({contextWindowTokens:c})},{alias:`opus-4.6`,label:`Opus 4.6`,providerModel:`claude-opus-4-6`,efforts:a,defaultEffort:`medium`,...d({contextWindowTokens:s})},{alias:`opus-4.6[1m]`,label:`Opus 4.6 1M`,providerModel:`claude-opus-4-6[1m]`,efforts:a,defaultEffort:`medium`,...d({contextWindowTokens:c})},{alias:`sonnet-4.6`,label:`Sonnet 4.6`,providerModel:`claude-sonnet-4-6`,efforts:a,defaultEffort:`medium`,...d({contextWindowTokens:s})},{alias:`sonnet-4.6[1m]`,label:`Sonnet 4.6 1M`,providerModel:`claude-sonnet-4-6[1m]`,efforts:a,defaultEffort:`medium`,...d({contextWindowTokens:c})},{alias:`haiku`,label:`Haiku`,providerModel:`haiku`,efforts:[],...d()}]},quotaWindows:[...u],capabilityDefaults:{lifecycle:{managed:!0,shutdownHard:{when:`headless`,value:!0,otherwise:!1},restartHard:{when:`headless`,value:!0,otherwise:!1},semanticStatus:!0,reconnect:!0},session:{fileRead:!0,fileWrite:{when:`approval:not-read-only`,value:!0,otherwise:!1},shell:!0,shellMode:{open:`unrestricted`,guarded:`guarded`,readOnly:`read-only-guarded`}},context:{managed:`headless`,compact:{when:`headless`,value:!0,otherwise:!1},clear:{when:`headless`,value:!0,otherwise:!1},inject:!0,resumePreference:[`native`,`clear-inject`,`none`]},quota:{supported:!0,source:`probe`,unit:`%`,windows:[...u]},terminal:{mode:`live`,condition:`headless`,live:{read:!0,write:!0}},liveSession:{capture:!0,inject:!0,interrupt:!0,promptEcho:!0,reasoning:!0,slashCommands:!0}},slashCommands:[`/clear`,`/compact`,`/model`,`/help`],resume:{supported:!0,idFormat:`uuid-v4`,idPattern:`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`},home:{configDir:`.claude`,envPrefix:`CLAUDE`,configDirEnvVar:`CLAUDE_CONFIG_DIR`,quotaCredentialFile:`.credentials.json`,authItems:[`.credentials.json`,`statsig`]},provisioning:{pluginManifestPath:`.claude-plugin/plugin.json`,bundledPlugin:{ref:`agent-relay-claude-plugin`,dir:`runner/plugins/claude`,title:`Agent Relay Claude plugin`,description:`Bundled Agent Relay Claude plugin, materialized whole through the provisioning registry.`,omitFiles:[`monitors/relay-monitor.provisioned.mjs`],fileOverrides:[{path:`monitors/relay-monitor.ts`,sourcePath:`monitors/relay-monitor.provisioned.mjs`}]}},upgrade:{packages:[`agent-relay-runner`,`agent-relay-plugin`],detectPackages:[`agent-relay-runner`],detectCommands:[`claude`]},probe:{command:`claude`,args:[`--version`],healthCheck:`claude --version`,featureChecks:[{name:`rig`,command:`claude-rig`,homeDir:`.claude-rig`}]},quotaPoll:{strategy:`none`},shim:{kind:`claude-relay`}});var f=[`low`,`medium`,`high`,`xhigh`],p={value:2e5,source:`catalog`,confidence:`declared`},m={modalities:{input:{text:!0,image:!0},output:{text:!0}},tools:{code:!0,review:!0,debug:!0,refactor:!0},source:`catalog`,confidence:`declared`},h=[{name:`primary`,unit:`%`,reset:!0},{name:`secondary`,unit:`%`,reset:!0}];function g(e){return{...e?{limits:e}:{},capabilities:m}}n({id:`codex`,label:`Codex`,icon:`⌨️`,launch:{managedPolicyPrompt:{alwaysOn:`prompt`}},catalog:{defaultModel:`gpt-5.5`,models:[{alias:`gpt-5.5`,label:`GPT-5.5`,providerModel:`gpt-5.5`,efforts:f,defaultEffort:`medium`,...g({contextWindowTokens:p})},{alias:`gpt-5.4`,label:`GPT-5.4`,providerModel:`gpt-5.4`,efforts:f,defaultEffort:`medium`,...g({contextWindowTokens:p})},{alias:`gpt-5.4-mini`,label:`GPT-5.4 Mini`,providerModel:`gpt-5.4-mini`,efforts:f,defaultEffort:`medium`,...g({contextWindowTokens:p})},{alias:`gpt-5.3-codex`,label:`GPT-5.3 Codex`,providerModel:`gpt-5.3-codex`,efforts:f,defaultEffort:`medium`,...g({contextWindowTokens:p})},{alias:`gpt-5.3-codex-spark`,label:`GPT-5.3 Codex Spark`,providerModel:`gpt-5.3-codex-spark`,efforts:f,defaultEffort:`high`,...g({contextWindowTokens:p})},{alias:`gpt-5.2`,label:`GPT-5.2`,providerModel:`gpt-5.2`,efforts:f,defaultEffort:`medium`,...g({contextWindowTokens:p})}]},quotaWindows:[...h],capabilityDefaults:{lifecycle:{managed:!0,shutdownHard:!0,restartHard:!0,semanticStatus:!0,reconnect:!0},session:{fileRead:!0,fileWrite:{when:`approval:not-read-only`,value:!0,otherwise:!1},shell:{when:`approval:not-read-only`,value:!0,otherwise:!1},shellMode:{open:`unrestricted`,guarded:`guarded`,readOnly:`none`}},context:{managed:`always`,compact:!0,clear:!0,inject:!0,resumePreference:[`native`,`clear-inject`,`none`]},quota:{supported:!0,source:`probe`,unit:`%`,windows:[...h]},terminal:{mode:`attach`,attach:{create:!0,read:!0,write:!0,detach:!0}},liveSession:{capture:!0,inject:!0,interrupt:!0,promptEcho:!0,reasoning:!0,slashCommands:!0}},slashCommands:[`/compact`,`/new`],resume:{supported:!0,idFormat:`codex-thread-id`,idPattern:`[\\w-]+`},home:{configDir:`.codex`,envPrefix:`CODEX`,configDirEnvVar:`CODEX_HOME`,quotaCredentialFile:`auth.json`,authItems:[`auth.json`,`installation_id`]},provisioning:{pluginManifestPath:`.codex-plugin/plugin.json`,bundledSkills:{refPrefix:`agent-relay-codex-`,dir:`runner/plugins/codex/skills`,titlePrefix:`Agent Relay Codex skill: `,description:`Bundled Agent Relay Codex skill, materialized through the provisioning registry.`,entryFile:`SKILL.md`}},upgrade:{packages:[`agent-relay-runner`,`agent-relay-codex`],detectPackages:[`agent-relay-runner`,`agent-relay-codex`],detectCommands:[`codex-relay`,`agent-relay-runner`]},probe:{command:`codex`,args:[`--version`],healthCheck:`codex --version`},quotaPoll:{strategy:`codex-app-server`},shim:{kind:`codex-relay`,extraBinPaths:[`codex/bin/codex`],cleanLegacyHooks:!0}});var _=[`bg-amber-500/10 text-amber-400 border-amber-500/30`,`bg-sky-500/10 text-sky-400 border-sky-500/30`,`bg-violet-500/10 text-violet-400 border-violet-500/30`,`bg-emerald-500/10 text-emerald-400 border-emerald-500/30`,`bg-rose-500/10 text-rose-400 border-rose-500/30`];function v(e){let t=0;for(let n=0;n<e.length;n++)t=t*31+e.charCodeAt(n)>>>0;return t}var y=new Map(i().map((e,t)=>[e.id,_[t%_.length]]));function b(e){return r(e)?.label??e.charAt(0).toUpperCase()+e.slice(1)}function x(e){let t=y.get(e);if(t)return t;let n=_[v(e)%_.length];return y.set(e,n),n}function S(){return i().map(e=>e.id)}function C(e){return r(e)}function w(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function T(e){if(typeof e!=`string`)return;let t=e.trim();return t.length>0?t:void 0}function E(e){return e instanceof Error?e.message:String(e)}var D=[`cleaned`,`merged`,`abandoned`],O={"admin:*":`system:admin`,"system:write":`system:admin`,"agents:read":`agent:read`,"agents:write":`agent:write`,"messages:read":`message:read`,"messages:write":`message:send`,"tasks:read":`task:read`,"tasks:write":`task:write`,"commands:read":`command:read`,"commands:write":`command:write`,"channels:read":`channel:read`,"channels:write":`channel:write`,"integrations:read":`integration:read`,"integrations:write":`integration:write`,"connectors:read":`integration:read`,"connectors:write":`integration:write`,"events:create":`integration:write`,"tasks:create":`task:write`};function k(e){return O[e]??e}function A(e,t){let n=k(t),r=j(n);if(!r)return!1;if(e?.includes(`*`))return!0;for(let t of e??[]){let e=k(t),i=j(e);if(i&&(e===`system:admin`||e===n||e===`${r.category}:*`||r.category===`settings`&&r.action&&r.namespace&&i.category===`settings`&&i.action===r.action&&i.namespace===void 0))return!0}return!1}function j(e){if(e===`*`)return{category:`*`};let t=e.split(`:`);if(t.some(e=>!e))return null;if(t.length===2){let[e,n]=t;return{category:e,action:n}}if(t.length===3){let[e,n,r]=t;return e!==`settings`||n!==`read`&&n!==`write`||r===`*`?null:{category:e,action:n,namespace:r}}return null}var M=`default`;function N(e){return e===`user`||typeof e==`string`&&e.startsWith(`user:`)}function P(e){if(e===`user`)return M;if(typeof e==`string`&&e.startsWith(`user:`))return e.slice(5)||`default`}i().map(e=>e.id);export{E as a,S as c,C as d,i as f,D as i,x as l,P as n,w as o,A as r,T as s,N as t,b as u};
2
+ //# sourceMappingURL=types-DorDDvEb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-DorDDvEb.js","names":[],"sources":["../../providers/registry.ts","../../providers/claude.ts","../../providers/codex.ts","../../dashboard/src/lib/provider-display.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","../../sdk/src/types/agents.ts"],"sourcesContent":["import type { ProviderManifest } from \"./types.js\";\n\nconst PROVIDER_ID_RE = /^[a-z][a-z0-9-]*$/;\nconst manifests = new Map<string, ProviderManifest>();\n\nexport function registerProvider(manifest: ProviderManifest): void {\n if (!PROVIDER_ID_RE.test(manifest.id)) {\n throw new Error(`invalid provider id: ${manifest.id}`);\n }\n if (!manifest.label.trim()) {\n throw new Error(`provider ${manifest.id} is missing a label`);\n }\n if (!manifest.icon.trim()) {\n throw new Error(`provider ${manifest.id} is missing an icon`);\n }\n if (manifests.has(manifest.id)) {\n throw new Error(`provider already registered: ${manifest.id}`);\n }\n manifests.set(manifest.id, manifest);\n}\n\nexport function getManifest(id: string): ProviderManifest | undefined {\n return manifests.get(id);\n}\n\nexport function getAllManifests(): ProviderManifest[] {\n return [...manifests.values()];\n}\n\nexport function isValidResumeId(providerId: string, id: string): boolean {\n if (!id) return false;\n const manifest = manifests.get(providerId);\n if (!manifest?.resume?.supported) return false;\n const pattern = manifest.resume.idPattern;\n if (!pattern) return true;\n return new RegExp(`^${pattern}$`, \"i\").test(id);\n}\n\nexport function resumeLogExtractionRe(providerId: string): RegExp | undefined {\n const manifest = manifests.get(providerId);\n if (!manifest?.resume?.supported || !manifest.resume.idPattern || !manifest.probe?.command) return undefined;\n return new RegExp(`\\\\b${manifest.probe.command}\\\\s+--resume\\\\s+(${manifest.resume.idPattern})\\\\b`, \"gi\");\n}\n\ntype AccountKeyAliasResolver = (accountKey: string) => string[];\nconst accountKeyAliasResolvers = new Map<string, AccountKeyAliasResolver>();\n\nexport function registerAccountKeyAliases(providerId: string, resolver: AccountKeyAliasResolver): void {\n accountKeyAliasResolvers.set(providerId, resolver);\n}\n\nexport function quotaAccountKeyAliases(providerId: string, accountKey: string): string[] {\n return accountKeyAliasResolvers.get(providerId)?.(accountKey) ?? [];\n}\n\nexport const PROVIDER_MANIFESTS: ReadonlyMap<string, ProviderManifest> = manifests;\n","import { registerProvider } from \"./registry.js\";\nimport type { ProviderManifest, ProviderModelCapabilities, ProviderModelLimits } from \"./types.js\";\n\nconst CLAUDE_LOW_TO_MAX = [\"low\", \"medium\", \"high\", \"max\"];\nconst CLAUDE_LOW_TO_XHIGH_MAX = [\"low\", \"medium\", \"high\", \"xhigh\", \"max\"];\nconst CONTEXT_200K = { value: 200_000, source: \"catalog\", confidence: \"declared\" } as const;\nconst CONTEXT_1M = { value: 1_000_000, source: \"catalog\", confidence: \"declared\" } as const;\nconst CODE_MODEL_CAPABILITIES: ProviderModelCapabilities = {\n modalities: {\n input: { text: true, image: true },\n output: { text: true },\n },\n tools: {\n code: true,\n review: true,\n debug: true,\n refactor: true,\n },\n source: \"catalog\",\n confidence: \"declared\",\n};\nconst quotaWindows = [\n { name: \"five_hour\", unit: \"%\", reset: true },\n { name: \"seven_day\", unit: \"%\", reset: true },\n] as const;\n\nfunction codeModel(limits?: ProviderModelLimits): Pick<NonNullable<ProviderManifest[\"catalog\"][\"models\"][number]>, \"limits\" | \"capabilities\"> {\n return {\n ...(limits ? { limits } : {}),\n capabilities: CODE_MODEL_CAPABILITIES,\n };\n}\n\nexport const claudeProviderManifest = {\n id: \"claude\",\n label: \"Claude\",\n icon: \"🟠\",\n launch: {\n managedPolicyPrompt: {\n alwaysOn: \"append-system-prompt\",\n },\n },\n catalog: {\n defaultModel: \"sonnet-4.6\",\n models: [\n { alias: \"fable-5\", label: \"Fable 5\", providerModel: \"claude-fable-5\", efforts: CLAUDE_LOW_TO_XHIGH_MAX, defaultEffort: \"medium\", unavailable: \"suspended by export-control directive, 2026-06-12\", ...codeModel({ contextWindowTokens: CONTEXT_1M }) },\n { alias: \"opus-4.8\", label: \"Opus 4.8\", providerModel: \"claude-opus-4-8\", efforts: CLAUDE_LOW_TO_XHIGH_MAX, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_200K }) },\n { alias: \"opus-4.8[1m]\", label: \"Opus 4.8 1M\", providerModel: \"claude-opus-4-8[1m]\", efforts: CLAUDE_LOW_TO_XHIGH_MAX, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_1M }) },\n { alias: \"opus-4.7\", label: \"Opus 4.7\", providerModel: \"claude-opus-4-7\", efforts: CLAUDE_LOW_TO_XHIGH_MAX, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_200K }) },\n { alias: \"opus-4.7[1m]\", label: \"Opus 4.7 1M\", providerModel: \"claude-opus-4-7[1m]\", efforts: CLAUDE_LOW_TO_XHIGH_MAX, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_1M }) },\n { alias: \"opus-4.6\", label: \"Opus 4.6\", providerModel: \"claude-opus-4-6\", efforts: CLAUDE_LOW_TO_MAX, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_200K }) },\n { alias: \"opus-4.6[1m]\", label: \"Opus 4.6 1M\", providerModel: \"claude-opus-4-6[1m]\", efforts: CLAUDE_LOW_TO_MAX, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_1M }) },\n { alias: \"sonnet-4.6\", label: \"Sonnet 4.6\", providerModel: \"claude-sonnet-4-6\", efforts: CLAUDE_LOW_TO_MAX, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_200K }) },\n { alias: \"sonnet-4.6[1m]\", label: \"Sonnet 4.6 1M\", providerModel: \"claude-sonnet-4-6[1m]\", efforts: CLAUDE_LOW_TO_MAX, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_1M }) },\n { alias: \"haiku\", label: \"Haiku\", providerModel: \"haiku\", efforts: [], ...codeModel() },\n ],\n },\n quotaWindows: [...quotaWindows],\n capabilityDefaults: {\n lifecycle: {\n managed: true,\n shutdownHard: { when: \"headless\", value: true, otherwise: false },\n restartHard: { when: \"headless\", value: true, otherwise: false },\n semanticStatus: true,\n reconnect: true,\n },\n session: {\n fileRead: true,\n fileWrite: { when: \"approval:not-read-only\", value: true, otherwise: false },\n shell: true,\n shellMode: { open: \"unrestricted\", guarded: \"guarded\", readOnly: \"read-only-guarded\" },\n },\n context: {\n managed: \"headless\",\n compact: { when: \"headless\", value: true, otherwise: false },\n clear: { when: \"headless\", value: true, otherwise: false },\n inject: true,\n resumePreference: [\"native\", \"clear-inject\", \"none\"],\n },\n quota: {\n supported: true,\n source: \"probe\",\n unit: \"%\",\n windows: [...quotaWindows],\n },\n terminal: {\n mode: \"live\",\n condition: \"headless\",\n live: { read: true, write: true },\n },\n liveSession: {\n capture: true,\n inject: true,\n interrupt: true,\n promptEcho: true,\n reasoning: true,\n slashCommands: true,\n },\n },\n slashCommands: [\"/clear\", \"/compact\", \"/model\", \"/help\"],\n resume: { supported: true, idFormat: \"uuid-v4\", idPattern: \"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\" },\n home: { configDir: \".claude\", envPrefix: \"CLAUDE\", configDirEnvVar: \"CLAUDE_CONFIG_DIR\", quotaCredentialFile: \".credentials.json\", authItems: [\".credentials.json\", \"statsig\"] },\n provisioning: {\n pluginManifestPath: \".claude-plugin/plugin.json\",\n bundledPlugin: {\n ref: \"agent-relay-claude-plugin\",\n dir: \"runner/plugins/claude\",\n title: \"Agent Relay Claude plugin\",\n description: \"Bundled Agent Relay Claude plugin, materialized whole through the provisioning registry.\",\n omitFiles: [\"monitors/relay-monitor.provisioned.mjs\"],\n fileOverrides: [{ path: \"monitors/relay-monitor.ts\", sourcePath: \"monitors/relay-monitor.provisioned.mjs\" }],\n },\n },\n upgrade: {\n packages: [\"agent-relay-runner\", \"agent-relay-plugin\"],\n detectPackages: [\"agent-relay-runner\"],\n detectCommands: [\"claude\"],\n },\n probe: {\n command: \"claude\",\n args: [\"--version\"],\n healthCheck: \"claude --version\",\n featureChecks: [{ name: \"rig\", command: \"claude-rig\", homeDir: \".claude-rig\" }],\n },\n quotaPoll: { strategy: \"none\" },\n shim: { kind: \"claude-relay\" },\n} satisfies ProviderManifest;\n\nregisterProvider(claudeProviderManifest);\n","import { registerProvider } from \"./registry.js\";\nimport type { ProviderManifest, ProviderModelCapabilities, ProviderModelLimits } from \"./types.js\";\n\nconst CODEX_REASONING = [\"low\", \"medium\", \"high\", \"xhigh\"];\nconst CONTEXT_200K = { value: 200_000, source: \"catalog\", confidence: \"declared\" } as const;\nconst CODE_MODEL_CAPABILITIES: ProviderModelCapabilities = {\n modalities: {\n input: { text: true, image: true },\n output: { text: true },\n },\n tools: {\n code: true,\n review: true,\n debug: true,\n refactor: true,\n },\n source: \"catalog\",\n confidence: \"declared\",\n};\nconst quotaWindows = [\n { name: \"primary\", unit: \"%\", reset: true },\n { name: \"secondary\", unit: \"%\", reset: true },\n] as const;\n\nfunction codeModel(limits?: ProviderModelLimits): Pick<NonNullable<ProviderManifest[\"catalog\"][\"models\"][number]>, \"limits\" | \"capabilities\"> {\n return {\n ...(limits ? { limits } : {}),\n capabilities: CODE_MODEL_CAPABILITIES,\n };\n}\n\nexport const codexProviderManifest = {\n id: \"codex\",\n label: \"Codex\",\n icon: \"⌨️\",\n launch: {\n managedPolicyPrompt: {\n alwaysOn: \"prompt\",\n },\n },\n catalog: {\n defaultModel: \"gpt-5.5\",\n models: [\n { alias: \"gpt-5.5\", label: \"GPT-5.5\", providerModel: \"gpt-5.5\", efforts: CODEX_REASONING, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_200K }) },\n { alias: \"gpt-5.4\", label: \"GPT-5.4\", providerModel: \"gpt-5.4\", efforts: CODEX_REASONING, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_200K }) },\n { alias: \"gpt-5.4-mini\", label: \"GPT-5.4 Mini\", providerModel: \"gpt-5.4-mini\", efforts: CODEX_REASONING, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_200K }) },\n { alias: \"gpt-5.3-codex\", label: \"GPT-5.3 Codex\", providerModel: \"gpt-5.3-codex\", efforts: CODEX_REASONING, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_200K }) },\n { alias: \"gpt-5.3-codex-spark\", label: \"GPT-5.3 Codex Spark\", providerModel: \"gpt-5.3-codex-spark\", efforts: CODEX_REASONING, defaultEffort: \"high\", ...codeModel({ contextWindowTokens: CONTEXT_200K }) },\n { alias: \"gpt-5.2\", label: \"GPT-5.2\", providerModel: \"gpt-5.2\", efforts: CODEX_REASONING, defaultEffort: \"medium\", ...codeModel({ contextWindowTokens: CONTEXT_200K }) },\n ],\n },\n quotaWindows: [...quotaWindows],\n capabilityDefaults: {\n lifecycle: {\n managed: true,\n shutdownHard: true,\n restartHard: true,\n semanticStatus: true,\n reconnect: true,\n },\n session: {\n fileRead: true,\n fileWrite: { when: \"approval:not-read-only\", value: true, otherwise: false },\n shell: { when: \"approval:not-read-only\", value: true, otherwise: false },\n shellMode: { open: \"unrestricted\", guarded: \"guarded\", readOnly: \"none\" },\n },\n context: {\n managed: \"always\",\n compact: true,\n clear: true,\n inject: true,\n resumePreference: [\"native\", \"clear-inject\", \"none\"],\n },\n quota: {\n supported: true,\n source: \"probe\",\n unit: \"%\",\n windows: [...quotaWindows],\n },\n terminal: {\n mode: \"attach\",\n attach: {\n create: true,\n read: true,\n write: true,\n detach: true,\n },\n },\n liveSession: {\n capture: true,\n inject: true,\n interrupt: true,\n promptEcho: true,\n reasoning: true,\n slashCommands: true,\n },\n },\n slashCommands: [\"/compact\", \"/new\"],\n resume: { supported: true, idFormat: \"codex-thread-id\", idPattern: \"[\\\\w-]+\" },\n home: { configDir: \".codex\", envPrefix: \"CODEX\", configDirEnvVar: \"CODEX_HOME\", quotaCredentialFile: \"auth.json\", authItems: [\"auth.json\", \"installation_id\"] },\n provisioning: {\n pluginManifestPath: \".codex-plugin/plugin.json\",\n bundledSkills: {\n refPrefix: \"agent-relay-codex-\",\n dir: \"runner/plugins/codex/skills\",\n titlePrefix: \"Agent Relay Codex skill: \",\n description: \"Bundled Agent Relay Codex skill, materialized through the provisioning registry.\",\n entryFile: \"SKILL.md\",\n },\n },\n upgrade: {\n packages: [\"agent-relay-runner\", \"agent-relay-codex\"],\n detectPackages: [\"agent-relay-runner\", \"agent-relay-codex\"],\n detectCommands: [\"codex-relay\", \"agent-relay-runner\"],\n },\n probe: {\n command: \"codex\",\n args: [\"--version\"],\n healthCheck: \"codex --version\",\n },\n quotaPoll: { strategy: \"codex-app-server\" },\n shim: { kind: \"codex-relay\", extraBinPaths: [\"codex/bin/codex\"], cleanLegacyHooks: true },\n} satisfies ProviderManifest;\n\nregisterProvider(codexProviderManifest);\n","import { getAllManifests, getManifest } from 'agent-relay-providers'\nimport type { ProviderManifest } from 'agent-relay-providers'\n\nconst BADGE_PALETTE = [\n 'bg-amber-500/10 text-amber-400 border-amber-500/30',\n 'bg-sky-500/10 text-sky-400 border-sky-500/30',\n 'bg-violet-500/10 text-violet-400 border-violet-500/30',\n 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30',\n 'bg-rose-500/10 text-rose-400 border-rose-500/30',\n]\n\nfunction stableHash(s: string): number {\n let h = 0\n for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0\n return h\n}\n\nconst badgeClassCache = new Map<string, string>(\n getAllManifests().map((m, i) => [m.id, BADGE_PALETTE[i % BADGE_PALETTE.length]!])\n)\n\nexport function getProviderLabel(id: string): string {\n return getManifest(id)?.label ?? id.charAt(0).toUpperCase() + id.slice(1)\n}\n\nexport function getProviderIcon(id: string): string {\n return getManifest(id)?.icon ?? '🔧'\n}\n\nexport function getProviderBadgeClass(id: string): string {\n const cached = badgeClassCache.get(id)\n if (cached) return cached\n const cls = BADGE_PALETTE[stableHash(id) % BADGE_PALETTE.length]!\n badgeClassCache.set(id, cls)\n return cls\n}\n\nexport function getKnownProviderIds(): string[] {\n return getAllManifests().map((m) => m.id)\n}\n\nexport function getProviderManifest(id: string): ProviderManifest | undefined {\n return getManifest(id)\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 /** Resolved agent-profile NAME stamped on a runner token at spawn (#559). Identity-only,\n * like projectId — it lets the scoped-memory tools resolve the caller's OWN profile store\n * (self-scoped writes: an agent writes only its own profile's memory, never another's).\n * Absent for non-spawned/human tokens. */\n profile?: 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 /** Relay tool group filter (#551): only tools in these groups are listed at tools/list. Absent = full surface. */\n toolGroups?: string[];\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 { getAllManifests } from \"agent-relay-providers\";\nimport type { WorkspaceMode, WorkspaceLandingPolicy, WorkspaceMetadata, WorkspaceStatus } from \"./workspaces.js\";\nimport type { MemoryType, Memory, TaskRoutingHints, ContextBudget } from \"./memory.js\";\nimport type { Artifact } from \"./artifacts.js\";\n\nexport type AgentKind = \"provider\" | \"channel\" | \"orchestrator\" | \"system\" | \"user\";\n\nexport interface AgentCard {\n id: string;\n name: string;\n kind: AgentKind;\n label?: string; // human-friendly alias; acts as a fan-out target (\"label:foo\")\n tags: string[];\n machine?: string;\n rig?: string;\n capabilities: string[];\n ready: boolean;\n status: AgentStatus;\n instanceId?: string;\n epoch: number;\n providerCapabilities?: ProviderCapabilities;\n context?: ContextState;\n quota?: QuotaState;\n meta?: import(\"./provider-state.js\").AgentMeta;\n lifecycle: AgentLifecycle;\n /** Authoritative parent id from the child's signed runner token; powers quotas, `spawnedBy:`, scoped shutdown, and no-grandchildren. */\n spawnedBy?: string;\n /** Durable first-class team id (`team_<id>`) shared by a coordinator and its spawned members. */\n teamId?: string;\n /** Server-derived merge-back badge for the owned isolated worktree (#236); absent for non-branch agents. */\n branchState?: BranchState;\n /** Live isolated workspace id backing `branchState`, for dashboard deep links (#236). */\n branchWorkspaceId?: string;\n /** Server-derived `owner/repo` of the agent's project (cwd→project→issueRepo), so the\n * dashboard linkifies a bare `#NNN` against the originating project's repo (#533). */\n issueRepo?: string;\n lastSeen: number;\n createdAt: number;\n}\n\nexport type AgentStatus = \"online\" | \"idle\" | \"busy\" | \"stale\" | \"offline\";\n\nexport const VALID_AGENT_LIFECYCLES = [\"persistent\", \"transient\"] as const;\nexport type AgentLifecycle = (typeof VALID_AGENT_LIFECYCLES)[number];\nexport const isAgentLifecycle = (value: unknown): value is AgentLifecycle => typeof value === \"string\" && (VALID_AGENT_LIFECYCLES as readonly string[]).includes(value);\nexport const normalizeAgentLifecycle = (value: unknown): AgentLifecycle | undefined => isAgentLifecycle(value) ? value : undefined;\n\n/** Branch-workspace state for an isolated-workspace owner (#236), derived from `WorkspaceStatus` + claim + git ahead/dirty.\n * - `idle` ⚪ active worktree, nothing to land (0 ahead, clean tree)\n * - `changes` 🟡 active worktree with commits/dirty work — human can mark it ready\n * - `ready` 🔵 handed off; the relay auto-merge will land it (~2 min sweep)\n * - `steward` 🟠 under reconciliation (a steward holds it, or conflict/merge in flight)\n * - `blocked` 🔴 escalated/failed — needs human attention\n */\nexport type BranchState = \"idle\" | \"changes\" | \"ready\" | \"steward\" | \"blocked\";\n/** All BranchState values, for exhaustive UI maps and validation. */\nexport const BRANCH_STATE_VALUES = [\"idle\", \"changes\", \"ready\", \"steward\", \"blocked\"] as const satisfies readonly BranchState[];\n\nexport type PlanStatus = \"pending\" | \"in-progress\" | \"landed\" | \"blocked\";\nexport type PlanOwnerRef = { agentId?: string; policyName?: string; label?: string };\n\nexport interface PlanNode {\n id: string;\n label: string;\n scopeBlurb?: string;\n ownerRef?: PlanOwnerRef;\n lane?: string;\n status: PlanStatus;\n statusSource: \"manual\" | \"bound\";\n refs?: { agentId?: string; taskId?: string; issueId?: string; landingRef?: string };\n blackboardRefs?: string[];\n}\n\nexport interface PlanEdge {\n from: string;\n to: string;\n}\n\nexport interface Plan {\n id: string;\n threadId: string;\n teamId?: string;\n channel?: string;\n version: number;\n title?: string;\n objective?: string;\n nodes: PlanNode[];\n edges: PlanEdge[];\n createdBy: string;\n createdAt: number;\n updatedAt: number;\n}\n\nexport type TeamRuntimeState = \"busy\" | \"idle\" | \"degraded\" | \"stalled\" | \"dissolved\";\n\nexport interface Team {\n id: string;\n coordinatorId?: string;\n rootCoordinatorId?: string;\n title?: string;\n brief?: string;\n definitionOfDone?: string;\n status: \"active\" | \"dissolved\";\n outcomeStatus?: \"success\" | \"partial\" | \"failed\";\n outcomeSummary?: string;\n composition?: string;\n costTokens?: number;\n costUsd?: number;\n wallClockMs?: number;\n landsClean?: boolean;\n zeroMembersSince?: number;\n runtimeState?: TeamRuntimeState;\n createdAt: number;\n updatedAt: number;\n dissolvedAt?: number;\n}\n\nexport interface TeamTemplateCompositionEntry { label: string; provider: SpawnProvider; model?: string; effort?: SpawnEffort; profile?: string; count: number; }\nexport interface TeamTemplatePlanSkeleton { nodes?: PlanNode[]; edges?: PlanEdge[]; } export interface TeamTemplate { id: string; slug: string; title: string; description?: string; composition: TeamTemplateCompositionEntry[]; brief?: string; definitionOfDone?: string; planSkeleton?: TeamTemplatePlanSkeleton; createdAt?: string; updatedAt?: string; }\nexport type BlackboardEntryKind = \"decision\" | \"fact\" | \"ruled_out\"; export type BlackboardEntryStatus = \"active\" | \"superseded\" | \"retracted\";\n\nexport interface BlackboardEntry {\n id: string;\n teamId: string;\n kind: BlackboardEntryKind;\n title: string;\n body?: string;\n status: BlackboardEntryStatus;\n supersedes?: string;\n tags: string[];\n authorId: string;\n createdAt: number;\n updatedAt: number;\n}\n\n// Projects (#403/#405) — the DURABLE knowledge scope (outlives every team). The\n// data shape is a near-exact re-scope of the Team/BlackboardEntry pair above\n// (team_id → project_id); kept beside it deliberately so the parallel is obvious.\n// #559 — the knowledge-scope axis. A project anchors on repo roots (cwd→root binding);\n// a `profile` store anchors on a profile name; a `user` store on an operator identity.\n// One table, one entry/edge/cascade machine — only the anchor-resolution differs. Existing\n// rows default `scope:'project'`, so the generalization is non-regressing.\nexport const PROJECT_SCOPES = [\"project\", \"profile\", \"user\"] as const;\nexport type ProjectScope = (typeof PROJECT_SCOPES)[number];\nexport interface Project {\n id: string;\n slug: string;\n title?: string;\n description?: string;\n tenantId?: string;\n /** `owner/repo` for issue-ref linkify/land-close (#533). Auto-derived from a root's git\n * remote at seed; an explicit value set via relay_project_* wins and is never re-derived. */\n issueRepo?: string;\n /** Knowledge-scope axis (#559). `project` (default) anchors on repo roots; `profile`\n * anchors on a profile name (role memory, inherited via the contains graph); `user`\n * anchors on an operator identity (cross-project operator memory). */\n scope: ProjectScope;\n /** For `profile`/`user` scopes, the anchor key — the profile name or operator id the\n * store is bound to. Null for `project` scope (bound via project_roots instead). */\n anchorKey?: string;\n status: \"active\" | \"archived\";\n createdAt: number;\n updatedAt: number;\n}\nexport interface ProjectRoot {\n projectId: string;\n rootPath: string;\n createdAt: number;\n}\nexport const PROJECT_ENTRY_KINDS = [\"rule\", \"fact\", \"decision\", \"memory\"] as const;\nexport type ProjectEntryKind = (typeof PROJECT_ENTRY_KINDS)[number];\n// `proposed` (#559) is the curation tier: a scoped-memory capture lands here and is NOT\n// injected at spawn (only `active` entries render into a preamble); promotion flips it to\n// `active`. Project-scope writes stay `active` by default, so #403 behavior is unchanged.\nexport type ProjectEntryStatus = \"proposed\" | \"active\" | \"superseded\" | \"retracted\";\n// `manual` = written by an agent directly; `promoted` = lifted from a team's\n// blackboard with an originRef back to its bb_ id; `ingested` = bootstrapped from\n// a root's CLAUDE.md. The source lever makes going-vanilla a no-op (#403 decision 4).\nexport const PROJECT_ENTRY_SOURCES = [\"manual\", \"promoted\", \"ingested\"] as const;\nexport type ProjectEntrySource = (typeof PROJECT_ENTRY_SOURCES)[number];\nexport interface ProjectEntry {\n id: string;\n projectId: string;\n kind: ProjectEntryKind;\n title: string;\n body?: string;\n status: ProjectEntryStatus;\n supersedes?: string;\n tags: string[];\n authorId: string;\n source: ProjectEntrySource;\n originRef?: string;\n createdAt: number;\n updatedAt: number;\n}\n// Project composition graph (#406). ASYMMETRIC by design: `contains` is the\n// umbrella→sub-project hierarchy and INHERITS (resolving a project's entries pulls\n// in its transitive contains-ancestors); `depends_on` is reference-only and NEVER\n// auto-inherits (a dependency's internal rules must not flood consuming agents).\nexport const PROJECT_EDGE_KINDS = [\"contains\", \"depends_on\"] as const;\nexport type ProjectEdgeKind = (typeof PROJECT_EDGE_KINDS)[number];\nexport interface ProjectEdge {\n fromProject: string;\n toProject: string;\n kind: ProjectEdgeKind;\n createdAt: number;\n}\n// A resolved entry carries `inherited` so a caller can tell an inherited\n// contains-ancestor rule from one the project owns directly (entry.projectId is the\n// owning project either way).\nexport interface ResolvedProjectEntry extends ProjectEntry {\n inherited: boolean;\n}\n\nexport const ISSUE_PROVIDERS = [\"github\", \"jira\", \"youtrack\", \"gitlab\", \"linear\"] as const;\nexport type IssueProvider = (typeof ISSUE_PROVIDERS)[number];\nexport const ISSUE_REF_STATUSES = [\"open\", \"closed\", \"unknown\"] as const;\nexport type IssueRefStatus = (typeof ISSUE_REF_STATUSES)[number];\nexport interface IssueRef {\n id: string;\n provider: IssueProvider;\n repo: string;\n externalKey: string;\n url: string;\n title?: string;\n status: IssueRefStatus;\n statusSyncedAt?: number;\n createdAt: number;\n updatedAt: number;\n}\nexport const ISSUE_ASSOCIATION_ENTITY_TYPES = [\"team\", \"plan-node\", \"epic\"] as const;\nexport type IssueAssociationEntityType = (typeof ISSUE_ASSOCIATION_ENTITY_TYPES)[number];\nexport interface IssueAssociation {\n issueRefId: string;\n entityType: IssueAssociationEntityType;\n entityId: string;\n createdAt: number;\n}\nexport interface IssueAssociationIssue {\n association: IssueAssociation;\n issueRef: IssueRef;\n}\nexport interface IssueAssociationEntity {\n entityType: IssueAssociationEntityType;\n entityId: string;\n createdAt: number;\n}\n\ntype CapabilitySource = \"catalog\" | \"provider\" | \"runtime\" | \"override\" | \"estimate\";\ntype CapabilityConfidence = \"declared\" | \"reported\" | \"verified\" | \"estimated\" | \"unknown\";\nexport type QuotaSource = \"header\" | \"probe\" | \"ingest\" | \"statusline\" | \"emulated\";\nexport type QuotaUnit = \"tokens\" | \"requests\" | \"%\";\nexport type QuotaWindowName = string;\n\nexport interface ProviderCapabilities {\n lifecycle: {\n managed: boolean;\n shutdownHard: boolean;\n restartHard: boolean;\n semanticStatus?: boolean;\n reconnect?: boolean;\n hibernate?: boolean;\n resume?: boolean;\n };\n model?: {\n provider?: SpawnProvider | string;\n id?: string;\n alias?: string;\n providerModel?: string;\n effort?: SpawnEffort | string;\n source: CapabilitySource;\n confidence: CapabilityConfidence;\n lastUpdatedAt?: number;\n };\n session?: {\n approvalMode?: SpawnApprovalMode | string;\n fileRead?: boolean;\n fileWrite?: boolean;\n shell?: boolean;\n shellMode?: \"none\" | \"unrestricted\" | \"guarded\" | \"read-only-guarded\" | string;\n source: CapabilitySource;\n confidence: CapabilityConfidence;\n lastUpdatedAt?: number;\n };\n context?: {\n stats?: {\n source: \"api\" | \"statusline\" | \"hook\" | \"estimate\";\n confidence: \"exact\" | \"reported\" | \"estimated\";\n };\n windowTokens?: number;\n compact?: boolean;\n clear?: boolean;\n inject?: boolean;\n resume: \"clear-inject\" | \"native\" | \"none\";\n fork?: boolean;\n rollback?: boolean;\n archive?: boolean;\n };\n quota?: {\n supported: boolean;\n source: QuotaSource;\n unit: QuotaUnit;\n windows?: Array<{\n name: string;\n unit: QuotaUnit;\n reset?: boolean;\n }>;\n };\n liveSession?: {\n capture?: boolean;\n inject?: boolean;\n /** Provider can be interrupted mid-turn from the dashboard (ESC / turn-interrupt). */\n interrupt?: boolean;\n /** Prompts typed directly into the provider (web terminal/TUI) are mirrored into chat. */\n promptEcho?: boolean;\n /** Reasoning/tool activity steps are surfaced to chat as discreet session events. */\n reasoning?: boolean;\n /** Provider exposes a slash-command catalog for the chat input palette. */\n slashCommands?: boolean;\n };\n terminal?: {\n live?: {\n read?: boolean;\n write?: boolean;\n };\n attach?: {\n create?: boolean;\n read?: boolean;\n write?: boolean;\n detach?: boolean;\n };\n };\n source: CapabilitySource;\n confidence: CapabilityConfidence;\n lastUpdatedAt: number;\n}\n\nexport interface QuotaState {\n windows: Array<{\n name: QuotaWindowName;\n utilization: number;\n resetsAt?: number;\n unit: QuotaUnit;\n }>;\n source: QuotaSource;\n updatedAt: number;\n}\n\n/**\n * Normalized liveness verdict — the Runner↔provider contract field for \"is this busy\n * agent actually alive, or wedged?\" (epic #725; LOCKED by #746, the proof-of-contract).\n *\n * Computed by the RUNNER (each provider adapter folds its own raw transport/timeline/\n * token/work-kind signals down into this verdict) and shipped on the agent card at\n * `meta.liveness`. The server + dashboard read ONLY these three fields — never the raw\n * signals they replace (`meta.transport.*`, `meta.timelineEvent`, `meta.lastProviderEventAt`,\n * `meta.activeWork[].kind`). This single declared field collapses the six raw `meta` reads\n * that the stuck-busy detector grew across #713 (Claude transport heartbeat) and #724\n * (Codex subprocess / fresh-lastSeen veto) — the \"patched twice\" leak, since the same\n * derivation had been re-implemented client-side in the dashboard.\n *\n * The server keeps only THRESHOLD POLICY: it never re-interprets provider runtime signals\n * and contains no `provider === …` branch. A future provider quirk becomes one adapter\n * change behind this stable shape.\n */\nexport interface LivenessSignal {\n /**\n * Epoch-ms of the most recent proof the runner→server transport is alive — the runner's\n * max of its frame / heartbeat-ack timestamps. The SERVER folds its own server-stamped\n * `AgentCard.lastSeen` in as a floor when reading (the runner cannot know `lastSeen`), so\n * a fresh heartbeat on EITHER side proves liveness. A fresh value is the authoritative\n * veto: provider-token silence alone (long thinking turns, native compaction, multi-minute\n * subprocesses) never triggers stuck-busy while the link is demonstrably alive (#713/#724).\n */\n transportLiveAt: number;\n /**\n * Epoch-ms of the most recent evidence of FORWARD PROGRESS, folding (whichever is latest):\n * provider tokens, a lifecycle/timeline transition (e.g. Claude native `compacting`, which\n * is token-silent yet progressing — #713), or live tool/subprocess work (Codex #724). The\n * adapter decides what counts as progress for its provider; the server only compares it\n * against the no-progress threshold.\n */\n lastProgressAt: number;\n /**\n * True while any tool/subprocess work-kind is in flight (e.g. `bun test`, `build`,\n * `typecheck`) — i.e. active work that is NOT the bare provider turn. The adapter knows\n * its own work-kinds and classifies them; the server MUST NOT enumerate them. Live work\n * keeps the agent out of stuck-busy regardless of provider-token silence.\n */\n hasLiveWork: boolean;\n}\n\n/**\n * A structured question surfaced inside an {@link InteractivePrompt} (Claude\n * AskUserQuestion). The dashboard renders these as a form and submits the chosen\n * option label(s) back as an `answer` decision (ProviderPermissionDecisionInput).\n */\nexport interface ProviderQuestion {\n question: string;\n header?: string;\n options: { label: string; description?: string }[];\n multiSelect?: boolean;\n}\n\n/**\n * The kind of interactive prompt an adapter surfaces. The §4.2 normalized set is\n * questions|plan|command|tool|approval; the Codex app-server additionally tags its\n * approval requests file-change|permission. The server/dashboard treat `kind` as a\n * display hint only — they never branch liveness, ordering, or resolution on it.\n */\nexport type InteractivePromptKind =\n | \"questions\"\n | \"plan\"\n | \"command\"\n | \"tool\"\n | \"approval\"\n | \"file-change\"\n | \"permission\";\n\n/**\n * Normalized interactive-prompt surface — the Runner↔provider contract field for \"the\n * agent is blocked waiting on a human decision\" (epic #725, §4.2; #435/#723). Formalizes\n * what used to be an untyped `providerState.pendingApproval` bag with a per-provider\n * `provider` tag the server never needed.\n *\n * Built by the RUNNER adapter (Claude's PreToolUse hook view; the Codex app-server\n * approval view) and shipped on `meta.providerState.pendingApproval`. Resolution rides\n * the existing ProviderPermissionDecisionInput (approve|approve-session|deny|abort|answer)\n * — the server persists the blocked state and clears it on the decision; it never\n * re-derives the prompt or its ordering.\n */\nexport interface InteractivePrompt {\n id: string;\n kind: InteractivePromptKind;\n title: string;\n body: string;\n /** Present for kind \"questions\" — the structured AskUserQuestion form. */\n questions?: ProviderQuestion[];\n /** Approve/deny-style choices the dashboard renders as buttons. */\n choices?: { id: string; label: string }[];\n /**\n * ORDERING INVARIANT (#435/#723): `true` asserts the adapter has already mirrored\n * every reasoning/narrative step of the turn into the session stream BEFORE this\n * prompt surfaced. The adapter OWNS the guarantee via its own mechanism — Claude\n * pre-flushes the transcript narrative at the PreToolUse hook seam; Codex flushes its\n * buffered reasoning/narration before emitting the approval — and stamps this only\n * once that flush has run. The server/dashboard render in arrival order and never\n * re-sort, so the reasoning is already present above the form. A literal `true`\n * because a prompt that cannot make this guarantee must not be surfaced as one.\n */\n reasoningSettled: true;\n}\n\nexport type QuotaSnapshot = {\n agentId: string;\n capturedAt: number;\n} & QuotaState;\n\ntype ContextLifecycleState =\n | \"fresh\"\n | \"primed\"\n | \"working\"\n | \"cooling\"\n | \"compacting\"\n | \"clearing-context\"\n | \"hibernating\";\n\nexport interface ContextProbeMetrics {\n agentId: string;\n contextPercent: number;\n tokensUsed?: number;\n tokensMax?: number;\n quotaUsed?: number;\n quotaLimit?: number;\n quotaResetIn?: number;\n quotaWindows?: Array<{\n name: QuotaWindowName;\n used?: number;\n limit?: number;\n utilization?: number;\n resetIn?: number;\n resetsAt?: number;\n unit?: QuotaUnit;\n }>;\n model?: string;\n effort?: string;\n source: \"statusline\" | \"hook\" | \"api\" | \"estimate\";\n confidence: \"exact\" | \"reported\" | \"estimated\";\n timestamp: number;\n}\n\nexport interface ContextState {\n utilization: number;\n tokensUsed?: number;\n tokensMax?: number;\n lifecycleState: ContextLifecycleState;\n warmTopics: string[];\n activeMemories: string[];\n tasksSinceCompact: number;\n lastCompactedAt?: number; lastContextAdvisoryBand?: number; lastContextAdvisoryAt?: number;\n lastUpdatedAt: number;\n source: \"statusline\" | \"hook\" | \"api\" | \"estimate\";\n confidence: \"exact\" | \"reported\" | \"estimated\";\n}\nexport interface ContextSnapshot {\n id: number;\n agentId: string;\n context: ContextState;\n utilization: number;\n lifecycleState: ContextLifecycleState;\n tokensUsed?: number;\n tokensMax?: number;\n source: ContextState[\"source\"];\n confidence: ContextState[\"confidence\"];\n capturedAt: number;\n}\n\nexport interface ContextPackageRequest {\n task: TaskRoutingHints;\n agent: AgentCard;\n budget: ContextBudget;\n memories?: Memory[];\n}\n\nexport interface RegisterAgentInput {\n id: string;\n name: string;\n kind?: AgentKind;\n label?: string | null;\n tags?: string[];\n machine?: string;\n rig?: string;\n capabilities?: string[];\n ready?: boolean;\n status?: AgentCard[\"status\"];\n instanceId?: string;\n providerCapabilities?: ProviderCapabilities;\n context?: ContextState;\n quota?: QuotaState;\n meta?: import(\"./provider-state.js\").AgentMeta;\n lifecycle?: AgentLifecycle;\n /** Parent agent id. Server sets this authoritatively from the registering token's\n * `spawnedBy`/`parentAgents` constraint; any client-supplied value is ignored. */\n spawnedBy?: string;\n /** Team id. Server sets this authoritatively from the registering token's `teamId` constraint. */\n teamId?: string;\n}\n\nexport interface AgentSessionGuard {\n instanceId?: string;\n epoch?: number;\n}\n\n// --- Orchestrators ---\n\nexport type OrchestratorStatus = \"online\" | \"offline\";\n\n/** Spawn providers — registered provider manifests are the runtime source of truth. */\nexport const SPAWN_PROVIDERS = getAllManifests().map((manifest) => manifest.id); export type SpawnProvider = string;\nexport function isSpawnProvider(value: unknown): value is SpawnProvider {\n return typeof value === \"string\" && SPAWN_PROVIDERS.includes(value);\n}\n\n/** Approval modes — runtime tuple + derived type. */\nexport const APPROVAL_MODES = [\"open\", \"guarded\", \"read-only\"] as const;\nexport type SpawnApprovalMode = (typeof APPROVAL_MODES)[number];\nfunction isApprovalMode(value: unknown): value is SpawnApprovalMode {\n return typeof value === \"string\" && (APPROVAL_MODES as readonly string[]).includes(value);\n}\n\nexport type SpawnEffort = string;\n\n// --- Runtime contract / capability types (used by Orchestrator and its inputs) ---\n\nexport type RuntimeContractName =\n | \"relayApi\"\n | \"orchestratorProtocol\"\n | \"runnerProtocol\"\n | \"providerPluginProtocol\";\n\nexport type RuntimeContracts = Partial<Record<RuntimeContractName, number>>;\nexport type RuntimeCapabilities = Record<string, boolean>;\n\nexport interface RuntimePackageMetadata {\n name: string;\n version: string;\n}\n\nexport interface ContractCompatibilityIssue {\n contract: string;\n expected: string;\n actual?: number;\n}\n\nexport interface ContractCompatibility {\n status: \"compatible\" | \"incompatible\" | \"unknown\";\n compatible: boolean;\n issues: ContractCompatibilityIssue[];\n}\n\nexport type ManagedAgentStatus = \"stopped\" | \"starting\" | \"running\" | \"stopping\" | \"backoff\" | \"failed\";\n\nexport interface ManagedAgentState {\n policyName: string;\n status: ManagedAgentStatus;\n agentId?: string;\n orchestratorId: string;\n provider: SpawnProvider;\n tmuxSession?: string;\n spawnRequestId?: string;\n workspaceId?: string;\n workspacePath?: string;\n workspaceBranch?: string;\n lastSpawnAt?: number;\n lastStopAt?: number;\n healthySince?: number;\n restartCount: number;\n consecutiveFailures: number;\n backoffUntil?: number;\n lastError?: string;\n updatedAt: number;\n}\n\ntype SpawnPolicyMode = \"always-on\" | \"on-demand\";\n\nexport type AgentProfileProvider = SpawnProvider | \"any\";\n// \"vanilla\" (#552) is the strictest base: an isolated provider config home PLUS\n// active suppression of the repo instruction cascade and every host customization\n// channel (plugins/skills/hooks/MCP/CLAUDE.md|AGENTS.md) at launch, while relay\n// provisions an explicit surface back per profile (#554/#555/#556). Claude launches\n// the isolated home under `--setting-sources \"user\"` (loads ONLY the relay-built\n// home, drops the project+local scopes that carry repo CLAUDE.md / .mcp.json);\n// Codex via `project_doc_max_bytes=0`. \"minimal\"/\"isolated\" already isolate the\n// provider home but leave the repo's CLAUDE.md/AGENTS.md loadable.\nexport type AgentProfileBase = \"host\" | \"minimal\" | \"isolated\" | \"vanilla\";\nexport type AgentProfileInstructionPolicy = \"allow\" | \"ignore\";\nexport type AgentProfileCategoryMode = \"host\" | \"profile\" | \"repo\" | \"none\";\nexport type AgentProfileFilesystemScope = \"repo\" | \"workspace\" | \"host\";\nexport type AgentProfileProjectInstructionPolicy = \"all\" | \"vanilla\";\n// Project `.mcp.json` projection policy (#667). `all` injects every server declared in the\n// spawn cwd's COMMITTED `.mcp.json`. Sole value today; the enum is the forward-compat seam\n// for future filtering (e.g. a denylist) without changing the wire/parse shape.\nexport type AgentProfileProjectMcpPolicy = \"all\";\nexport type AgentProfileProjectSkillsPolicy = \"all\";\nexport type AgentProfileProjectSettingsPolicy = \"all\";\n// Shared host-listener MCP policy (#672). `all` routes the agent's NON-IDENTITY MCP\n// (tokenlean, github) through one orchestrator-owned shared `callmux` listener via a per-agent\n// `callmux bridge` (session-cwd isolation). Sole value today; the enum is the forward-compat\n// seam for a future server-subset selector without changing the wire/parse shape.\nexport type AgentProfileSharedMcpPolicy = \"all\";\n\nexport interface AgentProfileAssetRef {\n source: \"relay\" | \"repo\" | \"inline\" | \"provider\";\n ref: string;\n enabled: boolean;\n provider?: AgentProfileProvider;\n meta?: Record<string, unknown>;\n}\n\n// #554/#555/#556 — relay-owned provisioning registry. A profile's asset refs\n// (skills/plugins/mcp.servers) are resolved against a relay-owned DB registry into\n// concrete, materializable definitions the runner writes into the launch. One shape\n// spans all three asset kinds so #557 (the per-profile assembly capstone) is a thin\n// unifier on top of this spine, not a fourth divergent module.\nexport type ProvisioningAssetKind = \"skill\" | \"mcp\" | \"plugin\";\n\n// A single file materialized into a skill/plugin dir (relative path + UTF-8 content).\nexport interface ProvisioningAssetFile {\n path: string;\n content: string;\n}\n\n// Provider-neutral MCP server spec. Mirrors a Claude `mcpServers` entry and the\n// Codex `mcp_servers.<name>.*` keys; the runner projects it per provider. `${ENV}`\n// tokens in headers/env are expanded by the provider at config-parse time, so a\n// bearer token never lands in argv.\nexport interface ProvisioningMcpServer {\n type?: \"stdio\" | \"http\" | \"sse\";\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n url?: string;\n headers?: Record<string, string>;\n}\n\nexport interface SkillAssetDefinition {\n kind: \"skill\";\n name: string;\n // Inline file set (SKILL.md + helpers) the runner writes into the skills dir.\n files?: ProvisioningAssetFile[];\n // Absolute/relay-bundled or repo-relative dir that already holds the skill,\n // resolved runner-side. Mutually exclusive with `files`.\n dir?: string;\n}\n\nexport interface PluginAssetDefinition {\n kind: \"plugin\";\n name: string;\n files?: ProvisioningAssetFile[];\n dir?: string;\n}\n\nexport interface McpAssetDefinition {\n kind: \"mcp\";\n name: string;\n server: ProvisioningMcpServer;\n}\n\nexport type ResolvedAssetDefinition = SkillAssetDefinition | PluginAssetDefinition | McpAssetDefinition;\n\nexport type ProvisioningSkipReason =\n | \"missing\"\n | \"disabled\"\n | \"unapproved\"\n | \"invalid\"\n | \"provider_mismatch\"\n | \"unsupported_source\"\n | \"invalid_inline\";\n\nexport interface ProvisioningSkip {\n kind: ProvisioningAssetKind;\n ref: string;\n provider: SpawnProvider;\n reason: ProvisioningSkipReason;\n detail: string;\n}\n\n// The fully-resolved provisioning bundle attached to a profile at spawn time (never\n// persisted — config-store's validator strips it on write). The runner materializes\n// it: Claude skills/plugins into the isolated config home + --plugin-dir, MCP into\n// --mcp-config; Codex skills into skills.config and MCP into -c mcp_servers.*.\nexport interface ResolvedProvisioning {\n skills: SkillAssetDefinition[];\n plugins: PluginAssetDefinition[];\n mcpServers: Record<string, ProvisioningMcpServer>;\n skipped?: ProvisioningSkip[];\n}\n\n// A named registry row (relay DB). `definition` is the kind-specific resolved shape.\nexport interface ProvisioningCapability {\n id: string;\n slug: string;\n kind: ProvisioningAssetKind;\n title?: string;\n description?: string;\n status: string;\n tenantId?: string | null;\n createdAt: number;\n updatedAt: number;\n}\n\nexport interface ProvisioningAsset {\n id: string;\n capabilityId?: string;\n kind: ProvisioningAssetKind;\n name: string;\n provider: AgentProfileProvider;\n definition: ResolvedAssetDefinition;\n enabled: boolean;\n sourceType?: string;\n sourceUrl?: string;\n sourceRef?: string;\n resolvedRef?: string;\n sourceSubpath?: string;\n versionConstraint?: string;\n approvalStatus?: string;\n validationStatus?: string;\n validationErrors?: unknown[];\n provenance?: Record<string, unknown>;\n importedAt?: number;\n createdAt: number;\n updatedAt: number;\n}\n\nexport interface AgentProfileProviderOptions extends Record<string, unknown> {\n codex?: {\n toolOutputTokenLimit?: number | null;\n } & Record<string, unknown>;\n}\n\nexport interface AgentProfile {\n name: string;\n description?: string;\n /** Optional emoji icon shown across dashboard agent surfaces (#751). Keyed to the\n * profile definition (not runtime role). Short string — a single emoji, possibly a\n * ZWJ sequence; capped at 8 chars by the config-store validator. Empty/absent falls\n * back to a neutral default in the UI. */\n icon?: string;\n provider?: AgentProfileProvider;\n base: AgentProfileBase;\n builtIn?: boolean;\n overridden?: boolean;\n instructions: {\n system?: string;\n append: string[];\n repoInstructions: AgentProfileInstructionPolicy;\n globalInstructions: AgentProfileInstructionPolicy;\n };\n /** Profile-level default for Project knowledge projection into systemPromptAppend.\n * Omit to keep projection off unless the spawn request explicitly asks for it. */\n projectInstructions?: {\n policy?: AgentProfileProjectInstructionPolicy;\n };\n /** Profile-level default for project `.mcp.json` server projection (#667). Sibling to\n * projectInstructions: when set, the runner reads the spawn cwd's COMMITTED `.mcp.json`\n * and injects those servers through the controlled `--mcp-config` path (composing with\n * `--strict-mcp-config`, NOT Claude's ambient project-discovery modal that wedges headless\n * agents — #662). Provenance: the project (in-repo, trusted), never ambient host MCP. Omit\n * to keep projection off unless the spawn request explicitly asks for it. */\n projectMcp?: {\n policy?: AgentProfileProjectMcpPolicy;\n };\n /** Profile-level default for project `.claude/skills` projection (#668). When set, the\n * runner reads the spawn cwd's committed `.claude/skills/<name>/SKILL.md` dirs and materializes\n * those skills into Relay's managed Claude config home. Host/global skills are excluded.\n * Omit to keep projection off unless the spawn request explicitly asks for it. */\n projectSkills?: {\n policy?: AgentProfileProjectSkillsPolicy;\n };\n /** Profile-level default for project `.claude/settings.json` projection (#669). When set,\n * the runner reads the spawn cwd's committed settings and injects a sanitized copy through\n * `--settings`. Relay's approvalMode/permission/security gates remain authoritative; project\n * settings cannot widen them. Host/global settings are excluded. */\n projectSettings?: {\n policy?: AgentProfileProjectSettingsPolicy;\n };\n /** Profile-level default for shared host-listener MCP (#672). When set, the runner emits a\n * per-agent `callmux bridge --url <listener> --cwd \"$WORKTREE\"` stdio entry so the agent's\n * NON-IDENTITY MCP (tokenlean, github) flows through ONE orchestrator-owned shared `callmux`\n * listener instead of per-agent server processes — the MCP session survives a listener/\n * downstream restart, and session-cwd routing keeps each isolated worktree's path-sensitive\n * tools resolving against its own cwd. Rides the controlled `--mcp-config` path (composes\n * with `--strict-mcp-config`). The identity-bearing relay endpoint stays per-agent (#215),\n * NEVER on the shared listener. Omit to keep current per-agent injection. */\n sharedMcp?: {\n policy?: AgentProfileSharedMcpPolicy;\n };\n relay: {\n context: boolean;\n skills: boolean;\n plugins: boolean;\n statusLine: boolean;\n // Inject the relay HTTP MCP endpoint into the spawned agent. Default-on for\n // host/minimal; isolated clean-room profiles disable it like they disable\n // plugins. Optional so existing profile literals stay valid; the gate\n // (profileAllowsRelayFeature) treats absent as enabled and config-store\n // resolves a concrete default at write time.\n mcp?: boolean;\n // Per-profile relay tool group filter (#551). When set, only tools in these groups are listed\n // at tools/list. Stamped into the runner token constraints at mint; absent = full surface.\n tools?: { groups?: string[] };\n };\n skills: AgentProfileAssetRef[];\n plugins: AgentProfileAssetRef[];\n mcp: {\n mode: AgentProfileCategoryMode;\n servers?: Record<string, unknown>;\n };\n hooks: {\n mode: AgentProfileCategoryMode;\n };\n permissions: {\n mode?: SpawnApprovalMode;\n filesystem: AgentProfileFilesystemScope;\n };\n env: Record<string, string>;\n providerOptions: AgentProfileProviderOptions;\n /** Spawn quota for agents granted this profile: max concurrent LIVE children they may\n * have at once. `0`/undefined = cannot spawn (the strict default). `N` = up to N live\n * children; a child exiting frees a slot. Drives both the minted token's `command:spawn`\n * scope (granted iff `> 0`) and the runtime quota count. Children never inherit it\n * (no grandchildren). */\n maxSpawnedAgents?: number;\n /**\n * Per-profile override for the landing strategy. When set, takes precedence over the\n * instance-wide `landing.strategy` config but yields to an explicit per-invocation\n * `--strategy` flag. \"direct\" → `auto` (host picks); \"pr\" → always open a PR.\n */\n landingStrategy?: WorkspaceLandingPolicy;\n /**\n * Resolved provisioning bundle (#554/#555/#556), populated at spawn time by\n * resolveProfileProvisioning against the relay DB registry. NOT persisted —\n * config-store's validator strips it on write; it rides only on the in-flight\n * profile serialized into the runner env, which materializes it into the launch.\n */\n provisioning?: ResolvedProvisioning;\n}\n\nexport type AgentProfileProjectionResult = \"applied\" | \"partial\" | \"unsupported\" | \"not-applicable\";\n\nexport interface AgentProfileProjectionEntry {\n capability: string;\n requested: string;\n result: AgentProfileProjectionResult;\n detail: string;\n}\n\nexport interface AgentProfileProjectionReport {\n profileName: string;\n provider: SpawnProvider;\n base: AgentProfileBase;\n generatedAt: number;\n entries: AgentProfileProjectionEntry[];\n warnings: string[];\n unsupported: string[];\n}\n\nexport interface SpawnPolicy {\n name: string;\n description?: string;\n // Desired on/off switch. `false` keeps the policy from (re)spawning even for\n // always-on mode, so a manual stop survives reconcile ticks. Defaults to true.\n enabled?: boolean;\n orchestratorId: string;\n cwd: string;\n provider: SpawnProvider;\n workspaceMode?: WorkspaceMode;\n rig?: string;\n model?: string;\n effort?: SpawnEffort;\n profile?: string;\n providerArgs: string[];\n prompt?: string;\n tags: string[];\n capabilities: string[];\n label?: string;\n mode: SpawnPolicyMode;\n permissionMode: SpawnApprovalMode;\n restartOnUpdate: boolean;\n scheduledDailyRestart: boolean;\n onDemand?: {\n keepaliveSeconds: number;\n idleDefinition: \"no-activity\";\n };\n backoff: {\n schedule: number[];\n resetAfterSeconds: number;\n };\n binding?: {\n type: \"channel\";\n channelId: string;\n };\n}\n\n/**\n * Global, provider-independent configuration for repo steward agents (issue #167).\n * Stewards are auto-provisioned per repo from these settings when one is needed.\n * Editable in the dashboard Settings → Stewards section. Disabled by default —\n * configuring a provider and enabling is the opt-in that also gates auto-spawn.\n */\nexport interface StewardConfig {\n /** When false, no steward is auto-provisioned or woken; the relay falls back to the legacy elected-steward ping. */\n enabled: boolean;\n /** Provider that runs the steward (e.g. claude, codex; future providers extend SpawnProvider). */\n provider: SpawnProvider;\n /** Optional model alias for the chosen provider (e.g. \"gpt-5.5\"). */\n model?: string;\n /** Optional reasoning effort. */\n effort?: SpawnEffort;\n /** Approval mode for the steward — needs merge/command authority, so defaults to \"open\". */\n permissionMode: SpawnApprovalMode;\n /** On-demand idle timeout before the steward is stopped when its queue drains. */\n keepaliveSeconds: number;\n}\n\n/**\n * Global workspace provisioning config for isolated worktrees (the dashboard \"Workspace\"\n * spawn option). A fresh git worktree only contains git-tracked files; node_modules are\n * symlinked from main automatically, and these additional untracked paths are too.\n */\nexport interface WorkspaceConfig {\n /**\n * Files or filename patterns to symlink from the main checkout into each isolated\n * worktree. Plain names (\"AGENTS.md\", \".claude-rig\") match files and directories;\n * entries containing glob metacharacters (*?[]{}) are expanded against main. A path\n * is only linked if it actually exists in main — missing entries are ignored.\n */\n symlinkPaths: string[];\n}\n\n/**\n * Continuous self-improvement (\"Insights\") module config — feature toggles for the\n * dogfooding flywheel (see docs/self-improvement.md, epic #183). Everything here is\n * opt-out: passive, read-only observation, so it defaults on. Each feature is\n * independently togglable so instrumentation can be disabled without losing the rest.\n */\nexport interface InsightsConfig {\n /** Master switch. When false, no signals are recorded regardless of per-feature flags. */\n enabled: boolean;\n /** #184 — server-side context-gathering ratio computed from the session trace at session end. */\n contextRatio: {\n enabled: boolean;\n };\n /** #185 — agent-authored end-of-session introspection artifact (3 fields), gated. */\n introspection: {\n enabled: boolean;\n /** Gate: skip sessions with fewer substantive turns than this (trivial work has nothing to learn). */\n minTurns: number;\n /** Gate: skip when remaining context fraction is below this (poor introspection = noise). 0–1. */\n minContextRemaining: number;\n };\n}\n\n/** Toggle for relay-driven lifecycle push notifications (#239 event bus). These wake\n * recipient agents, so they sit behind a switch the operator can flip off per-event. */\nexport interface NotificationsConfig {\n /** Master switch. When false, no lifecycle push notifications are sent. */\n enabled: boolean;\n /** #239 — push the author \"your branch landed\" + agents-on-main \"merged\" notices at land time. */\n branchLanded: boolean;\n // #308 — spawn lifecycle pushes to the spawned_by parent: ready (registered + past onboarding),\n // exited (a once-ready child went offline), spawn_failed (command failed OR never became ready).\n agentReady: boolean;\n agentExited: boolean;\n agentSpawnFailed: boolean;\n contextThreshold: { enabled: boolean; advisoryUtilization: number; selfCompactUtilization?: number; warnUtilization?: number; hardBackstopUtilization: number; bandSize: number };\n quotaThreshold: { enabled: boolean; warnRemaining: number; criticalRemaining: number; resetRemaining: number; bandSize: number };\n}\n\nexport type SelfResumeMode = \"clear-inject\" | \"native\";\n\nexport interface SelfResumeConfig {\n defaultMode: SelfResumeMode;\n budget: {\n maxGenerations: number;\n maxWallClockMs: number;\n maxTokens: number; hardStopMultiplier: number;\n };\n}\n\nexport interface ContinuationLedgerEntry {\n gen: number;\n ruledOut: string;\n why: string;\n at: number;\n}\n\nexport interface ContinuationBudget {\n maxGenerations: number;\n maxWallClockMs: number;\n maxTokens: number; hardStopMultiplier: number;\n spent: {\n generations: number;\n wallClockMs: number;\n tokens: number;\n };\n}\n\nexport interface ContinuationEnvelope {\n agentId: string;\n objective: string;\n generation: number;\n ledger: ContinuationLedgerEntry[];\n workingState: string;\n archiveRefs: string[];\n budget: ContinuationBudget;\n createdAt: number;\n updatedAt: number;\n}\n\n/** A single self-improvement datapoint. Generic by design: new instrumentation = new `signal`, not new schema. */\nexport interface InsightObservation {\n id: number;\n /** Session this datapoint belongs to (provider session id). */\n sessionId: string;\n /** Agent that produced the session, when known. */\n agentId?: string;\n /** Project/repo identity — baselines and deltas are scoped per-project. */\n project: string;\n /** Discriminator: which signal this row carries (e.g. \"context_ratio\", \"introspection\"). */\n signal: string;\n /** Signal-specific metric payload. */\n value: Record<string, unknown>;\n /** Paired outcome proxy (rework / operator-correction / success), kept alongside the metric — anti-Goodhart. */\n outcome?: Record<string, unknown>;\n /** Who computed it: \"server\" (relay, from trace) or \"agent\" (session-end hook). */\n source: \"server\" | \"agent\";\n createdAt: number;\n}\n\n/** Per-project + global rollups for the dashboard / API. */\nexport interface InsightsStats {\n signal: string;\n project: string | null; // null = global rollup across projects\n count: number;\n /** Mean of `value.ratio` where present (context_ratio); null for non-numeric signals. */\n avgRatio: number | null;\n lastAt: number | null;\n}\n\nexport interface ProviderStatusReport {\n name: SpawnProvider;\n available: boolean;\n checkedAt: number;\n reason?: string;\n version?: string;\n features?: Record<string, boolean>;\n cli?: {\n command: string;\n path?: string;\n ok: boolean;\n version?: string;\n error?: string;\n };\n runner?: {\n command: string;\n path?: string;\n ok: boolean;\n version?: string;\n error?: string;\n };\n}\n\nexport interface ProviderCatalogSummary {\n provider: SpawnProvider;\n label: string;\n defaultModel?: string;\n models: Array<{\n alias: string;\n label: string;\n providerModel: string;\n efforts: SpawnEffort[];\n defaultEffort?: SpawnEffort;\n disabled?: boolean;\n limits?: {\n contextWindowTokens?: {\n value: number;\n source: \"catalog\" | \"provider\" | \"runtime\" | \"override\";\n confidence: \"declared\" | \"verified\" | \"estimated\" | \"unknown\";\n lastUpdatedAt?: number;\n };\n maxOutputTokens?: {\n value: number;\n source: \"catalog\" | \"provider\" | \"runtime\" | \"override\";\n confidence: \"declared\" | \"verified\" | \"estimated\" | \"unknown\";\n lastUpdatedAt?: number;\n };\n };\n contextThreshold?: { selfCompactUtilization: number; warnUtilization?: number; hardBackstopUtilization?: number }; capabilities?: {\n modalities: {\n input: {\n text: boolean;\n image?: boolean;\n audio?: boolean;\n video?: boolean;\n pdf?: boolean;\n };\n output: {\n text: boolean;\n image?: boolean;\n audio?: boolean;\n video?: boolean;\n };\n };\n tools?: {\n code?: boolean;\n review?: boolean;\n debug?: boolean;\n refactor?: boolean;\n shell?: boolean;\n fileRead?: boolean;\n fileWrite?: boolean;\n webSearch?: boolean;\n imageGeneration?: boolean;\n imageEditing?: boolean;\n };\n source: \"catalog\" | \"provider\" | \"runtime\" | \"override\";\n confidence: \"declared\" | \"verified\" | \"estimated\" | \"unknown\";\n lastUpdatedAt?: number;\n };\n }>;\n}\n\nexport interface Orchestrator {\n id: string;\n hostname: string;\n status: OrchestratorStatus;\n agentId: string; // relay agent id for messaging\n providers: SpawnProvider[];\n providerStatus?: ProviderStatusReport[];\n providerCatalog?: ProviderCatalogSummary[];\n baseDir: string;\n apiUrl?: string;\n envKeys: string[]; // names only, never values\n package?: RuntimePackageMetadata;\n contracts?: RuntimeContracts;\n capabilities?: RuntimeCapabilities;\n contractCompatibility?: ContractCompatibility;\n version?: string;\n protocolVersion?: number;\n gitSha?: string;\n health?: OrchestratorHealth;\n // Self-reported host supervision (how the orchestrator's own process is run),\n // used to target a remote self-upgrade at the right unit.\n supervisor?: \"process\" | \"systemd\" | \"launchd\" | \"unknown\";\n selfUnit?: string;\n runtimePrefix?: string;\n // In-flight / last remote upgrade state, reconciled by the relay against the\n // version the orchestrator reports after it restarts.\n upgrade?: OrchestratorUpgradeState;\n meta: Record<string, unknown>;\n managedAgents: ManagedAgent[];\n lastSeen: number;\n createdAt: number;\n}\n\nexport interface OrchestratorUpgradeState {\n desiredVersion: string;\n status: \"pending\" | \"succeeded\" | \"failed\";\n commandId?: string;\n providers?: string[];\n fromVersion?: string;\n requestedBy?: string;\n requestedAt: number;\n settledAt?: number;\n error?: string;\n}\n\nexport interface OrchestratorHealth {\n status: \"ok\" | \"warn\" | \"restart-required\" | \"upgrade-required\" | \"unknown\";\n restartRequired: boolean;\n upgradeRequired?: boolean;\n issues: Array<{\n code: \"missing-version\" | \"package-drift\" | \"missing-contract\" | \"protocol-mismatch\" | \"restart-required\" | \"upgrade-required\";\n detail: string;\n }>;\n}\n\nexport interface ManagedAgent {\n agentId: string;\n provider: SpawnProvider;\n model?: string;\n effort?: SpawnEffort;\n profile?: string;\n workspaceMode?: WorkspaceMode;\n workspace?: WorkspaceMetadata;\n sessionName?: string;\n supervisor?: \"process\" | \"systemd\" | \"launchd\" | \"unknown\";\n systemdUnit?: string;\n terminalSession?: string;\n terminalAvailable?: boolean;\n tmuxSession: string;\n cwd: string;\n label?: string;\n approvalMode: SpawnApprovalMode;\n policyName?: string;\n spawnRequestId?: string;\n automationRunId?: string;\n pid?: number;\n startedAt: number;\n}\n\nexport interface ManagedSessionExitDiagnostics {\n agentId: string;\n provider: SpawnProvider;\n workspaceMode?: WorkspaceMode;\n workspace?: WorkspaceMetadata;\n sessionName?: string;\n tmuxSession: string;\n cwd: string;\n label?: string;\n policyName?: string;\n spawnRequestId?: string;\n automationRunId?: string;\n supervisor: \"process\" | \"systemd\" | \"launchd\" | \"unknown\";\n systemdUnit?: string;\n terminalSession?: string;\n terminalAvailable?: boolean;\n pid?: number;\n currentPid?: number;\n startedAt: number;\n detectedAt: number;\n runtimeMs: number;\n logFile?: string;\n logBytes?: number;\n logEmpty?: boolean;\n logTail?: string[];\n runnerInfoFile?: string;\n runnerInfoPresent?: boolean;\n systemd?: {\n unit: string;\n activeState?: string;\n subState?: string;\n result?: string;\n execMainCode?: string;\n execMainStatus?: string;\n mainPid?: number;\n unavailable?: string;\n };\n /** Termination signal name if the supervisor reported one (e.g. \"SIGKILL\", \"SIGSEGV\"). */\n signal?: string;\n /** Process exit code if known (0 = clean exit). */\n exitCode?: number;\n /** Positive OOM-kill detection (cgroup memory.events / journal OOM-killer / systemd Result=oom-kill). */\n oom?: { source: string; detail?: string };\n unavailable?: string[];\n lastError: string;\n}\n\n/**\n * Likely-cause label for an agent's death (#636). Best-effort classification mapped from the\n * available evidence — never raw logs, always a verdict the parent can act on.\n */\nexport type AgentDeathCause =\n | \"oom\"\n | \"killed\"\n | \"clean-exit\"\n | \"token-401\"\n | \"onboarding-gate\"\n | \"provider-crash\"\n | \"transport-loss\"\n | \"unknown\";\n\nexport const AGENT_DEATH_CAUSES = [\n \"oom\",\n \"killed\",\n \"clean-exit\",\n \"token-401\",\n \"onboarding-gate\",\n \"provider-crash\",\n \"transport-loss\",\n \"unknown\",\n] as const satisfies readonly AgentDeathCause[];\n\n/**\n * Structured post-mortem for an agent that died (#636). Rides on the `agent.exited` terminal\n * event (#633) so the death notification carries the WHY, and is persisted as a {@link CrashReport}\n * so the verdict survives the worktree/session reaper.\n */\nexport interface AgentDeathDiagnosis {\n /** Best-effort likely-cause label. */\n cause: AgentDeathCause;\n /** Confidence in the label — high (conclusive signal), medium (strong hint), low (fallback guess). */\n confidence: \"high\" | \"medium\" | \"low\";\n /** One-line human verdict, e.g. \"OOM-killed (systemd Result=oom-kill)\". */\n summary: string;\n /** Supporting evidence bullets that led to the label. */\n evidence: string[];\n /** Termination signal name if known (SIGKILL/SIGTERM/SIGSEGV). */\n signal?: string;\n /** Process exit code if known (0 = clean). */\n exitCode?: number;\n /** True when an OOM kill was positively detected. */\n oom?: boolean;\n /** Last N lines of the runner stderr / provider log around death. */\n logTail?: string[];\n /** Last heartbeat timestamp the server saw before death. */\n lastHeartbeatAt?: number;\n /** Gap (ms) between last heartbeat and death detection (ties to #634). */\n heartbeatGapMs?: number;\n /** Last time the agent reported busy (turn activity). */\n lastBusyAt?: number;\n /** Active work titles at death, if any. */\n activeWork?: string[];\n /** When this diagnosis was produced. */\n detectedAt: number;\n}\n\n/**\n * Persisted crash post-mortem (#636), retrievable by `agentId` after the agent row is reaped.\n * Stored in its own table so it outlives `pruneOfflineAgents` / `deleteAgent`.\n */\nexport interface CrashReport {\n agentId: string;\n parentId?: string;\n teamId?: string;\n spawnRequestId?: string;\n provider?: string;\n label?: string;\n reason: string;\n diagnosis: AgentDeathDiagnosis;\n createdAt: number;\n}\n\nexport interface RegisterOrchestratorInput {\n id: string;\n hostname: string;\n providers: SpawnProvider[];\n providerStatus?: ProviderStatusReport[];\n providerCatalog?: ProviderCatalogSummary[];\n baseDir: string;\n apiUrl?: string;\n envKeys?: string[];\n package?: RuntimePackageMetadata;\n contracts?: RuntimeContracts;\n capabilities?: RuntimeCapabilities;\n version?: string;\n protocolVersion?: number;\n gitSha?: string;\n meta?: Record<string, unknown>;\n}\n\nexport interface OrchestratorRuntimeInput {\n package?: RuntimePackageMetadata;\n contracts?: RuntimeContracts;\n capabilities?: RuntimeCapabilities;\n version?: string;\n protocolVersion?: number;\n gitSha?: string;\n providers?: SpawnProvider[];\n providerStatus?: ProviderStatusReport[];\n providerCatalog?: ProviderCatalogSummary[];\n}\n\ninterface OrchestratorSpawnInput {\n provider: SpawnProvider;\n model?: string;\n effort?: SpawnEffort;\n cwd?: string;\n workspaceMode?: WorkspaceMode;\n label?: string;\n approvalMode?: SpawnApprovalMode;\n prompt?: string;\n systemPromptAppend?: string;\n env?: Record<string, string>;\n}\n\ninterface OrchestratorSpawnResult {\n orchestratorId: string;\n provider: SpawnProvider;\n sessionName?: string;\n supervisor?: \"process\" | \"systemd\" | \"launchd\" | \"unknown\";\n systemdUnit?: string;\n terminalSession?: string;\n terminalAvailable?: boolean;\n tmuxSession: string;\n cwd: string;\n label?: string;\n approvalMode: SpawnApprovalMode;\n pid?: number;\n startedAt: number;\n}\n\nexport interface Recipe {\n name: string;\n description: string;\n version?: string;\n author?: string;\n agents: RecipeAgent[];\n workflow?: RecipeWorkflow;\n lifecycle?: RecipeLifecycle;\n}\n\nexport interface RecipeAgent {\n role: string;\n provider: SpawnProvider;\n count?: number;\n capabilities: string[];\n label?: string;\n tags?: string[];\n memoryTags?: string[];\n approvalMode?: \"open\" | \"guarded\" | \"read-only\";\n prompt?: string;\n model?: string;\n effort?: SpawnEffort;\n env?: Record<string, string>;\n}\n\ninterface RecipeWorkflow {\n trigger?: string;\n fanOut?: \"all\" | \"first\";\n collect?: string;\n routing?: RecipeRoute[];\n}\n\ninterface RecipeRoute {\n pattern: string;\n pipeline: string[];\n}\n\ninterface RecipeLifecycle {\n mode?: \"persistent\" | \"ephemeral\";\n idleTimeoutMs?: number;\n memory?: RecipeMemoryPolicy;\n}\n\nexport interface RecipeMemoryPolicy {\n injectOnAssign?: boolean;\n autoCapture?: boolean;\n captureTypes?: MemoryType[];\n memoryTags?: string[];\n alwaysReload?: string[];\n scope?: string;\n maxTokens?: number;\n maxMemories?: number;\n priorityCutoff?: 1 | 2 | 3;\n ttlMs?: number;\n}\n\nexport interface RecipeInstance {\n id: string;\n recipeName: string;\n recipeSource: \"builtin\" | \"user\";\n cwd: string;\n orchestratorId: string;\n status: \"starting\" | \"running\" | \"stopping\" | \"stopped\" | \"failed\";\n agents: RecipeAgentInstance[];\n artifacts?: Artifact[];\n startedAt: number;\n stoppedAt?: number;\n startedBy: string;\n error?: string;\n}\n\nexport interface RecipeAgentInstance {\n role: string;\n agentId: string;\n provider: string;\n status: \"spawning\" | \"running\" | \"stopping\" | \"stopped\" | \"failed\";\n index?: number;\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{r as e}from"./chunk-CilyBKbf.js";import{Qn as t,Xn as n,dr as r,er as i}from"./lucide-react-BYZuAhQs.js";import{_ as a,rt as o}from"./display-DRiGi1zj.js";import{D as s}from"./index-DeDosfVE.js";var c=e(r(),1),l=t(),u={id:o};function d({user:e,address:t,distinct:r=!1,className:o}){let d=e?.avatarUrl??(e?.avatarArtifactId?`/artifacts/${encodeURIComponent(e.avatarArtifactId)}/content`:null),[f,p]=(0,c.useState)(null);(0,c.useEffect)(()=>{if(!d){p(null);return}let e=!1,t=null;return(async()=>{try{let n=await i(d);if(e)return;t=URL.createObjectURL(n),p(t)}catch{e||p(null)}})(),()=>{e=!0,t&&URL.revokeObjectURL(t)}},[d]);let m=e?.displayName||e?.handle||`User`;if(f)return(0,l.jsx)(`img`,{src:f,alt:m,title:m,className:n(`w-7 h-7 rounded-md object-cover`,o)});if(r){let e=(m.trim()[0]||`U`).toUpperCase();return(0,l.jsx)(`span`,{title:m,className:n(`inline-flex w-7 h-7 items-center justify-center rounded-md bg-muted text-xs font-semibold ring-1 ring-foreground/10`,a(t),o),children:e})}return(0,l.jsx)(s,{agent:u,className:o})}export{d as t};
2
- //# sourceMappingURL=user-avatar-RliaJcaG.js.map
1
+ import{r as e}from"./chunk-CilyBKbf.js";import{Qn as t,Xn as n,dr as r,er as i}from"./lucide-react-BYZuAhQs.js";import{_ as a,rt as o}from"./display-D1xB8NJZ.js";import{D as s}from"./index-CN7QZlMb.js";var c=e(r(),1),l=t(),u={id:o};function d({user:e,address:t,distinct:r=!1,className:o}){let d=e?.avatarUrl??(e?.avatarArtifactId?`/artifacts/${encodeURIComponent(e.avatarArtifactId)}/content`:null),[f,p]=(0,c.useState)(null);(0,c.useEffect)(()=>{if(!d){p(null);return}let e=!1,t=null;return(async()=>{try{let n=await i(d);if(e)return;t=URL.createObjectURL(n),p(t)}catch{e||p(null)}})(),()=>{e=!0,t&&URL.revokeObjectURL(t)}},[d]);let m=e?.displayName||e?.handle||`User`;if(f)return(0,l.jsx)(`img`,{src:f,alt:m,title:m,className:n(`w-7 h-7 rounded-md object-cover`,o)});if(r){let e=(m.trim()[0]||`U`).toUpperCase();return(0,l.jsx)(`span`,{title:m,className:n(`inline-flex w-7 h-7 items-center justify-center rounded-md bg-muted text-xs font-semibold ring-1 ring-foreground/10`,a(t),o),children:e})}return(0,l.jsx)(s,{agent:u,className:o})}export{d as t};
2
+ //# sourceMappingURL=user-avatar-GFWMuAGK.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"user-avatar-RliaJcaG.js","names":[],"sources":["../../dashboard/src/components/shared/user-avatar.tsx"],"sourcesContent":["import { useEffect, useState } from 'react'\nimport { AgentTypeIcon } from '@/components/shared/agent-type-icon'\nimport { apiBlob } from '@/lib/api'\nimport { humanAccentClass } from '@/lib/display'\nimport { HUMAN_AGENT_ID } from '@/lib/constants'\nimport { cn } from '@/lib/utils'\nimport type { Agent, User } from '@/types'\n\n// Minimal stub so AgentTypeIcon resolves the generic 'user' face when no avatar exists.\nconst HUMAN_AGENT_STUB = { id: HUMAN_AGENT_ID } as unknown as Agent\n\n// A human's face (#390). Renders, in order of preference: the user's content-addressed avatar\n// image (the FK lands fully in #630 — until then `avatarArtifactId` is null and this path is\n// dormant), then a colored initial when we need to distinguish multiple operators, then the\n// generic operator icon. Degrades gracefully at every step so a single seeded user looks\n// exactly as before.\nexport function UserAvatar({\n user,\n address,\n distinct = false,\n className,\n}: {\n user: User | undefined\n /** The human address (`user` / `user:<id>`) — drives the deterministic accent color. */\n address?: string\n /** When true (multiple humans in view), fall back to a colored initial instead of the icon. */\n distinct?: boolean\n className?: string\n}) {\n const avatarUrl = user?.avatarUrl ?? (user?.avatarArtifactId ? `/artifacts/${encodeURIComponent(user.avatarArtifactId)}/content` : null)\n const [objectUrl, setObjectUrl] = useState<string | null>(null)\n\n useEffect(() => {\n if (!avatarUrl) { setObjectUrl(null); return }\n let cancelled = false\n let url: string | null = null\n void (async () => {\n try {\n const blob = await apiBlob(avatarUrl)\n if (cancelled) return\n url = URL.createObjectURL(blob)\n setObjectUrl(url)\n } catch {\n if (!cancelled) setObjectUrl(null)\n }\n })()\n return () => { cancelled = true; if (url) URL.revokeObjectURL(url) }\n }, [avatarUrl])\n\n const title = user?.displayName || user?.handle || 'User'\n\n if (objectUrl) {\n return <img src={objectUrl} alt={title} title={title} className={cn('w-7 h-7 rounded-md object-cover', className)} />\n }\n if (distinct) {\n const initial = (title.trim()[0] || 'U').toUpperCase()\n return (\n <span\n title={title}\n className={cn('inline-flex w-7 h-7 items-center justify-center rounded-md bg-muted text-xs font-semibold ring-1 ring-foreground/10', humanAccentClass(address), className)}\n >\n {initial}\n </span>\n )\n }\n return <AgentTypeIcon agent={HUMAN_AGENT_STUB} className={className} />\n}\n"],"mappings":"+NASM,EAAmB,CAAE,GAAI,EAAgB,CAO/C,SAAgB,EAAW,CACzB,OACA,UACA,WAAW,GACX,aAQC,CACD,IAAM,EAAY,GAAM,YAAc,GAAM,iBAAmB,cAAc,mBAAmB,EAAK,iBAAiB,CAAC,UAAY,MAC7H,CAAC,EAAW,IAAA,EAAA,EAAA,UAAwC,KAAK,EAE/D,EAAA,EAAA,eAAgB,CACd,GAAI,CAAC,EAAW,CAAE,EAAa,KAAK,CAAE,OACtC,IAAI,EAAY,GACZ,EAAqB,KAWzB,OAVM,SAAY,CAChB,GAAI,CACF,IAAM,EAAO,MAAM,EAAQ,EAAU,CACrC,GAAI,EAAW,OACf,EAAM,IAAI,gBAAgB,EAAK,CAC/B,EAAa,EAAI,MACX,CACD,GAAW,EAAa,KAAK,KAElC,KACS,CAAE,EAAY,GAAU,GAAK,IAAI,gBAAgB,EAAI,GACjE,CAAC,EAAU,CAAC,CAEf,IAAM,EAAQ,GAAM,aAAe,GAAM,QAAU,OAEnD,GAAI,EACF,OAAO,EAAA,EAAA,KAAC,MAAD,CAAK,IAAK,EAAW,IAAK,EAAc,QAAO,UAAW,EAAG,kCAAmC,EAAU,CAAI,CAAA,CAEvH,GAAI,EAAU,CACZ,IAAM,GAAW,EAAM,MAAM,CAAC,IAAM,KAAK,aAAa,CACtD,OACE,EAAA,EAAA,KAAC,OAAD,CACS,QACP,UAAW,EAAG,sHAAuH,EAAiB,EAAQ,CAAE,EAAU,UAEzK,EACI,CAAA,CAGX,OAAO,EAAA,EAAA,KAAC,EAAD,CAAe,MAAO,EAA6B,YAAa,CAAA"}
1
+ {"version":3,"file":"user-avatar-GFWMuAGK.js","names":[],"sources":["../../dashboard/src/components/shared/user-avatar.tsx"],"sourcesContent":["import { useEffect, useState } from 'react'\nimport { AgentTypeIcon } from '@/components/shared/agent-type-icon'\nimport { apiBlob } from '@/lib/api'\nimport { humanAccentClass } from '@/lib/display'\nimport { HUMAN_AGENT_ID } from '@/lib/constants'\nimport { cn } from '@/lib/utils'\nimport type { Agent, User } from '@/types'\n\n// Minimal stub so AgentTypeIcon resolves the generic 'user' face when no avatar exists.\nconst HUMAN_AGENT_STUB = { id: HUMAN_AGENT_ID } as unknown as Agent\n\n// A human's face (#390). Renders, in order of preference: the user's content-addressed avatar\n// image (the FK lands fully in #630 — until then `avatarArtifactId` is null and this path is\n// dormant), then a colored initial when we need to distinguish multiple operators, then the\n// generic operator icon. Degrades gracefully at every step so a single seeded user looks\n// exactly as before.\nexport function UserAvatar({\n user,\n address,\n distinct = false,\n className,\n}: {\n user: User | undefined\n /** The human address (`user` / `user:<id>`) — drives the deterministic accent color. */\n address?: string\n /** When true (multiple humans in view), fall back to a colored initial instead of the icon. */\n distinct?: boolean\n className?: string\n}) {\n const avatarUrl = user?.avatarUrl ?? (user?.avatarArtifactId ? `/artifacts/${encodeURIComponent(user.avatarArtifactId)}/content` : null)\n const [objectUrl, setObjectUrl] = useState<string | null>(null)\n\n useEffect(() => {\n if (!avatarUrl) { setObjectUrl(null); return }\n let cancelled = false\n let url: string | null = null\n void (async () => {\n try {\n const blob = await apiBlob(avatarUrl)\n if (cancelled) return\n url = URL.createObjectURL(blob)\n setObjectUrl(url)\n } catch {\n if (!cancelled) setObjectUrl(null)\n }\n })()\n return () => { cancelled = true; if (url) URL.revokeObjectURL(url) }\n }, [avatarUrl])\n\n const title = user?.displayName || user?.handle || 'User'\n\n if (objectUrl) {\n return <img src={objectUrl} alt={title} title={title} className={cn('w-7 h-7 rounded-md object-cover', className)} />\n }\n if (distinct) {\n const initial = (title.trim()[0] || 'U').toUpperCase()\n return (\n <span\n title={title}\n className={cn('inline-flex w-7 h-7 items-center justify-center rounded-md bg-muted text-xs font-semibold ring-1 ring-foreground/10', humanAccentClass(address), className)}\n >\n {initial}\n </span>\n )\n }\n return <AgentTypeIcon agent={HUMAN_AGENT_STUB} className={className} />\n}\n"],"mappings":"+NASM,EAAmB,CAAE,GAAI,EAAgB,CAO/C,SAAgB,EAAW,CACzB,OACA,UACA,WAAW,GACX,aAQC,CACD,IAAM,EAAY,GAAM,YAAc,GAAM,iBAAmB,cAAc,mBAAmB,EAAK,iBAAiB,CAAC,UAAY,MAC7H,CAAC,EAAW,IAAA,EAAA,EAAA,UAAwC,KAAK,EAE/D,EAAA,EAAA,eAAgB,CACd,GAAI,CAAC,EAAW,CAAE,EAAa,KAAK,CAAE,OACtC,IAAI,EAAY,GACZ,EAAqB,KAWzB,OAVM,SAAY,CAChB,GAAI,CACF,IAAM,EAAO,MAAM,EAAQ,EAAU,CACrC,GAAI,EAAW,OACf,EAAM,IAAI,gBAAgB,EAAK,CAC/B,EAAa,EAAI,MACX,CACD,GAAW,EAAa,KAAK,KAElC,KACS,CAAE,EAAY,GAAU,GAAK,IAAI,gBAAgB,EAAI,GACjE,CAAC,EAAU,CAAC,CAEf,IAAM,EAAQ,GAAM,aAAe,GAAM,QAAU,OAEnD,GAAI,EACF,OAAO,EAAA,EAAA,KAAC,MAAD,CAAK,IAAK,EAAW,IAAK,EAAc,QAAO,UAAW,EAAG,kCAAmC,EAAU,CAAI,CAAA,CAEvH,GAAI,EAAU,CACZ,IAAM,GAAW,EAAM,MAAM,CAAC,IAAM,KAAK,aAAa,CACtD,OACE,EAAA,EAAA,KAAC,OAAD,CACS,QACP,UAAW,EAAG,sHAAuH,EAAiB,EAAQ,CAAE,EAAU,UAEzK,EACI,CAAA,CAGX,OAAO,EAAA,EAAA,KAAC,EAAD,CAAe,MAAO,EAA6B,YAAa,CAAA"}
@@ -1,2 +1,2 @@
1
- import{Dn as e,Qn as t,hn as n,i as r,pt as i}from"./lucide-react-BYZuAhQs.js";import{d as a,t as o}from"./store-DNbkrqbU.js";import{K as s,ct as c,d as l}from"./display-DRiGi1zj.js";import{t as u}from"./badge-BLar6FjP.js";import{t as d}from"./button-BrIB69qd.js";import{W as f,X as p,u as m}from"./index-DeDosfVE.js";import{r as h,t as g}from"./card-lhnFnBoz.js";var _=t(),v={open:`bg-blue-500/10 text-blue-400 border-blue-500/20`,blocked:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,claimed:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,in_progress:`bg-purple-500/10 text-purple-400 border-purple-500/20`};function y(){let e=a(),t=o(e=>e.agentsById),n=o(e=>e.compose),r=o(e=>e.set),s=o(e=>e.doClaimTask),c=o(e=>e.doClaim),d=o(e=>e.openTaskEvents),h=o(e=>e.showError),g=o(e=>e.openConfirm),v=o(e=>e.doDeleteMessage),y=o(e=>e.doUpdateTaskStatus),x=f(),S=p(),C=S.filter(e=>e.claimable).length;function w(e){if(!n.from){h(`Validation`,`Select a "Claim as" agent first.`);return}e.sourceType===`task`&&e.task?s(e.task.id):e.sourceType===`message`&&e.message&&c(e.message.id)}function T(e){g(`Cancel Item`,`Cancel "${e.title||(e.sourceType===`task`?`Task #${e.id}`:`Message #${e.id}`)}"? This cannot be undone.`,()=>{e.sourceType===`task`&&e.task?y(e.task,`canceled`):e.sourceType===`message`&&e.message&&v(e.message.id)})}function E(e){e.task&&d(e.task)}return(0,_.jsxs)(`div`,{className:`space-y-4`,children:[(0,_.jsxs)(`div`,{className:`flex items-center justify-between flex-wrap gap-2`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,_.jsx)(i,{className:`w-5 h-5 text-muted-foreground`}),(0,_.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Work Queue`}),C>0&&(0,_.jsxs)(u,{className:`bg-orange-500/20 text-orange-400 border-orange-500/30 border`,children:[C,` claimable`]})]}),(0,_.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,_.jsx)(`label`,{className:`text-xs text-muted-foreground shrink-0`,children:`Claim as`}),(0,_.jsxs)(`select`,{className:`rounded-md border border-input bg-background px-3 py-1.5 text-sm`,value:n.from,onChange:e=>r({compose:{...n,from:e.target.value}}),children:[(0,_.jsx)(`option`,{value:``,children:`Select agent...`}),x.map(e=>(0,_.jsx)(`option`,{value:e.id,children:l(e)},e.id))]})]})]}),(0,_.jsx)(m,{className:`h-[calc(100dvh-11rem)]`,children:(0,_.jsxs)(`div`,{className:`space-y-2 pr-2`,children:[S.length===0&&(0,_.jsx)(`div`,{className:`text-center text-muted-foreground py-16 text-sm`,children:`Work queue is empty`}),S.map(n=>(0,_.jsx)(b,{item:n,now:e,agentName:e=>t[e]?l(t[e]):e.slice(-10),onClaim:()=>w(n),onCancel:()=>T(n),onEvents:n.task?()=>E(n):void 0},n.id))]})})]})}function b({item:t,now:i,agentName:a,onClaim:o,onCancel:l,onEvents:f}){let p=c[t.severity]||c.info,m=v[t.status]||`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`;return(0,_.jsx)(g,{className:t.claimable?`ring-1 ring-orange-500/30`:``,children:(0,_.jsx)(h,{className:`p-3`,children:(0,_.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,_.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap mb-1`,children:[(0,_.jsx)(`span`,{className:`font-medium text-sm truncate`,children:t.title}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${p}`,children:t.severity}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${m}`,children:t.status}),t.sourceType===`message`&&(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400`,children:`msg`})]}),t.body&&(0,_.jsx)(`p`,{className:`text-xs text-muted-foreground line-clamp-2 mb-2`,children:t.body}),(0,_.jsxs)(`div`,{className:`flex items-center gap-3 text-xs text-muted-foreground flex-wrap`,children:[t.owner&&(0,_.jsxs)(`span`,{title:`Owner`,children:[(0,_.jsx)(n,{className:`w-3 h-3 inline mr-1`}),a(t.owner)]}),t.source&&(0,_.jsx)(`span`,{className:`font-mono`,children:t.source}),(0,_.jsxs)(`span`,{title:String(t.updatedAt),children:[(0,_.jsx)(e,{className:`w-3 h-3 inline mr-1`}),s(i,t.updatedAt)]})]})]}),(0,_.jsxs)(`div`,{className:`flex flex-col gap-1 shrink-0`,children:[t.claimable&&(0,_.jsxs)(d,{size:`sm`,className:`h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white`,onClick:o,children:[(0,_.jsx)(n,{className:`w-3.5 h-3.5 mr-1`}),` Claim`]}),f&&(0,_.jsx)(d,{size:`sm`,variant:`outline`,className:`h-7 text-xs`,onClick:f,children:`Events`}),(0,_.jsxs)(d,{size:`sm`,variant:`outline`,className:`h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20`,onClick:l,children:[(0,_.jsx)(r,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
2
- //# sourceMappingURL=work-queue-C0hpmlv8.js.map
1
+ import{Dn as e,Qn as t,hn as n,i as r,pt as i}from"./lucide-react-BYZuAhQs.js";import{f as a,t as o}from"./store-zeyDiSKE.js";import{K as s,ct as c,d as l}from"./display-D1xB8NJZ.js";import{t as u}from"./badge-BLar6FjP.js";import{t as d}from"./button-BrIB69qd.js";import{W as f,X as p,u as m}from"./index-CN7QZlMb.js";import{r as h,t as g}from"./card-lhnFnBoz.js";var _=t(),v={open:`bg-blue-500/10 text-blue-400 border-blue-500/20`,blocked:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,claimed:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,in_progress:`bg-purple-500/10 text-purple-400 border-purple-500/20`};function y(){let e=a(),t=o(e=>e.agentsById),n=o(e=>e.compose),r=o(e=>e.set),s=o(e=>e.doClaimTask),c=o(e=>e.doClaim),d=o(e=>e.openTaskEvents),h=o(e=>e.showError),g=o(e=>e.openConfirm),v=o(e=>e.doDeleteMessage),y=o(e=>e.doUpdateTaskStatus),x=f(),S=p(),C=S.filter(e=>e.claimable).length;function w(e){if(!n.from){h(`Validation`,`Select a "Claim as" agent first.`);return}e.sourceType===`task`&&e.task?s(e.task.id):e.sourceType===`message`&&e.message&&c(e.message.id)}function T(e){g(`Cancel Item`,`Cancel "${e.title||(e.sourceType===`task`?`Task #${e.id}`:`Message #${e.id}`)}"? This cannot be undone.`,()=>{e.sourceType===`task`&&e.task?y(e.task,`canceled`):e.sourceType===`message`&&e.message&&v(e.message.id)})}function E(e){e.task&&d(e.task)}return(0,_.jsxs)(`div`,{className:`space-y-4`,children:[(0,_.jsxs)(`div`,{className:`flex items-center justify-between flex-wrap gap-2`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,_.jsx)(i,{className:`w-5 h-5 text-muted-foreground`}),(0,_.jsx)(`h2`,{className:`text-lg font-semibold`,children:`Work Queue`}),C>0&&(0,_.jsxs)(u,{className:`bg-orange-500/20 text-orange-400 border-orange-500/30 border`,children:[C,` claimable`]})]}),(0,_.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,_.jsx)(`label`,{className:`text-xs text-muted-foreground shrink-0`,children:`Claim as`}),(0,_.jsxs)(`select`,{className:`rounded-md border border-input bg-background px-3 py-1.5 text-sm`,value:n.from,onChange:e=>r({compose:{...n,from:e.target.value}}),children:[(0,_.jsx)(`option`,{value:``,children:`Select agent...`}),x.map(e=>(0,_.jsx)(`option`,{value:e.id,children:l(e)},e.id))]})]})]}),(0,_.jsx)(m,{className:`h-[calc(100dvh-11rem)]`,children:(0,_.jsxs)(`div`,{className:`space-y-2 pr-2`,children:[S.length===0&&(0,_.jsx)(`div`,{className:`text-center text-muted-foreground py-16 text-sm`,children:`Work queue is empty`}),S.map(n=>(0,_.jsx)(b,{item:n,now:e,agentName:e=>t[e]?l(t[e]):e.slice(-10),onClaim:()=>w(n),onCancel:()=>T(n),onEvents:n.task?()=>E(n):void 0},n.id))]})})]})}function b({item:t,now:i,agentName:a,onClaim:o,onCancel:l,onEvents:f}){let p=c[t.severity]||c.info,m=v[t.status]||`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`;return(0,_.jsx)(g,{className:t.claimable?`ring-1 ring-orange-500/30`:``,children:(0,_.jsx)(h,{className:`p-3`,children:(0,_.jsxs)(`div`,{className:`flex items-start gap-3`,children:[(0,_.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,_.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap mb-1`,children:[(0,_.jsx)(`span`,{className:`font-medium text-sm truncate`,children:t.title}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${p}`,children:t.severity}),(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${m}`,children:t.status}),t.sourceType===`message`&&(0,_.jsx)(u,{variant:`outline`,className:`text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400`,children:`msg`})]}),t.body&&(0,_.jsx)(`p`,{className:`text-xs text-muted-foreground line-clamp-2 mb-2`,children:t.body}),(0,_.jsxs)(`div`,{className:`flex items-center gap-3 text-xs text-muted-foreground flex-wrap`,children:[t.owner&&(0,_.jsxs)(`span`,{title:`Owner`,children:[(0,_.jsx)(n,{className:`w-3 h-3 inline mr-1`}),a(t.owner)]}),t.source&&(0,_.jsx)(`span`,{className:`font-mono`,children:t.source}),(0,_.jsxs)(`span`,{title:String(t.updatedAt),children:[(0,_.jsx)(e,{className:`w-3 h-3 inline mr-1`}),s(i,t.updatedAt)]})]})]}),(0,_.jsxs)(`div`,{className:`flex flex-col gap-1 shrink-0`,children:[t.claimable&&(0,_.jsxs)(d,{size:`sm`,className:`h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white`,onClick:o,children:[(0,_.jsx)(n,{className:`w-3.5 h-3.5 mr-1`}),` Claim`]}),f&&(0,_.jsx)(d,{size:`sm`,variant:`outline`,className:`h-7 text-xs`,onClick:f,children:`Events`}),(0,_.jsxs)(d,{size:`sm`,variant:`outline`,className:`h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20`,onClick:l,children:[(0,_.jsx)(r,{className:`w-3.5 h-3.5 mr-1`}),` Cancel`]})]})]})})})}export{y as WorkQueueView};
2
+ //# sourceMappingURL=work-queue-B30SNmuq.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"work-queue-C0hpmlv8.js","names":[],"sources":["../../dashboard/src/components/views/work-queue.tsx"],"sourcesContent":["import { useRelayStore, useNow } from '@/store'\nimport { useWorkQueueItems, useComposeAgents } from '@/hooks/use-selectors'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { ScrollArea } from '@/components/ui/scroll-area'\nimport { ListChecks, CheckCircle2, Calendar, X } from 'lucide-react'\nimport { displayName, timeAgo } from '@/lib/display'\nimport { SEVERITY_COLORS } from '@/lib/constants'\nimport type { WorkQueueItem } from '@/types'\n\nconst STATUS_COLORS: Record<string, string> = {\n open: 'bg-blue-500/10 text-blue-400 border-blue-500/20',\n blocked: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n claimed: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n in_progress: 'bg-purple-500/10 text-purple-400 border-purple-500/20',\n}\n\nexport function WorkQueueView() {\n const now = useNow()\n const agentsById = useRelayStore((s) => s.agentsById)\n const compose = useRelayStore((s) => s.compose)\n const set = useRelayStore((s) => s.set)\n const doClaimTask = useRelayStore((s) => s.doClaimTask)\n const doClaim = useRelayStore((s) => s.doClaim)\n const openTaskEvents = useRelayStore((s) => s.openTaskEvents)\n const showError = useRelayStore((s) => s.showError)\n const openConfirm = useRelayStore((s) => s.openConfirm)\n const doDeleteMessage = useRelayStore((s) => s.doDeleteMessage)\n const doUpdateTaskStatus = useRelayStore((s) => s.doUpdateTaskStatus)\n\n const composeAgents = useComposeAgents()\n const items = useWorkQueueItems()\n\n const claimableCount = items.filter((i) => i.claimable).length\n\n function handleClaim(item: WorkQueueItem) {\n if (!compose.from) { showError('Validation', 'Select a \"Claim as\" agent first.'); return }\n if (item.sourceType === 'task' && item.task) doClaimTask(item.task.id)\n else if (item.sourceType === 'message' && item.message) doClaim(item.message.id)\n }\n\n function handleCancel(item: WorkQueueItem) {\n const label = item.title || (item.sourceType === 'task' ? `Task #${item.id}` : `Message #${item.id}`)\n openConfirm('Cancel Item', `Cancel \"${label}\"? This cannot be undone.`, () => {\n if (item.sourceType === 'task' && item.task) doUpdateTaskStatus(item.task, 'canceled')\n else if (item.sourceType === 'message' && item.message) doDeleteMessage(item.message.id)\n })\n }\n\n function handleEvents(item: WorkQueueItem) {\n if (item.task) openTaskEvents(item.task)\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <ListChecks className=\"w-5 h-5 text-muted-foreground\" />\n <h2 className=\"text-lg font-semibold\">Work Queue</h2>\n {claimableCount > 0 && (\n <Badge className=\"bg-orange-500/20 text-orange-400 border-orange-500/30 border\">\n {claimableCount} claimable\n </Badge>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <label className=\"text-xs text-muted-foreground shrink-0\">Claim as</label>\n <select\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm\"\n value={compose.from}\n onChange={(e) => set({ compose: { ...compose, from: e.target.value } })}\n >\n <option value=\"\">Select agent...</option>\n {composeAgents.map((a) => <option key={a.id} value={a.id}>{displayName(a)}</option>)}\n </select>\n </div>\n </div>\n\n <ScrollArea className=\"h-[calc(100dvh-11rem)]\">\n <div className=\"space-y-2 pr-2\">\n {items.length === 0 && (\n <div className=\"text-center text-muted-foreground py-16 text-sm\">Work queue is empty</div>\n )}\n {items.map((item) => (\n <WorkQueueCard\n key={item.id}\n item={item}\n now={now}\n agentName={(id) => agentsById[id] ? displayName(agentsById[id]) : id.slice(-10)}\n onClaim={() => handleClaim(item)}\n onCancel={() => handleCancel(item)}\n onEvents={item.task ? () => handleEvents(item) : undefined}\n />\n ))}\n </div>\n </ScrollArea>\n </div>\n )\n}\n\nfunction WorkQueueCard({\n item, now, agentName, onClaim, onCancel, onEvents,\n}: {\n item: WorkQueueItem\n now: number\n agentName: (id: string) => string\n onClaim: () => void\n onCancel: () => void\n onEvents?: () => void\n}) {\n const severityClass = SEVERITY_COLORS[item.severity] || SEVERITY_COLORS.info\n const statusClass = STATUS_COLORS[item.status] || 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n\n return (\n <Card className={item.claimable ? 'ring-1 ring-orange-500/30' : ''}>\n <CardContent className=\"p-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap mb-1\">\n <span className=\"font-medium text-sm truncate\">{item.title}</span>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${severityClass}`}>{item.severity}</Badge>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${statusClass}`}>{item.status}</Badge>\n {item.sourceType === 'message' && (\n <Badge variant=\"outline\" className=\"text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400\">msg</Badge>\n )}\n </div>\n {item.body && (\n <p className=\"text-xs text-muted-foreground line-clamp-2 mb-2\">{item.body}</p>\n )}\n <div className=\"flex items-center gap-3 text-xs text-muted-foreground flex-wrap\">\n {item.owner && (\n <span title=\"Owner\">\n <CheckCircle2 className=\"w-3 h-3 inline mr-1\" />{agentName(item.owner)}\n </span>\n )}\n {item.source && (\n <span className=\"font-mono\">{item.source}</span>\n )}\n <span title={String(item.updatedAt)}>\n <Calendar className=\"w-3 h-3 inline mr-1\" />{timeAgo(now, item.updatedAt)}\n </span>\n </div>\n </div>\n <div className=\"flex flex-col gap-1 shrink-0\">\n {item.claimable && (\n <Button size=\"sm\" className=\"h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white\" onClick={onClaim}>\n <CheckCircle2 className=\"w-3.5 h-3.5 mr-1\" /> Claim\n </Button>\n )}\n {onEvents && (\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs\" onClick={onEvents}>\n Events\n </Button>\n )}\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20\" onClick={onCancel}>\n <X className=\"w-3.5 h-3.5 mr-1\" /> Cancel\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"work-queue-B30SNmuq.js","names":[],"sources":["../../dashboard/src/components/views/work-queue.tsx"],"sourcesContent":["import { useRelayStore, useNow } from '@/store'\nimport { useWorkQueueItems, useComposeAgents } from '@/hooks/use-selectors'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { ScrollArea } from '@/components/ui/scroll-area'\nimport { ListChecks, CheckCircle2, Calendar, X } from 'lucide-react'\nimport { displayName, timeAgo } from '@/lib/display'\nimport { SEVERITY_COLORS } from '@/lib/constants'\nimport type { WorkQueueItem } from '@/types'\n\nconst STATUS_COLORS: Record<string, string> = {\n open: 'bg-blue-500/10 text-blue-400 border-blue-500/20',\n blocked: 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20',\n claimed: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20',\n in_progress: 'bg-purple-500/10 text-purple-400 border-purple-500/20',\n}\n\nexport function WorkQueueView() {\n const now = useNow()\n const agentsById = useRelayStore((s) => s.agentsById)\n const compose = useRelayStore((s) => s.compose)\n const set = useRelayStore((s) => s.set)\n const doClaimTask = useRelayStore((s) => s.doClaimTask)\n const doClaim = useRelayStore((s) => s.doClaim)\n const openTaskEvents = useRelayStore((s) => s.openTaskEvents)\n const showError = useRelayStore((s) => s.showError)\n const openConfirm = useRelayStore((s) => s.openConfirm)\n const doDeleteMessage = useRelayStore((s) => s.doDeleteMessage)\n const doUpdateTaskStatus = useRelayStore((s) => s.doUpdateTaskStatus)\n\n const composeAgents = useComposeAgents()\n const items = useWorkQueueItems()\n\n const claimableCount = items.filter((i) => i.claimable).length\n\n function handleClaim(item: WorkQueueItem) {\n if (!compose.from) { showError('Validation', 'Select a \"Claim as\" agent first.'); return }\n if (item.sourceType === 'task' && item.task) doClaimTask(item.task.id)\n else if (item.sourceType === 'message' && item.message) doClaim(item.message.id)\n }\n\n function handleCancel(item: WorkQueueItem) {\n const label = item.title || (item.sourceType === 'task' ? `Task #${item.id}` : `Message #${item.id}`)\n openConfirm('Cancel Item', `Cancel \"${label}\"? This cannot be undone.`, () => {\n if (item.sourceType === 'task' && item.task) doUpdateTaskStatus(item.task, 'canceled')\n else if (item.sourceType === 'message' && item.message) doDeleteMessage(item.message.id)\n })\n }\n\n function handleEvents(item: WorkQueueItem) {\n if (item.task) openTaskEvents(item.task)\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <ListChecks className=\"w-5 h-5 text-muted-foreground\" />\n <h2 className=\"text-lg font-semibold\">Work Queue</h2>\n {claimableCount > 0 && (\n <Badge className=\"bg-orange-500/20 text-orange-400 border-orange-500/30 border\">\n {claimableCount} claimable\n </Badge>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <label className=\"text-xs text-muted-foreground shrink-0\">Claim as</label>\n <select\n className=\"rounded-md border border-input bg-background px-3 py-1.5 text-sm\"\n value={compose.from}\n onChange={(e) => set({ compose: { ...compose, from: e.target.value } })}\n >\n <option value=\"\">Select agent...</option>\n {composeAgents.map((a) => <option key={a.id} value={a.id}>{displayName(a)}</option>)}\n </select>\n </div>\n </div>\n\n <ScrollArea className=\"h-[calc(100dvh-11rem)]\">\n <div className=\"space-y-2 pr-2\">\n {items.length === 0 && (\n <div className=\"text-center text-muted-foreground py-16 text-sm\">Work queue is empty</div>\n )}\n {items.map((item) => (\n <WorkQueueCard\n key={item.id}\n item={item}\n now={now}\n agentName={(id) => agentsById[id] ? displayName(agentsById[id]) : id.slice(-10)}\n onClaim={() => handleClaim(item)}\n onCancel={() => handleCancel(item)}\n onEvents={item.task ? () => handleEvents(item) : undefined}\n />\n ))}\n </div>\n </ScrollArea>\n </div>\n )\n}\n\nfunction WorkQueueCard({\n item, now, agentName, onClaim, onCancel, onEvents,\n}: {\n item: WorkQueueItem\n now: number\n agentName: (id: string) => string\n onClaim: () => void\n onCancel: () => void\n onEvents?: () => void\n}) {\n const severityClass = SEVERITY_COLORS[item.severity] || SEVERITY_COLORS.info\n const statusClass = STATUS_COLORS[item.status] || 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20'\n\n return (\n <Card className={item.claimable ? 'ring-1 ring-orange-500/30' : ''}>\n <CardContent className=\"p-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap mb-1\">\n <span className=\"font-medium text-sm truncate\">{item.title}</span>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${severityClass}`}>{item.severity}</Badge>\n <Badge variant=\"outline\" className={`text-xs px-1.5 py-0 border ${statusClass}`}>{item.status}</Badge>\n {item.sourceType === 'message' && (\n <Badge variant=\"outline\" className=\"text-xs px-1.5 py-0 border border-zinc-500/20 text-zinc-400\">msg</Badge>\n )}\n </div>\n {item.body && (\n <p className=\"text-xs text-muted-foreground line-clamp-2 mb-2\">{item.body}</p>\n )}\n <div className=\"flex items-center gap-3 text-xs text-muted-foreground flex-wrap\">\n {item.owner && (\n <span title=\"Owner\">\n <CheckCircle2 className=\"w-3 h-3 inline mr-1\" />{agentName(item.owner)}\n </span>\n )}\n {item.source && (\n <span className=\"font-mono\">{item.source}</span>\n )}\n <span title={String(item.updatedAt)}>\n <Calendar className=\"w-3 h-3 inline mr-1\" />{timeAgo(now, item.updatedAt)}\n </span>\n </div>\n </div>\n <div className=\"flex flex-col gap-1 shrink-0\">\n {item.claimable && (\n <Button size=\"sm\" className=\"h-7 text-xs bg-orange-600 hover:bg-orange-700 text-white\" onClick={onClaim}>\n <CheckCircle2 className=\"w-3.5 h-3.5 mr-1\" /> Claim\n </Button>\n )}\n {onEvents && (\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs\" onClick={onEvents}>\n Events\n </Button>\n )}\n <Button size=\"sm\" variant=\"outline\" className=\"h-7 text-xs text-red-400 hover:text-red-300 hover:bg-red-500/10 border-red-500/20\" onClick={onCancel}>\n <X className=\"w-3.5 h-3.5 mr-1\" /> Cancel\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n )\n}\n"],"mappings":"sXAWM,EAAwC,CAC5C,KAAM,kDACN,QAAS,wDACT,QAAS,2DACT,YAAa,wDACd,CAED,SAAgB,GAAgB,CAC9B,IAAM,EAAM,GAAQ,CACd,EAAa,EAAe,GAAM,EAAE,WAAW,CAC/C,EAAU,EAAe,GAAM,EAAE,QAAQ,CACzC,EAAM,EAAe,GAAM,EAAE,IAAI,CACjC,EAAc,EAAe,GAAM,EAAE,YAAY,CACjD,EAAU,EAAe,GAAM,EAAE,QAAQ,CACzC,EAAiB,EAAe,GAAM,EAAE,eAAe,CACvD,EAAY,EAAe,GAAM,EAAE,UAAU,CAC7C,EAAc,EAAe,GAAM,EAAE,YAAY,CACjD,EAAkB,EAAe,GAAM,EAAE,gBAAgB,CACzD,EAAqB,EAAe,GAAM,EAAE,mBAAmB,CAE/D,EAAgB,GAAkB,CAClC,EAAQ,GAAmB,CAE3B,EAAiB,EAAM,OAAQ,GAAM,EAAE,UAAU,CAAC,OAExD,SAAS,EAAY,EAAqB,CACxC,GAAI,CAAC,EAAQ,KAAM,CAAE,EAAU,aAAc,mCAAmC,CAAE,OAC9E,EAAK,aAAe,QAAU,EAAK,KAAM,EAAY,EAAK,KAAK,GAAG,CAC7D,EAAK,aAAe,WAAa,EAAK,SAAS,EAAQ,EAAK,QAAQ,GAAG,CAGlF,SAAS,EAAa,EAAqB,CAEzC,EAAY,cAAe,WADb,EAAK,QAAU,EAAK,aAAe,OAAS,SAAS,EAAK,KAAO,YAAY,EAAK,MACpD,+BAAkC,CACxE,EAAK,aAAe,QAAU,EAAK,KAAM,EAAmB,EAAK,KAAM,WAAW,CAC7E,EAAK,aAAe,WAAa,EAAK,SAAS,EAAgB,EAAK,QAAQ,GAAG,EACxF,CAGJ,SAAS,EAAa,EAAqB,CACrC,EAAK,MAAM,EAAe,EAAK,KAAK,CAG1C,OACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,qBAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,6DAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,EACE,EAAA,EAAA,KAAC,EAAD,CAAY,UAAU,gCAAkC,CAAA,EACxD,EAAA,EAAA,KAAC,KAAD,CAAI,UAAU,iCAAwB,aAAe,CAAA,CACpD,EAAiB,IAChB,EAAA,EAAA,MAAC,EAAD,CAAO,UAAU,wEAAjB,CACG,EAAe,aACV,GAEN,IACN,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,EACE,EAAA,EAAA,KAAC,QAAD,CAAO,UAAU,kDAAyC,WAAgB,CAAA,EAC1E,EAAA,EAAA,MAAC,SAAD,CACE,UAAU,mEACV,MAAO,EAAQ,KACf,SAAW,GAAM,EAAI,CAAE,QAAS,CAAE,GAAG,EAAS,KAAM,EAAE,OAAO,MAAO,CAAE,CAAC,UAHzE,EAKE,EAAA,EAAA,KAAC,SAAD,CAAQ,MAAM,YAAG,kBAAwB,CAAA,CACxC,EAAc,IAAK,IAAM,EAAA,EAAA,KAAC,SAAD,CAAmB,MAAO,EAAE,YAAK,EAAY,EAAE,CAAU,CAA5C,EAAE,GAA0C,CAAC,CAC7E,GACL,GACF,IAEN,EAAA,EAAA,KAAC,EAAD,CAAY,UAAU,mCACpB,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,0BAAf,CACG,EAAM,SAAW,IAChB,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,2DAAkD,sBAAyB,CAAA,CAE3F,EAAM,IAAK,IACV,EAAA,EAAA,KAAC,EAAD,CAEQ,OACD,MACL,UAAY,GAAO,EAAW,GAAM,EAAY,EAAW,GAAI,CAAG,EAAG,MAAM,IAAI,CAC/E,YAAe,EAAY,EAAK,CAChC,aAAgB,EAAa,EAAK,CAClC,SAAU,EAAK,SAAa,EAAa,EAAK,CAAG,IAAA,GACjD,CAPK,EAAK,GAOV,CACF,CACE,GACK,CAAA,CACT,GAIV,SAAS,EAAc,CACrB,OAAM,MAAK,YAAW,UAAS,WAAU,YAQxC,CACD,IAAM,EAAgB,EAAgB,EAAK,WAAa,EAAgB,KAClE,EAAc,EAAc,EAAK,SAAW,kDAElD,OACE,EAAA,EAAA,KAAC,EAAD,CAAM,UAAW,EAAK,UAAY,4BAA8B,aAC9D,EAAA,EAAA,KAAC,EAAD,CAAa,UAAU,gBACrB,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,kCAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,0BAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,kDAAf,EACE,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,wCAAgC,EAAK,MAAa,CAAA,EAClE,EAAA,EAAA,KAAC,EAAD,CAAO,QAAQ,UAAU,UAAW,8BAA8B,aAAkB,EAAK,SAAiB,CAAA,EAC1G,EAAA,EAAA,KAAC,EAAD,CAAO,QAAQ,UAAU,UAAW,8BAA8B,aAAgB,EAAK,OAAe,CAAA,CACrG,EAAK,aAAe,YACnB,EAAA,EAAA,KAAC,EAAD,CAAO,QAAQ,UAAU,UAAU,uEAA8D,MAAW,CAAA,CAE1G,GACL,EAAK,OACJ,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,2DAAmD,EAAK,KAAS,CAAA,EAEhF,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,2EAAf,CACG,EAAK,QACJ,EAAA,EAAA,MAAC,OAAD,CAAM,MAAM,iBAAZ,EACE,EAAA,EAAA,KAAC,EAAD,CAAc,UAAU,sBAAwB,CAAA,CAAC,EAAU,EAAK,MAAM,CACjE,GAER,EAAK,SACJ,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,qBAAa,EAAK,OAAc,CAAA,EAElD,EAAA,EAAA,MAAC,OAAD,CAAM,MAAO,OAAO,EAAK,UAAU,UAAnC,EACE,EAAA,EAAA,KAAC,EAAD,CAAU,UAAU,sBAAwB,CAAA,CAAC,EAAQ,EAAK,EAAK,UAAU,CACpE,GACH,GACF,IACN,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,wCAAf,CACG,EAAK,YACJ,EAAA,EAAA,MAAC,EAAD,CAAQ,KAAK,KAAK,UAAU,2DAA2D,QAAS,WAAhG,EACE,EAAA,EAAA,KAAC,EAAD,CAAc,UAAU,mBAAqB,CAAA,CAAA,SACtC,GAEV,IACC,EAAA,EAAA,KAAC,EAAD,CAAQ,KAAK,KAAK,QAAQ,UAAU,UAAU,cAAc,QAAS,WAAU,SAEtE,CAAA,EAEX,EAAA,EAAA,MAAC,EAAD,CAAQ,KAAK,KAAK,QAAQ,UAAU,UAAU,oFAAoF,QAAS,WAA3I,EACE,EAAA,EAAA,KAAC,EAAD,CAAG,UAAU,mBAAqB,CAAA,CAAA,UAC3B,GACL,GACF,GACM,CAAA,CACT,CAAA"}
@@ -1,3 +1,3 @@
1
- import{r as e}from"./chunk-CilyBKbf.js";import{At as t,Bn as n,Dt as r,Gn as i,Ht as a,I as o,It as s,Jn as c,Kt as l,Qn as u,Sn as d,Wt as f,X as p,dr as m,h,i as g,kt as _,m as v,on as y,xn as b,yn as ee,zt as x}from"./lucide-react-BYZuAhQs.js";import{d as S,t as C}from"./store-DNbkrqbU.js";import{K as w,d as T}from"./display-DRiGi1zj.js";import{i as E}from"./types-Tv67voFu.js";import{t as D}from"./badge-BLar6FjP.js";import{t as O}from"./button-BrIB69qd.js";import{t as k}from"./copy-button-Cd-LbSbg.js";import{a as A,o as j,r as M,t as N}from"./card-lhnFnBoz.js";var P=e(m(),1),F=u(),I=new Set([`active`,`ready`,`conflict`,`review_requested`,`merge_planned`,`cleanup_requested`]),L=new Set([`active`,`ready`,`conflict`,`review_requested`,`merge_planned`]),R=new Set([`active`,`ready`,`review_requested`,`merge_planned`,`conflict`]),z={active:`bg-blue-500/10 text-blue-400 border-blue-500/20`,ready:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,conflict:`bg-red-500/10 text-red-400 border-red-500/20`,review_requested:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,merge_planned:`bg-indigo-500/10 text-indigo-400 border-indigo-500/20`,merged:`bg-violet-500/10 text-violet-400 border-violet-500/20`,abandoned:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`,cleanup_requested:`bg-orange-500/10 text-orange-400 border-orange-500/20`,cleaned:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`},B={active:`active`,ready:`ready`,conflict:`conflict`,review_requested:`review`,merge_planned:`merge planned`,merged:`merged`,abandoned:`abandoned`,cleanup_requested:`cleanup`,cleaned:`cleaned`},V={conflict:0,ready:1,review_requested:2,merge_planned:3,active:4,cleanup_requested:5,merged:6,abandoned:7,cleaned:8},H=new Set(E);function U(e){let t=e.split(`/`).filter(Boolean);return t.length<=3?e||`-`:`/${t.slice(-3).join(`/`)}`}function W(e,t){return e?T(e):t||`-`}function G(e){return z[e]||`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`}function K(e,t){let n=(V[e.status]??99)-(V[t.status]??99);return n===0?Number(t.updatedAt||0)-Number(e.updatedAt||0):n}function q(e){let t=new Map;for(let n of e){let e=n.repoRoot||n.sourceCwd||`unknown repo`;t.set(e,[...t.get(e)||[],n])}return[...t.entries()].map(([e,t])=>({repoRoot:e,workspaces:t.sort(K)})).sort((e,t)=>{let n=e.workspaces.some(e=>I.has(e.status));return n===t.workspaces.some(e=>I.has(e.status))?e.repoRoot.localeCompare(t.repoRoot):n?-1:1})}function J(e,t){return t===`all`?e:t===`cleaned`?e.filter(e=>H.has(e.status)):e.filter(e=>!H.has(e.status))}function Y({workspace:e,expanded:t,onToggleDetails:n}){let i=C(e=>e.workspaceAction),a=C(e=>e.purgeWorkspace),o=C(e=>e.fetchWorkspaceMergePreview),c=C(e=>e.openFilesAt),u=e.status===`cleaned`||e.status===`merged`||e.status===`abandoned`,f=u||e.status===`cleanup_requested`,p=e.worktreePath||e.sourceCwd||e.repoRoot,m=C(t=>t.workspaceGitState[e.id]),_=!!m&&m.available!==!1&&m.landed===!0,v=e.mode===`isolated`&&!!e.worktreePath&&R.has(e.status)&&!_;async function y(){await o(e.id),await i(e.id,`merge`)}return(0,F.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-1.5`,children:[(0,F.jsx)(O,{size:`icon-sm`,variant:`ghost`,title:t?`Hide details`:`Show diff & timeline`,onClick:n,children:t?(0,F.jsx)(b,{className:`h-3.5 w-3.5`}):(0,F.jsx)(ee,{className:`h-3.5 w-3.5`})}),(0,F.jsx)(O,{size:`icon-sm`,variant:`ghost`,title:`Open workspace`,disabled:!p||e.status===`cleaned`,onClick:()=>void c({path:p}),children:(0,F.jsx)(s,{className:`h-3.5 w-3.5`})}),(0,F.jsx)(k,{value:p,label:`Copy path`,copiedLabel:`Copied path`,size:`icon-sm`,variant:`ghost`,disabled:!p}),(0,F.jsx)(O,{size:`icon-sm`,variant:`outline`,title:`Mark ready`,disabled:f||e.status===`ready`,onClick:()=>void i(e.id,`ready`),children:(0,F.jsx)(d,{className:`h-3.5 w-3.5`})}),(0,F.jsx)(O,{size:`icon-sm`,variant:`outline`,title:`Request review`,disabled:f||e.status===`review_requested`,onClick:()=>void i(e.id,`request-review`),children:(0,F.jsx)(l,{className:`h-3.5 w-3.5`})}),(0,F.jsx)(O,{size:`icon-sm`,variant:`outline`,title:`Mark merge planned`,disabled:f||e.status===`merge_planned`,onClick:()=>void i(e.id,`merge-plan`),children:(0,F.jsx)(x,{className:`h-3.5 w-3.5`})}),(0,F.jsx)(O,{size:`icon-sm`,variant:`default`,title:_?`Already merged into base — nothing to land`:`Merge & land work`,disabled:!v,onClick:()=>void y(),children:(0,F.jsx)(r,{className:`h-3.5 w-3.5`})}),(0,F.jsx)(O,{size:`icon-sm`,variant:`outline`,title:`Request cleanup (removes the worktree on the host)`,disabled:e.status===`cleaned`||e.status===`cleanup_requested`,onClick:()=>void i(e.id,`cleanup`),children:(0,F.jsx)(h,{className:`h-3.5 w-3.5`})}),u&&(0,F.jsx)(O,{size:`icon-sm`,variant:`ghost`,title:`Purge record (clears the row, does not touch disk)`,onClick:()=>void a(e.id),children:(0,F.jsx)(g,{className:`h-3.5 w-3.5`})})]})}function X({workspace:e,unmerged:t}){let n=C(t=>t.workspaceMergePreview[e.id]),r=C(e=>e.fetchWorkspaceMergePreview),i=t>0&&R.has(e.status);return(0,P.useEffect)(()=>{i&&n===void 0&&r(e.id)},[i,n,e.id,r]),!i||n===void 0||n.available===!1?null:n.conflict?(0,F.jsxs)(`span`,{className:`flex items-center gap-0.5 text-amber-400`,title:`Merging into ${n.baseRef||`base`} would conflict — resolve before merging`,children:[(0,F.jsx)(v,{className:`h-3 w-3`}),`conflict`]}):n.conflict===!1?(0,F.jsxs)(`span`,{className:`flex items-center gap-0.5 text-emerald-400`,title:`Clean to merge via ${n.strategy===`pr`?`PR`:`rebase + fast-forward`} into ${n.baseRef||`base`}`,children:[(0,F.jsx)(d,{className:`h-3 w-3`}),n.strategy===`pr`?`PR ready`:`clean`]}):null}function Z({workspace:e}){let t=S(),r=C(t=>t.workspaceGitState[e.id]),s=C(e=>e.fetchWorkspaceGitState),c=e.mode===`isolated`&&!!e.worktreePath&&L.has(e.status);if((0,P.useEffect)(()=>{c&&r===void 0&&s(e.id)},[c,r,e.id,s]),!c)return null;let l=(0,F.jsx)(O,{size:`icon-sm`,variant:`ghost`,className:`h-5 w-5`,title:`Refresh git state`,onClick:()=>void s(e.id),children:(0,F.jsx)(o,{className:`h-3 w-3`})});if(r===void 0)return(0,F.jsx)(`div`,{className:`flex items-center gap-1 text-[11px] text-muted-foreground`,children:`Loading git state…`});if(r.available===!1)return(0,F.jsxs)(`div`,{className:`flex items-center gap-1 text-[11px] text-muted-foreground`,title:r.reason,children:[(0,F.jsx)(`span`,{children:`git state unavailable`}),l]});if(r.missing)return(0,F.jsxs)(`div`,{className:`flex items-center gap-1 text-[11px] text-muted-foreground`,children:[`worktree gone `,l]});if(r.error)return(0,F.jsxs)(`div`,{className:`flex items-center gap-1 text-[11px] text-red-400`,title:r.error,children:[(0,F.jsx)(`span`,{children:`git error`}),l]});let u=r.ahead??0,f=r.landed===!0,p=f?0:r.unmergedAhead??u,m=r.behind??0,h=r.dirtyCount??0,g=p===0&&h===0&&!f,v=r.lastCommit;return(0,F.jsxs)(`div`,{className:`space-y-0.5`,children:[(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[11px]`,children:[g?(0,F.jsx)(`span`,{className:`text-muted-foreground`,children:`no unmerged work`}):(0,F.jsxs)(F.Fragment,{children:[f?(0,F.jsxs)(`span`,{className:`flex items-center gap-0.5 text-emerald-400`,title:`Work already landed in ${r.baseRef||`base`} via squash/cherry-pick — safe to clean up`,children:[(0,F.jsx)(d,{className:`h-3 w-3`}),`landed`]}):(0,F.jsxs)(`span`,{className:p>0?`flex items-center text-emerald-400`:`flex items-center text-muted-foreground`,title:u===p?`${p} commit(s) ahead of base`:`${p} unmerged commit(s) (${u} ahead of base)`,children:[(0,F.jsx)(n,{className:`h-3 w-3`}),p]}),(0,F.jsxs)(`span`,{className:m>0?`flex items-center text-amber-400`:`flex items-center text-muted-foreground`,title:`${m} commit(s) behind base`,children:[(0,F.jsx)(i,{className:`h-3 w-3`}),m]}),h>0&&(0,F.jsxs)(`span`,{className:`flex items-center text-orange-400`,title:`${h} uncommitted change(s)`,children:[(0,F.jsx)(a,{className:`h-3 w-3`}),h]}),!f&&(0,F.jsx)(X,{workspace:e,unmerged:p})]}),l]}),v&&(0,F.jsxs)(`div`,{className:`flex min-w-0 items-center gap-1 text-[11px] text-muted-foreground`,title:`${v.sha.slice(0,8)} — ${v.message}`,children:[(0,F.jsx)(_,{className:`h-3 w-3 shrink-0`}),(0,F.jsx)(`span`,{className:`truncate`,children:v.message||v.sha.slice(0,8)}),v.at?(0,F.jsxs)(`span`,{className:`shrink-0 opacity-70`,children:[`· `,w(t,v.at)]}):null]})]})}function Q({workspace:e}){let t=S(),n=typeof e.metadata?.mergedAt==`number`?e.metadata.mergedAt:void 0,r=[{label:`created`,at:e.createdAt},{label:`ready`,at:e.readyAt},{label:`merged`,at:n},{label:`cleaned`,at:e.cleanedAt}].filter(e=>typeof e.at==`number`&&e.at>0);return(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-muted-foreground`,children:[(0,F.jsxs)(`span`,{className:`flex items-center gap-1 font-medium text-foreground`,children:[(0,F.jsx)(y,{className:`h-3 w-3`}),`Timeline`]}),r.map((e,n)=>(0,F.jsxs)(`span`,{className:`flex items-center gap-1`,children:[n>0&&(0,F.jsx)(`span`,{className:`opacity-40`,children:`→`}),(0,F.jsx)(`span`,{className:`text-foreground`,children:e.label}),(0,F.jsx)(`span`,{title:new Date(e.at).toLocaleString(),children:w(t,e.at)})]},e.label))]})}function te(e){return e.startsWith(`+`)&&!e.startsWith(`+++`)?`text-emerald-400`:e.startsWith(`-`)&&!e.startsWith(`---`)?`text-red-400`:e.startsWith(`@@`)?`text-cyan-400`:e.startsWith(`diff `)||e.startsWith(`index `)||e.startsWith(`+++`)||e.startsWith(`---`)?`text-muted-foreground`:``}function ne({workspace:e}){let t=C(t=>t.workspaceDiff[e.id]),n=C(e=>e.fetchWorkspaceDiff),r=e.mode===`isolated`&&!!e.worktreePath;if((0,P.useEffect)(()=>{r&&t===void 0&&n(e.id)},[r,t,e.id,n]),!r)return null;if(t===void 0)return(0,F.jsx)(`div`,{className:`text-[11px] text-muted-foreground`,children:`Loading diff…`});if(t.available===!1)return(0,F.jsxs)(`div`,{className:`text-[11px] text-muted-foreground`,children:[`Diff unavailable`,t.reason?`: ${t.reason}`:``]});if(t.missing)return(0,F.jsx)(`div`,{className:`text-[11px] text-muted-foreground`,children:`Worktree no longer on disk`});if(t.error)return(0,F.jsxs)(`div`,{className:`text-[11px] text-red-400`,children:[`Diff error: `,t.error]});if(!t.files.length)return(0,F.jsxs)(`div`,{className:`text-[11px] text-muted-foreground`,children:[`No committed changes against `,t.baseRef||`base`]});let i=(0,F.jsx)(O,{size:`icon-sm`,variant:`ghost`,className:`h-5 w-5`,title:`Refresh diff`,onClick:()=>void n(e.id),children:(0,F.jsx)(o,{className:`h-3 w-3`})});return(0,F.jsxs)(`div`,{className:`space-y-1`,children:[(0,F.jsxs)(`div`,{className:`flex items-center gap-2 text-[11px] font-medium`,children:[(0,F.jsx)(f,{className:`h-3 w-3`}),(0,F.jsxs)(`span`,{children:[t.files.length,` file`,t.files.length===1?``:`s`,` vs `,t.baseRef||`base`]}),i]}),(0,F.jsx)(`ul`,{className:`space-y-0.5 text-[11px]`,children:t.files.map(e=>(0,F.jsxs)(`li`,{className:`flex items-center gap-2 font-mono`,children:[(0,F.jsx)(`span`,{className:`truncate`,title:e.path,children:e.path}),e.binary?(0,F.jsx)(`span`,{className:`shrink-0 text-muted-foreground`,children:`binary`}):(0,F.jsxs)(`span`,{className:`shrink-0`,children:[(0,F.jsxs)(`span`,{className:`text-emerald-400`,children:[`+`,e.additions??0]}),` `,(0,F.jsxs)(`span`,{className:`text-red-400`,children:[`−`,e.deletions??0]})]})]},e.path))}),t.patch&&(0,F.jsxs)(`pre`,{className:`max-h-80 overflow-auto rounded border border-border bg-muted/30 p-2 text-[10.5px] leading-relaxed`,children:[t.patch.split(`
2
- `).map((e,t)=>(0,F.jsx)(`div`,{className:te(e),children:e||` `},t)),t.truncated&&(0,F.jsx)(`div`,{className:`mt-1 text-muted-foreground`,children:`… patch truncated`})]})]})}function $({workspace:e}){return(0,F.jsxs)(`div`,{className:`space-y-3 border-t border-dashed border-border bg-muted/20 px-3 py-3`,children:[(0,F.jsx)(Q,{workspace:e}),(0,F.jsx)(ne,{workspace:e})]})}function re(){let e=C(e=>e.workspaceOrphans),n=C(e=>e.fetchWorkspaceOrphans),r=C(e=>e.reclaimWorkspaceOrphan);return(0,P.useEffect)(()=>{n()},[n]),e.length?(0,F.jsxs)(N,{className:`border-amber-500/30`,children:[(0,F.jsxs)(A,{className:`px-4 py-3`,children:[(0,F.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,F.jsx)(p,{className:`h-4 w-4 text-amber-400`}),(0,F.jsx)(j,{className:`text-sm font-medium`,children:`Orphaned worktrees`}),(0,F.jsx)(D,{variant:`outline`,className:`border-amber-500/30 text-amber-400 text-[10px]`,children:e.length}),(0,F.jsx)(O,{size:`icon-sm`,variant:`ghost`,className:`ml-auto h-6 w-6`,title:`Rescan`,onClick:()=>void n(),children:(0,F.jsx)(o,{className:`h-3.5 w-3.5`})})]}),(0,F.jsx)(`p`,{className:`mt-1 text-xs text-muted-foreground`,children:`Agent worktrees on disk with no live workspace. Reclaim removes them from the host.`})]}),(0,F.jsx)(M,{className:`p-0`,children:e.map(e=>(0,F.jsxs)(`div`,{className:`flex items-center gap-3 border-t border-border px-4 py-2 text-xs`,children:[(0,F.jsx)(t,{className:`h-3.5 w-3.5 shrink-0 text-muted-foreground`}),(0,F.jsx)(`span`,{className:`truncate font-medium`,title:e.branch,children:e.branch||`(detached)`}),(0,F.jsx)(`span`,{className:`truncate font-mono text-muted-foreground`,title:e.worktreePath,children:U(e.worktreePath)}),e.hadTerminalRow&&(0,F.jsx)(D,{variant:`outline`,className:`text-[10px]`,children:`cleanup failed`}),(0,F.jsxs)(O,{size:`sm`,variant:`outline`,className:`ml-auto h-7 text-xs`,onClick:()=>void r(e),children:[(0,F.jsx)(c,{className:`mr-1 h-3.5 w-3.5`}),`Reclaim`]})]},e.worktreePath))})]}):null}function ie(e){let t=e.match(/\/pull\/(\d+)/);return t?`PR #${t[1]}`:`View PR`}function ae({workspace:e}){let t=e.metadata?.mergeResult,n=t?.prUrl,i=t?.error||e.metadata?.mergeError;return!n&&!i?null:(0,F.jsxs)(F.Fragment,{children:[n&&(0,F.jsxs)(`a`,{href:n,target:`_blank`,rel:`noreferrer`,title:n,className:`inline-flex items-center gap-0.5 rounded border border-violet-500/30 px-1.5 py-0 text-[10px] text-violet-400 hover:text-violet-300`,children:[(0,F.jsx)(r,{className:`h-2.5 w-2.5`}),ie(n)]}),i&&(0,F.jsxs)(D,{variant:`outline`,className:`max-w-[16rem] border-amber-500/30 text-amber-400 text-[10px]`,title:i,children:[(0,F.jsx)(v,{className:`mr-0.5 h-2.5 w-2.5 shrink-0`}),(0,F.jsxs)(`span`,{className:`truncate`,children:[`merge: `,i]})]})]})}function oe({workspace:e}){let n=S(),[r,i]=(0,P.useState)(!1),a=C(e=>e.agentsById),o=C(t=>t.workspaceFocusId===e.id),s=e.ownerAgentId?a[e.ownerAgentId]:void 0,c=e.stewardAgentId?a[e.stewardAgentId]:void 0,l=e.worktreePath||e.sourceCwd||e.repoRoot,u=(0,P.useRef)(null);return(0,P.useEffect)(()=>{o&&u.current?.scrollIntoView({behavior:`smooth`,block:`center`})},[o]),(0,F.jsxs)(`div`,{ref:u,className:`border-t border-border ${o?`bg-primary/5 ring-1 ring-inset ring-primary/40`:``}`,children:[(0,F.jsxs)(`div`,{className:`grid gap-3 px-3 py-3 text-sm lg:grid-cols-[minmax(180px,1.2fr)_minmax(220px,1.5fr)_minmax(160px,1fr)_minmax(170px,auto)]`,children:[(0,F.jsxs)(`div`,{className:`min-w-0 space-y-1`,children:[(0,F.jsxs)(`div`,{className:`flex min-w-0 items-center gap-2`,children:[(0,F.jsx)(t,{className:`h-3.5 w-3.5 shrink-0 text-muted-foreground`}),(0,F.jsx)(`span`,{className:`truncate font-medium`,title:e.branch||e.id,children:e.branch||e.id})]}),(0,F.jsxs)(`div`,{className:`flex flex-wrap gap-1`,children:[(0,F.jsx)(D,{variant:`outline`,className:`text-[10px] ${e.mode===`isolated`?`border-sky-500/30 text-sky-400`:`border-zinc-500/30 text-zinc-400`}`,children:e.mode}),e.requestedMode&&e.requestedMode!==e.mode&&(0,F.jsxs)(D,{variant:`outline`,className:`text-[10px]`,children:[`requested `,e.requestedMode]}),(0,F.jsx)(D,{variant:`outline`,className:`text-[10px] ${G(e.status)}`,children:B[e.status]||e.status}),(0,F.jsx)(ae,{workspace:e})]})]}),(0,F.jsxs)(`div`,{className:`min-w-0 space-y-1`,children:[(0,F.jsx)(`div`,{className:`truncate font-mono text-xs`,title:l,children:U(l)}),(0,F.jsx)(`div`,{className:`truncate text-xs text-muted-foreground`,title:e.sourceCwd,children:e.sourceCwd}),(0,F.jsx)(Z,{workspace:e})]}),(0,F.jsxs)(`div`,{className:`grid grid-cols-[4.5rem_minmax(0,1fr)] gap-x-2 gap-y-1 text-xs`,children:[(0,F.jsx)(`span`,{className:`text-muted-foreground`,children:`Owner`}),(0,F.jsx)(`span`,{className:`truncate`,title:e.ownerAgentId,children:W(s,e.ownerPolicyName||e.ownerAgentId)}),(0,F.jsx)(`span`,{className:`text-muted-foreground`,children:`Steward`}),(0,F.jsx)(`span`,{className:`truncate`,title:e.stewardAgentId,children:W(c,e.stewardAgentId)}),(0,F.jsx)(`span`,{className:`text-muted-foreground`,children:`Updated`}),(0,F.jsx)(`span`,{children:w(n,e.updatedAt)}),e.baseRef&&(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`span`,{className:`text-muted-foreground`,children:`Base`}),(0,F.jsx)(`span`,{className:`truncate`,title:e.baseSha,children:e.baseRef})]})]}),(0,F.jsx)(Y,{workspace:e,expanded:r,onToggleDetails:()=>i(e=>!e)})]}),r&&(0,F.jsx)($,{workspace:e})]})}function se({repoRoot:e,workspaces:t}){let n=t.filter(e=>I.has(e.status)).length,r=t.filter(e=>e.status===`ready`||e.status===`review_requested`||e.status===`merge_planned`).length,i=t.filter(e=>e.status===`conflict`).length;return(0,F.jsxs)(N,{children:[(0,F.jsx)(A,{className:`px-4 py-3`,children:(0,F.jsx)(`div`,{className:`flex min-w-0 items-start gap-3`,children:(0,F.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,F.jsx)(j,{className:`truncate text-sm font-medium`,title:e,children:e}),(0,F.jsxs)(`div`,{className:`mt-1 flex flex-wrap gap-1.5`,children:[(0,F.jsxs)(D,{variant:`outline`,className:`text-[10px]`,children:[t.length,` total`]}),(0,F.jsxs)(D,{variant:`outline`,className:`text-[10px]`,children:[n,` live`]}),r>0&&(0,F.jsxs)(D,{variant:`outline`,className:`border-emerald-500/20 text-emerald-400 text-[10px]`,children:[r,` merge signal`]}),i>0&&(0,F.jsxs)(D,{variant:`outline`,className:`border-red-500/20 text-red-400 text-[10px]`,children:[i,` conflict`]})]})]})})}),(0,F.jsx)(M,{className:`p-0`,children:t.map(e=>(0,F.jsx)(oe,{workspace:e},e.id))})]})}function ce(){let[e,t]=(0,P.useState)(`active`),n=C(e=>e.workspaces),r=C(e=>e.set),i=C(e=>e.workspaceFocusId);(0,P.useEffect)(()=>{i&&t(`all`)},[i]),(0,P.useEffect)(()=>()=>{r({workspaceFocusId:null})},[r]);let a=(0,P.useMemo)(()=>J(n,e),[n,e]),o=(0,P.useMemo)(()=>q(a),[a]),s=n.filter(e=>I.has(e.status)).length,c=n.filter(e=>e.mode===`isolated`).length,l=n.filter(e=>e.status===`ready`||e.status===`review_requested`||e.status===`merge_planned`).length,u=n.filter(e=>e.status===`conflict`).length,d=n.filter(e=>H.has(e.status)).length,f=[{key:`active`,label:`Active`,count:n.length-d},{key:`cleaned`,label:`Done`,count:d},{key:`all`,label:`All`,count:n.length}];return(0,F.jsxs)(`div`,{className:`space-y-4`,children:[(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-3`,children:[(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Workspaces`}),(0,F.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Agent worktrees, ownership, and merge readiness.`})]}),(0,F.jsxs)(`div`,{className:`ml-auto flex flex-wrap gap-1.5`,children:[(0,F.jsxs)(D,{variant:`outline`,children:[c,` isolated`]}),(0,F.jsxs)(D,{variant:`outline`,children:[s,` live`]}),(0,F.jsxs)(D,{variant:`outline`,children:[l,` ready`]}),u>0&&(0,F.jsxs)(D,{variant:`outline`,className:`border-red-500/20 text-red-400`,children:[u,` conflict`]})]})]}),(0,F.jsx)(re,{}),(0,F.jsx)(`div`,{className:`flex flex-wrap items-center gap-2`,children:(0,F.jsx)(`div`,{className:`flex rounded-md border border-border bg-card p-1`,children:f.map(n=>(0,F.jsxs)(O,{size:`sm`,variant:e===n.key?`secondary`:`ghost`,className:`h-7 text-xs`,onClick:()=>t(n.key),children:[n.label,(0,F.jsx)(`span`,{className:`ml-1 text-muted-foreground`,children:n.count})]},n.key))})}),o.length===0?(0,F.jsxs)(`div`,{className:`rounded-md border border-border px-3 py-12 text-center text-sm text-muted-foreground`,children:[`No `,e===`all`?``:e,` workspaces`]}):(0,F.jsx)(`div`,{className:`space-y-3`,children:o.map(e=>(0,F.jsx)(se,{repoRoot:e.repoRoot,workspaces:e.workspaces},e.repoRoot))})]})}export{ce as WorkspacesView};
3
- //# sourceMappingURL=workspaces-QXbQx_AG.js.map
1
+ import{r as e}from"./chunk-CilyBKbf.js";import{At as t,Bn as n,Dt as r,Gn as i,Ht as a,I as o,It as s,Jn as c,Kt as l,Qn as u,Sn as d,Wt as f,X as p,dr as m,h,i as g,kt as _,m as v,on as y,xn as b,yn as ee,zt as x}from"./lucide-react-BYZuAhQs.js";import{f as S,t as C}from"./store-zeyDiSKE.js";import{i as w}from"./types-DorDDvEb.js";import{K as T,d as E}from"./display-D1xB8NJZ.js";import{t as D}from"./badge-BLar6FjP.js";import{t as O}from"./button-BrIB69qd.js";import{t as k}from"./copy-button-Cd-LbSbg.js";import{a as A,o as j,r as M,t as N}from"./card-lhnFnBoz.js";var P=e(m(),1),F=u(),I=new Set([`active`,`ready`,`conflict`,`review_requested`,`merge_planned`,`cleanup_requested`]),L=new Set([`active`,`ready`,`conflict`,`review_requested`,`merge_planned`]),R=new Set([`active`,`ready`,`review_requested`,`merge_planned`,`conflict`]),z={active:`bg-blue-500/10 text-blue-400 border-blue-500/20`,ready:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,conflict:`bg-red-500/10 text-red-400 border-red-500/20`,review_requested:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,merge_planned:`bg-indigo-500/10 text-indigo-400 border-indigo-500/20`,merged:`bg-violet-500/10 text-violet-400 border-violet-500/20`,abandoned:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`,cleanup_requested:`bg-orange-500/10 text-orange-400 border-orange-500/20`,cleaned:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`},B={active:`active`,ready:`ready`,conflict:`conflict`,review_requested:`review`,merge_planned:`merge planned`,merged:`merged`,abandoned:`abandoned`,cleanup_requested:`cleanup`,cleaned:`cleaned`},V={conflict:0,ready:1,review_requested:2,merge_planned:3,active:4,cleanup_requested:5,merged:6,abandoned:7,cleaned:8},H=new Set(w);function U(e){let t=e.split(`/`).filter(Boolean);return t.length<=3?e||`-`:`/${t.slice(-3).join(`/`)}`}function W(e,t){return e?E(e):t||`-`}function G(e){return z[e]||`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`}function K(e,t){let n=(V[e.status]??99)-(V[t.status]??99);return n===0?Number(t.updatedAt||0)-Number(e.updatedAt||0):n}function q(e){let t=new Map;for(let n of e){let e=n.repoRoot||n.sourceCwd||`unknown repo`;t.set(e,[...t.get(e)||[],n])}return[...t.entries()].map(([e,t])=>({repoRoot:e,workspaces:t.sort(K)})).sort((e,t)=>{let n=e.workspaces.some(e=>I.has(e.status));return n===t.workspaces.some(e=>I.has(e.status))?e.repoRoot.localeCompare(t.repoRoot):n?-1:1})}function J(e,t){return t===`all`?e:t===`cleaned`?e.filter(e=>H.has(e.status)):e.filter(e=>!H.has(e.status))}function Y({workspace:e,expanded:t,onToggleDetails:n}){let i=C(e=>e.workspaceAction),a=C(e=>e.purgeWorkspace),o=C(e=>e.fetchWorkspaceMergePreview),c=C(e=>e.openFilesAt),u=e.status===`cleaned`||e.status===`merged`||e.status===`abandoned`,f=u||e.status===`cleanup_requested`,p=e.worktreePath||e.sourceCwd||e.repoRoot,m=C(t=>t.workspaceGitState[e.id]),_=!!m&&m.available!==!1&&m.landed===!0,v=e.mode===`isolated`&&!!e.worktreePath&&R.has(e.status)&&!_;async function y(){await o(e.id),await i(e.id,`merge`)}return(0,F.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-1.5`,children:[(0,F.jsx)(O,{size:`icon-sm`,variant:`ghost`,title:t?`Hide details`:`Show diff & timeline`,onClick:n,children:t?(0,F.jsx)(b,{className:`h-3.5 w-3.5`}):(0,F.jsx)(ee,{className:`h-3.5 w-3.5`})}),(0,F.jsx)(O,{size:`icon-sm`,variant:`ghost`,title:`Open workspace`,disabled:!p||e.status===`cleaned`,onClick:()=>void c({path:p}),children:(0,F.jsx)(s,{className:`h-3.5 w-3.5`})}),(0,F.jsx)(k,{value:p,label:`Copy path`,copiedLabel:`Copied path`,size:`icon-sm`,variant:`ghost`,disabled:!p}),(0,F.jsx)(O,{size:`icon-sm`,variant:`outline`,title:`Mark ready`,disabled:f||e.status===`ready`,onClick:()=>void i(e.id,`ready`),children:(0,F.jsx)(d,{className:`h-3.5 w-3.5`})}),(0,F.jsx)(O,{size:`icon-sm`,variant:`outline`,title:`Request review`,disabled:f||e.status===`review_requested`,onClick:()=>void i(e.id,`request-review`),children:(0,F.jsx)(l,{className:`h-3.5 w-3.5`})}),(0,F.jsx)(O,{size:`icon-sm`,variant:`outline`,title:`Mark merge planned`,disabled:f||e.status===`merge_planned`,onClick:()=>void i(e.id,`merge-plan`),children:(0,F.jsx)(x,{className:`h-3.5 w-3.5`})}),(0,F.jsx)(O,{size:`icon-sm`,variant:`default`,title:_?`Already merged into base — nothing to land`:`Merge & land work`,disabled:!v,onClick:()=>void y(),children:(0,F.jsx)(r,{className:`h-3.5 w-3.5`})}),(0,F.jsx)(O,{size:`icon-sm`,variant:`outline`,title:`Request cleanup (removes the worktree on the host)`,disabled:e.status===`cleaned`||e.status===`cleanup_requested`,onClick:()=>void i(e.id,`cleanup`),children:(0,F.jsx)(h,{className:`h-3.5 w-3.5`})}),u&&(0,F.jsx)(O,{size:`icon-sm`,variant:`ghost`,title:`Purge record (clears the row, does not touch disk)`,onClick:()=>void a(e.id),children:(0,F.jsx)(g,{className:`h-3.5 w-3.5`})})]})}function X({workspace:e,unmerged:t}){let n=C(t=>t.workspaceMergePreview[e.id]),r=C(e=>e.fetchWorkspaceMergePreview),i=t>0&&R.has(e.status);return(0,P.useEffect)(()=>{i&&n===void 0&&r(e.id)},[i,n,e.id,r]),!i||n===void 0||n.available===!1?null:n.conflict?(0,F.jsxs)(`span`,{className:`flex items-center gap-0.5 text-amber-400`,title:`Merging into ${n.baseRef||`base`} would conflict — resolve before merging`,children:[(0,F.jsx)(v,{className:`h-3 w-3`}),`conflict`]}):n.conflict===!1?(0,F.jsxs)(`span`,{className:`flex items-center gap-0.5 text-emerald-400`,title:`Clean to merge via ${n.strategy===`pr`?`PR`:`rebase + fast-forward`} into ${n.baseRef||`base`}`,children:[(0,F.jsx)(d,{className:`h-3 w-3`}),n.strategy===`pr`?`PR ready`:`clean`]}):null}function Z({workspace:e}){let t=S(),r=C(t=>t.workspaceGitState[e.id]),s=C(e=>e.fetchWorkspaceGitState),c=e.mode===`isolated`&&!!e.worktreePath&&L.has(e.status);if((0,P.useEffect)(()=>{c&&r===void 0&&s(e.id)},[c,r,e.id,s]),!c)return null;let l=(0,F.jsx)(O,{size:`icon-sm`,variant:`ghost`,className:`h-5 w-5`,title:`Refresh git state`,onClick:()=>void s(e.id),children:(0,F.jsx)(o,{className:`h-3 w-3`})});if(r===void 0)return(0,F.jsx)(`div`,{className:`flex items-center gap-1 text-[11px] text-muted-foreground`,children:`Loading git state…`});if(r.available===!1)return(0,F.jsxs)(`div`,{className:`flex items-center gap-1 text-[11px] text-muted-foreground`,title:r.reason,children:[(0,F.jsx)(`span`,{children:`git state unavailable`}),l]});if(r.missing)return(0,F.jsxs)(`div`,{className:`flex items-center gap-1 text-[11px] text-muted-foreground`,children:[`worktree gone `,l]});if(r.error)return(0,F.jsxs)(`div`,{className:`flex items-center gap-1 text-[11px] text-red-400`,title:r.error,children:[(0,F.jsx)(`span`,{children:`git error`}),l]});let u=r.ahead??0,f=r.landed===!0,p=f?0:r.unmergedAhead??u,m=r.behind??0,h=r.dirtyCount??0,g=p===0&&h===0&&!f,v=r.lastCommit;return(0,F.jsxs)(`div`,{className:`space-y-0.5`,children:[(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[11px]`,children:[g?(0,F.jsx)(`span`,{className:`text-muted-foreground`,children:`no unmerged work`}):(0,F.jsxs)(F.Fragment,{children:[f?(0,F.jsxs)(`span`,{className:`flex items-center gap-0.5 text-emerald-400`,title:`Work already landed in ${r.baseRef||`base`} via squash/cherry-pick — safe to clean up`,children:[(0,F.jsx)(d,{className:`h-3 w-3`}),`landed`]}):(0,F.jsxs)(`span`,{className:p>0?`flex items-center text-emerald-400`:`flex items-center text-muted-foreground`,title:u===p?`${p} commit(s) ahead of base`:`${p} unmerged commit(s) (${u} ahead of base)`,children:[(0,F.jsx)(n,{className:`h-3 w-3`}),p]}),(0,F.jsxs)(`span`,{className:m>0?`flex items-center text-amber-400`:`flex items-center text-muted-foreground`,title:`${m} commit(s) behind base`,children:[(0,F.jsx)(i,{className:`h-3 w-3`}),m]}),h>0&&(0,F.jsxs)(`span`,{className:`flex items-center text-orange-400`,title:`${h} uncommitted change(s)`,children:[(0,F.jsx)(a,{className:`h-3 w-3`}),h]}),!f&&(0,F.jsx)(X,{workspace:e,unmerged:p})]}),l]}),v&&(0,F.jsxs)(`div`,{className:`flex min-w-0 items-center gap-1 text-[11px] text-muted-foreground`,title:`${v.sha.slice(0,8)} — ${v.message}`,children:[(0,F.jsx)(_,{className:`h-3 w-3 shrink-0`}),(0,F.jsx)(`span`,{className:`truncate`,children:v.message||v.sha.slice(0,8)}),v.at?(0,F.jsxs)(`span`,{className:`shrink-0 opacity-70`,children:[`· `,T(t,v.at)]}):null]})]})}function Q({workspace:e}){let t=S(),n=typeof e.metadata?.mergedAt==`number`?e.metadata.mergedAt:void 0,r=[{label:`created`,at:e.createdAt},{label:`ready`,at:e.readyAt},{label:`merged`,at:n},{label:`cleaned`,at:e.cleanedAt}].filter(e=>typeof e.at==`number`&&e.at>0);return(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-muted-foreground`,children:[(0,F.jsxs)(`span`,{className:`flex items-center gap-1 font-medium text-foreground`,children:[(0,F.jsx)(y,{className:`h-3 w-3`}),`Timeline`]}),r.map((e,n)=>(0,F.jsxs)(`span`,{className:`flex items-center gap-1`,children:[n>0&&(0,F.jsx)(`span`,{className:`opacity-40`,children:`→`}),(0,F.jsx)(`span`,{className:`text-foreground`,children:e.label}),(0,F.jsx)(`span`,{title:new Date(e.at).toLocaleString(),children:T(t,e.at)})]},e.label))]})}function te(e){return e.startsWith(`+`)&&!e.startsWith(`+++`)?`text-emerald-400`:e.startsWith(`-`)&&!e.startsWith(`---`)?`text-red-400`:e.startsWith(`@@`)?`text-cyan-400`:e.startsWith(`diff `)||e.startsWith(`index `)||e.startsWith(`+++`)||e.startsWith(`---`)?`text-muted-foreground`:``}function ne({workspace:e}){let t=C(t=>t.workspaceDiff[e.id]),n=C(e=>e.fetchWorkspaceDiff),r=e.mode===`isolated`&&!!e.worktreePath;if((0,P.useEffect)(()=>{r&&t===void 0&&n(e.id)},[r,t,e.id,n]),!r)return null;if(t===void 0)return(0,F.jsx)(`div`,{className:`text-[11px] text-muted-foreground`,children:`Loading diff…`});if(t.available===!1)return(0,F.jsxs)(`div`,{className:`text-[11px] text-muted-foreground`,children:[`Diff unavailable`,t.reason?`: ${t.reason}`:``]});if(t.missing)return(0,F.jsx)(`div`,{className:`text-[11px] text-muted-foreground`,children:`Worktree no longer on disk`});if(t.error)return(0,F.jsxs)(`div`,{className:`text-[11px] text-red-400`,children:[`Diff error: `,t.error]});if(!t.files.length)return(0,F.jsxs)(`div`,{className:`text-[11px] text-muted-foreground`,children:[`No committed changes against `,t.baseRef||`base`]});let i=(0,F.jsx)(O,{size:`icon-sm`,variant:`ghost`,className:`h-5 w-5`,title:`Refresh diff`,onClick:()=>void n(e.id),children:(0,F.jsx)(o,{className:`h-3 w-3`})});return(0,F.jsxs)(`div`,{className:`space-y-1`,children:[(0,F.jsxs)(`div`,{className:`flex items-center gap-2 text-[11px] font-medium`,children:[(0,F.jsx)(f,{className:`h-3 w-3`}),(0,F.jsxs)(`span`,{children:[t.files.length,` file`,t.files.length===1?``:`s`,` vs `,t.baseRef||`base`]}),i]}),(0,F.jsx)(`ul`,{className:`space-y-0.5 text-[11px]`,children:t.files.map(e=>(0,F.jsxs)(`li`,{className:`flex items-center gap-2 font-mono`,children:[(0,F.jsx)(`span`,{className:`truncate`,title:e.path,children:e.path}),e.binary?(0,F.jsx)(`span`,{className:`shrink-0 text-muted-foreground`,children:`binary`}):(0,F.jsxs)(`span`,{className:`shrink-0`,children:[(0,F.jsxs)(`span`,{className:`text-emerald-400`,children:[`+`,e.additions??0]}),` `,(0,F.jsxs)(`span`,{className:`text-red-400`,children:[`−`,e.deletions??0]})]})]},e.path))}),t.patch&&(0,F.jsxs)(`pre`,{className:`max-h-80 overflow-auto rounded border border-border bg-muted/30 p-2 text-[10.5px] leading-relaxed`,children:[t.patch.split(`
2
+ `).map((e,t)=>(0,F.jsx)(`div`,{className:te(e),children:e||` `},t)),t.truncated&&(0,F.jsx)(`div`,{className:`mt-1 text-muted-foreground`,children:`… patch truncated`})]})]})}function $({workspace:e}){return(0,F.jsxs)(`div`,{className:`space-y-3 border-t border-dashed border-border bg-muted/20 px-3 py-3`,children:[(0,F.jsx)(Q,{workspace:e}),(0,F.jsx)(ne,{workspace:e})]})}function re(){let e=C(e=>e.workspaceOrphans),n=C(e=>e.fetchWorkspaceOrphans),r=C(e=>e.reclaimWorkspaceOrphan);return(0,P.useEffect)(()=>{n()},[n]),e.length?(0,F.jsxs)(N,{className:`border-amber-500/30`,children:[(0,F.jsxs)(A,{className:`px-4 py-3`,children:[(0,F.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,F.jsx)(p,{className:`h-4 w-4 text-amber-400`}),(0,F.jsx)(j,{className:`text-sm font-medium`,children:`Orphaned worktrees`}),(0,F.jsx)(D,{variant:`outline`,className:`border-amber-500/30 text-amber-400 text-[10px]`,children:e.length}),(0,F.jsx)(O,{size:`icon-sm`,variant:`ghost`,className:`ml-auto h-6 w-6`,title:`Rescan`,onClick:()=>void n(),children:(0,F.jsx)(o,{className:`h-3.5 w-3.5`})})]}),(0,F.jsx)(`p`,{className:`mt-1 text-xs text-muted-foreground`,children:`Agent worktrees on disk with no live workspace. Reclaim removes them from the host.`})]}),(0,F.jsx)(M,{className:`p-0`,children:e.map(e=>(0,F.jsxs)(`div`,{className:`flex items-center gap-3 border-t border-border px-4 py-2 text-xs`,children:[(0,F.jsx)(t,{className:`h-3.5 w-3.5 shrink-0 text-muted-foreground`}),(0,F.jsx)(`span`,{className:`truncate font-medium`,title:e.branch,children:e.branch||`(detached)`}),(0,F.jsx)(`span`,{className:`truncate font-mono text-muted-foreground`,title:e.worktreePath,children:U(e.worktreePath)}),e.hadTerminalRow&&(0,F.jsx)(D,{variant:`outline`,className:`text-[10px]`,children:`cleanup failed`}),(0,F.jsxs)(O,{size:`sm`,variant:`outline`,className:`ml-auto h-7 text-xs`,onClick:()=>void r(e),children:[(0,F.jsx)(c,{className:`mr-1 h-3.5 w-3.5`}),`Reclaim`]})]},e.worktreePath))})]}):null}function ie(e){let t=e.match(/\/pull\/(\d+)/);return t?`PR #${t[1]}`:`View PR`}function ae({workspace:e}){let t=e.metadata?.mergeResult,n=t?.prUrl,i=t?.error||e.metadata?.mergeError;return!n&&!i?null:(0,F.jsxs)(F.Fragment,{children:[n&&(0,F.jsxs)(`a`,{href:n,target:`_blank`,rel:`noreferrer`,title:n,className:`inline-flex items-center gap-0.5 rounded border border-violet-500/30 px-1.5 py-0 text-[10px] text-violet-400 hover:text-violet-300`,children:[(0,F.jsx)(r,{className:`h-2.5 w-2.5`}),ie(n)]}),i&&(0,F.jsxs)(D,{variant:`outline`,className:`max-w-[16rem] border-amber-500/30 text-amber-400 text-[10px]`,title:i,children:[(0,F.jsx)(v,{className:`mr-0.5 h-2.5 w-2.5 shrink-0`}),(0,F.jsxs)(`span`,{className:`truncate`,children:[`merge: `,i]})]})]})}function oe({workspace:e}){let n=S(),[r,i]=(0,P.useState)(!1),a=C(e=>e.agentsById),o=C(t=>t.workspaceFocusId===e.id),s=e.ownerAgentId?a[e.ownerAgentId]:void 0,c=e.stewardAgentId?a[e.stewardAgentId]:void 0,l=e.worktreePath||e.sourceCwd||e.repoRoot,u=(0,P.useRef)(null);return(0,P.useEffect)(()=>{o&&u.current?.scrollIntoView({behavior:`smooth`,block:`center`})},[o]),(0,F.jsxs)(`div`,{ref:u,className:`border-t border-border ${o?`bg-primary/5 ring-1 ring-inset ring-primary/40`:``}`,children:[(0,F.jsxs)(`div`,{className:`grid gap-3 px-3 py-3 text-sm lg:grid-cols-[minmax(180px,1.2fr)_minmax(220px,1.5fr)_minmax(160px,1fr)_minmax(170px,auto)]`,children:[(0,F.jsxs)(`div`,{className:`min-w-0 space-y-1`,children:[(0,F.jsxs)(`div`,{className:`flex min-w-0 items-center gap-2`,children:[(0,F.jsx)(t,{className:`h-3.5 w-3.5 shrink-0 text-muted-foreground`}),(0,F.jsx)(`span`,{className:`truncate font-medium`,title:e.branch||e.id,children:e.branch||e.id})]}),(0,F.jsxs)(`div`,{className:`flex flex-wrap gap-1`,children:[(0,F.jsx)(D,{variant:`outline`,className:`text-[10px] ${e.mode===`isolated`?`border-sky-500/30 text-sky-400`:`border-zinc-500/30 text-zinc-400`}`,children:e.mode}),e.requestedMode&&e.requestedMode!==e.mode&&(0,F.jsxs)(D,{variant:`outline`,className:`text-[10px]`,children:[`requested `,e.requestedMode]}),(0,F.jsx)(D,{variant:`outline`,className:`text-[10px] ${G(e.status)}`,children:B[e.status]||e.status}),(0,F.jsx)(ae,{workspace:e})]})]}),(0,F.jsxs)(`div`,{className:`min-w-0 space-y-1`,children:[(0,F.jsx)(`div`,{className:`truncate font-mono text-xs`,title:l,children:U(l)}),(0,F.jsx)(`div`,{className:`truncate text-xs text-muted-foreground`,title:e.sourceCwd,children:e.sourceCwd}),(0,F.jsx)(Z,{workspace:e})]}),(0,F.jsxs)(`div`,{className:`grid grid-cols-[4.5rem_minmax(0,1fr)] gap-x-2 gap-y-1 text-xs`,children:[(0,F.jsx)(`span`,{className:`text-muted-foreground`,children:`Owner`}),(0,F.jsx)(`span`,{className:`truncate`,title:e.ownerAgentId,children:W(s,e.ownerPolicyName||e.ownerAgentId)}),(0,F.jsx)(`span`,{className:`text-muted-foreground`,children:`Steward`}),(0,F.jsx)(`span`,{className:`truncate`,title:e.stewardAgentId,children:W(c,e.stewardAgentId)}),(0,F.jsx)(`span`,{className:`text-muted-foreground`,children:`Updated`}),(0,F.jsx)(`span`,{children:T(n,e.updatedAt)}),e.baseRef&&(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`span`,{className:`text-muted-foreground`,children:`Base`}),(0,F.jsx)(`span`,{className:`truncate`,title:e.baseSha,children:e.baseRef})]})]}),(0,F.jsx)(Y,{workspace:e,expanded:r,onToggleDetails:()=>i(e=>!e)})]}),r&&(0,F.jsx)($,{workspace:e})]})}function se({repoRoot:e,workspaces:t}){let n=t.filter(e=>I.has(e.status)).length,r=t.filter(e=>e.status===`ready`||e.status===`review_requested`||e.status===`merge_planned`).length,i=t.filter(e=>e.status===`conflict`).length;return(0,F.jsxs)(N,{children:[(0,F.jsx)(A,{className:`px-4 py-3`,children:(0,F.jsx)(`div`,{className:`flex min-w-0 items-start gap-3`,children:(0,F.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,F.jsx)(j,{className:`truncate text-sm font-medium`,title:e,children:e}),(0,F.jsxs)(`div`,{className:`mt-1 flex flex-wrap gap-1.5`,children:[(0,F.jsxs)(D,{variant:`outline`,className:`text-[10px]`,children:[t.length,` total`]}),(0,F.jsxs)(D,{variant:`outline`,className:`text-[10px]`,children:[n,` live`]}),r>0&&(0,F.jsxs)(D,{variant:`outline`,className:`border-emerald-500/20 text-emerald-400 text-[10px]`,children:[r,` merge signal`]}),i>0&&(0,F.jsxs)(D,{variant:`outline`,className:`border-red-500/20 text-red-400 text-[10px]`,children:[i,` conflict`]})]})]})})}),(0,F.jsx)(M,{className:`p-0`,children:t.map(e=>(0,F.jsx)(oe,{workspace:e},e.id))})]})}function ce(){let[e,t]=(0,P.useState)(`active`),n=C(e=>e.workspaces),r=C(e=>e.set),i=C(e=>e.workspaceFocusId);(0,P.useEffect)(()=>{i&&t(`all`)},[i]),(0,P.useEffect)(()=>()=>{r({workspaceFocusId:null})},[r]);let a=(0,P.useMemo)(()=>J(n,e),[n,e]),o=(0,P.useMemo)(()=>q(a),[a]),s=n.filter(e=>I.has(e.status)).length,c=n.filter(e=>e.mode===`isolated`).length,l=n.filter(e=>e.status===`ready`||e.status===`review_requested`||e.status===`merge_planned`).length,u=n.filter(e=>e.status===`conflict`).length,d=n.filter(e=>H.has(e.status)).length,f=[{key:`active`,label:`Active`,count:n.length-d},{key:`cleaned`,label:`Done`,count:d},{key:`all`,label:`All`,count:n.length}];return(0,F.jsxs)(`div`,{className:`space-y-4`,children:[(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center gap-3`,children:[(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`h1`,{className:`text-2xl font-semibold`,children:`Workspaces`}),(0,F.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Agent worktrees, ownership, and merge readiness.`})]}),(0,F.jsxs)(`div`,{className:`ml-auto flex flex-wrap gap-1.5`,children:[(0,F.jsxs)(D,{variant:`outline`,children:[c,` isolated`]}),(0,F.jsxs)(D,{variant:`outline`,children:[s,` live`]}),(0,F.jsxs)(D,{variant:`outline`,children:[l,` ready`]}),u>0&&(0,F.jsxs)(D,{variant:`outline`,className:`border-red-500/20 text-red-400`,children:[u,` conflict`]})]})]}),(0,F.jsx)(re,{}),(0,F.jsx)(`div`,{className:`flex flex-wrap items-center gap-2`,children:(0,F.jsx)(`div`,{className:`flex rounded-md border border-border bg-card p-1`,children:f.map(n=>(0,F.jsxs)(O,{size:`sm`,variant:e===n.key?`secondary`:`ghost`,className:`h-7 text-xs`,onClick:()=>t(n.key),children:[n.label,(0,F.jsx)(`span`,{className:`ml-1 text-muted-foreground`,children:n.count})]},n.key))})}),o.length===0?(0,F.jsxs)(`div`,{className:`rounded-md border border-border px-3 py-12 text-center text-sm text-muted-foreground`,children:[`No `,e===`all`?``:e,` workspaces`]}):(0,F.jsx)(`div`,{className:`space-y-3`,children:o.map(e=>(0,F.jsx)(se,{repoRoot:e.repoRoot,workspaces:e.workspaces},e.repoRoot))})]})}export{ce as WorkspacesView};
3
+ //# sourceMappingURL=workspaces-CSpmgbPT.js.map