dsh-thoughtdag 0.4.5 → 0.4.7

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 (25) hide show
  1. package/README.md +11 -1
  2. package/dist-app/assets/{canonical-DDfsxGbh.js → canonical-D2XBb2dX.js} +2 -2
  3. package/dist-app/assets/{canvas-record-vunI4XSG.js → canvas-record-GBNDUaVp.js} +1 -1
  4. package/dist-app/assets/{claude-code-session-w7XeYCbv.js → claude-code-session-KqpMLSSi.js} +1 -1
  5. package/dist-app/assets/{codex-session-BteJdhnJ.js → codex-session-Dcy2uWFk.js} +1 -1
  6. package/dist-app/assets/{dsh-session-DtVkAUjy.js → dsh-session-DtUFFQiI.js} +2 -2
  7. package/dist-app/assets/{experiment-loop-cKs-OaUA.js → experiment-loop-DmwbMQzX.js} +1 -1
  8. package/dist-app/assets/index-D1pfymrb.css +1 -0
  9. package/dist-app/assets/{index-CZgf0TxH.js → index-DalA8OYo.js} +1 -1
  10. package/dist-app/assets/index-NvCD8Rvo.js +3 -0
  11. package/dist-app/assets/{index-ZY9zV6mj.js → index-XXbBaf-t.js} +1 -1
  12. package/dist-app/assets/{index-BgFLKHxw.js → index-zDvcuppH.js} +141 -139
  13. package/dist-app/assets/{live-mirror-Cil__utB.js → live-mirror-DHhvAkDw.js} +2 -2
  14. package/dist-app/assets/pi-session-BQNKkZYe.js +14 -0
  15. package/dist-app/assets/{sensitive-scan-NBqP2Srd.js → sensitive-scan-BVopW_mj.js} +1 -1
  16. package/dist-app/assets/{session-handoff-C_88Qy1y.js → session-handoff-CdTJCZJl.js} +2 -2
  17. package/dist-app/assets/{shared-0Cyt1YSM.js → shared-DWUtbV9j.js} +1 -1
  18. package/dist-app/assets/{turndown-plugin-gfm.cjs-BkQZDlXD.js → turndown-plugin-gfm.cjs-DmDDHBxg.js} +1 -1
  19. package/dist-app/assets/{update-check-Ca6SiYKC.js → update-check-DFw8Y8PB.js} +1 -1
  20. package/dist-app/index.html +2 -2
  21. package/lib/index.js +68 -6
  22. package/lib/why.mjs +199 -16
  23. package/package.json +1 -1
  24. package/dist-app/assets/index-DEHbIrRr.css +0 -1
  25. package/dist-app/assets/index-DbmziCje.js +0 -3
