dsh-thoughtdag 0.4.10 → 0.4.12

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 (30) hide show
  1. package/README.md +7 -0
  2. package/dist-app/assets/{canonical-BZIQSZy0.js → canonical-DccXDqkX.js} +2 -2
  3. package/dist-app/assets/{canvas-record-D7Ns1Kpg.js → canvas-record-Dw4tjwhH.js} +1 -1
  4. package/dist-app/assets/{claude-code-session-0K23DXD3.js → claude-code-session-ON4i33o-.js} +1 -1
  5. package/dist-app/assets/{codex-session-Cs0gAA_-.js → codex-session-BWQTXLRK.js} +1 -1
  6. package/dist-app/assets/{dsh-session-Bl9dP5PX.js → dsh-session-DoRNUaQq.js} +1 -1
  7. package/dist-app/assets/{experiment-loop-GwbyJMFI.js → experiment-loop-BuZFR0si.js} +1 -1
  8. package/dist-app/assets/index-2pggDGtC.css +1 -0
  9. package/dist-app/assets/{index-CZpsihFG.js → index-CTQSx_k0.js} +3 -3
  10. package/dist-app/assets/{index-B5j_QAmX.js → index-DD461-fW.js} +1 -1
  11. package/dist-app/assets/{index-DWm1onYm.js → index-DaODh24h.js} +1 -1
  12. package/dist-app/assets/index-DwwxoOcO.js +788 -0
  13. package/dist-app/assets/{live-mirror-DXHCi_Hf.js → live-mirror-D3u92mbO.js} +2 -2
  14. package/dist-app/assets/{pi-session-D-6Qneax.js → pi-session-IbIlOArr.js} +1 -1
  15. package/dist-app/assets/{sensitive-scan-BiyGrjXf.js → sensitive-scan-C6YN1rzZ.js} +1 -1
  16. package/dist-app/assets/{session-handoff-IiyUUNcD.js → session-handoff-Fpl3jes7.js} +2 -2
  17. package/dist-app/assets/{shared-BUQtzATG.js → shared-DCtLTsEN.js} +1 -1
  18. package/dist-app/assets/{turndown-plugin-gfm.cjs-BOHTxH4C.js → turndown-plugin-gfm.cjs-CqvfCrbV.js} +1 -1
  19. package/dist-app/assets/{update-check-BojzO74W.js → update-check-B1kgBEyl.js} +1 -1
  20. package/dist-app/index.html +2 -2
  21. package/lib/client.js +3 -2
  22. package/lib/index.js +59 -11
  23. package/lib/runtime/agents/README.md +8 -6
  24. package/lib/runtime/agents/claude.cjs +303 -0
  25. package/lib/runtime/agents/codex.cjs +93 -15
  26. package/lib/runtime/agents/http.cjs +1 -0
  27. package/lib/runtime/agents/pi.cjs +21 -6
  28. package/package.json +1 -1
  29. package/dist-app/assets/index-BBqrNNdn.js +0 -787
  30. package/dist-app/assets/index-BfIl0Oie.css +0 -1
