agent-relay-server 0.102.2 → 0.103.4

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
@@ -1,2 +0,0 @@
1
- import{r as e}from"./chunk-CilyBKbf.js";import{$n as t,I as n,Qn as r,Xn as i,dr as a,h as o,l as s,tt as ee,wn as c}from"./lucide-react-BYZuAhQs.js";import{i as te,n as ne,t as l}from"./store-DNbkrqbU.js";import{G as re,K as u,d,h as f}from"./display-DRiGi1zj.js";import{a as p}from"./types-Tv67voFu.js";import{a as ie,i as ae,n as oe,r as se,t as ce}from"./select-s4BE-T62.js";import{t as m}from"./badge-BLar6FjP.js";import{t as h}from"./button-BrIB69qd.js";import{t as le}from"./input-B_fUHtLD.js";import{h as ue,u as g}from"./index-DeDosfVE.js";import{a as _,o as v,r as y,t as b}from"./card-lhnFnBoz.js";import{t as de}from"./PlanGraphPanel-DO5OY2qa.js";var x=e(a(),1),S=r();function C(e){if(e==null)return`-`;let t=Math.round(e/1e3);if(t<60)return`${t}s`;let n=Math.round(t/60);if(n<60)return`${n}m`;let r=Math.round(n/60);return r<48?`${r}h`:`${Math.round(r/24)}d`}function w(e){return`${Math.round(e*100)}%`}function T(e){return e===`success`?`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`:e===`partial`?`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`:e===`failed`?`bg-red-500/10 text-red-400 border-red-500/20`:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`}function fe({data:e,loading:t,error:n}){let r=e?.totals.byOutcomeStatus,i=e?.compositionShapes.slice(0,6)??[],a=e?.recent.slice(0,8)??[];return(0,S.jsxs)(b,{children:[(0,S.jsx)(_,{children:(0,S.jsxs)(v,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,S.jsxs)(`span`,{className:`flex items-center gap-2`,children:[(0,S.jsx)(c,{className:`size-4 text-primary`}),` Outcomes`]}),(0,S.jsxs)(m,{variant:`secondary`,children:[e?.totals.total??0,` dissolved`]})]})}),(0,S.jsxs)(y,{className:`space-y-5`,children:[n?(0,S.jsx)(`div`,{className:`rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:n}):null,t&&!e?(0,S.jsx)(`div`,{className:`py-8 text-center text-sm text-muted-foreground`,children:`Loading outcomes…`}):!e||e.totals.total===0?(0,S.jsx)(`div`,{className:`py-8 text-center text-sm text-muted-foreground`,children:`No dissolved teams yet`}):(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(`div`,{className:`flex flex-wrap gap-2 text-xs`,children:[`success`,`partial`,`failed`,`unknown`].map(e=>{let t=r?.[e]??0;return(0,S.jsxs)(m,{variant:`outline`,className:t===0?`text-muted-foreground`:T(e===`unknown`?null:e),children:[t,` `,e]},e)})}),(0,S.jsxs)(`section`,{className:`space-y-2`,children:[(0,S.jsx)(`div`,{className:`text-sm font-medium`,children:`Composition win-rate`}),(0,S.jsx)(`div`,{className:`divide-y divide-border rounded-md border border-border`,children:i.map(e=>(0,S.jsxs)(`div`,{className:`grid gap-2 px-3 py-2 text-sm md:grid-cols-[1fr_auto_auto] md:items-center`,children:[(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`div`,{className:`break-words font-medium`,title:e.shape,children:e.shape}),(0,S.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[e.count,` team`,e.count===1?``:`s`,` · avg `,C(e.avgWallClockMs)]})]}),(0,S.jsxs)(`span`,{className:`text-muted-foreground`,children:[e.successCount,`/`,e.count]}),(0,S.jsx)(m,{variant:`outline`,className:T(e.successRate>=.5?`success`:e.successRate>0?`partial`:`failed`),children:w(e.successRate)})]},e.shape))})]}),(0,S.jsxs)(`section`,{className:`space-y-2`,children:[(0,S.jsx)(`div`,{className:`text-sm font-medium`,children:`Recent outcomes`}),(0,S.jsx)(`div`,{className:`overflow-hidden rounded-md border border-border`,children:(0,S.jsxs)(`table`,{className:`w-full text-sm`,children:[(0,S.jsx)(`thead`,{className:`bg-muted/40 text-xs text-muted-foreground`,children:(0,S.jsxs)(`tr`,{children:[(0,S.jsx)(`th`,{className:`px-3 py-2 text-left font-medium`,children:`Team`}),(0,S.jsx)(`th`,{className:`px-3 py-2 text-left font-medium`,children:`Outcome`}),(0,S.jsx)(`th`,{className:`px-3 py-2 text-right font-medium`,children:`Members`}),(0,S.jsx)(`th`,{className:`px-3 py-2 text-right font-medium`,children:`Duration`})]})}),(0,S.jsx)(`tbody`,{className:`divide-y divide-border`,children:a.map(e=>(0,S.jsxs)(`tr`,{children:[(0,S.jsxs)(`td`,{className:`max-w-[220px] px-3 py-2`,children:[(0,S.jsx)(`div`,{className:`truncate font-medium`,children:e.title||e.id.slice(0,12)}),(0,S.jsx)(`div`,{className:`truncate text-xs text-muted-foreground`,children:e.id})]}),(0,S.jsx)(`td`,{className:`px-3 py-2`,children:(0,S.jsx)(m,{variant:`outline`,className:T(e.outcome),children:e.outcome??`unknown`})}),(0,S.jsx)(`td`,{className:`px-3 py-2 text-right`,children:e.memberCount}),(0,S.jsx)(`td`,{className:`px-3 py-2 text-right text-muted-foreground`,children:C(e.durationMs)})]},e.id))})]})})]})]})]})]})}var pe=[],me=[{value:`all`,label:`All kinds`},{value:`decision`,label:`Decisions`},{value:`fact`,label:`Facts`},{value:`ruled_out`,label:`Ruled out`}],he={decision:`decision`,fact:`fact`,ruled_out:`ruled out`};function ge(e){return e.runtimeState?e.runtimeState:e.status===`dissolved`?`dissolved`:e.zeroMembersSince==null?`active`:`orphaned`}var _e={busy:`bg-blue-500/10 text-blue-400 border-blue-500/20`,idle:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,degraded:`bg-orange-500/10 text-orange-400 border-orange-500/20`,stalled:`bg-amber-500/10 text-amber-400 border-amber-500/20`,active:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,orphaned:`bg-amber-500/10 text-amber-400 border-amber-500/20`,dissolved:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`},ve={success:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,partial:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,failed:`bg-red-500/10 text-red-400 border-red-500/20`},ye={active:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,superseded:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,retracted:`bg-red-500/10 text-red-400 border-red-500/20`};function E(e){return e?e.slice(0,12):`-`}function be(e){if(e===void 0)return`-`;let t=Math.round(e/1e3);if(t<60)return`${t}s`;let n=Math.round(t/60);if(n<60)return`${n}m`;let r=Math.round(n/60);return r<48?`${r}h`:`${Math.round(r/24)}d`}function xe(e){if(!e.composition)return null;try{let t=JSON.parse(e.composition);return Array.isArray(t)?t.length:null}catch{return null}}function Se(e,t){let n=t.trim().toLowerCase();return n?[e.title,e.id,e.coordinatorId,e.rootCoordinatorId,e.outcomeStatus,e.outcomeSummary].some(e=>String(e??``).toLowerCase().includes(n)):!0}function Ce(e){return e.status===`offline`?`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`:e.status===`busy`?`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`:e.status===`stale`?`bg-red-500/10 text-red-400 border-red-500/20`:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`}function D(){let[e,r]=(0,x.useState)(null),[a,c]=(0,x.useState)([]),[C,w]=(0,x.useState)(null),[T,D]=(0,x.useState)(`all`),[O,we]=(0,x.useState)(``),[k,A]=(0,x.useState)(!1),[Te,j]=(0,x.useState)(!1),[M,N]=(0,x.useState)(null),[P,Ee]=(0,x.useState)(()=>Date.now()),F=l(e=>e.teams),I=e??F[0]?.id??null,L=I?ne(`team`,I):``,R=l(e=>e.teamsLoading),z=l(e=>e.teamsError),De=l(e=>e.fetchTeams),B=l(e=>(I?e.issuesByEntity[L]:void 0)??pe),V=l(e=>e.issueRefsById),Oe=l(e=>L?!!e.issueLoading[L]:!1),H=l(e=>L?e.issueErrors[L]??null:null),U=l(e=>e.fetchIssuesForEntity),ke=l(e=>e.teamOutcomeAggregates),Ae=l(e=>e.teamOutcomeAggregatesLoading),je=l(e=>e.teamOutcomeAggregatesError),Me=l(e=>e.fetchTeamOutcomeAggregates),W=l(e=>e.fetchPlan),Ne=l(e=>e.dissolveTeam),Pe=l(e=>e.openConfirm),G=l(e=>e.agentsById),Fe=l(e=>e.switchView),Ie=l(e=>e.openInboxThread),Le=l(e=>I?e.plansByTeam[te(I)]??null:null),K=e=>{let t=G[e];return t?d(t):e.slice(-8)};async function q(e,n=T){j(!0),N(null);try{let r=new URLSearchParams({team:e});n!==`all`&&r.set(`kind`,n),c((await t(`GET`,`/blackboard?${r.toString()}`)).entries),w(e)}catch(t){N(p(t)),c([]),w(e)}finally{j(!1)}}async function J(){A(!0),N(null);try{let[t]=await Promise.all([De(),Me()]);Ee(Date.now());let n=e??t[0]?.id;n?(W({teamId:n}),await Promise.all([q(n,T),U(`team`,n)])):(c([]),w(null))}catch(e){N(p(e))}finally{A(!1)}}(0,x.useEffect)(()=>{J()},[]),(0,x.useEffect)(()=>{C&&q(C,T)},[T]);let Y=(0,x.useMemo)(()=>F.filter(e=>Se(e,O)),[F,O]),X=F.find(e=>e.id===I)??null,Z=C===I?a:[],Q=(0,x.useMemo)(()=>B.map(e=>({...e,issueRef:V[e.issueRef.id]??e.issueRef})),[V,B]),$=(0,x.useMemo)(()=>I?Object.values(G).filter(e=>e.teamId===I).sort((e,t)=>d(e).localeCompare(d(t))||e.id.localeCompare(t.id)):[],[G,I]),Re=(0,x.useMemo)(()=>{let e={decision:[],fact:[],ruled_out:[]};for(let t of Z)e[t.kind].push(t);return e},[Z]),ze=T===`all`?[`decision`,`fact`,`ruled_out`]:[T];return(0,S.jsxs)(`div`,{className:`space-y-4 p-4`,children:[(0,S.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,S.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,S.jsx)(s,{className:`size-5 text-primary`}),(0,S.jsx)(`h1`,{className:`text-lg font-semibold`,children:`Teams`}),(0,S.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`active and dissolved team working sets`})]}),(0,S.jsxs)(h,{variant:`outline`,size:`sm`,onClick:()=>void J(),disabled:k||R,children:[(0,S.jsx)(n,{className:i(`size-4`,(k||R)&&`animate-spin`)}),` Refresh`]})]}),(M||z)&&(0,S.jsx)(`div`,{className:`rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive`,children:M||z}),(0,S.jsxs)(`div`,{className:`grid gap-4 xl:grid-cols-[minmax(320px,420px)_1fr]`,children:[(0,S.jsxs)(`div`,{className:`min-h-[520px] min-w-0 space-y-3`,children:[(0,S.jsxs)(`div`,{className:`space-y-3`,children:[(0,S.jsxs)(`div`,{className:`flex items-center justify-between gap-3`,children:[(0,S.jsx)(`span`,{children:`Team list`}),(0,S.jsx)(m,{variant:`secondary`,children:F.length})]}),(0,S.jsx)(le,{value:O,onChange:e=>we(e.target.value),placeholder:`Filter teams`,className:`h-8`})]}),(0,S.jsx)(`div`,{children:(k||R)&&F.length===0?(0,S.jsx)(`div`,{className:`py-16 text-center text-sm text-muted-foreground`,children:`Loading teams…`}):Y.length===0?(0,S.jsx)(`div`,{className:`py-16 text-center text-sm text-muted-foreground`,children:F.length===0?`No teams`:`No teams match`}):(0,S.jsx)(g,{className:`h-[610px] pr-3`,children:(0,S.jsx)(`div`,{className:`space-y-2`,children:Y.map(e=>{let t=e.id===I,n=xe(e),a=ge(e),o=e.coordinatorId,s=()=>{r(e.id),W({teamId:e.id}),U(`team`,e.id),q(e.id,T)};return(0,S.jsx)(`div`,{role:`button`,tabIndex:0,onClick:s,onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),s())},className:`block w-full cursor-pointer text-left`,children:(0,S.jsx)(b,{className:i(`transition-colors hover:border-primary/50`,t&&`border-primary/60 bg-primary/5`),children:(0,S.jsxs)(y,{className:`space-y-3 p-3`,children:[(0,S.jsxs)(`div`,{className:`flex items-start justify-between gap-2`,children:[(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`div`,{className:`truncate text-sm font-medium`,children:e.title||E(e.id)}),(0,S.jsx)(`div`,{className:`truncate text-xs text-muted-foreground`,children:e.id})]}),(0,S.jsx)(m,{variant:`outline`,className:_e[a],children:a})]}),(0,S.jsxs)(`div`,{className:`grid grid-cols-2 gap-x-3 gap-y-1 text-xs text-muted-foreground`,children:[(0,S.jsx)(`span`,{children:`Coordinator`}),o?(0,S.jsxs)(`button`,{type:`button`,onClick:e=>{e.stopPropagation(),Ie(o),Fe(`chat`)},className:`flex min-w-0 items-center justify-end gap-1 text-right text-primary hover:underline`,title:`Chat with ${o}`,children:[(0,S.jsx)(ee,{className:`size-3 shrink-0`}),(0,S.jsx)(`span`,{className:`truncate`,children:K(o)})]}):(0,S.jsx)(`span`,{className:`text-right text-muted-foreground`,children:`—`}),(0,S.jsx)(`span`,{children:`Members`}),(0,S.jsx)(`span`,{className:`text-right text-foreground`,children:n??`-`}),(0,S.jsx)(`span`,{children:`Created`}),(0,S.jsx)(`span`,{className:`text-right text-foreground`,title:f(e.createdAt),children:u(P,e.createdAt)}),e.dissolvedAt?(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(`span`,{children:`Dissolved`}),(0,S.jsx)(`span`,{className:`text-right text-foreground`,title:f(e.dissolvedAt),children:u(P,e.dissolvedAt)})]}):null]}),a===`orphaned`&&e.zeroMembersSince!=null?(0,S.jsx)(`div`,{className:`flex items-center gap-1.5 border-t border-border pt-2 text-xs text-amber-400`,title:f(e.zeroMembersSince),children:(0,S.jsxs)(`span`,{children:[`No live members since `,u(P,e.zeroMembersSince),` — auto-dissolving`]})}):null,e.status===`dissolved`?(0,S.jsxs)(`div`,{className:`space-y-1 border-t border-border pt-2`,children:[(0,S.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[e.outcomeStatus?(0,S.jsx)(m,{variant:`outline`,className:ve[e.outcomeStatus],children:e.outcomeStatus}):(0,S.jsx)(m,{variant:`outline`,children:`no outcome`}),(0,S.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:be(e.wallClockMs)})]}),e.outcomeSummary?(0,S.jsx)(`p`,{className:`line-clamp-2 text-xs text-muted-foreground`,children:e.outcomeSummary}):null]}):null]})})},e.id)})})})})]}),(0,S.jsxs)(`div`,{className:`min-h-[520px] min-w-0 space-y-4`,children:[(0,S.jsx)(fe,{data:ke,loading:Ae,error:je}),X?(0,S.jsxs)(b,{children:[(0,S.jsx)(_,{children:(0,S.jsxs)(v,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`span`,{children:`Issues`}),(0,S.jsxs)(`span`,{className:`ml-2 text-xs font-normal text-muted-foreground`,children:[Q.length,` associated`]})]}),Oe?(0,S.jsx)(m,{variant:`secondary`,children:`loading`}):null]})}),(0,S.jsx)(y,{children:H?(0,S.jsx)(`div`,{className:`text-xs text-destructive`,children:H}):Q.length===0?(0,S.jsx)(`div`,{className:`text-xs text-muted-foreground`,children:`No associated issues`}):(0,S.jsx)(`div`,{className:`flex flex-wrap gap-2`,children:Q.map(({association:e,issueRef:t})=>(0,S.jsx)(ue,{issueRef:t,now:P,showSync:!0},`${e.issueRefId}:${e.createdAt}`))})})]}):null,X&&Le?(0,S.jsx)(b,{className:`p-0`,children:(0,S.jsx)(de,{teamId:X.id})}):null,(0,S.jsxs)(b,{children:[(0,S.jsx)(_,{children:(0,S.jsxs)(v,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`span`,{children:`Roster`}),(0,S.jsxs)(`span`,{className:`ml-2 text-xs font-normal text-muted-foreground`,children:[$.length,` member`,$.length===1?``:`s`]})]}),X&&X.status!==`dissolved`?(0,S.jsxs)(h,{variant:`destructive`,size:`sm`,onClick:()=>Pe(`Dissolve Team`,`Dissolve "${X.title||X.id}"? This closes the team working set but keeps its records visible.`,()=>void Ne(X.id)),children:[(0,S.jsx)(o,{className:`size-4`}),` Dissolve`]}):null]})}),(0,S.jsx)(y,{children:X?$.length===0?(0,S.jsx)(`div`,{className:`py-8 text-center text-sm text-muted-foreground`,children:`No members`}):(0,S.jsx)(`div`,{className:`grid gap-2 md:grid-cols-2`,children:$.map(e=>(0,S.jsxs)(`div`,{className:`flex min-w-0 items-center justify-between gap-3 rounded-md border border-border bg-background px-3 py-2`,children:[(0,S.jsxs)(`div`,{className:`flex min-w-0 items-center gap-2`,children:[(0,S.jsx)(`span`,{className:i(`h-2 w-2 shrink-0 rounded-full`,re(e,P))}),(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`div`,{className:`truncate text-sm font-medium`,children:d(e)}),(0,S.jsx)(`div`,{className:`truncate text-xs text-muted-foreground`,children:e.id})]})]}),(0,S.jsx)(m,{variant:`outline`,className:Ce(e),children:e.status})]},e.id))}):(0,S.jsx)(`div`,{className:`py-8 text-center text-sm text-muted-foreground`,children:`No team selected`})})]}),(0,S.jsxs)(b,{children:[(0,S.jsx)(_,{children:(0,S.jsxs)(v,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`span`,{children:`Blackboard`}),X?(0,S.jsx)(`span`,{className:`ml-2 text-xs font-normal text-muted-foreground`,children:X.title||X.id}):null]}),(0,S.jsxs)(ce,{value:T,onValueChange:e=>D(e),children:[(0,S.jsx)(ae,{size:`sm`,className:`w-36`,children:(0,S.jsx)(ie,{})}),(0,S.jsx)(oe,{children:me.map(e=>(0,S.jsx)(se,{value:e.value,children:e.label},e.value))})]})]})}),(0,S.jsx)(y,{children:X?Te&&Z.length===0?(0,S.jsx)(`div`,{className:`py-16 text-center text-sm text-muted-foreground`,children:`Loading blackboard…`}):Z.length===0?(0,S.jsx)(`div`,{className:`py-16 text-center text-sm text-muted-foreground`,children:`No blackboard entries`}):(0,S.jsx)(g,{className:`h-[640px] pr-3`,children:(0,S.jsx)(`div`,{className:`space-y-5`,children:ze.map(e=>{let t=Re[e];return t.length===0?null:(0,S.jsxs)(`section`,{className:`space-y-2`,children:[(0,S.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,S.jsx)(m,{variant:`secondary`,children:he[e]}),(0,S.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:t.length})]}),(0,S.jsx)(`div`,{className:`space-y-2`,children:t.map(e=>(0,S.jsxs)(`div`,{className:`space-y-3 rounded-md border border-border bg-background p-3`,children:[(0,S.jsxs)(`div`,{className:`flex flex-wrap items-start justify-between gap-2`,children:[(0,S.jsxs)(`div`,{className:`min-w-0`,children:[(0,S.jsx)(`div`,{className:`text-sm font-medium`,children:e.title}),(0,S.jsxs)(`div`,{className:`text-xs text-muted-foreground`,children:[`by `,E(e.authorId),` · `,u(P,e.updatedAt||e.createdAt)]})]}),(0,S.jsxs)(`div`,{className:`flex flex-wrap justify-end gap-1`,children:[(0,S.jsx)(m,{variant:`outline`,className:ye[e.status],children:e.status}),e.supersedes?(0,S.jsxs)(m,{variant:`outline`,children:[`supersedes `,E(e.supersedes)]}):null]})]}),e.body?(0,S.jsx)(`p`,{className:`whitespace-pre-wrap text-sm text-muted-foreground`,children:e.body}):null]},e.id))})]},e)})})}):(0,S.jsx)(`div`,{className:`py-16 text-center text-sm text-muted-foreground`,children:`No team selected`})})]})]})]})]})}export{D as TeamsView};
2
- //# sourceMappingURL=teams-B_HSkU9T.js.map
@@ -1,2 +0,0 @@
1
- function e(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 n(e){return e instanceof Error?e.message:String(e)}var r=[`cleaned`,`merged`,`abandoned`],i={"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 a(e){return i[e]??e}function o(e,t){let n=a(t),r=s(n);if(!r)return!1;if(e?.includes(`*`))return!0;for(let t of e??[]){let e=a(t),i=s(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 s(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 c=`default`;function l(e){return e===`user`||typeof e==`string`&&e.startsWith(`user:`)}function u(e){if(e===`user`)return c;if(typeof e==`string`&&e.startsWith(`user:`))return e.slice(5)||`default`}export{n as a,r as i,u as n,e as o,o as r,t as s,l as t};
2
- //# sourceMappingURL=types-Tv67voFu.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-Tv67voFu.js","names":[],"sources":["../../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"],"sourcesContent":["// --- 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"],"mappings":"AAGA,SAAgB,EAAS,EAAkD,CACzE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,EAAM,CAO7E,SAAgB,EAAY,EAAoC,CAC9D,GAAI,OAAO,GAAU,SAAU,OAC/B,IAAM,EAAU,EAAM,MAAM,CAC5B,OAAO,EAAQ,OAAS,EAAI,EAAU,IAAA,GAIxC,SAAgB,EAAW,EAAwB,CACjD,OAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CEP/D,IAAa,EAAmC,CAAC,UAAW,SAAU,YAAY,CC8H5E,EAAwC,CAC5C,UAAW,eACX,eAAgB,eAChB,cAAe,aACf,eAAgB,cAChB,gBAAiB,eACjB,iBAAkB,eAClB,aAAc,YACd,cAAe,aACf,gBAAiB,eACjB,iBAAkB,gBAClB,gBAAiB,eACjB,iBAAkB,gBAClB,oBAAqB,mBACrB,qBAAsB,oBACtB,kBAAmB,mBACnB,mBAAoB,oBACpB,gBAAiB,oBACjB,eAAgB,aACjB,CAED,SAAgB,EAAe,EAAuB,CACpD,OAAO,EAAc,IAAU,EAGjC,SAAgB,EAAS,EAAuC,EAAgC,CAC9F,IAAM,EAAW,EAAe,EAAc,CACxC,EAAgB,EAAgB,EAAS,CAC/C,GAAI,CAAC,EAAe,MAAO,GAC3B,GAAI,GAAQ,SAAS,IAAI,CAAE,MAAO,GAClC,IAAK,IAAM,KAAY,GAAU,EAAE,CAAE,CACnC,IAAM,EAAQ,EAAe,EAAS,CAChC,EAAa,EAAgB,EAAM,CACpC,OACD,IAAU,gBACV,IAAU,GACV,IAAU,GAAG,EAAc,SAAS,KAEtC,EAAc,WAAa,YAC3B,EAAc,QACd,EAAc,WACd,EAAW,WAAa,YACxB,EAAW,SAAW,EAAc,QACpC,EAAW,YAAc,IAAA,IACzB,MAAO,GAEX,MAAO,GAGT,SAAS,EAAgB,EAAiF,CACxG,GAAI,IAAU,IAAK,MAAO,CAAE,SAAU,IAAK,CAC3C,IAAM,EAAQ,EAAM,MAAM,IAAI,CAC9B,GAAI,EAAM,KAAM,GAAS,CAAC,EAAK,CAAE,OAAO,KACxC,GAAI,EAAM,SAAW,EAAG,CACtB,GAAM,CAAC,EAAU,GAAU,EAC3B,MAAO,CAAE,WAAU,SAAQ,CAE7B,GAAI,EAAM,SAAW,EAAG,CACtB,GAAM,CAAC,EAAU,EAAQ,GAAa,EAItC,OAHI,IAAa,YACb,IAAW,QAAU,IAAW,SAChC,IAAc,IAAY,KACvB,CAAE,WAAU,SAAQ,YAAW,CAExC,OAAO,KC9LT,IAAa,EAAkB,UAe/B,SAAgB,EAAe,EAAiC,CAC9D,OAAO,IAAA,QAAiC,OAAO,GAAO,UAAY,EAAG,WAAA,QAAiC,CAQxG,SAAgB,EAAkB,EAA4C,CAC5E,GAAI,IAAA,OAA8B,OAAO,EACzC,GAAI,OAAO,GAAO,UAAY,EAAG,WAAA,QAAiC,CAChE,OAAO,EAAG,MAAM,EAA6B,EAAA"}