@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.
- package/README.md +1 -0
- package/dist/adapters/opencode/command.d.ts +30 -0
- package/dist/adapters/opencode/command.js +85 -0
- package/dist/adapters/opencode/command.js.map +1 -0
- package/dist/adapters/opencode/index.d.ts +1 -0
- package/dist/adapters/opencode/index.js +1 -0
- package/dist/adapters/opencode/index.js.map +1 -1
- package/dist/gui-experimental.cjs +3 -1
- package/dist/gui.d.ts +2 -1
- package/dist/gui.js +7 -5
- package/dist/gui.js.map +1 -1
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/dist/rpc/handlers.d.ts +6 -0
- package/dist/rpc/handlers.js +24 -18
- package/dist/rpc/handlers.js.map +1 -1
- package/dist/tui.js +5 -3
- package/dist/tui.js.map +1 -1
- package/dist/ui/{tui → flow}/controller.d.ts +5 -8
- package/dist/ui/{tui → flow}/controller.js +4 -9
- package/dist/ui/flow/controller.js.map +1 -0
- package/dist/ui/{tui → flow}/menu.d.ts +2 -0
- package/dist/ui/{tui → flow}/menu.js +1 -1
- package/dist/ui/flow/menu.js.map +1 -0
- package/dist/ui/flow/options.d.ts +12 -0
- package/dist/ui/flow/options.js.map +1 -0
- package/dist/ui/{tui → flow}/receipts.d.ts +1 -1
- package/dist/ui/{tui → flow}/receipts.js +4 -7
- package/dist/ui/flow/receipts.js.map +1 -0
- package/dist/ui/{tui → flow}/status.d.ts +0 -2
- package/dist/ui/{tui → flow}/status.js +0 -6
- package/dist/ui/flow/status.js.map +1 -0
- package/dist/ui/{tui → flow}/tiers.d.ts +2 -9
- package/dist/ui/{tui → flow}/tiers.js +5 -25
- package/dist/ui/flow/tiers.js.map +1 -0
- package/dist/ui/gui/api.d.ts +6 -2
- package/dist/ui/gui/api.js +4 -4
- package/dist/ui/gui/api.js.map +1 -1
- package/dist/ui/gui/flow.d.ts +23 -0
- package/dist/ui/gui/flow.js +123 -0
- package/dist/ui/gui/flow.js.map +1 -0
- package/dist/ui/gui/view.js +2 -2
- package/dist/ui/gui/view.js.map +1 -1
- package/dist/ui/tui/search.d.ts +9 -0
- package/dist/ui/tui/search.js +10 -0
- package/dist/ui/tui/search.js.map +1 -0
- package/package.json +2 -1
- package/dist/ui/tui/controller.js.map +0 -1
- package/dist/ui/tui/menu.js.map +0 -1
- package/dist/ui/tui/options.d.ts +0 -19
- package/dist/ui/tui/options.js.map +0 -1
- package/dist/ui/tui/receipts.js.map +0 -1
- package/dist/ui/tui/status.js.map +0 -1
- package/dist/ui/tui/tiers.js.map +0 -1
- /package/dist/ui/{tui → flow}/options.js +0 -0
package/dist/ui/gui/api.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { StatusSnapshot } from "../../rpc.js";
|
|
|
5
5
|
/**
|
|
6
6
|
* Thin typed-RPC adapter for the GUI surface (ADP-034). Every call resolves to `undefined` (reads) or `false`
|
|
7
7
|
* (writes) when the server is unreachable, so the page can render an explicit disconnected state instead of
|
|
8
|
-
* throwing. The server stays the single source of truth (ADP-036): the GUI holds no local settings state.
|
|
8
|
+
* throwing. `at` supplies the per-call location (see `rpcLocation`). The server stays the single source of truth (ADP-036): the GUI holds no local settings state.
|
|
9
9
|
*/
|
|
10
10
|
export interface GuiApi {
|
|
11
11
|
status(): Promise<StatusSnapshot | undefined>;
|
|
@@ -14,4 +14,8 @@ export interface GuiApi {
|
|
|
14
14
|
/** Calls back whenever settings change on the server. Returns the unsubscribe function. */
|
|
15
15
|
watchSettings(onChange: () => void): () => void;
|
|
16
16
|
}
|
|
17
|
-
export declare function adaptiveGuiApi(client: OpenCodeClient
|
|
17
|
+
export declare function adaptiveGuiApi(client: OpenCodeClient, at: () => {
|
|
18
|
+
location?: {
|
|
19
|
+
directory: string;
|
|
20
|
+
};
|
|
21
|
+
}): GuiApi;
|
package/dist/ui/gui/api.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { adaptiveRpc } from "../../rpc.js";
|
|
2
|
-
export function adaptiveGuiApi(client) {
|
|
2
|
+
export function adaptiveGuiApi(client, at) {
|
|
3
3
|
const rpc = client.rpc(adaptiveRpc);
|
|
4
4
|
return {
|
|
5
|
-
status: () => rpc["status.get"]().catch(() => undefined),
|
|
6
|
-
receipts: () => rpc["receipts.list"]().catch(() => undefined),
|
|
5
|
+
status: () => rpc["status.get"](undefined, at()).catch(() => undefined),
|
|
6
|
+
receipts: () => rpc["receipts.list"](undefined, at()).catch(() => undefined),
|
|
7
7
|
update: async (patch) => {
|
|
8
8
|
try {
|
|
9
|
-
const result = await rpc["settings.set"](patch);
|
|
9
|
+
const result = await rpc["settings.set"](patch, at());
|
|
10
10
|
return result.ok;
|
|
11
11
|
}
|
|
12
12
|
catch {
|
package/dist/ui/gui/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/ui/gui/api.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAgB1C,MAAM,UAAU,cAAc,
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/ui/gui/api.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAgB1C,MAAM,UAAU,cAAc,CAC5B,MAAsB,EACtB,EAA8C;IAE9C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IACnC,OAAO;QACL,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;QACvE,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;QAC5E,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACtB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,cAAc,CAAC,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,CAAA;gBACrD,OAAO,MAAM,CAAC,EAAE,CAAA;YAClB,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;QACD,aAAa,EAAE,CAAC,QAAQ,EAAE,EAAE,CAC1B,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE;YACrC,QAAQ,EAAE,CAAA;QACZ,CAAC,CAAC;KACL,CAAA;AACH,CAAC","sourcesContent":["import type { OpenCodeClient } from \"@opencode/client/promise\"\n\nimport type { SettingsPatch } from \"../../state/settings/index.js\"\nimport type { Receipt } from \"../../state/receipts/index.js\"\nimport { adaptiveRpc } from \"../../rpc.js\"\nimport type { StatusSnapshot } from \"../../rpc.js\"\n\n/**\n * Thin typed-RPC adapter for the GUI surface (ADP-034). Every call resolves to `undefined` (reads) or `false`\n * (writes) when the server is unreachable, so the page can render an explicit disconnected state instead of\n * throwing. `at` supplies the per-call location (see `rpcLocation`). The server stays the single source of truth (ADP-036): the GUI holds no local settings state.\n */\nexport interface GuiApi {\n status(): Promise<StatusSnapshot | undefined>\n receipts(): Promise<Receipt[] | undefined>\n update(patch: SettingsPatch): Promise<boolean>\n /** Calls back whenever settings change on the server. Returns the unsubscribe function. */\n watchSettings(onChange: () => void): () => void\n}\n\nexport function adaptiveGuiApi(\n client: OpenCodeClient,\n at: () => { location?: { directory: string } },\n): GuiApi {\n const rpc = client.rpc(adaptiveRpc)\n return {\n status: () => rpc[\"status.get\"](undefined, at()).catch(() => undefined),\n receipts: () => rpc[\"receipts.list\"](undefined, at()).catch(() => undefined),\n update: async (patch) => {\n try {\n const result = await rpc[\"settings.set\"](patch, at())\n return result.ok\n } catch {\n return false\n }\n },\n watchSettings: (onChange) =>\n rpc.events.on(\"settings-changed\", () => {\n onChange()\n }),\n }\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Context } from "@opencode/plugin/gui";
|
|
2
|
+
import type { AdaptiveApi, AdaptiveView } from "../flow/controller.js";
|
|
3
|
+
/** The slice of the GUI host the shared `/adaptive` flow runs on. */
|
|
4
|
+
export type FlowHost = Pick<Context, "client" | "dialogs" | "toast" | "sessions" | "servers" | "ui">;
|
|
5
|
+
/**
|
|
6
|
+
* Dialog view over the host's own dialogs. The host select filters on title
|
|
7
|
+
* and description itself, so the model picker needs no search callback, and
|
|
8
|
+
* page flips are ordinary rows because its list has no footer actions.
|
|
9
|
+
*/
|
|
10
|
+
export declare function guiView(host: FlowHost): AdaptiveView;
|
|
11
|
+
/**
|
|
12
|
+
* RPC handlers resolve per location, so calls carry the session's directory (a project-local install is
|
|
13
|
+
* only reachable that way); on Home there is none and the host default applies.
|
|
14
|
+
*/
|
|
15
|
+
export declare function rpcLocation(host: FlowHost): {
|
|
16
|
+
location?: {
|
|
17
|
+
directory: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
/** Typed RPC for the flow. */
|
|
21
|
+
export declare function guiFlowApi(host: FlowHost): AdaptiveApi;
|
|
22
|
+
/** Runs the `/adaptive` flow, the same controller the TUI drives. */
|
|
23
|
+
export declare function openAdaptive(host: FlowHost): Promise<void>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { jsx as _jsx } from "solid-js/jsx-runtime";
|
|
2
|
+
import { onCleanup } from "solid-js";
|
|
3
|
+
import { adaptiveRpc } from "../../rpc.js";
|
|
4
|
+
import { runAdaptive } from "../flow/controller.js";
|
|
5
|
+
import { errorText } from "../flow/options.js";
|
|
6
|
+
function TableDialog(props) {
|
|
7
|
+
// Escape and outside clicks close the dialog without telling the opener;
|
|
8
|
+
// unmounting is the one signal that always fires.
|
|
9
|
+
onCleanup(props.done);
|
|
10
|
+
return (_jsx(props.host.ui.Dialog, { title: props.title, size: "x-large", footer: _jsx(props.host.ui.Button, { onClick: props.close, children: "Close" }), children: _jsx("pre", { class: "adaptive-table", children: props.lines.join("\n") }) }));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Dialog view over the host's own dialogs. The host select filters on title
|
|
14
|
+
* and description itself, so the model picker needs no search callback, and
|
|
15
|
+
* page flips are ordinary rows because its list has no footer actions.
|
|
16
|
+
*/
|
|
17
|
+
export function guiView(host) {
|
|
18
|
+
return {
|
|
19
|
+
select: ({ title, placeholder, options }) => host.dialogs.select({
|
|
20
|
+
title,
|
|
21
|
+
options,
|
|
22
|
+
...(placeholder !== undefined ? { placeholder } : {}),
|
|
23
|
+
}),
|
|
24
|
+
selectPaged: async (spec) => {
|
|
25
|
+
const choice = await host.dialogs.select({
|
|
26
|
+
title: spec.title,
|
|
27
|
+
options: [
|
|
28
|
+
...spec.options.map((option) => ({
|
|
29
|
+
title: option.title,
|
|
30
|
+
value: { kind: "pick", value: option.value },
|
|
31
|
+
})),
|
|
32
|
+
...spec.actions.map((action) => ({
|
|
33
|
+
title: action.title,
|
|
34
|
+
value: { kind: "action", value: action.value },
|
|
35
|
+
})),
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
if (choice === undefined)
|
|
39
|
+
return { picked: undefined, action: undefined };
|
|
40
|
+
return choice.kind === "pick"
|
|
41
|
+
? { picked: choice.value, action: undefined }
|
|
42
|
+
: { picked: undefined, action: choice.value };
|
|
43
|
+
},
|
|
44
|
+
confirm: ({ title, message }) => host.dialogs.confirm({ title, description: message }),
|
|
45
|
+
alert: ({ title, message }) => host.dialogs.alert({ title, description: message }),
|
|
46
|
+
table: ({ title, lines }) => new Promise((resolve) => {
|
|
47
|
+
host.dialogs.open((dialog) => (_jsx(TableDialog, { host: host, title: title, lines: lines, close: () => {
|
|
48
|
+
dialog.close();
|
|
49
|
+
}, done: resolve })));
|
|
50
|
+
}),
|
|
51
|
+
toast: (message, variant) => {
|
|
52
|
+
host.toast.show({ title: message, variant });
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The session the flow steers: the mounted one, when the plugin's server owns
|
|
58
|
+
* it (Adaptive only knows sessions of the server it runs on).
|
|
59
|
+
*/
|
|
60
|
+
function ownSession(host) {
|
|
61
|
+
const session = host.sessions.current();
|
|
62
|
+
return session?.server.builtin === true ? session : undefined;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* RPC handlers resolve per location, so calls carry the session's directory (a project-local install is
|
|
66
|
+
* only reachable that way); on Home there is none and the host default applies.
|
|
67
|
+
*/
|
|
68
|
+
export function rpcLocation(host) {
|
|
69
|
+
const directory = ownSession(host)?.location?.directory;
|
|
70
|
+
return directory === undefined ? {} : { location: { directory } };
|
|
71
|
+
}
|
|
72
|
+
/** Typed RPC for the flow. */
|
|
73
|
+
export function guiFlowApi(host) {
|
|
74
|
+
const client = host.client.rpc(adaptiveRpc);
|
|
75
|
+
const at = () => rpcLocation(host);
|
|
76
|
+
return {
|
|
77
|
+
settings: () => client["settings.get"](undefined, at()),
|
|
78
|
+
setSettings: (patch) => client["settings.set"](patch, at()),
|
|
79
|
+
status: () => client["status.get"](undefined, at()),
|
|
80
|
+
receipts: () => client["receipts.list"](undefined, at()),
|
|
81
|
+
sessionMode: (sessionID) => client["session-mode.get"]({ sessionID }, at()).then((out) => out.mode),
|
|
82
|
+
setSessionMode: (sessionID, mode) => client["session-mode.set"]({ sessionID, mode }, at()),
|
|
83
|
+
tierRequest: (sessionID) => client["tier-request.get"]({ sessionID }, at()).then((out) => out.tier),
|
|
84
|
+
setTierRequest: (sessionID, tier) => client["tier-request.set"]({ sessionID, tier }, at()),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function modelData(host) {
|
|
88
|
+
const server = host.servers
|
|
89
|
+
.list()
|
|
90
|
+
.map((id) => host.servers.get(id))
|
|
91
|
+
.find((candidate) => candidate?.builtin === true);
|
|
92
|
+
if (server === undefined)
|
|
93
|
+
return undefined;
|
|
94
|
+
// `Data` reaches us as `any` (see ModelCatalog); this is the one place the
|
|
95
|
+
// host shape is asserted, and the flow tests drive it through a fake host.
|
|
96
|
+
const data = server.data;
|
|
97
|
+
return data.location.model;
|
|
98
|
+
}
|
|
99
|
+
/** Runs the `/adaptive` flow, the same controller the TUI drives. */
|
|
100
|
+
export async function openAdaptive(host) {
|
|
101
|
+
const view = guiView(host);
|
|
102
|
+
const session = ownSession(host);
|
|
103
|
+
const models = modelData(host);
|
|
104
|
+
const run = async () => {
|
|
105
|
+
// The host loads models lazily; the picker needs them before it opens.
|
|
106
|
+
await models?.sync(session?.location).catch(() => undefined);
|
|
107
|
+
await runAdaptive({
|
|
108
|
+
api: guiFlowApi(host),
|
|
109
|
+
view,
|
|
110
|
+
models: () => (models?.list(session?.location) ?? []).map((info) => ({
|
|
111
|
+
providerID: info.providerID,
|
|
112
|
+
modelID: info.modelID,
|
|
113
|
+
name: info.name,
|
|
114
|
+
variants: info.variants.map((variant) => variant.id),
|
|
115
|
+
})),
|
|
116
|
+
...(session !== undefined ? { sessionID: session.id } : {}),
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
await run().catch((error) => {
|
|
120
|
+
view.toast(errorText(error), "error");
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow.js","sourceRoot":"","sources":["../../../src/ui/gui/flow.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { LocationRef } from \"@opencode/client/promise\"\nimport type { Context } from \"@opencode/plugin/gui\"\nimport { onCleanup } from \"solid-js\"\nimport type { JSX } from \"solid-js\"\n\nimport { adaptiveRpc } from \"../../rpc.js\"\nimport { runAdaptive } from \"../flow/controller.js\"\nimport type { AdaptiveApi, AdaptiveView } from \"../flow/controller.js\"\nimport { errorText } from \"../flow/options.js\"\nimport type { ModelSummary } from \"../flow/tiers.js\"\n\n/** The slice of the GUI host the shared `/adaptive` flow runs on. */\nexport type FlowHost = Pick<Context, \"client\" | \"dialogs\" | \"toast\" | \"sessions\" | \"servers\" | \"ui\">\n\ntype PagedChoice<Value> =\n | { readonly kind: \"pick\"; readonly value: Value }\n | { readonly kind: \"action\"; readonly value: string }\n\nfunction TableDialog(props: {\n host: FlowHost\n title: string\n lines: readonly string[]\n close: () => void\n done: () => void\n}): JSX.Element {\n // Escape and outside clicks close the dialog without telling the opener;\n // unmounting is the one signal that always fires.\n onCleanup(props.done)\n return (\n <props.host.ui.Dialog\n title={props.title}\n size=\"x-large\"\n footer={<props.host.ui.Button onClick={props.close}>Close</props.host.ui.Button>}\n >\n <pre class=\"adaptive-table\">{props.lines.join(\"\\n\")}</pre>\n </props.host.ui.Dialog>\n )\n}\n\n/**\n * Dialog view over the host's own dialogs. The host select filters on title\n * and description itself, so the model picker needs no search callback, and\n * page flips are ordinary rows because its list has no footer actions.\n */\nexport function guiView(host: FlowHost): AdaptiveView {\n return {\n select: ({ title, placeholder, options }) =>\n host.dialogs.select({\n title,\n options,\n ...(placeholder !== undefined ? { placeholder } : {}),\n }),\n selectPaged: async (spec) => {\n const choice = await host.dialogs.select<PagedChoice<(typeof spec.options)[number][\"value\"]>>(\n {\n title: spec.title,\n options: [\n ...spec.options.map((option) => ({\n title: option.title,\n value: { kind: \"pick\" as const, value: option.value },\n })),\n ...spec.actions.map((action) => ({\n title: action.title,\n value: { kind: \"action\" as const, value: action.value },\n })),\n ],\n },\n )\n if (choice === undefined) return { picked: undefined, action: undefined }\n return choice.kind === \"pick\"\n ? { picked: choice.value, action: undefined }\n : { picked: undefined, action: choice.value }\n },\n confirm: ({ title, message }) => host.dialogs.confirm({ title, description: message }),\n alert: ({ title, message }) => host.dialogs.alert({ title, description: message }),\n table: ({ title, lines }) =>\n new Promise<void>((resolve) => {\n host.dialogs.open((dialog) => (\n <TableDialog\n host={host}\n title={title}\n lines={lines}\n close={() => {\n dialog.close()\n }}\n done={resolve}\n />\n ))\n }),\n toast: (message, variant) => {\n host.toast.show({ title: message, variant })\n },\n }\n}\n\n/**\n * The session the flow steers: the mounted one, when the plugin's server owns\n * it (Adaptive only knows sessions of the server it runs on).\n */\nfunction ownSession(host: FlowHost): ReturnType<FlowHost[\"sessions\"][\"current\"]> {\n const session = host.sessions.current()\n return session?.server.builtin === true ? session : undefined\n}\n\n/**\n * RPC handlers resolve per location, so calls carry the session's directory (a project-local install is\n * only reachable that way); on Home there is none and the host default applies.\n */\nexport function rpcLocation(host: FlowHost): { location?: { directory: string } } {\n const directory = ownSession(host)?.location?.directory\n return directory === undefined ? {} : { location: { directory } }\n}\n\n/** Typed RPC for the flow. */\nexport function guiFlowApi(host: FlowHost): AdaptiveApi {\n const client = host.client.rpc(adaptiveRpc)\n const at = () => rpcLocation(host)\n return {\n settings: () => client[\"settings.get\"](undefined, at()),\n setSettings: (patch) => client[\"settings.set\"](patch, at()),\n status: () => client[\"status.get\"](undefined, at()),\n receipts: () => client[\"receipts.list\"](undefined, at()),\n sessionMode: (sessionID) =>\n client[\"session-mode.get\"]({ sessionID }, at()).then((out) => out.mode),\n setSessionMode: (sessionID, mode) => client[\"session-mode.set\"]({ sessionID, mode }, at()),\n tierRequest: (sessionID) =>\n client[\"tier-request.get\"]({ sessionID }, at()).then((out) => out.tier),\n setTierRequest: (sessionID, tier) => client[\"tier-request.set\"]({ sessionID, tier }, at()),\n }\n}\n\n/**\n * The model catalog slice the picker reads. Declared here because the host's `Data` type does not resolve in this\n * package (its `@opencode/client/solid` types sit behind an unreachable export condition), so it reaches us as `any`.\n */\ninterface ModelCatalog {\n sync(ref?: LocationRef): Promise<void>\n list(ref?: LocationRef):\n | readonly {\n providerID: string\n modelID: string\n name: string\n variants: readonly { id: string }[]\n }[]\n | undefined\n}\n\nfunction modelData(host: FlowHost): ModelCatalog | undefined {\n const server = host.servers\n .list()\n .map((id) => host.servers.get(id))\n .find((candidate) => candidate?.builtin === true)\n if (server === undefined) return undefined\n // `Data` reaches us as `any` (see ModelCatalog); this is the one place the\n // host shape is asserted, and the flow tests drive it through a fake host.\n const data: unknown = server.data\n return (data as { location: { model: ModelCatalog } }).location.model\n}\n\n/** Runs the `/adaptive` flow, the same controller the TUI drives. */\nexport async function openAdaptive(host: FlowHost): Promise<void> {\n const view = guiView(host)\n const session = ownSession(host)\n const models = modelData(host)\n const run = async (): Promise<void> => {\n // The host loads models lazily; the picker needs them before it opens.\n await models?.sync(session?.location).catch(() => undefined)\n await runAdaptive({\n api: guiFlowApi(host),\n view,\n models: (): readonly ModelSummary[] =>\n (models?.list(session?.location) ?? []).map((info) => ({\n providerID: info.providerID,\n modelID: info.modelID,\n name: info.name,\n variants: info.variants.map((variant) => variant.id),\n })),\n ...(session !== undefined ? { sessionID: session.id } : {}),\n })\n }\n await run().catch((error: unknown) => {\n view.toast(errorText(error), \"error\")\n })\n}\n"]}
|
package/dist/ui/gui/view.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { OPERATING_MODES } from "../../core/scheduler/types.js";
|
|
2
2
|
import { TIERS } from "../../core/tier-policy/index.js";
|
|
3
|
-
import { healthTag, resourceLabel } from "../
|
|
4
|
-
import { receiptLine } from "../
|
|
3
|
+
import { healthTag, resourceLabel } from "../flow/tiers.js";
|
|
4
|
+
import { receiptLine } from "../flow/receipts.js";
|
|
5
5
|
export const DISCONNECTED_ROW_ID = "connection";
|
|
6
6
|
/**
|
|
7
7
|
* Pure view model for the status section. An undefined snapshot means the server is unreachable; the page then
|
package/dist/ui/gui/view.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view.js","sourceRoot":"","sources":["../../../src/ui/gui/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAsB,MAAM,+BAA+B,CAAA;AACnF,OAAO,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAA;AAEvD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../../../src/ui/gui/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAsB,MAAM,+BAA+B,CAAA;AACnF,OAAO,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAA;AAEvD,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAWjD,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAAA;AAE/C;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,QAAoC;IAC7D,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,CAAC,EAAE,EAAE,EAAE,mBAAmB,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;IAC/F,CAAC;IACD,MAAM,IAAI,GAAa;QACrB,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE;QACnD;YACE,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,QAAQ,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC;SACpE;KACF,CAAA;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACtC,MAAM,KAAK,GACT,SAAS,CAAC,MAAM,KAAK,CAAC;YACpB,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,SAAS;iBACN,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;iBACzE,IAAI,CAAC,IAAI,CAAC,CAAA;QACnB,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IACjE,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC5E,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IAClF,OAAO,IAAI,CAAA;AACb,CAAC;AAQD,MAAM,UAAU,WAAW,CAAC,OAAsB;IAChD,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACpC,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,IAAI;KACrD,CAAC,CAAC,CAAA;AACL,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,KAAK,CAAC,CAAA;AACvD,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAAC,QAA4B;IACvD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;AACxD,CAAC","sourcesContent":["import { OPERATING_MODES, type OperatingMode } from \"../../core/scheduler/types.js\"\nimport { TIERS } from \"../../core/tier-policy/index.js\"\nimport type { StatusSnapshot } from \"../../rpc.js\"\nimport { healthTag, resourceLabel } from \"../flow/tiers.js\"\nimport { receiptLine } from \"../flow/receipts.js\"\nimport type { Receipt } from \"../../state/receipts/index.js\"\n\n/** One display row of the GUI settings page. `tone` marks rows that need attention. */\nexport interface GuiRow {\n readonly id: string\n readonly label: string\n readonly value: string\n readonly tone?: \"warning\"\n}\n\nexport const DISCONNECTED_ROW_ID = \"connection\"\n\n/**\n * Pure view model for the status section. An undefined snapshot means the server is unreachable; the page then\n * shows a single disconnected row (ADP-034 disconnect handling) and no stale data.\n */\nexport function statusRows(snapshot: StatusSnapshot | undefined): readonly GuiRow[] {\n if (snapshot === undefined) {\n return [{ id: DISCONNECTED_ROW_ID, label: \"Server\", value: \"disconnected\", tone: \"warning\" }]\n }\n const rows: GuiRow[] = [\n { id: \"mode\", label: \"Mode\", value: snapshot.mode },\n {\n id: \"pin\",\n label: \"Pin\",\n value: snapshot.pin === null ? \"none\" : resourceLabel(snapshot.pin),\n },\n ]\n for (const tier of TIERS) {\n const resources = snapshot.tiers[tier]\n const value =\n resources.length === 0\n ? \"empty\"\n : resources\n .map((resource) => `${resourceLabel(resource.ref)}${healthTag(resource)}`)\n .join(\", \")\n rows.push({ id: `tier-${tier}`, label: `Tier ${tier}`, value })\n }\n rows.push({ id: \"parked\", label: \"Parked\", value: String(snapshot.parked) })\n rows.push({ id: \"receipts\", label: \"Receipts\", value: String(snapshot.receipts) })\n return rows\n}\n\n/** One choice of the mode selector; the active mode is marked. */\nexport interface ModeChoice {\n readonly value: OperatingMode\n readonly title: string\n}\n\nexport function modeChoices(current: OperatingMode): readonly ModeChoice[] {\n return OPERATING_MODES.map((mode) => ({\n value: mode,\n title: mode === current ? `${mode} (current)` : mode,\n }))\n}\n\n/** Validity gate for the mode `<select>` change event: untrusted DOM values never reach RPC. */\nexport function parseModeValue(value: string): OperatingMode | undefined {\n return OPERATING_MODES.find((mode) => mode === value)\n}\n\n/** Display lines for the receipts section, reusing the TUI decision vocabulary. */\nexport function receiptLines(receipts: readonly Receipt[]): readonly string[] {\n return receipts.map((receipt) => receiptLine(receipt))\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The search a searchable list (one with a placeholder) applies in the TUI:
|
|
3
|
+
* case-insensitive substring over title and description; a blank query keeps
|
|
4
|
+
* everything. The GUI host filters the same two fields itself.
|
|
5
|
+
*/
|
|
6
|
+
export declare function matchOptions<Option extends {
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
}>(query: string, options: readonly Option[]): readonly Option[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The search a searchable list (one with a placeholder) applies in the TUI:
|
|
3
|
+
* case-insensitive substring over title and description; a blank query keeps
|
|
4
|
+
* everything. The GUI host filters the same two fields itself.
|
|
5
|
+
*/
|
|
6
|
+
export function matchOptions(query, options) {
|
|
7
|
+
const needle = query.trim().toLowerCase();
|
|
8
|
+
return options.filter((option) => `${option.title}\n${option.description ?? ""}`.toLowerCase().includes(needle));
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../../src/ui/tui/search.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAa,EACb,OAA0B;IAE1B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACzC,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAC/B,GAAG,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAC9E,CAAA;AACH,CAAC","sourcesContent":["/**\n * The search a searchable list (one with a placeholder) applies in the TUI:\n * case-insensitive substring over title and description; a blank query keeps\n * everything. The GUI host filters the same two fields itself.\n */\nexport function matchOptions<Option extends { title: string; description?: string }>(\n query: string,\n options: readonly Option[],\n): readonly Option[] {\n const needle = query.trim().toLowerCase()\n return options.filter((option) =>\n `${option.title}\\n${option.description ?? \"\"}`.toLowerCase().includes(needle),\n )\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coool/opencode-adaptive",
|
|
3
|
-
"version": "1.
|
|
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",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"format:check": "prettier --check .",
|
|
46
46
|
"plugin-test": "opencode-plugin-test",
|
|
47
47
|
"e2e:web": "playwright test",
|
|
48
|
+
"e2e:gui": "vitest run --config test/e2e-gui/vitest.config.ts --coverage",
|
|
48
49
|
"e2e:tui": "termwright test -- --config test/e2e-tui/vitest.config.ts",
|
|
49
50
|
"traceability": "vite-node scripts/traceability.ts",
|
|
50
51
|
"verify:release": "vite-node scripts/verify-release.ts"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"controller.js","sourceRoot":"","sources":["../../../src/ui/tui/controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The `/adaptive` keyboard-flow controller (ADP-033): a pure-ish state machine\n * over two seams — the typed RPC API and the host dialogs/toasts. It holds no\n * local settings state: every mutation goes through `settings.set` and the\n * returned snapshot becomes the new local view (the server is the single\n * source of truth, ADP-036).\n *\n * Flow contract: `undefined` selections always mean \"cancel this step\" — the\n * menu loop continues or exits; API failures toast and keep the loop alive.\n */\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 { ModelRef } from \"../../core/work-unit/types.js\"\nimport type { SettingsPatch, SettingsV1 } from \"../../state/settings/index.js\"\nimport type { PatchResult } from \"../../state/settings/types.js\"\nimport type { SessionModeSetResult } from \"../../state/session-modes/index.js\"\nimport type { TierRequestSetResult } from \"../../rpc/handlers.js\"\nimport type { Receipt } from \"../../state/receipts/types.js\"\nimport type { StatusSnapshot } from \"../../rpc/status.js\"\nimport { STATUS_EMPTY_MESSAGE, statusTable } from \"./status.js\"\nimport {\n mainMenuOptions,\n modeOptions,\n modeTargetOptions,\n sessionModeOptions,\n sessionTierOptions,\n type MainMenuChoice,\n} from \"./menu.js\"\nimport {\n modelPickerOptions,\n moveResource,\n moveResourceToTop,\n removeResource,\n resourceLabel,\n tierEditorOptions,\n tierMenuOptions,\n variantOptions,\n type ModelSummary,\n} from \"./tiers.js\"\nimport { errorText, type SelectOption } from \"./options.js\"\nimport { receiptDetail, receiptLine, receiptPage, RECEIPTS_PAGE_SIZE } from \"./receipts.js\"\n\nexport interface AdaptiveView {\n select<Value>(options: {\n title: string\n placeholder?: string\n options: readonly SelectOption<Value>[]\n current?: Value\n search?: (\n query: string,\n options: readonly SelectOption<Value>[],\n ) => readonly SelectOption<Value>[]\n }): Promise<Value | undefined>\n /** A select with footer actions (page flips): the action closes the\n * dialog and is reported separately from an option pick. */\n selectPaged<Value>(spec: {\n title: string\n options: readonly SelectOption<Value>[]\n actions: readonly { title: string; value: string; bind: string }[]\n }): Promise<{ picked: Value | undefined; action: string | undefined }>\n confirm(options: {\n title: string\n message: string\n label?: { confirm?: string; cancel?: string }\n }): Promise<boolean | undefined>\n alert(options: { title: string; message: string }): Promise<void>\n /** A column-aligned text table (header row first); Escape closes it. */\n table(spec: { title: string; lines: string[] }): Promise<void>\n toast(message: string, variant: \"info\" | \"success\" | \"warning\" | \"error\"): void\n}\n\nexport interface AdaptiveApi {\n settings: () => Promise<SettingsV1>\n setSettings: (patch: SettingsPatch) => Promise<PatchResult>\n status: () => Promise<StatusSnapshot>\n receipts: () => Promise<Receipt[]>\n /** The session's mode override (`null` = follows the global mode). */\n sessionMode: (sessionID: string) => Promise<OperatingMode | null>\n setSessionMode: (sessionID: string, mode: OperatingMode | null) => Promise<SessionModeSetResult>\n /** The session's pinned tier request (`null` = follows the routing ladder). */\n tierRequest: (sessionID: string) => Promise<TierName | null>\n setTierRequest: (sessionID: string, tier: string | null) => Promise<TierRequestSetResult>\n}\n\nexport type ModelLister = () => readonly ModelSummary[]\n\nexport interface AdaptiveEnv {\n api: AdaptiveApi\n view: AdaptiveView\n models: ModelLister\n /**\n * The session the command was opened from (ADP-054). Present only when the\n * palette command ran inside a session screen — the mode menu then steers\n * that session's override instead of the global mode.\n */\n sessionID?: string | undefined\n}\n\nexport const INVITE_TITLE = \"OpenCode Adaptive\"\nexport const INVITE_MESSAGE =\n \"Adaptive starts in Observe mode: routing decisions are recorded as receipts, \" +\n \"but no model is switched. Open /adaptive to configure tiers or switch to Active.\"\n\n/** Tracks whether this dialog session already showed the save toast. */\nexport interface SaveFeedback {\n once: boolean\n}\n\n/** Run one API call; on failure toast and return undefined. */\nasync function tryApi<T>(env: AdaptiveEnv, run: () => Promise<T>): Promise<T | undefined> {\n try {\n return await run()\n } catch (error) {\n env.view.toast(errorText(error), \"error\")\n return undefined\n }\n}\n\n/**\n * Apply one patch; alert on validation errors, toast on success. The host\n * never auto-dismisses toasts, so the success toast is shown only on the\n * first successful save of a dialog session — later saves stay silent\n * (the updated menu already reflects them).\n */\nasync function applySettings(\n env: AdaptiveEnv,\n patch: SettingsPatch,\n feedback: SaveFeedback,\n): Promise<SettingsV1 | undefined> {\n const result = await tryApi(env, () => env.api.setSettings(patch))\n if (result === undefined) return undefined\n if (!result.ok) {\n await env.view.alert({ title: \"Invalid settings\", message: result.errors.join(\"; \") })\n return undefined\n }\n if (!feedback.once) {\n env.view.toast(\"Settings saved\", \"success\")\n feedback.once = true\n }\n return result.settings\n}\n\n/**\n * Persist one session-mode override; alert on errors, toast on success. Same\n * once-toast semantics as `applySettings` — the success toast is shown only\n * on the first successful save of a dialog session.\n */\nasync function applySessionMode(\n env: AdaptiveEnv,\n sessionID: string,\n mode: OperatingMode | null,\n feedback: SaveFeedback,\n): Promise<SessionModeSetResult | undefined> {\n const result = await tryApi(env, () => env.api.setSessionMode(sessionID, mode))\n if (result === undefined) return undefined\n if (!result.ok) {\n await env.view.alert({ title: \"Invalid settings\", message: result.errors.join(\"; \") })\n return undefined\n }\n if (!feedback.once) {\n env.view.toast(\"Settings saved\", \"success\")\n feedback.once = true\n }\n return result\n}\n\n/**\n * Persist one session tier request; alert on errors, toast on success. Same\n * once-toast semantics as `applySettings` — the success toast is shown only\n * on the first successful save of a dialog session.\n */\nasync function applyTierRequest(\n env: AdaptiveEnv,\n sessionID: string,\n tier: string | null,\n feedback: SaveFeedback,\n): Promise<TierRequestSetResult | undefined> {\n const result = await tryApi(env, () => env.api.setTierRequest(sessionID, tier))\n if (result === undefined) return undefined\n if (!result.ok) {\n await env.view.alert({ title: \"Invalid tier request\", message: result.errors.join(\"; \") })\n return undefined\n }\n if (!feedback.once) {\n env.view.toast(\"Settings saved\", \"success\")\n feedback.once = true\n }\n return result\n}\n\nexport async function runAdaptive(env: AdaptiveEnv): Promise<void> {\n const loaded = await tryApi(env, () => env.api.settings())\n if (loaded === undefined) return\n let settings = loaded\n const feedback: SaveFeedback = { once: false }\n const sessionID = env.sessionID\n // The session's current override: fetched once when the flow starts inside\n // a session; a failure degrades to \"follow global\" (the menu still works).\n let override: OperatingMode | null =\n sessionID === undefined\n ? null\n : ((await tryApi(env, () => env.api.sessionMode(sessionID))) ?? null)\n if (!settings.inviteDismissed) {\n await env.view.confirm({ title: INVITE_TITLE, message: INVITE_MESSAGE })\n const pick = await env.view.select({\n title: INVITE_TITLE,\n options: [\n { title: \"Open the menu now\", value: true },\n { title: \"Later\", value: false },\n ],\n })\n const dismissed = await applySettings(env, { inviteDismissed: true }, feedback)\n if (dismissed !== undefined) settings = dismissed\n if (pick !== true) return\n }\n for (;;) {\n // Fresh status and settings on every pass: the menu rows pair the two,\n // and the server is the single source of truth (ADP-036). A failed read\n // degrades silently to the last known values — the menu descriptions\n // must not spam a toast on every loop iteration (the flows that need\n // status toast through tryApi themselves).\n const [freshSettings, status] = await Promise.all([\n env.api.settings().catch(() => undefined),\n env.api.status().catch(() => undefined),\n ])\n if (freshSettings !== undefined) settings = freshSettings\n // The session's pinned tier request: fetched per loop so the entry\n // description tracks the latest request; a failure degrades to the\n // generic description (the menu still works).\n const tierPinned =\n sessionID === undefined\n ? undefined\n : await env.api.tierRequest(sessionID).catch(() => undefined)\n const choice = await env.view.select({\n title: `Adaptive (${override ?? settings.mode})`,\n options: mainMenuOptions(settings, status, tierPinned, override ?? settings.mode),\n })\n if (choice === undefined) return\n if (choice === \"status\") {\n await runStatusView(env)\n continue\n }\n if (choice === \"models\") {\n settings = (await runTierEditor(env, feedback)) ?? settings\n continue\n }\n if (choice === \"mode\") {\n const next = await runModeMenu(env, settings, feedback, sessionID, override)\n if (next !== undefined) {\n settings = next.settings\n override = next.override\n }\n continue\n }\n if (choice === \"tier\") {\n if (sessionID === undefined) {\n // The tier request only exists inside a session; on home the pick is\n // explained (not silently dropped) and the menu re-opens.\n await env.view.alert({\n title: \"This session\",\n message:\n \"This session's tier request can only be changed inside a session. Open a session and run /adaptive from there.\",\n })\n continue\n }\n await runTierRequestMenu(env, sessionID, feedback)\n continue\n }\n if (choice === \"pin\") {\n settings = (await runPinMenu(env, settings, feedback)) ?? settings\n continue\n }\n await runReceiptsView(env)\n }\n}\n\n/** What one mode-menu pass changed (settings and/or the session override). */\ninterface ModeMenuOutcome {\n settings: SettingsV1\n override: OperatingMode | null\n}\n\nasync function runModeMenu(\n env: AdaptiveEnv,\n settings: SettingsV1,\n feedback: SaveFeedback,\n sessionID: string | undefined,\n override: OperatingMode | null,\n): Promise<ModeMenuOutcome | undefined> {\n // Every screen gets the same first question — which mode to steer — so a\n // user who opens the menu on home learns the two-level structure before\n // they ever run the command inside a session.\n for (;;) {\n const target = await env.view.select({\n title: \"Operating mode\",\n options: modeTargetOptions(settings.mode, override ?? settings.mode),\n })\n if (target === undefined) return undefined\n if (target === \"global\") {\n const pick = await env.view.select({\n title: \"Global mode\",\n options: modeOptions(settings.mode),\n })\n if (pick === undefined || pick === settings.mode) return undefined\n const applied = await applySettings(env, { mode: pick }, feedback)\n // The session override is deliberately untouched: changing the global\n // default must not rewrite what a session was pinned to.\n return applied === undefined ? undefined : { settings: applied, override }\n }\n if (sessionID === undefined) {\n // The session override only exists inside a session; on home the pick\n // is explained (not silently dropped) and the target question repeats.\n await env.view.alert({\n title: \"This session\",\n message:\n \"This session's mode can only be changed inside a session. Open a session and run /adaptive from there.\",\n })\n continue\n }\n const pick = await env.view.select({\n title: \"This session's mode\",\n options: sessionModeOptions({\n global: settings.mode,\n override,\n effective: override ?? settings.mode,\n }),\n })\n if (pick === undefined) return undefined\n if (pick.kind === \"global\") {\n if (override === null) return undefined\n const saved = await applySessionMode(env, sessionID, null, feedback)\n return saved === undefined ? undefined : { settings, override: null }\n }\n if (pick.mode === (override ?? settings.mode)) return undefined\n const saved = await applySessionMode(env, sessionID, pick.mode, feedback)\n return saved === undefined ? undefined : { settings, override: pick.mode }\n }\n}\n\n/** The per-session tier-request menu (ADP-055): pin a tier or clear it. */\nasync function runTierRequestMenu(\n env: AdaptiveEnv,\n sessionID: string,\n feedback: SaveFeedback,\n): Promise<void> {\n const pinned = await tryApi(env, () => env.api.tierRequest(sessionID))\n if (pinned === undefined) return\n const pick = await env.view.select({\n title: \"This session's tier\",\n options: sessionTierOptions({ pinned }),\n })\n if (pick === undefined) return\n const next = pick.kind === \"ladder\" ? null : pick.tier\n if (next === pinned) return\n await applyTierRequest(env, sessionID, next, feedback)\n}\n\n/**\n * The status view: every configured model with its state as a column-aligned\n * table (header row, tier order, priority order within tiers). No\n * per-resource selection step — the table IS the view; Escape returns to the\n * menu.\n */\nasync function runStatusView(env: AdaptiveEnv): Promise<void> {\n const snapshot = await tryApi(env, () => env.api.status())\n if (snapshot === undefined) return\n const configured = TIERS.some((tier) => snapshot.tiers[tier].length > 0)\n if (!configured) {\n await env.view.alert({ title: \"Status\", message: STATUS_EMPTY_MESSAGE })\n return\n }\n await env.view.table({ title: \"Status\", lines: statusTable(snapshot) })\n}\n\ntype TierEditorOutcome = SettingsV1 | undefined\n\nasync function runTierEditor(env: AdaptiveEnv, feedback: SaveFeedback): Promise<TierEditorOutcome> {\n // The last confirmed server snapshot from this editor session, returned\n // when the editor closes so the menu view stays in sync with the server.\n let saved: SettingsV1 | undefined\n for (;;) {\n // The server is the single source of truth (ADP-036): settings and\n // status are re-fetched on every tier-list pass — entering the editor\n // and after each Escape out of a tier page — so an edit can never\n // clobber a change made outside this dialog. A failed read toasts and\n // closes the editor: the tier list cannot render meaningfully without\n // them.\n const settings = await tryApi(env, () => env.api.settings())\n if (settings === undefined) return saved\n const status = await tryApi(env, () => env.api.status())\n if (status === undefined) return saved\n const tier = await env.view.select({ title: \"Tier\", options: tierMenuOptions(status) })\n if (tier === undefined) return saved\n let pageStatus = status\n let pageSettings = settings\n // The tier page owns the loop until Escape: an add, move, or remove\n // re-opens this page instead of bouncing back to the tier list, so\n // consecutive edits on one tier stay in context. The page's settings\n // and status carry across iterations: each action's save response\n // updates them for the next render.\n for (;;) {\n const choice = await env.view.select({\n title: `Tier: ${tier}`,\n options: tierEditorOptions(pageSettings, tier, pageStatus),\n })\n if (choice === undefined) break\n if (choice.kind === \"add\") {\n const next = await runAddModel(env, pageSettings, tier, feedback)\n if (next !== undefined) {\n saved = next\n pageSettings = next\n }\n } else {\n const next = await runResourceMenu(env, pageSettings, tier, choice.index, feedback)\n if (next !== undefined) {\n saved = next\n pageSettings = next\n }\n }\n // The rows re-pair with the server after every action; a failed read\n // keeps the last known values so the page still renders (the save\n // already reported itself — a failed cosmetic refresh must neither\n // spam a toast nor bounce the page).\n const [freshSettings, freshStatus] = await Promise.all([\n env.api.settings().catch(() => undefined),\n env.api.status().catch(() => undefined),\n ])\n if (freshSettings !== undefined) {\n saved = freshSettings\n pageSettings = freshSettings\n }\n if (freshStatus !== undefined) pageStatus = freshStatus\n }\n }\n}\n\nasync function runAddModel(\n env: AdaptiveEnv,\n settings: SettingsV1,\n tier: TierName,\n feedback: SaveFeedback,\n): Promise<SettingsV1 | undefined> {\n const models = env.models()\n const pick = await env.view.select({\n title: \"Add model\",\n placeholder: \"Search provider / model / name\",\n options: modelPickerOptions(models, \"\"),\n search: (query) => modelPickerOptions(models, query),\n })\n if (pick === undefined) return undefined\n const info = models.find((m) => m.providerID === pick.providerID && m.modelID === pick.modelID)\n const variants = info?.variants ?? []\n let variant: string | undefined\n if (variants.length > 0) {\n const choice = await env.view.select({\n title: `Variant: ${pick.providerID}/${pick.modelID}`,\n options: variantOptions(variants),\n })\n if (choice === undefined) return undefined\n variant = choice.variant\n }\n const ref: ModelRef =\n variant === undefined\n ? { providerID: pick.providerID, id: pick.modelID }\n : { providerID: pick.providerID, id: pick.modelID, variant }\n const list = [...settings.tiers[tier], ref]\n return applySettings(env, { tiers: { ...settings.tiers, [tier]: list } }, feedback)\n}\n\ntype ResourceMenuChoice = \"top\" | \"up\" | \"down\" | \"remove\"\n\nfunction resourceMenuOptions(): SelectOption<ResourceMenuChoice>[] {\n return [\n { title: \"Move to top\", value: \"top\", description: \"Make this the primary resource\" },\n { title: \"Move up\", value: \"up\", description: \"Higher priority within the tier\" },\n { title: \"Move down\", value: \"down\", description: \"Lower priority within the tier\" },\n { title: \"Remove\", value: \"remove\" },\n ]\n}\n\nasync function runResourceMenu(\n env: AdaptiveEnv,\n settings: SettingsV1,\n tier: TierName,\n index: number,\n feedback: SaveFeedback,\n): Promise<SettingsV1 | undefined> {\n const ref = settings.tiers[tier][index]\n if (ref === undefined) return undefined\n const pick = await env.view.select({\n title: `Resource: ${resourceLabel(ref)}`,\n options: resourceMenuOptions(),\n })\n if (pick === undefined) return undefined\n if (pick === \"remove\") {\n const confirmed = await env.view.confirm({\n title: \"Remove resource\",\n message: `Remove ${resourceLabel(ref)} from ${tier}?`,\n })\n if (confirmed !== true) return undefined\n return applySettings(\n env,\n { tiers: { ...settings.tiers, [tier]: removeResource(settings.tiers[tier], index) } },\n feedback,\n )\n }\n const list =\n pick === \"top\"\n ? moveResourceToTop(settings.tiers[tier], index)\n : moveResource(settings.tiers[tier], index, pick === \"up\" ? -1 : 1)\n return applySettings(env, { tiers: { ...settings.tiers, [tier]: list } }, feedback)\n}\n\ntype PinChoice = { kind: \"clear\" } | { kind: \"pin\"; ref: ModelRef }\n\nfunction pinOptions(settings: SettingsV1): SelectOption<PinChoice>[] {\n const options: SelectOption<PinChoice>[] = [\n { title: \"No pin\", value: { kind: \"clear\" }, description: \"clear the pin\" },\n ]\n for (const tier of TIERS) {\n for (const ref of settings.tiers[tier]) {\n options.push({\n title: `${resourceLabel(ref)} (${tier})`,\n description: \"route every turn to this resource, even in active mode\",\n value: { kind: \"pin\", ref },\n })\n }\n }\n return options\n}\n\nasync function runPinMenu(\n env: AdaptiveEnv,\n settings: SettingsV1,\n feedback: SaveFeedback,\n): Promise<SettingsV1 | undefined> {\n const configured = TIERS.some((tier) => settings.tiers[tier].length > 0)\n if (!configured) {\n await env.view.alert({ title: \"Pin\", message: \"(no configured resources to pin)\" })\n return undefined\n }\n const pick = await env.view.select({ title: \"Pin\", options: pinOptions(settings) })\n if (pick === undefined) return undefined\n return applySettings(env, { pin: pick.kind === \"clear\" ? null : pick.ref }, feedback)\n}\n\nasync function runReceiptsView(env: AdaptiveEnv): Promise<void> {\n const all = await tryApi(env, () => env.api.receipts())\n if (all === undefined) return\n if (all.length === 0) {\n await env.view.alert({\n title: \"Receipts\",\n message: \"No decisions recorded yet.\",\n })\n return\n }\n // Paginated browse: every page is a bounded view, and the page flips are\n // footer actions (not list items) — they close the dialog and the loop\n // reopens it at the new offset. Viewing a detail returns to the same\n // page; Escape (no pick, no action) leaves the browser.\n for (let offset = 0; ;) {\n const page = receiptPage(all.slice(offset))\n const hasNext = offset + RECEIPTS_PAGE_SIZE < all.length\n const result = await env.view.selectPaged({\n title: `Receipts (${String(all.length)}, newest first)`,\n options: page.map((receipt) => ({ title: receiptLine(receipt), value: receipt })),\n actions: [\n ...(offset > 0 ? [{ title: \"Previous page\", value: \"prev\", bind: \"ctrl+b\" }] : []),\n ...(hasNext ? [{ title: \"Next page\", value: \"next\", bind: \"ctrl+f\" }] : []),\n ],\n })\n if (result.action === \"next\") {\n offset += RECEIPTS_PAGE_SIZE\n continue\n }\n if (result.action === \"prev\") {\n offset = Math.max(0, offset - RECEIPTS_PAGE_SIZE)\n continue\n }\n const pick = result.picked\n if (pick === undefined) return\n await env.view.alert({ title: `Receipt #${String(pick.id)}`, message: receiptDetail(pick) })\n }\n}\n\n/** Exported for tests: the receipts page size used by the receipts browser. */\nexport { RECEIPTS_PAGE_SIZE }\n\nexport type {\n MainMenuChoice,\n OperatingMode,\n StatusSnapshot,\n SettingsV1,\n SettingsPatch,\n PatchResult,\n Receipt,\n}\n"]}
|
package/dist/ui/tui/menu.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"menu.js","sourceRoot":"","sources":["../../../src/ui/tui/menu.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAG/D,OAAO,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAA;AAIvD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,CAAU,CAAA;AAG/F,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,MAAkC;IAC9D,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,qCAAqC,CAAA;IACtE,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;IAC7E,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,0BAA0B,CAAA;IAClD,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAC3B,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,EAChF,CAAC,CACF,CAAA;IACD,MAAM,YAAY,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAA;IAC5E,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,YAAY,EAAE,CAAA;AAC5E,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,QAAoB;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAc,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC9F,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AAC1B,CAAC;AAED,SAAS,sBAAsB,CAAC,WAAwC;IACtE,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,oCAAoC,CAAA;IAC1E,IAAI,WAAW,KAAK,IAAI;QAAE,OAAO,yCAAyC,CAAA;IAC1E,OAAO,aAAa,WAAW,wBAAwB,CAAA;AACzD,CAAC;AAED,SAAS,UAAU,CACjB,IAAmB,EACnB,KAAmE;IAEnE,IAAI,IAAI,KAAK,KAAK,CAAC,QAAQ;QAAE,OAAO,UAAU,CAAA;IAC9C,IAAI,IAAI,KAAK,KAAK,CAAC,SAAS;QAAE,OAAO,SAAS,CAAA;IAC9C,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,QAAoB,EACpB,MAAkC,EAClC,WAA6B,EAC7B,gBAA+B,QAAQ,CAAC,IAAI;IAE5C,MAAM,SAAS,GAAG,CAAC,IAAoB,EAAgC,EAAE;QACvE,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,QAAQ;gBACX,OAAO;oBACL,KAAK,EAAE,QAAQ;oBACf,KAAK,EAAE,IAAI;oBACX,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC;iBACnC,CAAA;YACH,KAAK,QAAQ;gBACX,OAAO;oBACL,KAAK,EAAE,QAAQ;oBACf,KAAK,EAAE,IAAI;oBACX,WAAW,EAAE,aAAa,CAAC,QAAQ,CAAC;iBACrC,CAAA;YACH,KAAK,MAAM;gBACT,OAAO;oBACL,KAAK,EAAE,MAAM;oBACb,KAAK,EAAE,IAAI;oBACX,WAAW,EAAE,aAAa,aAAa,EAAE;iBAC1C,CAAA;YACH,KAAK,MAAM;gBACT,OAAO;oBACL,KAAK,EAAE,MAAM;oBACb,KAAK,EAAE,IAAI;oBACX,WAAW,EAAE,sBAAsB,CAAC,WAAW,CAAC;iBACjD,CAAA;YACH,KAAK,KAAK;gBACR,OAAO;oBACL,KAAK,EAAE,KAAK;oBACZ,KAAK,EAAE,IAAI;oBACX,WAAW,EACT,QAAQ,CAAC,GAAG,KAAK,IAAI;wBACnB,CAAC,CAAC,oBAAoB;wBACtB,CAAC,CAAC,aAAa,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;iBACjD,CAAA;YACH,KAAK,UAAU;gBACb,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,oCAAoC,EAAE,CAAA;QAChG,CAAC;IACH,CAAC,CAAA;IACD,OAAO,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;AACvC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAsB;IAChD,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACpC,KAAK,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,IAAI;QACpD,KAAK,EAAE,IAAI;QACX,WAAW,EAAE,eAAe,CAAC,IAAI,CAAC;KACnC,CAAC,CAAC,CAAA;AACL,CAAC;AAED,2EAA2E;AAC3E,SAAS,eAAe,CAAC,IAAmB;IAC1C,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,KAAK;YACR,OAAO,uBAAuB,CAAA;QAChC,KAAK,SAAS;YACZ,OAAO,wCAAwC,CAAA;QACjD,KAAK,QAAQ;YACX,OAAO,sCAAsC,CAAA;IACjD,CAAC;AACH,CAAC;AAQD;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAqB,EACrB,SAAwB;IAExB,OAAO;QACL;YACE,KAAK,EAAE,cAAc;YACrB,WAAW,EAAE,qDAAqD,MAAM,GAAG;YAC3E,KAAK,EAAE,QAAQ;SAChB;QACD;YACE,KAAK,EAAE,eAAe;YACtB,WAAW,EAAE,uCAAuC,SAAS,GAAG;YAChE,KAAK,EAAE,SAAS;SACjB;KACF,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAIlC;IACC,MAAM,OAAO,GAAsC;QACjD;YACE,KAAK,EAAE,kBAAkB,KAAK,CAAC,MAAM,GAAG;YACxC,WAAW,EAAE,+BAA+B;YAC5C,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;SAC1B;KACF,CAAA;IACD,KAAK,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QACtC,OAAO,CAAC,IAAI,CAAC;YACX,KAAK,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,MAAM,GAAG;YAC1D,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;YAC7B,WAAW,EAAE,eAAe,CAAC,IAAI,CAAC;SACnC,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,iFAAiF;AACjF,SAAS,eAAe,CAAC,IAAc;IACrC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,OAAO;YACV,OAAO,iCAAiC,CAAA;QAC1C,KAAK,UAAU;YACb,OAAO,aAAa,CAAA;QACtB,KAAK,UAAU;YACb,OAAO,oCAAoC,CAAA;IAC/C,CAAC;AACH,CAAC;AAKD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAElC;IACC,MAAM,OAAO,GAAsC;QACjD;YACE,KAAK,EAAE,uBAAuB;YAC9B,WAAW,EAAE,mCAAmC;YAChD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;SAC1B;KACF,CAAA;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC;YACX,KAAK,EAAE,IAAI,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,IAAI;YACxD,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;YAC7B,WAAW,EAAE,eAAe,CAAC,IAAI,CAAC;SACnC,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC","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"]}
|
package/dist/ui/tui/options.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared option shapes for the dialog-driven TUI. These mirror the subset of
|
|
3
|
-
* the host `ui.dialog.select` contract the Adaptive flows use; `tui.ts` adapts
|
|
4
|
-
* them to the real plugin types (structural superset).
|
|
5
|
-
*/
|
|
6
|
-
export interface SelectOption<Value> {
|
|
7
|
-
readonly title: string;
|
|
8
|
-
readonly value: Value;
|
|
9
|
-
readonly description?: string;
|
|
10
|
-
}
|
|
11
|
-
export interface SelectSpec<Value> {
|
|
12
|
-
readonly title: string;
|
|
13
|
-
readonly placeholder?: string;
|
|
14
|
-
readonly options: readonly SelectOption<Value>[];
|
|
15
|
-
readonly current?: Value;
|
|
16
|
-
readonly search?: (query: string, options: readonly SelectOption<Value>[]) => readonly SelectOption<Value>[];
|
|
17
|
-
}
|
|
18
|
-
/** Friendly one-line rendering of an API failure for toasts. */
|
|
19
|
-
export declare function errorText(error: unknown): string;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../../src/ui/tui/options.ts"],"names":[],"mappings":"AAsBA,gEAAgE;AAChE,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,KAAK,CAAC,OAAO,CAAA;IAChD,0EAA0E;IAC1E,6EAA6E;IAC7E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,MAAM,OAAO,GAAI,KAA+B,CAAC,OAAO,CAAA;QACxD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,OAAO,CAAA;QACrE,yEAAyE;QACzE,oEAAoE;QACpE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,KAAc,EAAE,EAAE,CACjD,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAClD,CAAA;IACH,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAA;AACtB,CAAC","sourcesContent":["/**\n * Shared option shapes for the dialog-driven TUI. These mirror the subset of\n * the host `ui.dialog.select` contract the Adaptive flows use; `tui.ts` adapts\n * them to the real plugin types (structural superset).\n */\nexport interface SelectOption<Value> {\n readonly title: string\n readonly value: Value\n readonly description?: string\n}\n\nexport interface SelectSpec<Value> {\n readonly title: string\n readonly placeholder?: string\n readonly options: readonly SelectOption<Value>[]\n readonly current?: Value\n readonly search?: (\n query: string,\n options: readonly SelectOption<Value>[],\n ) => readonly SelectOption<Value>[]\n}\n\n/** Friendly one-line rendering of an API failure for toasts. */\nexport function errorText(error: unknown): string {\n if (error instanceof Error) return error.message\n // RPC failures surface as plain objects ({type, message}); String() would\n // render \"[object Object]\" and throw away the only diagnostic the user gets.\n if (typeof error === \"object\" && error !== null) {\n const message = (error as { message?: unknown }).message\n if (typeof message === \"string\" && message.length > 0) return message\n // JSON.stringify throws on BigInt values, which host payloads can carry;\n // stringify them as strings instead of losing the whole diagnostic.\n return JSON.stringify(error, (_, value: unknown) =>\n typeof value === \"bigint\" ? String(value) : value,\n )\n }\n return String(error)\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"receipts.js","sourceRoot":"","sources":["../../../src/ui/tui/receipts.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG1C,MAAM,UAAU,iBAAiB,CAAC,QAAkB;IAClD,QAAQ,QAAQ,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,IAAI,GAAG,CAAA;YACrE,OAAO,SAAS,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,IAAI,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAA;QAC7E,CAAC;QACD,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAA;YAC3E,OAAO,UAAU,QAAQ,CAAC,MAAM,GAAG,MAAM,EAAE,CAAA;QAC7C,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAA;YAC3E,OAAO,SAAS,QAAQ,CAAC,MAAM,GAAG,MAAM,EAAE,CAAA;QAC5C,CAAC;QACD,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,OAAO,YAAY,QAAQ,CAAC,IAAI,MAAM,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,IAAI,GAAG,CAAA;QAC3F,CAAC;QACD,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,OAAO,YAAY,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAA;QACxD,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAmC;IAC7D,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC3C,IAAI,OAAO,CAAC,MAAM,KAAK,WAAW;QAAE,OAAO,WAAW,CAAA;IACtD,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,WAAW,OAAO,CAAC,QAAQ,GAAG,CAAA;IACtE,OAAO,gBAAgB,OAAO,CAAC,MAAM,GAAG,CAAA;AAC1C,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAA;AAC1G,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAgB,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IACtE,OAAO;QACL,OAAO,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;QAC3B,SAAS,YAAY,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,GAAG;QAChF,YAAY,OAAO,CAAC,SAAS,EAAE;QAC/B,SAAS,OAAO,CAAC,MAAM,IAAI,GAAG,EAAE;QAChC,SAAS,OAAO,CAAC,IAAI,IAAI,GAAG,EAAE;QAC9B,aAAa,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;QAClD,YAAY,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;QAC1C,EAAE;QACF,kBAAkB,YAAY,CAAC,OAAO,CAAC,EAAE;KAC1C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACd,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,YAAY,CAAC,EAAU,EAAE,GAAW;IAClD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAA;IAC1D,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,UAAU,CAAA;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAA;IACxC,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAA;IAClD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAA;IACtC,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAA;IAC9C,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,OAAO,CAAA;AACjD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAAgB;IAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;IACjC,QAAQ,QAAQ,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,OAAO;YACV,OAAO,CACL,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC;gBACjC,gCAAgC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CACjE,CAAA;QACH,KAAK,OAAO;YACV,OAAO,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,6CAA6C,CAAA;QAC3F,KAAK,MAAM;YACT,OAAO,CACL,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAChC,6DAA6D,CAC9D,CAAA;QACH,KAAK,UAAU;YACb,OAAO,iEAAiE,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAA;QAC3G,KAAK,cAAc;YACjB,OAAO,8EAA8E,CAAA;IACzF,CAAC;AACH,CAAC;AAED,MAAM,gBAAgB,GAA2B;IAC/C,GAAG,EAAE,6CAA6C;IAClD,SAAS,EACP,4FAA4F;IAC9F,QAAQ,EACN,sGAAsG;IACxG,KAAK,EACH,yGAAyG;IAC3G,QAAQ,EAAE,4EAA4E;CACvF,CAAA;AAED,MAAM,gBAAgB,GAA2B;IAC/C,eAAe,EAAE,gFAAgF;IACjG,qBAAqB,EACnB,yGAAyG;IAC3G,cAAc,EAAE,wEAAwE;IACxF,kBAAkB,EAChB,kGAAkG;IACpG,kBAAkB,EAChB,qGAAqG;IACvG,0BAA0B,EACxB,kGAAkG;IACpG,6BAA6B,EAC3B,6EAA6E;IAC/E,oBAAoB,EAClB,qFAAqF;IACvF,yBAAyB,EACvB,6FAA6F;CAChG,CAAA;AAED,MAAM,eAAe,GAA2B;IAC9C,aAAa,EACX,gGAAgG;IAClG,eAAe,EACb,yFAAyF;IAC3F,wBAAwB,EACtB,iGAAiG;IACnG,aAAa,EAAE,6EAA6E;CAC7F,CAAA;AAED,qDAAqD;AACrD,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAA;AAEpC,MAAM,UAAU,WAAW,CAAC,QAA4B;IACtD,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAA;AAC9C,CAAC","sourcesContent":["/**\n * Receipt view models (ADP-030/033): one-line summaries and bounded detail\n * text for the `/adaptive` receipts browser. No secrets — receipt content is\n * already bounded at write time; this layer only formats.\n */\nimport type { Decision } from \"../../core/scheduler/types.js\"\nimport { resourceLabel } from \"./tiers.js\"\nimport type { Receipt, ReceiptOutcome } from \"../../state/receipts/types.js\"\n\nexport function summarizeDecision(decision: Decision): string {\n switch (decision.kind) {\n case \"apply\": {\n const tier = decision.tier === undefined ? \"\" : ` [${decision.tier}]`\n return `apply ${resourceLabel(decision.model)}${tier} (${decision.reason})`\n }\n case \"defer\": {\n const detail = decision.detail === undefined ? \"\" : ` — ${decision.detail}`\n return `defer: ${decision.reason}${detail}`\n }\n case \"park\": {\n const detail = decision.detail === undefined ? \"\" : ` — ${decision.detail}`\n return `park: ${decision.reason}${detail}`\n }\n case \"escalate\": {\n return `escalate ${decision.step} → ${resourceLabel(decision.target)} [${decision.tier}]`\n }\n case \"observe-only\": {\n return `observe: ${summarizeDecision(decision.would)}`\n }\n }\n}\n\nexport function outcomeLine(outcome: ReceiptOutcome | undefined): string {\n if (outcome === undefined) return \"pending\"\n if (outcome.status === \"succeeded\") return \"succeeded\"\n if (outcome.status === \"failed\") return `failed (${outcome.category})`\n return `interrupted (${outcome.reason})`\n}\n\nexport function receiptLine(receipt: Receipt): string {\n return `#${String(receipt.id)} ${summarizeDecision(receipt.decision)} — ${outcomeLine(receipt.outcome)}`\n}\n\nexport function receiptDetail(receipt: Receipt, now: number = Date.now()): string {\n return [\n `id: ${String(receipt.id)}`,\n `when: ${relativeTime(receipt.at, now)} (${new Date(receipt.at).toISOString()})`,\n `session: ${receipt.sessionID}`,\n `unit: ${receipt.unitID ?? \"-\"}`,\n `tier: ${receipt.tier ?? \"-\"}`,\n `decision: ${summarizeDecision(receipt.decision)}`,\n `outcome: ${outcomeLine(receipt.outcome)}`,\n \"\",\n `What happened: ${whatHappened(receipt)}`,\n ].join(\"\\n\")\n}\n\n/** \"just now\" / \"2m ago\" / \"3h ago\" / \"5d ago\" for a receipt timestamp. */\nexport function relativeTime(at: number, now: number): string {\n const seconds = Math.max(0, Math.floor((now - at) / 1000))\n if (seconds < 60) return \"just now\"\n const minutes = Math.floor(seconds / 60)\n if (minutes < 60) return `${String(minutes)}m ago`\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${String(hours)}h ago`\n return `${String(Math.floor(hours / 24))}d ago`\n}\n\n/**\n * Plain-language explanation for a receipt, keyed on the finite reason\n * vocabulary. Unknown reasons fall through to the raw summary.\n */\nexport function whatHappened(receipt: Receipt): string {\n const decision = receipt.decision\n switch (decision.kind) {\n case \"apply\":\n return (\n applyExplanation[decision.reason] ??\n `Adaptive routed this turn to ${resourceLabel(decision.model)}.`\n )\n case \"defer\":\n return deferExplanation[decision.reason] ?? \"Adaptive left the model choice to OpenCode.\"\n case \"park\":\n return (\n parkExplanation[decision.reason] ??\n \"Adaptive could not route this turn and left it to OpenCode.\"\n )\n case \"escalate\":\n return `This turn needed stronger capability, so Adaptive moved it to ${resourceLabel(decision.target)}.`\n case \"observe-only\":\n return \"Adaptive is observing: this is what it would have done, without applying it.\"\n }\n}\n\nconst applyExplanation: Record<string, string> = {\n pin: \"Adaptive routed to the resource you pinned.\",\n preferred:\n \"Adaptive chose this resource: it is the preferred resource for the tier and was available.\",\n fallback:\n \"The preferred resource was unavailable, so Adaptive moved the turn to the next resource in the tier.\",\n probe:\n \"Adaptive sent this turn as a recovery probe after an outage, to check whether the resource works again.\",\n draining: \"Adaptive routed to this resource while its provider was draining capacity.\",\n}\n\nconst deferExplanation: Record<string, string> = {\n \"routing-defer\": \"No routing rule applied to this turn, so Adaptive left the choice to OpenCode.\",\n \"tier-not-configured\":\n \"The tier that would handle this turn has no models configured, so Adaptive left the choice to OpenCode.\",\n \"adaptive-off\": \"Adaptive is switched off, so every turn runs on OpenCode's own choice.\",\n \"pin-unverifiable\":\n \"The pinned resource could not be verified as available, so Adaptive left the choice to OpenCode.\",\n \"pin-incompatible\":\n \"The pinned resource does not fit this turn's requirements, so Adaptive left the choice to OpenCode.\",\n \"escalation-not-justified\":\n \"No structured capability evidence justified escalation, so Adaptive kept the current capability.\",\n \"escalation-budget-exhausted\":\n \"This unit has used its escalation budget; no further escalation is allowed.\",\n \"escalation-ceiling\":\n \"Adaptive is already at the highest capability tier; no stronger tier is configured.\",\n \"escalation-no-candidate\":\n \"No stronger resource was eligible to escalate to (unavailable, blocked, or not configured).\",\n}\n\nconst parkExplanation: Record<string, string> = {\n \"all-blocked\":\n \"Every resource in the tier is currently unavailable, so the turn ran without Adaptive routing.\",\n \"no-candidates\":\n \"There are no models configured for this tier, so the turn ran without Adaptive routing.\",\n \"no-compatible-resource\":\n \"No configured resource fits this turn's requirements, so the turn ran without Adaptive routing.\",\n \"pin-blocked\": \"The pinned resource is unavailable and no fallback is allowed while pinned.\",\n}\n\n/** Receipts shown per dialog page (bounded view). */\nexport const RECEIPTS_PAGE_SIZE = 20\n\nexport function receiptPage(receipts: readonly Receipt[]): Receipt[] {\n return receipts.slice(0, RECEIPTS_PAGE_SIZE)\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/ui/tui/status.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Status-view helpers (ADP-033): flatten the status snapshot into one page\n * showing every configured model with its state, grouped by tier in priority\n * order. Pure and deterministic; local wall-clock rendering is derived from\n * the snapshot's own timestamps.\n */\nimport { TIERS } from \"../../core/tier-policy/types.js\"\nimport type { TierName } from \"../../core/tier-policy/types.js\"\nimport type { StatusSnapshot, TierResourceStatus } from \"../../rpc/status.js\"\nimport { resourceLabel } from \"./tiers.js\"\n\n/** Configured resources in tier order (quick → standard → advanced). */\nexport function healthEntries(\n snapshot: StatusSnapshot,\n): { tier: TierName; status: TierResourceStatus }[] {\n const entries: { tier: TierName; status: TierResourceStatus }[] = []\n for (const tier of TIERS) {\n for (const status of snapshot.tiers[tier]) entries.push({ tier, status })\n }\n return entries\n}\n\n/** Local date and time: quota windows may reset days or weeks from now. */\nexport function clockTime(until: number): string {\n const at = new Date(until)\n const hours = String(at.getHours()).padStart(2, \"0\")\n const minutes = String(at.getMinutes()).padStart(2, \"0\")\n const month = String(at.getMonth() + 1).padStart(2, \"0\")\n const day = String(at.getDate()).padStart(2, \"0\")\n return `${String(at.getFullYear())}-${month}-${day} ${hours}:${minutes}`\n}\n\nfunction pinnedTo(ref: TierResourceStatus[\"ref\"], pin: StatusSnapshot[\"pin\"]): boolean {\n if (pin === null) return false\n return pin.providerID === ref.providerID && pin.id === ref.id && pin.variant === ref.variant\n}\n\n/** One plain status line for a resource: health, window, breaker, pin. */\nexport function statusLine(status: TierResourceStatus, pin: StatusSnapshot[\"pin\"]): string {\n return `${resourceLabel(status.ref)} — ${status.health}${stateSuffix(status, pin)}`\n}\n\n/** Everything after the health state: capacity usage, window, breaker, pin. */\nfunction stateSuffix(status: TierResourceStatus, pin: StatusSnapshot[\"pin\"]): string {\n let suffix = \"\"\n if (status.windowed) {\n suffix +=\n status.windowUntil === null\n ? \", windowed (deadline unknown)\"\n : `, windowed until ${clockTime(status.windowUntil)}`\n }\n if (status.usedPercent !== null) {\n suffix += `, used ${String(status.usedPercent)}%`\n if (status.resetAt !== null) suffix += `, resets ${clockTime(status.resetAt)}`\n }\n if (status.breaker === \"open\") suffix += \", breaker open\"\n else if (status.breaker === \"half_open\") suffix += \", probing\"\n if (pinnedTo(status.ref, pin)) suffix += \" (pinned)\"\n return suffix\n}\n\n/** The STATE column of the status table: window, breaker, pin markers. */\nexport function stateCell(status: TierResourceStatus, pin: StatusSnapshot[\"pin\"]): string {\n return stateSuffix(status, pin).replace(/^[, ]+/, \"\") || \"-\"\n}\n\n/**\n * The whole inventory as a column-aligned table (header row first): models\n * grouped in tier order (quick → standard → advanced), one row per model,\n * columns padded to the widest cell so the grid reads at a glance. There is\n * no per-resource selection step: the table IS the view.\n */\nexport function statusTable(snapshot: StatusSnapshot): string[] {\n const header = [\"MODEL\", \"TIER\", \"HEALTH\", \"STATE\"]\n const rows = healthEntries(snapshot).map(({ tier, status }) => [\n resourceLabel(status.ref),\n tier,\n status.health,\n stateCell(status, snapshot.pin),\n ])\n if (rows.length === 0) return []\n // Header and rows are built together, always four cells wide — the column\n // index cannot be out of range.\n const widths = header.map((cell, column) =>\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n Math.max(cell.length, ...rows.map((row) => row[column]!.length)),\n )\n const render = (cells: string[]): string =>\n cells\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n .map((cell, column) => cell.padEnd(widths[column]!))\n .join(\" \")\n .trimEnd()\n return [render(header), ...rows.map(render)]\n}\n\n/** Empty-state copy for the status view when nothing is configured. */\nexport const STATUS_EMPTY_MESSAGE = \"No models configured yet — open Models to add some.\"\n"]}
|
package/dist/ui/tui/tiers.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tiers.js","sourceRoot":"","sources":["../../../src/ui/tui/tiers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tier-editor view models (ADP-033): resource labels with clear health tags,\n * model search over the real OpenCode model list, variant picking, and pure\n * reorder/remove helpers. All functions are total and deterministic.\n */\nimport type { ModelRef } from \"../../core/work-unit/types.js\"\nimport { TIERS } from \"../../core/tier-policy/types.js\"\nimport type { TierName } from \"../../core/tier-policy/types.js\"\nimport type { StatusSnapshot, TierResourceStatus } from \"../../rpc/status.js\"\nimport type { SettingsV1 } from \"../../state/settings/index.js\"\nimport type { PatchResult } from \"../../state/settings/types.js\"\nimport type { SelectOption } from \"./options.js\"\n\n/** `provider/model` with an `@variant` suffix when pinned to one. */\nexport function resourceLabel(ref: ModelRef): string {\n const base = `${ref.providerID}/${ref.id}`\n return ref.variant === undefined ? base : `${base}@${ref.variant}`\n}\n\n/**\n * Unavailable-resource display: empty string when healthy, otherwise a\n * bracketed, comma-separated tag list (health, breaker, window).\n */\nexport function healthTag(status: TierResourceStatus | undefined): string {\n if (status === undefined) return \"\"\n const parts: string[] = []\n if (status.health !== \"healthy\") parts.push(status.health)\n if (status.breaker === \"open\") parts.push(\"breaker-open\")\n else if (status.breaker === \"half_open\") parts.push(\"half-open\")\n if (status.windowed) parts.push(\"windowed\")\n return parts.length === 0 ? \"\" : ` [${parts.join(\", \")}]`\n}\n\nexport type TierEditorChoice = { kind: \"add\" } | { kind: \"resource\"; index: number }\nexport function tierEditorOptions(\n settings: SettingsV1,\n tier: TierName,\n status: StatusSnapshot | undefined,\n): SelectOption<TierEditorChoice>[] {\n const options: SelectOption<TierEditorChoice>[] = [\n { title: \"Add model…\", value: { kind: \"add\" }, description: \"Search OpenCode models\" },\n ]\n const tierStatus = status?.tiers[tier]\n settings.tiers[tier].forEach((ref, index) => {\n options.push({\n title: `${resourceLabel(ref)}${healthTag(tierStatus?.[index])}`,\n value: { kind: \"resource\", index },\n description: \"Move or remove\",\n })\n })\n // No \"Back\" row: Escape steps back out of the dialog (undefined pick →\n // the editor loop re-shows the tier list).\n return options\n}\n\nexport function tierMenuOptions(status: StatusSnapshot): SelectOption<TierName>[] {\n return TIERS.map((tier) => {\n const resources = status.tiers[tier]\n // The lists are short: showing them inline answers \"what is configured\n // where\" without opening the editor.\n const description =\n resources.length === 0\n ? \"(empty — add models to enable routing)\"\n : resources.map((entry) => resourceLabel(entry.ref)).join(\" · \")\n return { title: `${tier} (${String(resources.length)})`, value: tier, description }\n })\n}\n\n/** Structural summary of one real OpenCode model (built by the entry). */\nexport interface ModelSummary {\n readonly providerID: string\n readonly modelID: string\n readonly name: string\n readonly variants: readonly string[]\n}\n\nexport interface ModelPick {\n providerID: string\n modelID: string\n}\n\nexport function modelMatches(info: ModelSummary, query: string): boolean {\n const q = query.trim().toLowerCase()\n if (q === \"\") return true\n return (\n info.providerID.toLowerCase().includes(q) ||\n info.modelID.toLowerCase().includes(q) ||\n info.name.toLowerCase().includes(q)\n )\n}\n\nexport function modelPickerOptions(\n models: readonly ModelSummary[],\n query: string,\n): SelectOption<ModelPick>[] {\n return models\n .filter((info) => modelMatches(info, query))\n .map((info) => ({\n title: `${info.providerID}/${info.modelID}`,\n description: info.name,\n value: { providerID: info.providerID, modelID: info.modelID },\n }))\n}\n\nexport interface VariantChoice {\n variant: string | undefined\n}\n\nexport function variantOptions(variants: readonly string[]): SelectOption<VariantChoice>[] {\n const options: SelectOption<VariantChoice>[] = [\n { title: \"(model default)\", value: { variant: undefined } },\n ]\n for (const variant of variants) {\n options.push({ title: `@${variant}`, value: { variant } })\n }\n return options\n}\n\n/** Move the resource at `index` to the front (make it primary); out-of-bounds is a no-op. */\nexport function moveResourceToTop(resources: readonly ModelRef[], index: number): ModelRef[] {\n const item = resources[index]\n if (item === undefined) return [...resources]\n return [item, ...resources.filter((_, i) => i !== index)]\n}\n\n/** Swap the resource at `index` with its `delta` neighbour; bounds are no-ops. */\nexport function moveResource(\n resources: readonly ModelRef[],\n index: number,\n delta: -1 | 1,\n): ModelRef[] {\n const next = [...resources]\n const target = index + delta\n const item = next[index]\n const other = next[target]\n if (item === undefined || other === undefined) return next\n next[index] = other\n next[target] = item\n return next\n}\n\n/** Drop the resource at `index`; out-of-bounds keeps the list unchanged. */\nexport function removeResource(resources: readonly ModelRef[], index: number): ModelRef[] {\n const item = resources[index]\n if (item === undefined) return [...resources]\n return resources.filter((_, i) => i !== index)\n}\n\n/** First validation error line for an alert; undefined when the patch applied. */\nexport function validationMessage(result: PatchResult): string | undefined {\n return result.ok ? undefined : result.errors.join(\"; \")\n}\n"]}
|
|
File without changes
|