@coool/opencode-adaptive 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +2 -1
  2. package/dist/adapters/opencode/index.d.ts +2 -0
  3. package/dist/adapters/opencode/index.js +2 -0
  4. package/dist/adapters/opencode/index.js.map +1 -1
  5. package/dist/adapters/opencode/process-ledger.d.ts +56 -0
  6. package/dist/adapters/opencode/process-ledger.js +143 -0
  7. package/dist/adapters/opencode/process-ledger.js.map +1 -0
  8. package/dist/adapters/opencode/session-ops.d.ts +2 -0
  9. package/dist/adapters/opencode/session-ops.js +15 -9
  10. package/dist/adapters/opencode/session-ops.js.map +1 -1
  11. package/dist/adapters/opencode/timer.d.ts +5 -0
  12. package/dist/adapters/opencode/timer.js +12 -0
  13. package/dist/adapters/opencode/timer.js.map +1 -0
  14. package/dist/execution/parking/index.d.ts +2 -1
  15. package/dist/execution/parking/index.js +2 -1
  16. package/dist/execution/parking/index.js.map +1 -1
  17. package/dist/execution/parking/wake.d.ts +52 -0
  18. package/dist/execution/parking/wake.js +95 -0
  19. package/dist/execution/parking/wake.js.map +1 -0
  20. package/dist/gui-experimental.cjs +3 -3
  21. package/dist/gui.d.ts +2 -2
  22. package/dist/gui.js +37 -3
  23. package/dist/gui.js.map +1 -1
  24. package/dist/index.js +33 -20
  25. package/dist/index.js.map +1 -1
  26. package/dist/rpc/status.d.ts +9 -0
  27. package/dist/rpc/status.js +1 -0
  28. package/dist/rpc/status.js.map +1 -1
  29. package/dist/state/parked-work/store.d.ts +5 -0
  30. package/dist/state/parked-work/store.js +24 -0
  31. package/dist/state/parked-work/store.js.map +1 -1
  32. package/dist/supervisor/supervisor.d.ts +2 -0
  33. package/dist/supervisor/supervisor.js +475 -106
  34. package/dist/supervisor/supervisor.js.map +1 -1
  35. package/dist/supervisor/types.d.ts +5 -0
  36. package/dist/supervisor/types.js.map +1 -1
  37. package/dist/tui.d.ts +13 -0
  38. package/dist/tui.js +62 -16
  39. package/dist/tui.js.map +1 -1
  40. package/dist/ui/flow/waiting.d.ts +11 -0
  41. package/dist/ui/flow/waiting.js +40 -0
  42. package/dist/ui/flow/waiting.js.map +1 -0
  43. package/dist/ui/gui/api.d.ts +2 -0
  44. package/dist/ui/gui/api.js +6 -2
  45. package/dist/ui/gui/api.js.map +1 -1
  46. package/package.json +1 -1
