@coool/opencode-adaptive 1.2.3 → 1.4.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 (55) hide show
  1. package/README.md +1 -0
  2. package/dist/adapters/opencode/command.d.ts +30 -0
  3. package/dist/adapters/opencode/command.js +85 -0
  4. package/dist/adapters/opencode/command.js.map +1 -0
  5. package/dist/adapters/opencode/index.d.ts +1 -0
  6. package/dist/adapters/opencode/index.js +1 -0
  7. package/dist/adapters/opencode/index.js.map +1 -1
  8. package/dist/gui-experimental.cjs +3 -1
  9. package/dist/gui.d.ts +2 -1
  10. package/dist/gui.js +7 -5
  11. package/dist/gui.js.map +1 -1
  12. package/dist/index.js +14 -2
  13. package/dist/index.js.map +1 -1
  14. package/dist/rpc/handlers.d.ts +6 -0
  15. package/dist/rpc/handlers.js +24 -18
  16. package/dist/rpc/handlers.js.map +1 -1
  17. package/dist/tui.js +5 -3
  18. package/dist/tui.js.map +1 -1
  19. package/dist/ui/{tui → flow}/controller.d.ts +5 -8
  20. package/dist/ui/{tui → flow}/controller.js +4 -9
  21. package/dist/ui/flow/controller.js.map +1 -0
  22. package/dist/ui/{tui → flow}/menu.d.ts +2 -0
  23. package/dist/ui/{tui → flow}/menu.js +1 -1
  24. package/dist/ui/flow/menu.js.map +1 -0
  25. package/dist/ui/flow/options.d.ts +12 -0
  26. package/dist/ui/flow/options.js.map +1 -0
  27. package/dist/ui/{tui → flow}/receipts.d.ts +1 -1
  28. package/dist/ui/{tui → flow}/receipts.js +4 -7
  29. package/dist/ui/flow/receipts.js.map +1 -0
  30. package/dist/ui/{tui → flow}/status.d.ts +0 -2
  31. package/dist/ui/{tui → flow}/status.js +0 -6
  32. package/dist/ui/flow/status.js.map +1 -0
  33. package/dist/ui/{tui → flow}/tiers.d.ts +2 -9
  34. package/dist/ui/{tui → flow}/tiers.js +5 -25
  35. package/dist/ui/flow/tiers.js.map +1 -0
  36. package/dist/ui/gui/api.d.ts +6 -2
  37. package/dist/ui/gui/api.js +4 -4
  38. package/dist/ui/gui/api.js.map +1 -1
  39. package/dist/ui/gui/flow.d.ts +23 -0
  40. package/dist/ui/gui/flow.js +123 -0
  41. package/dist/ui/gui/flow.js.map +1 -0
  42. package/dist/ui/gui/view.js +2 -2
  43. package/dist/ui/gui/view.js.map +1 -1
  44. package/dist/ui/tui/search.d.ts +9 -0
  45. package/dist/ui/tui/search.js +10 -0
  46. package/dist/ui/tui/search.js.map +1 -0
  47. package/package.json +2 -1
  48. package/dist/ui/tui/controller.js.map +0 -1
  49. package/dist/ui/tui/menu.js.map +0 -1
  50. package/dist/ui/tui/options.d.ts +0 -19
  51. package/dist/ui/tui/options.js.map +0 -1
  52. package/dist/ui/tui/receipts.js.map +0 -1
  53. package/dist/ui/tui/status.js.map +0 -1
  54. package/dist/ui/tui/tiers.js.map +0 -1
  55. /package/dist/ui/{tui → flow}/options.js +0 -0
package/README.md CHANGED
@@ -86,6 +86,7 @@ An outage never moves work to a stronger tier. Work only moves up on real eviden
86
86
  - A task that moved up a tier stays there. The next task is routed again from scratch.
87
87
  - Usage figures come from response headers and are best effort. Providers that use WebSockets don't expose them.
88
88
  - The browser/desktop settings page is experimental. No released OpenCode loads it yet, so use `/adaptive`.
89
+ - In the web UI (including on mobile) type `/web-adaptive` for the mode and model status, or `/web-adaptive on`, `off`, `observe`, `active` to switch the mode. It is a server command, which the web app lists in the composer's slash menu.
89
90
 
90
91
  ## Troubleshooting
91
92
 
