claude-code-session-manager 0.114.0 → 0.115.0
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/assets/{AgentLibrary-DZFM7Lto.js → AgentLibrary-CjsZLl5e.js} +1 -1
- package/dist/assets/{Button-BJVFKdGH.js → Button-mj0vvcdc.js} +1 -1
- package/dist/assets/{DataModel-CebvcP_y.js → DataModel-CG6ycpfG.js} +1 -1
- package/dist/assets/{History-BwISEUhR.js → History-DDqLQiAN.js} +1 -1
- package/dist/assets/{Hooks-Dac3smYP.js → Hooks-dRje24Nk.js} +1 -1
- package/dist/assets/{Library-DTjsUr9W.js → Library-Dl71nylN.js} +1 -1
- package/dist/assets/{ListDetail-B1L5TbjS.js → ListDetail-DcAD_cuA.js} +1 -1
- package/dist/assets/{MarkdownEditor-BFRhWZD4.js → MarkdownEditor-D7JTNOOp.js} +1 -1
- package/dist/assets/{McpServers-CBjGCMg0.js → McpServers-BbvbhemH.js} +1 -1
- package/dist/assets/{Memory-DdLi5fqe.js → Memory-dyz8ZU1e.js} +1 -1
- package/dist/assets/{Panel-BQkkkjex.js → Panel-BkfK-W74.js} +1 -1
- package/dist/assets/{Permissions-CaACwY0B.js → Permissions-BnjsXblr.js} +1 -1
- package/dist/assets/{Plugins-DJeBwVoh.js → Plugins-DSyTJtTp.js} +2 -2
- package/dist/assets/{ProvenanceBadge--N5j8xiK.js → ProvenanceBadge-CQXSiPHq.js} +1 -1
- package/dist/assets/{RemoteMachines-WjWaDHEA.js → RemoteMachines-li6EBPAt.js} +1 -1
- package/dist/assets/{SaveBar-DFmOjb4_.js → SaveBar-0aiuy1L2.js} +1 -1
- package/dist/assets/{Scheduler-Miap6_aY.js → Scheduler-Ul4jiK7q.js} +1 -1
- package/dist/assets/{ScopeSwitcher-CiEujp4L.js → ScopeSwitcher-DR262W_R.js} +1 -1
- package/dist/assets/{Settings-UGpW4P0j.js → Settings-BwUcGuar.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-BgyAnxGW.js → SkillReferenceGraph-fv2FJUNK.js} +1 -1
- package/dist/assets/{Skills-BQUpnNfx.js → Skills-BA2I-iRE.js} +1 -1
- package/dist/assets/{SystemPrompt-CQjOgHGT.js → SystemPrompt-BgvyrAKx.js} +1 -1
- package/dist/assets/{TagLibrary-CTVpoLl0.js → TagLibrary-B5rKEDVT.js} +1 -1
- package/dist/assets/{TiptapBody-BtOcrdRR.js → TiptapBody-CBxxEulT.js} +1 -1
- package/dist/assets/{Toggle-Chri4G19.js → Toggle-W944m375.js} +1 -1
- package/dist/assets/{ViewTabs-BIenDGVv.js → ViewTabs-BLQrBE2n.js} +1 -1
- package/dist/assets/{historyTotals-CX8mUxJY.js → historyTotals-BUeyjlNY.js} +1 -1
- package/dist/assets/{index-AVrhGmUO.js → index-t37UuEZu.js} +40 -40
- package/dist/assets/{settingsSchema-uqwlznbH.js → settingsSchema-HwiOXtC7.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-BaezRtWw.js → useAgentsDirWatch-D0rI9G_h.js} +1 -1
- package/dist/assets/{useRemotePeers-BuFbBqDt.js → useRemotePeers-BipAySOB.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +8 -2
- package/src/main/ipcSchemas.cjs +26 -1
- package/src/main/lib/remoteDrilldown.cjs +91 -0
- package/src/main/lib/remoteTranscriptTail.cjs +90 -0
- package/src/main/webRemote.cjs +90 -57
- package/src/preload/api.d.ts +3 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{aF as b,ap as n,S as N,aZ as O}from"./index-AVrhGmUO.js";import{r as K}from"./claude-settings-schema-COigsajx.js";function G({node:e,targetScope:r,onOverride:s,schema:a,rootLabel:m}){const[f,x]=b.useState(""),[u,o]=b.useState(!0),g=[],d=e.kind==="object"&&Object.keys(e.children).length===0,y=((a==null?void 0:a.rootKeys())??[]).length>0;return n.jsxs("div",{className:"h-full flex flex-col",children:[n.jsxs("div",{className:"shrink-0 border-b border-line bg-bg-elev px-3 py-1.5 flex items-center gap-2 text-xs",children:[n.jsxs("span",{className:"text-fg-faint",children:["merged · overrides → ",n.jsx("span",{className:"text-fg-dim",children:N[r].label})]}),y&&n.jsxs("label",{className:"flex items-center gap-1 text-fg-faint cursor-pointer",children:[n.jsx("input",{type:"checkbox",checked:u,onChange:t=>o(t.target.checked),className:"accent-accent"}),"show unset keys"]}),n.jsx("div",{className:"flex-1"}),n.jsx("input",{value:f,onChange:t=>x(t.target.value),placeholder:"filter keys or descriptions…",className:"bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg w-56"})]}),n.jsx("div",{className:"flex-1 overflow-auto px-3 py-2 font-mono text-xs",children:d&&!u?n.jsx("div",{className:"text-fg-faint italic px-2 py-4",children:"no settings resolved — all scope files empty or missing"}):n.jsx(E,{node:e,path:g,label:m??"(root)",targetScope:r,onOverride:s,filter:f,depth:0,schema:a,showGhosts:u,defaultOpen:!0})})]})}function E(e){return e.node.kind==="object"?n.jsx(P,{...e,node:e.node}):e.node.kind==="array"?n.jsx(J,{...e,node:e.node}):n.jsx(R,{...e,node:e.node})}function A(e,r){return r.trim()?e.toLowerCase().includes(r.toLowerCase()):!0}function w(e,r,s){return!!(!s.trim()||e.some(a=>A(a,s))||r!=null&&r.description&&A(r.description,s))}function P({node:e,path:r,label:s,targetScope:a,onOverride:m,filter:f,depth:x,schema:u,showGhosts:o,defaultOpen:g}){const[d,h]=b.useState(g??x<2),y=(u==null?void 0:u.at(r))??null,t=new Set(Object.keys(e.children)),i=o?(y==null?void 0:y.knownChildren)??[]:[],c=Array.from(new Set([...t,...i])).sort(),l=b.useMemo(()=>f.trim()?c.filter(p=>{const v=(u==null?void 0:u.at([...r,p]))??null;return w([...r,p],v,f)?!0:t.has(p)?L(e.children[p],[...r,p],f,u):!1}):c,[c.join(","),f,e,r.join("."),u]);if(f.trim()&&l.length===0&&!w(r,y,f))return null;if(r.length===0)return n.jsx("div",{children:l.map(p=>j(p))});return n.jsxs("div",{children:[n.jsxs("div",{className:"flex items-center gap-1 py-0.5 hover:bg-bg-elev/50 rounded cursor-pointer",style:{paddingLeft:x*14},onClick:()=>h(!d),children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0",children:d?"▾":"▸"}),n.jsx("span",{className:"text-fg",children:s}),n.jsxs("span",{className:"text-fg-faint",children:[" {",t.size,i.length>t.size?`/${new Set([...t,...i]).size}`:"","}"]}),(y==null?void 0:y.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:y.description,children:["— ",y.description]})]}),d&&l.map(p=>j(p))]});function j(p){if(t.has(p))return n.jsx(E,{node:e.children[p],path:[...r,p],label:p,targetScope:a,onOverride:m,filter:f,depth:x+1,schema:u,showGhosts:o},p);const v=(u==null?void 0:u.at([...r,p]))??null;return v?n.jsx(I,{path:[...r,p],label:p,info:v,targetScope:a,onSet:m,depth:x+1,filter:f},p):null}}function L(e,r,s,a){const m=(a==null?void 0:a.at(r))??null;if(w(r,m,s))return!0;if(e.kind!=="object")return!1;for(const f of Object.keys(e.children))if(L(e.children[f],[...r,f],s,a))return!0;return!1}function J({node:e,path:r,label:s,filter:a,depth:m,schema:f}){const[x,u]=b.useState(!0),o=(f==null?void 0:f.at(r))??null;return w(r,o,a)?n.jsxs("div",{children:[n.jsxs("div",{className:"flex items-center gap-1 py-0.5 hover:bg-bg-elev/50 rounded cursor-pointer",style:{paddingLeft:m*14},onClick:()=>u(!x),children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0",children:x?"▾":"▸"}),n.jsx("span",{className:"text-fg",children:s}),n.jsxs("span",{className:"text-fg-faint",children:[" [",e.items.length,"]"]}),n.jsx(S,{tone:"warn",children:"accumulated"}),(o==null?void 0:o.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:o.description,children:["— ",o.description]})]}),x&&e.items.map((g,d)=>n.jsxs("div",{className:"flex items-start gap-2 py-0.5",style:{paddingLeft:(m+1)*14},children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0"}),n.jsxs("span",{className:"text-fg-faint",children:[d,":"]}),n.jsx("span",{className:"text-fg-dim truncate max-w-xl",children:k(g.value)}),n.jsx(C,{scope:g.fromScope})]},d))]}):null}function R({node:e,path:r,label:s,targetScope:a,onOverride:m,filter:f,depth:x,schema:u}){const[o,g]=b.useState(!1),d=(u==null?void 0:u.at(r))??null;if(!w(r,d,f))return null;const h=e.winner!==a,y=e.shadowed.length>0;return n.jsxs("div",{children:[n.jsxs("div",{className:"group flex items-center gap-2 py-0.5 hover:bg-bg-elev/50 rounded",style:{paddingLeft:x*14},children:[n.jsx("span",{className:`w-3 shrink-0 ${y?"text-fg-faint cursor-pointer":""}`,onClick:()=>y&&g(!o),children:y?o?"▾":"▸":""}),n.jsxs("span",{className:"text-fg",children:[s,":"]}),n.jsx("span",{className:"text-fg-dim truncate max-w-md",children:k(e.value)}),n.jsx(C,{scope:e.winner}),(d==null?void 0:d.deprecated)&&n.jsx(S,{tone:"warn",children:"deprecated"}),(d==null?void 0:d.default)!==void 0&&_(e.value,d.default)&&n.jsx(S,{tone:"dim",children:"= default"}),e.typeMismatch&&n.jsx("span",{className:"text-yellow-500/80",title:"shadowed scopes have a different value type",children:"⚠"}),(d==null?void 0:d.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:d.description,children:["— ",d.description]}),n.jsx("div",{className:"flex-1"}),(d==null?void 0:d.enum)&&n.jsx(F,{values:d.enum}),n.jsx("button",{disabled:!h,onClick:()=>m(r,e.value),className:`opacity-0 group-hover:opacity-100 px-1.5 py-0 text-[10px] border rounded transition-opacity ${h?"border-accent/50 text-accent hover:bg-accent hover:text-bg":"border-line text-fg-faint cursor-not-allowed"}`,title:h?`copy into ${N[a].label} draft`:`already winning in ${N[a].label}`,children:"override"})]}),o&&e.shadowed.map((t,i)=>n.jsxs("div",{className:"flex items-center gap-2 py-0.5 text-fg-faint line-through",style:{paddingLeft:(x+1)*14+12},children:[n.jsx("span",{children:"shadowed:"}),n.jsx("span",{className:"truncate max-w-xl",children:k(t.value)}),n.jsx(C,{scope:t.scope,dim:!0})]},i))]})}function I({path:e,label:r,info:s,targetScope:a,onSet:m,depth:f,filter:x}){if(!w(e,s,x))return null;const u=s.default!==void 0?s.default:M(s.type),o=Array.isArray(s.type)?s.type.join("|"):s.type??"?";return n.jsxs("div",{className:"group flex items-center gap-2 py-0.5 hover:bg-bg-elev/50 rounded opacity-60",style:{paddingLeft:f*14},children:[n.jsx("span",{className:"w-3 shrink-0"}),n.jsx("span",{className:"text-fg-dim",children:r}),n.jsx(S,{tone:"dim",children:o}),s.required&&n.jsx(S,{tone:"warn",children:"required"}),s.default!==void 0&&n.jsxs("span",{className:"text-fg-faint",children:["default: ",k(s.default)]}),s.description&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:s.description,children:["— ",s.description]}),n.jsx("div",{className:"flex-1"}),s.enum&&n.jsx(F,{values:s.enum}),n.jsx("button",{onClick:()=>m(e,u),className:"opacity-0 group-hover:opacity-100 px-1.5 py-0 text-[10px] border border-accent/50 rounded text-accent hover:bg-accent hover:text-bg transition-opacity",title:`write to ${N[a].label} draft`,children:"set"})]})}function _(e,r){return e===r?!0:typeof e!=typeof r?!1:e&&r&&typeof e=="object"?JSON.stringify(e)===JSON.stringify(r):!1}function M(e){switch(Array.isArray(e)?e[0]:e){case"boolean":return!1;case"integer":case"number":return 0;case"string":return"";case"array":return[];case"object":return{};case"null":return null;default:return null}}function k(e){if(typeof e=="string")return JSON.stringify(e);if(e===null)return"null";if(typeof e=="object"){const r=JSON.stringify(e);return r.length>100?r.slice(0,100)+"…":r}return String(e)}function C({scope:e,dim:r}){const s=e==="user"?"bg-bg-hi text-fg-dim border-line":e==="project"?"bg-blue-950/30 text-blue-400/80 border-blue-900/40":"bg-purple-950/30 text-purple-400/80 border-purple-900/40";return n.jsx("span",{className:`text-[10px] px-1 py-0 rounded border uppercase tracking-wide shrink-0 ${s} ${r?"opacity-60":""}`,children:N[e].label})}function S({children:e,tone:r="default"}){const s={default:"bg-bg-hi text-fg-dim border-line",warn:"bg-yellow-950/30 text-yellow-500/80 border-yellow-900/40",dim:"bg-bg-elev text-fg-faint border-line"}[r];return n.jsx("span",{className:`text-[10px] px-1 py-0 rounded border uppercase tracking-wide shrink-0 ${s}`,children:e})}function F({values:e}){const r=e.map(s=>String(s)).join(" | ");return n.jsxs("span",{className:"text-[10px] text-fg-faint truncate max-w-xs",title:`allowed: ${r}`,children:["∈ ",r.length>30?r.slice(0,30)+"…":r]})}function V(e,r,s){const a=O(o=>o.files),m=O(o=>o.loadJson),f=O(o=>o.watchFile),x=O(o=>o.unwatchFile),u=b.useMemo(()=>{const o={};for(const g of e.scopes){const d=e.resolve(g,r??"",s);d&&(o[g]=d)}return o},[e,r,s]);return b.useEffect(()=>{const o=Object.values(u).filter(Boolean);return o.forEach(g=>{a[g]||m(g),f(g)}),()=>{o.forEach(g=>x(g))}},[JSON.stringify(u)]),u}function B(e){const r=e??{},s={...r.$defs??{},...r.definitions??{}},a=t=>{if(!t)return{};if(!t.$ref)return x(t);const i=/^#\/(\$defs|definitions)\/(.+)$/.exec(t.$ref);if(!i)return x(t);const c=s[i[2]];return c?a({...c,...u(t)}):x(t)},m=new Set(["__proto__","constructor","prototype"]),f=t=>{if(!t)return{};const i={};for(const[c,l]of Object.entries(t))m.has(c)||(i[c]=l);return i},x=t=>{if(!t.allOf||t.allOf.length===0)return t;const i={...t};delete i.allOf;for(const c of t.allOf){const l=a(c);Object.assign(i,{properties:{...f(i.properties),...f(l.properties)},required:q([...i.required??[],...l.required??[]])}),!i.description&&l.description&&(i.description=l.description),i.default===void 0&&l.default!==void 0&&(i.default=l.default),!i.type&&l.type&&(i.type=l.type)}return i},u=t=>{const{$ref:i,...c}=t;return c},o=(t,i)=>{let c=a(t);for(const l of i){if(!c)return null;if(/^\d+$/.test(l)){const v=Array.isArray(c.items)?c.items[Number(l)]:c.items;c=v?a(v):null;continue}const j=c.properties;if(j&&!m.has(l)&&l in j){c=a(j[l]);continue}const p=c.additionalProperties;if(p&&typeof p=="object"){c=a(p);continue}return null}return c},g=(t,i,c)=>{if(!t)return null;const l={};if(t.description&&(l.description=t.description),t.type&&(l.type=t.type),t.default!==void 0&&(l.default=t.default),t.enum&&(l.enum=t.enum),t.const!==void 0&&(l.const=t.const),t.deprecated&&(l.deprecated=!0),t.examples&&(l.examples=t.examples),t.properties&&(l.knownChildren=Object.keys(t.properties)),c&&(i!=null&&i.includes(c))&&(l.required=!0),t.items&&!Array.isArray(t.items)){const j=a(t.items),p=g(j,void 0,null);p&&(l.itemsInfo=p)}return(t.additionalProperties===!0||typeof t.additionalProperties=="object")&&(l.open=!0),l},d=t=>i=>{if(i.length===0)return g(a(t),void 0,null);const c=o(t,i.slice(0,-1));if(!c)return null;const l=i[i.length-1],j=o(c,[l]);return g(j,c.required,l)},h=t=>{const i=a(t);return Object.keys(i.properties??{})},y=t=>({at:d(t),rootKeys:()=>h(t),rootedAt:i=>{const c=o(t,i);return y(c??{})}});return y(r)}function q(e){return Array.from(new Set(e))}let $=null;function D(){return $||($=B(K)),$}export{G as E,D as s,V as u};
|
|
1
|
+
import{aF as b,ap as n,S as N,aZ as O}from"./index-t37UuEZu.js";import{r as K}from"./claude-settings-schema-COigsajx.js";function G({node:e,targetScope:r,onOverride:s,schema:a,rootLabel:m}){const[f,x]=b.useState(""),[u,o]=b.useState(!0),g=[],d=e.kind==="object"&&Object.keys(e.children).length===0,y=((a==null?void 0:a.rootKeys())??[]).length>0;return n.jsxs("div",{className:"h-full flex flex-col",children:[n.jsxs("div",{className:"shrink-0 border-b border-line bg-bg-elev px-3 py-1.5 flex items-center gap-2 text-xs",children:[n.jsxs("span",{className:"text-fg-faint",children:["merged · overrides → ",n.jsx("span",{className:"text-fg-dim",children:N[r].label})]}),y&&n.jsxs("label",{className:"flex items-center gap-1 text-fg-faint cursor-pointer",children:[n.jsx("input",{type:"checkbox",checked:u,onChange:t=>o(t.target.checked),className:"accent-accent"}),"show unset keys"]}),n.jsx("div",{className:"flex-1"}),n.jsx("input",{value:f,onChange:t=>x(t.target.value),placeholder:"filter keys or descriptions…",className:"bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg w-56"})]}),n.jsx("div",{className:"flex-1 overflow-auto px-3 py-2 font-mono text-xs",children:d&&!u?n.jsx("div",{className:"text-fg-faint italic px-2 py-4",children:"no settings resolved — all scope files empty or missing"}):n.jsx(E,{node:e,path:g,label:m??"(root)",targetScope:r,onOverride:s,filter:f,depth:0,schema:a,showGhosts:u,defaultOpen:!0})})]})}function E(e){return e.node.kind==="object"?n.jsx(P,{...e,node:e.node}):e.node.kind==="array"?n.jsx(J,{...e,node:e.node}):n.jsx(R,{...e,node:e.node})}function A(e,r){return r.trim()?e.toLowerCase().includes(r.toLowerCase()):!0}function w(e,r,s){return!!(!s.trim()||e.some(a=>A(a,s))||r!=null&&r.description&&A(r.description,s))}function P({node:e,path:r,label:s,targetScope:a,onOverride:m,filter:f,depth:x,schema:u,showGhosts:o,defaultOpen:g}){const[d,h]=b.useState(g??x<2),y=(u==null?void 0:u.at(r))??null,t=new Set(Object.keys(e.children)),i=o?(y==null?void 0:y.knownChildren)??[]:[],c=Array.from(new Set([...t,...i])).sort(),l=b.useMemo(()=>f.trim()?c.filter(p=>{const v=(u==null?void 0:u.at([...r,p]))??null;return w([...r,p],v,f)?!0:t.has(p)?L(e.children[p],[...r,p],f,u):!1}):c,[c.join(","),f,e,r.join("."),u]);if(f.trim()&&l.length===0&&!w(r,y,f))return null;if(r.length===0)return n.jsx("div",{children:l.map(p=>j(p))});return n.jsxs("div",{children:[n.jsxs("div",{className:"flex items-center gap-1 py-0.5 hover:bg-bg-elev/50 rounded cursor-pointer",style:{paddingLeft:x*14},onClick:()=>h(!d),children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0",children:d?"▾":"▸"}),n.jsx("span",{className:"text-fg",children:s}),n.jsxs("span",{className:"text-fg-faint",children:[" {",t.size,i.length>t.size?`/${new Set([...t,...i]).size}`:"","}"]}),(y==null?void 0:y.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:y.description,children:["— ",y.description]})]}),d&&l.map(p=>j(p))]});function j(p){if(t.has(p))return n.jsx(E,{node:e.children[p],path:[...r,p],label:p,targetScope:a,onOverride:m,filter:f,depth:x+1,schema:u,showGhosts:o},p);const v=(u==null?void 0:u.at([...r,p]))??null;return v?n.jsx(I,{path:[...r,p],label:p,info:v,targetScope:a,onSet:m,depth:x+1,filter:f},p):null}}function L(e,r,s,a){const m=(a==null?void 0:a.at(r))??null;if(w(r,m,s))return!0;if(e.kind!=="object")return!1;for(const f of Object.keys(e.children))if(L(e.children[f],[...r,f],s,a))return!0;return!1}function J({node:e,path:r,label:s,filter:a,depth:m,schema:f}){const[x,u]=b.useState(!0),o=(f==null?void 0:f.at(r))??null;return w(r,o,a)?n.jsxs("div",{children:[n.jsxs("div",{className:"flex items-center gap-1 py-0.5 hover:bg-bg-elev/50 rounded cursor-pointer",style:{paddingLeft:m*14},onClick:()=>u(!x),children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0",children:x?"▾":"▸"}),n.jsx("span",{className:"text-fg",children:s}),n.jsxs("span",{className:"text-fg-faint",children:[" [",e.items.length,"]"]}),n.jsx(S,{tone:"warn",children:"accumulated"}),(o==null?void 0:o.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:o.description,children:["— ",o.description]})]}),x&&e.items.map((g,d)=>n.jsxs("div",{className:"flex items-start gap-2 py-0.5",style:{paddingLeft:(m+1)*14},children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0"}),n.jsxs("span",{className:"text-fg-faint",children:[d,":"]}),n.jsx("span",{className:"text-fg-dim truncate max-w-xl",children:k(g.value)}),n.jsx(C,{scope:g.fromScope})]},d))]}):null}function R({node:e,path:r,label:s,targetScope:a,onOverride:m,filter:f,depth:x,schema:u}){const[o,g]=b.useState(!1),d=(u==null?void 0:u.at(r))??null;if(!w(r,d,f))return null;const h=e.winner!==a,y=e.shadowed.length>0;return n.jsxs("div",{children:[n.jsxs("div",{className:"group flex items-center gap-2 py-0.5 hover:bg-bg-elev/50 rounded",style:{paddingLeft:x*14},children:[n.jsx("span",{className:`w-3 shrink-0 ${y?"text-fg-faint cursor-pointer":""}`,onClick:()=>y&&g(!o),children:y?o?"▾":"▸":""}),n.jsxs("span",{className:"text-fg",children:[s,":"]}),n.jsx("span",{className:"text-fg-dim truncate max-w-md",children:k(e.value)}),n.jsx(C,{scope:e.winner}),(d==null?void 0:d.deprecated)&&n.jsx(S,{tone:"warn",children:"deprecated"}),(d==null?void 0:d.default)!==void 0&&_(e.value,d.default)&&n.jsx(S,{tone:"dim",children:"= default"}),e.typeMismatch&&n.jsx("span",{className:"text-yellow-500/80",title:"shadowed scopes have a different value type",children:"⚠"}),(d==null?void 0:d.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:d.description,children:["— ",d.description]}),n.jsx("div",{className:"flex-1"}),(d==null?void 0:d.enum)&&n.jsx(F,{values:d.enum}),n.jsx("button",{disabled:!h,onClick:()=>m(r,e.value),className:`opacity-0 group-hover:opacity-100 px-1.5 py-0 text-[10px] border rounded transition-opacity ${h?"border-accent/50 text-accent hover:bg-accent hover:text-bg":"border-line text-fg-faint cursor-not-allowed"}`,title:h?`copy into ${N[a].label} draft`:`already winning in ${N[a].label}`,children:"override"})]}),o&&e.shadowed.map((t,i)=>n.jsxs("div",{className:"flex items-center gap-2 py-0.5 text-fg-faint line-through",style:{paddingLeft:(x+1)*14+12},children:[n.jsx("span",{children:"shadowed:"}),n.jsx("span",{className:"truncate max-w-xl",children:k(t.value)}),n.jsx(C,{scope:t.scope,dim:!0})]},i))]})}function I({path:e,label:r,info:s,targetScope:a,onSet:m,depth:f,filter:x}){if(!w(e,s,x))return null;const u=s.default!==void 0?s.default:M(s.type),o=Array.isArray(s.type)?s.type.join("|"):s.type??"?";return n.jsxs("div",{className:"group flex items-center gap-2 py-0.5 hover:bg-bg-elev/50 rounded opacity-60",style:{paddingLeft:f*14},children:[n.jsx("span",{className:"w-3 shrink-0"}),n.jsx("span",{className:"text-fg-dim",children:r}),n.jsx(S,{tone:"dim",children:o}),s.required&&n.jsx(S,{tone:"warn",children:"required"}),s.default!==void 0&&n.jsxs("span",{className:"text-fg-faint",children:["default: ",k(s.default)]}),s.description&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:s.description,children:["— ",s.description]}),n.jsx("div",{className:"flex-1"}),s.enum&&n.jsx(F,{values:s.enum}),n.jsx("button",{onClick:()=>m(e,u),className:"opacity-0 group-hover:opacity-100 px-1.5 py-0 text-[10px] border border-accent/50 rounded text-accent hover:bg-accent hover:text-bg transition-opacity",title:`write to ${N[a].label} draft`,children:"set"})]})}function _(e,r){return e===r?!0:typeof e!=typeof r?!1:e&&r&&typeof e=="object"?JSON.stringify(e)===JSON.stringify(r):!1}function M(e){switch(Array.isArray(e)?e[0]:e){case"boolean":return!1;case"integer":case"number":return 0;case"string":return"";case"array":return[];case"object":return{};case"null":return null;default:return null}}function k(e){if(typeof e=="string")return JSON.stringify(e);if(e===null)return"null";if(typeof e=="object"){const r=JSON.stringify(e);return r.length>100?r.slice(0,100)+"…":r}return String(e)}function C({scope:e,dim:r}){const s=e==="user"?"bg-bg-hi text-fg-dim border-line":e==="project"?"bg-blue-950/30 text-blue-400/80 border-blue-900/40":"bg-purple-950/30 text-purple-400/80 border-purple-900/40";return n.jsx("span",{className:`text-[10px] px-1 py-0 rounded border uppercase tracking-wide shrink-0 ${s} ${r?"opacity-60":""}`,children:N[e].label})}function S({children:e,tone:r="default"}){const s={default:"bg-bg-hi text-fg-dim border-line",warn:"bg-yellow-950/30 text-yellow-500/80 border-yellow-900/40",dim:"bg-bg-elev text-fg-faint border-line"}[r];return n.jsx("span",{className:`text-[10px] px-1 py-0 rounded border uppercase tracking-wide shrink-0 ${s}`,children:e})}function F({values:e}){const r=e.map(s=>String(s)).join(" | ");return n.jsxs("span",{className:"text-[10px] text-fg-faint truncate max-w-xs",title:`allowed: ${r}`,children:["∈ ",r.length>30?r.slice(0,30)+"…":r]})}function V(e,r,s){const a=O(o=>o.files),m=O(o=>o.loadJson),f=O(o=>o.watchFile),x=O(o=>o.unwatchFile),u=b.useMemo(()=>{const o={};for(const g of e.scopes){const d=e.resolve(g,r??"",s);d&&(o[g]=d)}return o},[e,r,s]);return b.useEffect(()=>{const o=Object.values(u).filter(Boolean);return o.forEach(g=>{a[g]||m(g),f(g)}),()=>{o.forEach(g=>x(g))}},[JSON.stringify(u)]),u}function B(e){const r=e??{},s={...r.$defs??{},...r.definitions??{}},a=t=>{if(!t)return{};if(!t.$ref)return x(t);const i=/^#\/(\$defs|definitions)\/(.+)$/.exec(t.$ref);if(!i)return x(t);const c=s[i[2]];return c?a({...c,...u(t)}):x(t)},m=new Set(["__proto__","constructor","prototype"]),f=t=>{if(!t)return{};const i={};for(const[c,l]of Object.entries(t))m.has(c)||(i[c]=l);return i},x=t=>{if(!t.allOf||t.allOf.length===0)return t;const i={...t};delete i.allOf;for(const c of t.allOf){const l=a(c);Object.assign(i,{properties:{...f(i.properties),...f(l.properties)},required:q([...i.required??[],...l.required??[]])}),!i.description&&l.description&&(i.description=l.description),i.default===void 0&&l.default!==void 0&&(i.default=l.default),!i.type&&l.type&&(i.type=l.type)}return i},u=t=>{const{$ref:i,...c}=t;return c},o=(t,i)=>{let c=a(t);for(const l of i){if(!c)return null;if(/^\d+$/.test(l)){const v=Array.isArray(c.items)?c.items[Number(l)]:c.items;c=v?a(v):null;continue}const j=c.properties;if(j&&!m.has(l)&&l in j){c=a(j[l]);continue}const p=c.additionalProperties;if(p&&typeof p=="object"){c=a(p);continue}return null}return c},g=(t,i,c)=>{if(!t)return null;const l={};if(t.description&&(l.description=t.description),t.type&&(l.type=t.type),t.default!==void 0&&(l.default=t.default),t.enum&&(l.enum=t.enum),t.const!==void 0&&(l.const=t.const),t.deprecated&&(l.deprecated=!0),t.examples&&(l.examples=t.examples),t.properties&&(l.knownChildren=Object.keys(t.properties)),c&&(i!=null&&i.includes(c))&&(l.required=!0),t.items&&!Array.isArray(t.items)){const j=a(t.items),p=g(j,void 0,null);p&&(l.itemsInfo=p)}return(t.additionalProperties===!0||typeof t.additionalProperties=="object")&&(l.open=!0),l},d=t=>i=>{if(i.length===0)return g(a(t),void 0,null);const c=o(t,i.slice(0,-1));if(!c)return null;const l=i[i.length-1],j=o(c,[l]);return g(j,c.required,l)},h=t=>{const i=a(t);return Object.keys(i.properties??{})},y=t=>({at:d(t),rootKeys:()=>h(t),rootedAt:i=>{const c=o(t,i);return y(c??{})}});return y(r)}function q(e){return Array.from(new Set(e))}let $=null;function D(){return $||($=B(K)),$}export{G as E,D as s,V as u};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{V as e,b3 as a,a_ as n}from"./index-
|
|
1
|
+
import{V as e,b3 as a,a_ as n}from"./index-t37UuEZu.js";const c=new Set(e.map(t=>t.tag));function l(t){return t.filter(o=>c.has(o))}function s(t,o={}){return{name:t.name,originalName:t.name,description:t.description??"",tools:t.tools,model:t.model??"inherit",effort:t.effort??void 0,color:t.color??"",tags:l(t.tags),projects:t.projects,action:t.action??void 0,actionLabel:t.actionLabel??void 0,title:t.title??void 0,body:t.body,...o}}function d(t,o){const i=a();n([i?`${i}/.claude/agents`:null,t?`${t}/.claude/agents`:null],o)}export{s as a,l as t,d as u};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ap as t,A as f,be as g,aF as d}from"./index-
|
|
1
|
+
import{ap as t,A as f,be as g,aF as d}from"./index-t37UuEZu.js";function j({children:s,right:e}){return t.jsxs("div",{className:"flex items-baseline justify-between mb-2.5",children:[t.jsx("span",{className:"text-[11.5px] font-bold uppercase tracking-[0.8px] text-fg-faint",children:s}),e]})}function y({kind:s="soft",icon:e,children:c,onClick:i,disabled:o,testId:n}){return t.jsxs("button",{type:"button","data-testid":n,onClick:i,disabled:o,className:`shrink-0 inline-flex items-center gap-2 rounded-lg px-3.5 py-2 text-[13px] font-semibold transition-colors disabled:opacity-50 ${s==="soft"?"bg-bg-hi border border-line text-fg hover:bg-bg":"text-fg-dim hover:text-fg"}`,children:[e&&t.jsx(f,{name:e,size:15}),c]})}function N({icon:s,title:e,subtitle:c,badge:i,action:o,testId:n}){return t.jsxs("div",{"data-testid":n,className:"flex items-center gap-3.5 bg-bg-hi border border-line rounded-xl px-[18px] py-3.5",children:[t.jsx("span",{className:"w-[34px] h-[34px] rounded-lg bg-bg border border-line grid place-items-center text-fg-dim shrink-0",children:t.jsx(f,{name:s,size:17})}),t.jsxs("div",{className:"flex-1 min-w-0",children:[t.jsxs("div",{className:"flex items-center gap-2.5 mb-0.5",children:[t.jsx("span",{className:"text-sm font-semibold text-fg truncate",children:e}),i]}),t.jsx("div",{className:"font-mono text-[11.5px] text-fg-faint truncate",children:c})]}),o]})}function v({onClick:s,disabled:e,testId:c,children:i="Revoke"}){return t.jsx("button",{"data-testid":c,onClick:s,disabled:e,className:"shrink-0 rounded-lg border border-line bg-bg text-accent px-3.5 py-1.5 text-[12.5px] font-semibold hover:bg-bg-hi disabled:opacity-50 transition-colors",children:i})}function k({code:s,onConfirm:e,testId:c,codeTestId:i,confirmTestId:o,peer:n="browser"}){return t.jsxs("div",{"data-testid":c,className:"flex items-start gap-3.5 px-5 py-4 rounded-2xl border border-accent/40 bg-accent/[0.06]",children:[t.jsx("span",{className:"w-[38px] h-[38px] rounded-[11px] shrink-0 grid place-items-center bg-bg-hi text-accent",children:t.jsx(f,{name:"shield",size:20})}),t.jsxs("div",{className:"flex-1 min-w-0 text-sm text-fg",children:[t.jsx("strong",{className:"font-semibold",children:"Verify E2E session."})," Compare this code with the ",n," — they must match.",t.jsx("div",{"data-testid":i,className:"my-2 text-3xl font-mono tracking-[0.3em] text-center text-accent select-all",children:s}),t.jsx("p",{className:"text-[12.5px] text-fg-faint mb-3",children:"If the codes match, confirm below. Mutating commands are blocked until confirmed."}),t.jsx("button",{"data-testid":o,onClick:e,className:"rounded-lg bg-accent text-white px-4 py-2 text-[13px] font-semibold hover:opacity-90 transition-opacity",children:"Codes match — confirm"})]})]})}function C({children:s,testId:e}){return t.jsx("div",{"data-testid":e,className:"bg-bg-hi border border-line rounded-xl p-5 text-center text-[13.5px] text-fg-faint italic font-serif",children:s})}const m=s=>(s==null?void 0:s.message)||String(s);function S(s){const e=g(r=>r.show),[c,i]=d.useState([]),o=d.useRef(s);o.current=s;const n=d.useCallback(async()=>{try{i(await window.api.remoteClient.list())}catch(r){e("error",m(r))}},[e]);d.useEffect(()=>{n();const r=window.api.remoteClient.onStatus(a=>{var x;(x=o.current)==null||x.call(o,a),i(l=>l.map(b=>b.peerId===a.peerId?{...b,status:a.status,reason:a.reason,sas:a.sas}:b))});return()=>{r()}},[n]);const p=d.useCallback(async r=>{try{const a=await window.api.remoteClient.connect(r);i(x=>x.map(l=>l.peerId===r?{...l,status:a.status,reason:a.reason,sas:a.sas}:l))}catch(a){e("error",m(a))}},[e]),u=d.useCallback(async r=>{try{const a=await window.api.remoteClient.confirmSas(r);a.ok||e("error",a.reason??"SAS confirmation failed"),await n()}catch(a){e("error",m(a))}},[e,n]),h=d.useCallback(async r=>{try{await window.api.remoteClient.remove(r),await n()}catch(a){e("error",m(a))}},[e,n]);return{peers:c,refresh:n,connect:p,confirmSas:u,revoke:h}}export{C as E,y as P,N as R,k as S,v as a,j as b,S as u};
|
package/dist/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,400&family=Geist:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
10
|
-
<script type="module" crossorigin src="./assets/index-
|
|
10
|
+
<script type="module" crossorigin src="./assets/index-t37UuEZu.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="./assets/monaco-editor-BsIlgG7c.js">
|
|
12
12
|
<link rel="stylesheet" crossorigin href="./assets/monaco-editor-Cu5D__1H.css">
|
|
13
13
|
<link rel="stylesheet" crossorigin href="./assets/index-HpEbvDiE.css">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-session-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.115.0",
|
|
4
4
|
"description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main/index.cjs",
|
package/src/main/build-info.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"gitSha": "
|
|
4
|
-
"gitShortSha": "
|
|
2
|
+
"version": "0.115.0",
|
|
3
|
+
"gitSha": "76b8ec0587142349394786fefc05b0cfa0ef9a66",
|
|
4
|
+
"gitShortSha": "76b8ec05",
|
|
5
5
|
"gitBranch": "HEAD",
|
|
6
|
-
"builtAt": "2026-10-
|
|
6
|
+
"builtAt": "2026-10-13T18:38:06.307Z",
|
|
7
7
|
"dirty": false
|
|
8
8
|
}
|
package/src/main/chatRunner.cjs
CHANGED
|
@@ -966,8 +966,14 @@ function cancel(tabId) {
|
|
|
966
966
|
// directly, reusing send()'s existing queued-vs-immediate branching rather
|
|
967
967
|
// than duplicating it here.
|
|
968
968
|
|
|
969
|
-
function enqueueExternalPrompt(tabId, prompt) {
|
|
970
|
-
|
|
969
|
+
function enqueueExternalPrompt(tabId, prompt, extra = {}) {
|
|
970
|
+
const { sessionId, cwd } = extra ?? {};
|
|
971
|
+
broadcast('chat:external-send', {
|
|
972
|
+
tabId,
|
|
973
|
+
prompt,
|
|
974
|
+
...(sessionId !== undefined ? { sessionId } : {}),
|
|
975
|
+
...(cwd !== undefined ? { cwd } : {}),
|
|
976
|
+
});
|
|
971
977
|
}
|
|
972
978
|
|
|
973
979
|
// Asks the renderer to start a new Session (same launchSession a macro press
|
package/src/main/ipcSchemas.cjs
CHANGED
|
@@ -940,6 +940,21 @@ const chatExternalSend = z.object({
|
|
|
940
940
|
),
|
|
941
941
|
});
|
|
942
942
|
|
|
943
|
+
// Phone drill-down reads. cwd is re-checked against the open tabs in
|
|
944
|
+
// webRemote.cjs; sessionKey becomes a transcript filename lookup so it is
|
|
945
|
+
// restricted to a session-id charset (no '/', so no traversal).
|
|
946
|
+
const remoteProjectSessions = z.object({ cwd: z.string().min(1).max(4096) });
|
|
947
|
+
const remoteSessionTranscript = z.object({
|
|
948
|
+
cwd: z.string().min(1).max(4096),
|
|
949
|
+
sessionKey: z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9_.-]*$/),
|
|
950
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
951
|
+
});
|
|
952
|
+
// One Epic's PRD list (slug/title/status projection only, never bodies).
|
|
953
|
+
const remoteSessionPrds = z.object({
|
|
954
|
+
cwd: z.string().min(1).max(4096),
|
|
955
|
+
epicId: z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9_.-]*$/),
|
|
956
|
+
});
|
|
957
|
+
|
|
943
958
|
// Phone-initiated new Session. cwd/agentName are re-checked against the open
|
|
944
959
|
// tabs / persona list in webRemote.cjs before anything touches the filesystem.
|
|
945
960
|
const remoteSessionCreate = z.object({
|
|
@@ -1163,13 +1178,20 @@ const READ_COMMANDS = new Set([
|
|
|
1163
1178
|
// Sensitive reads — return user data; require SAS confirmation same as MUTATE.
|
|
1164
1179
|
const SAS_GATED_READS = new Set([
|
|
1165
1180
|
'cmd:sessions:load',
|
|
1166
|
-
// Same data
|
|
1181
|
+
// Same data the phone reads on demand (projects + their active Epics).
|
|
1167
1182
|
'cmd:sessions:list',
|
|
1168
1183
|
'cmd:history:aggregate',
|
|
1169
1184
|
// subscribe initiates a live stream of session state/summary — sensitive.
|
|
1170
1185
|
'cmd:session:subscribe',
|
|
1171
1186
|
// Picker data (projects, personas, tags, macros) for the phone's New Session.
|
|
1172
1187
|
'cmd:session:options',
|
|
1188
|
+
// Drill-down: project list, one project's Epics, one session's recent
|
|
1189
|
+
// transcript text — all user data, so SAS-gated.
|
|
1190
|
+
'cmd:projects:list',
|
|
1191
|
+
'cmd:project:sessions',
|
|
1192
|
+
'cmd:session:transcript',
|
|
1193
|
+
// PRD slugs/titles/statuses for one Epic — project work content, so SAS-gated.
|
|
1194
|
+
'cmd:session:prds',
|
|
1173
1195
|
// NOTE: cmd:exchanges:list is intentionally NOT allowlisted — webRemote.cjs has
|
|
1174
1196
|
// no dispatch handler for it, so an allowlist entry would only fail closed with
|
|
1175
1197
|
// an opaque reject. Re-add here together with the handler when remote exchanges
|
|
@@ -1305,6 +1327,9 @@ module.exports = {
|
|
|
1305
1327
|
chatClassifyTicket,
|
|
1306
1328
|
chatExternalSend,
|
|
1307
1329
|
remoteSessionCreate,
|
|
1330
|
+
remoteProjectSessions,
|
|
1331
|
+
remoteSessionTranscript,
|
|
1332
|
+
remoteSessionPrds,
|
|
1308
1333
|
exchangesList,
|
|
1309
1334
|
agentsSavePersona,
|
|
1310
1335
|
macrosSave,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Pure builders for the phone remote's drill-down lists: projects (open tabs),
|
|
4
|
+
// then one project's Epics in every lifecycle status. readIndex is injected
|
|
5
|
+
// (production passes epicMint's readActiveIndex) so this module stays pure.
|
|
6
|
+
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
|
|
9
|
+
const STATUS_RANK = { active: 0, proposed: 1, completed: 2 };
|
|
10
|
+
|
|
11
|
+
function readSessions(readIndex, cwd) {
|
|
12
|
+
try {
|
|
13
|
+
const sessions = readIndex(cwd)?.sessions;
|
|
14
|
+
return sessions && typeof sessions === 'object' ? sessions : {};
|
|
15
|
+
} catch {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function tabTitle(tab) {
|
|
21
|
+
return tab.label || path.basename(tab.cwd);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function validTabs(tabs) {
|
|
25
|
+
return (Array.isArray(tabs) ? tabs : []).filter((t) => t && typeof t.cwd === 'string' && t.cwd);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function toMs(value) {
|
|
29
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : null;
|
|
30
|
+
if (typeof value === 'string' && value) {
|
|
31
|
+
const ms = Date.parse(value);
|
|
32
|
+
return Number.isNaN(ms) ? null : ms;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function epicUpdatedAt(epic) {
|
|
38
|
+
for (const v of [epic.updatedAt, epic.completedAt, epic.createdAt]) {
|
|
39
|
+
if (v !== undefined && v !== null && v !== '') return toMs(v);
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function listRemoteProjects(tabs, readIndex) {
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const tab of validTabs(tabs)) {
|
|
48
|
+
if (seen.has(tab.cwd)) continue;
|
|
49
|
+
seen.add(tab.cwd);
|
|
50
|
+
const counts = { proposed: 0, active: 0, completed: 0 };
|
|
51
|
+
for (const epic of Object.values(readSessions(readIndex, tab.cwd))) {
|
|
52
|
+
if (epic && Object.prototype.hasOwnProperty.call(counts, epic.status)) counts[epic.status] += 1;
|
|
53
|
+
}
|
|
54
|
+
out.push({ cwd: tab.cwd, title: tabTitle(tab), mainTabId: tab.id ?? tab.sessionId, counts });
|
|
55
|
+
}
|
|
56
|
+
return out.sort((a, b) => a.title.localeCompare(b.title));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function listRemoteProjectSessions(tabs, readIndex, cwd, { completedLimit = 20 } = {}) {
|
|
60
|
+
const tab = validTabs(tabs).find((t) => t.cwd === cwd);
|
|
61
|
+
if (!tab) return null;
|
|
62
|
+
const project = { cwd: tab.cwd, title: tabTitle(tab), mainTabId: tab.id ?? tab.sessionId };
|
|
63
|
+
const rows = [];
|
|
64
|
+
for (const epic of Object.values(readSessions(readIndex, tab.cwd))) {
|
|
65
|
+
if (!epic || !Object.prototype.hasOwnProperty.call(STATUS_RANK, epic.status)) continue;
|
|
66
|
+
const firstLine = typeof epic.goalText === 'string' ? epic.goalText.split('\n')[0].trim().slice(0, 80).trim() : '';
|
|
67
|
+
rows.push({
|
|
68
|
+
epicId: epic.id,
|
|
69
|
+
claudeSessionId: typeof epic.claudeSessionId === 'string' && epic.claudeSessionId ? epic.claudeSessionId : null,
|
|
70
|
+
title: firstLine || epic.id,
|
|
71
|
+
status: epic.status,
|
|
72
|
+
tag: epic.tag,
|
|
73
|
+
agentType: epic.agentType,
|
|
74
|
+
spawnCwd: epic.worktree?.dir || epic.cwd || tab.cwd,
|
|
75
|
+
updatedAt: epicUpdatedAt(epic),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
rows.sort((a, b) => {
|
|
79
|
+
const r = STATUS_RANK[a.status] - STATUS_RANK[b.status];
|
|
80
|
+
if (r !== 0) return r;
|
|
81
|
+
if (a.updatedAt === b.updatedAt) return 0;
|
|
82
|
+
if (a.updatedAt === null) return 1;
|
|
83
|
+
if (b.updatedAt === null) return -1;
|
|
84
|
+
return b.updatedAt - a.updatedAt;
|
|
85
|
+
});
|
|
86
|
+
let completed = 0;
|
|
87
|
+
const sessions = rows.filter((s) => s.status !== 'completed' || ++completed <= completedLimit);
|
|
88
|
+
return { project, sessions };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { listRemoteProjects, listRemoteProjectSessions };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Bounded-tail reader for Claude Code JSONL transcripts: the last N human-readable
|
|
4
|
+
// chat messages (user prompts + assistant text) without reading the whole file.
|
|
5
|
+
// Top-level (no domain fits: epic/ transcripts are path-only; util/ is domain-free).
|
|
6
|
+
// Transcripts reach hundreds of MB, so cost is O(maxBytes), never O(file).
|
|
7
|
+
|
|
8
|
+
const fsp = require('node:fs/promises');
|
|
9
|
+
|
|
10
|
+
const DEFAULT_LIMIT = 30;
|
|
11
|
+
const MAX_LIMIT = 100;
|
|
12
|
+
const DEFAULT_MAX_BYTES = 524288;
|
|
13
|
+
const MAX_TEXT_CHARS = 4000;
|
|
14
|
+
|
|
15
|
+
/** Join the plain-text parts of a message `content` (string or block array). */
|
|
16
|
+
function textOf(content) {
|
|
17
|
+
if (typeof content === 'string') return content;
|
|
18
|
+
if (!Array.isArray(content)) return '';
|
|
19
|
+
return content
|
|
20
|
+
.filter((b) => b && b.type === 'text' && typeof b.text === 'string')
|
|
21
|
+
.map((b) => b.text)
|
|
22
|
+
.join('\n');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function truncate(text) {
|
|
26
|
+
return text.length > MAX_TEXT_CHARS ? text.slice(0, MAX_TEXT_CHARS - 1) + '…' : text;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Parse one JSONL line into a chat message, or null when it is not one. */
|
|
30
|
+
function toChatMessage(line) {
|
|
31
|
+
let entry;
|
|
32
|
+
try {
|
|
33
|
+
entry = JSON.parse(line);
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
38
|
+
if (entry.isMeta || entry.isSidechain) return null;
|
|
39
|
+
const role = entry.type;
|
|
40
|
+
if (role !== 'user' && role !== 'assistant') return null;
|
|
41
|
+
const text = textOf(entry.message?.content).trim();
|
|
42
|
+
if (!text) return null;
|
|
43
|
+
return {
|
|
44
|
+
role,
|
|
45
|
+
text: truncate(text),
|
|
46
|
+
ts: typeof entry.timestamp === 'string' ? entry.timestamp : null,
|
|
47
|
+
uuid: typeof entry.uuid === 'string' ? entry.uuid : null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} filePath
|
|
53
|
+
* @param {{ limit?: number, maxBytes?: number }} [opts]
|
|
54
|
+
* @returns {Promise<Array<{ role: 'user'|'assistant', text: string, ts: string|null, uuid: string|null }>>}
|
|
55
|
+
* oldest first; [] on any read failure.
|
|
56
|
+
*/
|
|
57
|
+
async function readRecentChatMessages(filePath, { limit = DEFAULT_LIMIT, maxBytes = DEFAULT_MAX_BYTES } = {}) {
|
|
58
|
+
if (typeof filePath !== 'string' || !filePath) return [];
|
|
59
|
+
const n = Math.floor(Number(limit));
|
|
60
|
+
const lim = Number.isFinite(n) ? Math.min(MAX_LIMIT, Math.max(1, n)) : DEFAULT_LIMIT;
|
|
61
|
+
const cap = Math.max(1, Math.floor(Number(maxBytes)) || DEFAULT_MAX_BYTES);
|
|
62
|
+
let fd;
|
|
63
|
+
try {
|
|
64
|
+
fd = await fsp.open(filePath, 'r');
|
|
65
|
+
const { size } = await fd.stat();
|
|
66
|
+
if (size === 0) return [];
|
|
67
|
+
const start = Math.max(0, size - cap);
|
|
68
|
+
// From mid-file, read one extra leading byte so the first split part is
|
|
69
|
+
// always a fragment (empty when start sits on a line boundary) — drop it.
|
|
70
|
+
const from = start > 0 ? start - 1 : 0;
|
|
71
|
+
const len = size - from;
|
|
72
|
+
const buf = Buffer.alloc(len);
|
|
73
|
+
const { bytesRead } = await fd.read(buf, 0, len, from);
|
|
74
|
+
const parts = buf.toString('utf8', 0, bytesRead).split('\n');
|
|
75
|
+
if (start > 0) parts.shift();
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const line of parts) {
|
|
78
|
+
if (!line) continue;
|
|
79
|
+
const msg = toChatMessage(line);
|
|
80
|
+
if (msg) out.push(msg);
|
|
81
|
+
}
|
|
82
|
+
return out.slice(-lim);
|
|
83
|
+
} catch {
|
|
84
|
+
return [];
|
|
85
|
+
} finally {
|
|
86
|
+
await fd?.close().catch(() => {});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { readRecentChatMessages };
|
package/src/main/webRemote.cjs
CHANGED
|
@@ -436,10 +436,6 @@ async function connect() {
|
|
|
436
436
|
broadcastStatus();
|
|
437
437
|
}).catch(() => {});
|
|
438
438
|
broadcastStatus();
|
|
439
|
-
// v2: begin pushing the live session list once connected.
|
|
440
|
-
// Reset diff-guard so the new client always receives a full session-list push.
|
|
441
|
-
for (const c of _clients.values()) c.lastSessionListJson = null;
|
|
442
|
-
startSessionListPush();
|
|
443
439
|
});
|
|
444
440
|
|
|
445
441
|
ws.on('message', (raw) => {
|
|
@@ -500,11 +496,9 @@ async function handleTokenRevoked(deviceId) {
|
|
|
500
496
|
// The last completed assistant turn drives the Haiku summary (see maybeSummarize).
|
|
501
497
|
|
|
502
498
|
const SESSION_POLL_MS = 1500;
|
|
503
|
-
const SESSION_LIST_PUSH_MS = 5000;
|
|
504
499
|
const SESSION_INIT_TAIL_BYTES = 512 * 1024; // bound the initial read
|
|
505
500
|
|
|
506
501
|
const _sessionWatchers = new Map(); // tabId → watcher
|
|
507
|
-
let _sessionListTimer = null;
|
|
508
502
|
|
|
509
503
|
/** Push an unsolicited event to ONE client, E2E-encrypted with its key and unicast via toConn. */
|
|
510
504
|
function pushEventTo(client, type, payload) {
|
|
@@ -691,7 +685,6 @@ function stopSessionWatch(tabId) {
|
|
|
691
685
|
|
|
692
686
|
function stopAllSessionWatches() {
|
|
693
687
|
for (const tabId of Array.from(_sessionWatchers.keys())) stopSessionWatch(tabId);
|
|
694
|
-
if (_sessionListTimer) { clearInterval(_sessionListTimer); _sessionListTimer = null; }
|
|
695
688
|
}
|
|
696
689
|
|
|
697
690
|
/**
|
|
@@ -729,47 +722,6 @@ function buildRemoteSessionList(tabs, readIndex) {
|
|
|
729
722
|
return out;
|
|
730
723
|
}
|
|
731
724
|
|
|
732
|
-
/** Push the current session list (reuses sessionsStore — the canonical source). */
|
|
733
|
-
async function pushSessionList() {
|
|
734
|
-
try {
|
|
735
|
-
// Honor the kill switch: when remote is disabled, push nothing (the project
|
|
736
|
-
// list = cwds/titles is sensitive). dispatchEnvelope already blocks cmd:*;
|
|
737
|
-
// this stops the unsolicited background push too.
|
|
738
|
-
const cfg = await loadConfig();
|
|
739
|
-
if (!cfg.remoteEnabled) return;
|
|
740
|
-
// Don't push before SAS is confirmed — session cwds/titles are sensitive user data.
|
|
741
|
-
// A relay that completes e2e:hello before the user confirms the SAS would otherwise
|
|
742
|
-
// receive the full session list immediately (same threat SAS_GATED_READS blocks for
|
|
743
|
-
// cmd:sessions:load). Guard here so client.lastSessionListJson is not poisoned either.
|
|
744
|
-
if (!anyAuthenticated()) return;
|
|
745
|
-
const sessionsStore = require('./sessionsStore.cjs');
|
|
746
|
-
const data = await sessionsStore.load();
|
|
747
|
-
// Tabs + their ACTIVE Epics → SessionMeta. tabId === claude sessionId so it
|
|
748
|
-
// matches the transcript JSONL name used by cmd:session:subscribe.
|
|
749
|
-
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
750
|
-
const sessions = buildRemoteSessionList(data?.tabs ?? [], readActiveIndex).map((s) => ({
|
|
751
|
-
...s,
|
|
752
|
-
state: _sessionWatchers.get(s.tabId)?.state ?? null,
|
|
753
|
-
}));
|
|
754
|
-
const payload = { sessions, activeTabId: data?.activeTabId ?? null };
|
|
755
|
-
const json = JSON.stringify(payload);
|
|
756
|
-
for (const c of _clients.authenticated()) {
|
|
757
|
-
if (json === c.lastSessionListJson) continue;
|
|
758
|
-
c.lastSessionListJson = json;
|
|
759
|
-
pushEventTo(c, 'event:session:list', payload);
|
|
760
|
-
}
|
|
761
|
-
} catch (e) {
|
|
762
|
-
logs.writeLine({ scope: 'webRemote', level: 'warn', message: 'pushSessionList failed', meta: { error: e?.message } });
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
function startSessionListPush() {
|
|
767
|
-
if (_sessionListTimer) return;
|
|
768
|
-
pushSessionList().catch(() => {});
|
|
769
|
-
_sessionListTimer = setInterval(() => pushSessionList().catch(() => {}), SESSION_LIST_PUSH_MS);
|
|
770
|
-
if (typeof _sessionListTimer.unref === 'function') _sessionListTimer.unref();
|
|
771
|
-
}
|
|
772
|
-
|
|
773
725
|
// ─── SM-V2-03: mobile summary via Claude Haiku 4.5 ───────────────────────────
|
|
774
726
|
// Summarization logic lives in lib/summarize.cjs (shared with exchanges.cjs).
|
|
775
727
|
|
|
@@ -811,14 +763,12 @@ async function maybeSummarize(w) {
|
|
|
811
763
|
/**
|
|
812
764
|
* Shared finish-up for any path that transitions a client's e2e state into 'authenticated'
|
|
813
765
|
* (manual SAS confirmation or pinned-key auto-trust): log, broadcast the new
|
|
814
|
-
* status, and
|
|
815
|
-
* next SESSION_LIST_PUSH_MS tick.
|
|
766
|
+
* status, and tell that one client it may start requesting (event:remote:ready).
|
|
816
767
|
*/
|
|
817
768
|
function finalizeE2eAuthenticated(logMessage, client) {
|
|
818
769
|
logs.writeLine({ scope: 'webRemote', level: 'info', message: logMessage });
|
|
819
770
|
broadcastStatus();
|
|
820
|
-
if (client) client.
|
|
821
|
-
pushSessionList().catch(() => {});
|
|
771
|
+
if (client && loadConfigSync().remoteEnabled) pushEventTo(client, 'event:remote:ready', { ts: Date.now() });
|
|
822
772
|
}
|
|
823
773
|
|
|
824
774
|
// ─── Message handling & command dispatch ─────────────────────────────────────
|
|
@@ -1111,6 +1061,70 @@ function getDispatchMap() {
|
|
|
1111
1061
|
};
|
|
1112
1062
|
},
|
|
1113
1063
|
|
|
1064
|
+
'cmd:projects:list': async () => {
|
|
1065
|
+
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
1066
|
+
const { listRemoteProjects } = require('./lib/remoteDrilldown.cjs');
|
|
1067
|
+
const data = await sessionsStore.load();
|
|
1068
|
+
return { projects: listRemoteProjects(data?.tabs ?? [], readActiveIndex) };
|
|
1069
|
+
},
|
|
1070
|
+
|
|
1071
|
+
'cmd:project:sessions': async (payload) => {
|
|
1072
|
+
const parsed = schemas.remoteProjectSessions.parse(payload);
|
|
1073
|
+
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
1074
|
+
const { listRemoteProjectSessions } = require('./lib/remoteDrilldown.cjs');
|
|
1075
|
+
const data = await sessionsStore.load();
|
|
1076
|
+
const result = listRemoteProjectSessions(data?.tabs ?? [], readActiveIndex, parsed.cwd);
|
|
1077
|
+
if (!result) throw new Error('cwd is not an open project');
|
|
1078
|
+
return result;
|
|
1079
|
+
},
|
|
1080
|
+
|
|
1081
|
+
// cwd must be exactly an open tab's cwd; sessionKey is the tab's main id or
|
|
1082
|
+
// an Epic id / claudeSessionId from that project's active index.
|
|
1083
|
+
'cmd:session:transcript': async (payload) => {
|
|
1084
|
+
const parsed = schemas.remoteSessionTranscript.parse(payload);
|
|
1085
|
+
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
1086
|
+
const { resolveEpicTranscriptPath } = require('./lib/epic/epicTranscriptPath.cjs');
|
|
1087
|
+
const { readRecentChatMessages } = require('./lib/remoteTranscriptTail.cjs');
|
|
1088
|
+
const data = await sessionsStore.load();
|
|
1089
|
+
const tab = (data?.tabs ?? []).find((t) => t && t.cwd === parsed.cwd);
|
|
1090
|
+
if (!tab) throw new Error('cwd is not an open project');
|
|
1091
|
+
validatePath(parsed.cwd);
|
|
1092
|
+
let filePath = null;
|
|
1093
|
+
if (parsed.sessionKey === (tab.id ?? tab.sessionId)) {
|
|
1094
|
+
filePath = require('./transcripts.cjs').transcriptPath(parsed.cwd, parsed.sessionKey);
|
|
1095
|
+
} else {
|
|
1096
|
+
const sessions = readActiveIndex(parsed.cwd)?.sessions ?? {};
|
|
1097
|
+
const epic = Object.values(sessions).find(
|
|
1098
|
+
(e) => e && (e.id === parsed.sessionKey || e.claudeSessionId === parsed.sessionKey),
|
|
1099
|
+
);
|
|
1100
|
+
if (epic && epic.claudeSessionId) {
|
|
1101
|
+
filePath = resolveEpicTranscriptPath({ cwd: parsed.cwd, claudeSessionId: epic.claudeSessionId }).path;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (!filePath) return { messages: [] };
|
|
1105
|
+
validatePath(filePath);
|
|
1106
|
+
return { messages: await readRecentChatMessages(filePath, { limit: parsed.limit }) };
|
|
1107
|
+
},
|
|
1108
|
+
|
|
1109
|
+
'cmd:session:prds': async (payload) => {
|
|
1110
|
+
const parsed = schemas.remoteSessionPrds.parse(payload);
|
|
1111
|
+
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
1112
|
+
const data = await sessionsStore.load();
|
|
1113
|
+
const tab = (data?.tabs ?? []).find((t) => t && t.cwd === parsed.cwd);
|
|
1114
|
+
if (!tab) throw new Error('cwd is not an open project');
|
|
1115
|
+
validatePath(parsed.cwd);
|
|
1116
|
+
const sessions = readActiveIndex(parsed.cwd)?.sessions ?? {};
|
|
1117
|
+
const known = Object.values(sessions).some((e) => e && e.id === parsed.epicId) || !!sessions[parsed.epicId];
|
|
1118
|
+
if (!known) throw new Error('unknown epic');
|
|
1119
|
+
const all = await require('./scheduler.cjs').listPrdsInternal();
|
|
1120
|
+
const prds = all
|
|
1121
|
+
.filter((e) => e && e.epicId === parsed.epicId)
|
|
1122
|
+
.map((e) => ({ slug: e.slug, title: e.title, status: e.status ?? null, archived: !!e.archived }))
|
|
1123
|
+
.sort((a, b) => Number(a.archived) - Number(b.archived) || String(a.slug).localeCompare(String(b.slug)))
|
|
1124
|
+
.slice(0, 50);
|
|
1125
|
+
return { prds };
|
|
1126
|
+
},
|
|
1127
|
+
|
|
1114
1128
|
'cmd:sessions:save': async (payload) => {
|
|
1115
1129
|
const parsed = schemas.sessionsPayload.parse(payload);
|
|
1116
1130
|
return sessionsStore.save(parsed);
|
|
@@ -1200,8 +1214,27 @@ function getDispatchMap() {
|
|
|
1200
1214
|
// already runs inside the Electron main process (PRD 753).
|
|
1201
1215
|
'cmd:chat:send': async (payload) => {
|
|
1202
1216
|
const parsed = schemas.chatExternalSend.parse(payload);
|
|
1203
|
-
|
|
1204
|
-
|
|
1217
|
+
const { readActiveIndex } = require('./lib/epic/epicMint.cjs');
|
|
1218
|
+
const data = await sessionsStore.load();
|
|
1219
|
+
const tabs = data?.tabs ?? [];
|
|
1220
|
+
// Resolve the target in main: an open tab, or an ACTIVE Epic (by id or
|
|
1221
|
+
// claudeSessionId) of an open project. Anything else fails loudly.
|
|
1222
|
+
if (tabs.some((t) => t.sessionId === parsed.tabId || t.id === parsed.tabId)) {
|
|
1223
|
+
chatRunner.enqueueExternalPrompt(parsed.tabId, parsed.prompt);
|
|
1224
|
+
return { ok: true };
|
|
1225
|
+
}
|
|
1226
|
+
for (const tab of tabs) {
|
|
1227
|
+
const sessions = Object.values(readActiveIndex(tab.cwd)?.sessions ?? {});
|
|
1228
|
+
const epic = sessions.find((e) => e && (e.id === parsed.tabId || e.claudeSessionId === parsed.tabId));
|
|
1229
|
+
if (!epic) continue;
|
|
1230
|
+
if (epic.status !== 'active' || !epic.claudeSessionId) throw new Error('target is not an active session');
|
|
1231
|
+
chatRunner.enqueueExternalPrompt(epic.id, parsed.prompt, {
|
|
1232
|
+
sessionId: epic.claudeSessionId,
|
|
1233
|
+
cwd: epic.cwd || tab.cwd,
|
|
1234
|
+
});
|
|
1235
|
+
return { ok: true };
|
|
1236
|
+
}
|
|
1237
|
+
throw new Error('unknown chat target');
|
|
1205
1238
|
},
|
|
1206
1239
|
|
|
1207
1240
|
'cmd:session:options': async () => {
|
|
@@ -1383,9 +1416,6 @@ function registerRemoteHandlers() {
|
|
|
1383
1416
|
logs.writeLine({ scope: 'webRemote', level: 'warn', message: 'failed to persist pinned peer pubkey', meta: { error: e?.message } });
|
|
1384
1417
|
}
|
|
1385
1418
|
}
|
|
1386
|
-
// Flush the session list immediately — the push loop was suppressed while
|
|
1387
|
-
// state !== 'authenticated', so the mobile app would otherwise wait up to
|
|
1388
|
-
// SESSION_LIST_PUSH_MS for the first useful data.
|
|
1389
1419
|
finalizeE2eAuthenticated('E2E session authenticated — SAS confirmed by user', target);
|
|
1390
1420
|
return { ok: true };
|
|
1391
1421
|
}));
|
|
@@ -1499,6 +1529,9 @@ module.exports = {
|
|
|
1499
1529
|
setE2eAuthenticated: (key, clientKey = LEGACY_CLIENT_KEY) => {
|
|
1500
1530
|
_clients.getOrCreate(clientKey).e2e = makeState('authenticated', key, null);
|
|
1501
1531
|
},
|
|
1532
|
+
setConfigCache: (cfg) => { _configCache = { ...defaultConfig(), ...cfg }; _configCacheAt = Date.now(); },
|
|
1533
|
+
finalizeE2eAuthenticated,
|
|
1534
|
+
getClient: (clientKey = LEGACY_CLIENT_KEY) => _clients.getOrCreate(clientKey),
|
|
1502
1535
|
revokeAllDevices,
|
|
1503
1536
|
streamCount: () => _clients.values().reduce((n, c) => n + c.ptyStreams.size, 0),
|
|
1504
1537
|
},
|
package/src/preload/api.d.ts
CHANGED
|
@@ -1598,6 +1598,9 @@ export interface ChatRunNoticeEvent {
|
|
|
1598
1598
|
export interface ChatExternalSendEvent {
|
|
1599
1599
|
tabId: string;
|
|
1600
1600
|
prompt: string;
|
|
1601
|
+
/** Epic's claudeSessionId + project cwd, resolved by main so the renderer can deliver before the Epic's project is loaded. */
|
|
1602
|
+
sessionId?: string;
|
|
1603
|
+
cwd?: string;
|
|
1601
1604
|
}
|
|
1602
1605
|
|
|
1603
1606
|
export type CreateProjectFolderResult =
|