claude-code-session-manager 0.114.0 → 0.116.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.
Files changed (54) hide show
  1. package/dist/assets/{AgentLibrary-DZFM7Lto.js → AgentLibrary-lXvlmehy.js} +1 -1
  2. package/dist/assets/{Button-BJVFKdGH.js → Button-C6ZxRSs0.js} +1 -1
  3. package/dist/assets/{DataModel-CebvcP_y.js → DataModel-CRNjAcGi.js} +1 -1
  4. package/dist/assets/{History-BwISEUhR.js → History-CBIYR5DT.js} +2 -2
  5. package/dist/assets/{Hooks-Dac3smYP.js → Hooks-DClb8etC.js} +3 -3
  6. package/dist/assets/{Library-DTjsUr9W.js → Library-CB61goZg.js} +7 -7
  7. package/dist/assets/{ListDetail-B1L5TbjS.js → ListDetail-6mP7on1x.js} +1 -1
  8. package/dist/assets/{McpServers-CBjGCMg0.js → McpServers-CgEhkk0x.js} +1 -1
  9. package/dist/assets/Memory-ISEb5yPN.js +18 -0
  10. package/dist/assets/{Panel-BQkkkjex.js → Panel-DRVTJ70d.js} +1 -1
  11. package/dist/assets/{Permissions-CaACwY0B.js → Permissions-crk6UxUN.js} +3 -3
  12. package/dist/assets/Plugins-DnqP6tpm.js +2 -0
  13. package/dist/assets/{ProvenanceBadge--N5j8xiK.js → ProvenanceBadge-DK2dwSU7.js} +1 -1
  14. package/dist/assets/{RemoteMachines-WjWaDHEA.js → RemoteMachines-7d2sUA_3.js} +1 -1
  15. package/dist/assets/{SaveBar-DFmOjb4_.js → SaveBar-DO1z0hF3.js} +1 -1
  16. package/dist/assets/{Scheduler-Miap6_aY.js → Scheduler-BqFJU2sP.js} +1 -1
  17. package/dist/assets/{ScopeSwitcher-CiEujp4L.js → ScopeSwitcher-BVlm3X8m.js} +1 -1
  18. package/dist/assets/{Settings-UGpW4P0j.js → Settings-CtVzhDIC.js} +4 -4
  19. package/dist/assets/{SkillReferenceGraph-BgyAnxGW.js → SkillReferenceGraph-DeLStEUI.js} +1 -1
  20. package/dist/assets/Skills-D3tIYqb5.js +2 -0
  21. package/dist/assets/SystemPrompt-vpwQTYnh.js +1 -0
  22. package/dist/assets/TagLibrary-D0PZAUh5.js +1 -0
  23. package/dist/assets/{TiptapBody-BtOcrdRR.js → TiptapBody-CmYsaMda.js} +1 -1
  24. package/dist/assets/{Toggle-Chri4G19.js → Toggle-CveuKhe8.js} +1 -1
  25. package/dist/assets/{ViewTabs-BIenDGVv.js → ViewTabs-18pC8oXm.js} +1 -1
  26. package/dist/assets/{historyTotals-CX8mUxJY.js → historyTotals-Bg6D8W2m.js} +1 -1
  27. package/dist/assets/{index-HpEbvDiE.css → index-CZsDDyLy.css} +1 -1
  28. package/dist/assets/{index-AVrhGmUO.js → index-iwwBBg_v.js} +73 -73
  29. package/dist/assets/{settingsSchema-uqwlznbH.js → settingsSchema-DLcTX-tX.js} +1 -1
  30. package/dist/assets/skillFrontmatter-Css435Xo.js +10 -0
  31. package/dist/assets/{useAgentsDirWatch-BaezRtWw.js → useAgentsDirWatch-BYEGo9_9.js} +1 -1
  32. package/dist/assets/{useRemotePeers-BuFbBqDt.js → useRemotePeers-DrjfyULz.js} +1 -1
  33. package/dist/index.html +2 -2
  34. package/package.json +2 -1
  35. package/scripts/README.md +2 -0
  36. package/src/main/build-info.json +4 -4
  37. package/src/main/chatRunner.cjs +8 -2
  38. package/src/main/ipcSchemas.cjs +52 -1
  39. package/src/main/lib/memoryOps.cjs +116 -0
  40. package/src/main/lib/memoryStale.cjs +24 -1
  41. package/src/main/lib/remoteDrilldown.cjs +91 -0
  42. package/src/main/lib/remoteTranscriptTail.cjs +90 -0
  43. package/src/main/memoryAssist.cjs +156 -0
  44. package/src/main/memoryTool.cjs +34 -0
  45. package/src/main/webRemote.cjs +142 -80
  46. package/src/preload/api.d.ts +22 -0
  47. package/src/preload/index.cjs +7 -0
  48. package/dist/assets/MarkdownEditor-BFRhWZD4.js +0 -1
  49. package/dist/assets/Memory-DdLi5fqe.js +0 -8
  50. package/dist/assets/Plugins-DJeBwVoh.js +0 -2
  51. package/dist/assets/Skills-BQUpnNfx.js +0 -2
  52. package/dist/assets/SystemPrompt-CQjOgHGT.js +0 -1
  53. package/dist/assets/TagLibrary-CTVpoLl0.js +0 -1
  54. package/dist/assets/skillFrontmatter-Dif5JIg7.js +0 -10
