@oh-my-pi/pi-coding-agent 17.0.1 → 17.0.3
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/CHANGELOG.md +162 -40
- package/dist/cli.js +4559 -4543
- package/dist/types/advisor/config.d.ts +14 -6
- package/dist/types/advisor/runtime.d.ts +20 -11
- package/dist/types/autolearn/controller.d.ts +1 -0
- package/dist/types/config/model-discovery.d.ts +17 -0
- package/dist/types/config/model-resolver.d.ts +6 -2
- package/dist/types/config/settings-schema.d.ts +39 -10
- package/dist/types/config/settings.d.ts +50 -0
- package/dist/types/cursor.d.ts +11 -0
- package/dist/types/discovery/helpers.d.ts +5 -2
- package/dist/types/eval/agent-bridge.d.ts +7 -19
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/managed-timers.d.ts +15 -0
- package/dist/types/extensibility/extensions/runner.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +18 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +55 -1
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/extensibility/utils.d.ts +2 -2
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/lsp/types.d.ts +3 -0
- package/dist/types/mcp/transports/stdio.d.ts +42 -1
- package/dist/types/modes/components/advisor-config.d.ts +8 -1
- package/dist/types/modes/components/agent-hub.d.ts +15 -0
- package/dist/types/modes/components/hook-editor.d.ts +7 -0
- package/dist/types/modes/components/model-hub.d.ts +5 -2
- package/dist/types/modes/components/session-selector.d.ts +2 -0
- package/dist/types/modes/controllers/command-controller.d.ts +8 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/print-mode.d.ts +1 -1
- package/dist/types/modes/warp-events.d.ts +24 -0
- package/dist/types/modes/warp-events.test.d.ts +1 -0
- package/dist/types/plan-mode/model-transition.d.ts +47 -0
- package/dist/types/plan-mode/model-transition.test.d.ts +1 -0
- package/dist/types/registry/agent-lifecycle.d.ts +26 -1
- package/dist/types/registry/persisted-agents.d.ts +3 -0
- package/dist/types/sdk.d.ts +27 -5
- package/dist/types/session/agent-session.d.ts +34 -10
- package/dist/types/session/session-entries.d.ts +6 -1
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-manager.d.ts +19 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
- package/dist/types/task/executor.d.ts +26 -1
- package/dist/types/task/index.d.ts +1 -1
- package/dist/types/task/label.d.ts +1 -1
- package/dist/types/task/parallel.d.ts +14 -0
- package/dist/types/task/structured-subagent.d.ts +111 -0
- package/dist/types/task/types.d.ts +51 -0
- package/dist/types/telemetry-export.d.ts +34 -9
- package/dist/types/tools/approval.d.ts +8 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/essential-tools.d.ts +29 -0
- package/dist/types/tools/fetch.d.ts +4 -5
- package/dist/types/tools/hub/messaging.d.ts +5 -1
- package/dist/types/tools/image-gen.d.ts +2 -1
- package/dist/types/tools/index.d.ts +17 -1
- package/dist/types/tools/todo.d.ts +31 -0
- package/dist/types/tools/xdev.d.ts +11 -2
- package/dist/types/tui/tree-list.d.ts +7 -0
- package/dist/types/utils/markit.d.ts +11 -0
- package/dist/types/utils/title-generator.d.ts +2 -1
- package/dist/types/web/search/providers/kimi.d.ts +4 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +21 -16
- package/src/advisor/__tests__/advisor.test.ts +1304 -42
- package/src/advisor/__tests__/config.test.ts +58 -2
- package/src/advisor/config.ts +76 -24
- package/src/advisor/runtime.ts +445 -92
- package/src/autolearn/controller.ts +23 -28
- package/src/cli/file-processor.ts +1 -2
- package/src/cli.ts +5 -1
- package/src/config/model-discovery.ts +81 -21
- package/src/config/model-registry.ts +25 -6
- package/src/config/model-resolver.ts +14 -7
- package/src/config/settings-schema.ts +46 -9
- package/src/config/settings.ts +405 -25
- package/src/cursor.ts +20 -3
- package/src/debug/report-bundle.ts +40 -4
- package/src/discovery/helpers.ts +28 -5
- package/src/eval/__tests__/agent-bridge.test.ts +133 -47
- package/src/eval/__tests__/prelude-agent.test.ts +29 -0
- package/src/eval/agent-bridge.ts +104 -477
- package/src/eval/jl/prelude.jl +7 -6
- package/src/eval/js/shared/prelude.txt +5 -4
- package/src/eval/py/prelude.py +11 -39
- package/src/eval/rb/prelude.rb +5 -6
- package/src/exec/bash-executor.ts +14 -5
- package/src/extensibility/custom-tools/loader.ts +3 -3
- package/src/extensibility/custom-tools/wrapper.ts +2 -1
- package/src/extensibility/extensions/loader.ts +3 -3
- package/src/extensibility/extensions/managed-timers.ts +83 -0
- package/src/extensibility/extensions/runner.ts +26 -0
- package/src/extensibility/extensions/types.ts +18 -0
- package/src/extensibility/extensions/wrapper.ts +2 -1
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +149 -8
- package/src/extensibility/plugins/legacy-pi-compat.ts +225 -22
- package/src/extensibility/plugins/manager.ts +2 -2
- package/src/extensibility/shared-events.ts +6 -0
- package/src/extensibility/utils.ts +91 -25
- package/src/irc/bus.ts +22 -3
- package/src/launch/broker.ts +3 -2
- package/src/launch/client.ts +2 -2
- package/src/launch/presence.ts +2 -2
- package/src/lsp/client.ts +58 -1
- package/src/lsp/index.ts +62 -6
- package/src/lsp/types.ts +3 -0
- package/src/main.ts +11 -8
- package/src/mcp/manager.ts +9 -3
- package/src/mcp/oauth-flow.ts +20 -0
- package/src/mcp/transports/stdio.test.ts +269 -1
- package/src/mcp/transports/stdio.ts +255 -24
- package/src/modes/components/advisor-config.ts +65 -3
- package/src/modes/components/agent-hub.ts +1 -72
- package/src/modes/components/ask-dialog.ts +1 -1
- package/src/modes/components/bash-execution.ts +7 -3
- package/src/modes/components/eval-execution.ts +3 -1
- package/src/modes/components/hook-editor.ts +18 -3
- package/src/modes/components/model-hub.ts +138 -42
- package/src/modes/components/session-selector.ts +4 -0
- package/src/modes/components/status-line/component.test.ts +1 -0
- package/src/modes/components/status-line/segments.ts +21 -6
- package/src/modes/controllers/command-controller.ts +167 -47
- package/src/modes/controllers/event-controller.ts +5 -0
- package/src/modes/controllers/extension-ui-controller.ts +4 -22
- package/src/modes/controllers/input-controller.ts +12 -12
- package/src/modes/controllers/selector-controller.ts +191 -31
- package/src/modes/interactive-mode.ts +169 -62
- package/src/modes/print-mode.ts +3 -3
- package/src/modes/rpc/host-tools.ts +2 -1
- package/src/modes/rpc/rpc-mode.ts +19 -4
- package/src/modes/warp-events.test.ts +794 -0
- package/src/modes/warp-events.ts +232 -0
- package/src/plan-mode/model-transition.test.ts +60 -0
- package/src/plan-mode/model-transition.ts +51 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/tools/eval.md +5 -2
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/task.md +12 -8
- package/src/prompts/tools/write.md +1 -1
- package/src/registry/agent-lifecycle.ts +133 -18
- package/src/registry/persisted-agents.ts +74 -0
- package/src/sdk.ts +343 -122
- package/src/session/agent-session.ts +1359 -368
- package/src/session/session-entries.ts +6 -1
- package/src/session/session-history-format.ts +20 -5
- package/src/session/session-manager.ts +57 -0
- package/src/session/streaming-output.ts +41 -1
- package/src/slash-commands/builtin-registry.ts +7 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
- package/src/system-prompt.ts +7 -2
- package/src/task/executor.ts +100 -22
- package/src/task/index.ts +258 -429
- package/src/task/label.ts +2 -0
- package/src/task/parallel.ts +43 -0
- package/src/task/persisted-revive.ts +19 -7
- package/src/task/structured-subagent.ts +642 -0
- package/src/task/types.ts +58 -0
- package/src/telemetry-export.ts +453 -97
- package/src/tools/__tests__/eval-description.test.ts +1 -1
- package/src/tools/approval.ts +11 -0
- package/src/tools/bash.ts +71 -38
- package/src/tools/essential-tools.ts +45 -0
- package/src/tools/fetch.ts +28 -105
- package/src/tools/gh.ts +169 -2
- package/src/tools/hub/messaging.ts +16 -3
- package/src/tools/image-gen.ts +69 -7
- package/src/tools/index.ts +50 -15
- package/src/tools/path-utils.ts +1 -0
- package/src/tools/read.ts +62 -29
- package/src/tools/todo.ts +126 -13
- package/src/tools/write.ts +22 -4
- package/src/tools/xdev.ts +14 -3
- package/src/tui/tree-list.ts +39 -0
- package/src/utils/markit.ts +12 -0
- package/src/utils/title-generator.ts +15 -4
- package/src/utils/zip.ts +16 -1
- package/src/web/search/providers/kimi.ts +18 -12
- package/src/web/search/types.ts +6 -1
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* `save` callback.
|
|
17
17
|
*/
|
|
18
18
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
19
|
-
import type { Model } from "@oh-my-pi/pi-ai";
|
|
19
|
+
import type { Model, UsageReport } from "@oh-my-pi/pi-ai";
|
|
20
20
|
import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking";
|
|
21
21
|
import {
|
|
22
22
|
type Component,
|
|
@@ -38,6 +38,9 @@ import {
|
|
|
38
38
|
import type { ModelRegistry } from "../../config/model-registry";
|
|
39
39
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
40
40
|
import type { Settings } from "../../config/settings";
|
|
41
|
+
import type { PerAdvisorStat } from "../../session/agent-session";
|
|
42
|
+
import type { OAuthAccountIdentity } from "../../session/auth-storage";
|
|
43
|
+
import { formatCompactQuota } from "../controllers/command-controller";
|
|
41
44
|
import { getSelectListTheme, theme } from "../theme/theme";
|
|
42
45
|
import { HookEditorComponent } from "./hook-editor";
|
|
43
46
|
import { buildBrowserItems, ModelBrowser, sortModelItems } from "./model-browser";
|
|
@@ -63,6 +66,11 @@ export interface AdvisorConfigCallbacks {
|
|
|
63
66
|
requestRender: () => void;
|
|
64
67
|
/** Surface a transient status/warning line to the user. */
|
|
65
68
|
notify: (message: string) => void;
|
|
69
|
+
/** Live advisor usage stats; lets the preview show tokens/cost per advisor. */
|
|
70
|
+
getAdvisorStats?: () => PerAdvisorStat[];
|
|
71
|
+
getUsageReports?: () => Promise<UsageReport[] | null>;
|
|
72
|
+
/** Resolve the active OAuth identity for quota filtering (per-advisor account stickiness). */
|
|
73
|
+
resolveActiveAccount?: (provider: string, sessionId?: string) => OAuthAccountIdentity | undefined;
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
export interface AdvisorConfigDeps {
|
|
@@ -126,6 +134,8 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
126
134
|
#cb: AdvisorConfigCallbacks;
|
|
127
135
|
#scope: AdvisorConfigScope;
|
|
128
136
|
#doc: WatchdogConfigDoc;
|
|
137
|
+
/** Cached usage reports (quota/window/reset) prefetched on overlay open. */
|
|
138
|
+
#cachedReports: UsageReport[] | null = null;
|
|
129
139
|
#dirty = false;
|
|
130
140
|
|
|
131
141
|
#screen: Screen = "list";
|
|
@@ -157,6 +167,16 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
157
167
|
this.#doc = doc;
|
|
158
168
|
this.#ensureRosterVisible();
|
|
159
169
|
this.#showList();
|
|
170
|
+
// Prefetch usage reports for quota display; non-fatal if unavailable.
|
|
171
|
+
if (callbacks.getUsageReports) {
|
|
172
|
+
void callbacks
|
|
173
|
+
.getUsageReports()
|
|
174
|
+
.then(r => {
|
|
175
|
+
this.#cachedReports = r;
|
|
176
|
+
this.#cb.requestRender();
|
|
177
|
+
})
|
|
178
|
+
.catch(() => {});
|
|
179
|
+
}
|
|
160
180
|
}
|
|
161
181
|
|
|
162
182
|
// ───────────────────────────── render ─────────────────────────────
|
|
@@ -275,6 +295,7 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
275
295
|
const lines = [
|
|
276
296
|
theme.bold(advisor.name || "(unnamed)"),
|
|
277
297
|
"",
|
|
298
|
+
`${theme.fg("dim", "Enabled:")} ${advisor.enabled === false ? "○ off" : "● on"}`,
|
|
278
299
|
`${theme.fg("dim", "Model:")} ${model}`,
|
|
279
300
|
`${theme.fg("dim", "Tools:")} ${tools}`,
|
|
280
301
|
"",
|
|
@@ -282,6 +303,34 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
282
303
|
];
|
|
283
304
|
const instr = advisor.instructions?.trim();
|
|
284
305
|
lines.push(...(instr ? wrap(instr, bodyWidth) : [theme.fg("muted", "(none)")]));
|
|
306
|
+
// Show live usage stats when available from the session.
|
|
307
|
+
const liveStat = this.#cb.getAdvisorStats?.()?.find(s => s.name === (advisor.name || "default"));
|
|
308
|
+
if (liveStat && (liveStat.status === "running" || liveStat.status === "quota_exhausted")) {
|
|
309
|
+
lines.push("", theme.fg("dim", "Usage:"));
|
|
310
|
+
const spendParts: string[] = [
|
|
311
|
+
`${liveStat.tokens.input.toLocaleString()} in`,
|
|
312
|
+
`${liveStat.tokens.output.toLocaleString()} out`,
|
|
313
|
+
];
|
|
314
|
+
if (liveStat.tokens.cacheRead > 0) spendParts.push(`${liveStat.tokens.cacheRead.toLocaleString()} cache`);
|
|
315
|
+
lines.push(theme.fg("dim", ` Tokens: ${spendParts.join(", ")}`));
|
|
316
|
+
if (liveStat.cost > 0) lines.push(theme.fg("dim", ` Cost: $${liveStat.cost.toFixed(4)}`));
|
|
317
|
+
if (liveStat.contextWindow > 0) {
|
|
318
|
+
const pct = Math.round((liveStat.contextTokens / liveStat.contextWindow) * 100);
|
|
319
|
+
lines.push(
|
|
320
|
+
theme.fg(
|
|
321
|
+
"dim",
|
|
322
|
+
` Context: ${liveStat.contextTokens.toLocaleString()}/${liveStat.contextWindow.toLocaleString()} (${pct}%)`,
|
|
323
|
+
),
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const quotaProvider =
|
|
328
|
+
(advisor.model?.includes("/") ? advisor.model.split("/")[0] : null) ?? liveStat?.model?.provider;
|
|
329
|
+
if (this.#cachedReports && quotaProvider) {
|
|
330
|
+
const activeAccount = this.#cb.resolveActiveAccount?.(quotaProvider, liveStat?.sessionId);
|
|
331
|
+
const quota = formatCompactQuota(quotaProvider, this.#cachedReports, Date.now(), activeAccount);
|
|
332
|
+
if (quota) lines.push(theme.fg("dim", ` ${quota}`));
|
|
333
|
+
}
|
|
285
334
|
return lines.map(line => truncateToWidth(line, bodyWidth));
|
|
286
335
|
}
|
|
287
336
|
|
|
@@ -311,7 +360,8 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
311
360
|
advisor.name === "default" &&
|
|
312
361
|
!advisor.model?.trim() &&
|
|
313
362
|
advisor.tools === undefined &&
|
|
314
|
-
!advisor.instructions?.trim()
|
|
363
|
+
!advisor.instructions?.trim() &&
|
|
364
|
+
advisor.enabled !== false
|
|
315
365
|
);
|
|
316
366
|
}
|
|
317
367
|
|
|
@@ -325,7 +375,7 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
325
375
|
this.#ensureRosterVisible();
|
|
326
376
|
const items: SelectItem[] = this.#doc.advisors.map((advisor, index) => ({
|
|
327
377
|
value: `advisor:${index}`,
|
|
328
|
-
label: advisor.name || "(unnamed)"
|
|
378
|
+
label: `${advisor.enabled === false ? "○" : "●"} ${advisor.name || "(unnamed)"}`,
|
|
329
379
|
description: this.#advisorSummary(advisor),
|
|
330
380
|
}));
|
|
331
381
|
items.push({ value: "add", label: "+ Add advisor" });
|
|
@@ -395,6 +445,11 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
395
445
|
const toolsDescription = formatAdvisorTools(advisor.tools, "no tools");
|
|
396
446
|
const items: SelectItem[] = [
|
|
397
447
|
{ value: "name", label: "Name", description: advisor.name },
|
|
448
|
+
{
|
|
449
|
+
value: "toggleEnabled",
|
|
450
|
+
label: "Enabled",
|
|
451
|
+
description: advisor.enabled === false ? "○ off" : "● on",
|
|
452
|
+
},
|
|
398
453
|
{ value: "model", label: "Model", description: modelDescription },
|
|
399
454
|
];
|
|
400
455
|
if (advisor.model?.trim()) {
|
|
@@ -414,6 +469,13 @@ export class AdvisorConfigOverlayComponent implements Component {
|
|
|
414
469
|
|
|
415
470
|
#onDetailSelect(index: number, field: string): void {
|
|
416
471
|
switch (field) {
|
|
472
|
+
case "toggleEnabled": {
|
|
473
|
+
const a = this.#doc.advisors[index];
|
|
474
|
+
a.enabled = a.enabled === false ? undefined : false;
|
|
475
|
+
this.#dirty = true;
|
|
476
|
+
this.#showDetail(index);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
417
479
|
case "name":
|
|
418
480
|
this.#showNameEditor(index);
|
|
419
481
|
return;
|
|
@@ -13,17 +13,15 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Replaces the old SessionObserverOverlayComponent (ctrl+s observer).
|
|
15
15
|
*/
|
|
16
|
-
import * as fs from "node:fs";
|
|
17
|
-
import * as path from "node:path";
|
|
18
16
|
import { type AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
19
17
|
import { Container, Ellipsis, matchesKey, type OverlayHandle, padding, type TUI, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
20
18
|
import { formatAge, getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
21
|
-
import { ADVISOR_TRANSCRIPT_FILENAME, isAdvisorTranscriptName } from "../../advisor";
|
|
22
19
|
import type { KeyId } from "../../config/keybindings";
|
|
23
20
|
import type { MessageRenderer } from "../../extensibility/extensions/types";
|
|
24
21
|
import { IrcBus } from "../../irc/bus";
|
|
25
22
|
import { AgentLifecycleManager } from "../../registry/agent-lifecycle";
|
|
26
23
|
import { type AgentRef, AgentRegistry, type AgentStatus, MAIN_AGENT_ID } from "../../registry/agent-registry";
|
|
24
|
+
import { registerPersistedSubagents } from "../../registry/persisted-agents";
|
|
27
25
|
import { USER_INTERRUPT_LABEL } from "../../session/messages";
|
|
28
26
|
import { parseThinkingLevel } from "../../thinking";
|
|
29
27
|
import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../../tools/render-utils";
|
|
@@ -100,75 +98,6 @@ function modelBadge(ref: AgentRef, observed: ObservableSession | undefined): str
|
|
|
100
98
|
return formatModelBadge(selector.slice(selector.indexOf("/") + 1), level);
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
async function registerPersistedSubagents(
|
|
104
|
-
registry: AgentRegistry,
|
|
105
|
-
sessionFile: string | null | undefined,
|
|
106
|
-
): Promise<void> {
|
|
107
|
-
if (!sessionFile?.endsWith(".jsonl")) return;
|
|
108
|
-
const root = sessionFile.slice(0, -6);
|
|
109
|
-
await registerPersistedSubagentsFromDir(registry, root, undefined);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function registerPersistedSubagentsFromDir(
|
|
113
|
-
registry: AgentRegistry,
|
|
114
|
-
dir: string,
|
|
115
|
-
parentId: string | undefined,
|
|
116
|
-
): Promise<void> {
|
|
117
|
-
let entries: fs.Dirent[];
|
|
118
|
-
try {
|
|
119
|
-
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
120
|
-
} catch {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
for (const entry of entries) {
|
|
124
|
-
if (!entry.isFile() || !entry.name.endsWith(".jsonl") || entry.name.includes(".bak")) continue;
|
|
125
|
-
const sessionFile = path.join(dir, entry.name);
|
|
126
|
-
// The advisor transcript is observability-only: register it as a non-peer
|
|
127
|
-
// `advisor` kind under its owning session so the Hub can show its read-only
|
|
128
|
-
// transcript, but it never joins agent-facing rosters and is not revivable.
|
|
129
|
-
if (isAdvisorTranscriptName(entry.name)) {
|
|
130
|
-
const owner = parentId ?? MAIN_AGENT_ID;
|
|
131
|
-
// `__advisor.jsonl` → the default advisor (no slug); `__advisor.<slug>.jsonl`
|
|
132
|
-
// → a named advisor, keyed and labeled by its slug.
|
|
133
|
-
const slug =
|
|
134
|
-
entry.name === ADVISOR_TRANSCRIPT_FILENAME ? "" : entry.name.slice("__advisor.".length, -".jsonl".length);
|
|
135
|
-
const advisorId = slug ? `${owner}/advisor:${slug}` : `${owner}/advisor`;
|
|
136
|
-
const displayName = slug ? `advisor:${slug}` : "advisor";
|
|
137
|
-
const existing = registry.get(advisorId);
|
|
138
|
-
// Never clobber a non-advisor ref that happens to share this id (a freak
|
|
139
|
-
// user task literally named `<owner>/advisor`): leave it, skip the advisor.
|
|
140
|
-
if (existing && existing.kind !== "advisor") continue;
|
|
141
|
-
if (existing?.sessionFile !== sessionFile) {
|
|
142
|
-
// The id is reused across `/new`; refresh it to the current session's file.
|
|
143
|
-
if (existing) registry.unregister(advisorId);
|
|
144
|
-
registry.register({
|
|
145
|
-
id: advisorId,
|
|
146
|
-
displayName,
|
|
147
|
-
kind: "advisor",
|
|
148
|
-
parentId: owner,
|
|
149
|
-
session: null,
|
|
150
|
-
sessionFile,
|
|
151
|
-
status: "parked",
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
const id = entry.name.slice(0, -6);
|
|
157
|
-
if (!registry.get(id)) {
|
|
158
|
-
registry.register({
|
|
159
|
-
id,
|
|
160
|
-
displayName: id,
|
|
161
|
-
kind: "sub",
|
|
162
|
-
parentId: parentId ?? MAIN_AGENT_ID,
|
|
163
|
-
session: null,
|
|
164
|
-
sessionFile,
|
|
165
|
-
status: "parked",
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
await registerPersistedSubagentsFromDir(registry, path.join(dir, id), id);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
101
|
/** Result of one host-backed transcript read for the Agent Hub viewer. */
|
|
173
102
|
export interface AgentHubRemoteTranscript {
|
|
174
103
|
text: string;
|
|
@@ -556,7 +556,7 @@ export class AskDialogComponent implements Component {
|
|
|
556
556
|
}
|
|
557
557
|
const isEnter = matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n";
|
|
558
558
|
const isSpace = matchesKey(keyData, "space") || keyData === " ";
|
|
559
|
-
if (!isEnter && !isSpace) return;
|
|
559
|
+
if (!isEnter && !(question.multi && isSpace)) return;
|
|
560
560
|
if (rowItem.kind === "other") {
|
|
561
561
|
void this.#promptForCustomInput(question, state, rowItem);
|
|
562
562
|
return;
|
|
@@ -146,14 +146,18 @@ export class BashExecutionComponent extends Container {
|
|
|
146
146
|
#updateDisplay(): void {
|
|
147
147
|
const availableLines = this.#outputLines;
|
|
148
148
|
|
|
149
|
-
//
|
|
149
|
+
// Full output is shown when expanded or when sixel passthrough renders
|
|
150
|
+
// the raw payload; the collapsed preview shows only the tail window.
|
|
150
151
|
const previewLogicalLines = availableLines.slice(-PREVIEW_LINES);
|
|
151
|
-
const hiddenLineCount = availableLines.length - previewLogicalLines.length;
|
|
152
152
|
const sixelLineMask =
|
|
153
153
|
TERMINAL.imageProtocol === ImageProtocol.Sixel && isSixelPassthroughEnabled()
|
|
154
154
|
? getSixelLineMask(availableLines)
|
|
155
155
|
: undefined;
|
|
156
156
|
const hasSixelOutput = sixelLineMask?.some(Boolean) ?? false;
|
|
157
|
+
const showingAllLines = this.#expanded || hasSixelOutput;
|
|
158
|
+
// Only the collapsed preview hides lines; when the full output is shown
|
|
159
|
+
// the footer must not keep advertising hidden lines / ctrl+o.
|
|
160
|
+
const hiddenLineCount = showingAllLines ? 0 : availableLines.length - previewLogicalLines.length;
|
|
157
161
|
|
|
158
162
|
// Rebuild content container
|
|
159
163
|
this.#contentContainer.clear();
|
|
@@ -163,7 +167,7 @@ export class BashExecutionComponent extends Container {
|
|
|
163
167
|
|
|
164
168
|
// Output
|
|
165
169
|
if (availableLines.length > 0) {
|
|
166
|
-
if (
|
|
170
|
+
if (showingAllLines) {
|
|
167
171
|
const displayText = availableLines
|
|
168
172
|
.map((line, index) => (sixelLineMask?.[index] ? line : theme.fg("muted", line)))
|
|
169
173
|
.join("\n");
|
|
@@ -114,7 +114,9 @@ export class EvalExecutionComponent extends Container {
|
|
|
114
114
|
#updateDisplay(): void {
|
|
115
115
|
const availableLines = this.#outputLines;
|
|
116
116
|
const previewLogicalLines = availableLines.slice(-PREVIEW_LINES);
|
|
117
|
-
|
|
117
|
+
// Only the collapsed preview hides lines; when expanded the footer must
|
|
118
|
+
// not keep advertising hidden lines / ctrl+o.
|
|
119
|
+
const hiddenLineCount = this.#expanded ? 0 : availableLines.length - previewLogicalLines.length;
|
|
118
120
|
|
|
119
121
|
this.#contentContainer.clear();
|
|
120
122
|
|
|
@@ -20,6 +20,13 @@ import { DynamicBorder } from "./dynamic-border";
|
|
|
20
20
|
export interface HookEditorOptions {
|
|
21
21
|
/** When true, use prompt-style keybindings with the legacy ask prompt chrome. */
|
|
22
22
|
promptStyle?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Max rows the inner Editor may occupy. When omitted, the editor is
|
|
25
|
+
* bounded to the current terminal height minus the component's chrome
|
|
26
|
+
* (≈10 rows) so long content scrolls instead of pushing the submit
|
|
27
|
+
* hint out of view.
|
|
28
|
+
*/
|
|
29
|
+
maxHeight?: number;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
/** Interactive multiline dialog used by hooks and the ask tool's Other response. */
|
|
@@ -50,8 +57,11 @@ export class HookEditorComponent extends Container implements Focusable {
|
|
|
50
57
|
this.addChild(new DynamicBorder());
|
|
51
58
|
this.addChild(new Spacer(1));
|
|
52
59
|
|
|
53
|
-
// Title
|
|
54
|
-
|
|
60
|
+
// Title. Prompt-style renders the borderless editor's `> ` gutter at
|
|
61
|
+
// column 0, so pad the title to match; hook-style keeps the 1-col indent
|
|
62
|
+
// that lines up with its bordered editor body (#5313).
|
|
63
|
+
const chromePadX = this.#promptStyle ? 0 : 1;
|
|
64
|
+
this.addChild(new Text(theme.fg("accent", title), chromePadX, 0));
|
|
55
65
|
this.addChild(new Spacer(1));
|
|
56
66
|
|
|
57
67
|
// Editor
|
|
@@ -61,6 +71,11 @@ export class HookEditorComponent extends Container implements Focusable {
|
|
|
61
71
|
this.#editor.setPromptGutter("> ");
|
|
62
72
|
this.#editor.disableSubmit = true;
|
|
63
73
|
}
|
|
74
|
+
// Bound the editor so long content scrolls instead of pushing the
|
|
75
|
+
// submit hint off-screen. Caller may override via options.maxHeight.
|
|
76
|
+
const termRows = this.#tui.terminal?.rows ?? process.stdout.rows ?? 40;
|
|
77
|
+
this.#editor.setMaxHeight(options?.maxHeight ?? Math.max(3, termRows - 12));
|
|
78
|
+
this.#editor.setScrollbarVisible(true);
|
|
64
79
|
if (prefill) {
|
|
65
80
|
this.#editor.setText(prefill);
|
|
66
81
|
}
|
|
@@ -72,7 +87,7 @@ export class HookEditorComponent extends Container implements Focusable {
|
|
|
72
87
|
const hint = this.#promptStyle
|
|
73
88
|
? "enter or ctrl+q submit esc cancel ctrl+g external editor"
|
|
74
89
|
: "ctrl+q/ctrl+enter submit esc cancel ctrl+g external editor";
|
|
75
|
-
this.addChild(new Text(theme.fg("dim", hint),
|
|
90
|
+
this.addChild(new Text(theme.fg("dim", hint), chromePadX, 0));
|
|
76
91
|
|
|
77
92
|
this.addChild(new Spacer(1));
|
|
78
93
|
this.addChild(new DynamicBorder());
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
visibleWidth,
|
|
29
29
|
} from "@oh-my-pi/pi-tui";
|
|
30
30
|
import type { ModelRegistry } from "../../config/model-registry";
|
|
31
|
+
import { type ModelRoleLookup, type ResolvedModelRoleValue, resolveModelRoleValue } from "../../config/model-resolver";
|
|
31
32
|
import { getKnownRoleIds, getRoleInfo } from "../../config/model-roles";
|
|
32
33
|
import type { Settings } from "../../config/settings";
|
|
33
34
|
import { AUTO_THINKING, type ConfiguredThinkingLevel, getConfiguredThinkingLevelMetadata } from "../../thinking";
|
|
@@ -75,11 +76,19 @@ export interface ScopedModelItem {
|
|
|
75
76
|
thinkingLevel?: string;
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
export type ModelRoleSelectionScope = "global" | "project";
|
|
80
|
+
|
|
78
81
|
export interface ModelHubCallbacks {
|
|
79
82
|
/** Persist a role assignment. */
|
|
80
|
-
onAssign: (
|
|
83
|
+
onAssign: (
|
|
84
|
+
model: Model,
|
|
85
|
+
role: string,
|
|
86
|
+
thinkingLevel: ConfiguredThinkingLevel | undefined,
|
|
87
|
+
selector: string,
|
|
88
|
+
scope?: ModelRoleSelectionScope,
|
|
89
|
+
) => void;
|
|
81
90
|
/** Clear a configured role back to auto-selection. */
|
|
82
|
-
onUnassign: (role: string) => void;
|
|
91
|
+
onUnassign: (role: string, scope?: ModelRoleSelectionScope) => void;
|
|
83
92
|
/** Persist a `retry.fallbackChains` entry — keyed by a role, `provider/model-id`, or `provider/*`; an empty chain clears the key. */
|
|
84
93
|
onFallbackChainChange?: (role: string, chain: string[]) => void;
|
|
85
94
|
/** Locked provider activation: forward to the /login flow. */
|
|
@@ -111,18 +120,20 @@ interface StripChip {
|
|
|
111
120
|
/** Pre-styled label body (without selection decoration). */
|
|
112
121
|
styled: string;
|
|
113
122
|
role?: string;
|
|
114
|
-
action: "assign" | "unassign" | "fallback" | "fallbackModel" | "fallbackProvider" | "thinking";
|
|
123
|
+
action: "assign" | "unassign" | "fallback" | "fallbackModel" | "fallbackProvider" | "scope" | "thinking";
|
|
115
124
|
thinkingLevel?: ConfiguredThinkingLevel;
|
|
125
|
+
scope?: ModelRoleSelectionScope;
|
|
116
126
|
}
|
|
117
127
|
|
|
118
128
|
type StripState =
|
|
119
129
|
| {
|
|
120
|
-
kind: "role" | "thinking";
|
|
130
|
+
kind: "role" | "scope" | "thinking";
|
|
121
131
|
item: ModelBrowserItem;
|
|
122
132
|
role?: string;
|
|
133
|
+
scope?: ModelRoleSelectionScope;
|
|
123
134
|
chips: StripChip[];
|
|
124
135
|
index: number;
|
|
125
|
-
/** Where to land when a thinking strip closes. */
|
|
136
|
+
/** Where to land when a scope or thinking strip closes. */
|
|
126
137
|
returnToRoles: boolean;
|
|
127
138
|
}
|
|
128
139
|
| {
|
|
@@ -556,6 +567,11 @@ export class ModelHubComponent implements Component {
|
|
|
556
567
|
this.#tui.requestRender();
|
|
557
568
|
}
|
|
558
569
|
|
|
570
|
+
/** Re-sync after an asynchronous callback finishes mutating settings. */
|
|
571
|
+
refreshAfterExternalMutation(): void {
|
|
572
|
+
this.#refreshAfterMutation();
|
|
573
|
+
}
|
|
574
|
+
|
|
559
575
|
/**
|
|
560
576
|
* Recompute per-provider match counts for the active query. Providers
|
|
561
577
|
* without matches gray out and the scope hop skips them; a provider scope
|
|
@@ -745,23 +761,55 @@ export class ModelHubComponent implements Component {
|
|
|
745
761
|
this.#openRoleStrip(item);
|
|
746
762
|
}
|
|
747
763
|
|
|
764
|
+
#roleForScope(role: string, scope: ModelRoleSelectionScope): ResolvedModelRoleValue {
|
|
765
|
+
const roleValue =
|
|
766
|
+
scope === "project" ? this.#settings.getProjectModelRole(role) : this.#settings.getGlobalModelRole(role);
|
|
767
|
+
const allModels =
|
|
768
|
+
this.#scopedModels.length > 0 ? this.#scopedModels.map(scoped => scoped.model) : this.#registry.getAll();
|
|
769
|
+
const roleLookup: ModelRoleLookup = {
|
|
770
|
+
getModelRole: scopedRole =>
|
|
771
|
+
scope === "project"
|
|
772
|
+
? (this.#settings.getProjectModelRole(scopedRole) ?? this.#settings.getGlobalModelRole(scopedRole))
|
|
773
|
+
: this.#settings.getGlobalModelRole(scopedRole),
|
|
774
|
+
};
|
|
775
|
+
return resolveModelRoleValue(roleValue, allModels, { settings: this.#settings, roleLookup });
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
#thinkingLevelForScope(role: string, scope: ModelRoleSelectionScope): ConfiguredThinkingLevel {
|
|
779
|
+
const resolved = this.#roleForScope(role, scope);
|
|
780
|
+
return resolved.explicitThinkingLevel ? (resolved.thinkingLevel ?? ThinkingLevel.Inherit) : ThinkingLevel.Inherit;
|
|
781
|
+
}
|
|
782
|
+
|
|
748
783
|
/** Persist `role → item`, preserving a still-supported thinking level, then open the thinking strip. */
|
|
749
|
-
#assignRole(item: ModelBrowserItem, role: string, returnToRoles: boolean): void {
|
|
784
|
+
#assignRole(item: ModelBrowserItem, role: string, returnToRoles: boolean, scope?: ModelRoleSelectionScope): void {
|
|
785
|
+
if (this.#settings.get("modelRoleStorage") === "project" && scope === undefined) {
|
|
786
|
+
this.#openScopeStrip(item, role, returnToRoles);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
750
790
|
const current = this.#roles[role];
|
|
751
791
|
let level: ConfiguredThinkingLevel = ThinkingLevel.Inherit;
|
|
752
|
-
if (
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
792
|
+
if (this.#settings.get("modelRoleStorage") === "project" && scope !== undefined) {
|
|
793
|
+
level = this.#thinkingLevelForScope(role, scope);
|
|
794
|
+
} else if (current && !current.autoSelected) {
|
|
795
|
+
level = current.thinkingLevel;
|
|
796
|
+
}
|
|
797
|
+
const supported = this.#thinkingOptionsFor(item.model);
|
|
798
|
+
if (!supported.includes(level)) level = ThinkingLevel.Inherit;
|
|
799
|
+
this.#callbacks.onAssign(item.model, role, level, item.selector, scope);
|
|
757
800
|
this.#refreshAfterMutation();
|
|
758
|
-
this.#openThinkingStrip(item, role, returnToRoles);
|
|
801
|
+
this.#openThinkingStrip(item, role, returnToRoles, scope);
|
|
759
802
|
}
|
|
760
803
|
|
|
761
804
|
#unassignRole(role: string): void {
|
|
762
805
|
const assignment = this.#roles[role];
|
|
763
806
|
if (!assignment || assignment.autoSelected) return;
|
|
764
|
-
this.#
|
|
807
|
+
if (this.#settings.get("modelRoleStorage") === "project") {
|
|
808
|
+
const source = this.#settings.getModelRoleSource(role);
|
|
809
|
+
this.#callbacks.onUnassign(role, source === "default" ? undefined : source);
|
|
810
|
+
} else {
|
|
811
|
+
this.#callbacks.onUnassign(role);
|
|
812
|
+
}
|
|
765
813
|
this.#refreshAfterMutation();
|
|
766
814
|
}
|
|
767
815
|
|
|
@@ -771,24 +819,32 @@ export class ModelHubComponent implements Component {
|
|
|
771
819
|
|
|
772
820
|
#openRoleStrip(item: ModelBrowserItem): void {
|
|
773
821
|
const chips: StripChip[] = [];
|
|
822
|
+
const scopedStorage = this.#settings.get("modelRoleStorage") === "project";
|
|
823
|
+
const scopes: readonly ModelRoleSelectionScope[] = scopedStorage ? ["project", "global"] : ["global"];
|
|
774
824
|
for (const role of this.#visibleRoleIds()) {
|
|
775
825
|
const info = getRoleInfo(role, this.#settings);
|
|
776
826
|
const assignment = this.#roles[role];
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
827
|
+
for (const scope of scopes) {
|
|
828
|
+
const scopedModel = scopedStorage
|
|
829
|
+
? this.#roleForScope(role, scope).model
|
|
830
|
+
: assignment && !assignment.autoSelected
|
|
831
|
+
? assignment.model
|
|
832
|
+
: undefined;
|
|
833
|
+
const assignedHere =
|
|
834
|
+
!!scopedModel && scopedModel.provider === item.model.provider && scopedModel.id === item.model.id;
|
|
835
|
+
const roleLabel = (info.tag ?? info.name ?? role).toLowerCase();
|
|
836
|
+
const label = scopedStorage ? `${scope} ${roleLabel}` : roleLabel;
|
|
837
|
+
chips.push({
|
|
838
|
+
label,
|
|
839
|
+
styled: assignedHere
|
|
840
|
+
? theme.fg(info.color ?? "muted", `${theme.status.enabled}${label}`) +
|
|
841
|
+
theme.fg("dim", ` ${theme.status.success}`)
|
|
842
|
+
: theme.fg(info.color ?? "muted", label),
|
|
843
|
+
role,
|
|
844
|
+
scope,
|
|
845
|
+
action: assignedHere ? "unassign" : "assign",
|
|
846
|
+
});
|
|
847
|
+
}
|
|
792
848
|
}
|
|
793
849
|
chips.push({
|
|
794
850
|
label: `fallbacks:${item.model.id}`,
|
|
@@ -804,9 +860,25 @@ export class ModelHubComponent implements Component {
|
|
|
804
860
|
this.#strip = { kind: "role", item, chips, index: 0, returnToRoles: false };
|
|
805
861
|
}
|
|
806
862
|
|
|
807
|
-
#
|
|
863
|
+
#openScopeStrip(item: ModelBrowserItem, role: string, returnToRoles: boolean): void {
|
|
864
|
+
const chips: StripChip[] = [
|
|
865
|
+
{ label: "project", styled: theme.fg("accent", "project"), action: "scope", scope: "project" },
|
|
866
|
+
{ label: "global", styled: theme.fg("muted", "global"), action: "scope", scope: "global" },
|
|
867
|
+
];
|
|
868
|
+
this.#strip = { kind: "scope", item, role, chips, index: 0, returnToRoles };
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
#openThinkingStrip(
|
|
872
|
+
item: ModelBrowserItem,
|
|
873
|
+
role: string,
|
|
874
|
+
returnToRoles: boolean,
|
|
875
|
+
scope?: ModelRoleSelectionScope,
|
|
876
|
+
): void {
|
|
808
877
|
const options = this.#thinkingOptionsFor(item.model);
|
|
809
|
-
const current =
|
|
878
|
+
const current =
|
|
879
|
+
this.#settings.get("modelRoleStorage") === "project" && scope !== undefined
|
|
880
|
+
? this.#thinkingLevelForScope(role, scope)
|
|
881
|
+
: (this.#roles[role]?.thinkingLevel ?? ThinkingLevel.Inherit);
|
|
810
882
|
const chips: StripChip[] = options.map(level => {
|
|
811
883
|
const label = getConfiguredThinkingLevelMetadata(level).label;
|
|
812
884
|
const glyph = thinkingLevelGlyph(level);
|
|
@@ -822,6 +894,7 @@ export class ModelHubComponent implements Component {
|
|
|
822
894
|
kind: "thinking",
|
|
823
895
|
item,
|
|
824
896
|
role,
|
|
897
|
+
scope,
|
|
825
898
|
chips,
|
|
826
899
|
index: preselect >= 0 ? preselect : 0,
|
|
827
900
|
returnToRoles,
|
|
@@ -832,7 +905,7 @@ export class ModelHubComponent implements Component {
|
|
|
832
905
|
const strip = this.#strip;
|
|
833
906
|
this.#strip = null;
|
|
834
907
|
this.#chipRanges = [];
|
|
835
|
-
if (strip?.kind === "thinking" && strip.returnToRoles) {
|
|
908
|
+
if ((strip?.kind === "scope" || strip?.kind === "thinking") && strip.returnToRoles) {
|
|
836
909
|
this.#setActiveEntry("roles");
|
|
837
910
|
this.#focus = "list";
|
|
838
911
|
}
|
|
@@ -847,12 +920,16 @@ export class ModelHubComponent implements Component {
|
|
|
847
920
|
case "assign":
|
|
848
921
|
if (chip.role) {
|
|
849
922
|
this.#strip = null;
|
|
850
|
-
this.#assignRole(strip.item, chip.role, false);
|
|
923
|
+
this.#assignRole(strip.item, chip.role, false, chip.scope);
|
|
851
924
|
}
|
|
852
925
|
return;
|
|
853
926
|
case "unassign":
|
|
854
927
|
if (chip.role) {
|
|
855
|
-
this.#
|
|
928
|
+
if (this.#settings.get("modelRoleStorage") === "project") {
|
|
929
|
+
this.#callbacks.onUnassign(chip.role, chip.scope);
|
|
930
|
+
} else {
|
|
931
|
+
this.#callbacks.onUnassign(chip.role);
|
|
932
|
+
}
|
|
856
933
|
this.#refreshAfterMutation();
|
|
857
934
|
}
|
|
858
935
|
this.#closeStrip();
|
|
@@ -869,9 +946,21 @@ export class ModelHubComponent implements Component {
|
|
|
869
946
|
this.#closeStrip();
|
|
870
947
|
this.#startAssignFallback(`${strip.item.model.provider}/*`, null);
|
|
871
948
|
return;
|
|
949
|
+
case "scope":
|
|
950
|
+
if (strip.role && chip.scope) {
|
|
951
|
+
this.#strip = null;
|
|
952
|
+
this.#assignRole(strip.item, strip.role, strip.returnToRoles, chip.scope);
|
|
953
|
+
}
|
|
954
|
+
return;
|
|
872
955
|
case "thinking":
|
|
873
956
|
if (strip.role && chip.thinkingLevel !== undefined) {
|
|
874
|
-
this.#callbacks.onAssign(
|
|
957
|
+
this.#callbacks.onAssign(
|
|
958
|
+
strip.item.model,
|
|
959
|
+
strip.role,
|
|
960
|
+
chip.thinkingLevel,
|
|
961
|
+
strip.item.selector,
|
|
962
|
+
strip.scope,
|
|
963
|
+
);
|
|
875
964
|
this.#refreshAfterMutation();
|
|
876
965
|
}
|
|
877
966
|
this.#closeStrip();
|
|
@@ -1305,13 +1394,20 @@ export class ModelHubComponent implements Component {
|
|
|
1305
1394
|
if (printable === "t") {
|
|
1306
1395
|
const assignment = role ? this.#roles[role] : undefined;
|
|
1307
1396
|
if (role && assignment) {
|
|
1397
|
+
const source =
|
|
1398
|
+
this.#settings.get("modelRoleStorage") === "project"
|
|
1399
|
+
? this.#settings.getModelRoleSource(role)
|
|
1400
|
+
: "default";
|
|
1401
|
+
const scope = source === "project" || source === "global" ? source : undefined;
|
|
1402
|
+
const scopedModel = scope ? this.#roleForScope(role, scope).model : assignment.model;
|
|
1403
|
+
if (!scopedModel) return;
|
|
1308
1404
|
const item: ModelBrowserItem = {
|
|
1309
|
-
provider:
|
|
1310
|
-
id:
|
|
1311
|
-
model:
|
|
1312
|
-
selector: `${
|
|
1405
|
+
provider: scopedModel.provider,
|
|
1406
|
+
id: scopedModel.id,
|
|
1407
|
+
model: scopedModel,
|
|
1408
|
+
selector: `${scopedModel.provider}/${scopedModel.id}`,
|
|
1313
1409
|
};
|
|
1314
|
-
this.#openThinkingStrip(item, role, true);
|
|
1410
|
+
this.#openThinkingStrip(item, role, true, scope);
|
|
1315
1411
|
}
|
|
1316
1412
|
return;
|
|
1317
1413
|
}
|
|
@@ -1770,9 +1866,9 @@ export class ModelHubComponent implements Component {
|
|
|
1770
1866
|
if (strip.kind === "roleName") {
|
|
1771
1867
|
return "Enter create + pick model · Esc cancel";
|
|
1772
1868
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1869
|
+
if (strip.kind === "role") return "←/→ choose · Enter assign/clear · Esc cancel";
|
|
1870
|
+
if (strip.kind === "scope") return "←/→ save scope · Enter choose · Esc cancel";
|
|
1871
|
+
return "←/→ thinking level · Enter apply · Esc keep";
|
|
1776
1872
|
}
|
|
1777
1873
|
if (this.#assigning !== null) {
|
|
1778
1874
|
switch (this.#assigning.kind) {
|
|
@@ -879,6 +879,10 @@ export class SessionSelectorComponent extends Container {
|
|
|
879
879
|
lockInput(): void {
|
|
880
880
|
this.#inputLocked = true;
|
|
881
881
|
}
|
|
882
|
+
/** Re-enable input after a failed resume so the user can pick again. */
|
|
883
|
+
unlockInput(): void {
|
|
884
|
+
this.#inputLocked = false;
|
|
885
|
+
}
|
|
882
886
|
|
|
883
887
|
/**
|
|
884
888
|
* Dispose the session list explicitly: while the delete-confirmation dialog
|
|
@@ -36,6 +36,7 @@ function makeSessionWithLastMessage(lastMessage: unknown, prewalkArmed: boolean
|
|
|
36
36
|
getPrewalkState: () => (prewalkArmed ? { target: { id: "cheap-model", provider: "openai" } } : undefined),
|
|
37
37
|
getAsyncJobSnapshot: () => undefined,
|
|
38
38
|
isAdvisorActive: () => false,
|
|
39
|
+
getAdvisorStatusOverview: () => ({ configured: false, advisors: [] }),
|
|
39
40
|
isFastModeActive: () => false,
|
|
40
41
|
configuredThinkingLevel: () => undefined,
|
|
41
42
|
modelRegistry: {
|