claude-code-session-manager 0.105.0 → 0.107.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 (59) hide show
  1. package/dist/assets/{AgentLibrary-BDtr4oI-.js → AgentLibrary-Bjeq2-kp.js} +1 -1
  2. package/dist/assets/{Button-CuXBCQDp.js → Button-CRLhoI2K.js} +1 -1
  3. package/dist/assets/{DataModel-DmvRipT5.js → DataModel-iPJw5jcG.js} +1 -1
  4. package/dist/assets/{History-B-v9pwwr.js → History-DfPR3_6N.js} +1 -1
  5. package/dist/assets/{Hooks-S_0oHHlr.js → Hooks-Bw7H_DmC.js} +1 -1
  6. package/dist/assets/{Library-BcbGJxQ3.js → Library-rqaJJJ1b.js} +1 -1
  7. package/dist/assets/{ListDetail-DtqHqJHt.js → ListDetail-qI27OYfy.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-B56sPp_l.js → MarkdownEditor-BEFuHVNz.js} +1 -1
  9. package/dist/assets/{McpServers-D5VnmxAn.js → McpServers-Bz9O79rz.js} +1 -1
  10. package/dist/assets/{Memory-0yGZLyRP.js → Memory-DobxIoZ-.js} +1 -1
  11. package/dist/assets/{Panel-NoAADJwx.js → Panel-Cyt1wpUf.js} +1 -1
  12. package/dist/assets/{Permissions-D2Dh-ee8.js → Permissions-ChSNRUJJ.js} +1 -1
  13. package/dist/assets/{Plugins-kGoUgS9b.js → Plugins-CII221R2.js} +2 -2
  14. package/dist/assets/{ProvenanceBadge-CHwDFTX7.js → ProvenanceBadge-C_sk5-v3.js} +1 -1
  15. package/dist/assets/{SaveBar-BtKNMIYK.js → SaveBar-BuYrg5E9.js} +1 -1
  16. package/dist/assets/Scheduler-eixbMGEk.js +16 -0
  17. package/dist/assets/{ScopeSwitcher-BusohWZ5.js → ScopeSwitcher-BM0gLPhd.js} +1 -1
  18. package/dist/assets/Settings-qD-0W-Ut.js +4 -0
  19. package/dist/assets/{SkillReferenceGraph-BHOdIpsy.js → SkillReferenceGraph-D0Hgdfxq.js} +1 -1
  20. package/dist/assets/{Skills-DSTLZimI.js → Skills-C3T_2_2o.js} +1 -1
  21. package/dist/assets/{SystemPrompt-BG7wFWdb.js → SystemPrompt-B-xTy9Qn.js} +1 -1
  22. package/dist/assets/{TagLibrary-Bx27aE3_.js → TagLibrary-BH67tR5z.js} +1 -1
  23. package/dist/assets/{TiptapBody-j0O8J7YI.js → TiptapBody-CP_F_mwb.js} +1 -1
  24. package/dist/assets/{Toggle-DzVZ6rC7.js → Toggle-haRPHS5N.js} +1 -1
  25. package/dist/assets/{ViewTabs-BizjuynT.js → ViewTabs-BHV3MA5u.js} +1 -1
  26. package/dist/assets/{index-BLVzrc4k.css → index-tr1w3hHB.css} +1 -1
  27. package/dist/assets/index-v_3LvhMx.js +3077 -0
  28. package/dist/assets/{settingsSchema-7MRAxaJk.js → settingsSchema-CE46TA3V.js} +1 -1
  29. package/dist/assets/useAgentsDirWatch-BZTi2K2e.js +1 -0
  30. package/dist/assets/{useConfigDirWatch-ot0VYwQC.js → useConfigDirWatch-CjcswsWR.js} +1 -1
  31. package/dist/index.html +2 -2
  32. package/package.json +2 -1
  33. package/scripts/scheduler-mcp-server.cjs +58 -2
  34. package/src/main/build-info.json +4 -4
  35. package/src/main/chatRunner.cjs +4 -2
  36. package/src/main/crashDiagnostics.cjs +39 -3
  37. package/src/main/index.cjs +47 -3
  38. package/src/main/ipcSchemas.cjs +120 -0
  39. package/src/main/lib/claudeBin.cjs +22 -8
  40. package/src/main/lib/cleanEnv.cjs +6 -5
  41. package/src/main/lib/customerFeedbackAdminRoutes.cjs +94 -0
  42. package/src/main/lib/customerFeedbackClient.cjs +261 -0
  43. package/src/main/lib/customerFeedbackInbox.cjs +286 -0
  44. package/src/main/lib/customerFeedbackIpc.cjs +43 -0
  45. package/src/main/lib/e2eStateMachine.cjs +39 -0
  46. package/src/main/lib/insideHome.cjs +1 -1
  47. package/src/main/lib/mcpToolCatalog.cjs +31 -6
  48. package/src/main/lib/projectFolder.cjs +89 -0
  49. package/src/main/lib/usageCircuit.cjs +15 -0
  50. package/src/main/lib/winUserData.cjs +22 -0
  51. package/src/main/scheduler.cjs +68 -20
  52. package/src/main/usage.cjs +10 -2
  53. package/src/main/webRemote.cjs +1364 -0
  54. package/src/preload/api.d.ts +132 -1
  55. package/src/preload/index.cjs +51 -0
  56. package/dist/assets/Scheduler-B0_kMMDs.js +0 -16
  57. package/dist/assets/Settings-DuRJntaE.js +0 -3
  58. package/dist/assets/index-CLIaYljt.js +0 -3073
  59. package/dist/assets/useAgentsDirWatch-BUwfU2Kq.js +0 -1