@@ -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{aE as b,ao as n,S as N,aZ as O}from"./index-iwwBBg_v.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};
@@ -0,0 +1,10 @@
1
+ import{ao as k,F as E}from"./index-iwwBBg_v.js";function M({value:t,onChange:n,path:e,readOnly:i}){return k.jsx(E,{path:`file://${e}`,defaultLanguage:"markdown",value:t,onChange:s=>n(s??""),theme:"vs-dark",options:{minimap:{enabled:!1},fontSize:13,fontFamily:"JetBrains Mono, ui-monospace, Menlo, monospace",lineNumbers:"on",renderLineHighlight:"line",scrollBeyondLastLine:!1,tabSize:2,wordWrap:"on",readOnly:i,automaticLayout:!0}})}const f=/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/,u="disable-model-invocation",g=/^\s*disable-model-invocation\s*:/;function j(t){const n=t.match(f);if(!n)return!1;for(const e of n[1].split(/\r?\n/)){if(!g.test(e))continue;const i=e.slice(e.indexOf(":")+1).trim().toLowerCase();return i==="true"||i==="yes"}return!1}function S(t){const n=t.trim();return n.length>=2&&(n[0]==='"'&&n[n.length-1]==='"'||n[0]==="'"&&n[n.length-1]==="'")?n.slice(1,-1):n}function h(t){return t.length-t.trimStart().length}function v(t){const n=t.match(f);if(!n)return{name:null,description:null,body:t};const e=n[1].split(/\r?\n/),i=n[2]??"";let s=null,r=null;for(let o=0;o<e.length;o++){const m=e[o],d=m.match(/^(\s*)(name|description)\s*:(.*)$/);if(!d)continue;const[,,y,$]=d,l=$.trim(),b=h(m);let c;if(l===""||l===">-"||l===">"||l==="|-"||l==="|"){const p=[];let a=o+1;for(;a<e.length&&e[a].trim()!==""&&h(e[a])>b;)p.push(e[a].trim()),a++;c=p.join(" ")}else c=S(l);y==="name"?s=c:r=c}return{name:s,description:r,body:i}}function w(t,n){const e=t.match(f);if(!e){if(!n)return t;const o=t.startsWith(`
2
+ `)?"":`
3
+ `;return`---
4
+ ${u}: true
5
+ ---
6
+ ${o}${t}`}const i=e[2]??"",s=e[1].split(/\r?\n/),r=s.findIndex(o=>g.test(o));if(n)r>=0?s[r]=`${u}: true`:s.push(`${u}: true`);else{if(r<0)return t;s.splice(r,1)}return`---
7
+ ${s.join(`
8
+ `)}
9
+ ---
10
+ ${i}`}export{M,v as p,j as r,w as s};
@@ -1 +1 @@
1
- import{V as e,b3 as a,a_ as n}from"./index-AVrhGmUO.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
+ import{V as e,b3 as a,a_ as n}from"./index-iwwBBg_v.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-AVrhGmUO.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};
1
+ import{ao as t,A as f,be as g,aE as d}from"./index-iwwBBg_v.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,10 +7,10 @@
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-AVrhGmUO.js"></script>
10
+ <script type="module" crossorigin src="./assets/index-iwwBBg_v.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
- <link rel="stylesheet" crossorigin href="./assets/index-HpEbvDiE.css">
13
+ <link rel="stylesheet" crossorigin href="./assets/index-CZsDDyLy.css">
14
14
  </head>
