@north-light/crouter 0.3.321 → 0.3.322

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 (66) hide show
  1. package/bin/runtime-selector.mjs +52 -3
  2. package/dist/api/client.d.ts +29 -16
  3. package/dist/api/client.js +2 -2
  4. package/dist/api/dto/modelauth.d.ts +15 -0
  5. package/dist/api/errors.d.ts +6 -3
  6. package/dist/api/errors.js +1 -1
  7. package/dist/api/index.d.ts +2 -2
  8. package/dist/api/index.js +1 -1
  9. package/dist/api/node-transport.d.ts +18 -0
  10. package/dist/api/node-transport.js +1 -0
  11. package/dist/api/routes.d.ts +1 -0
  12. package/dist/api/routes.js +1 -1
  13. package/dist/builtin-memory/crouter-sdk.md +86 -0
  14. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +1 -1
  15. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +1 -1
  16. package/dist/cli.js +8 -2
  17. package/dist/clients/attach/render/chat-view.d.ts +5 -1
  18. package/dist/clients/attach/render/chat-view.js +1 -1
  19. package/dist/clients/attach/session/frames.d.ts +1 -0
  20. package/dist/clients/attach/session/frames.js +1 -1
  21. package/dist/clients/attach/viewer.js +583 -583
  22. package/dist/commands/api-client.js +3 -3
  23. package/dist/commands/memory/read.js +2 -2
  24. package/dist/commands/sys/branch.js +1 -1
  25. package/dist/commands/sys/connect.d.ts +1 -0
  26. package/dist/commands/sys/connect.js +3 -0
  27. package/dist/commands/sys/daemon.js +1 -1
  28. package/dist/commands/sys/panels/provider-panel.js +1 -1
  29. package/dist/commands/sys/provider-login.d.ts +8 -0
  30. package/dist/commands/sys/provider-login.js +9 -0
  31. package/dist/commands/sys/setup-core.d.ts +4 -0
  32. package/dist/commands/sys/setup-core.js +4 -4
  33. package/dist/commands/sys.js +1 -1
  34. package/dist/core/canvas/paths.d.ts +4 -4
  35. package/dist/core/config.js +1 -1
  36. package/dist/core/runtime/broker/daemon-ops.js +1 -1
  37. package/dist/core/secrets.d.ts +3 -0
  38. package/dist/core/secrets.js +2 -2
  39. package/dist/core/subscription-state.d.ts +1 -1
  40. package/dist/core/subscription-state.js +3 -3
  41. package/dist/core/termrender/termrender.d.ts +3 -1
  42. package/dist/core/termrender/termrender.js +13 -13
  43. package/dist/core/user-settings.d.ts +4 -0
  44. package/dist/core/user-settings.js +1 -1
  45. package/dist/daemon/api/handlers/health.d.ts +1 -1
  46. package/dist/daemon/api/handlers/health.js +3 -3
  47. package/dist/daemon/api/handlers/modelauth.js +1 -1
  48. package/dist/daemon/api/server.d.ts +5 -1
  49. package/dist/daemon/api/server.js +4 -4
  50. package/dist/daemon/crtrd-cli.js +1 -1
  51. package/dist/daemon/manage.d.ts +1 -0
  52. package/dist/daemon/manage.js +2 -2
  53. package/dist/types.d.ts +7 -0
  54. package/dist/types.js +1 -1
  55. package/docs/sdk/README.md +54 -0
  56. package/docs/sdk/client.md +92 -0
  57. package/docs/sdk/docker.md +63 -0
  58. package/docs/sdk/errors.md +85 -0
  59. package/docs/sdk/getting-started.md +160 -0
  60. package/docs/sdk/memory.md +107 -0
  61. package/docs/sdk/migration.md +108 -0
  62. package/docs/sdk/nodes.md +193 -0
  63. package/docs/sdk/resources.md +71 -0
  64. package/docs/sdk/streaming.md +124 -0
  65. package/package.json +1 -1
  66. package/runtime.lock.json +6 -6
@@ -1,2 +1,2 @@
1
- var a=Object.defineProperty;var t=(e,r)=>a(e,"name",{value:r,configurable:!0});import{createHash as u}from"node:crypto";import{existsSync as l,readFileSync as p,writeFileSync as f,chmodSync as y}from"node:fs";import{join as m}from"node:path";import{scopeRoot as T,requireScopeRoot as k}from"./scope.js";import{ensureDir as S}from"./fs-utils.js";const d="secrets.json";function c(e){return m(e,d)}t(c,"secretsPathFor");function s(e){const r=T(e);if(!r)return{relayTokens:{}};const o=c(r);if(!l(o))return{relayTokens:{}};try{return{relayTokens:JSON.parse(p(o,"utf8")).relayTokens??{}}}catch{return{relayTokens:{}}}}t(s,"readSecrets");function i(e,r){const o=k(e);S(o);const n=c(o);f(n,JSON.stringify(r,null,2)+`
2
- `,{mode:384}),y(n,384)}t(i,"writeSecrets");function j(e,r){return s(r).relayTokens[e]}t(j,"getRelayToken");function w(e,r,o){const n=s(o);n.relayTokens[e]=r,i(o,n)}t(w,"setRelayToken");function J(e,r){const o=s(r);delete o.relayTokens[e],i(r,o)}t(J,"deleteRelayToken");function N(e){return u("sha256").update(e).digest("hex").slice(0,12)}t(N,"fingerprint");export{J as deleteRelayToken,N as fingerprint,j as getRelayToken,s as readSecrets,w as setRelayToken,i as writeSecrets};
1
+ var a=Object.defineProperty;var r=(e,o)=>a(e,"name",{value:o,configurable:!0});import{createHash as p}from"node:crypto";import{existsSync as u,readFileSync as f,writeFileSync as T,chmodSync as k}from"node:fs";import{join as l}from"node:path";import{scopeRoot as y,requireScopeRoot as m}from"./scope.js";import{ensureDir as S}from"./fs-utils.js";const x="secrets.json";function c(e){return l(e,x)}r(c,"secretsPathFor");function i(e){const o=y(e);if(!o)return{relayTokens:{}};const n=c(o);if(!u(n))return{relayTokens:{}};try{const t=JSON.parse(f(n,"utf8"));return{relayTokens:t.relayTokens??{},...typeof t.apiToken=="string"&&t.apiToken!==""?{apiToken:t.apiToken}:{}}}catch{return{relayTokens:{}}}}r(i,"readSecrets");function s(e,o){const n=m(e);S(n);const t=c(n);T(t,JSON.stringify(o,null,2)+`
2
+ `,{mode:384}),k(t,384)}r(s,"writeSecrets");function j(e,o){return i(o).relayTokens[e]}r(j,"getRelayToken");function w(e,o,n){const t=i(n);t.relayTokens[e]=o,s(n,t)}r(w,"setRelayToken");function A(e,o){const n=i(o);delete n.relayTokens[e],s(o,n)}r(A,"deleteRelayToken");function J(e){return i(e).apiToken}r(J,"getApiToken");function N(e,o){const n=i(o);n.apiToken=e,s(o,n)}r(N,"setApiToken");function O(e){return p("sha256").update(e).digest("hex").slice(0,12)}r(O,"fingerprint");export{A as deleteRelayToken,O as fingerprint,J as getApiToken,j as getRelayToken,i as readSecrets,N as setApiToken,w as setRelayToken,s as writeSecrets};
@@ -59,7 +59,7 @@ export declare function removeAllManagedAccounts(provider: ManagedProviderId): P
59
59
  export declare function removeManagedProvider(provider: ManagedProviderId, label?: string): Promise<SubscriptionCredential[]>;
60
60
  export declare function promoteSubscription(provider: ManagedProviderId, ref: string): Promise<SubscriptionCredential[]>;
61
61
  export declare function markSubscriptionAttempt(provider: ManagedProviderId, ref: string, attemptAt?: number): Promise<SubscriptionCredential[]>;
62
- export declare function markSubscriptionAuthenticationFailed(provider: ManagedProviderId, ref: string, retryAfterMs: number, attemptAt?: number): Promise<SubscriptionCredential[]>;
62
+ export declare function markSubscriptionAuthenticationFailed(provider: ManagedProviderId, ref: string, attempted: Pick<SubscriptionCredential, 'refresh' | 'access'>, retryAfterMs: number, attemptAt?: number): Promise<SubscriptionCredential[]>;
63
63
  export declare function markSubscriptionRateLimited(provider: ManagedProviderId, ref: string, retryAfterMs: number, attemptAt?: number, rateLimitedAt?: number): Promise<SubscriptionCredential[]>;
64
64
  export declare function markSubscriptionSuccess(provider: ManagedProviderId, ref: string, attemptAt?: number): Promise<SubscriptionCredential[]>;