@@ -1,3 +1,3 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const a=require("solid-js/web"),O=require("@opencode/plugin/gui"),m=require("solid-js"),X=require("@opencode/schema/rpc"),Z=128,K="coool.adaptive",N="coool.adaptive",S={internal:M()};function l(e,t){return{"~standard":{version:1,vendor:N,validate:i=>e(i)?{value:i}:{issues:[{message:t}]}}}}function M(){return{"~standard":{version:1,vendor:N,validate:e=>e===void 0?{value:void 0}:{issues:[{message:"unexpected input: no input expected"}]}}}}const v=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),T=e=>typeof e=="string",D=e=>{if(!(v(e)&&T(e.sessionID)))return!1;const t=e.sessionID;return t.length>0&&t.length<=Z},Q=e=>v(e)&&T(e.sessionID)&&(e.mode===null||T(e.mode)),ee=e=>v(e)&&T(e.sessionID)&&(e.tier===null||T(e.tier)),x=X.Rpc.define({id:K,methods:{"settings.get":{errors:S,input:M(),output:l(v,"output must be an object")},"settings.set":{errors:S,input:l(v,"input must be an object"),output:l(v,"output must be an object")},"status.get":{errors:S,input:M(),output:l(v,"output must be an object")},"receipts.list":{errors:S,input:M(),output:l(Array.isArray,"output must be an array")},"session-mode.get":{errors:S,input:l(D,"input must be an object with a sessionID string"),output:l(v,"output must be an object")},"session-mode.set":{errors:S,input:l(Q,"input must be an object with a sessionID string and a mode string or null"),output:l(v,"output must be an object")},"tier-request.get":{errors:S,input:l(D,"input must be an object with a sessionID string"),output:l(v,"output must be an object")},"tier-request.set":{errors:S,input:l(ee,"input must be an object with a sessionID string and a tier string or null"),output:l(v,"output must be an object")}},events:{"settings-changed":{schema:l(v,"event data must be an object")},"session-mode-changed":{schema:l(D,"event data must be an object with a sessionID string")},"tier-request-changed":{schema:l(D,"event data must be an object with a sessionID string")}}}),E=["off","observe","active"],w=["quick","standard","advanced"];function te(e,t){const i=e.rpc(x);return{status:()=>i["status.get"](void 0,t()).catch(()=>{}),receipts:()=>i["receipts.list"](void 0,t()).catch(()=>{}),update:async n=>{try{return(await i["settings.set"](n,t())).ok}catch{return!1}},watchSettings:n=>i.events.on("settings-changed",()=>{n()})}}function f(e){const t=`${e.providerID}/${e.id}`;return e.variant===void 0?t:`${t}@${e.variant}`}function L(e){if(e===void 0)return"";const t=[];return e.health!=="healthy"&&t.push(e.health),e.breaker==="open"?t.push("breaker-open"):e.breaker==="half_open"&&t.push("half-open"),e.windowed&&t.push("windowed"),t.length===0?"":` [${t.join(", ")}]`}function ie(e,t,i){const n=[{title:"Add model…",value:{kind:"add"},description:"Search OpenCode models"}],o=i==null?void 0:i.tiers[t];return e.tiers[t].forEach((r,s)=>{n.push({title:`${f(r)}${L(o==null?void 0:o[s])}`,value:{kind:"resource",index:s,ref:r},description:"Move or remove"})}),n}function ne(e){return w.map(t=>{const i=e.tiers[t],n=i.length===0?"(empty — add models to enable routing)":i.map(o=>f(o.ref)).join(" · ");return{title:`${t} (${String(i.length)})`,value:t,description:n}})}function oe(e){return e.map(t=>({title:`${t.providerID}/${t.modelID}`,description:t.name,value:t}))}function re(e){const t=[{title:"(model default)",value:{variant:void 0}}];for(const i of e)t.push({title:`@${i}`,value:{variant:i}});return t}function se(e,t){return[...e.filter((i,n)=>n===t),...e.filter((i,n)=>n!==t)]}function ae(e,t,i){const n=[...e],o=t+i,r=n[t],s=n[o];return r===void 0||s===void 0||(n[t]=s,n[o]=r),n}function ce(e,t){return e.filter((i,n)=>n!==t)}function ue(e){const t=[];for(const i of w)for(const n of e.tiers[i])t.push({tier:i,status:n});return t}function j(e){const t=new Date(e),i=String(t.getHours()).padStart(2,"0"),n=String(t.getMinutes()).padStart(2,"0"),o=String(t.getMonth()+1).padStart(2,"0"),r=String(t.getDate()).padStart(2,"0");return`${String(t.getFullYear())}-${o}-${r} ${i}:${n}`}function de(e,t){return t===null?!1:t.providerID===e.providerID&&t.id===e.id&&t.variant===e.variant}function le(e,t){let i="";return e.windowed&&(i+=e.windowUntil===null?", windowed (deadline unknown)":`, windowed until ${j(e.windowUntil)}`),e.usedPercent!==null&&(i+=`, used ${String(e.usedPercent)}%`,e.resetAt!==null&&(i+=`, resets ${j(e.resetAt)}`)),e.breaker==="open"?i+=", breaker open":e.breaker==="half_open"&&(i+=", probing"),de(e.ref,t)&&(i+=" (pinned)"),i}function pe(e,t){return le(e,t).replace(/^[, ]+/,"")||"-"}function ve(e){const t=["MODEL","TIER","HEALTH","STATE"],i=ue(e).map(({tier:r,status:s})=>[f(s.ref),r,s.health,pe(s,e.pin)]),n=t.map((r,s)=>Math.max(r.length,...i.map(c=>c[s].length))),o=r=>r.map((s,c)=>s.padEnd(n[c])).join(" ").trimEnd();return[o(t),...i.map(o)]}const fe="No models configured yet — open Models to add some.",ge=["status","models","mode","tier","pin","receipts"];function he(e){if(e===void 0)return"how every configured model is doing";const t=w.reduce((o,r)=>o+e.tiers[r].length,0);if(t===0)return"no models configured yet";const i=w.reduce((o,r)=>o+e.tiers[r].filter(s=>s.windowed).length,0),n=i===0?"":` · ${String(i)} windowed`;return`${String(t)} resource${t===1?"":"s"}${n}`}function me(e){return w.map(i=>`${i}: ${String(e.tiers[i].length)}`).join(" · ")}function we(e){return e===void 0?"pin a tier for the current session":e===null?"not pinned — the routing ladder decides":`pinned to ${e} — steers this session`}function be(e,t){if(e===t.override)return"override";if(e===t.effective)return"current"}function $e(e,t,i,n=e.mode){const o=r=>{switch(r){case"status":return{title:"Status",value:r,description:he(t)};case"models":return{title:"Models",value:r,description:me(e)};case"mode":return{title:"Mode",value:r,description:`currently ${n}`};case"tier":return{title:"Tier",value:r,description:we(i)};case"pin":return{title:"Pin",value:r,description:e.pin===null?"force one resource":`pinned to ${f(e.pin)}`};case"receipts":return{title:"Receipts",value:r,description:"why each turn ran the model it ran"}}};return ge.map(o)}function Se(e){return E.map(t=>({title:t===e?`${t} (current)`:t,value:t,description:V(t)}))}function V(e){switch(e){case"off":return"Adaptive does nothing";case"observe":return"Record decisions without applying them";case"active":return"Apply routing and recovery decisions"}}function ye(e,t){return[{title:"Global mode…",description:`applies to every session without an override (now ${e})`,value:"global"},{title:"This session…",description:`override for this session only (now ${t})`,value:"session"}]}function ke(e){const t=[{title:`Follow global (${e.global})`,description:"clear this session's override",value:{kind:"global"}}];for(const i of E){const n=be(i,e);t.push({title:n===void 0?i:`${i} (${n})`,value:{kind:"mode",mode:i},description:V(i)})}return t}function Ie(e){switch(e){case"quick":return"routine edits, mechanical steps";case"standard":return"the default";case"advanced":return"multi-step design, broad refactors"}}function Te(e){const t=[{title:"Follow routing ladder",description:"clear this session's tier request",value:{kind:"ladder"}}];for(const i of w)t.push({title:i===e.pinned?`${i} (pinned)`:i,value:{kind:"tier",tier:i},description:Ie(i)});return t}function F(e){if(e instanceof Error)return e.message;if(typeof e=="object"&&e!==null){const t=e.message;return typeof t=="string"&&t.length>0?t:JSON.stringify(e,(i,n)=>typeof n=="bigint"?String(n):n)}return String(e)}function C(e){switch(e.kind){case"apply":{const t=e.tier===void 0?"":` [${e.tier}]`;return`apply ${f(e.model)}${t} (${e.reason})`}case"defer":{const t=e.detail===void 0?"":` — ${e.detail}`;return`defer: ${e.reason}${t}`}case"park":{const t=e.detail===void 0?"":` — ${e.detail}`;return`park: ${e.reason}${t}`}case"escalate":return`escalate ${e.step} → ${f(e.target)} [${e.tier}]`;case"observe-only":return`observe: ${C(e.would)}`}}function G(e){return e===void 0?"pending":e.status==="succeeded"?"succeeded":e.status==="failed"?`failed (${e.category})`:`interrupted (${e.reason})`}function H(e){return`#${String(e.id)} ${C(e.decision)} — ${G(e.outcome)}`}function Ae(e,t=Date.now()){return[`id: ${String(e.id)}`,`when: ${De(e.at,t)} (${new Date(e.at).toISOString()})`,`session: ${e.sessionID}`,`unit: ${e.unitID??"-"}`,`tier: ${e.tier??"-"}`,`decision: ${C(e.decision)}`,`outcome: ${G(e.outcome)}`,"",`What happened: ${Me(e)}`].join(`
2
- `)}function De(e,t){const i=Math.max(0,Math.floor((t-e)/1e3));if(i<60)return"just now";const n=Math.floor(i/60);if(n<60)return`${String(n)}m ago`;const o=Math.floor(n/60);return o<24?`${String(o)}h ago`:`${String(Math.floor(o/24))}d ago`}function Me(e){const t=e.decision;switch(t.kind){case"apply":return _e[t.reason];case"defer":return Ee[t.reason];case"park":return Re[t.reason];case"escalate":return`This turn needed stronger capability, so Adaptive moved it to ${f(t.target)}.`;case"observe-only":return"Adaptive is observing: this is what it would have done, without applying it."}}const _e={pin:"Adaptive routed to the resource you pinned.",preferred:"Adaptive chose this resource: it is the preferred resource for the tier and was available.",fallback:"The preferred resource was unavailable, so Adaptive moved the turn to the next resource in the tier.",probe:"Adaptive sent this turn as a recovery probe after an outage, to check whether the resource works again.",draining:"Adaptive routed to this resource while its provider was draining capacity."},Ee={"routing-defer":"No routing rule applied to this turn, so Adaptive left the choice to OpenCode.","adaptive-off":"Adaptive is switched off, so every turn runs on OpenCode's own choice.","pin-unverifiable":"The pinned resource could not be verified as available, so Adaptive left the choice to OpenCode.","pin-incompatible":"The pinned resource does not fit this turn's requirements, so Adaptive left the choice to OpenCode.","escalation-not-justified":"No structured capability evidence justified escalation, so Adaptive kept the current capability.","escalation-budget-exhausted":"This unit has used its escalation budget; no further escalation is allowed.","escalation-ceiling":"Adaptive is already at the highest capability tier; no stronger tier is configured.","escalation-no-candidate":"No stronger resource was eligible to escalate to (unavailable, blocked, or not configured)."},Re={"all-blocked":"Every resource in the tier is currently unavailable, so the turn ran without Adaptive routing.","no-candidates":"There are no models configured for this tier, so the turn ran without Adaptive routing.","no-compatible-resource":"No configured resource fits this turn's requirements, so the turn ran without Adaptive routing.","pin-blocked":"The pinned resource is unavailable and no fallback is allowed while pinned."},_=20;function Oe(e){return e.slice(0,_)}const P="OpenCode Adaptive",Ce="Adaptive starts in Observe mode: routing decisions are recorded as receipts, but no model is switched. Open /adaptive to configure tiers or switch to Active.";async function b(e,t){try{return await t()}catch(i){e.view.toast(F(i),"error");return}}async function y(e,t,i){const n=await b(e,()=>e.api.setSettings(t));if(n!==void 0){if(!n.ok){await e.view.alert({title:"Invalid settings",message:n.errors.join("; ")});return}return i.once||(e.view.toast("Settings saved","success"),i.once=!0),n.settings}}async function q(e,t,i,n){const o=await b(e,()=>e.api.setSessionMode(t,i));if(o!==void 0){if(!o.ok){await e.view.alert({title:"Invalid settings",message:o.errors.join("; ")});return}return n.once||(e.view.toast("Settings saved","success"),n.once=!0),o}}async function je(e,t,i,n){const o=await b(e,()=>e.api.setTierRequest(t,i));if(o!==void 0){if(!o.ok){await e.view.alert({title:"Invalid tier request",message:o.errors.join("; ")});return}return n.once||(e.view.toast("Settings saved","success"),n.once=!0),o}}async function Pe(e){const t=await b(e,()=>e.api.settings());if(t===void 0)return;let i=t;const n={once:!1},o=e.sessionID;let r=o===void 0?null:await b(e,()=>e.api.sessionMode(o))??null;if(!i.inviteDismissed){await e.view.confirm({title:P,message:Ce});const s=await e.view.select({title:P,options:[{title:"Open the menu now",value:!0},{title:"Later",value:!1}]}),c=await y(e,{inviteDismissed:!0},n);if(c!==void 0&&(i=c),s!==!0)return}for(;;){const[s,c]=await Promise.all([e.api.settings().catch(()=>{}),e.api.status().catch(()=>{})]);s!==void 0&&(i=s);const p=o===void 0?void 0:await e.api.tierRequest(o).catch(()=>{}),d=await e.view.select({title:`Adaptive (${r??i.mode})`,options:$e(i,c,p,r??i.mode)});if(d===void 0)return;if(d==="status"){await xe(e);continue}if(d==="models"){i=await Le(e,n)??i;continue}if(d==="mode"){const g=await qe(e,i,n,o,r);g!==void 0&&(i=g.settings,r=g.override);continue}if(d==="tier"){if(o===void 0){await e.view.alert({title:"This session",message:"This session's tier request can only be changed inside a session. Open a session and run /adaptive from there."});continue}await Ne(e,o,n);continue}if(d==="pin"){i=await Ue(e,i,n)??i;continue}await We(e)}}async function qe(e,t,i,n,o){for(;;){const r=await e.view.select({title:"Operating mode",options:ye(t.mode,o??t.mode)});if(r===void 0)return;if(r==="global"){const p=await e.view.select({title:"Global mode",options:Se(t.mode)});if(p===void 0||p===t.mode)return;const d=await y(e,{mode:p},i);return d===void 0?void 0:{settings:d,override:o}}if(n===void 0){await e.view.alert({title:"This session",message:"This session's mode can only be changed inside a session. Open a session and run /adaptive from there."});continue}const s=await e.view.select({title:"This session's mode",options:ke({global:t.mode,override:o,effective:o??t.mode})});return s===void 0?void 0:s.kind==="global"?o===null||await q(e,n,null,i)===void 0?void 0:{settings:t,override:null}:s.mode===(o??t.mode)||await q(e,n,s.mode,i)===void 0?void 0:{settings:t,override:s.mode}}}async function Ne(e,t,i){const n=await b(e,()=>e.api.tierRequest(t));if(n===void 0)return;const o=await e.view.select({title:"This session's tier",options:Te({pinned:n})});if(o===void 0)return;const r=o.kind==="ladder"?null:o.tier;r!==n&&await je(e,t,r,i)}async function xe(e){const t=await b(e,()=>e.api.status());if(t===void 0)return;if(!w.some(n=>t.tiers[n].length>0)){await e.view.alert({title:"Status",message:fe});return}await e.view.table({title:"Status",lines:ve(t)})}async function Le(e,t){let i;for(;;){const n=await b(e,()=>e.api.settings());if(n===void 0)return i;const o=await b(e,()=>e.api.status());if(o===void 0)return i;const r=await e.view.select({title:"Tier",options:ne(o)});if(r===void 0)return i;let s=o,c=n;for(;;){const p=await e.view.select({title:`Tier: ${r}`,options:ie(c,r,s)});if(p===void 0)break;if(p.kind==="add"){const u=await Ve(e,c,r,t);u!==void 0&&(i=u,c=u)}else{const u=await Ge(e,c,r,p,t);u!==void 0&&(i=u,c=u)}const[d,g]=await Promise.all([e.api.settings().catch(()=>{}),e.api.status().catch(()=>{})]);d!==void 0&&(i=d,c=d),g!==void 0&&(s=g)}}}async function Ve(e,t,i,n){const o=e.models(),r=await e.view.select({title:"Add model",placeholder:"Search provider / model / name",options:oe(o)});if(r===void 0)return;const s=r.variants;let c;if(s.length>0){const g=await e.view.select({title:`Variant: ${r.providerID}/${r.modelID}`,options:re(s)});if(g===void 0)return;c=g.variant}const p=c===void 0?{providerID:r.providerID,id:r.modelID}:{providerID:r.providerID,id:r.modelID,variant:c},d=[...t.tiers[i],p];return y(e,{tiers:{...t.tiers,[i]:d}},n)}function Fe(){return[{title:"Move to top",value:"top",description:"Make this the primary resource"},{title:"Move up",value:"up",description:"Higher priority within the tier"},{title:"Move down",value:"down",description:"Lower priority within the tier"},{title:"Remove",value:"remove"}]}async function Ge(e,t,i,{index:n,ref:o},r){const s=await e.view.select({title:`Resource: ${f(o)}`,options:Fe()});if(s===void 0)return;if(s==="remove")return await e.view.confirm({title:"Remove resource",message:`Remove ${f(o)} from ${i}?`})!==!0?void 0:y(e,{tiers:{...t.tiers,[i]:ce(t.tiers[i],n)}},r);const c=s==="top"?se(t.tiers[i],n):ae(t.tiers[i],n,s==="up"?-1:1);return y(e,{tiers:{...t.tiers,[i]:c}},r)}function He(e){const t=[{title:"No pin",value:{kind:"clear"},description:"clear the pin"}];for(const i of w)for(const n of e.tiers[i])t.push({title:`${f(n)} (${i})`,description:"route every turn to this resource, even in active mode",value:{kind:"pin",ref:n}});return t}async function Ue(e,t,i){if(!w.some(r=>t.tiers[r].length>0)){await e.view.alert({title:"Pin",message:"(no configured resources to pin)"});return}const o=await e.view.select({title:"Pin",options:He(t)});if(o!==void 0)return y(e,{pin:o.kind==="clear"?null:o.ref},i)}async function We(e){const t=await b(e,()=>e.api.receipts());if(t!==void 0){if(t.length===0){await e.view.alert({title:"Receipts",message:"No decisions recorded yet."});return}for(let i=0;;){const n=Oe(t.slice(i)),o=i+_<t.length,r=await e.view.selectPaged({title:`Receipts (${String(t.length)}, newest first)`,options:n.map(c=>({title:H(c),value:c})),actions:[...i>0?[{title:"Previous page",value:"prev",bind:"ctrl+b"}]:[],...o?[{title:"Next page",value:"next",bind:"ctrl+f"}]:[]]});if(r.action==="next"){i+=_;continue}if(r.action==="prev"){i=Math.max(0,i-_);continue}const s=r.picked;if(s===void 0)return;await e.view.alert({title:`Receipt #${String(s.id)}`,message:Ae(s)})}}}var ze=a.template("<pre class=adaptive-table>");function Je(e){return m.onCleanup(e.done),a.createComponent(e.host.ui.Dialog,{get title(){return e.title},size:"x-large",get footer(){return a.createComponent(e.host.ui.Button,{get onClick(){return e.close},children:"Close"})},get children(){var t=ze();return a.insert(t,()=>e.lines.join(`
3
- `)),t}})}function Ye(e){return{select:({title:t,placeholder:i,options:n})=>e.dialogs.select({title:t,options:n,...i!==void 0?{placeholder:i}:{}}),selectPaged:async t=>{const i=await e.dialogs.select({title:t.title,options:[...t.options.map(n=>({title:n.title,value:{kind:"pick",value:n.value}})),...t.actions.map(n=>({title:n.title,value:{kind:"action",value:n.value}}))]});return i===void 0?{picked:void 0,action:void 0}:i.kind==="pick"?{picked:i.value,action:void 0}:{picked:void 0,action:i.value}},confirm:({title:t,message:i})=>e.dialogs.confirm({title:t,description:i}),alert:({title:t,message:i})=>e.dialogs.alert({title:t,description:i}),table:({title:t,lines:i})=>new Promise(n=>{e.dialogs.open(o=>a.createComponent(Je,{host:e,title:t,lines:i,close:()=>{o.close()},done:n}))}),toast:(t,i)=>{e.toast.show({title:t,variant:i})}}}function U(e){const t=e.sessions.current();return(t==null?void 0:t.server.builtin)===!0?t:void 0}function W(e){var i,n;const t=(n=(i=U(e))==null?void 0:i.location)==null?void 0:n.directory;return t===void 0?{}:{location:{directory:t}}}function Be(e){const t=e.client.rpc(x),i=()=>W(e);return{settings:()=>t["settings.get"](void 0,i()),setSettings:n=>t["settings.set"](n,i()),status:()=>t["status.get"](void 0,i()),receipts:()=>t["receipts.list"](void 0,i()),sessionMode:n=>t["session-mode.get"]({sessionID:n},i()).then(o=>o.mode),setSessionMode:(n,o)=>t["session-mode.set"]({sessionID:n,mode:o},i()),tierRequest:n=>t["tier-request.get"]({sessionID:n},i()).then(o=>o.tier),setTierRequest:(n,o)=>t["tier-request.set"]({sessionID:n,tier:o},i())}}function Xe(e){const t=e.servers.list().map(n=>e.servers.get(n)).find(n=>(n==null?void 0:n.builtin)===!0);return t===void 0?void 0:t.data.location.model}async function Ze(e){const t=Ye(e),i=U(e),n=Xe(e);await(async()=>{await(n==null?void 0:n.sync(i==null?void 0:i.location).catch(()=>{})),await Pe({api:Be(e),view:t,models:()=>((n==null?void 0:n.list(i==null?void 0:i.location))??[]).map(r=>({providerID:r.providerID,modelID:r.modelID,name:r.name,variants:r.variants.map(s=>s.id)})),...i!==void 0?{sessionID:i.id}:{}})})().catch(r=>{t.toast(F(r),"error")})}const Ke="connection";function Qe(e){if(e===void 0)return[{id:Ke,label:"Server",value:"disconnected",tone:"warning"}];const t=[{id:"mode",label:"Mode",value:e.mode},{id:"pin",label:"Pin",value:e.pin===null?"none":f(e.pin)}];for(const i of w){const n=e.tiers[i],o=n.length===0?"empty":n.map(r=>`${f(r.ref)}${L(r)}`).join(", ");t.push({id:`tier-${i}`,label:`Tier ${i}`,value:o})}return t.push({id:"parked",label:"Parked",value:String(e.parked)}),t.push({id:"receipts",label:"Receipts",value:String(e.receipts)}),t}function et(e){return E.map(t=>({value:t,title:t===e?`${t} (current)`:t}))}function tt(e){return E.find(t=>t===e)}function it(e){return e.map(t=>H(t))}var nt=a.template("<span class=adaptive-saved role=status>Saved"),ot=a.template("<div class=adaptive-receipts>"),rt=a.template('<div class=adaptive-page><table class=adaptive-status aria-label="OpenCode Adaptive status"><tbody></tbody></table><label class=adaptive-mode><span class=adaptive-row-label>Mode</span><select class=adaptive-mode-select>'),st=a.template("<tr class=adaptive-row><td class=adaptive-row-label></td><td class=adaptive-row-value>"),at=a.template("<option>"),ct=a.template("<div class=adaptive-receipt-line>");const ut=5;function z(e){const[t,i]=m.createSignal(void 0),[n,o]=m.createSignal("observe"),[r,s]=m.createSignal([]),[c,p]=m.createSignal(!1),d=()=>{e.api.status().then(u=>{i(u),u!==void 0&&o(u.mode)}),e.api.receipts().then(u=>{s(u??[])})};m.onCleanup(e.api.watchSettings(d)),d();const g=u=>{const k=tt(u.value);u.value=n(),k!==void 0&&e.api.update({mode:k}).then(R=>{p(R),d()})};return(()=>{var u=rt(),k=u.firstChild,R=k.firstChild,J=k.nextSibling,Y=J.firstChild,A=Y.nextSibling;return a.insert(R,a.createComponent(m.For,{get each(){return Qe(t())},children:h=>(()=>{var $=st(),I=$.firstChild,B=I.nextSibling;return a.insert(I,()=>h.label),a.insert(B,()=>h.value),a.effect(()=>a.setAttribute($,"data-tone",h.tone)),$})()})),A.addEventListener("change",h=>{g(h.currentTarget)}),a.insert(A,a.createComponent(m.For,{get each(){return et(n())},children:h=>(()=>{var $=at();return a.insert($,()=>h.title),a.effect(()=>$.value=h.value),$})()})),a.insert(u,a.createComponent(m.Show,{get when(){return c()},get children(){return nt()}}),null),a.insert(u,a.createComponent(m.Show,{get when(){return t()!==void 0},get children(){var h=ot();return a.insert(h,a.createComponent(m.For,{get each(){return it(r()).slice(0,ut)},children:$=>(()=>{var I=ct();return a.insert(I,$),I})()})),h}}),null),a.effect(()=>A.disabled=t()===void 0),a.effect(()=>A.value=n()),u})()}const dt=O.Plugin.define({setup(e){const t=te(e.client,()=>W(e));e.add(O.SettingsPage,{id:"adaptive",title:"OpenCode Adaptive",entries:[{id:"adaptive",title:"OpenCode Adaptive",description:"Execution tier routing, operating mode and resource health"}],render:()=>a.createComponent(z,{api:t})}),e.add(O.Command,{id:"open",title:"OpenCode Adaptive",group:"Adaptive",suggested:!0,slash:{name:"adaptive"},run:()=>Ze(e)})}});exports.AdaptiveStatusPage=z;exports.default=dt;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const c=require("solid-js/web"),T=require("@opencode/plugin/gui"),f=require("solid-js"),Z=require("@opencode/schema/rpc"),Q=1e4,ee=1440*6e4,te=6;function ie(e){const t={};for(const i of e.waiting)t[i.sessionID]=i.resumeAt;return t}function j(e){const t=new Date(e);return new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime()}function C(e){return`${String(e.getHours()).padStart(2,"0")}:${String(e.getMinutes()).padStart(2,"0")}`}function ne(e,t){if(e===null)return"waiting";if(e<=t)return"waiting · resuming";const i=new Date(e),n=Math.round((j(e)-j(t))/ee);if(n===0)return`waiting · resumes ${C(i)}`;const o=i.toDateString();return n<=te?`waiting · resumes ${o.slice(0,3)} ${C(i)}`:`waiting · resumes ${o.slice(4,7)} ${String(i.getDate())} ${C(i)}`}const oe=128,re="coool.adaptive",x="coool.adaptive",S={internal:_()};function p(e,t){return{"~standard":{version:1,vendor:x,validate:i=>e(i)?{value:i}:{issues:[{message:t}]}}}}function _(){return{"~standard":{version:1,vendor:x,validate:e=>e===void 0?{value:void 0}:{issues:[{message:"unexpected input: no input expected"}]}}}}const g=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),k=e=>typeof e=="string",M=e=>{if(!(g(e)&&k(e.sessionID)))return!1;const t=e.sessionID;return t.length>0&&t.length<=oe},se=e=>g(e)&&k(e.sessionID)&&(e.mode===null||k(e.mode)),ae=e=>g(e)&&k(e.sessionID)&&(e.tier===null||k(e.tier)),V=Z.Rpc.define({id:re,methods:{"settings.get":{errors:S,input:_(),output:p(g,"output must be an object")},"settings.set":{errors:S,input:p(g,"input must be an object"),output:p(g,"output must be an object")},"status.get":{errors:S,input:_(),output:p(g,"output must be an object")},"receipts.list":{errors:S,input:_(),output:p(Array.isArray,"output must be an array")},"session-mode.get":{errors:S,input:p(M,"input must be an object with a sessionID string"),output:p(g,"output must be an object")},"session-mode.set":{errors:S,input:p(se,"input must be an object with a sessionID string and a mode string or null"),output:p(g,"output must be an object")},"tier-request.get":{errors:S,input:p(M,"input must be an object with a sessionID string"),output:p(g,"output must be an object")},"tier-request.set":{errors:S,input:p(ae,"input must be an object with a sessionID string and a tier string or null"),output:p(g,"output must be an object")}},events:{"settings-changed":{schema:p(g,"event data must be an object")},"session-mode-changed":{schema:p(M,"event data must be an object with a sessionID string")},"tier-request-changed":{schema:p(M,"event data must be an object with a sessionID string")}}}),R=["off","observe","active"],w=["quick","standard","advanced"];function ce(e,t){const i=e.rpc(V),n=()=>{};return{status:()=>i["status.get"](void 0,t()).catch(n),waiting:()=>i["status.get"](void 0,t()).then(ie).catch(n),receipts:()=>i["receipts.list"](void 0,t()).catch(n),update:async o=>{try{return(await i["settings.set"](o,t())).ok}catch{return!1}},watchSettings:o=>i.events.on("settings-changed",()=>{o()})}}function h(e){const t=`${e.providerID}/${e.id}`;return e.variant===void 0?t:`${t}@${e.variant}`}function F(e){if(e===void 0)return"";const t=[];return e.health!=="healthy"&&t.push(e.health),e.breaker==="open"?t.push("breaker-open"):e.breaker==="half_open"&&t.push("half-open"),e.windowed&&t.push("windowed"),t.length===0?"":` [${t.join(", ")}]`}function ue(e,t,i){const n=[{title:"Add model…",value:{kind:"add"},description:"Search OpenCode models"}],o=i==null?void 0:i.tiers[t];return e.tiers[t].forEach((r,s)=>{n.push({title:`${h(r)}${F(o==null?void 0:o[s])}`,value:{kind:"resource",index:s,ref:r},description:"Move or remove"})}),n}function de(e){return w.map(t=>{const i=e.tiers[t],n=i.length===0?"(empty — add models to enable routing)":i.map(o=>h(o.ref)).join(" · ");return{title:`${t} (${String(i.length)})`,value:t,description:n}})}function le(e){return e.map(t=>({title:`${t.providerID}/${t.modelID}`,description:t.name,value:t}))}function pe(e){const t=[{title:"(model default)",value:{variant:void 0}}];for(const i of e)t.push({title:`@${i}`,value:{variant:i}});return t}function ve(e,t){return[...e.filter((i,n)=>n===t),...e.filter((i,n)=>n!==t)]}function fe(e,t,i){const n=[...e],o=t+i,r=n[t],s=n[o];return r===void 0||s===void 0||(n[t]=s,n[o]=r),n}function ge(e,t){return e.filter((i,n)=>n!==t)}function he(e){const t=[];for(const i of w)for(const n of e.tiers[i])t.push({tier:i,status:n});return t}function q(e){const t=new Date(e),i=String(t.getHours()).padStart(2,"0"),n=String(t.getMinutes()).padStart(2,"0"),o=String(t.getMonth()+1).padStart(2,"0"),r=String(t.getDate()).padStart(2,"0");return`${String(t.getFullYear())}-${o}-${r} ${i}:${n}`}function me(e,t){return t===null?!1:t.providerID===e.providerID&&t.id===e.id&&t.variant===e.variant}function we(e,t){let i="";return e.windowed&&(i+=e.windowUntil===null?", windowed (deadline unknown)":`, windowed until ${q(e.windowUntil)}`),e.usedPercent!==null&&(i+=`, used ${String(e.usedPercent)}%`,e.resetAt!==null&&(i+=`, resets ${q(e.resetAt)}`)),e.breaker==="open"?i+=", breaker open":e.breaker==="half_open"&&(i+=", probing"),me(e.ref,t)&&(i+=" (pinned)"),i}function be(e,t){return we(e,t).replace(/^[, ]+/,"")||"-"}function $e(e){const t=["MODEL","TIER","HEALTH","STATE"],i=he(e).map(({tier:r,status:s})=>[h(s.ref),r,s.health,be(s,e.pin)]),n=t.map((r,s)=>Math.max(r.length,...i.map(a=>a[s].length))),o=r=>r.map((s,a)=>s.padEnd(n[a])).join(" ").trimEnd();return[o(t),...i.map(o)]}const Se="No models configured yet — open Models to add some.",ye=["status","models","mode","tier","pin","receipts"];function De(e){if(e===void 0)return"how every configured model is doing";const t=w.reduce((o,r)=>o+e.tiers[r].length,0);if(t===0)return"no models configured yet";const i=w.reduce((o,r)=>o+e.tiers[r].filter(s=>s.windowed).length,0),n=i===0?"":` · ${String(i)} windowed`;return`${String(t)} resource${t===1?"":"s"}${n}`}function Ie(e){return w.map(i=>`${i}: ${String(e.tiers[i].length)}`).join(" · ")}function ke(e){return e===void 0?"pin a tier for the current session":e===null?"not pinned — the routing ladder decides":`pinned to ${e} — steers this session`}function Ae(e,t){if(e===t.override)return"override";if(e===t.effective)return"current"}function Te(e,t,i,n=e.mode){const o=r=>{switch(r){case"status":return{title:"Status",value:r,description:De(t)};case"models":return{title:"Models",value:r,description:Ie(e)};case"mode":return{title:"Mode",value:r,description:`currently ${n}`};case"tier":return{title:"Tier",value:r,description:ke(i)};case"pin":return{title:"Pin",value:r,description:e.pin===null?"force one resource":`pinned to ${h(e.pin)}`};case"receipts":return{title:"Receipts",value:r,description:"why each turn ran the model it ran"}}};return ye.map(o)}function Me(e){return R.map(t=>({title:t===e?`${t} (current)`:t,value:t,description:G(t)}))}function G(e){switch(e){case"off":return"Adaptive does nothing";case"observe":return"Record decisions without applying them";case"active":return"Apply routing and recovery decisions"}}function _e(e,t){return[{title:"Global mode…",description:`applies to every session without an override (now ${e})`,value:"global"},{title:"This session…",description:`override for this session only (now ${t})`,value:"session"}]}function Ee(e){const t=[{title:`Follow global (${e.global})`,description:"clear this session's override",value:{kind:"global"}}];for(const i of R){const n=Ae(i,e);t.push({title:n===void 0?i:`${i} (${n})`,value:{kind:"mode",mode:i},description:G(i)})}return t}function Re(e){switch(e){case"quick":return"routine edits, mechanical steps";case"standard":return"the default";case"advanced":return"multi-step design, broad refactors"}}function Oe(e){const t=[{title:"Follow routing ladder",description:"clear this session's tier request",value:{kind:"ladder"}}];for(const i of w)t.push({title:i===e.pinned?`${i} (pinned)`:i,value:{kind:"tier",tier:i},description:Re(i)});return t}function W(e){if(e instanceof Error)return e.message;if(typeof e=="object"&&e!==null){const t=e.message;return typeof t=="string"&&t.length>0?t:JSON.stringify(e,(i,n)=>typeof n=="bigint"?String(n):n)}return String(e)}function P(e){switch(e.kind){case"apply":{const t=e.tier===void 0?"":` [${e.tier}]`;return`apply ${h(e.model)}${t} (${e.reason})`}case"defer":{const t=e.detail===void 0?"":` — ${e.detail}`;return`defer: ${e.reason}${t}`}case"park":{const t=e.detail===void 0?"":` — ${e.detail}`;return`park: ${e.reason}${t}`}case"escalate":return`escalate ${e.step} → ${h(e.target)} [${e.tier}]`;case"observe-only":return`observe: ${P(e.would)}`}}function H(e){return e===void 0?"pending":e.status==="succeeded"?"succeeded":e.status==="failed"?`failed (${e.category})`:`interrupted (${e.reason})`}function U(e){return`#${String(e.id)} ${P(e.decision)} — ${H(e.outcome)}`}function Ce(e,t=Date.now()){return[`id: ${String(e.id)}`,`when: ${Pe(e.at,t)} (${new Date(e.at).toISOString()})`,`session: ${e.sessionID}`,`unit: ${e.unitID??"-"}`,`tier: ${e.tier??"-"}`,`decision: ${P(e.decision)}`,`outcome: ${H(e.outcome)}`,"",`What happened: ${je(e)}`].join(`
2
+ `)}function Pe(e,t){const i=Math.max(0,Math.floor((t-e)/1e3));if(i<60)return"just now";const n=Math.floor(i/60);if(n<60)return`${String(n)}m ago`;const o=Math.floor(n/60);return o<24?`${String(o)}h ago`:`${String(Math.floor(o/24))}d ago`}function je(e){const t=e.decision;switch(t.kind){case"apply":return qe[t.reason];case"defer":return Ne[t.reason];case"park":return Le[t.reason];case"escalate":return`This turn needed stronger capability, so Adaptive moved it to ${h(t.target)}.`;case"observe-only":return"Adaptive is observing: this is what it would have done, without applying it."}}const qe={pin:"Adaptive routed to the resource you pinned.",preferred:"Adaptive chose this resource: it is the preferred resource for the tier and was available.",fallback:"The preferred resource was unavailable, so Adaptive moved the turn to the next resource in the tier.",probe:"Adaptive sent this turn as a recovery probe after an outage, to check whether the resource works again.",draining:"Adaptive routed to this resource while its provider was draining capacity."},Ne={"routing-defer":"No routing rule applied to this turn, so Adaptive left the choice to OpenCode.","adaptive-off":"Adaptive is switched off, so every turn runs on OpenCode's own choice.","pin-unverifiable":"The pinned resource could not be verified as available, so Adaptive left the choice to OpenCode.","pin-incompatible":"The pinned resource does not fit this turn's requirements, so Adaptive left the choice to OpenCode.","escalation-not-justified":"No structured capability evidence justified escalation, so Adaptive kept the current capability.","escalation-budget-exhausted":"This unit has used its escalation budget; no further escalation is allowed.","escalation-ceiling":"Adaptive is already at the highest capability tier; no stronger tier is configured.","escalation-no-candidate":"No stronger resource was eligible to escalate to (unavailable, blocked, or not configured)."},Le={"all-blocked":"Every resource in the tier is currently unavailable, so the turn ran without Adaptive routing.","no-candidates":"There are no models configured for this tier, so the turn ran without Adaptive routing.","no-compatible-resource":"No configured resource fits this turn's requirements, so the turn ran without Adaptive routing.","pin-blocked":"The pinned resource is unavailable and no fallback is allowed while pinned."},E=20;function xe(e){return e.slice(0,E)}const N="OpenCode Adaptive",Ve="Adaptive starts in Observe mode: routing decisions are recorded as receipts, but no model is switched. Open /adaptive to configure tiers or switch to Active.";async function b(e,t){try{return await t()}catch(i){e.view.toast(W(i),"error");return}}async function y(e,t,i){const n=await b(e,()=>e.api.setSettings(t));if(n!==void 0){if(!n.ok){await e.view.alert({title:"Invalid settings",message:n.errors.join("; ")});return}return i.once||(e.view.toast("Settings saved","success"),i.once=!0),n.settings}}async function L(e,t,i,n){const o=await b(e,()=>e.api.setSessionMode(t,i));if(o!==void 0){if(!o.ok){await e.view.alert({title:"Invalid settings",message:o.errors.join("; ")});return}return n.once||(e.view.toast("Settings saved","success"),n.once=!0),o}}async function Fe(e,t,i,n){const o=await b(e,()=>e.api.setTierRequest(t,i));if(o!==void 0){if(!o.ok){await e.view.alert({title:"Invalid tier request",message:o.errors.join("; ")});return}return n.once||(e.view.toast("Settings saved","success"),n.once=!0),o}}async function Ge(e){const t=await b(e,()=>e.api.settings());if(t===void 0)return;let i=t;const n={once:!1},o=e.sessionID;let r=o===void 0?null:await b(e,()=>e.api.sessionMode(o))??null;if(!i.inviteDismissed){await e.view.confirm({title:N,message:Ve});const s=await e.view.select({title:N,options:[{title:"Open the menu now",value:!0},{title:"Later",value:!1}]}),a=await y(e,{inviteDismissed:!0},n);if(a!==void 0&&(i=a),s!==!0)return}for(;;){const[s,a]=await Promise.all([e.api.settings().catch(()=>{}),e.api.status().catch(()=>{})]);s!==void 0&&(i=s);const l=o===void 0?void 0:await e.api.tierRequest(o).catch(()=>{}),d=await e.view.select({title:`Adaptive (${r??i.mode})`,options:Te(i,a,l,r??i.mode)});if(d===void 0)return;if(d==="status"){await Ue(e);continue}if(d==="models"){i=await Ye(e,n)??i;continue}if(d==="mode"){const v=await We(e,i,n,o,r);v!==void 0&&(i=v.settings,r=v.override);continue}if(d==="tier"){if(o===void 0){await e.view.alert({title:"This session",message:"This session's tier request can only be changed inside a session. Open a session and run /adaptive from there."});continue}await He(e,o,n);continue}if(d==="pin"){i=await Xe(e,i,n)??i;continue}await Ze(e)}}async function We(e,t,i,n,o){for(;;){const r=await e.view.select({title:"Operating mode",options:_e(t.mode,o??t.mode)});if(r===void 0)return;if(r==="global"){const l=await e.view.select({title:"Global mode",options:Me(t.mode)});if(l===void 0||l===t.mode)return;const d=await y(e,{mode:l},i);return d===void 0?void 0:{settings:d,override:o}}if(n===void 0){await e.view.alert({title:"This session",message:"This session's mode can only be changed inside a session. Open a session and run /adaptive from there."});continue}const s=await e.view.select({title:"This session's mode",options:Ee({global:t.mode,override:o,effective:o??t.mode})});return s===void 0?void 0:s.kind==="global"?o===null||await L(e,n,null,i)===void 0?void 0:{settings:t,override:null}:s.mode===(o??t.mode)||await L(e,n,s.mode,i)===void 0?void 0:{settings:t,override:s.mode}}}async function He(e,t,i){const n=await b(e,()=>e.api.tierRequest(t));if(n===void 0)return;const o=await e.view.select({title:"This session's tier",options:Oe({pinned:n})});if(o===void 0)return;const r=o.kind==="ladder"?null:o.tier;r!==n&&await Fe(e,t,r,i)}async function Ue(e){const t=await b(e,()=>e.api.status());if(t===void 0)return;if(!w.some(n=>t.tiers[n].length>0)){await e.view.alert({title:"Status",message:Se});return}await e.view.table({title:"Status",lines:$e(t)})}async function Ye(e,t){let i;for(;;){const n=await b(e,()=>e.api.settings());if(n===void 0)return i;const o=await b(e,()=>e.api.status());if(o===void 0)return i;const r=await e.view.select({title:"Tier",options:de(o)});if(r===void 0)return i;let s=o,a=n;for(;;){const l=await e.view.select({title:`Tier: ${r}`,options:ue(a,r,s)});if(l===void 0)break;if(l.kind==="add"){const u=await ze(e,a,r,t);u!==void 0&&(i=u,a=u)}else{const u=await Be(e,a,r,l,t);u!==void 0&&(i=u,a=u)}const[d,v]=await Promise.all([e.api.settings().catch(()=>{}),e.api.status().catch(()=>{})]);d!==void 0&&(i=d,a=d),v!==void 0&&(s=v)}}}async function ze(e,t,i,n){const o=e.models(),r=await e.view.select({title:"Add model",placeholder:"Search provider / model / name",options:le(o)});if(r===void 0)return;const s=r.variants;let a;if(s.length>0){const v=await e.view.select({title:`Variant: ${r.providerID}/${r.modelID}`,options:pe(s)});if(v===void 0)return;a=v.variant}const l=a===void 0?{providerID:r.providerID,id:r.modelID}:{providerID:r.providerID,id:r.modelID,variant:a},d=[...t.tiers[i],l];return y(e,{tiers:{...t.tiers,[i]:d}},n)}function Je(){return[{title:"Move to top",value:"top",description:"Make this the primary resource"},{title:"Move up",value:"up",description:"Higher priority within the tier"},{title:"Move down",value:"down",description:"Lower priority within the tier"},{title:"Remove",value:"remove"}]}async function Be(e,t,i,{index:n,ref:o},r){const s=await e.view.select({title:`Resource: ${h(o)}`,options:Je()});if(s===void 0)return;if(s==="remove")return await e.view.confirm({title:"Remove resource",message:`Remove ${h(o)} from ${i}?`})!==!0?void 0:y(e,{tiers:{...t.tiers,[i]:ge(t.tiers[i],n)}},r);const a=s==="top"?ve(t.tiers[i],n):fe(t.tiers[i],n,s==="up"?-1:1);return y(e,{tiers:{...t.tiers,[i]:a}},r)}function Ke(e){const t=[{title:"No pin",value:{kind:"clear"},description:"clear the pin"}];for(const i of w)for(const n of e.tiers[i])t.push({title:`${h(n)} (${i})`,description:"route every turn to this resource, even in active mode",value:{kind:"pin",ref:n}});return t}async function Xe(e,t,i){if(!w.some(r=>t.tiers[r].length>0)){await e.view.alert({title:"Pin",message:"(no configured resources to pin)"});return}const o=await e.view.select({title:"Pin",options:Ke(t)});if(o!==void 0)return y(e,{pin:o.kind==="clear"?null:o.ref},i)}async function Ze(e){const t=await b(e,()=>e.api.receipts());if(t!==void 0){if(t.length===0){await e.view.alert({title:"Receipts",message:"No decisions recorded yet."});return}for(let i=0;;){const n=xe(t.slice(i)),o=i+E<t.length,r=await e.view.selectPaged({title:`Receipts (${String(t.length)}, newest first)`,options:n.map(a=>({title:U(a),value:a})),actions:[...i>0?[{title:"Previous page",value:"prev",bind:"ctrl+b"}]:[],...o?[{title:"Next page",value:"next",bind:"ctrl+f"}]:[]]});if(r.action==="next"){i+=E;continue}if(r.action==="prev"){i=Math.max(0,i-E);continue}const s=r.picked;if(s===void 0)return;await e.view.alert({title:`Receipt #${String(s.id)}`,message:Ce(s)})}}}var Qe=c.template("<pre class=adaptive-table>");function et(e){return f.onCleanup(e.done),c.createComponent(e.host.ui.Dialog,{get title(){return e.title},size:"x-large",get footer(){return c.createComponent(e.host.ui.Button,{get onClick(){return e.close},children:"Close"})},get children(){var t=Qe();return c.insert(t,()=>e.lines.join(`
3
+ `)),t}})}function tt(e){return{select:({title:t,placeholder:i,options:n})=>e.dialogs.select({title:t,options:n,...i!==void 0?{placeholder:i}:{}}),selectPaged:async t=>{const i=await e.dialogs.select({title:t.title,options:[...t.options.map(n=>({title:n.title,value:{kind:"pick",value:n.value}})),...t.actions.map(n=>({title:n.title,value:{kind:"action",value:n.value}}))]});return i===void 0?{picked:void 0,action:void 0}:i.kind==="pick"?{picked:i.value,action:void 0}:{picked:void 0,action:i.value}},confirm:({title:t,message:i})=>e.dialogs.confirm({title:t,description:i}),alert:({title:t,message:i})=>e.dialogs.alert({title:t,description:i}),table:({title:t,lines:i})=>new Promise(n=>{e.dialogs.open(o=>c.createComponent(et,{host:e,title:t,lines:i,close:()=>{o.close()},done:n}))}),toast:(t,i)=>{e.toast.show({title:t,variant:i})}}}function Y(e){const t=e.sessions.current();return(t==null?void 0:t.server.builtin)===!0?t:void 0}function z(e){var i,n;const t=(n=(i=Y(e))==null?void 0:i.location)==null?void 0:n.directory;return t===void 0?{}:{location:{directory:t}}}function it(e){const t=e.client.rpc(V),i=()=>z(e);return{settings:()=>t["settings.get"](void 0,i()),setSettings:n=>t["settings.set"](n,i()),status:()=>t["status.get"](void 0,i()),receipts:()=>t["receipts.list"](void 0,i()),sessionMode:n=>t["session-mode.get"]({sessionID:n},i()).then(o=>o.mode),setSessionMode:(n,o)=>t["session-mode.set"]({sessionID:n,mode:o},i()),tierRequest:n=>t["tier-request.get"]({sessionID:n},i()).then(o=>o.tier),setTierRequest:(n,o)=>t["tier-request.set"]({sessionID:n,tier:o},i())}}function nt(e){const t=e.servers.list().map(n=>e.servers.get(n)).find(n=>(n==null?void 0:n.builtin)===!0);return t===void 0?void 0:t.data.location.model}async function ot(e){const t=tt(e),i=Y(e),n=nt(e);await(async()=>{await(n==null?void 0:n.sync(i==null?void 0:i.location).catch(()=>{})),await Ge({api:it(e),view:t,models:()=>((n==null?void 0:n.list(i==null?void 0:i.location))??[]).map(r=>({providerID:r.providerID,modelID:r.modelID,name:r.name,variants:r.variants.map(s=>s.id)})),...i!==void 0?{sessionID:i.id}:{}})})().catch(r=>{t.toast(W(r),"error")})}const rt="connection";function st(e){if(e===void 0)return[{id:rt,label:"Server",value:"disconnected",tone:"warning"}];const t=[{id:"mode",label:"Mode",value:e.mode},{id:"pin",label:"Pin",value:e.pin===null?"none":h(e.pin)}];for(const i of w){const n=e.tiers[i],o=n.length===0?"empty":n.map(r=>`${h(r.ref)}${F(r)}`).join(", ");t.push({id:`tier-${i}`,label:`Tier ${i}`,value:o})}return t.push({id:"parked",label:"Parked",value:String(e.parked)}),t.push({id:"receipts",label:"Receipts",value:String(e.receipts)}),t}function at(e){return R.map(t=>({value:t,title:t===e?`${t} (current)`:t}))}function ct(e){return R.find(t=>t===e)}function ut(e){return e.map(t=>U(t))}var dt=c.template("<span class=adaptive-saved role=status>Saved"),lt=c.template("<div class=adaptive-receipts>"),pt=c.template('<div class=adaptive-page><table class=adaptive-status aria-label="OpenCode Adaptive status"><tbody></tbody></table><label class=adaptive-mode><span class=adaptive-row-label>Mode</span><select class=adaptive-mode-select>'),vt=c.template("<tr class=adaptive-row><td class=adaptive-row-label></td><td class=adaptive-row-value>"),ft=c.template("<option>"),gt=c.template("<div class=adaptive-receipt-line>");const ht=5;function J(e){const[t,i]=f.createSignal(void 0),[n,o]=f.createSignal("observe"),[r,s]=f.createSignal([]),[a,l]=f.createSignal(!1),d=()=>{e.api.status().then(u=>{i(u),u!==void 0&&o(u.mode)}),e.api.receipts().then(u=>{s(u??[])})};f.onCleanup(e.api.watchSettings(d)),d();const v=u=>{const D=ct(u.value);u.value=n(),D!==void 0&&e.api.update({mode:D}).then(O=>{l(O),d()})};return(()=>{var u=pt(),D=u.firstChild,O=D.firstChild,B=D.nextSibling,K=B.firstChild,A=K.nextSibling;return c.insert(O,c.createComponent(f.For,{get each(){return st(t())},children:m=>(()=>{var $=vt(),I=$.firstChild,X=I.nextSibling;return c.insert(I,()=>m.label),c.insert(X,()=>m.value),c.effect(()=>c.setAttribute($,"data-tone",m.tone)),$})()})),A.addEventListener("change",m=>{v(m.currentTarget)}),c.insert(A,c.createComponent(f.For,{get each(){return at(n())},children:m=>(()=>{var $=ft();return c.insert($,()=>m.title),c.effect(()=>$.value=m.value),$})()})),c.insert(u,c.createComponent(f.Show,{get when(){return a()},get children(){return dt()}}),null),c.insert(u,c.createComponent(f.Show,{get when(){return t()!==void 0},get children(){var m=lt();return c.insert(m,c.createComponent(f.For,{get each(){return ut(r()).slice(0,ht)},children:$=>(()=>{var I=gt();return c.insert(I,$),I})()})),m}}),null),c.effect(()=>A.disabled=t()===void 0),c.effect(()=>A.value=n()),u})()}const mt=T.Plugin.define({setup(e){const t=ce(e.client,()=>z(e));e.add(T.SettingsPage,{id:"adaptive",title:"OpenCode Adaptive",entries:[{id:"adaptive",title:"OpenCode Adaptive",description:"Execution tier routing, operating mode and resource health"}],render:()=>c.createComponent(J,{api:t})}),e.add(T.Command,{id:"open",title:"OpenCode Adaptive",group:"Adaptive",suggested:!0,slash:{name:"adaptive"},run:()=>ot(e)});const[i,n]=f.createSignal({sessions:{},now:0}),o=()=>{t.waiting().then(a=>{a!==void 0&&n({sessions:a,now:Date.now()})})};o();const r=setInterval(o,Q),s=t.watchSettings(o);return e.add(T.ComposerPill,{id:"waiting",show:a=>{const{sessions:l,now:d}=i(),v=l[a.session.id];if(v===void 0)return;const u=ne(v,d);return{label:u.charAt(0).toUpperCase()+u.slice(1),icon:"status",tone:"warning",title:"Waiting for quota: Adaptive continues this session automatically"}}}),()=>{clearInterval(r),s()}}});exports.AdaptiveStatusPage=J;exports.default=mt;
package/dist/gui.d.ts CHANGED
@@ -10,8 +10,8 @@ export declare function AdaptiveStatusPage(props: {
10
10
  }): JSX.Element;
