@north-light/cli 0.3.594 → 0.3.596

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 (32) hide show
  1. package/dist/api/client.js +3 -3
  2. package/dist/clients/attach/viewer.js +345 -345
  3. package/dist/commands/pkg/app-installs.js +2 -2
  4. package/dist/core/command-plugins/transport/http-plan.d.ts +4 -0
  5. package/dist/core/command-plugins/transport/http-plan.js +1 -1
  6. package/dist/core/io.js +4 -4
  7. package/dist/core/providers/call.js +1 -1
  8. package/dist/core/runs/initial-input.d.ts +15 -0
  9. package/dist/core/runs/initial-input.js +1 -0
  10. package/dist/core/runs/operations.js +5 -5
  11. package/dist/core/runtime/broker/model-adapter.d.ts +3 -3
  12. package/dist/core/runtime/broker/model-adapter.js +3 -3
  13. package/dist/core/runtime/broker/spilled-body.d.ts +2 -4
  14. package/dist/core/runtime/broker/spilled-body.js +1 -1
  15. package/dist/core/runtime/broker/turn-cause.d.ts +4 -0
  16. package/dist/core/runtime/broker/turn-cause.js +1 -1
  17. package/dist/core/runtime/headless-pi.d.ts +2 -0
  18. package/dist/core/runtime/headless-pi.js +1 -1
  19. package/dist/core/runtime/naming.d.ts +2 -2
  20. package/dist/core/runtime/naming.js +9 -9
  21. package/dist/core/runtime/recap.d.ts +1 -1
  22. package/dist/core/runtime/recap.js +4 -4
  23. package/dist/core/runtime/revive.js +2 -2
  24. package/dist/core/runtime/spawn.js +2 -2
  25. package/dist/pi-extensions/broker-local.d.ts +3 -1
  26. package/dist/pi-extensions/broker-local.js +1 -1
  27. package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
  28. package/dist/pi-extensions/canvas-recap.js +7 -7
  29. package/docs/ci-runner.md +5 -5
  30. package/package.json +2 -2
  31. package/packages/crouter-identity/package.json +1 -1
  32. package/runtime.lock.json +13 -13
@@ -1 +1 @@
1
- var j=Object.defineProperty;var s=(e,t)=>j(e,"name",{value:t,configurable:!0});import{envModelIntent as _,envNodeCwd as D,envProfileId as E}from"../../shared/env.js";import{join as N}from"node:path";import{InMemoryCredentialStore as O}from"@earendil-works/pi-ai";import{APP_FAMILY as c,expandModelCandidates as $,modelRequestFromConfig as I}from"../model-routes.js";import{defaultModelLaddersConfig as B}from"../../types.js";import{stripThinkingSuffix as H}from"./model-selection.js";import{configureBrokerModelRuntime as W}from"./broker/model-adapter.js";import{currentNodeToken as Y}from"./broker/node-token.js";function z(e){for(let t=e.length-1;t>=0;t--){const n=e[t];if(n?.role!=="assistant")continue;if(typeof n.content=="string")return n.content.trim();if(!Array.isArray(n.content))continue;const m=n.content.map(i=>{const u=i;return u.type==="text"&&typeof u.text=="string"?u.text:""}).join("").trim();if(m!=="")return m}return""}s(z,"lastAssistantText");function G(e,t){const n=_();if(!(n===void 0||!n.startsWith(`${c}/`)))return t?{modelRequest:I(e.modelSpec,n,!1),intent:n}:{modelRequest:{kind:"logical",family:c,strength:"light",spec:H(B().anthropic.light)},intent:`${c}/light`}}s(G,"appPaidRequest");async function f(e,t,n,m){let i,u=!1,d=!1,p;const g=s(o=>{i===void 0||u||(u=!0,o&&i.abort().catch(()=>{}),i.dispose())},"disposeSession"),R=new Promise(o=>{p=setTimeout(()=>{d=!0,g(!0),o(t)},e.timeoutMs)}),v=(async()=>{try{const o=await import("@earendil-works/pi-coding-agent");if(Y()===void 0||d)return t;const l=D()??process.cwd(),C=E(),y=o.getAgentDir(),x=await o.ModelRuntime.create({credentials:new O,modelsPath:N(y,"models.json")}),M=G(e,n);if(W(x,M?.intent),d)return t;const h=await o.createAgentSessionServices({cwd:l,agentDir:y,modelRuntime:x,settingsManager:o.SettingsManager.inMemory(),resourceLoaderOptions:{noExtensions:!0,noContextFiles:!0,noSkills:!0,noPromptTemplates:!0,noThemes:!0,systemPrompt:e.systemPrompt}});if(d)return t;const w=M?.modelRequest??I(e.modelSpec,e.modelIntent,!1);if(w===void 0)return t;const A=new o.ModelRegistry(h.modelRuntime),S=(await $(w,l,C)).map(a=>A.find(a.providerId,a.modelId)).find(a=>a!==void 0);if(S===void 0)return t;let T;const r=e.tool,F=r===void 0?void 0:o.defineTool({name:r.name,label:r.label,description:r.description,parameters:r.parameters,...r.constrainedSampling===void 0?{}:{constrainedSampling:r.constrainedSampling},async execute(a,L){return T=L,{content:[{type:"text",text:"Recorded."}],details:{recorded:!0},terminate:!0}}}),P=o.SessionManager.inMemory(l,e.sessionId===void 0?void 0:{id:e.sessionId});for(const a of e.seedMessages??[])P.appendMessage(a);return{session:i}=await o.createAgentSessionFromServices({services:h,sessionManager:P,model:S,thinkingLevel:"off",...r===void 0?e.toolPolicy==="builtin"?{}:{noTools:"all"}:{tools:[r.name],customTools:[F]}}),d||(await i.bindExtensions({}),d)?t:(await i.prompt(e.userPrompt),d?t:m(i,T))}catch{return t}finally{g(!1)}})();try{return await Promise.race([v,R])}finally{clearTimeout(p)}}s(f,"runHeadless");function b(e){return f(e,null,!1,(t,n)=>n)}s(b,"headlessToolCall");function ee(e){return f(e,"",!1,t=>z(t.messages))}s(ee,"headlessText");function te(e){return f(e,"failed",!0,()=>"completed")}s(te,"headlessTurn");export{ee as headlessText,b as headlessToolCall,te as headlessTurn};
1
+ var j=Object.defineProperty;var s=(e,t)=>j(e,"name",{value:t,configurable:!0});import{envModelIntent as O,envNodeCwd as _,envProfileId as D}from"../../shared/env.js";import{join as E}from"node:path";import{InMemoryCredentialStore as N}from"@earendil-works/pi-ai";import{APP_FAMILY as c,expandModelCandidates as $,modelRequestFromConfig as R}from"../model-routes.js";import{defaultModelLaddersConfig as H}from"../../types.js";import{stripThinkingSuffix as W}from"./model-selection.js";import{configureBrokerModelRuntime as Y}from"./broker/model-adapter.js";import{currentNodeToken as z}from"./broker/node-token.js";import{currentOrPendingCausedBy as G}from"./broker/turn-cause.js";function J(e){for(let t=e.length-1;t>=0;t--){const n=e[t];if(n?.role!=="assistant")continue;if(typeof n.content=="string")return n.content.trim();if(!Array.isArray(n.content))continue;const u=n.content.map(m=>{const i=m;return i.type==="text"&&typeof i.text=="string"?i.text:""}).join("").trim();if(u!=="")return u}return""}s(J,"lastAssistantText");function K(e,t){const n=O();if(!(n===void 0||!n.startsWith(`${c}/`)))return t?{modelRequest:R(e.modelSpec,n,!1),intent:n}:{modelRequest:{kind:"logical",family:c,strength:"light",spec:W(H().anthropic.light)},intent:`${c}/light`}}s(K,"appPaidRequest");async function f(e,t,n,u){const m=e.causedBy===void 0?G():e.causedBy??void 0;let i,p=!1,d=!1,g;const y=s(o=>{i===void 0||p||(p=!0,o&&i.abort().catch(()=>{}),i.dispose())},"disposeSession"),v=new Promise(o=>{g=setTimeout(()=>{d=!0,y(!0),o(t)},e.timeoutMs)}),C=(async()=>{try{const o=await import("@earendil-works/pi-coding-agent");if(z()===void 0||d)return t;const l=_()??process.cwd(),A=D(),x=o.getAgentDir(),M=await o.ModelRuntime.create({credentials:new N,modelsPath:E(x,"models.json")}),h=K(e,n);if(Y(M,h?.intent,m),d)return t;const w=await o.createAgentSessionServices({cwd:l,agentDir:x,modelRuntime:M,settingsManager:o.SettingsManager.inMemory(),resourceLoaderOptions:{noExtensions:!0,noContextFiles:!0,noSkills:!0,noPromptTemplates:!0,noThemes:!0,systemPrompt:e.systemPrompt}});if(d)return t;const S=h?.modelRequest??R(e.modelSpec,e.modelIntent,!1);if(S===void 0)return t;const B=new o.ModelRegistry(w.modelRuntime),P=(await $(S,l,A)).map(a=>B.find(a.providerId,a.modelId)).find(a=>a!==void 0);if(P===void 0)return t;let T;const r=e.tool,F=r===void 0?void 0:o.defineTool({name:r.name,label:r.label,description:r.description,parameters:r.parameters,...r.constrainedSampling===void 0?{}:{constrainedSampling:r.constrainedSampling},async execute(a,L){return T=L,{content:[{type:"text",text:"Recorded."}],details:{recorded:!0},terminate:!0}}}),I=o.SessionManager.inMemory(l,e.sessionId===void 0?void 0:{id:e.sessionId});for(const a of e.seedMessages??[])I.appendMessage(a);return{session:i}=await o.createAgentSessionFromServices({services:w,sessionManager:I,model:P,thinkingLevel:"off",...r===void 0?e.toolPolicy==="builtin"?{}:{noTools:"all"}:{tools:[r.name],customTools:[F]}}),d||(await i.bindExtensions({}),d)?t:(await i.prompt(e.userPrompt),d?t:u(i,T))}catch{return t}finally{y(!1)}})();try{return await Promise.race([C,v])}finally{clearTimeout(g)}}s(f,"runHeadless");function ne(e){return f(e,null,!1,(t,n)=>n)}s(ne,"headlessToolCall");function oe(e){return f(e,"",!1,t=>J(t.messages))}s(oe,"headlessText");function ie(e){return f(e,"failed",!0,()=>"completed")}s(ie,"headlessTurn");export{oe as headlessText,ne as headlessToolCall,ie as headlessTurn};
@@ -55,11 +55,11 @@ export declare function sanitizeIcon(raw: string): string;
55
55
  * This module stays canvas-free so CLI leaves can reach it. The canvas-coupled
56
56
  * commit path lives in pi-extensions/broker-local.ts, which imports this pure
57
57
  * helper and writes through crtrd's guarded generated-name operation. */
58
- export declare function headlessName(body: string, sourceCap?: number): Promise<SessionName>;
58
+ export declare function headlessName(body: string, sourceCap?: number, causedBy?: string | null | undefined): Promise<SessionName>;
59
59
  /** Generate a name from a bounded conversation source rather than a single
60
60
  * kickoff message. The caller owns selecting representative conversation text;
61
61
  * this cap keeps the one-shot prompt compact. */
62
- export declare function headlessNameFromConversation(conversation: string): Promise<SessionName>;
62
+ export declare function headlessNameFromConversation(conversation: string, causedBy?: string | null): Promise<SessionName>;
63
63
  /** A filesystem-safe slug for the `.todos/<slug>.md` fallback path: lowercase,
64
64
  * [a-z0-9] kept, everything else → single hyphen, clamped to 8 words. */
65
65
  export declare function titleSlug(title: string): string;
