@north-light/cli 0.3.577 → 0.3.579

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 (69) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/dto/run-events.d.ts +10 -4
  4. package/dist/build-root.js +1 -1
  5. package/dist/builtin-memory/crouter-sdk/release-notes.md +8 -0
  6. package/dist/builtin-memory/crouter-sdk/streaming.md +2 -0
  7. package/dist/builtin-memory/northlight-apps/concepts/runs.md +2 -0
  8. package/dist/builtin-memory/northlight-apps/guides/stream-a-run.md +1 -1
  9. package/dist/builtin-memory/northlight-apps/guides/test-your-app.md +1 -1
  10. package/dist/clients/attach/viewer.js +338 -338
  11. package/dist/core/command.js +5 -5
  12. package/dist/core/human/feedback-companion.js +1 -1
  13. package/dist/core/human/page.d.ts +2 -4
  14. package/dist/core/human/page.js +6 -6
  15. package/dist/core/human/request-files.d.ts +14 -0
  16. package/dist/core/human/request-files.js +1 -0
  17. package/dist/core/human/requests.d.ts +3 -0
  18. package/dist/core/human/requests.js +18 -17
  19. package/dist/core/human/tickets.js +1 -1
  20. package/dist/core/human/visible-paths.d.ts +7 -0
  21. package/dist/core/human/visible-paths.js +1 -1
  22. package/dist/core/llm/refusal.d.ts +2 -1
  23. package/dist/core/llm/refusal.js +1 -1
  24. package/dist/core/review/document.d.ts +4 -4
  25. package/dist/core/review/document.js +2 -2
  26. package/dist/core/review/project.d.ts +7 -5
  27. package/dist/core/review/project.js +2 -1
  28. package/dist/core/review/remap.js +1 -1
  29. package/dist/core/review/stage.d.ts +2 -0
  30. package/dist/core/review/stage.js +1 -1
  31. package/dist/core/review/store.js +1 -1
  32. package/dist/core/runs/operations.d.ts +1 -1
  33. package/dist/core/runs/operations.js +5 -5
  34. package/dist/core/runtime/broker/held-deferred-inbox.d.ts +4 -0
  35. package/dist/core/runtime/broker/held-deferred-inbox.js +1 -1
  36. package/dist/core/runtime/broker/rebind.d.ts +3 -0
  37. package/dist/core/runtime/broker/rebind.js +1 -1
  38. package/dist/core/runtime/broker/run-steer.d.ts +15 -0
  39. package/dist/core/runtime/broker/run-steer.js +1 -0
  40. package/dist/core/runtime/broker/run-turn-queue.d.ts +2 -0
  41. package/dist/core/runtime/broker/run-turn-queue.js +1 -1
  42. package/dist/core/runtime/broker.js +1 -1
  43. package/dist/core/runtime/runner/blaxel/client.d.ts +2 -2
  44. package/dist/core/runtime/runner/blaxel/client.js +1 -1
  45. package/dist/core/runtime/runner/blaxel/runner.js +2 -2
  46. package/dist/daemon/api/bridge.d.ts +1 -1
  47. package/dist/daemon/api/bridge.js +5 -5
  48. package/dist/daemon/api/handlers/feedback-comments.js +2 -2
  49. package/dist/daemon/api/handlers/inbox-page.js +1 -1
  50. package/dist/daemon/api/handlers/inbox.js +1 -1
  51. package/dist/daemon/api/handlers/reviews.js +1 -1
  52. package/dist/daemon/api/handlers/runs.js +1 -1
  53. package/dist/daemon/profile-delete.js +1 -1
  54. package/dist/daemon/review/finish.js +1 -1
  55. package/dist/daemon/review/sweep.js +1 -1
  56. package/dist/daemon/runs/append-section.js +2 -2
  57. package/dist/daemon/runs/observer.js +3 -3
  58. package/dist/pi-extensions/canvas-bash-valve.js +6 -6
  59. package/dist/pi-extensions/canvas-inbox-watcher.js +2 -2
  60. package/dist/shared/env.d.ts +5 -0
  61. package/dist/shared/env.js +2 -2
  62. package/docs/apps/concepts/runs.md +2 -0
  63. package/docs/apps/guides/stream-a-run.md +1 -1
  64. package/docs/apps/guides/test-your-app.md +1 -1
  65. package/docs/sdk/release-notes.md +8 -0
  66. package/docs/sdk/streaming.md +2 -0
  67. package/package.json +2 -2
  68. package/packages/crouter-identity/package.json +1 -1
  69. package/runtime.lock.json +162 -665