11
11
  /**
12
12
  * GUI plugin entry (the `./gui-experimental` bundle default export). Thin adapter: registers a native
13
- * SettingsPage and a palette/`/adaptive` slash command that runs the same dialog flow as the TUI (ui/flow); no
14
- * routing logic lives here (ADP-034/035).
13
+ * SettingsPage, a palette/`/adaptive` slash command that runs the same dialog flow as the TUI (ui/flow), and a
14
+ * composer pill on sessions parked until quota returns; no routing logic lives here (ADP-024/034/035).
15
15
  */
16
16
  declare const _default: Plugin.Definition<{}>;
17
17
  export default _default;
package/dist/gui.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "solid-js/jsx-runtime";
2
- import { Command, Plugin, SettingsPage } from "@opencode/plugin/gui";
2
+ import { Command, ComposerPill, Plugin, SettingsPage } from "@opencode/plugin/gui";
3
3
  import { For, Show, createSignal, onCleanup } from "solid-js";
4
4
  import { adaptiveGuiApi } from "./ui/gui/api.js";
5
5
  import { openAdaptive, rpcLocation } from "./ui/gui/flow.js";
6
6
  import { modeChoices, parseModeValue, receiptLines, statusRows } from "./ui/gui/view.js";
7
+ import { WAITING_POLL_MS, waitingLabel } from "./ui/flow/waiting.js";
7
8
  const RECEIPTS_PREVIEW_LIMIT = 5;
