@north-light/cli 0.3.504 → 0.3.505

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 (52) hide show
  1. package/dist/api/client.d.ts +5 -1
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/contract.d.ts +1 -1
  4. package/dist/api/contract.js +1 -1
  5. package/dist/api/dto/canvas.d.ts +40 -0
  6. package/dist/api/routes.d.ts +2 -0
  7. package/dist/api/routes.js +1 -1
  8. package/dist/builtin-memory/crouter-sdk/release-notes.md +8 -0
  9. package/dist/clients/attach/viewer.js +575 -575
  10. package/dist/clients/dashboard/app.d.ts +7 -0
  11. package/dist/clients/dashboard/app.js +2 -0
  12. package/dist/clients/dashboard/model.d.ts +39 -0
  13. package/dist/clients/dashboard/model.js +1 -0
  14. package/dist/clients/dashboard/render.d.ts +29 -0
  15. package/dist/clients/dashboard/render.js +3 -0
  16. package/dist/commands/canvas-browse.js +1 -1
  17. package/dist/commands/surface/node/focus.js +1 -1
  18. package/dist/commands/surface-dashboard.d.ts +2 -0
  19. package/dist/commands/surface-dashboard.js +3 -0
  20. package/dist/commands/surface.js +1 -1
  21. package/dist/core/canvas/browse/app.d.ts +3 -0
  22. package/dist/core/canvas/browse/app.js +3 -3
  23. package/dist/core/canvas/browse/model.d.ts +5 -1
  24. package/dist/core/canvas/browse/model.js +1 -1
  25. package/dist/core/canvas/browse/render.d.ts +5 -0
  26. package/dist/core/canvas/browse/render.js +3 -3
  27. package/dist/core/canvas/migrations.js +1 -1
  28. package/dist/core/canvas/recovery.d.ts +5 -0
  29. package/dist/core/canvas/recovery.js +2 -2
  30. package/dist/core/human/requests.d.ts +3 -0
  31. package/dist/core/human/requests.js +17 -17
  32. package/dist/core/keybindings/catalog.d.ts +4 -3
  33. package/dist/core/keybindings/catalog.js +1 -1
  34. package/dist/core/runtime/broker/model-adapter.d.ts +1 -2
  35. package/dist/core/runtime/focus-viewer.d.ts +6 -0
  36. package/dist/core/runtime/focus-viewer.js +1 -1
  37. package/dist/core/runtime/placement-tmux.d.ts +1 -1
  38. package/dist/core/runtime/placement-tmux.js +1 -1
  39. package/dist/core/runtime/tmux-bindings.js +3 -3
  40. package/dist/core/runtime/tmux-driver.d.ts +6 -0
  41. package/dist/core/runtime/tmux-driver.js +5 -5
  42. package/dist/daemon/api/bridge.js +3 -3
  43. package/dist/daemon/api/handlers/broker-recovery.js +1 -1
  44. package/dist/daemon/api/handlers/canvas.js +6 -6
  45. package/dist/daemon/api/handlers/nodes.js +1 -1
  46. package/dist/daemon/api/operations.js +1 -1
  47. package/dist/daemon/api/viewer-presence.d.ts +3 -0
  48. package/dist/daemon/api/viewer-presence.js +1 -1
  49. package/docs/sdk/release-notes.md +8 -0
  50. package/package.json +2 -2
  51. package/packages/crouter-identity/package.json +1 -1
  52. package/runtime.lock.json +12 -12
@@ -10,6 +10,10 @@ export interface FocusViewerOpts {
10
10
  pane?: string;
11
11
  newPane?: boolean;
12
12
  inPlace?: boolean;
13
+ /** With `inPlace`: leave this node's viewer in another pane open instead of closing it. */
14
+ keepOtherViewers?: boolean;
15
+ /** With `inPlace`: the inbox ticket the new viewer opens before showing the conversation. */
16
+ openTicket?: string;
13
17
  }
14
18
  export interface FocusViewerResult {
15
19
  focused: boolean;
@@ -36,4 +40,6 @@ export declare function focusNode(client: CrtrClient, node: Pick<NodeDetailDTO,
36
40
  pane: string;
37
41
  newPane?: boolean;
38
42
  inPlace?: boolean;
43
+ keepOtherViewers?: boolean;
44
+ openTicket?: string;
39
45
  }): Promise<FocusNodeResult>;
@@ -1 +1 @@
1
- var y=Object.defineProperty;var o=(r,e)=>y(r,"name",{value:e,configurable:!0});import{envHomeOverride as A}from"../../shared/env.js";import{isFinishedWork as E,isLiveStatus as R}from"../canvas/node-order.js";import{InputError as k}from"../io.js";import{closePane as P,currentTmux as C,displayPopupAsync as O,getPaneOption as h,listLivePanes as g,paneExists as v,paneLocation as f,renameWindow as H,respawnPaneSync as W,selectLayout as $,selectPane as _,selectWindow as L,setPaneOption as S,shellQuote as N,splitWindow as q,switchClient as z,windowOfPane as K}from"./placement-tmux.js";function M(){const r=A();return r!==void 0?{CRTR_HOME:r}:{}}o(M,"viewerEnv");function d(){return{...M(),CRTR_ATTACH_WAKE_PARKED:"0"}}o(d,"focusViewerEnv");function w(r){const e=g();if(e!==null){for(const n of e)if(h(n,"@crtr_node")===r)return n}}o(w,"viewerPaneOf");function T(r,e,n,t,a){const s=q(r,{cwd:n,env:d(),command:`crtr surface attach to ${e}`});if(s===null)return{focused:!1,session:null,inPlace:!1,revived:a};const c=f(s);return c!==null&&$(c.window,"even-horizontal"),_(s),{focused:!0,session:c?.session??t,inPlace:!1,revived:a}}o(T,"openBesideCaller");async function V(r,e,n){const t=n.pane??process.env.TMUX_PANE??C()?.pane;if(t===void 0||t==="")return{focused:!1,session:null,inPlace:!1,revived:!1};const s=(await r.ensureAttach(e,{resume:!0,revive:!0,wakeParked:!1})).revived,c=f(t)?.session??null;if(n.inPlace===!0){const i=h(t,"@crtr_node"),u=w(e);u!==void 0&&u!==t&&v(u)&&P(u);try{S(t,"@crtr_node","")}catch{}const p=K(t),x=i!==void 0&&i!==""?{...d(),CRTR_ATTACH_ROSTER:i}:d(),m=W({pane:t,cwd:n.cwd,env:x,command:`crtr surface attach to ${e}`});return m&&p!==null&&H(p,n.name),{focused:m,session:c,inPlace:!0,revived:s}}if(n.newPane===!0){const i=w(e);return i!==void 0&&v(i)&&P(i),T(t,e,n.cwd,c,s)}const l=w(e);if(l!==void 0){const i=f(l);if(i!==null)return z(i.session),L(i.session,i.window),_(l),{focused:!0,session:i.session,inPlace:!0,revived:s}}return T(t,e,n.cwd,c,s)}o(V,"focusViewer");async function U(r,e,n){const t=e.node_id;if(e.kind==="human")throw new k({error:"not_focusable",message:`node ${t} is a human-ask (kind:human), not a conversation \u2014 it has no pi session to focus.`,next:`The pending question is queued for the user; see it with \`crtr human list\`, or retract it with \`crtr human cancel ${t}\`.`});return!R(e.status)&&E({lifecycle:e.lifecycle,finalized:e.final_report!=null})?{focused:await O(n.pane,e.cwd,`crtr node inspect transcript ${N(t)}`),session:null,revived:!1,inPlace:!1}:V(r,t,{cwd:e.cwd,name:e.name,pane:n.pane,...n.newPane===!0?{newPane:!0}:{},...n.inPlace===!0?{inPlace:!0}:{}})}o(U,"focusNode");export{U as focusNode,V as focusViewer,M as viewerEnv,w as viewerPaneOf};
1
+ var A=Object.defineProperty;var o=(t,e)=>A(t,"name",{value:e,configurable:!0});import{envHomeOverride as E}from"../../shared/env.js";import{isFinishedWork as O,isLiveStatus as R}from"../canvas/node-order.js";import{InputError as C}from"../io.js";import{closePane as m,currentTmux as x,displayPopupAsync as y,getPaneOption as P,listLivePanes as H,paneExists as h,paneLocation as l,renameWindow as g,respawnPaneSync as V,selectLayout as W,selectPane as v,selectWindow as $,setPaneOption as L,shellQuote as N,splitWindow as S,switchClient as K,windowOfPane as q}from"./placement-tmux.js";function z(){const t=E();return t!==void 0?{CRTR_HOME:t}:{}}o(z,"viewerEnv");function T(){return{...z(),CRTR_ATTACH_WAKE_PARKED:"0"}}o(T,"focusViewerEnv");function d(t){const e=H();if(e!==null){for(const n of e)if(P(n,"@crtr_node")===t)return n}}o(d,"viewerPaneOf");function _(t,e,n,i,a){const s=S(t,{cwd:n,env:T(),command:`crtr surface attach to ${e}`});if(s===null)return{focused:!1,session:null,inPlace:!1,revived:a};const c=l(s);return c!==null&&W(c.window,"even-horizontal"),v(s),{focused:!0,session:c?.session??i,inPlace:!1,revived:a}}o(_,"openBesideCaller");async function M(t,e,n){const i=n.pane??process.env.TMUX_PANE??x()?.pane;if(i===void 0||i==="")return{focused:!1,session:null,inPlace:!1,revived:!1};const s=(await t.ensureAttach(e,{resume:!0,revive:!0,wakeParked:!1})).revived,c=l(i)?.session??null;if(n.inPlace===!0){const r=P(i,"@crtr_node"),u=d(e);n.keepOtherViewers!==!0&&u!==void 0&&u!==i&&h(u)&&m(u);try{L(i,"@crtr_node","")}catch{}const w=q(i),k={...T(),...r!==void 0&&r!==""?{CRTR_ATTACH_ROSTER:r}:{},...n.openTicket!==void 0?{CRTR_ATTACH_OPEN_TICKET:n.openTicket}:{}},p=V({pane:i,cwd:n.cwd,env:k,command:`crtr surface attach to ${e}`});return p&&w!==null&&g(w,n.name),{focused:p,session:c,inPlace:!0,revived:s}}if(n.newPane===!0){const r=d(e);return r!==void 0&&h(r)&&m(r),_(i,e,n.cwd,c,s)}const f=d(e);if(f!==void 0){const r=l(f);if(r!==null)return K(r.session),$(r.session,r.window),v(f),{focused:!0,session:r.session,inPlace:!0,revived:s}}return _(i,e,n.cwd,c,s)}o(M,"focusViewer");async function U(t,e,n){const i=e.node_id;if(e.kind==="human")throw new C({error:"not_focusable",message:`node ${i} is a human-ask (kind:human), not a conversation \u2014 it has no pi session to focus.`,next:`The pending question is queued for the user; see it with \`crtr human list\`, or retract it with \`crtr human cancel ${i}\`.`});return!R(e.status)&&O({lifecycle:e.lifecycle,finalized:e.final_report!=null})?{focused:await y(n.pane,e.cwd,`crtr node inspect transcript ${N(i)}`),session:null,revived:!1,inPlace:!1}:M(t,i,{cwd:e.cwd,name:e.name,pane:n.pane,...n.newPane===!0?{newPane:!0}:{},...n.inPlace===!0?{inPlace:!0}:{},...n.keepOtherViewers===!0?{keepOtherViewers:!0}:{},...n.openTicket!==void 0?{openTicket:n.openTicket}:{}})}o(U,"focusNode");export{U as focusNode,M as focusViewer,z as viewerEnv,d as viewerPaneOf};
@@ -1,4 +1,4 @@
1
- export { paneExists, paneLocation, paneOfNode, paneCurrentPath, paneSize, paneRunning, ensureSession, openNodeWindow, splitWindow, closePane, currentTmux, switchClient, selectWindow, selectLayout, selectPane, getPaneOption, setPaneOption, breakPane, inTmux, piCommand, shellQuote, renameWindow, displayPopup, displayPopupAsync, windowOfPane, respawnPaneSync, respawnPaneInBackground, respawnPaneInPlace, runShellInBackground, paneStartCommand, panePid, windowAlive, listLivePanes, VIEWER_STAMP_OPTION, stampViewerPaneAsync, liveViewerPanes, sendAttachControl, displayClientMessage, displayMessage, windowIndex, sessionExists, tmuxAvailable, tmuxInteractive, } from './tmux-driver.js';
1
+ export { paneExists, paneLocation, paneOfNode, paneCurrentPath, paneSize, paneRunning, ensureSession, openNodeWindow, splitWindow, closePane, currentTmux, switchClient, selectWindow, selectLayout, selectPane, getPaneOption, setPaneOption, breakPane, inTmux, piCommand, shellQuote, renameWindow, displayPopup, displayPopupAsync, windowOfPane, respawnPaneSync, respawnPaneInBackground, respawnPaneInPlace, runShellInBackground, paneStartCommand, swapPanes, paneCurrentCommand, closeWindow, panePid, windowAlive, listLivePanes, VIEWER_STAMP_OPTION, stampViewerPaneAsync, liveViewerPanes, sendAttachControl, displayClientMessage, displayMessage, windowIndex, sessionExists, tmuxAvailable, tmuxInteractive, } from './tmux-driver.js';
2
2
  export { installTmuxBindings } from './tmux-bindings.js';
3
3
  export { BROKER_VIEW_SOCKET_WAIT_MS, waitForBrokerViewSocket } from './view-socket.js';