15
15
  <body class="bg-bg text-fg font-sans antialiased">
16
16
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-session-manager",
3
- "version": "0.114.0",
3
+ "version": "0.116.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",
@@ -50,6 +50,7 @@
50
50
  "test:unit:stability": "bash scripts/test-unit-stability.sh",
51
51
  "bench": "node scripts/bench/main-bench.cjs",
52
52
  "bench:renderer": "vitest run --config vitest.bench.config.ts",
53
+ "preview:memory": "node scripts/memory-explorer-shots.mjs",
53
54
  "test:e2e": "xvfb-run -a playwright test",
54
55
  "smoke:darwin": "playwright test tests/smoke/darwin-boot.spec.ts",
55
56
  "smoke:packaged": "playwright test tests/smoke/packaged-boot.spec.ts",
package/scripts/README.md CHANGED
@@ -64,3 +64,5 @@ Bench numbers are machine-relative — compare runs on the same machine only; th
64
64
  ## Tests
65
65
 
66
66
  New tests under `scripts/` must be hand-registered in `vitest.config.ts` (see [tests/README.md](../tests/README.md)).
67
+
68
+ `memory-explorer-shots.mjs [outDir]` (`npm run preview:memory`) — dev-only: serves `src/renderer/preview/memory-explorer.html` with Vite, drives 13 Memory Explorer states in plain Chromium (no Electron), writes `NN-name.png` + `compare.html` against `design-mocks/memory-explorer/renders/`.
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "0.114.0",
3
- "gitSha": "f126cf4dacd500ba8561b705d295b48d6601c008",
4
- "gitShortSha": "f126cf4d",
2
+ "version": "0.116.0",
3
+ "gitSha": "c50d3ad9d419f673ef92e6b5030bc77da0077958",
4
+ "gitShortSha": "c50d3ad9",
5
5
  "gitBranch": "HEAD",
6
- "builtAt": "2026-10-13T15:58:38.587Z",
6
+ "builtAt": "2026-10-13T21:00:58.462Z",
7
7
  "dirty": false
8
8
  }
@@ -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
- broadcast('chat:external-send', { tabId, prompt });
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
@@ -687,6 +687,26 @@ const memoryStale = z.object({
687
687
  agent: MEMORY_AGENT,
688
688
  }).strict();
689
689
 