8
9
  /**
9
10
  * Native GUI settings page body (ADP-034). All data comes from the typed RPC adapter; the component holds no
@@ -44,8 +45,8 @@ export function AdaptiveStatusPage(props) {
44
45
  }
45
46
  /**
46
47
  * GUI plugin entry (the `./gui-experimental` bundle default export). Thin adapter: registers a native
47
- * SettingsPage and a palette/`/adaptive` slash command that runs the same dialog flow as the TUI (ui/flow); no
48
- * routing logic lives here (ADP-034/035).
48
+ * SettingsPage, a palette/`/adaptive` slash command that runs the same dialog flow as the TUI (ui/flow), and a
49
+ * composer pill on sessions parked until quota returns; no routing logic lives here (ADP-024/034/035).
49
50
  */
50
51
  export default Plugin.define({
51
52
  setup(context) {
@@ -70,6 +71,39 @@ export default Plugin.define({
70
71
  slash: { name: "adaptive" },
71
72
  run: () => openAdaptive(context),
72
73
  });
74
+ // Work parks and resumes on the server without any GUI action (a failed execution, the wake timer), so the
75
+ // pill polls; a failed read keeps the last known state.
76
+ const [waiting, setWaiting] = createSignal({ sessions: {}, now: 0 });
77
+ const refreshWaiting = () => {
78
+ void api.waiting().then((sessions) => {
79
+ if (sessions !== undefined)
80
+ setWaiting({ sessions, now: Date.now() });
81
+ });
82
+ };
83
+ refreshWaiting();
84
+ const poll = setInterval(refreshWaiting, WAITING_POLL_MS);
85
+ // Only active sessions wait: a mode change can start or end a wait.
86
+ const unwatch = api.watchSettings(refreshWaiting);
87
+ context.add(ComposerPill, {
88
+ id: "waiting",
89
+ show: (input) => {
90
+ const { sessions, now } = waiting();
91
+ const resumeAt = sessions[input.session.id];
92
+ if (resumeAt === undefined)
93
+ return undefined;
94
+ const label = waitingLabel(resumeAt, now);
95
+ return {
96
+ label: label.charAt(0).toUpperCase() + label.slice(1),
97
+ icon: "status",
98
+ tone: "warning",
99
+ title: "Waiting for quota: Adaptive continues this session automatically",
100
+ };
101
+ },
102
+ });
103
+ return () => {
104
+ clearInterval(poll);
105
+ unwatch();
106
+ };
73
107
  },
74
108
  });