@@ -1,2 +1,2 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-BgFLKHxw.js","assets/index-DEHbIrRr.css","assets/experiment-loop-cKs-OaUA.js","assets/index-DbmziCje.js","assets/canonical-DDfsxGbh.js"])))=>i.map(i=>d[i]);
2
- import{_ as l,i as j,t as p,a as h,f as R}from"./index-BgFLKHxw.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-BgFLKHxw.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-BgFLKHxw.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-cKs-OaUA.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-DbmziCje.js");return{streamRunnerConversation:w}},__vite__mapDeps([3,0,1])),{importOrAppendConversation:u,shellSessionReader:_}=await l(async()=>{const{importOrAppendConversation:w,shellSessionReader:E}=await import("./canonical-DDfsxGbh.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-BgFLKHxw.js").then(m=>m.G);return{useStore:o}},__vite__mapDeps([0,1])),{useUiStore:i}=await l(async()=>{const{useUiStore:o}=await import("./index-BgFLKHxw.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-BgFLKHxw.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-BgFLKHxw.js").then(n=>n.G);return{useStore:r}},__vite__mapDeps([0,1])),{useUiStore:f}=await l(async()=>{const{useUiStore:r}=await import("./index-BgFLKHxw.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-DbmziCje.js");return{streamRunnerConversation:n}},__vite__mapDeps([3,0,1])),{importOrAppendConversation:t,shellSessionReader:e}=await l(async()=>{const{importOrAppendConversation:n,shellSessionReader:u}=await import("./canonical-DDfsxGbh.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-BgFLKHxw.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-BgFLKHxw.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-zDvcuppH.js","assets/index-D1pfymrb.css","assets/experiment-loop-DmwbMQzX.js","assets/index-NvCD8Rvo.js","assets/canonical-D2XBb2dX.js"])))=>i.map(i=>d[i]);
2
+ import{_ as l,i as j,t as p,a as h,f as R}from"./index-zDvcuppH.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-zDvcuppH.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-zDvcuppH.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-DmwbMQzX.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-NvCD8Rvo.js");return{streamRunnerConversation:w}},__vite__mapDeps([3,0,1])),{importOrAppendConversation:u,shellSessionReader:_}=await l(async()=>{const{importOrAppendConversation:w,shellSessionReader:E}=await import("./canonical-D2XBb2dX.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-zDvcuppH.js").then(m=>m.G);return{useStore:o}},__vite__mapDeps([0,1])),{useUiStore:i}=await l(async()=>{const{useUiStore:o}=await import("./index-zDvcuppH.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-zDvcuppH.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-zDvcuppH.js").then(n=>n.G);return{useStore:r}},__vite__mapDeps([0,1])),{useUiStore:f}=await l(async()=>{const{useUiStore:r}=await import("./index-zDvcuppH.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-NvCD8Rvo.js");return{streamRunnerConversation:n}},__vite__mapDeps([3,0,1])),{importOrAppendConversation:t,shellSessionReader:e}=await l(async()=>{const{importOrAppendConversation:n,shellSessionReader:u}=await import("./canonical-D2XBb2dX.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-zDvcuppH.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-zDvcuppH.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};
@@ -0,0 +1,14 @@
1
+ import{v as w,B as x,o as T}from"./index-zDvcuppH.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-DWUtbV9j.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
+ `):"";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
+
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??""}
5
+ --- old
6
+ ${i.oldText??""}
7
+ +++ new
8
+ ${i.newText??""}`).join(`
9
+
10
+ `);return{...l(d,h),paths:n}}}return e==="run"&&typeof s.command=="string"?{...l(s.command,m),paths:n}:{...l(JSON.stringify(t??{}),m),paths:n,...o?{locator:o}:{}}}class q{turns=[];pending=new Map;current=null;sessionId=null;cwd=null;firstQuestion=null;feedLine(t){const s=t.trim();if(!s)return;let e;try{e=JSON.parse(s)}catch{return}this.feed(e)}flush(){const t=this.current;t&&(t.question||t.response||t.tools.length)&&this.turns.push(t),this.current=null}feed(t){if(t.type==="session"&&typeof t.id=="string"){this.sessionId||(this.sessionId=t.id),typeof t.cwd=="string"&&!this.cwd&&(this.cwd=t.cwd);return}if(t.type!=="message"){this.current&&typeof t.id=="string"&&this.current.itemIds.push(t.id);return}const s=t.message;if(s){if(s.role==="user"){const e=f(s.content).trim();if(!e)return;this.flush(),this.firstQuestion||(this.firstQuestion=e.split(`
11
+ `)[0].slice(0,80));const n=typeof t.timestamp=="string"?t.timestamp:typeof s.timestamp=="number"?new Date(s.timestamp).toISOString():void 0;this.current={question:e,response:"",itemIds:typeof t.id=="string"?[t.id]:[],tools:[],...typeof t.parentId=="string"?{parentItemId:t.parentId}:{},...n?{at:n}:{}};return}if(s.role==="assistant"){const e=this.current??(this.current={question:"",response:"",itemIds:[],tools:[]});typeof t.id=="string"&&e.itemIds.push(t.id);const n=f(s.content).trim();if(n&&(e.response=e.response?`${e.response}
12
+
13
+ ${n}`:n),Array.isArray(s.content))for(const o of s.content){if(o?.type!=="toolCall"||typeof o.id!="string"||typeof o.name!="string")continue;const a=b(o.name,o.arguments);this.pending.set(o.id,{name:o.name,call:a.text,truncated:a.truncated,op:I(o.name),paths:a.paths,...a.locator?{locator:a.locator}:{}})}return}if(s.role==="toolResult"){const e=s.toolCallId;if(!e)return;const n=this.pending.get(e);if(!n)return;const o=this.current??(this.current={question:"",response:"",itemIds:[],tools:[]});typeof t.id=="string"&&o.itemIds.push(t.id);const a=l(f(s.content),L);o.tools.push({name:n.name,call:n.call,result:a.text,truncated:n.truncated||a.truncated,op:n.op,nativeCallId:e,...n.paths.length?{paths:n.paths}:{},...n.locator?{locator:n.locator}:{}}),this.pending.delete(e)}}}finish(){if(this.flush(),!this.sessionId)return null;const t=this.firstQuestion??`session ${this.sessionId.slice(0,8)}`;return{sessionId:this.sessionId,title:t,turns:C(this.turns),...this.cwd?{cwd:this.cwd}:{}}}toConversation(){const t=this.finish();return!t||t.turns.length===0?null:{title:t.title,messageCount:t.turns.length,source:"pi",sessionId:t.sessionId,build:()=>v(t.turns,t.sessionId,t.cwd)}}}function y(r){const t=new q;for(const s of r.split(`
14
+ `))t.feedLine(s);return t}function v(r,t,s){const e=s?{cwd:s}:{},n=[],o=[],a=new Map,c=(u,i)=>o.push({id:T(),source:u.id,target:i.id,type:"smoothstep"});let d=null;for(const u of r){const i=w(u.question||"(tool-only turn)",u.response,d===null);i.data.importSource={runner:"pi",sessionId:t,itemIds:u.itemIds,...e},i.data.source={question:i.data.question,response:i.data.response},i.data.attachments=O(u),S(i),n.push(i);const p=u.parentItemId?a.get(u.parentItemId):void 0;p?c(p,i):d&&c(d,i);for(const g of u.itemIds)a.set(g,i);d=i}return{nodes:x(n,o),edges:o}}function _(r,t){const s=y(r).finish();return s?A(s.turns,s.sessionId,"pi",t,s.cwd):null}function Q(r){return y(r).toConversation()}export{q as PiSessionCollector,v as buildGraphFromTurns,_ as piSessionAsBranch,Q as piSessionConversation};
@@ -1,4 +1,4 @@
1
- import{f as c,a,w as g}from"./index-BgFLKHxw.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-zDvcuppH.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-DDfsxGbh.js","assets/index-BgFLKHxw.js","assets/index-DEHbIrRr.css"])))=>i.map(i=>d[i]);
2
- import{t as n,a as e,_ as p,f as a,u as h}from"./index-BgFLKHxw.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-DDfsxGbh.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-D2XBb2dX.js","assets/index-zDvcuppH.js","assets/index-D1pfymrb.css"])))=>i.map(i=>d[i]);
2
+ import{t as n,a as e,_ as p,f as a,u as h}from"./index-zDvcuppH.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-D2XBb2dX.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-BgFLKHxw.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-zDvcuppH.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-BgFLKHxw.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-zDvcuppH.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-BgFLKHxw.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-zDvcuppH.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-BgFLKHxw.js"></script>
26
- <link rel="stylesheet" crossorigin href="/thoughtdag/assets/index-DEHbIrRr.css">
25
+ <script type="module" crossorigin src="/thoughtdag/assets/index-zDvcuppH.js"></script>
26
+ <link rel="stylesheet" crossorigin href="/thoughtdag/assets/index-D1pfymrb.css">
27
27
  </head>
