@nghyane/arcane 0.1.13 → 0.1.15
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 +28 -0
- package/package.json +21 -70
- package/scripts/format-prompts.ts +1 -3
- package/src/cli/args.ts +2 -7
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli/update-cli.ts +1 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/cli.ts +0 -1
- package/src/commands/config.ts +1 -1
- package/src/commands/grep.ts +1 -1
- package/src/commands/jupyter.ts +1 -1
- package/src/commands/plugin.ts +1 -1
- package/src/commands/setup.ts +1 -1
- package/src/commands/shell.ts +1 -1
- package/src/commands/ssh.ts +1 -1
- package/src/commands/stats.ts +1 -1
- package/src/commands/update.ts +1 -1
- package/src/config/model-registry.ts +3 -4
- package/src/config/model-resolver.ts +36 -9
- package/src/config/prompt-templates.ts +1 -9
- package/src/config/settings-schema.ts +32 -88
- package/src/config/settings.ts +3 -4
- package/src/debug/index.ts +1 -1
- package/src/debug/log-formatting.ts +1 -1
- package/src/debug/log-viewer.ts +2 -2
- package/src/discovery/helpers.ts +13 -3
- package/src/exa/index.ts +1 -35
- package/src/exa/render.ts +30 -190
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/custom-tools/loader.ts +1 -1
- package/src/extensibility/custom-tools/types.ts +5 -1
- package/src/extensibility/custom-tools/wrapper.ts +1 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/extensions/wrapper.ts +7 -15
- package/src/extensibility/hooks/runner.ts +1 -1
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +1 -1
- package/src/index.ts +13 -13
- package/src/lsp/index.ts +77 -24
- package/src/lsp/render.ts +34 -583
- package/src/lsp/types.ts +3 -3
- package/src/lsp/utils.ts +1 -1
- package/src/main.ts +1 -1
- package/src/mcp/tool-bridge.ts +1 -24
- package/src/modes/components/assistant-message.ts +7 -7
- package/src/modes/components/bash-execution.ts +50 -112
- package/src/modes/components/bordered-loader.ts +1 -1
- package/src/modes/components/branch-summary-message.ts +16 -10
- package/src/modes/components/compaction-summary-message.ts +20 -12
- package/src/modes/components/context-group.ts +106 -0
- package/src/modes/components/custom-message.ts +4 -5
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/dynamic-border.ts +1 -1
- package/src/modes/components/extensions/extension-dashboard.ts +1 -1
- package/src/modes/components/extensions/extension-list.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +1 -1
- package/src/modes/components/footer.ts +2 -2
- package/src/modes/components/history-search.ts +1 -1
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-input.ts +1 -1
- package/src/modes/components/hook-message.ts +4 -5
- package/src/modes/components/hook-selector.ts +1 -1
- package/src/modes/components/index.ts +0 -2
- package/src/modes/components/keybinding-hints.ts +1 -1
- package/src/modes/components/login-dialog.ts +1 -1
- package/src/modes/components/mcp-add-wizard.ts +1 -1
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/oauth-selector.ts +1 -1
- package/src/modes/components/plugin-settings.ts +1 -1
- package/src/modes/components/python-execution.ts +51 -91
- package/src/modes/components/queue-mode-selector.ts +1 -1
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-defs.ts +5 -10
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/show-images-selector.ts +1 -1
- package/src/modes/components/skill-message.ts +4 -4
- package/src/modes/components/status-line/segments.ts +2 -2
- package/src/modes/components/status-line/separators.ts +1 -1
- package/src/modes/components/status-line-segment-editor.ts +1 -1
- package/src/modes/components/status-line.ts +1 -1
- package/src/modes/components/theme-selector.ts +1 -1
- package/src/modes/components/thinking-selector.ts +1 -1
- package/src/modes/components/todo-display.ts +2 -4
- package/src/modes/components/todo-reminder.ts +4 -4
- package/src/modes/components/tool-execution.ts +118 -440
- package/src/modes/components/tool-image-display.ts +107 -0
- package/src/modes/components/tree-selector.ts +2 -2
- package/src/modes/components/ttsr-notification.ts +4 -17
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +9 -10
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +58 -187
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +3 -1
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +3 -26
- package/src/modes/controllers/ssh-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +3 -7
- package/src/modes/print-mode.ts +5 -5
- package/src/modes/rpc/rpc-mode.ts +1 -1
- package/src/modes/types.ts +1 -2
- package/src/modes/utils/ui-helpers.ts +34 -32
- package/src/patch/edit-tool.ts +742 -0
- package/src/patch/index.ts +32 -898
- package/src/patch/schemas.ts +208 -0
- package/src/patch/shared.ts +83 -151
- package/src/prompts/agents/explore.md +22 -37
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/agents/librarian.md +29 -20
- package/src/prompts/agents/oracle.md +9 -2
- package/src/prompts/agents/reviewer.md +14 -48
- package/src/prompts/agents/task.md +16 -8
- package/src/prompts/compaction/branch-summary.md +4 -1
- package/src/prompts/compaction/compaction-summary.md +4 -1
- package/src/prompts/system/subagent-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +162 -178
- package/src/prompts/system/verification-reminder.md +6 -0
- package/src/sdk.ts +0 -9
- package/src/session/agent-session.ts +244 -1459
- package/src/session/model-controller.ts +406 -0
- package/src/session/retry-utils.ts +71 -0
- package/src/session/session-manager.ts +22 -186
- package/src/session/session-types.ts +312 -0
- package/src/session/stats.ts +387 -0
- package/src/session/streaming-edit.ts +258 -0
- package/src/session/ttsr.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +0 -8
- package/src/stt/recorder.ts +2 -2
- package/src/system-prompt.ts +1 -14
- package/src/task/agents.ts +7 -33
- package/src/task/executor.ts +50 -438
- package/src/task/index.ts +104 -71
- package/src/task/progress-tracker.ts +390 -0
- package/src/task/render.ts +371 -187
- package/src/task/subprocess-tool-registry.ts +1 -1
- package/src/task/types.ts +14 -47
- package/src/tools/ask.ts +31 -42
- package/src/tools/bash-interactive.ts +2 -2
- package/src/tools/bash-interceptor.ts +2 -2
- package/src/tools/bash-normalize.ts +1 -1
- package/src/tools/bash-skill-urls.ts +2 -2
- package/src/tools/bash.ts +87 -136
- package/src/tools/browser.ts +54 -84
- package/src/tools/create-tools.ts +186 -0
- package/src/tools/default-renderer.ts +104 -0
- package/src/tools/explore.ts +11 -10
- package/src/tools/fetch.ts +24 -114
- package/src/tools/find.ts +48 -132
- package/src/tools/gemini-image.ts +5 -15
- package/src/tools/github.ts +450 -0
- package/src/tools/grep.ts +43 -179
- package/src/tools/index.ts +35 -198
- package/src/tools/json-tree.ts +3 -3
- package/src/tools/librarian.ts +18 -18
- package/src/tools/list-limit.ts +2 -2
- package/src/tools/notebook.ts +35 -87
- package/src/tools/oracle.ts +25 -25
- package/src/tools/output-meta.ts +89 -4
- package/src/tools/output-utils.ts +2 -2
- package/src/tools/python.ts +86 -637
- package/src/tools/read.ts +36 -119
- package/src/tools/reviewer-tool.ts +19 -21
- package/src/tools/search-code.ts +128 -0
- package/src/tools/ssh.ts +67 -126
- package/src/tools/subagent-tool.ts +197 -123
- package/src/tools/todo-write.ts +15 -31
- package/src/tools/tool-errors.ts +0 -30
- package/src/tools/undo-edit.ts +30 -67
- package/src/tools/write.ts +78 -127
- package/src/tui/code-cell.ts +4 -4
- package/src/tui/file-list.ts +2 -2
- package/src/tui/output-block.ts +1 -1
- package/src/tui/status-line.ts +1 -1
- package/src/tui/tree-list.ts +2 -2
- package/src/tui/types.ts +1 -1
- package/src/tui/utils.ts +1 -1
- package/src/{tools → ui}/render-utils.ts +87 -126
- package/src/utils/external-editor.ts +4 -4
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/index.ts +30 -0
- package/src/utils/tools-manager.ts +9 -19
- package/src/web/github-client.ts +290 -0
- package/src/web/scrapers/github.ts +11 -62
- package/src/web/search/auth.ts +1 -3
- package/src/web/search/index.ts +82 -46
- package/src/web/search/provider.ts +11 -16
- package/src/web/search/providers/grep.ts +160 -0
- package/src/web/search/render.ts +48 -235
- package/src/web/search/types.ts +1 -1
- package/src/commands/commit.ts +0 -36
- package/src/commit/agentic/agent.ts +0 -311
- package/src/commit/agentic/fallback.ts +0 -96
- package/src/commit/agentic/index.ts +0 -359
- package/src/commit/agentic/prompts/analyze-file.md +0 -22
- package/src/commit/agentic/prompts/session-user.md +0 -25
- package/src/commit/agentic/prompts/split-confirm.md +0 -1
- package/src/commit/agentic/prompts/system.md +0 -38
- package/src/commit/agentic/state.ts +0 -69
- package/src/commit/agentic/tools/analyze-file.ts +0 -118
- package/src/commit/agentic/tools/git-file-diff.ts +0 -194
- package/src/commit/agentic/tools/git-hunk.ts +0 -50
- package/src/commit/agentic/tools/git-overview.ts +0 -84
- package/src/commit/agentic/tools/index.ts +0 -56
- package/src/commit/agentic/tools/propose-changelog.ts +0 -128
- package/src/commit/agentic/tools/propose-commit.ts +0 -154
- package/src/commit/agentic/tools/recent-commits.ts +0 -81
- package/src/commit/agentic/tools/split-commit.ts +0 -280
- package/src/commit/agentic/topo-sort.ts +0 -44
- package/src/commit/agentic/trivial.ts +0 -51
- package/src/commit/agentic/validation.ts +0 -200
- package/src/commit/analysis/conventional.ts +0 -165
- package/src/commit/analysis/index.ts +0 -4
- package/src/commit/analysis/scope.ts +0 -242
- package/src/commit/analysis/summary.ts +0 -112
- package/src/commit/analysis/validation.ts +0 -66
- package/src/commit/changelog/detect.ts +0 -37
- package/src/commit/changelog/generate.ts +0 -110
- package/src/commit/changelog/index.ts +0 -234
- package/src/commit/changelog/parse.ts +0 -44
- package/src/commit/cli.ts +0 -93
- package/src/commit/git/diff.ts +0 -148
- package/src/commit/git/errors.ts +0 -9
- package/src/commit/git/index.ts +0 -211
- package/src/commit/git/operations.ts +0 -54
- package/src/commit/index.ts +0 -5
- package/src/commit/map-reduce/index.ts +0 -64
- package/src/commit/map-reduce/map-phase.ts +0 -178
- package/src/commit/map-reduce/reduce-phase.ts +0 -145
- package/src/commit/map-reduce/utils.ts +0 -9
- package/src/commit/message.ts +0 -11
- package/src/commit/model-selection.ts +0 -69
- package/src/commit/pipeline.ts +0 -243
- package/src/commit/prompts/analysis-system.md +0 -148
- package/src/commit/prompts/analysis-user.md +0 -38
- package/src/commit/prompts/changelog-system.md +0 -50
- package/src/commit/prompts/changelog-user.md +0 -18
- package/src/commit/prompts/file-observer-system.md +0 -24
- package/src/commit/prompts/file-observer-user.md +0 -8
- package/src/commit/prompts/reduce-system.md +0 -50
- package/src/commit/prompts/reduce-user.md +0 -17
- package/src/commit/prompts/summary-retry.md +0 -3
- package/src/commit/prompts/summary-system.md +0 -38
- package/src/commit/prompts/summary-user.md +0 -13
- package/src/commit/prompts/types-description.md +0 -2
- package/src/commit/types.ts +0 -109
- package/src/commit/utils/exclusions.ts +0 -42
- package/src/mcp/render.ts +0 -123
- package/src/modes/components/agent-dashboard.ts +0 -1130
- package/src/modes/components/codemode-group.ts +0 -369
- package/src/modes/components/read-tool-group.ts +0 -119
- package/src/modes/components/visual-truncate.ts +0 -63
- package/src/prompts/system/subagent-user-prompt.md +0 -8
- package/src/prompts/tools/ask.md +0 -44
- package/src/prompts/tools/bash.md +0 -24
- package/src/prompts/tools/browser.md +0 -33
- package/src/prompts/tools/calculator.md +0 -12
- package/src/prompts/tools/explore.md +0 -29
- package/src/prompts/tools/fetch.md +0 -16
- package/src/prompts/tools/find.md +0 -18
- package/src/prompts/tools/gemini-image.md +0 -23
- package/src/prompts/tools/grep.md +0 -28
- package/src/prompts/tools/hashline.md +0 -232
- package/src/prompts/tools/librarian.md +0 -24
- package/src/prompts/tools/lsp.md +0 -28
- package/src/prompts/tools/oracle.md +0 -26
- package/src/prompts/tools/patch.md +0 -74
- package/src/prompts/tools/python.md +0 -66
- package/src/prompts/tools/read.md +0 -36
- package/src/prompts/tools/replace.md +0 -38
- package/src/prompts/tools/reviewer.md +0 -41
- package/src/prompts/tools/ssh.md +0 -51
- package/src/prompts/tools/task-summary.md +0 -28
- package/src/prompts/tools/task.md +0 -146
- package/src/prompts/tools/todo-write.md +0 -65
- package/src/prompts/tools/undo-edit.md +0 -7
- package/src/prompts/tools/web-search.md +0 -19
- package/src/prompts/tools/write.md +0 -18
- package/src/task/batch.ts +0 -102
- package/src/task/discovery.ts +0 -126
- package/src/task/parallel.ts +0 -84
- package/src/task/template.ts +0 -32
- package/src/tools/calculator.ts +0 -537
- package/src/tools/jtd-to-typescript.ts +0 -198
- package/src/tools/renderers.ts +0 -60
- package/src/tools/tool-result.ts +0 -86
- /package/src/{modes/theme → theme}/dark.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-catppuccin.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-dracula.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-gruvbox.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-solarized.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-tokyo-night.json +0 -0
- /package/src/{modes/theme → theme}/defaults/index.ts +0 -0
- /package/src/{modes/theme → theme}/defaults/light-catppuccin.json +0 -0
- /package/src/{modes/theme → theme}/defaults/light-github.json +0 -0
- /package/src/{modes/theme → theme}/defaults/light-solarized.json +0 -0
- /package/src/{modes/theme → theme}/light.json +0 -0
- /package/src/{modes/theme → theme}/mermaid-cache.ts +0 -0
- /package/src/{modes/theme → theme}/theme-schema.json +0 -0
- /package/src/{modes/theme → theme}/theme.ts +0 -0
package/src/tools/read.ts
CHANGED
|
@@ -8,11 +8,8 @@ import { Text } from "@nghyane/arcane-tui";
|
|
|
8
8
|
import { ptree, untilAborted } from "@nghyane/arcane-utils";
|
|
9
9
|
import { getRemoteDir } from "@nghyane/arcane-utils/dirs";
|
|
10
10
|
import { type Static, Type } from "@sinclair/typebox";
|
|
11
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
12
11
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
|
-
import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
|
|
14
12
|
import { computeLineHash } from "../patch/hashline";
|
|
15
|
-
import readDescription from "../prompts/tools/read.md" with { type: "text" };
|
|
16
13
|
import type { ToolSession } from "../sdk";
|
|
17
14
|
import {
|
|
18
15
|
DEFAULT_MAX_BYTES,
|
|
@@ -22,18 +19,17 @@ import {
|
|
|
22
19
|
truncateHead,
|
|
23
20
|
truncateStringToBytesFromStart,
|
|
24
21
|
} from "../session/streaming-output";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
22
|
+
import { getLanguageFromPath, type Theme } from "../theme/theme";
|
|
23
|
+
import { renderStatusLine } from "../tui";
|
|
24
|
+
import { formatAge, formatCount, formatErrorMessage, shortenPath } from "../ui/render-utils";
|
|
27
25
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
28
26
|
import { formatDimensionNote, resizeImage } from "../utils/image-resize";
|
|
29
27
|
import { detectSupportedImageMimeTypeFromFile } from "../utils/mime";
|
|
30
28
|
import { ensureTool } from "../utils/tools-manager";
|
|
31
29
|
import { applyListLimit } from "./list-limit";
|
|
32
|
-
import type
|
|
30
|
+
import { type OutputMeta, toolResult } from "./output-meta";
|
|
33
31
|
import { resolveReadPath, resolveToCwd } from "./path-utils";
|
|
34
|
-
import { formatAge, shortenPath, wrapBrackets } from "./render-utils";
|
|
35
32
|
import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
36
|
-
import { toolResult } from "./tool-result";
|
|
37
33
|
|
|
38
34
|
// Document types convertible via markitdown
|
|
39
35
|
const CONVERTIBLE_EXTENSIONS = new Set([".pdf", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".rtf", ".epub"]);
|
|
@@ -512,9 +508,9 @@ async function convertWithMarkitdown(
|
|
|
512
508
|
}
|
|
513
509
|
|
|
514
510
|
const readSchema = Type.Object({
|
|
515
|
-
path: Type.String({ description: "
|
|
516
|
-
offset: Type.Optional(Type.Number({ description: "
|
|
517
|
-
limit: Type.Optional(Type.Number({ description: "
|
|
511
|
+
path: Type.String({ description: "File path (relative or absolute)" }),
|
|
512
|
+
offset: Type.Optional(Type.Number({ description: "Start reading from this line number (1-indexed)" })),
|
|
513
|
+
limit: Type.Optional(Type.Number({ description: "Max number of lines to read" })),
|
|
518
514
|
});
|
|
519
515
|
|
|
520
516
|
export type ReadToolInput = Static<typeof readSchema>;
|
|
@@ -534,23 +530,17 @@ type ReadParams = ReadToolInput;
|
|
|
534
530
|
* Reads files with support for images, documents (via markitdown), and text.
|
|
535
531
|
* Directories return a formatted listing with modification times.
|
|
536
532
|
*/
|
|
537
|
-
export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
533
|
+
export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails, Theme> {
|
|
538
534
|
readonly name = "read";
|
|
539
535
|
readonly label = "Read";
|
|
540
|
-
|
|
536
|
+
description = "Read file contents, list directories, or view images";
|
|
541
537
|
readonly parameters = readSchema;
|
|
542
538
|
readonly nonAbortable = true;
|
|
543
539
|
|
|
544
540
|
readonly #autoResizeImages: boolean;
|
|
545
541
|
|
|
546
542
|
constructor(private readonly session: ToolSession) {
|
|
547
|
-
const displayMode = resolveFileDisplayMode(session);
|
|
548
543
|
this.#autoResizeImages = session.settings.get("images.autoResize");
|
|
549
|
-
this.description = renderPromptTemplate(readDescription, {
|
|
550
|
-
DEFAULT_MAX_LINES: String(DEFAULT_MAX_LINES),
|
|
551
|
-
IS_HASHLINE_MODE: displayMode.hashLines,
|
|
552
|
-
IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
|
|
553
|
-
});
|
|
554
544
|
}
|
|
555
545
|
|
|
556
546
|
async execute(
|
|
@@ -1063,20 +1053,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1063
1053
|
|
|
1064
1054
|
return resultBuilder.done();
|
|
1065
1055
|
}
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
// =============================================================================
|
|
1069
|
-
// TUI Renderer
|
|
1070
|
-
// =============================================================================
|
|
1071
|
-
|
|
1072
|
-
interface ReadRenderArgs {
|
|
1073
|
-
path?: string;
|
|
1074
|
-
file_path?: string;
|
|
1075
|
-
offset?: number;
|
|
1076
|
-
limit?: number;
|
|
1077
|
-
}
|
|
1078
1056
|
|
|
1079
|
-
export const readToolRenderer = {
|
|
1080
1057
|
renderCall(args: ReadRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
1081
1058
|
const rawPath = args.file_path || args.path || "";
|
|
1082
1059
|
const filePath = shortenPath(rawPath);
|
|
@@ -1092,102 +1069,42 @@ export const readToolRenderer = {
|
|
|
1092
1069
|
|
|
1093
1070
|
const text = renderStatusLine({ icon: "pending", title: "Read", description: pathDisplay }, uiTheme);
|
|
1094
1071
|
return new Text(text, 0, 0);
|
|
1095
|
-
}
|
|
1072
|
+
}
|
|
1096
1073
|
|
|
1097
1074
|
renderResult(
|
|
1098
|
-
result: { content: Array<{ type: string; text?: string }>; details?: ReadToolDetails },
|
|
1075
|
+
result: { content: Array<{ type: string; text?: string }>; details?: ReadToolDetails; isError?: boolean },
|
|
1099
1076
|
_options: RenderResultOptions,
|
|
1100
1077
|
uiTheme: Theme,
|
|
1101
1078
|
args?: ReadRenderArgs,
|
|
1102
1079
|
): Component {
|
|
1103
|
-
const details = result.details;
|
|
1104
|
-
const contentText = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
1105
|
-
const imageContent = result.content?.find(c => c.type === "image");
|
|
1106
1080
|
const rawPath = args?.file_path || args?.path || "";
|
|
1107
1081
|
const filePath = shortenPath(rawPath);
|
|
1108
|
-
const lang = getLanguageFromPath(rawPath);
|
|
1109
1082
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
if (details?.resolvedPath) {
|
|
1114
|
-
warningLines.push(uiTheme.fg("dim", wrapBrackets(`Resolved path: ${details.resolvedPath}`, uiTheme)));
|
|
1115
|
-
}
|
|
1116
|
-
if (truncation) {
|
|
1117
|
-
let warning: string;
|
|
1118
|
-
if (fallback?.firstLineExceedsLimit) {
|
|
1119
|
-
warning = `First line exceeds ${formatBytes(fallback.maxBytes ?? DEFAULT_MAX_BYTES)} limit`;
|
|
1120
|
-
} else if (truncation.truncatedBy === "lines") {
|
|
1121
|
-
warning = `Truncated: ${truncation.outputLines} of ${truncation.totalLines} lines (${DEFAULT_MAX_LINES} line limit)`;
|
|
1122
|
-
} else {
|
|
1123
|
-
const maxBytes = fallback?.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
1124
|
-
warning = `Truncated: ${truncation.outputLines} lines (${formatBytes(maxBytes)} limit)`;
|
|
1125
|
-
}
|
|
1126
|
-
if (truncation.artifactId) {
|
|
1127
|
-
warning += `. Full output: artifact://${truncation.artifactId}`;
|
|
1128
|
-
}
|
|
1129
|
-
warningLines.push(uiTheme.fg("warning", wrapBrackets(warning, uiTheme)));
|
|
1083
|
+
if (result.isError) {
|
|
1084
|
+
const errorText = result.content?.find(c => c.type === "text")?.text || "Unknown error";
|
|
1085
|
+
return new Text(formatErrorMessage(errorText, uiTheme), 0, 0);
|
|
1130
1086
|
}
|
|
1131
1087
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
return {
|
|
1141
|
-
render: (width: number) =>
|
|
1142
|
-
outputBlock.render(
|
|
1143
|
-
{
|
|
1144
|
-
header,
|
|
1145
|
-
state: "success",
|
|
1146
|
-
sections: [
|
|
1147
|
-
{
|
|
1148
|
-
label: uiTheme.fg("toolTitle", "Details"),
|
|
1149
|
-
lines: lines.length > 0 ? lines : [uiTheme.fg("dim", "(image)")],
|
|
1150
|
-
},
|
|
1151
|
-
],
|
|
1152
|
-
width,
|
|
1153
|
-
},
|
|
1154
|
-
uiTheme,
|
|
1155
|
-
),
|
|
1156
|
-
invalidate: () => outputBlock.invalidate(),
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1088
|
+
const details = result.details;
|
|
1089
|
+
const meta: string[] = [];
|
|
1090
|
+
const contentText = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
1091
|
+
const lineCount = contentText ? contentText.split("\n").length : 0;
|
|
1092
|
+
if (lineCount > 0) meta.push(formatCount("line", lineCount));
|
|
1093
|
+
const lang = getLanguageFromPath(rawPath);
|
|
1094
|
+
if (lang) meta.push(lang);
|
|
1095
|
+
if (details?.meta?.truncation) meta.push(uiTheme.fg("warning", "truncated"));
|
|
1159
1096
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
language: lang,
|
|
1175
|
-
title,
|
|
1176
|
-
status: "complete",
|
|
1177
|
-
output: warningLines.length > 0 ? warningLines.join("\n") : undefined,
|
|
1178
|
-
expanded: true,
|
|
1179
|
-
width,
|
|
1180
|
-
},
|
|
1181
|
-
uiTheme,
|
|
1182
|
-
);
|
|
1183
|
-
cachedWidth = width;
|
|
1184
|
-
return cachedLines;
|
|
1185
|
-
},
|
|
1186
|
-
invalidate: () => {
|
|
1187
|
-
cachedWidth = undefined;
|
|
1188
|
-
cachedLines = undefined;
|
|
1189
|
-
},
|
|
1190
|
-
};
|
|
1191
|
-
},
|
|
1192
|
-
mergeCallAndResult: true,
|
|
1193
|
-
};
|
|
1097
|
+
const text = renderStatusLine(
|
|
1098
|
+
{ icon: "success", title: "Read", description: filePath || rawPath || "file", meta },
|
|
1099
|
+
uiTheme,
|
|
1100
|
+
);
|
|
1101
|
+
return new Text(text, 0, 0);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
interface ReadRenderArgs {
|
|
1106
|
+
path?: string;
|
|
1107
|
+
file_path?: string;
|
|
1108
|
+
offset?: number;
|
|
1109
|
+
limit?: number;
|
|
1110
|
+
}
|
|
@@ -1,23 +1,10 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import
|
|
3
|
-
import { createSubagentTool } from "./subagent-tool";
|
|
2
|
+
import type { SubagentConfig } from "./subagent-tool";
|
|
4
3
|
|
|
5
4
|
const schema = Type.Object({
|
|
6
|
-
diff_description: Type.String({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}),
|
|
10
|
-
files: Type.Optional(
|
|
11
|
-
Type.Array(Type.String(), {
|
|
12
|
-
description: "Specific file paths to focus the review on. If omitted, all changed files are reviewed.",
|
|
13
|
-
}),
|
|
14
|
-
),
|
|
15
|
-
instructions: Type.Optional(
|
|
16
|
-
Type.String({
|
|
17
|
-
description:
|
|
18
|
-
'Additional guidance for the reviewer. Examples: "Focus on error handling", "Check for race conditions".',
|
|
19
|
-
}),
|
|
20
|
-
),
|
|
5
|
+
diff_description: Type.String({ description: "Description of the diff or change to review" }),
|
|
6
|
+
files: Type.Optional(Type.Array(Type.String(), { description: "Specific files to focus the review on" })),
|
|
7
|
+
instructions: Type.Optional(Type.String({ description: "Additional review instructions" })),
|
|
21
8
|
});
|
|
22
9
|
|
|
23
10
|
function buildTask(p: Record<string, unknown>): string {
|
|
@@ -28,14 +15,25 @@ function buildTask(p: Record<string, unknown>): string {
|
|
|
28
15
|
return parts.join("\n");
|
|
29
16
|
}
|
|
30
17
|
|
|
31
|
-
export const
|
|
18
|
+
export const reviewerConfig: SubagentConfig<typeof schema.properties> = {
|
|
32
19
|
name: "code_review",
|
|
33
20
|
label: "Code Review",
|
|
34
21
|
agent: "reviewer",
|
|
35
22
|
schema,
|
|
36
|
-
descriptionTemplate: reviewerDescription,
|
|
37
23
|
progressText: "Reviewing code...",
|
|
38
24
|
tmpPrefix: "arc-review-",
|
|
39
25
|
buildTask,
|
|
40
|
-
buildDescription: p =>
|
|
41
|
-
|
|
26
|
+
buildDescription: p => String(p.diff_description ?? "").slice(0, 80),
|
|
27
|
+
buildContextLine: p => {
|
|
28
|
+
const parts: string[] = [];
|
|
29
|
+
const files = p.files as string[] | undefined;
|
|
30
|
+
if (files?.length) parts.push(`${files.length} file${files.length > 1 ? "s" : ""}`);
|
|
31
|
+
if (p.instructions) parts.push(String(p.instructions).slice(0, 50));
|
|
32
|
+
return parts.length > 0 ? parts.join(" · ") : null;
|
|
33
|
+
},
|
|
34
|
+
toolDescription: [
|
|
35
|
+
"Code review specialist — spawns reviewer agent on a diff.",
|
|
36
|
+
'Pass diff_description (e.g. "uncommitted changes", "last commit"), optionally files and instructions.',
|
|
37
|
+
].join(" "),
|
|
38
|
+
passContext: false,
|
|
39
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentTool,
|
|
3
|
+
AgentToolContext,
|
|
4
|
+
AgentToolResult,
|
|
5
|
+
AgentToolUpdateCallback,
|
|
6
|
+
RenderResultOptions,
|
|
7
|
+
} from "@nghyane/arcane-agent";
|
|
8
|
+
import type { Component } from "@nghyane/arcane-tui";
|
|
9
|
+
import { Text } from "@nghyane/arcane-tui";
|
|
10
|
+
import { Type } from "@sinclair/typebox";
|
|
11
|
+
import type { Theme } from "../theme/theme";
|
|
12
|
+
import { renderStatusLine } from "../tui";
|
|
13
|
+
import { formatCount, formatErrorMessage, truncateToWidth } from "../ui/render-utils";
|
|
14
|
+
import { GrepAppProvider, type SearchCodeSource } from "../web/search/providers/grep";
|
|
15
|
+
import { renderSearchCall, type SearchRenderDetails } from "../web/search/render";
|
|
16
|
+
|
|
17
|
+
const grepProvider = new GrepAppProvider();
|
|
18
|
+
|
|
19
|
+
const searchCodeSchema = Type.Object({
|
|
20
|
+
query: Type.String({ description: "Search query or code pattern" }),
|
|
21
|
+
regexp: Type.Optional(Type.Boolean({ description: "Treat query as regex" })),
|
|
22
|
+
language: Type.Optional(Type.String({ description: "Filter by programming language" })),
|
|
23
|
+
repo: Type.Optional(Type.String({ description: "Filter by repository (owner/repo)" })),
|
|
24
|
+
limit: Type.Optional(Type.Number({ description: "Max number of results" })),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
interface SearchCodeToolParams {
|
|
28
|
+
query: string;
|
|
29
|
+
regexp?: boolean;
|
|
30
|
+
language?: string;
|
|
31
|
+
repo?: string;
|
|
32
|
+
limit?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class SearchCodeTool implements AgentTool<typeof searchCodeSchema, SearchRenderDetails, Theme> {
|
|
36
|
+
readonly name = "search_code";
|
|
37
|
+
readonly label = "Code Search";
|
|
38
|
+
readonly description = "Search source code across public GitHub repositories via grep.app";
|
|
39
|
+
readonly parameters = searchCodeSchema;
|
|
40
|
+
readonly renderCall = renderSearchCall;
|
|
41
|
+
|
|
42
|
+
renderResult(
|
|
43
|
+
result: { content: Array<{ type: string; text?: string }>; details?: SearchRenderDetails; isError?: boolean },
|
|
44
|
+
_options: RenderResultOptions,
|
|
45
|
+
theme: Theme,
|
|
46
|
+
args?: SearchCodeToolParams,
|
|
47
|
+
): Component {
|
|
48
|
+
if (result.isError || result.details?.error) {
|
|
49
|
+
const errorText =
|
|
50
|
+
result.details?.error || result.content?.find(c => c.type === "text")?.text || "Unknown error";
|
|
51
|
+
return new Text(formatErrorMessage(errorText, theme), 0, 0);
|
|
52
|
+
}
|
|
53
|
+
const query = args?.query ? truncateToWidth(args.query, 60) : "code";
|
|
54
|
+
const sourceCount = result.details?.response?.sources?.length ?? 0;
|
|
55
|
+
return new Text(
|
|
56
|
+
renderStatusLine(
|
|
57
|
+
{
|
|
58
|
+
icon: sourceCount > 0 ? "success" : "warning",
|
|
59
|
+
title: "Code Search",
|
|
60
|
+
description: `"${query}"`,
|
|
61
|
+
meta: [formatCount("result", sourceCount)],
|
|
62
|
+
},
|
|
63
|
+
theme,
|
|
64
|
+
),
|
|
65
|
+
0,
|
|
66
|
+
0,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async execute(
|
|
71
|
+
_toolCallId: string,
|
|
72
|
+
params: SearchCodeToolParams,
|
|
73
|
+
_signal?: AbortSignal,
|
|
74
|
+
_onUpdate?: AgentToolUpdateCallback<SearchRenderDetails>,
|
|
75
|
+
_context?: AgentToolContext,
|
|
76
|
+
): Promise<AgentToolResult<SearchRenderDetails>> {
|
|
77
|
+
try {
|
|
78
|
+
const response = await grepProvider.search({
|
|
79
|
+
query: params.query,
|
|
80
|
+
regexp: params.regexp,
|
|
81
|
+
language: params.language,
|
|
82
|
+
repo: params.repo,
|
|
83
|
+
limit: params.limit,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const parts: string[] = [];
|
|
87
|
+
if (response.sources.length === 0) {
|
|
88
|
+
parts.push("No code results found.");
|
|
89
|
+
} else {
|
|
90
|
+
const header = response.total
|
|
91
|
+
? `Found ${response.sources.length} of ${response.total} total matches across public GitHub repos.`
|
|
92
|
+
: `Found ${response.sources.length} code result(s) across public GitHub repos.`;
|
|
93
|
+
parts.push(header);
|
|
94
|
+
|
|
95
|
+
if (response.topLanguages && response.topLanguages.length > 0) {
|
|
96
|
+
parts.push(`Top languages: ${response.topLanguages.map(l => `${l.val} (${l.count})`).join(", ")}`);
|
|
97
|
+
}
|
|
98
|
+
if (response.topRepos && response.topRepos.length > 0) {
|
|
99
|
+
parts.push(`Top repos: ${response.topRepos.map(r => `${r.val} (${r.count})`).join(", ")}`);
|
|
100
|
+
}
|
|
101
|
+
parts.push("");
|
|
102
|
+
|
|
103
|
+
for (const source of response.sources as SearchCodeSource[]) {
|
|
104
|
+
const matchInfo = source.matchCount ? ` (${source.matchCount} matches)` : "";
|
|
105
|
+
parts.push(`### ${source.title}${matchInfo}`);
|
|
106
|
+
parts.push(source.url);
|
|
107
|
+
if (source.snippet) {
|
|
108
|
+
const lineInfo = source.lineNumbers?.[0] ? `L${source.lineNumbers[0]}` : "";
|
|
109
|
+
parts.push(`\`\`\`${lineInfo}\n${source.snippet}\n\`\`\``);
|
|
110
|
+
}
|
|
111
|
+
parts.push("");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
content: [{ type: "text" as const, text: parts.join("\n") }],
|
|
117
|
+
details: { response, error: undefined },
|
|
118
|
+
};
|
|
119
|
+
} catch (error) {
|
|
120
|
+
const message = `Code search failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: "text" as const, text: message }],
|
|
124
|
+
details: { response: { provider: "grep" as const, sources: [] }, error: message },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
package/src/tools/ssh.ts
CHANGED
|
@@ -4,29 +4,25 @@ import { Text } from "@nghyane/arcane-tui";
|
|
|
4
4
|
import { type Static, Type } from "@sinclair/typebox";
|
|
5
5
|
import type { SSHHost } from "../capability/ssh";
|
|
6
6
|
import { sshCapability } from "../capability/ssh";
|
|
7
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
8
7
|
import { loadCapability } from "../discovery";
|
|
9
8
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
10
|
-
import type { Theme } from "../modes/theme/theme";
|
|
11
|
-
import sshDescriptionBase from "../prompts/tools/ssh.md" with { type: "text" };
|
|
12
9
|
import { DEFAULT_MAX_BYTES } from "../session/streaming-output";
|
|
13
10
|
import type { SSHHostInfo } from "../ssh/connection-manager";
|
|
14
11
|
import { ensureHostInfo, getHostInfoForHost } from "../ssh/connection-manager";
|
|
15
12
|
import { executeSSH } from "../ssh/ssh-executor";
|
|
13
|
+
import type { Theme } from "../theme/theme";
|
|
16
14
|
import { renderStatusLine } from "../tui";
|
|
17
|
-
import {
|
|
15
|
+
import { formatClickHint, replaceTabs } from "../ui/render-utils";
|
|
18
16
|
import type { ToolSession } from ".";
|
|
19
|
-
import type
|
|
17
|
+
import { type OutputMeta, toolResult } from "./output-meta";
|
|
20
18
|
import { allocateOutputArtifact, createTailBuffer } from "./output-utils";
|
|
21
|
-
import { formatBytes, wrapBrackets } from "./render-utils";
|
|
22
19
|
import { ToolError } from "./tool-errors";
|
|
23
|
-
import { toolResult } from "./tool-result";
|
|
24
20
|
|
|
25
21
|
const sshSchema = Type.Object({
|
|
26
|
-
host: Type.String({ description: "
|
|
27
|
-
command: Type.String({ description: "
|
|
28
|
-
cwd: Type.Optional(Type.String({ description: "
|
|
29
|
-
timeout: Type.Optional(Type.Number({ description: "Timeout in
|
|
22
|
+
host: Type.String({ description: "SSH host alias (from ~/.ssh/config)" }),
|
|
23
|
+
command: Type.String({ description: "Shell command to execute on the remote host" }),
|
|
24
|
+
cwd: Type.Optional(Type.String({ description: "Working directory on the remote host" })),
|
|
25
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in milliseconds" })),
|
|
30
26
|
});
|
|
31
27
|
|
|
32
28
|
export interface SSHToolDetails {
|
|
@@ -60,12 +56,9 @@ async function formatHostEntry(host: SSHHost): Promise<string> {
|
|
|
60
56
|
}
|
|
61
57
|
|
|
62
58
|
async function formatDescription(hosts: SSHHost[]): Promise<string> {
|
|
63
|
-
|
|
64
|
-
if (hosts.length === 0) {
|
|
65
|
-
return baseDescription;
|
|
66
|
-
}
|
|
59
|
+
if (hosts.length === 0) return "";
|
|
67
60
|
const hostList = (await Promise.all(hosts.map(formatHostEntry))).join("\n");
|
|
68
|
-
return
|
|
61
|
+
return `Available hosts:\n${hostList}`;
|
|
69
62
|
}
|
|
70
63
|
|
|
71
64
|
function quoteRemotePath(value: string): string {
|
|
@@ -119,7 +112,7 @@ async function loadHosts(session: ToolSession): Promise<{
|
|
|
119
112
|
|
|
120
113
|
type SshToolParams = Static<typeof sshSchema>;
|
|
121
114
|
|
|
122
|
-
export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails> {
|
|
115
|
+
export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails, Theme> {
|
|
123
116
|
readonly name = "ssh";
|
|
124
117
|
readonly label = "SSH";
|
|
125
118
|
readonly parameters = sshSchema;
|
|
@@ -193,6 +186,62 @@ export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails> {
|
|
|
193
186
|
|
|
194
187
|
return resultBuilder.done();
|
|
195
188
|
}
|
|
189
|
+
|
|
190
|
+
renderCall(args: SshRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
191
|
+
const host = args.host || "…";
|
|
192
|
+
const command = args.command || "…";
|
|
193
|
+
const text = renderStatusLine({ icon: "pending", title: "SSH", description: `[${host}] $ ${command}` }, uiTheme);
|
|
194
|
+
return new Text(text, 0, 0);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
renderResult(
|
|
198
|
+
result: { content: Array<{ type: string; text?: string }>; details?: SSHToolDetails },
|
|
199
|
+
options: RenderResultOptions & { renderContext?: SshRenderContext },
|
|
200
|
+
uiTheme: Theme,
|
|
201
|
+
args?: SshRenderArgs,
|
|
202
|
+
): Component {
|
|
203
|
+
const host = args?.host || "…";
|
|
204
|
+
const command = args?.command || "…";
|
|
205
|
+
const cmdText = `[${host}] $ ${command}`;
|
|
206
|
+
const textContent = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
207
|
+
const output = textContent.trimEnd();
|
|
208
|
+
const outputLines = output ? output.split("\n") : [];
|
|
209
|
+
const total = outputLines.length;
|
|
210
|
+
const truncation = result.details?.meta?.truncation;
|
|
211
|
+
const isError = false;
|
|
212
|
+
|
|
213
|
+
const meta: string[] = [];
|
|
214
|
+
if (total > 0) meta.push(`${total} lines`);
|
|
215
|
+
|
|
216
|
+
const header = renderStatusLine(
|
|
217
|
+
{ icon: isError ? "error" : "success", title: "SSH", description: cmdText, meta },
|
|
218
|
+
uiTheme,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const TAIL = 4;
|
|
222
|
+
const expanded = options.expanded;
|
|
223
|
+
const showAll = isError || expanded;
|
|
224
|
+
const displayLines = showAll ? outputLines : outputLines.slice(-TAIL);
|
|
225
|
+
const skipped = total - displayLines.length;
|
|
226
|
+
|
|
227
|
+
const bodyLines: string[] = [];
|
|
228
|
+
if (skipped > 0) {
|
|
229
|
+
bodyLines.push(uiTheme.fg("dim", `… (${skipped} earlier lines)`));
|
|
230
|
+
}
|
|
231
|
+
const hasTruncation = Boolean(truncation);
|
|
232
|
+
for (let i = 0; i < displayLines.length; i++) {
|
|
233
|
+
bodyLines.push(uiTheme.fg("toolOutput", replaceTabs(displayLines[i])));
|
|
234
|
+
}
|
|
235
|
+
if (hasTruncation) {
|
|
236
|
+
bodyLines.push(uiTheme.fg("warning", "output truncated"));
|
|
237
|
+
}
|
|
238
|
+
if (!showAll && skipped > 0) {
|
|
239
|
+
bodyLines.push(formatClickHint(uiTheme));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const lines = bodyLines.length > 0 ? [header, ...bodyLines] : [header];
|
|
243
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
244
|
+
}
|
|
196
245
|
}
|
|
197
246
|
|
|
198
247
|
export async function loadSshTool(session: ToolSession): Promise<SshTool | null> {
|
|
@@ -209,118 +258,10 @@ export async function loadSshTool(session: ToolSession): Promise<SshTool | null>
|
|
|
209
258
|
return new SshTool(session, hostNames, hostsByName, description);
|
|
210
259
|
}
|
|
211
260
|
|
|
212
|
-
// =============================================================================
|
|
213
|
-
// TUI Renderer
|
|
214
|
-
// =============================================================================
|
|
215
|
-
|
|
216
261
|
interface SshRenderArgs {
|
|
217
262
|
host?: string;
|
|
218
263
|
command?: string;
|
|
219
264
|
timeout?: number;
|
|
220
265
|
}
|
|
221
266
|
|
|
222
|
-
interface SshRenderContext {
|
|
223
|
-
/** Visual lines for truncated output (pre-computed by tool-execution) */
|
|
224
|
-
visualLines?: string[];
|
|
225
|
-
/** Number of lines skipped */
|
|
226
|
-
skippedCount?: number;
|
|
227
|
-
/** Total visual lines */
|
|
228
|
-
totalVisualLines?: number;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export const sshToolRenderer = {
|
|
232
|
-
renderCall(args: SshRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
233
|
-
const host = args.host || "…";
|
|
234
|
-
const command = args.command || "…";
|
|
235
|
-
const text = renderStatusLine({ icon: "pending", title: "SSH", description: `[${host}] $ ${command}` }, uiTheme);
|
|
236
|
-
return new Text(text, 0, 0);
|
|
237
|
-
},
|
|
238
|
-
|
|
239
|
-
renderResult(
|
|
240
|
-
result: {
|
|
241
|
-
content: Array<{ type: string; text?: string }>;
|
|
242
|
-
details?: SSHToolDetails;
|
|
243
|
-
},
|
|
244
|
-
options: RenderResultOptions & { renderContext?: SshRenderContext },
|
|
245
|
-
uiTheme: Theme,
|
|
246
|
-
args?: SshRenderArgs,
|
|
247
|
-
): Component {
|
|
248
|
-
const details = result.details;
|
|
249
|
-
const host = args?.host || "…";
|
|
250
|
-
const command = args?.command || "…";
|
|
251
|
-
const header = renderStatusLine(
|
|
252
|
-
{ icon: "success", title: "SSH", description: `[${host}] $ ${command}` },
|
|
253
|
-
uiTheme,
|
|
254
|
-
);
|
|
255
|
-
const textContent = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
256
|
-
const truncation = details?.meta?.truncation;
|
|
257
|
-
const outputBlock = new CachedOutputBlock();
|
|
258
|
-
|
|
259
|
-
return {
|
|
260
|
-
render: (width: number): string[] => {
|
|
261
|
-
// REACTIVE: read mutable options at render time
|
|
262
|
-
const { expanded, renderContext } = options;
|
|
263
|
-
const output = textContent.trimEnd();
|
|
264
|
-
const outputLines: string[] = [];
|
|
265
|
-
|
|
266
|
-
if (output) {
|
|
267
|
-
if (expanded) {
|
|
268
|
-
outputLines.push(...output.split("\n").map(line => uiTheme.fg("toolOutput", line)));
|
|
269
|
-
} else if (renderContext?.visualLines) {
|
|
270
|
-
const { visualLines, skippedCount = 0, totalVisualLines = visualLines.length } = renderContext;
|
|
271
|
-
if (skippedCount > 0) {
|
|
272
|
-
outputLines.push(
|
|
273
|
-
uiTheme.fg(
|
|
274
|
-
"dim",
|
|
275
|
-
`… (${skippedCount} earlier lines, showing ${visualLines.length} of ${totalVisualLines}) (ctrl+o to expand)`,
|
|
276
|
-
),
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
const styledVisual = visualLines.map(line =>
|
|
280
|
-
line.includes("\x1b[") ? line : uiTheme.fg("toolOutput", line),
|
|
281
|
-
);
|
|
282
|
-
outputLines.push(...styledVisual);
|
|
283
|
-
} else {
|
|
284
|
-
const outputLinesRaw = output.split("\n");
|
|
285
|
-
const maxLines = 5;
|
|
286
|
-
const displayLines = outputLinesRaw.slice(0, maxLines);
|
|
287
|
-
const remaining = outputLinesRaw.length - maxLines;
|
|
288
|
-
outputLines.push(...displayLines.map(line => uiTheme.fg("toolOutput", line)));
|
|
289
|
-
if (remaining > 0) {
|
|
290
|
-
outputLines.push(uiTheme.fg("dim", `… (${remaining} more lines) (ctrl+o to expand)`));
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (truncation) {
|
|
296
|
-
const warnings: string[] = [];
|
|
297
|
-
if (truncation.artifactId) {
|
|
298
|
-
warnings.push(`Full output: artifact://${truncation.artifactId}`);
|
|
299
|
-
}
|
|
300
|
-
if (truncation.truncatedBy === "lines") {
|
|
301
|
-
warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
|
|
302
|
-
} else {
|
|
303
|
-
warnings.push(
|
|
304
|
-
`Truncated: ${truncation.outputLines} lines shown (${formatBytes(truncation.outputBytes)} limit)`,
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
outputLines.push(uiTheme.fg("warning", wrapBrackets(warnings.join(". "), uiTheme)));
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return outputBlock.render(
|
|
311
|
-
{
|
|
312
|
-
header,
|
|
313
|
-
state: "success",
|
|
314
|
-
sections: [{ label: uiTheme.fg("toolTitle", "Output"), lines: outputLines }],
|
|
315
|
-
width,
|
|
316
|
-
},
|
|
317
|
-
uiTheme,
|
|
318
|
-
);
|
|
319
|
-
},
|
|
320
|
-
invalidate: () => {
|
|
321
|
-
outputBlock.invalidate();
|
|
322
|
-
},
|
|
323
|
-
};
|
|
324
|
-
},
|
|
325
|
-
mergeCallAndResult: true,
|
|
326
|
-
};
|
|
267
|
+
interface SshRenderContext {}
|