75
109
  //# sourceMappingURL=gui.js.map
package/dist/gui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"gui.js","sourceRoot":"","sources":["../src/gui.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Command, Plugin, SettingsPage, type Context } from \"@opencode/plugin/gui\"\nimport { For, Show, createSignal, onCleanup } from \"solid-js\"\nimport type { JSX } from \"solid-js\"\n\nimport type { OperatingMode } from \"./core/scheduler/types.js\"\nimport type { Receipt } from \"./state/receipts/index.js\"\nimport type { StatusSnapshot } from \"./rpc.js\"\nimport { adaptiveGuiApi, type GuiApi } from \"./ui/gui/api.js\"\nimport { openAdaptive, rpcLocation } from \"./ui/gui/flow.js\"\nimport { modeChoices, parseModeValue, receiptLines, statusRows } from \"./ui/gui/view.js\"\n\nconst RECEIPTS_PREVIEW_LIMIT = 5\n\n/**\n * Native GUI settings page body (ADP-034). All data comes from the typed RPC adapter; the component holds no\n * local settings state. A failed read renders the disconnected row instead of stale data.\n */\nexport function AdaptiveStatusPage(props: { api: GuiApi }): JSX.Element {\n const [snapshot, setSnapshot] = createSignal<StatusSnapshot | undefined>(undefined)\n const [mode, setMode] = createSignal<OperatingMode>(\"observe\")\n const [receipts, setReceipts] = createSignal<Receipt[]>([])\n const [saved, setSaved] = createSignal(false)\n\n const refresh = (): void => {\n void props.api.status().then((next) => {\n setSnapshot(next)\n if (next !== undefined) setMode(next.mode)\n })\n void props.api.receipts().then((next) => {\n setReceipts(next ?? [])\n })\n }\n\n onCleanup(props.api.watchSettings(refresh))\n refresh()\n\n const pick = (select: HTMLSelectElement): void => {\n const value = parseModeValue(select.value)\n // The DOM changes before onChange, even if the signal stays unchanged\n // after a rejected write. Keep the control on the confirmed server value.\n select.value = mode()\n if (value === undefined) return\n void props.api.update({ mode: value }).then((ok) => {\n setSaved(ok)\n refresh()\n })\n }\n\n return (\n <div class=\"adaptive-page\">\n <table class=\"adaptive-status\" aria-label=\"OpenCode Adaptive status\">\n <tbody>\n <For each={statusRows(snapshot())}>\n {(row) => (\n <tr class=\"adaptive-row\" data-tone={row.tone}>\n <td class=\"adaptive-row-label\">{row.label}</td>\n <td class=\"adaptive-row-value\">{row.value}</td>\n </tr>\n )}\n </For>\n </tbody>\n </table>\n <label class=\"adaptive-mode\">\n <span class=\"adaptive-row-label\">Mode</span>\n <select\n class=\"adaptive-mode-select\"\n disabled={snapshot() === undefined}\n value={mode()}\n onChange={(event) => {\n pick(event.currentTarget)\n }}\n >\n <For each={modeChoices(mode())}>\n {(choice) => <option value={choice.value}>{choice.title}</option>}\n </For>\n </select>\n </label>\n <Show when={saved()}>\n <span class=\"adaptive-saved\" role=\"status\">\n Saved\n </span>\n </Show>\n <Show when={snapshot() !== undefined}>\n <div class=\"adaptive-receipts\">\n <For each={receiptLines(receipts()).slice(0, RECEIPTS_PREVIEW_LIMIT)}>\n {(line) => <div class=\"adaptive-receipt-line\">{line}</div>}\n </For>\n </div>\n </Show>\n </div>\n )\n}\n\n/**\n * GUI plugin entry (the `./gui-experimental` bundle default export). Thin adapter: registers a native\n * SettingsPage and a palette/`/adaptive` slash command that runs the same dialog flow as the TUI (ui/flow); no\n * routing logic lives here (ADP-034/035).\n */\nexport default Plugin.define({\n setup(context: Context): void {\n const api = adaptiveGuiApi(context.client, () => rpcLocation(context))\n context.add(SettingsPage, {\n id: \"adaptive\",\n title: \"OpenCode Adaptive\",\n entries: [\n {\n id: \"adaptive\",\n title: \"OpenCode Adaptive\",\n description: \"Execution tier routing, operating mode and resource health\",\n },\n ],\n render: () => <AdaptiveStatusPage api={api} />,\n })\n context.add(Command, {\n id: \"open\",\n title: \"OpenCode Adaptive\",\n group: \"Adaptive\",\n suggested: true,\n slash: { name: \"adaptive\" },\n run: () => openAdaptive(context),\n })\n },\n})\n"]}
1
+ {"version":3,"file":"gui.js","sourceRoot":"","sources":["../src/gui.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Command, ComposerPill, Plugin, SettingsPage, type Context } from \"@opencode/plugin/gui\"\nimport { For, Show, createSignal, onCleanup } from \"solid-js\"\nimport type { JSX } from \"solid-js\"\n\nimport type { OperatingMode } from \"./core/scheduler/types.js\"\nimport type { Receipt } from \"./state/receipts/index.js\"\nimport type { StatusSnapshot } from \"./rpc.js\"\nimport { adaptiveGuiApi, type GuiApi } from \"./ui/gui/api.js\"\nimport { openAdaptive, rpcLocation } from \"./ui/gui/flow.js\"\nimport { modeChoices, parseModeValue, receiptLines, statusRows } from \"./ui/gui/view.js\"\nimport { WAITING_POLL_MS, waitingLabel } from \"./ui/flow/waiting.js\"\n\nconst RECEIPTS_PREVIEW_LIMIT = 5\n\n/**\n * Native GUI settings page body (ADP-034). All data comes from the typed RPC adapter; the component holds no\n * local settings state. A failed read renders the disconnected row instead of stale data.\n */\nexport function AdaptiveStatusPage(props: { api: GuiApi }): JSX.Element {\n const [snapshot, setSnapshot] = createSignal<StatusSnapshot | undefined>(undefined)\n const [mode, setMode] = createSignal<OperatingMode>(\"observe\")\n const [receipts, setReceipts] = createSignal<Receipt[]>([])\n const [saved, setSaved] = createSignal(false)\n\n const refresh = (): void => {\n void props.api.status().then((next) => {\n setSnapshot(next)\n if (next !== undefined) setMode(next.mode)\n })\n void props.api.receipts().then((next) => {\n setReceipts(next ?? [])\n })\n }\n\n onCleanup(props.api.watchSettings(refresh))\n refresh()\n\n const pick = (select: HTMLSelectElement): void => {\n const value = parseModeValue(select.value)\n // The DOM changes before onChange, even if the signal stays unchanged\n // after a rejected write. Keep the control on the confirmed server value.\n select.value = mode()\n if (value === undefined) return\n void props.api.update({ mode: value }).then((ok) => {\n setSaved(ok)\n refresh()\n })\n }\n\n return (\n <div class=\"adaptive-page\">\n <table class=\"adaptive-status\" aria-label=\"OpenCode Adaptive status\">\n <tbody>\n <For each={statusRows(snapshot())}>\n {(row) => (\n <tr class=\"adaptive-row\" data-tone={row.tone}>\n <td class=\"adaptive-row-label\">{row.label}</td>\n <td class=\"adaptive-row-value\">{row.value}</td>\n </tr>\n )}\n </For>\n </tbody>\n </table>\n <label class=\"adaptive-mode\">\n <span class=\"adaptive-row-label\">Mode</span>\n <select\n class=\"adaptive-mode-select\"\n disabled={snapshot() === undefined}\n value={mode()}\n onChange={(event) => {\n pick(event.currentTarget)\n }}\n >\n <For each={modeChoices(mode())}>\n {(choice) => <option value={choice.value}>{choice.title}</option>}\n </For>\n </select>\n </label>\n <Show when={saved()}>\n <span class=\"adaptive-saved\" role=\"status\">\n Saved\n </span>\n </Show>\n <Show when={snapshot() !== undefined}>\n <div class=\"adaptive-receipts\">\n <For each={receiptLines(receipts()).slice(0, RECEIPTS_PREVIEW_LIMIT)}>\n {(line) => <div class=\"adaptive-receipt-line\">{line}</div>}\n </For>\n </div>\n </Show>\n </div>\n )\n}\n\n/**\n * GUI plugin entry (the `./gui-experimental` bundle default export). Thin adapter: registers a native\n * SettingsPage, a palette/`/adaptive` slash command that runs the same dialog flow as the TUI (ui/flow), and a\n * composer pill on sessions parked until quota returns; no routing logic lives here (ADP-024/034/035).\n */\nexport default Plugin.define({\n setup(context: Context): () => void {\n const api = adaptiveGuiApi(context.client, () => rpcLocation(context))\n context.add(SettingsPage, {\n id: \"adaptive\",\n title: \"OpenCode Adaptive\",\n entries: [\n {\n id: \"adaptive\",\n title: \"OpenCode Adaptive\",\n description: \"Execution tier routing, operating mode and resource health\",\n },\n ],\n render: () => <AdaptiveStatusPage api={api} />,\n })\n context.add(Command, {\n id: \"open\",\n title: \"OpenCode Adaptive\",\n group: \"Adaptive\",\n suggested: true,\n slash: { name: \"adaptive\" },\n run: () => openAdaptive(context),\n })\n // Work parks and resumes on the server without any GUI action (a failed execution, the wake timer), so the\n // pill polls; a failed read keeps the last known state.\n const [waiting, setWaiting] = createSignal<{\n sessions: Record<string, number | null>\n now: number\n }>({ sessions: {}, now: 0 })\n const refreshWaiting = (): void => {\n void api.waiting().then((sessions) => {\n if (sessions !== undefined) setWaiting({ sessions, now: Date.now() })\n })\n }\n refreshWaiting()\n const poll = setInterval(refreshWaiting, WAITING_POLL_MS)\n // Only active sessions wait: a mode change can start or end a wait.\n const unwatch = api.watchSettings(refreshWaiting)\n context.add(ComposerPill, {\n id: \"waiting\",\n show: (input) => {\n const { sessions, now } = waiting()\n const resumeAt = sessions[input.session.id]\n if (resumeAt === undefined) return undefined\n const label = waitingLabel(resumeAt, now)\n return {\n label: label.charAt(0).toUpperCase() + label.slice(1),\n icon: \"status\",\n tone: \"warning\",\n title: \"Waiting for quota: Adaptive continues this session automatically\",\n }\n },\n })\n return () => {\n clearInterval(poll)\n unwatch()\n }\n },\n})\n"]}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Plugin } from "@opencode/plugin";
2
2
  import { isCodexHeaderName } from "./adapters/codex-capacity/index.js";