@@ -1,2 +1,2 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-BBqrNNdn.js","assets/index-BfIl0Oie.css","assets/experiment-loop-GwbyJMFI.js","assets/index-CZpsihFG.js","assets/canonical-BZIQSZy0.js"])))=>i.map(i=>d[i]);
2
- import{_ as l,i as j,t as p,a as h,f as R}from"./index-BBqrNNdn.js";function K(d,S){return d.find(y=>(y.rel.split("/").pop()??y.rel).includes(S))}let D=!1,I=null;function H(){I?.()}function z(){const d=window.desktopSessions;if(!d||D)return;D=!0,d.watchStart();let S=!1,y=null;const g=async s=>{if(S){y=s;return}S=!0;try{window.dispatchEvent(new CustomEvent("td:sessions-changed"));const{useProjects:a,subscribedSessionIds:i}=await l(async()=>{const{useProjects:w,subscribedSessionIds:E}=await import("./index-BBqrNNdn.js").then(b=>b.H);return{useProjects:w,subscribedSessionIds:E}},__vite__mapDeps([0,1])),{activeId:c,projects:o}=a.getState(),m=o.find(w=>w.id===c),t=await d.head(s.rootKey,s.rel,262144).catch(()=>"");if(!t)return;const e=j(t);if(!e)return;const f=!!m&&i(m).includes(e);let r=null;if(!f){if(o.some(v=>i(v).includes(e)))return;const{useStore:E}=await l(async()=>{const{useStore:v}=await import("./index-BBqrNNdn.js").then(A=>A.G);return{useStore:v}},__vite__mapDeps([0,1]));if(!E.getState().nodes.some(v=>v.data.importSource?.sessionId===e)){const{parseAnchor:v}=await l(async()=>{const{parseAnchor:k}=await import("./experiment-loop-GwbyJMFI.js");return{parseAnchor:k}},__vite__mapDeps([2,0,1])),A=v(t);if(!A||!o.some(k=>k.id===A.project))return;if(r=A.project,r!==c){p("info",h("atlas.mountPending"),9e3);return}}}const{streamRunnerConversation:n}=await l(async()=>{const{streamRunnerConversation:w}=await import("./index-CZpsihFG.js");return{streamRunnerConversation:w}},__vite__mapDeps([3,0,1])),{importOrAppendConversation:u,shellSessionReader:_}=await l(async()=>{const{importOrAppendConversation:w,shellSessionReader:E}=await import("./canonical-BZIQSZy0.js");return{importOrAppendConversation:w,shellSessionReader:E}},__vite__mapDeps([4,0,1])),V=await n(_(s.rootKey,s.rel)).catch(()=>null),P=await u(V);P?.kind==="appended"?p("success",R(h("atlas.liveAppended"),{n:P.turns}),8e3):P?.kind==="mounted"&&p("success",R(h(P.mode==="continue"?"atlas.mountedChapter":"atlas.mountedBranch"),{n:P.turns}),9e3)}finally{if(S=!1,y){const a=y;y=null,g(a)}}};d.onSessionsChanged(s=>{g(s)});const C=async s=>{if(!s)return;const{useStore:a}=await l(async()=>{const{useStore:o}=await import("./index-BBqrNNdn.js").then(m=>m.G);return{useStore:o}},__vite__mapDeps([0,1])),{useUiStore:i}=await l(async()=>{const{useUiStore:o}=await import("./index-BBqrNNdn.js").then(m=>m.F);return{useUiStore:o}},__vite__mapDeps([0,1])),c=a.getState().nodes.find(o=>o.data.importSource?.itemIds?.includes(s));c&&i.getState().setArrivalFocusNodeId(c.id)},T=async(s,a)=>{const{useProjects:i,switchProject:c}=await l(async()=>{const{useProjects:e,switchProject:f}=await import("./index-BBqrNNdn.js").then(r=>r.H);return{useProjects:e,switchProject:f}},__vite__mapDeps([0,1])),o=i.getState().projects,m=o.filter(e=>e.name===s),t=o.find(e=>e.id===s)??(m.length===1?m[0]:void 0);if(!t){p("error",h("handoff.deeplinkMiss"));return}if(await c(t.id),a){const{useStore:e}=await l(async()=>{const{useStore:r}=await import("./index-BBqrNNdn.js").then(n=>n.G);return{useStore:r}},__vite__mapDeps([0,1])),{useUiStore:f}=await l(async()=>{const{useUiStore:r}=await import("./index-BBqrNNdn.js").then(n=>n.F);return{useUiStore:r}},__vite__mapDeps([0,1]));e.getState().nodes.some(r=>r.id===a)&&f.getState().setArrivalFocusNodeId(a)}},O=async s=>{let a="",i="";try{const n=new URL(s),u=n.searchParams.get("canvas");if(u){await T(u,n.searchParams.get("node")??"");return}a=n.searchParams.get("session")??"",i=n.searchParams.get("turn")??""}catch{return}if(!/^[A-Za-z0-9_-]{8,}$/.test(a))return;/^[A-Za-z0-9_-]*$/.test(i)||(i="");const c=await d.roots().catch(()=>[]);let o=null;for(const n of c){const u=await d.list(n.key).catch(()=>[]),_=K(u,a);if(_){o={rootKey:n.key,rel:_.rel};break}}if(!o){const n=[];for(const u of c)for(const _ of await d.list(u.key).catch(()=>[]))n.push({rootKey:u.key,rel:_.rel,mtime:_.mtime});n.sort((u,_)=>_.mtime-u.mtime);for(const u of n.slice(0,40)){const _=await d.head(u.rootKey,u.rel,262144).catch(()=>"");if(_&&j(_)===a){o=u;break}}}if(!o){p("error",h("handoff.deeplinkMiss"));return}const{streamRunnerConversation:m}=await l(async()=>{const{streamRunnerConversation:n}=await import("./index-CZpsihFG.js");return{streamRunnerConversation:n}},__vite__mapDeps([3,0,1])),{importOrAppendConversation:t,shellSessionReader:e}=await l(async()=>{const{importOrAppendConversation:n,shellSessionReader:u}=await import("./canonical-BZIQSZy0.js");return{importOrAppendConversation:n,shellSessionReader:u}},__vite__mapDeps([4,0,1])),f=await m(e(o.rootKey,o.rel)).catch(()=>null),r=await t(f);r?r.kind==="appended"?p("success",R(h("atlas.liveAppended"),{n:r.turns}),8e3):r.kind==="mounted"?p("success",R(h(r.mode==="continue"?"atlas.mountedChapter":"atlas.mountedBranch"),{n:r.turns}),9e3):r.kind==="imported"&&p("success",R(h("toast.importedChats"),{n:1,m:r.nodeCount}),8e3):p("error",h("handoff.notASession")),r&&i&&await C(i)};d.onDeepLink?.(s=>{O(s)}),(async()=>{const{bootProjects:s}=await l(async()=>{const{bootProjects:i}=await import("./index-BBqrNNdn.js").then(c=>c.H);return{bootProjects:i}},__vite__mapDeps([0,1]));await s();const a=await d.pendingDeepLink?.().catch(()=>null);a&&O(a)})();const L="thoughtdag.atlas.sweepmark";I=async()=>{const{bootProjects:s}=await l(async()=>{const{bootProjects:t}=await import("./index-BBqrNNdn.js").then(e=>e.H);return{bootProjects:t}},__vite__mapDeps([0,1]));await s();const a=await d.roots().catch(()=>[]),i=[];for(const t of a){const e=await d.list(t.key).catch(()=>[]);for(const f of e)i.push({rootKey:t.key,rel:f.rel,mtime:f.mtime,size:f.size})}let c={};try{c=JSON.parse(localStorage.getItem(L)??"{}")}catch{}const o=Object.keys(c).length===0;i.sort((t,e)=>e.mtime-t.mtime);const m=i.filter((t,e)=>{const f=`${t.mtime}|${t.size}`;return c[`${t.rootKey}|${t.rel}`]===f?!1:o?e<30:!0});for(const t of m)await g({rootKey:t.rootKey,rel:t.rel});for(const t of i)c[`${t.rootKey}|${t.rel}`]=`${t.mtime}|${t.size}`;localStorage.setItem(L,JSON.stringify(c))},I()}export{K as pickSessionFile,z as startLiveMirror,H as sweepRecentSessions};
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-DwwxoOcO.js","assets/index-2pggDGtC.css","assets/experiment-loop-BuZFR0si.js","assets/index-CTQSx_k0.js","assets/canonical-DccXDqkX.js"])))=>i.map(i=>d[i]);
2
+ import{_ as l,i as b,t as p,a as h,f as R}from"./index-DwwxoOcO.js";function K(d,S){return d.find(y=>(y.rel.split("/").pop()??y.rel).includes(S))}let j=!1,I=null;function H(){I?.()}function z(){const d=window.desktopSessions;if(!d||j)return;j=!0,d.watchStart();let S=!1,y=null;const g=async s=>{if(S){y=s;return}S=!0;try{window.dispatchEvent(new CustomEvent("td:sessions-changed"));const{useProjects:a,subscribedSessionIds:i}=await l(async()=>{const{useProjects:w,subscribedSessionIds:E}=await import("./index-DwwxoOcO.js").then(D=>D.H);return{useProjects:w,subscribedSessionIds:E}},__vite__mapDeps([0,1])),{activeId:c,projects:o}=a.getState(),m=o.find(w=>w.id===c),t=await d.head(s.rootKey,s.rel,262144).catch(()=>"");if(!t)return;const e=b(t);if(!e)return;const f=!!m&&i(m).includes(e);let r=null;if(!f){if(o.some(v=>i(v).includes(e)))return;const{useStore:E}=await l(async()=>{const{useStore:v}=await import("./index-DwwxoOcO.js").then(A=>A.G);return{useStore:v}},__vite__mapDeps([0,1]));if(!E.getState().nodes.some(v=>v.data.importSource?.sessionId===e)){const{parseAnchor:v}=await l(async()=>{const{parseAnchor:k}=await import("./experiment-loop-BuZFR0si.js");return{parseAnchor:k}},__vite__mapDeps([2,0,1])),A=v(t);if(!A||!o.some(k=>k.id===A.project))return;if(r=A.project,r!==c){p("info",h("atlas.mountPending"),9e3);return}}}const{streamRunnerConversation:n}=await l(async()=>{const{streamRunnerConversation:w}=await import("./index-CTQSx_k0.js");return{streamRunnerConversation:w}},__vite__mapDeps([3,0,1])),{importOrAppendConversation:u,shellSessionReader:_}=await l(async()=>{const{importOrAppendConversation:w,shellSessionReader:E}=await import("./canonical-DccXDqkX.js");return{importOrAppendConversation:w,shellSessionReader:E}},__vite__mapDeps([4,0,1])),V=await n(_(s.rootKey,s.rel)).catch(()=>null),P=await u(V);P?.kind==="appended"?p("success",R(h("atlas.liveAppended"),{n:P.turns}),8e3):P?.kind==="mounted"&&p("success",R(h(P.mode==="continue"?"atlas.mountedChapter":"atlas.mountedBranch"),{n:P.turns}),9e3)}finally{if(S=!1,y){const a=y;y=null,g(a)}}};d.onSessionsChanged(s=>{g(s)});const C=async s=>{if(!s)return;const{useStore:a}=await l(async()=>{const{useStore:o}=await import("./index-DwwxoOcO.js").then(m=>m.G);return{useStore:o}},__vite__mapDeps([0,1])),{useUiStore:i}=await l(async()=>{const{useUiStore:o}=await import("./index-DwwxoOcO.js").then(m=>m.D);return{useUiStore:o}},__vite__mapDeps([0,1])),c=a.getState().nodes.find(o=>o.data.importSource?.itemIds?.includes(s));c&&i.getState().setArrivalFocusNodeId(c.id)},T=async(s,a)=>{const{useProjects:i,switchProject:c}=await l(async()=>{const{useProjects:e,switchProject:f}=await import("./index-DwwxoOcO.js").then(r=>r.H);return{useProjects:e,switchProject:f}},__vite__mapDeps([0,1])),o=i.getState().projects,m=o.filter(e=>e.name===s),t=o.find(e=>e.id===s)??(m.length===1?m[0]:void 0);if(!t){p("error",h("handoff.deeplinkMiss"));return}if(await c(t.id),a){const{useStore:e}=await l(async()=>{const{useStore:r}=await import("./index-DwwxoOcO.js").then(n=>n.G);return{useStore:r}},__vite__mapDeps([0,1])),{useUiStore:f}=await l(async()=>{const{useUiStore:r}=await import("./index-DwwxoOcO.js").then(n=>n.D);return{useUiStore:r}},__vite__mapDeps([0,1]));e.getState().nodes.some(r=>r.id===a)&&f.getState().setArrivalFocusNodeId(a)}},O=async s=>{let a="",i="";try{const n=new URL(s),u=n.searchParams.get("canvas");if(u){await T(u,n.searchParams.get("node")??"");return}a=n.searchParams.get("session")??"",i=n.searchParams.get("turn")??""}catch{return}if(!/^[A-Za-z0-9_-]{8,}$/.test(a))return;/^[A-Za-z0-9_-]*$/.test(i)||(i="");const c=await d.roots().catch(()=>[]);let o=null;for(const n of c){const u=await d.list(n.key).catch(()=>[]),_=K(u,a);if(_){o={rootKey:n.key,rel:_.rel};break}}if(!o){const n=[];for(const u of c)for(const _ of await d.list(u.key).catch(()=>[]))n.push({rootKey:u.key,rel:_.rel,mtime:_.mtime});n.sort((u,_)=>_.mtime-u.mtime);for(const u of n.slice(0,40)){const _=await d.head(u.rootKey,u.rel,262144).catch(()=>"");if(_&&b(_)===a){o=u;break}}}if(!o){p("error",h("handoff.deeplinkMiss"));return}const{streamRunnerConversation:m}=await l(async()=>{const{streamRunnerConversation:n}=await import("./index-CTQSx_k0.js");return{streamRunnerConversation:n}},__vite__mapDeps([3,0,1])),{importOrAppendConversation:t,shellSessionReader:e}=await l(async()=>{const{importOrAppendConversation:n,shellSessionReader:u}=await import("./canonical-DccXDqkX.js");return{importOrAppendConversation:n,shellSessionReader:u}},__vite__mapDeps([4,0,1])),f=await m(e(o.rootKey,o.rel)).catch(()=>null),r=await t(f);r?r.kind==="appended"?p("success",R(h("atlas.liveAppended"),{n:r.turns}),8e3):r.kind==="mounted"?p("success",R(h(r.mode==="continue"?"atlas.mountedChapter":"atlas.mountedBranch"),{n:r.turns}),9e3):r.kind==="imported"&&p("success",R(h("toast.importedChats"),{n:1,m:r.nodeCount}),8e3):p("error",h("handoff.notASession")),r&&i&&await C(i)};d.onDeepLink?.(s=>{O(s)}),(async()=>{const{bootProjects:s}=await l(async()=>{const{bootProjects:i}=await import("./index-DwwxoOcO.js").then(c=>c.H);return{bootProjects:i}},__vite__mapDeps([0,1]));await s();const a=await d.pendingDeepLink?.().catch(()=>null);a&&O(a)})();const L="thoughtdag.atlas.sweepmark";I=async()=>{const{bootProjects:s}=await l(async()=>{const{bootProjects:t}=await import("./index-DwwxoOcO.js").then(e=>e.H);return{bootProjects:t}},__vite__mapDeps([0,1]));await s();const a=await d.roots().catch(()=>[]),i=[];for(const t of a){const e=await d.list(t.key).catch(()=>[]);for(const f of e)i.push({rootKey:t.key,rel:f.rel,mtime:f.mtime,size:f.size})}let c={};try{c=JSON.parse(localStorage.getItem(L)??"{}")}catch{}const o=Object.keys(c).length===0;i.sort((t,e)=>e.mtime-t.mtime);const m=i.filter((t,e)=>{const f=`${t.mtime}|${t.size}`;return c[`${t.rootKey}|${t.rel}`]===f?!1:o?e<30:!0});for(const t of m)await g({rootKey:t.rootKey,rel:t.rel});for(const t of i)c[`${t.rootKey}|${t.rel}`]=`${t.mtime}|${t.size}`;localStorage.setItem(L,JSON.stringify(c))},I()}export{K as pickSessionFile,z as startLiveMirror,H as sweepRecentSessions};
@@ -1,4 +1,4 @@
1
- import{v as w,B as x,o as T}from"./index-BBqrNNdn.js";import{t as A,a as I,e as l,d as C,T as L,A as h,g as m,f as O,s as S}from"./shared-BUQtzATG.js";const f=r=>typeof r=="string"?r:Array.isArray(r)?r.filter(t=>t&&t.type==="text"&&typeof t.text=="string").map(t=>t.text).join(`
1
+ import{v as w,B as x,o as T}from"./index-DwwxoOcO.js";import{t as A,a as I,e as l,d as C,T as L,A as h,g as m,f as O,s as S}from"./shared-DCtLTsEN.js";const f=r=>typeof r=="string"?r:Array.isArray(r)?r.filter(t=>t&&t.type==="text"&&typeof t.text=="string").map(t=>t.text).join(`
2
2
  `):"";function b(r,t){const s=t&&typeof t=="object"?t:{},e=I(r),n=typeof s.path=="string"&&s.path&&(e==="read"||e==="edit"||e==="write")?[s.path]:[];let o;if(e==="read"&&(typeof s.offset=="number"||typeof s.limit=="number")){const c=Math.max(1,typeof s.offset=="number"?s.offset:1),d=typeof s.limit=="number"?c+Math.max(0,s.limit)-1:c;o={lines:[c,Math.max(c,d)]}}if(e==="write"&&typeof s.content=="string")return{...l(`${s.path??""}
3
3
 
4
4
  ${s.content}`,h),paths:n};if(e==="edit"){const c=Array.isArray(s.edits)?s.edits:typeof s.newText=="string"?[{oldText:s.oldText,newText:s.newText}]:[];if(c.length){const d=c.map(i=>`${s.path??""}
@@ -1,4 +1,4 @@
1
- import{f as c,a,w as g}from"./index-BBqrNNdn.js";const k=[{kind:"sk-… key",re:/\bsk-[A-Za-z0-9_-]{16,}\b/g},{kind:"GitHub token",re:/\bgh[pousr]_[A-Za-z0-9]{20,}\b/g},{kind:"AWS access key",re:/\bAKIA[0-9A-Z]{16}\b/g},{kind:"Slack token",re:/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g},{kind:"Google API key",re:/\bAIza[0-9A-Za-z_-]{35}\b/g},{kind:"JWT",re:/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{6,}\b/g},{kind:"private key block",re:/-----BEGIN [A-Z ]*PRIVATE KEY-----/g},{kind:"token assignment",re:/\b(?:API_KEY|APIKEY|SECRET|TOKEN|PASSWORD|ACCESS_KEY|AUTH_TOKEN)[A-Z_]*\s*[=:]\s*['"]?[A-Za-z0-9_/+-]{16,}/g},{kind:"HuggingFace token",re:/\bhf_[A-Za-z0-9]{30,}\b/g},{kind:"npm token",re:/\bnpm_[A-Za-z0-9]{30,}\b/g}],f=t=>t.length<=10?`${t.slice(0,4)}…`:`${t.slice(0,6)}…${t.slice(-2)}`;function o(t,e,s){if(t)for(const{kind:i,re:n}of k){n.lastIndex=0;const r=n.exec(t);if(r&&s.push({where:e,kind:i,sample:f(r[0])}),s.length>=20)return}}function l(t){const e=[];for(const s of t){const i=(s.data.question||s.data.summaries?.[0]||s.id).replace(/\s+/g," ").slice(0,28);o(s.data.question,i,e);for(const n of s.data.responses??[])o(n,i,e);for(const n of s.data.attachments??[])n.type.startsWith("image/")||o(n.content,`${i} · ${n.name}`,e);if(e.length>=20)break}return e}async function b(t){const e=l(t);if(e.length===0)return!0;const s=e.slice(0,5).map(n=>`· ${n.where} — ${n.kind}(${n.sample})`).join(`
1
+ import{f as c,a,w as g}from"./index-DwwxoOcO.js";const k=[{kind:"sk-… key",re:/\bsk-[A-Za-z0-9_-]{16,}\b/g},{kind:"GitHub token",re:/\bgh[pousr]_[A-Za-z0-9]{20,}\b/g},{kind:"AWS access key",re:/\bAKIA[0-9A-Z]{16}\b/g},{kind:"Slack token",re:/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g},{kind:"Google API key",re:/\bAIza[0-9A-Za-z_-]{35}\b/g},{kind:"JWT",re:/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{6,}\b/g},{kind:"private key block",re:/-----BEGIN [A-Z ]*PRIVATE KEY-----/g},{kind:"token assignment",re:/\b(?:API_KEY|APIKEY|SECRET|TOKEN|PASSWORD|ACCESS_KEY|AUTH_TOKEN)[A-Z_]*\s*[=:]\s*['"]?[A-Za-z0-9_/+-]{16,}/g},{kind:"HuggingFace token",re:/\bhf_[A-Za-z0-9]{30,}\b/g},{kind:"npm token",re:/\bnpm_[A-Za-z0-9]{30,}\b/g}],f=t=>t.length<=10?`${t.slice(0,4)}…`:`${t.slice(0,6)}…${t.slice(-2)}`;function o(t,e,s){if(t)for(const{kind:i,re:n}of k){n.lastIndex=0;const r=n.exec(t);if(r&&s.push({where:e,kind:i,sample:f(r[0])}),s.length>=20)return}}function l(t){const e=[];for(const s of t){const i=(s.data.question||s.data.summaries?.[0]||s.id).replace(/\s+/g," ").slice(0,28);o(s.data.question,i,e);for(const n of s.data.responses??[])o(n,i,e);for(const n of s.data.attachments??[])n.type.startsWith("image/")||o(n.content,`${i} · ${n.name}`,e);if(e.length>=20)break}return e}async function b(t){const e=l(t);if(e.length===0)return!0;const s=e.slice(0,5).map(n=>`· ${n.where} — ${n.kind}(${n.sample})`).join(`
2
2
  `),i=e.length>5?`
3
3
  ${c(a("sensitive.more"),{n:e.length-5})}`:"";return g({title:a("sensitive.title"),message:`${c(a("sensitive.body"),{n:e.length})}
4
4
 
@@ -1,2 +1,2 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/canonical-BZIQSZy0.js","assets/index-BBqrNNdn.js","assets/index-BfIl0Oie.css"])))=>i.map(i=>d[i]);
2
- import{t as n,a as e,_ as p,f as a,u as h}from"./index-BBqrNNdn.js";const l=/^http:\/\/(127\.0\.0\.1|localhost|\[::1\])(:\d+)?\//i;async function w(){const s=window.location.hash,r=s.match(/[#&]import-url=([^&]+)/);if(!r)return;const c=/[#&]mode=harvest\b/.test(s);history.replaceState(null,"",window.location.pathname+window.location.search);const i=decodeURIComponent(r[1]);if(!l.test(i)){n("error",e("handoff.notLoopback"),9e3);return}try{const o=await fetch(i);if(!o.ok)throw new Error(`HTTP ${o.status}`);const d=await o.text(),{importOrAppendSession:f}=await p(async()=>{const{importOrAppendSession:u}=await import("./canonical-BZIQSZy0.js");return{importOrAppendSession:u}},__vite__mapDeps([0,1,2])),t=await f(d);if(!t)throw new Error(e("handoff.notASession"));t.kind==="appended"?n("success",a(e("atlas.appended"),{n:t.turns}),9e3):t.kind==="mounted"?n("success",a(e(t.mode==="continue"?"atlas.mountedChapter":"atlas.mountedBranch"),{n:t.turns}),12e3):t.kind==="opened"?n("info",e("atlas.upToDate"),6e3):n("info",a(e("handoff.saved"),{n:t.nodeCount}),12e3,{label:e("handoff.backupAction"),run:()=>h.getState().setBackupDialogOpen(!0)})}catch(o){n("error",a(e("handoff.failed"),{msg:o instanceof Error?o.message:String(o)}),12e3)}}export{w as consumeSessionHandoff};
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/canonical-DccXDqkX.js","assets/index-DwwxoOcO.js","assets/index-2pggDGtC.css"])))=>i.map(i=>d[i]);
2
+ import{t as n,a as e,_ as p,f as a,u as h}from"./index-DwwxoOcO.js";const l=/^http:\/\/(127\.0\.0\.1|localhost|\[::1\])(:\d+)?\//i;async function w(){const s=window.location.hash,r=s.match(/[#&]import-url=([^&]+)/);if(!r)return;const c=/[#&]mode=harvest\b/.test(s);history.replaceState(null,"",window.location.pathname+window.location.search);const i=decodeURIComponent(r[1]);if(!l.test(i)){n("error",e("handoff.notLoopback"),9e3);return}try{const o=await fetch(i);if(!o.ok)throw new Error(`HTTP ${o.status}`);const d=await o.text(),{importOrAppendSession:f}=await p(async()=>{const{importOrAppendSession:u}=await import("./canonical-DccXDqkX.js");return{importOrAppendSession:u}},__vite__mapDeps([0,1,2])),t=await f(d);if(!t)throw new Error(e("handoff.notASession"));t.kind==="appended"?n("success",a(e("atlas.appended"),{n:t.turns}),9e3):t.kind==="mounted"?n("success",a(e(t.mode==="continue"?"atlas.mountedChapter":"atlas.mountedBranch"),{n:t.turns}),12e3):t.kind==="opened"?n("info",e("atlas.upToDate"),6e3):n("info",a(e("handoff.saved"),{n:t.nodeCount}),12e3,{label:e("handoff.backupAction"),run:()=>h.getState().setBackupDialogOpen(!0)})}catch(o){n("error",a(e("handoff.failed"),{msg:o instanceof Error?o.message:String(o)}),12e3)}}export{w as consumeSessionHandoff};
@@ -1,4 +1,4 @@
1
- import{v as m,o as p,B as h,C as g}from"./index-BBqrNNdn.js";const _=[[/^(Read|NotebookRead|read_file|view_image)$/i,"read"],[/^(Write|write_file)$/i,"write"],[/^(Edit|MultiEdit|NotebookEdit|apply_patch)$/i,"edit"],[/^(Bash|BashOutput|Shell|exec_command|local_shell|write_stdin)$/i,"run"],[/^(Grep|Glob|LS|Search|WebSearch|web_search)$/i,"search"],[/^(WebFetch|Fetch)$/i,"fetch"],[/^(Task|Agent)$/i,"agent"]];function y(e){return _.find(([t])=>t.test(e))?.[1]??"other"}const x=800,O=32e3,S=4e3,I=(e,t)=>e.length>t?{text:`${e.slice(0,t)}
1
+ import{v as m,o as p,B as h,C as g}from"./index-DwwxoOcO.js";const _=[[/^(Read|NotebookRead|read_file|view_image)$/i,"read"],[/^(Write|write_file)$/i,"write"],[/^(Edit|MultiEdit|NotebookEdit|apply_patch)$/i,"edit"],[/^(Bash|BashOutput|Shell|exec_command|local_shell|write_stdin)$/i,"run"],[/^(Grep|Glob|LS|Search|WebSearch|web_search)$/i,"search"],[/^(WebFetch|Fetch)$/i,"fetch"],[/^(Task|Agent)$/i,"agent"]];function y(e){return _.find(([t])=>t.test(e))?.[1]??"other"}const x=800,O=32e3,S=4e3,I=(e,t)=>e.length>t?{text:`${e.slice(0,t)}
2
2
  …[truncated, ${e.length} chars total]`,truncated:!0}:{text:e,truncated:!1},$=["<command-name>/thoughtdag</command-name>","[[thoughtdag:command]]"];function M(e){return e.filter(t=>!$.some(n=>t.question.includes(n)))}function w(e){e.width=460}function b(e){const t=g(e.data.response,90);t&&(e.data.summaries=[t])}function L(e){return e.tools.map(t=>({id:p(),name:`tool: ${t.name}${t.truncated?" (truncated)":""}`,type:"text/plain",size:t.call.length+t.result.length,content:`[call] ${t.call}
3
3
  [result]
4
4
  ${t.result}`,...t.paths?.length?{paths:t.paths}:{},...t.op?{op:t.op}:{}}))}function k(e,t,n,s,i){if(e.length===0)return null;const r=[],l=[];let u={id:s.id};for(const o of e){const a=m(o.question||"(tool-only turn)",o.response,!1);a.data.importSource={runner:n,sessionId:t,itemIds:o.itemIds,...i?{cwd:i}:{}},a.data.source={question:a.data.question,response:a.data.response},a.data.attachments=L(o),b(a),r.length===0&&(a.data.isBranch=!0),r.push(a),l.push({id:p(),source:u.id,target:a.id,type:"smoothstep"}),u=a}const c=h(r,l.filter(o=>o.source!==s.id)),f=Math.min(...c.map(o=>o.position.x)),d=Math.min(...c.map(o=>o.position.y));for(const o of c)o.position={x:o.position.x-f+s.x+560,y:o.position.y-d+s.y};return{nodes:c,edges:l,turnCount:e.length}}function A(e){const t=e??{},n={};typeof t.url=="string"&&/^https?:\/\//.test(t.url)&&(n.url=t.url);const s={};if(typeof t.pages=="string"&&t.pages.trim()&&(s.pages=t.pages.trim()),typeof t.offset=="number"||typeof t.limit=="number"){const i=Math.max(1,typeof t.offset=="number"?t.offset:1),r=typeof t.limit=="number"?i+Math.max(0,t.limit)-1:i;s.lines=[i,Math.max(i,r)]}return Object.keys(s).length&&(n.locator=s),n}function C(e){const t=e??{},n=t.file_path??t.notebook_path;return typeof n=="string"&&n?[n]:[]}function q(e,t){const n=t??{},s=y(e);return s==="write"&&typeof n.content=="string"?`${n.file_path??""}
@@ -1,4 +1,4 @@
1
- import{A as W}from"./index-BBqrNNdn.js";function Y(d,p){for(var g=0;g<p.length;g++){const s=p[g];if(typeof s!="string"&&!Array.isArray(s)){for(const c in s)if(c!=="default"&&!(c in d)){const o=Object.getOwnPropertyDescriptor(s,c);o&&Object.defineProperty(d,c,o.get?o:{enumerable:!0,get:()=>s[c]})}}}return Object.freeze(Object.defineProperty(d,Symbol.toStringTag,{value:"Module"}))}var u={},E;function J(){if(E)return u;E=1,Object.defineProperty(u,"__esModule",{value:!0});var d=/highlight-(?:text|source)-([a-z0-9]+)/;function p(t){t.addRule("highlightedCodeBlock",{filter:function(e){var r=e.firstChild;return e.nodeName==="DIV"&&d.test(e.className)&&r&&r.nodeName==="PRE"},replacement:function(e,r,n){var l=r.className||"",i=(l.match(d)||[null,""])[1];return`
1
+ import{A as W}from"./index-DwwxoOcO.js";function Y(d,p){for(var g=0;g<p.length;g++){const s=p[g];if(typeof s!="string"&&!Array.isArray(s)){for(const c in s)if(c!=="default"&&!(c in d)){const o=Object.getOwnPropertyDescriptor(s,c);o&&Object.defineProperty(d,c,o.get?o:{enumerable:!0,get:()=>s[c]})}}}return Object.freeze(Object.defineProperty(d,Symbol.toStringTag,{value:"Module"}))}var u={},E;function J(){if(E)return u;E=1,Object.defineProperty(u,"__esModule",{value:!0});var d=/highlight-(?:text|source)-([a-z0-9]+)/;function p(t){t.addRule("highlightedCodeBlock",{filter:function(e){var r=e.firstChild;return e.nodeName==="DIV"&&d.test(e.className)&&r&&r.nodeName==="PRE"},replacement:function(e,r,n){var l=r.className||"",i=(l.match(d)||[null,""])[1];return`
2
2
 
3
3
  `+n.fence+i+`
4
4
  `+r.firstChild.textContent+`
@@ -1 +1 @@
1
- import{t as u,a as n,f}from"./index-BBqrNNdn.js";const h=15*6e4;let i=!1;const d=new URLSearchParams(window.location.search),s=d.get("dv"),p=d.get("su")==="1",w=30*6e4;let c=!1,r=0;async function a(){if(!(!s||p||c)&&!(Date.now()-r<w)){r=Date.now();try{const t=await fetch("https://api.github.com/repos/chenxiachan/thoughtdag/releases?per_page=1");if(!t.ok)return;const o=(await t.json())?.[0]?.tag_name?.replace(/^v/,"");if(!o||o===s)return;c=!0,u("info",f(n("update.desktopAvailable"),{v:o}),0,{label:n("update.desktopDownload"),run:()=>window.open("https://chenxiachan.github.io/thoughtdag/#download","_blank")})}catch{}}}function v(){for(const t of document.querySelectorAll("script[src]")){const e=t.src.match(/assets\/index-[^/?#]+\.js/);if(e)return e[0]}return null}async function m(){try{const t=await fetch(`${window.location.pathname}?u=${Date.now()}`,{cache:"no-store"});if(!t.ok)return null;const e=(await t.text()).match(/assets\/index-[^"'?#]+\.js/);return e?e[0]:null}catch{return null}}async function l(){if(i)return;const t=v();if(!t)return;const e=await m();e&&e!==t&&(i=!0,u("info",n("update.available"),0,{label:n("update.refresh"),run:()=>window.location.reload()}))}function b(){document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&(l(),a())}),setInterval(()=>{l(),a()},h),s&&setTimeout(()=>{a()},5e3)}export{b as bootUpdateCheck};
1
+ import{t as u,a as n,f}from"./index-DwwxoOcO.js";const h=15*6e4;let i=!1;const d=new URLSearchParams(window.location.search),s=d.get("dv"),p=d.get("su")==="1",w=30*6e4;let c=!1,r=0;async function a(){if(!(!s||p||c)&&!(Date.now()-r<w)){r=Date.now();try{const t=await fetch("https://api.github.com/repos/chenxiachan/thoughtdag/releases?per_page=1");if(!t.ok)return;const o=(await t.json())?.[0]?.tag_name?.replace(/^v/,"");if(!o||o===s)return;c=!0,u("info",f(n("update.desktopAvailable"),{v:o}),0,{label:n("update.desktopDownload"),run:()=>window.open("https://chenxiachan.github.io/thoughtdag/#download","_blank")})}catch{}}}function v(){for(const t of document.querySelectorAll("script[src]")){const e=t.src.match(/assets\/index-[^/?#]+\.js/);if(e)return e[0]}return null}async function m(){try{const t=await fetch(`${window.location.pathname}?u=${Date.now()}`,{cache:"no-store"});if(!t.ok)return null;const e=(await t.text()).match(/assets\/index-[^"'?#]+\.js/);return e?e[0]:null}catch{return null}}async function l(){if(i)return;const t=v();if(!t)return;const e=await m();e&&e!==t&&(i=!0,u("info",n("update.available"),0,{label:n("update.refresh"),run:()=>window.location.reload()}))}function b(){document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&(l(),a())}),setInterval(()=>{l(),a()},h),s&&setTimeout(()=>{a()},5e3)}export{b as bootUpdateCheck};
@@ -22,8 +22,8 @@
22
22
  <meta name="twitter:title" content="ThoughtDAG — Your thinking deserves a map" />
23
23
  <meta name="twitter:description" content="Wires are the context: an infinite canvas where LLM conversations grow into an editable thought graph. No signup; the example canvas needs no key." />
24
24
  <meta name="twitter:image" content="https://app.thoughtdag.workers.dev/og-twitter.jpg" />
25
- <script type="module" crossorigin src="/thoughtdag/assets/index-BBqrNNdn.js"></script>
26
- <link rel="stylesheet" crossorigin href="/thoughtdag/assets/index-BfIl0Oie.css">
25
+ <script type="module" crossorigin src="/thoughtdag/assets/index-DwwxoOcO.js"></script>
26
+ <link rel="stylesheet" crossorigin href="/thoughtdag/assets/index-2pggDGtC.css">
27
27
  </head>
28
28
  <body>
29
29
  <div id="root"></div>
package/lib/client.js CHANGED
@@ -48,7 +48,7 @@ window.__ModuleLoader__.load({
48
48
  mapBtn.classList.toggle('active', map)
49
49
  mapBtn.setAttribute('aria-pressed', String(map))
50
50
  }
51
- const close = () => { overlay.hidden = true; setView(false) }
51
+ const close = () => { overlay.hidden = true; setView(false); send('td:view', { shown: false }) }
52
52
  const send = (type, payload) => frame.contentWindow?.postMessage({ source: 'dsh-thoughtdag', type, ...payload }, location.origin)
53
53
 
54
54
  const syncCurrent = () => {
@@ -64,8 +64,9 @@ window.__ModuleLoader__.load({
64
64
  // box and shows nothing when revealed
65
65
  if (!frame.src) frame.src = frame.dataset.src + (pluginVersion ? (frame.dataset.src.includes('?') ? '&' : '?') + 'dv=' + encodeURIComponent(pluginVersion) : '')
66
66
  syncCurrent()
67
+ send('td:view', { shown: true })
67
68
  // let the SPA boot, then re-sync so its listener is ready
68
- window.setTimeout(syncCurrent, 400)
69
+ window.setTimeout(() => { syncCurrent(); send('td:view', { shown: true }) }, 400)
69
70
  })
70
71
  dialogBtn.addEventListener('click', close)
71
72
 
package/lib/index.js CHANGED
@@ -218,6 +218,14 @@ function sessionToJsonl(session) {
218
218
  return lines.join('\n')
219
219
  }
220
220
 
221
+ /** The events after `since`, as lines — the tail a reader appends to the
222
+ * log it already holds, instead of fetching the whole log again. */
223
+ function eventsJsonlSince(session, since) {
224
+ const lines = []
225
+ for (const event of allEvents(session)) if (typeof event.seq === 'number' && event.seq > since) lines.push(JSON.stringify(event))
226
+ return lines.join('\n')
227
+ }
228
+
221
229
  // ── durable-session bridge (disk) ──────────────────────────────────────
222
230
  // DSH persists each session as session.jsonl.zstd under
223
231
  // $DSH_HOME/sessions/<encoded-cwd>/<session-id>/. The zstd log is a
@@ -306,6 +314,18 @@ async function findSessionFiles(dshHome) {
306
314
  * header names the id+cwd; session/title arrives right after in live logs.
307
315
  * Falls back to the session id when the frame boundary cuts early. */
308
316
  const HEAD_READ_BYTES = 256 * 1024 // covers the first frames: header + title + opening turns
317
+ // a session's head is decoded once per (path, size, mtime): the listing is
318
+ // polled, and a poll must not decompress sixty files it already knows
319
+ const headCache = new Map()
320
+ async function cachedHead(f) {
321
+ const key = `${f.log}|${f.size}|${f.mtime}`
322
+ const hit = headCache.get(key)
323
+ if (hit) return hit
324
+ const head = await sessionTitleFromHead(f.log)
325
+ if (headCache.size > 4000) headCache.clear()
326
+ headCache.set(key, head)
327
+ return head
328
+ }
309
329
  async function sessionTitleFromHead(logPath) {
310
330
  try {
311
331
  const fh = await open(logPath, 'r')
@@ -412,9 +432,13 @@ function fileInRoot(rootKey, rel) {
412
432
  }
413
433
 
414
434
  /** Every session file under a root, two-to-five levels deep like the desktop's walk. */
435
+ const listCache = new Map() // rootKey → { at, files }: a walk is reused for a few seconds
436
+ const LIST_CACHE_MS = 5000
415
437
  async function listRoot(rootKey) {
416
438
  const root = FILE_ROOTS[rootKey]
417
439
  if (!root) throw new HttpError(404, 'no such root')
440
+ const hit = listCache.get(rootKey)
441
+ if (hit && Date.now() - hit.at < LIST_CACHE_MS) return hit.files
418
442
  const out = []
419
443
  const walk = async (dir, depth) => {
420
444
  if (depth > 5) return
@@ -429,6 +453,7 @@ async function listRoot(rootKey) {
429
453
  }
430
454
  }
431
455
  await walk(root, 0)
456
+ listCache.set(rootKey, { at: Date.now(), files: out })
432
457
  return out
433
458
  }
434
459
 
@@ -639,21 +664,38 @@ function installApprovalAnswerer(ctx) {
639
664
  }, true)
640
665
  }
641
666
 
642
- /** Run one question through the harness's agent loop in a fresh session and
643
- * report it as SSE-style frames via `emit`; resolves when the turn ends. */
667
+ /** Run one question through the harness's agent loop, continuing a mirrored
668
+ * session, forking a dsh mirror at the parent's exact turn, or creating a
669
+ * fresh session. Report progress as SSE-style frames via `emit`; resolves
670
+ * when the turn ends. */
644
671
  async function runAgentTurn(ctx, body, emit, isClosed, { answerApprovals = true } = {}) {
645
672
  const { question, context } = compileForAgent(body)
646
- // continue the session the canvas mirrors (a tail follow-up), else a fresh
647
- // session in the requested working directory (the canvas's project)
673
+ // A parent that is a current ledger tail continues in place. A historical
674
+ // dsh mirror forks at the exact turn named by its user-message id, so later
675
+ // turns (including sibling work) cannot leak into the branch. Otherwise a
676
+ // fresh session receives the compiled canvas context.
648
677
  const continueId = typeof body?.harness?.session === 'string' && body.harness.session ? body.harness.session : null
649
- let sessionId
678
+ const forkId = typeof body?.harness?.forkSession === 'string' && body.harness.forkSession ? body.harness.forkSession : null
679
+ const forkAnchor = typeof body?.harness?.forkAnchor === 'string' && body.harness.forkAnchor ? body.harness.forkAnchor : null
680
+ let sessionId = null
681
+ let forkedFrom = null
650
682
  if (continueId && (liveSession(ctx, continueId) || (await eventsOfSession(ctx, continueId)) !== null)) {
651
683
  sessionId = continueId
652
- } else {
684
+ } else if (forkId && forkAnchor) {
685
+ const events = await eventsOfSession(ctx, forkId)
686
+ if (events !== null) {
687
+ const turn = turnsOf(events).find(t => t.userMessageId === forkAnchor)
688
+ if (!turn) throw new HttpError(400, 'no such fork anchor: ' + forkAnchor)
689
+ if (turn.endSeq === null) throw new HttpError(409, 'turn ' + turn.turn + ' is still open; a fork needs a completed turn')
690
+ sessionId = (await ctx.sessionController.fork({ sessionId: forkId, atSeq: turn.endSeq })).sessionId
691
+ forkedFrom = forkId
692
+ }
693
+ }
694
+ if (!sessionId) {
653
695
  const cwd = typeof body?.harness?.cwd === 'string' && body.harness.cwd ? { cwd: body.harness.cwd } : {}
654
696
  sessionId = (await ctx.sessionController.create({ ...cwd })).sessionId
655
697
  }
656
- emit({ harnessSession: sessionId, continued: sessionId === continueId })
698
+ emit({ harnessSession: sessionId, continued: sessionId === continueId, ...(forkedFrom ? { forkedFrom } : {}) })
657
699
  // a streaming caller can show and answer approvals; a one-shot caller
658
700
  // (/claude) cannot, so its requests fall through to the harness's own panel
659
701
  const turnEntry = { emit, isClosed, calls: new Map() }
@@ -699,7 +741,8 @@ async function runAgentTurn(ctx, body, emit, isClosed, { answerApprovals = true
699
741
  }
700
742
  })
701
743
  try {
702
- const injectContext = continueId && sessionId === continueId ? (typeof body?.harness?.extraContext === 'string' ? body.harness.extraContext : '') : context
744
+ const inheritsSessionContext = (continueId && sessionId === continueId) || forkedFrom
745
+ const injectContext = inheritsSessionContext ? (typeof body?.harness?.extraContext === 'string' ? body.harness.extraContext : '') : context
703
746
  if (injectContext) agent.inject({ id: 'td-' + randomUUID(), role: 'user', content: [{ type: 'text', text: injectContext }], source: SOURCE })
704
747
  const promptContent = [{ type: 'text', text: question }]
705
748
  for (const img of Array.isArray(body?.images) ? body.images : []) {
@@ -844,7 +887,7 @@ export async function apply(ctx, config) {
844
887
  const files = await findSessionFiles(dshHome)
845
888
  const sessions = []
846
889
  for (const f of files) {
847
- const head = await sessionTitleFromHead(f.log)
890
+ const head = await cachedHead(f)
848
891
  sessions.push({
849
892
  id: head.id ?? f.sessionDir,
850
893
  title: head.title,
@@ -880,7 +923,11 @@ export async function apply(ctx, config) {
880
923
  const id = decodeURIComponent(one[1])
881
924
  const session = liveSession(ctx, id)
882
925
  if (session === undefined) return sendJson(res, 404, { error: 'no such session' })
883
- if (one[2] === '/log') return sendFile(res, 'application/x-ndjson; charset=utf-8', sessionToJsonl(session))
926
+ if (one[2] === '/log') {
927
+ const since = url.searchParams.get('since')
928
+ if (since !== null && since !== '') return sendFile(res, 'application/x-ndjson; charset=utf-8', eventsJsonlSince(session, Number(since)))
929
+ return sendFile(res, 'application/x-ndjson; charset=utf-8', sessionToJsonl(session))
930
+ }
884
931
  return sendJson(res, 200, { session: sessionSummary(session) })
885
932
  }
886
933
  if (path === '/why/status' && req.method === 'GET') return sendJson(res, 200, whyStatus)
@@ -891,12 +938,13 @@ export async function apply(ctx, config) {
891
938
  for (const [key, p] of Object.entries(FILE_ROOTS)) roots.push({ key, path: p, builtin: true, exists: await stat(p).then(st => st.isDirectory()).catch(() => false) })
892
939
  return sendJson(res, 200, { roots })
893
940
  }
894
- const rootRoute = /^\/roots\/([a-z-]+)\/(list|head|read|range)$/.exec(path)
941
+ const rootRoute = /^\/roots\/([a-z-]+)\/(list|head|read|range|stat)$/.exec(path)
895
942
  if (rootRoute !== null && req.method === 'GET') {
896
943
  const key = rootRoute[1]
897
944
  if (rootRoute[2] === 'list') return sendJson(res, 200, { files: await listRoot(key) })
898
945
  const rel = url.searchParams.get('rel') ?? ''
899
946
  const abs = fileInRoot(key, rel)
947
+ if (rootRoute[2] === 'stat') { const st = await stat(abs).catch(() => null); return st ? sendJson(res, 200, { size: st.size, mtime: st.mtimeMs }) : sendJson(res, 404, { error: 'no such file' }) }
900
948
  if (rootRoute[2] === 'head') return sendFile(res, 'text/plain; charset=utf-8', await headOfFile(abs, Number(url.searchParams.get('bytes')) || 16384).catch(() => ''))
901
949
  if (rootRoute[2] === 'read') {
902
950
  const st = await stat(abs).catch(() => null)
@@ -1,24 +1,26 @@
1
1
  # Agent runtimes
2
2
 
3
- One file per runtime (CommonJS, `.cjs`, so it loads the same under any package type): `pi.cjs` over `pi --mode rpc`, `codex.cjs` over `codex app-server`. Each exports a factory returning the same surface:
3
+ One file per runtime (CommonJS, `.cjs`, so it loads the same under any package type): `pi.cjs` over `pi --mode rpc`, `codex.cjs` over `codex app-server`, `claude.cjs` over `claude -p` with stream-json both ways. Each exports a factory returning the same surface:
4
4
 
5
5
  - `available()` → where the binary is, or null
6
- - `models()` → `{ installed, models: [{ provider, id, name, reasoning, vision }], default }`
7
- - `run(request, onEvent)` → run id at once; events follow, tagged with it
6
+ - `models()` → `{ installed, models: [{ provider, id, name, reasoning, vision, efforts, defaultEffort }], default }` — `efforts` are the effort levels the CLI accepts for that model, in its own words, read from the CLI (empty when it has none); `defaultEffort` its own default when it says
7
+ - `run(request, onEvent)` → run id at once; events follow, tagged with it. The request names the runtime, an absolute `cwd`, one `prompt`, optional `images`, a `model` from that runtime's own list, a session to continue (`sessionPath`, plus `forkEntryId` to branch it), and optionally `effort`: one of the levels the runtime itself listed for that model, in its own words (Codex from `model/list`, Claude Code and Pi from their `--help`), which it passes on as is (`--effort`, the turn's `effort`, `set_thinking_level`). A word the runtime did not list is dropped: the CLI's own setting stands.
8
8
  - `abort(runId)`, `answer(runId, requestId, { confirmed } | { value } | { cancelled: true })`, `shutdown()`
9
9
 
10
10
  A run emits exactly these event kinds (the renderer reads nothing else):
11
11
 
12
12
  | kind | meaning |
13
13
  |---|---|
14
- | `session` | which session file this run writes to, and where (cwd) |
14
+ | `session` | which session file this run writes to, and where (cwd); `model.resolved` names the real model when the run used an alias (Claude Code's `opus` → `claude-opus-5`); `effort` is the level the turn actually ran at, read back from the runtime's own record (Pi's state, Codex's rollout `turn_context`, Claude Code's transcript entries), never assumed |
15
15
  | `message_update` with a text or thinking delta | the answer or the reasoning growing |
16
16
  | `tool_execution_start` / `tool_execution_end` | a tool call, for the live trace |
17
- | `question` | the agent asks the person: confirm / select / input / editor |
18
- | `question_answered` | the person's answer went back (record) |
17
+ | `question` | the agent asks the person: confirm / select / input / editor. A confirm may carry `rule`, an opaque key naming what "allow for this conversation" would cover (Claude Code: its own permission suggestion; Codex: the command, or all file changes); the answer `{ confirmed: true, scope: 'session' }` allows it for good — the runtime tells the CLI where it can (Claude Code `updatedPermissions` scoped to the session, Codex `acceptForSession`) and the canvas keeps the rule on the record, sending every such rule back as `allowRules` on later runs of this conversation |
18
+ | `question_answered` | the person's answer went back (record); with `auto: true` the runtime decided from a standing rule of this conversation and nobody was asked — the record is made from its `title`/`message`/`rule` |
19
19
  | `fs_changes` | what the shell saw change on disk (produced here, not by the agent) |
20
20
  | `run_end` / `run_error` | the turn is over |
21
21
 
22
+ Nothing starts at launch. A runtime's process (Pi's rpc, Codex's app-server, a Pi catalog read) is started by the first request that needs it and retires after ten minutes without one (`TD_AGENT_IDLE_MS`); Claude Code runs one process per turn. A launch that never opens the agent group never starts a CLI.
23
+
22
24
  The finished turn is adopted from the session file by the atlas's adapter for that
23
25
  runtime; the stream is for showing, the file is the truth. Anything a runtime does
24
26
  beyond these kinds stays inside its own file.