@oh-my-pi/pi-coding-agent 16.1.22 → 16.2.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/CHANGELOG.md +99 -1
- package/dist/cli.js +4387 -4164
- package/dist/types/advisor/__tests__/emission-guard.test.d.ts +1 -0
- package/dist/types/advisor/emission-guard.d.ts +73 -0
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/runtime.d.ts +7 -0
- package/dist/types/advisor/watchdog.d.ts +2 -0
- package/dist/types/cli/flag-tables.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +1 -1
- package/dist/types/cli/gc-cli.d.ts +58 -0
- package/dist/types/collab/display-name.d.ts +3 -0
- package/dist/types/collab/host.d.ts +0 -2
- package/dist/types/commands/gc.d.ts +37 -0
- package/dist/types/commands/worktree.d.ts +0 -3
- package/dist/types/config/inline-tool-descriptors-mode.d.ts +13 -0
- package/dist/types/config/model-discovery.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +6 -2
- package/dist/types/config/model-resolver.d.ts +12 -0
- package/dist/types/config/models-config-schema.d.ts +29 -2
- package/dist/types/config/models-config.d.ts +22 -1
- package/dist/types/config/settings-schema.d.ts +145 -21
- package/dist/types/config/settings.d.ts +13 -0
- package/dist/types/dap/config.d.ts +1 -1
- package/dist/types/edit/hashline/filesystem.d.ts +4 -2
- package/dist/types/edit/renderer.d.ts +4 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -17
- package/dist/types/extensibility/hooks/types.d.ts +10 -10
- package/dist/types/extensibility/plugins/marketplace/types.d.ts +1 -0
- package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +1 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/registry-helpers.d.ts +2 -0
- package/dist/types/internal-urls/router.d.ts +1 -1
- package/dist/types/internal-urls/ssh-protocol.d.ts +10 -0
- package/dist/types/internal-urls/types.d.ts +19 -2
- package/dist/types/irc/bus.d.ts +6 -0
- package/dist/types/mcp/transports/stdio.d.ts +25 -1
- package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +7 -0
- package/dist/types/modes/components/move-overlay.d.ts +23 -0
- package/dist/types/modes/components/plugin-selector.d.ts +2 -1
- package/dist/types/modes/components/queue-mode-selector.d.ts +2 -1
- package/dist/types/modes/components/select-list-mouse-routing.d.ts +2 -0
- package/dist/types/modes/components/settings-defs.d.ts +4 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/show-images-selector.d.ts +2 -1
- package/dist/types/modes/components/status-line/component.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/theme-selector.d.ts +2 -1
- package/dist/types/modes/components/thinking-selector.d.ts +2 -1
- package/dist/types/modes/controllers/command-controller.d.ts +10 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -1
- package/dist/types/modes/internal-url-autocomplete.d.ts +1 -1
- package/dist/types/modes/running-subagent-badge.d.ts +6 -0
- package/dist/types/modes/theme/mermaid-rendering.test.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +9 -1
- package/dist/types/sdk.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +2 -1
- package/dist/types/session/session-listing.d.ts +10 -1
- package/dist/types/session/session-manager.d.ts +13 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
- package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +1 -0
- package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +1 -0
- package/dist/types/ssh/connection-manager.d.ts +2 -0
- package/dist/types/ssh/file-transfer.d.ts +79 -0
- package/dist/types/ssh/utils.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +5 -0
- package/dist/types/task/executor.d.ts +16 -0
- package/dist/types/tiny/text.d.ts +1 -1
- package/dist/types/tools/builtin-names.d.ts +5 -1
- package/dist/types/tools/{find.d.ts → glob.d.ts} +15 -15
- package/dist/types/tools/{search.d.ts → grep.d.ts} +14 -14
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/tools/path-utils.d.ts +29 -0
- package/dist/types/tools/plan-mode-guard.d.ts +7 -0
- package/dist/types/tools/read.d.ts +2 -2
- package/dist/types/tools/render-utils.d.ts +8 -0
- package/dist/types/tools/renderers.d.ts +11 -0
- package/dist/types/tools/ssh.d.ts +2 -0
- package/dist/types/tools/todo.d.ts +0 -16
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/active-repo-context.d.ts +8 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/utils/markit-cache.d.ts +23 -0
- package/dist/types/utils/markit.d.ts +5 -1
- package/dist/types/utils/prompt-path.d.ts +1 -0
- package/dist/types/web/search/providers/duckduckgo.d.ts +14 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +28 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +29 -0
- package/dist/types/web/search/providers/xai.d.ts +13 -0
- package/dist/types/web/search/types.d.ts +18 -2
- package/package.json +30 -15
- package/scripts/bench-guard.ts +1 -1
- package/scripts/build-binary.ts +9 -9
- package/scripts/bundle-dist.ts +2 -2
- package/src/advisor/__tests__/advisor.test.ts +40 -4
- package/src/advisor/__tests__/emission-guard.test.ts +147 -0
- package/src/advisor/advise-tool.ts +1 -1
- package/src/advisor/emission-guard.ts +172 -0
- package/src/advisor/index.ts +1 -0
- package/src/advisor/runtime.ts +11 -0
- package/src/advisor/watchdog.ts +12 -1
- package/src/cli/args.ts +5 -2
- package/src/cli/auth-broker-cli.ts +17 -0
- package/src/cli/config-cli.ts +4 -0
- package/src/cli/flag-tables.ts +7 -4
- package/src/cli/gallery-cli.ts +14 -2
- package/src/cli/gallery-fixtures/edit.ts +60 -0
- package/src/cli/gallery-fixtures/fs.ts +17 -17
- package/src/cli/gallery-fixtures/search.ts +4 -4
- package/src/cli/gc-cli.ts +939 -0
- package/src/cli/usage-cli.ts +20 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/cli-commands.ts +1 -0
- package/src/collab/display-name.ts +13 -0
- package/src/collab/guest.ts +5 -1
- package/src/collab/host.ts +2 -13
- package/src/commands/gc.ts +46 -0
- package/src/commands/worktree.ts +6 -0
- package/src/config/inline-tool-descriptors-mode.ts +27 -0
- package/src/config/model-discovery.ts +77 -8
- package/src/config/model-registry.ts +89 -11
- package/src/config/model-resolver.ts +39 -1
- package/src/config/models-config-schema.ts +41 -5
- package/src/config/models-config.ts +4 -1
- package/src/config/settings-schema.ts +130 -27
- package/src/config/settings.ts +216 -7
- package/src/cursor.ts +1 -1
- package/src/dap/config.ts +152 -8
- package/src/dap/session.ts +1 -1
- package/src/discovery/helpers.ts +3 -1
- package/src/edit/hashline/diff.ts +14 -3
- package/src/edit/hashline/execute.ts +42 -6
- package/src/edit/hashline/filesystem.ts +49 -8
- package/src/edit/index.ts +1 -0
- package/src/edit/modes/patch.ts +11 -1
- package/src/edit/renderer.ts +140 -13
- package/src/eval/__tests__/agent-bridge.test.ts +101 -0
- package/src/eval/__tests__/julia-prelude.test.ts +18 -0
- package/src/eval/agent-bridge.ts +26 -3
- package/src/eval/jl/runner.jl +7 -1
- package/src/eval/js/tool-bridge.ts +2 -2
- package/src/export/html/index.ts +1 -1
- package/src/export/html/template.js +3 -1
- package/src/export/html/tool-views.generated.js +20 -20
- package/src/extensibility/extensions/types.ts +22 -22
- package/src/extensibility/hooks/types.ts +11 -11
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +10 -3
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +31 -10
- package/src/extensibility/plugins/marketplace/manager.ts +22 -0
- package/src/extensibility/plugins/marketplace/types.ts +1 -0
- package/src/internal-urls/__tests__/ssh-protocol.test.ts +331 -0
- package/src/internal-urls/docs-index.generated.txt +2 -2
- package/src/internal-urls/docs-index.ts +2 -3
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/registry-helpers.ts +19 -4
- package/src/internal-urls/router.ts +5 -3
- package/src/internal-urls/ssh-protocol.ts +367 -0
- package/src/internal-urls/types.ts +19 -2
- package/src/irc/bus.ts +11 -3
- package/src/lsp/index.ts +8 -1
- package/src/mcp/oauth-discovery.ts +20 -20
- package/src/mcp/tool-bridge.ts +32 -2
- package/src/mcp/transports/stdio.test.ts +20 -3
- package/src/mcp/transports/stdio.ts +45 -14
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +2 -2
- package/src/modes/components/__tests__/move-overlay.test.ts +166 -0
- package/src/modes/components/agent-hub.ts +3 -0
- package/src/modes/components/agent-transcript-viewer.ts +9 -7
- package/src/modes/components/custom-editor.ts +90 -5
- package/src/modes/components/move-overlay.ts +282 -0
- package/src/modes/components/plan-review-overlay.ts +35 -35
- package/src/modes/components/plugin-selector.ts +6 -1
- package/src/modes/components/queue-mode-selector.ts +6 -1
- package/src/modes/components/select-list-mouse-routing.ts +35 -0
- package/src/modes/components/session-selector.ts +11 -10
- package/src/modes/components/settings-defs.ts +14 -1
- package/src/modes/components/settings-selector.ts +196 -29
- package/src/modes/components/show-images-selector.ts +6 -1
- package/src/modes/components/status-line/component.ts +108 -28
- package/src/modes/components/status-line/segments.ts +5 -1
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/theme-selector.ts +6 -1
- package/src/modes/components/thinking-selector.ts +6 -1
- package/src/modes/components/tool-execution.ts +25 -9
- package/src/modes/components/tree-selector.ts +5 -5
- package/src/modes/controllers/command-controller.ts +140 -47
- package/src/modes/controllers/event-controller.ts +59 -3
- package/src/modes/controllers/input-controller.ts +70 -4
- package/src/modes/controllers/selector-controller.ts +15 -2
- package/src/modes/controllers/streaming-reveal.ts +17 -0
- package/src/modes/controllers/tool-args-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +142 -59
- package/src/modes/internal-url-autocomplete.ts +17 -2
- package/src/modes/prompt-action-autocomplete.ts +3 -1
- package/src/modes/running-subagent-badge.ts +13 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +2 -13
- package/src/modes/setup-wizard/scenes/providers.ts +2 -1
- package/src/modes/setup-wizard/scenes/theme.ts +6 -12
- package/src/modes/setup-wizard/scenes/web-search.ts +8 -13
- package/src/modes/setup-wizard/wizard-overlay.ts +6 -5
- package/src/modes/theme/mermaid-rendering.test.ts +53 -0
- package/src/modes/theme/theme.ts +42 -15
- package/src/modes/types.ts +9 -1
- package/src/modes/utils/interactive-context-helpers.ts +1 -1
- package/src/priority.json +15 -0
- package/src/prompts/advisor/active-repo-watchdog.md +6 -0
- package/src/prompts/advisor/system.md +21 -7
- package/src/prompts/agents/designer.md +1 -1
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/librarian.md +2 -2
- package/src/prompts/agents/plan.md +2 -2
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +2 -2
- package/src/prompts/system/active-repo-context.md +4 -0
- package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
- package/src/prompts/system/plan-mode-active.md +12 -3
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/system-prompt.md +8 -6
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/{find.md → glob.md} +1 -1
- package/src/prompts/tools/{search.md → grep.md} +3 -3
- package/src/prompts/tools/read.md +4 -2
- package/src/sdk.ts +98 -29
- package/src/session/agent-session.ts +828 -191
- package/src/session/session-history-format.ts +2 -2
- package/src/session/session-listing.ts +35 -2
- package/src/session/session-manager.ts +46 -0
- package/src/slash-commands/builtin-registry.ts +158 -22
- package/src/slash-commands/helpers/usage-report.ts +23 -2
- package/src/ssh/__tests__/connection-manager-args.test.ts +69 -0
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +68 -0
- package/src/ssh/connection-manager.ts +12 -15
- package/src/ssh/file-transfer.ts +209 -0
- package/src/ssh/utils.ts +24 -0
- package/src/system-prompt.ts +60 -28
- package/src/task/executor.ts +96 -36
- package/src/task/index.ts +3 -3
- package/src/task/render.ts +14 -13
- package/src/task/worktree.ts +38 -1
- package/src/tiny/text.ts +49 -4
- package/src/tiny/worker.ts +3 -3
- package/src/tools/acp-bridge.ts +6 -1
- package/src/tools/bash.ts +2 -2
- package/src/tools/builtin-names.ts +26 -2
- package/src/tools/{find.ts → glob.ts} +48 -42
- package/src/tools/{search.ts → grep.ts} +311 -129
- package/src/tools/index.ts +13 -14
- package/src/tools/irc.ts +6 -2
- package/src/tools/path-utils.ts +64 -1
- package/src/tools/plan-mode-guard.ts +26 -13
- package/src/tools/read.ts +35 -6
- package/src/tools/render-utils.ts +14 -0
- package/src/tools/renderers.ts +15 -4
- package/src/tools/ssh.ts +17 -2
- package/src/tools/todo.ts +4 -26
- package/src/tools/write.ts +25 -9
- package/src/utils/active-repo-context.ts +143 -0
- package/src/utils/edit-mode.ts +19 -2
- package/src/utils/image-resize.ts +88 -7
- package/src/utils/lang-from-path.ts +3 -3
- package/src/utils/markit-cache.ts +166 -0
- package/src/utils/markit.ts +86 -18
- package/src/utils/prompt-path.ts +3 -0
- package/src/utils/shell-snapshot.ts +1 -1
- package/src/utils/title-generator.ts +1 -1
- package/src/web/search/provider.ts +54 -34
- package/src/web/search/providers/duckduckgo.ts +140 -0
- package/src/web/search/providers/firecrawl.ts +144 -0
- package/src/web/search/providers/tinyfish.ts +159 -0
- package/src/web/search/providers/xai.ts +292 -0
- package/src/web/search/providers/zai.ts +142 -56
- package/src/web/search/types.ts +6 -2
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
type ImageBudget,
|
|
11
11
|
Input,
|
|
12
12
|
matchesKey,
|
|
13
|
-
|
|
13
|
+
replaceTabs,
|
|
14
|
+
routeSelectListMouse,
|
|
15
|
+
routeSgrMouseInput,
|
|
14
16
|
type SelectItem,
|
|
15
17
|
SelectList,
|
|
16
18
|
type SettingItem,
|
|
@@ -24,7 +26,14 @@ import {
|
|
|
24
26
|
visibleWidth,
|
|
25
27
|
} from "@oh-my-pi/pi-tui";
|
|
26
28
|
import type { ShapeTarget } from "@oh-my-pi/snapcompact";
|
|
27
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
getDefault,
|
|
31
|
+
getType,
|
|
32
|
+
normalizeProviderMaxInFlightRequests,
|
|
33
|
+
type SettingPath,
|
|
34
|
+
settings,
|
|
35
|
+
validateProviderMaxInFlightRequests,
|
|
36
|
+
} from "../../config/settings";
|
|
28
37
|
import type {
|
|
29
38
|
SettingTab,
|
|
30
39
|
StatusLinePreset,
|
|
@@ -50,6 +59,7 @@ import { getPreset } from "./status-line/presets";
|
|
|
50
59
|
*/
|
|
51
60
|
class TextInputSubmenu extends Container {
|
|
52
61
|
#input: Input;
|
|
62
|
+
#error: Text;
|
|
53
63
|
|
|
54
64
|
constructor(
|
|
55
65
|
label: string,
|
|
@@ -71,11 +81,18 @@ class TextInputSubmenu extends Container {
|
|
|
71
81
|
if (currentValue) {
|
|
72
82
|
this.#input.setValue(currentValue);
|
|
73
83
|
}
|
|
84
|
+
this.#error = new Text("", 0, 0);
|
|
74
85
|
this.#input.onSubmit = value => {
|
|
75
|
-
|
|
86
|
+
try {
|
|
87
|
+
this.onSubmit(value); // empty string clears the setting
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
this.#error.setText(theme.fg("error", truncateToWidth(replaceTabs(message).replace(/[\r\n]+/g, " "), 100)));
|
|
91
|
+
}
|
|
76
92
|
};
|
|
77
93
|
this.addChild(this.#input);
|
|
78
94
|
this.addChild(new Spacer(1));
|
|
95
|
+
this.addChild(this.#error);
|
|
79
96
|
this.addChild(new Text(theme.fg("dim", " Enter to save · Esc to cancel · Clear field to unset"), 0, 0));
|
|
80
97
|
}
|
|
81
98
|
|
|
@@ -89,7 +106,6 @@ class SelectSubmenu extends Container {
|
|
|
89
106
|
#previewText: Text | null = null;
|
|
90
107
|
#previewUpdateRequestId: number = 0;
|
|
91
108
|
#selectListLineOffset = 0;
|
|
92
|
-
#selectListLineCount = 0;
|
|
93
109
|
|
|
94
110
|
constructor(
|
|
95
111
|
title: string,
|
|
@@ -187,7 +203,6 @@ class SelectSubmenu extends Container {
|
|
|
187
203
|
const childLines = child.render(Math.max(1, width));
|
|
188
204
|
if (child === this.#selectList) {
|
|
189
205
|
this.#selectListLineOffset = lines.length;
|
|
190
|
-
this.#selectListLineCount = childLines.length;
|
|
191
206
|
}
|
|
192
207
|
lines.push(...childLines);
|
|
193
208
|
}
|
|
@@ -196,20 +211,7 @@ class SelectSubmenu extends Container {
|
|
|
196
211
|
|
|
197
212
|
/** Mouse routed from the host: wheel steps, hover lights, click confirms. */
|
|
198
213
|
routeMouse(event: SgrMouseEvent, line: number, _col: number): void {
|
|
199
|
-
|
|
200
|
-
this.#selectList.handleWheel(event.wheel);
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
const listLine = line - this.#selectListLineOffset;
|
|
204
|
-
const within = listLine >= 0 && listLine < this.#selectListLineCount;
|
|
205
|
-
const index = within ? this.#selectList.hitTest(listLine) : undefined;
|
|
206
|
-
if (event.motion) {
|
|
207
|
-
this.#selectList.setHoverIndex(index ?? null);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
if (event.leftClick && index !== undefined) {
|
|
211
|
-
this.#selectList.clickItem(index);
|
|
212
|
-
}
|
|
214
|
+
routeSelectListMouse(this.#selectList, event, line - this.#selectListLineOffset);
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
handleInput(data: string): void {
|
|
@@ -217,6 +219,113 @@ class SelectSubmenu extends Container {
|
|
|
217
219
|
}
|
|
218
220
|
}
|
|
219
221
|
|
|
222
|
+
class ProviderLimitsSubmenu extends Container {
|
|
223
|
+
#selectList: SelectList | undefined;
|
|
224
|
+
|
|
225
|
+
constructor(
|
|
226
|
+
private readonly providers: readonly string[],
|
|
227
|
+
private readonly onChange: (value: Record<string, number>) => void,
|
|
228
|
+
private readonly onCancel: () => void,
|
|
229
|
+
private readonly requestRender?: () => void,
|
|
230
|
+
) {
|
|
231
|
+
super();
|
|
232
|
+
this.#showProviderList();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
#providerIds(): string[] {
|
|
236
|
+
const limits = normalizeProviderMaxInFlightRequests(settings.get("providers.maxInFlightRequests"));
|
|
237
|
+
return [...new Set([...this.providers, ...Object.keys(limits)])].sort((a, b) => a.localeCompare(b));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#showProviderList(): void {
|
|
241
|
+
this.clear();
|
|
242
|
+
this.addChild(new Text(theme.bold(theme.fg("accent", "Max In-Flight Requests")), 0, 0));
|
|
243
|
+
this.addChild(new Spacer(1));
|
|
244
|
+
this.addChild(
|
|
245
|
+
new Text(
|
|
246
|
+
theme.fg(
|
|
247
|
+
"muted",
|
|
248
|
+
"Select a provider, enter a positive number to cap concurrent LLM requests, or clear it for unlimited.",
|
|
249
|
+
),
|
|
250
|
+
0,
|
|
251
|
+
0,
|
|
252
|
+
),
|
|
253
|
+
);
|
|
254
|
+
this.addChild(new Spacer(1));
|
|
255
|
+
|
|
256
|
+
const limits = normalizeProviderMaxInFlightRequests(settings.get("providers.maxInFlightRequests"));
|
|
257
|
+
const providerItems = this.#providerIds().map((provider): SelectItem => {
|
|
258
|
+
const limit = limits[provider];
|
|
259
|
+
return {
|
|
260
|
+
value: provider,
|
|
261
|
+
label: provider,
|
|
262
|
+
description: limit === undefined ? "Unlimited" : `Limit: ${limit}`,
|
|
263
|
+
};
|
|
264
|
+
});
|
|
265
|
+
const clearItem: SelectItem[] =
|
|
266
|
+
Object.keys(limits).length === 0
|
|
267
|
+
? []
|
|
268
|
+
: [{ value: "__clear_all", label: "Clear all limits", description: "Make every provider unlimited" }];
|
|
269
|
+
const items = [...providerItems, ...clearItem];
|
|
270
|
+
this.#selectList = new SelectList(items, Math.min(Math.max(items.length, 1), 12), getSelectListTheme());
|
|
271
|
+
this.#selectList.onSelect = item => {
|
|
272
|
+
if (item.value === "__clear_all") {
|
|
273
|
+
settings.set("providers.maxInFlightRequests", {});
|
|
274
|
+
this.onChange({});
|
|
275
|
+
this.#showProviderList();
|
|
276
|
+
this.requestRender?.();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
this.#showProviderEditor(item.value);
|
|
280
|
+
};
|
|
281
|
+
this.#selectList.onCancel = this.onCancel;
|
|
282
|
+
this.addChild(this.#selectList);
|
|
283
|
+
this.addChild(new Spacer(1));
|
|
284
|
+
this.addChild(new Text(theme.fg("dim", " Enter to edit provider · Esc to go back"), 0, 0));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#showProviderEditor(provider: string): void {
|
|
288
|
+
const limits = normalizeProviderMaxInFlightRequests(settings.get("providers.maxInFlightRequests"));
|
|
289
|
+
this.clear();
|
|
290
|
+
this.#selectList = undefined;
|
|
291
|
+
this.addChild(
|
|
292
|
+
new TextInputSubmenu(
|
|
293
|
+
`Max In-Flight Requests: ${provider}`,
|
|
294
|
+
"Enter a positive number. Decimals round down. Clear the field to make this provider unlimited.",
|
|
295
|
+
limits[provider]?.toString() ?? "",
|
|
296
|
+
value => {
|
|
297
|
+
const next = { ...limits };
|
|
298
|
+
const trimmed = value.trim();
|
|
299
|
+
if (trimmed === "") {
|
|
300
|
+
delete next[provider];
|
|
301
|
+
} else {
|
|
302
|
+
const limit = Number(trimmed);
|
|
303
|
+
if (!Number.isFinite(limit) || limit <= 0) throw new Error("Limit must be a positive number.");
|
|
304
|
+
next[provider] = Math.max(1, Math.floor(limit));
|
|
305
|
+
}
|
|
306
|
+
const normalized = validateProviderMaxInFlightRequests(next);
|
|
307
|
+
settings.set("providers.maxInFlightRequests", normalized);
|
|
308
|
+
this.onChange(normalized);
|
|
309
|
+
this.#showProviderList();
|
|
310
|
+
this.requestRender?.();
|
|
311
|
+
},
|
|
312
|
+
() => {
|
|
313
|
+
this.#showProviderList();
|
|
314
|
+
this.requestRender?.();
|
|
315
|
+
},
|
|
316
|
+
),
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
handleInput(data: string): void {
|
|
321
|
+
if (this.#selectList) {
|
|
322
|
+
this.#selectList.handleInput(data);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
this.children[0]?.handleInput?.(data);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
220
329
|
let cachedSidebarWidth: number | undefined;
|
|
221
330
|
/**
|
|
222
331
|
* Split-sidebar width derived from every group name in the schema (not just
|
|
@@ -258,6 +367,8 @@ export interface SettingsRuntimeContext {
|
|
|
258
367
|
thinkingLevel: ThinkingLevel | undefined;
|
|
259
368
|
/** Available themes */
|
|
260
369
|
availableThemes: string[];
|
|
370
|
+
/** Provider/source ids shown in /model. */
|
|
371
|
+
providers: string[];
|
|
261
372
|
/** Working directory for plugins tab */
|
|
262
373
|
cwd: string;
|
|
263
374
|
/** Active model (api + id); resolves what the snapcompact `auto` shape maps to. */
|
|
@@ -458,12 +569,14 @@ export class SettingsSelectorComponent implements Component {
|
|
|
458
569
|
* activates it (toggle / open submenu).
|
|
459
570
|
*/
|
|
460
571
|
#handleMouse(data: string): boolean {
|
|
461
|
-
|
|
462
|
-
|
|
572
|
+
return routeSgrMouseInput(data, event => this.#routeMouseEvent(event));
|
|
573
|
+
}
|
|
463
574
|
|
|
575
|
+
#routeMouseEvent(event: SgrMouseEvent): boolean {
|
|
464
576
|
const list = this.#searchList ?? this.#currentList;
|
|
465
|
-
// row() insets content by
|
|
466
|
-
const
|
|
577
|
+
// row() insets content by the border column plus a space.
|
|
578
|
+
const contentColInset = 2;
|
|
579
|
+
const innerCol = event.col - contentColInset;
|
|
467
580
|
const contentLine = event.row - this.#contentRowStart;
|
|
468
581
|
|
|
469
582
|
// An open submenu owns the pointer: wheel, hover, and clicks route into
|
|
@@ -723,10 +836,20 @@ export class SettingsSelectorComponent implements Component {
|
|
|
723
836
|
id: def.path,
|
|
724
837
|
label: def.label,
|
|
725
838
|
description: def.description,
|
|
726
|
-
currentValue:
|
|
839
|
+
currentValue: this.#formatTextInputValue(def.path, currentValue),
|
|
727
840
|
submenu: (cv, done) => this.#createTextInput(def, cv, done),
|
|
728
841
|
changed,
|
|
729
842
|
};
|
|
843
|
+
|
|
844
|
+
case "providerLimits":
|
|
845
|
+
return {
|
|
846
|
+
id: def.path,
|
|
847
|
+
label: def.label,
|
|
848
|
+
description: def.description,
|
|
849
|
+
currentValue: this.#formatProviderLimitsValue(currentValue),
|
|
850
|
+
submenu: (_cv, done) => this.#createProviderLimitsInput(done),
|
|
851
|
+
changed,
|
|
852
|
+
};
|
|
730
853
|
}
|
|
731
854
|
}
|
|
732
855
|
|
|
@@ -856,7 +979,7 @@ export class SettingsSelectorComponent implements Component {
|
|
|
856
979
|
*/
|
|
857
980
|
#createTextInput(
|
|
858
981
|
def: SettingDef & { type: "text" },
|
|
859
|
-
|
|
982
|
+
_currentValue: string,
|
|
860
983
|
done: (value?: string) => void,
|
|
861
984
|
): Container {
|
|
862
985
|
this.#textInputActive = true;
|
|
@@ -867,28 +990,72 @@ export class SettingsSelectorComponent implements Component {
|
|
|
867
990
|
return new TextInputSubmenu(
|
|
868
991
|
def.label,
|
|
869
992
|
def.description,
|
|
870
|
-
|
|
993
|
+
this.#formatTextInputEditValue(def.path, settings.get(def.path)),
|
|
871
994
|
value => {
|
|
872
995
|
// Empty string clears the setting; undefined-typed string settings
|
|
873
996
|
// store "" which the browser.ts expandPath ignores (no-op fallback).
|
|
874
997
|
this.#setSettingValue(def.path, value);
|
|
875
|
-
this.callbacks.onChange(def.path,
|
|
876
|
-
wrappedDone(
|
|
998
|
+
this.callbacks.onChange(def.path, settings.get(def.path));
|
|
999
|
+
wrappedDone(this.#formatTextInputValue(def.path, settings.get(def.path)));
|
|
877
1000
|
},
|
|
878
1001
|
() => wrappedDone(),
|
|
879
1002
|
);
|
|
880
1003
|
}
|
|
881
1004
|
|
|
1005
|
+
#createProviderLimitsInput(done: (value?: string) => void): Container {
|
|
1006
|
+
return new ProviderLimitsSubmenu(
|
|
1007
|
+
this.context.providers,
|
|
1008
|
+
value => {
|
|
1009
|
+
this.callbacks.onChange("providers.maxInFlightRequests", value);
|
|
1010
|
+
done(this.#formatProviderLimitsValue(value));
|
|
1011
|
+
},
|
|
1012
|
+
() => done(),
|
|
1013
|
+
this.context.requestRender,
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
#formatProviderLimitsValue(value: unknown): string {
|
|
1018
|
+
const limits = normalizeProviderMaxInFlightRequests(value);
|
|
1019
|
+
const entries = Object.entries(limits).sort(([a], [b]) => a.localeCompare(b));
|
|
1020
|
+
if (entries.length === 0) return "Unlimited";
|
|
1021
|
+
return entries.map(([provider, limit]) => `${provider}: ${limit}`).join(", ");
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
#formatTextInputValue(path: SettingPath, value: unknown): string {
|
|
1025
|
+
if (path === "providers.maxInFlightRequests") return this.#formatProviderLimitsValue(value);
|
|
1026
|
+
return this.#formatTextInputEditValue(path, value);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
#formatTextInputEditValue(_path: SettingPath, value: unknown): string {
|
|
1030
|
+
if (value === undefined || value === null) return "";
|
|
1031
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
1032
|
+
return String(value);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
882
1035
|
/**
|
|
883
1036
|
* Set a setting value, handling type conversion.
|
|
884
1037
|
*/
|
|
885
1038
|
#setSettingValue(path: SettingPath, value: string): void {
|
|
886
|
-
// Handle number conversions
|
|
887
1039
|
const currentValue = settings.get(path);
|
|
1040
|
+
const schemaType = getType(path);
|
|
888
1041
|
if (path === "compaction.thresholdPercent" && value === "default") {
|
|
889
1042
|
settings.set(path, -1 as never);
|
|
890
1043
|
} else if (path === "compaction.thresholdTokens" && value === "default") {
|
|
891
1044
|
settings.set(path, -1 as never);
|
|
1045
|
+
} else if (schemaType === "record") {
|
|
1046
|
+
let parsed: unknown;
|
|
1047
|
+
try {
|
|
1048
|
+
parsed = JSON.parse(value || "{}");
|
|
1049
|
+
} catch {
|
|
1050
|
+
throw new Error(`Invalid record JSON for ${path}`);
|
|
1051
|
+
}
|
|
1052
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1053
|
+
throw new Error(`Invalid record JSON for ${path}`);
|
|
1054
|
+
}
|
|
1055
|
+
if (path === "providers.maxInFlightRequests") {
|
|
1056
|
+
parsed = validateProviderMaxInFlightRequests(parsed);
|
|
1057
|
+
}
|
|
1058
|
+
settings.set(path, parsed as never);
|
|
892
1059
|
} else if (typeof currentValue === "number") {
|
|
893
1060
|
settings.set(path, Number(value) as never);
|
|
894
1061
|
} else if (typeof currentValue === "boolean") {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
|
|
1
|
+
import { Container, type SelectItem, SelectList, type SgrMouseEvent } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { getSelectListTheme } from "../../modes/theme/theme";
|
|
3
3
|
import { DynamicBorder } from "./dynamic-border";
|
|
4
|
+
import { routeSelectListMouseWithTopBorder } from "./select-list-mouse-routing";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Component that renders a show images selector with borders
|
|
@@ -42,4 +43,8 @@ export class ShowImagesSelectorComponent extends Container {
|
|
|
42
43
|
getSelectList(): SelectList {
|
|
43
44
|
return this.#selectList;
|
|
44
45
|
}
|
|
46
|
+
|
|
47
|
+
routeMouse(event: SgrMouseEvent, line: number, col: number): void {
|
|
48
|
+
routeSelectListMouseWithTopBorder(this.#selectList, event, line, col);
|
|
49
|
+
}
|
|
45
50
|
}
|
|
@@ -9,6 +9,7 @@ import { settings } from "../../../config/settings";
|
|
|
9
9
|
import type { AgentSession } from "../../../session/agent-session";
|
|
10
10
|
import type { OAuthAccountIdentity } from "../../../session/auth-storage";
|
|
11
11
|
import { limitMatchesActiveAccount } from "../../../slash-commands/helpers/active-oauth-account";
|
|
12
|
+
import { type ActiveRepoContext, resolveActiveRepoContextSync } from "../../../utils/active-repo-context";
|
|
12
13
|
import * as git from "../../../utils/git";
|
|
13
14
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../../../utils/session-color";
|
|
14
15
|
import { sanitizeStatusText } from "../../shared";
|
|
@@ -155,6 +156,12 @@ interface ContextUsageMemo {
|
|
|
155
156
|
skillsRef: readonly any[] | undefined;
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
interface ActiveRepoCache {
|
|
160
|
+
projectDir: string;
|
|
161
|
+
activeRepo: ActiveRepoContext | null;
|
|
162
|
+
effectiveGitCwd: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
158
165
|
const EMPTY_MESSAGES: readonly AgentMessage[] = [];
|
|
159
166
|
const STATUS_USAGE_START_DELAY_MS = 0;
|
|
160
167
|
const STATUS_USAGE_REFRESH_TIMEOUT_MS = 2_000;
|
|
@@ -169,6 +176,10 @@ function hasGitSegment(segments: readonly StatusLineSegmentId[]): boolean {
|
|
|
169
176
|
function hasPrSegment(segments: readonly StatusLineSegmentId[]): boolean {
|
|
170
177
|
return segments.includes("pr");
|
|
171
178
|
}
|
|
179
|
+
function hasPathSegment(segments: readonly StatusLineSegmentId[]): boolean {
|
|
180
|
+
return segments.includes("path");
|
|
181
|
+
}
|
|
182
|
+
|
|
172
183
|
function hasGitBackedSegment(segments: readonly StatusLineSegmentId[]): boolean {
|
|
173
184
|
return hasGitSegment(segments) || hasPrSegment(segments);
|
|
174
185
|
}
|
|
@@ -189,23 +200,27 @@ export class StatusLineComponent implements Component {
|
|
|
189
200
|
#autoCompactEnabled: boolean = true;
|
|
190
201
|
#hookStatuses: Map<string, string> = new Map();
|
|
191
202
|
#subagentCount: number = 0;
|
|
203
|
+
#subagentHubHint: string | undefined;
|
|
192
204
|
#sessionStartTime: number = Date.now();
|
|
193
205
|
#planModeStatus: { enabled: boolean; paused: boolean } | null = null;
|
|
194
206
|
#loopModeStatus: { enabled: boolean } | null = null;
|
|
195
207
|
#goalModeStatus: { enabled: boolean; paused: boolean } | null = null;
|
|
196
208
|
#collabStatus: CollabStatus | null = null;
|
|
197
209
|
#focusedAgentId: string | undefined;
|
|
210
|
+
#activeRepoCache: ActiveRepoCache | undefined;
|
|
198
211
|
|
|
199
212
|
// Git status caching (1s TTL)
|
|
200
213
|
#cachedGitStatus: { staged: number; unstaged: number; untracked: number } | null = null;
|
|
214
|
+
#cachedGitStatusCwd: string | undefined = undefined;
|
|
201
215
|
#gitStatusLastFetch = 0;
|
|
202
|
-
#
|
|
216
|
+
#gitStatusInFlightCwd: string | undefined = undefined;
|
|
203
217
|
|
|
204
218
|
// PR lookup caching (invalidated on branch/repo context changes)
|
|
205
219
|
#cachedPr: { number: number; url: string } | null | undefined = undefined;
|
|
206
220
|
#cachedPrContext: PrCacheContext | undefined = undefined;
|
|
207
221
|
#prLookupInFlight = false;
|
|
208
222
|
#defaultBranch?: string;
|
|
223
|
+
#defaultBranchCwd: string | undefined = undefined;
|
|
209
224
|
#lastTokensPerSecond: number | null = null;
|
|
210
225
|
#lastTokensPerSecondTimestamp: number | null = null;
|
|
211
226
|
|
|
@@ -248,6 +263,18 @@ export class StatusLineComponent implements Component {
|
|
|
248
263
|
);
|
|
249
264
|
}
|
|
250
265
|
|
|
266
|
+
#resolveActiveRepoCache(): ActiveRepoCache {
|
|
267
|
+
const projectDir = getProjectDir();
|
|
268
|
+
if (this.#activeRepoCache?.projectDir === projectDir) {
|
|
269
|
+
return this.#activeRepoCache;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const activeRepo = resolveActiveRepoContextSync(projectDir);
|
|
273
|
+
const effectiveGitCwd = activeRepo?.repoRoot ?? projectDir;
|
|
274
|
+
this.#activeRepoCache = { projectDir, activeRepo, effectiveGitCwd };
|
|
275
|
+
return this.#activeRepoCache;
|
|
276
|
+
}
|
|
277
|
+
|
|
251
278
|
/**
|
|
252
279
|
* Re-point the status line at another session (focus proxy). Invalidate: model/context/usage all derive
|
|
253
280
|
* from it. `focusedAgentId` is the focused subagent id while the view is proxied, undefined for main.
|
|
@@ -279,6 +306,12 @@ export class StatusLineComponent implements Component {
|
|
|
279
306
|
this.#subagentCount = count;
|
|
280
307
|
}
|
|
281
308
|
|
|
309
|
+
/** Hub key label shown in the forced running-subagents badge. */
|
|
310
|
+
setSubagentHubHint(hint: string | undefined): void {
|
|
311
|
+
const trimmed = hint?.trim();
|
|
312
|
+
this.#subagentHubHint = trimmed ? trimmed : undefined;
|
|
313
|
+
}
|
|
314
|
+
|
|
282
315
|
/** Active subagent count as currently displayed (collab state mirroring). */
|
|
283
316
|
get subagentCount(): number {
|
|
284
317
|
return this.#subagentCount;
|
|
@@ -328,7 +361,8 @@ export class StatusLineComponent implements Component {
|
|
|
328
361
|
return;
|
|
329
362
|
}
|
|
330
363
|
|
|
331
|
-
const
|
|
364
|
+
const { effectiveGitCwd } = this.#resolveActiveRepoCache();
|
|
365
|
+
const repository = git.repo.resolveSync(effectiveGitCwd);
|
|
332
366
|
if (!repository) return;
|
|
333
367
|
|
|
334
368
|
const watchPath = git.repo.isReftableSync(repository)
|
|
@@ -383,17 +417,17 @@ export class StatusLineComponent implements Component {
|
|
|
383
417
|
this.#cachedBranchCwd = undefined;
|
|
384
418
|
this.#cachedPrContext = undefined;
|
|
385
419
|
}
|
|
386
|
-
#getCurrentBranch(): string | null {
|
|
420
|
+
#getCurrentBranch(effectiveGitCwd?: string): string | null {
|
|
387
421
|
if (!this.#gitEnabled()) return null;
|
|
388
422
|
|
|
389
|
-
const
|
|
390
|
-
if (this.#cachedBranch !== undefined && this.#cachedBranchCwd ===
|
|
423
|
+
const gitCwd = effectiveGitCwd ?? this.#resolveActiveRepoCache().effectiveGitCwd;
|
|
424
|
+
if (this.#cachedBranch !== undefined && this.#cachedBranchCwd === gitCwd) {
|
|
391
425
|
return this.#cachedBranch;
|
|
392
426
|
}
|
|
393
427
|
|
|
394
|
-
const head = git.head.resolveSync(
|
|
428
|
+
const head = git.head.resolveSync(gitCwd);
|
|
395
429
|
const gitHeadPath = head?.headPath ?? null;
|
|
396
|
-
this.#cachedBranchCwd =
|
|
430
|
+
this.#cachedBranchCwd = gitCwd;
|
|
397
431
|
this.#cachedBranchRepoId = gitHeadPath;
|
|
398
432
|
if (!head) {
|
|
399
433
|
this.#cachedBranch = null;
|
|
@@ -405,12 +439,18 @@ export class StatusLineComponent implements Component {
|
|
|
405
439
|
return this.#cachedBranch ?? null;
|
|
406
440
|
}
|
|
407
441
|
|
|
408
|
-
#isDefaultBranch(branch: string): boolean {
|
|
442
|
+
#isDefaultBranch(branch: string, effectiveGitCwd: string): boolean {
|
|
443
|
+
if (this.#defaultBranchCwd !== effectiveGitCwd) {
|
|
444
|
+
this.#defaultBranch = undefined;
|
|
445
|
+
this.#defaultBranchCwd = effectiveGitCwd;
|
|
446
|
+
}
|
|
447
|
+
|
|
409
448
|
if (this.#defaultBranch === undefined) {
|
|
410
449
|
this.#defaultBranch = "main";
|
|
450
|
+
const lookupCwd = effectiveGitCwd;
|
|
411
451
|
(async () => {
|
|
412
|
-
const resolved = await git.branch.default(
|
|
413
|
-
if (this.#disposed) return;
|
|
452
|
+
const resolved = await git.branch.default(lookupCwd);
|
|
453
|
+
if (this.#disposed || this.#defaultBranchCwd !== lookupCwd) return;
|
|
414
454
|
if (resolved) {
|
|
415
455
|
this.#defaultBranch = resolved;
|
|
416
456
|
if (this.#onBranchChange) {
|
|
@@ -422,32 +462,43 @@ export class StatusLineComponent implements Component {
|
|
|
422
462
|
return branch === this.#defaultBranch;
|
|
423
463
|
}
|
|
424
464
|
|
|
425
|
-
#getGitStatus(): { staged: number; unstaged: number; untracked: number } | null {
|
|
465
|
+
#getGitStatus(effectiveGitCwd?: string): { staged: number; unstaged: number; untracked: number } | null {
|
|
426
466
|
if (!this.#gitEnabled()) return null;
|
|
427
|
-
|
|
467
|
+
|
|
468
|
+
const gitCwd = effectiveGitCwd ?? this.#resolveActiveRepoCache().effectiveGitCwd;
|
|
469
|
+
if (this.#gitStatusInFlightCwd !== undefined) {
|
|
470
|
+
return this.#cachedGitStatusCwd === gitCwd ? this.#cachedGitStatus : null;
|
|
471
|
+
}
|
|
472
|
+
if (this.#cachedGitStatusCwd === gitCwd && Date.now() - this.#gitStatusLastFetch < 1000) {
|
|
428
473
|
return this.#cachedGitStatus;
|
|
429
474
|
}
|
|
430
475
|
|
|
431
|
-
this.#
|
|
476
|
+
this.#gitStatusInFlightCwd = gitCwd;
|
|
432
477
|
|
|
433
478
|
(async () => {
|
|
479
|
+
let nextStatus: { staged: number; unstaged: number; untracked: number } | null = null;
|
|
434
480
|
try {
|
|
435
|
-
|
|
481
|
+
nextStatus = await git.status.summary(gitCwd);
|
|
436
482
|
} catch {
|
|
437
|
-
|
|
483
|
+
nextStatus = null;
|
|
438
484
|
} finally {
|
|
439
|
-
this.#
|
|
440
|
-
|
|
485
|
+
if (this.#gitStatusInFlightCwd === gitCwd) {
|
|
486
|
+
this.#cachedGitStatus = nextStatus;
|
|
487
|
+
this.#cachedGitStatusCwd = gitCwd;
|
|
488
|
+
this.#gitStatusLastFetch = Date.now();
|
|
489
|
+
this.#gitStatusInFlightCwd = undefined;
|
|
490
|
+
}
|
|
441
491
|
}
|
|
442
492
|
})();
|
|
443
493
|
|
|
444
|
-
return this.#cachedGitStatus;
|
|
494
|
+
return this.#cachedGitStatusCwd === gitCwd ? this.#cachedGitStatus : null;
|
|
445
495
|
}
|
|
446
496
|
|
|
447
|
-
#lookupPr(): { number: number; url: string } | null {
|
|
497
|
+
#lookupPr(effectiveGitCwd?: string): { number: number; url: string } | null {
|
|
448
498
|
if (!this.#gitEnabled()) return null;
|
|
449
499
|
|
|
450
|
-
const
|
|
500
|
+
const gitCwd = effectiveGitCwd ?? this.#resolveActiveRepoCache().effectiveGitCwd;
|
|
501
|
+
const branch = this.#getCurrentBranch(gitCwd);
|
|
451
502
|
const currentContext = branch ? createPrCacheContext(branch, this.#cachedBranchRepoId ?? null) : null;
|
|
452
503
|
|
|
453
504
|
if (canReuseCachedPr(this.#cachedPr, this.#cachedPrContext, currentContext)) {
|
|
@@ -456,19 +507,26 @@ export class StatusLineComponent implements Component {
|
|
|
456
507
|
|
|
457
508
|
const stalePr = this.#cachedPr;
|
|
458
509
|
|
|
459
|
-
|
|
460
|
-
|
|
510
|
+
if (!branch) {
|
|
511
|
+
this.#cachedPr = null;
|
|
512
|
+
this.#cachedPrContext = undefined;
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Don't look up if detached, default branch, or already in flight.
|
|
517
|
+
if (branch === "detached" || this.#isDefaultBranch(branch, gitCwd) || this.#prLookupInFlight) {
|
|
461
518
|
return stalePr ?? null;
|
|
462
519
|
}
|
|
463
520
|
|
|
464
521
|
this.#prLookupInFlight = true;
|
|
465
522
|
const lookupContext = currentContext;
|
|
523
|
+
const lookupCwd = gitCwd;
|
|
466
524
|
|
|
467
525
|
// Fire async lookup, keep stale value visible until resolved
|
|
468
526
|
(async () => {
|
|
469
527
|
// Helper: only write cache if branch/repo context hasn't changed since launch
|
|
470
528
|
const setCachedPr = (value: { number: number; url: string } | null) => {
|
|
471
|
-
const latestBranch = this.#getCurrentBranch();
|
|
529
|
+
const latestBranch = this.#getCurrentBranch(lookupCwd);
|
|
472
530
|
const latestContext = latestBranch
|
|
473
531
|
? createPrCacheContext(latestBranch, this.#cachedBranchRepoId ?? null)
|
|
474
532
|
: undefined;
|
|
@@ -479,7 +537,7 @@ export class StatusLineComponent implements Component {
|
|
|
479
537
|
};
|
|
480
538
|
try {
|
|
481
539
|
// Requires `gh repo set-default` to be configured; fails gracefully if not
|
|
482
|
-
const result = await $`gh pr view --json number,url`.quiet().nothrow();
|
|
540
|
+
const result = await $`gh pr view --json number,url`.cwd(lookupCwd).quiet().nothrow();
|
|
483
541
|
if (this.#disposed) return;
|
|
484
542
|
if (result.exitCode !== 0) {
|
|
485
543
|
setCachedPr(null);
|
|
@@ -746,6 +804,7 @@ export class StatusLineComponent implements Component {
|
|
|
746
804
|
#buildSegmentContext(
|
|
747
805
|
width: number,
|
|
748
806
|
segmentOptions: StatusLineSettings["segmentOptions"],
|
|
807
|
+
includePath: boolean,
|
|
749
808
|
includeContext: boolean,
|
|
750
809
|
includeGit: boolean,
|
|
751
810
|
includePr: boolean,
|
|
@@ -788,13 +847,18 @@ export class StatusLineComponent implements Component {
|
|
|
788
847
|
contextPercent = collabState.contextUsage.percent ?? contextPercent;
|
|
789
848
|
}
|
|
790
849
|
|
|
791
|
-
const
|
|
792
|
-
const
|
|
793
|
-
const
|
|
794
|
-
|
|
850
|
+
const shouldResolveActiveRepo = this.#gitEnabled() && (includePath || includeGit || includePr);
|
|
851
|
+
const projectDir = getProjectDir();
|
|
852
|
+
const activeRepoCache = shouldResolveActiveRepo
|
|
853
|
+
? this.#resolveActiveRepoCache()
|
|
854
|
+
: { projectDir, activeRepo: null, effectiveGitCwd: projectDir };
|
|
855
|
+
const gitBranch = includeGit || includePr ? this.#getCurrentBranch(activeRepoCache.effectiveGitCwd) : null;
|
|
856
|
+
const gitStatus = includeGit ? this.#getGitStatus(activeRepoCache.effectiveGitCwd) : null;
|
|
857
|
+
const gitPr = includePr ? this.#lookupPr(activeRepoCache.effectiveGitCwd) : null;
|
|
795
858
|
return {
|
|
796
859
|
session: this.session,
|
|
797
860
|
focusedAgentId: this.#focusedAgentId,
|
|
861
|
+
activeRepo: activeRepoCache.activeRepo,
|
|
798
862
|
width,
|
|
799
863
|
options: segmentOptions ?? {},
|
|
800
864
|
planMode: this.#planModeStatus,
|
|
@@ -858,8 +922,17 @@ export class StatusLineComponent implements Component {
|
|
|
858
922
|
};
|
|
859
923
|
}
|
|
860
924
|
|
|
925
|
+
#subagentBadgeText(): string | undefined {
|
|
926
|
+
if (this.#subagentCount === 0) return undefined;
|
|
927
|
+
const noun = this.#subagentCount === 1 ? "agent" : "agents";
|
|
928
|
+
const hubHint = this.#subagentHubHint ? ` — ${this.#subagentHubHint} hub` : " — Agent Hub";
|
|
929
|
+
return theme.fg("statusLineSubagents", `${theme.icon.agents} ${this.#subagentCount} ${noun} running${hubHint}`);
|
|
930
|
+
}
|
|
931
|
+
|
|
861
932
|
#buildStatusLine(width: number): string {
|
|
862
933
|
const effectiveSettings = this.#resolveSettings();
|
|
934
|
+
const includePath =
|
|
935
|
+
hasPathSegment(effectiveSettings.leftSegments) || hasPathSegment(effectiveSettings.rightSegments);
|
|
863
936
|
const includeContext =
|
|
864
937
|
hasContextSegment(effectiveSettings.leftSegments) || hasContextSegment(effectiveSettings.rightSegments);
|
|
865
938
|
const gitEnabled = this.#gitEnabled();
|
|
@@ -871,6 +944,7 @@ export class StatusLineComponent implements Component {
|
|
|
871
944
|
const ctx = this.#buildSegmentContext(
|
|
872
945
|
width,
|
|
873
946
|
effectiveSettings.segmentOptions,
|
|
947
|
+
includePath,
|
|
874
948
|
includeContext,
|
|
875
949
|
includeGit,
|
|
876
950
|
includePr,
|
|
@@ -888,11 +962,13 @@ export class StatusLineComponent implements Component {
|
|
|
888
962
|
const transparentBg = bgAnsi === TRANSPARENT_BG_ANSI;
|
|
889
963
|
const fgAnsi = theme.getFgAnsi("text");
|
|
890
964
|
const sepAnsi = theme.getFgAnsi("statusLineSep");
|
|
965
|
+
const subagentBadge = this.#subagentBadgeText();
|
|
891
966
|
|
|
892
967
|
// Collect visible segment contents
|
|
893
968
|
const leftParts: string[] = [];
|
|
894
969
|
const leftSegIds: StatusLineSegmentId[] = [];
|
|
895
970
|
for (const segId of effectiveSettings.leftSegments) {
|
|
971
|
+
if (subagentBadge && segId === "subagents") continue;
|
|
896
972
|
const rendered = renderSegment(segId, ctx);
|
|
897
973
|
if (rendered.visible && rendered.content) {
|
|
898
974
|
leftParts.push(rendered.content);
|
|
@@ -902,6 +978,7 @@ export class StatusLineComponent implements Component {
|
|
|
902
978
|
|
|
903
979
|
const rightParts: string[] = [];
|
|
904
980
|
for (const segId of effectiveSettings.rightSegments) {
|
|
981
|
+
if (subagentBadge && segId === "subagents") continue;
|
|
905
982
|
const rendered = renderSegment(segId, ctx);
|
|
906
983
|
if (rendered.visible && rendered.content) {
|
|
907
984
|
rightParts.push(rendered.content);
|
|
@@ -912,6 +989,9 @@ export class StatusLineComponent implements Component {
|
|
|
912
989
|
if (runningBackgroundJobs > 0) {
|
|
913
990
|
rightParts.unshift(theme.fg("statusLineSubagents", `${theme.icon.job} ${runningBackgroundJobs}`));
|
|
914
991
|
}
|
|
992
|
+
if (subagentBadge) {
|
|
993
|
+
rightParts.unshift(subagentBadge);
|
|
994
|
+
}
|
|
915
995
|
const topFillWidth = Math.max(0, width);
|
|
916
996
|
const left = [...leftParts];
|
|
917
997
|
const right = [...rightParts];
|