@@ -0,0 +1,30 @@
1
+ import type { OperatingMode } from "../../core/scheduler/types.js";
2
+ import type { StatusSnapshot } from "../../rpc/status.js";
3
+ export interface CommandSource {
4
+ status(): Promise<StatusSnapshot>;
5
+ setMode(mode: OperatingMode): Promise<{
6
+ readonly ok: true;
7
+ } | {
8
+ readonly ok: false;
9
+ readonly errors: readonly string[];
10
+ }>;
11
+ }
12
+ /**
13
+ * The web UI's way to see and switch the mode (ADP-034). The host's browser
14
+ * app never mounts plugin GUI bundles, but it lists server commands in the
15
+ * composer's slash menu, so this is the one channel that reaches it. The name
16
+ * differs from the TUI's `/adaptive` on purpose: a same-named server command
17
+ * appears twice in the TUI palette. Replies are plain text posted without
18
+ * waking the model, one message per line: the web timeline shows each message
19
+ * as a single truncated row, so a long reply would hide its end.
20
+ */
21
+ export declare function adaptiveCommand(source: CommandSource, say: (sessionID: string, text: string) => Promise<unknown>): {
22
+ name: string;
23
+ description: string;
24
+ execute: (input: {
25
+ sessionID: string;
26
+ prompt: {
27
+ text: string;
28
+ };
29
+ }) => Promise<void>;
30
+ };
@@ -0,0 +1,85 @@
1
+ import { OPERATING_MODES } from "../../core/scheduler/types.js";
2
+ import { logFailOpen } from "../../supervisor/log.js";
3
+ import { modeDescription } from "../../ui/flow/menu.js";
4
+ import { healthEntries, stateCell } from "../../ui/flow/status.js";
5
+ import { resourceLabel } from "../../ui/flow/tiers.js";
6
+ const COMMAND_NAME = "web-adaptive";
7
+ /** `on` is the everyday word for `active`. */
8
+ const MODE_WORDS = ["on", "off", "observe", "active"];
9
+ const USAGE = `Usage: /${COMMAND_NAME} [status | ${MODE_WORDS.join(" | ")}]`;
10
+ /** How to switch, appended to the status so the next step is on screen. */
11
+ const HELP = [
12
+ "Switch the mode:",
13
+ ...OPERATING_MODES.map((mode) => `/${COMMAND_NAME} ${mode === "active" ? "on" : mode} — ${modeDescription(mode)}`),
14
+ `/${COMMAND_NAME} — show this status again`,
15
+ ];
16
+ function parse(text) {
17
+ const argument = text
18
+ .trim()
19
+ .replace(new RegExp(`^/${COMMAND_NAME}\\b\\s*`, "i"), "")
20
+ .trim()
21
+ .toLowerCase();
22
+ if (argument === "" || argument === "status")
23
+ return { kind: "status" };
24
+ if (argument === "on")
25
+ return { kind: "mode", mode: "active" };
26
+ const mode = OPERATING_MODES.find((candidate) => candidate === argument);
27
+ return mode === undefined ? { kind: "usage" } : { kind: "mode", mode };
28
+ }
29
+ function modeLine(prefix, mode) {
30
+ return `${prefix} ${mode} — ${modeDescription(mode)}.`;
31
+ }
32
+ /** One model as `provider/model (tier, health[, window, usage, pin])`. */
33
+ function modelLine(entry, pin) {
34
+ const state = stateCell(entry.status, pin);
35
+ const detail = state === "-" ? entry.status.health : `${entry.status.health}, ${state}`;
36
+ return `${resourceLabel(entry.status.ref)} (${entry.tier}, ${detail})`;
37
+ }
38
+ function statusLines(snapshot) {
39
+ const entries = healthEntries(snapshot);
40
+ const models = entries.length === 0
41
+ ? ["No models configured yet."]
42
+ : entries.map((entry) => modelLine(entry, snapshot.pin));
43
+ return [modeLine("Adaptive:", snapshot.mode), ...models, ...HELP];
44
+ }
45
+ async function reply(source, text) {
46
+ const action = parse(text);
47
+ switch (action.kind) {
48
+ case "usage":
49
+ return [USAGE];
50
+ case "status":
51
+ return statusLines(await source.status());
52
+ case "mode": {
53
+ const result = await source.setMode(action.mode);
54
+ return [
55
+ result.ok
56
+ ? modeLine("Adaptive switched to", action.mode)
57
+ : `Adaptive could not switch mode: ${result.errors.join("; ")}`,
58
+ ];
59
+ }
60
+ }
61
+ }
62
+ /**
63
+ * The web UI's way to see and switch the mode (ADP-034). The host's browser
64
+ * app never mounts plugin GUI bundles, but it lists server commands in the
65
+ * composer's slash menu, so this is the one channel that reaches it. The name
66
+ * differs from the TUI's `/adaptive` on purpose: a same-named server command
67
+ * appears twice in the TUI palette. Replies are plain text posted without
68
+ * waking the model, one message per line: the web timeline shows each message
69
+ * as a single truncated row, so a long reply would hide its end.
70
+ */
71
+ export function adaptiveCommand(source, say) {
72
+ return {
73
+ name: COMMAND_NAME,
74
+ description: `Adaptive mode and status: /${COMMAND_NAME} | on | off | observe | active`,
75
+ execute: async (input) => {
76
+ const lines = await reply(source, input.prompt.text).catch((error) => {
77
+ logFailOpen("command", error);
78
+ return ["Adaptive could not complete that command."];
79
+ });
80
+ for (const line of lines)
81
+ await say(input.sessionID, line);
82
+ },
83
+ };
84
+ }
85
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../../src/adapters/opencode/command.ts"],"names":[],"mappings":"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","sourcesContent":["import { OPERATING_MODES } from \"../../core/scheduler/types.js\"\nimport type { OperatingMode } from \"../../core/scheduler/types.js\"\nimport type { StatusSnapshot } from \"../../rpc/status.js\"\nimport { logFailOpen } from \"../../supervisor/log.js\"\nimport { modeDescription } from \"../../ui/flow/menu.js\"\nimport { healthEntries, stateCell } from \"../../ui/flow/status.js\"\nimport { resourceLabel } from \"../../ui/flow/tiers.js\"\n\nconst COMMAND_NAME = \"web-adaptive\"\n\n/** `on` is the everyday word for `active`. */\nconst MODE_WORDS = [\"on\", \"off\", \"observe\", \"active\"] as const\n\nconst USAGE = `Usage: /${COMMAND_NAME} [status | ${MODE_WORDS.join(\" | \")}]`\n\n/** How to switch, appended to the status so the next step is on screen. */\nconst HELP = [\n \"Switch the mode:\",\n ...OPERATING_MODES.map(\n (mode) => `/${COMMAND_NAME} ${mode === \"active\" ? \"on\" : mode} — ${modeDescription(mode)}`,\n ),\n `/${COMMAND_NAME} — show this status again`,\n]\n\nexport interface CommandSource {\n status(): Promise<StatusSnapshot>\n setMode(\n mode: OperatingMode,\n ): Promise<{ readonly ok: true } | { readonly ok: false; readonly errors: readonly string[] }>\n}\n\ntype Action = { kind: \"status\" } | { kind: \"mode\"; mode: OperatingMode } | { kind: \"usage\" }\n\nfunction parse(text: string): Action {\n const argument = text\n .trim()\n .replace(new RegExp(`^/${COMMAND_NAME}\\\\b\\\\s*`, \"i\"), \"\")\n .trim()\n .toLowerCase()\n if (argument === \"\" || argument === \"status\") return { kind: \"status\" }\n if (argument === \"on\") return { kind: \"mode\", mode: \"active\" }\n const mode = OPERATING_MODES.find((candidate) => candidate === argument)\n return mode === undefined ? { kind: \"usage\" } : { kind: \"mode\", mode }\n}\n\nfunction modeLine(prefix: string, mode: OperatingMode): string {\n return `${prefix} ${mode} — ${modeDescription(mode)}.`\n}\n\n/** One model as `provider/model (tier, health[, window, usage, pin])`. */\nfunction modelLine(entry: ReturnType<typeof healthEntries>[number], pin: StatusSnapshot[\"pin\"]) {\n const state = stateCell(entry.status, pin)\n const detail = state === \"-\" ? entry.status.health : `${entry.status.health}, ${state}`\n return `${resourceLabel(entry.status.ref)} (${entry.tier}, ${detail})`\n}\n\nfunction statusLines(snapshot: StatusSnapshot): string[] {\n const entries = healthEntries(snapshot)\n const models =\n entries.length === 0\n ? [\"No models configured yet.\"]\n : entries.map((entry) => modelLine(entry, snapshot.pin))\n return [modeLine(\"Adaptive:\", snapshot.mode), ...models, ...HELP]\n}\n\nasync function reply(source: CommandSource, text: string): Promise<string[]> {\n const action = parse(text)\n switch (action.kind) {\n case \"usage\":\n return [USAGE]\n case \"status\":\n return statusLines(await source.status())\n case \"mode\": {\n const result = await source.setMode(action.mode)\n return [\n result.ok\n ? modeLine(\"Adaptive switched to\", action.mode)\n : `Adaptive could not switch mode: ${result.errors.join(\"; \")}`,\n ]\n }\n }\n}\n\n/**\n * The web UI's way to see and switch the mode (ADP-034). The host's browser\n * app never mounts plugin GUI bundles, but it lists server commands in the\n * composer's slash menu, so this is the one channel that reaches it. The name\n * differs from the TUI's `/adaptive` on purpose: a same-named server command\n * appears twice in the TUI palette. Replies are plain text posted without\n * waking the model, one message per line: the web timeline shows each message\n * as a single truncated row, so a long reply would hide its end.\n */\nexport function adaptiveCommand(\n source: CommandSource,\n say: (sessionID: string, text: string) => Promise<unknown>,\n) {\n return {\n name: COMMAND_NAME,\n description: `Adaptive mode and status: /${COMMAND_NAME} | on | off | observe | active`,\n execute: async (input: { sessionID: string; prompt: { text: string } }): Promise<void> => {\n const lines = await reply(source, input.prompt.text).catch((error: unknown) => {\n logFailOpen(\"command\", error)\n return [\"Adaptive could not complete that command.\"]\n })\n for (const line of lines) await say(input.sessionID, line)\n },\n }\n}\n"]}
@@ -13,4 +13,5 @@ export { registerSessionHooks } from "./hooks.js";
13
13
  export type { SessionHookHandlers, SessionHookSource } from "./hooks.js";
