@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
|
@@ -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");
|
|
@@ -969,7 +1024,7 @@ export class InputController {
|
|
|
969
1024
|
//
|
|
970
1025
|
// SIGTSTP: brush-core (the embedded shell behind every bash tool call)
|
|
971
1026
|
// installs a tokio SIGTSTP listener on `Process::wait` to detect when
|
|
972
|
-
// its children have been stopped (`crates/brush-core
|
|
1027
|
+
// its children have been stopped (`crates/vendor/brush-core/src/sys/
|
|
973
1028
|
// unix/signal.rs::tstp_signal_listener` → `tokio::signal::unix::
|
|
974
1029
|
// signal(SIGTSTP)`). Per tokio's documented contract, the first call
|
|
975
1030
|
// for a given SignalKind permanently replaces the kernel-default
|
|
@@ -993,7 +1048,7 @@ export class InputController {
|
|
|
993
1048
|
// children that must survive the suspend (MCP stdio servers via
|
|
994
1049
|
// the `detached: true` spawn in `mcp/transports/stdio.ts`, every
|
|
995
1050
|
// brush external command via brush's per-child `setsid` in
|
|
996
|
-
// `crates/brush-core
|
|
1051
|
+
// `crates/vendor/brush-core/src/commands.rs`) are already in
|
|
997
1052
|
// their own sessions, so pgid=0 does not reach them.
|
|
998
1053
|
process.kill(0, "SIGSTOP");
|
|
999
1054
|
} catch (err) {
|
|
@@ -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
|
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
getSymbolTheme,
|
|
23
23
|
previewTheme,
|
|
24
24
|
setColorBlindMode,
|
|
25
|
+
setMarkdownMermaidRendering,
|
|
25
26
|
setSymbolPreset,
|
|
26
27
|
setTheme,
|
|
27
28
|
theme,
|
|
@@ -133,6 +134,9 @@ export class SelectorController {
|
|
|
133
134
|
availableThinkingLevels: [...this.ctx.session.getAvailableThinkingLevels()],
|
|
134
135
|
thinkingLevel: this.ctx.session.thinkingLevel,
|
|
135
136
|
availableThemes,
|
|
137
|
+
providers: [...new Set(this.ctx.session.getAvailableModels().map(model => model.provider))].sort(
|
|
138
|
+
(a, b) => a.localeCompare(b),
|
|
139
|
+
),
|
|
136
140
|
cwd: getProjectDir(),
|
|
137
141
|
model: this.ctx.session.model,
|
|
138
142
|
imageBudget: this.ctx.ui.imageBudget,
|
|
@@ -340,7 +344,7 @@ export class SelectorController {
|
|
|
340
344
|
this.ctx.hideThinkingBlock = value as boolean;
|
|
341
345
|
for (const child of this.ctx.chatContainer.children) {
|
|
342
346
|
if (child instanceof AssistantMessageComponent) {
|
|
343
|
-
child.setHideThinkingBlock(
|
|
347
|
+
child.setHideThinkingBlock(this.ctx.effectiveHideThinkingBlock);
|
|
344
348
|
}
|
|
345
349
|
}
|
|
346
350
|
// Full clear + replay so blocks frozen in committed scrollback on
|
|
@@ -374,6 +378,15 @@ export class SelectorController {
|
|
|
374
378
|
this.ctx.ui.requestRender();
|
|
375
379
|
break;
|
|
376
380
|
|
|
381
|
+
case "tui.renderMermaid":
|
|
382
|
+
setMarkdownMermaidRendering(value as boolean);
|
|
383
|
+
this.ctx.session.refreshBaseSystemPrompt().catch(err => {
|
|
384
|
+
this.ctx.showError(`Failed to apply Mermaid rendering setting: ${err}`);
|
|
385
|
+
});
|
|
386
|
+
this.ctx.rebuildChatFromMessages();
|
|
387
|
+
this.ctx.ui.resetDisplay();
|
|
388
|
+
break;
|
|
389
|
+
|
|
377
390
|
case "theme": {
|
|
378
391
|
setTheme(value as string, true).then(result => {
|
|
379
392
|
this.ctx.statusLine.invalidate();
|
|
@@ -1292,7 +1305,7 @@ export class SelectorController {
|
|
|
1292
1305
|
getTool: name => this.ctx.session.getToolByName(name),
|
|
1293
1306
|
getMessageRenderer: type => this.ctx.session.extensionRunner?.getMessageRenderer(type),
|
|
1294
1307
|
cwd: this.ctx.sessionManager.getCwd(),
|
|
1295
|
-
hideThinkingBlock: () => this.ctx.
|
|
1308
|
+
hideThinkingBlock: () => this.ctx.effectiveHideThinkingBlock,
|
|
1296
1309
|
proseOnlyThinking: () => this.ctx.proseOnlyThinking,
|
|
1297
1310
|
focusAgent: id => this.ctx.focusAgentSession(id),
|
|
1298
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";
|
|
@@ -167,6 +170,7 @@ import {
|
|
|
167
170
|
getSymbolTheme,
|
|
168
171
|
onTerminalAppearanceChange,
|
|
169
172
|
onThemeChange,
|
|
173
|
+
setMarkdownMermaidRendering,
|
|
170
174
|
theme,
|
|
171
175
|
} from "./theme/theme";
|
|
172
176
|
import type {
|
|
@@ -308,13 +312,15 @@ export interface InteractiveModeOptions {
|
|
|
308
312
|
}
|
|
309
313
|
|
|
310
314
|
/**
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
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).
|
|
316
322
|
*/
|
|
317
|
-
class
|
|
323
|
+
class AnchoredLiveContainer extends Container implements NativeScrollbackLiveRegion {
|
|
318
324
|
getNativeScrollbackLiveRegionStart(): number | undefined {
|
|
319
325
|
return this.children.length > 0 ? 0 : undefined;
|
|
320
326
|
}
|
|
@@ -407,6 +413,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
407
413
|
#modelCycleClearTimer: NodeJS.Timeout | undefined;
|
|
408
414
|
todoPhases: TodoPhase[] = [];
|
|
409
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
|
+
}
|
|
410
425
|
proseOnlyThinking = true;
|
|
411
426
|
compactionQueuedMessages: CompactionQueuedMessage[] = [];
|
|
412
427
|
pendingTools = new Map<string, ToolExecutionHandle>();
|
|
@@ -540,6 +555,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
540
555
|
#observerRegistry: SessionObserverRegistry;
|
|
541
556
|
#eventBus?: EventBus;
|
|
542
557
|
#eventBusUnsubscribers: Array<() => void> = [];
|
|
558
|
+
#agentRegistryUnsubscribe?: () => void;
|
|
559
|
+
#agentRegistrySubscriptionTarget?: AgentRegistry;
|
|
543
560
|
#mcpStatusOrder: string[] = [];
|
|
544
561
|
#mcpPendingServers = new Set<string>();
|
|
545
562
|
#mcpConnectedServers = new Set<string>();
|
|
@@ -588,6 +605,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
588
605
|
}
|
|
589
606
|
|
|
590
607
|
setTuiTight(settings.get("tui.tight"));
|
|
608
|
+
setMarkdownMermaidRendering(settings.get("tui.renderMermaid"));
|
|
591
609
|
this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
|
|
592
610
|
this.ui.setMaxInlineImages(settings.get("tui.maxInlineImages"));
|
|
593
611
|
// OSC 66 text-sizing is Kitty-only; resolve the setting against the terminal's
|
|
@@ -596,13 +614,13 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
596
614
|
setTerminalTextSizing(settings.get("tui.textSizing") && TERMINAL.textSizing);
|
|
597
615
|
this.chatContainer = new TranscriptContainer();
|
|
598
616
|
this.pendingMessagesContainer = new Container();
|
|
599
|
-
this.statusContainer = new
|
|
600
|
-
this.todoContainer = new
|
|
601
|
-
this.subagentContainer = new
|
|
602
|
-
this.btwContainer = new
|
|
603
|
-
this.omfgContainer = new
|
|
604
|
-
this.errorBannerContainer = new
|
|
605
|
-
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();
|
|
606
624
|
this.editor = new CustomEditor(getEditorTheme());
|
|
607
625
|
this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
608
626
|
this.editor.setAutocompleteMaxVisible(settings.get("autocompleteMaxVisible"));
|
|
@@ -816,13 +834,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
816
834
|
|
|
817
835
|
this.ui.addChild(this.chatContainer);
|
|
818
836
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
819
|
-
this.ui.addChild(this.statusContainer);
|
|
820
837
|
this.ui.addChild(this.todoContainer);
|
|
821
838
|
this.ui.addChild(this.subagentContainer);
|
|
822
839
|
this.ui.addChild(this.btwContainer);
|
|
823
840
|
this.ui.addChild(this.omfgContainer);
|
|
824
841
|
this.ui.addChild(this.errorBannerContainer);
|
|
825
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);
|
|
826
847
|
this.ui.addChild(this.statusLine); // Only renders hook statuses (main status in editor border)
|
|
827
848
|
this.ui.addChild(this.hookWidgetContainerAbove);
|
|
828
849
|
this.ui.addChild(this.editorContainer);
|
|
@@ -837,8 +858,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
837
858
|
this.#observerRegistry.subscribeToEventBus(this.#eventBus);
|
|
838
859
|
}
|
|
839
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();
|
|
840
867
|
this.#observerRegistry.onChange(() => {
|
|
841
|
-
this.
|
|
868
|
+
this.syncRunningSubagentBadge();
|
|
842
869
|
// Auto-checkmark todos whose matching subagent just succeeded, then
|
|
843
870
|
// re-render so the running override (the static "live" glyph when a
|
|
844
871
|
// subagent is doing the work for a still-pending todo) updates as
|
|
@@ -1433,6 +1460,22 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1433
1460
|
this.ui.requestRender();
|
|
1434
1461
|
}
|
|
1435
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
|
+
|
|
1436
1479
|
updateEditorTopBorder(): void {
|
|
1437
1480
|
const availableWidth = this.editor.getTopBorderAvailableWidth(this.ui.terminal.columns);
|
|
1438
1481
|
const topBorder = this.statusLine.getTopBorder(availableWidth);
|
|
@@ -1484,9 +1527,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1484
1527
|
case "abandoned":
|
|
1485
1528
|
return theme.fg("error", `${prefix}${checkbox.unchecked} ${chalk.strikethrough(todo.content)}`) + marker;
|
|
1486
1529
|
default:
|
|
1487
|
-
if (matched) {
|
|
1488
|
-
return theme.fg("accent", `${prefix}${checkbox.unchecked} ${todo.content}`) + marker;
|
|
1489
|
-
}
|
|
1530
|
+
if (matched) return theme.fg("accent", `${prefix}${checkbox.unchecked} ${todo.content}`) + marker;
|
|
1490
1531
|
return theme.fg("dim", `${prefix}${checkbox.unchecked} ${todo.content}`) + marker;
|
|
1491
1532
|
}
|
|
1492
1533
|
}
|
|
@@ -1627,44 +1668,66 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1627
1668
|
this.todoContainer.clear();
|
|
1628
1669
|
const phases = this.todoPhases.filter(phase => phase.tasks.length > 0);
|
|
1629
1670
|
if (phases.length === 0) return;
|
|
1630
|
-
const
|
|
1631
|
-
const
|
|
1632
|
-
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
|
|
1633
1677
|
|
|
1634
1678
|
const activeDescs = this.#getActiveSubagentDescriptions();
|
|
1635
|
-
// A pending todo "lights up" (accent
|
|
1636
|
-
//
|
|
1679
|
+
// A pending todo "lights up" (accent) when an in-flight subagent is doing
|
|
1680
|
+
// its work, matched by normalized content overlap.
|
|
1637
1681
|
const isMatched = (todo: TodoItem): boolean =>
|
|
1638
1682
|
activeDescs.length > 0 && todoMatchesAnyDescription(todo.content, activeDescs);
|
|
1639
1683
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
const
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
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,
|
|
1648
1694
|
);
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
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;
|
|
1655
1707
|
}
|
|
1656
|
-
|
|
1657
|
-
return;
|
|
1658
|
-
}
|
|
1708
|
+
const header = theme.bold(theme.fg("accent", label)) + theme.fg("dim", progress);
|
|
1709
|
+
return [header, ...renderTasks(phase)];
|
|
1710
|
+
};
|
|
1659
1711
|
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
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
|
+
);
|
|
1667
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}`)];
|
|
1668
1731
|
this.todoContainer.addChild(new Text(lines.join("\n"), 1, 0));
|
|
1669
1732
|
}
|
|
1670
1733
|
|
|
@@ -3021,16 +3084,31 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3021
3084
|
// Capture the operator's tier choice and hand it to #approvePlan, which
|
|
3022
3085
|
// applies it AFTER #exitPlanMode. #exitPlanMode normally restores
|
|
3023
3086
|
// #planModePreviousModelState (the model from before plan mode), so
|
|
3024
|
-
// applying the slider choice any earlier would be silently reverted
|
|
3025
|
-
//
|
|
3026
|
-
//
|
|
3027
|
-
//
|
|
3028
|
-
//
|
|
3029
|
-
//
|
|
3030
|
-
//
|
|
3031
|
-
//
|
|
3087
|
+
// applying the slider choice any earlier would be silently reverted.
|
|
3088
|
+
// Pass executionModel only when the slider was actually shown — a
|
|
3089
|
+
// singleton cycle (e.g. only modelRoles.plan is configured, so
|
|
3090
|
+
// getRoleModelCycle synthesizes a lone `default` entry from the
|
|
3091
|
+
// currently active plan model) hides the slider, the operator made
|
|
3092
|
+
// no selection, and the pre-plan model is not in the cycle. Pinning
|
|
3093
|
+
// that singleton would silently switch the session back to the plan
|
|
3094
|
+
// model after #exitPlanMode restored the pre-plan model.
|
|
3095
|
+
// Treat the choice as implicit only when applying the selected role
|
|
3096
|
+
// would land on the same end state as the restore — same model AND
|
|
3097
|
+
// the same effective thinking level. A role with an explicit thinking
|
|
3098
|
+
// suffix that differs from the restored thinking level must still go
|
|
3099
|
+
// through applyRoleModel, otherwise approving on the same model with a
|
|
3100
|
+
// different configured thinking level silently keeps the pre-plan level.
|
|
3101
|
+
const restoredState = this.#planModePreviousModelState;
|
|
3102
|
+
const restoredIndex =
|
|
3103
|
+
cycle && restoredState
|
|
3104
|
+
? cycle.models.findIndex(entry => {
|
|
3105
|
+
if (!modelsAreEqual(entry.model, restoredState.model)) return false;
|
|
3106
|
+
if (!entry.explicitThinkingLevel) return true;
|
|
3107
|
+
return entry.thinkingLevel === restoredState.thinkingLevel;
|
|
3108
|
+
})
|
|
3109
|
+
: -1;
|
|
3032
3110
|
const executionModel =
|
|
3033
|
-
cycle && selectedTierIndex !==
|
|
3111
|
+
slider && cycle && selectedTierIndex !== restoredIndex ? cycle.models[selectedTierIndex] : undefined;
|
|
3034
3112
|
await this.#approvePlan(latestPlanContent, {
|
|
3035
3113
|
planFilePath,
|
|
3036
3114
|
title: details.title,
|
|
@@ -3139,6 +3217,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3139
3217
|
}
|
|
3140
3218
|
this.#eventBusUnsubscribers = [];
|
|
3141
3219
|
this.#observerRegistry.dispose();
|
|
3220
|
+
this.#agentRegistryUnsubscribe?.();
|
|
3221
|
+
this.#agentRegistryUnsubscribe = undefined;
|
|
3222
|
+
this.#agentRegistrySubscriptionTarget = undefined;
|
|
3142
3223
|
this.#eventController.dispose();
|
|
3143
3224
|
this.statusLine.dispose();
|
|
3144
3225
|
if (this.#resizeHandler) {
|
|
@@ -3424,8 +3505,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3424
3505
|
getSymbolTheme().spinnerFrames,
|
|
3425
3506
|
);
|
|
3426
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);
|
|
3427
3511
|
}
|
|
3428
|
-
|
|
3429
3512
|
this.applyPendingWorkingMessage();
|
|
3430
3513
|
}
|
|
3431
3514
|
|
|
@@ -3609,7 +3692,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3609
3692
|
return this.#commandController.handleForkCommand();
|
|
3610
3693
|
}
|
|
3611
3694
|
|
|
3612
|
-
handleMoveCommand(targetPath
|
|
3695
|
+
handleMoveCommand(targetPath?: string): Promise<void> {
|
|
3613
3696
|
return this.#commandController.handleMoveCommand(targetPath);
|
|
3614
3697
|
}
|
|
3615
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
|
+
}
|