28
28
  <body>
29
29
  <div id="root"></div>
package/lib/index.js CHANGED
@@ -75,7 +75,7 @@ import { fileURLToPath } from 'node:url'
75
75
  import { zstdDecompressSync } from 'node:zlib'
76
76
 
77
77
  export const name = 'thoughtdag'
78
- export const inject = ['webServer', 'sessions', 'sessionController', 'agents', 'llm', 'attachments', 'web', 'tools', 'commands', 'systemPrompt']
78
+ export const inject = ['webServer', 'sessions', 'sessionController', 'agents', 'llm', 'attachments', 'web', 'tools', 'commands', 'systemPrompt', 'approval']
79
79
 
80
80
  const __dirname = fileURLToPath(new URL('.', import.meta.url))
81
81
  const APP_DIR = resolve(__dirname, '../dist-app')
@@ -368,6 +368,7 @@ const SOURCE = { kind: 'plugin', plugin: 'dsh-thoughtdag' }
368
368
  const FILE_ROOTS = {
369
369
  'claude-projects': join(os.homedir(), '.claude', 'projects'),
370
370
  'codex-sessions': join(os.homedir(), '.codex', 'sessions'),
371
+ 'pi-sessions': join(os.homedir(), '.pi', 'agent', 'sessions'),
371
372
  }
372
373
  const FILE_READ_MAX = 256 * 1024 * 1024
373
374
 
@@ -544,9 +545,55 @@ function toolQuery(name, args) {
544
545
  return first ? first.slice(0, 120) : ''
545
546
  }
546
547
 
548
+ // ── approvals from the canvas ──────────────────────────────────────────
549
+ // A turn launched from the canvas runs behind the harness's own approval
550
+ // panel, which the canvas view covers. So the plugin answers first: while a
551
+ // canvas turn is running, its session is registered here; an approval the
552
+ // harness asks for that session becomes an `approval` frame on the turn's
553
+ // stream, the node shows the question, and the person's answer comes back
554
+ // through POST /approvals/:id. When the canvas stream is gone, the request
555
+ // is handed down the chain (`next()`) to the harness's panel instead.
556
+
557
+ /** sessionId → the canvas turn running in it: its frame emitter, its
558
+ * close probe, and the tool calls seen so far (callId → name, arguments). */
559
+ const canvasTurns = new Map()
560
+ /** approval id → the resolver waiting for the person's decision */
561
+ const pendingApprovals = new Map()
562
+
563
+ const APPROVAL_OUTCOMES = new Set(['allowed-once', 'rejected'])
564
+
565
+ function installApprovalAnswerer(ctx) {
566
+ ctx.on('approval/request', async function (req, next) {
567
+ const entry = canvasTurns.get(req?.agent?.id)
568
+ if (!entry) return next()
569
+ const id = 'td-' + randomUUID()
570
+ const call = req.callId !== undefined ? entry.calls.get(req.callId) : undefined
571
+ const name = call?.name ?? req.toolName
572
+ entry.emit({ approval: {
573
+ id, toolName: req.toolName, callId: req.callId ?? null, reason: req.reason ?? null,
574
+ name, query: call ? toolQuery(name, call.arguments) : '', arguments: typeof call?.arguments === 'string' ? call.arguments : (call?.arguments ? JSON.stringify(call.arguments) : null),
575
+ } })
576
+ let clear
577
+ const decision = new Promise(resolve => { pendingApprovals.set(id, { resolve, sessionId: req.agent.id }) })
578
+ const closed = new Promise(resolve => { const t = setInterval(() => { if (entry.isClosed()) { clearInterval(t); resolve('closed') } }, 500); clear = () => clearInterval(t) })
579
+ const aborted = new Promise(resolve => {
580
+ if (!req.signal) return
581
+ if (req.signal.aborted) resolve('aborted')
582
+ else req.signal.addEventListener('abort', () => resolve('aborted'), { once: true })
583
+ })
584
+ const r = await Promise.race([decision, closed, aborted])
585
+ pendingApprovals.delete(id)
586
+ clear?.()
587
+ if (r === 'closed') return next()
588
+ const outcome = r === 'aborted' ? 'cancelled' : r
589
+ entry.emit({ approvalDecided: { id, outcome } })
590
+ return outcome
591
+ }, true)
592
+ }
593
+
547
594
  /** Run one question through the harness's agent loop in a fresh session and
548
595
  * report it as SSE-style frames via `emit`; resolves when the turn ends. */
