@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.2
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 +142 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/startup-cwd.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-provider-priority.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/config/settings.d.ts +7 -2
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/debug/report-bundle.d.ts +3 -0
- package/dist/types/edit/file-snapshot-store.d.ts +18 -10
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -1
- package/dist/types/lsp/client.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -9
- package/dist/types/mcp/tool-bridge.d.ts +2 -0
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +4 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +17 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +16 -5
- package/dist/types/modes/magic-keywords.d.ts +1 -1
- package/dist/types/modes/markdown-prose.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +21 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/modes/workflow.d.ts +3 -3
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +8 -3
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +17 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval.d.ts +8 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
- package/dist/types/tools/github-cache.d.ts +12 -0
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +5 -9
- package/dist/types/tools/search.d.ts +6 -2
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +3 -0
- package/dist/types/tools/yield.d.ts +8 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +3 -1
- package/src/cli/dry-balance-cli.ts +54 -21
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/cli/startup-cwd.ts +68 -0
- package/src/commands/launch.ts +3 -0
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +36 -11
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-provider-priority.ts +55 -0
- package/src/config/model-registry.ts +29 -24
- package/src/config/model-resolver.ts +39 -7
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +106 -43
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +47 -53
- package/src/debug/raw-sse-buffer.ts +7 -4
- package/src/debug/report-bundle.ts +9 -0
- package/src/edit/file-snapshot-store.ts +33 -1
- package/src/edit/hashline/filesystem.ts +2 -1
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +110 -31
- package/src/eval/js/context-manager.ts +32 -15
- package/src/eval/llm-bridge.ts +22 -6
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/executor.ts +23 -11
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +8 -8
- package/src/lsp/client.ts +23 -11
- package/src/lsp/config.ts +11 -1
- package/src/lsp/index.ts +61 -9
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +100 -72
- package/src/mcp/tool-bridge.ts +2 -0
- package/src/memories/index.ts +14 -7
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +164 -109
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/model-selector.ts +59 -13
- package/src/modes/components/oauth-selector.ts +33 -7
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +19 -4
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +2 -3
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +57 -55
- package/src/modes/controllers/event-controller.ts +67 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +170 -126
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +274 -112
- package/src/modes/magic-keywords.ts +1 -1
- package/src/modes/markdown-prose.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/shimmer.ts +20 -9
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +21 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/modes/workflow.ts +10 -10
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/eval.md +2 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +37 -46
- package/src/session/agent-session.ts +119 -18
- package/src/session/auth-storage.ts +2 -0
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +109 -28
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +76 -38
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +211 -147
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +57 -6
- package/src/tools/browser/tab-supervisor.ts +13 -1
- package/src/tools/browser/tab-worker.ts +33 -4
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval.ts +13 -2
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +51 -30
- package/src/tools/gh-cache-invalidation.ts +200 -0
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/github-cache.ts +25 -0
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +10 -3
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/path-utils.ts +28 -2
- package/src/tools/plan-mode-guard.ts +66 -39
- package/src/tools/read.ts +48 -28
- package/src/tools/render-utils.ts +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +118 -81
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +83 -64
- package/src/tools/yield.ts +10 -1
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +11 -3
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/codex.ts +37 -8
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
+
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import type { AutocompleteProvider, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
3
4
|
import { $env, logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import { getRoleInfo } from "../../config/model-registry";
|
|
@@ -9,16 +10,16 @@ import { expandEmoticons } from "../../modes/emoji-autocomplete";
|
|
|
9
10
|
import { materializeImageReferenceLinks } from "../../modes/image-references";
|
|
10
11
|
import { createPromptActionAutocompleteProvider } from "../../modes/prompt-action-autocomplete";
|
|
11
12
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
12
|
-
import type
|
|
13
|
-
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
13
|
+
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails, USER_INTERRUPT_LABEL } from "../../session/messages";
|
|
14
14
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
15
15
|
import { isTinyTitleLocalModelKey } from "../../tiny/models";
|
|
16
16
|
import { isLowSignalTitleInput } from "../../tiny/text";
|
|
17
17
|
import { tinyTitleClient } from "../../tiny/title-client";
|
|
18
18
|
import type { TinyTitleProgressEvent } from "../../tiny/title-protocol";
|
|
19
19
|
import { copyToClipboard, readImageFromClipboard, readTextFromClipboard } from "../../utils/clipboard";
|
|
20
|
+
import { EnhancedPasteController } from "../../utils/enhanced-paste";
|
|
20
21
|
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
21
|
-
import { ensureSupportedImageInput } from "../../utils/image-loading";
|
|
22
|
+
import { ensureSupportedImageInput, ImageInputTooLargeError, loadImageInput } from "../../utils/image-loading";
|
|
22
23
|
import { resizeImage } from "../../utils/image-resize";
|
|
23
24
|
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
24
25
|
|
|
@@ -40,8 +41,10 @@ const TINY_TITLE_PROGRESS_REVEAL_DELAY_MS = 1_000;
|
|
|
40
41
|
export class InputController {
|
|
41
42
|
constructor(private ctx: InteractiveModeContext) {}
|
|
42
43
|
|
|
44
|
+
#enhancedPaste?: EnhancedPasteController;
|
|
45
|
+
|
|
43
46
|
#showTinyTitleDownloadProgress(modelKey: string): void {
|
|
44
|
-
if (!isTinyTitleLocalModelKey(modelKey)
|
|
47
|
+
if (!isTinyTitleLocalModelKey(modelKey)) return;
|
|
45
48
|
const component = new TinyTitleDownloadProgressComponent(modelKey);
|
|
46
49
|
let added = false;
|
|
47
50
|
let disposed = false;
|
|
@@ -91,7 +94,8 @@ export class InputController {
|
|
|
91
94
|
if (this.ctx.loopModeEnabled) {
|
|
92
95
|
this.ctx.pauseLoop();
|
|
93
96
|
if (this.ctx.session.isStreaming) {
|
|
94
|
-
|
|
97
|
+
this.ctx.notifyInterrupting();
|
|
98
|
+
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
95
99
|
} else {
|
|
96
100
|
this.ctx.cancelPendingSubmission();
|
|
97
101
|
}
|
|
@@ -121,7 +125,8 @@ export class InputController {
|
|
|
121
125
|
this.ctx.isPythonMode = false;
|
|
122
126
|
this.ctx.updateEditorBorderColor();
|
|
123
127
|
} else if (this.ctx.session.isStreaming) {
|
|
124
|
-
|
|
128
|
+
this.ctx.notifyInterrupting();
|
|
129
|
+
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
125
130
|
} else if (!this.ctx.editor.getText().trim()) {
|
|
126
131
|
// Double-interrupt with empty editor triggers /tree, /branch, or nothing based on setting
|
|
127
132
|
const action = settings.get("doubleEscapeAction");
|
|
@@ -176,6 +181,7 @@ export class InputController {
|
|
|
176
181
|
this.ctx.keybindings.getKeys("app.clipboard.pasteImage"),
|
|
177
182
|
);
|
|
178
183
|
this.ctx.editor.onPasteImage = () => this.handleImagePaste();
|
|
184
|
+
this.ctx.editor.onPasteImagePath = path => void this.handleImagePathPaste(path);
|
|
179
185
|
this.ctx.editor.setActionKeys(
|
|
180
186
|
"app.clipboard.pasteTextRaw",
|
|
181
187
|
this.ctx.keybindings.getKeys("app.clipboard.pasteTextRaw"),
|
|
@@ -223,6 +229,8 @@ export class InputController {
|
|
|
223
229
|
this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showSessionObserver());
|
|
224
230
|
}
|
|
225
231
|
|
|
232
|
+
this.#setupEnhancedPaste();
|
|
233
|
+
|
|
226
234
|
this.ctx.editor.onChange = (text: string) => {
|
|
227
235
|
const wasBashMode = this.ctx.isBashMode;
|
|
228
236
|
const wasPythonMode = this.ctx.isPythonMode;
|
|
@@ -235,16 +243,45 @@ export class InputController {
|
|
|
235
243
|
};
|
|
236
244
|
}
|
|
237
245
|
|
|
246
|
+
#setupEnhancedPaste(): void {
|
|
247
|
+
if (this.#enhancedPaste) return;
|
|
248
|
+
|
|
249
|
+
this.#enhancedPaste = new EnhancedPasteController({
|
|
250
|
+
write: data => this.ctx.ui.terminal.write(data),
|
|
251
|
+
pasteText: text => {
|
|
252
|
+
this.ctx.editor.pasteText(text);
|
|
253
|
+
this.ctx.ui.requestRender(false, { allowUnknownViewportMutation: true });
|
|
254
|
+
},
|
|
255
|
+
pasteImage: async image => {
|
|
256
|
+
await this.#normalizeAndInsertPastedImage(image, `Unsupported pasted image format: ${image.mimeType}`);
|
|
257
|
+
},
|
|
258
|
+
showStatus: message => this.ctx.showStatus(message),
|
|
259
|
+
});
|
|
260
|
+
this.ctx.ui.addInputListener(data => (this.#enhancedPaste?.handleInput(data) ? { consume: true } : undefined));
|
|
261
|
+
this.ctx.ui.addStartListener(() => this.#enhancedPaste?.enable());
|
|
262
|
+
}
|
|
263
|
+
|
|
238
264
|
setupEditorSubmitHandler(): void {
|
|
239
265
|
this.ctx.editor.onSubmit = async (text: string) => {
|
|
240
266
|
text = text.trim();
|
|
241
267
|
if ((!isSettingsInitialized() || settings.get("emojiAutocomplete")) && text) text = expandEmoticons(text);
|
|
242
268
|
|
|
243
|
-
// Empty submit while streaming with queued
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
269
|
+
// Empty submit while streaming with queued steering: interrupt now and
|
|
270
|
+
// immediately resume so the visible `Steer:` entry is sent without
|
|
271
|
+
// waiting for the current tool/model boundary.
|
|
272
|
+
if (!text && this.ctx.session.isStreaming) {
|
|
273
|
+
const queuedMessages = this.ctx.session.getQueuedMessages();
|
|
274
|
+
if (queuedMessages.steering.length > 0) {
|
|
275
|
+
await this.ctx.session.interruptAndFlushQueuedMessages({ reason: USER_INTERRUPT_LABEL });
|
|
276
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
277
|
+
this.ctx.ui.requestRender();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (this.ctx.session.queuedMessageCount > 0) {
|
|
281
|
+
// Preserve the existing empty-submit flush for non-steer queues.
|
|
282
|
+
await this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
248
285
|
}
|
|
249
286
|
|
|
250
287
|
if (!text) return;
|
|
@@ -291,7 +328,6 @@ export class InputController {
|
|
|
291
328
|
// Handle built-in slash commands
|
|
292
329
|
const slashResult = await executeBuiltinSlashCommand(text, {
|
|
293
330
|
ctx: this.ctx,
|
|
294
|
-
handleBackgroundCommand: () => this.handleBackgroundCommand(),
|
|
295
331
|
});
|
|
296
332
|
if (slashResult === true) {
|
|
297
333
|
return;
|
|
@@ -465,17 +501,44 @@ export class InputController {
|
|
|
465
501
|
}
|
|
466
502
|
|
|
467
503
|
handleCtrlZ(): void {
|
|
468
|
-
//
|
|
469
|
-
process.
|
|
504
|
+
// SIGTSTP is POSIX job-control: Windows has no equivalent and
|
|
505
|
+
// `process.kill(_, "SIGTSTP")` throws `TypeError: Unknown signal:
|
|
506
|
+
// SIGTSTP` there, taking the whole agent down via an uncaught
|
|
507
|
+
// exception (issue #2036). No-op on platforms that cannot suspend.
|
|
508
|
+
if (process.platform === "win32") {
|
|
509
|
+
this.ctx.showStatus("Suspend (Ctrl+Z) is not supported on this platform");
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Capture the listener so we can detach it if the signal never
|
|
514
|
+
// fires; otherwise a failed suspend would leave a stale SIGCONT
|
|
515
|
+
// handler that fires on the next unrelated continue and tries to
|
|
516
|
+
// re-`start()` an already-running TUI.
|
|
517
|
+
const onResume = (): void => {
|
|
470
518
|
this.ctx.ui.start();
|
|
471
519
|
this.ctx.ui.requestRender(true);
|
|
472
|
-
}
|
|
520
|
+
};
|
|
521
|
+
process.once("SIGCONT", onResume);
|
|
473
522
|
|
|
474
|
-
// Stop the TUI (restore terminal to normal mode)
|
|
523
|
+
// Stop the TUI (restore terminal to normal mode) before sending the
|
|
524
|
+
// signal so the parent shell sees a sane terminal state.
|
|
475
525
|
this.ctx.ui.stop();
|
|
476
526
|
|
|
477
|
-
|
|
478
|
-
|
|
527
|
+
try {
|
|
528
|
+
// pid=0 → entire foreground process group; the shell receives
|
|
529
|
+
// SIGTSTP and parks the job.
|
|
530
|
+
process.kill(0, "SIGTSTP");
|
|
531
|
+
} catch (err) {
|
|
532
|
+
// Either the runtime refused the signal or the kernel rejected
|
|
533
|
+
// it (some sandboxes block sending to pid=0). Tear the resume
|
|
534
|
+
// hook down and bring the TUI back so the user is not stranded
|
|
535
|
+
// on a frozen prompt.
|
|
536
|
+
process.removeListener("SIGCONT", onResume);
|
|
537
|
+
this.ctx.ui.start();
|
|
538
|
+
this.ctx.ui.requestRender(true);
|
|
539
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
540
|
+
this.ctx.showError(`Failed to suspend: ${reason}`);
|
|
541
|
+
}
|
|
479
542
|
}
|
|
480
543
|
|
|
481
544
|
handleDequeue(): void {
|
|
@@ -555,7 +618,7 @@ export class InputController {
|
|
|
555
618
|
|
|
556
619
|
/** Send editor text as a follow-up message (queued behind current stream). */
|
|
557
620
|
async handleFollowUp(): Promise<void> {
|
|
558
|
-
|
|
621
|
+
let text = this.ctx.editor.getText().trim();
|
|
559
622
|
if (!text) return;
|
|
560
623
|
|
|
561
624
|
// Compaction first: while compacting, free text gets queued via
|
|
@@ -569,6 +632,16 @@ export class InputController {
|
|
|
569
632
|
return;
|
|
570
633
|
}
|
|
571
634
|
|
|
635
|
+
const slashResult = await executeBuiltinSlashCommand(text, {
|
|
636
|
+
ctx: this.ctx,
|
|
637
|
+
});
|
|
638
|
+
if (slashResult === true) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (typeof slashResult === "string") {
|
|
642
|
+
text = slashResult;
|
|
643
|
+
}
|
|
644
|
+
|
|
572
645
|
// Skill commands invoke through the custom-message path regardless of
|
|
573
646
|
// which keybinding submitted them. Enter routes them as `steer`;
|
|
574
647
|
// Ctrl+Enter (this handler) routes them as `followUp`.
|
|
@@ -600,7 +673,7 @@ export class InputController {
|
|
|
600
673
|
if (allQueued.length === 0) {
|
|
601
674
|
this.ctx.updatePendingMessagesDisplay();
|
|
602
675
|
if (options?.abort) {
|
|
603
|
-
this.ctx.session.abort();
|
|
676
|
+
this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
604
677
|
}
|
|
605
678
|
return 0;
|
|
606
679
|
}
|
|
@@ -610,128 +683,99 @@ export class InputController {
|
|
|
610
683
|
this.ctx.editor.setText(combinedText);
|
|
611
684
|
this.ctx.updatePendingMessagesDisplay();
|
|
612
685
|
if (options?.abort) {
|
|
613
|
-
this.ctx.session.abort();
|
|
686
|
+
this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
614
687
|
}
|
|
615
688
|
return allQueued.length;
|
|
616
689
|
}
|
|
617
690
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const backgroundUiContext = this.ctx.createBackgroundUiContext();
|
|
636
|
-
|
|
637
|
-
// Background mode disables interactive UI so tools like ask fail fast.
|
|
638
|
-
this.ctx.setToolUIContext(backgroundUiContext, false);
|
|
639
|
-
this.ctx.initializeHookRunner(backgroundUiContext, false);
|
|
640
|
-
|
|
641
|
-
if (this.ctx.loadingAnimation) {
|
|
642
|
-
this.ctx.loadingAnimation.stop();
|
|
643
|
-
this.ctx.loadingAnimation = undefined;
|
|
644
|
-
}
|
|
645
|
-
if (this.ctx.autoCompactionLoader) {
|
|
646
|
-
this.ctx.autoCompactionLoader.stop();
|
|
647
|
-
this.ctx.autoCompactionLoader = undefined;
|
|
648
|
-
}
|
|
649
|
-
if (this.ctx.retryLoader) {
|
|
650
|
-
this.ctx.retryLoader.stop();
|
|
651
|
-
this.ctx.retryLoader = undefined;
|
|
652
|
-
}
|
|
653
|
-
this.ctx.statusContainer.clear();
|
|
654
|
-
this.ctx.statusLine.dispose();
|
|
655
|
-
|
|
656
|
-
if (this.ctx.unsubscribe) {
|
|
657
|
-
this.ctx.unsubscribe();
|
|
658
|
-
}
|
|
659
|
-
this.ctx.unsubscribe = this.ctx.session.subscribe(async (event: AgentSessionEvent) => {
|
|
660
|
-
await this.ctx.handleBackgroundEvent(event);
|
|
691
|
+
async #insertPendingImage(imageData: ImageContent): Promise<void> {
|
|
692
|
+
const imageLink = (
|
|
693
|
+
await materializeImageReferenceLinks(
|
|
694
|
+
[
|
|
695
|
+
{
|
|
696
|
+
type: "image",
|
|
697
|
+
data: imageData.data,
|
|
698
|
+
mimeType: imageData.mimeType,
|
|
699
|
+
},
|
|
700
|
+
],
|
|
701
|
+
this.ctx.sessionManager.putBlob.bind(this.ctx.sessionManager),
|
|
702
|
+
)
|
|
703
|
+
)?.[0];
|
|
704
|
+
this.ctx.pendingImages.push({
|
|
705
|
+
type: "image",
|
|
706
|
+
data: imageData.data,
|
|
707
|
+
mimeType: imageData.mimeType,
|
|
661
708
|
});
|
|
709
|
+
this.ctx.pendingImageLinks.push(imageLink);
|
|
710
|
+
this.ctx.editor.imageLinks = this.ctx.pendingImageLinks;
|
|
711
|
+
const imageNum = this.ctx.pendingImages.length;
|
|
712
|
+
this.ctx.editor.insertText(`[Image #${imageNum}] `);
|
|
713
|
+
this.ctx.ui.requestRender(false, { allowUnknownViewportMutation: true });
|
|
714
|
+
}
|
|
662
715
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
this.ctx.
|
|
716
|
+
async #normalizeAndInsertPastedImage(image: ImageContent, unsupportedMessage: string): Promise<boolean> {
|
|
717
|
+
let imageData = await ensureSupportedImageInput(image);
|
|
718
|
+
if (!imageData) {
|
|
719
|
+
this.ctx.showStatus(unsupportedMessage);
|
|
720
|
+
return false;
|
|
667
721
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
722
|
+
if (settings.get("images.autoResize")) {
|
|
723
|
+
try {
|
|
724
|
+
const resized = await resizeImage({
|
|
725
|
+
type: "image",
|
|
726
|
+
data: imageData.data,
|
|
727
|
+
mimeType: imageData.mimeType,
|
|
728
|
+
});
|
|
729
|
+
imageData = { type: "image", data: resized.data, mimeType: resized.mimeType };
|
|
730
|
+
} catch {
|
|
731
|
+
// Keep the normalized image when resize fails.
|
|
732
|
+
}
|
|
674
733
|
}
|
|
734
|
+
await this.#insertPendingImage(imageData);
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
675
737
|
|
|
676
|
-
|
|
738
|
+
async handleImagePathPaste(path: string): Promise<void> {
|
|
739
|
+
try {
|
|
740
|
+
const image = await loadImageInput({
|
|
741
|
+
path,
|
|
742
|
+
cwd: this.ctx.sessionManager.getCwd(),
|
|
743
|
+
autoResize: false,
|
|
744
|
+
});
|
|
745
|
+
if (!image) {
|
|
746
|
+
this.ctx.editor.pasteText(path);
|
|
747
|
+
this.ctx.ui.requestRender(false, { allowUnknownViewportMutation: true });
|
|
748
|
+
this.ctx.showStatus("Pasted path is not a supported image");
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
await this.#normalizeAndInsertPastedImage(
|
|
752
|
+
{ type: "image", data: image.data, mimeType: image.mimeType },
|
|
753
|
+
`Unsupported pasted image format: ${image.mimeType}`,
|
|
754
|
+
);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
this.ctx.editor.pasteText(path);
|
|
757
|
+
this.ctx.ui.requestRender(false, { allowUnknownViewportMutation: true });
|
|
758
|
+
this.ctx.showStatus(
|
|
759
|
+
error instanceof ImageInputTooLargeError ? error.message : "Failed to read pasted image path",
|
|
760
|
+
);
|
|
761
|
+
}
|
|
677
762
|
}
|
|
678
763
|
|
|
679
764
|
async handleImagePaste(): Promise<boolean> {
|
|
680
765
|
try {
|
|
681
766
|
const image = await readImageFromClipboard();
|
|
682
|
-
if (image) {
|
|
683
|
-
|
|
684
|
-
|
|
767
|
+
if (!image) {
|
|
768
|
+
this.ctx.showStatus("No image in clipboard (use terminal paste for text)");
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
return await this.#normalizeAndInsertPastedImage(
|
|
772
|
+
{
|
|
685
773
|
type: "image",
|
|
686
|
-
data:
|
|
774
|
+
data: image.data.toBase64(),
|
|
687
775
|
mimeType: image.mimeType,
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
return false;
|
|
692
|
-
}
|
|
693
|
-
if (settings.get("images.autoResize")) {
|
|
694
|
-
try {
|
|
695
|
-
const resized = await resizeImage({
|
|
696
|
-
type: "image",
|
|
697
|
-
data: imageData.data,
|
|
698
|
-
mimeType: imageData.mimeType,
|
|
699
|
-
});
|
|
700
|
-
imageData = { type: "image", data: resized.data, mimeType: resized.mimeType };
|
|
701
|
-
} catch {
|
|
702
|
-
// Keep the normalized image when resize fails.
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const imageLink = (
|
|
707
|
-
await materializeImageReferenceLinks(
|
|
708
|
-
[
|
|
709
|
-
{
|
|
710
|
-
type: "image",
|
|
711
|
-
data: imageData.data,
|
|
712
|
-
mimeType: imageData.mimeType,
|
|
713
|
-
},
|
|
714
|
-
],
|
|
715
|
-
this.ctx.sessionManager.putBlob.bind(this.ctx.sessionManager),
|
|
716
|
-
)
|
|
717
|
-
)?.[0];
|
|
718
|
-
this.ctx.pendingImages.push({
|
|
719
|
-
type: "image",
|
|
720
|
-
data: imageData.data,
|
|
721
|
-
mimeType: imageData.mimeType,
|
|
722
|
-
});
|
|
723
|
-
this.ctx.pendingImageLinks.push(imageLink);
|
|
724
|
-
this.ctx.editor.imageLinks = this.ctx.pendingImageLinks;
|
|
725
|
-
// Insert placeholder at cursor like Claude does
|
|
726
|
-
const imageNum = this.ctx.pendingImages.length;
|
|
727
|
-
const placeholder = `[Image #${imageNum}]`;
|
|
728
|
-
this.ctx.editor.insertText(`${placeholder} `);
|
|
729
|
-
this.ctx.ui.requestRender();
|
|
730
|
-
return true;
|
|
731
|
-
}
|
|
732
|
-
// No image in clipboard - show hint
|
|
733
|
-
this.ctx.showStatus("No image in clipboard (use terminal paste for text)");
|
|
734
|
-
return false;
|
|
776
|
+
},
|
|
777
|
+
`Unsupported clipboard image format: ${image.mimeType}`,
|
|
778
|
+
);
|
|
735
779
|
} catch {
|
|
736
780
|
this.ctx.showStatus("Failed to read clipboard");
|
|
737
781
|
return false;
|
|
@@ -37,7 +37,9 @@ import type { MCPAuthConfig, MCPServerConfig, MCPServerConnection } from "../../
|
|
|
37
37
|
import type { OAuthCredential } from "../../session/auth-storage";
|
|
38
38
|
import { shortenPath } from "../../tools/render-utils";
|
|
39
39
|
import { openPath } from "../../utils/open";
|
|
40
|
+
import { ChatBlock } from "../components/chat-block";
|
|
40
41
|
import { MCPAddWizard } from "../components/mcp-add-wizard";
|
|
42
|
+
import { TranscriptBlock } from "../components/transcript-container";
|
|
41
43
|
import { parseCommandArgs } from "../shared";
|
|
42
44
|
import { theme } from "../theme/theme";
|
|
43
45
|
import type { InteractiveModeContext } from "../types";
|
|
@@ -49,6 +51,42 @@ function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string)
|
|
|
49
51
|
return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timer));
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Animated "Connecting to …" transcript block. Owns its spinner interval: it
|
|
56
|
+
* starts on mount and is cleared on {@link ChatBlock.finish}/dispose, so callers
|
|
57
|
+
* never juggle `setInterval`/`clearInterval` or `requestRender` by hand.
|
|
58
|
+
*/
|
|
59
|
+
class McpConnectingBlock extends ChatBlock {
|
|
60
|
+
readonly #text: Text;
|
|
61
|
+
|
|
62
|
+
constructor(private readonly serverName: string) {
|
|
63
|
+
super();
|
|
64
|
+
this.addChild(new Spacer(1));
|
|
65
|
+
const frame = theme.spinnerFrames[0] ?? "|";
|
|
66
|
+
this.#text = new Text(theme.fg("muted", `${frame} Connecting to "${serverName}"...`), 1, 0);
|
|
67
|
+
this.addChild(this.#text);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected override onMount(): void {
|
|
71
|
+
const frames = theme.spinnerFrames;
|
|
72
|
+
let frame = 0;
|
|
73
|
+
const interval = setInterval(() => {
|
|
74
|
+
frame++;
|
|
75
|
+
this.#text.setText(
|
|
76
|
+
theme.fg("muted", `${frames[frame % frames.length] ?? "|"} Connecting to "${this.serverName}"...`),
|
|
77
|
+
);
|
|
78
|
+
this.requestRender();
|
|
79
|
+
}, 80);
|
|
80
|
+
this.onCleanup(() => clearInterval(interval));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Replace the spinner line with a terminal status; pair with {@link finish}. */
|
|
84
|
+
setStatus(text: string): void {
|
|
85
|
+
this.#text.setText(text);
|
|
86
|
+
this.requestRender();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
52
90
|
/**
|
|
53
91
|
* Outcome of {@link MCPCommandController}'s OAuth handler.
|
|
54
92
|
*
|
|
@@ -547,63 +585,45 @@ export class MCPCommandController {
|
|
|
547
585
|
},
|
|
548
586
|
{
|
|
549
587
|
onAuth: (info: { url: string; instructions?: string }) => {
|
|
550
|
-
// Show auth URL prominently in chat
|
|
551
|
-
|
|
552
|
-
this.ctx.
|
|
553
|
-
|
|
554
|
-
);
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
);
|
|
559
|
-
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
560
|
-
this.ctx.chatContainer.addChild(
|
|
588
|
+
// Show auth URL prominently in chat as one block
|
|
589
|
+
const block = new TranscriptBlock();
|
|
590
|
+
this.ctx.present(block);
|
|
591
|
+
block.addChild(new Text(theme.fg("accent", "━━━ OAuth Authorization Required ━━━"), 1, 0));
|
|
592
|
+
block.addChild(new Spacer(1));
|
|
593
|
+
block.addChild(new Text(theme.fg("muted", "Preparing browser authorization..."), 1, 0));
|
|
594
|
+
block.addChild(new Spacer(1));
|
|
595
|
+
block.addChild(
|
|
561
596
|
new Text(
|
|
562
597
|
theme.fg("muted", "Waiting for authorization... (Press Ctrl+C to cancel, 5 minute timeout)"),
|
|
563
598
|
1,
|
|
564
599
|
0,
|
|
565
600
|
),
|
|
566
601
|
);
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
new Text(theme.fg("accent", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"), 1, 0),
|
|
570
|
-
);
|
|
571
|
-
this.ctx.ui.requestRender();
|
|
602
|
+
block.addChild(new Spacer(1));
|
|
603
|
+
block.addChild(new Text(theme.fg("accent", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"), 1, 0));
|
|
572
604
|
// Try to open browser automatically
|
|
573
605
|
try {
|
|
574
606
|
openPath(info.url);
|
|
575
607
|
|
|
576
608
|
// Show confirmation that browser should open
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
);
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
new Text(theme.fg("muted", "Alternative if browser did not open:"), 1, 0),
|
|
584
|
-
);
|
|
585
|
-
this.ctx.chatContainer.addChild(
|
|
586
|
-
new Text(theme.fg("success", "Copy this exact URL in your browser:"), 1, 0),
|
|
587
|
-
);
|
|
588
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("accent", info.url), 1, 0));
|
|
609
|
+
block.addChild(new Spacer(1));
|
|
610
|
+
block.addChild(new Text(theme.fg("success", "→ Opening browser automatically..."), 1, 0));
|
|
611
|
+
block.addChild(new Spacer(1));
|
|
612
|
+
block.addChild(new Text(theme.fg("muted", "Alternative if browser did not open:"), 1, 0));
|
|
613
|
+
block.addChild(new Text(theme.fg("success", "Copy this exact URL in your browser:"), 1, 0));
|
|
614
|
+
block.addChild(new Text(theme.fg("accent", info.url), 1, 0));
|
|
589
615
|
this.ctx.ui.requestRender();
|
|
590
616
|
} catch (_error) {
|
|
591
617
|
// Show error if browser doesn't open
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
);
|
|
596
|
-
this.ctx.chatContainer.addChild(
|
|
597
|
-
new Text(theme.fg("success", "Copy this exact URL in your browser:"), 1, 0),
|
|
598
|
-
);
|
|
599
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("accent", info.url), 1, 0));
|
|
618
|
+
block.addChild(new Spacer(1));
|
|
619
|
+
block.addChild(new Text(theme.fg("warning", "→ Could not open browser automatically"), 1, 0));
|
|
620
|
+
block.addChild(new Text(theme.fg("success", "Copy this exact URL in your browser:"), 1, 0));
|
|
621
|
+
block.addChild(new Text(theme.fg("accent", info.url), 1, 0));
|
|
600
622
|
this.ctx.ui.requestRender();
|
|
601
623
|
}
|
|
602
624
|
},
|
|
603
625
|
onProgress: (message: string) => {
|
|
604
|
-
this.ctx.
|
|
605
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("muted", message), 1, 0));
|
|
606
|
-
this.ctx.ui.requestRender();
|
|
626
|
+
this.ctx.present([new Spacer(1), new Text(theme.fg("muted", message), 1, 0)]);
|
|
607
627
|
},
|
|
608
628
|
},
|
|
609
629
|
);
|
|
@@ -611,9 +631,10 @@ export class MCPCommandController {
|
|
|
611
631
|
// Execute OAuth flow with 5 minute timeout
|
|
612
632
|
const credentials = await withTimeout(flow.login(), 5 * 60 * 1000, "OAuth flow timed out after 5 minutes");
|
|
613
633
|
|
|
614
|
-
this.ctx.
|
|
615
|
-
|
|
616
|
-
|
|
634
|
+
this.ctx.present([
|
|
635
|
+
new Spacer(1),
|
|
636
|
+
new Text(theme.fg("success", "✓ Authorization completed in browser."), 1, 0),
|
|
637
|
+
]);
|
|
617
638
|
|
|
618
639
|
// Generate a unique credential ID
|
|
619
640
|
const credentialId = `mcp_oauth_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
@@ -766,19 +787,8 @@ export class MCPCommandController {
|
|
|
766
787
|
): Promise<"connected" | "connecting" | "disconnected"> {
|
|
767
788
|
if (!this.ctx.mcpManager) return "disconnected";
|
|
768
789
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const initialFrame = frames[0] ?? "|";
|
|
772
|
-
const statusText = new Text(theme.fg("muted", `${initialFrame} Connecting to "${name}"...`), 1, 0);
|
|
773
|
-
this.ctx.chatContainer.addChild(statusText);
|
|
774
|
-
this.ctx.ui.requestRender();
|
|
775
|
-
|
|
776
|
-
let frame = 0;
|
|
777
|
-
const interval = setInterval(() => {
|
|
778
|
-
statusText.setText(theme.fg("muted", `${frames[frame % frames.length]} Connecting to "${name}"...`));
|
|
779
|
-
frame++;
|
|
780
|
-
this.ctx.ui.requestRender();
|
|
781
|
-
}, 80);
|
|
790
|
+
const block = new McpConnectingBlock(name);
|
|
791
|
+
this.ctx.present(block);
|
|
782
792
|
|
|
783
793
|
try {
|
|
784
794
|
try {
|
|
@@ -792,20 +802,19 @@ export class MCPCommandController {
|
|
|
792
802
|
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager.getTools());
|
|
793
803
|
}
|
|
794
804
|
if (state === "connected") {
|
|
795
|
-
|
|
805
|
+
block.setStatus(theme.fg("success", `✓ Connected to "${name}"`));
|
|
796
806
|
} else if (state === "connecting") {
|
|
797
|
-
|
|
807
|
+
block.setStatus(theme.fg("muted", `◌ "${name}" is still connecting...`));
|
|
798
808
|
} else {
|
|
799
|
-
|
|
809
|
+
block.setStatus(
|
|
800
810
|
options?.suppressDisconnectedWarning
|
|
801
811
|
? theme.fg("muted", `◌ Connection check complete for "${name}"`)
|
|
802
812
|
: theme.fg("warning", `⚠ Could not connect to "${name}" yet`),
|
|
803
813
|
);
|
|
804
814
|
}
|
|
805
|
-
this.ctx.ui.requestRender();
|
|
806
815
|
return state;
|
|
807
816
|
} finally {
|
|
808
|
-
|
|
817
|
+
block.finish();
|
|
809
818
|
}
|
|
810
819
|
}
|
|
811
820
|
|