claude-code-session-manager 0.112.0 → 0.113.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 (68) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/assets/{AgentLibrary-RwPvEgqm.js → AgentLibrary-wvrcLGGS.js} +2 -2
  3. package/dist/assets/{Button-36j3PzYv.js → Button-B6h0F0XT.js} +1 -1
  4. package/dist/assets/{DataModel-DUhANo4l.js → DataModel-C5pW9E3u.js} +1 -1
  5. package/dist/assets/{History-BeZzNjmv.js → History-BV4e8YOM.js} +2 -2
  6. package/dist/assets/{Hooks-iQ8845Hp.js → Hooks-CrFSv4x1.js} +3 -3
  7. package/dist/assets/{Library-DPE1h-1L.js → Library-D-GIt3EY.js} +7 -7
  8. package/dist/assets/{ListDetail-PStOuQAo.js → ListDetail-Bco_wfs6.js} +1 -1
  9. package/dist/assets/{MarkdownEditor-DPw-N2-m.js → MarkdownEditor-C-yHSXNV.js} +1 -1
  10. package/dist/assets/{McpServers-CUyRTNep.js → McpServers-CfDekadG.js} +2 -2
  11. package/dist/assets/{Memory-bZ2xjBGy.js → Memory-CGGLexkR.js} +6 -6
  12. package/dist/assets/{Panel-_VZ_uXNg.js → Panel-DGMEuYYG.js} +1 -1
  13. package/dist/assets/{Permissions-PPRmfNbK.js → Permissions-Cx2zjtYZ.js} +3 -3
  14. package/dist/assets/{Plugins-BFcvfvlx.js → Plugins-Bc_W3IyR.js} +2 -2
  15. package/dist/assets/ProvenanceBadge-DiKAUR1u.js +1 -0
  16. package/dist/assets/{RemoteMachines-D5V0lzgW.js → RemoteMachines-8_77-WPD.js} +2 -2
  17. package/dist/assets/{SaveBar-Dw64FVdM.js → SaveBar-Blpzdq1R.js} +1 -1
  18. package/dist/assets/{Scheduler-C8s_pJ3I.js → Scheduler-Bd70AM4X.js} +8 -8
  19. package/dist/assets/{ScopeSwitcher-D-nKDMAE.js → ScopeSwitcher-wxN0h4YW.js} +1 -1
  20. package/dist/assets/Settings-CQ7DA3zW.js +4 -0
  21. package/dist/assets/{SkillReferenceGraph-BbZFMHim.js → SkillReferenceGraph-DH1NLOiS.js} +1 -1
  22. package/dist/assets/Skills-CmyRyu4_.js +2 -0
  23. package/dist/assets/SystemPrompt-KAc3UMCd.js +1 -0
  24. package/dist/assets/TagLibrary-C8EvHnpg.js +1 -0
  25. package/dist/assets/{TiptapBody-Cr-hxX8L.js → TiptapBody-d423mH6D.js} +1 -1
  26. package/dist/assets/{Toggle-7WpPzcZH.js → Toggle-D1wsGE9S.js} +1 -1
  27. package/dist/assets/{ViewTabs-9lQCRwWJ.js → ViewTabs-CjHw3kGY.js} +1 -1
  28. package/dist/assets/{historyTotals-BrFxw5J1.js → historyTotals-DFHzqLt7.js} +1 -1
  29. package/dist/assets/{index-CTBpf-26.js → index-Ci1u018F.js} +143 -143
  30. package/dist/assets/{settingsSchema-Dh5Kl7VO.js → settingsSchema-Ctf3pZVI.js} +1 -1
  31. package/dist/assets/{useAgentsDirWatch-CMpx0d04.js → useAgentsDirWatch-B1NBmOAe.js} +1 -1
  32. package/dist/assets/{useRemotePeers-CI2axd-L.js → useRemotePeers-BHjSJs9D.js} +1 -1
  33. package/dist/index.html +1 -1
  34. package/package.json +1 -1
  35. package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
  36. package/plugins/session-manager-dev/skills/send-feedback/SKILL.md +13 -0
  37. package/scripts/scheduler-mcp-server.cjs +23 -5
  38. package/src/main/build-info.json +4 -4
  39. package/src/main/chatRunner.cjs +18 -1
  40. package/src/main/config.cjs +18 -1
  41. package/src/main/index.cjs +6 -0
  42. package/src/main/ipcSchemas.cjs +3 -0
  43. package/src/main/lib/activeSessions.cjs +13 -0
  44. package/src/main/lib/claude/rateLimitDetect.cjs +29 -10
  45. package/src/main/lib/claude/rateLimitWindow.cjs +23 -11
  46. package/src/main/lib/credentials.cjs +16 -90
  47. package/src/main/lib/feedback-remote/crossProjectFeedback.cjs +107 -9
  48. package/src/main/lib/feedback-remote/humanPromptLedger.cjs +93 -0
  49. package/src/main/lib/mcpToolCatalog.cjs +9 -3
  50. package/src/main/lib/prd/depSlugResolve.cjs +16 -1
  51. package/src/main/lib/scheduler/finalizeReason.cjs +30 -0
  52. package/src/main/lib/scheduler/flatSweepThrottle.cjs +17 -0
  53. package/src/main/lib/scheduler/runLogRetention.cjs +34 -13
  54. package/src/main/lib/scheduler/schedulerBatch.cjs +8 -12
  55. package/src/main/lib/telemetry/telemetryClient.cjs +8 -4
  56. package/src/main/lib/util/atomicFs.cjs +42 -0
  57. package/src/main/lib/util/loopDelay.cjs +1 -1
  58. package/src/main/runVerify.cjs +5 -2
  59. package/src/main/scheduler.cjs +43 -10
  60. package/src/main/supervisor.cjs +45 -22
  61. package/src/main/webRemote.cjs +15 -0
  62. package/src/preload/api.d.ts +6 -0
  63. package/src/preload/index.cjs +6 -0
  64. package/dist/assets/ProvenanceBadge-B1WUknak.js +0 -1
  65. package/dist/assets/Settings-CQSrR4mO.js +0 -4
  66. package/dist/assets/Skills-Bzyrh6EQ.js +0 -2
  67. package/dist/assets/SystemPrompt-DP6i3Dx0.js +0 -1
  68. package/dist/assets/TagLibrary-iip0C6Bl.js +0 -1