690
+ // memory:verify/archive/restore/listArchived/ask/proposeFix — Memory Explorer actions.
691
+ const memoryNamed = z.object({
692
+ workspace: z.string().regex(MEMORY_WORKSPACE_RE).optional(),
693
+ name: z.string().regex(MEMORY_SLUG_RE),
694
+ cwd: MEMORY_CWD,
695
+ agent: MEMORY_AGENT,
696
+ }).strict();
697
+ const memoryVerify = memoryNamed;
698
+ const memoryArchive = memoryNamed;
699
+ const memoryRestore = memoryNamed;
700
+ const memoryProposeFix = memoryNamed;
701
+ const memoryListArchived = memoryList;
702
+ const memoryAsk = z.object({
703
+ workspace: z.string().regex(MEMORY_WORKSPACE_RE).optional(),
704
+ question: z.string().min(1).max(4000),
705
+ contextName: z.string().regex(MEMORY_SLUG_RE).optional(),
706
+ cwd: MEMORY_CWD,
707
+ agent: MEMORY_AGENT,
708
+ }).strict();
709
+
690
710
  const memoryAgents = z.object({
691
711
  cwd: MEMORY_CWD,
692
712
  }).strict();
@@ -940,6 +960,21 @@ const chatExternalSend = z.object({
940
960
  ),
941
961
  });
942
962
 
963
+ // Phone drill-down reads. cwd is re-checked against the open tabs in
964
+ // webRemote.cjs; sessionKey becomes a transcript filename lookup so it is
965
+ // restricted to a session-id charset (no '/', so no traversal).
966
+ const remoteProjectSessions = z.object({ cwd: z.string().min(1).max(4096) });
967
+ const remoteSessionTranscript = z.object({
968
+ cwd: z.string().min(1).max(4096),
969
+ sessionKey: z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9_.-]*$/),
970
+ limit: z.number().int().min(1).max(100).optional(),
971
+ });
972
+ // One Epic's PRD list (slug/title/status projection only, never bodies).
973
+ const remoteSessionPrds = z.object({
974
+ cwd: z.string().min(1).max(4096),
975
+ epicId: z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9_.-]*$/),
976
+ });
977
+
943
978
  // Phone-initiated new Session. cwd/agentName are re-checked against the open
944
979
  // tabs / persona list in webRemote.cjs before anything touches the filesystem.