3
- import { adaptiveCommand, escalationTool, registerSessionHooks, subscribeSessions, tierTool, } from "./adapters/opencode/index.js";
3
+ import { adaptiveCommand, escalationTool, processLedger, registerSessionHooks, subscribeSessions, tierTool, unrefTimeout, } from "./adapters/opencode/index.js";
4
4
  import { PLUGIN_ID } from "./plugin-id.js";
5
5
  import { adaptiveRpc } from "./rpc/definition.js";
6
6
  import { adaptiveRpcHandlers, patchSettings } from "./rpc/handlers.js";
@@ -21,6 +21,34 @@ export default Plugin.define({
21
21
  model: context.model,
22
22
  storage: context.storage,
23
23
  clock: () => Date.now(),
24
+ schedule: unrefTimeout,
25
+ ledger: processLedger(),
26
+ });
27
+ // Every disposal settles independently: a failing dispose is logged, never
28
+ // allowed to mask the other disposals or (during rollback) the original
29
+ // setup error. Disposals are awaited so a late rejection cannot land after
30
+ // the host considers the plugin gone.
31
+ const disposals = [];
32
+ const settled = async (name, dispose) => {
33
+ try {
34
+ await dispose();
35
+ }
36
+ catch (error) {
37
+ logFailOpen(name, error);
38
+ }
39
+ };
40
+ const settledAll = async () => {
41
+ for (const { name, dispose } of disposals) {
42
+ await settled(name, dispose);
43
+ }
44
+ };
45
+ // The supervisor arms its parked-work wake on construction, so it is
46
+ // disposed even when a later setup step throws.
47
+ disposals.push({
48
+ name: "wake",
49
+ dispose: () => {
50
+ supervisor.dispose();
51
+ },
24
52
  });
25
53
  const hooks = await registerSessionHooks(context.session, {
26
54
  onRetry: async (input) => {
@@ -38,30 +66,15 @@ export default Plugin.define({
38
66
  onPrompt: async (report) => {
39
67
  await supervisor.handleReport(report);
40
68
  },
41
- }, { capture: isCodexHeaderName });
69
+ }, { capture: isCodexHeaderName }).catch(async (error) => {
70
+ await settledAll();
71
+ throw error;
72
+ });
42
73
  const subscription = subscribeSessions(context.event, (report) => {
43
74
  void supervisor.handleReport(report);
44
75
  }, (error) => {
45
76
  logFailOpen("events", error);
46
77
  });
47
- // Every disposal settles independently: a failing dispose is logged, never
48
- // allowed to mask the other disposals or (during rollback) the original
49
- // setup error. Disposals are awaited so a late rejection cannot land after
50
- // the host considers the plugin gone.
51
- const disposals = [];
52
- const settled = async (name, dispose) => {
53
- try {
54
- await dispose();
55
- }
56
- catch (error) {
57
- logFailOpen(name, error);
58
- }
59
- };
60
- const settledAll = async () => {
61
- for (const { name, dispose } of disposals) {
62
- await settled(name, dispose);
63
- }
64
- };
65
78
  for (const [index, hookRegistration] of hooks.entries()) {
66
79
  disposals.push({ name: `hook ${String(index)}`, dispose: () => hookRegistration.dispose() });
67
80
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { Plugin } from \"@opencode/plugin\"\nimport { isCodexHeaderName } from \"./adapters/codex-capacity/index.js\"\nimport {\n adaptiveCommand,\n escalationTool,\n registerSessionHooks,\n subscribeSessions,\n tierTool,\n} from \"./adapters/opencode/index.js\"\nimport { PLUGIN_ID } from \"./plugin-id.js\"\nimport { adaptiveRpc } from \"./rpc/definition.js\"\nimport { adaptiveRpcHandlers, patchSettings } from \"./rpc/handlers.js\"\nimport type { RpcRegistration } from \"@opencode/plugin/promise/rpc\"\nimport type { AdaptiveRpcEmit } from \"./rpc/handlers.js\"\nimport { Supervisor, logFailOpen } from \"./supervisor/index.js\"\n\nexport { PLUGIN_ID } from \"./plugin-id.js\"\n\n/**\n * Entry point wired into OpenCode via `opencode.json(c)`. Setup glues the\n * adapter touchpoints (session hooks, event subscription, storage) to the\n * supervisor and returns a cleanup that disposes every registration.\n * All supervision errors are fail-open (ADP-031): the supervisor's public\n * entry points never throw; the event pump's stream errors are logged.\n */\nexport default Plugin.define({\n id: PLUGIN_ID,\n async setup(context) {\n const supervisor = new Supervisor({\n session: context.session,\n model: context.model,\n storage: context.storage,\n clock: () => Date.now(),\n })\n const hooks = await registerSessionHooks(\n context.session,\n {\n onRetry: async (input) => {\n await supervisor.handleRetry(input)\n },\n onModelRequest: async (report) => {\n await supervisor.handleReport(report)\n },\n onHttpResponse: async (report) => {\n await supervisor.handleReport(report)\n },\n onIdle: async (report) => {\n await supervisor.handleReport(report)\n },\n onPrompt: async (report) => {\n await supervisor.handleReport(report)\n },\n },\n { capture: isCodexHeaderName },\n )\n const subscription = subscribeSessions(\n context.event,\n (report) => {\n void supervisor.handleReport(report)\n },\n (error) => {\n logFailOpen(\"events\", error)\n },\n )\n // Every disposal settles independently: a failing dispose is logged, never\n // allowed to mask the other disposals or (during rollback) the original\n // setup error. Disposals are awaited so a late rejection cannot land after\n // the host considers the plugin gone.\n const disposals: { name: string; dispose: () => void | Promise<void> }[] = []\n const settled = async (name: string, dispose: () => void | Promise<void>): Promise<void> => {\n try {\n await dispose()\n } catch (error) {\n logFailOpen(name, error)\n }\n }\n const settledAll = async (): Promise<void> => {\n for (const { name, dispose } of disposals) {\n await settled(name, dispose)\n }\n }\n for (const [index, hookRegistration] of hooks.entries()) {\n disposals.push({ name: `hook ${String(index)}`, dispose: () => hookRegistration.dispose() })\n }\n disposals.push({\n name: \"events\",\n dispose: () => {\n subscription.dispose()\n },\n })\n // The typed escalation tool (T-18, ADP-015): the agent's self-report\n // surface for stronger capability, applied at the next safe boundary.\n // The tier tool (ADP-055): the agent declares the task's tier; the\n // declaration steers the session's next admissions until cleared.\n // Typed RPC (ADP-034/036): the only channel to the control surfaces.\n // The settings-changed emitter resolves the registration lazily: handlers\n // only run after `register` resolves, so the holder is always populated.\n const rpcHolder: { registration?: RpcRegistration<typeof adaptiveRpc> } = {}\n const emit: AdaptiveRpcEmit = (...args) =>\n // The correlated tuple pairing is checked by the handler types; the\n // registration forwards it to the host emitter untouched.\n rpcHolder.registration?.events.emit(...args)\n try {\n const toolRegistration = await context.tool.transform((editor) => {\n editor.add(\n escalationTool((sessionID, reason, evidence) =>\n supervisor.noteEscalationRequest(sessionID, reason, evidence),\n ),\n )\n editor.add(tierTool((sessionID, tier) => supervisor.noteTierRequest(sessionID, tier)))\n })\n disposals.push({ name: \"tools\", dispose: () => toolRegistration.dispose() })\n const rpcRegistration = await context.rpc.register(\n adaptiveRpc,\n adaptiveRpcHandlers(supervisor, emit),\n )\n rpcHolder.registration = rpcRegistration\n disposals.push({ name: \"rpc\", dispose: () => rpcRegistration.dispose() })\n // The web UI never mounts plugin GUI bundles, but it lists server\n // commands in the composer's slash menu (ADP-034).\n const commandRegistration = await context.command.transform((editor) => {\n editor.add(\n adaptiveCommand(\n {\n status: () => supervisor.status(),\n setMode: (mode) => patchSettings(supervisor, emit, { mode }),\n },\n // The web timeline shows a synthetic message as a notice only when\n // it has a description, and labels it with that description.\n (sessionID, text) =>\n context.session.synthetic({ sessionID, text, description: text, resume: false }),\n ),\n )\n })\n disposals.push({ name: \"command\", dispose: () => commandRegistration.dispose() })\n } catch (error) {\n // Roll back everything already registered so a failure at the tool or\n // rpc stage does not leak live hooks and the event pump.\n await settledAll()\n throw error\n }\n return () => settledAll()\n },\n})\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { Plugin } from \"@opencode/plugin\"\nimport { isCodexHeaderName } from \"./adapters/codex-capacity/index.js\"\nimport {\n adaptiveCommand,\n escalationTool,\n processLedger,\n registerSessionHooks,\n subscribeSessions,\n tierTool,\n unrefTimeout,\n} from \"./adapters/opencode/index.js\"\nimport { PLUGIN_ID } from \"./plugin-id.js\"\nimport { adaptiveRpc } from \"./rpc/definition.js\"\nimport { adaptiveRpcHandlers, patchSettings } from \"./rpc/handlers.js\"\nimport type { RpcRegistration } from \"@opencode/plugin/promise/rpc\"\nimport type { AdaptiveRpcEmit } from \"./rpc/handlers.js\"\nimport { Supervisor, logFailOpen } from \"./supervisor/index.js\"\n\nexport { PLUGIN_ID } from \"./plugin-id.js\"\n\n/**\n * Entry point wired into OpenCode via `opencode.json(c)`. Setup glues the\n * adapter touchpoints (session hooks, event subscription, storage) to the\n * supervisor and returns a cleanup that disposes every registration.\n * All supervision errors are fail-open (ADP-031): the supervisor's public\n * entry points never throw; the event pump's stream errors are logged.\n */\nexport default Plugin.define({\n id: PLUGIN_ID,\n async setup(context) {\n const supervisor = new Supervisor({\n session: context.session,\n model: context.model,\n storage: context.storage,\n clock: () => Date.now(),\n schedule: unrefTimeout,\n ledger: processLedger(),\n })\n // Every disposal settles independently: a failing dispose is logged, never\n // allowed to mask the other disposals or (during rollback) the original\n // setup error. Disposals are awaited so a late rejection cannot land after\n // the host considers the plugin gone.\n const disposals: { name: string; dispose: () => void | Promise<void> }[] = []\n const settled = async (name: string, dispose: () => void | Promise<void>): Promise<void> => {\n try {\n await dispose()\n } catch (error) {\n logFailOpen(name, error)\n }\n }\n const settledAll = async (): Promise<void> => {\n for (const { name, dispose } of disposals) {\n await settled(name, dispose)\n }\n }\n // The supervisor arms its parked-work wake on construction, so it is\n // disposed even when a later setup step throws.\n disposals.push({\n name: \"wake\",\n dispose: () => {\n supervisor.dispose()\n },\n })\n const hooks = await registerSessionHooks(\n context.session,\n {\n onRetry: async (input) => {\n await supervisor.handleRetry(input)\n },\n onModelRequest: async (report) => {\n await supervisor.handleReport(report)\n },\n onHttpResponse: async (report) => {\n await supervisor.handleReport(report)\n },\n onIdle: async (report) => {\n await supervisor.handleReport(report)\n },\n onPrompt: async (report) => {\n await supervisor.handleReport(report)\n },\n },\n { capture: isCodexHeaderName },\n ).catch(async (error: unknown) => {\n await settledAll()\n throw error\n })\n const subscription = subscribeSessions(\n context.event,\n (report) => {\n void supervisor.handleReport(report)\n },\n (error) => {\n logFailOpen(\"events\", error)\n },\n )\n for (const [index, hookRegistration] of hooks.entries()) {\n disposals.push({ name: `hook ${String(index)}`, dispose: () => hookRegistration.dispose() })\n }\n disposals.push({\n name: \"events\",\n dispose: () => {\n subscription.dispose()\n },\n })\n // The typed escalation tool (T-18, ADP-015): the agent's self-report\n // surface for stronger capability, applied at the next safe boundary.\n // The tier tool (ADP-055): the agent declares the task's tier; the\n // declaration steers the session's next admissions until cleared.\n // Typed RPC (ADP-034/036): the only channel to the control surfaces.\n // The settings-changed emitter resolves the registration lazily: handlers\n // only run after `register` resolves, so the holder is always populated.\n const rpcHolder: { registration?: RpcRegistration<typeof adaptiveRpc> } = {}\n const emit: AdaptiveRpcEmit = (...args) =>\n // The correlated tuple pairing is checked by the handler types; the\n // registration forwards it to the host emitter untouched.\n rpcHolder.registration?.events.emit(...args)\n try {\n const toolRegistration = await context.tool.transform((editor) => {\n editor.add(\n escalationTool((sessionID, reason, evidence) =>\n supervisor.noteEscalationRequest(sessionID, reason, evidence),\n ),\n )\n editor.add(tierTool((sessionID, tier) => supervisor.noteTierRequest(sessionID, tier)))\n })\n disposals.push({ name: \"tools\", dispose: () => toolRegistration.dispose() })\n const rpcRegistration = await context.rpc.register(\n adaptiveRpc,\n adaptiveRpcHandlers(supervisor, emit),\n )\n rpcHolder.registration = rpcRegistration\n disposals.push({ name: \"rpc\", dispose: () => rpcRegistration.dispose() })\n // The web UI never mounts plugin GUI bundles, but it lists server\n // commands in the composer's slash menu (ADP-034).\n const commandRegistration = await context.command.transform((editor) => {\n editor.add(\n adaptiveCommand(\n {\n status: () => supervisor.status(),\n setMode: (mode) => patchSettings(supervisor, emit, { mode }),\n },\n // The web timeline shows a synthetic message as a notice only when\n // it has a description, and labels it with that description.\n (sessionID, text) =>\n context.session.synthetic({ sessionID, text, description: text, resume: false }),\n ),\n )\n })\n disposals.push({ name: \"command\", dispose: () => commandRegistration.dispose() })\n } catch (error) {\n // Roll back everything already registered so a failure at the tool or\n // rpc stage does not leak live hooks and the event pump.\n await settledAll()\n throw error\n }\n return () => settledAll()\n },\n})\n"]}
@@ -26,11 +26,19 @@ export interface TierResourceStatus {
26
26
  /** Wall-clock capacity reset deadline (ms epoch); null when unread. */
27
27
  resetAt: number | null;
28
28
  }
