@oh-my-pi/pi-coding-agent 16.1.23 → 16.2.1
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 +82 -0
- package/dist/cli.js +3785 -3695
- 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 +13 -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/collab/display-name.d.ts +3 -0
- package/dist/types/collab/host.d.ts +0 -2
- 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 +91 -21
- package/dist/types/config/settings.d.ts +2 -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/modes/acp/acp-event-mapper.d.ts +1 -0
- 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/theme.d.ts +1 -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 +8 -1
- package/dist/types/session/blob-store.d.ts +4 -0
- 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 +3 -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/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/build-binary.ts +9 -9
- package/scripts/bundle-dist.ts +2 -2
- package/src/advisor/__tests__/advisor.test.ts +61 -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 +27 -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/usage-cli.ts +20 -1
- package/src/cli/web-search-cli.ts +1 -1
- 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/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 +85 -27
- package/src/config/settings.ts +172 -1
- package/src/cursor.ts +1 -1
- package/src/dap/config.ts +152 -8
- package/src/dap/session.ts +1 -1
- package/src/discovery/claude-plugins.ts +3 -2
- 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 +38 -2
- 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/agent-bridge.ts +26 -3
- 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 +8 -3
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +25 -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/mcp/tool-bridge.ts +32 -2
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-agent.ts +14 -1
- package/src/modes/acp/acp-event-mapper.ts +91 -27
- 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 +68 -2
- package/src/modes/controllers/selector-controller.ts +5 -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 +116 -50
- 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/theme.ts +9 -1
- 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/context-files.md +8 -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 +3 -3
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/system-prompt.md +6 -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 +65 -25
- package/src/session/agent-session.ts +519 -132
- package/src/session/blob-store.ts +24 -0
- package/src/session/session-history-format.ts +2 -2
- package/src/session/session-manager.ts +46 -0
- package/src/slash-commands/builtin-registry.ts +138 -20
- 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 +56 -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/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/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/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/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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
3
4
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
4
5
|
import { type AutocompleteProvider, matchesKey, type SlashCommand } from "@oh-my-pi/pi-tui";
|
|
5
6
|
import { $env, isEnoent, logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
@@ -124,6 +125,7 @@ const TINY_TITLE_PROGRESS_REVEAL_DELAY_MS = 1_000;
|
|
|
124
125
|
// deliberate human double-tap is always tens of milliseconds apart.
|
|
125
126
|
const LEFT_DOUBLE_TAP_MIN_GAP_MS = 40;
|
|
126
127
|
const LEFT_DOUBLE_TAP_MAX_GAP_MS = 500;
|
|
128
|
+
const STREAMING_ESCAPE_CANCEL_WINDOW_MS = 2_000;
|
|
127
129
|
|
|
128
130
|
export class InputController {
|
|
129
131
|
constructor(
|
|
@@ -148,6 +150,16 @@ export class InputController {
|
|
|
148
150
|
// (>= LEFT_DOUBLE_TAP_MAX_GAP_MS) starts a fresh sequence. See
|
|
149
151
|
// #detectLeftDoubleTap.
|
|
150
152
|
#leftTapCount = 0;
|
|
153
|
+
// Streaming turns use a two-step Esc: first press arms this token, second press
|
|
154
|
+
// within the window aborts the same live assistant turn. The token is a per-turn
|
|
155
|
+
// sentinel minted lazily on demand and reset on every `agent_start`/`agent_end`
|
|
156
|
+
// (see setupKeyHandlers), so it survives `message_start`/`message_update`
|
|
157
|
+
// transitions inside a single turn but cannot leak across turn boundaries.
|
|
158
|
+
#streamingEscapeTurnSentinel: object | undefined;
|
|
159
|
+
#streamingEscapeArmedToken: object | undefined;
|
|
160
|
+
#streamingEscapeArmedUntil = 0;
|
|
161
|
+
#streamingEscapeTimer: NodeJS.Timeout | undefined;
|
|
162
|
+
#streamingEscapeSessionSubscribed = false;
|
|
151
163
|
// Sequential index for `local://attachment-N` references created by large-paste and
|
|
152
164
|
// pasted-file attachments. Seeded from 0 and bumped past existing attachment files.
|
|
153
165
|
#attachmentCounter = 0;
|
|
@@ -197,8 +209,50 @@ export class InputController {
|
|
|
197
209
|
const unsubscribe = tinyTitleClient.onProgress(update);
|
|
198
210
|
}
|
|
199
211
|
|
|
212
|
+
#clearStreamingEscapeArm(): void {
|
|
213
|
+
this.#streamingEscapeArmedToken = undefined;
|
|
214
|
+
this.#streamingEscapeArmedUntil = 0;
|
|
215
|
+
if (this.#streamingEscapeTimer) {
|
|
216
|
+
clearTimeout(this.#streamingEscapeTimer);
|
|
217
|
+
this.#streamingEscapeTimer = undefined;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#handleStreamingEscape(): void {
|
|
222
|
+
if (!this.#streamingEscapeTurnSentinel) {
|
|
223
|
+
this.#streamingEscapeTurnSentinel = {};
|
|
224
|
+
}
|
|
225
|
+
const token = this.#streamingEscapeTurnSentinel;
|
|
226
|
+
const now = Date.now();
|
|
227
|
+
if (this.#streamingEscapeArmedToken === token && now <= this.#streamingEscapeArmedUntil) {
|
|
228
|
+
this.#clearStreamingEscapeArm();
|
|
229
|
+
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
this.#clearStreamingEscapeArm();
|
|
234
|
+
this.#streamingEscapeArmedToken = token;
|
|
235
|
+
this.#streamingEscapeArmedUntil = now + STREAMING_ESCAPE_CANCEL_WINDOW_MS;
|
|
236
|
+
this.#streamingEscapeTimer = setTimeout(() => {
|
|
237
|
+
if (this.#streamingEscapeArmedToken === token && Date.now() >= this.#streamingEscapeArmedUntil) {
|
|
238
|
+
this.#clearStreamingEscapeArm();
|
|
239
|
+
}
|
|
240
|
+
}, STREAMING_ESCAPE_CANCEL_WINDOW_MS);
|
|
241
|
+
this.#streamingEscapeTimer.unref?.();
|
|
242
|
+
this.ctx.showStatus("Press Esc again within 2s to cancel streaming.");
|
|
243
|
+
}
|
|
244
|
+
|
|
200
245
|
setupKeyHandlers(): void {
|
|
201
246
|
this.ctx.editor.setActionKeys("app.interrupt", this.ctx.keybindings.getKeys("app.interrupt"));
|
|
247
|
+
if (!this.#streamingEscapeSessionSubscribed && typeof this.ctx.session.subscribe === "function") {
|
|
248
|
+
this.#streamingEscapeSessionSubscribed = true;
|
|
249
|
+
this.ctx.session.subscribe(event => {
|
|
250
|
+
if (event.type === "agent_start" || event.type === "agent_end") {
|
|
251
|
+
this.#streamingEscapeTurnSentinel = undefined;
|
|
252
|
+
this.#clearStreamingEscapeArm();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
202
256
|
if (!this.#focusedLeftTapListenerInstalled) {
|
|
203
257
|
this.#focusedLeftTapListenerInstalled = true;
|
|
204
258
|
this.ctx.ui.addInputListener(data => {
|
|
@@ -274,7 +328,7 @@ export class InputController {
|
|
|
274
328
|
if (this.ctx.loopModeEnabled) {
|
|
275
329
|
this.ctx.pauseLoop();
|
|
276
330
|
if (this.ctx.session.isStreaming) {
|
|
277
|
-
|
|
331
|
+
this.#handleStreamingEscape();
|
|
278
332
|
} else {
|
|
279
333
|
this.ctx.cancelPendingSubmission();
|
|
280
334
|
}
|
|
@@ -325,12 +379,13 @@ export class InputController {
|
|
|
325
379
|
this.ctx.isPythonMode = false;
|
|
326
380
|
this.ctx.updateEditorBorderColor();
|
|
327
381
|
} else if (this.ctx.session.isStreaming) {
|
|
328
|
-
|
|
382
|
+
this.#handleStreamingEscape();
|
|
329
383
|
} else if (this.ctx.editor.getText().trim()) {
|
|
330
384
|
// Esc with typed text clears the draft instead of (or before) any double-Esc action
|
|
331
385
|
this.ctx.editor.setText("");
|
|
332
386
|
this.ctx.ui.requestRender();
|
|
333
387
|
this.ctx.lastEscapeTime = 0;
|
|
388
|
+
this.#clearStreamingEscapeArm();
|
|
334
389
|
} else {
|
|
335
390
|
// Double-interrupt with empty editor triggers /tree, /branch, or nothing based on setting
|
|
336
391
|
const action = settings.get("doubleEscapeAction");
|
|
@@ -1684,6 +1739,17 @@ export class InputController {
|
|
|
1684
1739
|
}
|
|
1685
1740
|
|
|
1686
1741
|
toggleThinkingBlockVisibility(): void {
|
|
1742
|
+
// When thinking is "off", thinking blocks are always hidden (some
|
|
1743
|
+
// providers return them regardless). The toggle is meaningless in
|
|
1744
|
+
// that state — inform the user instead of silently flipping the
|
|
1745
|
+
// persisted value. When thinking is on, the toggle works normally
|
|
1746
|
+
// even if blocks are already hidden (user may want to show them).
|
|
1747
|
+
const thinkingOff =
|
|
1748
|
+
((this.ctx.viewSession ?? this.ctx.session)?.thinkingLevel ?? ThinkingLevel.Off) === ThinkingLevel.Off;
|
|
1749
|
+
if (thinkingOff) {
|
|
1750
|
+
this.ctx.showStatus("Thinking is off — enable thinking to show blocks");
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1687
1753
|
this.ctx.hideThinkingBlock = !this.ctx.hideThinkingBlock;
|
|
1688
1754
|
this.ctx.settings.set("hideThinkingBlock", this.ctx.hideThinkingBlock);
|
|
1689
1755
|
|
|
@@ -134,6 +134,9 @@ export class SelectorController {
|
|
|
134
134
|
availableThinkingLevels: [...this.ctx.session.getAvailableThinkingLevels()],
|
|
135
135
|
thinkingLevel: this.ctx.session.thinkingLevel,
|
|
136
136
|
availableThemes,
|
|
137
|
+
providers: [...new Set(this.ctx.session.getAvailableModels().map(model => model.provider))].sort(
|
|
138
|
+
(a, b) => a.localeCompare(b),
|
|
139
|
+
),
|
|
137
140
|
cwd: getProjectDir(),
|
|
138
141
|
model: this.ctx.session.model,
|
|
139
142
|
imageBudget: this.ctx.ui.imageBudget,
|
|
@@ -341,7 +344,7 @@ export class SelectorController {
|
|
|
341
344
|
this.ctx.hideThinkingBlock = value as boolean;
|
|
342
345
|
for (const child of this.ctx.chatContainer.children) {
|
|
343
346
|
if (child instanceof AssistantMessageComponent) {
|
|
344
|
-
child.setHideThinkingBlock(
|
|
347
|
+
child.setHideThinkingBlock(this.ctx.effectiveHideThinkingBlock);
|
|
345
348
|
}
|
|
346
349
|
}
|
|
347
350
|
// Full clear + replay so blocks frozen in committed scrollback on
|
|
@@ -1302,7 +1305,7 @@ export class SelectorController {
|
|
|
1302
1305
|
getTool: name => this.ctx.session.getToolByName(name),
|
|
1303
1306
|
getMessageRenderer: type => this.ctx.session.extensionRunner?.getMessageRenderer(type),
|
|
1304
1307
|
cwd: this.ctx.sessionManager.getCwd(),
|
|
1305
|
-
hideThinkingBlock: () => this.ctx.
|
|
1308
|
+
hideThinkingBlock: () => this.ctx.effectiveHideThinkingBlock,
|
|
1306
1309
|
proseOnlyThinking: () => this.ctx.proseOnlyThinking,
|
|
1307
1310
|
focusAgent: id => this.ctx.focusAgentSession(id),
|
|
1308
1311
|
sessionFile: this.ctx.sessionManager.getSessionFile() ?? null,
|
|
@@ -270,6 +270,23 @@ export class StreamingRevealController {
|
|
|
270
270
|
this.#unitCounter.reset();
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Re-read cached visibility flags (hideThinkingBlock, proseOnlyThinking)
|
|
275
|
+
* and re-render the current target. Called when the thinking level changes
|
|
276
|
+
* mid-stream so the reveal controller doesn't keep rendering with stale values.
|
|
277
|
+
*/
|
|
278
|
+
resyncVisibility(): void {
|
|
279
|
+
if (!this.#target || !this.#component) return;
|
|
280
|
+
this.#hideThinkingBlock = this.#getHideThinkingBlock();
|
|
281
|
+
this.#proseOnlyThinking = this.#getProseOnlyThinking();
|
|
282
|
+
// Recalculate visible units — hiding thinking blocks may reduce the total,
|
|
283
|
+
// and the reveal position may now exceed it.
|
|
284
|
+
const total = this.#visibleUnits(this.#target);
|
|
285
|
+
this.#revealed = Math.min(this.#revealed, total);
|
|
286
|
+
this.#renderCurrent();
|
|
287
|
+
this.#syncTimer(total);
|
|
288
|
+
}
|
|
289
|
+
|
|
273
290
|
/** Total reveal units of `message`, memoized per block across ticks. */
|
|
274
291
|
#visibleUnits(message: AssistantMessage): number {
|
|
275
292
|
let total = 0;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseStreamingJson } from "@oh-my-pi/pi-
|
|
1
|
+
import { parseStreamingJson } from "@oh-my-pi/pi-utils";
|
|
2
2
|
import { nextStep, STREAMING_REVEAL_FRAME_MS } from "./streaming-reveal";
|
|
3
3
|
|
|
4
4
|
/** Minimal component surface the reveal pushes frames into. */
|
|
@@ -86,6 +86,7 @@ import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" wit
|
|
|
86
86
|
import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with {
|
|
87
87
|
type: "text",
|
|
88
88
|
};
|
|
89
|
+
import type { AgentRegistry } from "../registry/agent-registry";
|
|
89
90
|
import type { AgentSession, AgentSessionEvent, ResolvedRoleModel } from "../session/agent-session";
|
|
90
91
|
import type { CompactMode } from "../session/compact-modes";
|
|
91
92
|
import { HistoryStorage } from "../session/history-storage";
|
|
@@ -104,9 +105,10 @@ import { normalizeLocalScheme } from "../tools/path-utils";
|
|
|
104
105
|
import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../tools/render-utils";
|
|
105
106
|
import { setAutoQaConsentHandler } from "../tools/report-tool-issue";
|
|
106
107
|
import { type ResolveToolDetails, runResolveInvocation } from "../tools/resolve";
|
|
107
|
-
import { formatPhaseDisplayName,
|
|
108
|
+
import { formatPhaseDisplayName, todoMatchesAnyDescription } from "../tools/todo";
|
|
108
109
|
import { ToolError } from "../tools/tool-errors";
|
|
109
110
|
import { vocalizer } from "../tts/vocalizer";
|
|
111
|
+
import { renderTreeList } from "../tui/tree-list";
|
|
110
112
|
import type { EventBus } from "../utils/event-bus";
|
|
111
113
|
import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
112
114
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
@@ -154,6 +156,7 @@ import {
|
|
|
154
156
|
parseLoopLimitArgs,
|
|
155
157
|
} from "./loop-limit";
|
|
156
158
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
159
|
+
import { countRunningSubagentBadgeAgents, getRunningSubagentBadgeRegistry } from "./running-subagent-badge";
|
|
157
160
|
import type { ObservableSession } from "./session-observer-registry";
|
|
158
161
|
import { SessionObserverRegistry } from "./session-observer-registry";
|
|
159
162
|
import { runProviderSetupWizard } from "./setup-wizard/lazy";
|
|
@@ -309,13 +312,15 @@ export interface InteractiveModeOptions {
|
|
|
309
312
|
}
|
|
310
313
|
|
|
311
314
|
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
315
|
+
* Anchored live-region container for the HUD/status rows between the transcript
|
|
316
|
+
* and the editor (working loader, todo + subagent HUDs, transient notification
|
|
317
|
+
* panels). While it has content every row is live: it reports a seam at 0 so the
|
|
318
|
+
* engine never commits these anchored, rebuilt-in-place rows to native
|
|
319
|
+
* scrollback — otherwise stale duplicates pile up above the live copy on short
|
|
320
|
+
* terminals once the loader sits below a tall HUD. The transcript's own seam,
|
|
321
|
+
* when present, sits higher and wins (topmost-seam merge in TUI.render).
|
|
317
322
|
*/
|
|
318
|
-
class
|
|
323
|
+
class AnchoredLiveContainer extends Container implements NativeScrollbackLiveRegion {
|
|
319
324
|
getNativeScrollbackLiveRegionStart(): number | undefined {
|
|
320
325
|
return this.children.length > 0 ? 0 : undefined;
|
|
321
326
|
}
|
|
@@ -408,6 +413,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
408
413
|
#modelCycleClearTimer: NodeJS.Timeout | undefined;
|
|
409
414
|
todoPhases: TodoPhase[] = [];
|
|
410
415
|
hideThinkingBlock = false;
|
|
416
|
+
/**
|
|
417
|
+
* Effective thinking-block visibility: hidden when the user's setting is on
|
|
418
|
+
* OR the session thinking level is "off". Some providers (MiniMax, GLM,
|
|
419
|
+
* DeepSeek) return thinking blocks even with reasoning disabled; this
|
|
420
|
+
* respects the user's intent when they set thinking to "off" (#626).
|
|
421
|
+
*/
|
|
422
|
+
get effectiveHideThinkingBlock(): boolean {
|
|
423
|
+
return this.hideThinkingBlock || (this.viewSession?.thinkingLevel ?? ThinkingLevel.Off) === ThinkingLevel.Off;
|
|
424
|
+
}
|
|
411
425
|
proseOnlyThinking = true;
|
|
412
426
|
compactionQueuedMessages: CompactionQueuedMessage[] = [];
|
|
413
427
|
pendingTools = new Map<string, ToolExecutionHandle>();
|
|
@@ -541,6 +555,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
541
555
|
#observerRegistry: SessionObserverRegistry;
|
|
542
556
|
#eventBus?: EventBus;
|
|
543
557
|
#eventBusUnsubscribers: Array<() => void> = [];
|
|
558
|
+
#agentRegistryUnsubscribe?: () => void;
|
|
559
|
+
#agentRegistrySubscriptionTarget?: AgentRegistry;
|
|
544
560
|
#mcpStatusOrder: string[] = [];
|
|
545
561
|
#mcpPendingServers = new Set<string>();
|
|
546
562
|
#mcpConnectedServers = new Set<string>();
|
|
@@ -598,13 +614,13 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
598
614
|
setTerminalTextSizing(settings.get("tui.textSizing") && TERMINAL.textSizing);
|
|
599
615
|
this.chatContainer = new TranscriptContainer();
|
|
600
616
|
this.pendingMessagesContainer = new Container();
|
|
601
|
-
this.statusContainer = new
|
|
602
|
-
this.todoContainer = new
|
|
603
|
-
this.subagentContainer = new
|
|
604
|
-
this.btwContainer = new
|
|
605
|
-
this.omfgContainer = new
|
|
606
|
-
this.errorBannerContainer = new
|
|
607
|
-
this.modelCycleContainer = new
|
|
617
|
+
this.statusContainer = new AnchoredLiveContainer();
|
|
618
|
+
this.todoContainer = new AnchoredLiveContainer();
|
|
619
|
+
this.subagentContainer = new AnchoredLiveContainer();
|
|
620
|
+
this.btwContainer = new AnchoredLiveContainer();
|
|
621
|
+
this.omfgContainer = new AnchoredLiveContainer();
|
|
622
|
+
this.errorBannerContainer = new AnchoredLiveContainer();
|
|
623
|
+
this.modelCycleContainer = new AnchoredLiveContainer();
|
|
608
624
|
this.editor = new CustomEditor(getEditorTheme());
|
|
609
625
|
this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
610
626
|
this.editor.setAutocompleteMaxVisible(settings.get("autocompleteMaxVisible"));
|
|
@@ -818,13 +834,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
818
834
|
|
|
819
835
|
this.ui.addChild(this.chatContainer);
|
|
820
836
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
821
|
-
this.ui.addChild(this.statusContainer);
|
|
822
837
|
this.ui.addChild(this.todoContainer);
|
|
823
838
|
this.ui.addChild(this.subagentContainer);
|
|
824
839
|
this.ui.addChild(this.btwContainer);
|
|
825
840
|
this.ui.addChild(this.omfgContainer);
|
|
826
841
|
this.ui.addChild(this.errorBannerContainer);
|
|
827
842
|
this.ui.addChild(this.modelCycleContainer);
|
|
843
|
+
// Working loader / transient status sits below the sticky todo + subagent
|
|
844
|
+
// HUDs, just above the editor's hook-widget top margin — so it reads next to
|
|
845
|
+
// the prompt while keeping the one-line gap above the editor.
|
|
846
|
+
this.ui.addChild(this.statusContainer);
|
|
828
847
|
this.ui.addChild(this.statusLine); // Only renders hook statuses (main status in editor border)
|
|
829
848
|
this.ui.addChild(this.hookWidgetContainerAbove);
|
|
830
849
|
this.ui.addChild(this.editorContainer);
|
|
@@ -839,8 +858,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
839
858
|
this.#observerRegistry.subscribeToEventBus(this.#eventBus);
|
|
840
859
|
}
|
|
841
860
|
this.#observerRegistry.setMainSession(this.sessionManager.getSessionFile() ?? undefined);
|
|
861
|
+
this.statusLine.setSubagentHubHint(
|
|
862
|
+
this.keybindings.getDisplayString("app.agents.hub") ||
|
|
863
|
+
this.keybindings.getDisplayString("app.session.observe") ||
|
|
864
|
+
undefined,
|
|
865
|
+
);
|
|
866
|
+
this.syncRunningSubagentBadge();
|
|
842
867
|
this.#observerRegistry.onChange(() => {
|
|
843
|
-
this.
|
|
868
|
+
this.syncRunningSubagentBadge();
|
|
844
869
|
// Auto-checkmark todos whose matching subagent just succeeded, then
|
|
845
870
|
// re-render so the running override (the static "live" glyph when a
|
|
846
871
|
// subagent is doing the work for a still-pending todo) updates as
|
|
@@ -1435,6 +1460,22 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1435
1460
|
this.ui.requestRender();
|
|
1436
1461
|
}
|
|
1437
1462
|
|
|
1463
|
+
/** Refresh the running-subagents status badge from the active local or collab registry. */
|
|
1464
|
+
syncRunningSubagentBadge(): void {
|
|
1465
|
+
const registry = getRunningSubagentBadgeRegistry(this.collabGuest);
|
|
1466
|
+
if (this.#agentRegistrySubscriptionTarget !== registry) {
|
|
1467
|
+
this.#agentRegistryUnsubscribe?.();
|
|
1468
|
+
this.#agentRegistrySubscriptionTarget = registry;
|
|
1469
|
+
this.#agentRegistryUnsubscribe = registry.onChange(() => {
|
|
1470
|
+
this.syncRunningSubagentBadge();
|
|
1471
|
+
this.ui.requestRender();
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
const count = countRunningSubagentBadgeAgents(registry);
|
|
1475
|
+
this.statusLine.setSubagentCount(count);
|
|
1476
|
+
this.updateEditorTopBorder();
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1438
1479
|
updateEditorTopBorder(): void {
|
|
1439
1480
|
const availableWidth = this.editor.getTopBorderAvailableWidth(this.ui.terminal.columns);
|
|
1440
1481
|
const topBorder = this.statusLine.getTopBorder(availableWidth);
|
|
@@ -1486,9 +1527,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1486
1527
|
case "abandoned":
|
|
1487
1528
|
return theme.fg("error", `${prefix}${checkbox.unchecked} ${chalk.strikethrough(todo.content)}`) + marker;
|
|
1488
1529
|
default:
|
|
1489
|
-
if (matched) {
|
|
1490
|
-
return theme.fg("accent", `${prefix}${checkbox.unchecked} ${todo.content}`) + marker;
|
|
1491
|
-
}
|
|
1530
|
+
if (matched) return theme.fg("accent", `${prefix}${checkbox.unchecked} ${todo.content}`) + marker;
|
|
1492
1531
|
return theme.fg("dim", `${prefix}${checkbox.unchecked} ${todo.content}`) + marker;
|
|
1493
1532
|
}
|
|
1494
1533
|
}
|
|
@@ -1629,44 +1668,66 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1629
1668
|
this.todoContainer.clear();
|
|
1630
1669
|
const phases = this.todoPhases.filter(phase => phase.tasks.length > 0);
|
|
1631
1670
|
if (phases.length === 0) return;
|
|
1632
|
-
const
|
|
1633
|
-
const
|
|
1634
|
-
const
|
|
1671
|
+
const expanded = this.todoExpanded;
|
|
1672
|
+
const multiPhase = phases.length > 1;
|
|
1673
|
+
const activeIdx = phases.indexOf(this.#getActivePhase(phases) ?? phases[0]);
|
|
1674
|
+
// Fixed budgets keep the HUD bounded regardless of plan size / progress.
|
|
1675
|
+
const subsequentStageCap = 4; // stages shown after the active one (header count implies the rest)
|
|
1676
|
+
const activeTaskCap = 5; // open tasks previewed for the active stage
|
|
1635
1677
|
|
|
1636
1678
|
const activeDescs = this.#getActiveSubagentDescriptions();
|
|
1637
|
-
// A pending todo "lights up" (accent
|
|
1638
|
-
//
|
|
1679
|
+
// A pending todo "lights up" (accent) when an in-flight subagent is doing
|
|
1680
|
+
// its work, matched by normalized content overlap.
|
|
1639
1681
|
const isMatched = (todo: TodoItem): boolean =>
|
|
1640
1682
|
activeDescs.length > 0 && todoMatchesAnyDescription(todo.content, activeDescs);
|
|
1641
1683
|
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
const
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1684
|
+
// Task subtree for a phase. Collapsed previews the first open tasks — the
|
|
1685
|
+
// stage's `done/total` makes the hidden count obvious, so there is no
|
|
1686
|
+
// "… more" row; expanded lists every task.
|
|
1687
|
+
const renderTasks = (phase: TodoPhase): string[] => {
|
|
1688
|
+
const open = phase.tasks.filter(t => t.status === "pending" || t.status === "in_progress");
|
|
1689
|
+
const base = expanded ? phase.tasks : open.length > 0 ? open : phase.tasks;
|
|
1690
|
+
const items = expanded ? base : base.slice(0, activeTaskCap);
|
|
1691
|
+
return renderTreeList(
|
|
1692
|
+
{ items, expanded: true, renderItem: todo => this.#formatTodoLine(todo, "", isMatched(todo)) },
|
|
1693
|
+
theme,
|
|
1650
1694
|
);
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1695
|
+
};
|
|
1696
|
+
|
|
1697
|
+
// One phase node. The active stage is highlighted with normal-brightness task
|
|
1698
|
+
// progress; other stages render their whole row (name + progress) in the
|
|
1699
|
+
// brighter muted gray. The root header carries overall stage progression.
|
|
1700
|
+
const renderPhase = (phase: TodoPhase, oneBased: number, isActive: boolean): string | string[] => {
|
|
1701
|
+
const label = multiPhase ? formatPhaseDisplayName(phase.name, oneBased) : phase.name;
|
|
1702
|
+
const done = phase.tasks.filter(t => t.status === "completed").length;
|
|
1703
|
+
const progress = ` · ${done}/${phase.tasks.length}`;
|
|
1704
|
+
if (!isActive) {
|
|
1705
|
+
const header = theme.fg("muted", label) + theme.fg("dim", progress);
|
|
1706
|
+
return expanded ? [header, ...renderTasks(phase)] : header;
|
|
1657
1707
|
}
|
|
1658
|
-
|
|
1659
|
-
return;
|
|
1660
|
-
}
|
|
1708
|
+
const header = theme.bold(theme.fg("accent", label)) + theme.fg("dim", progress);
|
|
1709
|
+
return [header, ...renderTasks(phase)];
|
|
1710
|
+
};
|
|
1661
1711
|
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1712
|
+
// Collapsed: active stage + a bounded number of following stages (the
|
|
1713
|
+
// header's "n/total" count implies any not shown). Expanded: every stage
|
|
1714
|
+
// from the top. Roman numerals stay tied to the real phase index.
|
|
1715
|
+
const baseIdx = expanded ? 0 : activeIdx;
|
|
1716
|
+
const phaseSlice = expanded ? phases.slice(baseIdx) : phases.slice(baseIdx, baseIdx + 1 + subsequentStageCap);
|
|
1717
|
+
const phaseTreeLines = renderTreeList(
|
|
1718
|
+
{
|
|
1719
|
+
items: phaseSlice,
|
|
1720
|
+
expanded: true,
|
|
1721
|
+
renderItem: (phase, ctx) => renderPhase(phase, baseIdx + ctx.index + 1, baseIdx + ctx.index === activeIdx),
|
|
1722
|
+
},
|
|
1723
|
+
theme,
|
|
1724
|
+
);
|
|
1669
1725
|
|
|
1726
|
+
// Header carries overall stage progression, e.g. "Todos · 1/8".
|
|
1727
|
+
const root =
|
|
1728
|
+
theme.bold(theme.fg("accent", "Todos")) +
|
|
1729
|
+
(multiPhase ? theme.fg("dim", ` · ${activeIdx + 1}/${phases.length}`) : "");
|
|
1730
|
+
const lines = ["", root, ...phaseTreeLines.map(line => ` ${line}`)];
|
|
1670
1731
|
this.todoContainer.addChild(new Text(lines.join("\n"), 1, 0));
|
|
1671
1732
|
}
|
|
1672
1733
|
|
|
@@ -3156,6 +3217,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3156
3217
|
}
|
|
3157
3218
|
this.#eventBusUnsubscribers = [];
|
|
3158
3219
|
this.#observerRegistry.dispose();
|
|
3220
|
+
this.#agentRegistryUnsubscribe?.();
|
|
3221
|
+
this.#agentRegistryUnsubscribe = undefined;
|
|
3222
|
+
this.#agentRegistrySubscriptionTarget = undefined;
|
|
3159
3223
|
this.#eventController.dispose();
|
|
3160
3224
|
this.statusLine.dispose();
|
|
3161
3225
|
if (this.#resizeHandler) {
|
|
@@ -3441,8 +3505,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3441
3505
|
getSymbolTheme().spinnerFrames,
|
|
3442
3506
|
);
|
|
3443
3507
|
this.statusContainer.addChild(this.loadingAnimation);
|
|
3508
|
+
} else if (!this.statusContainer.children.includes(this.loadingAnimation)) {
|
|
3509
|
+
this.statusContainer.clear();
|
|
3510
|
+
this.statusContainer.addChild(this.loadingAnimation);
|
|
3444
3511
|
}
|
|
3445
|
-
|
|
3446
3512
|
this.applyPendingWorkingMessage();
|
|
3447
3513
|
}
|
|
3448
3514
|
|
|
@@ -3626,7 +3692,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3626
3692
|
return this.#commandController.handleForkCommand();
|
|
3627
3693
|
}
|
|
3628
3694
|
|
|
3629
|
-
handleMoveCommand(targetPath
|
|
3695
|
+
handleMoveCommand(targetPath?: string): Promise<void> {
|
|
3630
3696
|
return this.#commandController.handleMoveCommand(targetPath);
|
|
3631
3697
|
}
|
|
3632
3698
|
|
|
@@ -60,6 +60,16 @@ function fuzzyScore(query: string, target: string): number {
|
|
|
60
60
|
return Math.max(1, 40 - gaps * 5);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/** Decode a completion `value` for fuzzy matching (the inserted value may be
|
|
64
|
+
* percent-encoded, e.g. an ssh host `alice%40prod`); identity for plain values. */
|
|
65
|
+
function decodeUrlCompletionValue(value: string): string {
|
|
66
|
+
try {
|
|
67
|
+
return decodeURIComponent(value);
|
|
68
|
+
} catch {
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
/**
|
|
64
74
|
* Detect a completable internal-url token immediately before the cursor.
|
|
65
75
|
* Returns `null` when the text is not a `scheme://` token whose scheme is
|
|
@@ -82,17 +92,22 @@ export function extractInternalUrlContext(textBeforeCursor: string): InternalUrl
|
|
|
82
92
|
*/
|
|
83
93
|
export async function getInternalUrlSuggestions(
|
|
84
94
|
textBeforeCursor: string,
|
|
95
|
+
cwd?: string,
|
|
85
96
|
): Promise<{ items: AutocompleteItem[]; prefix: string } | null> {
|
|
86
97
|
const ctx = extractInternalUrlContext(textBeforeCursor);
|
|
87
98
|
if (!ctx) return null;
|
|
88
99
|
|
|
89
|
-
const candidates = await InternalUrlRouter.instance().complete(
|
|
100
|
+
const candidates = await InternalUrlRouter.instance().complete(
|
|
101
|
+
ctx.scheme,
|
|
102
|
+
ctx.query,
|
|
103
|
+
cwd === undefined ? undefined : { cwd },
|
|
104
|
+
);
|
|
90
105
|
if (!candidates || candidates.length === 0) return null;
|
|
91
106
|
|
|
92
107
|
const query = ctx.query.toLowerCase();
|
|
93
108
|
const scored: Array<{ item: AutocompleteItem; score: number }> = [];
|
|
94
109
|
for (const candidate of candidates) {
|
|
95
|
-
const target = candidate.value.toLowerCase();
|
|
110
|
+
const target = decodeUrlCompletionValue(candidate.value).toLowerCase();
|
|
96
111
|
if (!fuzzyMatch(query, target)) continue;
|
|
97
112
|
scored.push({
|
|
98
113
|
item: {
|
|
@@ -96,9 +96,11 @@ function getPromptActionPrefix(textBeforeCursor: string): string | null {
|
|
|
96
96
|
export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
97
97
|
#baseProvider: CombinedAutocompleteProvider;
|
|
98
98
|
#actions: PromptActionDefinition[];
|
|
99
|
+
#basePath: string;
|
|
99
100
|
|
|
100
101
|
constructor(commands: SlashCommand[], basePath: string, actions: PromptActionDefinition[]) {
|
|
101
102
|
this.#baseProvider = new CombinedAutocompleteProvider(commands, basePath);
|
|
103
|
+
this.#basePath = basePath;
|
|
102
104
|
this.#actions = actions;
|
|
103
105
|
}
|
|
104
106
|
|
|
@@ -133,7 +135,7 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
const urlSuggestions = await getInternalUrlSuggestions(textBeforeCursor);
|
|
138
|
+
const urlSuggestions = await getInternalUrlSuggestions(textBeforeCursor, this.#basePath);
|
|
137
139
|
if (urlSuggestions) return urlSuggestions;
|
|
138
140
|
|
|
139
141
|
if (!isSettingsInitialized() || settings.get("emojiAutocomplete")) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AgentRegistry } from "../registry/agent-registry";
|
|
2
|
+
|
|
3
|
+
export interface RunningSubagentRegistrySource {
|
|
4
|
+
agentRegistry: AgentRegistry;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getRunningSubagentBadgeRegistry(collabGuest: RunningSubagentRegistrySource | undefined): AgentRegistry {
|
|
8
|
+
return collabGuest?.agentRegistry ?? AgentRegistry.global();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function countRunningSubagentBadgeAgents(registry: AgentRegistry): number {
|
|
12
|
+
return registry.list().filter(ref => ref.kind === "sub" && ref.status === "running").length;
|
|
13
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type SelectItem, SelectList, type SgrMouseEvent } from "@oh-my-pi/pi-tui";
|
|
1
|
+
import { routeSelectListMouse, type SelectItem, SelectList, type SgrMouseEvent } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { getSelectListTheme, type SymbolPreset, setSymbolPreset, theme } from "../../theme/theme";
|
|
3
3
|
import type { SetupScene, SetupSceneController, SetupSceneHost } from "./types";
|
|
4
4
|
|
|
@@ -65,18 +65,7 @@ class GlyphSceneController implements SetupSceneController {
|
|
|
65
65
|
/** Wheel moves the highlight (live preview); hover lights the row under the pointer; click confirms it. */
|
|
66
66
|
routeMouse(event: SgrMouseEvent, line: number, _col: number): void {
|
|
67
67
|
if (this.#committing) return;
|
|
68
|
-
|
|
69
|
-
this.#selectList.handleWheel(event.wheel);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const index = this.#selectList.hitTest(line - this.#listRowStart);
|
|
73
|
-
if (event.motion) {
|
|
74
|
-
this.#selectList.setHoverIndex(index ?? null);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
if (event.leftClick && index !== undefined) {
|
|
78
|
-
this.#selectList.clickItem(index);
|
|
79
|
-
}
|
|
68
|
+
routeSelectListMouse(this.#selectList, event, line - this.#listRowStart);
|
|
80
69
|
}
|
|
81
70
|
|
|
82
71
|
render(width: number): readonly string[] {
|
|
@@ -74,7 +74,8 @@ class ProvidersSceneController implements SetupSceneController {
|
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
76
|
if (event.motion) this.#tabBar.setHoverTab(null);
|
|
77
|
-
const
|
|
77
|
+
const spacerRowsAfterTabs = 1;
|
|
78
|
+
const bodyLine = line - this.#tabRowCount - spacerRowsAfterTabs;
|
|
78
79
|
if (tab.routeMouse) {
|
|
79
80
|
tab.routeMouse(event, bodyLine, col);
|
|
80
81
|
return;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
padding,
|
|
3
|
+
routeSelectListMouse,
|
|
3
4
|
type SelectItem,
|
|
4
5
|
SelectList,
|
|
5
6
|
type SgrMouseEvent,
|
|
@@ -128,18 +129,11 @@ class ThemeSceneController implements SetupSceneController {
|
|
|
128
129
|
|
|
129
130
|
/** Wheel moves the highlight (live preview); hover lights the row under the pointer; click confirms it. */
|
|
130
131
|
routeMouse(event: SgrMouseEvent, line: number, _col: number): void {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (event.motion) {
|
|
137
|
-
this.#selectList.setHoverIndex(index ?? null);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (event.leftClick && index !== undefined) {
|
|
141
|
-
this.#selectList.clickItem(index);
|
|
142
|
-
}
|
|
132
|
+
// Mirror the pre-helper flow: wheel/motion are always processed, but a
|
|
133
|
+
// hidden list (#listRowStart < 0, e.g. while loading all themes) must
|
|
134
|
+
// never hit-test a row — route through a line that resolves to undefined.
|
|
135
|
+
const listLine = this.#listRowStart >= 0 ? line - this.#listRowStart : Number.NEGATIVE_INFINITY;
|
|
136
|
+
routeSelectListMouse(this.#selectList, event, listLine);
|
|
143
137
|
}
|
|
144
138
|
|
|
145
139
|
render(width: number): readonly string[] {
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
routeSelectListMouse,
|
|
3
|
+
type SelectItem,
|
|
4
|
+
SelectList,
|
|
5
|
+
type SgrMouseEvent,
|
|
6
|
+
truncateToWidth,
|
|
7
|
+
} from "@oh-my-pi/pi-tui";
|
|
2
8
|
import { SETTINGS_SCHEMA } from "../../../config/settings-schema";
|
|
3
9
|
import { getSearchProvider, setPreferredSearchProvider } from "../../../web/search/provider";
|
|
4
10
|
import { isSearchProviderPreference, type SearchProviderId } from "../../../web/search/types";
|
|
@@ -59,18 +65,7 @@ export class WebSearchTab implements SetupTab {
|
|
|
59
65
|
|
|
60
66
|
/** Wheel moves the highlight; hover lights the row under the pointer; click confirms it. */
|
|
61
67
|
routeMouse(event: SgrMouseEvent, line: number, _col: number): void {
|
|
62
|
-
|
|
63
|
-
this.#list.handleWheel(event.wheel);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const index = this.#list.hitTest(line - this.#listRowStart);
|
|
67
|
-
if (event.motion) {
|
|
68
|
-
this.#list.setHoverIndex(index ?? null);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
if (event.leftClick && index !== undefined) {
|
|
72
|
-
this.#list.clickItem(index);
|
|
73
|
-
}
|
|
68
|
+
routeSelectListMouse(this.#list, event, line - this.#listRowStart);
|
|
74
69
|
}
|
|
75
70
|
|
|
76
71
|
invalidate(): void {
|