14
14
  export { ESCALATION_TOOL_NAME, escalationTool } from "./escalation-tool.js";
15
15
  export { tierTool } from "./tier-tool.js";
16
+ export { adaptiveCommand } from "./command.js";
16
17
  export type { EscalationRecorder } from "./escalation-tool.js";
@@ -6,4 +6,5 @@ export { SESSION_EVENT_TYPES, subscribeSessions } from "./events.js";
6
6
  export { registerSessionHooks } from "./hooks.js";
7
7
  export { ESCALATION_TOOL_NAME, escalationTool } from "./escalation-tool.js";
8
8
  export { tierTool } from "./tier-tool.js";
9
+ export { adaptiveCommand } from "./command.js";
9
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/opencode/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAU/F,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE3E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1C,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEjD,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA","sourcesContent":["export { BODY_EXCERPT_LENGTH, mapHttpResponse, mapSessionEvent, modelRefOf } from \"./report.js\"\nexport type { SessionEvent } from \"./report.js\"\nexport type {\n AdapterStructuredError,\n EventReport,\n ExecutionReport,\n HttpResponseReport,\n IdleReport,\n ModelRequestReport,\n} from \"./types.js\"\nexport { candidateFor, listCandidates, mapModelInfo } from \"./inventory.js\"\nexport type { ModelSource } from \"./inventory.js\"\nexport { sessionOps } from \"./session-ops.js\"\nexport type { SessionOpsHooks, SessionOpsSource } from \"./session-ops.js\"\nexport { storageSeam } from \"./storage.js\"\nexport type { JsonSeam, StorageSource } from \"./storage.js\"\nexport { SESSION_EVENT_TYPES, subscribeSessions } from \"./events.js\"\nexport type { EventSource, SessionSubscription } from \"./events.js\"\nexport { registerSessionHooks } from \"./hooks.js\"\nexport type { SessionHookHandlers, SessionHookSource } from \"./hooks.js\"\nexport { ESCALATION_TOOL_NAME, escalationTool } from \"./escalation-tool.js\"\nexport { tierTool } from \"./tier-tool.js\"\nexport type { EscalationRecorder } from \"./escalation-tool.js\"\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/opencode/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAU/F,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE3E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1C,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEjD,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA","sourcesContent":["export { BODY_EXCERPT_LENGTH, mapHttpResponse, mapSessionEvent, modelRefOf } from \"./report.js\"\nexport type { SessionEvent } from \"./report.js\"\nexport type {\n AdapterStructuredError,\n EventReport,\n ExecutionReport,\n HttpResponseReport,\n IdleReport,\n ModelRequestReport,\n} from \"./types.js\"\nexport { candidateFor, listCandidates, mapModelInfo } from \"./inventory.js\"\nexport type { ModelSource } from \"./inventory.js\"\nexport { sessionOps } from \"./session-ops.js\"\nexport type { SessionOpsHooks, SessionOpsSource } from \"./session-ops.js\"\nexport { storageSeam } from \"./storage.js\"\nexport type { JsonSeam, StorageSource } from \"./storage.js\"\nexport { SESSION_EVENT_TYPES, subscribeSessions } from \"./events.js\"\nexport type { EventSource, SessionSubscription } from \"./events.js\"\nexport { registerSessionHooks } from \"./hooks.js\"\nexport type { SessionHookHandlers, SessionHookSource } from \"./hooks.js\"\nexport { ESCALATION_TOOL_NAME, escalationTool } from \"./escalation-tool.js\"\nexport { tierTool } from \"./tier-tool.js\"\nexport { adaptiveCommand } from \"./command.js\"\nexport type { EscalationRecorder } from \"./escalation-tool.js\"\n"]}
@@ -1 +1,3 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const n=require("solid-js/web"),w=require("@opencode/plugin/gui"),u=require("solid-js"),M=require("@opencode/schema/rpc"),q=128,L="coool.adaptive",C="coool.adaptive",l={internal:f()};function r(e,t){return{"~standard":{version:1,vendor:C,validate:a=>e(a)?{value:a}:{issues:[{message:t}]}}}}function f(){return{"~standard":{version:1,vendor:C,validate:e=>e===void 0?{value:void 0}:{issues:[{message:"unexpected input: no input expected"}]}}}}const i=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),g=e=>typeof e=="string",m=e=>{if(!(i(e)&&g(e.sessionID)))return!1;const t=e.sessionID;return t.length>0&&t.length<=q},N=e=>i(e)&&g(e.sessionID)&&(e.mode===null||g(e.mode)),V=e=>i(e)&&g(e.sessionID)&&(e.tier===null||g(e.tier)),F=M.Rpc.define({id:L,methods:{"settings.get":{errors:l,input:f(),output:r(i,"output must be an object")},"settings.set":{errors:l,input:r(i,"input must be an object"),output:r(i,"output must be an object")},"status.get":{errors:l,input:f(),output:r(i,"output must be an object")},"receipts.list":{errors:l,input:f(),output:r(Array.isArray,"output must be an array")},"session-mode.get":{errors:l,input:r(m,"input must be an object with a sessionID string"),output:r(i,"output must be an object")},"session-mode.set":{errors:l,input:r(N,"input must be an object with a sessionID string and a mode string or null"),output:r(i,"output must be an object")},"tier-request.get":{errors:l,input:r(m,"input must be an object with a sessionID string"),output:r(i,"output must be an object")},"tier-request.set":{errors:l,input:r(V,"input must be an object with a sessionID string and a tier string or null"),output:r(i,"output must be an object")}},events:{"settings-changed":{schema:r(i,"event data must be an object")},"session-mode-changed":{schema:r(m,"event data must be an object with a sessionID string")},"tier-request-changed":{schema:r(m,"event data must be an object with a sessionID string")}}}),D=["off","observe","active"],G=["quick","standard","advanced"];function W(e){const t=e.rpc(F);return{status:()=>t["status.get"]().catch(()=>{}),receipts:()=>t["receipts.list"]().catch(()=>{}),update:async a=>{try{return(await t["settings.set"](a)).ok}catch{return!1}},watchSettings:a=>t.events.on("settings-changed",()=>{a()})}}function $(e){const t=`${e.providerID}/${e.id}`;return e.variant===void 0?t:`${t}@${e.variant}`}function x(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 j(e){switch(e.kind){case"apply":{const t=e.tier===void 0?"":` [${e.tier}]`;return`apply ${$(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} → ${$(e.target)} [${e.tier}]`;case"observe-only":return`observe: ${j(e.would)}`}}function z(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)} ${j(e.decision)} — ${z(e.outcome)}`}const J="connection";function X(e){if(e===void 0)return[{id:J,label:"Server",value:"disconnected",tone:"warning"}];const t=[{id:"mode",label:"Mode",value:e.mode},{id:"pin",label:"Pin",value:e.pin===null?"none":$(e.pin)}];for(const a of G){const c=e.tiers[a],S=c.length===0?"empty":c.map(b=>`${$(b.ref)}${x(b)}`).join(", ");t.push({id:`tier-${a}`,label:`Tier ${a}`,value:S})}return t.push({id:"parked",label:"Parked",value:String(e.parked)}),t.push({id:"receipts",label:"Receipts",value:String(e.receipts)}),t}function B(e){return D.map(t=>({value:t,title:t===e?`${t} (current)`:t}))}function K(e){return D.find(t=>t===e)}function Q(e){return e.map(t=>H(t))}var U=n.template("<span class=adaptive-saved role=status>Saved"),Y=n.template("<div class=adaptive-receipts>"),Z=n.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>'),ee=n.template("<tr class=adaptive-row><td class=adaptive-row-label></td><td class=adaptive-row-value>"),te=n.template("<option>"),ne=n.template("<div class=adaptive-receipt-line>");const re=5;function y(e){const[t,a]=u.createSignal(void 0),[c,S]=u.createSignal("observe"),[b,R]=u.createSignal([]),[A,E]=u.createSignal(!1),_=()=>{e.api.status().then(s=>{a(s),s!==void 0&&S(s.mode)}),e.api.receipts().then(s=>{R(s??[])})};u.onCleanup(e.api.watchSettings(_)),_();const P=s=>{const p=K(s.value);s.value=c(),p!==void 0&&e.api.update({mode:p}).then(I=>{E(I),_()})};return(()=>{var s=Z(),p=s.firstChild,I=p.firstChild,T=p.nextSibling,k=T.firstChild,h=k.nextSibling;return n.insert(I,n.createComponent(u.For,{get each(){return X(t())},children:o=>(()=>{var d=ee(),v=d.firstChild,O=v.nextSibling;return n.insert(v,()=>o.label),n.insert(O,()=>o.value),n.effect(()=>n.setAttribute(d,"data-tone",o.tone)),d})()})),h.addEventListener("change",o=>{P(o.currentTarget)}),n.insert(h,n.createComponent(u.For,{get each(){return B(c())},children:o=>(()=>{var d=te();return n.insert(d,()=>o.title),n.effect(()=>d.value=o.value),d})()})),n.insert(s,n.createComponent(u.Show,{get when(){return A()},get children(){return U()}}),null),n.insert(s,n.createComponent(u.Show,{get when(){return t()!==void 0},get children(){var o=Y();return n.insert(o,n.createComponent(u.For,{get each(){return Q(b()).slice(0,re)},children:d=>(()=>{var v=ne();return n.insert(v,d),v})()})),o}}),null),n.effect(()=>h.disabled=t()===void 0),n.effect(()=>h.value=c()),s})()}const se=w.Plugin.define({setup(e){const t=W(e.client);e.add(w.SettingsPage,{id:"adaptive",title:"OpenCode Adaptive",entries:[{id:"adaptive",title:"OpenCode Adaptive",description:"Execution tier routing, operating mode and resource health"}],render:()=>n.createComponent(y,{api:t})}),e.add(w.Command,{id:"open",title:"OpenCode Adaptive",group:"Adaptive",run:()=>{e.layout.settings("adaptive")}})}});exports.AdaptiveStatusPage=y;exports.default=se;
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;
package/dist/gui.d.ts CHANGED
@@ -10,7 +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 command that opens it; no routing logic lives here (ADP-034/035).
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).
14
15
  */
15
16
  declare const _default: Plugin.Definition<{}>;
16
17
  export default _default;
package/dist/gui.js CHANGED
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "solid-js/jsx-runtime";
2
2
  import { Command, 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
+ import { openAdaptive, rpcLocation } from "./ui/gui/flow.js";
5
6
  import { modeChoices, parseModeValue, receiptLines, statusRows } from "./ui/gui/view.js";
6
7
  const RECEIPTS_PREVIEW_LIMIT = 5;
7
8
  /**
@@ -43,11 +44,12 @@ export function AdaptiveStatusPage(props) {
43
44
  }
44
45
  /**
45
46
  * GUI plugin entry (the `./gui-experimental` bundle default export). Thin adapter: registers a native
46
- * SettingsPage and a palette command that opens it; no routing logic lives here (ADP-034/035).
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).
47
49
  */
48
50
  export default Plugin.define({
49
51
  setup(context) {
50
- const api = adaptiveGuiApi(context.client);
52
+ const api = adaptiveGuiApi(context.client, () => rpcLocation(context));
51
53
  context.add(SettingsPage, {
52
54
  id: "adaptive",
53
55
  title: "OpenCode Adaptive",
@@ -64,9 +66,9 @@ export default Plugin.define({
64
66
  id: "open",
65
67
  title: "OpenCode Adaptive",
66
68
  group: "Adaptive",
67
- run: () => {
68
- context.layout.settings("adaptive");
69
- },
69
+ suggested: true,
70
+ slash: { name: "adaptive" },
71
+ run: () => openAdaptive(context),
70
72
  });
71
73
  },
72
74
  });
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 { 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 command that opens it; no routing logic lives here (ADP-034/035).\n */\nexport default Plugin.define({\n setup(context: Context): void {\n const api = adaptiveGuiApi(context.client)\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 run: () => {\n context.layout.settings(\"adaptive\")\n },\n })\n },\n})\n"]}
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"]}
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { Plugin } from "@opencode/plugin";
2
2
  import { isCodexHeaderName } from "./adapters/codex-capacity/index.js";
3
- import { escalationTool, registerSessionHooks, subscribeSessions, tierTool, } from "./adapters/opencode/index.js";
3
+ import { adaptiveCommand, escalationTool, registerSessionHooks, subscribeSessions, tierTool, } from "./adapters/opencode/index.js";
4
4
  import { PLUGIN_ID } from "./plugin-id.js";
5
5
  import { adaptiveRpc } from "./rpc/definition.js";
6
- import { adaptiveRpcHandlers } from "./rpc/handlers.js";
6
+ import { adaptiveRpcHandlers, patchSettings } from "./rpc/handlers.js";
7
7
  import { Supervisor, logFailOpen } from "./supervisor/index.js";
8
8
  export { PLUGIN_ID } from "./plugin-id.js";
9
9
  /**
@@ -92,6 +92,18 @@ export default Plugin.define({
92
92
  const rpcRegistration = await context.rpc.register(adaptiveRpc, adaptiveRpcHandlers(supervisor, emit));
93
93
  rpcHolder.registration = rpcRegistration;
94
94
  disposals.push({ name: "rpc", dispose: () => rpcRegistration.dispose() });
95
+ // The web UI never mounts plugin GUI bundles, but it lists server
96
+ // commands in the composer's slash menu (ADP-034).
97
+ const commandRegistration = await context.command.transform((editor) => {
98
+ editor.add(adaptiveCommand({
99
+ status: () => supervisor.status(),
100
+ setMode: (mode) => patchSettings(supervisor, emit, { mode }),
101
+ },
102
+ // The web timeline shows a synthetic message as a notice only when
103
+ // it has a description, and labels it with that description.
104
+ (sessionID, text) => context.session.synthetic({ sessionID, text, description: text, resume: false })));
105
+ });
106
+ disposals.push({ name: "command", dispose: () => commandRegistration.dispose() });
95
107
  }
96
108
  catch (error) {
97
109
  // Roll back everything already registered so a failure at the tool or
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 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 } 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 } 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 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"]}
@@ -47,6 +47,12 @@ export interface AdaptiveRpcCallContext<Name extends MethodName> {
47
47
  }
48
48
  type HandlerErrorOf<Name extends MethodName> = Rpc.HandlerError<Methods[Name]>;
49
49
  type HandlerOf<Name extends MethodName> = (input: Rpc.Output<Methods[Name]["input"]>, context: AdaptiveRpcCallContext<Name>) => Promise<Rpc.HandlerOutput<Methods[Name]["output"]> | HandlerErrorOf<Name>>;
50
+ /**
51
+ * One settings write for every surface (RPC and the web command): persist,
52
+ * then announce so open views refresh. An internal failure degrades to an
53
+ * error result (fail-open, ADP-031).
54
+ */
55
+ export declare function patchSettings(source: Pick<AdaptiveRpcSource, "updateSettings">, emit: AdaptiveRpcEmit | undefined, patch: SettingsPatch): Promise<PatchResult>;
50
56
  export declare function adaptiveRpcHandlers(source: AdaptiveRpcSource, emit?: AdaptiveRpcEmit): {
51
57
  [Name in MethodName]: HandlerOf<Name>;
52
58
  };
@@ -31,29 +31,35 @@ function tierRequestSetError(input) {
31
31
  }
32
32
  return undefined;
33
33
  }
34
+ /**
35
+ * One settings write for every surface (RPC and the web command): persist,
36
+ * then announce so open views refresh. An internal failure degrades to an
37
+ * error result (fail-open, ADP-031).
38
+ */
39
+ export async function patchSettings(source, emit, patch) {
40
+ try {
41
+ const result = await source.updateSettings(patch);
42
+ if (result.ok && emit !== undefined) {
43
+ try {
44
+ await emit("settings-changed", { version: result.settings.version });
45
+ }
46
+ catch (error) {
47
+ logFailOpen("rpc-emit", error);
48
+ }
49
+ }
50
+ return result;
51
+ }
52
+ catch (error) {
53
+ logFailOpen("rpc-set", error);
54
+ return { ok: false, errors: ["internal error"] };
55
+ }
56
+ }
34
57
  export function adaptiveRpcHandlers(source, emit) {
35
58
  return {
36
59
  "settings.get": (_input, context) => source
37
60
  .settings()
38
61
  .catch((error) => context.error("internal", String(error).slice(0, LOG_ERROR_LENGTH))),
39
- "settings.set": async (patch) => {
40
- try {
41
- const result = await source.updateSettings(patch);
42
- if (result.ok && emit !== undefined) {
43
- try {
44
- await emit("settings-changed", { version: result.settings.version });
45
- }
46
- catch (error) {
47
- logFailOpen("rpc-emit", error);
48
- }
49
- }
50
- return result;
51
- }
52
- catch (error) {
53
- logFailOpen("rpc-set", error);
54
- return { ok: false, errors: ["internal error"] };
55
- }
56
- },
62
+ "settings.set": (patch) => patchSettings(source, emit, patch),
57
63
  "status.get": (_input, context) => source
58
64
  .status()
59
65
  .catch((error) => context.error("internal", String(error).slice(0, LOG_ERROR_LENGTH))),
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/rpc/handlers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RPC handler binding (ADP-034/036): adapts the supervisor's state API to the\n * typed RPC methods. Handlers never throw for expected outcomes — an invalid\n * patch is a per-field `PatchResult`, and an internal failure degrades to\n * `{ ok: false }` (fail-open, ADP-031). Read methods declare internal failures\n * through the call context's error factory: a plain throw becomes a server\n * defect whose generic `rpc.internal` message hides the cause (verified on the\n * sibling @coool plugins).\n */\nimport { OPERATING_MODES } from \"../core/scheduler/types.js\"\nimport type { OperatingMode } from \"../core/scheduler/types.js\"\nimport { TIERS } from \"../core/tier-policy/types.js\"\nimport type { TierName } from \"../core/tier-policy/types.js\"\nimport { LOG_ERROR_LENGTH, logFailOpen } from \"../supervisor/log.js\"\nimport { isValidSessionID } from \"../state/session-modes/index.js\"\nimport type { PatchResult } from \"../state/settings/types.js\"\nimport type { SettingsPatch, SettingsV1 } from \"../state/settings/index.js\"\nimport type { Rpc } from \"@opencode/schema/rpc\"\nimport type { Receipt } from \"../state/receipts/types.js\"\nimport type { StatusSnapshot } from \"./status.js\"\nimport type { AdaptiveRpc } from \"./definition.js\"\n\nexport interface AdaptiveRpcSource {\n settings(): Promise<SettingsV1>\n updateSettings(patch: SettingsPatch): Promise<PatchResult>\n status(): Promise<StatusSnapshot>\n receipts(): Promise<Receipt[]>\n getSessionMode(sessionID: string): Promise<OperatingMode | null>\n setSessionMode(sessionID: string, mode: OperatingMode | null): Promise<void>\n getTierRequest(sessionID: string): Promise<TierName | null>\n noteTierRequest(sessionID: string, tier: string): boolean\n setTierRequest(sessionID: string, tier: TierName | null): Promise<void>\n}\n\nexport type SettingsChangedEmit = (name: \"settings-changed\", data: { version: number }) => unknown\n\n/**\n * Correlated emit: each call site pairs the event name with its own payload\n * shape — an invalid pairing is a type error, and the registration site can\n * forward the tuple to the host emitter untouched.\n */\nexport type AdaptiveRpcEmit = (\n ...args:\n | [\"settings-changed\", { version: number }]\n | [\"session-mode-changed\", { sessionID: string }]\n | [\"tier-request-changed\", { sessionID: string }]\n) => unknown\n\n/** A successful tier-request write; mirrors the SessionModeSetResult convention. */\nexport type TierRequestSetResult =\n { readonly ok: true } | { readonly ok: false; readonly errors: readonly string[] }\n\n/** Per-field errors for a session-mode patch, mirroring the settings contract. */\nfunction sessionModeSetError(input: {\n sessionID: string\n mode: OperatingMode | null\n}): string | undefined {\n if (!isValidSessionID(input.sessionID)) {\n return \"sessionID must be a non-empty string of at most 128 characters\"\n }\n if (input.mode !== null && !(OPERATING_MODES as readonly string[]).includes(input.mode)) {\n return `mode must be one of ${OPERATING_MODES.join(\", \")} or null`\n }\n return undefined\n}\n\n/** Same contract for a tier-request patch (ADP-055): vocabulary-gated tier. */\nfunction tierRequestSetError(input: {\n sessionID: string\n tier: string | null\n}): string | undefined {\n if (!isValidSessionID(input.sessionID)) {\n return \"sessionID must be a non-empty string of at most 128 characters\"\n }\n if (input.tier !== null && !(TIERS as readonly string[]).includes(input.tier)) {\n return `tier must be one of ${TIERS.join(\", \")} or null`\n }\n return undefined\n}\n\ntype Methods = AdaptiveRpc[\"methods\"]\ntype MethodName = Extract<keyof Methods, string>\n\n/** The subset of the host's RpcCallContext the handlers use (no plugin import). */\nexport interface AdaptiveRpcCallContext<Name extends MethodName> {\n readonly error: Rpc.ErrorFactory<Methods[Name]>\n}\n\ntype HandlerErrorOf<Name extends MethodName> = Rpc.HandlerError<Methods[Name]>\n\ntype HandlerOf<Name extends MethodName> = (\n input: Rpc.Output<Methods[Name][\"input\"]>,\n context: AdaptiveRpcCallContext<Name>,\n) => Promise<Rpc.HandlerOutput<Methods[Name][\"output\"]> | HandlerErrorOf<Name>>\n\nexport function adaptiveRpcHandlers(\n source: AdaptiveRpcSource,\n emit?: AdaptiveRpcEmit,\n): { [Name in MethodName]: HandlerOf<Name> } {\n return {\n \"settings.get\": (_input, context) =>\n source\n .settings()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"settings.set\": async (patch) => {\n try {\n const result = await source.updateSettings(patch)\n if (result.ok && emit !== undefined) {\n try {\n await emit(\"settings-changed\", { version: result.settings.version })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return result\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n },\n \"status.get\": (_input, context) =>\n source\n .status()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"receipts.list\": (_input, context) =>\n source\n .receipts()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"session-mode.get\": (input, context) =>\n source\n .getSessionMode(input.sessionID)\n .then((mode) => ({ mode }))\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"session-mode.set\": async (input) => {\n const invalid = sessionModeSetError(input)\n if (invalid !== undefined) return { ok: false, errors: [invalid] }\n try {\n await source.setSessionMode(input.sessionID, input.mode)\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n if (emit !== undefined) {\n try {\n await emit(\"session-mode-changed\", { sessionID: input.sessionID })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return { ok: true }\n },\n \"tier-request.get\": (input, context) =>\n source\n .getTierRequest(input.sessionID)\n .then((tier) => ({ tier }))\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"tier-request.set\": async (input) => {\n const invalid = tierRequestSetError(input)\n if (invalid !== undefined) return { ok: false, errors: [invalid] }\n if (input.tier !== null) {\n // A non-null tier is a sticky record: noteTierRequest re-validates the\n // vocabulary (post-gate this cannot fail, so treat `false` as internal).\n if (!source.noteTierRequest(input.sessionID, input.tier)) {\n logFailOpen(\"rpc-set\", new Error(\"tier request rejected\"))\n return { ok: false, errors: [\"internal error\"] }\n }\n } else {\n try {\n await source.setTierRequest(input.sessionID, null)\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n }\n if (emit !== undefined) {\n try {\n await emit(\"tier-request-changed\", { sessionID: input.sessionID })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return { ok: true }\n },\n }\n}\n"]}
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/rpc/handlers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RPC handler binding (ADP-034/036): adapts the supervisor's state API to the\n * typed RPC methods. Handlers never throw for expected outcomes — an invalid\n * patch is a per-field `PatchResult`, and an internal failure degrades to\n * `{ ok: false }` (fail-open, ADP-031). Read methods declare internal failures\n * through the call context's error factory: a plain throw becomes a server\n * defect whose generic `rpc.internal` message hides the cause (verified on the\n * sibling @coool plugins).\n */\nimport { OPERATING_MODES } from \"../core/scheduler/types.js\"\nimport type { OperatingMode } from \"../core/scheduler/types.js\"\nimport { TIERS } from \"../core/tier-policy/types.js\"\nimport type { TierName } from \"../core/tier-policy/types.js\"\nimport { LOG_ERROR_LENGTH, logFailOpen } from \"../supervisor/log.js\"\nimport { isValidSessionID } from \"../state/session-modes/index.js\"\nimport type { PatchResult } from \"../state/settings/types.js\"\nimport type { SettingsPatch, SettingsV1 } from \"../state/settings/index.js\"\nimport type { Rpc } from \"@opencode/schema/rpc\"\nimport type { Receipt } from \"../state/receipts/types.js\"\nimport type { StatusSnapshot } from \"./status.js\"\nimport type { AdaptiveRpc } from \"./definition.js\"\n\nexport interface AdaptiveRpcSource {\n settings(): Promise<SettingsV1>\n updateSettings(patch: SettingsPatch): Promise<PatchResult>\n status(): Promise<StatusSnapshot>\n receipts(): Promise<Receipt[]>\n getSessionMode(sessionID: string): Promise<OperatingMode | null>\n setSessionMode(sessionID: string, mode: OperatingMode | null): Promise<void>\n getTierRequest(sessionID: string): Promise<TierName | null>\n noteTierRequest(sessionID: string, tier: string): boolean\n setTierRequest(sessionID: string, tier: TierName | null): Promise<void>\n}\n\nexport type SettingsChangedEmit = (name: \"settings-changed\", data: { version: number }) => unknown\n\n/**\n * Correlated emit: each call site pairs the event name with its own payload\n * shape — an invalid pairing is a type error, and the registration site can\n * forward the tuple to the host emitter untouched.\n */\nexport type AdaptiveRpcEmit = (\n ...args:\n | [\"settings-changed\", { version: number }]\n | [\"session-mode-changed\", { sessionID: string }]\n | [\"tier-request-changed\", { sessionID: string }]\n) => unknown\n\n/** A successful tier-request write; mirrors the SessionModeSetResult convention. */\nexport type TierRequestSetResult =\n { readonly ok: true } | { readonly ok: false; readonly errors: readonly string[] }\n\n/** Per-field errors for a session-mode patch, mirroring the settings contract. */\nfunction sessionModeSetError(input: {\n sessionID: string\n mode: OperatingMode | null\n}): string | undefined {\n if (!isValidSessionID(input.sessionID)) {\n return \"sessionID must be a non-empty string of at most 128 characters\"\n }\n if (input.mode !== null && !(OPERATING_MODES as readonly string[]).includes(input.mode)) {\n return `mode must be one of ${OPERATING_MODES.join(\", \")} or null`\n }\n return undefined\n}\n\n/** Same contract for a tier-request patch (ADP-055): vocabulary-gated tier. */\nfunction tierRequestSetError(input: {\n sessionID: string\n tier: string | null\n}): string | undefined {\n if (!isValidSessionID(input.sessionID)) {\n return \"sessionID must be a non-empty string of at most 128 characters\"\n }\n if (input.tier !== null && !(TIERS as readonly string[]).includes(input.tier)) {\n return `tier must be one of ${TIERS.join(\", \")} or null`\n }\n return undefined\n}\n\ntype Methods = AdaptiveRpc[\"methods\"]\ntype MethodName = Extract<keyof Methods, string>\n\n/** The subset of the host's RpcCallContext the handlers use (no plugin import). */\nexport interface AdaptiveRpcCallContext<Name extends MethodName> {\n readonly error: Rpc.ErrorFactory<Methods[Name]>\n}\n\ntype HandlerErrorOf<Name extends MethodName> = Rpc.HandlerError<Methods[Name]>\n\ntype HandlerOf<Name extends MethodName> = (\n input: Rpc.Output<Methods[Name][\"input\"]>,\n context: AdaptiveRpcCallContext<Name>,\n) => Promise<Rpc.HandlerOutput<Methods[Name][\"output\"]> | HandlerErrorOf<Name>>\n\n/**\n * One settings write for every surface (RPC and the web command): persist,\n * then announce so open views refresh. An internal failure degrades to an\n * error result (fail-open, ADP-031).\n */\nexport async function patchSettings(\n source: Pick<AdaptiveRpcSource, \"updateSettings\">,\n emit: AdaptiveRpcEmit | undefined,\n patch: SettingsPatch,\n): Promise<PatchResult> {\n try {\n const result = await source.updateSettings(patch)\n if (result.ok && emit !== undefined) {\n try {\n await emit(\"settings-changed\", { version: result.settings.version })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return result\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n}\n\nexport function adaptiveRpcHandlers(\n source: AdaptiveRpcSource,\n emit?: AdaptiveRpcEmit,\n): { [Name in MethodName]: HandlerOf<Name> } {\n return {\n \"settings.get\": (_input, context) =>\n source\n .settings()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"settings.set\": (patch) => patchSettings(source, emit, patch),\n \"status.get\": (_input, context) =>\n source\n .status()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"receipts.list\": (_input, context) =>\n source\n .receipts()\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"session-mode.get\": (input, context) =>\n source\n .getSessionMode(input.sessionID)\n .then((mode) => ({ mode }))\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"session-mode.set\": async (input) => {\n const invalid = sessionModeSetError(input)\n if (invalid !== undefined) return { ok: false, errors: [invalid] }\n try {\n await source.setSessionMode(input.sessionID, input.mode)\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n if (emit !== undefined) {\n try {\n await emit(\"session-mode-changed\", { sessionID: input.sessionID })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return { ok: true }\n },\n \"tier-request.get\": (input, context) =>\n source\n .getTierRequest(input.sessionID)\n .then((tier) => ({ tier }))\n .catch((error: unknown) =>\n context.error(\"internal\", String(error).slice(0, LOG_ERROR_LENGTH)),\n ),\n \"tier-request.set\": async (input) => {\n const invalid = tierRequestSetError(input)\n if (invalid !== undefined) return { ok: false, errors: [invalid] }\n if (input.tier !== null) {\n // A non-null tier is a sticky record: noteTierRequest re-validates the\n // vocabulary (post-gate this cannot fail, so treat `false` as internal).\n if (!source.noteTierRequest(input.sessionID, input.tier)) {\n logFailOpen(\"rpc-set\", new Error(\"tier request rejected\"))\n return { ok: false, errors: [\"internal error\"] }\n }\n } else {\n try {\n await source.setTierRequest(input.sessionID, null)\n } catch (error) {\n logFailOpen(\"rpc-set\", error)\n return { ok: false, errors: [\"internal error\"] }\n }\n }\n if (emit !== undefined) {\n try {\n await emit(\"tier-request-changed\", { sessionID: input.sessionID })\n } catch (error) {\n logFailOpen(\"rpc-emit\", error)\n }\n }\n return { ok: true }\n },\n }\n}\n"]}
package/dist/tui.js CHANGED
@@ -18,8 +18,9 @@ import { createStore } from "solid-js/store";
18
18
  import { PLUGIN_ID } from "./plugin-id.js";
19
19
  import { adaptiveRpc } from "./rpc/definition.js";
20
20
  import { parseMode } from "./state/settings/index.js";
21
- import { runAdaptive } from "./ui/tui/controller.js";
22
- import { errorText } from "./ui/tui/options.js";
21
+ import { runAdaptive } from "./ui/flow/controller.js";
22
+ import { errorText } from "./ui/flow/options.js";
23
+ import { matchOptions } from "./ui/tui/search.js";
23
24
  /**
24
25
  * Label/color mapping for the footer chip. Pure so the mapping is testable
25
26
  * without a JSX runtime: active is green (supervision engaged), observe is
@@ -57,7 +58,8 @@ export default define({
57
58
  setTierRequest: (sessionID, tier) => client["tier-request.set"]({ sessionID, tier }, at()),
58
59
  };
59
60
  const view = {
60
- select: (options) => context.ui.dialog.select(options),
61
+ // A placeholder marks a searchable list (the model picker).
62
+ select: (options) => context.ui.dialog.select(options.placeholder === undefined ? options : { ...options, search: matchOptions }),
61
63
  selectPaged: (spec) =>
62
64
  // Footer actions flip pages: triggering one closes the dialog and
63
65
  // resolves with the action instead of a pick; the controller loop