29
+ /** A session whose parked work waits for its tier to recover (ADP-024). */
30
+ export interface WaitingSession {
31
+ sessionID: string;
32
+ tier: TierName;
33
+ /** When the wake resumes it (ms epoch); null when no resource states a reset. */
34
+ resumeAt: number | null;
35
+ }
29
36
  export interface StatusSnapshot {
30
37
  mode: OperatingMode;
31
38
  pin: ModelRef | null;
32
39
  tiers: Readonly<Record<TierName, readonly TierResourceStatus[]>>;
33
40
  parked: number;
41
+ waiting: readonly WaitingSession[];
34
42
  receipts: number;
35
43
  }
36
44
  export declare function buildStatus(input: {
@@ -38,5 +46,6 @@ export declare function buildStatus(input: {
38
46
  health: readonly ResourceRecord[];
39
47
  capacity: Readonly<Record<string, CapacityReading | null>>;
40
48
  parked: number;
49
+ waiting: readonly WaitingSession[];
41
50
  receipts: number;
42
51
  }): StatusSnapshot;
@@ -26,6 +26,7 @@ export function buildStatus(input) {
26
26
  pin: input.settings.pin,
27
27
  tiers,
28
28
  parked: input.parked,
29
+ waiting: input.waiting,
29
30
  receipts: input.receipts,
30
31
  };
31
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/rpc/status.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAG/D,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAA;AAGpD,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAA;AA6BlE,MAAM,UAAU,WAAW,CAAC,KAM3B;IACC,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0B,CAAA;IAC/C,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,MAAM;QAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAA;IAC/E,MAAM,KAAK,GAAG,EAA4C,CAAA;IAC1D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YACnD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAA;YACjD,MAAM,eAAe,GAAG,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAA;YACvE,OAAO;gBACL,GAAG;gBACH,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,SAAS;gBACnC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,QAAQ;gBAC1C,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS;gBACtC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI;gBAC1C,WAAW,EAAE,mBAAmB,CAAC,eAAe,CAAC;gBACjD,OAAO,EAAE,eAAe,EAAE,OAAO,IAAI,IAAI;aAC1C,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IACD,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,IAAI;QACzB,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,GAAG;QACvB,KAAK;QACL,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,OAA+B;IAC1D,IAAI,OAAO,EAAE,SAAS,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,GAAG,GAAG,CAAC,CAAA;IAChF,IAAI,OAAO,EAAE,cAAc,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,GAAG,CAAC,CAAA;IAChG,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["/**\n * Status snapshot for the control surfaces (ADP-033): a bounded, secret-free\n * view of the settings and the resource-health state of every configured\n * resource. Pure — the supervisor (or any future caller) gathers the inputs.\n */\nimport type { OperatingMode } from \"../core/scheduler/types.js\"\nimport { schedulerHealthKey } from \"../core/scheduler/index.js\"\nimport type { ModelRef } from \"../core/work-unit/types.js\"\nimport type { TierName } from \"../core/tier-policy/types.js\"\nimport { TIERS } from \"../core/tier-policy/types.js\"\nimport type { ResourceRecord } from \"../health/resource-health/types.js\"\nimport type { CapacityReading } from \"../health/capacity/types.js\"\nimport { resourceKeyID } from \"../health/resource-health/index.js\"\nimport type { SettingsV1 } from \"../state/settings/index.js\"\n\nexport interface TierResourceStatus {\n ref: ModelRef\n health: ResourceRecord[\"health\"]\n breaker: ResourceRecord[\"breaker\"][\"state\"]\n windowed: boolean\n /**\n * Wall-clock deadline of the active window (ms epoch); null when unwindowed.\n * Null — not undefined: the host RPC encoder rejects JSON objects carrying a\n * present-but-undefined key (\"Expected JSON value\"), which 500'd every\n * status.get once any resource was configured.\n */\n windowUntil: number | null\n /** Used capacity as a rounded whole percent; null when no reading exists. */\n usedPercent: number | null\n /** Wall-clock capacity reset deadline (ms epoch); null when unread. */\n resetAt: number | null\n}\n\nexport interface StatusSnapshot {\n mode: OperatingMode\n pin: ModelRef | null\n tiers: Readonly<Record<TierName, readonly TierResourceStatus[]>>\n parked: number\n receipts: number\n}\n\nexport function buildStatus(input: {\n settings: SettingsV1\n health: readonly ResourceRecord[]\n capacity: Readonly<Record<string, CapacityReading | null>>\n parked: number\n receipts: number\n}): StatusSnapshot {\n const byKey = new Map<string, ResourceRecord>()\n for (const record of input.health) byKey.set(resourceKeyID(record.key), record)\n const tiers = {} as Record<TierName, TierResourceStatus[]>\n for (const tier of TIERS) {\n tiers[tier] = input.settings.tiers[tier].map((ref) => {\n const record = byKey.get(schedulerHealthKey(ref))\n const capacityReading = input.capacity[schedulerHealthKey(ref)] ?? null\n return {\n ref,\n health: record?.health ?? \"healthy\",\n breaker: record?.breaker.state ?? \"closed\",\n windowed: record?.window !== undefined,\n windowUntil: record?.window?.until ?? null,\n usedPercent: capacityUsedPercent(capacityReading),\n resetAt: capacityReading?.resetAt ?? null,\n }\n })\n }\n return {\n mode: input.settings.mode,\n pin: input.settings.pin,\n tiers,\n parked: input.parked,\n receipts: input.receipts,\n }\n}\n\n/**\n * Derives the used-capacity percentage from a reading. Readings may carry any\n * subset of the two ratios: usedRatio directly, or the remainder inferred from\n * remainingRatio. A reading with neither ratio is still present (confidence,\n * resetAt may be set) but yields no percentage — null, not undefined.\n */\nfunction capacityUsedPercent(reading: CapacityReading | null): number | null {\n if (reading?.usedRatio !== undefined) return Math.round(reading.usedRatio * 100)\n if (reading?.remainingRatio !== undefined) return Math.round((1 - reading.remainingRatio) * 100)\n return null\n}\n"]}
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/rpc/status.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAG/D,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAA;AAGpD,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAA;AAsClE,MAAM,UAAU,WAAW,CAAC,KAO3B;IACC,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0B,CAAA;IAC/C,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,MAAM;QAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAA;IAC/E,MAAM,KAAK,GAAG,EAA4C,CAAA;IAC1D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YACnD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAA;YACjD,MAAM,eAAe,GAAG,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAA;YACvE,OAAO;gBACL,GAAG;gBACH,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,SAAS;gBACnC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,QAAQ;gBAC1C,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS;gBACtC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI;gBAC1C,WAAW,EAAE,mBAAmB,CAAC,eAAe,CAAC;gBACjD,OAAO,EAAE,eAAe,EAAE,OAAO,IAAI,IAAI;aAC1C,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IACD,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,IAAI;QACzB,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,GAAG;QACvB,KAAK;QACL,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,OAA+B;IAC1D,IAAI,OAAO,EAAE,SAAS,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,GAAG,GAAG,CAAC,CAAA;IAChF,IAAI,OAAO,EAAE,cAAc,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,GAAG,CAAC,CAAA;IAChG,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["/**\n * Status snapshot for the control surfaces (ADP-033): a bounded, secret-free\n * view of the settings and the resource-health state of every configured\n * resource. Pure — the supervisor (or any future caller) gathers the inputs.\n */\nimport type { OperatingMode } from \"../core/scheduler/types.js\"\nimport { schedulerHealthKey } from \"../core/scheduler/index.js\"\nimport type { ModelRef } from \"../core/work-unit/types.js\"\nimport type { TierName } from \"../core/tier-policy/types.js\"\nimport { TIERS } from \"../core/tier-policy/types.js\"\nimport type { ResourceRecord } from \"../health/resource-health/types.js\"\nimport type { CapacityReading } from \"../health/capacity/types.js\"\nimport { resourceKeyID } from \"../health/resource-health/index.js\"\nimport type { SettingsV1 } from \"../state/settings/index.js\"\n\nexport interface TierResourceStatus {\n ref: ModelRef\n health: ResourceRecord[\"health\"]\n breaker: ResourceRecord[\"breaker\"][\"state\"]\n windowed: boolean\n /**\n * Wall-clock deadline of the active window (ms epoch); null when unwindowed.\n * Null — not undefined: the host RPC encoder rejects JSON objects carrying a\n * present-but-undefined key (\"Expected JSON value\"), which 500'd every\n * status.get once any resource was configured.\n */\n windowUntil: number | null\n /** Used capacity as a rounded whole percent; null when no reading exists. */\n usedPercent: number | null\n /** Wall-clock capacity reset deadline (ms epoch); null when unread. */\n resetAt: number | null\n}\n\n/** A session whose parked work waits for its tier to recover (ADP-024). */\nexport interface WaitingSession {\n sessionID: string\n tier: TierName\n /** When the wake resumes it (ms epoch); null when no resource states a reset. */\n resumeAt: number | null\n}\n\nexport interface StatusSnapshot {\n mode: OperatingMode\n pin: ModelRef | null\n tiers: Readonly<Record<TierName, readonly TierResourceStatus[]>>\n parked: number\n waiting: readonly WaitingSession[]\n receipts: number\n}\n\nexport function buildStatus(input: {\n settings: SettingsV1\n health: readonly ResourceRecord[]\n capacity: Readonly<Record<string, CapacityReading | null>>\n parked: number\n waiting: readonly WaitingSession[]\n receipts: number\n}): StatusSnapshot {\n const byKey = new Map<string, ResourceRecord>()\n for (const record of input.health) byKey.set(resourceKeyID(record.key), record)\n const tiers = {} as Record<TierName, TierResourceStatus[]>\n for (const tier of TIERS) {\n tiers[tier] = input.settings.tiers[tier].map((ref) => {\n const record = byKey.get(schedulerHealthKey(ref))\n const capacityReading = input.capacity[schedulerHealthKey(ref)] ?? null\n return {\n ref,\n health: record?.health ?? \"healthy\",\n breaker: record?.breaker.state ?? \"closed\",\n windowed: record?.window !== undefined,\n windowUntil: record?.window?.until ?? null,\n usedPercent: capacityUsedPercent(capacityReading),\n resetAt: capacityReading?.resetAt ?? null,\n }\n })\n }\n return {\n mode: input.settings.mode,\n pin: input.settings.pin,\n tiers,\n parked: input.parked,\n waiting: input.waiting,\n receipts: input.receipts,\n }\n}\n\n/**\n * Derives the used-capacity percentage from a reading. Readings may carry any\n * subset of the two ratios: usedRatio directly, or the remainder inferred from\n * remainingRatio. A reading with neither ratio is still present (confidence,\n * resetAt may be set) but yields no percentage — null, not undefined.\n */\nfunction capacityUsedPercent(reading: CapacityReading | null): number | null {\n if (reading?.usedRatio !== undefined) return Math.round(reading.usedRatio * 100)\n if (reading?.remainingRatio !== undefined) return Math.round((1 - reading.remainingRatio) * 100)\n return null\n}\n"]}
@@ -5,6 +5,11 @@ export declare class ParkedWorkStore {
5
5
  constructor(storage: ParkedStorage);
6
6
  /** Rebuild from storage (restart behavior). A read failure yields an empty store. */
7
7
  load(): Promise<void>;
8
+ /**
9
+ * Re-read storage without the load-time failure policy: a read failure
10
+ * keeps the current records instead of forgetting parked work.
11
+ */
12
+ refresh(): Promise<void>;
8
13
  /** All parked records, insertion order. */
9
14
  list(): readonly ParkedRecord[];
10
15
  count(): number;
@@ -6,6 +6,11 @@
6
6
  * failures propagate to the caller (fail-open is the wiring layer's call), and
7
7
  * the in-memory list is only advanced after a successful write — memory and
8
8
  * disk never diverge.
9
+ *
10
+ * Storage is shared: the host may load the plugin twice in one process, and
11
+ * only one instance's store changes on each event. Every mutation therefore
12
+ * re-reads storage first, so a stale instance can neither resurrect a record
13
+ * another instance resumed nor resume it a second time.
9
14
  */
10
15
  import { parseParkedPayload } from "./parse.js";
11
16
  import { MAX_PARKED, MAX_PARKED_TEXT, PARKED_VERSION } from "./types.js";
@@ -34,6 +39,23 @@ export class ParkedWorkStore {
34
39
  }
35
40
  this.#records = parseParkedPayload(raw);
36
41
  }
42
+ /**
43
+ * Re-read storage without the load-time failure policy: a read failure
44
+ * keeps the current records instead of forgetting parked work.
45
+ */
46
+ refresh() {
47
+ return this.#serialize(() => this.#refresh());
48
+ }
49
+ async #refresh() {
50
+ let raw;
51
+ try {
52
+ raw = await this.storage.read();
53
+ }
54
+ catch {
55
+ return;
56
+ }
57
+ this.#records = parseParkedPayload(raw);
58
+ }
37
59
  /** All parked records, insertion order. */