@@ -1 +1 @@
1
- import{aw as b,ah as n,S as N,aN as O}from"./index-CLIaYljt.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{aw as b,ah as n,S as N,aN as O}from"./index-v_3LvhMx.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 @@
1
+ import{aS as u}from"./index-v_3LvhMx.js";import{u as a}from"./useConfigDirWatch-CjcswsWR.js";function r(e,s){const o=u();a([o?`${o}/.claude/agents`:null,e?`${e}/.claude/agents`:null],s)}export{r as u};
@@ -1 +1 @@
1
- import{aw as c}from"./index-CLIaYljt.js";function s(t){const n=t.replace(/\/{2,}/g,"/");return n.length>1?n.replace(/\/+$/,""):n}function u(t,n){const r=c.useRef(n);r.current=n;const e=Array.from(new Set(t.filter(o=>!!o).map(s))),i=e.slice().sort().join("\0");c.useEffect(()=>{if(e.length===0)return;window.api.config.watch(e);const o=window.api.config.onChanged(a=>{e.includes(a.path)&&r.current()});return()=>{o(),window.api.config.unwatch(e)}},[i])}export{u};
1
+ import{aw as c}from"./index-v_3LvhMx.js";function s(t){const n=t.replace(/\/{2,}/g,"/");return n.length>1?n.replace(/\/+$/,""):n}function u(t,n){const r=c.useRef(n);r.current=n;const e=Array.from(new Set(t.filter(o=>!!o).map(s))),i=e.slice().sort().join("\0");c.useEffect(()=>{if(e.length===0)return;window.api.config.watch(e);const o=window.api.config.onChanged(a=>{e.includes(a.path)&&r.current()});return()=>{o(),window.api.config.unwatch(e)}},[i])}export{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-CLIaYljt.js"></script>
10
+ <script type="module" crossorigin src="./assets/index-v_3LvhMx.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-BLVzrc4k.css">
13
+ <link rel="stylesheet" crossorigin href="./assets/index-tr1w3hHB.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.105.0",
3
+ "version": "0.107.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",
@@ -132,6 +132,7 @@
132
132
  "chokidar": "^4.0.1",
133
133
  "electron": "42.11.10",
134
134
  "node-pty": "^1.2.0-beta.12",
135
+ "ws": "^8.18.0",
135
136
  "zod": "^3.23.8"
136
137
  },