@@ -1 +1 @@
1
- import{aE as b,ao as n,S as N,aY as O}from"./index-CTBpf-26.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 Y(){return $||($=B(K)),$}export{G as E,Y as s,V as u};
1
+ import{aF as b,ap as n,S as N,aZ as O}from"./index-Ci1u018F.js";import{r as K}from"./claude-settings-schema-COigsajx.js";function G({node:e,targetScope:r,onOverride:s,schema:a,rootLabel:m}){const[f,x]=b.useState(""),[u,o]=b.useState(!0),g=[],d=e.kind==="object"&&Object.keys(e.children).length===0,y=((a==null?void 0:a.rootKeys())??[]).length>0;return n.jsxs("div",{className:"h-full flex flex-col",children:[n.jsxs("div",{className:"shrink-0 border-b border-line bg-bg-elev px-3 py-1.5 flex items-center gap-2 text-xs",children:[n.jsxs("span",{className:"text-fg-faint",children:["merged · overrides → ",n.jsx("span",{className:"text-fg-dim",children:N[r].label})]}),y&&n.jsxs("label",{className:"flex items-center gap-1 text-fg-faint cursor-pointer",children:[n.jsx("input",{type:"checkbox",checked:u,onChange:t=>o(t.target.checked),className:"accent-accent"}),"show unset keys"]}),n.jsx("div",{className:"flex-1"}),n.jsx("input",{value:f,onChange:t=>x(t.target.value),placeholder:"filter keys or descriptions…",className:"bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg w-56"})]}),n.jsx("div",{className:"flex-1 overflow-auto px-3 py-2 font-mono text-xs",children:d&&!u?n.jsx("div",{className:"text-fg-faint italic px-2 py-4",children:"no settings resolved — all scope files empty or missing"}):n.jsx(E,{node:e,path:g,label:m??"(root)",targetScope:r,onOverride:s,filter:f,depth:0,schema:a,showGhosts:u,defaultOpen:!0})})]})}function E(e){return e.node.kind==="object"?n.jsx(P,{...e,node:e.node}):e.node.kind==="array"?n.jsx(J,{...e,node:e.node}):n.jsx(R,{...e,node:e.node})}function A(e,r){return r.trim()?e.toLowerCase().includes(r.toLowerCase()):!0}function w(e,r,s){return!!(!s.trim()||e.some(a=>A(a,s))||r!=null&&r.description&&A(r.description,s))}function P({node:e,path:r,label:s,targetScope:a,onOverride:m,filter:f,depth:x,schema:u,showGhosts:o,defaultOpen:g}){const[d,h]=b.useState(g??x<2),y=(u==null?void 0:u.at(r))??null,t=new Set(Object.keys(e.children)),i=o?(y==null?void 0:y.knownChildren)??[]:[],c=Array.from(new Set([...t,...i])).sort(),l=b.useMemo(()=>f.trim()?c.filter(p=>{const v=(u==null?void 0:u.at([...r,p]))??null;return w([...r,p],v,f)?!0:t.has(p)?L(e.children[p],[...r,p],f,u):!1}):c,[c.join(","),f,e,r.join("."),u]);if(f.trim()&&l.length===0&&!w(r,y,f))return null;if(r.length===0)return n.jsx("div",{children:l.map(p=>j(p))});return n.jsxs("div",{children:[n.jsxs("div",{className:"flex items-center gap-1 py-0.5 hover:bg-bg-elev/50 rounded cursor-pointer",style:{paddingLeft:x*14},onClick:()=>h(!d),children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0",children:d?"▾":"▸"}),n.jsx("span",{className:"text-fg",children:s}),n.jsxs("span",{className:"text-fg-faint",children:[" {",t.size,i.length>t.size?`/${new Set([...t,...i]).size}`:"","}"]}),(y==null?void 0:y.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:y.description,children:["— ",y.description]})]}),d&&l.map(p=>j(p))]});function j(p){if(t.has(p))return n.jsx(E,{node:e.children[p],path:[...r,p],label:p,targetScope:a,onOverride:m,filter:f,depth:x+1,schema:u,showGhosts:o},p);const v=(u==null?void 0:u.at([...r,p]))??null;return v?n.jsx(I,{path:[...r,p],label:p,info:v,targetScope:a,onSet:m,depth:x+1,filter:f},p):null}}function L(e,r,s,a){const m=(a==null?void 0:a.at(r))??null;if(w(r,m,s))return!0;if(e.kind!=="object")return!1;for(const f of Object.keys(e.children))if(L(e.children[f],[...r,f],s,a))return!0;return!1}function J({node:e,path:r,label:s,filter:a,depth:m,schema:f}){const[x,u]=b.useState(!0),o=(f==null?void 0:f.at(r))??null;return w(r,o,a)?n.jsxs("div",{children:[n.jsxs("div",{className:"flex items-center gap-1 py-0.5 hover:bg-bg-elev/50 rounded cursor-pointer",style:{paddingLeft:m*14},onClick:()=>u(!x),children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0",children:x?"▾":"▸"}),n.jsx("span",{className:"text-fg",children:s}),n.jsxs("span",{className:"text-fg-faint",children:[" [",e.items.length,"]"]}),n.jsx(S,{tone:"warn",children:"accumulated"}),(o==null?void 0:o.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:o.description,children:["— ",o.description]})]}),x&&e.items.map((g,d)=>n.jsxs("div",{className:"flex items-start gap-2 py-0.5",style:{paddingLeft:(m+1)*14},children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0"}),n.jsxs("span",{className:"text-fg-faint",children:[d,":"]}),n.jsx("span",{className:"text-fg-dim truncate max-w-xl",children:k(g.value)}),n.jsx(C,{scope:g.fromScope})]},d))]}):null}function R({node:e,path:r,label:s,targetScope:a,onOverride:m,filter:f,depth:x,schema:u}){const[o,g]=b.useState(!1),d=(u==null?void 0:u.at(r))??null;if(!w(r,d,f))return null;const h=e.winner!==a,y=e.shadowed.length>0;return n.jsxs("div",{children:[n.jsxs("div",{className:"group flex items-center gap-2 py-0.5 hover:bg-bg-elev/50 rounded",style:{paddingLeft:x*14},children:[n.jsx("span",{className:`w-3 shrink-0 ${y?"text-fg-faint cursor-pointer":""}`,onClick:()=>y&&g(!o),children:y?o?"▾":"▸":""}),n.jsxs("span",{className:"text-fg",children:[s,":"]}),n.jsx("span",{className:"text-fg-dim truncate max-w-md",children:k(e.value)}),n.jsx(C,{scope:e.winner}),(d==null?void 0:d.deprecated)&&n.jsx(S,{tone:"warn",children:"deprecated"}),(d==null?void 0:d.default)!==void 0&&_(e.value,d.default)&&n.jsx(S,{tone:"dim",children:"= default"}),e.typeMismatch&&n.jsx("span",{className:"text-yellow-500/80",title:"shadowed scopes have a different value type",children:"⚠"}),(d==null?void 0:d.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:d.description,children:["— ",d.description]}),n.jsx("div",{className:"flex-1"}),(d==null?void 0:d.enum)&&n.jsx(F,{values:d.enum}),n.jsx("button",{disabled:!h,onClick:()=>m(r,e.value),className:`opacity-0 group-hover:opacity-100 px-1.5 py-0 text-[10px] border rounded transition-opacity ${h?"border-accent/50 text-accent hover:bg-accent hover:text-bg":"border-line text-fg-faint cursor-not-allowed"}`,title:h?`copy into ${N[a].label} draft`:`already winning in ${N[a].label}`,children:"override"})]}),o&&e.shadowed.map((t,i)=>n.jsxs("div",{className:"flex items-center gap-2 py-0.5 text-fg-faint line-through",style:{paddingLeft:(x+1)*14+12},children:[n.jsx("span",{children:"shadowed:"}),n.jsx("span",{className:"truncate max-w-xl",children:k(t.value)}),n.jsx(C,{scope:t.scope,dim:!0})]},i))]})}function I({path:e,label:r,info:s,targetScope:a,onSet:m,depth:f,filter:x}){if(!w(e,s,x))return null;const u=s.default!==void 0?s.default:M(s.type),o=Array.isArray(s.type)?s.type.join("|"):s.type??"?";return n.jsxs("div",{className:"group flex items-center gap-2 py-0.5 hover:bg-bg-elev/50 rounded opacity-60",style:{paddingLeft:f*14},children:[n.jsx("span",{className:"w-3 shrink-0"}),n.jsx("span",{className:"text-fg-dim",children:r}),n.jsx(S,{tone:"dim",children:o}),s.required&&n.jsx(S,{tone:"warn",children:"required"}),s.default!==void 0&&n.jsxs("span",{className:"text-fg-faint",children:["default: ",k(s.default)]}),s.description&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:s.description,children:["— ",s.description]}),n.jsx("div",{className:"flex-1"}),s.enum&&n.jsx(F,{values:s.enum}),n.jsx("button",{onClick:()=>m(e,u),className:"opacity-0 group-hover:opacity-100 px-1.5 py-0 text-[10px] border border-accent/50 rounded text-accent hover:bg-accent hover:text-bg transition-opacity",title:`write to ${N[a].label} draft`,children:"set"})]})}function _(e,r){return e===r?!0:typeof e!=typeof r?!1:e&&r&&typeof e=="object"?JSON.stringify(e)===JSON.stringify(r):!1}function M(e){switch(Array.isArray(e)?e[0]:e){case"boolean":return!1;case"integer":case"number":return 0;case"string":return"";case"array":return[];case"object":return{};case"null":return null;default:return null}}function k(e){if(typeof e=="string")return JSON.stringify(e);if(e===null)return"null";if(typeof e=="object"){const r=JSON.stringify(e);return r.length>100?r.slice(0,100)+"…":r}return String(e)}function C({scope:e,dim:r}){const s=e==="user"?"bg-bg-hi text-fg-dim border-line":e==="project"?"bg-blue-950/30 text-blue-400/80 border-blue-900/40":"bg-purple-950/30 text-purple-400/80 border-purple-900/40";return n.jsx("span",{className:`text-[10px] px-1 py-0 rounded border uppercase tracking-wide shrink-0 ${s} ${r?"opacity-60":""}`,children:N[e].label})}function S({children:e,tone:r="default"}){const s={default:"bg-bg-hi text-fg-dim border-line",warn:"bg-yellow-950/30 text-yellow-500/80 border-yellow-900/40",dim:"bg-bg-elev text-fg-faint border-line"}[r];return n.jsx("span",{className:`text-[10px] px-1 py-0 rounded border uppercase tracking-wide shrink-0 ${s}`,children:e})}function F({values:e}){const r=e.map(s=>String(s)).join(" | ");return n.jsxs("span",{className:"text-[10px] text-fg-faint truncate max-w-xs",title:`allowed: ${r}`,children:["∈ ",r.length>30?r.slice(0,30)+"…":r]})}function V(e,r,s){const a=O(o=>o.files),m=O(o=>o.loadJson),f=O(o=>o.watchFile),x=O(o=>o.unwatchFile),u=b.useMemo(()=>{const o={};for(const g of e.scopes){const d=e.resolve(g,r??"",s);d&&(o[g]=d)}return o},[e,r,s]);return b.useEffect(()=>{const o=Object.values(u).filter(Boolean);return o.forEach(g=>{a[g]||m(g),f(g)}),()=>{o.forEach(g=>x(g))}},[JSON.stringify(u)]),u}function B(e){const r=e??{},s={...r.$defs??{},...r.definitions??{}},a=t=>{if(!t)return{};if(!t.$ref)return x(t);const i=/^#\/(\$defs|definitions)\/(.+)$/.exec(t.$ref);if(!i)return x(t);const c=s[i[2]];return c?a({...c,...u(t)}):x(t)},m=new Set(["__proto__","constructor","prototype"]),f=t=>{if(!t)return{};const i={};for(const[c,l]of Object.entries(t))m.has(c)||(i[c]=l);return i},x=t=>{if(!t.allOf||t.allOf.length===0)return t;const i={...t};delete i.allOf;for(const c of t.allOf){const l=a(c);Object.assign(i,{properties:{...f(i.properties),...f(l.properties)},required:q([...i.required??[],...l.required??[]])}),!i.description&&l.description&&(i.description=l.description),i.default===void 0&&l.default!==void 0&&(i.default=l.default),!i.type&&l.type&&(i.type=l.type)}return i},u=t=>{const{$ref:i,...c}=t;return c},o=(t,i)=>{let c=a(t);for(const l of i){if(!c)return null;if(/^\d+$/.test(l)){const v=Array.isArray(c.items)?c.items[Number(l)]:c.items;c=v?a(v):null;continue}const j=c.properties;if(j&&!m.has(l)&&l in j){c=a(j[l]);continue}const p=c.additionalProperties;if(p&&typeof p=="object"){c=a(p);continue}return null}return c},g=(t,i,c)=>{if(!t)return null;const l={};if(t.description&&(l.description=t.description),t.type&&(l.type=t.type),t.default!==void 0&&(l.default=t.default),t.enum&&(l.enum=t.enum),t.const!==void 0&&(l.const=t.const),t.deprecated&&(l.deprecated=!0),t.examples&&(l.examples=t.examples),t.properties&&(l.knownChildren=Object.keys(t.properties)),c&&(i!=null&&i.includes(c))&&(l.required=!0),t.items&&!Array.isArray(t.items)){const j=a(t.items),p=g(j,void 0,null);p&&(l.itemsInfo=p)}return(t.additionalProperties===!0||typeof t.additionalProperties=="object")&&(l.open=!0),l},d=t=>i=>{if(i.length===0)return g(a(t),void 0,null);const c=o(t,i.slice(0,-1));if(!c)return null;const l=i[i.length-1],j=o(c,[l]);return g(j,c.required,l)},h=t=>{const i=a(t);return Object.keys(i.properties??{})},y=t=>({at:d(t),rootKeys:()=>h(t),rootedAt:i=>{const c=o(t,i);return y(c??{})}});return y(r)}function q(e){return Array.from(new Set(e))}let $=null;function D(){return $||($=B(K)),$}export{G as E,D as s,V as u};
@@ -1 +1 @@
1
- import{V as e,b2 as a,aZ as n}from"./index-CTBpf-26.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-Ci1u018F.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{ao as t,A as f,bd as g,aE as d}from"./index-CTBpf-26.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{ap as t,A as f,be as g,aF as d}from"./index-Ci1u018F.js";function j({children:s,right:e}){return t.jsxs("div",{className:"flex items-baseline justify-between mb-2.5",children:[t.jsx("span",{className:"text-[11.5px] font-bold uppercase tracking-[0.8px] text-fg-faint",children:s}),e]})}function y({kind:s="soft",icon:e,children:c,onClick:i,disabled:o,testId:n}){return t.jsxs("button",{type:"button","data-testid":n,onClick:i,disabled:o,className:`shrink-0 inline-flex items-center gap-2 rounded-lg px-3.5 py-2 text-[13px] font-semibold transition-colors disabled:opacity-50 ${s==="soft"?"bg-bg-hi border border-line text-fg hover:bg-bg":"text-fg-dim hover:text-fg"}`,children:[e&&t.jsx(f,{name:e,size:15}),c]})}function N({icon:s,title:e,subtitle:c,badge:i,action:o,testId:n}){return t.jsxs("div",{"data-testid":n,className:"flex items-center gap-3.5 bg-bg-hi border border-line rounded-xl px-[18px] py-3.5",children:[t.jsx("span",{className:"w-[34px] h-[34px] rounded-lg bg-bg border border-line grid place-items-center text-fg-dim shrink-0",children:t.jsx(f,{name:s,size:17})}),t.jsxs("div",{className:"flex-1 min-w-0",children:[t.jsxs("div",{className:"flex items-center gap-2.5 mb-0.5",children:[t.jsx("span",{className:"text-sm font-semibold text-fg truncate",children:e}),i]}),t.jsx("div",{className:"font-mono text-[11.5px] text-fg-faint truncate",children:c})]}),o]})}function v({onClick:s,disabled:e,testId:c,children:i="Revoke"}){return t.jsx("button",{"data-testid":c,onClick:s,disabled:e,className:"shrink-0 rounded-lg border border-line bg-bg text-accent px-3.5 py-1.5 text-[12.5px] font-semibold hover:bg-bg-hi disabled:opacity-50 transition-colors",children:i})}function k({code:s,onConfirm:e,testId:c,codeTestId:i,confirmTestId:o,peer:n="browser"}){return t.jsxs("div",{"data-testid":c,className:"flex items-start gap-3.5 px-5 py-4 rounded-2xl border border-accent/40 bg-accent/[0.06]",children:[t.jsx("span",{className:"w-[38px] h-[38px] rounded-[11px] shrink-0 grid place-items-center bg-bg-hi text-accent",children:t.jsx(f,{name:"shield",size:20})}),t.jsxs("div",{className:"flex-1 min-w-0 text-sm text-fg",children:[t.jsx("strong",{className:"font-semibold",children:"Verify E2E session."})," Compare this code with the ",n," — they must match.",t.jsx("div",{"data-testid":i,className:"my-2 text-3xl font-mono tracking-[0.3em] text-center text-accent select-all",children:s}),t.jsx("p",{className:"text-[12.5px] text-fg-faint mb-3",children:"If the codes match, confirm below. Mutating commands are blocked until confirmed."}),t.jsx("button",{"data-testid":o,onClick:e,className:"rounded-lg bg-accent text-white px-4 py-2 text-[13px] font-semibold hover:opacity-90 transition-opacity",children:"Codes match — confirm"})]})]})}function C({children:s,testId:e}){return t.jsx("div",{"data-testid":e,className:"bg-bg-hi border border-line rounded-xl p-5 text-center text-[13.5px] text-fg-faint italic font-serif",children:s})}const m=s=>(s==null?void 0:s.message)||String(s);function S(s){const e=g(r=>r.show),[c,i]=d.useState([]),o=d.useRef(s);o.current=s;const n=d.useCallback(async()=>{try{i(await window.api.remoteClient.list())}catch(r){e("error",m(r))}},[e]);d.useEffect(()=>{n();const r=window.api.remoteClient.onStatus(a=>{var x;(x=o.current)==null||x.call(o,a),i(l=>l.map(b=>b.peerId===a.peerId?{...b,status:a.status,reason:a.reason,sas:a.sas}:b))});return()=>{r()}},[n]);const p=d.useCallback(async r=>{try{const a=await window.api.remoteClient.connect(r);i(x=>x.map(l=>l.peerId===r?{...l,status:a.status,reason:a.reason,sas:a.sas}:l))}catch(a){e("error",m(a))}},[e]),u=d.useCallback(async r=>{try{const a=await window.api.remoteClient.confirmSas(r);a.ok||e("error",a.reason??"SAS confirmation failed"),await n()}catch(a){e("error",m(a))}},[e,n]),h=d.useCallback(async r=>{try{await window.api.remoteClient.remove(r),await n()}catch(a){e("error",m(a))}},[e,n]);return{peers:c,refresh:n,connect:p,confirmSas:u,revoke:h}}export{C as E,y as P,N as R,k as S,v as a,j as b,S as u};
package/dist/index.html CHANGED
@@ -7,7 +7,7 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,400&family=Geist:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
10
- <script type="module" crossorigin src="./assets/index-CTBpf-26.js"></script>
10
+ <script type="module" crossorigin src="./assets/index-Ci1u018F.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="./assets/monaco-editor-BsIlgG7c.js">
12
12
  <link rel="stylesheet" crossorigin href="./assets/monaco-editor-Cu5D__1H.css">
13
13
  <link rel="stylesheet" crossorigin href="./assets/index-Dutcsly-.css">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-session-manager",
3
- "version": "0.112.0",
3
+ "version": "0.113.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",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "session-manager-dev",
3
3
  "description": "The default end-to-end development skillset for the Claude Code Session Manager: decompose work into scheduler PRDs and track them to a validated finish (/develop), publish releases to npm (/builder), and hand a finding to another project on this machine (/send-feedback). Together with the session-manager scheduler these form a develop → queue → validate → release loop.",
4
- "version": "0.2.0",
4
+ "version": "0.2.1",
5
5
  "author": {
6
6
  "name": "Stanislav Georgiev",
7
7
  "url": "https://github.com/StanislavBG"
@@ -139,6 +139,15 @@ project has not yet agreed with. Use `bug` or `feature` only when the finding is
139
139
  unambiguous and you'd expect them to start decomposing it on sight (those two
140
140
  treat PRD decomposition as the expected next step).
141
141
 
142
+ **Auto-start (only when the human asked).** If the human in THIS conversation
143
+ explicitly asked you to do the work in the other project, also pass
144
+ `autoStart: true` and `humanRequestQuote` — copied VERBATIM from their own
145
+ message, which must name the target project. Never write or paraphrase the
146
+ quote, and never pass `autoStart` for something you decided on your own: that
147
+ stays a plain proposal. If the quote cannot be verified, the call silently falls
148
+ back to a normal `proposed` session (the result note gives the reason), so
149
+ report it as a proposal in that case.
150
+
142
151
  `fromEpicId` is filled in automatically from your running session; pass it
143
152
  explicitly only if you know it and the auto-fill failed. When it resolves, a
144
153
  receipt is chained onto your own session's event log so your transcript records
@@ -151,6 +160,10 @@ Say exactly this much: the proposal was **delivered as a proposed session** in
151
160
  human sees the motivation, plan and acceptance criteria on the proposal before
152
161
  approving.
153
162
 
163
+ If the result status is `auto-approved`, say instead that the session was
164
+ auto-approved on the human's request and is starting in `<toCwd>` — not that it
165
+ is done.
166
+
154
167
  Do **not** say the other project has been notified-and-is-working-on-it, that
155
168
  the bug is filed/fixed/assigned, or that you'll follow up when they respond.
156
169
  **There is no reply channel and no callback.** If the user needs an answer, the
@@ -410,6 +410,14 @@ const TOOLS = [
410
410
  type: 'array', items: { type: 'string' },
411
411
  description: 'Optional absolute paths worth reading, appended as Reference lines',
412
412
  },
413
+ autoStart: {
414
+ type: 'boolean',
415
+ description: 'Set true ONLY when the human in THIS conversation explicitly asked for this work in the other project. Requires humanRequestQuote. Never set it for work you decided on your own initiative. An unverified request silently falls back to a plain PROPOSED session.',
416
+ },
417
+ humanRequestQuote: {
418
+ type: 'string',
419
+ description: "Required with autoStart. Copy VERBATIM from the human's own message in this conversation; that message must name the target project. Do not paraphrase or write it yourself.",
420
+ },
413
421
  },
414
422
  required: ['toCwd', 'fromCwd', 'title', 'motivation', 'acceptanceCriteria', 'body'],
415
423
  },
@@ -699,11 +707,21 @@ async function handleCallTool(request) {
699
707
  payload.originProjectRoot = process.env.SM_PROJECT_ROOT;
700
708
  }
701
709
  const result = await adminRequest('POST', '/admin/feedback/open-session', payload);
702
- // Never say "sent", "filed" or "fixed" — this call delivers a PROPOSAL
703
- // that a human in the other project must still approve.
704
- const note = result?.ok !== false
705
- ? ' — delivered as a PROPOSED session in the receiving project; it runs only if a human there presses Approve & start. No reply channel.'
706
- : '';
710
+ // Never say "sent", "filed" or "fixed" — this call delivers a session
711
+ // that is either a PROPOSAL a human must approve or, when the human's own
712
+ // request was verified, one that is auto-approved and starting. It never
713
+ // reports the work as done.
714
+ let note = '';
715
+ if (result?.ok !== false) {
716
+ if (result?.status === 'auto-approved') {
717
+ note = ' — auto-approved on the human\'s explicit request; the session is starting in the receiving project. No reply channel; the work is not done yet.';
718
+ } else {
719
+ note = ' — delivered as a PROPOSED session in the receiving project; it runs only if a human there presses Approve & start. No reply channel.';
720
+ if (args.autoStart && result?.autoStart && result.autoStart.granted !== true && result.autoStart.reason) {
721
+ note += ` Auto-start was requested but not granted: ${result.autoStart.reason}`;
722
+ }
723
+ }
724
+ }
707
725
  return { content: [{ type: 'text', text: JSON.stringify(result) + note }] };
708
726
  }