65
65
  export declare function refreshSubscriptionCredential(provider: ManagedProviderId, label: string, exchange: (refreshToken: string) => Promise<{
@@ -1,3 +1,3 @@
1
- var B=Object.defineProperty;var s=(t,n)=>B(t,"name",{value:n,configurable:!0});import{createHash as z,randomUUID as F}from"node:crypto";import{join as J}from"node:path";import{LockedJsonFile as Q}from"./locked-file.js";import{piAgentDir as G}from"./runtime/package-health.js";import{openScopedStore as X}from"./scoped-state/db.js";import{providerStore as Y}from"./scoped-state/paths.js";import{providerRowByReference as Z,providerRows as v,providerWrite as A}from"./scoped-state/providers.js";const P="anthropic",tt="openai-codex",et=720*60*60*1e3,W="strong",H={anthropic:{label:"Claude",credentials:"anthropic-credentials.json"},"openai-codex":{label:"OpenAI Codex",credentials:"openai-codex-credentials.json"}};function b(){return G()}s(b,"agentDir");function N(t){return J(b(),H[t].credentials)}s(N,"credentialFile");function K(t){return t==="ultra"||t==="strong"||t==="medium"||t==="light"?t:void 0}s(K,"normalizeStrength");function f(t){return t.toLowerCase()}s(f,"key");function nt(t){const n=typeof t.label=="string"?t.label.trim():"",e=typeof t.refresh=="string"?t.refresh.trim():"",a=typeof t.access=="string"?t.access.trim():"",r=Number(t.expires);if(!n||!e||!a||!Number.isFinite(r))return;const i=typeof t.accountId=="string"&&t.accountId.trim()?t.accountId.trim():void 0;return{label:n,refresh:e,access:a,expires:r,rateLimitedUntil:Number.isFinite(Number(t.rateLimitedUntil))?Number(t.rateLimitedUntil):0,lastAttemptAt:Number.isFinite(Number(t.lastAttemptAt))?Number(t.lastAttemptAt):0,lastRateLimitedAt:Number.isFinite(Number(t.lastRateLimitedAt))?Number(t.lastRateLimitedAt):0,...t.authFailure==="invalid_grant"?{authFailure:"invalid_grant"}:{},...i?{accountId:i}:{}}}s(nt,"credential");function $(t,n){if(!n.label?.trim())throw new Error(`${h(t)} subscription label cannot be empty`);const e=nt(n);if(!e)throw new Error(`Refusing to persist incomplete ${h(t)} subscription "${n.label.trim()}"; pool rows require both OAuth tokens.`);return e}s($,"assertCredential");function j(t,n){if(!n||n.trim()==="")return{version:1,accounts:{}};let e;try{e=JSON.parse(n)}catch(a){throw new Error(`managed credential file ${t} is corrupt: ${a instanceof Error?a.message:String(a)}`)}if(typeof e!="object"||e===null||Array.isArray(e)||e.version!==1||typeof e.accounts!="object"||e.accounts===null||Array.isArray(e.accounts))throw new Error(`managed credential file ${t} is corrupt`);return e}s(j,"parseCredentials");function at(t,n){const e=n.accounts[t.id];if(!e||typeof e.refresh!="string"||typeof e.access!="string"||!Number.isFinite(e.expires))throw new Error(`managed ${t.providerId} account ${t.id} has no credential record`);return{label:t.label,refresh:e.refresh,access:e.access,expires:e.expires,rateLimitedUntil:t.rateLimitedUntil,lastAttemptAt:t.lastAttemptAt,lastRateLimitedAt:t.lastRateLimitedAt,...t.authFailure?{authFailure:t.authFailure}:{},...e.accountId?{accountId:e.accountId}:{}}}s(at,"toPublic");function D(t,n){return v(b(),t).map(e=>({row:e,entry:at(e,n)}))}s(D,"rowsWithCredentials");function q(t){return new Q(N(t),!0,`{"version":1,"accounts":{}}
2
- `)}s(q,"managedCredentialFile");function rt(t){return j(N(t),q(t).readUnlocked())}s(rt,"readFile");function y(t){return t.accountId?.trim()||(t.refresh.trim()?`refresh:${z("sha256").update(t.refresh.trim()).digest("hex")}`:void 0)}s(y,"identity");function V(t,n,e){const a=e.refresh?.trim()??"",r=e.access?.trim()??"",i=Number(e.expires);if(!a||!r||!Number.isFinite(i))throw new Error(`${h(t)} subscription "${n}" returned an incomplete credential; the stored credential was left unchanged.`);return{refresh:a,access:r,expires:i}}s(V,"assertTokens");function gt(t){return t===P||t===tt}s(gt,"isManagedProvider");function h(t){return H[t].label}s(h,"getProviderLabel");function xt(t,n=Date.now(),e={}){const a=[],r=[];for(let i=0;i<t.length;i++){const c=t[i],u=c.registered?c.automaticFallback===!1?"non-managed-fallback":c.authenticated?(c.coolingUntil??0)>n?"cooling":void 0:"unauthenticated":"unregistered";if(!u)a.push(c);else if(r.push({candidate:c,reason:u}),e.pinnedToFirst&&i===0&&u!=="cooling")break}return{candidates:a,rejected:r}}s(xt,"resolveProviderCandidates");class I extends Error{static{s(this,"DuplicateSubscriptionError")}constructor(n){super(n),this.name="DuplicateSubscriptionError"}}function M(t){return D(t,rt(t)).map(({entry:n})=>n)}s(M,"readSubscriptionPool");async function x(t,n){const e=N(t);return q(t).withLockAsync(async(a,r,i)=>{const c=j(e,a);return{result:await n(c,D(t,c),u=>i(`${JSON.stringify(u)}
3
- `))}})}s(x,"locked");function S(t,n,e){t.prepare("INSERT INTO managed_accounts(provider_id,id,label,label_key,position,last_attempt_at,last_rate_limited_at,rate_limited_until,auth_failure) VALUES(?,?,?,?,?,?,?,?,?)").run(n,e.id,e.label,e.labelKey,e.position,e.lastAttemptAt,e.lastRateLimitedAt,e.rateLimitedUntil,e.authFailure??null)}s(S,"insert");function E(t,n){t.prepare("UPDATE managed_accounts SET label=?,label_key=?,position=?,last_attempt_at=?,last_rate_limited_at=?,rate_limited_until=?,auth_failure=? WHERE provider_id=? AND id=?").run(n.label,n.labelKey,n.position,n.lastAttemptAt,n.lastRateLimitedAt,n.rateLimitedUntil,n.authFailure??null,n.providerId,n.id)}s(E,"update");function L(t,n){return D(t,n).map(({entry:e})=>e)}s(L,"publicPool");function R(t,n){try{n()}catch(e){throw new Error(`managed ${h(t)} credential file was published, but its SQL membership update failed; SQL membership remains authoritative: ${e instanceof Error?e.message:String(e)}`,{cause:e})}}s(R,"writeMembershipAfterCredentialPublish");async function Lt(t,n){return x(t,async(e,a,r)=>{const i=n.map(d=>$(t,d)),c=i.map((d,l)=>({entry:d,id:a[l]?.row.id??F()})),u={};for(const{entry:d,id:l}of c)u[l]={refresh:d.refresh,access:d.access,expires:d.expires,...d.accountId?{accountId:d.accountId}:{}};return r({version:1,accounts:u}),R(t,()=>A(b(),d=>{d.prepare("DELETE FROM managed_accounts WHERE provider_id=?").run(t),c.forEach(({entry:l,id:m},o)=>S(d,t,{providerId:t,id:m,label:l.label,labelKey:f(l.label),position:o,lastAttemptAt:l.lastAttemptAt,lastRateLimitedAt:l.lastRateLimitedAt,rateLimitedUntil:l.rateLimitedUntil,...l.authFailure?{authFailure:l.authFailure}:{}}))})),i})}s(Lt,"writeSubscriptionPool");function U(t,n){const e=n.trim().toLowerCase(),a=Number.parseInt(e,10);return Number.isFinite(a)&&String(a)===e&&a>=1&&a<=t.length?a-1:t.findIndex(({entry:r})=>f(r.label)===e)}s(U,"resolve");async function it(t,n){const e=$(t,n);return x(t,async(a,r,i)=>{if(r.some(({entry:l})=>f(l.label)===f(e.label)))throw new I(`${h(t)} subscription already exists: ${e.label}`);const c=y(e),u=r.find(({entry:l})=>y(l)===c);if(c&&u)throw new I(`This ${h(t)} account is already added as \u201C${u.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);const d=F();return a.accounts[d]={refresh:e.refresh,access:e.access,expires:e.expires,...e.accountId?{accountId:e.accountId}:{}},i(a),R(t,()=>A(b(),l=>S(l,t,{providerId:t,id:d,label:e.label,labelKey:f(e.label),position:r.length,lastAttemptAt:e.lastAttemptAt,lastRateLimitedAt:e.lastRateLimitedAt,rateLimitedUntil:e.rateLimitedUntil,...e.authFailure?{authFailure:e.authFailure}:{}}))),L(t,a)})}s(it,"addSubscription");async function _t(t,n){const e=$(t,n);return M(t).find(r=>f(r.label)===f(e.label))?st(t,e):it(t,e)}s(_t,"upsertSubscription");async function st(t,n){const e=$(t,n);return x(t,async(a,r,i)=>{const c=r.findIndex(({entry:o})=>f(o.label)===f(e.label)),u=r[c],d=r.find(({entry:o},g)=>g!==c&&y(o)===y(e));if(y(e)&&d)throw new I(`This ${h(t)} account is already added as \u201C${d.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);if(!u){const o=F();return a.accounts[o]={refresh:e.refresh,access:e.access,expires:e.expires,...e.accountId?{accountId:e.accountId}:{}},i(a),R(t,()=>A(b(),g=>S(g,t,{providerId:t,id:o,label:e.label,labelKey:f(e.label),position:r.length,lastAttemptAt:e.lastAttemptAt,lastRateLimitedAt:e.lastRateLimitedAt,rateLimitedUntil:e.rateLimitedUntil}))),L(t,a)}const l=u.row,m=e.lastRateLimitedAt>=l.lastRateLimitedAt;return a.accounts[l.id]={refresh:e.refresh,access:e.access,expires:e.expires,...e.accountId?{accountId:e.accountId}:{}},i(a),R(t,()=>A(b(),o=>E(o,{...l,label:e.label,labelKey:f(e.label),lastAttemptAt:Math.max(l.lastAttemptAt,e.lastAttemptAt),lastRateLimitedAt:m?e.lastRateLimitedAt:l.lastRateLimitedAt,rateLimitedUntil:m?e.rateLimitedUntil:l.rateLimitedUntil}))),L(t,a)})}s(st,"upsertSubscriptionWithUniqueAccount");async function It(t,n,e){return x(t,async(a,r)=>{const i=U(r,n);if(i<0)throw new Error(`Unknown ${h(t)} subscription: ${n}`);const c=e.trim();if(!c)throw new Error(`${h(t)} subscription label cannot be empty`);if(r.some((u,d)=>d!==i&&f(u.entry.label)===f(c)))throw new I(`${h(t)} subscription already exists: ${c}`);return A(b(),u=>E(u,{...r[i].row,label:c,labelKey:f(c)})),L(t,a)})}s(It,"renameSubscription");async function T(t,n){return x(t,async(e,a,r)=>{const i=U(a,n);if(i<0)throw new Error(`Unknown ${h(t)} subscription: ${n}`);const c=a[i].row;return A(b(),u=>u.prepare("DELETE FROM managed_accounts WHERE provider_id=? AND id=?").run(t,c.id)),delete e.accounts[c.id],r(e),L(t,e)})}s(T,"remove");async function Et(t,n){return T(t,n)}s(Et,"removeManagedAccount");async function Rt(t,n){return T(t,n)}s(Rt,"removeSubscription");async function ct(t){return x(t,async(n,e,a)=>{A(b(),r=>r.prepare("DELETE FROM managed_accounts WHERE provider_id=?").run(t));for(const{row:r}of e)delete n.accounts[r.id];return a(n),e.length})}s(ct,"removeAllManagedAccounts");async function wt(t,n){return n===void 0?(await ct(t),[]):T(t,n)}s(wt,"removeManagedProvider");async function $t(t,n){return x(t,async(e,a)=>{const r=U(a,n);if(r<0)throw new Error(`Unknown ${h(t)} subscription: ${n}`);return A(b(),i=>{[a[r],...a.filter((u,d)=>d!==r)].forEach((u,d)=>E(i,{...u.row,position:d}))}),L(t,e)})}s($t,"promoteSubscription");async function k(t,n,e){return A(b(),a=>{const r=Z(a,t,n);if(!r)throw new Error(`Unknown ${h(t)} subscription: ${n}`);const i=e(r);i&&E(a,i)}),M(t)}s(k,"mutateHealth");async function Ft(t,n,e=Date.now()){return k(t,n,a=>({...a,lastAttemptAt:Math.max(a.lastAttemptAt,e)}))}s(Ft,"markSubscriptionAttempt");async function St(t,n,e,a=Date.now()){return k(t,n,r=>({...r,authFailure:"invalid_grant",lastAttemptAt:Math.max(r.lastAttemptAt,a),rateLimitedUntil:Math.max(r.rateLimitedUntil,a+Math.max(0,e))}))}s(St,"markSubscriptionAuthenticationFailed");async function Ut(t,n,e,a=Date.now(),r=Date.now()){return k(t,n,i=>r<i.lastRateLimitedAt?null:{...i,lastAttemptAt:Math.max(i.lastAttemptAt,a),lastRateLimitedAt:r,rateLimitedUntil:r+Math.max(0,e)})}s(Ut,"markSubscriptionRateLimited");async function kt(t,n,e=Date.now()){return k(t,n,a=>e<a.lastRateLimitedAt?null:{...a,authFailure:void 0,lastAttemptAt:Math.max(a.lastAttemptAt,e),rateLimitedUntil:0})}s(kt,"markSubscriptionSuccess");async function Nt(t,n,e){return x(t,async(a,r,i)=>{const c=U(r,n);if(c<0)throw new Error(`${h(t)} subscription "${n}" no longer exists`);const u=r[c];if(u.entry.expires>Date.now())return u.entry;const d=V(t,u.entry.label,await e(u.entry.refresh));return a.accounts[u.row.id]={...a.accounts[u.row.id],...d},i(a),{...u.entry,...d}})}s(Nt,"refreshSubscriptionCredential");function ot(t){const n=t,e=[n?.errorMessage,n?.message,t instanceof Error?t.message:""].filter(r=>typeof r=="string").join(" ");return(typeof n?.status=="number"?n.status:Number(e.match(/\bstatus[=:]\s*(\d+)/i)?.[1]))===400&&/invalid_grant|refresh token .*invalid|refresh token .*not found/i.test(e)}s(ot,"isInvalidRefreshTokenError");async function Dt(t,n,e){return x(t,async(a,r,i)=>{const c=new Set;let u;for(;c.size<r.length;){const d=n(r.filter(({entry:m})=>!c.has(m.label)).map(({entry:m})=>m));if(!d)break;const l=r.find(({entry:m})=>m.label===d.label);c.add(l.entry.label);try{const m=await e(l.entry);if(m){const o=V(t,l.entry.label,m);return a.accounts[l.row.id]={...a.accounts[l.row.id],...o},i(a),{...l.entry,...o}}return l.entry}catch(m){if(!ot(m))throw m;u=m;const o=Date.now();A(b(),g=>E(g,{...l.row,authFailure:"invalid_grant",lastAttemptAt:Math.max(l.row.lastAttemptAt,o),rateLimitedUntil:Math.max(l.row.rateLimitedUntil,o+et)}))}}if(u)throw u})}s(Dt,"modifyElectedSubscription");async function lt(t,n){const e=n.accountId?.trim();if(t!==P)return e?{accountId:e}:{};try{const a=await fetch("https://api.anthropic.com/api/oauth/profile",{headers:{Authorization:`Bearer ${n.access}`,Accept:"application/json"},signal:AbortSignal.timeout(3e3)});if(!a.ok)return e?{accountId:e}:{};const r=await a.json(),i=typeof r.account?.uuid=="string"&&r.account.uuid.trim()?r.account.uuid.trim():void 0,c=typeof r.account?.email=="string"&&r.account.email.trim()?r.account.email.trim():void 0;return{...i?{accountId:i}:{},...c?{email:c}:{}}}catch{return e?{accountId:e}:{}}}s(lt,"enrichManagedLoginIdentity");async function Mt(t,n,e){const a=await lt(t,e);return x(t,async(r,i,c)=>{const u=e.autoLabel?void 0:n?.trim(),d=y({refresh:e.refresh,accountId:a.accountId}),l=(u?i.find(({entry:p})=>f(p.label)===f(u)):void 0)??i.find(({entry:p})=>y(p)===d);let m=l?.entry.label??u;if(!m&&e.autoLabel){const p=a.email||n?.trim()||`Account ${i.length+1}`;m=p;for(let w=2;i.some(({entry:_})=>f(_.label)===f(m));w++)m=`${p} (${w})`}if(!m)throw new Error(`${h(t)} subscription label cannot be empty`);const o=$(t,{label:m,refresh:e.refresh,access:e.access,expires:e.expires,...a.accountId?{accountId:a.accountId}:{},rateLimitedUntil:0,lastAttemptAt:Date.now(),lastRateLimitedAt:e.lastRateLimitedAt});if(!l){if(i.some(({entry:_})=>f(_.label)===f(o.label)))throw new I(`${h(t)} subscription already exists: ${o.label}`);const p=i.find(({entry:_})=>y(_)===y(o));if(y(o)&&p)throw new I(`This ${h(t)} account is already added as \u201C${p.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);const w=F();return r.accounts[w]={refresh:o.refresh,access:o.access,expires:o.expires,...o.accountId?{accountId:o.accountId}:{}},c(r),R(t,()=>A(b(),_=>S(_,t,{providerId:t,id:w,label:o.label,labelKey:f(o.label),position:i.length,lastAttemptAt:o.lastAttemptAt,lastRateLimitedAt:o.lastRateLimitedAt,rateLimitedUntil:o.rateLimitedUntil}))),L(t,r)}const g=l.row,O=i.find(({entry:p})=>p.label!==l.entry.label&&y(p)===y(o));if(y(o)&&O)throw new I(`This ${h(t)} account is already added as \u201C${O.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);r.accounts[g.id]={refresh:o.refresh,access:o.access,expires:o.expires,...o.accountId?{accountId:o.accountId}:{}},c(r);const C=o.lastRateLimitedAt>=g.lastRateLimitedAt;return R(t,()=>A(b(),p=>E(p,{...g,lastAttemptAt:Math.max(g.lastAttemptAt,o.lastAttemptAt),lastRateLimitedAt:C?o.lastRateLimitedAt:g.lastRateLimitedAt,rateLimitedUntil:C?0:g.rateLimitedUntil}))),L(t,r)})}s(Mt,"commitManagedLogin");function Tt(t,n=Date.now()){if(!t?.trim())return;const e=Number(t.trim());if(Number.isFinite(e))return Math.max(0,Math.round(e*1e3));const a=Date.parse(t);return Number.isFinite(a)?Math.max(0,a-n):void 0}s(Tt,"parseRetryAfterHeader");function ut(){const t=X(Y(b())).prepare("SELECT default_fallback_strength,revert_when_available FROM rotation_settings WHERE singleton=1").get();return{defaultFallbackStrength:K(t?.default_fallback_strength)??W,revertWhenAvailable:t?.revert_when_available===void 0||t.revert_when_available===null?!0:!!t.revert_when_available}}s(ut,"readRotationConfig");function Ot(t){const n=ut(),e={defaultFallbackStrength:K(t.defaultFallbackStrength)??n.defaultFallbackStrength??W,revertWhenAvailable:t.revertWhenAvailable??n.revertWhenAvailable??!0};return A(b(),a=>a.prepare("INSERT INTO rotation_settings(singleton,default_fallback_strength,revert_when_available) VALUES(1,?,?) ON CONFLICT(singleton) DO UPDATE SET default_fallback_strength=excluded.default_fallback_strength,revert_when_available=excluded.revert_when_available").run(e.defaultFallbackStrength,e.revertWhenAvailable?1:0)),e}s(Ot,"writeRotationConfig");function Ct(t,n=Date.now()){const e=M(t),a=e.filter(i=>i.authFailure===void 0&&i.rateLimitedUntil<=n),r=h(t);return a.length===0?`${r} pool exhausted`:`${r} ${a[0]?.label??t} (${a.length}/${e.length} ready)`}s(Ct,"formatStatusLine");export{P as ANTHROPIC_PROVIDER_ID,I as DuplicateSubscriptionError,et as INVALID_REFRESH_TOKEN_BACKOFF_MS,tt as OPENAI_CODEX_PROVIDER_ID,it as addSubscription,Mt as commitManagedLogin,lt as enrichManagedLoginIdentity,Ct as formatStatusLine,h as getProviderLabel,ot as isInvalidRefreshTokenError,gt as isManagedProvider,Ft as markSubscriptionAttempt,St as markSubscriptionAuthenticationFailed,Ut as markSubscriptionRateLimited,kt as markSubscriptionSuccess,Dt as modifyElectedSubscription,Tt as parseRetryAfterHeader,$t as promoteSubscription,ut as readRotationConfig,M as readSubscriptionPool,Nt as refreshSubscriptionCredential,ct as removeAllManagedAccounts,Et as removeManagedAccount,wt as removeManagedProvider,Rt as removeSubscription,It as renameSubscription,xt as resolveProviderCandidates,_t as upsertSubscription,st as upsertSubscriptionWithUniqueAccount,Ot as writeRotationConfig,Lt as writeSubscriptionPool};
1
+ var B=Object.defineProperty;var s=(t,n)=>B(t,"name",{value:n,configurable:!0});import{createHash as z,randomUUID as F}from"node:crypto";import{join as J}from"node:path";import{LockedJsonFile as Q}from"./locked-file.js";import{piAgentDir as G}from"./runtime/package-health.js";import{openScopedStore as X}from"./scoped-state/db.js";import{providerStore as Y}from"./scoped-state/paths.js";import{providerRowByReference as Z,providerRows as v,providerWrite as A}from"./scoped-state/providers.js";const P="anthropic",tt="openai-codex",et=720*60*60*1e3,W="strong",H={anthropic:{label:"Claude",credentials:"anthropic-credentials.json"},"openai-codex":{label:"OpenAI Codex",credentials:"openai-codex-credentials.json"}};function b(){return G()}s(b,"agentDir");function k(t){return J(b(),H[t].credentials)}s(k,"credentialFile");function K(t){return t==="ultra"||t==="strong"||t==="medium"||t==="light"?t:void 0}s(K,"normalizeStrength");function f(t){return t.toLowerCase()}s(f,"key");function nt(t){const n=typeof t.label=="string"?t.label.trim():"",e=typeof t.refresh=="string"?t.refresh.trim():"",a=typeof t.access=="string"?t.access.trim():"",r=Number(t.expires);if(!n||!e||!a||!Number.isFinite(r))return;const i=typeof t.accountId=="string"&&t.accountId.trim()?t.accountId.trim():void 0;return{label:n,refresh:e,access:a,expires:r,rateLimitedUntil:Number.isFinite(Number(t.rateLimitedUntil))?Number(t.rateLimitedUntil):0,lastAttemptAt:Number.isFinite(Number(t.lastAttemptAt))?Number(t.lastAttemptAt):0,lastRateLimitedAt:Number.isFinite(Number(t.lastRateLimitedAt))?Number(t.lastRateLimitedAt):0,...t.authFailure==="invalid_grant"?{authFailure:"invalid_grant"}:{},...i?{accountId:i}:{}}}s(nt,"credential");function $(t,n){if(!n.label?.trim())throw new Error(`${h(t)} subscription label cannot be empty`);const e=nt(n);if(!e)throw new Error(`Refusing to persist incomplete ${h(t)} subscription "${n.label.trim()}"; pool rows require both OAuth tokens.`);return e}s($,"assertCredential");function j(t,n){if(!n||n.trim()==="")return{version:1,accounts:{}};let e;try{e=JSON.parse(n)}catch(a){throw new Error(`managed credential file ${t} is corrupt: ${a instanceof Error?a.message:String(a)}`)}if(typeof e!="object"||e===null||Array.isArray(e)||e.version!==1||typeof e.accounts!="object"||e.accounts===null||Array.isArray(e.accounts))throw new Error(`managed credential file ${t} is corrupt`);return e}s(j,"parseCredentials");function at(t,n){const e=n.accounts[t.id];if(!e||typeof e.refresh!="string"||typeof e.access!="string"||!Number.isFinite(e.expires))throw new Error(`managed ${t.providerId} account ${t.id} has no credential record`);return{label:t.label,refresh:e.refresh,access:e.access,expires:e.expires,rateLimitedUntil:t.rateLimitedUntil,lastAttemptAt:t.lastAttemptAt,lastRateLimitedAt:t.lastRateLimitedAt,...t.authFailure?{authFailure:t.authFailure}:{},...e.accountId?{accountId:e.accountId}:{}}}s(at,"toPublic");function N(t,n){return v(b(),t).map(e=>({row:e,entry:at(e,n)}))}s(N,"rowsWithCredentials");function q(t){return new Q(k(t),!0,`{"version":1,"accounts":{}}
2
+ `)}s(q,"managedCredentialFile");function rt(t){return j(k(t),q(t).readUnlocked())}s(rt,"readFile");function y(t){return t.accountId?.trim()||(t.refresh.trim()?`refresh:${z("sha256").update(t.refresh.trim()).digest("hex")}`:void 0)}s(y,"identity");function V(t,n,e){const a=e.refresh?.trim()??"",r=e.access?.trim()??"",i=Number(e.expires);if(!a||!r||!Number.isFinite(i))throw new Error(`${h(t)} subscription "${n}" returned an incomplete credential; the stored credential was left unchanged.`);return{refresh:a,access:r,expires:i}}s(V,"assertTokens");function gt(t){return t===P||t===tt}s(gt,"isManagedProvider");function h(t){return H[t].label}s(h,"getProviderLabel");function xt(t,n=Date.now(),e={}){const a=[],r=[];for(let i=0;i<t.length;i++){const o=t[i],c=o.registered?o.automaticFallback===!1?"non-managed-fallback":o.authenticated?(o.coolingUntil??0)>n?"cooling":void 0:"unauthenticated":"unregistered";if(!c)a.push(o);else if(r.push({candidate:o,reason:c}),e.pinnedToFirst&&i===0&&c!=="cooling")break}return{candidates:a,rejected:r}}s(xt,"resolveProviderCandidates");class E extends Error{static{s(this,"DuplicateSubscriptionError")}constructor(n){super(n),this.name="DuplicateSubscriptionError"}}function D(t){return N(t,rt(t)).map(({entry:n})=>n)}s(D,"readSubscriptionPool");async function x(t,n){const e=k(t);return q(t).withLockAsync(async(a,r,i)=>{const o=j(e,a);return{result:await n(o,N(t,o),c=>i(`${JSON.stringify(c)}
3
+ `))}})}s(x,"locked");function S(t,n,e){t.prepare("INSERT INTO managed_accounts(provider_id,id,label,label_key,position,last_attempt_at,last_rate_limited_at,rate_limited_until,auth_failure) VALUES(?,?,?,?,?,?,?,?,?)").run(n,e.id,e.label,e.labelKey,e.position,e.lastAttemptAt,e.lastRateLimitedAt,e.rateLimitedUntil,e.authFailure??null)}s(S,"insert");function I(t,n){t.prepare("UPDATE managed_accounts SET label=?,label_key=?,position=?,last_attempt_at=?,last_rate_limited_at=?,rate_limited_until=?,auth_failure=? WHERE provider_id=? AND id=?").run(n.label,n.labelKey,n.position,n.lastAttemptAt,n.lastRateLimitedAt,n.rateLimitedUntil,n.authFailure??null,n.providerId,n.id)}s(I,"update");function L(t,n){return N(t,n).map(({entry:e})=>e)}s(L,"publicPool");function w(t,n){try{n()}catch(e){throw new Error(`managed ${h(t)} credential file was published, but its SQL membership update failed; SQL membership remains authoritative: ${e instanceof Error?e.message:String(e)}`,{cause:e})}}s(w,"writeMembershipAfterCredentialPublish");async function Lt(t,n){return x(t,async(e,a,r)=>{const i=n.map(d=>$(t,d)),o=i.map((d,u)=>({entry:d,id:a[u]?.row.id??F()})),c={};for(const{entry:d,id:u}of o)c[u]={refresh:d.refresh,access:d.access,expires:d.expires,...d.accountId?{accountId:d.accountId}:{}};return r({version:1,accounts:c}),w(t,()=>A(b(),d=>{d.prepare("DELETE FROM managed_accounts WHERE provider_id=?").run(t),o.forEach(({entry:u,id:m},l)=>S(d,t,{providerId:t,id:m,label:u.label,labelKey:f(u.label),position:l,lastAttemptAt:u.lastAttemptAt,lastRateLimitedAt:u.lastRateLimitedAt,rateLimitedUntil:u.rateLimitedUntil,...u.authFailure?{authFailure:u.authFailure}:{}}))})),i})}s(Lt,"writeSubscriptionPool");function U(t,n){const e=n.trim().toLowerCase(),a=Number.parseInt(e,10);return Number.isFinite(a)&&String(a)===e&&a>=1&&a<=t.length?a-1:t.findIndex(({entry:r})=>f(r.label)===e)}s(U,"resolve");async function it(t,n){const e=$(t,n);return x(t,async(a,r,i)=>{if(r.some(({entry:u})=>f(u.label)===f(e.label)))throw new E(`${h(t)} subscription already exists: ${e.label}`);const o=y(e),c=r.find(({entry:u})=>y(u)===o);if(o&&c)throw new E(`This ${h(t)} account is already added as \u201C${c.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);const d=F();return a.accounts[d]={refresh:e.refresh,access:e.access,expires:e.expires,...e.accountId?{accountId:e.accountId}:{}},i(a),w(t,()=>A(b(),u=>S(u,t,{providerId:t,id:d,label:e.label,labelKey:f(e.label),position:r.length,lastAttemptAt:e.lastAttemptAt,lastRateLimitedAt:e.lastRateLimitedAt,rateLimitedUntil:e.rateLimitedUntil,...e.authFailure?{authFailure:e.authFailure}:{}}))),L(t,a)})}s(it,"addSubscription");async function _t(t,n){const e=$(t,n);return D(t).find(r=>f(r.label)===f(e.label))?st(t,e):it(t,e)}s(_t,"upsertSubscription");async function st(t,n){const e=$(t,n);return x(t,async(a,r,i)=>{const o=r.findIndex(({entry:l})=>f(l.label)===f(e.label)),c=r[o],d=r.find(({entry:l},g)=>g!==o&&y(l)===y(e));if(y(e)&&d)throw new E(`This ${h(t)} account is already added as \u201C${d.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);if(!c){const l=F();return a.accounts[l]={refresh:e.refresh,access:e.access,expires:e.expires,...e.accountId?{accountId:e.accountId}:{}},i(a),w(t,()=>A(b(),g=>S(g,t,{providerId:t,id:l,label:e.label,labelKey:f(e.label),position:r.length,lastAttemptAt:e.lastAttemptAt,lastRateLimitedAt:e.lastRateLimitedAt,rateLimitedUntil:e.rateLimitedUntil}))),L(t,a)}const u=c.row,m=e.lastRateLimitedAt>=u.lastRateLimitedAt;return a.accounts[u.id]={refresh:e.refresh,access:e.access,expires:e.expires,...e.accountId?{accountId:e.accountId}:{}},i(a),w(t,()=>A(b(),l=>I(l,{...u,label:e.label,labelKey:f(e.label),lastAttemptAt:Math.max(u.lastAttemptAt,e.lastAttemptAt),lastRateLimitedAt:m?e.lastRateLimitedAt:u.lastRateLimitedAt,rateLimitedUntil:m?e.rateLimitedUntil:u.rateLimitedUntil}))),L(t,a)})}s(st,"upsertSubscriptionWithUniqueAccount");async function Et(t,n,e){return x(t,async(a,r)=>{const i=U(r,n);if(i<0)throw new Error(`Unknown ${h(t)} subscription: ${n}`);const o=e.trim();if(!o)throw new Error(`${h(t)} subscription label cannot be empty`);if(r.some((c,d)=>d!==i&&f(c.entry.label)===f(o)))throw new E(`${h(t)} subscription already exists: ${o}`);return A(b(),c=>I(c,{...r[i].row,label:o,labelKey:f(o)})),L(t,a)})}s(Et,"renameSubscription");async function M(t,n){return x(t,async(e,a,r)=>{const i=U(a,n);if(i<0)throw new Error(`Unknown ${h(t)} subscription: ${n}`);const o=a[i].row;return A(b(),c=>c.prepare("DELETE FROM managed_accounts WHERE provider_id=? AND id=?").run(t,o.id)),delete e.accounts[o.id],r(e),L(t,e)})}s(M,"remove");async function It(t,n){return M(t,n)}s(It,"removeManagedAccount");async function wt(t,n){return M(t,n)}s(wt,"removeSubscription");async function ct(t){return x(t,async(n,e,a)=>{A(b(),r=>r.prepare("DELETE FROM managed_accounts WHERE provider_id=?").run(t));for(const{row:r}of e)delete n.accounts[r.id];return a(n),e.length})}s(ct,"removeAllManagedAccounts");async function Rt(t,n){return n===void 0?(await ct(t),[]):M(t,n)}s(Rt,"removeManagedProvider");async function $t(t,n){return x(t,async(e,a)=>{const r=U(a,n);if(r<0)throw new Error(`Unknown ${h(t)} subscription: ${n}`);return A(b(),i=>{[a[r],...a.filter((c,d)=>d!==r)].forEach((c,d)=>I(i,{...c.row,position:d}))}),L(t,e)})}s($t,"promoteSubscription");async function T(t,n,e){return A(b(),a=>{const r=Z(a,t,n);if(!r)throw new Error(`Unknown ${h(t)} subscription: ${n}`);const i=e(r);i&&I(a,i)}),D(t)}s(T,"mutateHealth");async function Ft(t,n,e=Date.now()){return T(t,n,a=>({...a,lastAttemptAt:Math.max(a.lastAttemptAt,e)}))}s(Ft,"markSubscriptionAttempt");async function St(t,n,e,a,r=Date.now()){return x(t,async(i,o)=>{const c=o.find(({entry:d})=>f(d.label)===f(n));if(!c)throw new Error(`Unknown ${h(t)} subscription: ${n}`);return c.entry.refresh!==e.refresh||c.entry.access!==e.access||A(b(),d=>I(d,{...c.row,authFailure:"invalid_grant",lastAttemptAt:Math.max(c.row.lastAttemptAt,r),rateLimitedUntil:Math.max(c.row.rateLimitedUntil,r+Math.max(0,a))})),L(t,i)})}s(St,"markSubscriptionAuthenticationFailed");async function Ut(t,n,e,a=Date.now(),r=Date.now()){return T(t,n,i=>r<i.lastRateLimitedAt?null:{...i,lastAttemptAt:Math.max(i.lastAttemptAt,a),lastRateLimitedAt:r,rateLimitedUntil:r+Math.max(0,e)})}s(Ut,"markSubscriptionRateLimited");async function kt(t,n,e=Date.now()){return T(t,n,a=>e<a.lastRateLimitedAt?null:{...a,authFailure:void 0,lastAttemptAt:Math.max(a.lastAttemptAt,e),rateLimitedUntil:0})}s(kt,"markSubscriptionSuccess");async function Nt(t,n,e){return x(t,async(a,r,i)=>{const o=U(r,n);if(o<0)throw new Error(`${h(t)} subscription "${n}" no longer exists`);const c=r[o];if(c.entry.expires>Date.now())return c.entry;const d=V(t,c.entry.label,await e(c.entry.refresh));return a.accounts[c.row.id]={...a.accounts[c.row.id],...d},i(a),{...c.entry,...d}})}s(Nt,"refreshSubscriptionCredential");function ot(t){const n=t,e=[n?.errorMessage,n?.message,t instanceof Error?t.message:""].filter(r=>typeof r=="string").join(" ");return(typeof n?.status=="number"?n.status:Number(e.match(/\bstatus[=:]\s*(\d+)/i)?.[1]))===400&&/invalid_grant|refresh token .*invalid|refresh token .*not found/i.test(e)}s(ot,"isInvalidRefreshTokenError");async function Dt(t,n,e){return x(t,async(a,r,i)=>{const o=new Set;let c;for(;o.size<r.length;){const d=n(r.filter(({entry:m})=>!o.has(m.label)).map(({entry:m})=>m));if(!d)break;const u=r.find(({entry:m})=>m.label===d.label);o.add(u.entry.label);try{const m=await e(u.entry);if(m){const l=V(t,u.entry.label,m);return a.accounts[u.row.id]={...a.accounts[u.row.id],...l},i(a),{...u.entry,...l}}return u.entry}catch(m){if(!ot(m))throw m;c=m;const l=Date.now();A(b(),g=>I(g,{...u.row,authFailure:"invalid_grant",lastAttemptAt:Math.max(u.row.lastAttemptAt,l),rateLimitedUntil:Math.max(u.row.rateLimitedUntil,l+et)}))}}if(c)throw c})}s(Dt,"modifyElectedSubscription");async function lt(t,n){const e=n.accountId?.trim();if(t!==P)return e?{accountId:e}:{};try{const a=await fetch("https://api.anthropic.com/api/oauth/profile",{headers:{Authorization:`Bearer ${n.access}`,Accept:"application/json"},signal:AbortSignal.timeout(3e3)});if(!a.ok)return e?{accountId:e}:{};const r=await a.json(),i=typeof r.account?.uuid=="string"&&r.account.uuid.trim()?r.account.uuid.trim():void 0,o=typeof r.account?.email=="string"&&r.account.email.trim()?r.account.email.trim():void 0;return{...i?{accountId:i}:{},...o?{email:o}:{}}}catch{return e?{accountId:e}:{}}}s(lt,"enrichManagedLoginIdentity");async function Mt(t,n,e){const a=await lt(t,e);return x(t,async(r,i,o)=>{const c=e.autoLabel?void 0:n?.trim(),d=y({refresh:e.refresh,accountId:a.accountId}),u=(c?i.find(({entry:p})=>f(p.label)===f(c)):void 0)??i.find(({entry:p})=>y(p)===d);let m=u?.entry.label??c;if(!m&&e.autoLabel){const p=a.email||n?.trim()||`Account ${i.length+1}`;m=p;for(let R=2;i.some(({entry:_})=>f(_.label)===f(m));R++)m=`${p} (${R})`}if(!m)throw new Error(`${h(t)} subscription label cannot be empty`);const l=$(t,{label:m,refresh:e.refresh,access:e.access,expires:e.expires,...a.accountId?{accountId:a.accountId}:{},rateLimitedUntil:0,lastAttemptAt:Date.now(),lastRateLimitedAt:e.lastRateLimitedAt});if(!u){if(i.some(({entry:_})=>f(_.label)===f(l.label)))throw new E(`${h(t)} subscription already exists: ${l.label}`);const p=i.find(({entry:_})=>y(_)===y(l));if(y(l)&&p)throw new E(`This ${h(t)} account is already added as \u201C${p.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);const R=F();return r.accounts[R]={refresh:l.refresh,access:l.access,expires:l.expires,...l.accountId?{accountId:l.accountId}:{}},o(r),w(t,()=>A(b(),_=>S(_,t,{providerId:t,id:R,label:l.label,labelKey:f(l.label),position:i.length,lastAttemptAt:l.lastAttemptAt,lastRateLimitedAt:l.lastRateLimitedAt,rateLimitedUntil:l.rateLimitedUntil}))),L(t,r)}const g=u.row,O=i.find(({entry:p})=>p.label!==u.entry.label&&y(p)===y(l));if(y(l)&&O)throw new E(`This ${h(t)} account is already added as \u201C${O.entry.label}\u201D \u2014 it shares the same quota, so a second slot adds no redundancy.`);r.accounts[g.id]={refresh:l.refresh,access:l.access,expires:l.expires,...l.accountId?{accountId:l.accountId}:{}},o(r);const C=l.lastRateLimitedAt>=g.lastRateLimitedAt;return w(t,()=>A(b(),p=>I(p,{...g,authFailure:void 0,lastAttemptAt:Math.max(g.lastAttemptAt,l.lastAttemptAt),lastRateLimitedAt:C?l.lastRateLimitedAt:g.lastRateLimitedAt,rateLimitedUntil:C?0:g.rateLimitedUntil}))),L(t,r)})}s(Mt,"commitManagedLogin");function Tt(t,n=Date.now()){if(!t?.trim())return;const e=Number(t.trim());if(Number.isFinite(e))return Math.max(0,Math.round(e*1e3));const a=Date.parse(t);return Number.isFinite(a)?Math.max(0,a-n):void 0}s(Tt,"parseRetryAfterHeader");function ut(){const t=X(Y(b())).prepare("SELECT default_fallback_strength,revert_when_available FROM rotation_settings WHERE singleton=1").get();return{defaultFallbackStrength:K(t?.default_fallback_strength)??W,revertWhenAvailable:t?.revert_when_available===void 0||t.revert_when_available===null?!0:!!t.revert_when_available}}s(ut,"readRotationConfig");function Ot(t){const n=ut(),e={defaultFallbackStrength:K(t.defaultFallbackStrength)??n.defaultFallbackStrength??W,revertWhenAvailable:t.revertWhenAvailable??n.revertWhenAvailable??!0};return A(b(),a=>a.prepare("INSERT INTO rotation_settings(singleton,default_fallback_strength,revert_when_available) VALUES(1,?,?) ON CONFLICT(singleton) DO UPDATE SET default_fallback_strength=excluded.default_fallback_strength,revert_when_available=excluded.revert_when_available").run(e.defaultFallbackStrength,e.revertWhenAvailable?1:0)),e}s(Ot,"writeRotationConfig");function Ct(t,n=Date.now()){const e=D(t),a=e.filter(i=>i.authFailure===void 0&&i.rateLimitedUntil<=n),r=h(t);return a.length===0?`${r} pool exhausted`:`${r} ${a[0]?.label??t} (${a.length}/${e.length} ready)`}s(Ct,"formatStatusLine");export{P as ANTHROPIC_PROVIDER_ID,E as DuplicateSubscriptionError,et as INVALID_REFRESH_TOKEN_BACKOFF_MS,tt as OPENAI_CODEX_PROVIDER_ID,it as addSubscription,Mt as commitManagedLogin,lt as enrichManagedLoginIdentity,Ct as formatStatusLine,h as getProviderLabel,ot as isInvalidRefreshTokenError,gt as isManagedProvider,Ft as markSubscriptionAttempt,St as markSubscriptionAuthenticationFailed,Ut as markSubscriptionRateLimited,kt as markSubscriptionSuccess,Dt as modifyElectedSubscription,Tt as parseRetryAfterHeader,$t as promoteSubscription,ut as readRotationConfig,D as readSubscriptionPool,Nt as refreshSubscriptionCredential,ct as removeAllManagedAccounts,It as removeManagedAccount,Rt as removeManagedProvider,wt as removeSubscription,Et as renameSubscription,xt as resolveProviderCandidates,_t as upsertSubscription,st as upsertSubscriptionWithUniqueAccount,Ot as writeRotationConfig,Lt as writeSubscriptionPool};
@@ -1,3 +1,4 @@
1
+ type RendererDiagnostic = (message: string) => void;
1
2
  /**
2
3
  * Memoized, self-healing. Guarantees at most one authoritative renderer
3
4
  * lifecycle per process: trust a valid stamp outright (zero subprocess spawns),
@@ -12,7 +13,7 @@
12
13
  * Invoked at postinstall AND lazily on the first render/check/display call,
13
14
  * so `npm ci --ignore-scripts` consumers still self-heal on first use.
14
15
  */
15
- export declare function ensureRenderer(): void;
16
+ export declare function ensureRenderer(report?: RendererDiagnostic): void;
16
17
  /** Cheap predicate — true when the pinned managed binary is verified ready. Does not install or spawn. */
17
18
  export declare function isRendererReady(): boolean;
18
19
  /** Render markdown to terminal lines via the pinned binary; plaintext fallback.
@@ -67,3 +68,4 @@ export declare function checkMarkdown(md: string): {
67
68
  /** Shell command for a pane that live-renders `path` with crouter's pinned
68
69
  * termrender. Null means the managed renderer could not be provisioned. */
69
70
  export declare function documentWatchCommand(path: string): string | null;
71
+ export {};
@@ -1,14 +1,14 @@
1
- var j=Object.defineProperty;var a=(r,e)=>j(r,"name",{value:e,configurable:!0});import{execFileSync as g,spawnSync as G}from"node:child_process";import{existsSync as A,readFileSync as X,writeFileSync as Y,statSync as q,mkdirSync as Q,unlinkSync as C,renameSync as W,accessSync as Z,realpathSync as I,constants as ee}from"node:fs";import{homedir as re}from"node:os";import{join as b,resolve as te}from"node:path";import N from"string-width";import{shellQuote as V}from"../../shared/shell-quote.js";import{withExclusiveDirectoryLock as ne}from"../exclusive-lock.js";import{TERMRENDER_VERSION as w}from"./version.js";const R=b(te(process.env.XDG_CACHE_HOME||b(re(),".cache")),"crouter","termrender",w),E=b(R,"venv"),h=b(E,"bin/termrender"),m=b(E,"bin/python"),k=b(E,".crtr-termrender-stamp.json"),se=b(R,".crtr-termrender.lock"),ie=36e4;let f="unchecked";function T(r){const e=r;return!!e&&typeof e.mtimeMs=="number"&&typeof e.size=="number"&&typeof e.mode=="number"}a(T,"isFp");function S(r){try{const e=q(r);return{mtimeMs:e.mtimeMs,size:e.size,mode:e.mode}}catch{return null}}a(S,"fingerprint");function D(r,e){return!!r&&r.mtimeMs===e.mtimeMs&&r.size===e.size&&r.mode===e.mode}a(D,"fpMatch");function oe(){try{const r=JSON.parse(X(k,"utf8"));return r&&typeof r.version=="string"&&T(r.bin)&&T(r.python)&&typeof r.pythonRealpath=="string"?r:null}catch{return null}}a(oe,"readStamp");function le(){const r=S(h),e=S(m);if(!r||!e){process.stderr.write(`[crtr] termrender stamp skipped: venv files vanished immediately after verify
2
- `);return}let n;try{n=I(m)}catch{n=m}const t={version:w,bin:r,python:e,pythonRealpath:n},s=`${k}.${process.pid}.tmp`;try{Y(s,JSON.stringify(t)),W(s,k)}catch(o){try{C(s)}catch{}process.stderr.write(`[crtr] termrender ready but stamp not persisted (${o instanceof Error?o.message:String(o)}); future launches will re-verify the slow way
3
- `)}}a(le,"publishStamp");function x(r){let e=!0;try{C(k)}catch(n){n.code!=="ENOENT"&&(e=!1)}f="unavailable",e?process.stderr.write(`[crtr] termrender invocation failed (${r}); invalidated readiness, future launches will repair
4
- `):process.stderr.write(`[crtr] termrender invocation failed (${r}) but its readiness marker could not be removed; future launches may keep trusting a broken renderer until ${k} is deleted
5
- `)}a(x,"invalidateRenderer");function B(r){const e=r;return e?.code==="ETIMEDOUT"||!!e?.killed&&!!e?.signal}a(B,"isTimeout");function _(){const r=oe();if(!r||r.version!==w)return!1;try{Z(h,ee.X_OK)}catch{return!1}if(!D(S(h),r.bin)||!D(S(m),r.python))return!1;try{if(I(m)!==r.pythonRealpath)return!1}catch{return!1}return!0}a(_,"stampValid");function ae(r){try{Q(R,{recursive:!0})}catch(e){f="unavailable",process.stderr.write(`[crtr] termrender unavailable \u2014 renderer cache cannot be created (${e instanceof Error?e.message:String(e)}); using plaintext fallback
6
- `);return}try{ne(se,r,{timeoutMs:ie})}catch{f="unavailable"}}a(ae,"withProvisionLock");function L(){if(!A(h))return!1;try{return g(h,["-h"],{encoding:"utf8",stdio:["ignore","pipe","pipe"],timeout:5e3}),!0}catch{return!1}}a(L,"binaryOk");function z(){if(!A(m))return null;try{return g(m,["-c",'import importlib.metadata as m; print(m.version("termrender"))'],{encoding:"utf8",stdio:["ignore","pipe","pipe"],timeout:5e3}).trim()||null}catch{return null}}a(z,"installedVersion");function ue(){try{return g("uv",["--version"],{stdio:"pipe",timeout:5e3}),!0}catch{return!1}}a(ue,"uvAvailable");function M(){if(f==="unchecked"){if(process.platform==="win32"){f="unavailable";return}if(_()){f="ready";return}ce()}}a(M,"ensureRenderer");function ce(){ae(()=>{if(_()){f="ready";return}let r=L()&&z()===w;if(!r){if(!ue()){process.stderr.write(`[crtr] termrender unavailable \u2014 install uv to enable rich rendering:
1
+ var j=Object.defineProperty;var a=(t,e)=>j(t,"name",{value:e,configurable:!0});import{execFileSync as g,spawnSync as G}from"node:child_process";import{existsSync as A,readFileSync as X,writeFileSync as Y,statSync as q,mkdirSync as Q,unlinkSync as C,renameSync as W,accessSync as Z,realpathSync as I,constants as ee}from"node:fs";import{homedir as te}from"node:os";import{join as b,resolve as re}from"node:path";import N from"string-width";import{shellQuote as V}from"../../shared/shell-quote.js";import{withExclusiveDirectoryLock as ne}from"../exclusive-lock.js";import{TERMRENDER_VERSION as v}from"./version.js";const R=b(re(process.env.XDG_CACHE_HOME||b(te(),".cache")),"crouter","termrender",v),E=b(R,"venv"),h=b(E,"bin/termrender"),m=b(E,"bin/python"),w=b(E,".crtr-termrender-stamp.json"),se=b(R,".crtr-termrender.lock"),ie=36e4;let f="unchecked";function T(t){const e=t;return!!e&&typeof e.mtimeMs=="number"&&typeof e.size=="number"&&typeof e.mode=="number"}a(T,"isFp");function S(t){try{const e=q(t);return{mtimeMs:e.mtimeMs,size:e.size,mode:e.mode}}catch{return null}}a(S,"fingerprint");function D(t,e){return!!t&&t.mtimeMs===e.mtimeMs&&t.size===e.size&&t.mode===e.mode}a(D,"fpMatch");function oe(){try{const t=JSON.parse(X(w,"utf8"));return t&&typeof t.version=="string"&&T(t.bin)&&T(t.python)&&typeof t.pythonRealpath=="string"?t:null}catch{return null}}a(oe,"readStamp");function le(t){const e=S(h),n=S(m);if(!e||!n){t(`[crtr] termrender stamp skipped: venv files vanished immediately after verify
2
+ `);return}let r;try{r=I(m)}catch{r=m}const i={version:v,bin:e,python:n,pythonRealpath:r},o=`${w}.${process.pid}.tmp`;try{Y(o,JSON.stringify(i)),W(o,w)}catch(u){try{C(o)}catch{}t(`[crtr] termrender ready but stamp not persisted (${u instanceof Error?u.message:String(u)}); future launches will re-verify the slow way
3
+ `)}}a(le,"publishStamp");function x(t){let e=!0;try{C(w)}catch(n){n.code!=="ENOENT"&&(e=!1)}f="unavailable",e?process.stderr.write(`[crtr] termrender invocation failed (${t}); invalidated readiness, future launches will repair
4
+ `):process.stderr.write(`[crtr] termrender invocation failed (${t}) but its readiness marker could not be removed; future launches may keep trusting a broken renderer until ${w} is deleted
5
+ `)}a(x,"invalidateRenderer");function B(t){const e=t;return e?.code==="ETIMEDOUT"||!!e?.killed&&!!e?.signal}a(B,"isTimeout");function _(){const t=oe();if(!t||t.version!==v)return!1;try{Z(h,ee.X_OK)}catch{return!1}if(!D(S(h),t.bin)||!D(S(m),t.python))return!1;try{if(I(m)!==t.pythonRealpath)return!1}catch{return!1}return!0}a(_,"stampValid");function ae(t,e){try{Q(R,{recursive:!0})}catch(n){f="unavailable",e(`[crtr] termrender unavailable \u2014 renderer cache cannot be created (${n instanceof Error?n.message:String(n)}); using plaintext fallback
6
+ `);return}try{ne(se,t,{timeoutMs:ie})}catch{f="unavailable"}}a(ae,"withProvisionLock");function L(){if(!A(h))return!1;try{return g(h,["-h"],{encoding:"utf8",stdio:["ignore","pipe","pipe"],timeout:5e3}),!0}catch{return!1}}a(L,"binaryOk");function z(){if(!A(m))return null;try{return g(m,["-c",'import importlib.metadata as m; print(m.version("termrender"))'],{encoding:"utf8",stdio:["ignore","pipe","pipe"],timeout:5e3}).trim()||null}catch{return null}}a(z,"installedVersion");function ue(){try{return g("uv",["--version"],{stdio:"pipe",timeout:5e3}),!0}catch{return!1}}a(ue,"uvAvailable");function M(t=e=>process.stderr.write(e)){if(f==="unchecked"){if(process.platform==="win32"){f="unavailable";return}if(_()){f="ready";return}ce(t)}}a(M,"ensureRenderer");function ce(t){ae(()=>{if(_()){f="ready";return}let e=L()&&z()===v;if(!e){if(!ue()){t(`[crtr] termrender unavailable \u2014 install uv to enable rich rendering:
7
7
  curl -LsSf https://astral.sh/uv/install.sh | sh
8
- `),f="unavailable";return}try{A(m)||g("uv",["venv","--clear",E],{stdio:"pipe",timeout:6e4}),g("uv",["pip","install","--reinstall","--python",m,`termrender==${w}`],{stdio:"pipe",timeout:12e4})}catch(e){process.stderr.write(`[crtr] termrender install failed (${e instanceof Error?e.message:String(e)}); using plaintext fallback
9
- `),f="unavailable";return}r=L()&&z()===w}if(!r){f="unavailable",process.stderr.write(`[crtr] termrender install completed but health check failed; using plaintext fallback
10
- `);return}le(),f="ready"})}a(ce,"provisionAndPublish");function Me(){return f==="ready"?!0:f==="unavailable"?!1:process.platform!=="win32"&&_()}a(Me,"isRendererReady");const pe=/\x1b\[[0-9;?]*[a-zA-Z]|\x1b[@-_]|[\x00-\x08\x0B\x0E-\x1F\x7F-\x9F]/g;function F(r){return typeof r!="string"?"":r.replace(pe,"")}a(F,"sanitize");function fe(r,e){let n=0,t="";for(const s of r){const o=N(s);if(n+o>e)break;t+=s,n+=o}return t===""&&r.length>0&&(t=[...r][0]),t}a(fe,"sliceByWidth");function P(r,e){if(e<1)return[r];const n=[],t=r.split(`
11
- `);for(let s=0;s<t.length;s++){const o=t[s];if(o===""){n.push("");continue}const c=o.split(/[ \t]+/).filter(Boolean);let p="";for(let l of c){for(;N(l)>e;){p&&(n.push(p),p="");const u=fe(l,e);n.push(u),l=l.slice(u.length)}const i=p?`${p} ${l}`:l;N(i)<=e?p=i:(p&&n.push(p),p=l)}p&&n.push(p)}return n.length>0?n:[""]}a(P,"wrap");class H{static{a(this,"BoundedLRUCache")}map=new Map;maxEntries;constructor(e){this.maxEntries=e}get(e){if(!this.map.has(e))return;const n=this.map.get(e);return this.map.delete(e),this.map.set(e,n),n}set(e,n){if(this.map.has(e))this.map.delete(e);else if(this.map.size>=this.maxEntries){const t=this.map.keys().next().value;this.map.delete(t)}this.map.set(e,n)}}const $=new H(20);function Ae(r,e){const n=`${r}\0${e}`,t=$.get(n);if(t)return[...t];if(M(),f==="ready")try{const c=g(h,["doc","render","--width",String(e),"--color","on"],{input:r,encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).split(`
12
- `);return c.length>0&&c[c.length-1]===""&&c.pop(),$.set(n,c),[...c]}catch(o){B(o)||x("doc render")}const s=P(F(r),e);return $.set(n,s),[...s]}a(Ae,"renderMarkdown");const K=new Set(["mermaid"]);function de(r,e){let n;try{n=JSON.parse(r)}catch{return null}const t=n;if(!Array.isArray(t.lines)||!Array.isArray(t.rows)||!Array.isArray(t.spans)||!Array.isArray(t.blocks)||t.rows.length!==t.lines.length||t.spans.length!==t.lines.length||!t.lines.every(i=>typeof i=="string"))return null;const s=t.blocks.length;if(!t.rows.every(i=>i===null||typeof i=="number"&&Number.isInteger(i)&&i>=0&&i<s))return null;const o=Math.max(1,e.split(`
13
- `).length),c=[];let p=0;for(const i of t.blocks){if(typeof i!="object"||i===null||typeof i.type!="string"||i.type===""||i.start!=null&&!Number.isInteger(i.start)||i.end!=null&&!Number.isInteger(i.end))return null;const u=typeof i.start=="number"?i.start:p+1,d=typeof i.end=="number"?Math.max(i.end,u):u,y=Math.max(1,Math.min(u,o)),v=Math.max(y,Math.min(d,o));c.push({type:i.type,start:y,end:v}),p=v}c.length===0&&c.push({type:"paragraph",start:1,end:o});const l=[];for(let i=0;i<t.spans.length;i++){const u=t.spans[i];if(u===null){l.push(null);continue}if(!Array.isArray(u)||u.length!==2)return null;const[d,y]=u;if(!Number.isInteger(d)||!Number.isInteger(y)||d<1||y<d||y>o)return null;const v=t.rows[i];if(v===null)return null;const O=c[v];if(d<O.start||y>O.end)return null;l.push([d,y])}return{lines:t.lines,rows:t.rows,spans:l,blocks:c}}a(de,"parseRenderedDoc");function he(r,e){const n=F(r).split(`
14
- `),t=[];let s=null;for(let l=0;l<n.length;l++)n[l].trim()!==""?s===null&&(s=l):s!==null&&(t.push({type:"paragraph",start:s+1,end:l}),s=null);s!==null&&t.push({type:"paragraph",start:s+1,end:n.length}),t.length===0&&t.push({type:"paragraph",start:1,end:Math.max(1,n.length)});const o=[],c=[],p=[];return t.forEach((l,i)=>{i>0&&(o.push(""),c.push(null),p.push(null));for(let u=l.start;u<=l.end;u++)for(const d of P(n[u-1]??"",e))o.push(d),c.push(i),p.push([u,u])}),{lines:o,rows:c,spans:p,blocks:t}}a(he,"fallbackDocWithMap");const J=new H(20);function U(r,e){const n=`${r}\0${e}`,t=J.get(n);if(t)return t;if(M(),f==="ready")try{const s=g(h,["doc","render","--width",String(e),"--color","on","--line-map"],{input:r,encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"],maxBuffer:67108864}),o=de(s.trim(),r);if(o!==null)return J.set(n,o),o;x("doc render --line-map (malformed output)")}catch(s){B(s)||x("doc render --line-map")}return he(r,e)}a(U,"renderMarkdownWithMap");function me(r,e,n){const t=U(r,e);if(n<=e||!t.blocks.some(l=>K.has(l.type)))return t;const s=U(r,n);if(s.blocks.length!==t.blocks.length)return t;const o=[],c=[],p=[];for(let l=0;l<t.lines.length;l++){const i=t.rows[l]??null;if(i===null||!K.has(t.blocks[i].type)){o.push(t.lines[l]),c.push(i),p.push(t.spans[l]??null);continue}if(l===0||t.rows[l-1]!==i)for(let u=0;u<s.lines.length;u++)s.rows[u]===i&&(o.push(s.lines[u]),c.push(i),p.push(s.spans[u]??null))}return{lines:o,rows:c,spans:p,blocks:t.blocks}}a(me,"renderMarkdownBlockAware");function Ne(r,e,n){return[...me(r,e,n).lines]}a(Ne,"renderMarkdownBlockAwareLines");function Re(r){if(M(),f!=="ready")return{ok:!0};const e=G(h,["doc","check"],{input:r,encoding:"utf-8",timeout:5e3});if(e.error)return e.error.code==="ETIMEDOUT"||e.signal==="SIGTERM"||x("doc check"),{ok:!1,error:`termrender: invocation failed: ${e.error.message}`};let n=null;const t=typeof e.stdout=="string"?e.stdout:"";if(t)try{n=JSON.parse(t.trim())}catch{}if(n!==null){if(n.ok)return{ok:!0};const s=Array.isArray(n.errors)?n.errors[0]:void 0;return{ok:!1,error:`termrender: ${s&&typeof s.message=="string"&&s.message?s.message:"invalid markdown"}`}}return e.status!==0?{ok:!1,error:`termrender: doc check exited ${e.status}`}:{ok:!0}}a(Re,"checkMarkdown");function _e(r){return M(),f!=="ready"?null:`${V(h)} doc watch ${V(r)}`}a(_e,"documentWatchCommand");export{Re as checkMarkdown,_e as documentWatchCommand,M as ensureRenderer,Me as isRendererReady,Ae as renderMarkdown,me as renderMarkdownBlockAware,Ne as renderMarkdownBlockAwareLines,U as renderMarkdownWithMap};
8
+ `),f="unavailable";return}try{A(m)||g("uv",["venv","--clear",E],{stdio:"pipe",timeout:6e4}),g("uv",["pip","install","--reinstall","--python",m,`termrender==${v}`],{stdio:"pipe",timeout:12e4})}catch(n){t(`[crtr] termrender install failed (${n instanceof Error?n.message:String(n)}); using plaintext fallback
9
+ `),f="unavailable";return}e=L()&&z()===v}if(!e){f="unavailable",t(`[crtr] termrender install completed but health check failed; using plaintext fallback
10
+ `);return}le(t),f="ready"},t)}a(ce,"provisionAndPublish");function Me(){return f==="ready"?!0:f==="unavailable"?!1:process.platform!=="win32"&&_()}a(Me,"isRendererReady");const pe=/\x1b\[[0-9;?]*[a-zA-Z]|\x1b[@-_]|[\x00-\x08\x0B\x0E-\x1F\x7F-\x9F]/g;function F(t){return typeof t!="string"?"":t.replace(pe,"")}a(F,"sanitize");function fe(t,e){let n=0,r="";for(const i of t){const o=N(i);if(n+o>e)break;r+=i,n+=o}return r===""&&t.length>0&&(r=[...t][0]),r}a(fe,"sliceByWidth");function P(t,e){if(e<1)return[t];const n=[],r=t.split(`
11
+ `);for(let i=0;i<r.length;i++){const o=r[i];if(o===""){n.push("");continue}const u=o.split(/[ \t]+/).filter(Boolean);let p="";for(let l of u){for(;N(l)>e;){p&&(n.push(p),p="");const c=fe(l,e);n.push(c),l=l.slice(c.length)}const s=p?`${p} ${l}`:l;N(s)<=e?p=s:(p&&n.push(p),p=l)}p&&n.push(p)}return n.length>0?n:[""]}a(P,"wrap");class H{static{a(this,"BoundedLRUCache")}map=new Map;maxEntries;constructor(e){this.maxEntries=e}get(e){if(!this.map.has(e))return;const n=this.map.get(e);return this.map.delete(e),this.map.set(e,n),n}set(e,n){if(this.map.has(e))this.map.delete(e);else if(this.map.size>=this.maxEntries){const r=this.map.keys().next().value;this.map.delete(r)}this.map.set(e,n)}}const $=new H(20);function Ae(t,e){const n=`${t}\0${e}`,r=$.get(n);if(r)return[...r];if(M(),f==="ready")try{const u=g(h,["doc","render","--width",String(e),"--color","on"],{input:t,encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).split(`
12
+ `);return u.length>0&&u[u.length-1]===""&&u.pop(),$.set(n,u),[...u]}catch(o){B(o)||x("doc render")}const i=P(F(t),e);return $.set(n,i),[...i]}a(Ae,"renderMarkdown");const K=new Set(["mermaid"]);function de(t,e){let n;try{n=JSON.parse(t)}catch{return null}const r=n;if(!Array.isArray(r.lines)||!Array.isArray(r.rows)||!Array.isArray(r.spans)||!Array.isArray(r.blocks)||r.rows.length!==r.lines.length||r.spans.length!==r.lines.length||!r.lines.every(s=>typeof s=="string"))return null;const i=r.blocks.length;if(!r.rows.every(s=>s===null||typeof s=="number"&&Number.isInteger(s)&&s>=0&&s<i))return null;const o=Math.max(1,e.split(`
13
+ `).length),u=[];let p=0;for(const s of r.blocks){if(typeof s!="object"||s===null||typeof s.type!="string"||s.type===""||s.start!=null&&!Number.isInteger(s.start)||s.end!=null&&!Number.isInteger(s.end))return null;const c=typeof s.start=="number"?s.start:p+1,d=typeof s.end=="number"?Math.max(s.end,c):c,y=Math.max(1,Math.min(c,o)),k=Math.max(y,Math.min(d,o));u.push({type:s.type,start:y,end:k}),p=k}u.length===0&&u.push({type:"paragraph",start:1,end:o});const l=[];for(let s=0;s<r.spans.length;s++){const c=r.spans[s];if(c===null){l.push(null);continue}if(!Array.isArray(c)||c.length!==2)return null;const[d,y]=c;if(!Number.isInteger(d)||!Number.isInteger(y)||d<1||y<d||y>o)return null;const k=r.rows[s];if(k===null)return null;const O=u[k];if(d<O.start||y>O.end)return null;l.push([d,y])}return{lines:r.lines,rows:r.rows,spans:l,blocks:u}}a(de,"parseRenderedDoc");function he(t,e){const n=F(t).split(`
14
+ `),r=[];let i=null;for(let l=0;l<n.length;l++)n[l].trim()!==""?i===null&&(i=l):i!==null&&(r.push({type:"paragraph",start:i+1,end:l}),i=null);i!==null&&r.push({type:"paragraph",start:i+1,end:n.length}),r.length===0&&r.push({type:"paragraph",start:1,end:Math.max(1,n.length)});const o=[],u=[],p=[];return r.forEach((l,s)=>{s>0&&(o.push(""),u.push(null),p.push(null));for(let c=l.start;c<=l.end;c++)for(const d of P(n[c-1]??"",e))o.push(d),u.push(s),p.push([c,c])}),{lines:o,rows:u,spans:p,blocks:r}}a(he,"fallbackDocWithMap");const J=new H(20);function U(t,e){const n=`${t}\0${e}`,r=J.get(n);if(r)return r;if(M(),f==="ready")try{const i=g(h,["doc","render","--width",String(e),"--color","on","--line-map"],{input:t,encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"],maxBuffer:67108864}),o=de(i.trim(),t);if(o!==null)return J.set(n,o),o;x("doc render --line-map (malformed output)")}catch(i){B(i)||x("doc render --line-map")}return he(t,e)}a(U,"renderMarkdownWithMap");function me(t,e,n){const r=U(t,e);if(n<=e||!r.blocks.some(l=>K.has(l.type)))return r;const i=U(t,n);if(i.blocks.length!==r.blocks.length)return r;const o=[],u=[],p=[];for(let l=0;l<r.lines.length;l++){const s=r.rows[l]??null;if(s===null||!K.has(r.blocks[s].type)){o.push(r.lines[l]),u.push(s),p.push(r.spans[l]??null);continue}if(l===0||r.rows[l-1]!==s)for(let c=0;c<i.lines.length;c++)i.rows[c]===s&&(o.push(i.lines[c]),u.push(s),p.push(i.spans[c]??null))}return{lines:o,rows:u,spans:p,blocks:r.blocks}}a(me,"renderMarkdownBlockAware");function Ne(t,e,n){return[...me(t,e,n).lines]}a(Ne,"renderMarkdownBlockAwareLines");function Re(t){if(M(),f!=="ready")return{ok:!0};const e=G(h,["doc","check"],{input:t,encoding:"utf-8",timeout:5e3});if(e.error)return e.error.code==="ETIMEDOUT"||e.signal==="SIGTERM"||x("doc check"),{ok:!1,error:`termrender: invocation failed: ${e.error.message}`};let n=null;const r=typeof e.stdout=="string"?e.stdout:"";if(r)try{n=JSON.parse(r.trim())}catch{}if(n!==null){if(n.ok)return{ok:!0};const i=Array.isArray(n.errors)?n.errors[0]:void 0;return{ok:!1,error:`termrender: ${i&&typeof i.message=="string"&&i.message?i.message:"invalid markdown"}`}}return e.status!==0?{ok:!1,error:`termrender: doc check exited ${e.status}`}:{ok:!0}}a(Re,"checkMarkdown");function _e(t){return M(),f!=="ready"?null:`${V(h)} doc watch ${V(t)}`}a(_e,"documentWatchCommand");export{Re as checkMarkdown,_e as documentWatchCommand,M as ensureRenderer,Me as isRendererReady,Ae as renderMarkdown,me as renderMarkdownBlockAware,Ne as renderMarkdownBlockAwareLines,U as renderMarkdownWithMap};
@@ -339,6 +339,10 @@ export declare const CONFIG_SETTINGS_COVERAGE: {
339
339
  surface: "specialized";
340
340
  owner: string;
341
341
  };
342
+ api: {
343
+ surface: "specialized";
344
+ owner: string;
345
+ };
342
346
  spawnEnv: {
343
347
  surface: "specialized";
344
348
  owner: string;
@@ -1 +1 @@
1
- var v=Object.defineProperty;var n=(e,t)=>v(e,"name",{value:t,configurable:!0});import{CONDENSED_HISTORY_MODES as u,MOUSE_MODE_DEFAULTS as p,WHIP_MESSAGE_MODES as d}from"../types.js";function i(e,t,o){const r=e.indexOf(t),a=r>=0?r:0;return e[(a+o+e.length)%e.length]}n(i,"cycleChoice");function c(e,t,o){const r=e.indexOf(t);return r>=0?e[(r+o+e.length)%e.length]:o>=0?e.find(a=>a>t)??e[0]:[...e].reverse().find(a=>a<t)??e[e.length-1]}n(c,"cycleNumber");function s(e,t){return e==="true"?!0:e==="false"?!1:`${t} must be true or false`}n(s,"booleanFromString");function f(e,t){if(!/^\d+$/.test(e))return`${t} must be an integer >= 0`;const o=Number(e);return Number.isSafeInteger(o)?o:`${t} must be an integer >= 0`}n(f,"nonNegativeIntegerFromString");function _(e,t){const o=f(e,t);return typeof o=="string"||o<1?`${t} must be an integer >= 1`:o}n(_,"positiveIntegerFromString");const m=[!1,"notify","apply"],w=[0,1,6,12,24,48,72,168],b=[1,2,3,4,5,6,8,10,12],l="openai-codex";function g(e,t){const o=e.providerOptions[t];if(o!==void 0)return o;const r={};return e.providerOptions[t]=r,r}n(g,"providerOptionsFor");const x=[1,2,3,4,5,6],h={schema_version:{surface:"specialized",owner:"internal schema migration"},marketplaces:{surface:"specialized",owner:"Plugins and pkg market commands"},plugins:{surface:"specialized",owner:"Plugins tab and pkg plugin commands"},auto_update:{surface:"settings",settings:{crtr:{key:"auto_update.crtr",label:"crouter updates",description:"Notify about crouter releases, install them automatically, or turn checks off.",valueConstraint:"true|false|notify|apply; true stores notify",applies:"Applies on the next crouter command.",group:"updates",order:70,format:n(e=>e.auto_update.crtr===!1?"off":e.auto_update.crtr,"format"),cycle:n((e,t)=>{e.auto_update.crtr=i(m,e.auto_update.crtr,t)},"cycle"),setFromString:n((e,t)=>{const o=t==="true"?"notify":t;return o!=="notify"&&o!=="apply"&&o!=="false"?"auto_update.crtr must be notify, apply, or false":(e.auto_update.crtr=o==="false"?!1:o,null)},"setFromString")},content:{key:"auto_update.content",label:"Content updates",description:"Notify about plugin and marketplace updates, apply them automatically, or turn checks off.",valueConstraint:"false|notify|apply",applies:"Applies on the next crouter command.",group:"updates",order:80,format:n(e=>e.auto_update.content===!1?"off":e.auto_update.content,"format"),cycle:n((e,t)=>{e.auto_update.content=i(m,e.auto_update.content,t)},"cycle"),setFromString:n((e,t)=>t!=="notify"&&t!=="apply"&&t!=="false"?"auto_update.content must be notify, apply, or false":(e.auto_update.content=t==="false"?!1:t,null),"setFromString")},interval_hours:{key:"auto_update.interval_hours",label:"Update interval",description:"Minimum hours between automatic update checks. Zero checks on every command.",valueConstraint:"integer >= 0",applies:"Applies on the next crouter command.",group:"updates",order:90,format:n(e=>`${e.auto_update.interval_hours}h`,"format"),cycle:n((e,t)=>{e.auto_update.interval_hours=c(w,e.auto_update.interval_hours,t)},"cycle"),setFromString:n((e,t)=>{const o=f(t,"auto_update.interval_hours");return typeof o=="string"?o:(e.auto_update.interval_hours=o,null)},"setFromString")}}},max_panes_per_window:{surface:"settings",setting:{key:"max_panes_per_window",label:"Maximum panes",description:"Maximum human-review panes placed in one tmux window before using another window.",valueConstraint:"integer >= 1",applies:"Applies to the next pane placement.",group:"panes",order:20,format:n(e=>String(e.max_panes_per_window),"format"),cycle:n((e,t)=>{e.max_panes_per_window=c(b,e.max_panes_per_window,t)},"cycle"),setFromString:n((e,t)=>{const o=_(t,"max_panes_per_window");return typeof o=="string"?o:(e.max_panes_per_window=o,null)},"setFromString")}},auto_open_child_viewers:{surface:"settings",setting:{key:"auto_open_child_viewers",label:"Open child panes",description:"Open each managed child beside its watched parent. Off keeps new children running in the background.",valueConstraint:"true|false",applies:"Applies to the next managed child created; independent roots still open their attended viewer.",group:"panes",order:10,format:n(e=>e.auto_open_child_viewers?"on":"off","format"),cycle:n(e=>{e.auto_open_child_viewers=!e.auto_open_child_viewers},"cycle"),setFromString:n((e,t)=>{const o=s(t,"auto_open_child_viewers");return typeof o=="string"?o:(e.auto_open_child_viewers=o,null)},"setFromString")}},brokerThresholds:{surface:"specialized",owner:"Broker limits tab"},lifecycle:{surface:"specialized",owner:"sys config set lifecycle.unattendedParkMs"},completion_bell:{surface:"settings",setting:{key:"completion_bell",label:"Completion bell",description:"Ring a short sound when the node in an attached viewer finishes a turn.",valueConstraint:"true|false",applies:"Applies on the next turn end.",group:"display",order:41,format:n(e=>e.completion_bell?"on":"off","format"),cycle:n(e=>{e.completion_bell=!e.completion_bell},"cycle"),setFromString:n((e,t)=>{const o=s(t,"completion_bell");return typeof o=="string"?o:(e.completion_bell=o,null)},"setFromString")}},working_gerunds:{surface:"specialized",owner:"sys config set working_gerunds"},whip_mode:{surface:"settings",setting:{key:"whip_mode",label:"Whip mode",description:"Play the whip animation when you send a prompt and when a viewer opens for an agent with an initial prompt.",valueConstraint:"true|false",applies:"Applies on the next attach.",group:"display",order:39,format:n(e=>e.whip_mode?"on":"off","format"),cycle:n(e=>{e.whip_mode=!e.whip_mode},"cycle"),setFromString:n((e,t)=>{const o=s(t,"whip_mode");return typeof o=="string"?o:(e.whip_mode=o,null)},"setFromString")}},page_surface:{surface:"settings",setting:{key:"page_surface",label:"Page human surface",description:"This host reads pages in a browser rather than a terminal. Agents are told the page module runs as written, HTML pages are offered, and document review and live display leave the command tree.",valueConstraint:"true|false",applies:"Applies when human command help is assembled.",group:"display",order:37,format:n(e=>e.page_surface?"on":"off","format"),cycle:n(e=>{e.page_surface=!e.page_surface},"cycle"),setFromString:n((e,t)=>{const o=s(t,"page_surface");return typeof o=="string"?o:(e.page_surface=o,null)},"setFromString")}},whip_messages:{surface:"specialized",owner:"sys config set whip_messages"},whip_message_mode:{surface:"settings",setting:{key:"whip_message_mode",label:"Whip message",description:"What the whip action sends: a random line from whip_messages, or the fixed \u201CGo faster\u201D.",valueConstraint:d.join("|"),applies:"Applies on the next attach.",group:"display",order:40,format:n(e=>e.whip_message_mode==="go-faster"?"go faster":"random","format"),cycle:n((e,t)=>{e.whip_message_mode=i(d,e.whip_message_mode,t)},"cycle"),setFromString:n((e,t)=>d.includes(t)?(e.whip_message_mode=t,null):`whip_message_mode must be one of: ${d.join("|")}`,"setFromString")}},mouse_mode_default:{surface:"settings",setting:{key:"mouse_mode_default",label:"Default mouse mode",description:"Initial mouse wheel scrolling for each attached viewer.",valueConstraint:p.join("|"),applies:"Applies on the next attach.",group:"display",order:38,format:n(e=>e.mouse_mode_default==="tmux"?"follow tmux":e.mouse_mode_default,"format"),cycle:n((e,t)=>{e.mouse_mode_default=i(p,e.mouse_mode_default,t)},"cycle"),setFromString:n((e,t)=>p.includes(t)?(e.mouse_mode_default=t,null):`mouse_mode_default must be one of: ${p.join("|")}`,"setFromString")}},live_cycles:{surface:"settings",setting:{key:"live_cycles",label:"Live cycles",description:"Trailing refresh cycles rendered at full fidelity; older cycles condense to text.",valueConstraint:"integer >= 1",applies:"Applies on the next attach.",group:"display",order:50,format:n(e=>String(e.live_cycles),"format"),cycle:n((e,t)=>{e.live_cycles=c(x,e.live_cycles,t)},"cycle"),setFromString:n((e,t)=>{const o=_(t,"live_cycles");return typeof o=="string"?o:(e.live_cycles=o,null)},"setFromString")}},condensed_history:{surface:"settings",setting:{key:"condensed_history",label:"Condensed history",description:"Messages retained from condensed cycles. Tool calls are never kept.",valueConstraint:u.join("|"),applies:"Applies on the next attach.",group:"display",order:60,format:n(e=>e.condensed_history,"format"),cycle:n((e,t)=>{e.condensed_history=i(u,e.condensed_history,t)},"cycle"),setFromString:n((e,t)=>u.includes(t)?(e.condensed_history=t,null):`condensed_history must be one of: ${u.join("|")}`,"setFromString")}},bash_tool_purpose:{surface:"settings",setting:{key:"bash_tool_purpose",label:"Bash purpose field",description:"Expose the optional purpose label to agents in the bash tool schema. Off removes the field without disabling the bash valve.",valueConstraint:"true|false",applies:"Applies on the next node session.",group:"display",order:25,format:n(e=>e.bash_tool_purpose?"on":"off","format"),cycle:n(e=>{e.bash_tool_purpose=!e.bash_tool_purpose},"cycle"),setFromString:n((e,t)=>{const o=s(t,"bash_tool_purpose");return typeof o=="string"?o:(e.bash_tool_purpose=o,null)},"setFromString")}},fold_finished_tools:{surface:"settings",setting:{key:"fold_finished_tools",label:"Fold finished tools",description:"Fold every settled tool call to its call line. Toggle live with /fold-tools.",valueConstraint:"true|false",applies:"Applies on the next attach.",group:"display",order:30,format:n(e=>e.fold_finished_tools?"on":"off","format"),cycle:n(e=>{e.fold_finished_tools=!e.fold_finished_tools},"cycle"),setFromString:n((e,t)=>{const o=s(t,"fold_finished_tools");return typeof o=="string"?o:(e.fold_finished_tools=o,null)},"setFromString")}},summarize_tool_calls:{surface:"settings",setting:{key:"summarize_tool_calls",label:"Summarize tool calls",description:"Replace completed folded tool-call groups with a one-line broker summary when available.",valueConstraint:"true|false",applies:"Applies when the next visible message closes a tool-call group.",group:"display",order:35,format:n(e=>e.summarize_tool_calls?"on":"off","format"),cycle:n(e=>{e.summarize_tool_calls=!e.summarize_tool_calls},"cycle"),setFromString:n((e,t)=>{const o=s(t,"summarize_tool_calls");return typeof o=="string"?o:(e.summarize_tool_calls=o,null)},"setFromString")}},detailed_tool_recaps:{surface:"settings",setting:{key:"detailed_tool_recaps",label:"Detailed tool recaps",description:"Show each memory read and edited file in folded tool recaps. Off shows a compact count line instead.",valueConstraint:"true|false",applies:"Applies on the next attach.",group:"display",order:36,format:n(e=>e.detailed_tool_recaps?"on":"off","format"),cycle:n(e=>{e.detailed_tool_recaps=!e.detailed_tool_recaps},"cycle"),setFromString:n((e,t)=>{const o=s(t,"detailed_tool_recaps");return typeof o=="string"?o:(e.detailed_tool_recaps=o,null)},"setFromString")}},keybindings:{surface:"specialized",owner:"Keybindings tab"},tmux_passthrough:{surface:"specialized",owner:"Keybindings tab"},providerOptions:{surface:"settings",settings:{"openai-codex.fastMode":{key:`providerOptions.${l}.fastMode`,label:"Codex fast mode",description:"Serve Codex turns at the ChatGPT priority tier: about 1.5x faster generation for about 2x the credits. Displayed cost does not reflect the premium.",valueConstraint:"true|false",applies:"Applies on each node's next turn.",group:"providers",order:40,format:n(e=>e.providerOptions[l]?.fastMode?"on":"off","format"),cycle:n(e=>{const t=g(e,l);t.fastMode=!t.fastMode},"cycle"),setFromString:n((e,t)=>{const o=s(t,`providerOptions.${l}.fastMode`);return typeof o=="string"?o:(g(e,l).fastMode=o,null)},"setFromString")}}},modelLadders:{surface:"specialized",owner:"Models tab"},modelRoutes:{surface:"specialized",owner:"Models route declarations"},modelRouting:{surface:"specialized",owner:"Models route declarations"},kinds:{surface:"specialized",owner:"Models tab and kind registry"},page_components:{surface:"specialized",owner:"Page component catalog: product-registered page component kinds"},remoteCanvas:{surface:"specialized",owner:"canvas config commands"},spawnEnv:{surface:"specialized",owner:"spawn environment allow-list"},bin:{surface:"specialized",owner:"bare-binary contributions on every node PATH"},humanActions:{surface:"specialized",owner:"scope-configured human completion actions"}},O=Object.keys(h);function S(){const e=[];for(const t of Object.values(h))t.surface==="settings"&&("setting"in t?e.push(t.setting):e.push(...Object.values(t.settings)));return e.sort((t,o)=>t.order-o.order)}n(S,"collectUserSettings");const y=S();function A(e){return y.find(t=>t.key===e)}n(A,"findUserSetting");function E(e){return y.filter(t=>t.group===e)}n(E,"userSettingsInGroup");export{h as CONFIG_SETTINGS_COVERAGE,O as SCOPE_CONFIG_KEYS,y as USER_SETTINGS,A as findUserSetting,E as userSettingsInGroup};
1
+ var v=Object.defineProperty;var s=(e,t)=>v(e,"name",{value:t,configurable:!0});import{CONDENSED_HISTORY_MODES as u,MOUSE_MODE_DEFAULTS as p,WHIP_MESSAGE_MODES as d}from"../types.js";function i(e,t,o){const r=e.indexOf(t),a=r>=0?r:0;return e[(a+o+e.length)%e.length]}s(i,"cycleChoice");function c(e,t,o){const r=e.indexOf(t);return r>=0?e[(r+o+e.length)%e.length]:o>=0?e.find(a=>a>t)??e[0]:[...e].reverse().find(a=>a<t)??e[e.length-1]}s(c,"cycleNumber");function n(e,t){return e==="true"?!0:e==="false"?!1:`${t} must be true or false`}s(n,"booleanFromString");function f(e,t){if(!/^\d+$/.test(e))return`${t} must be an integer >= 0`;const o=Number(e);return Number.isSafeInteger(o)?o:`${t} must be an integer >= 0`}s(f,"nonNegativeIntegerFromString");function _(e,t){const o=f(e,t);return typeof o=="string"||o<1?`${t} must be an integer >= 1`:o}s(_,"positiveIntegerFromString");const m=[!1,"notify","apply"],w=[0,1,6,12,24,48,72,168],b=[1,2,3,4,5,6,8,10,12],l="openai-codex";function g(e,t){const o=e.providerOptions[t];if(o!==void 0)return o;const r={};return e.providerOptions[t]=r,r}s(g,"providerOptionsFor");const x=[1,2,3,4,5,6],h={schema_version:{surface:"specialized",owner:"internal schema migration"},marketplaces:{surface:"specialized",owner:"Plugins and pkg market commands"},plugins:{surface:"specialized",owner:"Plugins tab and pkg plugin commands"},auto_update:{surface:"settings",settings:{crtr:{key:"auto_update.crtr",label:"crouter updates",description:"Notify about crouter releases, install them automatically, or turn checks off.",valueConstraint:"true|false|notify|apply; true stores notify",applies:"Applies on the next crouter command.",group:"updates",order:70,format:s(e=>e.auto_update.crtr===!1?"off":e.auto_update.crtr,"format"),cycle:s((e,t)=>{e.auto_update.crtr=i(m,e.auto_update.crtr,t)},"cycle"),setFromString:s((e,t)=>{const o=t==="true"?"notify":t;return o!=="notify"&&o!=="apply"&&o!=="false"?"auto_update.crtr must be notify, apply, or false":(e.auto_update.crtr=o==="false"?!1:o,null)},"setFromString")},content:{key:"auto_update.content",label:"Content updates",description:"Notify about plugin and marketplace updates, apply them automatically, or turn checks off.",valueConstraint:"false|notify|apply",applies:"Applies on the next crouter command.",group:"updates",order:80,format:s(e=>e.auto_update.content===!1?"off":e.auto_update.content,"format"),cycle:s((e,t)=>{e.auto_update.content=i(m,e.auto_update.content,t)},"cycle"),setFromString:s((e,t)=>t!=="notify"&&t!=="apply"&&t!=="false"?"auto_update.content must be notify, apply, or false":(e.auto_update.content=t==="false"?!1:t,null),"setFromString")},interval_hours:{key:"auto_update.interval_hours",label:"Update interval",description:"Minimum hours between automatic update checks. Zero checks on every command.",valueConstraint:"integer >= 0",applies:"Applies on the next crouter command.",group:"updates",order:90,format:s(e=>`${e.auto_update.interval_hours}h`,"format"),cycle:s((e,t)=>{e.auto_update.interval_hours=c(w,e.auto_update.interval_hours,t)},"cycle"),setFromString:s((e,t)=>{const o=f(t,"auto_update.interval_hours");return typeof o=="string"?o:(e.auto_update.interval_hours=o,null)},"setFromString")}}},max_panes_per_window:{surface:"settings",setting:{key:"max_panes_per_window",label:"Maximum panes",description:"Maximum human-review panes placed in one tmux window before using another window.",valueConstraint:"integer >= 1",applies:"Applies to the next pane placement.",group:"panes",order:20,format:s(e=>String(e.max_panes_per_window),"format"),cycle:s((e,t)=>{e.max_panes_per_window=c(b,e.max_panes_per_window,t)},"cycle"),setFromString:s((e,t)=>{const o=_(t,"max_panes_per_window");return typeof o=="string"?o:(e.max_panes_per_window=o,null)},"setFromString")}},auto_open_child_viewers:{surface:"settings",setting:{key:"auto_open_child_viewers",label:"Open child panes",description:"Open each managed child beside its watched parent. Off keeps new children running in the background.",valueConstraint:"true|false",applies:"Applies to the next managed child created; independent roots still open their attended viewer.",group:"panes",order:10,format:s(e=>e.auto_open_child_viewers?"on":"off","format"),cycle:s(e=>{e.auto_open_child_viewers=!e.auto_open_child_viewers},"cycle"),setFromString:s((e,t)=>{const o=n(t,"auto_open_child_viewers");return typeof o=="string"?o:(e.auto_open_child_viewers=o,null)},"setFromString")}},brokerThresholds:{surface:"specialized",owner:"Broker limits tab"},lifecycle:{surface:"specialized",owner:"sys config set lifecycle.unattendedParkMs"},completion_bell:{surface:"settings",setting:{key:"completion_bell",label:"Completion bell",description:"Ring a short sound when the node in an attached viewer finishes a turn.",valueConstraint:"true|false",applies:"Applies on the next turn end.",group:"display",order:41,format:s(e=>e.completion_bell?"on":"off","format"),cycle:s(e=>{e.completion_bell=!e.completion_bell},"cycle"),setFromString:s((e,t)=>{const o=n(t,"completion_bell");return typeof o=="string"?o:(e.completion_bell=o,null)},"setFromString")}},working_gerunds:{surface:"specialized",owner:"sys config set working_gerunds"},whip_mode:{surface:"settings",setting:{key:"whip_mode",label:"Whip mode",description:"Play the whip animation when you send a prompt and when a viewer opens for an agent with an initial prompt.",valueConstraint:"true|false",applies:"Applies on the next attach.",group:"display",order:39,format:s(e=>e.whip_mode?"on":"off","format"),cycle:s(e=>{e.whip_mode=!e.whip_mode},"cycle"),setFromString:s((e,t)=>{const o=n(t,"whip_mode");return typeof o=="string"?o:(e.whip_mode=o,null)},"setFromString")}},page_surface:{surface:"settings",setting:{key:"page_surface",label:"Page human surface",description:"This host reads pages in a browser rather than a terminal. Agents are told the page module runs as written, HTML pages are offered, and document review and live display leave the command tree.",valueConstraint:"true|false",applies:"Applies when human command help is assembled.",group:"display",order:37,format:s(e=>e.page_surface?"on":"off","format"),cycle:s(e=>{e.page_surface=!e.page_surface},"cycle"),setFromString:s((e,t)=>{const o=n(t,"page_surface");return typeof o=="string"?o:(e.page_surface=o,null)},"setFromString")}},whip_messages:{surface:"specialized",owner:"sys config set whip_messages"},whip_message_mode:{surface:"settings",setting:{key:"whip_message_mode",label:"Whip message",description:"What the whip action sends: a random line from whip_messages, or the fixed \u201CGo faster\u201D.",valueConstraint:d.join("|"),applies:"Applies on the next attach.",group:"display",order:40,format:s(e=>e.whip_message_mode==="go-faster"?"go faster":"random","format"),cycle:s((e,t)=>{e.whip_message_mode=i(d,e.whip_message_mode,t)},"cycle"),setFromString:s((e,t)=>d.includes(t)?(e.whip_message_mode=t,null):`whip_message_mode must be one of: ${d.join("|")}`,"setFromString")}},mouse_mode_default:{surface:"settings",setting:{key:"mouse_mode_default",label:"Default mouse mode",description:"Initial mouse wheel scrolling for each attached viewer.",valueConstraint:p.join("|"),applies:"Applies on the next attach.",group:"display",order:38,format:s(e=>e.mouse_mode_default==="tmux"?"follow tmux":e.mouse_mode_default,"format"),cycle:s((e,t)=>{e.mouse_mode_default=i(p,e.mouse_mode_default,t)},"cycle"),setFromString:s((e,t)=>p.includes(t)?(e.mouse_mode_default=t,null):`mouse_mode_default must be one of: ${p.join("|")}`,"setFromString")}},live_cycles:{surface:"settings",setting:{key:"live_cycles",label:"Live cycles",description:"Trailing refresh cycles rendered at full fidelity; older cycles condense to text.",valueConstraint:"integer >= 1",applies:"Applies on the next attach.",group:"display",order:50,format:s(e=>String(e.live_cycles),"format"),cycle:s((e,t)=>{e.live_cycles=c(x,e.live_cycles,t)},"cycle"),setFromString:s((e,t)=>{const o=_(t,"live_cycles");return typeof o=="string"?o:(e.live_cycles=o,null)},"setFromString")}},condensed_history:{surface:"settings",setting:{key:"condensed_history",label:"Condensed history",description:"Messages retained from condensed cycles. Tool calls are never kept.",valueConstraint:u.join("|"),applies:"Applies on the next attach.",group:"display",order:60,format:s(e=>e.condensed_history,"format"),cycle:s((e,t)=>{e.condensed_history=i(u,e.condensed_history,t)},"cycle"),setFromString:s((e,t)=>u.includes(t)?(e.condensed_history=t,null):`condensed_history must be one of: ${u.join("|")}`,"setFromString")}},bash_tool_purpose:{surface:"settings",setting:{key:"bash_tool_purpose",label:"Bash purpose field",description:"Expose the optional purpose label to agents in the bash tool schema. Off removes the field without disabling the bash valve.",valueConstraint:"true|false",applies:"Applies on the next node session.",group:"display",order:25,format:s(e=>e.bash_tool_purpose?"on":"off","format"),cycle:s(e=>{e.bash_tool_purpose=!e.bash_tool_purpose},"cycle"),setFromString:s((e,t)=>{const o=n(t,"bash_tool_purpose");return typeof o=="string"?o:(e.bash_tool_purpose=o,null)},"setFromString")}},fold_finished_tools:{surface:"settings",setting:{key:"fold_finished_tools",label:"Fold finished tools",description:"Fold every settled tool call to its call line. Toggle live with /fold-tools.",valueConstraint:"true|false",applies:"Applies on the next attach.",group:"display",order:30,format:s(e=>e.fold_finished_tools?"on":"off","format"),cycle:s(e=>{e.fold_finished_tools=!e.fold_finished_tools},"cycle"),setFromString:s((e,t)=>{const o=n(t,"fold_finished_tools");return typeof o=="string"?o:(e.fold_finished_tools=o,null)},"setFromString")}},summarize_tool_calls:{surface:"settings",setting:{key:"summarize_tool_calls",label:"Summarize tool calls",description:"Replace completed folded tool-call groups with a one-line broker summary when available.",valueConstraint:"true|false",applies:"Applies when the next visible message closes a tool-call group.",group:"display",order:35,format:s(e=>e.summarize_tool_calls?"on":"off","format"),cycle:s(e=>{e.summarize_tool_calls=!e.summarize_tool_calls},"cycle"),setFromString:s((e,t)=>{const o=n(t,"summarize_tool_calls");return typeof o=="string"?o:(e.summarize_tool_calls=o,null)},"setFromString")}},detailed_tool_recaps:{surface:"settings",setting:{key:"detailed_tool_recaps",label:"Detailed tool recaps",description:"Show each memory read and edited file in folded tool recaps. Off shows a compact count line instead.",valueConstraint:"true|false",applies:"Applies on the next attach.",group:"display",order:36,format:s(e=>e.detailed_tool_recaps?"on":"off","format"),cycle:s(e=>{e.detailed_tool_recaps=!e.detailed_tool_recaps},"cycle"),setFromString:s((e,t)=>{const o=n(t,"detailed_tool_recaps");return typeof o=="string"?o:(e.detailed_tool_recaps=o,null)},"setFromString")}},keybindings:{surface:"specialized",owner:"Keybindings tab"},tmux_passthrough:{surface:"specialized",owner:"Keybindings tab"},providerOptions:{surface:"settings",settings:{"openai-codex.fastMode":{key:`providerOptions.${l}.fastMode`,label:"Codex fast mode",description:"Serve Codex turns at the ChatGPT priority tier: about 1.5x faster generation for about 2x the credits. Displayed cost does not reflect the premium.",valueConstraint:"true|false",applies:"Applies on each node's next turn.",group:"providers",order:40,format:s(e=>e.providerOptions[l]?.fastMode?"on":"off","format"),cycle:s(e=>{const t=g(e,l);t.fastMode=!t.fastMode},"cycle"),setFromString:s((e,t)=>{const o=n(t,`providerOptions.${l}.fastMode`);return typeof o=="string"?o:(g(e,l).fastMode=o,null)},"setFromString")}}},modelLadders:{surface:"specialized",owner:"Models tab"},modelRoutes:{surface:"specialized",owner:"Models route declarations"},modelRouting:{surface:"specialized",owner:"Models route declarations"},kinds:{surface:"specialized",owner:"Models tab and kind registry"},page_components:{surface:"specialized",owner:"Page component catalog: product-registered page component kinds"},remoteCanvas:{surface:"specialized",owner:"canvas config commands"},api:{surface:"specialized",owner:"sys connect"},spawnEnv:{surface:"specialized",owner:"spawn environment allow-list"},bin:{surface:"specialized",owner:"bare-binary contributions on every node PATH"},humanActions:{surface:"specialized",owner:"scope-configured human completion actions"}},O=Object.keys(h);function S(){const e=[];for(const t of Object.values(h))t.surface==="settings"&&("setting"in t?e.push(t.setting):e.push(...Object.values(t.settings)));return e.sort((t,o)=>t.order-o.order)}s(S,"collectUserSettings");const y=S();function A(e){return y.find(t=>t.key===e)}s(A,"findUserSetting");function E(e){return y.filter(t=>t.group===e)}s(E,"userSettingsInGroup");export{h as CONFIG_SETTINGS_COVERAGE,O as SCOPE_CONFIG_KEYS,y as USER_SETTINGS,A as findUserSetting,E as userSettingsInGroup};
@@ -1,2 +1,2 @@
1
1
  import type { RouteTable } from '../router.js';
2
- export declare const healthRoutes: RouteTable;
2
+ export declare function healthRoutes(tcpAddress: () => string | null): RouteTable;
@@ -1,9 +1,9 @@
1
- var u=Object.defineProperty;var e=(t,o)=>u(t,"name",{value:o,configurable:!0});import{openDb as r}from"../../../core/canvas/db.js";import{apiSocketPath as c}from"../../../core/canvas/paths.js";import{currentVersion as i}from"../../../core/self-update.js";import{API_VERSION as p}from"../../../api/index.js";import{DEFAULT_INTERVAL_MS as d}from"../../supervise-cadence.js";import{envCrtrdTcp as l}from"../../../shared/env.js";import{isPidAlive as _}from"../../../core/canvas/pid.js";import{boundDaemonControl as s}from"../../control.js";const m=d;function a(){try{return r().prepare("SELECT 1 FROM nodes LIMIT 1").get(),!0}catch{return!1}}e(a,"canvasAccessible");function h(){const t={},o=r().prepare(`
1
+ var u=Object.defineProperty;var e=(o,t)=>u(o,"name",{value:t,configurable:!0});import{openDb as n}from"../../../core/canvas/db.js";import{apiSocketPath as i}from"../../../core/canvas/paths.js";import{currentVersion as c}from"../../../core/self-update.js";import{API_VERSION as p}from"../../../api/index.js";import{DEFAULT_INTERVAL_MS as d}from"../../supervise-cadence.js";import{isPidAlive as l}from"../../../core/canvas/pid.js";import{boundDaemonControl as s}from"../../control.js";const _=d;function a(){try{return n().prepare("SELECT 1 FROM nodes LIMIT 1").get(),!0}catch{return!1}}e(a,"canvasAccessible");function m(){const o={},t=n().prepare(`
2
2
  SELECT status, COUNT(*) AS count
3
3
  FROM node_rows
4
4
  WHERE node_id NOT IN (SELECT node_id FROM warm_pool)
5
5
  GROUP BY status
6
- `).all();for(const n of o)t[n.status]=n.count;return t}e(h,"nodeCounts");function E(){return r().prepare(`
6
+ `).all();for(const r of t)o[r.status]=r.count;return o}e(m,"nodeCounts");function h(){return n().prepare(`
7
7
  SELECT pi_pid
8
8
  FROM node_rows
9
9
  WHERE node_id NOT IN (SELECT node_id FROM warm_pool)
@@ -11,4 +11,4 @@ var u=Object.defineProperty;var e=(t,o)=>u(t,"name",{value:o,configurable:!0});i
11
11
  AND pi_pid IS NOT NULL
12
12
  AND turn_open_at IS NOT NULL
13
13
  AND turn_closed_at IS NULL
14
- `).all().filter(o=>_(o.pi_pid)).length}e(E,"busyNodeCount");function f(){const t=a(),o=s()?.startupBlock()??null,n={ok:t&&o===null,daemon_up:!0,store_accessible:t,brokers_reconciled:o===null,startup_phase:o?.phase??"ready",...o===null?{}:{startup_blocked:o.message}};return{status:n.ok?200:503,body:n}}e(f,"handleHealthz");function N(){if(!a())throw{status:503,code:"store_unavailable",message:"canvas store is inaccessible"};const t=l();return{status:200,body:{daemon_pid:process.pid,tick_interval_ms:m,node_counts:h(),busy_node_count:E(),listeners:{socket_path:c(),tcp:t!==void 0&&t!==""?t:null},runtime_version:i(),api_version:p,startup_phase:s()?.startupBlock()?.phase??"ready"}}}e(N,"handleStatus");const k=[{method:"GET",pattern:"/healthz",handler:e(t=>f(),"handler")},{method:"GET",pattern:"/v1/status",handler:e(t=>N(),"handler")}];export{k as healthRoutes};
14
+ `).all().filter(t=>l(t.pi_pid)).length}e(h,"busyNodeCount");function E(){const o=a(),t=s()?.startupBlock()??null,r={ok:o&&t===null,daemon_up:!0,store_accessible:o,brokers_reconciled:t===null,startup_phase:t?.phase??"ready",...t===null?{}:{startup_blocked:t.message}};return{status:r.ok?200:503,body:r}}e(E,"handleHealthz");function f(o){if(!a())throw{status:503,code:"store_unavailable",message:"canvas store is inaccessible"};return{status:200,body:{daemon_pid:process.pid,tick_interval_ms:_,node_counts:m(),busy_node_count:h(),listeners:{socket_path:i(),tcp:o()},runtime_version:c(),api_version:p,startup_phase:s()?.startupBlock()?.phase??"ready"}}}e(f,"handleStatus");function R(o){return[{method:"GET",pattern:"/healthz",handler:e(t=>E(),"handler")},{method:"GET",pattern:"/v1/status",handler:e(t=>f(o),"handler")}]}e(R,"healthRoutes");export{R as healthRoutes};
@@ -1 +1 @@
1
- var I=Object.defineProperty;var r=(a,t)=>I(a,"name",{value:t,configurable:!0});import{homedir as D}from"node:os";import{join as m}from"node:path";import{deleteAuthCredential as f,readAuthCredentials as q,setAuthCredential as h}from"../../../core/auth-file.js";import{ApiError as i}from"../../../api/index.js";import{ANTHROPIC_PROVIDER_ID as g,OPENAI_CODEX_PROVIDER_ID as y,commitManagedLogin as x,readSubscriptionPool as w,removeAllManagedAccounts as E}from"../../../core/subscription-state.js";function s(){const a=process.env.PI_CODING_AGENT_DIR,t=a!==void 0&&a!==""?a:m(D(),".pi","agent");return m(t,"auth.json")}r(s,"authPath");function u(a){return a===g||a===y}r(u,"isManagedProvider");function b(a,t){if(typeof t!="string"||t==="")throw new i(400,"invalid_request",`oauth credential requires \`${a}\``);const e=Date.parse(t);if(Number.isNaN(e))throw new i(400,"invalid_request",`\`${a}\` is not a valid ISO timestamp`);return e}r(b,"parseExpiry");function d(a,t,e){if(typeof e!="string"||e==="")throw new i(400,"invalid_request",`${a} credential requires \`${t}\``);return e}r(d,"requireField");function O(a){return{label:a.label,expires_at:new Date(a.expires).toISOString(),rate_limited_until:new Date(a.rateLimitedUntil).toISOString(),...a.authFailure?{auth_failure:a.authFailure}:{},...a.accountId?{account_id:a.accountId}:{}}}r(O,"toManagedAccountStatus");function k(a){const t=w(a).map(O);return{provider:a,managed:!0,configured:t.length>0,accounts:t}}r(k,"managedStatus");async function P(){const a=await q(s()),t=[k(g),k(y)];for(const[e,n]of Object.entries(a))u(e)||n.type!=="api_key"&&n.type!=="oauth"||t.push({provider:e,managed:!1,configured:!0,kind:n.type});return{status:200,body:{providers:t}}}r(P,"handleList");async function R(a){const t=a.params.provider,e=a.body;if(e===void 0||typeof e!="object"||typeof e.kind!="string")throw new i(400,"invalid_request","a credential `kind` is required");switch(e.kind){case"api_key":{const n=d("api_key","api_key",e.api_key);return await h(t,{type:"api_key",key:n},s()),{status:200,body:{provider:t,installed:!0,managed:!1}}}case"oauth":{if(u(t))throw new i(422,"unprocessable",`provider ${t} stores oauth credentials as managed accounts \u2014 use kind \`managed_account\``);const n=d("oauth","access_token",e.access_token),o=d("oauth","refresh_token",e.refresh_token),l=b("expires_at",e.expires_at),p=e.extra??{};await h(t,{type:"oauth",...p,access:n,refresh:o,expires:l},s());const c={provider:t,installed:!0,managed:!1};return typeof e.account_id=="string"&&e.account_id!==""&&(c.account_id=e.account_id),{status:200,body:c}}case"managed_account":{if(!u(t))throw new i(422,"unprocessable",`provider ${t} does not support managed accounts`);const n=typeof e.label=="string"&&e.label.trim()?e.label.trim():void 0;if(!n&&e.auto_label!==!0)throw new i(400,"invalid_request","managed_account credential requires `label` or `auto_label`");const o=typeof e.account_id=="string"&&e.account_id!==""?e.account_id:void 0,l=d("managed_account","access_token",e.access_token),p=d("managed_account","refresh_token",e.refresh_token),c=b("expires_at",e.expires_at),v=typeof e.last_rate_limited_at=="number"?e.last_rate_limited_at:Math.max(0,...w(t).map(A=>A.lastRateLimitedAt));await x(t,n,{refresh:p,access:l,expires:c,...o?{accountId:o}:{},lastRateLimitedAt:v,autoLabel:e.auto_label===!0});const _={provider:t,installed:!0,managed:!0};return o&&(_.account_id=o),{status:200,body:_}}default:throw new i(400,"invalid_request",`unknown credential kind: ${String(e.kind)}`)}}r(R,"handleInstall");async function S(a){const t=a.params.provider;if(u(t)){const n=await E(t),o=await f(t,s());return{status:200,body:{provider:t,removed_accounts:n+(o?1:0)}}}const e=await f(t,s())?1:0;return{status:200,body:{provider:t,removed_accounts:e}}}r(S,"handleDelete");const j=[{method:"GET",pattern:"/v1/model-auth",handler:P},{method:"PUT",pattern:"/v1/model-auth/:provider",handler:R},{method:"DELETE",pattern:"/v1/model-auth/:provider",handler:S}];export{j as modelAuthRoutes};
1
+ var L=Object.defineProperty;var o=(n,t)=>L(n,"name",{value:t,configurable:!0});import{homedir as P}from"node:os";import{join as k}from"node:path";import{deleteAuthCredential as A,readAuthCredentials as I,setAuthCredential as q}from"../../../core/auth-file.js";import{ApiError as u}from"../../../api/index.js";import{usage as y}from"../../../core/errors.js";import{resolveLaunchTarget as R}from"../../../core/runtime/launch-target.js";import{ANTHROPIC_PROVIDER_ID as D,OPENAI_CODEX_PROVIDER_ID as O,commitManagedLogin as $,readSubscriptionPool as w,removeAllManagedAccounts as M}from"../../../core/subscription-state.js";function l(){const n=process.env.PI_CODING_AGENT_DIR,t=n!==void 0&&n!==""?n:k(P(),".pi","agent");return k(t,"auth.json")}o(l,"authPath");function f(n){return n===D||n===O}o(f,"isManagedProvider");function E(n,t){if(typeof t!="string"||t==="")throw new u(400,"invalid_request",`oauth credential requires \`${n}\``);const e=Date.parse(t);if(Number.isNaN(e))throw new u(400,"invalid_request",`\`${n}\` is not a valid ISO timestamp`);return e}o(E,"parseExpiry");function h(n,t,e){if(typeof e!="string"||e==="")throw new u(400,"invalid_request",`${n} credential requires \`${t}\``);return e}o(h,"requireField");function N(n){return{label:n.label,expires_at:new Date(n.expires).toISOString(),rate_limited_until:new Date(n.rateLimitedUntil).toISOString(),...n.authFailure?{auth_failure:n.authFailure}:{},...n.accountId?{account_id:n.accountId}:{}}}o(N,"toManagedAccountStatus");function S(n){const t=w(n).map(N);return{provider:n,managed:!0,configured:t.length>0,accounts:t}}o(S,"managedStatus");function g(n,t){const e=n.query.getAll(t);if(e.length>1)throw y(`${t} must appear at most once`);return e[0]}o(g,"singleQueryValue");function T(n){const t=n.indexOf("/"),e=t>0?n.slice(0,t):"";if(e==="")throw new u(422,"unresolved_model","the selected launch has no concrete model provider");return e}o(T,"providerFromModel");async function C(){const n=await I(l()),t=[S(D),S(O)];for(const[e,a]of Object.entries(n))f(e)||a.type!=="api_key"&&a.type!=="oauth"||t.push({provider:e,managed:!1,configured:!0,kind:a.type});return{status:200,body:{providers:t}}}o(C,"handleList");async function F(n){for(const[s]of n.query)if(s!=="profile"&&s!=="cwd"&&s!=="kind"&&s!=="model")throw y(`unknown model-auth readiness query parameter: ${s}`);const t=g(n,"profile"),e=g(n,"cwd"),a=g(n,"kind"),r=g(n,"model");if(a!==void 0&&a.trim()==="")throw y("kind must be a non-empty string when provided");if(r!==void 0&&r.trim()==="")throw y("model must be a non-empty string when provided");const d=await R({root:!0,...t!==void 0?{profile:t}:{},...e!==void 0?{cwd:e}:{},...a!==void 0?{kind:a}:{},...r!==void 0?{model:r}:{}},{readOnlyProfileSelection:!0});if(d.launch.model===void 0)throw new u(422,"unresolved_model","the selected launch has no concrete model");const i=T(d.launch.model),c=await I(l());if(f(i)){const s=w(i),m=s.filter(p=>p.authFailure===void 0);if(m.length===0)return{status:200,body:s.length===0?{provider:i,model:d.launch.model,credential:"missing"}:{provider:i,model:d.launch.model,credential:"unusable",reason:"invalid_grant"}};const v=Date.now(),x=m.some(p=>p.rateLimitedUntil<=v),b=Math.min(...m.map(p=>p.rateLimitedUntil));return{status:200,body:{provider:i,model:d.launch.model,credential:"ready",...!x&&b>v?{rate_limited_until:new Date(b).toISOString()}:{}}}}const _=c[i];return{status:200,body:{provider:i,model:d.launch.model,credential:_?.type==="api_key"||_?.type==="oauth"?"ready":"missing"}}}o(F,"handleReadiness");async function j(n){const t=n.params.provider,e=n.body;if(e===void 0||typeof e!="object"||typeof e.kind!="string")throw new u(400,"invalid_request","a credential `kind` is required");switch(e.kind){case"api_key":{const a=h("api_key","api_key",e.api_key);return await q(t,{type:"api_key",key:a},l()),{status:200,body:{provider:t,installed:!0,managed:!1}}}case"oauth":{if(f(t))throw new u(422,"unprocessable",`provider ${t} stores oauth credentials as managed accounts \u2014 use kind \`managed_account\``);const a=h("oauth","access_token",e.access_token),r=h("oauth","refresh_token",e.refresh_token),d=E("expires_at",e.expires_at),i=e.extra??{};await q(t,{type:"oauth",...i,access:a,refresh:r,expires:d},l());const c={provider:t,installed:!0,managed:!1};return typeof e.account_id=="string"&&e.account_id!==""&&(c.account_id=e.account_id),{status:200,body:c}}case"managed_account":{if(!f(t))throw new u(422,"unprocessable",`provider ${t} does not support managed accounts`);const a=typeof e.label=="string"&&e.label.trim()?e.label.trim():void 0;if(!a&&e.auto_label!==!0)throw new u(400,"invalid_request","managed_account credential requires `label` or `auto_label`");const r=typeof e.account_id=="string"&&e.account_id!==""?e.account_id:void 0,d=h("managed_account","access_token",e.access_token),i=h("managed_account","refresh_token",e.refresh_token),c=E("expires_at",e.expires_at),_=typeof e.last_rate_limited_at=="number"?e.last_rate_limited_at:Math.max(0,...w(t).map(m=>m.lastRateLimitedAt));await $(t,a,{refresh:i,access:d,expires:c,...r?{accountId:r}:{},lastRateLimitedAt:_,autoLabel:e.auto_label===!0});const s={provider:t,installed:!0,managed:!0};return r&&(s.account_id=r),{status:200,body:s}}default:throw new u(400,"invalid_request",`unknown credential kind: ${String(e.kind)}`)}}o(j,"handleInstall");async function G(n){const t=n.params.provider;if(f(t)){const a=await M(t),r=await A(t,l());return{status:200,body:{provider:t,removed_accounts:a+(r?1:0)}}}const e=await A(t,l())?1:0;return{status:200,body:{provider:t,removed_accounts:e}}}o(G,"handleDelete");const J=[{method:"GET",pattern:"/v1/model-auth",handler:C},{method:"GET",pattern:"/v1/model-auth/readiness",handler:F},{method:"PUT",pattern:"/v1/model-auth/:provider",handler:j},{method:"DELETE",pattern:"/v1/model-auth/:provider",handler:G}];export{J as modelAuthRoutes};
@@ -1,11 +1,15 @@
1
1
  export interface CreateApiServerOptions {
2
2
  /** Opt-in TCP listener as `host:port` (or `:port` for all interfaces).
3
- * Falls back to `CRTRD_TCP` when unset. Absent → unix socket only. */
3
+ * Falls back to `CRTRD_TCP`, then the user setting. Absent → unix socket only. */
4
4
  tcp?: string;
5
+ /** Bearer token for the TCP listener. Falls back to `CRTRD_TOKEN`, then the 0600 user secrets store. */
6
+ token?: string;
5
7
  }
6
8
  export interface ApiServer {
7
9
  /** Settles only after the mandatory Unix socket binds; rejects on its bind failure. */
8
10
  ready: Promise<void>;
11
+ /** The actual bound TCP address, including a kernel-selected port; null when TCP is off or not yet bound. */
12
+ tcpAddress(): string | null;
9
13
  /** Tear down both listeners and unlink the socket file. Double-close safe. */
10
14
  close(): Promise<void>;
11
15
  }
@@ -1,11 +1,11 @@
1
- var j=Object.defineProperty;var i=(t,o)=>j(t,"name",{value:o,configurable:!0});import{createServer as S}from"node:http";import{chmodSync as z,existsSync as w,unlinkSync as U}from"node:fs";import{apiSocketPath as J}from"../../core/canvas/paths.js";import{emitEvent as g}from"../../core/events/emit.js";import{envCrtrdTcp as x,envCrtrdToken as L}from"../../shared/env.js";import{Router as T}from"./router.js";import{handleAttachUpgrade as _}from"./bridge.js";import{boundDaemonControl as $}from"../control.js";import{attachRoutes as G}from"./handlers/attach.js";import{bashJobRoutes as D}from"./handlers/bash-jobs.js";import{brokerOperationRoutes as k}from"./handlers/broker-ops.js";import{brokerRecoveryRoutes as C,nodeFaultRoutes as F}from"./handlers/broker-recovery.js";import{canvasRoutes as M}from"./handlers/canvas.js";import{chatInventoryRoutes as Y}from"./handlers/chat-inventory.js";import{daemonRoutes as Z}from"./handlers/daemon.js";import{fileRoutes as K}from"./handlers/files.js";import{focusRoutes as Q}from"./handlers/focus.js";import{healthRoutes as V}from"./handlers/health.js";import{humanRequestRoutes as W}from"./handlers/human-requests.js";import{inboxRoutes as X}from"./handlers/inbox.js";import{feedbackCommentRoutes as q}from"./handlers/feedback-comments.js";import{memoryRoutes as ee}from"./handlers/memory.js";import{brokerMailRoutes as O,messageRoutes as re}from"./handlers/messages.js";import{modelAuthRoutes as te}from"./handlers/modelauth.js";import{modelConfigRoutes as oe}from"./handlers/model-config.js";import{nodeRoutes as ne}from"./handlers/nodes.js";import{nodeOutcomeRoutes as ie}from"./handlers/node-outcomes.js";import{profileRoutes as se}from"./handlers/profiles.js";import{prospectiveChatInventoryRoutes as le}from"./handlers/prospective-chat-inventory.js";import{reportRoutes as ce}from"./handlers/reports.js";import{reviewCommentRoutes as ae}from"./handlers/review-comments.js";import{reviewRoutes as ue}from"./handlers/reviews.js";import{subscriptionRoutes as pe}from"./handlers/subscriptions.js";import{cronRoutes as me}from"./handlers/crons.js";import{worktreeRoutes as de}from"./handlers/worktree.js";function fe(){return new T().registerAll(V).registerAll(Z).registerAll(ne).registerAll(ie).registerAll(D).registerAll(k).registerAll(C).registerAll(F).registerAll(de).registerAll(O).registerAll(re).registerAll(ce).registerAll(G).registerAll(M).registerAll(K).registerAll(ee).registerAll(Q).registerAll(pe).registerAll(me).registerAll(se).registerAll(te).registerAll(oe).registerAll(ue).registerAll(ae).registerAll(X).registerAll(W).registerAll(Y).registerAll(le).registerAll(q)}i(fe,"buildRouter");function ge(t,o){if(t.toUpperCase()!=="GET")return null;const s=o.split("/").filter(c=>c.length>0);if(s.length!==4||s[0]!=="v1"||s[1]!=="nodes"||s[3]!=="attach")return null;const l=s[2];return l===void 0||l===""?null:decodeURIComponent(l)}i(ge,"attachNodeId");function he(t){const o=t.trim();if(o==="")return null;const s=o.lastIndexOf(":");if(s<0)return null;const l=o.slice(0,s),c=o.slice(s+1),u=Number(c);return!Number.isInteger(u)||u<0||u>65535?null:{host:l===""?"0.0.0.0":l,port:u}}i(he,"parseTcp");const E={error:{code:"unauthorized",message:"missing or invalid bearer token"}};function N(t,o){return t.headers.authorization===`Bearer ${o}`}i(N,"isAuthorized");function ye(t){const o=JSON.stringify(E);t.writeHead(401,{"content-type":"application/json; charset=utf-8"}),t.end(o)}i(ye,"writeUnauthorized");function Re(t,o){t.writeHead(503,{"content-type":"application/json; charset=utf-8"}),t.end(JSON.stringify({error:{code:"startup_blocked",message:o}}))}i(Re,"writeStartupBlocked");function ve(t,o){const s=JSON.stringify({error:{code:"startup_blocked",message:o}}),l=`HTTP/1.1 503 Service Unavailable\r
1
+ var j=Object.defineProperty;var i=(e,t)=>j(e,"name",{value:t,configurable:!0});import{createServer as w}from"node:http";import{chmodSync as z,existsSync as C,unlinkSync as O}from"node:fs";import{apiSocketPath as x}from"../../core/canvas/paths.js";import{readConfig as G}from"../../core/config.js";import{getApiToken as J}from"../../core/secrets.js";import{emitEvent as g}from"../../core/events/emit.js";import{envCrtrdTcp as L,envCrtrdToken as _}from"../../shared/env.js";import{Router as U}from"./router.js";import{handleAttachUpgrade as D}from"./bridge.js";import{boundDaemonControl as F}from"../control.js";import{attachRoutes as M}from"./handlers/attach.js";import{bashJobRoutes as Y}from"./handlers/bash-jobs.js";import{brokerOperationRoutes as k}from"./handlers/broker-ops.js";import{brokerRecoveryRoutes as E,nodeFaultRoutes as Z}from"./handlers/broker-recovery.js";import{canvasRoutes as K}from"./handlers/canvas.js";import{chatInventoryRoutes as Q}from"./handlers/chat-inventory.js";import{daemonRoutes as V}from"./handlers/daemon.js";import{fileRoutes as W}from"./handlers/files.js";import{focusRoutes as X}from"./handlers/focus.js";import{healthRoutes as q}from"./handlers/health.js";import{humanRequestRoutes as ee}from"./handlers/human-requests.js";import{inboxRoutes as re}from"./handlers/inbox.js";import{feedbackCommentRoutes as te}from"./handlers/feedback-comments.js";import{memoryRoutes as oe}from"./handlers/memory.js";import{brokerMailRoutes as P,messageRoutes as ne}from"./handlers/messages.js";import{modelAuthRoutes as ie}from"./handlers/modelauth.js";import{modelConfigRoutes as se}from"./handlers/model-config.js";import{nodeRoutes as le}from"./handlers/nodes.js";import{nodeOutcomeRoutes as ce}from"./handlers/node-outcomes.js";import{profileRoutes as ae}from"./handlers/profiles.js";import{prospectiveChatInventoryRoutes as ue}from"./handlers/prospective-chat-inventory.js";import{reportRoutes as de}from"./handlers/reports.js";import{reviewCommentRoutes as pe}from"./handlers/review-comments.js";import{reviewRoutes as me}from"./handlers/reviews.js";import{subscriptionRoutes as fe}from"./handlers/subscriptions.js";import{cronRoutes as ge}from"./handlers/crons.js";import{worktreeRoutes as he}from"./handlers/worktree.js";function Ae(e){return new U().registerAll(q(e)).registerAll(V).registerAll(le).registerAll(ce).registerAll(Y).registerAll(k).registerAll(E).registerAll(Z).registerAll(he).registerAll(P).registerAll(ne).registerAll(de).registerAll(M).registerAll(K).registerAll(W).registerAll(oe).registerAll(X).registerAll(fe).registerAll(ge).registerAll(ae).registerAll(ie).registerAll(se).registerAll(me).registerAll(pe).registerAll(re).registerAll(ee).registerAll(Q).registerAll(ue).registerAll(te)}i(Ae,"buildRouter");function ye(e,t){if(e.toUpperCase()!=="GET")return null;const s=t.split("/").filter(m=>m.length>0);if(s.length!==4||s[0]!=="v1"||s[1]!=="nodes"||s[3]!=="attach")return null;const a=s[2];return a===void 0||a===""?null:decodeURIComponent(a)}i(ye,"attachNodeId");function Re(e){const t=e.trim();if(t==="")return null;const s=t.lastIndexOf(":");if(s<0)return null;const a=t.slice(0,s),m=t.slice(s+1),c=Number(m);return!Number.isInteger(c)||c<0||c>65535?null:{host:a===""?"0.0.0.0":a,port:c}}i(Re,"parseTcp");function ve(e,t){return`${e.includes(":")?`[${e}]`:e}:${t}`}i(ve,"formatTcpAddress");const H={error:{code:"unauthorized",message:"missing or invalid bearer token"}};function N(e,t){return e.headers.authorization===`Bearer ${t}`}i(N,"isAuthorized");function Te(e){const t=JSON.stringify(H);e.writeHead(401,{"content-type":"application/json; charset=utf-8"}),e.end(t)}i(Te,"writeUnauthorized");function Se(e,t){e.writeHead(503,{"content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify({error:{code:"startup_blocked",message:t}}))}i(Se,"writeStartupBlocked");function be(e,t){const s=JSON.stringify({error:{code:"startup_blocked",message:t}}),a=`HTTP/1.1 503 Service Unavailable\r
2
2
  content-type: application/json; charset=utf-8\r
3
3
  content-length: ${Buffer.byteLength(s)}\r
4
4
  connection: close\r
5
5
  \r
6
- ${s}`;try{t.end(l)}catch{t.destroy()}}i(ve,"destroyUpgradeStartupBlocked");function Ae(t){const o=JSON.stringify(E),s=`HTTP/1.1 401 Unauthorized\r
6
+ ${s}`;try{e.end(a)}catch{e.destroy()}}i(be,"destroyUpgradeStartupBlocked");function we(e){const t=JSON.stringify(H),s=`HTTP/1.1 401 Unauthorized\r
7
7
  content-type: application/json; charset=utf-8\r
8
- content-length: ${Buffer.byteLength(o)}\r
8
+ content-length: ${Buffer.byteLength(t)}\r
9
9
  connection: close\r
10
10
  \r
11
- ${o}`;try{t.end(s)}catch{try{t.destroy()}catch{}}}i(Ae,"destroyUpgradeUnauthorized");function P(t){return new Promise(o=>{try{t.closeAllConnections()}catch{}t.close(()=>o())})}i(P,"closeServer");function nr(t={}){const o=fe(),s=new T().registerAll(k).registerAll(C).registerAll(O),l=i((r,e)=>{const a=$()?.startupBlock()??null;return a===null||r==="GET"&&(e==="/healthz"||e==="/v1/status")||r==="POST"&&(e==="/v1/daemon/migrate"||e==="/v1/daemon/admit")||a.phase==="recovering"&&s.match(r,e)!==null?null:a},"startupBlockFor"),c=J(),u=i((r,e)=>{const a=i(n=>{g({event:"api.request.failed",level:"error",error:n instanceof Error?n:new Error(String(n))});try{e.headersSent||e.writeHead(500,{"content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify({error:{code:"internal",message:"internal error"}}))}catch{}},"internalFailure");let p;try{p=new URL(r.url??"/","http://127.0.0.1")}catch{try{e.writeHead(400,{"content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify({error:{code:"invalid_request",message:"request URL is invalid"}}))}catch{}return}try{const n=l((r.method??"GET").toUpperCase(),p.pathname);if(n!==null){Re(e,n.message);return}o.handle(r,e).catch(a)}catch(n){a(n)}},"onRequest"),y=i((r,e,a)=>{e.on("error",()=>{});try{const p=new URL(r.url??"/","http://127.0.0.1"),n=l((r.method??"GET").toUpperCase(),p.pathname);if(n!==null){ve(e,n.message);return}const m=ge(r.method??"GET",p.pathname);if(m===null){e.destroy();return}_(r,e,a,m).catch(()=>{try{e.destroy()}catch{}})}catch{try{e.destroy()}catch{}}},"onUpgrade"),B=i(r=>{r.on("error",e=>{g({event:"api.server.failed",level:"error",error:e})})},"bindErrorHandler"),f=S(u);f.on("upgrade",y);let R=!1,v,A;const H=new Promise((r,e)=>{v=r,A=e});f.once("listening",()=>{R=!0,v()}),f.on("error",r=>{g({event:"api.server.failed",level:"error",error:r}),R||A(r)});try{w(c)&&U(c)}catch{}f.listen(c,()=>{try{z(c,384)}catch(r){g({event:"api.socket.chmod_failed",level:"error",error:r instanceof Error?r:new Error(String(r))})}});let d;const h=t.tcp??x();if(h!==void 0&&h!==""){const r=he(h);if(r===null)g({event:"api.tcp.invalid",level:"error",error:new Error(`invalid CRTRD_TCP/--tcp spec: ${h}`)});else{const e=L(),a=e===void 0?u:(n,m)=>{if(!N(n,e)){ye(m);return}u(n,m)},p=e===void 0?y:(n,m,I)=>{if(!N(n,e)){Ae(m);return}y(n,m,I)};d=S(a),d.on("upgrade",p),B(d),d.listen(r.port,r.host)}}let b=!1;return{ready:H,close:i(async()=>{if(!b){b=!0,await Promise.all([P(f),d!==void 0?P(d):Promise.resolve()]);try{w(c)&&U(c)}catch{}}},"close")}}i(nr,"createApiServer");export{nr as createApiServer};
11
+ ${t}`;try{e.end(s)}catch{try{e.destroy()}catch{}}}i(we,"destroyUpgradeUnauthorized");function I(e){return new Promise(t=>{try{e.closeAllConnections()}catch{}e.close(()=>t())})}i(I,"closeServer");function ur(e={}){let t=null;const s=Ae(()=>t),a=new U().registerAll(k).registerAll(E).registerAll(P),m=i((o,r)=>{const u=F()?.startupBlock()??null;return u===null||o==="GET"&&(r==="/healthz"||r==="/v1/status")||o==="POST"&&(r==="/v1/daemon/migrate"||r==="/v1/daemon/admit")||u.phase==="recovering"&&a.match(o,r)!==null?null:u},"startupBlockFor"),c=x(),A=i((o,r)=>{const u=i(n=>{g({event:"api.request.failed",level:"error",error:n instanceof Error?n:new Error(String(n))});try{r.headersSent||r.writeHead(500,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"internal",message:"internal error"}}))}catch{}},"internalFailure");let p;try{p=new URL(o.url??"/","http://127.0.0.1")}catch{try{r.writeHead(400,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify({error:{code:"invalid_request",message:"request URL is invalid"}}))}catch{}return}try{const n=m((o.method??"GET").toUpperCase(),p.pathname);if(n!==null){Se(r,n.message);return}s.handle(o,r).catch(u)}catch(n){u(n)}},"onRequest"),y=i((o,r,u)=>{r.on("error",()=>{});try{const p=new URL(o.url??"/","http://127.0.0.1"),n=m((o.method??"GET").toUpperCase(),p.pathname);if(n!==null){be(r,n.message);return}const l=ye(o.method??"GET",p.pathname);if(l===null){r.destroy();return}D(o,r,u,l).catch(()=>{try{r.destroy()}catch{}})}catch{try{r.destroy()}catch{}}},"onUpgrade"),B=i(o=>{o.on("error",r=>{g({event:"api.server.failed",level:"error",error:r})})},"bindErrorHandler"),f=w(A);f.on("upgrade",y);let v=!1,T,S;const $=new Promise((o,r)=>{T=o,S=r});f.once("listening",()=>{v=!0,T()}),f.on("error",o=>{g({event:"api.server.failed",level:"error",error:o}),v||S(o)});try{C(c)&&O(c)}catch{}f.listen(c,()=>{try{z(c,384)}catch(o){g({event:"api.socket.chmod_failed",level:"error",error:o instanceof Error?o:new Error(String(o))})}});let d;const h=e.tcp??L()??G("user").api.tcp;if(h!==void 0&&h!==""){const o=Re(h);if(o===null)g({event:"api.tcp.invalid",level:"error",error:new Error(`invalid CRTRD_TCP/--tcp spec: ${h}`)});else{const r=e.token??_()??J("user"),u=r===void 0?A:(n,l)=>{if((n.method??"GET").toUpperCase()==="OPTIONS"){l.writeHead(204,{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Headers":"authorization, content-type","Access-Control-Allow-Methods":"GET, POST, PATCH, PUT, DELETE, OPTIONS","Access-Control-Max-Age":"600"}),l.end();return}if(!N(n,r)){Te(l);return}l.setHeader("Access-Control-Allow-Origin","*"),A(n,l)},p=r===void 0?y:(n,l,R)=>{if(!N(n,r)){we(l);return}y(n,l,R)};d=w(u),d.on("upgrade",p),B(d),d.once("listening",()=>{const n=d?.address();if(n!==null&&typeof n=="object"){const{address:l,port:R}=n;t=ve(l,R)}}),d.listen(o.port,o.host)}}let b=!1;return{ready:$,tcpAddress:i(()=>t,"tcpAddress"),close:i(async()=>{if(!b){b=!0,await Promise.all([I(f),d!==void 0?I(d):Promise.resolve()]);try{C(c)&&O(c)}catch{}}},"close")}}i(ur,"createApiServer");export{ur as createApiServer};
@@ -1 +1 @@
1
- var c=Object.defineProperty;var n=(r,e)=>c(r,"name",{value:e,configurable:!0});import"../suppress-experimental-warnings.js";import{runDaemon as i}from"./crtrd.js";import{envCrtrdTcp as o}from"../shared/env.js";function s(r){for(let e=0;e<r.length;e++){const t=r[e];if(t==="--tcp")return r[e+1];if(t!==void 0&&t.startsWith("--tcp="))return t.slice(6)}return o()}n(s,"parseTcpArg");function a(r){return r.includes("--prepare")}n(a,"parsePrepareArg");const p=process.argv.slice(2);await i({tcp:s(p),prepare:a(p)});
1
+ var i=Object.defineProperty;var n=(r,e)=>i(r,"name",{value:e,configurable:!0});import"../suppress-experimental-warnings.js";import{runDaemon as c}from"./crtrd.js";function s(r){for(let e=0;e<r.length;e++){const t=r[e];if(t==="--tcp")return r[e+1];if(t!==void 0&&t.startsWith("--tcp="))return t.slice(6)}}n(s,"parseTcpArg");function a(r){return r.includes("--prepare")}n(a,"parsePrepareArg");const p=process.argv.slice(2);await c({tcp:s(p),prepare:a(p)});
@@ -62,6 +62,7 @@ export interface SpawnDaemonResult {
62
62
  }
63
63
  /** Make one `/healthz` observation without client-side recovery. The caller owns the availability window, so this probe cannot extend it. */
64
64
  export declare function probeDaemonHealth(timeoutMs?: number): Promise<HealthDTO>;
65
+ export declare function probeErrorMessage(error: unknown): string;
65
66
  /** True only when one mandatory Unix API socket probe answers the existing ready health contract. */
66
67
  export declare function probeDaemonServing(timeoutMs?: number): Promise<void>;
67
68
  /** True only when one mandatory Unix API socket probe answers `/healthz`.
@@ -1,2 +1,2 @@
1
- var H=Object.defineProperty;var o=(e,t)=>H(e,"name",{value:t,configurable:!0});import{spawn as B,execFileSync as k}from"node:child_process";import{createRequire as K}from"node:module";import{dirname as T,join as h}from"node:path";import{fileURLToPath as G,pathToFileURL as b}from"node:url";import{existsSync as j,lstatSync as Y,mkdirSync as X,openSync as z,closeSync as V}from"node:fs";import{crtrHome as x}from"../core/canvas/paths.js";import{bindDaemonEventSource as q,eventSource as J}from"../core/events/source.js";import{hostExecPath as L}from"../core/runtime/branded-host.js";import{isDaemonRunning as Q,readPidfile as y,isPidAlive as w}from"./pidfile.js";import{DAEMON_STARTUP_WINDOW_MS as C}from"./startup-policy.js";import{exclusiveLockOwnerPid as Z}from"../core/exclusive-lock.js";import{onDiskMigrationLockPath as ee}from"../core/canvas/paths.js";import{envNoDaemonAutostart as te}from"../shared/env.js";import{CrtrClient as re,waitForDaemonAvailability as ne}from"../api/index.js";const oe=["CRTR_NODE_ID","CRTR_KIND","CRTR_MODE","CRTR_LIFECYCLE","CRTR_NODE_CWD","CRTR_CONTEXT_DIR","CRTR_CYCLES","CRTR_PROFILE_ID","CRTR_PARENT_NODE_ID","CRTR_FRONT_DOOR","TMUX","TMUX_PANE","CRTR_BROKER_ENGINE","CRTR_PI_BINARY","NODE_OPTIONS"];function $(){const e={...process.env};for(const t of oe)delete e[t];return e}o($,"sanitizedDaemonEnv");function ae(){for(let e=T(G(import.meta.url));;e=T(e)){if(j(h(e,"bin","runtime-generation-manifest.mjs")))return e;if(T(e)===e)throw new Error(`cannot locate the crouter generation root from ${import.meta.url}`)}}o(ae,"generationRoot");async function F(){const e=ae(),t=b(h(e,"bin","runtime-generation-manifest.mjs")).href,{runtimeHome:r}=await import(t);try{Y(h(r(),"selected"))}catch(s){if(s.code!=="ENOENT")throw s;return["--conditions","crtr-src","--import",b(K(import.meta.url).resolve("tsx/esm")).href,h(e,"src","daemon","crtrd-cli.ts")]}const n=b(h(e,"bin","runtime-selector.mjs")).href,{tryResolveRuntime:c}=await import(n),i=c("daemon");if(!i.ok)throw new Error(`cannot resolve the selected runtime generation's daemon entry: ${i.error}`);return[i.runtime.entryPath]}o(F,"resolveDaemonLaunch");function ie(e,t){return e.includes(` --canvas-home ${t} `)||e.endsWith(` --canvas-home ${t}`)}o(ie,"daemonCommandDeclaresCanvasHome");function se(){let e;try{e=k("ps",["-ax","-o","pid=,command="],{encoding:"utf8",timeout:2e3})}catch{return[]}const t=x(),r=[];for(const n of e.split(`
2
- `)){if(!n.includes("daemon/crtrd")||!ie(n,t))continue;const c=n.trim(),i=c.indexOf(" ");if(i<0)continue;const s=Number.parseInt(c.slice(0,i),10);Number.isInteger(s)&&s!==process.pid&&r.push(s)}return r}o(se,"findDaemonPids");function ke(e){const t=se().filter(n=>n!==e);for(const n of t)try{process.kill(n,"SIGTERM")}catch{}if(t.length===0)return t;const r=Date.now()+2e3;for(;Date.now()<r&&t.some(n=>w(n));)k("sleep",["0.1"]);for(const n of t)if(w(n))try{process.kill(n,"SIGKILL")}catch{}return t}o(ke,"sweepStrayDaemons");async function Le(){const e=process.execve;if(e===void 0)throw new Error(`this Node.js ${process.version} runtime cannot replace PID 1 with the successor daemon`);const t=L(),r=await F();return e(t,[t,...r,"--canvas-home",x()],$())}o(Le,"execDaemon");const U=2e4,O=10,N=1e3;async function M(e=N){const t=Math.min(e,N);return re.forLocalSocket({autostart:!1,timeoutMs:t}).probeHealthz(t)}o(M,"probeDaemonHealth");async function W(e=N){const t=await M(e);if(t.startup_blocked!==void 0){const r=y()??process.pid;throw new g(r,t.startup_blocked)}if(!t.ok)throw new Error("crtrd health check reported not ready")}o(W,"probeDaemonServing");async function Ce(){try{return await W(),!0}catch{return!1}}o(Ce,"isDaemonServing");function I(e){return new Promise(t=>{setTimeout(t,e)})}o(I,"sleepMs");class g extends Error{static{o(this,"DaemonStartupBlockedError")}constructor(t,r){super(`daemon ${t} is running but normal startup is blocked: ${r}`),this.name="DaemonStartupBlockedError"}}class A extends Error{static{o(this,"DaemonNotPreparedError")}constructor(t,r){super(r===void 0?`daemon ${t} is serving but did not report the prepared startup phase`:`daemon ${t} reported startup phase ${r}, not prepared`),this.name="DaemonNotPreparedError"}}async function ce(e,t=C,r={},n="ready"){const c=r.readPidfile??y,i=r.isPidAlive??w,s=r.childExited,u=r.isDaemonServing,_=r.probeDaemonServing??(async f=>{if(u!==void 0){if(await u())return;throw new Error("crtrd health check reported not ready")}await W(f)}),D=r.probeDaemonHealth??M,a=r.sleepMs??I,E=r.now??Date.now;let R=null,l=null;return await ne({windowMs:t,pollIntervalMs:O,now:E,sleep:a,retry:o(f=>f!==l,"retry"),probe:o(async f=>{const d=c();if(d===null||!i(d)){const p=s?.();throw p!=null&&(p.signal!==null?l=new Error(`daemon ${e} exited before becoming ready (by ${p.signal})`):p.code!==0&&(l=new Error(`daemon ${e} exited before becoming ready (with exit code ${p.code??"?"})`)),l!==null)?l:new Error(`daemon ${e} has no live pidfile owner`)}if(n==="ready"){try{await _(f)}catch(p){throw p instanceof g&&(l=p),p}R=d;return}const m=await D(f);if(m.startup_phase==="prepared"){R=d;return}throw m.startup_phase==="blocked"?(l=new g(d,m.startup_blocked??"on-disk migration is blocked"),l):m.startup_phase==="ready"?(l=new A(d,m.startup_phase),l):new Error(`daemon ${d} is ${m.startup_phase??"not reporting a startup phase"}; waiting for prepared`)},"probe")}),R===e?null:R}o(ce,"verifyDaemonStartup");async function ue(e,t=U,r={}){const n=r.readPidfile??y,c=r.isPidAlive??w,i=r.sleepMs??I,s=r.now??Date.now,u=s()+t;for(;s()<=u;){if(!c(e)&&n()!==e)return;await i(O)}throw new Error(`daemon ${e} did not exit and release its pidfile within ${t}ms`)}o(ue,"waitForDaemonExit");const le=2e3,pe=5e3;async function de(e,t=U,r={}){const n=r.isPidAlive??w,c=r.sleepMs??I,i=r.now??Date.now;try{process.kill(e,"SIGTERM")}catch{}try{return await ue(e,t,r),"terminated"}catch{}try{process.kill(e,"SIGKILL")}catch{}const s=i()+le;for(;i()<=s;){if(!n(e))return"killed";await c(O)}throw new Error(`daemon ${e} survived both SIGTERM and SIGKILL`)}o(de,"stopDaemonProcess");async function fe(e={}){const t=e.targetPhase??"ready",r=y();if(r!==null&&w(r))try{const a=await M();if(t==="prepared"){if(a.startup_phase==="prepared")return{started:!1,existing_pid:r,startup_phase:a.startup_phase};throw new A(r,a.startup_phase)}return a.ok?{started:!1,existing_pid:r,...a.startup_phase===void 0?{}:{startup_phase:a.startup_phase}}:{started:!1,existing_pid:r,running:!0,serving:!1,probe_error:a.startup_blocked??"crtrd health check reported not ready",...a.startup_phase===void 0?{}:{startup_phase:a.startup_phase}}}catch(a){if(a instanceof A)throw a;return{started:!1,existing_pid:r,running:!0,serving:!1,probe_error:a.message}}X(x(),{recursive:!0});const n=await F(),c=h(x(),"crtrd.err"),i=z(c,"a");J()===void 0&&q();const s=B(L(),[...n,"--canvas-home",x(),...t==="prepared"?["--prepare"]:[]],{detached:!0,stdio:["ignore",i,i],env:$()});V(i);const u=s.pid;if(u===void 0)throw new Error("daemon spawn did not return a pid");let _=null;s.once("exit",(a,E)=>{_={code:a,signal:E}}),s.unref();let D;try{D=await ce(u,C,{childExited:o(()=>_,"childExited")},t)}catch(a){const E=Z(ee())===u;if(_===null&&!E&&!(a instanceof g))try{await de(u,pe)}catch{}throw a}return D!==null?{started:!1,existing_pid:D,startup_phase:t}:{started:!0,pid:u,startup_phase:t}}o(fe,"spawnDaemon");function me(){return te()||process.argv.includes("--no-autostart")}o(me,"autostartDisabled");const he=5e3,we=6e4;let P=!1,v=0,S=0;function _e(e){return Math.min(he*2**(e-1),we)}o(_e,"autostartBackoffMs");function De(){v=0,S=0}o(De,"resetDaemonAutostart");function $e(e={}){const t=e.isDaemonRunning??Q,r=e.spawnDaemon??fe,n=e.now??Date.now;if(!me()&&!P&&!(n()<S)){if(t()){De();return}P=!0,r().then(()=>{v=0,S=0},c=>{v+=1,S=n()+_e(v)}).finally(()=>{P=!1})}}o($e,"ensureDaemon");export{oe as DAEMON_ENV_STRIP_KEYS,A as DaemonNotPreparedError,g as DaemonStartupBlockedError,me as autostartDisabled,ie as daemonCommandDeclaresCanvasHome,$e as ensureDaemon,Le as execDaemon,se as findDaemonPids,Ce as isDaemonServing,M as probeDaemonHealth,W as probeDaemonServing,De as resetDaemonAutostart,$ as sanitizedDaemonEnv,fe as spawnDaemon,de as stopDaemonProcess,ke as sweepStrayDaemons,ce as verifyDaemonStartup,ue as waitForDaemonExit};
1
+ var H=Object.defineProperty;var o=(e,t)=>H(e,"name",{value:t,configurable:!0});import{spawn as B,execFileSync as k}from"node:child_process";import{createRequire as K}from"node:module";import{dirname as T,join as h}from"node:path";import{fileURLToPath as G,pathToFileURL as b}from"node:url";import{existsSync as j,lstatSync as Y,mkdirSync as X,openSync as z,closeSync as V}from"node:fs";import{crtrHome as x}from"../core/canvas/paths.js";import{bindDaemonEventSource as q,eventSource as J}from"../core/events/source.js";import{hostExecPath as L}from"../core/runtime/branded-host.js";import{isDaemonRunning as Q,readPidfile as g,isPidAlive as w}from"./pidfile.js";import{DAEMON_STARTUP_WINDOW_MS as C}from"./startup-policy.js";import{exclusiveLockOwnerPid as Z}from"../core/exclusive-lock.js";import{onDiskMigrationLockPath as ee}from"../core/canvas/paths.js";import{envNoDaemonAutostart as te}from"../shared/env.js";import{waitForDaemonAvailability as re}from"../api/index.js";import{localClient as ne}from"../api/node-transport.js";const oe=["CRTR_NODE_ID","CRTR_KIND","CRTR_MODE","CRTR_LIFECYCLE","CRTR_NODE_CWD","CRTR_CONTEXT_DIR","CRTR_CYCLES","CRTR_PROFILE_ID","CRTR_PARENT_NODE_ID","CRTR_FRONT_DOOR","TMUX","TMUX_PANE","CRTR_BROKER_ENGINE","CRTR_PI_BINARY","NODE_OPTIONS"];function $(){const e={...process.env};for(const t of oe)delete e[t];return e}o($,"sanitizedDaemonEnv");function ae(){for(let e=T(G(import.meta.url));;e=T(e)){if(j(h(e,"bin","runtime-generation-manifest.mjs")))return e;if(T(e)===e)throw new Error(`cannot locate the crouter generation root from ${import.meta.url}`)}}o(ae,"generationRoot");async function F(){const e=ae(),t=b(h(e,"bin","runtime-generation-manifest.mjs")).href,{runtimeHome:r}=await import(t);try{Y(h(r(),"selected"))}catch(c){if(c.code!=="ENOENT")throw c;return["--conditions","crtr-src","--import",b(K(import.meta.url).resolve("tsx/esm")).href,h(e,"src","daemon","crtrd-cli.ts")]}const n=b(h(e,"bin","runtime-selector.mjs")).href,{tryResolveRuntime:i}=await import(n),s=i("daemon");if(!s.ok)throw new Error(`cannot resolve the selected runtime generation's daemon entry: ${s.error}`);return[s.runtime.entryPath]}o(F,"resolveDaemonLaunch");function ie(e,t){return e.includes(` --canvas-home ${t} `)||e.endsWith(` --canvas-home ${t}`)}o(ie,"daemonCommandDeclaresCanvasHome");function se(){let e;try{e=k("ps",["-ax","-o","pid=,command="],{encoding:"utf8",timeout:2e3})}catch{return[]}const t=x(),r=[];for(const n of e.split(`
2
+ `)){if(!n.includes("daemon/crtrd")||!ie(n,t))continue;const i=n.trim(),s=i.indexOf(" ");if(s<0)continue;const c=Number.parseInt(i.slice(0,s),10);Number.isInteger(c)&&c!==process.pid&&r.push(c)}return r}o(se,"findDaemonPids");function Ce(e){const t=se().filter(n=>n!==e);for(const n of t)try{process.kill(n,"SIGTERM")}catch{}if(t.length===0)return t;const r=Date.now()+2e3;for(;Date.now()<r&&t.some(n=>w(n));)k("sleep",["0.1"]);for(const n of t)if(w(n))try{process.kill(n,"SIGKILL")}catch{}return t}o(Ce,"sweepStrayDaemons");async function $e(){const e=process.execve;if(e===void 0)throw new Error(`this Node.js ${process.version} runtime cannot replace PID 1 with the successor daemon`);const t=L(),r=await F();return e(t,[t,...r,"--canvas-home",x()],$())}o($e,"execDaemon");const U=2e4,O=10,M=1e3;async function N(e=M){const t=Math.min(e,M);return ne({autostart:!1,timeoutMs:t}).probeHealthz(t)}o(N,"probeDaemonHealth");function ce(e){const t=e instanceof Error?e.message:String(e);if(typeof e!="object"||e===null)return t;const r=e.code,n=e.cause,i=typeof r=="string"?r:n?.code;return typeof i=="string"&&!t.includes(i)?`${t}: ${i}`:t}o(ce,"probeErrorMessage");async function W(e=M){const t=await N(e);if(t.startup_blocked!==void 0){const r=g()??process.pid;throw new R(r,t.startup_blocked)}if(!t.ok)throw new Error("crtrd health check reported not ready")}o(W,"probeDaemonServing");async function Fe(){try{return await W(),!0}catch{return!1}}o(Fe,"isDaemonServing");function I(e){return new Promise(t=>{setTimeout(t,e)})}o(I,"sleepMs");class R extends Error{static{o(this,"DaemonStartupBlockedError")}constructor(t,r){super(`daemon ${t} is running but normal startup is blocked: ${r}`),this.name="DaemonStartupBlockedError"}}class A extends Error{static{o(this,"DaemonNotPreparedError")}constructor(t,r){super(r===void 0?`daemon ${t} is serving but did not report the prepared startup phase`:`daemon ${t} reported startup phase ${r}, not prepared`),this.name="DaemonNotPreparedError"}}async function ue(e,t=C,r={},n="ready"){const i=r.readPidfile??g,s=r.isPidAlive??w,c=r.childExited,u=r.isDaemonServing,_=r.probeDaemonServing??(async f=>{if(u!==void 0){if(await u())return;throw new Error("crtrd health check reported not ready")}await W(f)}),E=r.probeDaemonHealth??N,a=r.sleepMs??I,D=r.now??Date.now;let y=null,l=null;return await re({windowMs:t,pollIntervalMs:O,now:D,sleep:a,retry:o(f=>f!==l,"retry"),probe:o(async f=>{const d=i();if(d===null||!s(d)){const p=c?.();throw p!=null&&(p.signal!==null?l=new Error(`daemon ${e} exited before becoming ready (by ${p.signal})`):p.code!==0&&(l=new Error(`daemon ${e} exited before becoming ready (with exit code ${p.code??"?"})`)),l!==null)?l:new Error(`daemon ${e} has no live pidfile owner`)}if(n==="ready"){try{await _(f)}catch(p){throw p instanceof R&&(l=p),p}y=d;return}const m=await E(f);if(m.startup_phase==="prepared"){y=d;return}throw m.startup_phase==="blocked"?(l=new R(d,m.startup_blocked??"on-disk migration is blocked"),l):m.startup_phase==="ready"?(l=new A(d,m.startup_phase),l):new Error(`daemon ${d} is ${m.startup_phase??"not reporting a startup phase"}; waiting for prepared`)},"probe")}),y===e?null:y}o(ue,"verifyDaemonStartup");async function le(e,t=U,r={}){const n=r.readPidfile??g,i=r.isPidAlive??w,s=r.sleepMs??I,c=r.now??Date.now,u=c()+t;for(;c()<=u;){if(!i(e)&&n()!==e)return;await s(O)}throw new Error(`daemon ${e} did not exit and release its pidfile within ${t}ms`)}o(le,"waitForDaemonExit");const pe=2e3,de=5e3;async function fe(e,t=U,r={}){const n=r.isPidAlive??w,i=r.sleepMs??I,s=r.now??Date.now;try{process.kill(e,"SIGTERM")}catch{}try{return await le(e,t,r),"terminated"}catch{}try{process.kill(e,"SIGKILL")}catch{}const c=s()+pe;for(;s()<=c;){if(!n(e))return"killed";await i(O)}throw new Error(`daemon ${e} survived both SIGTERM and SIGKILL`)}o(fe,"stopDaemonProcess");async function me(e={}){const t=e.targetPhase??"ready",r=g();if(r!==null&&w(r))try{const a=await N();if(t==="prepared"){if(a.startup_phase==="prepared")return{started:!1,existing_pid:r,startup_phase:a.startup_phase};throw new A(r,a.startup_phase)}return a.ok?{started:!1,existing_pid:r,...a.startup_phase===void 0?{}:{startup_phase:a.startup_phase}}:{started:!1,existing_pid:r,running:!0,serving:!1,probe_error:a.startup_blocked??"crtrd health check reported not ready",...a.startup_phase===void 0?{}:{startup_phase:a.startup_phase}}}catch(a){if(a instanceof A)throw a;return{started:!1,existing_pid:r,running:!0,serving:!1,probe_error:ce(a)}}X(x(),{recursive:!0});const n=await F(),i=h(x(),"crtrd.err"),s=z(i,"a");J()===void 0&&q();const c=B(L(),[...n,"--canvas-home",x(),...t==="prepared"?["--prepare"]:[]],{detached:!0,stdio:["ignore",s,s],env:$()});V(s);const u=c.pid;if(u===void 0)throw new Error("daemon spawn did not return a pid");let _=null;c.once("exit",(a,D)=>{_={code:a,signal:D}}),c.unref();let E;try{E=await ue(u,C,{childExited:o(()=>_,"childExited")},t)}catch(a){const D=Z(ee())===u;if(_===null&&!D&&!(a instanceof R))try{await fe(u,de)}catch{}throw a}return E!==null?{started:!1,existing_pid:E,startup_phase:t}:{started:!0,pid:u,startup_phase:t}}o(me,"spawnDaemon");function he(){return te()||process.argv.includes("--no-autostart")}o(he,"autostartDisabled");const we=5e3,_e=6e4;let P=!1,v=0,S=0;function Ee(e){return Math.min(we*2**(e-1),_e)}o(Ee,"autostartBackoffMs");function De(){v=0,S=0}o(De,"resetDaemonAutostart");function Ue(e={}){const t=e.isDaemonRunning??Q,r=e.spawnDaemon??me,n=e.now??Date.now;if(!he()&&!P&&!(n()<S)){if(t()){De();return}P=!0,r().then(()=>{v=0,S=0},i=>{v+=1,S=n()+Ee(v)}).finally(()=>{P=!1})}}o(Ue,"ensureDaemon");export{oe as DAEMON_ENV_STRIP_KEYS,A as DaemonNotPreparedError,R as DaemonStartupBlockedError,he as autostartDisabled,ie as daemonCommandDeclaresCanvasHome,Ue as ensureDaemon,$e as execDaemon,se as findDaemonPids,Fe as isDaemonServing,N as probeDaemonHealth,W as probeDaemonServing,ce as probeErrorMessage,De as resetDaemonAutostart,$ as sanitizedDaemonEnv,me as spawnDaemon,fe as stopDaemonProcess,Ce as sweepStrayDaemons,ue as verifyDaemonStartup,le as waitForDaemonExit};
package/dist/types.d.ts CHANGED
@@ -354,6 +354,8 @@ export interface ScopeConfig {
354
354
  * via `crtr canvas use`. Unset
355
355
  * via `sys config set` — not in `TOP_LEVEL_KEYS`. */
356
356
  remoteCanvas: RemoteCanvasConfig;
357
+ /** The user-owned TCP listener address. Empty leaves the TCP listener off. */
358
+ api: ApiConfig;
357
359
  /** Extra env-var names/globs a scope explicitly admits across the broker
358
360
  * spawn-env boundary (`buildBrokerEnv`, `core/runtime/spawn-env.ts`) — the
359
361
  * "I accept this crosses as a shell-visible credential" escape hatch for a
@@ -394,6 +396,11 @@ export interface RemoteCanvasTarget {
394
396
  export interface RemoteCanvasConfig {
395
397
  targets: Record<string, RemoteCanvasTarget>;
396
398
  }
399
+ /** User-owned daemon TCP listener settings. The bearer token lives separately
400
+ * in the 0600 secrets store. */
401
+ export interface ApiConfig {
402
+ tcp: string;
403
+ }
397
404
  export interface ScopeState {
398
405
  marketplaces: Record<string, {
399
406
  last_updated?: string;