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.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/assets/{AgentLibrary-RwPvEgqm.js → AgentLibrary-wvrcLGGS.js} +2 -2
- package/dist/assets/{Button-36j3PzYv.js → Button-B6h0F0XT.js} +1 -1
- package/dist/assets/{DataModel-DUhANo4l.js → DataModel-C5pW9E3u.js} +1 -1
- package/dist/assets/{History-BeZzNjmv.js → History-BV4e8YOM.js} +2 -2
- package/dist/assets/{Hooks-iQ8845Hp.js → Hooks-CrFSv4x1.js} +3 -3
- package/dist/assets/{Library-DPE1h-1L.js → Library-D-GIt3EY.js} +7 -7
- package/dist/assets/{ListDetail-PStOuQAo.js → ListDetail-Bco_wfs6.js} +1 -1
- package/dist/assets/{MarkdownEditor-DPw-N2-m.js → MarkdownEditor-C-yHSXNV.js} +1 -1
- package/dist/assets/{McpServers-CUyRTNep.js → McpServers-CfDekadG.js} +2 -2
- package/dist/assets/{Memory-bZ2xjBGy.js → Memory-CGGLexkR.js} +6 -6
- package/dist/assets/{Panel-_VZ_uXNg.js → Panel-DGMEuYYG.js} +1 -1
- package/dist/assets/{Permissions-PPRmfNbK.js → Permissions-Cx2zjtYZ.js} +3 -3
- package/dist/assets/{Plugins-BFcvfvlx.js → Plugins-Bc_W3IyR.js} +2 -2
- package/dist/assets/ProvenanceBadge-DiKAUR1u.js +1 -0
- package/dist/assets/{RemoteMachines-D5V0lzgW.js → RemoteMachines-8_77-WPD.js} +2 -2
- package/dist/assets/{SaveBar-Dw64FVdM.js → SaveBar-Blpzdq1R.js} +1 -1
- package/dist/assets/{Scheduler-C8s_pJ3I.js → Scheduler-Bd70AM4X.js} +8 -8
- package/dist/assets/{ScopeSwitcher-D-nKDMAE.js → ScopeSwitcher-wxN0h4YW.js} +1 -1
- package/dist/assets/Settings-CQ7DA3zW.js +4 -0
- package/dist/assets/{SkillReferenceGraph-BbZFMHim.js → SkillReferenceGraph-DH1NLOiS.js} +1 -1
- package/dist/assets/Skills-CmyRyu4_.js +2 -0
- package/dist/assets/SystemPrompt-KAc3UMCd.js +1 -0
- package/dist/assets/TagLibrary-C8EvHnpg.js +1 -0
- package/dist/assets/{TiptapBody-Cr-hxX8L.js → TiptapBody-d423mH6D.js} +1 -1
- package/dist/assets/{Toggle-7WpPzcZH.js → Toggle-D1wsGE9S.js} +1 -1
- package/dist/assets/{ViewTabs-9lQCRwWJ.js → ViewTabs-CjHw3kGY.js} +1 -1
- package/dist/assets/{historyTotals-BrFxw5J1.js → historyTotals-DFHzqLt7.js} +1 -1
- package/dist/assets/{index-CTBpf-26.js → index-Ci1u018F.js} +143 -143
- package/dist/assets/{settingsSchema-Dh5Kl7VO.js → settingsSchema-Ctf3pZVI.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-CMpx0d04.js → useAgentsDirWatch-B1NBmOAe.js} +1 -1
- package/dist/assets/{useRemotePeers-CI2axd-L.js → useRemotePeers-BHjSJs9D.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/session-manager-dev/skills/send-feedback/SKILL.md +13 -0
- package/scripts/scheduler-mcp-server.cjs +23 -5
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +18 -1
- package/src/main/config.cjs +18 -1
- package/src/main/index.cjs +6 -0
- package/src/main/ipcSchemas.cjs +3 -0
- package/src/main/lib/activeSessions.cjs +13 -0
- package/src/main/lib/claude/rateLimitDetect.cjs +29 -10
- package/src/main/lib/claude/rateLimitWindow.cjs +23 -11
- package/src/main/lib/credentials.cjs +16 -90
- package/src/main/lib/feedback-remote/crossProjectFeedback.cjs +107 -9
- package/src/main/lib/feedback-remote/humanPromptLedger.cjs +93 -0
- package/src/main/lib/mcpToolCatalog.cjs +9 -3
- package/src/main/lib/prd/depSlugResolve.cjs +16 -1
- package/src/main/lib/scheduler/finalizeReason.cjs +30 -0
- package/src/main/lib/scheduler/flatSweepThrottle.cjs +17 -0
- package/src/main/lib/scheduler/runLogRetention.cjs +34 -13
- package/src/main/lib/scheduler/schedulerBatch.cjs +8 -12
- package/src/main/lib/telemetry/telemetryClient.cjs +8 -4
- package/src/main/lib/util/atomicFs.cjs +42 -0
- package/src/main/lib/util/loopDelay.cjs +1 -1
- package/src/main/runVerify.cjs +5 -2
- package/src/main/scheduler.cjs +43 -10
- package/src/main/supervisor.cjs +45 -22
- package/src/main/webRemote.cjs +15 -0
- package/src/preload/api.d.ts +6 -0
- package/src/preload/index.cjs +6 -0
- package/dist/assets/ProvenanceBadge-B1WUknak.js +0 -1
- package/dist/assets/Settings-CQSrR4mO.js +0 -4
- package/dist/assets/Skills-Bzyrh6EQ.js +0 -2
- package/dist/assets/SystemPrompt-DP6i3Dx0.js +0 -1
- 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,
|
|
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{
|
|
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-
|
|
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.
|
|
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.
|
|
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
|
|
703
|
-
// that a human
|
|
704
|
-
|
|
705
|
-
|
|
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') {
|
package/src/main/build-info.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"gitSha": "
|
|
4
|
-
"gitShortSha": "
|
|
2
|
+
"version": "0.113.0",
|
|
3
|
+
"gitSha": "07baa2cc0601e109b6591f57bfb2ced8f34884dd",
|
|
4
|
+
"gitShortSha": "07baa2cc",
|
|
5
5
|
"gitBranch": "HEAD",
|
|
6
|
-
"builtAt": "2026-10-
|
|
6
|
+
"builtAt": "2026-10-12T19:15:00.757Z",
|
|
7
7
|
"dirty": false
|
|
8
8
|
}
|
package/src/main/chatRunner.cjs
CHANGED
|
@@ -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,
|
package/src/main/config.cjs
CHANGED
|
@@ -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
|
-
|
|
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);
|
package/src/main/index.cjs
CHANGED
|
@@ -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.
|
package/src/main/ipcSchemas.cjs
CHANGED
|
@@ -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
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
||
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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.
|
|
257
|
-
//
|
|
258
|
-
//
|
|
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
|
|
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
|
-
//
|
|
283
|
-
const
|
|
284
|
-
appendRefreshLog({ event:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
|
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 });
|