945
980
  const remoteSessionCreate = z.object({
@@ -1163,13 +1198,20 @@ const READ_COMMANDS = new Set([
1163
1198
  // Sensitive reads — return user data; require SAS confirmation same as MUTATE.
1164
1199
  const SAS_GATED_READS = new Set([
1165
1200
  'cmd:sessions:load',
1166
- // Same data as the event:session:list push (projects + their active Epics).
1201
+ // Same data the phone reads on demand (projects + their active Epics).
1167
1202
  'cmd:sessions:list',
1168
1203
  'cmd:history:aggregate',
1169
1204
  // subscribe initiates a live stream of session state/summary — sensitive.
1170
1205
  'cmd:session:subscribe',
1171
1206
  // Picker data (projects, personas, tags, macros) for the phone's New Session.
1172
1207
  'cmd:session:options',
1208
+ // Drill-down: project list, one project's Epics, one session's recent
1209
+ // transcript text — all user data, so SAS-gated.
1210
+ 'cmd:projects:list',
1211
+ 'cmd:project:sessions',
1212
+ 'cmd:session:transcript',
1213
+ // PRD slugs/titles/statuses for one Epic — project work content, so SAS-gated.
1214
+ 'cmd:session:prds',
1173
1215
  // NOTE: cmd:exchanges:list is intentionally NOT allowlisted — webRemote.cjs has
1174
1216
  // no dispatch handler for it, so an allowlist entry would only fail closed with
1175
1217
  // an opaque reject. Re-add here together with the handler when remote exchanges
@@ -1276,6 +1318,12 @@ module.exports = {
1276
1318
  memoryAggregate,
1277
1319
  memoryStale,
1278
1320
  memoryAgents,
1321
+ memoryVerify,
1322
+ memoryArchive,
1323
+ memoryRestore,
1324
+ memoryListArchived,
1325
+ memoryAsk,
1326
+ memoryProposeFix,
1279
1327
  projectBriefCwd,
1280
1328
  projectPagesCwd,
1281
1329
  projectHomeAdminWriteBody,
@@ -1305,6 +1353,9 @@ module.exports = {
1305
1353
  chatClassifyTicket,
1306
1354
  chatExternalSend,
1307
1355
  remoteSessionCreate,
1356
+ remoteProjectSessions,
1357
+ remoteSessionTranscript,
1358
+ remoteSessionPrds,
1308
1359
  exchangesList,
1309
1360
  agentsSavePersona,
1310
1361
  macrosSave,
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Memory page actions over an already-resolved memory folder: verify (touch),
5
+ * archive / restore (move to / from `<dir>/.archive/`) and listArchived.
6
+ * Claude Code's native files stay the only truth — no new frontmatter fields.
7
+ * Every path goes through config.validatePath + validateWrite before any mutation.
8
+ * Top-level in lib/ (not a domain subfolder): sits beside memoryIndex/memorySlug,
9
+ * which it composes.
10
+ */
11
+
12
+ const fs = require('node:fs');
13
+ const path = require('node:path');
14
+ const config = require('../config.cjs');
15
+ const { addIndexLine, removeIndexLines } = require('./memoryIndex.cjs');
16
+ const { MEMORY_SLUG_RE } = require('./memorySlug.cjs');
17
+
18
+ const fsp = fs.promises;
19
+ const INDEX_NAME = 'MEMORY.md';
20
+ const ARCHIVE_DIR = '.archive';
21
+
22
+ function checkName(name) {
23
+ if (typeof name !== 'string' || name === INDEX_NAME || !MEMORY_SLUG_RE.test(name)) {
24
+ throw new Error('invalid memory name');
25
+ }
26
+ }
27
+
28
+ /** Validate a path for writing; returns the symlink-resolved path. */
29
+ function guard(abs) {
30
+ const real = config.validatePath(abs);
31
+ config.validateWrite(real);
32
+ return real;
33
+ }
34
+
35
+ async function exists(abs) {
36
+ try { await fsp.lstat(abs); return true; } catch { return false; }
37
+ }
38
+
39
+ async function readIndex(dir) {
40
+ const r = await config.readText(path.join(dir, INDEX_NAME));
41
+ return r.exists ? r.text : null;
42
+ }
43
+
44
+ /** Set atime/mtime to now; content untouched. */
45
+ async function verify(dir, name) {
46
+ try {
47
+ checkName(name);
48
+ const real = guard(path.join(dir, name));
49
+ const now = new Date();
50
+ await fsp.utimes(real, now, now);
51
+ return { ok: true, error: null };
52
+ } catch (e) {
53
+ return { ok: false, error: e.message };
54
+ }
55
+ }
56
+
57
+ /** Move `<dir>/<name>` into `<dir>/.archive/` and drop its index line(s). */
58
+ async function archive(dir, name) {
59
+ try {
60
+ checkName(name);
61
+ const src = guard(path.join(dir, name));
62
+ const archDir = guard(path.join(dir, ARCHIVE_DIR));
63
+ const dest = guard(path.join(archDir, name));
64
+ if (!(await exists(src))) throw new Error('memory not found');
65
+ if (await exists(dest)) throw new Error('archived copy already exists');
66
+ await fsp.mkdir(archDir, { recursive: true });
67
+ await fsp.rename(src, dest);
68
+ const text = await readIndex(dir);
69
+ if (text !== null) {
70
+ const next = removeIndexLines(text, name);
71
+ if (next !== text) await config.writeTextAtomic(path.join(dir, INDEX_NAME), next);
72
+ }
73
+ return { ok: true, error: null };
74
+ } catch (e) {
75
+ return { ok: false, error: e.message };
76
+ }
77
+ }
78
+
79
+ /** Move an archived memory back and re-add its index line. */
80
+ async function restore(dir, name) {
81
+ try {
82
+ checkName(name);
83
+ const dest = guard(path.join(dir, name));
84
+ const src = guard(path.join(dir, ARCHIVE_DIR, name));
85
+ if (!(await exists(src))) throw new Error('archived memory not found');
86
+ if (await exists(dest)) throw new Error('memory already exists');
87
+ await fsp.rename(src, dest);
88
+ const head = (await fsp.readFile(dest, 'utf8')).match(/^---\r?\n([\s\S]*?)\r?\n---/);
89
+ const d = head && head[1].match(/^\s*description:\s*"?(.*?)"?\s*$/m);
90
+ const hook = d ? d[1].trim() : '';
91
+ const text = (await readIndex(dir)) || '';
92
+ const next = addIndexLine(text, { file: name, title: name.replace(/\.md$/, ''), hook });
93
+ if (next !== text) await config.writeTextAtomic(path.join(dir, INDEX_NAME), next);
94
+ return { ok: true, error: null };
95
+ } catch (e) {
96
+ return { ok: false, error: e.message };
97
+ }
98
+ }
99
+
100
+ /** Names in `<dir>/.archive/`; a missing folder is an empty list. */
101
+ async function listArchived(dir) {
102
+ try {
103
+ const real = config.validatePath(path.join(dir, ARCHIVE_DIR));
104
+ const entries = await fsp.readdir(real, { withFileTypes: true });
105
+ const names = entries
106
+ .filter((e) => e.isFile() && MEMORY_SLUG_RE.test(e.name))
107
+ .map((e) => e.name)
108
+ .sort();
109
+ return { names, error: null };
110
+ } catch (e) {
111
+ if (e.code === 'ENOENT') return { names: [], error: null };
112
+ return { names: [], error: e.message };
113
+ }
114
+ }
115
+
116
+ module.exports = { verify, archive, restore, listArchived };
@@ -24,6 +24,22 @@ function stripName(name) {
24
24
  return name.replace(/\.md$/, '');
25
25
  }
26
26
 
27
+ /**
28
+ * Frontmatter meta from a leading `---` block only. `type:` / `originSessionId:`
29
+ * match at any indentation (real files nest them under `metadata:`).
30
+ */
31
+ function parseMeta(body) {
32
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(body);
33
+ if (!fm) return { description: null, type: null, originSessionId: null };
34
+ const pick = (key) => {
35
+ const m = new RegExp(`^[ \\t]*${key}:[ \\t]*(.+?)[ \\t]*$`, 'm').exec(fm[1]);
36
+ return m ? m[1] : null;
37
+ };
38
+ let description = pick('description');
39
+ if (description !== null) description = description.replace(/^"(.*)"$/, '$1');
40
+ return { description, type: pick('type'), originSessionId: pick('originSessionId') };
41
+ }
42
+
27
43
  /**
28
44
  * Extract dead-ref candidate paths from a memory body.
29
45
  * Complexity: O(body length) — one regex pass, then O(candidates) filtering.
@@ -81,7 +97,9 @@ function scoreMemories({ entries, now, existsPath }) {
81
97
  }
82
98
  });
83
99
 
84
- return entries.map((e) => {
100
+ const knownSlugs = new Set(entries.map((e) => stripName(e.name)));
101
+
102
+ return entries.map((e, i) => {
85
103
  const ageDays = Math.floor((now - e.mtimeMs) / DAY_MS);
86
104
  const slug = stripName(e.name);
87
105
  const inboundLinks = inboundCounts.get(slug) || 0;
@@ -102,8 +120,13 @@ function scoreMemories({ entries, now, existsPath }) {
102
120
  reasons.push('90+ days old with no inbound links');
103
121
  }
104
122
 
123
+ const outLinks = [...perEntryTargets[i]].filter((t) => t !== slug);
124
+
105
125
  return {
106
126
  name: e.name,
127
+ ...parseMeta(e.body),
128
+ outLinks,
129
+ danglingLinks: outLinks.filter((t) => !knownSlugs.has(t)),
107
130
  ageDays,
108
131
  inboundLinks,
109
132
  deadRefs,
@@ -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 };