4
4
  /** Env for a `crtr surface attach` VIEWER pane (the focus split, the spawn viewer
@@ -1 +1 @@
1
- var u=Object.defineProperty;var s=(e,n)=>u(e,"name",{value:n,configurable:!0});import{envHomeOverride as w}from"../../shared/env.js";import{paneLocation as m,ensureSession as P,openNodeWindow as T,splitWindow as f,selectLayout as _}from"./tmux-driver.js";import{BROKER_VIEW_SOCKET_WAIT_MS as I}from"./view-socket.js";import{paneExists as v,paneLocation as y,paneOfNode as g,paneCurrentPath as h,paneSize as B,paneRunning as M,ensureSession as V,openNodeWindow as k,splitWindow as H,closePane as L,currentTmux as N,switchClient as b,selectWindow as K,selectLayout as z,selectPane as F,getPaneOption as Q,setPaneOption as $,breakPane as j,inTmux as q,piCommand as D,shellQuote as G,renameWindow as J,displayPopup as U,displayPopupAsync as X,windowOfPane as Y,respawnPaneSync as Z,respawnPaneInBackground as ee,respawnPaneInPlace as ne,runShellInBackground as ie,paneStartCommand as oe,panePid as te,windowAlive as ae,listLivePanes as re,VIEWER_STAMP_OPTION as se,stampViewerPaneAsync as le,liveViewerPanes as ce,sendAttachControl as pe,displayClientMessage as de,displayMessage as ue,windowIndex as we,sessionExists as me,tmuxAvailable as Pe,tmuxInteractive as Te}from"./tmux-driver.js";import{installTmuxBindings as _e}from"./tmux-bindings.js";import{BROKER_VIEW_SOCKET_WAIT_MS as Se,waitForBrokerViewSocket as Ae}from"./view-socket.js";function S(){const e=w();return e!==void 0?{CRTR_HOME:e}:{}}s(S,"viewerSplitEnv");function E(e,n,i={}){const l=`crtr surface attach to ${e}`,c={...S(),...i.waitForBroker===!0?{CRTR_ATTACH_WAIT_MS:String(I)}:{},...i.initialWhip===!0?{CRTR_ATTACH_INITIAL_WHIP:"1"}:{}},o=i.cwd??process.cwd();if(i.besidePane!==void 0){const a=f(i.besidePane,{cwd:o,env:c,command:l});if(a===null)return null;const p=m(a),d=p?.session??n,r=p?.window??null;return r!==null&&_(r,"even-horizontal"),{pane:a,session:d,window:r}}P(n,o);const t=T({session:n,name:i.name??e,cwd:o,env:c,command:l});return t===null?null:{pane:t.pane,session:n,window:t.window}}s(E,"openViewerPane");export{Se as BROKER_VIEW_SOCKET_WAIT_MS,se as VIEWER_STAMP_OPTION,j as breakPane,L as closePane,N as currentTmux,de as displayClientMessage,ue as displayMessage,U as displayPopup,X as displayPopupAsync,V as ensureSession,Q as getPaneOption,q as inTmux,_e as installTmuxBindings,re as listLivePanes,ce as liveViewerPanes,k as openNodeWindow,E as openViewerPane,h as paneCurrentPath,v as paneExists,y as paneLocation,g as paneOfNode,te as panePid,M as paneRunning,B as paneSize,oe as paneStartCommand,D as piCommand,J as renameWindow,ee as respawnPaneInBackground,ne as respawnPaneInPlace,Z as respawnPaneSync,ie as runShellInBackground,z as selectLayout,F as selectPane,K as selectWindow,pe as sendAttachControl,me as sessionExists,$ as setPaneOption,G as shellQuote,H as splitWindow,le as stampViewerPaneAsync,b as switchClient,Pe as tmuxAvailable,Te as tmuxInteractive,S as viewerSplitEnv,Ae as waitForBrokerViewSocket,ae as windowAlive,we as windowIndex,Y as windowOfPane};
1
+ var u=Object.defineProperty;var s=(e,n)=>u(e,"name",{value:n,configurable:!0});import{envHomeOverride as w}from"../../shared/env.js";import{paneLocation as m,ensureSession as P,openNodeWindow as T,splitWindow as f,selectLayout as _}from"./tmux-driver.js";import{BROKER_VIEW_SOCKET_WAIT_MS as C}from"./view-socket.js";import{paneExists as v,paneLocation as y,paneOfNode as g,paneCurrentPath as h,paneSize as B,paneRunning as M,ensureSession as V,openNodeWindow as k,splitWindow as H,closePane as L,currentTmux as N,switchClient as b,selectWindow as K,selectLayout as z,selectPane as F,getPaneOption as Q,setPaneOption as $,breakPane as j,inTmux as q,piCommand as D,shellQuote as G,renameWindow as J,displayPopup as U,displayPopupAsync as X,windowOfPane as Y,respawnPaneSync as Z,respawnPaneInBackground as ee,respawnPaneInPlace as ne,runShellInBackground as oe,paneStartCommand as ie,swapPanes as ae,paneCurrentCommand as te,closeWindow as re,panePid as se,windowAlive as le,listLivePanes as pe,VIEWER_STAMP_OPTION as ce,stampViewerPaneAsync as de,liveViewerPanes as ue,sendAttachControl as we,displayClientMessage as me,displayMessage as Pe,windowIndex as Te,sessionExists as fe,tmuxAvailable as _e,tmuxInteractive as Ce}from"./tmux-driver.js";import{installTmuxBindings as Se}from"./tmux-bindings.js";import{BROKER_VIEW_SOCKET_WAIT_MS as Ae,waitForBrokerViewSocket as xe}from"./view-socket.js";function I(){const e=w();return e!==void 0?{CRTR_HOME:e}:{}}s(I,"viewerSplitEnv");function E(e,n,o={}){const l=`crtr surface attach to ${e}`,p={...I(),...o.waitForBroker===!0?{CRTR_ATTACH_WAIT_MS:String(C)}:{},...o.initialWhip===!0?{CRTR_ATTACH_INITIAL_WHIP:"1"}:{}},i=o.cwd??process.cwd();if(o.besidePane!==void 0){const t=f(o.besidePane,{cwd:i,env:p,command:l});if(t===null)return null;const c=m(t),d=c?.session??n,r=c?.window??null;return r!==null&&_(r,"even-horizontal"),{pane:t,session:d,window:r}}P(n,i);const a=T({session:n,name:o.name??e,cwd:i,env:p,command:l});return a===null?null:{pane:a.pane,session:n,window:a.window}}s(E,"openViewerPane");export{Ae as BROKER_VIEW_SOCKET_WAIT_MS,ce as VIEWER_STAMP_OPTION,j as breakPane,L as closePane,re as closeWindow,N as currentTmux,me as displayClientMessage,Pe as displayMessage,U as displayPopup,X as displayPopupAsync,V as ensureSession,Q as getPaneOption,q as inTmux,Se as installTmuxBindings,pe as listLivePanes,ue as liveViewerPanes,k as openNodeWindow,E as openViewerPane,te as paneCurrentCommand,h as paneCurrentPath,v as paneExists,y as paneLocation,g as paneOfNode,se as panePid,M as paneRunning,B as paneSize,ie as paneStartCommand,D as piCommand,J as renameWindow,ee as respawnPaneInBackground,ne as respawnPaneInPlace,Z as respawnPaneSync,oe as runShellInBackground,z as selectLayout,F as selectPane,K as selectWindow,we as sendAttachControl,fe as sessionExists,$ as setPaneOption,G as shellQuote,H as splitWindow,de as stampViewerPaneAsync,ae as swapPanes,b as switchClient,_e as tmuxAvailable,Ce as tmuxInteractive,I as viewerSplitEnv,xe as waitForBrokerViewSocket,le as windowAlive,Te as windowIndex,Y as windowOfPane};
@@ -1,4 +1,4 @@
1
- var Q=Object.defineProperty;var u=(e,n)=>Q(e,"name",{value:n,configurable:!0});import{mkdtempSync as Y,rmSync as Z,writeFileSync as V}from"node:fs";import{tmpdir as O}from"node:os";import{join as M}from"node:path";import{ATTACH_CONTROL_BINDINGS as U,BINDING_CATALOG as _,BINDING_IDS as ee,encodeAttachControlInput as ne,formatGesture as te,isAttachPaneBinding as re,resolveUserKeybindingSettings as q}from"../keybindings/index.js";import{normalizeTmuxPassthrough as ie}from"../config.js";import{withExclusiveDirectoryLock as oe}from"../exclusive-lock.js";import{surfaceTmuxStyleArgs as se}from"./surface-bg.js";import{runTmux as f,shellQuote as h}from"./tmux-driver.js";const A="@crtr_keybindings_manifest",p="root",ue={"crtr.tmux.menu.promote":{description:"promote to orchestrator",action:{kind:"keys",keys:"/promote"}},"crtr.tmux.menu.resume":{description:"find node",action:{kind:"keys",keys:"/resume"}},"crtr.tmux.menu.fork":{description:"fork into idle root",action:{kind:"direct",run:"node fork --pane {pane}"}},"crtr.tmux.menu.demote-mode":{description:"demote to base node",action:{kind:"command",run:"node config --mode base --pane {pane}"}},"crtr.tmux.menu.demote":{description:"demote to terminal (finish when done)",action:{kind:"command",run:"node lifecycle demote --pane {pane}"}},"crtr.tmux.menu.make-resident":{description:"make resident (interactable)",action:{kind:"command",run:"node config --lifecycle resident --pane {pane}"}},"crtr.tmux.menu.detach":{description:"demote + close viewer",action:{kind:"command",run:"node lifecycle demote --pane {pane} --detach"}},"crtr.tmux.menu.close-subtree":{description:"close agent + subtree",action:{kind:"command",run:"node lifecycle close --pane {pane}"}},"crtr.tmux.menu.background-bash":{description:"background running bash",action:{kind:"direct",run:"node bash background --pane {pane}"}},"crtr.tmux.menu.file-review":{description:"review a file",action:{kind:"raw-keys",keys:"M-S-r"}},"crtr.tmux.menu.metadata":{description:"metadata",action:{kind:"keys",keys:"/node-metadata"}},"crtr.tmux.menu.fold-tools":{description:"fold finished tool calls",action:{kind:"keys",keys:"/fold-tools"}},"crtr.tmux.menu.show-working":{description:"show/hide working",action:{kind:"keys",keys:"/show-working"}},"crtr.tmux.menu.context":{description:"context + reports",action:{kind:"keys",keys:"/context"}},"crtr.tmux.menu.providers":{description:"settings",action:{kind:"popup",run:"sys settings",size:{w:"90%",h:"85%"}}},"crtr.tmux.menu.provider-options":{description:"provider options",action:{kind:"popup",run:"sys settings --page provider-options",size:{w:"90%",h:"85%"}}},"crtr.tmux.menu.plugins":{description:"plugins",action:{kind:"popup",run:"pkg browse",size:{w:"90%",h:"85%"}}},"crtr.tmux.menu.analytics":{description:"analytics",action:{kind:"popup",run:"canvas analytics --pane {pane}",size:{w:"90%",h:"85%"}}},"crtr.tmux.menu.context-admin":{description:"context surface",action:{kind:"popup",run:"sys context admin",size:{w:"98%",h:"96%"}}},"crtr.tmux.menu.graph":{description:"canvas graph",action:{kind:"keys",keys:"/graph"}},"crtr.tmux.menu.focus-manager":{description:"go to manager",action:{kind:"command",run:"surface node focus {manager}"}},"crtr.tmux.menu.copy-id":{description:"copy node id",action:{kind:"command",run:"surface node id --pane {pane}"}},"crtr.tmux.menu.reload-viewer":{description:"reload viewer",action:{kind:"direct",run:"surface reload --pane {pane}"}},"crtr.tmux.menu.recap":{description:"generate recap",action:{kind:"keys",keys:"/recap"}},"crtr.tmux.menu.pin":{description:"pin conversation",action:{kind:"direct",run:"surface node pin --pane {pane}"}},"crtr.tmux.menu.issue.idea":{description:"idea",action:{kind:"popup",run:"canvas issue new --type idea"}},"crtr.tmux.menu.issue.bug":{description:"bug",action:{kind:"popup",run:"canvas issue new --type bug"}}};function b(e){return e.replace(/#/g,"##")}u(b,"escapeTmuxFormats");function ce(e){return e.map(h).join(" ")}u(ce,"tmuxCommand");const ae={w:"60%",h:"50%"};function G(e,n=!1,t=ae){const r=`crtr canvas chord --pane '#{pane_id}' --run ${h(b(e))}${n?" --interactive":""}`;return n?{argv:["display-popup","-E","-w",t.w,"-h",t.h,"-d","#{pane_current_path}",r],menu:`display-popup -E -w ${t.w} -h ${t.h} -d "#{pane_current_path}" ${h(r)}`}:{argv:["run-shell",`${r} >/dev/null 2>&1`],menu:`run-shell ${h(`${r} >/dev/null 2>&1`)}`}}u(G,"chordCommand");function de(e){const t=`crtr ${b(e).replace("{pane}","#{pane_id}")} >/dev/null 2>&1`;return{argv:["run-shell","-b",t],menu:`run-shell -b ${h(t)}`}}u(de,"directCommand");function j(e){switch(e.kind){case"disabled":return null;case"separator":return null;case"dismiss":return{argv:["refresh-client"],menu:"refresh-client"};case"keys":{const n=b(e.keys);return{argv:["send-keys",n,"Enter"],menu:`send-keys -t '#{pane_id}' ${h(n)} Enter`}}case"raw-keys":{const n=b(e.keys);return{argv:["send-keys",n],menu:`send-keys -t '#{pane_id}' ${h(n)}`}}case"literal-input":{const n=b(e.input);return{argv:["send-keys","-l",n],menu:`send-keys -l -t '#{pane_id}' ${h(n)}`}}case"command":return G(e.run);case"direct":return de(e.run);case"popup":return G(e.run,!0,e.size);case"submenu":{const n=N(e.title,e.items);return{argv:n,menu:ce(n)}}}}u(j,"menuActionCommand");function me(e){return j(e)?.menu??""}u(me,"renderMenuAction");function pe(e){return e.replace(/#\[[^\]]*\]/g,"").length}u(pe,"menuItemWidth");function N(e,n){const t=b(e),r=n.reduce((o,c)=>Math.max(o,pe(c.description)),0),s=["display-menu","-T",`#[align=centre]${t}`,...se()];s.push("-x",`#{e|-:#{pane_right},${Math.max(t.length,r)+7}}`,"-y","#{e|+:#{pane_top},3}","--");for(const o of n){if(o.action.kind==="separator"){s.push("");continue}const c=o.action.kind==="disabled"?o.description:b(o.description);s.push(c,o.selector,me(o.action))}return s.push("","-esc / q to close","",""),s}u(N,"buildMenuArgs");const le={enter:"Enter",escape:"Escape",tab:"Tab",backspace:"BSpace",delete:"DC",space:"Space",up:"Up",down:"Down",left:"Left",right:"Right",pageup:"PPage",pagedown:"NPage",home:"Home",end:"End"};function z(e){if(e==="+")return{key:"+",modifiers:[]};if(e.endsWith("++"))return{key:"+",modifiers:e.slice(0,-2).split("+")};const n=e.split("+");return{key:n.pop(),modifiers:n}}u(z,"normalizedStrokeParts");function fe(e){return v(e)[0]}u(fe,"tmuxKey");function v(e){let{key:n,modifiers:t}=z(e);const r=new Set(t),s=u((o,c)=>[r.has("ctrl")?"C-":"",r.has("alt")?"M-":"",c?"S-":"",le[o]??o].join(""),"name");if(r.has("shift")&&n.length===1&&/[a-z]/.test(n)){const o=n.toUpperCase();return!r.has("ctrl")&&!r.has("alt")?[s(o,!1)]:[s(o,!0),s(o,!1),s(n,!0)]}return[s(n,r.has("shift"))]}u(v,"tmuxKeyNames");function ge(e){const{key:n,modifiers:t}=z(e);return t.includes("shift")&&/[a-z]/.test(n)?n.toUpperCase():n==="space"?" ":n}u(ge,"menuSelector");function T(e,n,t){const r=[];for(const s of e){const o=n.get(s);if(o!==void 0)for(const c of t.gestures(s))r.push({description:o.description,selector:ge(c),action:o.action})}return r}u(T,"boundMenuItems");function F(e){const n=new Map;for(const[c,l]of Object.entries(ue))l!==void 0&&n.set(c,l);for(const{menuId:c,actionId:l}of U){const x=_.find(w=>w.id===l);if(x===void 0)throw new Error(`missing attach binding definition ${l}`);n.set(c,{description:x.name.toLowerCase(),action:{kind:"literal-input",input:ne(l)}})}const t=T(U.map(({menuId:c})=>c),n,e);t.length>0&&n.set("crtr.tmux.menu.attach",{description:"attach viewer \u203A",action:{kind:"submenu",title:" attach viewer ",items:t}});const r=T(["crtr.tmux.menu.metadata","crtr.tmux.menu.context"],n,e);n.set("crtr.tmux.menu.show",{description:"inspect \u203A",action:{kind:"submenu",title:" inspect node ",items:r}});const s=T(["crtr.tmux.menu.fold-tools","crtr.tmux.menu.show-working"],n,e);n.set("crtr.tmux.menu.view-settings",{description:"display \u203A",action:{kind:"submenu",title:" display ",items:s}});const o=T(["crtr.tmux.menu.issue.idea","crtr.tmux.menu.issue.bug"],n,e);return n.set("crtr.tmux.menu.issues",{description:"create \u203A",action:{kind:"submenu",title:" create ",items:o}}),n}u(F,"resolvedMenuActions");function K(e,n){const t=u(c=>T(c,n,e),"boundItems"),r=[],s=u((c,l)=>{l.length!==0&&(r.length>0&&r.push({description:"",selector:"",action:{kind:"separator"}}),r.push({description:`-#[bold]${c}`,selector:"",action:{kind:"disabled"}},...l))},"addGroup");s("Explore",t(["crtr.tmux.menu.resume","crtr.tmux.menu.graph","crtr.tmux.menu.focus-manager","crtr.tmux.menu.show","crtr.tmux.menu.view-settings"])),s("Current node",t(["crtr.tmux.menu.attach","crtr.tmux.menu.background-bash","crtr.tmux.menu.file-review","crtr.tmux.menu.pin","crtr.tmux.menu.copy-id","crtr.tmux.menu.reload-viewer","crtr.tmux.menu.recap"])),s("Lifecycle",t(["crtr.tmux.menu.fork","crtr.tmux.menu.promote","crtr.tmux.menu.demote-mode","crtr.tmux.menu.demote","crtr.tmux.menu.make-resident","crtr.tmux.menu.detach","crtr.tmux.menu.close-subtree"])),s("System",t(["crtr.tmux.menu.providers","crtr.tmux.menu.provider-options","crtr.tmux.menu.plugins","crtr.tmux.menu.analytics","crtr.tmux.menu.context-admin","crtr.tmux.menu.issues"]));const o=e.gestures("crtr.tmux.menu.open");if(o.length>0&&r.length>0){r.push({description:"",selector:"",action:{kind:"separator"}});for(const c of o)r.push({description:`${te(c)} to close`,selector:fe(c),action:{kind:"dismiss"}})}return r}u(K,"menuItems");function Pe(e){const n=e??q().bindings;return N(" crtr ",K(n,F(n)))}u(Pe,"crouterMenuTmuxArgs");function S(e,n){let t=n;for(;t<e.length&&/\s/.test(e[t]);)t+=1;if(t>=e.length)return null;let r=null,s="";for(;t<e.length;){const o=e[t];if(r===null&&/\s/.test(o))break;if(r===null&&(o==="'"||o==='"')){r=o,t+=1;continue}if(r===o){r=null,t+=1;continue}if(o==="\\"&&r!=="'"&&t+1<e.length){s+=e[t+1],t+=2;continue}s+=o,t+=1}return{value:s,end:t}}u(S,"readTmuxToken");function B(e){const n=S(e,0);if(n?.value!=="bind-key")return null;let t=n.end,r=!1;for(;;){const s=S(e,t);if(s===null)return null;if(t=s.end,s.value==="-r"){r=!0;continue}if(s.value!=="-T")continue;const o=S(e,t);if(o===null)return null;const c=S(e,o.end);return c===null?null:{table:o.value,key:c.value,command:e.slice(c.end).trimStart(),repeat:r}}}u(B,"parseTmuxBinding");function $(e,n){const t=f(["list-keys","-T",e]);return t.ok?t.stdout.split(`
1
+ var Q=Object.defineProperty;var u=(e,n)=>Q(e,"name",{value:n,configurable:!0});import{mkdtempSync as Y,rmSync as Z,writeFileSync as V}from"node:fs";import{tmpdir as O}from"node:os";import{join as M}from"node:path";import{ATTACH_CONTROL_BINDINGS as U,BINDING_CATALOG as _,BINDING_IDS as ee,encodeAttachControlInput as ne,formatGesture as te,isAttachPaneBinding as re,resolveUserKeybindingSettings as q}from"../keybindings/index.js";import{normalizeTmuxPassthrough as ie}from"../config.js";import{withExclusiveDirectoryLock as oe}from"../exclusive-lock.js";import{surfaceTmuxStyleArgs as se}from"./surface-bg.js";import{runTmux as f,shellQuote as k}from"./tmux-driver.js";const A="@crtr_keybindings_manifest",p="root",ue={"crtr.tmux.menu.promote":{description:"promote to orchestrator",action:{kind:"keys",keys:"/promote"}},"crtr.tmux.menu.resume":{description:"find node",action:{kind:"keys",keys:"/resume"}},"crtr.tmux.menu.dashboard":{description:"dashboard",action:{kind:"direct",run:"surface dashboard --pane {pane}"}},"crtr.tmux.menu.fork":{description:"fork into idle root",action:{kind:"direct",run:"node fork --pane {pane}"}},"crtr.tmux.menu.demote-mode":{description:"demote to base node",action:{kind:"command",run:"node config --mode base --pane {pane}"}},"crtr.tmux.menu.demote":{description:"demote to terminal (finish when done)",action:{kind:"command",run:"node lifecycle demote --pane {pane}"}},"crtr.tmux.menu.make-resident":{description:"make resident (interactable)",action:{kind:"command",run:"node config --lifecycle resident --pane {pane}"}},"crtr.tmux.menu.detach":{description:"demote + close viewer",action:{kind:"command",run:"node lifecycle demote --pane {pane} --detach"}},"crtr.tmux.menu.close-subtree":{description:"close agent + subtree",action:{kind:"command",run:"node lifecycle close --pane {pane}"}},"crtr.tmux.menu.background-bash":{description:"background running bash",action:{kind:"direct",run:"node bash background --pane {pane}"}},"crtr.tmux.menu.file-review":{description:"review a file",action:{kind:"raw-keys",keys:"M-S-r"}},"crtr.tmux.menu.metadata":{description:"metadata",action:{kind:"keys",keys:"/node-metadata"}},"crtr.tmux.menu.fold-tools":{description:"fold finished tool calls",action:{kind:"keys",keys:"/fold-tools"}},"crtr.tmux.menu.show-working":{description:"show/hide working",action:{kind:"keys",keys:"/show-working"}},"crtr.tmux.menu.context":{description:"context + reports",action:{kind:"keys",keys:"/context"}},"crtr.tmux.menu.providers":{description:"settings",action:{kind:"popup",run:"sys settings",size:{w:"90%",h:"85%"}}},"crtr.tmux.menu.provider-options":{description:"provider options",action:{kind:"popup",run:"sys settings --page provider-options",size:{w:"90%",h:"85%"}}},"crtr.tmux.menu.plugins":{description:"plugins",action:{kind:"popup",run:"pkg browse",size:{w:"90%",h:"85%"}}},"crtr.tmux.menu.analytics":{description:"analytics",action:{kind:"popup",run:"canvas analytics --pane {pane}",size:{w:"90%",h:"85%"}}},"crtr.tmux.menu.context-admin":{description:"context surface",action:{kind:"popup",run:"sys context admin",size:{w:"98%",h:"96%"}}},"crtr.tmux.menu.graph":{description:"canvas graph",action:{kind:"keys",keys:"/graph"}},"crtr.tmux.menu.focus-manager":{description:"go to manager",action:{kind:"command",run:"surface node focus {manager}"}},"crtr.tmux.menu.copy-id":{description:"copy node id",action:{kind:"command",run:"surface node id --pane {pane}"}},"crtr.tmux.menu.reload-viewer":{description:"reload viewer",action:{kind:"direct",run:"surface reload --pane {pane}"}},"crtr.tmux.menu.recap":{description:"generate recap",action:{kind:"keys",keys:"/recap"}},"crtr.tmux.menu.pin":{description:"pin conversation",action:{kind:"direct",run:"surface node pin --pane {pane}"}},"crtr.tmux.menu.issue.idea":{description:"idea",action:{kind:"popup",run:"canvas issue new --type idea"}},"crtr.tmux.menu.issue.bug":{description:"bug",action:{kind:"popup",run:"canvas issue new --type bug"}}};function b(e){return e.replace(/#/g,"##")}u(b,"escapeTmuxFormats");function ce(e){return e.map(k).join(" ")}u(ce,"tmuxCommand");const ae={w:"60%",h:"50%"};function G(e,n=!1,t=ae){const r=`crtr canvas chord --pane '#{pane_id}' --run ${k(b(e))}${n?" --interactive":""}`;return n?{argv:["display-popup","-E","-w",t.w,"-h",t.h,"-d","#{pane_current_path}",r],menu:`display-popup -E -w ${t.w} -h ${t.h} -d "#{pane_current_path}" ${k(r)}`}:{argv:["run-shell",`${r} >/dev/null 2>&1`],menu:`run-shell ${k(`${r} >/dev/null 2>&1`)}`}}u(G,"chordCommand");function de(e){const t=`crtr ${b(e).replace("{pane}","#{pane_id}")} >/dev/null 2>&1`;return{argv:["run-shell","-b",t],menu:`run-shell -b ${k(t)}`}}u(de,"directCommand");function j(e){switch(e.kind){case"disabled":return null;case"separator":return null;case"dismiss":return{argv:["refresh-client"],menu:"refresh-client"};case"keys":{const n=b(e.keys);return{argv:["send-keys",n,"Enter"],menu:`send-keys -t '#{pane_id}' ${k(n)} Enter`}}case"raw-keys":{const n=b(e.keys);return{argv:["send-keys",n],menu:`send-keys -t '#{pane_id}' ${k(n)}`}}case"literal-input":{const n=b(e.input);return{argv:["send-keys","-l",n],menu:`send-keys -l -t '#{pane_id}' ${k(n)}`}}case"command":return G(e.run);case"direct":return de(e.run);case"popup":return G(e.run,!0,e.size);case"submenu":{const n=N(e.title,e.items);return{argv:n,menu:ce(n)}}}}u(j,"menuActionCommand");function me(e){return j(e)?.menu??""}u(me,"renderMenuAction");function pe(e){return e.replace(/#\[[^\]]*\]/g,"").length}u(pe,"menuItemWidth");function N(e,n){const t=b(e),r=n.reduce((o,c)=>Math.max(o,pe(c.description)),0),s=["display-menu","-T",`#[align=centre]${t}`,...se()];s.push("-x",`#{e|-:#{pane_right},${Math.max(t.length,r)+7}}`,"-y","#{e|+:#{pane_top},3}","--");for(const o of n){if(o.action.kind==="separator"){s.push("");continue}const c=o.action.kind==="disabled"?o.description:b(o.description);s.push(c,o.selector,me(o.action))}return s.push("","-esc / q to close","",""),s}u(N,"buildMenuArgs");const le={enter:"Enter",escape:"Escape",tab:"Tab",backspace:"BSpace",delete:"DC",space:"Space",up:"Up",down:"Down",left:"Left",right:"Right",pageup:"PPage",pagedown:"NPage",home:"Home",end:"End"};function z(e){if(e==="+")return{key:"+",modifiers:[]};if(e.endsWith("++"))return{key:"+",modifiers:e.slice(0,-2).split("+")};const n=e.split("+");return{key:n.pop(),modifiers:n}}u(z,"normalizedStrokeParts");function fe(e){return v(e)[0]}u(fe,"tmuxKey");function v(e){let{key:n,modifiers:t}=z(e);const r=new Set(t),s=u((o,c)=>[r.has("ctrl")?"C-":"",r.has("alt")?"M-":"",c?"S-":"",le[o]??o].join(""),"name");if(r.has("shift")&&n.length===1&&/[a-z]/.test(n)){const o=n.toUpperCase();return!r.has("ctrl")&&!r.has("alt")?[s(o,!1)]:[s(o,!0),s(o,!1),s(n,!0)]}return[s(n,r.has("shift"))]}u(v,"tmuxKeyNames");function ge(e){const{key:n,modifiers:t}=z(e);return t.includes("shift")&&/[a-z]/.test(n)?n.toUpperCase():n==="space"?" ":n}u(ge,"menuSelector");function T(e,n,t){const r=[];for(const s of e){const o=n.get(s);if(o!==void 0)for(const c of t.gestures(s))r.push({description:o.description,selector:ge(c),action:o.action})}return r}u(T,"boundMenuItems");function F(e){const n=new Map;for(const[c,l]of Object.entries(ue))l!==void 0&&n.set(c,l);for(const{menuId:c,actionId:l}of U){const x=_.find(w=>w.id===l);if(x===void 0)throw new Error(`missing attach binding definition ${l}`);n.set(c,{description:x.name.toLowerCase(),action:{kind:"literal-input",input:ne(l)}})}const t=T(U.map(({menuId:c})=>c),n,e);t.length>0&&n.set("crtr.tmux.menu.attach",{description:"attach viewer \u203A",action:{kind:"submenu",title:" attach viewer ",items:t}});const r=T(["crtr.tmux.menu.metadata","crtr.tmux.menu.context"],n,e);n.set("crtr.tmux.menu.show",{description:"inspect \u203A",action:{kind:"submenu",title:" inspect node ",items:r}});const s=T(["crtr.tmux.menu.fold-tools","crtr.tmux.menu.show-working"],n,e);n.set("crtr.tmux.menu.view-settings",{description:"display \u203A",action:{kind:"submenu",title:" display ",items:s}});const o=T(["crtr.tmux.menu.issue.idea","crtr.tmux.menu.issue.bug"],n,e);return n.set("crtr.tmux.menu.issues",{description:"create \u203A",action:{kind:"submenu",title:" create ",items:o}}),n}u(F,"resolvedMenuActions");function K(e,n){const t=u(c=>T(c,n,e),"boundItems"),r=[],s=u((c,l)=>{l.length!==0&&(r.length>0&&r.push({description:"",selector:"",action:{kind:"separator"}}),r.push({description:`-#[bold]${c}`,selector:"",action:{kind:"disabled"}},...l))},"addGroup");s("Explore",t(["crtr.tmux.menu.dashboard","crtr.tmux.menu.resume","crtr.tmux.menu.graph","crtr.tmux.menu.focus-manager","crtr.tmux.menu.show","crtr.tmux.menu.view-settings"])),s("Current node",t(["crtr.tmux.menu.attach","crtr.tmux.menu.background-bash","crtr.tmux.menu.file-review","crtr.tmux.menu.pin","crtr.tmux.menu.copy-id","crtr.tmux.menu.reload-viewer","crtr.tmux.menu.recap"])),s("Lifecycle",t(["crtr.tmux.menu.fork","crtr.tmux.menu.promote","crtr.tmux.menu.demote-mode","crtr.tmux.menu.demote","crtr.tmux.menu.make-resident","crtr.tmux.menu.detach","crtr.tmux.menu.close-subtree"])),s("System",t(["crtr.tmux.menu.providers","crtr.tmux.menu.provider-options","crtr.tmux.menu.plugins","crtr.tmux.menu.analytics","crtr.tmux.menu.context-admin","crtr.tmux.menu.issues"]));const o=e.gestures("crtr.tmux.menu.open");if(o.length>0&&r.length>0){r.push({description:"",selector:"",action:{kind:"separator"}});for(const c of o)r.push({description:`${te(c)} to close`,selector:fe(c),action:{kind:"dismiss"}})}return r}u(K,"menuItems");function Pe(e){const n=e??q().bindings;return N(" crtr ",K(n,F(n)))}u(Pe,"crouterMenuTmuxArgs");function S(e,n){let t=n;for(;t<e.length&&/\s/.test(e[t]);)t+=1;if(t>=e.length)return null;let r=null,s="";for(;t<e.length;){const o=e[t];if(r===null&&/\s/.test(o))break;if(r===null&&(o==="'"||o==='"')){r=o,t+=1;continue}if(r===o){r=null,t+=1;continue}if(o==="\\"&&r!=="'"&&t+1<e.length){s+=e[t+1],t+=2;continue}s+=o,t+=1}return{value:s,end:t}}u(S,"readTmuxToken");function B(e){const n=S(e,0);if(n?.value!=="bind-key")return null;let t=n.end,r=!1;for(;;){const s=S(e,t);if(s===null)return null;if(t=s.end,s.value==="-r"){r=!0;continue}if(s.value!=="-T")continue;const o=S(e,t);if(o===null)return null;const c=S(e,o.end);return c===null?null:{table:o.value,key:c.value,command:e.slice(c.end).trimStart(),repeat:r}}}u(B,"parseTmuxBinding");function $(e,n){const t=f(["list-keys","-T",e]);return t.ok?t.stdout.split(`
2
2
  `).find(r=>{const s=B(r);return s?.table===e&&s.key===n})??null:null}u($,"bindingAt");function Le(e){const n=f(["list-keys","-T",p]);if(!n.ok)return[];const t=new Set(n.stdout.split(`
3
- `).flatMap(r=>{const s=B(r);return s?.table===p?[s.key]:[]}));return e.filter(r=>!r.includes(" > ")&&v(r).some(s=>t.has(s)))}u(Le,"occupiedTmuxRootGestures");function R(e){return typeof e=="object"&&e!==null&&typeof e.table=="string"&&typeof e.key=="string"}u(R,"isInstalledPair");function ke(e){if(typeof e!="object"||e===null)return!1;const n=e;return typeof n.bindingId=="string"&&ee.has(n.bindingId)&&typeof n.gesture=="string"&&typeof n.key=="string"&&(typeof n.original=="string"||n.original===null)&&typeof n.wrapper=="string"&&(n.state==="owned"||n.state==="relinquished")}u(ke,"isWrappedRootBinding");function he(){const e=f(["show-options","-s","-q",A]);if(!e.ok)return null;if(e.stdout==="")return{pairs:[],wrappers:[]};const n=f(["show-options","-s","-qv",A]);if(!n.ok)return null;try{const t=JSON.parse(n.stdout);if(Array.isArray(t))return t.every(R)?{pairs:t,wrappers:[]}:null;if(typeof t!="object"||t===null)return null;const r=t;return!Array.isArray(r.pairs)||!r.pairs.every(R)||!Array.isArray(r.wrappers)||!r.wrappers.every(ke)?null:{pairs:r.pairs,wrappers:r.wrappers}}catch{return null}}u(he,"readManifest");function W(e){return f(["set-option","-s",A,JSON.stringify(e)])}u(W,"writeManifest");function C(e){return e.replace(/\\/g,"\\\\").replace(/;/g,"\\;")}u(C,"tmuxKeyArgument");function xe(e){return`"${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/;/g,"\\;")}"`}u(xe,"tmuxCommandToken");function ye(e){let n=null,t="";for(let r=0;r<e.length;r+=1){const s=e[r];if(n===null&&(s==="'"||s==='"')){n=s,t+=s;continue}if(n===s){n=null,t+=s;continue}if(s==="\\"&&r+1<e.length){const o=e[r+1];n===null&&o===";"?t+=";":t+=s+o,r+=1;continue}t+=s}return t}u(ye,"bindingCommandForBranch");function be(e){const n=Y(M(O(),"crtr-tmux-binding-")),t=M(n,"binding.conf");try{return V(t,`${e}
4
- `,"utf8"),f(["source-file",t])}finally{Z(n,{recursive:!0,force:!0})}}u(be,"sourceBindingLine");function X(e){return e.original===null?f(["unbind-key","-T",p,C(e.key)]):be(e.original)}u(X,"restoreWrappedBinding");function we(){return["run-shell","-b",'crtr surface inbox --quiet --tmux-socket "#{socket_path}" --tmux-client "#{client_name}" --target-pane "#{pane_id}"']}u(we,"inboxToggleTmuxCommand");function ve(e){switch(e){case"crtr.tmux.node.next":return["run-shell","crtr surface node cycle --dir next --pane '#{pane_id}' >/dev/null 2>&1"];case"crtr.tmux.node.previous":return["run-shell","crtr surface node cycle --dir prev --pane '#{pane_id}' >/dev/null 2>&1"];case"crtr.tmux.inbox.toggle":return we();default:throw new Error(`no tmux command for ${e}`)}}u(ve,"actionCommand");function $e(){const e=process.env.TMUX?.split(",")[0]?.trim();if(e!==void 0&&e!=="")return`${e}.crtr-keybindings.lock`;const n=f(["display-message","-p","#{socket_path}"]);if(n.ok&&n.stdout!=="")return`${n.stdout}.crtr-keybindings.lock`;const t=typeof process.getuid=="function"?process.getuid():"user";return M(O(),`crtr-tmux-keybindings-${t}.lock`)}u($e,"tmuxBindingLockPath");function De(e,n=[]){return oe($e(),()=>Ie(e,n))}u(De,"installTmuxBindings");function Ie(e,n=[]){const t=e===void 0?q():void 0,r=e??t.bindings,s=e===void 0?t.tmuxPassthrough:ie(n),o=[],c=[],l=he();if(l===null)return o.push({code:"tmux-error",message:`tmux is unavailable or ${A} is malformed`}),{ok:!1,installed:c,diagnostics:o};const x=new Map;for(const i of s){const a=_.find(d=>d.id===i);if(!(a===void 0||!re(a))){for(const d of r.gestures(i))if(!d.includes(" > "))for(const m of v(d))x.has(m)||x.set(m,{bindingId:i,gesture:d})}}const w=new Set,I=[];for(const i of l.wrappers){const a=x.get(i.key),d=$(p,i.key);if(d!==null&&d===i.original)continue;if(i.state==="relinquished"||d!==i.wrapper){w.add(i.key),a!==void 0&&I.push({...i,...a,state:"relinquished"});continue}const m=X(i);m.ok||(w.add(i.key),I.push(i),o.push({code:"tmux-error",bindingId:i.bindingId,gesture:i.gesture,message:`could not restore tmux binding ${p}:${i.key}: ${m.stderr}`}))}for(const i of l.pairs){if(i.table===p&&w.has(i.key))continue;const a=f(["unbind-key","-T",i.table,C(i.key)]);!a.ok&&$(i.table,i.key)!==null&&o.push({code:"tmux-error",message:`could not remove crouter binding ${i.table}:${i.key}: ${a.stderr}`})}if(o.some(i=>i.code==="tmux-error")){const i=W({pairs:l.pairs,wrappers:I});return i.ok||o.push({code:"tmux-error",message:`could not save crouter tmux ownership manifest: ${i.stderr}`}),{ok:!1,installed:c,diagnostics:o}}const E=u((i,a,d,m,k)=>{if($(i,a)!==null)return o.push({code:"occupied",bindingId:m,gesture:k,message:`${i}:${a} is already bound by tmux; ${m} was not installed`}),!1;const g=f(["bind-key","-T",i,C(a),...d]);return g.ok?(c.push({table:i,key:a}),!0):(o.push({code:"tmux-error",bindingId:m,gesture:k,message:`could not install ${m}: ${g.stderr}`}),!1)},"install"),P=F(r),H=N(" crtr ",K(r,P));for(const i of r.gestures("crtr.tmux.menu.open"))for(const a of v(i))E(p,a,H,"crtr.tmux.menu.open",i);for(const i of _){if(i.pair?.field!=="menu")continue;const a=_.find(y=>y.id===i.pair?.pairedId);if(a?.pair?.field!=="global")continue;const d=i.id,m=a.id,k=P.get(d),g=k===void 0?null:j(k.action);if(g===null)throw new Error(`no direct tmux action for paired menu binding ${d}`);for(const y of r.gestures(m))for(const J of v(y))E(p,J,g.argv,m,y)}for(const i of["crtr.tmux.node.next","crtr.tmux.node.previous","crtr.tmux.inbox.toggle"])for(const a of r.gestures(i))for(const d of v(a))E(p,d,ve(i),i,a);const L=[];for(const[i,a]of x){if(w.has(i))continue;const d=$(p,i);if(d===null)continue;const m=B(d);if(m===null||m.command===""){o.push({code:"tmux-error",bindingId:a.bindingId,gesture:a.gesture,message:`could not parse occupied tmux binding ${p}:${i}`});continue}const k=f(["bind-key",...m.repeat?["-r"]:[],"-T",p,C(i),"if-shell","-F","#{!=:#{@crtr_attach},}",`send-keys ${xe(i)}`,ye(m.command)]);if(!k.ok){o.push({code:"tmux-error",bindingId:a.bindingId,gesture:a.gesture,message:`could not wrap ${p}:${i}: ${k.stderr}`});continue}const g=$(p,i);if(g===null||!g.includes("@crtr_attach")){o.push({code:"tmux-error",bindingId:a.bindingId,gesture:a.gesture,message:`could not fingerprint wrapped tmux binding ${p}:${i}`});continue}const y={...a,key:i,original:d,wrapper:g,state:"owned"};I.push(y),L.push(y)}const D=W({pairs:c,wrappers:I});if(!D.ok){o.push({code:"tmux-error",message:`could not save crouter tmux ownership manifest: ${D.stderr}`});for(const i of L)$(p,i.key)===i.wrapper&&X(i)}return{ok:o.every(i=>i.code!=="tmux-error"),installed:c,diagnostics:o}}u(Ie,"reconcileTmuxBindings");export{Pe as crouterMenuTmuxArgs,we as inboxToggleTmuxCommand,De as installTmuxBindings,Le as occupiedTmuxRootGestures};
3
+ `).flatMap(r=>{const s=B(r);return s?.table===p?[s.key]:[]}));return e.filter(r=>!r.includes(" > ")&&v(r).some(s=>t.has(s)))}u(Le,"occupiedTmuxRootGestures");function R(e){return typeof e=="object"&&e!==null&&typeof e.table=="string"&&typeof e.key=="string"}u(R,"isInstalledPair");function he(e){if(typeof e!="object"||e===null)return!1;const n=e;return typeof n.bindingId=="string"&&ee.has(n.bindingId)&&typeof n.gesture=="string"&&typeof n.key=="string"&&(typeof n.original=="string"||n.original===null)&&typeof n.wrapper=="string"&&(n.state==="owned"||n.state==="relinquished")}u(he,"isWrappedRootBinding");function ke(){const e=f(["show-options","-s","-q",A]);if(!e.ok)return null;if(e.stdout==="")return{pairs:[],wrappers:[]};const n=f(["show-options","-s","-qv",A]);if(!n.ok)return null;try{const t=JSON.parse(n.stdout);if(Array.isArray(t))return t.every(R)?{pairs:t,wrappers:[]}:null;if(typeof t!="object"||t===null)return null;const r=t;return!Array.isArray(r.pairs)||!r.pairs.every(R)||!Array.isArray(r.wrappers)||!r.wrappers.every(he)?null:{pairs:r.pairs,wrappers:r.wrappers}}catch{return null}}u(ke,"readManifest");function W(e){return f(["set-option","-s",A,JSON.stringify(e)])}u(W,"writeManifest");function C(e){return e.replace(/\\/g,"\\\\").replace(/;/g,"\\;")}u(C,"tmuxKeyArgument");function xe(e){return`"${e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/;/g,"\\;")}"`}u(xe,"tmuxCommandToken");function ye(e){let n=null,t="";for(let r=0;r<e.length;r+=1){const s=e[r];if(n===null&&(s==="'"||s==='"')){n=s,t+=s;continue}if(n===s){n=null,t+=s;continue}if(s==="\\"&&r+1<e.length){const o=e[r+1];n===null&&o===";"?t+=";":t+=s+o,r+=1;continue}t+=s}return t}u(ye,"bindingCommandForBranch");function be(e){const n=Y(M(O(),"crtr-tmux-binding-")),t=M(n,"binding.conf");try{return V(t,`${e}
4
+ `,"utf8"),f(["source-file",t])}finally{Z(n,{recursive:!0,force:!0})}}u(be,"sourceBindingLine");function X(e){return e.original===null?f(["unbind-key","-T",p,C(e.key)]):be(e.original)}u(X,"restoreWrappedBinding");function we(){return["run-shell","-b",'crtr surface inbox --quiet --tmux-socket "#{socket_path}" --tmux-client "#{client_name}" --target-pane "#{pane_id}"']}u(we,"inboxToggleTmuxCommand");function ve(e){switch(e){case"crtr.tmux.node.next":return["run-shell","crtr surface node cycle --dir next --pane '#{pane_id}' >/dev/null 2>&1"];case"crtr.tmux.node.previous":return["run-shell","crtr surface node cycle --dir prev --pane '#{pane_id}' >/dev/null 2>&1"];case"crtr.tmux.inbox.toggle":return we();default:throw new Error(`no tmux command for ${e}`)}}u(ve,"actionCommand");function $e(){const e=process.env.TMUX?.split(",")[0]?.trim();if(e!==void 0&&e!=="")return`${e}.crtr-keybindings.lock`;const n=f(["display-message","-p","#{socket_path}"]);if(n.ok&&n.stdout!=="")return`${n.stdout}.crtr-keybindings.lock`;const t=typeof process.getuid=="function"?process.getuid():"user";return M(O(),`crtr-tmux-keybindings-${t}.lock`)}u($e,"tmuxBindingLockPath");function De(e,n=[]){return oe($e(),()=>Ie(e,n))}u(De,"installTmuxBindings");function Ie(e,n=[]){const t=e===void 0?q():void 0,r=e??t.bindings,s=e===void 0?t.tmuxPassthrough:ie(n),o=[],c=[],l=ke();if(l===null)return o.push({code:"tmux-error",message:`tmux is unavailable or ${A} is malformed`}),{ok:!1,installed:c,diagnostics:o};const x=new Map;for(const i of s){const a=_.find(d=>d.id===i);if(!(a===void 0||!re(a))){for(const d of r.gestures(i))if(!d.includes(" > "))for(const m of v(d))x.has(m)||x.set(m,{bindingId:i,gesture:d})}}const w=new Set,I=[];for(const i of l.wrappers){const a=x.get(i.key),d=$(p,i.key);if(d!==null&&d===i.original)continue;if(i.state==="relinquished"||d!==i.wrapper){w.add(i.key),a!==void 0&&I.push({...i,...a,state:"relinquished"});continue}const m=X(i);m.ok||(w.add(i.key),I.push(i),o.push({code:"tmux-error",bindingId:i.bindingId,gesture:i.gesture,message:`could not restore tmux binding ${p}:${i.key}: ${m.stderr}`}))}for(const i of l.pairs){if(i.table===p&&w.has(i.key))continue;const a=f(["unbind-key","-T",i.table,C(i.key)]);!a.ok&&$(i.table,i.key)!==null&&o.push({code:"tmux-error",message:`could not remove crouter binding ${i.table}:${i.key}: ${a.stderr}`})}if(o.some(i=>i.code==="tmux-error")){const i=W({pairs:l.pairs,wrappers:I});return i.ok||o.push({code:"tmux-error",message:`could not save crouter tmux ownership manifest: ${i.stderr}`}),{ok:!1,installed:c,diagnostics:o}}const E=u((i,a,d,m,h)=>{if($(i,a)!==null)return o.push({code:"occupied",bindingId:m,gesture:h,message:`${i}:${a} is already bound by tmux; ${m} was not installed`}),!1;const g=f(["bind-key","-T",i,C(a),...d]);return g.ok?(c.push({table:i,key:a}),!0):(o.push({code:"tmux-error",bindingId:m,gesture:h,message:`could not install ${m}: ${g.stderr}`}),!1)},"install"),P=F(r),H=N(" crtr ",K(r,P));for(const i of r.gestures("crtr.tmux.menu.open"))for(const a of v(i))E(p,a,H,"crtr.tmux.menu.open",i);for(const i of _){if(i.pair?.field!=="menu")continue;const a=_.find(y=>y.id===i.pair?.pairedId);if(a?.pair?.field!=="global")continue;const d=i.id,m=a.id,h=P.get(d),g=h===void 0?null:j(h.action);if(g===null)throw new Error(`no direct tmux action for paired menu binding ${d}`);for(const y of r.gestures(m))for(const J of v(y))E(p,J,g.argv,m,y)}for(const i of["crtr.tmux.node.next","crtr.tmux.node.previous","crtr.tmux.inbox.toggle"])for(const a of r.gestures(i))for(const d of v(a))E(p,d,ve(i),i,a);const L=[];for(const[i,a]of x){if(w.has(i))continue;const d=$(p,i);if(d===null)continue;const m=B(d);if(m===null||m.command===""){o.push({code:"tmux-error",bindingId:a.bindingId,gesture:a.gesture,message:`could not parse occupied tmux binding ${p}:${i}`});continue}const h=f(["bind-key",...m.repeat?["-r"]:[],"-T",p,C(i),"if-shell","-F","#{!=:#{@crtr_attach},}",`send-keys ${xe(i)}`,ye(m.command)]);if(!h.ok){o.push({code:"tmux-error",bindingId:a.bindingId,gesture:a.gesture,message:`could not wrap ${p}:${i}: ${h.stderr}`});continue}const g=$(p,i);if(g===null||!g.includes("@crtr_attach")){o.push({code:"tmux-error",bindingId:a.bindingId,gesture:a.gesture,message:`could not fingerprint wrapped tmux binding ${p}:${i}`});continue}const y={...a,key:i,original:d,wrapper:g,state:"owned"};I.push(y),L.push(y)}const D=W({pairs:c,wrappers:I});if(!D.ok){o.push({code:"tmux-error",message:`could not save crouter tmux ownership manifest: ${D.stderr}`});for(const i of L)$(p,i.key)===i.wrapper&&X(i)}return{ok:o.every(i=>i.code!=="tmux-error"),installed:c,diagnostics:o}}u(Ie,"reconcileTmuxBindings");export{Pe as crouterMenuTmuxArgs,we as inboxToggleTmuxCommand,De as installTmuxBindings,Le as occupiedTmuxRootGestures};
@@ -260,6 +260,12 @@ export declare function runShellInBackground(command: string): boolean;
260
260
  * — the exact command `respawn-pane` with no argument re-runs. undefined when
261
261
  * tmux is unreachable or the pane is gone. */
