dsh-thoughtdag 0.5.4 → 0.5.5
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.
- package/dist-app/assets/{canonical-lpYt01q8.js → canonical-DyO4cfs8.js} +2 -2
- package/dist-app/assets/{canvas-record-C954zMyU.js → canvas-record-B106EgJV.js} +1 -1
- package/dist-app/assets/claude-code-session-COqNVArs.js +8 -0
- package/dist-app/assets/codex-session-CVhXEocv.js +12 -0
- package/dist-app/assets/{dsh-session-ClUmglrW.js → dsh-session-y6pdDHlg.js} +3 -3
- package/dist-app/assets/{experiment-loop-C9mVK7yX.js → experiment-loop-Bj2wGvJp.js} +1 -1
- package/dist-app/assets/{http-bridge-BL1SuJsm.js → http-bridge-TYkQm1R3.js} +1 -1
- package/dist-app/assets/{index-C3YBa-cP.js → index-BZP0zxsA.js} +3 -3
- package/dist-app/assets/{index-CHV1get9.js → index-BndAg25p.js} +1 -1
- package/dist-app/assets/index-CqebPxzE.css +1 -0
- package/dist-app/assets/{index-DRuVr_fe.js → index-D2RofBbL.js} +1 -1
- package/dist-app/assets/index-DR0C_9n-.js +912 -0
- package/dist-app/assets/pi-session-fSCIsceL.js +14 -0
- package/dist-app/assets/{sensitive-scan-ClJE_sMR.js → sensitive-scan-_-YPfT-P.js} +1 -1
- package/dist-app/assets/{session-handoff-BPbFjcS2.js → session-handoff-rVZFB7ll.js} +2 -2
- package/dist-app/assets/shared-svBDp9_R.js +10 -0
- package/dist-app/assets/{turndown-plugin-gfm.cjs-B51wGC07.js → turndown-plugin-gfm.cjs-DBiLx1SX.js} +1 -1
- package/dist-app/assets/{update-check-4z5bqXf5.js → update-check-n2hvSdhA.js} +1 -1
- package/dist-app/index.html +2 -2
- package/lib/runtime/agents/README.md +1 -1
- package/lib/runtime/agents/claude.cjs +13 -14
- package/lib/runtime/agents/codex.cjs +13 -14
- package/lib/runtime/agents/pi.cjs +14 -27
- package/lib/runtime/agents/where.cjs +99 -0
- package/lib/why.mjs +100 -6
- package/package.json +1 -1
- package/dist-app/assets/claude-code-session-Dvg2z_fp.js +0 -7
- package/dist-app/assets/codex-session-C8Wr73Y-.js +0 -12
- package/dist-app/assets/index-C8yf-QJz.js +0 -904
- package/dist-app/assets/index-Lvtth6pP.css +0 -1
- package/dist-app/assets/pi-session-BNZYRZki.js +0 -14
- package/dist-app/assets/shared-B7orUlMM.js +0 -10
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import{q as x,B as A,n as T}from"./index-DR0C_9n-.js";import{t as w,a as g,f as d,d as b,T as S,A as h,i as m,g as k,s as C,h as L}from"./shared-svBDp9_R.js";const f=i=>typeof i=="string"?i:Array.isArray(i)?i.filter(t=>t&&t.type==="text"&&typeof t.text=="string").map(t=>t.text).join(`
|
|
2
|
+
`):"";function $(i,t){const s=t&&typeof t=="object"?t:{},e=g(i),r=typeof s.path=="string"&&s.path&&(e==="read"||e==="edit"||e==="write")?[s.path]:[];let n;if(e==="read"&&(typeof s.offset=="number"||typeof s.limit=="number")){const o=Math.max(1,typeof s.offset=="number"?s.offset:1),u=typeof s.limit=="number"?o+Math.max(0,s.limit)-1:o;n={lines:[o,Math.max(o,u)]}}if(e==="write"&&typeof s.content=="string")return{...d(`${s.path??""}
|
|
3
|
+
|
|
4
|
+
${s.content}`,h),paths:r};if(e==="edit"){const o=Array.isArray(s.edits)?s.edits:typeof s.newText=="string"?[{oldText:s.oldText,newText:s.newText}]:[];if(o.length){const u=o.map(a=>`${s.path??""}
|
|
5
|
+
--- old
|
|
6
|
+
${a.oldText??""}
|
|
7
|
+
+++ new
|
|
8
|
+
${a.newText??""}`).join(`
|
|
9
|
+
|
|
10
|
+
`);return{...d(u,h),paths:r}}}return e==="run"&&typeof s.command=="string"?{...d(s.command,m),paths:r}:{...d(JSON.stringify(t??{}),m),paths:r,...n?{locator:n}:{}}}const O=i=>{if(!Array.isArray(i))return"";for(let t=i.length-1;t>=0;t--){const s=i[t];if(s?.role==="assistant"){const e=f(s.content).trim();if(e)return e}}return""};function q(i,t,s,e){const r=s;if(Array.isArray(r?.results)&&r.results.length)return r.results.map((u,l)=>({agent:String(u.agent??"agent"),task:String(u.task??""),output:O(u.messages)||String(u.stderr??"").trim(),itemIds:[`${e}#${l}`]}));const n=i&&typeof i=="object"?i:{},c=Array.isArray(n.tasks)&&n.tasks.length?n.tasks:Array.isArray(n.chain)&&n.chain.length?n.chain:n.agent||n.task?[{agent:n.agent,task:n.task}]:[],o=f(t).trim();return c.map((u,l)=>({agent:String(u.agent??"agent"),task:String(u.task??""),output:c.length===1?o:"",itemIds:[`${e}#${l}`]}))}class M{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 r=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}:{},...r?{at:r}:{}};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 r=f(s.content).trim();if(r&&(e.response=e.response?`${e.response}
|
|
12
|
+
|
|
13
|
+
${r}`:r),Array.isArray(s.content))for(const n of s.content){if(n?.type!=="toolCall"||typeof n.id!="string"||typeof n.name!="string")continue;const c=$(n.name,n.arguments);this.pending.set(n.id,{name:n.name,call:c.text,truncated:c.truncated,op:g(n.name),paths:c.paths,...c.locator?{locator:c.locator}:{},...n.name==="subagent"?{args:n.arguments}:{}})}return}if(s.role==="toolResult"){const e=s.toolCallId;if(!e)return;const r=this.pending.get(e);if(!r)return;const n=this.current??(this.current={question:"",response:"",itemIds:[],tools:[]});typeof t.id=="string"&&n.itemIds.push(t.id);const c=d(f(s.content),S);if(n.tools.push({name:r.name,call:r.call,result:c.text,truncated:r.truncated||c.truncated,op:r.op,nativeCallId:e,...r.paths.length?{paths:r.paths}:{},...r.locator?{locator:r.locator}:{}}),r.name==="subagent"){const o=q(r.args,s.content,s.details,e);o.length&&(n.subagents=[...n.subagents??[],...o])}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:b(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(i){const t=new M;for(const s of i.split(`
|
|
14
|
+
`))t.feedLine(s);return t}function v(i,t,s){const e=s?{cwd:s}:{},r=[],n=[],c=new Map,o=(l,a)=>n.push({id:T(),source:l.id,target:a.id,type:"smoothstep"});let u=null;for(const l of i){const a=x(l.question||"(tool-only turn)",l.response,u===null);a.data.importSource={runner:"pi",sessionId:t,itemIds:l.itemIds,...e},a.data.source={question:a.data.question,response:a.data.response},a.data.attachments=k(l),C(a),r.push(a);const p=l.parentItemId?c.get(l.parentItemId):void 0;p?o(p,a):u&&o(u,a),L(a,l,r,n,"pi",t,s);for(const I of l.itemIds)c.set(I,a);u=a}return{nodes:A(r,n),edges:n}}function B(i,t){const s=y(i).finish();return s?w(s.turns,s.sessionId,"pi",t,s.cwd):null}function Q(i){return y(i).toConversation()}export{M as PiSessionCollector,v as buildGraphFromTurns,B as piSessionAsBranch,Q as piSessionConversation};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{f as c,a,v as g}from"./index-
|
|
1
|
+
import{f as c,a,v as g}from"./index-DR0C_9n-.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-
|
|
2
|
-
import{t as n,a as e,_ as p,f as a,u as h}from"./index-
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/canonical-DyO4cfs8.js","assets/index-DR0C_9n-.js","assets/index-CqebPxzE.css"])))=>i.map(i=>d[i]);
|
|
2
|
+
import{t as n,a as e,_ as p,f as a,u as h}from"./index-DR0C_9n-.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-DyO4cfs8.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};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import{q as d,n as p,B as _,C as y}from"./index-DR0C_9n-.js";const I=[[/^(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 $(e){return I.find(([t])=>t.test(e))?.[1]??"other"}const T=800,k=32e3,O=4e3,x=(e,t)=>e.length>t?{text:`${e.slice(0,t)}
|
|
2
|
+
…[truncated, ${e.length} chars total]`,truncated:!0}:{text:e,truncated:!1},b=["<command-name>/thoughtdag</command-name>","[[thoughtdag:command]]"];function M(e){return e.filter(t=>!b.some(s=>t.question.includes(s)))}function q(e){e.width=460}function m(e){const t=y(e.data.response,90);t&&(e.data.summaries=[t])}function h(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
|
+
[result]
|
|
4
|
+
${t.result}`,...t.paths?.length?{paths:t.paths}:{},...t.op?{op:t.op}:{}}))}function w(e,t){const s=new Set,i=(n,a)=>{s.add(a);const c=a.turns.flatMap(r=>r.tools);c.length&&(n.tools=c),n.output.trim()||(n.output=a.turns.map(r=>r.response).filter(r=>r.trim()).at(-1)??""),n.itemIds.includes(a.sessionId)||n.itemIds.push(a.sessionId),!n.at&&a.at&&(n.at=a.at)};for(const n of e)for(const a of n.subagents??[]){const c=a.match?.trim();if(!c)continue;const r=t.find(o=>!s.has(o)&&o.firstQuestion.trim()===c);r&&i(a,r)}for(const n of t){if(s.has(n)||!n.at)continue;const c=e.filter(o=>o.at&&o.at<=n.at).at(-1)??e.at(-1);if(!c)continue;const r={agent:n.runner,task:n.firstQuestion,output:"",itemIds:[n.sessionId],at:n.at};i(r,n),(c.subagents??=[]).push(r)}}function S(e,t,s,i,n,a,c){const r=[];for(const o of t.subagents??[]){const f=d(`[${o.agent}] ${o.task}`.trim(),o.output,!1);f.data.isBranch=!0,f.data.importSource={runner:n,sessionId:a,itemIds:o.itemIds,...c?{cwd:c}:{},subagent:{agent:o.agent,of:t.itemIds[0]??""}},f.data.source={question:f.data.question,response:f.data.response},o.tools?.length&&(f.data.attachments=h({question:o.task,response:o.output,itemIds:o.itemIds,tools:o.tools})),m(f),s.push(f),r.push(f),i.push({id:p(),source:e.id,target:f.id,type:"smoothstep",data:{isBranchFromSelection:!0,branchYRatio:.5}})}return r}function A(e,t,s,i,n){if(e.length===0)return null;const a=[],c=[];let r={id:i.id};for(const u of e){const l=d(u.question||"(tool-only turn)",u.response,!1);l.data.importSource={runner:s,sessionId:t,itemIds:u.itemIds,...n?{cwd:n}:{}},l.data.source={question:l.data.question,response:l.data.response},l.data.attachments=h(u),m(l),a.length===0&&(l.data.isBranch=!0),a.push(l),c.push({id:p(),source:r.id,target:l.id,type:"smoothstep"}),S(l,u,a,c,s,t,n),r=l}const o=_(a,c.filter(u=>u.source!==i.id)),f=Math.min(...o.map(u=>u.position.x)),g=Math.min(...o.map(u=>u.position.y));for(const u of o)u.position={x:u.position.x-f+i.x+560,y:u.position.y-g+i.y};return{nodes:o,edges:c,turnCount:e.length}}function B(e){const t=e??{},s={};typeof t.url=="string"&&/^https?:\/\//.test(t.url)&&(s.url=t.url);const i={};if(typeof t.pages=="string"&&t.pages.trim()&&(i.pages=t.pages.trim()),typeof t.offset=="number"||typeof t.limit=="number"){const n=Math.max(1,typeof t.offset=="number"?t.offset:1),a=typeof t.limit=="number"?n+Math.max(0,t.limit)-1:n;i.lines=[n,Math.max(n,a)]}return Object.keys(i).length&&(s.locator=i),s}function C(e){const t=e??{},s=t.file_path??t.notebook_path;return typeof s=="string"&&s?[s]:[]}function R(e,t){const s=t??{},i=$(e);return i==="write"&&typeof s.content=="string"?`${s.file_path??""}
|
|
5
|
+
|
|
6
|
+
${s.content}`:i==="edit"&&typeof s.new_string=="string"?`${s.file_path??s.notebook_path??""}${s.replace_all?" (replace all)":""}
|
|
7
|
+
--- old
|
|
8
|
+
${s.old_string??""}
|
|
9
|
+
+++ new
|
|
10
|
+
${s.new_string}`:JSON.stringify(t??{})}export{k as A,O as T,$ as a,B as b,C as c,M as d,w as e,x as f,h as g,S as h,T as i,q as m,R as r,m as s,A as t};
|
package/dist-app/assets/{turndown-plugin-gfm.cjs-B51wGC07.js → turndown-plugin-gfm.cjs-DBiLx1SX.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{A as W}from"./index-
|
|
1
|
+
import{A as W}from"./index-DR0C_9n-.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-
|
|
1
|
+
import{t as u,a as n,f}from"./index-DR0C_9n-.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};
|
package/dist-app/index.html
CHANGED
|
@@ -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-
|
|
26
|
-
<link rel="stylesheet" crossorigin href="/thoughtdag/assets/index-
|
|
25
|
+
<script type="module" crossorigin src="/thoughtdag/assets/index-DR0C_9n-.js"></script>
|
|
26
|
+
<link rel="stylesheet" crossorigin href="/thoughtdag/assets/index-CqebPxzE.css">
|
|
27
27
|
</head>
|
|
28
28
|
<body>
|
|
29
29
|
<div id="root"></div>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
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, 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
|
|
6
|
+
- `models()` → `{ installed, models: [{ provider, id, name, reasoning, vision, efforts, defaultEffort }], default }`; when the CLI is nowhere, `{ installed: false, searched: [dirs] }` names every directory the lookup walked (see `where.cjs`: the process PATH, then the login shell's PATH, then the usual homes and Node version managers), and the next ask looks again — `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
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
|
|
|
@@ -16,19 +16,13 @@ const { randomUUID } = require('node:crypto');
|
|
|
16
16
|
const { snapshotDir, diffSnapshots } = require('./fs-diff.cjs');
|
|
17
17
|
|
|
18
18
|
const fsp = fs.promises;
|
|
19
|
-
const
|
|
19
|
+
const { locate, childEnv: envWith } = require('./where.cjs');
|
|
20
|
+
const EXTRA_HOMES = [path.join(os.homedir(), '.claude', 'local')]; // the CLI's own install location, on top of the usual ones
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
for (const d of dirs) for (const n of names) { const p = path.join(d, n); if (await executable(p)) return p; }
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
function childEnv() {
|
|
29
|
-
const seen = new Set((process.env.PATH || '').split(path.delimiter).filter(Boolean));
|
|
30
|
-
return { ...process.env, PATH: [...seen, ...CANDIDATE_DIRS.filter((d) => !seen.has(d))].join(path.delimiter) };
|
|
31
|
-
}
|
|
22
|
+
/** The claude binary and the directories walked to find it (see where.cjs). */
|
|
23
|
+
let located = null;
|
|
24
|
+
const findClaude = () => (located ??= locate('claude', EXTRA_HOMES));
|
|
25
|
+
const childEnv = () => envWith(EXTRA_HOMES);
|
|
32
26
|
|
|
33
27
|
/** Claude Code has no model list; its aliases resolve to the newest model
|
|
34
28
|
* of each family inside the CLI, so they never go stale. The CLI's own
|
|
@@ -114,14 +108,19 @@ function createClaudeRuntime({ log } = {}) {
|
|
|
114
108
|
const say = log || (() => {});
|
|
115
109
|
const runs = new Map(); // runId → state
|
|
116
110
|
let binPromise = null;
|
|
117
|
-
const bin = () => {
|
|
111
|
+
const bin = () => {
|
|
112
|
+
if (!binPromise) binPromise = findClaude().then((r) => { if (!r.path) { located = null; binPromise = null; } return r.path; });
|
|
113
|
+
return binPromise;
|
|
114
|
+
};
|
|
115
|
+
/** Where the lookup walked, for the picker's "not found" note. */
|
|
116
|
+
const searched = async () => (await findClaude()).searched;
|
|
118
117
|
|
|
119
118
|
return {
|
|
120
119
|
available: () => bin(),
|
|
121
120
|
|
|
122
121
|
async models() {
|
|
123
122
|
const b = await bin();
|
|
124
|
-
if (!b) return { installed: false, models: [], default: null };
|
|
123
|
+
if (!b) return { installed: false, models: [], default: null, searched: await searched() };
|
|
125
124
|
const [efforts, defaultEffort] = await Promise.all([effortsOf(b), configuredEffort()]);
|
|
126
125
|
return { installed: true, models: MODELS.map((m) => ({ ...m, efforts, defaultEffort })), default: 'claude/claude-code/opus' };
|
|
127
126
|
},
|
|
@@ -17,19 +17,13 @@ const { snapshotDir, diffSnapshots } = require('./fs-diff.cjs');
|
|
|
17
17
|
|
|
18
18
|
const fsp = fs.promises;
|
|
19
19
|
const RESPONSE_MS = 30 * 1000;
|
|
20
|
-
const
|
|
20
|
+
const { locate, childEnv: envWith } = require('./where.cjs');
|
|
21
|
+
const EXTRA_HOMES = [path.join(os.homedir(), '.codex', 'bin')];
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
for (const d of dirs) for (const n of names) { const p = path.join(d, n); if (await executable(p)) return p; }
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
function childEnv() {
|
|
30
|
-
const seen = new Set((process.env.PATH || '').split(path.delimiter).filter(Boolean));
|
|
31
|
-
return { ...process.env, PATH: [...seen, ...CANDIDATE_DIRS.filter((d) => !seen.has(d))].join(path.delimiter) };
|
|
32
|
-
}
|
|
23
|
+
/** The codex binary and the directories walked to find it (see where.cjs). */
|
|
24
|
+
let located = null;
|
|
25
|
+
const findCodex = () => (located ??= locate('codex', EXTRA_HOMES));
|
|
26
|
+
const childEnv = () => envWith(EXTRA_HOMES);
|
|
33
27
|
|
|
34
28
|
/** The guard file the canvas writes per working directory, as Codex policy. */
|
|
35
29
|
async function policyFor(cwd) {
|
|
@@ -58,7 +52,12 @@ function createCodexRuntime({ log } = {}) {
|
|
|
58
52
|
const byThread = new Map(); // threadId → runId
|
|
59
53
|
const serverAsks = new Map(); // question id → { rpcId, respond(answer) }
|
|
60
54
|
let binPromise = null;
|
|
61
|
-
const bin = () => {
|
|
55
|
+
const bin = () => {
|
|
56
|
+
if (!binPromise) binPromise = findCodex().then((r) => { if (!r.path) { located = null; binPromise = null; } return r.path; });
|
|
57
|
+
return binPromise;
|
|
58
|
+
};
|
|
59
|
+
/** Where the lookup walked, for the picker's "not found" note. */
|
|
60
|
+
const searched = async () => (await findCodex()).searched;
|
|
62
61
|
|
|
63
62
|
const write = (obj) => { try { proc.stdin.write(JSON.stringify(obj) + '\n'); return true; } catch { return false; } };
|
|
64
63
|
const send = (method, params, timeoutMs = RESPONSE_MS) => new Promise((resolve, reject) => {
|
|
@@ -283,7 +282,7 @@ function createCodexRuntime({ log } = {}) {
|
|
|
283
282
|
|
|
284
283
|
async models() {
|
|
285
284
|
const b = await bin();
|
|
286
|
-
if (!b) return { installed: false, models: [], default: null };
|
|
285
|
+
if (!b) return { installed: false, models: [], default: null, searched: await searched() };
|
|
287
286
|
await ensureServer();
|
|
288
287
|
const r = await send('model/list', {});
|
|
289
288
|
noteEfforts(r);
|
|
@@ -23,32 +23,14 @@ const fsp = fs.promises;
|
|
|
23
23
|
const GUARD = path.join(__dirname, 'pi-guard.mjs');
|
|
24
24
|
const IDLE_MS = Number(process.env.TD_AGENT_IDLE_MS) || 10 * 60 * 1000; // a Pi process (session or catalog) retires after this long without a listener
|
|
25
25
|
const RESPONSE_MS = 30 * 1000;
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
path.join(os.homedir(), '.bun', 'bin'), path.join(os.homedir(), '.npm-global', 'bin'),
|
|
29
|
-
path.join(os.homedir(), '.local', 'bin'), path.join(os.homedir(), '.pi', 'bin'),
|
|
30
|
-
];
|
|
26
|
+
const { locate, childEnv: envWith } = require('./where.cjs');
|
|
27
|
+
const EXTRA_HOMES = [path.join(os.homedir(), '.pi', 'bin')]; // pi's own home, on top of the usual ones (see where.cjs)
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* (a Finder launch carries almost no PATH). */
|
|
38
|
-
async function findPi() {
|
|
39
|
-
const names = process.platform === 'win32' ? ['pi.cmd', 'pi.exe', 'pi'] : ['pi'];
|
|
40
|
-
const dirs = [...(process.env.PATH || '').split(path.delimiter), ...CANDIDATE_DIRS].filter(Boolean);
|
|
41
|
-
for (const d of dirs) for (const n of names) { const p = path.join(d, n); if (await executable(p)) return p; }
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** PATH for the child: the shell's plus the usual homes, so Pi's own bash
|
|
46
|
-
* tool finds git, node and friends even under a Finder launch. */
|
|
47
|
-
function childEnv() {
|
|
48
|
-
const seen = new Set((process.env.PATH || '').split(path.delimiter).filter(Boolean));
|
|
49
|
-
const extra = CANDIDATE_DIRS.filter((d) => !seen.has(d));
|
|
50
|
-
return { ...process.env, PATH: [...seen, ...extra].join(path.delimiter) };
|
|
51
|
-
}
|
|
29
|
+
/** The pi binary and the directories walked to find it (PATH, the login shell's PATH, the usual homes). */
|
|
30
|
+
let located = null;
|
|
31
|
+
const findPi = () => (located ??= locate('pi', EXTRA_HOMES));
|
|
32
|
+
/** PATH for the child: the shell's plus the usual homes, so Pi's own bash tool finds git, node and friends even under a Finder launch. */
|
|
33
|
+
const childEnv = () => envWith(EXTRA_HOMES);
|
|
52
34
|
|
|
53
35
|
class PiProcess {
|
|
54
36
|
constructor(bin, cwd, opts) {
|
|
@@ -191,7 +173,12 @@ function createPiRuntime({ log } = {}) {
|
|
|
191
173
|
const runs = new Map(); // runId → { proc, abort() }
|
|
192
174
|
let binPromise = null;
|
|
193
175
|
|
|
194
|
-
const bin = () => {
|
|
176
|
+
const bin = () => {
|
|
177
|
+
if (!binPromise) binPromise = findPi().then((r) => { if (!r.path) { located = null; binPromise = null; } return r.path; });
|
|
178
|
+
return binPromise;
|
|
179
|
+
};
|
|
180
|
+
/** Where the lookup walked, for the picker's "not found" note. */
|
|
181
|
+
const searched = async () => (await findPi()).searched;
|
|
195
182
|
// Pi's thinking levels in its own words, from its own `--help`
|
|
196
183
|
// (`--thinking <level> Set thinking level: off, minimal, …`); read once
|
|
197
184
|
let levelsPromise = null;
|
|
@@ -228,7 +215,7 @@ function createPiRuntime({ log } = {}) {
|
|
|
228
215
|
* sessionless process in the home directory. */
|
|
229
216
|
async models() {
|
|
230
217
|
const b = await bin();
|
|
231
|
-
if (!b) return { installed: false, models: [], default: null };
|
|
218
|
+
if (!b) return { installed: false, models: [], default: null, searched: await searched() };
|
|
232
219
|
const key = '\0catalog';
|
|
233
220
|
let p = procs.get(key);
|
|
234
221
|
if (!p || p.dead) { p = new PiProcess(b, os.homedir(), { log: say, noSession: true, onExit: (d) => { if (procs.get(key) === d) procs.delete(key); } }); procs.set(key, p); }
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Where a CLI lives when the process that launched us had no PATH to speak of.
|
|
2
|
+
//
|
|
3
|
+
// An app opened from the Finder, the Dock or a Start menu inherits a bare PATH
|
|
4
|
+
// (/usr/bin:/bin on macOS), while the person's CLIs sit wherever their Node
|
|
5
|
+
// version manager put them (nvm, fnm, volta, pnpm, asdf, mise …). A lookup
|
|
6
|
+
// therefore walks three lists in order: the process PATH, the PATH a login
|
|
7
|
+
// shell would give a terminal (asked once, with a timeout), and the usual
|
|
8
|
+
// homes, version managers included. Every lookup returns what it searched,
|
|
9
|
+
// so the picker can say where it looked when a runtime is missing.
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const { execFile } = require('node:child_process');
|
|
13
|
+
const fs = require('node:fs');
|
|
14
|
+
const os = require('node:os');
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
|
|
17
|
+
const fsp = fs.promises;
|
|
18
|
+
const home = os.homedir();
|
|
19
|
+
const isWin = process.platform === 'win32';
|
|
20
|
+
const LOGIN_SHELL_MS = 4000;
|
|
21
|
+
|
|
22
|
+
/** The login shell's PATH (zsh, bash and fish read their rc files there). Asked once; empty on Windows or when the shell is silent. */
|
|
23
|
+
let loginPathPromise = null;
|
|
24
|
+
function loginPath() {
|
|
25
|
+
if (loginPathPromise) return loginPathPromise;
|
|
26
|
+
loginPathPromise = new Promise((resolve) => {
|
|
27
|
+
if (isWin) return resolve([]);
|
|
28
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
29
|
+
const args = /fish$/.test(shell) ? ['-l', '-c', 'string join : $PATH'] : ['-ilc', 'printf "%s" "$PATH"'];
|
|
30
|
+
execFile(shell, args, { timeout: LOGIN_SHELL_MS, env: { ...process.env, TERM: 'dumb' }, maxBuffer: 1 << 16 }, (err, stdout) => {
|
|
31
|
+
if (err) return resolve([]);
|
|
32
|
+
// rc files may print greetings; PATH itself has no newline, so the last line is ours
|
|
33
|
+
const line = String(stdout || '').split('\n').map((s) => s.trim()).filter(Boolean).pop() || '';
|
|
34
|
+
resolve(line.split(path.delimiter).map((d) => d.trim()).filter(Boolean));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
return loginPathPromise;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const numeric = (v) => String(v).replace(/^v/, '').split('.').map((n) => parseInt(n, 10) || 0);
|
|
41
|
+
const newestFirst = (a, b) => { const x = numeric(a), y = numeric(b); for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return (y[i] || 0) - (x[i] || 0); return 0; };
|
|
42
|
+
const versionsUnder = (dir, ...sub) => { try { return fs.readdirSync(dir).sort(newestFirst).map((v) => path.join(dir, v, ...sub)); } catch { return []; } };
|
|
43
|
+
|
|
44
|
+
/** Where package managers and Node version managers put global binaries. Newest Node first, so a lookup lands on the version the person uses. */
|
|
45
|
+
function managerDirs() {
|
|
46
|
+
const dirs = [];
|
|
47
|
+
dirs.push(...versionsUnder(path.join(process.env.NVM_DIR || path.join(home, '.nvm'), 'versions', 'node'), 'bin'));
|
|
48
|
+
for (const base of [process.env.FNM_DIR, path.join(home, '.fnm'), path.join(home, '.local', 'share', 'fnm'), path.join(home, 'Library', 'Application Support', 'fnm')].filter(Boolean)) {
|
|
49
|
+
dirs.push(path.join(base, 'aliases', 'default', 'bin'));
|
|
50
|
+
dirs.push(...versionsUnder(path.join(base, 'node-versions'), 'installation', 'bin'));
|
|
51
|
+
}
|
|
52
|
+
dirs.push(
|
|
53
|
+
path.join(home, '.volta', 'bin'), path.join(process.env.N_PREFIX || path.join(home, 'n'), 'bin'), path.join(home, '.nodenv', 'shims'),
|
|
54
|
+
path.join(home, '.asdf', 'shims'), path.join(home, '.local', 'share', 'mise', 'shims'), path.join(home, '.proto', 'shims'),
|
|
55
|
+
path.join(home, 'Library', 'pnpm'), path.join(home, '.local', 'share', 'pnpm'), path.join(home, '.yarn', 'bin'), path.join(home, '.config', 'yarn', 'global', 'node_modules', '.bin'),
|
|
56
|
+
path.join(home, '.deno', 'bin'), path.join(home, '.cargo', 'bin'), path.join(home, 'bin'),
|
|
57
|
+
);
|
|
58
|
+
if (isWin) {
|
|
59
|
+
const { APPDATA, LOCALAPPDATA, ProgramFiles } = process.env;
|
|
60
|
+
if (APPDATA) dirs.push(path.join(APPDATA, 'npm'));
|
|
61
|
+
if (LOCALAPPDATA) dirs.push(path.join(LOCALAPPDATA, 'pnpm'), path.join(LOCALAPPDATA, 'Volta', 'bin'), path.join(LOCALAPPDATA, 'Programs', 'nvm'));
|
|
62
|
+
if (ProgramFiles) dirs.push(path.join(ProgramFiles, 'nodejs'));
|
|
63
|
+
}
|
|
64
|
+
return dirs;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const COMMON_HOMES = ['/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', path.join(home, '.bun', 'bin'), path.join(home, '.npm-global', 'bin'), path.join(home, '.local', 'bin')];
|
|
68
|
+
|
|
69
|
+
/** Every directory a lookup walks, in order and without repeats. Sync when the login shell has not answered yet. */
|
|
70
|
+
function dirsSync(extraHomes, login) {
|
|
71
|
+
const seen = new Set(); const out = [];
|
|
72
|
+
const add = (d) => { if (d && !seen.has(d)) { seen.add(d); out.push(d); } };
|
|
73
|
+
(process.env.PATH || '').split(path.delimiter).forEach(add);
|
|
74
|
+
login.forEach(add);
|
|
75
|
+
[...COMMON_HOMES, ...extraHomes, ...managerDirs()].forEach(add);
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
let loginSeen = [];
|
|
79
|
+
async function searchDirs(extraHomes = []) {
|
|
80
|
+
loginSeen = await loginPath();
|
|
81
|
+
return dirsSync(extraHomes, loginSeen);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function executable(p) { try { await fsp.access(p, fs.constants.X_OK); return true; } catch { return false; } }
|
|
85
|
+
|
|
86
|
+
/** The first executable called `name` (Windows tries .cmd and .exe too): `{ path, searched }`, path null when nowhere. */
|
|
87
|
+
async function locate(name, extraHomes = []) {
|
|
88
|
+
const names = isWin ? [`${name}.cmd`, `${name}.exe`, name] : [name];
|
|
89
|
+
const searched = await searchDirs(extraHomes);
|
|
90
|
+
for (const d of searched) for (const n of names) { const p = path.join(d, n); if (await executable(p)) return { path: p, searched }; }
|
|
91
|
+
return { path: null, searched };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The process environment with PATH widened the same way, so a CLI's own tools (git, node, bash) resolve under a Finder launch too. */
|
|
95
|
+
function childEnv(extraHomes = []) {
|
|
96
|
+
return { ...process.env, PATH: dirsSync(extraHomes, loginSeen).join(path.delimiter) };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { locate, childEnv, searchDirs, loginPath };
|
package/lib/why.mjs
CHANGED
|
@@ -61,8 +61,8 @@ function frameMembers(frame, nodes) {
|
|
|
61
61
|
// ../src/lib/constants.ts
|
|
62
62
|
var API_BASE = define_import_meta_env_default.VITE_API_BASE ?? (define_import_meta_env_default.DEV ? "http://localhost:3001" : "");
|
|
63
63
|
var LAYOUT_COL_WIDTH = 540;
|
|
64
|
-
var LAYOUT_H_GAP =
|
|
65
|
-
var LAYOUT_V_GAP =
|
|
64
|
+
var LAYOUT_H_GAP = 64;
|
|
65
|
+
var LAYOUT_V_GAP = 96;
|
|
66
66
|
var COLLAPSED_LAYOUT_HEIGHT = 240;
|
|
67
67
|
|
|
68
68
|
// ../src/lib/layout.ts
|
|
@@ -548,6 +548,47 @@ ${tool.result}`,
|
|
|
548
548
|
...tool.op ? { op: tool.op } : {}
|
|
549
549
|
}));
|
|
550
550
|
}
|
|
551
|
+
function adoptSubagentSessions(turns, subs) {
|
|
552
|
+
const used = /* @__PURE__ */ new Set();
|
|
553
|
+
const settle = (run, sub) => {
|
|
554
|
+
used.add(sub);
|
|
555
|
+
const tools = sub.turns.flatMap((t) => t.tools);
|
|
556
|
+
if (tools.length) run.tools = tools;
|
|
557
|
+
if (!run.output.trim()) run.output = sub.turns.map((t) => t.response).filter((r) => r.trim()).at(-1) ?? "";
|
|
558
|
+
if (!run.itemIds.includes(sub.sessionId)) run.itemIds.push(sub.sessionId);
|
|
559
|
+
if (!run.at && sub.at) run.at = sub.at;
|
|
560
|
+
};
|
|
561
|
+
for (const turn of turns) for (const run of turn.subagents ?? []) {
|
|
562
|
+
const key = run.match?.trim();
|
|
563
|
+
if (!key) continue;
|
|
564
|
+
const sub = subs.find((x) => !used.has(x) && x.firstQuestion.trim() === key);
|
|
565
|
+
if (sub) settle(run, sub);
|
|
566
|
+
}
|
|
567
|
+
for (const sub of subs) {
|
|
568
|
+
if (used.has(sub) || !sub.at) continue;
|
|
569
|
+
const before = turns.filter((t) => t.at && t.at <= sub.at);
|
|
570
|
+
const turn = before.at(-1) ?? turns.at(-1);
|
|
571
|
+
if (!turn) continue;
|
|
572
|
+
const run = { agent: sub.runner, task: sub.firstQuestion, output: "", itemIds: [sub.sessionId], at: sub.at };
|
|
573
|
+
settle(run, sub);
|
|
574
|
+
(turn.subagents ??= []).push(run);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function attachSubagentBranches(parent, turn, nodes, edges, runner, sessionId, cwd) {
|
|
578
|
+
const kids = [];
|
|
579
|
+
for (const run of turn.subagents ?? []) {
|
|
580
|
+
const node = makeNode(`[${run.agent}] ${run.task}`.trim(), run.output, false);
|
|
581
|
+
node.data.isBranch = true;
|
|
582
|
+
node.data.importSource = { runner, sessionId, itemIds: run.itemIds, ...cwd ? { cwd } : {}, subagent: { agent: run.agent, of: turn.itemIds[0] ?? "" } };
|
|
583
|
+
node.data.source = { question: node.data.question, response: node.data.response };
|
|
584
|
+
if (run.tools?.length) node.data.attachments = toolAttachments({ question: run.task, response: run.output, itemIds: run.itemIds, tools: run.tools });
|
|
585
|
+
seedPlaque(node);
|
|
586
|
+
nodes.push(node);
|
|
587
|
+
kids.push(node);
|
|
588
|
+
edges.push({ id: generateId(), source: parent.id, target: node.id, type: "smoothstep", data: { isBranchFromSelection: true, branchYRatio: 0.5 } });
|
|
589
|
+
}
|
|
590
|
+
return kids;
|
|
591
|
+
}
|
|
551
592
|
function toolScope(input) {
|
|
552
593
|
const i = input ?? {};
|
|
553
594
|
const out = {};
|
|
@@ -664,6 +705,10 @@ var ClaudeSessionCollector = class {
|
|
|
664
705
|
...reg.url ? { url: reg.url } : {},
|
|
665
706
|
...reg.locator ? { locator: reg.locator } : {}
|
|
666
707
|
});
|
|
708
|
+
if (reg.spawn) {
|
|
709
|
+
const report = clip(resultText(p.content), ARTIFACT_CALL_LIMIT);
|
|
710
|
+
(this.current.subagents ??= []).push({ agent: reg.spawn.agent, task: reg.spawn.task, output: report.text, itemIds: [p.tool_use_id], match: reg.spawn.prompt, ...line.timestamp ? { at: line.timestamp } : {} });
|
|
711
|
+
}
|
|
667
712
|
this.pendingTools.delete(p.tool_use_id);
|
|
668
713
|
}
|
|
669
714
|
}
|
|
@@ -708,7 +753,9 @@ ${text}` : text;
|
|
|
708
753
|
if (p.type === "tool_use" && p.id && p.name) {
|
|
709
754
|
const op = toolOpOf(p.name);
|
|
710
755
|
const call = clip(renderCall(p.name, p.input), op === "write" || op === "edit" ? ARTIFACT_CALL_LIMIT : TOOL_CALL_LIMIT);
|
|
711
|
-
|
|
756
|
+
const inp = p.input && typeof p.input === "object" ? p.input : {};
|
|
757
|
+
const spawn = op === "agent" ? { agent: String(inp.subagent_type ?? "agent"), task: String(inp.description ?? inp.prompt?.split("\n")[0] ?? ""), prompt: String(inp.prompt ?? "") } : void 0;
|
|
758
|
+
this.pendingTools.set(p.id, { name: p.name, call: call.text, paths: toolPaths(p.input), op, ...toolScope(p.input), ...spawn ? { spawn } : {} });
|
|
712
759
|
}
|
|
713
760
|
}
|
|
714
761
|
}
|
|
@@ -729,9 +776,17 @@ ${text}` : text;
|
|
|
729
776
|
messageCount: s.turns.length,
|
|
730
777
|
source: "claude-code",
|
|
731
778
|
sessionId: s.sessionId,
|
|
732
|
-
build: () => buildGraphFromTurns(s.turns, s.sessionId, s.cwd)
|
|
779
|
+
build: () => buildGraphFromTurns(s.turns, s.sessionId, s.cwd),
|
|
780
|
+
adopt: (subs) => adoptSubagentSessions(s.turns, subs)
|
|
733
781
|
};
|
|
734
782
|
}
|
|
783
|
+
/** This file as a subagent's transcript (a sidechain file), for its parent to adopt. */
|
|
784
|
+
toSubagentSession() {
|
|
785
|
+
const s = this.finish();
|
|
786
|
+
if (!s || s.turns.length === 0 || this.mode !== "sidechain") return null;
|
|
787
|
+
const first = s.turns[0];
|
|
788
|
+
return { runner: "claude-code", sessionId: s.sessionId, firstQuestion: this.firstQuestion ?? first.question, turns: s.turns, ...s.cwd ? { cwd: s.cwd } : {}, ...first.at ? { at: first.at } : {} };
|
|
789
|
+
}
|
|
735
790
|
};
|
|
736
791
|
function noteNode(text) {
|
|
737
792
|
const n = makeNode(text, "", false);
|
|
@@ -762,6 +817,7 @@ function buildGraphFromTurns(turns, sessionId, cwd) {
|
|
|
762
817
|
seedPlaque(node);
|
|
763
818
|
node.data.attachments = toolAttachments(turn);
|
|
764
819
|
nodes.push(node);
|
|
820
|
+
attachSubagentBranches(node, turn, nodes, edges, "claude-code", sessionId, cwd);
|
|
765
821
|
const parent = turn.parentItemId ? byItem.get(turn.parentItemId) : void 0;
|
|
766
822
|
if (parent) link(parent, node);
|
|
767
823
|
else if (prev) link(prev, node);
|
|
@@ -799,6 +855,7 @@ var CodexSessionCollector = class {
|
|
|
799
855
|
sawItems = false;
|
|
800
856
|
firstQuestion = null;
|
|
801
857
|
parentThreadId = null;
|
|
858
|
+
startedAt = null;
|
|
802
859
|
feedLine(raw) {
|
|
803
860
|
const t = raw.trim();
|
|
804
861
|
if (!t) return;
|
|
@@ -832,6 +889,7 @@ var CodexSessionCollector = class {
|
|
|
832
889
|
this.cwd = p.cwd ?? "";
|
|
833
890
|
this.day = p.timestamp?.slice(0, 10) ?? "";
|
|
834
891
|
this.parentThreadId = p.parent_thread_id ?? null;
|
|
892
|
+
this.startedAt = p.timestamp ?? line.timestamp ?? null;
|
|
835
893
|
return;
|
|
836
894
|
}
|
|
837
895
|
if (line.type === "response_item" || line.type === "event_msg") this.sawItems = true;
|
|
@@ -931,9 +989,18 @@ ${turn.response}` : turn.response;
|
|
|
931
989
|
messageCount: s.turns.length,
|
|
932
990
|
source: "codex",
|
|
933
991
|
sessionId: s.sessionId,
|
|
934
|
-
build: () => buildGraphFromTurns2(s.turns, s.sessionId, s.cwd)
|
|
992
|
+
build: () => buildGraphFromTurns2(s.turns, s.sessionId, s.cwd),
|
|
993
|
+
adopt: (subs) => adoptSubagentSessions(s.turns, subs)
|
|
935
994
|
};
|
|
936
995
|
}
|
|
996
|
+
/** This rollout as a child thread's transcript, for the parent named in its header to adopt. */
|
|
997
|
+
toSubagentSession() {
|
|
998
|
+
const s = this.finish();
|
|
999
|
+
if (!s || s.turns.length === 0 || !s.subagent) return null;
|
|
1000
|
+
const first = s.turns[0];
|
|
1001
|
+
const at = first.at ?? this.startedAt ?? void 0;
|
|
1002
|
+
return { runner: "codex", sessionId: s.sessionId, firstQuestion: this.firstQuestion ?? first.question, turns: s.turns, ...s.cwd ? { cwd: s.cwd } : {}, ...at ? { at } : {} };
|
|
1003
|
+
}
|
|
937
1004
|
};
|
|
938
1005
|
function buildGraphFromTurns2(turns, sessionId, cwd) {
|
|
939
1006
|
const origin = cwd ? { cwd } : {};
|
|
@@ -958,6 +1025,7 @@ function buildGraphFromTurns2(turns, sessionId, cwd) {
|
|
|
958
1025
|
nodes.push(node);
|
|
959
1026
|
if (prev) edges.push({ id: generateId(), source: prev.id, target: node.id, type: "smoothstep" });
|
|
960
1027
|
if (noteToWire) edges.push({ id: generateId(), source: noteToWire.id, target: node.id, type: "smoothstep" });
|
|
1028
|
+
attachSubagentBranches(node, turn, nodes, edges, "codex", sessionId, cwd);
|
|
961
1029
|
prev = node;
|
|
962
1030
|
}
|
|
963
1031
|
return { nodes: autoLayout(nodes, edges), edges };
|
|
@@ -1199,6 +1267,27 @@ ${e.newText ?? ""}`).join("\n\n");
|
|
|
1199
1267
|
const c = clipText(JSON.stringify(args ?? {}), TOOL_CALL_LIMIT);
|
|
1200
1268
|
return { ...c, paths, ...locator ? { locator } : {} };
|
|
1201
1269
|
}
|
|
1270
|
+
var finalOutput = (messages) => {
|
|
1271
|
+
if (!Array.isArray(messages)) return "";
|
|
1272
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1273
|
+
const m = messages[i];
|
|
1274
|
+
if (m?.role === "assistant") {
|
|
1275
|
+
const text = textOf(m.content).trim();
|
|
1276
|
+
if (text) return text;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return "";
|
|
1280
|
+
};
|
|
1281
|
+
function subagentRuns(args, content, details, callId) {
|
|
1282
|
+
const d = details;
|
|
1283
|
+
if (Array.isArray(d?.results) && d.results.length) {
|
|
1284
|
+
return d.results.map((r, i) => ({ agent: String(r.agent ?? "agent"), task: String(r.task ?? ""), output: finalOutput(r.messages) || String(r.stderr ?? "").trim(), itemIds: [`${callId}#${i}`] }));
|
|
1285
|
+
}
|
|
1286
|
+
const a = args && typeof args === "object" ? args : {};
|
|
1287
|
+
const asked = Array.isArray(a.tasks) && a.tasks.length ? a.tasks : Array.isArray(a.chain) && a.chain.length ? a.chain : a.agent || a.task ? [{ agent: a.agent, task: a.task }] : [];
|
|
1288
|
+
const text = textOf(content).trim();
|
|
1289
|
+
return asked.map((x, i) => ({ agent: String(x.agent ?? "agent"), task: String(x.task ?? ""), output: asked.length === 1 ? text : "", itemIds: [`${callId}#${i}`] }));
|
|
1290
|
+
}
|
|
1202
1291
|
var PiSessionCollector = class {
|
|
1203
1292
|
turns = [];
|
|
1204
1293
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -1261,7 +1350,7 @@ ${text}` : text;
|
|
|
1261
1350
|
for (const b of m.content) {
|
|
1262
1351
|
if (b?.type !== "toolCall" || typeof b.id !== "string" || typeof b.name !== "string") continue;
|
|
1263
1352
|
const r = renderCall2(b.name, b.arguments);
|
|
1264
|
-
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 } : {} });
|
|
1353
|
+
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 } : {}, ...b.name === "subagent" ? { args: b.arguments } : {} });
|
|
1265
1354
|
}
|
|
1266
1355
|
}
|
|
1267
1356
|
return;
|
|
@@ -1284,6 +1373,10 @@ ${text}` : text;
|
|
|
1284
1373
|
...reg.paths.length ? { paths: reg.paths } : {},
|
|
1285
1374
|
...reg.locator ? { locator: reg.locator } : {}
|
|
1286
1375
|
});
|
|
1376
|
+
if (reg.name === "subagent") {
|
|
1377
|
+
const runs = subagentRuns(reg.args, m.content, m.details, id);
|
|
1378
|
+
if (runs.length) cur.subagents = [...cur.subagents ?? [], ...runs];
|
|
1379
|
+
}
|
|
1287
1380
|
this.pending.delete(id);
|
|
1288
1381
|
}
|
|
1289
1382
|
}
|
|
@@ -1322,6 +1415,7 @@ function buildGraphFromTurns4(turns, sessionId, cwd) {
|
|
|
1322
1415
|
const parent = turn.parentItemId ? byItem.get(turn.parentItemId) : void 0;
|
|
1323
1416
|
if (parent) link(parent, node);
|
|
1324
1417
|
else if (prev) link(prev, node);
|
|
1418
|
+
attachSubagentBranches(node, turn, nodes, edges, "pi", sessionId, cwd);
|
|
1325
1419
|
for (const id of turn.itemIds) byItem.set(id, node);
|
|
1326
1420
|
prev = node;
|
|
1327
1421
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import{q as I,B as g,n as T}from"./index-C8yf-QJz.js";import{t as C,a as _,r as A,b as x,c as w,d as L,e as S,s as k,f as v,m as O,T as b,A as q,g as B}from"./shared-B7orUlMM.js";const M=16e3;function f(o){return typeof o=="string"?o:Array.isArray(o)?o.filter(t=>t.type==="text"&&t.text).map(t=>t.text).join(`
|
|
2
|
-
`):""}function N(o){return typeof o=="string"?o:Array.isArray(o)?o.map(t=>t.type==="text"&&t.text?t.text:t.type==="image"?"[image result omitted]":"").filter(Boolean).join(`
|
|
3
|
-
`):""}const p=S;class ${turns=[];pendingTools=new Map;current=null;pendingCompaction;sessionId=null;customTitle=null;summary=null;slug=null;firstQuestion=null;cwd=null;mode=null;feedLine(t){const s=t.trim();if(!s)return;let n;try{n=JSON.parse(s)}catch{return}this.feed(n)}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==="custom-title"&&t.customTitle&&(this.customTitle=t.customTitle),t.type==="summary"&&t.summary&&!this.summary&&(this.summary=t.summary),t.slug&&!this.slug&&(this.slug=t.slug),t.cwd&&!this.cwd&&(this.cwd=t.cwd),this.mode===null&&(t.type==="user"||t.type==="assistant")&&t.uuid&&(this.mode=t.isSidechain&&t.agentId?"sidechain":"main",this.mode==="sidechain"&&(this.sessionId=t.agentId)),!!t.isSidechain==(this.mode==="sidechain")){if(t.type==="system"&&t.subtype==="compact_boundary"){this.flush();const s=t.compactMetadata;this.pendingCompaction=`[Compaction] The source runner compacted its history here${s?.preTokens?` (${s.preTokens} → ${s.postTokens??"?"} tokens)`:""}. Everything above this point reached later turns only as a summary.`;return}if((t.type==="user"||t.type==="assistant")&&t.uuid&&t.sessionId&&!this.sessionId&&(this.sessionId=t.sessionId),t.type==="user"){const s=t.message?.content;if(Array.isArray(s)){for(const e of s)if(e.type==="tool_result"&&e.tool_use_id){const i=this.pendingTools.get(e.tool_use_id);if(i&&this.current){const a=p(N(e.content),b);this.current.tools.push({name:i.name,call:i.call,result:a.text,truncated:a.truncated,...i.paths.length?{paths:i.paths}:{},op:i.op,nativeCallId:e.tool_use_id,...i.url?{url:i.url}:{},...i.locator?{locator:i.locator}:{}}),this.pendingTools.delete(e.tool_use_id)}}}const n=f(s);if(!n.trim())return;if(/^\s*<task-notification>/.test(n)){if(this.current){const e=p(n,M);this.current.tools.push({name:"Agent",call:"(task notification)",result:e.text,truncated:e.truncated,op:"agent"})}return}this.flush(),this.firstQuestion||(this.firstQuestion=n.trim()),this.current={question:n,response:"",itemIds:t.uuid?[t.uuid]:[],tools:[],parentItemId:t.parentUuid??void 0,...t.timestamp?{at:t.timestamp}:{}},this.pendingCompaction&&(this.current.compactionBefore=this.pendingCompaction,this.pendingCompaction=void 0);return}if(t.type==="assistant"&&this.current){t.uuid&&this.current.itemIds.push(t.uuid);const s=t.message?.content,n=f(s);if(n.trim()&&(this.current.response=this.current.response?`${this.current.response}
|
|
4
|
-
|
|
5
|
-
${n}`:n),Array.isArray(s)){for(const e of s)if(e.type==="tool_use"&&e.id&&e.name){const i=_(e.name),a=p(A(e.name,e.input),i==="write"||i==="edit"?q:B);this.pendingTools.set(e.id,{name:e.name,call:a.text,paths:w(e.input),op:i,...x(e.input)})}}}}}finish(){if(this.flush(),!this.sessionId)return null;const t=this.firstQuestion?.split(`
|
|
6
|
-
`).map(n=>n.trim()).find(n=>n&&!n.startsWith("<"))?.slice(0,80)??null,s=this.customTitle??this.summary??t??this.slug??`session ${this.sessionId.slice(0,8)}`;return{sessionId:this.sessionId,title:s,turns:L(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:"claude-code",sessionId:t.sessionId,build:()=>Q(t.turns,t.sessionId,t.cwd)}}}function y(o){const t=new $;for(const s of o.split(`
|
|
7
|
-
`))t.feedLine(s);return t}function F(o){const t=I(o,"",!1);return t.data.stepKind="note",O(t),t}function Q(o,t,s){const n=s?{cwd:s}:{},e=[],i=[];let a=null;const m=new Map,l=(u,c)=>{i.push({id:T(),source:u.id,target:c.id,type:"smoothstep"})};for(const u of o){let c=null;if(u.compactionBefore){const d=F(u.compactionBefore);d.data.importSource={runner:"claude-code",sessionId:t,itemIds:[],...n},e.push(d),c=d}const r=I(u.question,u.response,a===null);r.data.importSource={runner:"claude-code",sessionId:t,itemIds:u.itemIds,...n},r.data.source={question:r.data.question,response:r.data.response},k(r),r.data.attachments=v(u),e.push(r);const h=u.parentItemId?m.get(u.parentItemId):void 0;h?l(h,r):a&&l(a,r),c&&l(c,r);for(const d of u.itemIds)m.set(d,r);a=r}return{nodes:g(e,i),edges:i}}function E(o,t){const s=y(o).finish();return s?C(s.turns,s.sessionId,"claude-code",t,s.cwd):null}function R(o){return y(o).toConversation()}export{$ as ClaudeSessionCollector,E as claudeCodeSessionAsBranch,R as claudeCodeSessionConversation};
|