549
- async function runAgentTurn(ctx, body, emit, isClosed) {
596
+ async function runAgentTurn(ctx, body, emit, isClosed, { answerApprovals = true } = {}) {
550
597
  const { question, context } = compileForAgent(body)
551
598
  // continue the session the canvas mirrors (a tail follow-up), else a fresh
552
599
  // session in the requested working directory (the canvas's project)
@@ -559,6 +606,10 @@ async function runAgentTurn(ctx, body, emit, isClosed) {
559
606
  sessionId = (await ctx.sessionController.create({ ...cwd })).sessionId
560
607
  }
561
608
  emit({ harnessSession: sessionId, continued: sessionId === continueId })
609
+ // a streaming caller can show and answer approvals; a one-shot caller
610
+ // (/claude) cannot, so its requests fall through to the harness's own panel
611
+ const turnEntry = { emit, isClosed, calls: new Map() }
612
+ if (answerApprovals) canvasTurns.set(sessionId, turnEntry)
562
613
  const hasImages = Array.isArray(body?.images) && body.images.length > 0
563
614
  if (hasImages) {
564
615
  const vm = await visionModel(ctx).catch(() => null)
@@ -588,9 +639,8 @@ async function runAgentTurn(ctx, body, emit, isClosed) {
588
639
  // chunks did not reach us live (e.g. a compacted row): the whole text at once
589
640
  const text = (event.data?.message?.content ?? []).filter(b => b.type === 'text').map(b => b.text).join('')
590
641
  if (text) { fullText += text; emit({ text }) }
591
- } else if (event.type === 'tool/call') {
592
- emit({ tool: { name: event.data?.name ?? 'tool', query: toolQuery(event.data?.name, event.data?.arguments) } })
593
- } else if (event.type === 'tool/code-dispatch-start') {
642
+ } else if (event.type === 'tool/call' || event.type === 'tool/code-dispatch-start') {
643
+ if (event.data?.callId) turnEntry.calls.set(event.data.callId, { name: event.data?.name ?? 'tool', arguments: event.data?.arguments })
594
644
  emit({ tool: { name: event.data?.name ?? 'tool', query: toolQuery(event.data?.name, event.data?.arguments) } })
595
645
  } else if (event.type === 'turn/end') {
596
646
  done()
@@ -610,6 +660,7 @@ async function runAgentTurn(ctx, body, emit, isClosed) {
610
660
  if (isClosed()) { try { agent.cancel({ kind: 'user' }) } catch { /* best effort */ } }
611
661
  } finally {
612
662
  if (typeof off === 'function') off()
663
+ if (canvasTurns.get(sessionId) === turnEntry) canvasTurns.delete(sessionId)
613
664
  }
614
665
  return { sessionId, text: fullText }
615
666
  }
@@ -819,11 +870,21 @@ export async function apply(ctx, config) {
819
870
  }
820
871
  // ── model connection (the SPA's proxy protocol, on the harness's providers) ──
821
872
  if (path === '/models' && req.method === 'GET') return sendJson(res, 200, await modelsPayload(ctx))
873
+ const approvalRoute = /^\/approvals\/([^/]+)$/.exec(path)
874
+ if (approvalRoute !== null && req.method === 'POST') {
875
+ const id = decodeURIComponent(approvalRoute[1])
876
+ const body = await readJson(req, MAX_WRITE_BODY_BYTES)
877
+ const pending = pendingApprovals.get(id)
878
+ if (!pending) return sendJson(res, 404, { error: 'no such pending approval' })
879
+ const outcome = APPROVAL_OUTCOMES.has(body?.outcome) ? body.outcome : 'rejected'
880
+ pending.resolve(outcome)
881
+ return sendJson(res, 200, { id, outcome })
882
+ }
822
883
  if ((path === '/stream' || path === '/claude') && req.method === 'POST') {
823
884
  const body = await readJson(req, MAX_CALL_BODY_BYTES)
824
885
  if (body?.model === AGENT_MODEL) {
825
886
  if (path === '/claude') {
826
- const r = await runAgentTurn(ctx, body, () => {}, () => false)
887
+ const r = await runAgentTurn(ctx, body, () => {}, () => false, { answerApprovals: false })
827
888
  return sendJson(res, 200, { text: r.text, model: AGENT_MODEL, harnessSession: r.sessionId })
828
889
  }
829
890
  res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-cache', connection: 'keep-alive' })
@@ -934,5 +995,6 @@ export async function apply(ctx, config) {
934
995
  ctx.effect(() => ctx.webServer.register({ kind: 'prefix', path: prefix + '/api', handler: api }), 'thoughtdag: api')
935
996
  ctx.effect(() => ctx.webServer.register({ kind: 'prefix', path: prefix, handler: staticHandler }), 'thoughtdag: static')
936
997
  ctx.logger.info('[dsh-thoughtdag] ThoughtDAG mounted at ' + prefix + '/')
998
+ installApprovalAnswerer(ctx)
937
999
  await installWhyLayer(ctx, config)
938
1000
  }
package/lib/why.mjs CHANGED
@@ -80,11 +80,10 @@ function autoLayout(allNodes, allEdges) {
80
80
  const mats = allEdges.filter((e) => e.target === root.id && !e.data?.isCrossLink && contentIds.has(e.source)).map((e) => allNodes.find((n) => n.id === e.source)).filter((m) => !!m);
81
81
  if (mats.length === 0) continue;
82
82
  const lowest = mats.reduce((a, b) => a.position.y + nodeHeight(a) > b.position.y + nodeHeight(b) ? a : b);
83
- const midX = mats.reduce((t, m) => t + m.position.x, 0) / mats.length;
84
83
  const k = perMaterialCount.get(lowest.id) ?? 0;
85
84
  perMaterialCount.set(lowest.id, k + 1);
86
85
  materialAnchors.set(root.id, {
87
- x: midX - 60 + k * (LAYOUT_COL_WIDTH + LAYOUT_H_GAP),
86
+ x: lowest.position.x - 60 + k * (LAYOUT_COL_WIDTH + LAYOUT_H_GAP),
88
87
  y: lowest.position.y + nodeHeight(lowest) + LAYOUT_V_GAP
89
88
  });
90
89
  }
@@ -172,18 +171,12 @@ function autoLayout(allNodes, allEdges) {
172
171
  }
173
172
  return { nodeColumn: nodeColumn2, colX: colX2 };
174
173
  }
175
- const provisional = assignAllColumns(/* @__PURE__ */ new Map());
176
174
  const claimant = /* @__PURE__ */ new Map();
177
175
  for (const [child, ps] of structuralParents) {
178
176
  if (ps.length < 2) continue;
179
- const known = ps.filter((p) => provisional.nodeColumn.has(p));
180
- const continued = known.filter((p) => !exploreEdges.has(edgeKey(p, child)));
181
- const pool = continued.length ? continued : known;
182
- if (!pool.length) continue;
183
- const ranked = [...pool].sort(
184
- (a, b) => provisional.colX(provisional.nodeColumn.get(a)) - provisional.colX(provisional.nodeColumn.get(b)) || (a < b ? -1 : a > b ? 1 : 0)
185
- );
186
- claimant.set(child, ranked[Math.floor((ranked.length - 1) / 2)]);
177
+ const continued = ps.filter((p) => !exploreEdges.has(edgeKey(p, child)));
178
+ const pool = continued.length ? continued : ps;
179
+ claimant.set(child, pool[0]);
187
180
  }
188
181
  const { nodeColumn, colX } = assignAllColumns(claimant);
189
182
  const nodeHeightMap = /* @__PURE__ */ new Map();
@@ -943,7 +936,7 @@ var DshSessionCollector = class {
943
936
  return this.current;
944
937
  }
945
938
  feed(line) {
946
- if (line.type === "session" && line.id) {
939
+ if (line.type === "session" && line.id && !("timestamp" in line)) {
947
940
  if (!this.sessionId) this.sessionId = line.id;
948
941
  if (line.cwd && !this.cwd) this.cwd = line.cwd;
949
942
  return;
@@ -1069,6 +1062,176 @@ function buildGraphFromTurns3(turns, sessionId, cwd) {
1069
1062
  return { nodes: autoLayout(nodes, edges), edges };
1070
1063
  }
1071
1064
 
1065
+ // ../src/lib/adapters/pi-session.ts
1066
+ var textOf = (content) => {
1067
+ if (typeof content === "string") return content;
1068
+ if (!Array.isArray(content)) return "";
1069
+ return content.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
1070
+ };
1071
+ function renderCall2(name, args) {
1072
+ const a = args && typeof args === "object" ? args : {};
1073
+ const op = toolOpOf(name);
1074
+ const paths = typeof a.path === "string" && a.path && (op === "read" || op === "edit" || op === "write") ? [a.path] : [];
1075
+ let locator;
1076
+ if (op === "read" && (typeof a.offset === "number" || typeof a.limit === "number")) {
1077
+ const start = Math.max(1, typeof a.offset === "number" ? a.offset : 1);
1078
+ const end = typeof a.limit === "number" ? start + Math.max(0, a.limit) - 1 : start;
1079
+ locator = { lines: [start, Math.max(start, end)] };
1080
+ }
1081
+ if (op === "write" && typeof a.content === "string") {
1082
+ const c2 = clipText(`${a.path ?? ""}
1083
+
1084
+ ${a.content}`, ARTIFACT_CALL_LIMIT);
1085
+ return { ...c2, paths };
1086
+ }
1087
+ if (op === "edit") {
1088
+ const edits = Array.isArray(a.edits) ? a.edits : typeof a.newText === "string" ? [{ oldText: a.oldText, newText: a.newText }] : [];
1089
+ if (edits.length) {
1090
+ const body = edits.map((e) => `${a.path ?? ""}
1091
+ --- old
1092
+ ${e.oldText ?? ""}
1093
+ +++ new
1094
+ ${e.newText ?? ""}`).join("\n\n");
1095
+ const c2 = clipText(body, ARTIFACT_CALL_LIMIT);
1096
+ return { ...c2, paths };
1097
+ }
1098
+ }
1099
+ if (op === "run" && typeof a.command === "string") {
1100
+ const c2 = clipText(a.command, TOOL_CALL_LIMIT);
1101
+ return { ...c2, paths };
1102
+ }
1103
+ const c = clipText(JSON.stringify(args ?? {}), TOOL_CALL_LIMIT);
1104
+ return { ...c, paths, ...locator ? { locator } : {} };
1105
+ }
1106
+ var PiSessionCollector = class {
1107
+ turns = [];
1108
+ pending = /* @__PURE__ */ new Map();
1109
+ current = null;
1110
+ sessionId = null;
1111
+ cwd = null;
1112
+ firstQuestion = null;
1113
+ feedLine(raw) {
1114
+ const t = raw.trim();
1115
+ if (!t) return;
1116
+ let e;
1117
+ try {
1118
+ e = JSON.parse(t);
1119
+ } catch {
1120
+ return;
1121
+ }
1122
+ this.feed(e);
1123
+ }
1124
+ flush() {
1125
+ const c = this.current;
1126
+ if (c && (c.question || c.response || c.tools.length)) this.turns.push(c);
1127
+ this.current = null;
1128
+ }
1129
+ feed(e) {
1130
+ if (e.type === "session" && typeof e.id === "string") {
1131
+ if (!this.sessionId) this.sessionId = e.id;
1132
+ if (typeof e.cwd === "string" && !this.cwd) this.cwd = e.cwd;
1133
+ return;
1134
+ }
1135
+ if (e.type !== "message") {
1136
+ if (this.current && typeof e.id === "string") this.current.itemIds.push(e.id);
1137
+ return;
1138
+ }
1139
+ const m = e.message;
1140
+ if (!m) return;
1141
+ if (m.role === "user") {
1142
+ const text = textOf(m.content).trim();
1143
+ if (!text) return;
1144
+ this.flush();
1145
+ if (!this.firstQuestion) this.firstQuestion = text.split("\n")[0].slice(0, 80);
1146
+ const at = typeof e.timestamp === "string" ? e.timestamp : typeof m.timestamp === "number" ? new Date(m.timestamp).toISOString() : void 0;
1147
+ this.current = {
1148
+ question: text,
1149
+ response: "",
1150
+ itemIds: typeof e.id === "string" ? [e.id] : [],
1151
+ tools: [],
1152
+ ...typeof e.parentId === "string" ? { parentItemId: e.parentId } : {},
1153
+ ...at ? { at } : {}
1154
+ };
1155
+ return;
1156
+ }
1157
+ if (m.role === "assistant") {
1158
+ const cur = this.current ?? (this.current = { question: "", response: "", itemIds: [], tools: [] });
1159
+ if (typeof e.id === "string") cur.itemIds.push(e.id);
1160
+ const text = textOf(m.content).trim();
1161
+ if (text) cur.response = cur.response ? `${cur.response}
1162
+
1163
+ ${text}` : text;
1164
+ if (Array.isArray(m.content)) {
1165
+ for (const b of m.content) {
1166
+ if (b?.type !== "toolCall" || typeof b.id !== "string" || typeof b.name !== "string") continue;
1167
+ const r = renderCall2(b.name, b.arguments);
1168
+ this.pending.set(b.id, { name: b.name, call: r.text, truncated: r.truncated, op: toolOpOf(b.name), paths: r.paths, ...r.locator ? { locator: r.locator } : {} });
1169
+ }
1170
+ }
1171
+ return;
1172
+ }
1173
+ if (m.role === "toolResult") {
1174
+ const id = m.toolCallId;
1175
+ if (!id) return;
1176
+ const reg = this.pending.get(id);
1177
+ if (!reg) return;
1178
+ const cur = this.current ?? (this.current = { question: "", response: "", itemIds: [], tools: [] });
1179
+ if (typeof e.id === "string") cur.itemIds.push(e.id);
1180
+ const res = clipText(textOf(m.content), TOOL_RESULT_LIMIT);
1181
+ cur.tools.push({
1182
+ name: reg.name,
1183
+ call: reg.call,
1184
+ result: res.text,
1185
+ truncated: reg.truncated || res.truncated,
1186
+ op: reg.op,
1187
+ nativeCallId: id,
1188
+ ...reg.paths.length ? { paths: reg.paths } : {},
1189
+ ...reg.locator ? { locator: reg.locator } : {}
1190
+ });
1191
+ this.pending.delete(id);
1192
+ }
1193
+ }
1194
+ finish() {
1195
+ this.flush();
1196
+ if (!this.sessionId) return null;
1197
+ const title = this.firstQuestion ?? `session ${this.sessionId.slice(0, 8)}`;
1198
+ return { sessionId: this.sessionId, title, turns: dropSelfCommandTurns(this.turns), ...this.cwd ? { cwd: this.cwd } : {} };
1199
+ }
1200
+ toConversation() {
1201
+ const s = this.finish();
1202
+ if (!s || s.turns.length === 0) return null;
1203
+ return {
1204
+ title: s.title,
1205
+ messageCount: s.turns.length,
1206
+ source: "pi",
1207
+ sessionId: s.sessionId,
1208
+ build: () => buildGraphFromTurns4(s.turns, s.sessionId, s.cwd)
1209
+ };
1210
+ }
1211
+ };
1212
+ function buildGraphFromTurns4(turns, sessionId, cwd) {
1213
+ const origin = cwd ? { cwd } : {};
1214
+ const nodes = [];
1215
+ const edges = [];
1216
+ const byItem = /* @__PURE__ */ new Map();
1217
+ const link = (a, b) => edges.push({ id: generateId(), source: a.id, target: b.id, type: "smoothstep" });
1218
+ let prev = null;
1219
+ for (const turn of turns) {
1220
+ const node = makeNode(turn.question || "(tool-only turn)", turn.response, prev === null);
1221
+ node.data.importSource = { runner: "pi", sessionId, itemIds: turn.itemIds, ...origin };
1222
+ node.data.source = { question: node.data.question, response: node.data.response };
1223
+ node.data.attachments = toolAttachments(turn);
1224
+ seedPlaque(node);
1225
+ nodes.push(node);
1226
+ const parent = turn.parentItemId ? byItem.get(turn.parentItemId) : void 0;
1227
+ if (parent) link(parent, node);
1228
+ else if (prev) link(prev, node);
1229
+ for (const id of turn.itemIds) byItem.set(id, node);
1230
+ prev = node;
1231
+ }
1232
+ return { nodes: autoLayout(nodes, edges), edges };
1233
+ }
1234
+
1072
1235
  // ../src/lib/adapters/dsh-zstd.ts
1073
1236
  var ZSTD_MAGIC = 4247762216;
1074
1237
  function scanZstdFrames(buf) {
@@ -1462,6 +1625,24 @@ var THOUGHTDAG_MANIFEST = {
1462
1625
  contextSurface: OBS_PART2
1463
1626
  // exact for logged commits, upstream-only before
1464
1627
  };
1628
+ var PI_MANIFEST = {
1629
+ runner: "pi",
1630
+ schema: "pi-session/v3",
1631
+ turns: OBS_FULL2,
1632
+ // one user message opens every turn
1633
+ parenting: OBS_FULL2,
1634
+ // every entry names its parent; the session is a tree
1635
+ messages: OBS_PART2,
1636
+ // assistant text blocks fold per turn; thinking is dropped
1637
+ toolPairing: OBS_FULL2,
1638
+ // toolCall blocks ↔ toolResult messages pair by toolCallId
1639
+ artifactTouch: OBS_FULL2,
1640
+ // `path` on read/edit/write; shell commands stay opaque, as for every runner
1641
+ compaction: UNKNOWN,
1642
+ // not projected yet
1643
+ contextSurface: UNKNOWN
1644
+ // the log records messages, not requests
1645
+ };
1465
1646
  var DSH_MANIFEST = {
1466
1647
  runner: "dsh",
1467
1648
  schema: "dsh-events/2026-09",
@@ -1484,6 +1665,7 @@ var MANIFESTS = {
1484
1665
  "claude-code": CLAUDE_CODE_MANIFEST,
1485
1666
  codex: CODEX_MANIFEST,
1486
1667
  dsh: DSH_MANIFEST,
1668
+ pi: PI_MANIFEST,
1487
1669
  thoughtdag: THOUGHTDAG_MANIFEST
1488
1670
  };
1489
1671
 
@@ -1752,7 +1934,8 @@ var TEXT_LINES = path.join(HOME, "text-index.jsonl");
1752
1934
  var LEGACY_FILE = path.join(HOME, "why-index.json");
1753
1935
  var CONFIG_FILE = path.join(HOME, "config.json");
1754
1936
  var DSH_ROOT = path.join(process.env.DSH_HOME ?? path.join(os.homedir(), ".dsh"), "sessions");
1755
- var ROOTS = process.env.THOUGHTDAG_SESSION_ROOTS?.split(path.delimiter).filter(Boolean) ?? [path.join(os.homedir(), ".claude", "projects"), path.join(os.homedir(), ".codex", "sessions"), DSH_ROOT];
1937
+ var PI_ROOT = path.join(os.homedir(), ".pi", "agent", "sessions");
1938
+ var ROOTS = process.env.THOUGHTDAG_SESSION_ROOTS?.split(path.delimiter).filter(Boolean) ?? [path.join(os.homedir(), ".claude", "projects"), path.join(os.homedir(), ".codex", "sessions"), DSH_ROOT, PI_ROOT];
1756
1939
  var CANVAS_RECORDS = path.join(HOME, "canvases");
1757
1940
  async function canvasRoots() {
1758
1941
  const env = process.env.THOUGHTDAG_CANVAS_ROOTS?.split(path.delimiter).filter(Boolean);
@@ -1924,8 +2107,8 @@ async function eventsOf(file, sourceId) {
1924
2107
  }
1925
2108
  lines = createInterface({ input: createReadStream(file, { encoding: "utf8", highWaterMark: 1 << 20 }) });
1926
2109
  }
1927
- const runner = meta.type === "session_meta" ? "codex" : meta.type === "session" && typeof meta.id === "string" ? "dsh" : "claude-code";
1928
- const collector = runner === "codex" ? new CodexSessionCollector() : runner === "dsh" ? new DshSessionCollector() : new ClaudeSessionCollector();
2110
+ const runner = meta.type === "session_meta" ? "codex" : meta.type === "session" && typeof meta.id === "string" ? typeof meta.timestamp === "string" ? "pi" : "dsh" : "claude-code";
2111
+ const collector = runner === "codex" ? new CodexSessionCollector() : runner === "dsh" ? new DshSessionCollector() : runner === "pi" ? new PiSessionCollector() : new ClaudeSessionCollector();
1929
2112
  for await (const line of lines) collector.feedLine(line);
1930
2113
  const s = collector.finish();
1931
2114
  if (!s) return null;
@@ -2528,7 +2711,7 @@ ${turn.response.trim() || "(none)"}
2528
2711
  var CLI_VERSION = "0.1.0";
2529
2712
  var MCP_TOOLS = [
2530
2713
  { name: "why_check", description: "Cheap first question before editing a file: does this artifact have any history in local agent sessions? One line; history true/false.", inputSchema: { type: "object", properties: { path: { type: "string", description: "file path (absolute or relative to cwd), URL, or arxiv:<id>" } }, required: ["path"] } },
2531
- { name: "why_file", description: "The turns across local Claude Code, Codex, DeepSeek Harness and ThoughtDAG sessions that touched a file, URL or paper: when, what changed (\u0394, observed), what was asked, what the answer said about it (\u2248, a candidate explanation, not a verified reason). Each hit carries a deep link.", inputSchema: { type: "object", properties: { path: { type: "string" }, include_read: { type: "boolean", description: "also list turns that only read it (default false)" }, limit: { type: "number", description: "max hits (default 10)" } }, required: ["path"] } },
2714
+ { name: "why_file", description: "The turns across local Claude Code, Codex, DeepSeek Harness, Pi and ThoughtDAG sessions that touched a file, URL or paper: when, what changed (\u0394, observed), what was asked, what the answer said about it (\u2248, a candidate explanation, not a verified reason). Each hit carries a deep link.", inputSchema: { type: "object", properties: { path: { type: "string" }, include_read: { type: "boolean", description: "also list turns that only read it (default false)" }, limit: { type: "number", description: "max hits (default 10)" } }, required: ["path"] } },
2532
2715
  { name: "find", description: "Where these exact words were asked (Q), answered (A) or attached (M) across local sessions and canvases. Exact, case-insensitive match; every hit is a verbatim snippet with a pointer.", inputSchema: { type: "object", properties: { phrase: { type: "string" }, in: { type: "string", enum: ["q", "a", "m", "all"] }, limit: { type: "number" } }, required: ["phrase"] } },
2533
2716
  { name: "recall_turn", description: "One turn in full \u2014 the question, the answer, the tool calls with their diffs \u2014 by session id (or prefix) and turn number as shown by why_file.", inputSchema: { type: "object", properties: { session: { type: "string" }, turn: { type: "number" } }, required: ["session", "turn"] } }
2534
2717
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-thoughtdag",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "ThoughtDAG for DeepSeek Harness: turn the current DSH session into an editable thought graph on an infinite canvas.",
5
5
  "author": "Xia Chen",
6
6
  "repository": {