@@ -1,14 +1,14 @@
1
- var U=Object.defineProperty;var s=(e,t)=>U(e,"name",{value:t,configurable:!0});import{headlessText as k,headlessToolCall as p}from"./headless-pi.js";import{defaultProvider as h,modelLadders as m,stripThinkingSuffix as g}from"./model-selection.js";const a=2e3,f=45e3,x=["fixing/diagnosis: U+F188 bug, U+F0F1 stethoscope, U+F06D fire, U+F071 warning","building/changing code: U+F0AD wrench, U+F067 plus, U+F121 code, U+F085 cogs, U+F021 refresh","testing/verifying: U+F0C3 flask, U+F00C check, U+F24E balance","writing/docs/research: U+F02D book, U+F15C document, U+F002 magnifier, U+F0EB lightbulb","data/infra: U+F1C0 database, U+F0C2 cloud, U+F120 terminal, U+F013 gear, U+F1B3 packages","ship/perf/scale: U+F135 rocket, U+F0E7 bolt, U+F080 chart, U+F201 trend line","security/access: U+F132 shield, U+F023 lock, U+F084 key","interface/design: U+F1FC paintbrush, U+F03E image, U+F108 display","communication/coordination: U+F1D8 paper plane, U+F0E6 comments, U+F0E8 sitemap, U+F0C1 link"].join(`
2
- `),T="You label agent sessions. A session is whatever a person opened it for: a coding task, a question, a piece of research, an idea they are turning over, or an ordinary conversation. You are labelling that session, never taking part in it \u2014 do not answer the message, do not do the work it asks for, do not comment on it. Every session gets a label, however short, casual, misspelled, or open-ended its opening message is.\n\nCall the `name_session` tool exactly once with:\n- `title`: how a person would refer to this session in a list of their conversations \u2014 2-8 words, sentence case, written naturally with its punctuation intact (e.g. `What's your favorite color?`, `Payroll export mapping bug`, `Ideas for the launch email`). No quotes, no markdown, no trailing period.\n- `name`: the same subject as a concise 3-8 word kebab-case handle \u2014 lowercase words joined by single hyphens (e.g. `favorite-color-question`, `refactor-auth-token-flow`). No punctuation, quotes, or prose.\n- `icon`: ONE Nerd Font glyph, written as its codepoint in `U+XXXX` form (e.g. `U+F188`). Emit a codepoint, not a name like `nf-fa-bug`, and not an emoji.\n\nChoosing the icon \u2014 work through it in this order:\n1. Name the concrete SUBJECT of the task: the thing being acted on. A font, a trash can, an account, a browser tab, a clock, an invoice, a map, a robot, a specific technology's logo. If a glyph depicts that thing, use it. This is almost always the better icon, because it is what distinguishes this session from every other one.\n2. Only when the task has no depictable subject, fall back to the ACTIVITY (fixing, documenting, testing) using the generic set below.\n\nANY Nerd Font codepoint is allowed and the set is enormous \u2014 the whole Font Awesome range, Devicons and Seti technology logos, Octicons, Codicons, Weather, and the thousands of Material Design nouns above U+F0001. Reach into it. The examples below are the generic tier, NOT a menu: if your pick came from them and the task had a real subject, you picked wrong. Never reuse a generic gear, wrench, or bug for a task that is actually about something.\n\nGeneric fallback set, by kind of work:\n"+x;function C(e,t=a){return`<source>
1
+ var k=Object.defineProperty;var s=(e,t)=>k(e,"name",{value:t,configurable:!0});import{headlessText as x,headlessToolCall as h}from"./headless-pi.js";import{currentOrPendingCausedBy as T}from"./broker/turn-cause.js";import{defaultProvider as m,modelLadders as g,stripThinkingSuffix as f}from"./model-selection.js";const c=2e3,w=45e3,C=["fixing/diagnosis: U+F188 bug, U+F0F1 stethoscope, U+F06D fire, U+F071 warning","building/changing code: U+F0AD wrench, U+F067 plus, U+F121 code, U+F085 cogs, U+F021 refresh","testing/verifying: U+F0C3 flask, U+F00C check, U+F24E balance","writing/docs/research: U+F02D book, U+F15C document, U+F002 magnifier, U+F0EB lightbulb","data/infra: U+F1C0 database, U+F0C2 cloud, U+F120 terminal, U+F013 gear, U+F1B3 packages","ship/perf/scale: U+F135 rocket, U+F0E7 bolt, U+F080 chart, U+F201 trend line","security/access: U+F132 shield, U+F023 lock, U+F084 key","interface/design: U+F1FC paintbrush, U+F03E image, U+F108 display","communication/coordination: U+F1D8 paper plane, U+F0E6 comments, U+F0E8 sitemap, U+F0C1 link"].join(`
2
+ `),E="You label agent sessions. A session is whatever a person opened it for: a coding task, a question, a piece of research, an idea they are turning over, or an ordinary conversation. You are labelling that session, never taking part in it \u2014 do not answer the message, do not do the work it asks for, do not comment on it. Every session gets a label, however short, casual, misspelled, or open-ended its opening message is.\n\nCall the `name_session` tool exactly once with:\n- `title`: how a person would refer to this session in a list of their conversations \u2014 2-8 words, sentence case, written naturally with its punctuation intact (e.g. `What's your favorite color?`, `Payroll export mapping bug`, `Ideas for the launch email`). No quotes, no markdown, no trailing period.\n- `name`: the same subject as a concise 3-8 word kebab-case handle \u2014 lowercase words joined by single hyphens (e.g. `favorite-color-question`, `refactor-auth-token-flow`). No punctuation, quotes, or prose.\n- `icon`: ONE Nerd Font glyph, written as its codepoint in `U+XXXX` form (e.g. `U+F188`). Emit a codepoint, not a name like `nf-fa-bug`, and not an emoji.\n\nChoosing the icon \u2014 work through it in this order:\n1. Name the concrete SUBJECT of the task: the thing being acted on. A font, a trash can, an account, a browser tab, a clock, an invoice, a map, a robot, a specific technology's logo. If a glyph depicts that thing, use it. This is almost always the better icon, because it is what distinguishes this session from every other one.\n2. Only when the task has no depictable subject, fall back to the ACTIVITY (fixing, documenting, testing) using the generic set below.\n\nANY Nerd Font codepoint is allowed and the set is enormous \u2014 the whole Font Awesome range, Devicons and Seti technology logos, Octicons, Codicons, Weather, and the thousands of Material Design nouns above U+F0001. Reach into it. The examples below are the generic tier, NOT a menu: if your pick came from them and the task had a real subject, you picked wrong. Never reuse a generic gear, wrench, or bug for a task that is actually about something.\n\nGeneric fallback set, by kind of work:\n"+C;function N(e,t=c){return`<source>
3
3
  ${e.slice(0,t)}
4
4
  </source>
5
5
 
6
- Label the session this text is from: call \`name_session\` once with a title, a name, and an icon depicting it, then stop. Do not answer it.`}s(C,"nameUserPrompt");const c=72;function l(e){return((e??"").split(`
7
- `).map(o=>o.trim()).find(o=>o!=="")??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").split("-").filter(o=>o!=="").slice(0,8).join("-")}s(l,"sanitizeSessionName");function w(e){const n=((e??"").split(`
8
- `).map(i=>i.trim()).find(i=>i!=="")??"").replace(/\s+/g," ").replace(/^["'`]+|["'`]+$/g,"").trim();if(n==="")return"";let o=n;if(n.length>c){const i=n.slice(0,c),r=i.lastIndexOf(" ");o=`${(r>c/2?i.slice(0,r):i).trimEnd()}\u2026`}return o.charAt(0).toUpperCase()+o.slice(1)}s(w,"sanitizeSessionTitle");function E(e){return w((e??"").replace(/\s+/g," "))}s(E,"titleFromPrompt");function O(e){return l(E(e))||"session"}s(O,"slugFromPrompt");function j(e){const t=e===void 0?void 0:process.env[e];if(t!==void 0&&t.trim()!=="")return t.trim();const n=m();return g(n[h(n)].light)}s(j,"lightModel");function b(){const e=process.env.CRTR_NAME_MODEL;if(e!==void 0&&e.trim()!=="")return{modelSpec:e.trim()};const t=m(),n=h(t);return{modelSpec:g(t[n].light),modelIntent:`${n}/light`}}s(b,"nameModelRequest");const N={type:"object",properties:{title:{type:"string",description:"A 2-8 word sentence-case title a person would recognize this session by in a list, with its punctuation intact, e.g. Payroll export mapping bug. No quotes, markdown, or trailing period."},name:{type:"string",description:"A 3-8 word kebab-case session name: lowercase words joined by single hyphens, e.g. refactor-auth-token-flow. No punctuation, quotes, or prose."},icon:{type:"string",description:"The Nerd Font glyph depicting this session, as a codepoint in U+XXXX form (e.g. U+F188)."}},required:["title","name","icon"],additionalProperties:!1};function v(e){const t=(e??"").trim();if(t==="")return"";const n=/^(?:u\+|0x|\\u\{?|&#x)?([0-9a-f]{4,6})\}?;?$/i.exec(t);if(n!==null){const i=Number.parseInt(n[1],16);if(i>=8192&&i<=1114111)try{return String.fromCodePoint(i)}catch{return""}return""}const o=[...t];return o.length>2?"":o[0].codePointAt(0)>=8192?t:""}s(v,"sanitizeIcon");async function P(e,t=a){const n={name:"",title:"",icon:""},o={systemPrompt:T,userPrompt:C(e,t),...b(),timeoutMs:f,tool:{name:"name_session",label:"Name session",description:"Record the title, name, and icon for this session. Call exactly once, then stop.",parameters:N,constrainedSampling:{type:"json_schema",strict:"prefer"}}},i=await p(o)??await p(o);if(i===null||typeof i!="object")return n;const r=i,u=w(typeof r.title=="string"?r.title:""),d=l(typeof r.name=="string"?r.name:"")||l(u),F=v(typeof r.icon=="string"?r.icon:"");return d!==""?{name:d,title:u,icon:F}:n}s(P,"headlessName");function L(e){return P(e,3e4)}s(L,"headlessNameFromConversation");const S='You write GitHub issue titles. Given a free-text note (a todo, idea, or bug report), reply with ONLY a concise issue title: sentence case, 4-10 words, no trailing period, no quotes, no prefix like "Bug:" or "Idea:", no markdown. Capture the gist as an actionable title. Output JUST the title, nothing else.';function M(e){return`<note>
9
- ${e.slice(0,a)}
6
+ Label the session this text is from: call \`name_session\` once with a title, a name, and an icon depicting it, then stop. Do not answer it.`}s(N,"nameUserPrompt");const l=72;function u(e){return((e??"").split(`
7
+ `).map(o=>o.trim()).find(o=>o!=="")??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").split("-").filter(o=>o!=="").slice(0,8).join("-")}s(u,"sanitizeSessionName");function b(e){const n=((e??"").split(`
8
+ `).map(i=>i.trim()).find(i=>i!=="")??"").replace(/\s+/g," ").replace(/^["'`]+|["'`]+$/g,"").trim();if(n==="")return"";let o=n;if(n.length>l){const i=n.slice(0,l),r=i.lastIndexOf(" ");o=`${(r>l/2?i.slice(0,r):i).trimEnd()}\u2026`}return o.charAt(0).toUpperCase()+o.slice(1)}s(b,"sanitizeSessionTitle");function P(e){return b((e??"").replace(/\s+/g," "))}s(P,"titleFromPrompt");function q(e){return u(P(e))||"session"}s(q,"slugFromPrompt");function X(e){const t=e===void 0?void 0:process.env[e];if(t!==void 0&&t.trim()!=="")return t.trim();const n=g();return f(n[m(n)].light)}s(X,"lightModel");function y(){const e=process.env.CRTR_NAME_MODEL;if(e!==void 0&&e.trim()!=="")return{modelSpec:e.trim()};const t=g(),n=m(t);return{modelSpec:f(t[n].light),modelIntent:`${n}/light`}}s(y,"nameModelRequest");const v={type:"object",properties:{title:{type:"string",description:"A 2-8 word sentence-case title a person would recognize this session by in a list, with its punctuation intact, e.g. Payroll export mapping bug. No quotes, markdown, or trailing period."},name:{type:"string",description:"A 3-8 word kebab-case session name: lowercase words joined by single hyphens, e.g. refactor-auth-token-flow. No punctuation, quotes, or prose."},icon:{type:"string",description:"The Nerd Font glyph depicting this session, as a codepoint in U+XXXX form (e.g. U+F188)."}},required:["title","name","icon"],additionalProperties:!1};function S(e){const t=(e??"").trim();if(t==="")return"";const n=/^(?:u\+|0x|\\u\{?|&#x)?([0-9a-f]{4,6})\}?;?$/i.exec(t);if(n!==null){const i=Number.parseInt(n[1],16);if(i>=8192&&i<=1114111)try{return String.fromCodePoint(i)}catch{return""}return""}const o=[...t];return o.length>2?"":o[0].codePointAt(0)>=8192?t:""}s(S,"sanitizeIcon");async function M(e,t=c,n=T()??null){const o={name:"",title:"",icon:""},i={systemPrompt:E,userPrompt:N(e,t),...y(),timeoutMs:w,causedBy:n,tool:{name:"name_session",label:"Name session",description:"Record the title, name, and icon for this session. Call exactly once, then stop.",parameters:v,constrainedSampling:{type:"json_schema",strict:"prefer"}}},r=await h(i)??await h(i);if(r===null||typeof r!="object")return o;const a=r,d=b(typeof a.title=="string"?a.title:""),p=u(typeof a.name=="string"?a.name:"")||u(d),U=S(typeof a.icon=="string"?a.icon:"");return p!==""?{name:p,title:d,icon:U}:o}s(M,"headlessName");function $(e,t){return M(e,3e4,t)}s($,"headlessNameFromConversation");const _='You write GitHub issue titles. Given a free-text note (a todo, idea, or bug report), reply with ONLY a concise issue title: sentence case, 4-10 words, no trailing period, no quotes, no prefix like "Bug:" or "Idea:", no markdown. Capture the gist as an actionable title. Output JUST the title, nothing else.';function A(e){return`<note>
9
+ ${e.slice(0,c)}
10
10
  </note>
11
11
 
12
- Write a concise GitHub issue title for the note above. Output JUST the title, nothing else.`}s(M,"titleUserPrompt");function y(e){const t=(e??"").split(`
13
- `).map(o=>o.trim()).find(o=>o!=="")??"",n=t.length>80?`${t.slice(0,77).trimEnd()}\u2026`:t;return n!==""?n:"untitled"}s(y,"firstLineTitle");function q(e){return(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").split("-").filter(n=>n!=="").slice(0,8).join("-")||"untitled"}s(q,"titleSlug");async function X(e){const t=(e??"").trim();if(t==="")return"untitled";try{const o=(await k({systemPrompt:S,userPrompt:M(t),...b(),timeoutMs:f})).split(`
14
- `).map(i=>i.trim()).find(i=>i!=="")??"";return o!==""?o.replace(/^["']|["']$/g,"").replace(/\.$/,""):y(t)}catch{return y(t)}}s(X,"generateIssueTitle");export{X as generateIssueTitle,P as headlessName,L as headlessNameFromConversation,j as lightModel,v as sanitizeIcon,l as sanitizeSessionName,w as sanitizeSessionTitle,O as slugFromPrompt,E as titleFromPrompt,q as titleSlug};
12
+ Write a concise GitHub issue title for the note above. Output JUST the title, nothing else.`}s(A,"titleUserPrompt");function F(e){const t=(e??"").split(`
13
+ `).map(o=>o.trim()).find(o=>o!=="")??"",n=t.length>80?`${t.slice(0,77).trimEnd()}\u2026`:t;return n!==""?n:"untitled"}s(F,"firstLineTitle");function D(e){return(e??"").toLowerCase().replace(/[^a-z0-9]+/g,"-").split("-").filter(n=>n!=="").slice(0,8).join("-")||"untitled"}s(D,"titleSlug");async function R(e){const t=(e??"").trim();if(t==="")return"untitled";try{const o=(await x({systemPrompt:_,userPrompt:A(t),...y(),timeoutMs:w})).split(`
14
+ `).map(i=>i.trim()).find(i=>i!=="")??"";return o!==""?o.replace(/^["']|["']$/g,"").replace(/\.$/,""):F(t)}catch{return F(t)}}s(R,"generateIssueTitle");export{R as generateIssueTitle,M as headlessName,$ as headlessNameFromConversation,X as lightModel,S as sanitizeIcon,u as sanitizeSessionName,b as sanitizeSessionTitle,q as slugFromPrompt,P as titleFromPrompt,D as titleSlug};
@@ -1,3 +1,3 @@
1
1
  /** Ask Pi for a concise briefing of `conversation`. Invokes `onRecap` with
2
2
  * the parsed string on success, or never on timeout, empty, or garbled output. */
3
- export declare function generateRecap(conversation: string, onRecap: (briefing: string) => void, liveModel?: string, roadmap?: string): void;
3
+ export declare function generateRecap(conversation: string, onRecap: (briefing: string) => void, liveModel?: string, roadmap?: string, causedBy?: string | null): void;
@@ -1,4 +1,4 @@
1
- var h=Object.defineProperty;var n=(t,e)=>h(t,"name",{value:e,configurable:!0});import{headlessText as l}from"./headless-pi.js";import{envRecapModelOverride as c}from"../../shared/env.js";const d=25e3,m="anthropic/claude-haiku-5-5",u=`You brief a developer returning to a paused coding-agent conversation. They run many agent sessions in parallel and have lost this one's thread: they no longer know what this conversation is about or what they originally asked for. The brief exists to solve exactly that \u2014 pinned above their input box, it must tell them at a glance what this conversation is FOR and where it actually stands. You are given the first and most recent messages of the literal user/agent back-and-forth (tool output omitted), and the agent's roadmap file when one exists.
1
+ var l=Object.defineProperty;var n=(t,e)=>l(t,"name",{value:e,configurable:!0});import{headlessText as c}from"./headless-pi.js";import{envRecapModelOverride as d}from"../../shared/env.js";const m=25e3,u="anthropic/claude-haiku-5-5",p=`You brief a developer returning to a paused coding-agent conversation. They run many agent sessions in parallel and have lost this one's thread: they no longer know what this conversation is about or what they originally asked for. The brief exists to solve exactly that \u2014 pinned above their input box, it must tell them at a glance what this conversation is FOR and where it actually stands. You are given the first and most recent messages of the literal user/agent back-and-forth (tool output omitted), and the agent's roadmap file when one exists.
2
2
 
3
3
  Output EXACTLY two plain-text lines, nothing else:
4
4
  Goal: <what was requested \u2014 the original ask / what this conversation is for>
@@ -14,7 +14,7 @@ Goal: Build the trimmed-v1 command-plugin seam (orchestrating phases).
14
14
  State: Phase 1 landed; waiting on the core-seam worker's final.
15
15
 
16
16
  Goal: Standing personal assistant \u2014 iMessage wakes, Reddit scans, small tasks.
17
- State: Idle \u2014 waiting on you: hook pick, map export, Omi timing.`;function p(t,e){return`${e!==void 0&&e.trim()!==""?`<roadmap>
17
+ State: Idle \u2014 waiting on you: hook pick, map export, Omi timing.`;function g(t,e){return`${e!==void 0&&e.trim()!==""?`<roadmap>
18
18
  ${e.trim()}
19
19
  </roadmap>
20
20
 
@@ -22,6 +22,6 @@ ${e.trim()}
22
22
  ${t}
23
23
  </conversation>
24
24
 
25
- Write the two-line Goal:/State: briefing for the conversation above. Output JUST the two lines.`}n(p,"recapUserPrompt");function g(t){const e=c();return e!==void 0?e:t!==void 0&&t.trim()!==""?t.trim():m}n(g,"recapModel");function f(t){return t.split(`
25
+ Write the two-line Goal:/State: briefing for the conversation above. Output JUST the two lines.`}n(g,"recapUserPrompt");function f(t){const e=d();return e!==void 0?e:t!==void 0&&t.trim()!==""?t.trim():u}n(f,"recapModel");function w(t){return t.split(`
26
26
  `).map(e=>e.replace(/\s+/g," ").trim()).filter(e=>e!=="").join(`
27
- `)}n(f,"parseRecap");function b(t,e,a,i){const o=(t??"").trim();o!==""&&l({systemPrompt:u,userPrompt:p(o,i),modelSpec:g(a),timeoutMs:d}).then(s=>{const r=f(s);if(r!=="")try{e(r)}catch{}}).catch(()=>{})}n(b,"generateRecap");export{b as generateRecap};
27
+ `)}n(w,"parseRecap");function k(t,e,a,s,o){const i=(t??"").trim();i!==""&&c({systemPrompt:p,userPrompt:g(i,s),modelSpec:f(a),timeoutMs:m,...o===void 0?{}:{causedBy:o}}).then(h=>{const r=w(h);if(r!=="")try{e(r)}catch{}}).catch(()=>{})}n(k,"generateRecap");export{k as generateRecap};
@@ -1,3 +1,3 @@
1
- var le=Object.defineProperty;var s=(e,r)=>le(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as ce}from"./unsandboxed.js";import{existsSync as U}from"node:fs";import{conversationIdFromPath as _,conversationStore as h,ConversationNotFoundError as H}from"../conversation-store/index.js";import{isAbsolute as ue}from"node:path";import{findNodeBySessionFile as fe,getNode as R,updateNode as N,beginExecution as we,ExecutionAlreadyLive as me,recordExecutionPid as de,abortExecution as F,endLiveExecution as he,unbindSession as G,recordLaunch as pe,setFrozen as ve,clearFrozen as K,fullName as _e}from"../canvas/index.js";import{emitEvent as W}from"../events/emit.js";import{consumeDeadlineOnWake as ye}from"../feed/messages.js";import{isParked as J,transition as A}from"./lifecycle.js";import{publishNotice as ke}from"./close.js";import{InputError as O}from"../io.js";import{buildPiArgv as y}from"./launch.js";import{buildReviveKickoff as ge,drainBearings as be,hasRetainedReports as Re,readGoal as E,readYieldMessage as Ee,RUNTIME_RESTART_CONTINUATION as xe}from"./kickoff.js";import{readRoadmap as j}from"./roadmap.js";import{encodeKickoffOrigin as Se,KICKOFF_ORIGIN_ENV as Ce}from"./stamp/protocol.js";import{headlessBrokerHost as $e,brokerLaunchJson as Ne}from"./host.js";import{isCloudRole as Fe}from"../canvas/person.js";import{asNodePerson as Ae}from"../../daemon/reconcilers/node-lifecycle/node-person.js";import{processRunner as Oe}from"./runner/process.js";import{runnerForEnvironment as V}from"./runner/index.js";import{MachineLaunchUncertainError as Pe,MachineOfflineError as Be}from"./runner/machine/ops.js";import{blaxelEnvironmentOf as X}from"./blaxel-environment.js";import{recordLaunchFailure as De}from"../../daemon/reconcilers/node-lifecycle/launch-failure.js";import{acceptedRelease as Y,remoteMachineEnvironmentOf as Me,checkBilledWake as Te,EnvironmentImagePending as Le,imageForWake as ze}from"../../daemon/reconcilers/node-lifecycle/remote-wake.js";import{ExecutionCapReached as Ue,liveExecutionForNode as He,setExecutionLaunchJson as Ge}from"../canvas/executions.js";import{isDrained as Ke}from"../../daemon/drain.js";import{appMemoryFull as We,appMemoryTooSmall as Je,boundFleet as q,brokerCapReached as Q,brokerThresholdsForDaemon as P}from"./fleet.js";import{admitBrokerMemory as je}from"./memory-admission.js";import{beginBootFaultAttempt as Ve,clearFault as Xe,hasAdmittedTurn as Ye,readFault as qe}from"../canvas/recovery.js";import{clearNodeLedger as Z,forkNodeLedger as Qe,noticeChangedWhileDone as Ze}from"./ledger.js";import{forkSourceNode as Ie,rootOfSpine as er}from"./nodes.js";import{ApiError as rr}from"../../api/errors.js";import{grantSnapshotMissing as ir}from"../../daemon/readiness.js";import{checkStart as tr}from"./start-check.js";import{isReviewCompanionBound as nr}from"../review/companion.js";import{assertProfileActive as ar}from"../profiles/manifest.js";import{isPidAlive as or}from"../canvas/pid.js";import{crtrHome as sr,jobDir as lr}from"../canvas/paths.js";import{contextDir as cr,ensureNodeDirs as ur,nodeAgentDir as fr}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as wr}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as mr}from"../command-hooks/transport/exec-lifecycle.js";async function dr(e,r){const i=e.pi_session_file,t=i?_(i):null;if(t===null)return{};try{const a=await h().header(t);return r?{resumeSessionPath:a.path}:{resumeSessionPath:a.path,newCycle:!0}}catch(a){if(a instanceof H)return{};throw a}}s(dr,"resumeArgs");async function I(e){const r=_(e);if(r===null)return!1;try{return await h().header(r),!0}catch(i){if(i instanceof H)return!1;throw i}}s(I,"conversationExists");async function hr(e){const r=_(e);return r===null?!0:(await h().read(r)).entries.some(i=>i.type==="message")}s(hr,"sessionHasConversation");async function pr(e){const r=_(e);if(r===null)return!0;const i=(await h().header(r)).header.cwd;return typeof i!="string"||U(i)}s(pr,"sessionCwdExists");async function vr(e,r,i,t){const a=r.pi_session_file;if(!a||!await I(a))return{};const u=await fe(a);return u!==null&&u.node_id!==e&&or(u.pi_pid)?{}:t&&!await pr(a)?{}:await dr(r,i)}s(vr,"resumableSessionArgs");async function _r(e){return await j(e)===null&&await Ee(e)===null&&!await Re(e)}s(_r,"isUnstartedBirth");async function ci(e){const r=await E(e);if(r!==null&&r.trim()!=="")return!0;const i=await j(e);return i!==null&&i.trim()!==""}s(ci,"hasFreshGroundState");async function ui(e,r){const i=await ee(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}s(ui,"reviveNode");async function fi(e,r,i){return ee(e,{...r,viewerAttach:!0},i)}s(fi,"decideAttachRevive");async function ee(e,r,i){const t=Fe()?await R(e):null;return t===null?await re(e,r,i):await Ae({node_id:e,sub:t.sub??null,profile_id:t.profile_id??null},()=>re(e,r,i))}s(ee,"decideRevive");async function re(e,r,i){let t=await R(e);if(t===null)throw new Error(`reviveNode: unknown node ${e}`);if(t=await R(e)??t,t.run_id&&(await R(t.run_id))?.deleting_at)throw new Error("run is deleting");if(t.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${t.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);ar(t.profile_id),ce(t.grantee);const a=q(),u=t.grantee==="app:terminal"&&await ir();if(!await a.has(e)&&!await a.isClaimed(e)&&r.wakeReason!=="runtime-restart-abort"&&r.viewerAttach!==!0&&!(!i&&t.lifecycle==="resident"&&J(t))&&(await tr(t),await X(t.profile_id??null)!==null&&await Te(t)),await a.has(e))return await A(e,t.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(await a.isClaimed(e))return{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&t.lifecycle==="resident"&&J(t))return{parked:!0};if(u){if(r.capacity==="refuse")throw new rr(503,"runtime_starting","the first grant snapshot from the directory has not committed");return await ie(e,t,{reason:"grant_snapshot"}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}const k=await Ke();if(k&&r.capacity==="refuse")throw new Error(`the daemon is drained for an import; ${e} cannot start until it ends`);const p=!k&&await a.reserveSlot(e);let o=null;if(p){try{o=await je(e)}catch(w){throw a.releaseReservation(e),w}o.verdict!=="admitted"&&a.releaseReservation(e)}if(o?.verdict==="too_small")throw Je(o);if(!p||o?.verdict==="denied"){if(r.capacity==="refuse")throw o?.verdict==="denied"?We(o):Q(P().automaticReviveCap,await a.occupancy());return await ie(e,t,k?{reason:"drain"}:o?.verdict==="denied"?{reason:"app_memory",free_bytes:o.free_bytes,needed_bytes:o.needed_bytes}:{reason:"broker_cap",live_count:await a.occupancy(),hard_cap:P().automaticReviveCap}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await kr(e,t,r)}}finally{a.releaseReservation(e)}}s(re,"decideReviveAsCaller");async function ie(e,r,i){r.frozen_at==null&&(await ve(e,new Date().toISOString()),await he(e,{reason:"frozen"}),W({level:"info",event:"broker.frozen",node_id:e,fields:i}))}s(ie,"freezeForWait");async function yr(e,r){const i=await X(r.profile_id??null),t=await Me(r.profile_id??null)!==null;if(!(i!==null||t)&&!U(r.cwd)){const u=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new O({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:u})}return{blaxelEnvironment:i,remoteMachine:t}}s(yr,"assertRecordedCwdExists");async function kr(e,r,i){const{blaxelEnvironment:t,remoteMachine:a}=await yr(e,r),u=t!==null||a;r.host_kind!=="broker"&&(await N(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!await nr(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await G(e),r.pi_session_id=null,r.pi_session_file=null);const p=r.fork_from!=null&&r.pi_session_id==null,o=r.fork_source_file;if(p&&(typeof o!="string"||!ue(o)))throw new O({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const w=p&&typeof o=="string";if(w&&!await I(o))throw new O({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${o}`,next:`Restore ${o}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const{automaticReviveCap:te}=P();if(t!==null){const l=await ze(t);if(l.state==="pending")throw new Le(t,l.error)}const ne=t!==null?V("blaxel"):a?V("machine"):Oe;let f;try{f=await we(e,{daemonEpoch:q().epoch(),cap:te,...t!==null?{runner:"blaxel",release:Y()}:a?{runner:"machine",release:Y()}:{}})}catch(l){if(l instanceof Ue)throw Q(l.cap,l.live);if(l instanceof me)return{window:null,session:r.tmux_session??null,resumed:!1,outcome:"already-live"};throw l}try{r.cycles=(r.cycles??0)+1,await N(e,{cycles:r.cycles});const x=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,d=w?{}:await vr(e,r,x,!u),g=d.resumeSessionPath!==void 0,B=g&&d.newCycle===!0,v=g&&!B,D=!g&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&await _r(e),S=v&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!await Ye(e)&&!await hr(d.resumeSessionPath)?(await E(e))?.trim():void 0,M=S!==void 0&&S!=="",ae=w||D||M,T=r.cycle_pending===!0||B;await N(e,{cycle_pending:T}),r.cycle_pending=T,!w&&!g&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await G(e),r.pi_session_id=null,r.pi_session_file=null);let c,m=null;const C=s(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(w){const n=await E(e),$=_(o);if($===null)throw new Error(`invalid fork source ${o}`);const se=(await h().header(await h().fork($,e))).path;c=y(r,{resumeSessionPath:se,...n!=null&&n!==""?{prompt:n}:{}}),n!=null&&n!==""&&(m=C()),await Qe(e,(await Ie(o))?.node_id??null)}else if(M)c=y(r,{...d,prompt:S}),m=C();else if(v)c=y(r,{...d,...i.wakeReason==="runtime-restart-abort"?{prompt:xe}:{}}),i.wakeReason==="runtime-restart-abort"&&(m={type:"runtime",kind:"restart-continuation"});else if(D){const n=(await E(e))?.trim();c=y(r,n!==void 0&&n!==""?{prompt:n}:{}),n!==void 0&&n!==""&&(m=C()),await Z(e)}else{const n=await be(r,f);c=y(r,{...d,prompt:await ge(r,n,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&n.unreadMessageIds?.length&&(c.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(n.unreadMessageIds)),m=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},await Z(e)}c.env={...c.env,CRTR_SUBTREE:await er(e)},m!==null&&(c.env[Ce]=Se(m)),r.status==="done"&&r.last_settled_at!=null&&await Ze(e,r.last_settled_at),await A(e,r.intent==="refresh"?"refresh-launch":"revive"),(await qe(e))?.op!=="conversation_write_failed"&&await Xe(e,{preserveEpisode:!0});const L=await Ve(e);i.recovery!==!0&&i.viewerAttach!==!0&&await ye(e),await ur(e,r.grantee);const oe=wr(r.cwd,r.profile_id).plans.get("node:start")??[];await mr(oe,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:await fr(e),contextDir:await cr(e),jobDir:lr(e)},runtime:{isBirth:ae,canvasHome:sr(),profile:r.profile_id??null}}),c.env={...c.env,CRTR_EXECUTION_ID:f};const z=await Ne(e,c,r.cwd);await Ge(f,z);let b;try{const n=await He(e);if(b=await ne.start({executionId:f,nodeId:e,attempt:n?.attempt??1,sandboxName:n?.sandboxName??null,release:n?.release??"",launchJson:z,cwd:r.cwd,resuming:v}),!u&&b.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(n){if(n instanceof Be)throw n;try{await A(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:n instanceof Error?n.message:String(n)}}}),await ke(e,"notice.crash",`Child crashed \u2014 ${_e(r)} (${e}) failed to relaunch and is now dead: ${n.message}
1
+ var ce=Object.defineProperty;var s=(e,r)=>ce(e,"name",{value:r,configurable:!0});import{assertGranteeRunsHere as ue}from"./unsandboxed.js";import{existsSync as U}from"node:fs";import{conversationIdFromPath as _,conversationStore as h,ConversationNotFoundError as H}from"../conversation-store/index.js";import{isAbsolute as fe}from"node:path";import{findNodeBySessionFile as we,getNode as R,updateNode as N,beginExecution as me,ExecutionAlreadyLive as de,recordExecutionPid as he,abortExecution as F,endLiveExecution as pe,unbindSession as G,recordLaunch as ve,setFrozen as _e,clearFrozen as K,fullName as ye}from"../canvas/index.js";import{emitEvent as W}from"../events/emit.js";import{consumeDeadlineOnWake as ke}from"../feed/messages.js";import{isParked as J,transition as A}from"./lifecycle.js";import{publishNotice as ge}from"./close.js";import{InputError as O}from"../io.js";import{buildPiArgv as y}from"./launch.js";import{buildReviveKickoff as be,drainBearings as Re,hasRetainedReports as Ee,readGoal as E,readYieldMessage as xe,RUNTIME_RESTART_CONTINUATION as Se}from"./kickoff.js";import{readRoadmap as j}from"./roadmap.js";import{encodeKickoffOrigin as Ce,KICKOFF_ORIGIN_ENV as $e}from"./stamp/protocol.js";import{carryInitialInput as V}from"../runs/initial-input.js";import{headlessBrokerHost as Ne,brokerLaunchJson as Fe}from"./host.js";import{isCloudRole as Ae}from"../canvas/person.js";import{asNodePerson as Oe}from"../../daemon/reconcilers/node-lifecycle/node-person.js";import{processRunner as Pe}from"./runner/process.js";import{runnerForEnvironment as X}from"./runner/index.js";import{MachineLaunchUncertainError as Be,MachineOfflineError as De}from"./runner/machine/ops.js";import{blaxelEnvironmentOf as Y}from"./blaxel-environment.js";import{recordLaunchFailure as Me}from"../../daemon/reconcilers/node-lifecycle/launch-failure.js";import{acceptedRelease as q,remoteMachineEnvironmentOf as Te,checkBilledWake as Le,EnvironmentImagePending as ze,imageForWake as Ue}from"../../daemon/reconcilers/node-lifecycle/remote-wake.js";import{ExecutionCapReached as He,liveExecutionForNode as Ge,setExecutionLaunchJson as Ke}from"../canvas/executions.js";import{isDrained as We}from"../../daemon/drain.js";import{appMemoryFull as Je,appMemoryTooSmall as je,boundFleet as Q,brokerCapReached as Z,brokerThresholdsForDaemon as P}from"./fleet.js";import{admitBrokerMemory as Ve}from"./memory-admission.js";import{beginBootFaultAttempt as Xe,clearFault as Ye,hasAdmittedTurn as qe,readFault as Qe}from"../canvas/recovery.js";import{clearNodeLedger as I,forkNodeLedger as Ze,noticeChangedWhileDone as Ie}from"./ledger.js";import{forkSourceNode as er,rootOfSpine as rr}from"./nodes.js";import{ApiError as ir}from"../../api/errors.js";import{grantSnapshotMissing as tr}from"../../daemon/readiness.js";import{checkStart as nr}from"./start-check.js";import{isReviewCompanionBound as ar}from"../review/companion.js";import{assertProfileActive as or}from"../profiles/manifest.js";import{isPidAlive as sr}from"../canvas/pid.js";import{crtrHome as lr,jobDir as cr}from"../canvas/paths.js";import{contextDir as ur,ensureNodeDirs as fr,nodeAgentDir as wr}from"../canvas/node-agent-paths.js";import{discoverLifecycleHookRegistry as mr}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as dr}from"../command-hooks/transport/exec-lifecycle.js";async function hr(e,r){const i=e.pi_session_file,t=i?_(i):null;if(t===null)return{};try{const a=await h().header(t);return r?{resumeSessionPath:a.path}:{resumeSessionPath:a.path,newCycle:!0}}catch(a){if(a instanceof H)return{};throw a}}s(hr,"resumeArgs");async function ee(e){const r=_(e);if(r===null)return!1;try{return await h().header(r),!0}catch(i){if(i instanceof H)return!1;throw i}}s(ee,"conversationExists");async function pr(e){const r=_(e);return r===null?!0:(await h().read(r)).entries.some(i=>i.type==="message")}s(pr,"sessionHasConversation");async function vr(e){const r=_(e);if(r===null)return!0;const i=(await h().header(r)).header.cwd;return typeof i!="string"||U(i)}s(vr,"sessionCwdExists");async function _r(e,r,i,t){const a=r.pi_session_file;if(!a||!await ee(a))return{};const u=await we(a);return u!==null&&u.node_id!==e&&sr(u.pi_pid)?{}:t&&!await vr(a)?{}:await hr(r,i)}s(_r,"resumableSessionArgs");async function yr(e){return await j(e)===null&&await xe(e)===null&&!await Ee(e)}s(yr,"isUnstartedBirth");async function fi(e){const r=await E(e);if(r!==null&&r.trim()!=="")return!0;const i=await j(e);return i!==null&&i.trim()!==""}s(fi,"hasFreshGroundState");async function wi(e,r){const i=await re(e,r,!0);if(i.parked)throw new Error(`reviveNode: internal parked decision for activating revive ${e}`);return i.result}s(wi,"reviveNode");async function mi(e,r,i){return re(e,{...r,viewerAttach:!0},i)}s(mi,"decideAttachRevive");async function re(e,r,i){const t=Ae()?await R(e):null;return t===null?await ie(e,r,i):await Oe({node_id:e,sub:t.sub??null,profile_id:t.profile_id??null},()=>ie(e,r,i))}s(re,"decideRevive");async function ie(e,r,i){let t=await R(e);if(t===null)throw new Error(`reviveNode: unknown node ${e}`);if(t=await R(e)??t,t.run_id&&(await R(t.run_id))?.deleting_at)throw new Error("run is deleting");if(t.final_report!==null)throw new Error(`reviveNode: refusing to revive ${e} \u2014 its finalization latch is set (final_report=${t.final_report}). A final landed between the caller's reopen-gate check and this revive; re-run with --reopen if this retask is still wanted.`);or(t.profile_id),ue(t.grantee);const a=Q(),u=t.grantee==="app:terminal"&&await tr();if(!await a.has(e)&&!await a.isClaimed(e)&&r.wakeReason!=="runtime-restart-abort"&&r.viewerAttach!==!0&&!(!i&&t.lifecycle==="resident"&&J(t))&&(await nr(t),await Y(t.profile_id??null)!==null&&await Le(t)),await a.has(e))return await A(e,t.intent==="refresh"?"refresh-launch":"revive"),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(await a.isClaimed(e))return{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"already-live"}};if(!i&&t.lifecycle==="resident"&&J(t))return{parked:!0};if(u){if(r.capacity==="refuse")throw new ir(503,"runtime_starting","the first grant snapshot from the directory has not committed");return await te(e,t,{reason:"grant_snapshot"}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}const k=await We();if(k&&r.capacity==="refuse")throw new Error(`the daemon is drained for an import; ${e} cannot start until it ends`);const p=!k&&await a.reserveSlot(e);let o=null;if(p){try{o=await Ve(e)}catch(w){throw a.releaseReservation(e),w}o.verdict!=="admitted"&&a.releaseReservation(e)}if(o?.verdict==="too_small")throw je(o);if(!p||o?.verdict==="denied"){if(r.capacity==="refuse")throw o?.verdict==="denied"?Je(o):Z(P().automaticReviveCap,await a.occupancy());return await te(e,t,k?{reason:"drain"}:o?.verdict==="denied"?{reason:"app_memory",free_bytes:o.free_bytes,needed_bytes:o.needed_bytes}:{reason:"broker_cap",live_count:await a.occupancy(),hard_cap:P().automaticReviveCap}),{parked:!1,result:{window:null,session:t.tmux_session??null,resumed:!1,outcome:"frozen"}}}try{return{parked:!1,result:await gr(e,t,r)}}finally{a.releaseReservation(e)}}s(ie,"decideReviveAsCaller");async function te(e,r,i){r.frozen_at==null&&(await _e(e,new Date().toISOString()),await pe(e,{reason:"frozen"}),W({level:"info",event:"broker.frozen",node_id:e,fields:i}))}s(te,"freezeForWait");async function kr(e,r){const i=await Y(r.profile_id??null),t=await Te(r.profile_id??null)!==null;if(!(i!==null||t)&&!U(r.cwd)){const u=r.fork_from!=null?`Restore ${r.cwd}, then revive the fork normally with \`crtr node lifecycle revive ${e}\`.`:r.managed_worktree?.state==="open"?"Restore or reconcile the recorded checkout, then use the managed-worktree close or abandon recovery.":`Recreate ${r.cwd}, or repair it with \`crtr node config --cwd <path> --node ${e}\`; durable node work resumes in a fresh conversation. Then retry \`crtr node lifecycle revive ${e}\`.`;throw new O({error:"cwd_missing",message:`${e} cannot be revived \u2014 its recorded cwd no longer exists: ${r.cwd}`,next:u})}return{blaxelEnvironment:i,remoteMachine:t}}s(kr,"assertRecordedCwdExists");async function gr(e,r,i){const{blaxelEnvironment:t,remoteMachine:a}=await kr(e,r),u=t!==null||a;r.host_kind!=="broker"&&(await N(e,{host_kind:"broker"}),r.host_kind="broker"),r.review_binding!=null&&!await ar(e)&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await G(e),r.pi_session_id=null,r.pi_session_file=null);const p=r.fork_from!=null&&r.pi_session_id==null,o=r.fork_source_file;if(p&&(typeof o!="string"||!fe(o)))throw new O({error:"fork_source_invalid",message:`${e} cannot be revived \u2014 its resolved fork source file is missing or invalid.`,next:`Restore the node's fork source metadata, then retry \`crtr node lifecycle revive ${e}\`.`});const w=p&&typeof o=="string";if(w&&!await ee(o))throw new O({error:"fork_from_missing",message:`${e} cannot be revived \u2014 its fork source no longer exists: ${o}`,next:`Restore ${o}, or update the node's fork source, then retry \`crtr node lifecycle revive ${e}\`.`});const{automaticReviveCap:ne}=P();if(t!==null){const l=await Ue(t);if(l.state==="pending")throw new ze(t,l.error)}const ae=t!==null?X("blaxel"):a?X("machine"):Pe;let f;try{f=await me(e,{daemonEpoch:Q().epoch(),cap:ne,...t!==null?{runner:"blaxel",release:q()}:a?{runner:"machine",release:q()}:{}})}catch(l){if(l instanceof He)throw Z(l.cap,l.live);if(l instanceof de)return{window:null,session:r.tmux_session??null,resumed:!1,outcome:"already-live"};throw l}try{r.cycles=(r.cycles??0)+1,await N(e,{cycles:r.cycles});const x=r.intent==="refresh"||r.cycle_pending===!0?!1:i.resume,d=w?{}:await _r(e,r,x,!u),g=d.resumeSessionPath!==void 0,B=g&&d.newCycle===!0,v=g&&!B,D=!g&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&await yr(e),S=v&&(i.wakeReason===void 0||i.wakeReason==="runtime-restart-abort")&&!await qe(e)&&!await pr(d.resumeSessionPath)?(await E(e))?.trim():void 0,M=S!==void 0&&S!=="",oe=w||D||M,T=r.cycle_pending===!0||B;await N(e,{cycle_pending:T}),r.cycle_pending=T,!w&&!g&&(r.pi_session_id!=null||r.pi_session_file!=null)&&(await G(e),r.pi_session_id=null,r.pi_session_file=null);let c,m=null;const C=s(()=>({type:"runtime",kind:"kickoff",...r.spawned_by!=null&&r.spawned_by!==""?{spawnedBy:r.spawned_by}:{}}),"birthOrigin");if(w){const n=await E(e),$=_(o);if($===null)throw new Error(`invalid fork source ${o}`);const le=(await h().header(await h().fork($,e))).path;c=y(r,{resumeSessionPath:le,...n!=null&&n!==""?{prompt:n}:{}}),n!=null&&n!==""&&(m=C()),await Ze(e,(await er(o))?.node_id??null)}else if(M)c=y(r,{...d,prompt:S}),m=C(),await V(c,r);else if(v)c=y(r,{...d,...i.wakeReason==="runtime-restart-abort"?{prompt:Se}:{}}),i.wakeReason==="runtime-restart-abort"&&(m={type:"runtime",kind:"restart-continuation"});else if(D){const n=(await E(e))?.trim();c=y(r,n!==void 0&&n!==""?{prompt:n}:{}),n!==void 0&&n!==""&&(m=C(),await V(c,r)),await I(e)}else{const n=await Re(r,f);c=y(r,{...d,prompt:await be(r,n,i.wakeReason==="runtime-restart-abort"?void 0:i.wakeReason)}),r.run_id&&n.unreadMessageIds?.length&&(c.env.CRTR_KICKOFF_MESSAGE_IDS=JSON.stringify(n.unreadMessageIds)),m=i.wakeReason!==void 0&&i.wakeReason!=="runtime-restart-abort"?{type:"runtime",kind:"wake",wake:i.wakeReason}:{type:"runtime",kind:"revive",cycle:r.cycles??0},await I(e)}c.env={...c.env,CRTR_SUBTREE:await rr(e)},m!==null&&(c.env[$e]=Ce(m)),r.status==="done"&&r.last_settled_at!=null&&await Ie(e,r.last_settled_at),await A(e,r.intent==="refresh"?"refresh-launch":"revive"),(await Qe(e))?.op!=="conversation_write_failed"&&await Ye(e,{preserveEpisode:!0});const L=await Xe(e);i.recovery!==!0&&i.viewerAttach!==!0&&await ke(e),await fr(e,r.grantee);const se=mr(r.cwd,r.profile_id).plans.get("node:start")??[];await dr(se,{node:{id:e,name:r.name,kind:r.kind,mode:r.mode,lifecycle:r.lifecycle,cwd:r.cwd,nodeDir:await wr(e),contextDir:await ur(e),jobDir:cr(e)},runtime:{isBirth:oe,canvasHome:lr(),profile:r.profile_id??null}}),c.env={...c.env,CRTR_EXECUTION_ID:f};const z=await Fe(e,c,r.cwd);await Ke(f,z);let b;try{const n=await Ge(e);if(b=await ae.start({executionId:f,nodeId:e,attempt:n?.attempt??1,sandboxName:n?.sandboxName??null,release:n?.release??"",launchJson:z,cwd:r.cwd,resuming:v}),!u&&b.pid===null)throw new Error(`broker host returned no pid for ${e}`)}catch(n){if(n instanceof De)throw n;try{await A(e,"crash",{reason:"launch_failed",outcome:{detail:{schema:"crtr.node-outcome-detail/v1",message:n instanceof Error?n.message:String(n)}}}),await ge(e,"notice.crash",`Child crashed \u2014 ${ye(r)} (${e}) failed to relaunch and is now dead: ${n.message}
2
2
 
3
- It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw await F(f,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${n.message}`)}if(await pe(e,new Date().toISOString()),u)return await K(e),{window:null,session:r.tmux_session??null,resumed:v,faultExplanation:L.explanation,outcome:"launched"};if(await de(f,e,b.pid)==="shutdown")throw await F(f,{reason:"superseded"}),$e.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return await K(e),{window:null,session:r.tmux_session??null,resumed:v,launch:b,faultExplanation:L.explanation,outcome:"launched"}}catch(l){throw l instanceof Pe||(await F(f,{reason:"launch_failed"}),await De(f,l).catch(x=>{W({level:"warn",event:"broker.launch_failure.unrecorded",node_id:e,error:x})})),l}}s(kr,"launchRevive");export{yr as assertRecordedCwdExists,fi as decideAttachRevive,ci as hasFreshGroundState,dr as resumeArgs,ui as reviveNode};
3
+ It stays dead until you revive it \u2014 \`crtr node lifecycle revive ${e}\`.`,{reason:"child-crashed",child:e})}catch{}throw await F(f,{reason:"launch_failed"}),new Error(`[broker] refusing to revive ${e}: ${n.message}`)}if(await ve(e,new Date().toISOString()),u)return await K(e),{window:null,session:r.tmux_session??null,resumed:v,faultExplanation:L.explanation,outcome:"launched"};if(await he(f,e,b.pid)==="shutdown")throw await F(f,{reason:"superseded"}),Ne.teardown(e),new Error(`[broker] refusing to revive ${e}: execution was superseded during launch`);return await K(e),{window:null,session:r.tmux_session??null,resumed:v,launch:b,faultExplanation:L.explanation,outcome:"launched"}}catch(l){throw l instanceof Be||(await F(f,{reason:"launch_failed"}),await Me(f,l).catch(x=>{W({level:"warn",event:"broker.launch_failure.unrecorded",node_id:e,error:x})})),l}}s(gr,"launchRevive");export{kr as assertRecordedCwdExists,mi as decideAttachRevive,fi as hasFreshGroundState,hr as resumeArgs,wi as reviveNode};
@@ -1,3 +1,3 @@
1
- var me=Object.defineProperty;var d=(e,o)=>me(e,"name",{value:o,configurable:!0});import{accessSync as he,constants as pe}from"node:fs";import{isAbsolute as _e,resolve as ke,join as ve}from"node:path";import{appSpace as ge}from"../layout.js";import{ApiError as ne}from"../../api/errors.js";import{spawnNode as ye,forkSourceNode as be,currentNodeContext as xe,rootOfSpine as $e,newNodeId as Se,preflightNodeId as Ce}from"./nodes.js";import{resolveProfileOperand as Ee}from"../profiles/manifest.js";import{selectProfileForCwd as Re,selectProfileForCwdReadOnly as Oe}from"../profiles/select.js";import{buildLaunchSpecAsync as oe,buildPiArgv as Fe}from"./launch.js";import{runsOnBlaxel as Ie,sandboxWorktreeRequest as Pe}from"../../daemon/api/environment-of.js";import{createManagedWorktreeAsync as Be,rollbackManagedWorktreeAsync as De}from"../worktree-mutation-async.js";import{usage as w,brokerLaunchFailed as D}from"../errors.js";import{writeGoal as Ne}from"./kickoff.js";import{appendSituationalContext as Le,formatSituationalProse as Me}from"./situational-context.js";import{ensureRoadmap as Te}from"./roadmap.js";import{buildWakeBearings as Ae}from"./bearings.js";import{encodeKickoffOrigin as We,KICKOFF_ORIGIN_ENV as je}from"./stamp/protocol.js";import{ExecutionCapReached as ze,beginExecution as He,crtrHome as Ke,getNode as u,fullName as re,jobDir as Ge,recordExecutionPid as Ue,abortExecution as j,setFrozen as z}from"../canvas/index.js";import{contextDir as H,nodeAgentDir as K}from"../canvas/node-agent-paths.js";import{copyTreeBeneath as Ve}from"../spaces/open-beneath.js";import{conversationIdFromPath as N,conversationStore as S}from"../conversation-store/index.js";import{appMemoryTooSmall as Xe,boundFleet as qe,brokerThresholdsForDaemon as te}from"./fleet.js";import{admitBrokerMemory as Ye}from"./memory-admission.js";import{emitEvent as L}from"../events/emit.js";import{blaxelEnvironmentOf as Je}from"./blaxel-environment.js";import{openViewerWindow as Qe,focusOf as Ze,windowOfPane as en}from"./placement.js";import{awaitBrokerViewSocket as nn}from"./view-socket.js";import{readLogSnapshot as on}from"../events/read.js";import{transition as k}from"./lifecycle.js";import{beginBootFaultAttempt as rn}from"../canvas/recovery.js";import{headlessBrokerHost as G}from"./host.js";import{ensureDaemon as tn}from"../../daemon/manage.js";import{readConfig as an}from"../config.js";import{modelRequestFromConfig as dn,unregisteredConcreteModel as sn,unroutableConcreteModel as ln}from"../model-routes.js";import{openModelRegistry as cn}from"./model-registry.js";import{MODEL_SPEC_NEXT as un}from"./model-selection.js";import{forkNodeLedger as fn}from"./ledger.js";import{appRungForGrant as wn}from"../llm/app-rungs.js";import{APP_FAMILY as U}from"../model-routes.js";import{discoverLifecycleHookRegistry as mn}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as hn}from"../command-hooks/transport/exec-lifecycle.js";import{someAsync as pn}from"../async-array.js";import{checkStart as _n}from"./start-check.js";function v(e){return{detail:{schema:"crtr.node-outcome-detail/v1",message:e instanceof Error?e.message:String(e)}}}d(v,"launchFailureOutcome");async function kn(e,o,a){try{return await G.launch(e,o,a)}catch(r){try{await k(e,"crash",{reason:"launch_failed",outcome:v(r)})}catch{}const t=r.message;throw D(t.startsWith("[broker]")?t:`[broker] refusing to launch ${e}: ${t}`,"Inspect the cause above, fix the launch recipe or environment, then retry.")}}d(kn,"launchBrokerOrCrash");function vn(e){return on({nodeId:e,component:"broker",pattern:"broker.startup."},!1).items.flatMap(a=>a.kind==="event"&&a.envelope.event.startsWith("broker.startup.")&&a.envelope.event!=="broker.startup.warning"?[a.envelope.event.slice(15)]:[]).at(-1)??null}d(vn,"lastStartupStep");function gn(e,o){const a=vn(e),r=a===null?"it had logged no startup step":`the last startup step it logged was \`${a}\``;if(o.reason==="exited"){const{code:t,signal:s}=o.status;return{text:`exited (code ${t??"null"}, signal ${s??"null"}) before binding its view socket; ${r}`,fields:{reason:"exited",code:t,signal:s,last_startup_step:a}}}return{text:`was still starting after ${Math.round(o.waitedMs/1e3)} s without binding its view socket, so the launcher stopped it; ${r}`,fields:{reason:"timeout",waited_ms:o.waitedMs,last_startup_step:a}}}d(gn,"notReadyDescription");async function yn(e,o){try{await k(e,"crash",{reason:"launch_failed",outcome:v(o)})}catch{}try{G.teardown(e)}catch{}}d(yn,"crashAndTearDown");async function bn(e){const o=(await S().list()).map(({conversation_id:t,path:s})=>({id:t,path:s})),a=o.filter(t=>t.id===e),r=a.length>0?a:o.filter(t=>t.id.startsWith(e));if(r.length===0)throw w(`--fork-from '${e}': no pi session with that id found.`,{next:"Pass a live node id, a real session uuid/prefix, or an absolute .jsonl session path."});if(r.length>1)throw w(`--fork-from '${e}': ambiguous \u2014 ${r.length} sessions match that id prefix; pass the full id or the .jsonl path.`,{next:"Pass the full session uuid or an absolute .jsonl path to disambiguate."});return r[0].path}d(bn,"resolveSessionUuid");async function ie(e){const o=e.trim();if(o==="")throw w("--fork-from requires a node id, session file, or session uuid.",{next:"Pass a live node id, an absolute .jsonl session path, or a session uuid/prefix."});if(o.includes("/")||o.endsWith(".jsonl")){const r=N(_e(o)?o:ke(process.cwd(),o));if(r===null)throw w(`--fork-from '${o}': invalid conversation path.`,{next:"Pass a conversation file path or id."});return(await S().header(r)).path}const a=await u(o);if(a!==null){const r=a.pi_session_file;if(r==null||r==="")throw w(`node ${o} has no pi session yet \u2014 it has not captured a session FILE to fork from (the broker forks a .jsonl path, not a bare id).`,{next:"Pick a node that has captured a session, or drop --fork-from."});const t=N(r);if(t===null)throw w(`node ${o} has no valid conversation id.`,{next:"Pick a node with a captured conversation."});return(await S().header(t)).path}return await bn(o)}d(ie,"resolveForkSource");async function xn(e,o,a,r={}){return e===null?null:e!==void 0&&e!==""?Ee(e).profileId:o!==null?(await u(o))?.profile_id??null:r.readOnlySelection===!0?Oe(a):Re(a)}d(xn,"resolveProfileId");function ae(e){const o=e.modelIntent===void 0?void 0:`${e.modelIntent.family}/${e.modelIntent.strength}`;return dn(e.model,o,e.modelExact===!0)}d(ae,"requestFromLaunch");async function de(e,o,a){const r=a??(e.modelIntent?`${e.modelIntent.family}/${e.modelIntent.strength}`:e.model),t=await wn(o,r,e.modelExact===!0);if(t===null)return e;const{CRTR_MODEL_INTENT:s,...l}=e.env??{};return{...e,model:t.model,modelIntent:{family:U,strength:t.strength},modelExact:void 0,env:{...l,CRTR_MODEL_INTENT:`${U}/${t.strength}`}}}d(de,"appPaidLaunch");async function se(e,o,a){if(e===void 0||e.kind==="logical"&&e.family!==U)return;const r=await sn(e.kind==="logical"?{kind:"concrete",spec:e.spec}:e,a??await cn(),o);if(r!==void 0)throw w(`model '${r.request.spec}' is not registered in this daemon.`,{received:r.request.spec,next:un});const t=await ln(e,o);if(t!==void 0)throw w(`model '${t.spec}' is registered but no configured model route offers it, so no turn could be served.`,{received:t.spec,next:"Pick a model from your configured routes (`crtr sys config get modelLadders`), or add this one to a ladder or route."})}d(se,"assertLaunchModelRegistered");function $n(e,o,a){const r=e??o??null;if(!a&&r===null)throw new Error("spawnChild requires a calling node (CRTR_NODE_ID) or an explicit parent");return r}d($n,"resolveSpawner");async function le(e,o,a){try{tn()}catch{}const r=xe(),t=e.root===!0,s=$n(e.parent,r.nodeId??null,t),l=e.mode??"base",g=e.rootLifecycle??(t?"resident":"terminal"),ce=t&&g==="resident",R=s===null?null:await u(s),V=e.creator===void 0||e.creator===null?null:await u(e.creator);e.nodeId!==void 0&&await Ce(e.nodeId);const y=e.forkFrom!==void 0?await ie(e.forkFrom):void 0,M=y===void 0?null:await be(y),T=await pn([R,V,M],async f=>f?.isolated===!0||f?.run_id!=null&&(await u(f.run_id))?.isolated===!0);if(T&&e.isolated===!1)throw new ne(403,"isolated_run","isolation cannot be turned off under an isolated run",{param:"isolated"});const O=T||e.isolated===!0;if(O&&e.worktree===!0)throw new ne(400,"invalid_request","isolated nodes cannot use a worktree",{param:"worktree"});const C=e.worktree===!0,m=O||C?e.nodeId??await Se():e.nodeId,F=O?e.run!==void 0&&t&&!T?e.cwd:ve(ge(e.run?.grantee??R?.grantee??"app:terminal").nodes,m):e.cwd,h=await xn(e.profile,s,F),X=e.run?.grant_id??R?.grant_id??null,E=e.run??V??R??M,q=await de((await oe(e.kind,l,{lifecycle:g,hasManager:!t,model:e.model,modelOverride:e.model!==void 0,cwd:F,profileId:h,app:E?.grantee??"app:terminal"})).launch,X,e.model);await se(ae(q),{cwd:F,profileId:h}),await _n({grantee:E?.grantee??"app:terminal",sub:E?.sub,grant_id:E?.grant_id});const Y=M?.node_id??null;let b,I=F;const A=C&&await Ie(h)?await Pe(h,e.worktreeCwd??e.cwd,m,e.worktreeBase):void 0;try{C&&A===void 0&&(b=await Be(e.worktreeCwd??e.cwd,m,e.worktreeBase),I=b.path);const f=C?await de((await oe(e.kind,l,{lifecycle:g,hasManager:!t,model:e.model,modelOverride:e.model!==void 0,cwd:I,profileId:h,app:E?.grantee??"app:terminal"})).launch,X,e.model):q;A!==void 0&&(f.worktree=A),C&&await se(ae(f),{cwd:I,profileId:h});const n=await ye({kind:e.kind,mode:l,lifecycle:g,cwd:I,name:e.name??e.kind,description:e.description,parent:t?null:s,creator:e.creator,...e.actor===void 0?{}:{actor:e.actor},forkSourceNode:Y,spawnedBy:t?s??void 0:void 0,profile_id:h,...e.scopes===void 0?{}:{scopes:e.scopes},isolated:O,...e.run===void 0?{}:{run:e.run},...e.causedBy===void 0?{}:{causedBy:e.causedBy},...e.withinCreate===void 0?{}:{withinCreate:e.withinCreate},forkFrom:e.forkFrom,forkSourceFile:y,modelOverride:e.model===void 0?void 0:f.model,...b!==void 0?{managedWorktree:b}:{},...m!==void 0?{nodeId:m}:{},...e.reviewBinding===void 0?{}:{reviewBinding:e.reviewBinding},...e.deadlineAt===void 0?{}:{deadlineAt:e.deadlineAt},...e.outcomeDelivery===void 0?{}:{outcomeDelivery:e.outcomeDelivery},launch:f});if(a?.(n.node_id),e.forkFrom!==void 0)try{await fn(n.node_id,Y??null)}catch(i){throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(i)}),i}if(await Ne(n.node_id,e.prompt),e.situationalContext!==void 0&&await Le(n.node_id,Me(e.situationalContext)),o!==void 0)try{o(n.node_id)}catch(i){throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(i)}),i}const W=e.prompt!=="",ue=`${W&&e.wakeOrigin!==void 0?`${Ae(e.wakeOrigin)}
1
+ var me=Object.defineProperty;var d=(e,o)=>me(e,"name",{value:o,configurable:!0});import{accessSync as he,constants as pe}from"node:fs";import{isAbsolute as _e,resolve as ke,join as ve}from"node:path";import{appSpace as ge}from"../layout.js";import{ApiError as ne}from"../../api/errors.js";import{spawnNode as ye,forkSourceNode as be,currentNodeContext as xe,rootOfSpine as $e,newNodeId as Se,preflightNodeId as Ce}from"./nodes.js";import{resolveProfileOperand as Ee}from"../profiles/manifest.js";import{selectProfileForCwd as Re,selectProfileForCwdReadOnly as Oe}from"../profiles/select.js";import{buildLaunchSpecAsync as oe,buildPiArgv as Ie}from"./launch.js";import{runsOnBlaxel as Fe,sandboxWorktreeRequest as Pe}from"../../daemon/api/environment-of.js";import{createManagedWorktreeAsync as Be,rollbackManagedWorktreeAsync as De}from"../worktree-mutation-async.js";import{usage as w,brokerLaunchFailed as D}from"../errors.js";import{writeGoal as Ne}from"./kickoff.js";import{appendSituationalContext as Le,formatSituationalProse as Me}from"./situational-context.js";import{ensureRoadmap as Te}from"./roadmap.js";import{buildWakeBearings as Ae}from"./bearings.js";import{encodeKickoffOrigin as We,KICKOFF_ORIGIN_ENV as je}from"./stamp/protocol.js";import{carryInitialInput as ze}from"../runs/initial-input.js";import{ExecutionCapReached as He,beginExecution as Ke,crtrHome as Ge,getNode as u,fullName as re,jobDir as Ue,recordExecutionPid as Ve,abortExecution as j,setFrozen as z}from"../canvas/index.js";import{contextDir as H,nodeAgentDir as K}from"../canvas/node-agent-paths.js";import{copyTreeBeneath as Xe}from"../spaces/open-beneath.js";import{conversationIdFromPath as N,conversationStore as S}from"../conversation-store/index.js";import{appMemoryTooSmall as qe,boundFleet as Ye,brokerThresholdsForDaemon as te}from"./fleet.js";import{admitBrokerMemory as Je}from"./memory-admission.js";import{emitEvent as L}from"../events/emit.js";import{blaxelEnvironmentOf as Qe}from"./blaxel-environment.js";import{openViewerWindow as Ze,focusOf as en,windowOfPane as nn}from"./placement.js";import{awaitBrokerViewSocket as on}from"./view-socket.js";import{readLogSnapshot as rn}from"../events/read.js";import{transition as v}from"./lifecycle.js";import{beginBootFaultAttempt as tn}from"../canvas/recovery.js";import{headlessBrokerHost as G}from"./host.js";import{ensureDaemon as an}from"../../daemon/manage.js";import{readConfig as dn}from"../config.js";import{modelRequestFromConfig as sn,unregisteredConcreteModel as ln,unroutableConcreteModel as cn}from"../model-routes.js";import{openModelRegistry as un}from"./model-registry.js";import{MODEL_SPEC_NEXT as fn}from"./model-selection.js";import{forkNodeLedger as wn}from"./ledger.js";import{appRungForGrant as mn}from"../llm/app-rungs.js";import{APP_FAMILY as U}from"../model-routes.js";import{discoverLifecycleHookRegistry as hn}from"../command-hooks/discovery.js";import{invokeLifecycleHooks as pn}from"../command-hooks/transport/exec-lifecycle.js";import{someAsync as _n}from"../async-array.js";import{checkStart as kn}from"./start-check.js";function g(e){return{detail:{schema:"crtr.node-outcome-detail/v1",message:e instanceof Error?e.message:String(e)}}}d(g,"launchFailureOutcome");async function vn(e,o,a){try{return await G.launch(e,o,a)}catch(r){try{await v(e,"crash",{reason:"launch_failed",outcome:g(r)})}catch{}const t=r.message;throw D(t.startsWith("[broker]")?t:`[broker] refusing to launch ${e}: ${t}`,"Inspect the cause above, fix the launch recipe or environment, then retry.")}}d(vn,"launchBrokerOrCrash");function gn(e){return rn({nodeId:e,component:"broker",pattern:"broker.startup."},!1).items.flatMap(a=>a.kind==="event"&&a.envelope.event.startsWith("broker.startup.")&&a.envelope.event!=="broker.startup.warning"?[a.envelope.event.slice(15)]:[]).at(-1)??null}d(gn,"lastStartupStep");function yn(e,o){const a=gn(e),r=a===null?"it had logged no startup step":`the last startup step it logged was \`${a}\``;if(o.reason==="exited"){const{code:t,signal:s}=o.status;return{text:`exited (code ${t??"null"}, signal ${s??"null"}) before binding its view socket; ${r}`,fields:{reason:"exited",code:t,signal:s,last_startup_step:a}}}return{text:`was still starting after ${Math.round(o.waitedMs/1e3)} s without binding its view socket, so the launcher stopped it; ${r}`,fields:{reason:"timeout",waited_ms:o.waitedMs,last_startup_step:a}}}d(yn,"notReadyDescription");async function bn(e,o){try{await v(e,"crash",{reason:"launch_failed",outcome:g(o)})}catch{}try{G.teardown(e)}catch{}}d(bn,"crashAndTearDown");async function xn(e){const o=(await S().list()).map(({conversation_id:t,path:s})=>({id:t,path:s})),a=o.filter(t=>t.id===e),r=a.length>0?a:o.filter(t=>t.id.startsWith(e));if(r.length===0)throw w(`--fork-from '${e}': no pi session with that id found.`,{next:"Pass a live node id, a real session uuid/prefix, or an absolute .jsonl session path."});if(r.length>1)throw w(`--fork-from '${e}': ambiguous \u2014 ${r.length} sessions match that id prefix; pass the full id or the .jsonl path.`,{next:"Pass the full session uuid or an absolute .jsonl path to disambiguate."});return r[0].path}d(xn,"resolveSessionUuid");async function ie(e){const o=e.trim();if(o==="")throw w("--fork-from requires a node id, session file, or session uuid.",{next:"Pass a live node id, an absolute .jsonl session path, or a session uuid/prefix."});if(o.includes("/")||o.endsWith(".jsonl")){const r=N(_e(o)?o:ke(process.cwd(),o));if(r===null)throw w(`--fork-from '${o}': invalid conversation path.`,{next:"Pass a conversation file path or id."});return(await S().header(r)).path}const a=await u(o);if(a!==null){const r=a.pi_session_file;if(r==null||r==="")throw w(`node ${o} has no pi session yet \u2014 it has not captured a session FILE to fork from (the broker forks a .jsonl path, not a bare id).`,{next:"Pick a node that has captured a session, or drop --fork-from."});const t=N(r);if(t===null)throw w(`node ${o} has no valid conversation id.`,{next:"Pick a node with a captured conversation."});return(await S().header(t)).path}return await xn(o)}d(ie,"resolveForkSource");async function $n(e,o,a,r={}){return e===null?null:e!==void 0&&e!==""?Ee(e).profileId:o!==null?(await u(o))?.profile_id??null:r.readOnlySelection===!0?Oe(a):Re(a)}d($n,"resolveProfileId");function ae(e){const o=e.modelIntent===void 0?void 0:`${e.modelIntent.family}/${e.modelIntent.strength}`;return sn(e.model,o,e.modelExact===!0)}d(ae,"requestFromLaunch");async function de(e,o,a){const r=a??(e.modelIntent?`${e.modelIntent.family}/${e.modelIntent.strength}`:e.model),t=await mn(o,r,e.modelExact===!0);if(t===null)return e;const{CRTR_MODEL_INTENT:s,...l}=e.env??{};return{...e,model:t.model,modelIntent:{family:U,strength:t.strength},modelExact:void 0,env:{...l,CRTR_MODEL_INTENT:`${U}/${t.strength}`}}}d(de,"appPaidLaunch");async function se(e,o,a){if(e===void 0||e.kind==="logical"&&e.family!==U)return;const r=await ln(e.kind==="logical"?{kind:"concrete",spec:e.spec}:e,a??await un(),o);if(r!==void 0)throw w(`model '${r.request.spec}' is not registered in this daemon.`,{received:r.request.spec,next:fn});const t=await cn(e,o);if(t!==void 0)throw w(`model '${t.spec}' is registered but no configured model route offers it, so no turn could be served.`,{received:t.spec,next:"Pick a model from your configured routes (`crtr sys config get modelLadders`), or add this one to a ladder or route."})}d(se,"assertLaunchModelRegistered");function Sn(e,o,a){const r=e??o??null;if(!a&&r===null)throw new Error("spawnChild requires a calling node (CRTR_NODE_ID) or an explicit parent");return r}d(Sn,"resolveSpawner");async function le(e,o,a){try{an()}catch{}const r=xe(),t=e.root===!0,s=Sn(e.parent,r.nodeId??null,t),l=e.mode??"base",y=e.rootLifecycle??(t?"resident":"terminal"),ce=t&&y==="resident",R=s===null?null:await u(s),V=e.creator===void 0||e.creator===null?null:await u(e.creator);e.nodeId!==void 0&&await Ce(e.nodeId);const b=e.forkFrom!==void 0?await ie(e.forkFrom):void 0,M=b===void 0?null:await be(b),T=await _n([R,V,M],async f=>f?.isolated===!0||f?.run_id!=null&&(await u(f.run_id))?.isolated===!0);if(T&&e.isolated===!1)throw new ne(403,"isolated_run","isolation cannot be turned off under an isolated run",{param:"isolated"});const O=T||e.isolated===!0;if(O&&e.worktree===!0)throw new ne(400,"invalid_request","isolated nodes cannot use a worktree",{param:"worktree"});const C=e.worktree===!0,m=O||C?e.nodeId??await Se():e.nodeId,I=O?e.run!==void 0&&t&&!T?e.cwd:ve(ge(e.run?.grantee??R?.grantee??"app:terminal").nodes,m):e.cwd,h=await $n(e.profile,s,I),X=e.run?.grant_id??R?.grant_id??null,E=e.run??V??R??M,q=await de((await oe(e.kind,l,{lifecycle:y,hasManager:!t,model:e.model,modelOverride:e.model!==void 0,cwd:I,profileId:h,app:E?.grantee??"app:terminal"})).launch,X,e.model);await se(ae(q),{cwd:I,profileId:h}),await kn({grantee:E?.grantee??"app:terminal",sub:E?.sub,grant_id:E?.grant_id});const Y=M?.node_id??null;let x,F=I;const A=C&&await Fe(h)?await Pe(h,e.worktreeCwd??e.cwd,m,e.worktreeBase):void 0;try{C&&A===void 0&&(x=await Be(e.worktreeCwd??e.cwd,m,e.worktreeBase),F=x.path);const f=C?await de((await oe(e.kind,l,{lifecycle:y,hasManager:!t,model:e.model,modelOverride:e.model!==void 0,cwd:F,profileId:h,app:E?.grantee??"app:terminal"})).launch,X,e.model):q;A!==void 0&&(f.worktree=A),C&&await se(ae(f),{cwd:F,profileId:h});const n=await ye({kind:e.kind,mode:l,lifecycle:y,cwd:F,name:e.name??e.kind,description:e.description,parent:t?null:s,creator:e.creator,...e.actor===void 0?{}:{actor:e.actor},forkSourceNode:Y,spawnedBy:t?s??void 0:void 0,profile_id:h,...e.scopes===void 0?{}:{scopes:e.scopes},isolated:O,...e.run===void 0?{}:{run:e.run},...e.causedBy===void 0?{}:{causedBy:e.causedBy},...e.withinCreate===void 0?{}:{withinCreate:e.withinCreate},forkFrom:e.forkFrom,forkSourceFile:b,modelOverride:e.model===void 0?void 0:f.model,...x!==void 0?{managedWorktree:x}:{},...m!==void 0?{nodeId:m}:{},...e.reviewBinding===void 0?{}:{reviewBinding:e.reviewBinding},...e.deadlineAt===void 0?{}:{deadlineAt:e.deadlineAt},...e.outcomeDelivery===void 0?{}:{outcomeDelivery:e.outcomeDelivery},launch:f});if(a?.(n.node_id),e.forkFrom!==void 0)try{await wn(n.node_id,Y??null)}catch(i){throw await v(n.node_id,"crash",{reason:"launch_failed",outcome:g(i)}),i}if(await Ne(n.node_id,e.prompt),e.situationalContext!==void 0&&await Le(n.node_id,Me(e.situationalContext)),o!==void 0)try{o(n.node_id)}catch(i){throw await v(n.node_id,"crash",{reason:"launch_failed",outcome:g(i)}),i}const W=e.prompt!=="",ue=`${W&&e.wakeOrigin!==void 0?`${Ae(e.wakeOrigin)}
2
2
 
3
- `:""}${e.prompt}`;l==="orchestrator"&&await Te(n.node_id,{goal:e.prompt.trim()}),await Sn(n);let J;if(y!==void 0)try{const i=N(y);if(i===null)throw new Error(`invalid fork source ${y}`);const c=await S().fork(i,n.node_id);J=(await S().header(c)).path}catch(i){throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(i)}),i}const x=Fe(n,{prompt:ue,resumeSessionPath:J});if(x.env={...x.env,CRTR_SUBTREE:await $e(n.node_id)},W){const i=e.wakeOrigin!==void 0?{type:"runtime",kind:"wake",wake:e.wakeOrigin}:{type:"runtime",kind:"kickoff",...n.spawned_by!=null&&n.spawned_by!==""?{spawnedBy:n.spawned_by}:{}};x.env[je]=We(i)}const p=qe();if(await Je(n.profile_id??null)!==null)return await z(n.node_id,new Date().toISOString()),L({level:"info",event:"broker.frozen",node_id:n.node_id,fields:{reason:"remote_environment"}}),{node:await u(n.node_id),frozen:!0,window:null,session:null};const Q=await p.reserveSlot(n.node_id),_=Q?await Ye(n.node_id):null;if(_?.verdict==="too_small"){p.releaseReservation(n.node_id);const i=Xe(_);throw await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(i)}),i}if(!Q||_?.verdict==="denied")return _?.verdict==="denied"&&p.releaseReservation(n.node_id),await z(n.node_id,new Date().toISOString()),L({level:"info",event:"broker.frozen",node_id:n.node_id,fields:_?.verdict==="denied"?{reason:"app_memory",free_bytes:_.free_bytes,needed_bytes:_.needed_bytes}:{reason:"broker_cap",live_count:await p.occupancy(),hard_cap:te().automaticReviveCap}}),{node:await u(n.node_id),frozen:!0,window:null,session:null};const fe=await rn(n.node_id),{automaticReviveCap:we}=te();let $;try{$=await He(n.node_id,{daemonEpoch:p.epoch(),cap:we})}catch(i){if(!(i instanceof ze))throw i;return p.releaseReservation(n.node_id),await z(n.node_id,new Date().toISOString()),L({level:"info",event:"broker.frozen",node_id:n.node_id,fields:{reason:"broker_cap",live_count:i.live,hard_cap:i.cap}}),{node:await u(n.node_id),frozen:!0,window:null,session:null}}x.env={...x.env,CRTR_EXECUTION_ID:$};let P;try{P=await kn(n.node_id,x,{cwd:n.cwd,name:re(n),resuming:!1})}catch(i){throw await j($,{reason:"launch_failed"}),i}finally{p.releaseReservation(n.node_id)}if(P.pid===null){const i=new Error(`broker host returned no pid for ${n.node_id}`);throw await j($,{reason:"launch_failed"}),await k(n.node_id,"crash",{reason:"launch_failed",outcome:v(i)}),D(`failed to launch the broker engine for ${n.node_id} (${n.name}) \u2014 the node was not started.`,`Inspect canonical event diagnostics with \`crtr sys logs --node ${n.node_id}\`, then verify the pi engine can start in this environment.`)}if(await Ue($,n.node_id,P.pid)==="shutdown")throw await j($,{reason:"superseded"}),G.teardown(n.node_id),D(`broker execution was superseded for ${n.node_id}`,"Retry spawning the node.");if(ce&&e.readiness==="await"){const i=await nn(n.node_id,P.exited);if(!i.ready){const c=gn(n.node_id,i);L({level:"error",event:"broker.launch.not_ready",node_id:n.node_id,fields:c.fields});const B=await fe.explanation();throw await yn(n.node_id,new Error(B??c.text)),D(B!==null?`the root broker ${n.node_id} could not start \u2014 ${B}`:`the root broker ${n.node_id} ${c.text} \u2014 it was not started.`,B!==null?"Resolve the cause named above, then retry.":`Inspect canonical event diagnostics with \`crtr sys logs --node ${n.node_id}\`, then retry.`)}}let Z=null,ee=null;if(!t&&s!==null&&an("user").auto_open_child_viewers)try{const i=await Ze(s);if(i!==null&&i.pane!==null&&i.pane!==""){const c=await Qe(n.node_id,i.session??"",{name:re(n),cwd:e.cwd,besidePane:i.pane,waitForBroker:!0,initialWhip:W});c!==null&&c.pane!==null&&c.pane!==""&&(ee=c.session,Z=en(c.pane))}}catch{}return{node:await u(n.node_id),frozen:!1,window:Z,session:ee}}catch(f){throw b!==void 0&&m!==void 0&&await u(m)===null&&await De(b),f}}d(le,"spawnChildPrepared");async function vo(e){return le(e)}d(vo,"spawnChild");async function Sn(e){const o=mn(e.cwd,e.profile_id).plans.get("node:start")??[];await hn(o,{node:{id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,cwd:e.cwd,nodeDir:await K(e.node_id),contextDir:await H(e.node_id),jobDir:Ge(e.node_id)},runtime:{isBirth:!0,canvasHome:Ke(),profile:e.profile_id??null}})}d(Sn,"invokeBirthLifecycleHooks");const Cn=new Set(["initial-prompt.md","yield-message.md","result.json","jobs"]);async function go(e,o,a){const r=await u(e);if(r===null)throw w(`unknown node: ${e}`,{received:e,next:"List nodes and pass the id of a node with a captured pi session."});const t=await ie(e),s=await H(e);try{await S().header(N(t)),he(s,pe.R_OK)}catch(l){throw new Error(`cannot fork ${e}: its conversation or context is unreadable \u2014 ${l.message}`)}return le({kind:r.kind,mode:"base",cwd:r.cwd,prompt:"",root:!0,readiness:"await",forkFrom:e,...a===void 0?{}:{actor:a},profile:r.profile_id,model:r.model_override??void 0,...o===void 0?{}:{scopes:o}},async l=>{const g=await H(l);Ve(await K(e),s,await K(l),g,Cn)})}d(go,"forkNode");export{de as appPaidLaunch,se as assertLaunchModelRegistered,go as forkNode,Sn as invokeBirthLifecycleHooks,ie as resolveForkSource,xn as resolveProfileId,$n as resolveSpawner,vo as spawnChild,le as spawnChildPrepared};
3
+ `:""}${e.prompt}`;l==="orchestrator"&&await Te(n.node_id,{goal:e.prompt.trim()}),await Cn(n);let J;if(b!==void 0)try{const i=N(b);if(i===null)throw new Error(`invalid fork source ${b}`);const c=await S().fork(i,n.node_id);J=(await S().header(c)).path}catch(i){throw await v(n.node_id,"crash",{reason:"launch_failed",outcome:g(i)}),i}const p=Ie(n,{prompt:ue,resumeSessionPath:J});if(p.env={...p.env,CRTR_SUBTREE:await $e(n.node_id)},W){const i=e.wakeOrigin!==void 0?{type:"runtime",kind:"wake",wake:e.wakeOrigin}:{type:"runtime",kind:"kickoff",...n.spawned_by!=null&&n.spawned_by!==""?{spawnedBy:n.spawned_by}:{}};p.env[je]=We(i),await ze(p,n)}const _=Ye();if(await Qe(n.profile_id??null)!==null)return await z(n.node_id,new Date().toISOString()),L({level:"info",event:"broker.frozen",node_id:n.node_id,fields:{reason:"remote_environment"}}),{node:await u(n.node_id),frozen:!0,window:null,session:null};const Q=await _.reserveSlot(n.node_id),k=Q?await Je(n.node_id):null;if(k?.verdict==="too_small"){_.releaseReservation(n.node_id);const i=qe(k);throw await v(n.node_id,"crash",{reason:"launch_failed",outcome:g(i)}),i}if(!Q||k?.verdict==="denied")return k?.verdict==="denied"&&_.releaseReservation(n.node_id),await z(n.node_id,new Date().toISOString()),L({level:"info",event:"broker.frozen",node_id:n.node_id,fields:k?.verdict==="denied"?{reason:"app_memory",free_bytes:k.free_bytes,needed_bytes:k.needed_bytes}:{reason:"broker_cap",live_count:await _.occupancy(),hard_cap:te().automaticReviveCap}}),{node:await u(n.node_id),frozen:!0,window:null,session:null};const fe=await tn(n.node_id),{automaticReviveCap:we}=te();let $;try{$=await Ke(n.node_id,{daemonEpoch:_.epoch(),cap:we})}catch(i){if(!(i instanceof He))throw i;return _.releaseReservation(n.node_id),await z(n.node_id,new Date().toISOString()),L({level:"info",event:"broker.frozen",node_id:n.node_id,fields:{reason:"broker_cap",live_count:i.live,hard_cap:i.cap}}),{node:await u(n.node_id),frozen:!0,window:null,session:null}}p.env={...p.env,CRTR_EXECUTION_ID:$};let P;try{P=await vn(n.node_id,p,{cwd:n.cwd,name:re(n),resuming:!1})}catch(i){throw await j($,{reason:"launch_failed"}),i}finally{_.releaseReservation(n.node_id)}if(P.pid===null){const i=new Error(`broker host returned no pid for ${n.node_id}`);throw await j($,{reason:"launch_failed"}),await v(n.node_id,"crash",{reason:"launch_failed",outcome:g(i)}),D(`failed to launch the broker engine for ${n.node_id} (${n.name}) \u2014 the node was not started.`,`Inspect canonical event diagnostics with \`crtr sys logs --node ${n.node_id}\`, then verify the pi engine can start in this environment.`)}if(await Ve($,n.node_id,P.pid)==="shutdown")throw await j($,{reason:"superseded"}),G.teardown(n.node_id),D(`broker execution was superseded for ${n.node_id}`,"Retry spawning the node.");if(ce&&e.readiness==="await"){const i=await on(n.node_id,P.exited);if(!i.ready){const c=yn(n.node_id,i);L({level:"error",event:"broker.launch.not_ready",node_id:n.node_id,fields:c.fields});const B=await fe.explanation();throw await bn(n.node_id,new Error(B??c.text)),D(B!==null?`the root broker ${n.node_id} could not start \u2014 ${B}`:`the root broker ${n.node_id} ${c.text} \u2014 it was not started.`,B!==null?"Resolve the cause named above, then retry.":`Inspect canonical event diagnostics with \`crtr sys logs --node ${n.node_id}\`, then retry.`)}}let Z=null,ee=null;if(!t&&s!==null&&dn("user").auto_open_child_viewers)try{const i=await en(s);if(i!==null&&i.pane!==null&&i.pane!==""){const c=await Ze(n.node_id,i.session??"",{name:re(n),cwd:e.cwd,besidePane:i.pane,waitForBroker:!0,initialWhip:W});c!==null&&c.pane!==null&&c.pane!==""&&(ee=c.session,Z=nn(c.pane))}}catch{}return{node:await u(n.node_id),frozen:!1,window:Z,session:ee}}catch(f){throw x!==void 0&&m!==void 0&&await u(m)===null&&await De(x),f}}d(le,"spawnChildPrepared");async function yo(e){return le(e)}d(yo,"spawnChild");async function Cn(e){const o=hn(e.cwd,e.profile_id).plans.get("node:start")??[];await pn(o,{node:{id:e.node_id,name:e.name,kind:e.kind,mode:e.mode,lifecycle:e.lifecycle,cwd:e.cwd,nodeDir:await K(e.node_id),contextDir:await H(e.node_id),jobDir:Ue(e.node_id)},runtime:{isBirth:!0,canvasHome:Ge(),profile:e.profile_id??null}})}d(Cn,"invokeBirthLifecycleHooks");const En=new Set(["initial-prompt.md","yield-message.md","result.json","jobs"]);async function bo(e,o,a){const r=await u(e);if(r===null)throw w(`unknown node: ${e}`,{received:e,next:"List nodes and pass the id of a node with a captured pi session."});const t=await ie(e),s=await H(e);try{await S().header(N(t)),he(s,pe.R_OK)}catch(l){throw new Error(`cannot fork ${e}: its conversation or context is unreadable \u2014 ${l.message}`)}return le({kind:r.kind,mode:"base",cwd:r.cwd,prompt:"",root:!0,readiness:"await",forkFrom:e,...a===void 0?{}:{actor:a},profile:r.profile_id,model:r.model_override??void 0,...o===void 0?{}:{scopes:o}},async l=>{const y=await H(l);Xe(await K(e),s,await K(l),y,En)})}d(bo,"forkNode");export{de as appPaidLaunch,se as assertLaunchModelRegistered,bo as forkNode,Cn as invokeBirthLifecycleHooks,ie as resolveForkSource,$n as resolveProfileId,Sn as resolveSpawner,yo as spawnChild,le as spawnChildPrepared};
@@ -16,4 +16,6 @@ export declare function generateAndCommitRecapName(nodeId: string, conversation:
16
16
  title: string;
17
17
  icon: string;
18
18
  kind: string;
19
- }, isStillAutomatic: () => boolean, onNamed?: (editorLabel: string) => void): void;
19
+ }, isStillAutomatic: () => boolean,
20
+ /** The turn cause the caller took with `conversation`, before any await; null names none. */
21
+ causedBy: string | null, onNamed?: (editorLabel: string) => void): void;
@@ -1 +1 @@
1
- var f=Object.defineProperty;var r=(e,t)=>f(e,"name",{value:t,configurable:!0});import{localClient as l}from"../api/node-transport.js";import{brokerExecutionId as s,brokerExtensionState as m,commitBrokerGeneratedName as c}from"../core/runtime/broker/daemon-ops.js";import{publishNodeNamed as u}from"../core/runtime/broker/node-named.js";import{headlessName as b,headlessNameFromConversation as h,sanitizeSessionName as x,sanitizeSessionTitle as N,slugFromPrompt as L,titleFromPrompt as k}from"../core/runtime/naming.js";async function G(e,t){const n=t.trim();return n===""?!1:(await l({autostart:!1}).captureBrokerGoal(e,{text:n,expected_execution_id:s()})).captured}r(G,"captureGoalIfAbsent");async function S(e){return(await m(e)).roadmap}r(S,"readRoadmap");function p(e,t){return{description:x(t.name)||L(e),title:N(t.title)||k(e),icon:t.icon}}r(p,"generatedLabel");function g(e,t,n){const o=t.trim();o!==""&&m(e).then(i=>{if((i.node.description??"").trim()==="")return b(o).then(a=>c(e,{kind:"initial",...p(o,a)}))}).then(i=>{!i?.applied||i.editorLabel===void 0||(u({description:i.description??"",title:i.title??"",icon:i.icon??"",editorLabel:i.editorLabel}),n?.(i.editorLabel))}).catch(()=>{})}r(g,"generateAndCommitInitialName");function w(e,t,n,o,i){const a=t.trim();a!==""&&h(a).then(d=>{if(o())return c(e,{kind:"recap",...p(a,d),expected:n})}).then(d=>{d?.applied&&d.editorLabel!==void 0&&i?.(d.editorLabel)}).catch(()=>{})}r(w,"generateAndCommitRecapName");export{G as captureGoalIfAbsent,g as generateAndCommitInitialName,w as generateAndCommitRecapName,S as readRoadmap};
1
+ var f=Object.defineProperty;var o=(e,t)=>f(e,"name",{value:t,configurable:!0});import{localClient as l}from"../api/node-transport.js";import{brokerExecutionId as s,brokerExtensionState as m,commitBrokerGeneratedName as p}from"../core/runtime/broker/daemon-ops.js";import{publishNodeNamed as b}from"../core/runtime/broker/node-named.js";import{currentOrPendingCausedBy as h}from"../core/runtime/broker/turn-cause.js";import{headlessName as x,headlessNameFromConversation as N,sanitizeSessionName as y,sanitizeSessionTitle as L,slugFromPrompt as k,titleFromPrompt as C}from"../core/runtime/naming.js";async function P(e,t){const n=t.trim();return n===""?!1:(await l({autostart:!1}).captureBrokerGoal(e,{text:n,expected_execution_id:s()})).captured}o(P,"captureGoalIfAbsent");async function S(e){return(await m(e)).roadmap}o(S,"readRoadmap");function u(e,t){return{description:y(t.name)||k(e),title:L(t.title)||C(e),icon:t.icon}}o(u,"generatedLabel");function w(e,t,n){const r=t.trim();if(r==="")return;const c=h()??null;m(e).then(i=>{if((i.node.description??"").trim()==="")return x(r,void 0,c).then(a=>p(e,{kind:"initial",...u(r,a)}))}).then(i=>{!i?.applied||i.editorLabel===void 0||(b({description:i.description??"",title:i.title??"",icon:i.icon??"",editorLabel:i.editorLabel}),n?.(i.editorLabel))}).catch(()=>{})}o(w,"generateAndCommitInitialName");function z(e,t,n,r,c,i){const a=t.trim();a!==""&&N(a,c).then(d=>{if(r())return p(e,{kind:"recap",...u(a,d),expected:n})}).then(d=>{d?.applied&&d.editorLabel!==void 0&&i?.(d.editorLabel)}).catch(()=>{})}o(z,"generateAndCommitRecapName");export{P as captureGoalIfAbsent,w as generateAndCommitInitialName,z as generateAndCommitRecapName,S as readRoadmap};
@@ -1,2 +1,2 @@
1
1
  var J=Object.defineProperty;var l=(r,t)=>J(r,"name",{value:t,configurable:!0});import{envInboxBetweenTurns as $,envNodeId as G,envProfileId as X,envWatcherDebounceMs as j,envWatcherTickMs as z,envWatcherBackstopMs as L}from"../shared/env.js";import{coalesceBrokerInbox as V,entryCards as Q}from"../core/runtime/broker/inbox.js";import{acknowledgeBrokerMail as Y,claimBrokerMail as K,brokerExtensionState as O,recordConversationWriteFault as Z}from"../core/runtime/broker/daemon-ops.js";import{materializeSpilledBody as ee}from"../core/runtime/broker/spilled-body.js";import{brokerSignalClient as P}from"../core/runtime/broker/signal-stream.js";import{wasTurnRefused as te}from"../core/runtime/broker/extension-abort.js";import{isDaemonTransportApiError as A,isTokenRevokedApiError as ie}from"../api/errors.js";import{announceMessageOrigin as ne,messageSignature as C}from"../core/runtime/stamp/channel.js";import{captureGoalIfAbsent as se,generateAndCommitInitialName as re}from"./broker-local.js";import{emitEvent as D}from"../core/events/emit.js";import{errorClassFromError as ae}from"../core/events/errors.js";import{operationIdContext as g}from"../core/events/operation-id.js";import{SITUATIONAL_CONTEXT_CUSTOM_TYPE as x}from"../shared/generated-context.js";import{isProfilePaused as de}from"../core/profiles/manifest.js";import{brokerDisposeAndExit as H,brokerIdleTurnStart as oe,brokerRunTurnBusy as fe,brokerRunSteer as _,HELD_DEFERRED_INBOX_PROMPT_JOIN as le}from"../core/runtime/broker/held-deferred-inbox.js";const E=64,U=16;let v,he=0;class q extends Error{static{l(this,"WatcherFatalError")}constructor(t,e){super(t,{cause:e}),this.name="WatcherFatalError"}}function ue(){return{tickMs:z(),debounceMs:j()}}l(ue,"resolveCadence");function T(){const r=G();return r===void 0||r.trim()===""?void 0:r}l(T,"currentNodeId");function B(){const r=X();return r!==void 0&&r!==""&&de(r)}l(B,"profilePaused");function M(r){try{return ae(r)}catch{return{class:"unknown"}}}l(M,"safeErrorClass");function w(r){try{D(r)}catch{}}l(w,"emitBestEffort");function I(r){try{g.fresh(t=>{w({...r,operation_id:t})})}catch{}}l(I,"emitFreshBestEffort");function S(r,t){try{g.run(r,()=>{w({...t,operation_id:r})})}catch{}}l(S,"emitStoredBestEffort");function ce(r){return r.kind==="human"?{type:"human"}:{type:"runtime",kind:"inbox",entries:r.entries.map(t=>({...t.from===null?{}:{from:t.from},...t.from_name===void 0?{}:{fromName:t.from_name},kind:t.kind,tier:t.tier,...t.ref===void 0?{}:{ref:t.ref}}))}}l(ce,"deliveryOrigin");function k(r,t){if(r.length===0)throw new Error(`cannot create an empty ${t} delivery`);const e=[...new Set(r.map(n=>n.operation_id))].sort(),i=e.length-U;return{channel:t,entryIds:r.map(n=>n.entry_id),throughEntryId:r.at(-1).entry_id,sourceOperationIds:e.slice(0,U),...i>0?{sourceOperationIdsOmitted:i}:{}}}l(k,"deliveryFacts");function b(r){return{channel:r.channel,entry_ids:r.entryIds,through_entry_id:r.throughEntryId,source_operation_ids:r.sourceOperationIds,...r.sourceOperationIdsOmitted===void 0?{}:{source_operation_ids_omitted:r.sourceOperationIdsOmitted}}}l(b,"deliveryFields");function me(r){S(r.operationId,{level:"info",event:"inbox.delivery.completed",outcome:"succeeded",fields:b(r)})}l(me,"emitDeliveryCompleted");function F(r,t,e,i){S(r,{level:"error",event:"inbox.delivery.failed",outcome:"failed",error:i,error_class:M(i),fields:{stage:e,...b(t)}})}l(F,"emitDeliveryFailure");function R(r,t){S(r.operationId,{level:"error",event:"inbox.delivery.failed",outcome:"failed",error_class:{class:"unknown"},fields:{stage:"abandoned",reason:t,...b(r)}})}l(R,"emitDeliveryAbandoned");function pe(r){return typeof r=="string"?r:Array.isArray(r)?r.filter(t=>typeof t=="object"&&t!==null&&typeof t.text=="string").map(t=>t.text).join(`
2
- `):""}l(pe,"messageText");function W(r){return r==="quit"||r==="reload"||r==="new"||r==="resume"||r==="fork"}l(W,"isSessionShutdownReason");function ye(r){switch(r){case"quit":return"shutdown";case"reload":case"new":case"resume":case"fork":return"reload";default:return r}}l(ye,"disposalReasonForShutdown");function ge(r){return typeof r=="string"?r.slice(0,128):r===null?"null":r===void 0?"undefined":typeof r=="number"||typeof r=="boolean"||typeof r=="bigint"?String(r).slice(0,128):typeof r}l(ge,"boundedRuntimeReason");class be{static{l(this,"CanvasInboxWatcher")}pi;cadence;generation;timer;backstop;debounceTimer;signalUnsubscribers=[];rerun=!1;retryNeeded=!1;polledOk=!1;armed=!1;lastCtx;streaming=!1;runNode=$();nodeId;claims=new Map;reportBodies=new Map;tickInFlight;buffer=[];lastArrival=0;pendingHandoffs=[];pendingCommit;promptJoinInFlight=!1;replayUnseenInherited=!1;seeded=!1;disposed=!1;disposalReason;fatalError;fatalEscaped=!1;constructor(t,e,i){this.pi=t,this.cadence=e,this.generation=i}start(){const t=l((e,i)=>{this.canMutate()&&this.captureCtx(e,i)},"capture");this.pi.on("session_start",(e,i)=>{this.canMutate()&&(this.captureCtx(e,i),this.arm())}),this.pi.on("turn_end",t),this.pi.on("agent_start",(e,i)=>{this.canMutate()&&(this.captureCtx(e,i),this.streaming=!0)}),this.pi.on("agent_end",(e,i)=>{this.canMutate()&&(this.captureCtx(e,i),this.streaming=!1)}),this.pi.on("message_end",e=>{if(!this.canMutate())return;const i=e?.message;if(i?.role!=="user"&&i?.role!=="custom"||!this.pendingHandoffs.some(d=>d.awaitingDequeue!==void 0))return;const n=C(pe(i.content)),s=this.pendingHandoffs.find(d=>d.awaitingDequeue===n);s!==void 0&&delete s.awaitingDequeue}),this.pi.on("agent_settled",async(e,i)=>{if(!this.canMutate())return;this.captureCtx(e,i);const n=T();if(n!==void 0)try{this.assertNodeOwnership(n,"settle"),await this.tickInFlight,this.seeded||await this.claimMail(n),this.returnUnconsumedSteers();const s=await this.settle(n,i,!1,!0);s==="refresh_held"&&this.dispose("refresh",!1),this.runNode&&s==="settled"&&this.canMutate()&&this.buffer.length>0&&await this.flushOrdinary(n),this.syncTimers()}catch(s){this.dispose("shutdown",!1,void 0,{error:s,stage:"settle"})}}),this.pi.on("session_shutdown",async e=>{if(!this.canMutate())return;let i;try{i=e?.reason}catch{i=void 0}const n=W(i)?ye(i):"reload";W(i)||I({level:"warn",event:"inbox.lifecycle.unknown_shutdown_reason",outcome:"failed",fields:{reason:ge(i)}});try{await this.disposeAtSettlement(n,this.lastCtx)}catch(s){this.dispose(n,!1,void 0,{error:s,stage:"settle"})}})}arm(){if(this.armed)return;this.armed=!0;const t=P();this.signalUnsubscribers.push(t.subscribe("state",()=>this.syncTimers()),t.subscribe("mail",()=>this.runTick("schedule")),t.subscribe("resync",()=>this.runTick("schedule"))),this.syncTimers(),t.state!=="connected"&&this.runTick("schedule")}async disposeAtSettlement(t,e){const i=T();if(i===void 0){this.dispose(t,!1);return}try{this.assertNodeOwnership(i,"settle"),this.returnUnconsumedSteers();const n=await this.settle(i,e,t!=="refresh");this.dispose(n==="refresh_held"?"refresh":t,!1)}catch(n){this.dispose(t,!1,void 0,{error:n,stage:"settle"})}}dispose(t,e,i,n){if(this.disposed)return;this.disposed=!0,this.disposalReason=t,this.clearTimer();for(const d of this.signalUnsubscribers.splice(0))d();const s=n;{const d=[...new Set([...this.pendingCommit?.handoffs??[],...this.pendingHandoffs.splice(0)])];this.buffer.length=0,this.claims.clear(),this.reportBodies.clear(),this.pendingCommit=void 0,this.promptJoinInFlight=!1,this.replayUnseenInherited=!1,this.nodeId=void 0,this.lastCtx=void 0,this.lastArrival=0,this.streaming=!1,this.seeded=!1,this.ownsCurrentGeneration()&&(v=void 0);for(const a of d)R(a,t)}if(s!==void 0){this.scheduleHandlerFatal(s.error,s.stage);return}}ownsCurrentGeneration(){return v===this&&v.generation===this.generation}canMutate(){return this.ownsCurrentGeneration()&&!this.disposed&&this.fatalError===void 0}captureCtx(t,e){e!==void 0&&(this.lastCtx=e)}clearTimer(){this.timer!==void 0&&clearInterval(this.timer),this.backstop!==void 0&&clearInterval(this.backstop),this.debounceTimer!==void 0&&clearTimeout(this.debounceTimer),this.timer=this.backstop=this.debounceTimer=void 0}syncTimers(){if(!this.armed||!this.canMutate())return;const t=P().state,e=t==="connected",i=t!=="stopped"&&(t!=="connected"||this.buffer.length>0||this.pendingCommit!==void 0||this.retryNeeded);i&&this.timer===void 0?(this.timer=setInterval(()=>this.runTick("schedule"),this.cadence.tickMs),this.timer.unref?.()):!i&&this.timer!==void 0&&(clearInterval(this.timer),this.timer=void 0),e&&this.backstop===void 0?(this.backstop=setInterval(()=>this.runTick("schedule"),L()),this.backstop.unref?.()):!e&&this.backstop!==void 0&&(clearInterval(this.backstop),this.backstop=void 0),this.buffer.length===0&&this.debounceTimer!==void 0&&(clearTimeout(this.debounceTimer),this.debounceTimer=void 0)}armDebounce(){this.debounceTimer!==void 0&&clearTimeout(this.debounceTimer);const t=Math.max(0,this.lastArrival+this.cadence.debounceMs-Date.now());this.debounceTimer=setTimeout(()=>{this.debounceTimer=void 0,this.runTick("schedule")},t),this.debounceTimer.unref?.()}isIdle(){if(this.runNode&&fe())return!1;try{if(typeof this.lastCtx?.isIdle=="function")return this.lastCtx.isIdle()===!0}catch{}return!this.streaming}assertNodeOwnership(t,e){!this.seeded||this.nodeId===void 0||this.nodeId===t||this.raiseReadFatal(e,new Error(`watcher node changed from ${this.nodeId} to ${t}`))}raiseFatal(t,e,i,n){throw this.fatalError===void 0&&(this.fatalError=new q(e,i),this.clearTimer(),I({level:"error",event:t,outcome:"failed",error:i,error_class:M(i),fields:n})),this.fatalError}raiseReadFatal(t,e){return this.raiseFatal("inbox.read.failed",`inbox ${t} state is corrupt`,e,{stage:t})}raiseSituationalFatal(t,e){return this.raiseFatal("inbox.situational_context.missing","situational context marker has no readable sidecar",e,{entry_id:t})}escapeFatal(t,e){if(!this.fatalEscaped){if(this.fatalEscaped=!0,e==="throw")throw t;queueMicrotask(()=>{throw t})}}scheduleHandlerFatal(t,e){let i;if(t instanceof q)i=t;else try{this.raiseReadFatal(e,t)}catch(n){i=n}this.escapeFatal(i,"schedule")}runTick(t="throw"){if(!this.canMutate())return;if(this.tickInFlight!==void 0){this.rerun=!0;return}const e=this.tick().then(()=>{this.retryNeeded=!1,this.polledOk=!0}).catch(i=>{if(this.canMutate()){if(A(i)){this.retryNeeded=!0,I({level:"warn",event:"inbox.read.failed",outcome:"failed",error:i,error_class:M(i),fields:{stage:"poll"}});return}if(this.polledOk&&ie(i)){this.clearTimer(),I({level:"info",event:"inbox.poll.token_revoked",outcome:"succeeded",fields:{stage:"poll"}}),H()?.("token-revoked");return}this.scheduleHandlerFatal(i,"poll")}}).finally(()=>{this.tickInFlight===e&&(this.tickInFlight=void 0),this.syncTimers(),this.rerun&&this.canMutate()&&(this.rerun=!1,this.runTick("schedule"))});this.tickInFlight=e}async claimMail(t){this.assertNodeOwnership(t,"poll");let e;try{e=await K(t,"wake",this.reportBodies)}catch(a){throw A(a)&&(this.replayUnseenInherited=!0),a}if(!this.canMutate()||e.action==="hold")return!1;const i=this.replayUnseenInherited;this.replayUnseenInherited=!1,this.nodeId=t,this.seeded=!0;const n=new Set(e.deliveries.map(a=>a.entry.entry_id)),s=this.buffer.filter(a=>!n.has(a.entry_id));for(const a of s)this.claims.delete(a.entry_id);if(this.buffer.splice(0,this.buffer.length,...this.buffer.filter(a=>n.has(a.entry_id))),s.length>0){const a=globalThis[Symbol.for("@crouton-kit/crtr:watcher-cancel-probe")];typeof a=="function"&&a(s.map(f=>f.entry_id),process.hrtime.bigint())}let d=!1;for(const a of e.deliveries){const f=a.entry.entry_id;this.claims.has(f)||(a.entry=ee(a.entry),this.claims.set(f,a),a.handoff==="inherited"&&!i?g.fresh(o=>{this.pendingHandoffs.push({...k([a.entry],"visible"),operationId:o,settleAtAgentSettled:!0})}):(this.buffer.push(a.entry),this.lastArrival=Date.now(),d=!0))}return d&&this.armDebounce(),this.syncTimers(),!0}async tick(){const t=T();if(t!==void 0){if(this.pendingCommit!==void 0){await this.retryPendingCommit(t)==="refresh_held"&&this.canMutate()&&this.dispose("refresh",!1);return}this.promptJoinInFlight||B()||this.pendingHandoffs.length>=E||await this.claimMail(t)&&this.buffer.length>0&&(!this.runNode||this.isIdle()||this.buffer.some(e=>e.tier==="critical")||this.buffer.some(e=>this.isRunSteer(e))&&_()?.steerable())&&Date.now()-this.lastArrival>=this.cadence.debounceMs&&await this.flushOrdinary(t)}}missingChannelEntries(t){const e=t.filter(s=>this.claims.has(s.entry_id)),i=this.coveredEntryIds("hidden"),n=this.coveredEntryIds("visible");return{hiddenEntries:e.filter(s=>(s.data?.situational===!0||s.data?.situationalOnly===!0)&&!i.has(s.entry_id)),visibleEntries:e.filter(s=>s.data?.situationalOnly!==!0&&!n.has(s.entry_id))}}isRunSteer(t){return this.runNode&&t.kind==="message"&&(t.from??"system")==="human"&&t.data?.steer===!0}returnUnconsumedSteers(){const t=_()?.takeUnconsumed()??[];if(t.length===0)return;const e=new Set(t.flatMap(n=>n.messageIds));this.detachPending(n=>n.settlesThroughBridge===!0&&n.entryIds.every(s=>e.has(s)));const i=[...e].map(n=>this.claims.get(n)?.entry).filter(n=>n!==void 0);this.requeueFromBatch(i,e)}visibleRoute(t){return t.length===0?"none":this.isIdle()?"idle":this.runNode?t.some(e=>e.tier==="critical")?"critical":t.some(e=>this.isRunSteer(e))&&_()?.steerable()?"steer":"none":t.some(e=>e.tier==="critical")?"critical":t.some(e=>e.tier==="urgent"||e.kind==="final")?"steer":"followUp"}buildPlan(t){const{hiddenEntries:e,visibleEntries:i}=this.missingChannelEntries(t),n=new Set(e.map(u=>u.entry_id)),s=new Set(i.map(u=>u.entry_id)),d=t.filter(u=>n.has(u.entry_id)||s.has(u.entry_id)),a=this.isIdle()&&d.length>0&&d.every(u=>u.tier==="deferred"),f=this.visibleRoute(i)==="critical",o=!a&&!f&&e.length>0,m=!a&&!f&&i.length>0;return{hiddenEntries:e,visibleEntries:i,deferredHold:a,sendCount:Number(o)+Number(m)}}ordinaryPlan(){return this.buildPlan(this.buffer)}async contextCards(t,e){const i=[];if(e.some(n=>n.data?.situational===!0)){const n=e.at(-1).entry_id;let s;try{s=(await O(t)).situational_card}catch(d){this.raiseSituationalFatal(n,d)}s===null&&this.raiseSituationalFatal(n,new Error(`missing situational context for entry ${n}`)),i.push(s)}for(const n of e)n.data?.situationalOnly===!0&&i.push(...Q(n));return i}digest(t,e){try{return V(t)}catch(i){this.raiseReadFatal(e,i)}}sendDelivery(t,e,i,n,s){this.pendingHandoffs.length>=E&&this.raiseReadFatal(n,new Error(`pending handoff capacity exceeded before ${t} send`));let d;try{d=k(e,t)}catch(o){this.raiseReadFatal(n,o)}let a;try{g.fresh(o=>{a=o,w({level:"info",event:"inbox.delivery.started",operation_id:o,fields:b(d)}),i()})}catch(o){return a===void 0&&this.raiseReadFatal(n,o),F(a,d,"send",o),"failed"}a===void 0&&this.raiseReadFatal(n,new Error(`${t} delivery returned without an operation id`));const f={operationId:a,...d,...s===void 0?{}:{awaitingDequeue:s}};return this.canMutate()?(this.pendingHandoffs.push(f),"handed-off"):(this.disposed&&this.disposalReason!==void 0&&R(f,this.disposalReason),"inactive")}sendContext(t,e,i,n,s,d){const a=n==="steer"||n==="followUp"?n:this.isIdle()?void 0:"followUp",f=a===void 0||i.length===0?void 0:C(i.at(-1));return this.sendDelivery("hidden",e,()=>{this.runNode&&s&&this.pi.appendEntry("crtr-turn-input",{message_ids:e.map(o=>o.entry_id)}),i.forEach((o,m)=>{const u=s&&m===i.length-1;this.pi.sendMessage({customType:x,content:o,display:!0,details:{nodeId:t}},a!==void 0?{deliverAs:a}:u?{deliverAs:"followUp",triggerTurn:!0}:{})})},d,f)}sendVisible(t,e,i,n,s,d){return this.sendDelivery("visible",t,()=>{if(s!=="idle"&&s!=="steer"&&s!=="followUp")throw new Error(`visible send requested for non-send route ${s}`);for(const a of e)this.pi.sendMessage({customType:x,content:a,display:!0},s==="idle"?{}:{deliverAs:s});if(ne(i,n),s==="idle"){const a=oe();if(a===void 0)throw new Error("broker idle-turn admission is unavailable");a(i,this.runNode?t.map(f=>f.entry_id):[])}else this.pi.sendUserMessage(i,{deliverAs:s})},d,s==="steer"||s==="followUp"?C(i):void 0)}abortCritical(){try{this.lastCtx?.abort?.()}catch{}}requeueFromBatch(t,e){if(e.size===0)return;const i=t.filter(d=>e.has(d.entry_id)),n=new Set(i.map(d=>d.entry_id)),s=this.buffer.filter(d=>!n.has(d.entry_id));this.buffer.splice(0,this.buffer.length,...i,...s)}async executePlan(t,e,i){const n=new Set,s=this.visibleRoute(e.visibleEntries);if(s==="critical"){if(this.abortCritical(),!this.canMutate())return{inactive:!0,requeueEntryIds:n};for(const o of e.visibleEntries)n.add(o.entry_id);for(const o of e.hiddenEntries)n.add(o.entry_id);return{inactive:!1,requeueEntryIds:n}}if(this.runNode&&s==="none")return{inactive:!1,requeueEntryIds:new Set([...e.hiddenEntries,...e.visibleEntries].map(o=>o.entry_id))};if(this.runNode&&s==="steer"){let o=-1;e.visibleEntries.forEach((h,y)=>{this.isRunSteer(h)&&(o=y)});const m=e.visibleEntries.slice(0,o+1);if(e.hiddenEntries.length>0||m.some(h=>h.kind!=="message"||h.tier==="deferred"||(h.from??"system")!=="human"))return{inactive:!1,requeueEntryIds:new Set([...e.hiddenEntries,...e.visibleEntries].map(h=>h.entry_id))};const u=this.digest(m,i),c=u.map(h=>({text:h.text,messageIds:h.entries.map(y=>y.entry_id),cards:h.cards}));if(_()?.accept(c)!=="accepted")return{inactive:!1,requeueEntryIds:new Set([...e.hiddenEntries,...e.visibleEntries].map(h=>h.entry_id))};for(const h of u){const y=k(h.entries,"visible");g.fresh(N=>{w({level:"info",event:"inbox.delivery.started",operation_id:N,fields:b(y)}),this.pendingHandoffs.push({...y,operationId:N,settlesThroughBridge:!0})})}const p=new Set(m.map(h=>h.entry_id));for(const h of e.visibleEntries)p.has(h.entry_id)||n.add(h.entry_id);for(const h of e.hiddenEntries)n.add(h.entry_id);return{inactive:!1,requeueEntryIds:n}}const d=e.visibleEntries.length>0?this.digest(e.visibleEntries,i):[],[a,...f]=d;for(const o of f)for(const m of o.entries)n.add(m.entry_id);if(e.hiddenEntries.length>0){const o=await this.contextCards(t,e.hiddenEntries),m=this.sendContext(t,e.hiddenEntries,o,s,a===void 0,i);if(m==="inactive")return{inactive:!0,requeueEntryIds:n};if(m==="failed")for(const u of e.hiddenEntries)n.add(u.entry_id)}if(!this.canMutate())return{inactive:!0,requeueEntryIds:n};if(a!==void 0){const o=this.sendVisible(a.entries,a.cards,a.text,ce(a),s,i);if(o==="inactive")return{inactive:!0,requeueEntryIds:n};if(o==="failed")for(const m of a.entries)n.add(m.entry_id);else a.kind==="human"&&(await se(t,a.text).catch(()=>{}),re(t,a.text))}return{inactive:!1,requeueEntryIds:n}}async flushOrdinary(t){if(this.buffer.length===0)return"empty";const e=this.ordinaryPlan();if(e.deferredHold)return"held";if(this.pendingHandoffs.length+e.sendCount>E)return"capacity-blocked";const i=[...this.buffer],n=await this.executePlan(t,e,"poll");return n.inactive||(this.buffer.splice(0,i.length),this.requeueFromBatch(i,n.requeueEntryIds)),"attempted"}async prependHeldDeferredInbox(t){if(!this.canMutate()||this.nodeId===void 0||B())return;const e=this.ordinaryPlan();if(!e.deferredHold)return;const i=e.visibleEntries.length===0?[]:this.digest(e.visibleEntries,"poll"),n=+(e.hiddenEntries.length>0)+i.length;if(this.pendingHandoffs.length+n>E)return;const s=[...this.buffer];this.promptJoinInFlight=!0;try{this.runNode&&this.pi.appendEntry("crtr-turn-input",{message_ids:[...e.hiddenEntries,...e.visibleEntries].map(d=>d.entry_id)}),e.hiddenEntries.length>0&&await this.sendPromptJoinDelivery(t,"hidden",e.hiddenEntries,await this.contextCards(this.nodeId,e.hiddenEntries));for(const d of i)await this.sendPromptJoinDelivery(t,"visible",d.entries,[...d.cards,d.text]);if(!this.canMutate())return;this.buffer.splice(0,s.length)}finally{this.promptJoinInFlight=!1}}async sendPromptJoinDelivery(t,e,i,n){let s;try{s=k(i,e)}catch(f){this.raiseReadFatal("poll",f)}let d;try{await g.fresh(async f=>{d=f,w({level:"info",event:"inbox.delivery.started",operation_id:f,fields:b(s)});for(const o of n)await t.sendCustomMessage({customType:x,content:o,display:!0})})}catch(f){throw d===void 0&&this.raiseReadFatal("poll",f),F(d,s,"send",f),f}d===void 0&&this.raiseReadFatal("poll",new Error(`${e} prompt-join delivery returned without an operation id`));const a={operationId:d,...s,settleAtAgentSettled:!0};if(!this.canMutate()){this.disposed&&this.disposalReason!==void 0&&R(a,this.disposalReason);return}this.pendingHandoffs.push(a)}coveredEntryIds(t){const e=new Set;for(const i of this.pendingHandoffs)if(i.channel===t)for(const n of i.entryIds)e.add(n);return e}retryPendingCommit(t){const e=this.pendingCommit;if(e===void 0)return Promise.resolve("settled");if(e.attempt!==void 0)return e.attempt;const i=this.attemptPendingCommit(t,e).finally(()=>{e.attempt===i&&(e.attempt=void 0)});return e.attempt=i,i}async attemptPendingCommit(t,e){let i=!1;try{const s=await Y(t,e.receipts);if(s.action==="hold")return"refresh_held";for(const d of e.receipts){const a=s.results.find(f=>f.delivery_id===d.delivery_id);if(a?.outcome!==d.disposition&&a?.outcome!=="stale")throw new Error("daemon returned no terminal mail settlement outcome")}i=s.results.every(d=>d.outcome==="acknowledged")}catch(s){if(this.pendingCommit===e)for(const d of e.handoffs)F(d.operationId,d,"receipt_commit",s);return String(s?.message).startsWith("conversation_write_failed")&&(D({level:"error",event:"broker.exited",node_id:t,error:s,fields:{fault:"conversation_write_failed"}}),await Z(t,s),H()?.("conversation_write_failed",1)),"held"}if(this.pendingCommit!==e)return"settled";for(const s of e.entryIds)this.claims.delete(s);if(e.receipts.some(s=>s.disposition==="released")){const s=new Set(e.entryIds);this.buffer.splice(0,this.buffer.length,...this.buffer.filter(d=>!s.has(d.entry_id)))}this.pendingCommit=void 0;const n=this.detachPending(s=>s.entryIds.every(d=>!this.claims.has(d)));for(const s of n)i&&me(s);return"settled"}async settle(t,e,i,n=!1){if(i)try{if(e?.hasPendingMessages?.()===!0)return"held"}catch{}if(this.pendingCommit!==void 0)return this.retryPendingCommit(t);const s=te(),d=!s&&this.pendingHandoffs.some(c=>c.awaitingDequeue!==void 0)&&await this.nodeRefreshing(t);if(this.pendingCommit!==void 0)return this.retryPendingCommit(t);const a=new Set(this.buffer.map(c=>c.entry_id)),f=new Set(d?this.pendingHandoffs.filter(c=>c.awaitingDequeue!==void 0).flatMap(c=>[...c.entryIds]):[]),o=this.pendingHandoffs.filter(c=>(n||c.settleAtAgentSettled!==!0)&&c.entryIds.every(p=>!f.has(p))),m=new Set(o.flatMap(c=>[...c.entryIds])),u=[...this.claims.keys()].filter(c=>s||m.has(c)&&!a.has(c));return u.length===0?"settled":(this.pendingCommit={handoffs:o,entryIds:u,receipts:u.map(c=>{const p=this.claims.get(c);return{delivery_id:p.delivery_id,attempts:p.attempts,disposition:s?"released":"acknowledged"}})},this.retryPendingCommit(t))}async nodeRefreshing(t){try{return(await O(t)).node.intent==="refresh"}catch{return!0}}detachPending(t){const e=[];for(let i=this.pendingHandoffs.length-1;i>=0;i-=1){const n=this.pendingHandoffs[i];t(n)&&e.push(...this.pendingHandoffs.splice(i,1))}return e.reverse(),e}}function ve(r,t){v?.dispose("reload",!1);const e=new be(r,t,++he);v=e;const i=l(n=>e.prependHeldDeferredInbox(n),"promptJoin");globalThis[le]=i;try{e.start()}catch(n){throw e.dispose("reload",!1),n}return e}l(ve,"installWatcher");function we(r){const t=ve(r,ue());return()=>t.dispose("shutdown",!1)}l(we,"registerCanvasInboxWatcher");var He=we;export{He as default,we as registerCanvasInboxWatcher};
2
+ `):""}l(pe,"messageText");function W(r){return r==="quit"||r==="reload"||r==="new"||r==="resume"||r==="fork"}l(W,"isSessionShutdownReason");function ye(r){switch(r){case"quit":return"shutdown";case"reload":case"new":case"resume":case"fork":return"reload";default:return r}}l(ye,"disposalReasonForShutdown");function ge(r){return typeof r=="string"?r.slice(0,128):r===null?"null":r===void 0?"undefined":typeof r=="number"||typeof r=="boolean"||typeof r=="bigint"?String(r).slice(0,128):typeof r}l(ge,"boundedRuntimeReason");class be{static{l(this,"CanvasInboxWatcher")}pi;cadence;generation;timer;backstop;debounceTimer;signalUnsubscribers=[];rerun=!1;retryNeeded=!1;polledOk=!1;armed=!1;lastCtx;streaming=!1;runNode=$();nodeId;claims=new Map;reportBodies=new Map;tickInFlight;buffer=[];lastArrival=0;pendingHandoffs=[];pendingCommit;promptJoinInFlight=!1;replayUnseenInherited=!1;seeded=!1;disposed=!1;disposalReason;fatalError;fatalEscaped=!1;constructor(t,e,i){this.pi=t,this.cadence=e,this.generation=i}start(){const t=l((e,i)=>{this.canMutate()&&this.captureCtx(e,i)},"capture");this.pi.on("session_start",(e,i)=>{this.canMutate()&&(this.captureCtx(e,i),this.arm())}),this.pi.on("turn_end",t),this.pi.on("agent_start",(e,i)=>{this.canMutate()&&(this.captureCtx(e,i),this.streaming=!0)}),this.pi.on("agent_end",(e,i)=>{this.canMutate()&&(this.captureCtx(e,i),this.streaming=!1)}),this.pi.on("message_end",e=>{if(!this.canMutate())return;const i=e?.message;if(i?.role!=="user"&&i?.role!=="custom"||!this.pendingHandoffs.some(d=>d.awaitingDequeue!==void 0))return;const n=C(pe(i.content)),s=this.pendingHandoffs.find(d=>d.awaitingDequeue===n);s!==void 0&&delete s.awaitingDequeue}),this.pi.on("agent_settled",async(e,i)=>{if(!this.canMutate())return;this.captureCtx(e,i);const n=T();if(n!==void 0)try{this.assertNodeOwnership(n,"settle"),await this.tickInFlight,this.seeded||await this.claimMail(n),this.returnUnconsumedSteers();const s=await this.settle(n,i,!1,!0);s==="refresh_held"&&this.dispose("refresh",!1),this.runNode&&s==="settled"&&this.canMutate()&&this.buffer.length>0&&await this.flushOrdinary(n),this.syncTimers()}catch(s){this.dispose("shutdown",!1,void 0,{error:s,stage:"settle"})}}),this.pi.on("session_shutdown",async e=>{if(!this.canMutate())return;let i;try{i=e?.reason}catch{i=void 0}const n=W(i)?ye(i):"reload";W(i)||I({level:"warn",event:"inbox.lifecycle.unknown_shutdown_reason",outcome:"failed",fields:{reason:ge(i)}});try{await this.disposeAtSettlement(n,this.lastCtx)}catch(s){this.dispose(n,!1,void 0,{error:s,stage:"settle"})}})}arm(){if(this.armed)return;this.armed=!0;const t=P();this.signalUnsubscribers.push(t.subscribe("state",()=>this.syncTimers()),t.subscribe("mail",()=>this.runTick("schedule")),t.subscribe("resync",()=>this.runTick("schedule"))),this.syncTimers(),t.state!=="connected"&&this.runTick("schedule")}async disposeAtSettlement(t,e){const i=T();if(i===void 0){this.dispose(t,!1);return}try{this.assertNodeOwnership(i,"settle"),this.returnUnconsumedSteers();const n=await this.settle(i,e,t!=="refresh");this.dispose(n==="refresh_held"?"refresh":t,!1)}catch(n){this.dispose(t,!1,void 0,{error:n,stage:"settle"})}}dispose(t,e,i,n){if(this.disposed)return;this.disposed=!0,this.disposalReason=t,this.clearTimer();for(const d of this.signalUnsubscribers.splice(0))d();const s=n;{const d=[...new Set([...this.pendingCommit?.handoffs??[],...this.pendingHandoffs.splice(0)])];this.buffer.length=0,this.claims.clear(),this.reportBodies.clear(),this.pendingCommit=void 0,this.promptJoinInFlight=!1,this.replayUnseenInherited=!1,this.nodeId=void 0,this.lastCtx=void 0,this.lastArrival=0,this.streaming=!1,this.seeded=!1,this.ownsCurrentGeneration()&&(v=void 0);for(const a of d)R(a,t)}if(s!==void 0){this.scheduleHandlerFatal(s.error,s.stage);return}}ownsCurrentGeneration(){return v===this&&v.generation===this.generation}canMutate(){return this.ownsCurrentGeneration()&&!this.disposed&&this.fatalError===void 0}captureCtx(t,e){e!==void 0&&(this.lastCtx=e)}clearTimer(){this.timer!==void 0&&clearInterval(this.timer),this.backstop!==void 0&&clearInterval(this.backstop),this.debounceTimer!==void 0&&clearTimeout(this.debounceTimer),this.timer=this.backstop=this.debounceTimer=void 0}syncTimers(){if(!this.armed||!this.canMutate())return;const t=P().state,e=t==="connected",i=t!=="stopped"&&(t!=="connected"||this.buffer.length>0||this.pendingCommit!==void 0||this.retryNeeded);i&&this.timer===void 0?(this.timer=setInterval(()=>this.runTick("schedule"),this.cadence.tickMs),this.timer.unref?.()):!i&&this.timer!==void 0&&(clearInterval(this.timer),this.timer=void 0),e&&this.backstop===void 0?(this.backstop=setInterval(()=>this.runTick("schedule"),L()),this.backstop.unref?.()):!e&&this.backstop!==void 0&&(clearInterval(this.backstop),this.backstop=void 0),this.buffer.length===0&&this.debounceTimer!==void 0&&(clearTimeout(this.debounceTimer),this.debounceTimer=void 0)}armDebounce(){this.debounceTimer!==void 0&&clearTimeout(this.debounceTimer);const t=Math.max(0,this.lastArrival+this.cadence.debounceMs-Date.now());this.debounceTimer=setTimeout(()=>{this.debounceTimer=void 0,this.runTick("schedule")},t),this.debounceTimer.unref?.()}isIdle(){if(this.runNode&&fe())return!1;try{if(typeof this.lastCtx?.isIdle=="function")return this.lastCtx.isIdle()===!0}catch{}return!this.streaming}assertNodeOwnership(t,e){!this.seeded||this.nodeId===void 0||this.nodeId===t||this.raiseReadFatal(e,new Error(`watcher node changed from ${this.nodeId} to ${t}`))}raiseFatal(t,e,i,n){throw this.fatalError===void 0&&(this.fatalError=new q(e,i),this.clearTimer(),I({level:"error",event:t,outcome:"failed",error:i,error_class:M(i),fields:n})),this.fatalError}raiseReadFatal(t,e){return this.raiseFatal("inbox.read.failed",`inbox ${t} state is corrupt`,e,{stage:t})}raiseSituationalFatal(t,e){return this.raiseFatal("inbox.situational_context.missing","situational context marker has no readable sidecar",e,{entry_id:t})}escapeFatal(t,e){if(!this.fatalEscaped){if(this.fatalEscaped=!0,e==="throw")throw t;queueMicrotask(()=>{throw t})}}scheduleHandlerFatal(t,e){let i;if(t instanceof q)i=t;else try{this.raiseReadFatal(e,t)}catch(n){i=n}this.escapeFatal(i,"schedule")}runTick(t="throw"){if(!this.canMutate())return;if(this.tickInFlight!==void 0){this.rerun=!0;return}const e=this.tick().then(()=>{this.retryNeeded=!1,this.polledOk=!0}).catch(i=>{if(this.canMutate()){if(A(i)){this.retryNeeded=!0,I({level:"warn",event:"inbox.read.failed",outcome:"failed",error:i,error_class:M(i),fields:{stage:"poll"}});return}if(this.polledOk&&ie(i)){this.clearTimer(),I({level:"info",event:"inbox.poll.token_revoked",outcome:"succeeded",fields:{stage:"poll"}}),H()?.("token-revoked");return}this.scheduleHandlerFatal(i,"poll")}}).finally(()=>{this.tickInFlight===e&&(this.tickInFlight=void 0),this.syncTimers(),this.rerun&&this.canMutate()&&(this.rerun=!1,this.runTick("schedule"))});this.tickInFlight=e}async claimMail(t){this.assertNodeOwnership(t,"poll");let e;try{e=await K(t,"wake",this.reportBodies)}catch(a){throw A(a)&&(this.replayUnseenInherited=!0),a}if(!this.canMutate()||e.action==="hold")return!1;const i=this.replayUnseenInherited;this.replayUnseenInherited=!1,this.nodeId=t,this.seeded=!0;const n=new Set(e.deliveries.map(a=>a.entry.entry_id)),s=this.buffer.filter(a=>!n.has(a.entry_id));for(const a of s)this.claims.delete(a.entry_id);if(this.buffer.splice(0,this.buffer.length,...this.buffer.filter(a=>n.has(a.entry_id))),s.length>0){const a=globalThis[Symbol.for("@crouton-kit/crtr:watcher-cancel-probe")];typeof a=="function"&&a(s.map(f=>f.entry_id),process.hrtime.bigint())}let d=!1;for(const a of e.deliveries){const f=a.entry.entry_id;this.claims.has(f)||(a.entry=ee(a.entry,t),this.claims.set(f,a),a.handoff==="inherited"&&!i?g.fresh(o=>{this.pendingHandoffs.push({...k([a.entry],"visible"),operationId:o,settleAtAgentSettled:!0})}):(this.buffer.push(a.entry),this.lastArrival=Date.now(),d=!0))}return d&&this.armDebounce(),this.syncTimers(),!0}async tick(){const t=T();if(t!==void 0){if(this.pendingCommit!==void 0){await this.retryPendingCommit(t)==="refresh_held"&&this.canMutate()&&this.dispose("refresh",!1);return}this.promptJoinInFlight||B()||this.pendingHandoffs.length>=E||await this.claimMail(t)&&this.buffer.length>0&&(!this.runNode||this.isIdle()||this.buffer.some(e=>e.tier==="critical")||this.buffer.some(e=>this.isRunSteer(e))&&_()?.steerable())&&Date.now()-this.lastArrival>=this.cadence.debounceMs&&await this.flushOrdinary(t)}}missingChannelEntries(t){const e=t.filter(s=>this.claims.has(s.entry_id)),i=this.coveredEntryIds("hidden"),n=this.coveredEntryIds("visible");return{hiddenEntries:e.filter(s=>(s.data?.situational===!0||s.data?.situationalOnly===!0)&&!i.has(s.entry_id)),visibleEntries:e.filter(s=>s.data?.situationalOnly!==!0&&!n.has(s.entry_id))}}isRunSteer(t){return this.runNode&&t.kind==="message"&&(t.from??"system")==="human"&&t.data?.steer===!0}returnUnconsumedSteers(){const t=_()?.takeUnconsumed()??[];if(t.length===0)return;const e=new Set(t.flatMap(n=>n.messageIds));this.detachPending(n=>n.settlesThroughBridge===!0&&n.entryIds.every(s=>e.has(s)));const i=[...e].map(n=>this.claims.get(n)?.entry).filter(n=>n!==void 0);this.requeueFromBatch(i,e)}visibleRoute(t){return t.length===0?"none":this.isIdle()?"idle":this.runNode?t.some(e=>e.tier==="critical")?"critical":t.some(e=>this.isRunSteer(e))&&_()?.steerable()?"steer":"none":t.some(e=>e.tier==="critical")?"critical":t.some(e=>e.tier==="urgent"||e.kind==="final")?"steer":"followUp"}buildPlan(t){const{hiddenEntries:e,visibleEntries:i}=this.missingChannelEntries(t),n=new Set(e.map(u=>u.entry_id)),s=new Set(i.map(u=>u.entry_id)),d=t.filter(u=>n.has(u.entry_id)||s.has(u.entry_id)),a=this.isIdle()&&d.length>0&&d.every(u=>u.tier==="deferred"),f=this.visibleRoute(i)==="critical",o=!a&&!f&&e.length>0,m=!a&&!f&&i.length>0;return{hiddenEntries:e,visibleEntries:i,deferredHold:a,sendCount:Number(o)+Number(m)}}ordinaryPlan(){return this.buildPlan(this.buffer)}async contextCards(t,e){const i=[];if(e.some(n=>n.data?.situational===!0)){const n=e.at(-1).entry_id;let s;try{s=(await O(t)).situational_card}catch(d){this.raiseSituationalFatal(n,d)}s===null&&this.raiseSituationalFatal(n,new Error(`missing situational context for entry ${n}`)),i.push(s)}for(const n of e)n.data?.situationalOnly===!0&&i.push(...Q(n));return i}digest(t,e){try{return V(t)}catch(i){this.raiseReadFatal(e,i)}}sendDelivery(t,e,i,n,s){this.pendingHandoffs.length>=E&&this.raiseReadFatal(n,new Error(`pending handoff capacity exceeded before ${t} send`));let d;try{d=k(e,t)}catch(o){this.raiseReadFatal(n,o)}let a;try{g.fresh(o=>{a=o,w({level:"info",event:"inbox.delivery.started",operation_id:o,fields:b(d)}),i()})}catch(o){return a===void 0&&this.raiseReadFatal(n,o),F(a,d,"send",o),"failed"}a===void 0&&this.raiseReadFatal(n,new Error(`${t} delivery returned without an operation id`));const f={operationId:a,...d,...s===void 0?{}:{awaitingDequeue:s}};return this.canMutate()?(this.pendingHandoffs.push(f),"handed-off"):(this.disposed&&this.disposalReason!==void 0&&R(f,this.disposalReason),"inactive")}sendContext(t,e,i,n,s,d){const a=n==="steer"||n==="followUp"?n:this.isIdle()?void 0:"followUp",f=a===void 0||i.length===0?void 0:C(i.at(-1));return this.sendDelivery("hidden",e,()=>{this.runNode&&s&&this.pi.appendEntry("crtr-turn-input",{message_ids:e.map(o=>o.entry_id)}),i.forEach((o,m)=>{const u=s&&m===i.length-1;this.pi.sendMessage({customType:x,content:o,display:!0,details:{nodeId:t}},a!==void 0?{deliverAs:a}:u?{deliverAs:"followUp",triggerTurn:!0}:{})})},d,f)}sendVisible(t,e,i,n,s,d){return this.sendDelivery("visible",t,()=>{if(s!=="idle"&&s!=="steer"&&s!=="followUp")throw new Error(`visible send requested for non-send route ${s}`);for(const a of e)this.pi.sendMessage({customType:x,content:a,display:!0},s==="idle"?{}:{deliverAs:s});if(ne(i,n),s==="idle"){const a=oe();if(a===void 0)throw new Error("broker idle-turn admission is unavailable");a(i,this.runNode?t.map(f=>f.entry_id):[])}else this.pi.sendUserMessage(i,{deliverAs:s})},d,s==="steer"||s==="followUp"?C(i):void 0)}abortCritical(){try{this.lastCtx?.abort?.()}catch{}}requeueFromBatch(t,e){if(e.size===0)return;const i=t.filter(d=>e.has(d.entry_id)),n=new Set(i.map(d=>d.entry_id)),s=this.buffer.filter(d=>!n.has(d.entry_id));this.buffer.splice(0,this.buffer.length,...i,...s)}async executePlan(t,e,i){const n=new Set,s=this.visibleRoute(e.visibleEntries);if(s==="critical"){if(this.abortCritical(),!this.canMutate())return{inactive:!0,requeueEntryIds:n};for(const o of e.visibleEntries)n.add(o.entry_id);for(const o of e.hiddenEntries)n.add(o.entry_id);return{inactive:!1,requeueEntryIds:n}}if(this.runNode&&s==="none")return{inactive:!1,requeueEntryIds:new Set([...e.hiddenEntries,...e.visibleEntries].map(o=>o.entry_id))};if(this.runNode&&s==="steer"){let o=-1;e.visibleEntries.forEach((h,y)=>{this.isRunSteer(h)&&(o=y)});const m=e.visibleEntries.slice(0,o+1);if(e.hiddenEntries.length>0||m.some(h=>h.kind!=="message"||h.tier==="deferred"||(h.from??"system")!=="human"))return{inactive:!1,requeueEntryIds:new Set([...e.hiddenEntries,...e.visibleEntries].map(h=>h.entry_id))};const u=this.digest(m,i),c=u.map(h=>({text:h.text,messageIds:h.entries.map(y=>y.entry_id),cards:h.cards}));if(_()?.accept(c)!=="accepted")return{inactive:!1,requeueEntryIds:new Set([...e.hiddenEntries,...e.visibleEntries].map(h=>h.entry_id))};for(const h of u){const y=k(h.entries,"visible");g.fresh(N=>{w({level:"info",event:"inbox.delivery.started",operation_id:N,fields:b(y)}),this.pendingHandoffs.push({...y,operationId:N,settlesThroughBridge:!0})})}const p=new Set(m.map(h=>h.entry_id));for(const h of e.visibleEntries)p.has(h.entry_id)||n.add(h.entry_id);for(const h of e.hiddenEntries)n.add(h.entry_id);return{inactive:!1,requeueEntryIds:n}}const d=e.visibleEntries.length>0?this.digest(e.visibleEntries,i):[],[a,...f]=d;for(const o of f)for(const m of o.entries)n.add(m.entry_id);if(e.hiddenEntries.length>0){const o=await this.contextCards(t,e.hiddenEntries),m=this.sendContext(t,e.hiddenEntries,o,s,a===void 0,i);if(m==="inactive")return{inactive:!0,requeueEntryIds:n};if(m==="failed")for(const u of e.hiddenEntries)n.add(u.entry_id)}if(!this.canMutate())return{inactive:!0,requeueEntryIds:n};if(a!==void 0){const o=this.sendVisible(a.entries,a.cards,a.text,ce(a),s,i);if(o==="inactive")return{inactive:!0,requeueEntryIds:n};if(o==="failed")for(const m of a.entries)n.add(m.entry_id);else a.kind==="human"&&(await se(t,a.text).catch(()=>{}),re(t,a.text))}return{inactive:!1,requeueEntryIds:n}}async flushOrdinary(t){if(this.buffer.length===0)return"empty";const e=this.ordinaryPlan();if(e.deferredHold)return"held";if(this.pendingHandoffs.length+e.sendCount>E)return"capacity-blocked";const i=[...this.buffer],n=await this.executePlan(t,e,"poll");return n.inactive||(this.buffer.splice(0,i.length),this.requeueFromBatch(i,n.requeueEntryIds)),"attempted"}async prependHeldDeferredInbox(t){if(!this.canMutate()||this.nodeId===void 0||B())return;const e=this.ordinaryPlan();if(!e.deferredHold)return;const i=e.visibleEntries.length===0?[]:this.digest(e.visibleEntries,"poll"),n=+(e.hiddenEntries.length>0)+i.length;if(this.pendingHandoffs.length+n>E)return;const s=[...this.buffer];this.promptJoinInFlight=!0;try{this.runNode&&this.pi.appendEntry("crtr-turn-input",{message_ids:[...e.hiddenEntries,...e.visibleEntries].map(d=>d.entry_id)}),e.hiddenEntries.length>0&&await this.sendPromptJoinDelivery(t,"hidden",e.hiddenEntries,await this.contextCards(this.nodeId,e.hiddenEntries));for(const d of i)await this.sendPromptJoinDelivery(t,"visible",d.entries,[...d.cards,d.text]);if(!this.canMutate())return;this.buffer.splice(0,s.length)}finally{this.promptJoinInFlight=!1}}async sendPromptJoinDelivery(t,e,i,n){let s;try{s=k(i,e)}catch(f){this.raiseReadFatal("poll",f)}let d;try{await g.fresh(async f=>{d=f,w({level:"info",event:"inbox.delivery.started",operation_id:f,fields:b(s)});for(const o of n)await t.sendCustomMessage({customType:x,content:o,display:!0})})}catch(f){throw d===void 0&&this.raiseReadFatal("poll",f),F(d,s,"send",f),f}d===void 0&&this.raiseReadFatal("poll",new Error(`${e} prompt-join delivery returned without an operation id`));const a={operationId:d,...s,settleAtAgentSettled:!0};if(!this.canMutate()){this.disposed&&this.disposalReason!==void 0&&R(a,this.disposalReason);return}this.pendingHandoffs.push(a)}coveredEntryIds(t){const e=new Set;for(const i of this.pendingHandoffs)if(i.channel===t)for(const n of i.entryIds)e.add(n);return e}retryPendingCommit(t){const e=this.pendingCommit;if(e===void 0)return Promise.resolve("settled");if(e.attempt!==void 0)return e.attempt;const i=this.attemptPendingCommit(t,e).finally(()=>{e.attempt===i&&(e.attempt=void 0)});return e.attempt=i,i}async attemptPendingCommit(t,e){let i=!1;try{const s=await Y(t,e.receipts);if(s.action==="hold")return"refresh_held";for(const d of e.receipts){const a=s.results.find(f=>f.delivery_id===d.delivery_id);if(a?.outcome!==d.disposition&&a?.outcome!=="stale")throw new Error("daemon returned no terminal mail settlement outcome")}i=s.results.every(d=>d.outcome==="acknowledged")}catch(s){if(this.pendingCommit===e)for(const d of e.handoffs)F(d.operationId,d,"receipt_commit",s);return String(s?.message).startsWith("conversation_write_failed")&&(D({level:"error",event:"broker.exited",node_id:t,error:s,fields:{fault:"conversation_write_failed"}}),await Z(t,s),H()?.("conversation_write_failed",1)),"held"}if(this.pendingCommit!==e)return"settled";for(const s of e.entryIds)this.claims.delete(s);if(e.receipts.some(s=>s.disposition==="released")){const s=new Set(e.entryIds);this.buffer.splice(0,this.buffer.length,...this.buffer.filter(d=>!s.has(d.entry_id)))}this.pendingCommit=void 0;const n=this.detachPending(s=>s.entryIds.every(d=>!this.claims.has(d)));for(const s of n)i&&me(s);return"settled"}async settle(t,e,i,n=!1){if(i)try{if(e?.hasPendingMessages?.()===!0)return"held"}catch{}if(this.pendingCommit!==void 0)return this.retryPendingCommit(t);const s=te(),d=!s&&this.pendingHandoffs.some(c=>c.awaitingDequeue!==void 0)&&await this.nodeRefreshing(t);if(this.pendingCommit!==void 0)return this.retryPendingCommit(t);const a=new Set(this.buffer.map(c=>c.entry_id)),f=new Set(d?this.pendingHandoffs.filter(c=>c.awaitingDequeue!==void 0).flatMap(c=>[...c.entryIds]):[]),o=this.pendingHandoffs.filter(c=>(n||c.settleAtAgentSettled!==!0)&&c.entryIds.every(p=>!f.has(p))),m=new Set(o.flatMap(c=>[...c.entryIds])),u=[...this.claims.keys()].filter(c=>s||m.has(c)&&!a.has(c));return u.length===0?"settled":(this.pendingCommit={handoffs:o,entryIds:u,receipts:u.map(c=>{const p=this.claims.get(c);return{delivery_id:p.delivery_id,attempts:p.attempts,disposition:s?"released":"acknowledged"}})},this.retryPendingCommit(t))}async nodeRefreshing(t){try{return(await O(t)).node.intent==="refresh"}catch{return!0}}detachPending(t){const e=[];for(let i=this.pendingHandoffs.length-1;i>=0;i-=1){const n=this.pendingHandoffs[i];t(n)&&e.push(...this.pendingHandoffs.splice(i,1))}return e.reverse(),e}}function ve(r,t){v?.dispose("reload",!1);const e=new be(r,t,++he);v=e;const i=l(n=>e.prependHeldDeferredInbox(n),"promptJoin");globalThis[le]=i;try{e.start()}catch(n){throw e.dispose("reload",!1),n}return e}l(ve,"installWatcher");function we(r){const t=ve(r,ue());return()=>t.dispose("shutdown",!1)}l(we,"registerCanvasInboxWatcher");var He=we;export{He as default,we as registerCanvasInboxWatcher};
@@ -1,11 +1,11 @@
1
- var B=Object.defineProperty;var a=(n,s)=>B(n,"name",{value:s,configurable:!0});import{envNodeId as O,envRecapIdleMs as U}from"../shared/env.js";import{brokerExtensionState as $}from"../core/runtime/broker/daemon-ops.js";import{generateRecap as V}from"../core/runtime/recap.js";import{putBrokerRecap as q}from"../core/runtime/broker/daemon-ops.js";import{isReviewBoundary as F}from"../core/runtime/session-visibility.js";import{editorLabelForBrokerNode as H}from"../core/runtime/broker-extension-render.js";import{generateAndCommitRecapName as K,readRoadmap as Y}from"./broker-local.js";function z(){return U()}a(z,"idleMs");const J=7e3,b=5,Q=1200,C="crtr-recap";let A;function X(n){const s=n.content;return typeof s=="string"?s.trim():Array.isArray(s)?s.filter(r=>r!==null&&typeof r=="object"&&r.type==="text"&&typeof r.text=="string").map(r=>(r.text??"").trim()).filter(r=>r!=="").join(`
2
- `).trim():""}a(X,"messageText");function Z(n,s){let r;try{r=n.getEntries()}catch{return{entries:[],boundaryPresent:s===void 0}}if(s===void 0)return{entries:r,boundaryPresent:!0};const t=r.findIndex(o=>F(o,s));return t===-1?{entries:[],boundaryPresent:!1}:{entries:r.slice(t+1),boundaryPresent:!0}}a(Z,"recapEntries");function D(n,s,r,t){const{entries:o,boundaryPresent:u}=Z(n,t),l=[],m=a(d=>{const p=d.role;if(p!=="user"&&p!=="assistant")return;let f=X(d);if(f==="")return;f.length>s&&(f=`${f.slice(0,s)} \u2026[truncated]`);const g=`${p==="user"?"User":"Agent"}: ${f}`;l.at(-1)!==g&&l.push(g)},"append");for(const d of o)d.type!=="message"||d.message===void 0||m(d.message);return u&&r?.role==="assistant"&&m(r),l}a(D,"conversationParts");function ee(n,s,r){const t=D(n,Q,s,r);if(t.length===0)return"";if(t.length<=b*2)return t.join(`
1
+ var U=Object.defineProperty;var c=(t,o)=>U(t,"name",{value:o,configurable:!0});import{envNodeId as V,envRecapIdleMs as q}from"../shared/env.js";import{brokerExtensionState as $}from"../core/runtime/broker/daemon-ops.js";import{generateRecap as F}from"../core/runtime/recap.js";import{currentOrPendingCausedBy as D}from"../core/runtime/broker/turn-cause.js";import{putBrokerRecap as H}from"../core/runtime/broker/daemon-ops.js";import{isReviewBoundary as K}from"../core/runtime/session-visibility.js";import{editorLabelForBrokerNode as Y}from"../core/runtime/broker-extension-render.js";import{generateAndCommitRecapName as z,readRoadmap as J}from"./broker-local.js";function Q(){return q()}c(Q,"idleMs");const X=7e3,b=5,Z=1200,j="crtr-recap";let M;function ee(t){const o=t.content;return typeof o=="string"?o.trim():Array.isArray(o)?o.filter(r=>r!==null&&typeof r=="object"&&r.type==="text"&&typeof r.text=="string").map(r=>(r.text??"").trim()).filter(r=>r!=="").join(`
2
+ `).trim():""}c(ee,"messageText");function te(t,o){let r;try{r=t.getEntries()}catch{return{entries:[],boundaryPresent:o===void 0}}if(o===void 0)return{entries:r,boundaryPresent:!0};const e=r.findIndex(a=>K(a,o));return e===-1?{entries:[],boundaryPresent:!1}:{entries:r.slice(e+1),boundaryPresent:!0}}c(te,"recapEntries");function B(t,o,r,e){const{entries:a,boundaryPresent:u}=te(t,e),l=[],m=c(d=>{const p=d.role;if(p!=="user"&&p!=="assistant")return;let f=ee(d);if(f==="")return;f.length>o&&(f=`${f.slice(0,o)} \u2026[truncated]`);const g=`${p==="user"?"User":"Agent"}: ${f}`;l.at(-1)!==g&&l.push(g)},"append");for(const d of a)d.type!=="message"||d.message===void 0||m(d.message);return u&&r?.role==="assistant"&&m(r),l}c(B,"conversationParts");function ne(t,o,r){const e=B(t,Z,o,r);if(e.length===0)return"";if(e.length<=b*2)return e.join(`
3
3
 
4
- `);const o=t.length-b*2;return[...t.slice(0,b),`[\u2026 ${o} message${o===1?"":"s"} omitted \u2026]`,...t.slice(t.length-b)].join(`
4
+ `);const a=e.length-b*2;return[...e.slice(0,b),`[\u2026 ${a} message${a===1?"":"s"} omitted \u2026]`,...e.slice(e.length-b)].join(`
5
5
 
6
- `)}a(ee,"buildConversation");function te(n,s,r){const t=D(n,600,s,r);if(t.length===0)return"";const o=48;return t.length<=o?t.join(`
6
+ `)}c(ne,"buildConversation");function re(t,o,r){const e=B(t,600,o,r);if(e.length===0)return"";const a=48;return e.length<=a?e.join(`
7
7
 
8
- `):Array.from({length:o},(l,m)=>{const d=Math.round(m*(t.length-1)/(o-1));return t[d]}).join(`
8
+ `):Array.from({length:a},(l,m)=>{const d=Math.round(m*(e.length-1)/(a-1));return e[d]}).join(`
9
9
 
10
- `)}a(te,"buildNamingConversation");function ne(n,s=V,r){const t=O();if(t===void 0||t.trim()==="")return;let o,u,l,m;const d=a(i=>{const e=i.model?.provider?.trim(),c=i.model?.id?.trim();e&&c&&(m=`${e}/${c}`)},"captureLiveModel");let p=Date.now(),f=0,g,y=!1,_=!1,S=!1,h=!1,I;const v=a(()=>o!==void 0&&typeof o.setWidget=="function","canPaint"),w=a(i=>{try{o?.notify?.(i,"info")}catch{}},"notify"),x=a(()=>{if(v())try{o.setWidget(C,void 0,{placement:"aboveEditor"})}catch{}},"clearRecap"),j=a(i=>{if(!v()||i==="")return;const e=i.split(`
11
- `).filter(c=>c.trim()!=="");if(e.length!==0)try{o.setWidget(C,e,{placement:"aboveEditor"})}catch{}},"showRecap"),P=a(()=>{p=Date.now(),f+=1,S=!1,y&&(_=!0)},"onMessage"),T=a(i=>{if(!(n.getSessionName===void 0||n.setSessionName===void 0)){if(r!==void 0){const e=n.getSessionName();if(e===void 0)return;r(t,i,()=>n.getSessionName?.()===e,c=>{try{n.setSessionName?.(c)}catch{}});return}$(t).then(({node:e})=>{if(e.name.trim()!==""&&e.name!==e.kind)return;const c=H(e);n.getSessionName?.()===c&&K(t,i,{name:e.name,description:e.description??"",title:e.title??"",icon:e.icon??"",kind:e.kind},()=>n.getSessionName?.()===c,M=>{try{n.setSessionName?.(M)}catch{}})}).catch(()=>{})}},"refreshAutomaticName"),k=a(i=>{if(!v()||u===void 0||h)return;const e=g,c=l?.review_binding?.review_id??u.getEntries().map(N=>N.details?.reviewId).find(N=>typeof N=="string"),M=ee(u,e,c);if(M===""){S=!0,y=!1,_=!1,x();return}S=!1;const L=i?te(u,e,c):"",R=f;i||(I=R),h=!0,i&&(w("Generating recap\u2026"),T(L)),Y(t).catch(()=>null).then(N=>s(M,G=>{!i&&f!==R||!v()||q(t,G).then(()=>{!i&&f!==R||!v()||(y=!0,_=f!==R,j(G),i&&w("Recap ready"))}).catch(W=>w(`Recap could not be saved: ${String(W)}`)).finally(()=>{h=!1})},m,N??void 0)),setTimeout(()=>{h=!1},3e4).unref?.()},"requestRecap");n.registerCommand?.("recap",{description:"Generate a Goal/State recap and refresh the original generated session title",handler:a((i,e)=>{o=e.ui,u=e.sessionManager,d(e),k(!0)},"handler")}),n.on("session_start",(i,e)=>{o=e.ui,u=e.sessionManager,d(e),l=void 0,$(t).then(c=>{l=c.node}).catch(()=>{}),p=Date.now(),f=0,g=void 0,y=!1,_=!1,S=!1,h=!1,I=void 0,x()}),n.on("input",(i,e)=>{P()}),n.on("message_update",(i,e)=>{const c=i?.message;c?.role==="assistant"&&(g=c),P()}),n.on("turn_end",(i,e)=>{g=void 0,d(e),P()}),A!==void 0&&clearInterval(A);const E=setInterval(()=>{try{if(!v()||u===void 0||S||y&&!_||h||I===f||Date.now()-p<z())return;k(!1)}catch{}},J);typeof E.unref=="function"&&E.unref(),A=E,n.on("session_shutdown",()=>{clearInterval(E),A===E&&(A=void 0)})}a(ne,"registerCanvasRecap");var ue=ne;export{ue as default,ne as registerCanvasRecap};
10
+ `)}c(re,"buildNamingConversation");function ie(t,o=F,r){const e=V();if(e===void 0||e.trim()==="")return;let a,u,l,m;const d=c(i=>{const s=i.model?.provider?.trim(),n=i.model?.id?.trim();s&&n&&(m=`${s}/${n}`)},"captureLiveModel");let p=Date.now(),f=0,g,_=!1,S=!1,E=!1,y=!1,I;const v=c(()=>a!==void 0&&typeof a.setWidget=="function","canPaint"),P=c(i=>{try{a?.notify?.(i,"info")}catch{}},"notify"),k=c(()=>{if(v())try{a.setWidget(j,void 0,{placement:"aboveEditor"})}catch{}},"clearRecap"),T=c(i=>{if(!v()||i==="")return;const s=i.split(`
11
+ `).filter(n=>n.trim()!=="");if(s.length!==0)try{a.setWidget(j,s,{placement:"aboveEditor"})}catch{}},"showRecap"),w=c(()=>{p=Date.now(),f+=1,E=!1,_&&(S=!0)},"onMessage"),L=c(i=>{if(t.getSessionName===void 0||t.setSessionName===void 0)return;if(r!==void 0){const n=t.getSessionName();if(n===void 0)return;r(e,i,()=>t.getSessionName?.()===n,h=>{try{t.setSessionName?.(h)}catch{}});return}const s=D()??null;$(e).then(({node:n})=>{if(n.name.trim()!==""&&n.name!==n.kind)return;const h=Y(n);t.getSessionName?.()===h&&z(e,i,{name:n.name,description:n.description??"",title:n.title??"",icon:n.icon??"",kind:n.kind},()=>t.getSessionName?.()===h,s,x=>{try{t.setSessionName?.(x)}catch{}})}).catch(()=>{})},"refreshAutomaticName"),C=c(i=>{if(!v()||u===void 0||y)return;const s=g,n=l?.review_binding?.review_id??u.getEntries().map(A=>A.details?.reviewId).find(A=>typeof A=="string"),h=ne(u,s,n);if(h===""){E=!0,_=!1,S=!1,k();return}E=!1;const x=i?re(u,s,n):"",R=f;i||(I=R),y=!0,i&&(P("Generating recap\u2026"),L(x));const O=D()??null;J(e).catch(()=>null).then(A=>o(h,G=>{!i&&f!==R||!v()||H(e,G).then(()=>{!i&&f!==R||!v()||(_=!0,S=f!==R,T(G),i&&P("Recap ready"))}).catch(W=>P(`Recap could not be saved: ${String(W)}`)).finally(()=>{y=!1})},m,A??void 0,O)),setTimeout(()=>{y=!1},3e4).unref?.()},"requestRecap");t.registerCommand?.("recap",{description:"Generate a Goal/State recap and refresh the original generated session title",handler:c((i,s)=>{a=s.ui,u=s.sessionManager,d(s),C(!0)},"handler")}),t.on("session_start",(i,s)=>{a=s.ui,u=s.sessionManager,d(s),l=void 0,$(e).then(n=>{l=n.node}).catch(()=>{}),p=Date.now(),f=0,g=void 0,_=!1,S=!1,E=!1,y=!1,I=void 0,k()}),t.on("input",(i,s)=>{w()}),t.on("message_update",(i,s)=>{const n=i?.message;n?.role==="assistant"&&(g=n),w()}),t.on("turn_end",(i,s)=>{g=void 0,d(s),w()}),M!==void 0&&clearInterval(M);const N=setInterval(()=>{try{if(!v()||u===void 0||E||_&&!S||y||I===f||Date.now()-p<Q())return;C(!1)}catch{}},X);typeof N.unref=="function"&&N.unref(),M=N,t.on("session_shutdown",()=>{clearInterval(N),M===N&&(M=void 0)})}c(ie,"registerCanvasRecap");var pe=ie;export{pe as default,ie as registerCanvasRecap};
package/docs/ci-runner.md CHANGED
@@ -83,7 +83,7 @@ Installing and upgrading: get a registration token (`gh api -X POST repos/Northl
83
83
 