@@ -1,7 +1,7 @@
1
- var ce=Object.defineProperty;var o=(e,n)=>ce(e,"name",{value:n,configurable:!0});import{envBashAutoBgDeadlineMs as z,envBashValveMs as ue,envNodeId as de}from"../shared/env.js";import{closeSync as le,existsSync as v,mkdirSync as K,openSync as fe,readFileSync as W,readSync as he,rmSync as q,statSync as X,writeFileSync as $}from"node:fs";import{homedir as be}from"node:os";import{dirname as me}from"node:path";import{performance as N}from"node:perf_hooks";import{backgroundBashJob as Y,bashJobPaths as pe,clearJobDeadlineFile as ge,writeJobDeadlineFile as we,cancelBashJobProcessTree as ye,countJobLogLines as ve,formatBashElapsed as Q,newBashJobId as Ee,normalizeBashJobPurpose as je,tailJobLog as Ie}from"../core/bash-jobs.js";import{MAX_BACKGROUND_BASH_LOG_BYTES as ht,BACKGROUND_BASH_LOG_TRUNCATION_MARKER as bt}from"../core/bash-job-supervisor.js";import{brokerApiUrl as _e,recordBackgroundedJob as Be,reportBrokerJob as Se,stopBrokerBashJob as ke}from"../core/runtime/broker/daemon-ops.js";import{bashJobsUseLauncher as Le,launchBashJobSupervisor as Ae}from"../core/runtime/broker/bash-job-launch.js";import{JobReporter as xe}from"../core/runtime/broker/job-reporter.js";import{bashCommandRefusal as Oe}from"../core/bash-guard.js";import{readConfig as Je}from"../core/config.js";import{PREVIEW_RESULT_PATH_ENV as Re,previewResultPath as Te}from"../core/preview-result-path.js";import{Type as _}from"typebox";import{createBashToolDefinition as Z}from"@earendil-works/pi-coding-agent";const $e=200;function Ne(){return ue()}o(Ne,"resolveValveMs");function Ce(e){return pe(e,Ee())}o(Ce,"makeJobPaths");const B=new Set;let ee=!1;function te(){for(const e of[...B])try{Y(e),B.delete(e)}catch(n){process.stderr.write(`crtr: failed to background bash job ${e.jobId} during broker termination: ${n.message}
2
- `)}}o(te,"backgroundLiveJobsForTermination");function Me(){ee||(ee=!0,process.prependListener("SIGTERM",te),process.platform!=="win32"&&process.prependListener("SIGHUP",te))}o(Me,"installTerminationHandoff");const De=64*1024;function C(e,n,i){let s;try{s=X(e)}catch{return n}if(s.size<=n)return n;const c=fe(e,"r");try{for(;n<s.size;){const b=Math.min(De,s.size-n),m=Buffer.allocUnsafe(b),u=he(c,m,0,b,n);if(u===0)break;i(m.subarray(0,u)),n+=u}return n}finally{le(c)}}o(C,"tailOnce");function E(e){try{q(e,{recursive:!0,force:!0})}catch{}}o(E,"cleanupJobDir");function Pe(e){try{const n=W(e,"utf8").trim(),i=Number(n);return Number.isFinite(i)?i:null}catch{return null}}o(Pe,"readExitCode");function Ue(e){try{if(X(e).isDirectory())return{cwd:e}}catch{}const n=be();return{cwd:n,warning:`[working directory no longer exists: ${e}; running from ${n}]
3
- `}}o(Ue,"resolveExecutionCwd");function ne(e,n,i=()=>null,s,c=()=>{}){return{exec:o(async(b,m,{onData:u,signal:f,timeout:p,env:S})=>new Promise((j,d)=>{const w=i();if(f?.aborted){d(new Error("aborted"));return}const k=Oe(b,m);if(k!==null){d(new Error(k));return}const t=Ce(n);K(t.dir,{recursive:!0}),$(t.cmdSh,b),$(t.jobLog,""),$(t.jobRun,"");let l=0,g=!1,I,L,A,M=!1;const oe=Le(),D=N.now(),re=Ne();let y;const ie=o(()=>{y&&clearInterval(y),f&&f.removeEventListener("abort",G),B.delete(t)},"teardown"),h=o(r=>{g||(g=!0,ie(),r())},"finish");let P=!1;function x(r=!1){if(g||P)return;P=!0,y&&clearInterval(y);const a=r?Date.now()+z():void 0;a!==void 0&&we(t,a),Be(e,t.jobId,{command:W(t.cmdSh,"utf8"),...w===null?{}:{purpose:w},...a===void 0?{}:{deadline_at_ms:a}}).then(()=>{try{ge(t)}catch{}U(r,"")},T=>U(r,`
4
- [crtr could not record job ${t.jobId} (${T.message}); it will NOT message you when it exits. Watch its log.]`))}o(x,"finishBackground");function U(r,a){const T=Q(N.now()-D);let H="";if(r){const ae=z();H=`It will be CANCELED in ${Q(ae)} unless you claim it: crtr node bash extend ${t.jobId} --for <duration> (or --keep to run to completion). `}const V=`
5
- [backgrounded as job ${t.jobId} after ${T} in the foreground${r?" (auto)":""}. `+H+(a===""?"It will interrupt you with a message when it exits. ":"")+`Watch: tail -f ${t.jobLog} ; stop: crtr node bash kill ${t.jobId}]`;u(Buffer.from(V+a)),c({jobId:t.jobId,logPath:t.jobLog,notice:V+a}),h(()=>j({exitCode:0}))}o(U,"handOff");function F(){l=C(t.jobLog,l,u);const r=Pe(t.jobExit);h(()=>j({exitCode:r})),E(t.dir)}o(F,"finishForeground");function se(r=!1){const a=Y(t);if(a==="backgrounded"||a==="already-backgrounded"){x(r);return}if(a==="finished"){F();return}h(()=>d(new Error(`bash job ${t.jobId} disappeared before it could be backgrounded`))),E(t.dir)}o(se,"requestBackground");function O(r){if(!(g||M)){if(l=C(t.jobLog,l,u),v(t.jobBg)){x();return}if(!v(t.jobExit)){if(I===void 0){A??=r;return}if(oe){M=!0,ke(e,t.jobId).then(()=>{h(()=>d(r)),E(t.dir)},a=>h(()=>d(a)));return}I!==void 0&&L!==void 0&&ye(I,L),h(()=>d(r)),E(t.dir)}}}o(O,"requestStop");function G(){O(new Error("aborted"))}o(G,"onAbort"),f&&f.addEventListener("abort",G,{once:!0});const J=Ue(m);J.warning&&u(Buffer.from(J.warning));let R=S??process.env;s!==void 0&&(K(me(s),{recursive:!0}),q(s,{force:!0}),R={...R,[Re]:s}),Ae(e,t,n,J.cwd,R).then(r=>{I=r.pgid,L=r.identity,B.add(t),A!==void 0&&O(A)},r=>{h(()=>d(r)),E(t.dir)}),y=setInterval(()=>{if(l=C(t.jobLog,l,u),v(t.jobDone)){F();return}if(v(t.jobBg)){x();return}if(v(t.jobExit))return;const r=N.now()-D;if(p!==void 0&&p>0){r>=p*1e3&&O(new Error(`timeout:${p}`));return}r>=re&&se(!0)},$e)}),"exec")}}o(ne,"createValveOperations");const Fe=["One short clause naming what this step is FOR, in the words of the person you",'are working for \u2014 "Reading the Acme thread", not "Fetching emails" and never','"Running GMAIL_FETCH_EMAILS". A person reads this while the command runs; it',"is the only thing they see of this step. Under about eight words. Omit it for","steps that only touch your own scratch work."].join(" "),Ge=3;function He(e){const n=Ie(e.logPath,Ge),i=Math.max(0,ve(e.logPath)-n.length),s=n.length===0?"(no output yet)":n.join(`
1
+ var fe=Object.defineProperty;var o=(e,n)=>fe(e,"name",{value:n,configurable:!0});import{envBashAutoBgDeadlineMs as q,envBashValveMs as he,envNodeId as be}from"../shared/env.js";import{closeSync as me,existsSync as v,mkdirSync as X,openSync as pe,readFileSync as Y,readSync as ge,rmSync as Q,statSync as Z,writeFileSync as M}from"node:fs";import{homedir as we}from"node:os";import{dirname as ye}from"node:path";import{performance as N}from"node:perf_hooks";import{backgroundBashJob as ee,bashJobPaths as ve,clearJobDeadlineFile as Ee,writeJobDeadlineFile as _e,cancelBashJobProcessTree as je,countJobLogLines as Se,formatBashElapsed as te,newBashJobId as ke,normalizeBashJobPurpose as Ie,tailJobLog as Ae}from"../core/bash-jobs.js";import{MAX_BACKGROUND_BASH_LOG_BYTES as gt,BACKGROUND_BASH_LOG_TRUNCATION_MARKER as wt}from"../core/bash-job-supervisor.js";import{brokerApiUrl as Be,recordBackgroundedJob as Le,reportBrokerJob as xe,stopBrokerBashJob as Oe}from"../core/runtime/broker/daemon-ops.js";import{bashJobsUseLauncher as Te,launchBashJobSupervisor as Je}from"../core/runtime/broker/bash-job-launch.js";import{JobReporter as Re}from"../core/runtime/broker/job-reporter.js";import{bashCommandRefusal as $e}from"../core/bash-guard.js";import{readConfig as Me}from"../core/config.js";import{PREVIEW_RESULT_PATH_ENV as Ne,previewResultPath as Ce}from"../core/preview-result-path.js";import{Type as S}from"typebox";import{createBashToolDefinition as ne}from"@earendil-works/pi-coding-agent";const oe=200,De=5e3;function Pe(){return he()}o(Pe,"resolveValveMs");function Ue(e){return ve(e,ke())}o(Ue,"makeJobPaths");const k=new Set;let re=!1;function ie(){for(const e of[...k])try{ee(e),k.delete(e)}catch(n){process.stderr.write(`crtr: failed to background bash job ${e.jobId} during broker termination: ${n.message}
2
+ `)}}o(ie,"backgroundLiveJobsForTermination");function Fe(){re||(re=!0,process.prependListener("SIGTERM",ie),process.platform!=="win32"&&process.prependListener("SIGHUP",ie))}o(Fe,"installTerminationHandoff");const Ge=64*1024;function C(e,n,i){let s;try{s=Z(e)}catch{return n}if(s.size<=n)return n;const c=pe(e,"r");try{for(;n<s.size;){const b=Math.min(Ge,s.size-n),m=Buffer.allocUnsafe(b),u=ge(c,m,0,b,n);if(u===0)break;i(m.subarray(0,u)),n+=u}return n}finally{me(c)}}o(C,"tailOnce");function E(e){try{Q(e,{recursive:!0,force:!0})}catch{}}o(E,"cleanupJobDir");function He(e){try{const n=Y(e,"utf8").trim(),i=Number(n);return Number.isFinite(i)?i:null}catch{return null}}o(He,"readExitCode");function Ve(e){try{if(Z(e).isDirectory())return{cwd:e}}catch{}const n=we();return{cwd:n,warning:`[working directory no longer exists: ${e}; running from ${n}]
3
+ `}}o(Ve,"resolveExecutionCwd");function se(e,n,i=()=>null,s,c=()=>{}){return{exec:o(async(b,m,{onData:u,signal:f,timeout:p,env:I})=>new Promise((_,d)=>{const w=i();if(f?.aborted){d(new Error("aborted"));return}const A=$e(b,m);if(A!==null){d(new Error(A));return}const t=Ue(n);X(t.dir,{recursive:!0}),M(t.cmdSh,b),M(t.jobLog,""),M(t.jobRun,"");let l=0,g=!1,j,B,L,D=!1;const ae=Te(),P=N.now();let U=0,x=P;const F=o(()=>N.now()-P-U,"foregroundMs"),ce=Pe();let y;const ue=o(()=>{y&&clearInterval(y),f&&f.removeEventListener("abort",z),k.delete(t)},"teardown"),h=o(r=>{g||(g=!0,ue(),r())},"finish");let G=!1;function O(r=!1){if(g||G)return;G=!0,y&&clearInterval(y);const a=r?Date.now()+q():void 0;a!==void 0&&_e(t,a),Le(e,t.jobId,{command:Y(t.cmdSh,"utf8"),...w===null?{}:{purpose:w},...a===void 0?{}:{deadline_at_ms:a}}).then(()=>{try{Ee(t)}catch{}H(r,"")},$=>H(r,`
4
+ [crtr could not record job ${t.jobId} (${$.message}); it will NOT message you when it exits. Watch its log.]`))}o(O,"finishBackground");function H(r,a){const $=te(F());let K="";if(r){const le=q();K=`It will be CANCELED in ${te(le)} unless you claim it: crtr node bash extend ${t.jobId} --for <duration> (or --keep to run to completion). `}const W=`
5
+ [backgrounded as job ${t.jobId} after ${$} in the foreground${r?" (auto)":""}. `+K+(a===""?"It will interrupt you with a message when it exits. ":"")+`Watch: tail -f ${t.jobLog} ; stop: crtr node bash kill ${t.jobId}]`;u(Buffer.from(W+a)),c({jobId:t.jobId,logPath:t.jobLog,notice:W+a}),h(()=>_({exitCode:0}))}o(H,"handOff");function V(){l=C(t.jobLog,l,u);const r=He(t.jobExit);h(()=>_({exitCode:r})),E(t.dir)}o(V,"finishForeground");function de(r=!1){const a=ee(t);if(a==="backgrounded"||a==="already-backgrounded"){O(r);return}if(a==="finished"){V();return}h(()=>d(new Error(`bash job ${t.jobId} disappeared before it could be backgrounded`))),E(t.dir)}o(de,"requestBackground");function T(r){if(!(g||D)){if(l=C(t.jobLog,l,u),v(t.jobBg)){O();return}if(!v(t.jobExit)){if(j===void 0){L??=r;return}if(ae){D=!0,Oe(e,t.jobId).then(()=>{h(()=>d(r)),E(t.dir)},a=>h(()=>d(a)));return}j!==void 0&&B!==void 0&&je(j,B),h(()=>d(r)),E(t.dir)}}}o(T,"requestStop");function z(){T(new Error("aborted"))}o(z,"onAbort"),f&&f.addEventListener("abort",z,{once:!0});const J=Ve(m);J.warning&&u(Buffer.from(J.warning));let R=I??process.env;s!==void 0&&(X(ye(s),{recursive:!0}),Q(s,{force:!0}),R={...R,[Ne]:s}),Je(e,t,n,J.cwd,R).then(r=>{j=r.pgid,B=r.identity,k.add(t),L!==void 0&&T(L)},r=>{h(()=>d(r)),E(t.dir)}),y=setInterval(()=>{const r=N.now();if(r-x>De&&(U+=r-x-oe),x=r,l=C(t.jobLog,l,u),v(t.jobDone)){V();return}if(v(t.jobBg)){O();return}if(v(t.jobExit))return;const a=F();if(p!==void 0&&p>0){a>=p*1e3&&T(new Error(`timeout:${p}`));return}a>=ce&&de(!0)},oe)}),"exec")}}o(se,"createValveOperations");const ze=["One short clause naming what this step is FOR, in the words of the person you",'are working for \u2014 "Reading the Acme thread", not "Fetching emails" and never','"Running GMAIL_FETCH_EMAILS". A person reads this while the command runs; it',"is the only thing they see of this step. Under about eight words. Omit it for","steps that only touch your own scratch work."].join(" "),Ke=3;function We(e){const n=Ae(e.logPath,Ke),i=Math.max(0,Se(e.logPath)-n.length),s=n.length===0?"(no output yet)":n.join(`
6
6
  `),c=i===0?"":`
7
- [${i} earlier output line${i===1?"":"s"} are in the job log only]`;return{content:[{type:"text",text:`${s}${c}${e.notice}`}],details:void 0}}o(He,"handoffResult");function Ve(e){return{...e,parameters:_.Object({purpose:_.Optional(_.String({description:Fe})),...e.parameters.properties})}}o(Ve,"withPurpose");const ze=["A command that needs longer than one call \u2014 a test lane, a build, a soak \u2014 is started with","`crtr node bash run '<command>'`, which returns at once and wakes you with the exit code; the job is a canvas","object others can watch. Do not use `&`, `nohup`, `setsid`, or a cron that polls a log. A foreground command","past 5 minutes is moved to a job automatically; a `timeout` kills it and everything it started."].join(" ");function Ke(e){const{command:n,...i}=e.parameters.properties;return{...e,parameters:_.Object({command:{...n,description:`${n.description??""} ${ze}`.trim()},...i})}}o(Ke,"withJobGuidance");function We(e,n,i,s){const c=Ke(Z(i,{operations:ne(e,n)}));return{...s?Ve(c):c,execute:o(async(u,f,p,S,j)=>{const d=s?je(f.purpose):null;let w=null;const t=await Z(i,{operations:ne(e,n,()=>d,Te(u),g=>{w=g})}).execute(u,f,p,S,j),l=w;return l===null?t:He(l)},"execute")}}o(We,"createValveToolDefinition");function qe(e){const n=de();if(n===void 0||n.trim()==="")return;const i=process.env.CRTR_CONTEXT_DIR;i===void 0||i.trim()===""||(Me(),_e()!==void 0&&new xe(i,(s,c)=>Se(n,s,c)).start(),e.on("session_start",(s,c)=>{e.registerTool(We(n,i,c.cwd,Je("user").bash_tool_purpose))}))}o(qe,"default");export{bt as BACKGROUND_BASH_LOG_TRUNCATION_MARKER,ht as MAX_BACKGROUND_BASH_LOG_BYTES,ne as createValveOperations,We as createValveToolDefinition,qe as default};
7
+ [${i} earlier output line${i===1?"":"s"} are in the job log only]`;return{content:[{type:"text",text:`${s}${c}${e.notice}`}],details:void 0}}o(We,"handoffResult");function qe(e){return{...e,parameters:S.Object({purpose:S.Optional(S.String({description:ze})),...e.parameters.properties})}}o(qe,"withPurpose");const Xe=["A command that needs longer than one call \u2014 a test lane, a build, a soak \u2014 is started with","`crtr node bash run '<command>'`, which returns at once and wakes you with the exit code; the job is a canvas","object others can watch. Do not use `&`, `nohup`, `setsid`, or a cron that polls a log. A foreground command","past 5 minutes is moved to a job automatically; a `timeout` kills it and everything it started."].join(" ");function Ye(e){const{command:n,...i}=e.parameters.properties;return{...e,parameters:S.Object({command:{...n,description:`${n.description??""} ${Xe}`.trim()},...i})}}o(Ye,"withJobGuidance");function Qe(e,n,i,s){const c=Ye(ne(i,{operations:se(e,n)}));return{...s?qe(c):c,execute:o(async(u,f,p,I,_)=>{const d=s?Ie(f.purpose):null;let w=null;const t=await ne(i,{operations:se(e,n,()=>d,Ce(u),g=>{w=g})}).execute(u,f,p,I,_),l=w;return l===null?t:We(l)},"execute")}}o(Qe,"createValveToolDefinition");function Ze(e){const n=be();if(n===void 0||n.trim()==="")return;const i=process.env.CRTR_CONTEXT_DIR;i===void 0||i.trim()===""||(Fe(),Be()!==void 0&&new Re(i,(s,c)=>xe(n,s,c)).start(),e.on("session_start",(s,c)=>{e.registerTool(Qe(n,i,c.cwd,Me("user").bash_tool_purpose))}))}o(Ze,"default");export{wt as BACKGROUND_BASH_LOG_TRUNCATION_MARKER,gt as MAX_BACKGROUND_BASH_LOG_BYTES,se as createValveOperations,Qe as createValveToolDefinition,Ze as default};
@@ -1,2 +1,2 @@
1
- var U=Object.defineProperty;var f=(s,t)=>U(s,"name",{value:t,configurable:!0});import{envInboxBetweenTurns as q,envNodeId as B,envProfileId as W,envWatcherDebounceMs as J,envWatcherTickMs as $,envWatcherBackstopMs as G}from"../shared/env.js";import{coalesceBrokerInbox as X,entryCards as j}from"../core/runtime/broker/inbox.js";import{acknowledgeBrokerMail as z,claimBrokerMail as L,brokerExtensionState as F,recordConversationWriteFault as V}from"../core/runtime/broker/daemon-ops.js";import{materializeSpilledBody as Q}from"../core/runtime/broker/spilled-body.js";import{brokerSignalClient as S}from"../core/runtime/broker/signal-stream.js";import{wasTurnRefused as Y}from"../core/runtime/broker/extension-abort.js";import{isDaemonTransportApiError as R,isTokenRevokedApiError as K}from"../api/errors.js";import{announceMessageOrigin as Z,messageSignature as _}from"../core/runtime/stamp/channel.js";import{captureGoalIfAbsent as ee,generateAndCommitInitialName as te}from"./broker-local.js";import{emitEvent as O}from"../core/events/emit.js";import{errorClassFromError as ie}from"../core/events/errors.js";import{operationIdContext as m}from"../core/events/operation-id.js";import{SITUATIONAL_CONTEXT_CUSTOM_TYPE as I}from"../shared/generated-context.js";import{isProfilePaused as ne}from"../core/profiles/manifest.js";import{brokerDisposeAndExit as N,brokerIdleTurnStart as re,brokerRunTurnBusy as se,HELD_DEFERRED_INBOX_PROMPT_JOIN as ae}from"../core/runtime/broker/held-deferred-inbox.js";const b=64,A=16;let p,oe=0;class D extends Error{static{f(this,"WatcherFatalError")}constructor(t,e){super(t,{cause:e}),this.name="WatcherFatalError"}}function de(){return{tickMs:$(),debounceMs:J()}}f(de,"resolveCadence");function E(){const s=B();return s===void 0||s.trim()===""?void 0:s}f(E,"currentNodeId");function P(){const s=W();return s!==void 0&&s!==""&&ne(s)}f(P,"profilePaused");function k(s){try{return ie(s)}catch{return{class:"unknown"}}}f(k,"safeErrorClass");function v(s){try{O(s)}catch{}}f(v,"emitBestEffort");function w(s){try{m.fresh(t=>{v({...s,operation_id:t})})}catch{}}f(w,"emitFreshBestEffort");function C(s,t){try{m.run(s,()=>{v({...t,operation_id:s})})}catch{}}f(C,"emitStoredBestEffort");function le(s){return s.kind==="human"?{type:"human"}:{type:"runtime",kind:"inbox",entries:s.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}}))}}f(le,"deliveryOrigin");function x(s,t){if(s.length===0)throw new Error(`cannot create an empty ${t} delivery`);const e=[...new Set(s.map(r=>r.operation_id))].sort(),i=e.length-A;return{channel:t,entryIds:s.map(r=>r.entry_id),throughEntryId:s.at(-1).entry_id,sourceOperationIds:e.slice(0,A),...i>0?{sourceOperationIdsOmitted:i}:{}}}f(x,"deliveryFacts");function y(s){return{channel:s.channel,entry_ids:s.entryIds,through_entry_id:s.throughEntryId,source_operation_ids:s.sourceOperationIds,...s.sourceOperationIdsOmitted===void 0?{}:{source_operation_ids_omitted:s.sourceOperationIdsOmitted}}}f(y,"deliveryFields");function fe(s){C(s.operationId,{level:"info",event:"inbox.delivery.completed",outcome:"succeeded",fields:y(s)})}f(fe,"emitDeliveryCompleted");function T(s,t,e,i){C(s,{level:"error",event:"inbox.delivery.failed",outcome:"failed",error:i,error_class:k(i),fields:{stage:e,...y(t)}})}f(T,"emitDeliveryFailure");function M(s,t){C(s.operationId,{level:"error",event:"inbox.delivery.failed",outcome:"failed",error_class:{class:"unknown"},fields:{stage:"abandoned",reason:t,...y(s)}})}f(M,"emitDeliveryAbandoned");function he(s){return typeof s=="string"?s:Array.isArray(s)?s.filter(t=>typeof t=="object"&&t!==null&&typeof t.text=="string").map(t=>t.text).join(`
2
- `):""}f(he,"messageText");function H(s){return s==="quit"||s==="reload"||s==="new"||s==="resume"||s==="fork"}f(H,"isSessionShutdownReason");function ue(s){switch(s){case"quit":return"shutdown";case"reload":case"new":case"resume":case"fork":return"reload";default:return s}}f(ue,"disposalReasonForShutdown");function ce(s){return typeof s=="string"?s.slice(0,128):s===null?"null":s===void 0?"undefined":typeof s=="number"||typeof s=="boolean"||typeof s=="bigint"?String(s).slice(0,128):typeof s}f(ce,"boundedRuntimeReason");class me{static{f(this,"CanvasInboxWatcher")}pi;cadence;generation;timer;backstop;debounceTimer;signalUnsubscribers=[];rerun=!1;retryNeeded=!1;polledOk=!1;armed=!1;lastCtx;streaming=!1;runNode=q();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=f((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(o=>o.awaitingDequeue!==void 0))return;const r=_(he(i.content)),n=this.pendingHandoffs.find(o=>o.awaitingDequeue===r);n!==void 0&&delete n.awaitingDequeue}),this.pi.on("agent_settled",async(e,i)=>{if(!this.canMutate())return;this.captureCtx(e,i);const r=E();if(r!==void 0)try{this.assertNodeOwnership(r,"settle"),await this.tickInFlight,this.seeded||await this.claimMail(r);const n=await this.settle(r,i,!1,!0);n==="refresh_held"&&this.dispose("refresh",!1),this.runNode&&n==="settled"&&this.canMutate()&&this.buffer.length>0&&await this.flushOrdinary(r),this.syncTimers()}catch(n){this.dispose("shutdown",!1,void 0,{error:n,stage:"settle"})}}),this.pi.on("session_shutdown",async e=>{if(!this.canMutate())return;let i;try{i=e?.reason}catch{i=void 0}const r=H(i)?ue(i):"reload";H(i)||w({level:"warn",event:"inbox.lifecycle.unknown_shutdown_reason",outcome:"failed",fields:{reason:ce(i)}});try{await this.disposeAtSettlement(r,this.lastCtx)}catch(n){this.dispose(r,!1,void 0,{error:n,stage:"settle"})}})}arm(){if(this.armed)return;this.armed=!0;const t=S();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=E();if(i===void 0){this.dispose(t,!1);return}try{this.assertNodeOwnership(i,"settle");const r=await this.settle(i,e,t!=="refresh");this.dispose(r==="refresh_held"?"refresh":t,!1)}catch(r){this.dispose(t,!1,void 0,{error:r,stage:"settle"})}}dispose(t,e,i,r){if(this.disposed)return;this.disposed=!0,this.disposalReason=t,this.clearTimer();for(const o of this.signalUnsubscribers.splice(0))o();const n=r;{const o=[...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()&&(p=void 0);for(const a of o)M(a,t)}if(n!==void 0){this.scheduleHandlerFatal(n.error,n.stage);return}}ownsCurrentGeneration(){return p===this&&p.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=S().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"),G()),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&&se())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,r){throw this.fatalError===void 0&&(this.fatalError=new D(e,i),this.clearTimer(),w({level:"error",event:t,outcome:"failed",error:i,error_class:k(i),fields:r})),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 D)i=t;else try{this.raiseReadFatal(e,t)}catch(r){i=r}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(R(i)){this.retryNeeded=!0,w({level:"warn",event:"inbox.read.failed",outcome:"failed",error:i,error_class:k(i),fields:{stage:"poll"}});return}if(this.polledOk&&K(i)){this.clearTimer(),w({level:"info",event:"inbox.poll.token_revoked",outcome:"succeeded",fields:{stage:"poll"}}),N()?.("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 L(t,"wake",this.reportBodies)}catch(a){throw R(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 r=new Set(e.deliveries.map(a=>a.entry.entry_id)),n=this.buffer.filter(a=>!r.has(a.entry_id));for(const a of n)this.claims.delete(a.entry_id);if(this.buffer.splice(0,this.buffer.length,...this.buffer.filter(a=>r.has(a.entry_id))),n.length>0){const a=globalThis[Symbol.for("@crouton-kit/crtr:watcher-cancel-probe")];typeof a=="function"&&a(n.map(l=>l.entry_id),process.hrtime.bigint())}let o=!1;for(const a of e.deliveries){const l=a.entry.entry_id;this.claims.has(l)||(a.entry=Q(a.entry),this.claims.set(l,a),a.handoff==="inherited"&&!i?m.fresh(d=>{this.pendingHandoffs.push({...x([a.entry],"visible"),operationId:d,settleAtAgentSettled:!0})}):(this.buffer.push(a.entry),this.lastArrival=Date.now(),o=!0))}return o&&this.armDebounce(),this.syncTimers(),!0}async tick(){const t=E();if(t!==void 0){if(this.pendingCommit!==void 0){await this.retryPendingCommit(t)==="refresh_held"&&this.canMutate()&&this.dispose("refresh",!1);return}this.promptJoinInFlight||P()||this.pendingHandoffs.length>=b||await this.claimMail(t)&&this.buffer.length>0&&(!this.runNode||this.isIdle()||this.buffer.some(e=>e.tier==="critical"))&&Date.now()-this.lastArrival>=this.cadence.debounceMs&&await this.flushOrdinary(t)}}missingChannelEntries(t){const e=t.filter(n=>this.claims.has(n.entry_id)),i=this.coveredEntryIds("hidden"),r=this.coveredEntryIds("visible");return{hiddenEntries:e.filter(n=>(n.data?.situational===!0||n.data?.situationalOnly===!0)&&!i.has(n.entry_id)),visibleEntries:e.filter(n=>n.data?.situationalOnly!==!0&&!r.has(n.entry_id))}}visibleRoute(t){return t.length===0?"none":this.isIdle()?"idle":this.runNode?t.some(e=>e.tier==="critical")?"critical":"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),r=new Set(e.map(u=>u.entry_id)),n=new Set(i.map(u=>u.entry_id)),o=t.filter(u=>r.has(u.entry_id)||n.has(u.entry_id)),a=this.isIdle()&&o.length>0&&o.every(u=>u.tier==="deferred"),l=this.visibleRoute(i)==="critical",d=!a&&!l&&e.length>0,c=!a&&!l&&i.length>0;return{hiddenEntries:e,visibleEntries:i,deferredHold:a,sendCount:Number(d)+Number(c)}}ordinaryPlan(){return this.buildPlan(this.buffer)}async contextCards(t,e){const i=[];if(e.some(r=>r.data?.situational===!0)){const r=e.at(-1).entry_id;let n;try{n=(await F(t)).situational_card}catch(o){this.raiseSituationalFatal(r,o)}n===null&&this.raiseSituationalFatal(r,new Error(`missing situational context for entry ${r}`)),i.push(n)}for(const r of e)r.data?.situationalOnly===!0&&i.push(...j(r));return i}digest(t,e){try{return X(t)}catch(i){this.raiseReadFatal(e,i)}}sendDelivery(t,e,i,r,n){this.pendingHandoffs.length>=b&&this.raiseReadFatal(r,new Error(`pending handoff capacity exceeded before ${t} send`));let o;try{o=x(e,t)}catch(d){this.raiseReadFatal(r,d)}let a;try{m.fresh(d=>{a=d,v({level:"info",event:"inbox.delivery.started",operation_id:d,fields:y(o)}),i()})}catch(d){return a===void 0&&this.raiseReadFatal(r,d),T(a,o,"send",d),"failed"}a===void 0&&this.raiseReadFatal(r,new Error(`${t} delivery returned without an operation id`));const l={operationId:a,...o,...n===void 0?{}:{awaitingDequeue:n}};return this.canMutate()?(this.pendingHandoffs.push(l),"handed-off"):(this.disposed&&this.disposalReason!==void 0&&M(l,this.disposalReason),"inactive")}sendContext(t,e,i,r,n,o){const a=r==="steer"||r==="followUp"?r:this.isIdle()?void 0:"followUp",l=a===void 0||i.length===0?void 0:_(i.at(-1));return this.sendDelivery("hidden",e,()=>{this.runNode&&n&&this.pi.appendEntry("crtr-turn-input",{message_ids:e.map(d=>d.entry_id)}),i.forEach((d,c)=>{const u=n&&c===i.length-1;this.pi.sendMessage({customType:I,content:d,display:!0,details:{nodeId:t}},a!==void 0?{deliverAs:a}:u?{deliverAs:"followUp",triggerTurn:!0}:{})})},o,l)}sendVisible(t,e,i,r,n,o){return this.sendDelivery("visible",t,()=>{if(n!=="idle"&&n!=="steer"&&n!=="followUp")throw new Error(`visible send requested for non-send route ${n}`);for(const a of e)this.pi.sendMessage({customType:I,content:a,display:!0},n==="idle"?{}:{deliverAs:n});if(Z(i,r),n==="idle"){const a=re();if(a===void 0)throw new Error("broker idle-turn admission is unavailable");a(i,this.runNode?t.map(l=>l.entry_id):[])}else this.pi.sendUserMessage(i,{deliverAs:n})},o,n==="steer"||n==="followUp"?_(i):void 0)}abortCritical(){try{this.lastCtx?.abort?.()}catch{}}requeueFromBatch(t,e){if(e.size===0)return;const i=t.filter(o=>e.has(o.entry_id)),r=new Set(i.map(o=>o.entry_id)),n=this.buffer.filter(o=>!r.has(o.entry_id));this.buffer.splice(0,this.buffer.length,...i,...n)}async executePlan(t,e,i){const r=new Set,n=this.visibleRoute(e.visibleEntries);if(n==="critical"){if(this.abortCritical(),!this.canMutate())return{inactive:!0,requeueEntryIds:r};for(const d of e.visibleEntries)r.add(d.entry_id);for(const d of e.hiddenEntries)r.add(d.entry_id);return{inactive:!1,requeueEntryIds:r}}if(this.runNode&&n==="none")return{inactive:!1,requeueEntryIds:new Set([...e.hiddenEntries,...e.visibleEntries].map(d=>d.entry_id))};const o=e.visibleEntries.length>0?this.digest(e.visibleEntries,i):[],[a,...l]=o;for(const d of l)for(const c of d.entries)r.add(c.entry_id);if(e.hiddenEntries.length>0){const d=await this.contextCards(t,e.hiddenEntries),c=this.sendContext(t,e.hiddenEntries,d,n,a===void 0,i);if(c==="inactive")return{inactive:!0,requeueEntryIds:r};if(c==="failed")for(const u of e.hiddenEntries)r.add(u.entry_id)}if(!this.canMutate())return{inactive:!0,requeueEntryIds:r};if(a!==void 0){const d=this.sendVisible(a.entries,a.cards,a.text,le(a),n,i);if(d==="inactive")return{inactive:!0,requeueEntryIds:r};if(d==="failed")for(const c of a.entries)r.add(c.entry_id);else a.kind==="human"&&(await ee(t,a.text).catch(()=>{}),te(t,a.text))}return{inactive:!1,requeueEntryIds:r}}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>b)return"capacity-blocked";const i=[...this.buffer],r=await this.executePlan(t,e,"poll");return r.inactive||(this.buffer.splice(0,i.length),this.requeueFromBatch(i,r.requeueEntryIds)),"attempted"}async prependHeldDeferredInbox(t){if(!this.canMutate()||this.nodeId===void 0||P())return;const e=this.ordinaryPlan();if(!e.deferredHold)return;const i=e.visibleEntries.length===0?[]:this.digest(e.visibleEntries,"poll"),r=+(e.hiddenEntries.length>0)+i.length;if(this.pendingHandoffs.length+r>b)return;const n=[...this.buffer];this.promptJoinInFlight=!0;try{this.runNode&&this.pi.appendEntry("crtr-turn-input",{message_ids:[...e.hiddenEntries,...e.visibleEntries].map(o=>o.entry_id)}),e.hiddenEntries.length>0&&await this.sendPromptJoinDelivery(t,"hidden",e.hiddenEntries,await this.contextCards(this.nodeId,e.hiddenEntries));for(const o of i)await this.sendPromptJoinDelivery(t,"visible",o.entries,[...o.cards,o.text]);if(!this.canMutate())return;this.buffer.splice(0,n.length)}finally{this.promptJoinInFlight=!1}}async sendPromptJoinDelivery(t,e,i,r){let n;try{n=x(i,e)}catch(l){this.raiseReadFatal("poll",l)}let o;try{await m.fresh(async l=>{o=l,v({level:"info",event:"inbox.delivery.started",operation_id:l,fields:y(n)});for(const d of r)await t.sendCustomMessage({customType:I,content:d,display:!0})})}catch(l){throw o===void 0&&this.raiseReadFatal("poll",l),T(o,n,"send",l),l}o===void 0&&this.raiseReadFatal("poll",new Error(`${e} prompt-join delivery returned without an operation id`));const a={operationId:o,...n,settleAtAgentSettled:!0};if(!this.canMutate()){this.disposed&&this.disposalReason!==void 0&&M(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 r of i.entryIds)e.add(r);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 n=await z(t,e.receipts);if(n.action==="hold")return"refresh_held";for(const o of e.receipts){const a=n.results.find(l=>l.delivery_id===o.delivery_id);if(a?.outcome!==o.disposition&&a?.outcome!=="stale")throw new Error("daemon returned no terminal mail settlement outcome")}i=n.results.every(o=>o.outcome==="acknowledged")}catch(n){if(this.pendingCommit===e)for(const o of e.handoffs)T(o.operationId,o,"receipt_commit",n);return String(n?.message).startsWith("conversation_write_failed")&&(O({level:"error",event:"broker.exited",node_id:t,error:n,fields:{fault:"conversation_write_failed"}}),await V(t,n),N()?.("conversation_write_failed",1)),"held"}if(this.pendingCommit!==e)return"settled";for(const n of e.entryIds)this.claims.delete(n);if(e.receipts.some(n=>n.disposition==="released")){const n=new Set(e.entryIds);this.buffer.splice(0,this.buffer.length,...this.buffer.filter(o=>!n.has(o.entry_id)))}this.pendingCommit=void 0;const r=this.detachPending(n=>n.entryIds.every(o=>!this.claims.has(o)));for(const n of r)i&&fe(n);return"settled"}async settle(t,e,i,r=!1){if(i)try{if(e?.hasPendingMessages?.()===!0)return"held"}catch{}if(this.pendingCommit!==void 0)return this.retryPendingCommit(t);const n=Y(),o=!n&&this.pendingHandoffs.some(h=>h.awaitingDequeue!==void 0)&&await this.nodeRefreshing(t);if(this.pendingCommit!==void 0)return this.retryPendingCommit(t);const a=new Set(this.buffer.map(h=>h.entry_id)),l=new Set(o?this.pendingHandoffs.filter(h=>h.awaitingDequeue!==void 0).flatMap(h=>[...h.entryIds]):[]),d=this.pendingHandoffs.filter(h=>(r||h.settleAtAgentSettled!==!0)&&h.entryIds.every(g=>!l.has(g))),c=new Set(d.flatMap(h=>[...h.entryIds])),u=[...this.claims.keys()].filter(h=>n||c.has(h)&&!a.has(h));return u.length===0?"settled":(this.pendingCommit={handoffs:d,entryIds:u,receipts:u.map(h=>{const g=this.claims.get(h);return{delivery_id:g.delivery_id,attempts:g.attempts,disposition:n?"released":"acknowledged"}})},this.retryPendingCommit(t))}async nodeRefreshing(t){try{return(await F(t)).node.intent==="refresh"}catch{return!0}}detachPending(t){const e=[];for(let i=this.pendingHandoffs.length-1;i>=0;i-=1){const r=this.pendingHandoffs[i];t(r)&&e.push(...this.pendingHandoffs.splice(i,1))}return e.reverse(),e}}function pe(s,t){p?.dispose("reload",!1);const e=new me(s,t,++oe);p=e;const i=f(r=>e.prependHeldDeferredInbox(r),"promptJoin");globalThis[ae]=i;try{e.start()}catch(r){throw e.dispose("reload",!1),r}return e}f(pe,"installWatcher");function ye(s){const t=pe(s,de());return()=>t.dispose("shutdown",!1)}f(ye,"registerCanvasInboxWatcher");var Ne=ye;export{Ne as default,ye as registerCanvasInboxWatcher};
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};
@@ -11,6 +11,11 @@ export declare function launchSnapshotFile(dir: string): string | undefined;
11
11
  export declare function sandboxCliNodeId(): string | undefined;
12
12
  /** A Linux app command uses its mounted launch snapshot, not host user stores. */
13
13
  export declare function isSandboxedNode(): boolean;
14
+ /** True when this CLI reaches `/v1` through its broker's CLI relay to a remote control plane: the broker exports `CRTR_API_URL` (an
15
+ * http(s) URL, the fleet launcher's) and `CRTR_API_SOCKET` (the relay it serves). It chooses only where this process gets provider command
16
+ * descriptions (`GET /v1/providers/tools` through that relay); the relay's node token stays the authority. It is not `isSandboxedNode()`,
17
+ * which gates owner-only commands and launch-snapshot reads on a Linux launcher mount. */
18
+ export declare function providerProjectionOverBrokerRelay(): boolean;
14
19
  /** The app that owns the current node, supplied in its broker launch identity. */
15
20
  export declare function envGrantee(): string | undefined;
16
21
  /** Set to '1' by `nodeEnv` for an app-started run's brokers, which admit inbox
@@ -1,2 +1,2 @@
1
- var p=Object.defineProperty;var n=(e,r)=>p(e,"name",{value:r,configurable:!0});import{lstatSync as f,readFileSync as c,readdirSync as T}from"node:fs";import{join as i}from"node:path";import{covers as _}from"@north-light/identity";function W(){return process.env.CRTR_NODE_ID}n(W,"envNodeId");function d(e){for(const r of["broker-launch.json","cron-launch.json"]){const t=i(e,r);if(f(t,{throwIfNoEntry:!1})?.isFile())return t}}n(d,"launchSnapshotFile");function E(){if(process.platform==="linux")try{const e="/run/crtr/nodes",r=new Set(c("/proc/self/mountinfo","utf8").split(`
2
- `).map(o=>o.split(" ")[4])),t=T(e,{withFileTypes:!0}).filter(o=>o.isDirectory()&&/^[a-zA-Z0-9][a-zA-Z0-9-]{0,127}$/.test(o.name)&&r.has(i(e,o.name))&&d(i(e,o.name))!==void 0);return t.length===1?t[0].name:void 0}catch{return}}n(E,"sandboxCliNodeId");function F(){return E()!==void 0}n(F,"isSandboxedNode");function h(){return process.env.CRTR_GRANTEE}n(h,"envGrantee");const v="CRTR_INBOX_BETWEEN_TURNS";function H(){return process.env[v]==="1"}n(H,"envInboxBetweenTurns");const C="CRTR_SCOPES_FILE";function K(){const e=process.env[C];if(e!==void 0&&e!==""){const t=JSON.parse(c(e,"utf8"));if(!Array.isArray(t)||t.some(o=>typeof o!="string"))throw new Error(`invalid scopes file ${e}`);return t}const r=process.env.CRTR_SCOPES;return r===void 0?null:r===""?[]:r.split(",")}n(K,"envScopes");function G(e,r){return e==null?!0:a(e,r)}n(G,"scopeAllowed");function a(e,r){return e.includes(r)?(_(r,r),!0):e.some(t=>_(t,r))}n(a,"coveredByAny");function b(){return process.env.CRTR_EXECUTION_ID}n(b,"envExecutionId");function k(){return process.env.CRTR_PROFILE_ID}n(k,"envProfileId");function y(){return process.env.CRTR_NODE_CWD}n(y,"envNodeCwd");function w(){return process.env.CRTR_MODEL_INTENT}n(w,"envModelIntent");function g(){return process.env.CRTR_MODEL_EXACT==="1"}n(g,"envModelExact");function V(){return process.env.CRTR_HOME}n(V,"envHomeOverride");function X(){return process.env.CRTR_RUNTIME_HOME}n(X,"envRuntimeHomeOverride");function s(e,r){if(e===void 0)return r;const t=Number(e);return Number.isFinite(t)&&t>0?t:r}n(s,"parsePositiveMs");const S=5*6e4;function j(){return s(process.env.CRTR_STREAM_WATCHDOG_MS,S)}n(j,"envStreamWatchdogMs");const A=800;function Y(){return s(process.env.CRTR_WATCHER_TICK_MS,A)}n(Y,"envWatcherTickMs");const M=6e4;function $(){return s(process.env.CRTR_WATCHER_BACKSTOP_MS,M)}n($,"envWatcherBackstopMs");const N=1e3;function z(){return s(process.env.CRTR_WATCHER_DEBOUNCE_MS,N)}n(z,"envWatcherDebounceMs");const x=6e4;function Z(){return s(process.env.CRTR_RECAP_IDLE_MS,x)}n(Z,"envRecapIdleMs");function J(){const e=process.env.CRTR_RECAP_MODEL;return e!==void 0&&e.trim()!==""?e.trim():void 0}n(J,"envRecapModelOverride");const D=300*1e3;function q(){return s(process.env.CRTR_BASH_VALVE_MS,D)}n(q,"envBashValveMs");const O=60*1e3;function Q(){return s(process.env.CRTR_BASH_AUTO_BG_DEADLINE_MS,O)}n(Q,"envBashAutoBgDeadlineMs");function ee(){const e=process.env.CRTR_PIDFILE;return e!==void 0&&e!==""?e:void 0}n(ee,"envPidfileOverride");const I="@earendil-works/pi-coding-agent";function ne(){return process.env.CRTR_BROKER_ENGINE??I}n(ne,"envBrokerEngine");function re(){return process.env.CRTR_PI_BINARY}n(re,"envPiBinaryOverride");function te(){return process.env.CRTR_NO_DAEMON_AUTOSTART==="1"}n(te,"envNoDaemonAutostart");function oe(){const e=process.env.CRTR_DOCUMENT_PORT;if(e===void 0||!/^\d+$/.test(e))return;const r=Number(e);return r<=65535?r:void 0}n(oe,"envDocumentPort");function se(){return process.env.CRTRD_TCP}n(se,"envCrtrdTcp");function ue(){const e=process.env.CRTRD_TOKEN;return e!==void 0&&e!==""?e:void 0}n(ue,"envCrtrdToken");function ie(){return process.env.CRTR_NO_EXPORTS==="1"}n(ie,"envNoExports");function ce(){return process.env.CRTR_DEBUG==="1"}n(ce,"envDebug");function R(e){if(e===void 0||e==="")return;const r=Number(e);return Number.isSafeInteger(r)&&r>=1?r:void 0}n(R,"parsePositiveInteger");function _e(){return R(process.env.CRTR_MAX_LIVE_BROKERS)}n(_e,"envMaxLiveBrokers");function Re(){return R(process.env.CRTR_WARN_LIVE_BROKERS)}n(Re,"envWarnLiveBrokers");function u(e){if(e===void 0)return;const r=Number(e);return Number.isFinite(r)&&r>0?r:void 0}n(u,"parsePositiveMsNoDefault");function pe(){return u(process.env.CRTR_TEST_NODE_SOCKET_WINDOW_MS)}n(pe,"envTestNodeSocketWindowMs");function fe(){return u(process.env.CRTR_TEST_UNATTENDED_PARK_MS)}n(fe,"envTestUnattendedParkMs");function Te(){return u(process.env.CRTR_UNATTENDED_PARK_MS)}n(Te,"envUnattendedParkMs");function de(){return u(process.env.CRTR_BASH_SILENCE_MS)??15*6e4}n(de,"envBashSilenceMs");function l(){return u(process.env.CRTR_TEST_PARK_SUMMARY_GRACE_MS)}n(l,"envTestParkSummaryGraceMs");const m=5*6e4;function Ee(){return l()??m}n(Ee,"parkSummaryGraceMs");export{v as INBOX_BETWEEN_TURNS_ENV,C as SCOPES_FILE_ENV,a as coveredByAny,Q as envBashAutoBgDeadlineMs,de as envBashSilenceMs,q as envBashValveMs,ne as envBrokerEngine,se as envCrtrdTcp,ue as envCrtrdToken,ce as envDebug,oe as envDocumentPort,b as envExecutionId,h as envGrantee,V as envHomeOverride,H as envInboxBetweenTurns,_e as envMaxLiveBrokers,g as envModelExact,w as envModelIntent,te as envNoDaemonAutostart,ie as envNoExports,y as envNodeCwd,W as envNodeId,re as envPiBinaryOverride,ee as envPidfileOverride,k as envProfileId,Z as envRecapIdleMs,J as envRecapModelOverride,X as envRuntimeHomeOverride,K as envScopes,j as envStreamWatchdogMs,pe as envTestNodeSocketWindowMs,l as envTestParkSummaryGraceMs,fe as envTestUnattendedParkMs,Te as envUnattendedParkMs,Re as envWarnLiveBrokers,$ as envWatcherBackstopMs,z as envWatcherDebounceMs,Y as envWatcherTickMs,F as isSandboxedNode,d as launchSnapshotFile,Ee as parkSummaryGraceMs,E as sandboxCliNodeId,G as scopeAllowed};
1
+ var p=Object.defineProperty;var e=(n,r)=>p(n,"name",{value:r,configurable:!0});import{lstatSync as T,readFileSync as c,readdirSync as f}from"node:fs";import{join as i}from"node:path";import{covers as _}from"@north-light/identity";function W(){return process.env.CRTR_NODE_ID}e(W,"envNodeId");function d(n){for(const r of["broker-launch.json","cron-launch.json"]){const t=i(n,r);if(T(t,{throwIfNoEntry:!1})?.isFile())return t}}e(d,"launchSnapshotFile");function E(){if(process.platform==="linux")try{const n="/run/crtr/nodes",r=new Set(c("/proc/self/mountinfo","utf8").split(`
2
+ `).map(o=>o.split(" ")[4])),t=f(n,{withFileTypes:!0}).filter(o=>o.isDirectory()&&/^[a-zA-Z0-9][a-zA-Z0-9-]{0,127}$/.test(o.name)&&r.has(i(n,o.name))&&d(i(n,o.name))!==void 0);return t.length===1?t[0].name:void 0}catch{return}}e(E,"sandboxCliNodeId");function F(){return E()!==void 0}e(F,"isSandboxedNode");function h(){return/^https?:\/\//.test(process.env.CRTR_API_URL??"")&&(process.env.CRTR_API_SOCKET??"")!==""}e(h,"providerProjectionOverBrokerRelay");function H(){return process.env.CRTR_GRANTEE}e(H,"envGrantee");const v="CRTR_INBOX_BETWEEN_TURNS";function K(){return process.env[v]==="1"}e(K,"envInboxBetweenTurns");const C="CRTR_SCOPES_FILE";function k(){const n=process.env[C];if(n!==void 0&&n!==""){const t=JSON.parse(c(n,"utf8"));if(!Array.isArray(t)||t.some(o=>typeof o!="string"))throw new Error(`invalid scopes file ${n}`);return t}const r=process.env.CRTR_SCOPES;return r===void 0?null:r===""?[]:r.split(",")}e(k,"envScopes");function y(n,r){return n==null?!0:a(n,r)}e(y,"scopeAllowed");function a(n,r){return n.includes(r)?(_(r,r),!0):n.some(t=>_(t,r))}e(a,"coveredByAny");function G(){return process.env.CRTR_EXECUTION_ID}e(G,"envExecutionId");function b(){return process.env.CRTR_PROFILE_ID}e(b,"envProfileId");function w(){return process.env.CRTR_NODE_CWD}e(w,"envNodeCwd");function g(){return process.env.CRTR_MODEL_INTENT}e(g,"envModelIntent");function V(){return process.env.CRTR_MODEL_EXACT==="1"}e(V,"envModelExact");function X(){return process.env.CRTR_HOME}e(X,"envHomeOverride");function j(){return process.env.CRTR_RUNTIME_HOME}e(j,"envRuntimeHomeOverride");function s(n,r){if(n===void 0)return r;const t=Number(n);return Number.isFinite(t)&&t>0?t:r}e(s,"parsePositiveMs");const A=5*6e4;function Y(){return s(process.env.CRTR_STREAM_WATCHDOG_MS,A)}e(Y,"envStreamWatchdogMs");const S=800;function $(){return s(process.env.CRTR_WATCHER_TICK_MS,S)}e($,"envWatcherTickMs");const M=6e4;function z(){return s(process.env.CRTR_WATCHER_BACKSTOP_MS,M)}e(z,"envWatcherBackstopMs");const N=1e3;function Z(){return s(process.env.CRTR_WATCHER_DEBOUNCE_MS,N)}e(Z,"envWatcherDebounceMs");const x=6e4;function J(){return s(process.env.CRTR_RECAP_IDLE_MS,x)}e(J,"envRecapIdleMs");function q(){const n=process.env.CRTR_RECAP_MODEL;return n!==void 0&&n.trim()!==""?n.trim():void 0}e(q,"envRecapModelOverride");const D=300*1e3;function Q(){return s(process.env.CRTR_BASH_VALVE_MS,D)}e(Q,"envBashValveMs");const O=60*1e3;function ee(){return s(process.env.CRTR_BASH_AUTO_BG_DEADLINE_MS,O)}e(ee,"envBashAutoBgDeadlineMs");function ne(){const n=process.env.CRTR_PIDFILE;return n!==void 0&&n!==""?n:void 0}e(ne,"envPidfileOverride");const I="@earendil-works/pi-coding-agent";function re(){return process.env.CRTR_BROKER_ENGINE??I}e(re,"envBrokerEngine");function te(){return process.env.CRTR_PI_BINARY}e(te,"envPiBinaryOverride");function oe(){return process.env.CRTR_NO_DAEMON_AUTOSTART==="1"}e(oe,"envNoDaemonAutostart");function se(){const n=process.env.CRTR_DOCUMENT_PORT;if(n===void 0||!/^\d+$/.test(n))return;const r=Number(n);return r<=65535?r:void 0}e(se,"envDocumentPort");function ue(){return process.env.CRTRD_TCP}e(ue,"envCrtrdTcp");function ie(){const n=process.env.CRTRD_TOKEN;return n!==void 0&&n!==""?n:void 0}e(ie,"envCrtrdToken");function ce(){return process.env.CRTR_NO_EXPORTS==="1"}e(ce,"envNoExports");function _e(){return process.env.CRTR_DEBUG==="1"}e(_e,"envDebug");function R(n){if(n===void 0||n==="")return;const r=Number(n);return Number.isSafeInteger(r)&&r>=1?r:void 0}e(R,"parsePositiveInteger");function Re(){return R(process.env.CRTR_MAX_LIVE_BROKERS)}e(Re,"envMaxLiveBrokers");function pe(){return R(process.env.CRTR_WARN_LIVE_BROKERS)}e(pe,"envWarnLiveBrokers");function u(n){if(n===void 0)return;const r=Number(n);return Number.isFinite(r)&&r>0?r:void 0}e(u,"parsePositiveMsNoDefault");function Te(){return u(process.env.CRTR_TEST_NODE_SOCKET_WINDOW_MS)}e(Te,"envTestNodeSocketWindowMs");function fe(){return u(process.env.CRTR_TEST_UNATTENDED_PARK_MS)}e(fe,"envTestUnattendedParkMs");function de(){return u(process.env.CRTR_UNATTENDED_PARK_MS)}e(de,"envUnattendedParkMs");function Ee(){return u(process.env.CRTR_BASH_SILENCE_MS)??15*6e4}e(Ee,"envBashSilenceMs");function l(){return u(process.env.CRTR_TEST_PARK_SUMMARY_GRACE_MS)}e(l,"envTestParkSummaryGraceMs");const m=5*6e4;function ve(){return l()??m}e(ve,"parkSummaryGraceMs");export{v as INBOX_BETWEEN_TURNS_ENV,C as SCOPES_FILE_ENV,a as coveredByAny,ee as envBashAutoBgDeadlineMs,Ee as envBashSilenceMs,Q as envBashValveMs,re as envBrokerEngine,ue as envCrtrdTcp,ie as envCrtrdToken,_e as envDebug,se as envDocumentPort,G as envExecutionId,H as envGrantee,X as envHomeOverride,K as envInboxBetweenTurns,Re as envMaxLiveBrokers,V as envModelExact,g as envModelIntent,oe as envNoDaemonAutostart,ce as envNoExports,w as envNodeCwd,W as envNodeId,te as envPiBinaryOverride,ne as envPidfileOverride,b as envProfileId,J as envRecapIdleMs,q as envRecapModelOverride,j as envRuntimeHomeOverride,k as envScopes,Y as envStreamWatchdogMs,Te as envTestNodeSocketWindowMs,l as envTestParkSummaryGraceMs,fe as envTestUnattendedParkMs,de as envUnattendedParkMs,pe as envWarnLiveBrokers,z as envWatcherBackstopMs,Z as envWatcherDebounceMs,$ as envWatcherTickMs,F as isSandboxedNode,d as launchSnapshotFile,ve as parkSummaryGraceMs,h as providerProjectionOverBrokerRelay,E as sandboxCliNodeId,y as scopeAllowed};
@@ -24,6 +24,8 @@ A run is an agent that works on a person's Northlight runtime, on your behalf an
24
24
 
25
25
  `runs.wait` returns on `waiting_on_user`, `idle` and `settled`, so check the status before you read a result.
26
26
 
27
+ A turn that fails on a model error leaves the run `idle`, not `settled`, with `last_turn_error` set (`{code, message}`); the same error is on that turn's `turn.completed` event. The run stays usable, and a retry is a `runs.message`. When `code` is `rate_limited`, every model route is rate-limited and `retry_at` (an ISO time) says when the limit is expected to lift: send the retry after `retry_at`, or `runs.cancel` the run, rather than wait for its deadline. A message sent before `retry_at` fails the same way.
28
+
27
29
  ## Outcome and result
28
30
 
29
31
  A `settled` run has an `outcome`:
@@ -42,7 +42,7 @@ A chat app sends a message and shows the answer to it. `runs.reply(runId, sent,
42
42
  const sent = await runtime.runs.message(runId, 'What did the report say?');
43
43
  for await (const event of runtime.runs.reply(runId, sent)) {
44
44
  if (event.type === 'text.delta') append(event.delta);
45
- if (event.type === 'turn.completed' && event.error) showError(event.error); // {code: 'content_refused' | 'model_error', message}
45
+ if (event.type === 'turn.completed' && event.error) showError(event.error); // {code: 'content_refused' | 'model_error' | 'rate_limited', message, retry_at?}
46
46
  }
47
47
  // or collect it: text as it streams, then the whole text and how the turn ended
48
48
  const {text, ended, error, outcome} = await runtime.runs.reply(runId, sent).collect({onDelta: (delta) => append(delta)});
@@ -60,7 +60,7 @@ A responder gets `{run, kind: 'start' | 'message', messages}`. It is called afte
60
60
  | Method | Effect |
61
61
  |---|---|
62
62
  | `reply(text, {chunks = 2, messageIds?})` | One turn: `node.turn.started`, text deltas, `node.output_text.done`, `node.turn.completed`. Delivers `messageIds` (default: every pending message). The run goes `idle` (`waiting_on_user` with an open question). |
63
- | `failTurn({code: 'model_error' \| 'content_refused', message})` | The same turn ending `stop_reason: 'error'`; `runs.get` shows `last_turn_error`; `runs.reply(...).collect()` ends `error`. |
63
+ | `failTurn({code: 'model_error' \| 'content_refused' \| 'rate_limited', message, retry_at?})` | The same turn ending `stop_reason: 'error'`; `runs.get` shows `last_turn_error`; `runs.reply(...).collect()` ends `error`. `retry_at` (ISO) is required with `rate_limited` and refused with any other code. |
64
64
  | `setActivity(activity, {at?})` | Fake-only: status `running` with this `activity` until the next event. The only way to reach `writing` or a streaming tool call. |
65
65
  | `settle(ending)` | `{result}`, `{declined: {reason, code, retryable}}`, `{failure: 'canceled' \| 'deadline_exceeded'}`, or `{runtimeError: reason}`. Emits `node.settled` with the outcome the runtime writes. |
66
66
  | `idle()` | Goes idle without a new turn (stopped without submitting). |
@@ -7,6 +7,10 @@ description: Behavior changes in @north-light/sdk that an app must act on, newes
7
7
 
8
8
  Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
9
9
 
10
+ ## 0.3.578
11
+
12
+ `runs.message` now steers a running root turn by default: the agent reads it at its next step. Pass `{ steer: false }` to wait for the current turn to end and start a separate turn. `start_turn: false` remains deferred and cannot use `steer: true`. When a later message joins a turn, `runs.reply` stops yielding text for the earlier message at that boundary, but still waits for the turn's completion. An app on an older SDK gets steering as soon as the runtime upgrades, because it cannot send `steer: false`; its `runs.reply` then streams the answer to a joined later message as part of the earlier reply. Upgrade the SDK to get the `RunReply` boundary and the opt-out.
13
+
10
14
  ## 0.3.577
11
15
 
12
16
  `POST /v1/nodes` with a pinned `cwd` that is not an existing directory on the runtime's host now answers `cwd_missing` instead of creating a node whose broker then fails to launch. A sandbox, isolated or worktree node is not checked. `POST /v1/nodes/{id}/messages` with `reopen` to a dormant node whose recorded cwd is gone now answers `cwd_missing` before the reopen is committed or any mail is queued, instead of accepting the message and reporting `wake_failed`.
@@ -15,6 +19,10 @@ Changes to `@north-light/sdk` that can change what an existing app sees. Newest
15
19
 
16
20
  On a cloud role, `provider.tools` now reports a granted provider as `available` once its bundle is installed in the deployment's shared plugin directory. It no longer answers `500` when a person has no materialized bundle; a granted provider still being installed reports `installing` or `install_failed`. Providers without that person's grant remain hidden. The Mac's plugin settings and provider tools behavior are unchanged.
17
21
 
22
+ ## 0.3.579
23
+
24
+ A turn that ends because every model route is rate-limited for longer than a turn waits now reports `error.code: 'rate_limited'` with `error.retry_at` (ISO time the limit is expected to lift), on `turn.completed` and `runs.get().last_turn_error`. It was `model_error` with the time only inside `message`. The run stays `idle` and usable, as before. An app that matches `error.code` exhaustively should handle the new value; one that treats any other code as a model error needs no change.
25
+
18
26
  ## 0.3.572
19
27
 
20
28
  `POST /v1/runs` works on a cloud role (a runtime started with `--role api` or `--role reconcile`). It answered `500 server_failure` because the role has no root launcher and the run start tried to prepare an app OS user and folder. Behavior on a person's Mac and on a hosted guest with a launcher is unchanged.
@@ -68,6 +68,8 @@ Every event except `error` carries `node_id` and `sequence_number` in addition t
68
68
  | `node.settled` | `{ node_id, sequence_number, outcome }` — terminal; the daemon ends the response after it |
69
69
  | `error` | `{ error: { code: 'stream_gap' \| 'stream_dropped' \| 'stream_error', message, details?: { earliest_sequence?: number } } }` — `stream_gap` is recoverable; the other codes terminate the response |
70
70
 
71
+ A turn can emit more than one `node.turn.started`. A `run.message` sent while the turn is running steers by default, so the agent reads it at its next step and the stream emits another `node.turn.started` whose `input.message_ids` holds the joined message; the turn ends with one `node.turn.completed` whose `message_ids` lists every message that joined it. A reply to one message therefore ends at the turn's completion, not at the next `node.turn.started`; the SDK's `RunReply` stops yielding text once a later message joins. Send `{ steer: false }` to start a separate turn after the current one ends.
72
+
71
73
  `summary` on a tool-call event states whether arguments are null, an array and its item count, an object and its field count, or a primitive type. It never includes argument values or tool output.
72
74
 
73
75
  **Deliberately not carried:** thinking deltas, the model's tool-call construction deltas, raw tool output, and the system prompt. Those belong to the owner's viewer, not to an application's run stream.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.577",
3
+ "version": "0.3.579",
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.577",
148
+ "@north-light/identity": "0.3.579",
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.577",
3
+ "version": "0.3.579",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",