@coool/opencode-adaptive 1.3.0 → 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.
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"]}
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"]}
@@ -11,6 +11,8 @@ export declare function statusSummary(status: StatusSnapshot | undefined): strin
11
11
  export declare function modelsSummary(settings: SettingsV1): string;
12
12
  export declare function mainMenuOptions(settings: SettingsV1, status: StatusSnapshot | undefined, tierRequest?: TierName | null, effectiveMode?: OperatingMode): SelectOption<MainMenuChoice>[];
13
13
  export declare function modeOptions(current: OperatingMode): SelectOption<OperatingMode>[];
14
+ /** What one mode does — shared by the global and the per-session menus. */
15
+ export declare function modeDescription(mode: OperatingMode): string;
14
16
  /** One per-session mode-menu pick: follow the global mode, or set an override. */
15
17
  export type SessionModeChoice = {
16
18
  kind: "global";
@@ -86,7 +86,7 @@ export function modeOptions(current) {
86
86
  }));
87
87
  }
88
88
  /** What one mode does — shared by the global and the per-session menus. */
89
- function modeDescription(mode) {
89
+ export function modeDescription(mode) {
90
90
  switch (mode) {
91
91
  case "off":
92
92
  return "Adaptive does nothing";
@@ -1 +1 @@
1
- {"version":3,"file":"menu.js","sourceRoot":"","sources":["../../../src/ui/flow/menu.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Main-menu and mode-menu view models for `/adaptive` (ADP-033): pure data\n * that the controller feeds into `ui.dialog.select`. Descriptions are dynamic\n * where state makes them more informative (health counters, tier contents).\n */\nimport { OPERATING_MODES } from \"../../core/scheduler/types.js\"\nimport type { OperatingMode } from \"../../core/scheduler/types.js\"\nimport type { TierName } from \"../../core/tier-policy/types.js\"\nimport { TIERS } from \"../../core/tier-policy/types.js\"\nimport type { StatusSnapshot } from \"../../rpc/status.js\"\nimport type { SettingsV1 } from \"../../state/settings/index.js\"\nimport type { SelectOption } from \"./options.js\"\nimport { resourceLabel } from \"./tiers.js\"\n\nexport const MAIN_MENU_ITEMS = [\"status\", \"models\", \"mode\", \"tier\", \"pin\", \"receipts\"] as const\nexport type MainMenuChoice = (typeof MAIN_MENU_ITEMS)[number]\n\n/** \"3 resources · 1 windowed\" — the status line under the menu entry. */\nexport function statusSummary(status: StatusSnapshot | undefined): string {\n if (status === undefined) return \"how every configured model is doing\"\n const total = TIERS.reduce((sum, tier) => sum + status.tiers[tier].length, 0)\n if (total === 0) return \"no models configured yet\"\n const windowed = TIERS.reduce(\n (sum, tier) => sum + status.tiers[tier].filter((entry) => entry.windowed).length,\n 0,\n )\n const windowedNote = windowed === 0 ? \"\" : ` · ${String(windowed)} windowed`\n return `${String(total)} resource${total === 1 ? \"\" : \"s\"}${windowedNote}`\n}\n\n/** \"quick: 1 · standard: 0 · advanced: 2\" — what is configured where. */\nexport function modelsSummary(settings: SettingsV1): string {\n const parts = TIERS.map((tier: TierName) => `${tier}: ${String(settings.tiers[tier].length)}`)\n return parts.join(\" · \")\n}\n\nfunction tierRequestDescription(tierRequest: TierName | null | undefined): string {\n if (tierRequest === undefined) return \"pin a tier for the current session\"\n if (tierRequest === null) return \"not pinned — the routing ladder decides\"\n return `pinned to ${tierRequest} — steers this session`\n}\n\nfunction modeMarker(\n mode: OperatingMode,\n input: { override: OperatingMode | null; effective: OperatingMode },\n): \"override\" | \"current\" | undefined {\n if (mode === input.override) return \"override\"\n if (mode === input.effective) return \"current\"\n return undefined\n}\n\nexport function mainMenuOptions(\n settings: SettingsV1,\n status: StatusSnapshot | undefined,\n tierRequest?: TierName | null,\n effectiveMode: OperatingMode = settings.mode,\n): SelectOption<MainMenuChoice>[] {\n const optionFor = (item: MainMenuChoice): SelectOption<MainMenuChoice> => {\n switch (item) {\n case \"status\":\n return {\n title: \"Status\",\n value: item,\n description: statusSummary(status),\n }\n case \"models\":\n return {\n title: \"Models\",\n value: item,\n description: modelsSummary(settings),\n }\n case \"mode\":\n return {\n title: \"Mode\",\n value: item,\n description: `currently ${effectiveMode}`,\n }\n case \"tier\":\n return {\n title: \"Tier\",\n value: item,\n description: tierRequestDescription(tierRequest),\n }\n case \"pin\":\n return {\n title: \"Pin\",\n value: item,\n description:\n settings.pin === null\n ? \"force one resource\"\n : `pinned to ${resourceLabel(settings.pin)}`,\n }\n case \"receipts\":\n return { title: \"Receipts\", value: item, description: \"why each turn ran the model it ran\" }\n }\n }\n return MAIN_MENU_ITEMS.map(optionFor)\n}\n\nexport function modeOptions(current: OperatingMode): SelectOption<OperatingMode>[] {\n return OPERATING_MODES.map((mode) => ({\n title: mode === current ? `${mode} (current)` : mode,\n value: mode,\n description: modeDescription(mode),\n }))\n}\n\n/** What one mode does — shared by the global and the per-session menus. */\nfunction modeDescription(mode: OperatingMode): string {\n switch (mode) {\n case \"off\":\n return \"Adaptive does nothing\"\n case \"observe\":\n return \"Record decisions without applying them\"\n case \"active\":\n return \"Apply routing and recovery decisions\"\n }\n}\n\n/** One per-session mode-menu pick: follow the global mode, or set an override. */\nexport type SessionModeChoice = { kind: \"global\" } | { kind: \"mode\"; mode: OperatingMode }\n\n/** Which mode the user steers inside a session: the global default or the session override. */\nexport type ModeTarget = \"global\" | \"session\"\n\n/**\n * The first select of the in-session mode flow (ADP-054): ask which mode to\n * steer before showing any mode list — the global default or this session's\n * override. Each row names the current value so the user can tell at a\n * glance which side needs changing.\n */\nexport function modeTargetOptions(\n global: OperatingMode,\n effective: OperatingMode,\n): SelectOption<ModeTarget>[] {\n return [\n {\n title: \"Global mode…\",\n description: `applies to every session without an override (now ${global})`,\n value: \"global\",\n },\n {\n title: \"This session…\",\n description: `override for this session only (now ${effective})`,\n value: \"session\",\n },\n ]\n}\n\n/**\n * The per-session mode menu (ADP-054): first \"Follow global\" (clearing the\n * override), then every mode. An existing override is marked \"(override)\" —\n * distinct from the effective mode's \"(current)\" — so the user can tell a\n * set override apart from a session merely inheriting the global mode.\n */\nexport function sessionModeOptions(input: {\n global: OperatingMode\n override: OperatingMode | null\n effective: OperatingMode\n}): SelectOption<SessionModeChoice>[] {\n const options: SelectOption<SessionModeChoice>[] = [\n {\n title: `Follow global (${input.global})`,\n description: \"clear this session's override\",\n value: { kind: \"global\" },\n },\n ]\n for (const mode of OPERATING_MODES) {\n const marker = modeMarker(mode, input)\n options.push({\n title: marker === undefined ? mode : `${mode} (${marker})`,\n value: { kind: \"mode\", mode },\n description: modeDescription(mode),\n })\n }\n return options\n}\n\n/** What one tier is for — shared by the tier editor and the per-session menu. */\nfunction tierDescription(tier: TierName): string {\n switch (tier) {\n case \"quick\":\n return \"routine edits, mechanical steps\"\n case \"standard\":\n return \"the default\"\n case \"advanced\":\n return \"multi-step design, broad refactors\"\n }\n}\n\n/** One per-session tier-menu pick: follow the routing ladder, or pin a tier. */\nexport type SessionTierChoice = { kind: \"ladder\" } | { kind: \"tier\"; tier: TierName }\n\n/**\n * The per-session tier menu (ADP-055): first \"Follow routing ladder\" (clearing\n * the request), then every tier. A pinned request is marked \"(pinned)\" so the\n * user can tell the sticky request apart from the ordinary ladder.\n */\nexport function sessionTierOptions(input: {\n pinned: TierName | null\n}): SelectOption<SessionTierChoice>[] {\n const options: SelectOption<SessionTierChoice>[] = [\n {\n title: \"Follow routing ladder\",\n description: \"clear this session's tier request\",\n value: { kind: \"ladder\" },\n },\n ]\n for (const tier of TIERS) {\n options.push({\n title: tier === input.pinned ? `${tier} (pinned)` : tier,\n value: { kind: \"tier\", tier },\n description: tierDescription(tier),\n })\n }\n return options\n}\n"]}
1
+ {"version":3,"file":"menu.js","sourceRoot":"","sources":["../../../src/ui/flow/menu.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Main-menu and mode-menu view models for `/adaptive` (ADP-033): pure data\n * that the controller feeds into `ui.dialog.select`. Descriptions are dynamic\n * where state makes them more informative (health counters, tier contents).\n */\nimport { OPERATING_MODES } from \"../../core/scheduler/types.js\"\nimport type { OperatingMode } from \"../../core/scheduler/types.js\"\nimport type { TierName } from \"../../core/tier-policy/types.js\"\nimport { TIERS } from \"../../core/tier-policy/types.js\"\nimport type { StatusSnapshot } from \"../../rpc/status.js\"\nimport type { SettingsV1 } from \"../../state/settings/index.js\"\nimport type { SelectOption } from \"./options.js\"\nimport { resourceLabel } from \"./tiers.js\"\n\nexport const MAIN_MENU_ITEMS = [\"status\", \"models\", \"mode\", \"tier\", \"pin\", \"receipts\"] as const\nexport type MainMenuChoice = (typeof MAIN_MENU_ITEMS)[number]\n\n/** \"3 resources · 1 windowed\" — the status line under the menu entry. */\nexport function statusSummary(status: StatusSnapshot | undefined): string {\n if (status === undefined) return \"how every configured model is doing\"\n const total = TIERS.reduce((sum, tier) => sum + status.tiers[tier].length, 0)\n if (total === 0) return \"no models configured yet\"\n const windowed = TIERS.reduce(\n (sum, tier) => sum + status.tiers[tier].filter((entry) => entry.windowed).length,\n 0,\n )\n const windowedNote = windowed === 0 ? \"\" : ` · ${String(windowed)} windowed`\n return `${String(total)} resource${total === 1 ? \"\" : \"s\"}${windowedNote}`\n}\n\n/** \"quick: 1 · standard: 0 · advanced: 2\" — what is configured where. */\nexport function modelsSummary(settings: SettingsV1): string {\n const parts = TIERS.map((tier: TierName) => `${tier}: ${String(settings.tiers[tier].length)}`)\n return parts.join(\" · \")\n}\n\nfunction tierRequestDescription(tierRequest: TierName | null | undefined): string {\n if (tierRequest === undefined) return \"pin a tier for the current session\"\n if (tierRequest === null) return \"not pinned — the routing ladder decides\"\n return `pinned to ${tierRequest} — steers this session`\n}\n\nfunction modeMarker(\n mode: OperatingMode,\n input: { override: OperatingMode | null; effective: OperatingMode },\n): \"override\" | \"current\" | undefined {\n if (mode === input.override) return \"override\"\n if (mode === input.effective) return \"current\"\n return undefined\n}\n\nexport function mainMenuOptions(\n settings: SettingsV1,\n status: StatusSnapshot | undefined,\n tierRequest?: TierName | null,\n effectiveMode: OperatingMode = settings.mode,\n): SelectOption<MainMenuChoice>[] {\n const optionFor = (item: MainMenuChoice): SelectOption<MainMenuChoice> => {\n switch (item) {\n case \"status\":\n return {\n title: \"Status\",\n value: item,\n description: statusSummary(status),\n }\n case \"models\":\n return {\n title: \"Models\",\n value: item,\n description: modelsSummary(settings),\n }\n case \"mode\":\n return {\n title: \"Mode\",\n value: item,\n description: `currently ${effectiveMode}`,\n }\n case \"tier\":\n return {\n title: \"Tier\",\n value: item,\n description: tierRequestDescription(tierRequest),\n }\n case \"pin\":\n return {\n title: \"Pin\",\n value: item,\n description:\n settings.pin === null\n ? \"force one resource\"\n : `pinned to ${resourceLabel(settings.pin)}`,\n }\n case \"receipts\":\n return { title: \"Receipts\", value: item, description: \"why each turn ran the model it ran\" }\n }\n }\n return MAIN_MENU_ITEMS.map(optionFor)\n}\n\nexport function modeOptions(current: OperatingMode): SelectOption<OperatingMode>[] {\n return OPERATING_MODES.map((mode) => ({\n title: mode === current ? `${mode} (current)` : mode,\n value: mode,\n description: modeDescription(mode),\n }))\n}\n\n/** What one mode does — shared by the global and the per-session menus. */\nexport function modeDescription(mode: OperatingMode): string {\n switch (mode) {\n case \"off\":\n return \"Adaptive does nothing\"\n case \"observe\":\n return \"Record decisions without applying them\"\n case \"active\":\n return \"Apply routing and recovery decisions\"\n }\n}\n\n/** One per-session mode-menu pick: follow the global mode, or set an override. */\nexport type SessionModeChoice = { kind: \"global\" } | { kind: \"mode\"; mode: OperatingMode }\n\n/** Which mode the user steers inside a session: the global default or the session override. */\nexport type ModeTarget = \"global\" | \"session\"\n\n/**\n * The first select of the in-session mode flow (ADP-054): ask which mode to\n * steer before showing any mode list — the global default or this session's\n * override. Each row names the current value so the user can tell at a\n * glance which side needs changing.\n */\nexport function modeTargetOptions(\n global: OperatingMode,\n effective: OperatingMode,\n): SelectOption<ModeTarget>[] {\n return [\n {\n title: \"Global mode…\",\n description: `applies to every session without an override (now ${global})`,\n value: \"global\",\n },\n {\n title: \"This session…\",\n description: `override for this session only (now ${effective})`,\n value: \"session\",\n },\n ]\n}\n\n/**\n * The per-session mode menu (ADP-054): first \"Follow global\" (clearing the\n * override), then every mode. An existing override is marked \"(override)\" —\n * distinct from the effective mode's \"(current)\" — so the user can tell a\n * set override apart from a session merely inheriting the global mode.\n */\nexport function sessionModeOptions(input: {\n global: OperatingMode\n override: OperatingMode | null\n effective: OperatingMode\n}): SelectOption<SessionModeChoice>[] {\n const options: SelectOption<SessionModeChoice>[] = [\n {\n title: `Follow global (${input.global})`,\n description: \"clear this session's override\",\n value: { kind: \"global\" },\n },\n ]\n for (const mode of OPERATING_MODES) {\n const marker = modeMarker(mode, input)\n options.push({\n title: marker === undefined ? mode : `${mode} (${marker})`,\n value: { kind: \"mode\", mode },\n description: modeDescription(mode),\n })\n }\n return options\n}\n\n/** What one tier is for — shared by the tier editor and the per-session menu. */\nfunction tierDescription(tier: TierName): string {\n switch (tier) {\n case \"quick\":\n return \"routine edits, mechanical steps\"\n case \"standard\":\n return \"the default\"\n case \"advanced\":\n return \"multi-step design, broad refactors\"\n }\n}\n\n/** One per-session tier-menu pick: follow the routing ladder, or pin a tier. */\nexport type SessionTierChoice = { kind: \"ladder\" } | { kind: \"tier\"; tier: TierName }\n\n/**\n * The per-session tier menu (ADP-055): first \"Follow routing ladder\" (clearing\n * the request), then every tier. A pinned request is marked \"(pinned)\" so the\n * user can tell the sticky request apart from the ordinary ladder.\n */\nexport function sessionTierOptions(input: {\n pinned: TierName | null\n}): SelectOption<SessionTierChoice>[] {\n const options: SelectOption<SessionTierChoice>[] = [\n {\n title: \"Follow routing ladder\",\n description: \"clear this session's tier request\",\n value: { kind: \"ladder\" },\n },\n ]\n for (const tier of TIERS) {\n options.push({\n title: tier === input.pinned ? `${tier} (pinned)` : tier,\n value: { kind: \"tier\", tier },\n description: tierDescription(tier),\n })\n }\n return options\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coool/opencode-adaptive",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Execution supervisor plugin for OpenCode v2: capability-tier routing, structured-evidence escalation, horizontal failure recovery, and resource health tracking.",
5
5
  "type": "module",
6
6
  "license": "MIT",