84
84
  ## No double run on `main`
85
85
 
86
- `publish.yml` starts with a `gate` job. It reads the pushed commit's **git tree** and looks for a workflow artifact named `tested-tree-<tree>`. `test.yml`'s final `tested` job creates that artifact only after `checks` and every `lane` shard pass, on every pull request run and on every run of Publish's own `tests` job.
86
+ `publish.yml` starts with a `gate` job. It reads the pushed commit's **git tree** and looks for a workflow artifact named `tested-tree-<tree>`. `test.yml`'s final `tested` job creates that artifact only in mode `full`, after `checks` and every `linux` shard pass and every `macos` job passed (or was skipped because this is a pull request, where macOS never runs), on every such pull request run and on every run of Publish's own `tests` job. A tree released from a pull request's record therefore never ran its `laptopOnly` tests on macOS.
87
87
 
88
88
  - Rebase-merging a PR whose tested merge ref has the same tree as the resulting `main` commit → artifact found → `tests` is skipped and `publish` proceeds.
89
89
  - A direct push to `main`, or a merge after `main` moved (a different tree from the one the PR run tested) → no artifact → the full suite runs exactly as before. A release commit (`chore: release …`, pushed by Publish itself) changes the tree, so a PR not updated since before the latest release runs the suite once on merge; update the branch to avoid it.