262
262
  export declare function paneStartCommand(pane: string): string | undefined;
263
+ /** Swap two panes' places on screen (`swap-pane -s a -t b`). The panes keep their ids and
264
+ * processes; only their positions (and windows) exchange. Returns tmux's error text on
265
+ * failure, null on success. */
266
+ export declare function swapPanes(source: string, target: string): string | null;
267
+ /** The foreground command name of a pane (`#{pane_current_command}`). */
268
+ export declare function paneCurrentCommand(pane: string): string | undefined;
263
269
  /** The pane's root process pid (`#{pane_pid}`) — the process tmux launched in
264
270
  * the pane (the viewer itself, or the login shell hosting it). undefined when
265
271
  * tmux is unreachable or the pane is gone. */
@@ -1,6 +1,6 @@
1
- var O=Object.defineProperty;var r=(n,t)=>O(n,"name",{value:t,configurable:!0});import{execFile as w,spawnSync as a}from"node:child_process";import{encodeAttachControlInput as E}from"../keybindings/index.js";import{listProcessTable as $}from"../canvas/pid.js";import{bundledPiCommandForShell as N}from"./pi-cli.js";import{shellQuote as l}from"../../shared/shell-quote.js";import{envPiBinaryOverride as C}from"../../shared/env.js";function o(n){const t=a("tmux",n,{encoding:"utf8"});return{ok:t.status===0,stdout:(t.stdout??"").trim(),stderr:(t.stderr??"").trim()}}r(o,"tmux");function p(n,t){return o(n===void 0||n===""?t:["-S",n,...t])}r(p,"tmuxOnSocket");function M(){return process.env.TMUX!==void 0&&process.env.TMUX!==""}r(M,"inTmux");function Y(){return a("tmux",["list-sessions"],{encoding:"utf8",stdio:"ignore"}).status===0}r(Y,"tmuxSessionsReachable");function Z(){return a("tmux",["list-clients","-F","#{client_name}"],{encoding:"utf8"}).status===0}r(Z,"tmuxClientsReachable");function nn(n){if(n==="")return null;const t=o(["list-panes","-a","-F","#{pane_id} #{@crtr_node}"]);if(!t.ok)return null;for(const e of t.stdout.split(`
2
- `)){const s=e.indexOf(" ");if(!(s<0)&&e.slice(s+1)===n){const u=e.slice(0,s);if(u!=="")return u}}return null}r(nn,"paneOfNode");function T(){if(!M())return null;const n=o(["display-message","-p","#{session_name} #{window_id} #{pane_id}"]);if(!n.ok)return null;const[t,e,s]=n.stdout.split(" ");return{session:t,window:e,pane:s}}r(T,"currentTmux");function tn(){const n=a("tmux",["-V"],{stdio:"ignore"});return n.error===void 0&&n.status===0}r(tn,"tmuxAvailable");function en(n){return a("tmux",n,{stdio:"inherit"}).status??1}r(en,"tmuxInteractive");function F(n){return o(["has-session","-t",n]).ok}r(F,"sessionExists");function A(){const n=o(["show-options","-s","terminal-overrides"]).stdout;/Enbp=\\+E/.test(n)||o(["set-option","-sga","terminal-overrides","*:Enbp=\\E[?2004h:Dsbp=\\E[?2004l"])}r(A,"ensureBracketedPaste");function R(){A(),o(["set-option","-s","focus-events","on"])}r(R,"ensureServerOptions");function rn(n,t){F(n)||o(["new-session","-d","-s",n,"-c",t]),R()}r(rn,"ensureSession");function x(n){const t=[];for(const[e,s]of Object.entries(n))t.push("-e",`${e}=${s}`);return t}r(x,"envFlags");function on(n){const t=o(["new-window","-d","-a","-P","-F","#{window_id} #{pane_id}","-t",`${n.session}:`,"-n",n.name,"-c",n.cwd,...x(n.env),n.command]);if(!t.ok)return null;const[e,s]=t.stdout.split(" ");return e===void 0||e===""||s===void 0||s===""?null:{window:e,pane:s}}r(on,"openNodeWindow");function sn(n,t){const e=o(["split-window","-d",...t.vertical===!0?[]:["-h"],...t.before===!0?["-b"]:[],...t.size!==void 0?["-l",String(t.size)]:[],"-P","-F","#{pane_id}","-t",n,"-c",t.cwd,...x(t.env),t.command]);return e.ok&&e.stdout!==""?e.stdout:null}r(sn,"splitWindow");function un(n,t){return!(T()?.session===n)&&!o(["switch-client","-t",n]).ok?!1:o(["select-window","-t",t]).ok}r(un,"focusWindow");function cn(n){return o(["kill-window","-t",n]).ok}r(cn,"closeWindow");function dn(n,t){return o(["rename-window","-t",n,t]).ok}r(dn,"renameWindow");function an(n){const t=o(["break-pane","-d","-P","-F","#{window_id} #{pane_id}","-s",n]);if(!t.ok)return null;const[e,s]=t.stdout.split(" ");return e===void 0||e===""||s===void 0||s===""?null:{window:e,pane:s}}r(an,"breakPane");function pn(n){return o(["kill-pane","-t",n]).ok}r(pn,"closePane");function ln(n,t,e){return o(h(n,t,e)).ok}r(ln,"displayPopup");function fn(n,t,e){return new Promise(s=>{try{w("tmux",h(n,t,e),i=>s(i===null)).stdin?.end()}catch{s(!1)}})}r(fn,"displayPopupAsync");function h(n,t,e){return["display-popup","-E","-t",n,"-d",t,"-w","90%","-h","90%",e]}r(h,"displayPopupArgs");function mn(n,t){const e=o(["display-message","-p","-t",`${n}:${t}`,"#{pane_id}"]);return e.ok&&e.stdout!==""?e.stdout:null}r(mn,"paneOfWindow");function wn(n){const t=o(["display-message","-p","-t",n,"#{window_id}"]);return t.ok&&t.stdout!==""?t.stdout:null}r(wn,"windowOfPane");function xn(n){const t=o(["display-message","-p","-t",n,"#{session_name} #{window_id}"]);if(!t.ok)return null;const e=/^(.*) (@\d+)$/.exec(t.stdout);return e===null||e[1]===""?null:{session:e[1],window:e[2]}}r(xn,"paneLocation");function kn(n){const t=o(["display-message","-p","-t",n,"#{window_index}"]);return!t.ok||!/^\d+$/.test(t.stdout)?null:Number.parseInt(t.stdout,10)}r(kn,"windowIndex");function gn(n){const t=o(["display-message","-p","-t",n,"#{pane_id}"]);return t.ok&&t.stdout===n}r(gn,"paneExists");function hn(n){const t=o(["display-message","-p","-t",n,"#{pane_id} #{pane_dead}"]);return t.ok&&t.stdout===`${n} 0`}r(hn,"paneRunning");function _n(){const n=o(["list-panes","-a","-F","#{pane_id}"]);return n.ok?new Set(n.stdout.split(`
1
+ var O=Object.defineProperty;var r=(n,t)=>O(n,"name",{value:t,configurable:!0});import{execFile as w,spawnSync as d}from"node:child_process";import{encodeAttachControlInput as C}from"../keybindings/index.js";import{listProcessTable as E}from"../canvas/pid.js";import{bundledPiCommandForShell as $}from"./pi-cli.js";import{shellQuote as l}from"../../shared/shell-quote.js";import{envPiBinaryOverride as N}from"../../shared/env.js";function o(n){const t=d("tmux",n,{encoding:"utf8"});return{ok:t.status===0,stdout:(t.stdout??"").trim(),stderr:(t.stderr??"").trim()}}r(o,"tmux");function p(n,t){return o(n===void 0||n===""?t:["-S",n,...t])}r(p,"tmuxOnSocket");function M(){return process.env.TMUX!==void 0&&process.env.TMUX!==""}r(M,"inTmux");function Y(){return d("tmux",["list-sessions"],{encoding:"utf8",stdio:"ignore"}).status===0}r(Y,"tmuxSessionsReachable");function Z(){return d("tmux",["list-clients","-F","#{client_name}"],{encoding:"utf8"}).status===0}r(Z,"tmuxClientsReachable");function nn(n){if(n==="")return null;const t=o(["list-panes","-a","-F","#{pane_id} #{@crtr_node}"]);if(!t.ok)return null;for(const e of t.stdout.split(`
2
+ `)){const s=e.indexOf(" ");if(!(s<0)&&e.slice(s+1)===n){const u=e.slice(0,s);if(u!=="")return u}}return null}r(nn,"paneOfNode");function T(){if(!M())return null;const n=o(["display-message","-p","#{session_name} #{window_id} #{pane_id}"]);if(!n.ok)return null;const[t,e,s]=n.stdout.split(" ");return{session:t,window:e,pane:s}}r(T,"currentTmux");function tn(){const n=d("tmux",["-V"],{stdio:"ignore"});return n.error===void 0&&n.status===0}r(tn,"tmuxAvailable");function en(n){return d("tmux",n,{stdio:"inherit"}).status??1}r(en,"tmuxInteractive");function F(n){return o(["has-session","-t",n]).ok}r(F,"sessionExists");function A(){const n=o(["show-options","-s","terminal-overrides"]).stdout;/Enbp=\\+E/.test(n)||o(["set-option","-sga","terminal-overrides","*:Enbp=\\E[?2004h:Dsbp=\\E[?2004l"])}r(A,"ensureBracketedPaste");function R(){A(),o(["set-option","-s","focus-events","on"])}r(R,"ensureServerOptions");function rn(n,t){F(n)||o(["new-session","-d","-s",n,"-c",t]),R()}r(rn,"ensureSession");function x(n){const t=[];for(const[e,s]of Object.entries(n))t.push("-e",`${e}=${s}`);return t}r(x,"envFlags");function on(n){const t=o(["new-window","-d","-a","-P","-F","#{window_id} #{pane_id}","-t",`${n.session}:`,"-n",n.name,"-c",n.cwd,...x(n.env),n.command]);if(!t.ok)return null;const[e,s]=t.stdout.split(" ");return e===void 0||e===""||s===void 0||s===""?null:{window:e,pane:s}}r(on,"openNodeWindow");function sn(n,t){const e=o(["split-window","-d",...t.vertical===!0?[]:["-h"],...t.before===!0?["-b"]:[],...t.size!==void 0?["-l",String(t.size)]:[],"-P","-F","#{pane_id}","-t",n,"-c",t.cwd,...x(t.env),t.command]);return e.ok&&e.stdout!==""?e.stdout:null}r(sn,"splitWindow");function un(n,t){return!(T()?.session===n)&&!o(["switch-client","-t",n]).ok?!1:o(["select-window","-t",t]).ok}r(un,"focusWindow");function an(n){return o(["kill-window","-t",n]).ok}r(an,"closeWindow");function cn(n,t){return o(["rename-window","-t",n,t]).ok}r(cn,"renameWindow");function dn(n){const t=o(["break-pane","-d","-P","-F","#{window_id} #{pane_id}","-s",n]);if(!t.ok)return null;const[e,s]=t.stdout.split(" ");return e===void 0||e===""||s===void 0||s===""?null:{window:e,pane:s}}r(dn,"breakPane");function pn(n){return o(["kill-pane","-t",n]).ok}r(pn,"closePane");function ln(n,t,e){return o(h(n,t,e)).ok}r(ln,"displayPopup");function fn(n,t,e){return new Promise(s=>{try{w("tmux",h(n,t,e),i=>s(i===null)).stdin?.end()}catch{s(!1)}})}r(fn,"displayPopupAsync");function h(n,t,e){return["display-popup","-E","-t",n,"-d",t,"-w","90%","-h","90%",e]}r(h,"displayPopupArgs");function mn(n,t){const e=o(["display-message","-p","-t",`${n}:${t}`,"#{pane_id}"]);return e.ok&&e.stdout!==""?e.stdout:null}r(mn,"paneOfWindow");function wn(n){const t=o(["display-message","-p","-t",n,"#{window_id}"]);return t.ok&&t.stdout!==""?t.stdout:null}r(wn,"windowOfPane");function xn(n){const t=o(["display-message","-p","-t",n,"#{session_name} #{window_id}"]);if(!t.ok)return null;const e=/^(.*) (@\d+)$/.exec(t.stdout);return e===null||e[1]===""?null:{session:e[1],window:e[2]}}r(xn,"paneLocation");function kn(n){const t=o(["display-message","-p","-t",n,"#{window_index}"]);return!t.ok||!/^\d+$/.test(t.stdout)?null:Number.parseInt(t.stdout,10)}r(kn,"windowIndex");function gn(n){const t=o(["display-message","-p","-t",n,"#{pane_id}"]);return t.ok&&t.stdout===n}r(gn,"paneExists");function hn(n){const t=o(["display-message","-p","-t",n,"#{pane_id} #{pane_dead}"]);return t.ok&&t.stdout===`${n} 0`}r(hn,"paneRunning");function _n(){const n=o(["list-panes","-a","-F","#{pane_id}"]);return n.ok?new Set(n.stdout.split(`
3
3
  `).filter(t=>t!=="")):null}r(_n,"listLivePanes");const V=2e3;async function Pn(){const n=await new Promise(t=>{try{w("tmux",["list-panes","-a","-F","#{pane_id}"],{encoding:"utf8",timeout:V,maxBuffer:4194304},(e,s)=>t(e?null:s))}catch{t(null)}});return n===null?null:new Set(n.trim().split(`
4
- `).filter(t=>t!==""))}r(Pn,"listLivePanesAsync");function yn(n){const t=o(["display-message","-p","-t",n,"#{pane_current_path}"]);return t.ok&&t.stdout!==""?t.stdout:null}r(yn,"paneCurrentPath");function bn(n){const t=o(["display-message","-p","-t",n,"#{pane_width} #{pane_height}"]);if(!t.ok)return null;const[e,s]=t.stdout.split(" "),u=Number.parseInt(e,10),i=Number.parseInt(s,10);return Number.isInteger(u)&&u>0&&Number.isInteger(i)&&i>0?{cols:u,rows:i}:null}r(bn,"paneSize");function vn(n,t,e=4e3){const s=r(u=>{const i=["display-message",...u?["-C"]:[],"-d",String(e)];return t!==void 0&&t!==""&&i.push("-t",t),i.push(n),i},"args");return o(s(!0)).ok||o(s(!1)).ok}r(vn,"displayMessage");function Sn(n,t,e){return o(["set-option","-p","-t",n,t,e]).ok}r(Sn,"setPaneOption");function In(n,t){const e=o(["show-options","-p","-t",n,"-q","-v",t]);return e.ok?e.stdout:void 0}r(In,"getPaneOption");const W="@crtr_viewer_at";let _=0,P=Promise.resolve();function On(n){if(n==="")return;const t=Math.max(Date.now(),_+1);_=t,P=P.then(()=>new Promise(e=>{try{w("tmux",["set-option","-p","-t",n,W,String(t)],()=>e())}catch{e()}}))}r(On,"stampViewerPaneAsync");function B(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}r(B,"escapeRegex");function k(n,t){const e=n.replace(/^"|"$/g,"");return new RegExp(`(?:^|\\s)surface\\s+attach\\s+to\\s+${B(t)}(?:\\s|$)`).test(e)}r(k,"looksLikeViewerCommand");function L(){const n=$();if(n===null)return null;const t=new Map,e=new Map;for(const{pid:s,ppid:u,command:i}of n){e.set(s,i);const c=t.get(u)??[];c.push(s),t.set(u,c)}return{childrenOf:t,commandOf:e}}r(L,"readProcessTree");function j(n,t,e){const s=[t];for(;s.length>0;){const u=s.pop();if(k(n.commandOf.get(u)??"",e))return!0;const i=n.childrenOf.get(u);i!==void 0&&s.push(...i)}return!1}r(j,"processTreeHasViewer");function En(n){const t=p(n,["list-panes","-a","-F","#{pane_id} #{session_name} #{@crtr_node} #{@crtr_viewer_at}"]);if(!t.ok||t.stdout==="")return[];const e=[];let s;for(const u of t.stdout.split(`
5
- `)){const[i,c,d,b]=u.split(" ");if(i===void 0||i===""||c===void 0||c===""||d===void 0||d==="")continue;const g=p(n,["display-message","-p","-t",i,"#{pane_current_command} #{pane_start_command} #{pane_pid}"]);if(!g.ok)continue;const[v="",S="",I]=g.stdout.split(" ");if(!(k(v,d)||k(S,d))){s??=L();const m=Number.parseInt(I,10);if(s===null||!Number.isInteger(m)||m<=0||!j(s,m,d))continue}const f=Number.parseInt(b??"",10);e.push({pane:i,session:c,nodeId:d,stampMs:Number.isSafeInteger(f)&&f>=0?f:0})}return e}r(En,"liveViewerPanes");function $n(n,t,e){return p(n,["send-keys","-l","-t",t,E(e)]).ok}r($n,"sendAttachControl");function Nn(n,t,e){const s=r(u=>{const i=["display-message",...u?["-C"]:[]];return t!==void 0&&t!==""&&i.push("-c",t),i.push(e),i},"args");return p(n,s(!0)).ok||p(n,s(!1)).ok}r(Nn,"displayClientMessage");function y(n){return["respawn-pane","-k","-c",n.cwd,...x(n.env),"-t",n.pane,n.command]}r(y,"respawnPaneArgs");function Cn(n){return o(y(n)).ok}r(Cn,"respawnPaneSync");function Mn(n){return z(`tmux ${y(n).map(l).join(" ")} >/dev/null 2>&1`)}r(Mn,"respawnPaneInBackground");function z(n){return o(["run-shell","-b",n]).ok}r(z,"runShellInBackground");function Tn(n){const t=o(["display-message","-p","-t",n,"#{pane_start_command}"]);return t.ok?t.stdout:void 0}r(Tn,"paneStartCommand");function Fn(n){const t=o(["display-message","-p","-t",n,"#{pane_pid}"]);if(!t.ok)return;const e=Number.parseInt(t.stdout,10);return Number.isInteger(e)&&e>0?e:void 0}r(Fn,"panePid");function An(n){return o(["respawn-pane","-k","-t",n]).ok}r(An,"respawnPaneInPlace");function Rn(n,t=C()??N(l)){return[t,...n.map(l)].join(" ")}r(Rn,"piCommand");function U(n){const t=o(["list-windows","-t",n,"-F","#{window_id}"]);return!t.ok||t.stdout===""?[]:t.stdout.split(`
6
- `).filter(e=>e!=="")}r(U,"listWindowIds");function Vn(n,t){return n==null||t==null?!1:U(n).includes(t)}r(Vn,"windowAlive");function Wn(n,t){return o(["select-window","-t",`${n}:${t}`]).ok}r(Wn,"selectWindow");function Bn(n,t){return o(["select-layout","-t",n,t]).ok}r(Bn,"selectLayout");function Ln(n){return o(["select-pane","-t",n]).ok}r(Ln,"selectPane");function jn(n){return o(["switch-client","-t",n]).ok}r(jn,"switchClient");function zn(n,t){return o(["send-keys","-t",n,t,"Enter"]).ok}r(zn,"sendKeysEnter");export{W as VIEWER_STAMP_OPTION,an as breakPane,pn as closePane,cn as closeWindow,T as currentTmux,Nn as displayClientMessage,vn as displayMessage,ln as displayPopup,fn as displayPopupAsync,rn as ensureSession,un as focusWindow,In as getPaneOption,M as inTmux,_n as listLivePanes,Pn as listLivePanesAsync,U as listWindowIds,En as liveViewerPanes,on as openNodeWindow,yn as paneCurrentPath,gn as paneExists,xn as paneLocation,nn as paneOfNode,mn as paneOfWindow,Fn as panePid,hn as paneRunning,bn as paneSize,Tn as paneStartCommand,Rn as piCommand,dn as renameWindow,Mn as respawnPaneInBackground,An as respawnPaneInPlace,Cn as respawnPaneSync,z as runShellInBackground,o as runTmux,Bn as selectLayout,Ln as selectPane,Wn as selectWindow,$n as sendAttachControl,zn as sendKeysEnter,F as sessionExists,Sn as setPaneOption,l as shellQuote,sn as splitWindow,On as stampViewerPaneAsync,jn as switchClient,tn as tmuxAvailable,Z as tmuxClientsReachable,en as tmuxInteractive,Y as tmuxSessionsReachable,Vn as windowAlive,kn as windowIndex,wn as windowOfPane};
4
+ `).filter(t=>t!==""))}r(Pn,"listLivePanesAsync");function yn(n){const t=o(["display-message","-p","-t",n,"#{pane_current_path}"]);return t.ok&&t.stdout!==""?t.stdout:null}r(yn,"paneCurrentPath");function bn(n){const t=o(["display-message","-p","-t",n,"#{pane_width} #{pane_height}"]);if(!t.ok)return null;const[e,s]=t.stdout.split(" "),u=Number.parseInt(e,10),i=Number.parseInt(s,10);return Number.isInteger(u)&&u>0&&Number.isInteger(i)&&i>0?{cols:u,rows:i}:null}r(bn,"paneSize");function vn(n,t,e=4e3){const s=r(u=>{const i=["display-message",...u?["-C"]:[],"-d",String(e)];return t!==void 0&&t!==""&&i.push("-t",t),i.push(n),i},"args");return o(s(!0)).ok||o(s(!1)).ok}r(vn,"displayMessage");function Sn(n,t,e){return o(["set-option","-p","-t",n,t,e]).ok}r(Sn,"setPaneOption");function In(n,t){const e=o(["show-options","-p","-t",n,"-q","-v",t]);return e.ok?e.stdout:void 0}r(In,"getPaneOption");const W="@crtr_viewer_at";let _=0,P=Promise.resolve();function On(n){if(n==="")return;const t=Math.max(Date.now(),_+1);_=t,P=P.then(()=>new Promise(e=>{try{w("tmux",["set-option","-p","-t",n,W,String(t)],()=>e())}catch{e()}}))}r(On,"stampViewerPaneAsync");function B(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}r(B,"escapeRegex");function k(n,t){const e=n.replace(/^"|"$/g,"");return new RegExp(`(?:^|\\s)surface\\s+attach\\s+to\\s+${B(t)}(?:\\s|$)`).test(e)}r(k,"looksLikeViewerCommand");function L(){const n=E();if(n===null)return null;const t=new Map,e=new Map;for(const{pid:s,ppid:u,command:i}of n){e.set(s,i);const a=t.get(u)??[];a.push(s),t.set(u,a)}return{childrenOf:t,commandOf:e}}r(L,"readProcessTree");function j(n,t,e){const s=[t];for(;s.length>0;){const u=s.pop();if(k(n.commandOf.get(u)??"",e))return!0;const i=n.childrenOf.get(u);i!==void 0&&s.push(...i)}return!1}r(j,"processTreeHasViewer");function Cn(n){const t=p(n,["list-panes","-a","-F","#{pane_id} #{session_name} #{@crtr_node} #{@crtr_viewer_at}"]);if(!t.ok||t.stdout==="")return[];const e=[];let s;for(const u of t.stdout.split(`
5
+ `)){const[i,a,c,b]=u.split(" ");if(i===void 0||i===""||a===void 0||a===""||c===void 0||c==="")continue;const g=p(n,["display-message","-p","-t",i,"#{pane_current_command} #{pane_start_command} #{pane_pid}"]);if(!g.ok)continue;const[v="",S="",I]=g.stdout.split(" ");if(!(k(v,c)||k(S,c))){s??=L();const m=Number.parseInt(I,10);if(s===null||!Number.isInteger(m)||m<=0||!j(s,m,c))continue}const f=Number.parseInt(b??"",10);e.push({pane:i,session:a,nodeId:c,stampMs:Number.isSafeInteger(f)&&f>=0?f:0})}return e}r(Cn,"liveViewerPanes");function En(n,t,e){return p(n,["send-keys","-l","-t",t,C(e)]).ok}r(En,"sendAttachControl");function $n(n,t,e){const s=r(u=>{const i=["display-message",...u?["-C"]:[]];return t!==void 0&&t!==""&&i.push("-c",t),i.push(e),i},"args");return p(n,s(!0)).ok||p(n,s(!1)).ok}r($n,"displayClientMessage");function y(n){return["respawn-pane","-k","-c",n.cwd,...x(n.env),"-t",n.pane,n.command]}r(y,"respawnPaneArgs");function Nn(n){return o(y(n)).ok}r(Nn,"respawnPaneSync");function Mn(n){return z(`tmux ${y(n).map(l).join(" ")} >/dev/null 2>&1`)}r(Mn,"respawnPaneInBackground");function z(n){return o(["run-shell","-b",n]).ok}r(z,"runShellInBackground");function Tn(n){const t=o(["display-message","-p","-t",n,"#{pane_start_command}"]);return t.ok?t.stdout:void 0}r(Tn,"paneStartCommand");function Fn(n,t){const e=o(["swap-pane","-s",n,"-t",t]);return e.ok?null:e.stderr===""?"tmux swap-pane failed":e.stderr}r(Fn,"swapPanes");function An(n){const t=o(["display-message","-p","-t",n,"#{pane_current_command}"]);return t.ok&&t.stdout!==""?t.stdout:void 0}r(An,"paneCurrentCommand");function Rn(n){const t=o(["display-message","-p","-t",n,"#{pane_pid}"]);if(!t.ok)return;const e=Number.parseInt(t.stdout,10);return Number.isInteger(e)&&e>0?e:void 0}r(Rn,"panePid");function Vn(n){return o(["respawn-pane","-k","-t",n]).ok}r(Vn,"respawnPaneInPlace");function Wn(n,t=N()??$(l)){return[t,...n.map(l)].join(" ")}r(Wn,"piCommand");function U(n){const t=o(["list-windows","-t",n,"-F","#{window_id}"]);return!t.ok||t.stdout===""?[]:t.stdout.split(`
6
+ `).filter(e=>e!=="")}r(U,"listWindowIds");function Bn(n,t){return n==null||t==null?!1:U(n).includes(t)}r(Bn,"windowAlive");function Ln(n,t){return o(["select-window","-t",`${n}:${t}`]).ok}r(Ln,"selectWindow");function jn(n,t){return o(["select-layout","-t",n,t]).ok}r(jn,"selectLayout");function zn(n){return o(["select-pane","-t",n]).ok}r(zn,"selectPane");function Un(n){return o(["switch-client","-t",n]).ok}r(Un,"switchClient");function qn(n,t){return o(["send-keys","-t",n,t,"Enter"]).ok}r(qn,"sendKeysEnter");export{W as VIEWER_STAMP_OPTION,dn as breakPane,pn as closePane,an as closeWindow,T as currentTmux,$n as displayClientMessage,vn as displayMessage,ln as displayPopup,fn as displayPopupAsync,rn as ensureSession,un as focusWindow,In as getPaneOption,M as inTmux,_n as listLivePanes,Pn as listLivePanesAsync,U as listWindowIds,Cn as liveViewerPanes,on as openNodeWindow,An as paneCurrentCommand,yn as paneCurrentPath,gn as paneExists,xn as paneLocation,nn as paneOfNode,mn as paneOfWindow,Rn as panePid,hn as paneRunning,bn as paneSize,Tn as paneStartCommand,Wn as piCommand,cn as renameWindow,Mn as respawnPaneInBackground,Vn as respawnPaneInPlace,Nn as respawnPaneSync,z as runShellInBackground,o as runTmux,jn as selectLayout,zn as selectPane,Ln as selectWindow,En as sendAttachControl,qn as sendKeysEnter,F as sessionExists,Sn as setPaneOption,l as shellQuote,sn as splitWindow,On as stampViewerPaneAsync,Fn as swapPanes,Un as switchClient,tn as tmuxAvailable,Z as tmuxClientsReachable,en as tmuxInteractive,Y as tmuxSessionsReachable,Bn as windowAlive,kn as windowIndex,wn as windowOfPane};
@@ -1,8 +1,8 @@
1
- var $=Object.defineProperty;var s=(r,e)=>$(r,"name",{value:e,configurable:!0});import{WebSocketServer as R}from"ws";import{isSafeNodeId as A}from"../../core/canvas/paths.js";import{getNode as S}from"../../core/canvas/index.js";import{BROKER_READ_CAPS as C,CLIENT_READ_CAPS as T,FrameDecoder as x,FrameOverflowError as B}from"../../core/runtime/broker-protocol.js";import{classify as k}from"../../core/fault-classifier.js";import{clearFault as d,recordFault as g}from"../../core/canvas/recovery.js";import{decideAttachRevive as N}from"../../core/runtime/revive.js";import{connectViewSocket as O,probeViewSocket as P,waitForBrokerViewSocket as F}from"../../core/runtime/view-socket.js";import{isBeneathRefusal as U}from"../../core/spaces/open-beneath.js";import{attachViewer as _,detachViewer as D}from"./viewer-presence.js";const L=new R({noServer:!0,maxPayload:C.maxLineBytes}),V=25e3,H=6e4;function M(r,e=V,i=H){let n=!0,o=!1,a=Date.now();const f=s(()=>{n=!0},"acknowledge"),m=s(()=>{o||(o=!0,clearInterval(h),r.off("pong",f))},"stop");r.on("pong",f);const h=setInterval(()=>{if(Date.now()-a>i){n=!0;return}if(!n){m(),r.terminate();return}n=!1;try{r.ping()}catch{m(),r.terminate()}},e);return h.unref(),{stop:m,noteActivity:s(()=>{a=Date.now()},"noteActivity")}}s(M,"startRemoteAttachHeartbeat");function b(r){const e=Buffer.from(r,"utf8");return e.byteLength<=123?r:e.subarray(0,123).toString("utf8")}s(b,"clampReason");function u(r,e,i,n,o){const a=JSON.stringify({error:{code:n,message:o}}),f=`HTTP/1.1 ${e} ${i}\r
1
+ var R=Object.defineProperty;var s=(r,e)=>R(r,"name",{value:e,configurable:!0});import{WebSocketServer as $}from"ws";import{isSafeNodeId as A}from"../../core/canvas/paths.js";import{getNode as S}from"../../core/canvas/index.js";import{BROKER_READ_CAPS as C,CLIENT_READ_CAPS as T,FrameDecoder as x,FrameOverflowError as B}from"../../core/runtime/broker-protocol.js";import{classify as k}from"../../core/fault-classifier.js";import{clearFault as v,markNodeRead as N,recordFault as g}from"../../core/canvas/recovery.js";import{decideAttachRevive as O}from"../../core/runtime/revive.js";import{connectViewSocket as P,probeViewSocket as F,waitForBrokerViewSocket as U}from"../../core/runtime/view-socket.js";import{isBeneathRefusal as D}from"../../core/spaces/open-beneath.js";import{attachViewer as _,detachViewer as V,presentViewer as L}from"./viewer-presence.js";const H=new $({noServer:!0,maxPayload:C.maxLineBytes}),M=25e3,J=6e4;function W(r,e=M,i=J){let o=!0,n=!1,a=Date.now();const f=s(()=>{o=!0},"acknowledge"),m=s(()=>{n||(n=!0,clearInterval(h),r.off("pong",f))},"stop");r.on("pong",f);const h=setInterval(()=>{if(Date.now()-a>i){o=!0;return}if(!o){m(),r.terminate();return}o=!1;try{r.ping()}catch{m(),r.terminate()}},e);return h.unref(),{stop:m,noteActivity:s(()=>{a=Date.now()},"noteActivity")}}s(W,"startRemoteAttachHeartbeat");function b(r){const e=Buffer.from(r,"utf8");return e.byteLength<=123?r:e.subarray(0,123).toString("utf8")}s(b,"clampReason");function u(r,e,i,o,n){const a=JSON.stringify({error:{code:o,message:n}}),f=`HTTP/1.1 ${e} ${i}\r
2
2
  content-type: application/json; charset=utf-8\r
3
3
  content-length: ${Buffer.byteLength(a)}\r
4
4
  connection: close\r
5
5
  \r
6
- `+a;try{r.end(f)}catch{try{r.destroy()}catch{}}}s(u,"refuseUpgrade");function J(r){if(new URL(r.url??"/","http://127.0.0.1").searchParams.get("revive")==="0")return!0;const i=r.headers["x-crtr-attach-revive"];return(Array.isArray(i)?i[0]:i)==="0"}s(J,"reviveOptedOut");function W(r){return new URL(r.url??"/","http://127.0.0.1").searchParams.get("wakeParked")==="1"}s(W,"wakeParkedRequested");function E(r){return r===1e3||r===1001||r===1005}s(E,"isCleanViewerClose");function j(r,e){if(!A(e)){try{r.close(1008,b(`invalid node id: ${e}`))}catch{}return}if(S(e)===null){try{r.close(1011,b(`no node ${e}`))}catch{}return}d(e,{link:"viewer\u2194crtrd"});const i=O(e),n=new x(T);let o=!1,a=!1,f,m=!1,h=!1,v=!1;i.on("connect",()=>{m=!0,!o&&v&&_(e,r),d(e,{link:"relay\u2194broker"})});const p=s((t,l)=>{if(!o){if(o=!0,D(e,r),f?.stop(),!i.destroyed)try{i.destroy()}catch{}try{r.close(t,b(l))}catch{}}},"teardown");f=M(r),i.on("data",t=>{f?.noteActivity();let l;try{l=n.push(t)}catch(c){const y=c instanceof B?`broker sent an oversized frame (${c.message})`:`failed to decode a broker frame for ${e}`;p(1009,y);return}for(const c of l)r.readyState===r.OPEN&&r.send(JSON.stringify(c))}),i.on("error",t=>{const l=k("relay\u2194broker",t);g(e,{link:"relay\u2194broker",op:"bridge socket",kind:l.kind,retry:{disposition:"manual"},message:t.message});const c=t.code==="ECONNREFUSED"||t.code==="ENOENT"?`no running broker for ${e}`:`view socket error for ${e}: ${t.message}`;p(1011,c)}),i.on("close",t=>{o||t||(d(e,{link:"relay\u2194broker"}),p(1e3,"broker socket closed"))}),r.on("close",(t,l)=>{const c=l.toString("utf8");if(o){E(t)&&d(e,{link:"viewer\u2194crtrd"});return}if(E(t))d(e,{link:"viewer\u2194crtrd"});else if(!a){a=!0;const y=k("viewer\u2194crtrd",{code:t,reason:c});g(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:y.kind,retry:{disposition:"auto"},message:c||`remote attach closed (${t})`})}p(t,c||`remote attach closed (${t})`)}),r.on("error",()=>{if(!a){a=!0;const t=k("viewer\u2194crtrd",{code:1006,reason:"remote attach websocket error"});g(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:t.kind,retry:{disposition:"auto"},message:"remote attach websocket error"})}p(1011,"remote attach websocket error")}),r.on("message",t=>{if(o||i.destroyed)return;f?.noteActivity();const c=(Array.isArray(t)?Buffer.concat(t):Buffer.isBuffer(t)?t:Buffer.from(new Uint8Array(t))).toString("utf8");if(!h)try{const w=JSON.parse(c);w?.type==="hello"&&(h=!0,v=w.attends!==!1,v&&m&&_(e,r))}catch{}const y=c.endsWith(`
6
+ `+a;try{r.end(f)}catch{try{r.destroy()}catch{}}}s(u,"refuseUpgrade");function j(r){if(new URL(r.url??"/","http://127.0.0.1").searchParams.get("revive")==="0")return!0;const i=r.headers["x-crtr-attach-revive"];return(Array.isArray(i)?i[0]:i)==="0"}s(j,"reviveOptedOut");function q(r){return new URL(r.url??"/","http://127.0.0.1").searchParams.get("wakeParked")==="1"}s(q,"wakeParkedRequested");function E(r){return r===1e3||r===1001||r===1005}s(E,"isCleanViewerClose");function z(r,e){if(!A(e)){try{r.close(1008,b(`invalid node id: ${e}`))}catch{}return}if(S(e)===null){try{r.close(1011,b(`no node ${e}`))}catch{}return}v(e,{link:"viewer\u2194crtrd"});const i=P(e),o=new x(T);let n=!1,a=!1,f,m=!1,h=!1,d=!1;i.on("connect",()=>{m=!0,!n&&d&&_(e,r),v(e,{link:"relay\u2194broker"})});const p=s((t,l)=>{if(!n){if(n=!0,V(e,r),f?.stop(),!i.destroyed)try{i.destroy()}catch{}try{r.close(t,b(l))}catch{}}},"teardown");f=W(r),i.on("data",t=>{f?.noteActivity();let l;try{l=o.push(t)}catch(c){const y=c instanceof B?`broker sent an oversized frame (${c.message})`:`failed to decode a broker frame for ${e}`;p(1009,y);return}for(const c of l)if(r.readyState===r.OPEN&&(r.send(JSON.stringify(c)),d&&c?.type==="welcome"&&L(e,r)))try{N(e)}catch{}}),i.on("error",t=>{const l=k("relay\u2194broker",t);g(e,{link:"relay\u2194broker",op:"bridge socket",kind:l.kind,retry:{disposition:"manual"},message:t.message});const c=t.code==="ECONNREFUSED"||t.code==="ENOENT"?`no running broker for ${e}`:`view socket error for ${e}: ${t.message}`;p(1011,c)}),i.on("close",t=>{n||t||(v(e,{link:"relay\u2194broker"}),p(1e3,"broker socket closed"))}),r.on("close",(t,l)=>{const c=l.toString("utf8");if(n){E(t)&&v(e,{link:"viewer\u2194crtrd"});return}if(E(t))v(e,{link:"viewer\u2194crtrd"});else if(!a){a=!0;const y=k("viewer\u2194crtrd",{code:t,reason:c});g(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:y.kind,retry:{disposition:"auto"},message:c||`remote attach closed (${t})`})}p(t,c||`remote attach closed (${t})`)}),r.on("error",()=>{if(!a){a=!0;const t=k("viewer\u2194crtrd",{code:1006,reason:"remote attach websocket error"});g(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:t.kind,retry:{disposition:"auto"},message:"remote attach websocket error"})}p(1011,"remote attach websocket error")}),r.on("message",t=>{if(n||i.destroyed)return;f?.noteActivity();const c=(Array.isArray(t)?Buffer.concat(t):Buffer.isBuffer(t)?t:Buffer.from(new Uint8Array(t))).toString("utf8");if(!h)try{const w=JSON.parse(c);w?.type==="hello"&&(h=!0,d=w.attends!==!1,d&&m&&_(e,r))}catch{}const y=c.endsWith(`
7
7
  `)?c:c+`
8
- `;try{i.write(y)}catch{}})}s(j,"bridgeConnection");async function te(r,e,i,n){e.on("error",()=>{});try{if(!A(n)){u(e,400,"Bad Request","invalid_node_id",`invalid node id: ${n}`);return}if(S(n)===null){u(e,404,"Not Found","node_not_found",`no node ${n}`);return}if(J(r)){if(!await P(n,250)){u(e,409,"Conflict","node_dormant",`node ${n} is dormant and revive was opted out (?revive=0)`);return}}else{let o;try{const a=await N(n,{resume:!0,capacity:"refuse"},W(r));if(a.parked){u(e,409,"Conflict","node_dormant",`node ${n} is parked; attach with ?wakeParked=1 to wake it`);return}o=a.result.launch?.exited}catch(a){const f=a instanceof Error?a.message:String(a);u(e,409,"Conflict","attach_revive_failed",`attach revive failed: ${f}`);return}if(!await F(n,o)){u(e,409,"Conflict","broker_not_ready",`broker view socket for ${n} did not become listenable after revive`);return}}if(e.destroyed)return;L.handleUpgrade(r,e,i,o=>j(o,n))}catch(o){const a=o instanceof Error?o.message:String(o);if(U(o)){u(e,400,"Bad Request","invalid_request",a);return}u(e,500,"Internal Server Error","internal",`attach upgrade failed: ${a}`)}}s(te,"handleAttachUpgrade");export{V as REMOTE_ATTACH_HEARTBEAT_MS,H as REMOTE_ATTACH_QUIET_MS,te as handleAttachUpgrade,E as isCleanViewerClose,u as refuseUpgrade,M as startRemoteAttachHeartbeat};
8
+ `;try{i.write(y)}catch{}})}s(z,"bridgeConnection");async function ne(r,e,i,o){e.on("error",()=>{});try{if(!A(o)){u(e,400,"Bad Request","invalid_node_id",`invalid node id: ${o}`);return}if(S(o)===null){u(e,404,"Not Found","node_not_found",`no node ${o}`);return}if(j(r)){if(!await F(o,250)){u(e,409,"Conflict","node_dormant",`node ${o} is dormant and revive was opted out (?revive=0)`);return}}else{let n;try{const a=await O(o,{resume:!0,capacity:"refuse"},q(r));if(a.parked){u(e,409,"Conflict","node_dormant",`node ${o} is parked; attach with ?wakeParked=1 to wake it`);return}n=a.result.launch?.exited}catch(a){const f=a instanceof Error?a.message:String(a);u(e,409,"Conflict","attach_revive_failed",`attach revive failed: ${f}`);return}if(!await U(o,n)){u(e,409,"Conflict","broker_not_ready",`broker view socket for ${o} did not become listenable after revive`);return}}if(e.destroyed)return;H.handleUpgrade(r,e,i,n=>z(n,o))}catch(n){const a=n instanceof Error?n.message:String(n);if(D(n)){u(e,400,"Bad Request","invalid_request",a);return}u(e,500,"Internal Server Error","internal",`attach upgrade failed: ${a}`)}}s(ne,"handleAttachUpgrade");export{M as REMOTE_ATTACH_HEARTBEAT_MS,J as REMOTE_ATTACH_QUIET_MS,ne as handleAttachUpgrade,E as isCleanViewerClose,u as refuseUpgrade,W as startRemoteAttachHeartbeat};
@@ -1 +1 @@
1
- var w=Object.defineProperty;var i=(r,e)=>w(r,"name",{value:e,configurable:!0});import{ApiError as E}from"../../../api/errors.js";import{getNode as I}from"../../../core/canvas/canvas.js";import{liveExecutionForNode as S}from"../../../core/canvas/executions.js";import{admitEpisode as T,clearEpisode as q,clearFault as h,closeTurn as R,invalidateEpisode as F,openTurn as v,parseProviderError as A,readEpisode as N,readFault as l,recordFault as b,recordPendingEpisode as P,recordTransportPoisonVerdict as O,recordTurnProgress as j,reopenEpisode as L}from"../../../core/canvas/recovery.js";import{notFound as D,usage as n}from"../../../core/errors.js";import{ownerOnly as g}from"../router.js";const f=new Set(["pi\u2192provider","viewer\u2194broker","relay\u2194broker","viewer\u2194crtrd","daemon\u2192node","crtr\u2192pi"]),B=new Set(["rate-limit","overloaded","connection","auth","protocol","context-overflow","other","wedged","model-not-found"]),$=new Set(["auto","manual","fatal"]),C=new Set(["sdk","daemon","client"]);function d(r,e){if(typeof r!="object"||r===null||Array.isArray(r))throw n(`${e} requires a JSON object`);return r}i(d,"objectBody");function a(r){if(I(r)===null)throw D(`unknown node: ${r}`,{received:r})}i(a,"requireNode");function u(r,e){if(typeof e!="string"||e==="")throw n("expected_execution_id must be a non-empty string");const t=S(r);if(t===null||t.executionId!==e)throw new E(409,"stale_execution","broker execution is no longer current")}i(u,"requireCurrentExecution");function x(r){if(typeof r!="object"||r===null||Array.isArray(r))throw n("retry must be an object");const e=r;if(!$.has(e.disposition))throw n("retry.disposition must be auto, manual, or fatal");if(e.by!==void 0&&!C.has(e.by))throw n("retry.by must be sdk, daemon, or client");for(const t of["attempt","max"])if(e[t]!==void 0&&(!Number.isSafeInteger(e[t])||e[t]<0))throw n(`retry.${t} must be a non-negative safe integer`);if(e.nextAt!==void 0&&typeof e.nextAt!="string")throw n("retry.nextAt must be a string");return e}i(x,"parseRetry");function _(r){const e=d(r,"fault input");if(!f.has(e.link))throw n("fault link is invalid");if(typeof e.op!="string"||e.op==="")throw n("fault op must be a non-empty string");if(!B.has(e.kind))throw n("fault kind is invalid");if(typeof e.message!="string")throw n("fault message must be a string");if(e.operation_id!==void 0&&(typeof e.operation_id!="string"||e.operation_id===""))throw n("operation_id must be a non-empty string");if(e.since!==void 0&&typeof e.since!="string")throw n("since must be a string");if(e.anchor_entry_id!==void 0&&(typeof e.anchor_entry_id!="string"||e.anchor_entry_id===""))throw n("anchor_entry_id must be a non-empty string");const t=e.provider_error===void 0?void 0:A(e.provider_error);if(e.provider_error!==void 0&&t===void 0)throw n("provider_error is invalid");return{...e,...t===void 0?{}:{provider_error:t},retry:x(e.retry)}}i(_,"parseFaultInput");function y(r){return{link:r.link,op:r.op,kind:r.kind,retry:r.retry,message:r.message,...r.operation_id===void 0?{}:{operation_id:r.operation_id},...r.since===void 0?{}:{since:r.since},...r.anchor_entry_id===void 0?{}:{anchorEntryId:r.anchor_entry_id},...r.provider_error===void 0?{}:{providerError:r.provider_error}}}i(y,"faultInput");function c(r){return{fault_id:r.faultId,link:r.link,op:r.op,kind:r.kind,retry:r.retry,message:r.message,since:r.since,operation_id:r.operation_id,...r.anchorEntryId===void 0?{}:{anchor_entry_id:r.anchorEntryId},...r.providerError===void 0?{}:{provider_error:r.providerError}}}i(c,"faultDto");function k(r){const e=l(r),t=N(r);return{fault:e===null?null:c(e),episode:t===null?null:{state:t.state,session_id:t.sessionId,fault:c(t.fault)}}}i(k,"recoveryState");function K(r){const e=d(r,"broker turn"),t=e.event;if(t!=="started"&&t!=="progress"&&t!=="closed"&&t!=="interrupted"&&t!=="exit-diagnostic")throw n("event is invalid");if(typeof e.expected_execution_id!="string"||e.expected_execution_id==="")throw n("expected_execution_id must be a non-empty string");if(t==="closed"){if(e.end_reason!=="settled"&&e.end_reason!=="teardown-aborted")throw n("closed turn requires end_reason settled or teardown-aborted")}else if(e.end_reason!==void 0)throw n("end_reason is valid only for closed turns");if(t==="exit-diagnostic"){const o=d(e.exit_diagnostic,"exit_diagnostic"),s=d(o.transport_poison,"exit_diagnostic.transport_poison");if(o.schema!=="crtr.execution-exit-diagnostic/v1"||!Number.isSafeInteger(s.consecutive_passes)||s.consecutive_passes<1||!Array.isArray(s.routes)||s.routes.length===0||!s.routes.every(p=>typeof p=="string"&&p!==""))throw n("exit_diagnostic is invalid")}else if(e.exit_diagnostic!==void 0)throw n("exit_diagnostic is valid only for exit-diagnostic turns");return e}i(K,"parseTurn");function U(r){const e=r.params.id;a(e);const t=K(r.body);u(e,t.expected_execution_id);let o;switch(t.event){case"started":o=v(t.expected_execution_id,{reason:"agent-start"});break;case"interrupted":o=v(t.expected_execution_id,{reason:"prompt-unsettled"});break;case"progress":o=j(t.expected_execution_id);break;case"closed":o=R(t.expected_execution_id,{endReason:t.end_reason});break;case"exit-diagnostic":{const m=t.exit_diagnostic.transport_poison;o=O(t.expected_execution_id,{consecutivePasses:m.consecutive_passes,routes:m.routes});break}}const s=l(e)?.link;return{status:200,body:{applied:o,fault_link:s==="daemon\u2192node"||s==="pi\u2192provider"?s:null}}}i(U,"handleTurn");function Y(r){const e=d(r,"provider retry");if(typeof e.expected_execution_id!="string"||e.expected_execution_id==="")throw n("expected_execution_id must be a non-empty string");if(!["record-pending","admit","reopen","invalidate","clear"].includes(e.op))throw n("provider retry op is invalid");if(typeof e.session_id!="string"||e.session_id==="")throw n("session_id must be a non-empty string");if(e.op==="record-pending"&&(e.input=_(e.input)),e.op==="admit"){if(typeof e.fault_id!="string"||e.fault_id==="")throw n("admit requires fault_id");e.retry=x(e.retry)}return e}i(Y,"parseProviderRetry");function G(r){const e=r.params.id;a(e);const t=Y(r.body);u(e,t.expected_execution_id);let o;switch(t.op){case"record-pending":o=P(e,{sessionId:t.session_id,input:y(t.input)})!==null;break;case"admit":o=T(e,{sessionId:t.session_id,faultId:t.fault_id,retry:t.retry});break;case"reopen":o=L(e,{sessionId:t.session_id});break;case"invalidate":o=F(e,{sessionId:t.session_id});break;case"clear":o=q(e,{sessionId:t.session_id});break}return{status:200,body:{applied:o,episode:k(e).episode}}}i(G,"handleProviderRetry");function J(r){const e=d(r,"broker fault");if(typeof e.expected_execution_id!="string"||e.expected_execution_id==="")throw n("expected_execution_id must be a non-empty string");if(e.op!=="record"&&e.op!=="clear")throw n("fault op must be record or clear");if(e.op==="record"&&(e.input=_(e.input)),e.op==="clear"&&e.link!==void 0&&!f.has(e.link))throw n("fault link is invalid");if(e.preserve_episode!==void 0&&typeof e.preserve_episode!="boolean")throw n("preserve_episode must be boolean");return e}i(J,"parseBrokerFault");function V(r){const e=r.params.id;a(e);const t=J(r.body);u(e,t.expected_execution_id);const o=t.op==="record"?b(e,y(t.input)):(h(e,{...t.link===void 0?{}:{link:t.link},...t.preserve_episode===void 0?{}:{preserveEpisode:t.preserve_episode}}),l(e));return{status:200,body:{fault:o===null?null:c(o)}}}i(V,"handleBrokerFault");function W(r){const e=r.params.id;return a(e),u(e,r.query.get("expected_execution_id")),{status:200,body:k(e)}}i(W,"handleRecovery");function z(r){const e=r.params.id;a(e);const t=b(e,y(_(r.body)));return{status:200,body:{fault:t===null?null:c(t)}}}i(z,"handleNodeFaultRecord");function H(r){const e=r.params.id;a(e);const t=r.query.get("link");if(t!==null&&!f.has(t))throw n("fault link is invalid");const o=r.query.get("preserve_episode");if(o!==null&&o!=="true"&&o!=="false")throw n("preserve_episode must be true or false");return{status:200,body:{cleared:h(e,{...t===null?{}:{link:t},...o===null?{}:{preserveEpisode:o==="true"}})}}}i(H,"handleNodeFaultClear");const ne=g([{method:"POST",pattern:"/v1/broker/:id/turn",handler:U},{method:"POST",pattern:"/v1/broker/:id/provider-retry",handler:G},{method:"POST",pattern:"/v1/broker/:id/fault",handler:V},{method:"GET",pattern:"/v1/broker/:id/recovery",handler:W}]),oe=g([{method:"POST",pattern:"/v1/nodes/:id/fault",handler:z},{method:"DELETE",pattern:"/v1/nodes/:id/fault",handler:H}]);export{ne as brokerRecoveryRoutes,oe as nodeFaultRoutes,u as requireCurrentExecution};
1
+ var w=Object.defineProperty;var i=(r,e)=>w(r,"name",{value:e,configurable:!0});import{ApiError as E}from"../../../api/errors.js";import{getNode as I}from"../../../core/canvas/canvas.js";import{liveExecutionForNode as S}from"../../../core/canvas/executions.js";import{admitEpisode as T,clearEpisode as q,clearFault as h,closeTurn as P,invalidateEpisode as R,openTurn as v,parseProviderError as F,readEpisode as A,readFault as l,recordFault as b,recordPendingEpisode as N,recordTransportPoisonVerdict as O,recordTurnProgress as j,reopenEpisode as L}from"../../../core/canvas/recovery.js";import{notFound as D,usage as n}from"../../../core/errors.js";import{hasPresentedViewer as B}from"../viewer-presence.js";import{ownerOnly as g}from"../router.js";const f=new Set(["pi\u2192provider","viewer\u2194broker","relay\u2194broker","viewer\u2194crtrd","daemon\u2192node","crtr\u2192pi"]),$=new Set(["rate-limit","overloaded","connection","auth","protocol","context-overflow","other","wedged","model-not-found"]),C=new Set(["auto","manual","fatal"]),K=new Set(["sdk","daemon","client"]);function d(r,e){if(typeof r!="object"||r===null||Array.isArray(r))throw n(`${e} requires a JSON object`);return r}i(d,"objectBody");function a(r){if(I(r)===null)throw D(`unknown node: ${r}`,{received:r})}i(a,"requireNode");function u(r,e){if(typeof e!="string"||e==="")throw n("expected_execution_id must be a non-empty string");const t=S(r);if(t===null||t.executionId!==e)throw new E(409,"stale_execution","broker execution is no longer current")}i(u,"requireCurrentExecution");function x(r){if(typeof r!="object"||r===null||Array.isArray(r))throw n("retry must be an object");const e=r;if(!C.has(e.disposition))throw n("retry.disposition must be auto, manual, or fatal");if(e.by!==void 0&&!K.has(e.by))throw n("retry.by must be sdk, daemon, or client");for(const t of["attempt","max"])if(e[t]!==void 0&&(!Number.isSafeInteger(e[t])||e[t]<0))throw n(`retry.${t} must be a non-negative safe integer`);if(e.nextAt!==void 0&&typeof e.nextAt!="string")throw n("retry.nextAt must be a string");return e}i(x,"parseRetry");function _(r){const e=d(r,"fault input");if(!f.has(e.link))throw n("fault link is invalid");if(typeof e.op!="string"||e.op==="")throw n("fault op must be a non-empty string");if(!$.has(e.kind))throw n("fault kind is invalid");if(typeof e.message!="string")throw n("fault message must be a string");if(e.operation_id!==void 0&&(typeof e.operation_id!="string"||e.operation_id===""))throw n("operation_id must be a non-empty string");if(e.since!==void 0&&typeof e.since!="string")throw n("since must be a string");if(e.anchor_entry_id!==void 0&&(typeof e.anchor_entry_id!="string"||e.anchor_entry_id===""))throw n("anchor_entry_id must be a non-empty string");const t=e.provider_error===void 0?void 0:F(e.provider_error);if(e.provider_error!==void 0&&t===void 0)throw n("provider_error is invalid");return{...e,...t===void 0?{}:{provider_error:t},retry:x(e.retry)}}i(_,"parseFaultInput");function y(r){return{link:r.link,op:r.op,kind:r.kind,retry:r.retry,message:r.message,...r.operation_id===void 0?{}:{operation_id:r.operation_id},...r.since===void 0?{}:{since:r.since},...r.anchor_entry_id===void 0?{}:{anchorEntryId:r.anchor_entry_id},...r.provider_error===void 0?{}:{providerError:r.provider_error}}}i(y,"faultInput");function c(r){return{fault_id:r.faultId,link:r.link,op:r.op,kind:r.kind,retry:r.retry,message:r.message,since:r.since,operation_id:r.operation_id,...r.anchorEntryId===void 0?{}:{anchor_entry_id:r.anchorEntryId},...r.providerError===void 0?{}:{provider_error:r.providerError}}}i(c,"faultDto");function k(r){const e=l(r),t=A(r);return{fault:e===null?null:c(e),episode:t===null?null:{state:t.state,session_id:t.sessionId,fault:c(t.fault)}}}i(k,"recoveryState");function U(r){const e=d(r,"broker turn"),t=e.event;if(t!=="started"&&t!=="progress"&&t!=="closed"&&t!=="interrupted"&&t!=="exit-diagnostic")throw n("event is invalid");if(typeof e.expected_execution_id!="string"||e.expected_execution_id==="")throw n("expected_execution_id must be a non-empty string");if(t==="closed"){if(e.end_reason!=="settled"&&e.end_reason!=="teardown-aborted")throw n("closed turn requires end_reason settled or teardown-aborted")}else if(e.end_reason!==void 0)throw n("end_reason is valid only for closed turns");if(t==="exit-diagnostic"){const o=d(e.exit_diagnostic,"exit_diagnostic"),s=d(o.transport_poison,"exit_diagnostic.transport_poison");if(o.schema!=="crtr.execution-exit-diagnostic/v1"||!Number.isSafeInteger(s.consecutive_passes)||s.consecutive_passes<1||!Array.isArray(s.routes)||s.routes.length===0||!s.routes.every(p=>typeof p=="string"&&p!==""))throw n("exit_diagnostic is invalid")}else if(e.exit_diagnostic!==void 0)throw n("exit_diagnostic is valid only for exit-diagnostic turns");return e}i(U,"parseTurn");function V(r){const e=r.params.id;a(e);const t=U(r.body);u(e,t.expected_execution_id);let o;switch(t.event){case"started":o=v(t.expected_execution_id,{reason:"agent-start"});break;case"interrupted":o=v(t.expected_execution_id,{reason:"prompt-unsettled"});break;case"progress":o=j(t.expected_execution_id);break;case"closed":o=P(t.expected_execution_id,{endReason:t.end_reason,viewerPresent:B(e)});break;case"exit-diagnostic":{const m=t.exit_diagnostic.transport_poison;o=O(t.expected_execution_id,{consecutivePasses:m.consecutive_passes,routes:m.routes});break}}const s=l(e)?.link;return{status:200,body:{applied:o,fault_link:s==="daemon\u2192node"||s==="pi\u2192provider"?s:null}}}i(V,"handleTurn");function Y(r){const e=d(r,"provider retry");if(typeof e.expected_execution_id!="string"||e.expected_execution_id==="")throw n("expected_execution_id must be a non-empty string");if(!["record-pending","admit","reopen","invalidate","clear"].includes(e.op))throw n("provider retry op is invalid");if(typeof e.session_id!="string"||e.session_id==="")throw n("session_id must be a non-empty string");if(e.op==="record-pending"&&(e.input=_(e.input)),e.op==="admit"){if(typeof e.fault_id!="string"||e.fault_id==="")throw n("admit requires fault_id");e.retry=x(e.retry)}return e}i(Y,"parseProviderRetry");function G(r){const e=r.params.id;a(e);const t=Y(r.body);u(e,t.expected_execution_id);let o;switch(t.op){case"record-pending":o=N(e,{sessionId:t.session_id,input:y(t.input)})!==null;break;case"admit":o=T(e,{sessionId:t.session_id,faultId:t.fault_id,retry:t.retry});break;case"reopen":o=L(e,{sessionId:t.session_id});break;case"invalidate":o=R(e,{sessionId:t.session_id});break;case"clear":o=q(e,{sessionId:t.session_id});break}return{status:200,body:{applied:o,episode:k(e).episode}}}i(G,"handleProviderRetry");function J(r){const e=d(r,"broker fault");if(typeof e.expected_execution_id!="string"||e.expected_execution_id==="")throw n("expected_execution_id must be a non-empty string");if(e.op!=="record"&&e.op!=="clear")throw n("fault op must be record or clear");if(e.op==="record"&&(e.input=_(e.input)),e.op==="clear"&&e.link!==void 0&&!f.has(e.link))throw n("fault link is invalid");if(e.preserve_episode!==void 0&&typeof e.preserve_episode!="boolean")throw n("preserve_episode must be boolean");return e}i(J,"parseBrokerFault");function W(r){const e=r.params.id;a(e);const t=J(r.body);u(e,t.expected_execution_id);const o=t.op==="record"?b(e,y(t.input)):(h(e,{...t.link===void 0?{}:{link:t.link},...t.preserve_episode===void 0?{}:{preserveEpisode:t.preserve_episode}}),l(e));return{status:200,body:{fault:o===null?null:c(o)}}}i(W,"handleBrokerFault");function z(r){const e=r.params.id;return a(e),u(e,r.query.get("expected_execution_id")),{status:200,body:k(e)}}i(z,"handleRecovery");function H(r){const e=r.params.id;a(e);const t=b(e,y(_(r.body)));return{status:200,body:{fault:t===null?null:c(t)}}}i(H,"handleNodeFaultRecord");function M(r){const e=r.params.id;a(e);const t=r.query.get("link");if(t!==null&&!f.has(t))throw n("fault link is invalid");const o=r.query.get("preserve_episode");if(o!==null&&o!=="true"&&o!=="false")throw n("preserve_episode must be true or false");return{status:200,body:{cleared:h(e,{...t===null?{}:{link:t},...o===null?{}:{preserveEpisode:o==="true"}})}}}i(M,"handleNodeFaultClear");const ie=g([{method:"POST",pattern:"/v1/broker/:id/turn",handler:V},{method:"POST",pattern:"/v1/broker/:id/provider-retry",handler:G},{method:"POST",pattern:"/v1/broker/:id/fault",handler:W},{method:"GET",pattern:"/v1/broker/:id/recovery",handler:z}]),se=g([{method:"POST",pattern:"/v1/nodes/:id/fault",handler:H},{method:"DELETE",pattern:"/v1/nodes/:id/fault",handler:M}]);export{ie as brokerRecoveryRoutes,se as nodeFaultRoutes,u as requireCurrentExecution};
@@ -1,6 +1,6 @@
1
- var x=Object.defineProperty;var l=(t,e)=>x(t,"name",{value:e,configurable:!0});import{gzipSync as z}from"node:zlib";import{browseNodes as J,graphNodes as q,listNodes as B,pruneNodes as K,pruneToLimit as Q,rosterEdges as k,rosterNodes as U}from"../../../core/canvas/canvas.js";import{activeBackgroundBashJobs as Y}from"../../../core/bash-jobs.js";import{contextDir as V}from"../../../core/canvas/node-agent-paths.js";import{nowIso as C}from"../../../core/fs-utils.js";import{isPidPresent as W}from"../../../core/canvas/pid.js";import{dashboardRowsAllFromSource as X,enrichRowsFromSource as Z}from"../../../core/canvas/render-source.js";import{localCanvasSource as j}from"../../../core/canvas/source.js";import{ticketCountsForNodes as O,ticketsAcrossCanvas as ee}from"../../../core/canvas/attention.js";import{buildCorpus as te,groupCorpus as ne,resolveRef as oe}from"../../../core/canvas/history.js";import{listFocuses as L,reapEmptyNodes as se}from"../../../core/runtime/placement.js";import{boundFleet as re}from"../../../core/runtime/fleet.js";import{retentionHold as ie}from"../../reconcilers/live-obligation.js";import{notFound as ae,usage as p}from"../../../core/errors.js";import{toGraphNodeDTO as de}from"../map.js";import{canReadNode as R,canReadRun as le}from"../router.js";import{ApiError as ce}from"../../../api/errors.js";function g(t){return t.principal.class==="person"?null:new Set(B().filter(e=>R(t.principal,e.grantee??"app:terminal",e.node_id)).map(e=>e.node_id))}l(g,"visibleNodes");function T(t){return t.principal.class==="person"?null:new Set(B().filter(e=>e.run_id?le(t.principal,e.grantee??"app:terminal",e.node_id):R(t.principal,e.grantee??"app:terminal",e.node_id)).map(e=>e.node_id))}l(T,"visibleHistoryNodes");function ue(t){const e=g(t);return{status:200,body:{items:ee().filter(o=>e===null||e.has(o.node_id)).map(o=>({node_id:o.node_id,name:o.name,cwd:o.cwd,count:o.count,subjects:o.subjects}))}}}l(ue,"handleAttention");function fe(t){const e=t.body;if(e===null||typeof e!="object"||!Array.isArray(e.node_ids))throw p("attention count request requires node_ids: string[]");const n=e.node_ids;if(!n.every(s=>typeof s=="string"))throw p("attention count request node_ids must contain only strings");const o=g(t);return{status:200,body:{counts:O(n.filter(s=>o===null||o.has(s)))}}}l(fe,"handleAttentionCounts");function E(t){return Buffer.from(String(t),"utf8").toString("base64")}l(E,"encodeCursor");function G(t){const e=Number(Buffer.from(t,"base64").toString("utf8"));if(!Number.isInteger(e)||e<0)throw p(`invalid cursor: ${t}`);return e}l(G,"decodeCursor");function pe(t,e,n){const i=t.loadBody().split(`
2
- `);if(e.length>0){const d=i.findIndex(u=>{const c=u.toLowerCase();return e.some(m=>c.includes(m))});if(d!==-1)return i.slice(d,d+n).map(u=>u.trim()).filter(u=>u!=="").join(" \u23CE ")}const s=i.map(d=>d.trim()).filter(d=>d!=="").slice(0,n).join(" \u23CE ");return s!==""?s:t.title}l(pe,"snippet");function me(t,e,n){const o=[...t];return e==="relevance"&&n!==void 0?o.sort((i,s)=>(n.get(s.ref)??0)-(n.get(i.ref)??0)||s.tsMs-i.tsMs||i.ref.localeCompare(s.ref)):e==="oldest"?o.sort((i,s)=>i.tsMs-s.tsMs||i.ref.localeCompare(s.ref)):o.sort((i,s)=>s.tsMs-i.tsMs||i.ref.localeCompare(s.ref)),o}l(me,"sortArtifacts");function he(t){if(t==null)return{};if(typeof t!="object")throw p("history search body must be a JSON object");return t}l(he,"parseSearchBody");function ye(t){if(t===null||typeof t!="object")throw p("history grep body must be a JSON object");const e=t;if(typeof e.pattern!="string"||e.pattern==="")throw p("history grep body requires a non-empty pattern");return e}l(ye,"parseGrepBody");function $(t,e){const n={cwd:t.cwd,allCwds:t.all_cwds===!0,under:t.under,nodes:t.nodes},o={types:t.types,kinds:t.kinds,statuses:t.statuses,sinceMs:t.since_ms,untilMs:t.until_ms,origins:t.origins,roles:t.roles,from:t.from},i=T(e);return te(n,o).filter(s=>i===null||i.has(s.nodeId))}l($,"buildCorpusFromQuery");function _e(t){if(t==null)return{};if(typeof t!="object")throw p("history stats body must be a JSON object");return t}l(_e,"parseStatsBody");const I=["origin","node","kind","day","type","role"];function be(t){const e=_e(t.body),n=e.group_by??"type";if(!I.includes(n))throw p(`--group-by must be one of: ${I.join(", ")}; received: ${String(e.group_by)}`);const o=$(e,t),i=ne(o,n);return{status:200,body:{group_by:n,groups:i,total:i.reduce((d,u)=>d+u.count,0),scanned:o.length}}}l(be,"handleHistoryStats");function ge(t){const e=ye(t.body),n=Math.min(100,Math.max(1,e.limit??20)),o=$(e,t),i=e.cursor!==void 0?G(e.cursor):0;let s;try{s=new RegExp(e.pattern,"i")}catch{throw p(`invalid regex pattern: ${e.pattern}`)}const d=[];for(const r of o)r.loadBody().split(`
3
- `).forEach((y,b)=>{s.test(y)&&d.push({ref:r.ref,node:`${r.nodeName} (${r.nodeId})`,ts:r.ts,line:b+1,text:y.trim()})});d.sort((r,a)=>r.ref.localeCompare(a.ref)||r.line-a.line);const u=d.slice(i,i+n),c=i+n<d.length?E(i+n):null;return{status:200,body:{hits:u,next_cursor:c,total:d.length}}}l(ge,"handleHistoryGrep");function ve(t){const e=he(t.body),n=(e.query??"").trim(),o=e.weigh_body===!0,i=e.full===!0,s=Math.max(1,e.snippet_lines??1),d=Math.min(100,Math.max(1,e.limit??20)),u=$(e,t),c=e.cursor!==void 0?G(e.cursor):0,m=n.toLowerCase().split(/\s+/).filter(f=>f.length>0);let r=u,a;const y=e.sort??(m.length>0?"relevance":"recency");if(m.length>0&&y==="relevance"){a=new Map;const f=[];for(const h of u){const N=h.title.toLowerCase(),D=h.nodeName.toLowerCase(),H=h.nodeDesc.toLowerCase(),M=o?h.loadBody().toLowerCase():null;let _=0;for(const S of m)N.includes(S)&&(_+=10),D.includes(S)&&(_+=5),H.includes(S)&&(_+=3),M!==null&&M.includes(S)&&(_+=1);_>0&&(a.set(h.ref,_),f.push(h))}r=f}else m.length>0&&(r=u.filter(f=>{const h=`${f.title}
4
- ${f.nodeName}
5
- ${f.nodeDesc}${o?`
6
- `+f.loadBody():""}`.toLowerCase();return m.some(N=>h.includes(N))}));const b=me(r,y,a),P=b.slice(c,c+d),A=c+d<b.length?E(c+d):null;return{status:200,body:{hits:P.map(f=>{const h={ref:f.ref,node:`${f.nodeName} (${f.nodeId})`,kind:f.nodeKind,source:f.source,ts:f.ts};return y==="relevance"&&a!==void 0&&(h.score=a.get(f.ref)??0),i?h.body=f.loadBody():h.snippet=pe(f,m,s),h}),next_cursor:A,total:b.length}}}l(ve,"handleHistorySearch");function Se(t){const e=(t.query.get("ref")??"").trim();if(!e.includes(":"))throw p(`ref must be <node-id>:inbox/<n>, <node-id>:session, or <node-id>:session/<n>; received: ${e}`,{received:e,next:"Get a valid ref from `crtr canvas history search`. Reports and documents are `crtr canvas read <name>`."});const n=oe(e);if(n===null)throw ae(`no inbox entry or message for ref: ${e}`,{ref:e,next:"Re-run `crtr canvas history search` for a current ref. Reports and documents are `crtr canvas read <name>`."});if(T(t)?.has(n.nodeId)===!1)throw new ce(404,"not_found","artifact not found");const o=n.source,s=t.query.get("format")==="raw"&&n.raw!==null?n.raw:n.body;return{status:200,body:{ref:e,node:`${n.nodeName} (${n.nodeId})`,source:o,ts:n.ts,content:s}}}l(Se,"handleHistoryRead");function ke(t){for(const[o]of t.query)if(o!=="live")throw p(`unknown snapshot query parameter: ${o}`);const e=t.query.getAll("live");if(e.length>1)throw p("live must appear at most once");const n=e[0];if(n===void 0||n==="false"||n==="0")return!1;if(n==="true"||n==="1")return!0;throw p("live must be true or false")}l(ke,"snapshotLiveOnly");async function we(t){const e=new Set(L().map(r=>r.node_id)),n=g(t),o=(await X(j,e,ke(t))).filter(r=>n===null||n.has(r.node_id)),i=O(o.map(r=>r.node_id));await Z(j,o,i);const s=o.map(r=>{const a=r.srcRow,y=a?.host_kind??"tmux";return{node_id:r.node_id,name:r.name,title:r.handle??r.name,description:r.description??null,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle??a?.lifecycle,status:r.status,cwd:r.cwd,profile_id:r.profile_id??null,parent:a?.parent??null,created:r.created,host_kind:y,enterable:y==="broker",attention_count:r.asks,...a?.cycles!=null?{cycles:a.cycles}:{},...r.mtimeMs!==void 0?{last_activity:new Date(r.mtimeMs).toISOString()}:{},ctx_tokens:r.ctx_tokens,streaming:r.streaming??!1,jobs_active:a!==void 0&&W(a.pi_pid)?Y(V(r.node_id)).length:0,waiting_for:a?.waiting_for??null,hanging:r.hanging??null,viewed:r.viewed??!1,final_report:a?.final_report??null,finalized_at:a?.finalized_at??null}}),d=new Map;for(const r of k()){const a=d.get(r.from_id);a===void 0?d.set(r.from_id,[r.to_id]):a.push(r.to_id)}const u=new Set(o.map(r=>r.node_id)),c=Object.fromEntries(o.map(r=>[r.node_id,(d.get(r.node_id)??[]).filter(a=>u.has(a))]));return{status:200,body:{generated_at:C(),nodes:s,subscriptions:c}}}l(we,"handleSnapshot");function F(){const t=U().map(n=>{const o=n.host_kind??"tmux";return{node_id:n.node_id,name:n.name,kind:n.kind,mode:n.mode,lifecycle:n.lifecycle,status:n.status,cwd:n.cwd,host_kind:o,enterable:o==="broker",parent:n.parent,created:n.created}}),e=k().map(n=>({from_id:n.from_id,to_id:n.to_id,active:n.active,created:n.created}));return{generated_at:C(),nodes:t,edges:e}}l(F,"buildRoster");let v=null,w=null;function Ne(){return v===null?(v=Promise.resolve().then(()=>F()).finally(()=>{v=null}),v):(w===null&&(w=v.then(()=>F()).finally(()=>{w=null})),w)}l(Ne,"rosterCoalesced");async function Ce(t){const e=await Ne(),n=g(t);return n===null?{status:200,body:e}:{status:200,body:{...e,nodes:e.nodes.filter(o=>n.has(o.node_id)),edges:e.edges.filter(o=>n.has(o.from_id)&&n.has(o.to_id))}}}l(Ce,"handleRoster");function Re(t){const e=J().filter(s=>t.principal.class==="person"||R(t.principal,s.grantee??"app:terminal",s.node_id)).map(s=>({node_id:s.node_id,name:s.name,description:s.description,kind:s.kind,mode:s.mode,lifecycle:s.lifecycle,status:s.status,cwd:s.cwd,profile_id:s.profile_id,grantee:s.grantee??"app:terminal",parent:s.parent,created:s.created,frozen_at:s.frozen_at??null,terminal_reason:s.terminal_reason??null,final_report:s.final_report??null,telemetry_tokens_in:s.telemetry_tokens_in??null,telemetry_updated_at:s.telemetry_updated_at??null,fault:s.fault&&s.fault.link!=="broker\u2194crtrd"?{...s.fault,link:s.fault.link}:null,streaming:s.streaming??!1})),n=new Set(e.map(s=>s.node_id)),o=k().filter(s=>n.has(s.from_id)&&n.has(s.to_id));return{status:200,rawBody:z(JSON.stringify({nodes:e,edges:o}),{level:1}),headers:{"content-type":"application/gzip"}}}l(Re,"handleBrowse");function $e(t){const e=g(t);return{status:200,body:{generated_at:C(),nodes:q().filter(o=>e===null||e.has(o.node_id)).map(de),edges:k().filter(o=>e===null||e.has(o.from_id)&&e.has(o.to_id)).map(o=>({from_id:o.from_id,to_id:o.to_id,active:o.active,created:o.created})),focused_node_ids:L().map(o=>o.node_id).filter(o=>e===null||e.has(o))}}}l($e,"handleGraph");function Me(t){if(t==null)return{};if(typeof t!="object")throw p("prune body must be a JSON object");const e=t,n=l(r=>{const a=e[r];if(a!==void 0){if(typeof a!="number"||!Number.isFinite(a))throw p(`${r} must be a number`);return a}},"num"),o=l(r=>{const a=e[r];if(a!==void 0){if(typeof a!="boolean")throw p(`${r} must be a boolean`);return a}},"bool"),i={},s=n("limit");s!==void 0&&(i.limit=s);const d=n("ttl_days");d!==void 0&&(i.ttl_days=d);const u=o("include_stale");u!==void 0&&(i.include_stale=u);const c=o("empty");c!==void 0&&(i.empty=c);const m=o("dry_run");return m!==void 0&&(i.dry_run=m),i}l(Me,"parsePruneBody");function Be(t){const e=Me(t.body),n=e.ttl_days??14,o=e.dry_run??!1,i=ie(re());let s;return e.limit!==void 0?s=Q({maxNodes:e.limit,held:i,dryRun:o}).pruned.map(c=>({node_id:c.node_id,status:c.status,created:c.created})):e.empty===!0?s=se({dryRun:o}).map(c=>({node_id:c,status:"empty",created:""})):s=K({ttlDays:n,held:i,dryRun:o,includeStale:e.include_stale??!1}).pruned.map(c=>({node_id:c.node_id,status:c.status,created:c.created})),{status:200,body:{pruned:s,dry_run:o,ttl_days:n}}}l(Be,"handlePrune");const Ve=[{method:"GET",pattern:"/v1/canvas/attention",handler:ue},{method:"POST",pattern:"/v1/canvas/attention/counts",handler:fe},{method:"POST",pattern:"/v1/canvas/history/search",handler:ve},{method:"POST",pattern:"/v1/canvas/history/grep",handler:ge},{method:"GET",pattern:"/v1/canvas/history/read",handler:Se},{method:"POST",pattern:"/v1/canvas/history/stats",handler:be},{method:"GET",pattern:"/v1/canvas/snapshot",handler:we},{method:"GET",pattern:"/v1/canvas/roster",handler:Ce},{method:"GET",pattern:"/v1/canvas/browse",handler:Re},{method:"GET",pattern:"/v1/canvas/graph",handler:$e},{method:"POST",pattern:"/v1/canvas/prune",ownerOnly:!0,handler:Be}];export{Ve as canvasRoutes,Re as handleBrowse};
1
+ var Y=Object.defineProperty;var l=(t,e)=>Y(t,"name",{value:e,configurable:!0});import{gzipSync as V}from"node:zlib";import{browseNodes as F,graphNodes as X,listNodes as G,pruneNodes as Z,pruneToLimit as ee,rosterEdges as w,rosterNodes as te}from"../../../core/canvas/canvas.js";import{activeBackgroundBashJobs as ne}from"../../../core/bash-jobs.js";import{contextDir as se}from"../../../core/canvas/node-agent-paths.js";import{nowIso as $}from"../../../core/fs-utils.js";import{isPidPresent as oe}from"../../../core/canvas/pid.js";import{dashboardRowsAllFromSource as re,enrichRowsFromSource as ie}from"../../../core/canvas/render-source.js";import{localCanvasSource as P}from"../../../core/canvas/source.js";import{ticketCountsForNodes as q,ticketsAcrossCanvas as ae}from"../../../core/canvas/attention.js";import{buildCorpus as de,groupCorpus as le,resolveRef as ce}from"../../../core/canvas/history.js";import{listFocuses as D,reapEmptyNodes as ue}from"../../../core/runtime/placement.js";import{boundFleet as fe}from"../../../core/runtime/fleet.js";import{retentionHold as pe}from"../../reconcilers/live-obligation.js";import{notFound as me,usage as _}from"../../../core/errors.js";import{openDb as he}from"../../../core/canvas/db.js";import{prepared as _e}from"../../../core/sqlite-statements.js";import{view as ye}from"../../../core/canvas/tree.js";import{isFinishedWork as ge,isLiveStatus as be}from"../../../core/canvas/node-order.js";import{readNodeRecap as ve}from"../../../core/canvas/node-recap.js";import{readGoal as ke}from"../../../core/runtime/kickoff.js";import{faultSummary as Se}from"../../../core/canvas/status-glyph.js";import{listPendingAsks as we}from"../../../core/human/requests.js";import{toGraphNodeDTO as Ne}from"../map.js";import{actsAsPerson as Re,canReadNode as O,canReadRun as Ce}from"../router.js";import{ApiError as T}from"../../../api/errors.js";function N(t){return t.principal.class==="person"?null:new Set(G().filter(e=>O(t.principal,e.grantee??"app:terminal",e.node_id)).map(e=>e.node_id))}l(N,"visibleNodes");function H(t){return t.principal.class==="person"?null:new Set(G().filter(e=>e.run_id?Ce(t.principal,e.grantee??"app:terminal",e.node_id):O(t.principal,e.grantee??"app:terminal",e.node_id)).map(e=>e.node_id))}l(H,"visibleHistoryNodes");function Me(t){const e=N(t);return{status:200,body:{items:ae().filter(s=>e===null||e.has(s.node_id)).map(s=>({node_id:s.node_id,name:s.name,cwd:s.cwd,count:s.count,subjects:s.subjects}))}}}l(Me,"handleAttention");function Le(t){const e=t.body;if(e===null||typeof e!="object"||!Array.isArray(e.node_ids))throw _("attention count request requires node_ids: string[]");const n=e.node_ids;if(!n.every(o=>typeof o=="string"))throw _("attention count request node_ids must contain only strings");const s=N(t);return{status:200,body:{counts:q(n.filter(o=>s===null||s.has(o)))}}}l(Le,"handleAttentionCounts");function z(t){return Buffer.from(String(t),"utf8").toString("base64")}l(z,"encodeCursor");function x(t){const e=Number(Buffer.from(t,"base64").toString("utf8"));if(!Number.isInteger(e)||e<0)throw _(`invalid cursor: ${t}`);return e}l(x,"decodeCursor");function $e(t,e,n){const a=t.loadBody().split(`
2
+ `);if(e.length>0){const c=a.findIndex(u=>{const f=u.toLowerCase();return e.some(i=>f.includes(i))});if(c!==-1)return a.slice(c,c+n).map(u=>u.trim()).filter(u=>u!=="").join(" \u23CE ")}const o=a.map(c=>c.trim()).filter(c=>c!=="").slice(0,n).join(" \u23CE ");return o!==""?o:t.title}l($e,"snippet");function Be(t,e,n){const s=[...t];return e==="relevance"&&n!==void 0?s.sort((a,o)=>(n.get(o.ref)??0)-(n.get(a.ref)??0)||o.tsMs-a.tsMs||a.ref.localeCompare(o.ref)):e==="oldest"?s.sort((a,o)=>a.tsMs-o.tsMs||a.ref.localeCompare(o.ref)):s.sort((a,o)=>o.tsMs-a.tsMs||a.ref.localeCompare(o.ref)),s}l(Be,"sortArtifacts");function Ee(t){if(t==null)return{};if(typeof t!="object")throw _("history search body must be a JSON object");return t}l(Ee,"parseSearchBody");function Ie(t){if(t===null||typeof t!="object")throw _("history grep body must be a JSON object");const e=t;if(typeof e.pattern!="string"||e.pattern==="")throw _("history grep body requires a non-empty pattern");return e}l(Ie,"parseGrepBody");function j(t,e){const n={cwd:t.cwd,allCwds:t.all_cwds===!0,under:t.under,nodes:t.nodes},s={types:t.types,kinds:t.kinds,statuses:t.statuses,sinceMs:t.since_ms,untilMs:t.until_ms,origins:t.origins,roles:t.roles,from:t.from},a=H(e);return de(n,s).filter(o=>a===null||a.has(o.nodeId))}l(j,"buildCorpusFromQuery");function Oe(t){if(t==null)return{};if(typeof t!="object")throw _("history stats body must be a JSON object");return t}l(Oe,"parseStatsBody");const J=["origin","node","kind","day","type","role"];function Te(t){const e=Oe(t.body),n=e.group_by??"type";if(!J.includes(n))throw _(`--group-by must be one of: ${J.join(", ")}; received: ${String(e.group_by)}`);const s=j(e,t),a=le(s,n);return{status:200,body:{group_by:n,groups:a,total:a.reduce((c,u)=>c+u.count,0),scanned:s.length}}}l(Te,"handleHistoryStats");function je(t){const e=Ie(t.body),n=Math.min(100,Math.max(1,e.limit??20)),s=j(e,t),a=e.cursor!==void 0?x(e.cursor):0;let o;try{o=new RegExp(e.pattern,"i")}catch{throw _(`invalid regex pattern: ${e.pattern}`)}const c=[];for(const r of s)r.loadBody().split(`
3
+ `).forEach((y,b)=>{o.test(y)&&c.push({ref:r.ref,node:`${r.nodeName} (${r.nodeId})`,ts:r.ts,line:b+1,text:y.trim()})});c.sort((r,d)=>r.ref.localeCompare(d.ref)||r.line-d.line);const u=c.slice(a,a+n),f=a+n<c.length?z(a+n):null;return{status:200,body:{hits:u,next_cursor:f,total:c.length}}}l(je,"handleHistoryGrep");function Ae(t){const e=Ee(t.body),n=(e.query??"").trim(),s=e.weigh_body===!0,a=e.full===!0,o=Math.max(1,e.snippet_lines??1),c=Math.min(100,Math.max(1,e.limit??20)),u=e.per_node===!0;if(u&&(e.nodes===void 0||e.nodes.length===0))throw new T(400,"invalid_request","`per_node` requires a non-empty `nodes` list",{param:"nodes"});const f=j(e,t),i=e.cursor!==void 0?x(e.cursor):0,r=n.toLowerCase().split(/\s+/).filter(p=>p.length>0);let d=f,y;const b=e.sort??(r.length>0?"relevance":"recency");if(r.length>0&&b==="relevance"){y=new Map;const p=[];for(const h of f){const I=h.title.toLowerCase(),W=h.nodeName.toLowerCase(),Q=h.nodeDesc.toLowerCase(),A=s?h.loadBody().toLowerCase():null;let S=0;for(const L of r)I.includes(L)&&(S+=10),W.includes(L)&&(S+=5),Q.includes(L)&&(S+=3),A!==null&&A.includes(L)&&(S+=1);S>0&&(y.set(h.ref,S),p.push(h))}d=p}else r.length>0&&(d=f.filter(p=>{const h=`${p.title}
4
+ ${p.nodeName}
5
+ ${p.nodeDesc}${s?`
6
+ `+p.loadBody():""}`.toLowerCase();return r.some(I=>h.includes(I))}));let g=Be(d,b,y);if(u){const p=new Set;g=g.filter(h=>!p.has(h.nodeId)&&p.add(h.nodeId))}const M=u?g:g.slice(i,i+c),k=!u&&i+c<g.length?z(i+c):null;return{status:200,body:{hits:M.map(p=>{const h={ref:p.ref,node:`${p.nodeName} (${p.nodeId})`,kind:p.nodeKind,source:p.source,ts:p.ts,...u?{node_id:p.nodeId}:{}};return b==="relevance"&&y!==void 0&&(h.score=y.get(p.ref)??0),a?h.body=p.loadBody():h.snippet=$e(p,r,o),h}),next_cursor:k,total:g.length}}}l(Ae,"handleHistorySearch");function Fe(t){const e=(t.query.get("ref")??"").trim();if(!e.includes(":"))throw _(`ref must be <node-id>:inbox/<n>, <node-id>:session, or <node-id>:session/<n>; received: ${e}`,{received:e,next:"Get a valid ref from `crtr canvas history search`. Reports and documents are `crtr canvas read <name>`."});const n=ce(e);if(n===null)throw me(`no inbox entry or message for ref: ${e}`,{ref:e,next:"Re-run `crtr canvas history search` for a current ref. Reports and documents are `crtr canvas read <name>`."});if(H(t)?.has(n.nodeId)===!1)throw new T(404,"not_found","artifact not found");const s=n.source,o=t.query.get("format")==="raw"&&n.raw!==null?n.raw:n.body;return{status:200,body:{ref:e,node:`${n.nodeName} (${n.nodeId})`,source:s,ts:n.ts,content:o}}}l(Fe,"handleHistoryRead");function Ge(t){for(const[s]of t.query)if(s!=="live")throw _(`unknown snapshot query parameter: ${s}`);const e=t.query.getAll("live");if(e.length>1)throw _("live must appear at most once");const n=e[0];if(n===void 0||n==="false"||n==="0")return!1;if(n==="true"||n==="1")return!0;throw _("live must be true or false")}l(Ge,"snapshotLiveOnly");async function Pe(t){const e=new Set(D().map(r=>r.node_id)),n=N(t),s=(await re(P,e,Ge(t))).filter(r=>n===null||n.has(r.node_id)),a=q(s.map(r=>r.node_id));await ie(P,s,a);const o=s.map(r=>{const d=r.srcRow,y=d?.host_kind??"tmux";return{node_id:r.node_id,name:r.name,title:r.handle??r.name,description:r.description??null,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle??d?.lifecycle,status:r.status,cwd:r.cwd,profile_id:r.profile_id??null,parent:d?.parent??null,created:r.created,host_kind:y,enterable:y==="broker",attention_count:r.asks,...d?.cycles!=null?{cycles:d.cycles}:{},...r.mtimeMs!==void 0?{last_activity:new Date(r.mtimeMs).toISOString()}:{},ctx_tokens:r.ctx_tokens,streaming:r.streaming??!1,jobs_active:d!==void 0&&oe(d.pi_pid)?ne(se(r.node_id)).length:0,waiting_for:d?.waiting_for??null,hanging:r.hanging??null,viewed:r.viewed??!1,final_report:d?.final_report??null,finalized_at:d?.finalized_at??null}}),c=new Map;for(const r of w()){const d=c.get(r.from_id);d===void 0?c.set(r.from_id,[r.to_id]):d.push(r.to_id)}const u=new Set(s.map(r=>r.node_id)),f=Object.fromEntries(s.map(r=>[r.node_id,(c.get(r.node_id)??[]).filter(d=>u.has(d))]));return{status:200,body:{generated_at:$(),nodes:o,subscriptions:f}}}l(Pe,"handleSnapshot");function K(){const t=te().map(n=>{const s=n.host_kind??"tmux";return{node_id:n.node_id,name:n.name,kind:n.kind,mode:n.mode,lifecycle:n.lifecycle,status:n.status,cwd:n.cwd,host_kind:s,enterable:s==="broker",parent:n.parent,created:n.created}}),e=w().map(n=>({from_id:n.from_id,to_id:n.to_id,active:n.active,created:n.created}));return{generated_at:$(),nodes:t,edges:e}}l(K,"buildRoster");let R=null,B=null;function qe(){return R===null?(R=Promise.resolve().then(()=>K()).finally(()=>{R=null}),R):(B===null&&(B=R.then(()=>K()).finally(()=>{B=null})),B)}l(qe,"rosterCoalesced");async function De(t){const e=await qe(),n=N(t);return n===null?{status:200,body:e}:{status:200,body:{...e,nodes:e.nodes.filter(s=>n.has(s.node_id)),edges:e.edges.filter(s=>n.has(s.from_id)&&n.has(s.to_id))}}}l(De,"handleRoster");function U(t){if(t.name!==""&&t.name!==t.kind)return t.name;const e=(t.description??"").trim();return e!==""?e:t.name}l(U,"liveRootName");function He(){const t=F(),e=new Map(t.map(i=>[i.node_id,i])),n=new Map(_e(he(),"SELECT node_id, reply_at, read_at FROM nodes WHERE parent IS NULL").all().map(i=>[i.node_id,i])),s=t.filter(i=>i.parent===null&&i.kind!=="human"&&(be(i.status)||!ge({lifecycle:i.lifecycle,finalized:i.final_report!=null}))),a=we(),o=new Map;for(const i of a)o.set(i.requester_node_id,(o.get(i.requester_node_id)??0)+1);const c=new Map;for(const i of w())c.set(i.from_id,[...c.get(i.from_id)??[],i.to_id]);const u=l(i=>{const r=e.get(i)?.fault;return r&&r.link!=="broker\u2194crtrd"?Se(r):null},"faultOf"),f=s.map(i=>{const r=[i.node_id,...ye(i.node_id)].filter(m=>e.get(m)?.kind!=="human"&&e.has(m)),d=new Set(r),y=a.filter(m=>d.has(m.requester_node_id)),b=n.get(i.node_id),g=b?.reply_at??null,M=b?.read_at??null;let k=i.created;for(const m of r){const v=e.get(m).telemetry_updated_at;v!=null&&v>k&&(k=v)}return g!==null&&g>k&&(k=g),{node_id:i.node_id,name:U(i),cwd:i.cwd,profile_id:i.profile_id,status:i.status,state_line:(()=>{const m=ve(i.node_id)?.state.trim();return m||null})(),goal:ke(i.node_id)?.trim()||null,asks:y.length,first_ask_ticket_id:y[0]?.inbox_ticket_id??null,fault:r.map(u).find(m=>m!==null)??null,running_count:r.filter(m=>e.get(m).streaming===!0).length,unread:g!==null&&(M===null||g>M),last_activity:k,children:(c.get(i.node_id)??[]).filter(m=>e.has(m)&&e.get(m).kind!=="human").map(m=>{const v=e.get(m);return{node_id:m,name:U(v),status:v.status,streaming:v.streaming===!0,asks:o.get(m)??0,fault:u(m)}})}});return{generated_at:$(),rows:f}}l(He,"buildLiveRoots");let C=null,E=null;function ze(){const t=l(()=>Promise.resolve().then(He),"run");return C===null?(C=t().finally(()=>{C=null}),C):(E===null&&(E=C.then(t).finally(()=>{E=null})),E)}l(ze,"liveRootsCoalesced");async function xe(t){if(!Re(t.principal))throw new T(403,"permission_error","only the person reads the dashboard");const e=await ze(),n=t.query.get("profile");return{status:200,body:n===null?e:{...e,rows:e.rows.filter(s=>s.profile_id===n)}}}l(xe,"handleLiveRoots");function Je(t){const e=F().filter(o=>t.principal.class==="person"||O(t.principal,o.grantee??"app:terminal",o.node_id)).map(o=>({node_id:o.node_id,name:o.name,description:o.description,kind:o.kind,mode:o.mode,lifecycle:o.lifecycle,status:o.status,cwd:o.cwd,profile_id:o.profile_id,grantee:o.grantee??"app:terminal",parent:o.parent,created:o.created,frozen_at:o.frozen_at??null,terminal_reason:o.terminal_reason??null,final_report:o.final_report??null,telemetry_tokens_in:o.telemetry_tokens_in??null,telemetry_updated_at:o.telemetry_updated_at??null,fault:o.fault&&o.fault.link!=="broker\u2194crtrd"?{...o.fault,link:o.fault.link}:null,streaming:o.streaming??!1})),n=new Set(e.map(o=>o.node_id)),s=w().filter(o=>n.has(o.from_id)&&n.has(o.to_id));return{status:200,rawBody:V(JSON.stringify({nodes:e,edges:s}),{level:1}),headers:{"content-type":"application/gzip"}}}l(Je,"handleBrowse");function Ke(t){const e=N(t);return{status:200,body:{generated_at:$(),nodes:X().filter(s=>e===null||e.has(s.node_id)).map(Ne),edges:w().filter(s=>e===null||e.has(s.from_id)&&e.has(s.to_id)).map(s=>({from_id:s.from_id,to_id:s.to_id,active:s.active,created:s.created})),focused_node_ids:D().map(s=>s.node_id).filter(s=>e===null||e.has(s))}}}l(Ke,"handleGraph");function Ue(t){if(t==null)return{};if(typeof t!="object")throw _("prune body must be a JSON object");const e=t,n=l(r=>{const d=e[r];if(d!==void 0){if(typeof d!="number"||!Number.isFinite(d))throw _(`${r} must be a number`);return d}},"num"),s=l(r=>{const d=e[r];if(d!==void 0){if(typeof d!="boolean")throw _(`${r} must be a boolean`);return d}},"bool"),a={},o=n("limit");o!==void 0&&(a.limit=o);const c=n("ttl_days");c!==void 0&&(a.ttl_days=c);const u=s("include_stale");u!==void 0&&(a.include_stale=u);const f=s("empty");f!==void 0&&(a.empty=f);const i=s("dry_run");return i!==void 0&&(a.dry_run=i),a}l(Ue,"parsePruneBody");function We(t){const e=Ue(t.body),n=e.ttl_days??14,s=e.dry_run??!1,a=pe(fe());let o;return e.limit!==void 0?o=ee({maxNodes:e.limit,held:a,dryRun:s}).pruned.map(f=>({node_id:f.node_id,status:f.status,created:f.created})):e.empty===!0?o=ue({dryRun:s}).map(f=>({node_id:f,status:"empty",created:""})):o=Z({ttlDays:n,held:a,dryRun:s,includeStale:e.include_stale??!1}).pruned.map(f=>({node_id:f.node_id,status:f.status,created:f.created})),{status:200,body:{pruned:o,dry_run:s,ttl_days:n}}}l(We,"handlePrune");const kt=[{method:"GET",pattern:"/v1/canvas/attention",handler:Me},{method:"POST",pattern:"/v1/canvas/attention/counts",handler:Le},{method:"POST",pattern:"/v1/canvas/history/search",handler:Ae},{method:"POST",pattern:"/v1/canvas/history/grep",handler:je},{method:"GET",pattern:"/v1/canvas/history/read",handler:Fe},{method:"POST",pattern:"/v1/canvas/history/stats",handler:Te},{method:"GET",pattern:"/v1/canvas/snapshot",handler:Pe},{method:"GET",pattern:"/v1/canvas/roster",handler:De},{method:"GET",pattern:"/v1/canvas/live-roots",handler:xe},{method:"GET",pattern:"/v1/canvas/browse",handler:Je},{method:"GET",pattern:"/v1/canvas/graph",handler:Ke},{method:"POST",pattern:"/v1/canvas/prune",ownerOnly:!0,handler:We}];export{kt as canvasRoutes,Je as handleBrowse};