137
138
  "devDependencies": {
@@ -16,6 +16,8 @@
16
16
  *
17
17
  * macro_list({ cwd? }) -> GET /admin/macros?cwd=<encoded>
18
18
  * macro_save({ cwd?, id?, label, agentName, tag, prompt }) -> POST /admin/macros/save
19
+ * customer_feedback_list({ pull?, includeHidden? }) -> GET /admin/customer-feedback/inbox
20
+ * customer_feedback_set_status({ id, status, note? }) -> POST /admin/customer-feedback/status
19
21
  *
20
22
  * This is a separate process from the Electron app — it only ever reaches
21
23
  * it over the token-authed loopback HTTP API in admin-api.json, never by
@@ -141,7 +143,12 @@ const TOOLS = [
141
143
  {
142
144
  name: 'scheduler_pause',
143
145
  description: descriptionFor('scheduler_pause'),
144
- inputSchema: { type: 'object', properties: {} },
146
+ inputSchema: {
147
+ type: 'object',
148
+ properties: {
149
+ reason: { type: 'string', description: 'Why you are pausing — shown to the human.' },
150
+ },
151
+ },
145
152
  },
146
153
  {
147
154
  name: 'scheduler_resume',
@@ -419,6 +426,30 @@ const TOOLS = [
419
426
  required: ['label', 'agentName', 'tag', 'prompt'],
420
427
  },
421
428
  },
429
+ {
430
+ name: 'customer_feedback_list',
431
+ description: descriptionFor('customer_feedback_list'),
432
+ inputSchema: {
433
+ type: 'object',
434
+ properties: {
435
+ pull: { type: 'boolean', description: 'Fetch new items from the feedback service before listing. Default true.' },
436
+ includeHidden: { type: 'boolean', description: 'Include moderated-away items. Default false.' },
437
+ },
438
+ },
439
+ },
440
+ {
441
+ name: 'customer_feedback_set_status',
442
+ description: descriptionFor('customer_feedback_set_status'),
443
+ inputSchema: {
444
+ type: 'object',
445
+ properties: {
446
+ id: { type: 'string', description: 'Feedback item id (from customer_feedback_list).' },
447
+ status: { type: 'string', enum: ['open', 'in_progress', 'resolved', 'wontfix'], description: 'New status.' },
448
+ note: { type: 'string', description: 'Optional short note shown to the customer.' },
449
+ },
450
+ required: ['id', 'status'],
451
+ },
452
+ },
422
453
  {
423
454
  name: 'session_manager_help',
424
455
  description: descriptionFor('session_manager_help'),
@@ -450,7 +481,14 @@ async function handleCallTool(request) {
450
481
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
451
482
  }
452
483
  if (name === 'scheduler_pause' || name === 'scheduler_resume') {
453
- const result = await adminRequest('POST', `/admin/scheduler/${name === 'scheduler_pause' ? 'pause' : 'resume'}`, {});
484
+ const body = name === 'scheduler_pause'
485
+ ? {
486
+ originClaudeSessionId: process.env.SM_CHAT_SESSION_ID ?? null,
487
+ cwd: process.cwd(),
488
+ reason: args && typeof args.reason === 'string' ? args.reason : undefined,
489
+ }
490
+ : {};
491
+ const result = await adminRequest('POST', `/admin/scheduler/${name === 'scheduler_pause' ? 'pause' : 'resume'}`, body);
454
492
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
455
493
  }
456
494
  if (name === 'scheduler_list_jobs') {
@@ -662,6 +700,24 @@ async function handleCallTool(request) {
662
700
  const result = await adminRequest('POST', '/admin/macros/save', payload);
663
701
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
664
702
  }
703
+ if (name === 'customer_feedback_list') {
704
+ const qs = new URLSearchParams();
705
+ if (!args || args.pull !== false) qs.set('pull', '1');
706
+ if (args && args.includeHidden === true) qs.set('includeHidden', '1');
707
+ const result = await adminRequest('GET', `/admin/customer-feedback/inbox?${qs.toString()}`);
708
+ if (result?.ok === false) return errorResult(result.error ?? 'customer_feedback_list failed');
709
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
710
+ }
711
+ if (name === 'customer_feedback_set_status') {
712
+ if (!args || typeof args.id !== 'string' || !args.id || typeof args.status !== 'string') {
713
+ return errorResult('missing or invalid required argument(s): id, status');
714
+ }
715
+ const payload = { id: args.id, status: args.status };
716
+ if (typeof args.note === 'string' && args.note) payload.note = args.note;
717
+ const result = await adminRequest('POST', '/admin/customer-feedback/status', payload);
718
+ if (result?.ok === false) return errorResult(result.error ?? 'customer_feedback_set_status failed');
719
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
720
+ }
665
721
  if (name === 'session_manager_help') {
666
722
  const toolName = args && typeof args.tool === 'string' ? args.tool : null;
667
723
  const topic = args && typeof args.topic === 'string' ? args.topic : null;
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "0.105.0",
3
- "gitSha": "13d0cde6c719549e850585a834783382050c8bd7",
4
- "gitShortSha": "13d0cde6",
2
+ "version": "0.107.0",
3
+ "gitSha": "a18e184a574f54d301d80aeeb88e477d88741be9",
4
+ "gitShortSha": "a18e184a",
5
5
  "gitBranch": "HEAD",
6
- "builtAt": "2026-10-05T18:06:40.040Z",
6
+ "builtAt": "2026-10-09T18:00:32.692Z",
7
7
  "dirty": false
8
8
  }
@@ -690,9 +690,11 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume, silent, onSilentRes
690
690
  // real-time NDJSON streaming; stderr piped for error-message capture.
691
691
  let thisChild;
692
692
  try {
693
- const target = claudeSpawnTarget('chat', sessionId, claudeBin);
694
- thisChild = spawn(target.command, buildArgs(resumeFlag), {
693
+ const chatArgs = buildArgs(resumeFlag);
694
+ const target = claudeSpawnTarget('chat', sessionId, claudeBin, chatArgs);
695
+ thisChild = spawn(target.command, target.args || chatArgs, {
695
696
  ...(target.argv0 ? { argv0: target.argv0 } : {}),
697
+ ...(target.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
696
698
  cwd: execCwd,
697
699
  env: childEnv,
698
700
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -56,15 +56,51 @@ function sentinelFile() {
56
56
  // crashes (NOT OOM — a SIGKILL leaves no dump). Local-only, never uploaded.
57
57
  // Must run before the app is ready, so call this at module require time from
58
58
  // index.cjs's top level.
59
- function startCrashReporter() {
59
+ //
60
+ // On Windows Electron relaunches this exe as the crashpad handler with
61
+ // --database=<crashDumps path>; an empty path makes the handler exit with
62
+ // "--database is required" and the browser process self-terminates before any
63
+ // window opens. So we resolve a non-empty dir first, and skip start() entirely
64
+ // if we cannot — crash reporting must never be able to stop boot.
65
+ // `deps` is injectable for tests; the runtime call signature is unchanged.
66
+ function startCrashReporter(deps = {}) {
67
+ const a = deps.app || app;
68
+ const reporter = deps.crashReporter || crashReporter;
69
+ const fsImpl = deps.fs || fs;
70
+ const log = deps.log || ((msg) => console.warn(msg));
60
71
  try {
61
- crashReporter.start({
72
+ const reasons = [];
73
+ const getDir = (name) => {
74
+ try {
75
+ const d = a.getPath(name);
76
+ if (typeof d === 'string') return d.trim();
77
+ reasons.push(`${name}: non-string ${typeof d}`);
78
+ } catch (err) {
79
+ reasons.push(`${name}: ${err && err.message}`);
80
+ }
81
+ return '';
82
+ };
83
+ let dir = getDir('crashDumps');
84
+ let fellBack = false;
85
+ if (!dir) {
86
+ const userData = getDir('userData');
87
+ if (userData) { dir = path.join(userData, 'Crashpad'); fellBack = true; }
88
+ }
89
+ if (!dir) {
90
+ log('[crash-diag] no crashDumps/userData path; crash reporter skipped' + (reasons.length ? ` (${reasons.join('; ')})` : ''));
91
+ return;
92
+ }
93
+ fsImpl.mkdirSync(dir, { recursive: true });
94
+ if (fellBack) a.setPath('crashDumps', dir);
95
+ reporter.start({
62
96
  productName: 'claude-code-session-manager',
63
97
  companyName: 'session-manager',
64
98
  uploadToServer: false,
65
99
  compress: true,
66
100
  });
67
- } catch { /* best-effort: diagnostics must never break boot */ }
101
+ } catch (err) {
102
+ try { (deps.log || ((m) => console.warn(m)))(`[crash-diag] crash reporter skipped: ${err && err.message}`); } catch { /* ignore */ }
103
+ }
68
104
  }
69
105
 
70
106
  // Snapshot per-process memory. app.getAppMetrics() returns one entry per
@@ -1,3 +1,15 @@
1
+ // Boot diagnostic (SM_BOOT_DIAG=1, set by the packaged smoke): one stderr line that
2
+ // shows whether `electron` resolved to the built-in module or ran in node mode.
3
+ if (process.env.SM_BOOT_DIAG === '1') {
4
+ try {
5
+ const e = require('electron');
6
+ let userData;
7
+ try { userData = String(e.app.getPath('userData')); } catch (err) { userData = `error: ${err && err.message}`; }
8
+ process.stderr.write(`[boot-diag] platform=${process.platform} electron=${process.versions.electron} typeof electron=${typeof e} typeof electron.app=${typeof (e && e.app)} ELECTRON_RUN_AS_NODE=${process.env.ELECTRON_RUN_AS_NODE} execPath=${process.execPath} __dirname=${__dirname} userData=${userData}\n`);
9
+ } catch (err) {
10
+ process.stderr.write(`[boot-diag] failed: ${err && err.message}\n`);
11
+ }
12
+ }
1
13
  const { PROC_NAMES, setProcessTitle, inhibitHolderShell } = require('./lib/smProcNames.cjs');
2
14
  // Self-describing `comm` in System Monitor (pn-03). process.argv is a JS copy
3
15
  // made at startup, so the argv-memory overwrite below never affects the
@@ -10,6 +22,7 @@ const fs = require('node:fs');
10
22
  const fsp = require('node:fs/promises');
11
23
  const os = require('node:os');
12
24
  const { schemas, validated } = require('./ipcSchemas.cjs');
25
+ const { createProjectFolder } = require('./lib/projectFolder.cjs');
13
26
  const { cleanChildEnv } = require('./lib/cleanEnv.cjs');
14
27
  const { applyLoginShellPath } = require('./lib/loginShellPath.cjs');
15
28
  const { terminateLosingInstance } = require('./lib/singleInstanceGuard.cjs');
@@ -26,6 +39,20 @@ const crashDiagnostics = require('./crashDiagnostics.cjs');
26
39
  // Start the local minidump collector before app-ready (required by Electron).
27
40
  // Catches renderer/GPU SIGSEGV/SIGABRT; OOM-kills leave no dump but are caught
28
41
  // by the sentinel + render-process-gone hooks in crashDiagnostics.init().
42
+ // ELECTRON_RUN_AS_NODE audit: every src/main site (appRuntime, watchdogHelpers,
43
+ // seedSchedulerMcp, timeoutShim) sets it only in a child's env / launcher text,
44
+ // or deletes it from a copied env — none writes the main process.env, so it
45
+ // cannot leak into Electron's own children (crashpad handler included).
46
+ // Chromium can't resolve known folders under a stripped Windows profile; pin an explicit
47
+ // userData path first (never allowed to stop boot).
48
+ try {
49
+ require('./lib/winUserData.cjs').ensureWinUserDataPath({
50
+ app: require('electron').app, env: process.env, platform: process.platform, fs: require('node:fs'), os: require('node:os'),
51
+ log: (m) => process.stderr.write(`${m}\n`),
52
+ });
53
+ } catch (err) {
54
+ process.stderr.write(`[win-userdata] fallback failed: ${err && err.message}\n`);
55
+ }
29
56
  crashDiagnostics.startCrashReporter();
30
57
  const voiceHotkey = require('./voiceHotkey.cjs');
31
58
  const voiceWizard = require('./voiceWizard.cjs');
@@ -41,6 +68,7 @@ const chatRunner = require('./chatRunner.cjs');
41
68
  const promptSessionEvents = require('./promptSessionEvents.cjs');
42
69
  const activeIndexMerge = require('./lib/activeIndexMerge.cjs');
43
70
  const promptSessionsCreateEpic = require('./lib/promptSessionsCreateEpic.cjs');
71
+ const customerFeedbackIpc = require('./lib/customerFeedbackIpc.cjs');
44
72
  const epicWorktreeMint = require('./lib/epicWorktreeMint.cjs');
45
73
  const epicWorktreeMerge = require('./lib/epicWorktreeMerge.cjs');
46
74
  const epicWorktreeProjectConfig = require('./lib/epicWorktreeProjectConfig.cjs');
@@ -68,6 +96,7 @@ chatRunner.registerAdminRoute(adminHttp);
68
96
  // several projects' operations roots open, so it performs the cross-folder
69
97
  // write (lib/crossProjectFeedback.cjs).
70
98
  crossProjectFeedback.registerAdminRoute(adminHttp);
99
+ require('./lib/customerFeedbackAdminRoutes.cjs').registerAdminRoute(adminHttp);
71
100
  const supervisor = require('./supervisor.cjs');
72
101
  const watchers = require('./watchers.cjs');
73
102
  const queueOps = require('./queueOps.cjs');
@@ -99,6 +128,7 @@ const git = require('./git.cjs');
99
128
  const heapSnapshot = require('./heapSnapshot.cjs');
100
129
  const filesIpc = require('./files.cjs');
101
130
  const { registerDocEditHandlers, attachWindow: attachDocEditWindow } = require('./docEdit.cjs');
131
+ const webRemote = require('./webRemote.cjs');
102
132
  const { listExchanges } = require('./exchanges.cjs');
103
133
  const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
104
134
  const { checkInsideHome, assertInsideHome } = require('./lib/insideHome.cjs');
@@ -366,6 +396,7 @@ async function performReboot() {
366
396
  scheduler.attachWindow(mainWindow);
367
397
  watchers.attachWindow(mainWindow);
368
398
  pluginInstall.attachWindow(mainWindow);
399
+ webRemote.attachWindow(mainWindow);
369
400
  chatRunner.attachWindow(mainWindow);
370
401
  promptSessionEvents.attachWindow(mainWindow);
371
402
  attachDocEditWindow(mainWindow);
@@ -743,9 +774,9 @@ ipcMain.handle('app:seed-status', () => getSeedStatus());
743
774
  ipcMain.handle('app:pick-directory', async () => {
744
775
  console.log('[main] pick-directory invoked');
745
776
  const result = await dialog.showOpenDialog(mainWindow, {
746
- // createDirectory enables the picker's "New Folder" affordance (macOS;
747
- // the GTK chooser has its own) — this is the "Start Project" path:
748
- // create a fresh folder in place, open it as the project's TAB.
777
+ // createDirectory is macOS-only (it adds the picker's "New Folder"
778
+ // button there). Linux/Windows users create folders via the in-app
779
+ // Start new project form (app:create-project-folder, below).
749
780
  properties: ['openDirectory', 'createDirectory'],
750
781
  defaultPath: os.homedir(),
751
782
  title: 'Open a project folder — or create a new one to start a project',
@@ -755,6 +786,12 @@ ipcMain.handle('app:pick-directory', async () => {
755
786
  return result.filePaths[0];
756
787
  });
757
788
 
789
+ ipcMain.handle('app:create-project-folder', validated(schemas.appCreateProjectFolder, async (payload) => {
790
+ const result = await createProjectFolder(payload);
791
+ console.log('[main] create-project-folder', result.ok ? 'ok' : result.code, JSON.stringify({ parentDir: payload.parentDir, name: payload.name }));
792
+ return result;
793
+ }));
794
+
758
795
  ipcMain.on('app:reboot-app', (_e, opts) => { rebootApp({ force: opts?.force === true }).catch((e) => logReboot(`reboot failed: ${e?.message}`)); });
759
796
 
760
797
  // Image paste — Ctrl+V in the Terminal pane. Reads the OS clipboard via
@@ -1030,6 +1067,7 @@ pluginInstall.registerPluginInstallHandlers();
1030
1067
  memoryTool.registerMemoryHandlers();
1031
1068
  activeIndexMerge.registerActiveIndexMergeHandlers();
1032
1069
  promptSessionsCreateEpic.registerPromptSessionsCreateEpicHandlers();
1070
+ customerFeedbackIpc.registerCustomerFeedbackHandlers();
1033
1071
  epicWorktreeMint.registerEpicWorktreeMintHandlers();
1034
1072
  epicWorktreeMerge.registerEpicWorktreeMergeHandlers();
1035
1073
  epicWorktreeProjectConfig.registerEpicWorktreeProjectConfigHandlers();
@@ -1059,6 +1097,7 @@ agentMemory.registerAgentMemoryHandlers();
1059
1097
  git.register(ipcMain);
1060
1098
  filesIpc.registerFilesHandlers();
1061
1099
  registerDocEditHandlers();
1100
+ webRemote.registerRemoteHandlers();
1062
1101
  chatRunner.registerChatHandlers();
1063
1102
 
1064
1103
  // Direct in-process PRD authoring for a queued PromptTicket classified
@@ -1418,6 +1457,7 @@ app.whenReady().then(async () => {
1418
1457
  scheduler.attachWindow(mainWindow);
1419
1458
  watchers.attachWindow(mainWindow);
1420
1459
  pluginInstall.attachWindow(mainWindow);
1460
+ webRemote.attachWindow(mainWindow);
1421
1461
  chatRunner.attachWindow(mainWindow);
1422
1462
  promptSessionEvents.attachWindow(mainWindow);
1423
1463
  attachDocEditWindow(mainWindow);
@@ -1448,6 +1488,9 @@ app.whenReady().then(async () => {
1448
1488
  seedDevPlugin({ logger: console, writeLog: logs.writeLine }).catch((e) => {
1449
1489
  logs.writeLine({ scope: 'seed-dev-plugin', level: 'error', message: 'seed failed', meta: { error: e?.message } });
1450
1490
  });
1491
+ webRemote.init().catch((e) => {
1492
+ logs.writeLine({ scope: 'webRemote', level: 'error', message: 'init failed', meta: { error: e?.message } });
1493
+ });
1451
1494
  // First-boot default: seed the bundled Architect + Dev Lead Agent personas
1452
1495
  // into ~/.claude/agents/ so the Agent Library isn't empty on a fresh
1453
1496
  // install. One-shot + idempotent; never overwrites an existing persona;
@@ -1558,6 +1601,7 @@ let teardownDone = false;
1558
1601
  function runShutdownCleanup() {
1559
1602
  if (teardownDone) return; // idempotent — will-quit may still fire after an app.exit path
1560
1603
  teardownDone = true;
1604
+ webRemote.destroy();
1561
1605
  // Mark a clean exit so the next boot can distinguish a graceful quit from an
1562
1606
  // OOM-kill / native crash (which leaves the sentinel `open`).
1563
1607
  crashDiagnostics.markCleanShutdown();
@@ -30,6 +30,17 @@ const ptyTabId = z.object({ tabId: z.string().min(1).max(128) });
30
30
  // have a live PTY? Bounded so the renderer can't ask about unbounded lists.
31
31
  const ptyAlive = z.object({ tabIds: z.array(z.string().min(1).max(128)).max(500) });
32
32
 
33
+ // v2 mobile: subscribe to a session's live state + summary. cwd is needed to
34
+ // locate the transcript JSONL (transcriptPath); validated against home-dir boundary
35
+ // in webRemote before any fs access.
36
+ const sessionSubscribe = z.object({
37
+ // tabId becomes a transcript FILENAME (`<tabId>.jsonl`) — restrict to a
38
+ // session-id charset (no '/', no '.', so it can't traverse out of the project
39
+ // transcript dir). sessionId is a UUID, which satisfies this.
40
+ tabId: z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9_-]*$/),
41
+ cwd: z.string().min(1).max(4096),
42
+ });
43
+
33
44
  // 64 KiB cap per pty:write — typewriter input is bounded; a renderer firing
34
45
  // megabytes per call is either a bug or an attack. Block it at the boundary.
35
46
  const PTY_WRITE_MAX_BYTES = 64 * 1024;
@@ -687,6 +698,13 @@ const delegationReadinessCwd = z.object({
687
698
  cwd: z.string().min(1).max(4096),
688
699
  }).strict();
689
700
 
701
+ // app:create-project-folder — name semantics (separators, '..') are checked in
702
+ // lib/projectFolder.cjs, not here.
703
+ const appCreateProjectFolder = z.object({
704
+ parentDir: z.string().min(1).max(4096),
705
+ name: z.string().min(1).max(255),
706
+ }).strict();
707
+
690
708
  // ──────────────────────────────────────────── Agent persona body (New Epic AIM)
691
709
  const agentsGetPersonaBody = z.object({
692
710
  cwd: z.string().min(1).max(4096),
@@ -925,6 +943,23 @@ const chatExternalSend = z.object({
925
943
  ),
926
944
  });
927
945
 
946
+ // ──────────────────────────────────────────── Web Remote
947
+ // OTP is 8 uppercase alphanumeric chars (case-insensitive entry, normalised to upper in handler).
948
+ const WEB_REMOTE_OTP_RE = /^[A-Z0-9]{8}$/i;
949
+ const DEVICE_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
950
+
951
+ const webRemotePair = z.object({
952
+ otp: z.string().regex(WEB_REMOTE_OTP_RE),
953
+ }).strict();
954
+
955
+ const webRemoteRevokeDevice = z.object({
956
+ deviceId: z.string().regex(DEVICE_ID_RE),
957
+ }).strict();
958
+
959
+ const webRemoteAuditTail = z.object({
960
+ lines: z.number().int().min(1).max(500).optional(),
961
+ }).strict();
962
+
928
963
  // ──────────────────────────────────────────── History
929
964
  const DATE_YYYY_MM_DD = /^\d{4}-\d{2}-\d{2}$/;
930
965
 
@@ -1051,6 +1086,20 @@ const telemetrySetConfig = z.object({
1051
1086
  schemaVersion: z.literal(1),
1052
1087
  });
1053
1088
 
1089
+ const customerFeedbackSubmit = z.object({
1090
+ title: z.string().max(120),
1091
+ body: z.string().max(4000),
1092
+ tag: z.enum(['bug', 'feature', 'discussion']),
1093
+ });
1094
+ const customerFeedbackMarkSeen = z.object({ ids: z.array(z.string().max(200)).max(500).optional() });
1095
+ const customerFeedbackInboxList = z.object({ includeHidden: z.boolean().optional() });
1096
+ const customerFeedbackInboxSetStatus = z.object({
1097
+ id: z.string().max(200),
1098
+ status: z.enum(['open', 'in_progress', 'resolved', 'wontfix']),
1099
+ note: z.string().max(500).optional(),
1100
+ });
1101
+ const customerFeedbackInboxLinkEpic = z.object({ id: z.string().max(200), epicId: z.string().max(200) });
1102
+
1054
1103
  /**
1055
1104
  * Wrap an IPC handler with schema validation. Returns a new handler that
1056
1105
  * parses the payload before calling the original. On invalid payload throws
@@ -1072,13 +1121,78 @@ function validated(schema, handler) {
1072
1121
  };
1073
1122
  }
1074
1123
 
1124
+ // ──────────────────────────────────────────── Web Remote command allowlist
1125
+ // Commands are split into three tiers:
1126
+ // READ_COMMANDS — return data; allowed when remoteEnabled=true.
1127
+ // SAS_GATED_READS — return sensitive user data (sessions, PRDs, logs,
1128
+ // transcript summaries); additionally require
1129
+ // _e2eAuthenticated=true (SAS confirmed by user).
1130
+ // A compromised relay cannot exfiltrate this data from
1131
+ // a session that has not been SAS-confirmed.
1132
+ // MUTATE_COMMANDS — write files, spawn processes, or mutate persisted
1133
+ // state; gated behind remoteControlEnabled=true AND
1134
+ // _e2eAuthenticated=true.
1135
+ // ALLOWED_COMMANDS is the union, kept for existing import compatibility.
1136
+ //
1137
+ // Ungated READ_COMMANDS (justify each):
1138
+ // cmd:app:version — exposes only the app semver string; no user data.
1139
+ // cmd:session:unsubscribe — teardown lifecycle; returns nothing sensitive.
1140
+ const READ_COMMANDS = new Set([
1141
+ 'cmd:app:version',
1142
+ // v2 mobile: unsubscribe is a teardown lifecycle call with no data payload.
1143
+ 'cmd:session:unsubscribe',
1144
+ ]);
1145
+
1146
+ // Sensitive reads — return user data; require SAS confirmation same as MUTATE.
1147
+ const SAS_GATED_READS = new Set([
1148
+ 'cmd:sessions:load',
1149
+ 'cmd:history:aggregate',
1150
+ // subscribe initiates a live stream of session state/summary — sensitive.
1151
+ 'cmd:session:subscribe',
1152
+ // NOTE: cmd:exchanges:list is intentionally NOT allowlisted — webRemote.cjs has
1153
+ // no dispatch handler for it, so an allowlist entry would only fail closed with
1154
+ // an opaque reject. Re-add here together with the handler when remote exchanges
1155
+ // are wired, so the allowlist always mirrors an actual capability.
1156
+ //
1157
+ // Scheduler/Epics commands (cmd:schedule:*) were removed here deliberately
1158
+ // (core scheduler/Epics redesign) — Remote no longer reaches into scheduler
1159
+ // internals at all; a future Remote rebuild will talk to Scheduler/Epics
1160
+ // through whatever higher-level surface that redesign lands on, not this
1161
+ // per-command allowlist.
1162
+ ]);
1163
+
1164
+ const MUTATE_COMMANDS = new Set([
1165
+ 'cmd:sessions:save',
1166
+ 'cmd:pty:spawn',
1167
+ 'cmd:pty:write',
1168
+ // pty:kill terminates a live session; pty:resize drives the geometry of the
1169
+ // user's interactive PTY — both write live process state, so they are gated
1170
+ // behind remoteControlEnabled + SAS like every other mutation. A read-only
1171
+ // mobile mirror has no business killing or resizing the desktop's session.
1172
+ 'cmd:pty:kill',
1173
+ 'cmd:pty:resize',
1174
+ // Pushes a prompt into an open tab's chat queue — a real mutation of that
1175
+ // tab's session, same tier as pty:write.
1176
+ 'cmd:chat:send',
1177
+ ]);
1178
+
1179
+ const ALLOWED_COMMANDS = new Set([...READ_COMMANDS, ...SAS_GATED_READS, ...MUTATE_COMMANDS]);
1180
+
1075
1181
  module.exports = {
1076
1182
  // Centralized slug regex — used by scheduler.cjs and queueOps.cjs for
1077
1183
  // direct test()/match() containment checks alongside the zod parses.
1078
1184
  SCHEDULE_SLUG_RE,
1079
1185
  SCHEDULE_RUN_ID_RE,
1080
1186
  PRD_CREATE_SLUG_RE,
1187
+ READ_COMMANDS,
1188
+ SAS_GATED_READS,
1189
+ MUTATE_COMMANDS,
1190
+ ALLOWED_COMMANDS,
1081
1191
  schemas: {
1192
+ webRemotePair,
1193
+ webRemoteRevokeDevice,
1194
+ webRemoteAuditTail,
1195
+ sessionSubscribe,
1082
1196
  ptySpawn,
1083
1197
  ptyTabId,
1084
1198
  ptyAlive,
@@ -1179,7 +1293,13 @@ module.exports = {
1179
1293
  agentsResolveEpicModel,
1180
1294
  agentsResolveEpicEffort,
1181
1295
  delegationReadinessCwd,
1296
+ appCreateProjectFolder,
1182
1297
  telemetrySetConfig,
1298
+ customerFeedbackSubmit,
1299
+ customerFeedbackMarkSeen,
1300
+ customerFeedbackInboxList,
1301
+ customerFeedbackInboxSetStatus,
1302
+ customerFeedbackInboxLinkEpic,
1183
1303
  },
1184
1304
  validated,
1185
1305
  };