709
727
  if (name === 'chat_send_prompt') {
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "0.112.0",
3
- "gitSha": "766f7a3483a11060c0fad4f64ce760da47af9905",
4
- "gitShortSha": "766f7a34",
2
+ "version": "0.113.0",
3
+ "gitSha": "07baa2cc0601e109b6591f57bfb2ced8f34884dd",
4
+ "gitShortSha": "07baa2cc",
5
5
  "gitBranch": "HEAD",
6
- "builtAt": "2026-10-11T20:38:06.016Z",
6
+ "builtAt": "2026-10-12T19:15:00.757Z",
7
7
  "dirty": false
8
8
  }
@@ -1008,10 +1008,26 @@ function registerAdminRoute(adminHttp) {
1008
1008
 
1009
1009
  // ─── IPC handler registration ─────────────────────────────────────────────
1010
1010
 
1011
+ /**
1012
+ * Record a chat prompt into the human prompt ledger, only when the renderer
1013
+ * explicitly declared origin === 'human'. Absent/other origins are not-human
1014
+ * (fail-closed). Never throws.
1015
+ * @param {{ sessionId: string, prompt: string, origin?: string }} args
1016
+ */
1017
+ function noteChatRunOrigin({ sessionId, prompt, origin }) {
1018
+ try {
1019
+ if (origin !== 'human') return;
1020
+ require('./lib/feedback-remote/humanPromptLedger.cjs').recordHumanPrompt(sessionId, prompt);
1021
+ } catch {
1022
+ /* never throws */
1023
+ }
1024
+ }
1025
+
1011
1026
  function registerChatHandlers() {
1012
1027
  const { schemas, validated } = require('./ipcSchemas.cjs');
1013
1028
 
1014
- ipcMain.handle('chat:run', validated(schemas.chatRun, async ({ tabId, sessionId, prompt, cwd, resume, promptId }) => {
1029
+ ipcMain.handle('chat:run', validated(schemas.chatRun, async ({ tabId, sessionId, prompt, cwd, resume, promptId, origin }) => {
1030
+ noteChatRunOrigin({ sessionId, prompt, origin });
1015
1031
  // Report what actually happened instead of an unconditional { ok: true }.
1016
1032
  // A user send is always accepted now (queued behind the tab's live run if
1017
1033
  // one holds it), but the renderer gets the truth either way rather than a
@@ -1042,6 +1058,7 @@ module.exports = {
1042
1058
  __liveSessionCount: () => liveSessions.size,
1043
1059
  attachWindow,
1044
1060
  registerChatHandlers,
1061
+ noteChatRunOrigin,
1045
1062
  parseStopSignal,
1046
1063
  splitStopSignal,
1047
1064
  parseContextUsageMarkdown,
@@ -392,8 +392,21 @@ function watchKeyOf(key, depth) {
392
392
  */
393
393
  function watch(paths, senderId, opts) {
394
394
  const depth = opts?.depth;
395
+ const watched = [];
396
+ const rejected = [];
395
397
  for (const rawPath of paths) {
396
- validatePath(expandHome(rawPath)); // security check — throws if out of bounds
398
+ try {
399
+ validatePath(expandHome(rawPath)); // security check — throws if out of bounds
400
+ } catch (err) {
401
+ // Per-path: one bad path must not abort the rest of the batch.
402
+ rejected.push(rawPath);
403
+ if (!warnedRejectedPaths.has(rawPath)) {
404
+ warnedRejectedPaths.add(rawPath);
405
+ logs.writeLine({ level: 'warn', scope: 'config', message: 'config:watch path reject', meta: { path: rawPath, error: err?.message } });
406
+ }
407
+ continue;
408
+ }
409
+ watched.push(rawPath);
397
410
  const key = normalizePathKey(rawPath);
398
411
  const mapKey = watchKeyOf(key, depth);
399
412
  if (senderId != null) {
@@ -430,8 +443,12 @@ function watch(paths, senderId, opts) {
430
443
  });
431
444
  watchers.set(mapKey, { watcher: w, refCount: 1 });
432
445
  }
446
+ return { watched, rejected };
433
447
  }
434
448
 
449
+ // Distinct rejected watch paths already warned about (once per process).
450
+ const warnedRejectedPaths = new Set();
451
+
435
452
  function unwatch(paths, senderId, opts) {
436
453
  for (const rawPath of paths) {
437
454
  const key = watchKeyOf(normalizePathKey(rawPath), opts?.depth);
@@ -1402,6 +1402,12 @@ app.whenReady().then(async () => {
1402
1402
  console.log(`[main] scheduler ownership held by pid ${ownership.holderPid} — running scheduler-passive (no reconciliation, no admin server)`);
1403
1403
  logs.writeLine({ scope: 'scheduler', level: 'info', message: 'scheduler-passive: ownership held by another instance', meta: { holderPid: ownership.holderPid } });
1404
1404
  }
1405
+ // Sweep orphaned `<file>.tmp-<pid>-…` files from crashed atomic writers in the
1406
+ // machine-global state dir (per-project dirs belong to reconcile).
1407
+ try {
1408
+ const { removed } = require('./lib/util/atomicFs.cjs').sweepStaleTmpFiles(path.join(os.homedir(), '.claude', 'session-manager'));
1409
+ logs.writeLine({ scope: 'main', level: 'info', message: 'stale tmp sweep', meta: { removed: removed.length } });
1410
+ } catch { /* sweep is best-effort */ }
1405
1411
  // First-boot default: install the bundled session-manager-dev plugin (its 3
1406
1412
  // dev skills) from the app's own marketplace. One-shot + idempotent; never
1407
1413
  // throws. SM_SEED_DEV_PLUGIN_DISABLE=1 to opt out.
@@ -919,6 +919,9 @@ const chatRun = z.object({
919
919
  // Originating PromptTicket.id (PRD 748) when this run was dequeued from a
920
920
  // tab's prompt queue — absent for a fresh manual send with no ticket.
921
921
  promptId: z.string().min(1).max(128).optional(),
922
+ // Who authored this send. Only 'human' is auto-approve evidence for
923
+ // crossProjectFeedback; absent or any other value is treated as not-human.
924
+ origin: z.enum(['human', 'external', 'inbound-proposal']).optional(),
922
925
  });
923
926
 
924
927
  const chatCancel = z.object({
@@ -37,6 +37,17 @@ const TMP_DROP_ROOTS = {
37
37
  get prefixDropRoots() { return [WORKTREE_KIND_CONFIG.job.root, WORKTREE_KIND_CONFIG.epic.root]; },
38
38
  };
39
39
 
40
+ // Test-harness project cwds live at <tmpdir>/sst-*/…. They are never real
41
+ // projects, and leaking them into the active list made the renderer's batched
42
+ // config:watch hit 618 "Path outside allowed boundaries" rejects. Only the
43
+ // `sst-` first segment is matched, so test-stubbed HOMEs that are not sst-*
44
+ // dirs (see the exactDropCwds note above) are still kept.
45
+ function isSstHarnessCwd(cwd) {
46
+ const rel = path.relative(TMPDIR, cwd);
47
+ if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) return false;
48
+ return rel.split(path.sep)[0].startsWith('sst-');
49
+ }
50
+
40
51
  // Transcript reads only need the last line with a `cwd` field — a few dozen
41
52
  // lines is always enough. 64 KB keeps peak RSS proportionate.
42
53
  const TAIL_BYTES = 64 * 1024;
@@ -389,6 +400,7 @@ function deriveCwds(records, maxAgeMin, maxCwds, tmpDropRoots) {
389
400
  return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
390
401
  });
391
402
  if (isUnderPrefixDropRoot) return;
403
+ if (isSstHarnessCwd(cwd)) return;
392
404
  if (seen.has(cwd) || result.length >= maxCwds) return;
393
405
  // Must exist AND be a directory — a transcript naming a since-deleted
394
406
  // path, or a file, is not a project.
@@ -456,6 +468,7 @@ module.exports = {
456
468
  allProjectCwds,
457
469
  projectRootOf,
458
470
  worktreeMainRootOf,
471
+ isSstHarnessCwd,
459
472
  bustProjectCwdCache,
460
473
  __waitForPendingRefresh,
461
474
  };
@@ -13,20 +13,39 @@
13
13
 
14
14
  const { readTail } = require('../util/fileTail.cjs');
15
15
 
16
- /** Scan the tail of a job's log for the canonical rate-limit signal. We look
17
- * at the last 16 KB — final result event always lands at the end. Covers
18
- * both unified-window rate-limit types (five_hour, seven_day), the raw
19
- * 429 status, and the two human-readable limit-message phrasings the CLI
20
- * emits ("You've hit your limit" / "You've reached your <model> limit"). */
16
+ /** Scan the tail of a job's log for a GENUINE rate-limit rejection. Parses the
17
+ * last 16 KB line by line as JSON (non-JSON / partial lines are skipped) and
18
+ * returns true only for: (a) a rate_limit_event whose rate_limit_info.status
19
+ * is 'rejected'; (b) a result event with api_error_status 429; (c) an
20
+ * is_error result event whose `result` text is the CLI's limit message
21
+ * ("You've hit your ... limit" / "You've reached your ... limit").
22
+ *
23
+ * Never substring-match the raw text: informational rate_limit_event lines
24
+ * (status 'allowed' / 'allowed_warning') carry rateLimitType on every run, so
25
+ * the old substring match turned any non-zero exit (e.g. a watchdog SIGTERM,
26
+ * exit 143) into a machine-wide rate_limit pause (2026-10-11 incident: 4-day
27
+ * pause at 58% weekly). Tool output may also mention 429 or "rate limit". */
21
28
  function detectRateLimitInLog(logPath) {
22
29
  try {
23
30
  const text = readTail(logPath, 16384);
24
31
  if (!text) return false;
25
- return /"rateLimitType":"five_hour"/.test(text)
26
- || /"rateLimitType":"seven_day"/.test(text)
27
- || /"api_error_status":429/.test(text)
28
- || /You'?ve hit your limit/.test(text)
29
- || /You'?ve reached your .* limit/.test(text);
32
+ for (const line of text.split('\n')) {
33
+ const t = line.trim();
34
+ if (!t || t[0] !== '{') continue;
35
+ let ev;
36
+ try { ev = JSON.parse(t); } catch { continue; }
37
+ if (!ev || typeof ev !== 'object') continue;
38
+ if (ev.type === 'rate_limit_event') {
39
+ if (ev.rate_limit_info && ev.rate_limit_info.status === 'rejected') return true;
40
+ } else if (ev.type === 'result') {
41
+ if (ev.api_error_status === 429) return true;
42
+ if (ev.is_error === true && typeof ev.result === 'string'
43
+ && (/You'?ve hit your .*limit/.test(ev.result) || /You'?ve reached your .* limit/.test(ev.result))) {
44
+ return true;
45
+ }
46
+ }
47
+ }
48
+ return false;
30
49
  } catch {
31
50
  return false;
32
51
  }
@@ -21,17 +21,21 @@ const { readTail } = require('../util/fileTail.cjs');
21
21
  const TAIL_BYTES = 64 * 1024;
22
22
 
23
23
  /**
24
- * Scans a run log's tail for rate_limit_event entries and returns the
25
- * UNIX-seconds resetsAt of the window that actually bound (utilization >= 1.0)
26
- * in the LAST such event. When multiple windows bind in that event, the
27
- * latest resetsAt wins. Returns null when no rate_limit_event is found, or
28
- * none of its windows are at/above 1.0 utilization.
24
+ * Scans a run log's tail for rate_limit_event entries. The REJECTED event is
25
+ * authoritative (2026-10-11 incident: an allowed-only log with a seven_day
26
+ * window pause landed on the seven_day reset 4 days out): if any event has
27
+ * status 'rejected', the LAST one's reset wins — rate_limit_info.resetsAt, else
28
+ * unifiedWindows[rateLimitType].resetsAt. Otherwise, a last event carrying a
29
+ * status (allowed / allowed_warning) yields null, even if a window shows
30
+ * utilization >= 1.0 (overage is not a rejection). Legacy: a last event with
31
+ * no status falls back to the latest resetsAt among windows at utilization >= 1.0.
29
32
  */
30
33
  function resolveBindingRateLimitReset(logPath) {
31
34
  const text = readTail(logPath, TAIL_BYTES);
32
35
  if (!text) return null;
33
36
 
34
- let lastWindows = null;
37
+ let lastRejected = null;
38
+ let lastEvent = null;
35
39
  for (const line of text.split('\n')) {
36
40
  const trimmed = line.trim();
37
41
  if (!trimmed || trimmed[0] !== '{') continue;
@@ -41,12 +45,20 @@ function resolveBindingRateLimitReset(logPath) {
41
45
  } catch {
42
46
  continue;
43
47
  }
44
- const windows = event?.rate_limit_info?.unifiedWindows;
45
- if (event?.type === 'rate_limit_event' && windows && typeof windows === 'object') {
46
- lastWindows = windows;
47
- }
48
+ if (event?.type !== 'rate_limit_event' || !event.rate_limit_info) continue;
49
+ lastEvent = event.rate_limit_info;
50
+ if (lastEvent.status === 'rejected') lastRejected = lastEvent;
51
+ }
52
+
53
+ if (lastRejected) {
54
+ if (typeof lastRejected.resetsAt === 'number') return lastRejected.resetsAt;
55
+ const typed = lastRejected.unifiedWindows?.[lastRejected.rateLimitType]?.resetsAt;
56
+ return typeof typed === 'number' ? typed : null;
48
57
  }
49
- if (!lastWindows) return null;
58
+ if (!lastEvent || lastEvent.status !== undefined) return null;
59
+
60
+ const lastWindows = lastEvent.unifiedWindows;
61
+ if (!lastWindows || typeof lastWindows !== 'object') return null;
50
62
 
51
63
  let latestBoundReset = null;
52
64
  for (const window of Object.values(lastWindows)) {
@@ -6,7 +6,6 @@ const path = require('node:path');
6
6
  const os = require('node:os');
7
7
  const { spawn, execFileSync } = require('node:child_process');
8
8
  const { cleanChildEnv, pathWithUserBins } = require('./process/cleanEnv.cjs');
9
- const atomicFs = require('./util/atomicFs.cjs');
10
9
  const { resolveClaudeBin, claudeSpawnTarget } = require('./claude/claudeBin.cjs');
11
10
 
12
11
  const CREDS_PATH = path.join(os.homedir(), '.claude', '.credentials.json');
@@ -47,22 +46,9 @@ function keychainAccount() {
47
46
  return os.userInfo().username;
48
47
  }
49
48
 
50
- /** Write the credential JSON back into the Keychain (-U upserts in place). */
51
- function writeKeychainRaw(value) {
52
- execFileSync(
53
- 'security',
54
- ['add-generic-password', '-U', '-s', KEYCHAIN_SERVICE, '-a', keychainAccount(), '-w', value],
55
- { timeout: 10_000, stdio: 'ignore' },
56
- );
57
- }
58
49
  const REFRESH_LOG_PATH = path.join(os.homedir(), '.claude', 'session-manager', 'credential-refresh.log');
59
50
  const REFRESH_LOG_MAX_BYTES = 100 * 1024;
60
51
 
61
- // Standard OAuth 2.0 refresh grant — endpoint discovered from Claude Code CLI behavior.
62
- // Returns { kind: 'unsupported' } if the endpoint returns 404 or cannot be reached,
63
- // allowing the caller to fall back gracefully.
64
- const OAUTH_TOKEN_URL = 'https://claude.ai/api/auth/oauth/token';
65
-
66
52
  /** Parse a raw credential JSON blob (from file or Keychain) into a result. */
67
53
  function parseCredsRaw(raw, source) {
68
54
  let data;
@@ -125,14 +111,13 @@ function isExpiringSoon(creds, withinMs = 5 * 60_000) {
125
111
  * isRefreshFutile(creds) → boolean
126
112
  *
127
113
  * True when the token is already expired AND there is no refresh token to
128
- * present. In that state every refresh avenue is a dead end: tryOAuthRefresh
129
- * short-circuits to 'unsupported' (it needs `creds.refreshToken`), and the
114
+ * present. In that state the refresh avenue is a dead end: the
130
115
  * `claude --version` CLI fallback performs its own silent OAuth refresh from
131
116
  * that SAME missing refresh token, so it cannot help either. The only recovery
132
117
  * is an out-of-band `claude login` — which refreshIfNeeded picks up on its very
133
118
  * next call via the isExpiringSoon early-return, since fresh creds never reach
134
119
  * this path. Left ungated, this state made pollLoop spawn a `claude --version`
135
- * process and write the oauth_refresh_unsupported → cli_fallback_ok →
120
+ * process and write the cli_fallback_ok →
136
121
  * auth_failed_expired triple on EVERY 15 s poll, forever (observed 2026-09-23:
137
122
  * expiredAtMs 1775277875159, identical triple every ~15 min across the log).
138
123
  */
@@ -153,17 +138,6 @@ function __resetFutileRefreshThrottle() {
153
138
  lastFutileRefreshLoggedAt = null;
154
139
  }
155
140
 
156
- async function writeCredentials(rawData, freshOauth, source = 'file') {
157
- const next = { ...rawData, claudeAiOauth: { ...rawData.claudeAiOauth, ...freshOauth } };
158
- if (source === 'keychain') {
159
- // macOS: upsert back into the Keychain. Sync + may throw — caller catches
160
- // and falls back to the `claude --version` CLI refresh path.
161
- writeKeychainRaw(JSON.stringify(next));
162
- return;
163
- }
164
- await atomicFs.writeJsonAtomic(CREDS_PATH, next, { mode: 0o600, newline: false });
165
- }
166
-
167
141
  function appendRefreshLog(entry) {
168
142
  try {
169
143
  const line = JSON.stringify({ ...entry, ts: new Date().toISOString() }) + '\n';
@@ -179,36 +153,7 @@ function appendRefreshLog(entry) {
179
153
  } catch { /* non-fatal; telemetry must not break the main flow */ }
180
154
  }
181
155
 
182
- // Stretch: attempt standard OAuth 2.0 refresh token grant.
183
- async function tryOAuthRefresh(creds) {
184
- if (!creds.refreshToken) return { kind: 'unsupported', message: 'no refresh token in credentials' };
185
- try {
186
- const r = await fetch(OAUTH_TOKEN_URL, {
187
- method: 'POST',
188
- headers: { 'Content-Type': 'application/json' },
189
- body: JSON.stringify({ grant_type: 'refresh_token', refresh_token: creds.refreshToken }),
190
- signal: AbortSignal.timeout(10_000),
191
- });
192
- if (r.status === 404) return { kind: 'unsupported', message: 'refresh endpoint not found (HTTP 404)' };
193
- if (r.status === 401 || r.status === 403) return { kind: 'auth', message: `refresh rejected: HTTP ${r.status}` };
194
- if (!r.ok) return { kind: 'transient', message: `HTTP ${r.status}` };
195
- const j = await r.json();
196
- if (!j.access_token) return { kind: 'auth', message: 'no access_token in refresh response' };
197
- return {
198
- kind: 'ok',
199
- fresh: {
200
- accessToken: j.access_token,
201
- refreshToken: j.refresh_token ?? creds.refreshToken,
202
- expiresAt: j.expires_at ?? (Date.now() + (j.expires_in ?? 3600) * 1000),
203
- },
204
- };
205
- } catch (e) {
206
- if (e?.name === 'TimeoutError') return { kind: 'transient', message: 'refresh request timed out' };
207
- return { kind: 'unsupported', message: `refresh error: ${e?.message}` };
208
- }
209
- }
210
-
211
- // Stretch fallback: spawning `claude --version` triggers silent token refresh in the CLI binary.
156
+ // Sole refresh avenue: spawning `claude --version` triggers silent token refresh in the CLI binary.
212
157
  function tryCliFallback() {
213
158
  return new Promise((resolve) => {
214
159
  let settled = false;
@@ -244,7 +189,7 @@ function tryCliFallback() {
244
189
  async function refreshIfNeeded(forceRefresh = false) {
245
190
  const cr = await readCredentials();
246
191
  if (cr.kind !== 'ok') return cr;
247
- const { creds, raw, source } = cr;
192
+ const { creds } = cr;
248
193
 
249
194
  if (!forceRefresh && !isExpiringSoon(creds)) {
250
195
  return { kind: 'ok', creds };
@@ -253,12 +198,12 @@ async function refreshIfNeeded(forceRefresh = false) {
253
198
  const alreadyExpired = isExpired(creds);
254
199
 
255
200
  // Futile-refresh short-circuit: token already expired AND no refresh token
256
- // to present. Every downstream avenue (OAuth grant, `claude --version` CLI
257
- // fallback) is a guaranteed dead end from the same missing token, so running
258
- // them on every 15 s poll only spawns a doomed child process and re-logs the
201
+ // to present. The `claude --version` CLI fallback is a guaranteed dead end
202
+ // from the same missing token, so running it on every 15 s poll only spawns
203
+ // a doomed child process and re-logs the
259
204
  // same triple forever (2026-09-23 storm). Re-read once — cheap, and the ONE
260
205
  // real recovery (`claude login`) lands here — then, if still futile, return
261
- // the auth verdict WITHOUT the OAuth call or the CLI-fallback spawn, logging
206
+ // the auth verdict WITHOUT the CLI-fallback spawn, logging
262
207
  // at most once per FUTILE_REFRESH_COOLDOWN_MS instead of every poll.
263
208
  if (isRefreshFutile(creds)) {
264
209
  const recheckCr = await readCredentials();
@@ -279,38 +224,19 @@ async function refreshIfNeeded(forceRefresh = false) {
279
224
  };
280
225
  }
281
226
 
282
- // Stretch: try OAuth refresh endpoint first.
283
- const oauthResult = await tryOAuthRefresh(creds);
284
- appendRefreshLog({ event: `oauth_refresh_${oauthResult.kind}`, message: oauthResult.message ?? null });
285
-
286
- if (oauthResult.kind === 'ok') {
287
- try {
288
- await writeCredentials(raw, oauthResult.fresh, source);
289
- const freshCr = await readCredentials();
290
- if (freshCr.kind === 'ok') {
291
- appendRefreshLog({ event: 'oauth_refresh_written_ok' });
292
- return { kind: 'ok', creds: freshCr.creds };
293
- }
294
- } catch (e) {
295
- appendRefreshLog({ event: 'oauth_refresh_write_failed', error: e?.message });
296
- }
297
- }
298
-
299
- // Stretch: if OAuth didn't explicitly reject the token, try `claude --version` fallback.
300
- if (oauthResult.kind !== 'auth') {
301
- const cliResult = await tryCliFallback();
302
- appendRefreshLog({ event: cliResult.ok ? 'cli_fallback_ok' : 'cli_fallback_failed', reason: cliResult.reason ?? null });
303
- if (cliResult.ok) {
304
- const freshCr = await readCredentials();
305
- if (freshCr.kind === 'ok' && !isExpired(freshCr.creds)) {
306
- return { kind: 'ok', creds: freshCr.creds };
307
- }
227
+ // The CLI owns the real refresh contract: `claude --version` refreshes silently.
228
+ const cliResult = await tryCliFallback();
229
+ appendRefreshLog({ event: cliResult.ok ? 'cli_fallback_ok' : 'cli_fallback_failed', reason: cliResult.reason ?? null });
230
+ if (cliResult.ok) {
231
+ const freshCr = await readCredentials();
232
+ if (freshCr.kind === 'ok' && !isExpired(freshCr.creds)) {
233
+ return { kind: 'ok', creds: freshCr.creds };
308
234
  }
309
235
  }
310
236
 
311
237
  if (alreadyExpired) {
312
238
  // Re-read from disk in case credentials were externally refreshed (e.g. via
313
- // `claude login`) between our initial read and the failed OAuth attempt.
239
+ // `claude login`) between our initial read and the failed CLI fallback.
314
240
  const recheckCr = await readCredentials();
315
241
  if (recheckCr.kind === 'ok' && !isExpired(recheckCr.creds)) {
316
242
  appendRefreshLog({ event: 'externally_refreshed_ok', recheckExpiresAt: recheckCr.creds.expiresAt ?? null });