@@ -107,16 +107,16 @@ The hourly `17 * * * *` schedule and the `promote`, `api_ref`, `env_docker_ref`
107
107
 
108
108
  | Event | ubuntu `checks` + `select` | ubuntu `linux` (container) | `macos` (laptop-only tests) |
109
109
  | --- | --- | --- | --- |
110
- | pull request push | yes | the selected files (narrowed by Preflight's pick when there is one), sharded to ~4 min; none for a docs-only change | the selected files that carry `laptopOnly` tags, if any |
111
- | pull request with the `full-ci` label, or a change whose path forces it | yes | all of it | all of it |
110
+ | pull request push | yes | the selected files (narrowed by Preflight's pick when there is one), sharded to ~4 min; none for a docs-only change | never (macOS does not run on a pull request) |
111
+ | pull request with the `full-ci` label, or a change whose path forces it | yes | all of it | never (macOS does not run on a pull request) |
112
112
  | merge queue run (`merge_group`; inert until a queue ruleset exists) | yes | all of it | all of it |
113
113
  | `workflow_dispatch` (`gh workflow run test.yml --ref <branch>`) | yes | all of it | all of it |
114
114
  | push to `main` (Publish's `tests` job) | yes | all of it, unless `gate` found `tested-tree-<tree>` | same |
115
115
 
116
116
  - Selected tests run on every push with no label. Add `full-ci` to force the whole suite (`gh pr edit <n> --add-label full-ci`). Labels other than `full-ci` start nothing and cancel nothing.
117
- - On a pull request each matrix (`linux`, `macos`) is `fail-fast`: the first failing job cancels that matrix's other jobs, the matrix result is `failure`, and `full suite` still runs and is red. On main, merge queue and dispatch runs every job finishes. A Linux failure does not cancel the macOS matrix.
117
+ - On a pull request each matrix (`linux`; `macos` never runs there) is `fail-fast`: the first failing job cancels that matrix's other jobs, the matrix result is `failure`, and `full suite` still runs and is red. On main, merge queue and dispatch runs every job finishes. A Linux failure does not cancel the macOS matrix.
118
118
  - `record tested tree` runs only in mode `full`, so a partial run never lets Publish skip the suite.
119
- - The `full suite` job is the single result to require. It fails if `checks` or `select` did not succeed or any job failed or was cancelled; in mode `full` it also fails unless every `linux` and `macos` job succeeded (skipped is not enough), so Publish (which calls this file) can only release a tree whose full suite passed. In mode `none` it passes and says no tests are affected.
119
+ - The `full suite` job is the single result to require. It fails if `checks` or `select` did not succeed or any job failed or was cancelled; in mode `full` it also fails unless every `linux` and `macos` job succeeded (skipped is not enough, except `macos` on a pull request, where it never runs), so Publish (which calls this file) can only release a tree whose full suite passed. In mode `none` it passes and says no tests are affected.
120
120
  - `checks` builds once on ubuntu and uploads the JS output; the `linux` jobs unpack it and compile the Linux native addon in the container (the build is glibc, the image musl), and the `macos` jobs unpack it as is.
121
121
 
122
122
  ## The selector: `scripts/ci-select.mjs`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.594",
3
+ "version": "0.3.596",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -145,7 +145,7 @@
145
145
  },
146
146
  "license": "UNLICENSED",
147
147
  "dependencies": {
148
- "@north-light/identity": "0.3.594",
148
+ "@north-light/identity": "0.3.596",
149
149
  "@earendil-works/chord": "1.1.0",
150
150
  "@earendil-works/pi-agent-core": "1.1.0",
151
151
  "@earendil-works/pi-ai": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.594",
3
+ "version": "0.3.596",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",