38
60
  list() {
39
61
  return this.#records;
@@ -45,6 +67,7 @@ export class ParkedWorkStore {
45
67
  return this.#serialize(() => this.#park(record));
46
68
  }
47
69
  async #park(record) {
70
+ await this.#refresh();
48
71
  const bounded = {
49
72
  ...record,
50
73
  ...(record.detail !== undefined ? { detail: boundDetail(record.detail) } : {}),
@@ -64,6 +87,7 @@ export class ParkedWorkStore {
64
87
  return this.#serialize(() => this.#resume(id));
65
88
  }
66
89
  async #resume(id) {
90
+ await this.#refresh();
67
91
  const record = this.#records.find((entry) => entry.id === id);
68
92
  if (record === undefined)
69
93
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/state/parked-work/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Parked-work store (ADP-024). Records are keyed by unit id — parking the\n * same unit again replaces the record (latest park wins). The cap\n * `MAX_PARKED` evicts the oldest `parkedAt` first: parked work is waiting\n * work, and unbounded growth would mean recovery never happens. Write\n * failures propagate to the caller (fail-open is the wiring layer's call), and\n * the in-memory list is only advanced after a successful write — memory and\n * disk never diverge.\n */\nimport { parseParkedPayload } from \"./parse.js\"\nimport { MAX_PARKED, MAX_PARKED_TEXT, PARKED_VERSION } from \"./types.js\"\nimport type { ParkedPayload, ParkedRecord, ParkedStorage } from \"./types.js\"\n\nfunction boundDetail(raw: string): string {\n return raw.length > MAX_PARKED_TEXT ? raw.slice(0, MAX_PARKED_TEXT) : raw\n}\n\nexport class ParkedWorkStore {\n #records: ParkedRecord[] = []\n #queue: Promise<unknown> = Promise.resolve()\n\n constructor(private readonly storage: ParkedStorage) {}\n\n /** Rebuild from storage (restart behavior). A read failure yields an empty store. */\n load(): Promise<void> {\n return this.#serialize(() => this.#load())\n }\n\n async #load(): Promise<void> {\n let raw: unknown\n try {\n raw = await this.storage.read()\n } catch {\n this.#records = []\n return\n }\n this.#records = parseParkedPayload(raw)\n }\n\n /** All parked records, insertion order. */\n list(): readonly ParkedRecord[] {\n return this.#records\n }\n\n count(): number {\n return this.#records.length\n }\n\n park(record: ParkedRecord): Promise<void> {\n return this.#serialize(() => this.#park(record))\n }\n\n async #park(record: ParkedRecord): Promise<void> {\n const bounded: ParkedRecord = {\n ...record,\n ...(record.detail !== undefined ? { detail: boundDetail(record.detail) } : {}),\n }\n const withoutId = this.#records.filter((entry) => entry.id !== bounded.id)\n const next = this.#evictOldest([...withoutId, bounded])\n // Persist BEFORE committing memory: a failed resume must not silently\n // drop the parked unit (memory and disk stay consistent on failure).\n await this.#persist(next)\n this.#records = next\n }\n\n /**\n * Remove a parked record and return it so the execution layer knows what\n * to continue. `undefined` (no record, no write) when the id is unknown.\n */\n resume(id: string): Promise<ParkedRecord | undefined> {\n return this.#serialize(() => this.#resume(id))\n }\n\n async #resume(id: string): Promise<ParkedRecord | undefined> {\n const record = this.#records.find((entry) => entry.id === id)\n if (record === undefined) return undefined\n const next = this.#records.filter((entry) => entry.id !== id)\n await this.#persist(next)\n this.#records = next\n return record\n }\n\n #evictOldest(records: ParkedRecord[]): ParkedRecord[] {\n if (records.length <= MAX_PARKED) return records\n const byOldest = [...records].sort((a, b) => a.parkedAt - b.parkedAt)\n const dropped = new Set(byOldest.slice(0, records.length - MAX_PARKED).map((r) => r.id))\n return records.filter((record) => !dropped.has(record.id))\n }\n\n // Reads share the queue so reloads cannot overwrite newer commits. Recover\n // the queue after rejection while preserving the failing caller's promise.\n #serialize<T>(operation: () => Promise<T>): Promise<T> {\n const run = this.#queue.then(operation)\n this.#queue = run.catch(() => undefined)\n return run\n }\n\n /** Writes the given next state; the caller commits memory only on success. */\n async #persist(parked: readonly ParkedRecord[]): Promise<void> {\n const payload: ParkedPayload = {\n version: PARKED_VERSION,\n parked,\n }\n await this.storage.write(payload)\n }\n}\n"]}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/state/parked-work/store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Parked-work store (ADP-024). Records are keyed by unit id — parking the\n * same unit again replaces the record (latest park wins). The cap\n * `MAX_PARKED` evicts the oldest `parkedAt` first: parked work is waiting\n * work, and unbounded growth would mean recovery never happens. Write\n * failures propagate to the caller (fail-open is the wiring layer's call), and\n * the in-memory list is only advanced after a successful write — memory and\n * disk never diverge.\n *\n * Storage is shared: the host may load the plugin twice in one process, and\n * only one instance's store changes on each event. Every mutation therefore\n * re-reads storage first, so a stale instance can neither resurrect a record\n * another instance resumed nor resume it a second time.\n */\nimport { parseParkedPayload } from \"./parse.js\"\nimport { MAX_PARKED, MAX_PARKED_TEXT, PARKED_VERSION } from \"./types.js\"\nimport type { ParkedPayload, ParkedRecord, ParkedStorage } from \"./types.js\"\n\nfunction boundDetail(raw: string): string {\n return raw.length > MAX_PARKED_TEXT ? raw.slice(0, MAX_PARKED_TEXT) : raw\n}\n\nexport class ParkedWorkStore {\n #records: ParkedRecord[] = []\n #queue: Promise<unknown> = Promise.resolve()\n\n constructor(private readonly storage: ParkedStorage) {}\n\n /** Rebuild from storage (restart behavior). A read failure yields an empty store. */\n load(): Promise<void> {\n return this.#serialize(() => this.#load())\n }\n\n async #load(): Promise<void> {\n let raw: unknown\n try {\n raw = await this.storage.read()\n } catch {\n this.#records = []\n return\n }\n this.#records = parseParkedPayload(raw)\n }\n\n /**\n * Re-read storage without the load-time failure policy: a read failure\n * keeps the current records instead of forgetting parked work.\n */\n refresh(): Promise<void> {\n return this.#serialize(() => this.#refresh())\n }\n\n async #refresh(): Promise<void> {\n let raw: unknown\n try {\n raw = await this.storage.read()\n } catch {\n return\n }\n this.#records = parseParkedPayload(raw)\n }\n\n /** All parked records, insertion order. */\n list(): readonly ParkedRecord[] {\n return this.#records\n }\n\n count(): number {\n return this.#records.length\n }\n\n park(record: ParkedRecord): Promise<void> {\n return this.#serialize(() => this.#park(record))\n }\n\n async #park(record: ParkedRecord): Promise<void> {\n await this.#refresh()\n const bounded: ParkedRecord = {\n ...record,\n ...(record.detail !== undefined ? { detail: boundDetail(record.detail) } : {}),\n }\n const withoutId = this.#records.filter((entry) => entry.id !== bounded.id)\n const next = this.#evictOldest([...withoutId, bounded])\n // Persist BEFORE committing memory: a failed resume must not silently\n // drop the parked unit (memory and disk stay consistent on failure).\n await this.#persist(next)\n this.#records = next\n }\n\n /**\n * Remove a parked record and return it so the execution layer knows what\n * to continue. `undefined` (no record, no write) when the id is unknown.\n */\n resume(id: string): Promise<ParkedRecord | undefined> {\n return this.#serialize(() => this.#resume(id))\n }\n\n async #resume(id: string): Promise<ParkedRecord | undefined> {\n await this.#refresh()\n const record = this.#records.find((entry) => entry.id === id)\n if (record === undefined) return undefined\n const next = this.#records.filter((entry) => entry.id !== id)\n await this.#persist(next)\n this.#records = next\n return record\n }\n\n #evictOldest(records: ParkedRecord[]): ParkedRecord[] {\n if (records.length <= MAX_PARKED) return records\n const byOldest = [...records].sort((a, b) => a.parkedAt - b.parkedAt)\n const dropped = new Set(byOldest.slice(0, records.length - MAX_PARKED).map((r) => r.id))\n return records.filter((record) => !dropped.has(record.id))\n }\n\n // Reads share the queue so reloads cannot overwrite newer commits. Recover\n // the queue after rejection while preserving the failing caller's promise.\n #serialize<T>(operation: () => Promise<T>): Promise<T> {\n const run = this.#queue.then(operation)\n this.#queue = run.catch(() => undefined)\n return run\n }\n\n /** Writes the given next state; the caller commits memory only on success. */\n async #persist(parked: readonly ParkedRecord[]): Promise<void> {\n const payload: ParkedPayload = {\n version: PARKED_VERSION,\n parked,\n }\n await this.storage.write(payload)\n }\n}\n"]}
@@ -28,6 +28,8 @@ export declare class Supervisor {
28
28
  */
29
29
  static readonly MAX_MANUAL_SELECTIONS = 1024;
30
30
  constructor(options: SupervisorOptions);
31
+ /** Stop the wake timer; a disposed supervisor never re-arms it. */
32
+ dispose(): void;
31
33
  /** Handle one host report (event pump or session hook). */
32
34
  handleReport(report: EventReport): Promise<void>;
33
35
  /**