@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/python.ts
CHANGED
|
@@ -6,67 +6,43 @@ import type { Component } from "@nghyane/arcane-tui";
|
|
|
6
6
|
import { Text } from "@nghyane/arcane-tui";
|
|
7
7
|
import { getProjectDir } from "@nghyane/arcane-utils/dirs";
|
|
8
8
|
import { type Static, Type } from "@sinclair/typebox";
|
|
9
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
10
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
11
|
-
import { executePython,
|
|
12
|
-
import type {
|
|
13
|
-
import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
14
|
-
import type { Theme } from "../modes/theme/theme";
|
|
15
|
-
import pythonDescription from "../prompts/tools/python.md" with { type: "text" };
|
|
10
|
+
import { executePython, type PythonExecutorOptions } from "../ipy/executor";
|
|
11
|
+
import type { PythonStatusEvent } from "../ipy/kernel";
|
|
16
12
|
import { DEFAULT_MAX_BYTES, OutputSink, type OutputSummary } from "../session/streaming-output";
|
|
17
|
-
import {
|
|
13
|
+
import type { Theme } from "../theme/theme";
|
|
14
|
+
import { renderCodeCell, renderStatusLine } from "../tui";
|
|
15
|
+
import { formatClickHint, PREVIEW_LIMITS, 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
19
|
import { resolveToCwd } from "./path-utils";
|
|
22
|
-
import { replaceTabs, shortenPath, ToolUIKit, truncateToWidth } from "./render-utils";
|
|
23
20
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
24
|
-
import { toolResult } from "./tool-result";
|
|
25
21
|
|
|
26
22
|
export const PYTHON_DEFAULT_PREVIEW_LINES = 10;
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
name: string;
|
|
30
|
-
functions: PreludeHelper[];
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
function groupPreludeHelpers(helpers: PreludeHelper[]): PreludeCategory[] {
|
|
34
|
-
const categories: PreludeCategory[] = [];
|
|
35
|
-
const byName = new Map<string, PreludeHelper[]>();
|
|
36
|
-
for (const helper of helpers) {
|
|
37
|
-
let bucket = byName.get(helper.category);
|
|
38
|
-
if (!bucket) {
|
|
39
|
-
bucket = [];
|
|
40
|
-
byName.set(helper.category, bucket);
|
|
41
|
-
categories.push({ name: helper.category, functions: bucket });
|
|
42
|
-
}
|
|
43
|
-
bucket.push(helper);
|
|
44
|
-
}
|
|
45
|
-
return categories;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export const pythonSchema = Type.Object({
|
|
24
|
+
const pythonSchema = Type.Object({
|
|
49
25
|
cells: Type.Array(
|
|
50
26
|
Type.Object({
|
|
51
27
|
code: Type.String({ description: "Python code to execute" }),
|
|
52
|
-
title: Type.Optional(Type.String({ description: "
|
|
28
|
+
title: Type.Optional(Type.String({ description: "Optional label for the cell" })),
|
|
53
29
|
}),
|
|
54
|
-
{ description: "
|
|
30
|
+
{ description: "Code cells to execute sequentially" },
|
|
55
31
|
),
|
|
56
|
-
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds
|
|
57
|
-
cwd: Type.Optional(Type.String({ description: "Working directory
|
|
58
|
-
reset: Type.Optional(Type.Boolean({ description: "
|
|
32
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds" })),
|
|
33
|
+
cwd: Type.Optional(Type.String({ description: "Working directory" })),
|
|
34
|
+
reset: Type.Optional(Type.Boolean({ description: "Reset kernel state before execution" })),
|
|
59
35
|
});
|
|
60
|
-
|
|
36
|
+
type PythonToolParams = Static<typeof pythonSchema>;
|
|
61
37
|
|
|
62
|
-
|
|
38
|
+
type PythonToolResult = {
|
|
63
39
|
content: Array<{ type: "text"; text: string }>;
|
|
64
40
|
details: PythonToolDetails | undefined;
|
|
65
41
|
};
|
|
66
42
|
|
|
67
|
-
|
|
43
|
+
type PythonProxyExecutor = (params: PythonToolParams, signal?: AbortSignal) => Promise<PythonToolResult>;
|
|
68
44
|
|
|
69
|
-
|
|
45
|
+
interface PythonCellResult {
|
|
70
46
|
index: number;
|
|
71
47
|
title?: string;
|
|
72
48
|
code: string;
|
|
@@ -88,64 +64,14 @@ export interface PythonToolDetails {
|
|
|
88
64
|
meta?: OutputMeta;
|
|
89
65
|
}
|
|
90
66
|
|
|
91
|
-
function formatJsonScalar(value: unknown): string {
|
|
92
|
-
if (value === null) return "null";
|
|
93
|
-
if (value === undefined) return "undefined";
|
|
94
|
-
if (typeof value === "string") return JSON.stringify(value);
|
|
95
|
-
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
|
96
|
-
if (typeof value === "function") return "[function]";
|
|
97
|
-
return "[object]";
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function renderJsonTree(value: unknown, theme: Theme, expanded: boolean, maxDepth = expanded ? 6 : 2): string[] {
|
|
101
|
-
const maxItems = expanded ? 20 : 5;
|
|
102
|
-
|
|
103
|
-
const renderNode = (node: unknown, prefix: string, depth: number, isLast: boolean, label?: string): string[] => {
|
|
104
|
-
const branch = getTreeBranch(isLast, theme);
|
|
105
|
-
const displayLabel = label ? `${label}: ` : "";
|
|
106
|
-
|
|
107
|
-
if (depth >= maxDepth || node === null || typeof node !== "object") {
|
|
108
|
-
return [`${prefix}${branch} ${displayLabel}${formatJsonScalar(node)}`];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const isArray = Array.isArray(node);
|
|
112
|
-
const entries = isArray
|
|
113
|
-
? node.map((val, index) => [String(index), val] as const)
|
|
114
|
-
: Object.entries(node as object);
|
|
115
|
-
const header = `${prefix}${branch} ${displayLabel}${isArray ? `Array(${entries.length})` : `Object(${entries.length})`}`;
|
|
116
|
-
const lines = [header];
|
|
117
|
-
|
|
118
|
-
const childPrefix = prefix + getTreeContinuePrefix(isLast, theme);
|
|
119
|
-
const visible = entries.slice(0, maxItems);
|
|
120
|
-
for (let i = 0; i < visible.length; i++) {
|
|
121
|
-
const [key, val] = visible[i];
|
|
122
|
-
const childLast = i === visible.length - 1 && (expanded || entries.length <= maxItems);
|
|
123
|
-
lines.push(...renderNode(val, childPrefix, depth + 1, childLast, isArray ? `[${key}]` : key));
|
|
124
|
-
}
|
|
125
|
-
if (!expanded && entries.length > maxItems) {
|
|
126
|
-
const moreBranch = theme.tree.last;
|
|
127
|
-
lines.push(`${childPrefix}${moreBranch} ${entries.length - maxItems} more item(s)`);
|
|
128
|
-
}
|
|
129
|
-
return lines;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
return renderNode(value, "", 0, true);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function getPythonToolDescription(): string {
|
|
136
|
-
const helpers = getPreludeDocs();
|
|
137
|
-
const categories = groupPreludeHelpers(helpers);
|
|
138
|
-
return renderPromptTemplate(pythonDescription, { categories });
|
|
139
|
-
}
|
|
140
|
-
|
|
141
67
|
export interface PythonToolOptions {
|
|
142
68
|
proxyExecutor?: PythonProxyExecutor;
|
|
143
69
|
}
|
|
144
70
|
|
|
145
|
-
export class PythonTool implements AgentTool<typeof pythonSchema> {
|
|
71
|
+
export class PythonTool implements AgentTool<typeof pythonSchema, any, Theme> {
|
|
146
72
|
readonly name = "python";
|
|
147
73
|
readonly label = "Python";
|
|
148
|
-
|
|
74
|
+
description = "Execute Python code in a persistent kernel";
|
|
149
75
|
readonly parameters = pythonSchema;
|
|
150
76
|
readonly concurrency = "exclusive";
|
|
151
77
|
|
|
@@ -156,7 +82,6 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
|
|
|
156
82
|
options?: PythonToolOptions,
|
|
157
83
|
) {
|
|
158
84
|
this.#proxyExecutor = options?.proxyExecutor;
|
|
159
|
-
this.description = getPythonToolDescription();
|
|
160
85
|
}
|
|
161
86
|
|
|
162
87
|
async execute(
|
|
@@ -480,361 +405,26 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
|
|
|
480
405
|
}
|
|
481
406
|
}
|
|
482
407
|
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
interface PythonRenderArgs {
|
|
486
|
-
cells?: Array<{ code: string; title?: string }>;
|
|
487
|
-
timeout?: number;
|
|
488
|
-
cwd?: string;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
interface PythonRenderContext {
|
|
492
|
-
output?: string;
|
|
493
|
-
expanded?: boolean;
|
|
494
|
-
previewLines?: number;
|
|
495
|
-
timeout?: number;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/** Format a status event as a single line for display. */
|
|
499
|
-
function formatStatusEvent(event: PythonStatusEvent, theme: Theme): string {
|
|
500
|
-
const { op, ...data } = event;
|
|
501
|
-
|
|
502
|
-
// Map operations to available theme icons
|
|
503
|
-
type AvailableIcon = "icon.file" | "icon.folder" | "icon.git" | "icon.package";
|
|
504
|
-
const opIcons: Record<string, AvailableIcon> = {
|
|
505
|
-
// File I/O
|
|
506
|
-
read: "icon.file",
|
|
507
|
-
write: "icon.file",
|
|
508
|
-
append: "icon.file",
|
|
509
|
-
cat: "icon.file",
|
|
510
|
-
touch: "icon.file",
|
|
511
|
-
lines: "icon.file",
|
|
512
|
-
// Navigation/Directory
|
|
513
|
-
ls: "icon.folder",
|
|
514
|
-
cd: "icon.folder",
|
|
515
|
-
pwd: "icon.folder",
|
|
516
|
-
mkdir: "icon.folder",
|
|
517
|
-
tree: "icon.folder",
|
|
518
|
-
stat: "icon.folder",
|
|
519
|
-
// Search (use file icon since no search icon)
|
|
520
|
-
find: "icon.file",
|
|
521
|
-
grep: "icon.file",
|
|
522
|
-
rgrep: "icon.file",
|
|
523
|
-
glob: "icon.file",
|
|
524
|
-
// Edit operations (use file icon)
|
|
525
|
-
replace: "icon.file",
|
|
526
|
-
sed: "icon.file",
|
|
527
|
-
rsed: "icon.file",
|
|
528
|
-
delete_lines: "icon.file",
|
|
529
|
-
delete_matching: "icon.file",
|
|
530
|
-
insert_at: "icon.file",
|
|
531
|
-
// Git
|
|
532
|
-
git_status: "icon.git",
|
|
533
|
-
git_diff: "icon.git",
|
|
534
|
-
git_log: "icon.git",
|
|
535
|
-
git_show: "icon.git",
|
|
536
|
-
git_branch: "icon.git",
|
|
537
|
-
git_file_at: "icon.git",
|
|
538
|
-
git_has_changes: "icon.git",
|
|
539
|
-
// Shell/batch (use package icon)
|
|
540
|
-
run: "icon.package",
|
|
541
|
-
sh: "icon.package",
|
|
542
|
-
env: "icon.package",
|
|
543
|
-
batch: "icon.package",
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
const iconKey = opIcons[op] ?? "icon.file";
|
|
547
|
-
const icon = theme.styledSymbol(iconKey, "muted");
|
|
548
|
-
|
|
549
|
-
// Format the status message based on operation type
|
|
550
|
-
const parts: string[] = [];
|
|
551
|
-
|
|
552
|
-
// Error handling
|
|
553
|
-
if (data.error) {
|
|
554
|
-
return `${icon} ${theme.fg("warning", op)}: ${theme.fg("dim", String(data.error))}`;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Build description based on common fields
|
|
558
|
-
switch (op) {
|
|
559
|
-
case "read":
|
|
560
|
-
parts.push(`${data.chars} chars`);
|
|
561
|
-
if (data.path) parts.push(`from ${shortenPath(String(data.path))}`);
|
|
562
|
-
break;
|
|
563
|
-
case "write":
|
|
564
|
-
case "append":
|
|
565
|
-
parts.push(`${data.chars} chars`);
|
|
566
|
-
if (data.path) parts.push(`to ${shortenPath(String(data.path))}`);
|
|
567
|
-
break;
|
|
568
|
-
case "cat":
|
|
569
|
-
parts.push(`${data.files} file${(data.files as number) !== 1 ? "s" : ""}`);
|
|
570
|
-
parts.push(`${data.chars} chars`);
|
|
571
|
-
break;
|
|
572
|
-
case "find":
|
|
573
|
-
case "glob":
|
|
574
|
-
parts.push(`${data.count} match${(data.count as number) !== 1 ? "es" : ""}`);
|
|
575
|
-
if (data.pattern) parts.push(`for "${truncateToWidth(String(data.pattern), 20)}"`);
|
|
576
|
-
break;
|
|
577
|
-
case "grep":
|
|
578
|
-
parts.push(`${data.count} match${(data.count as number) !== 1 ? "es" : ""}`);
|
|
579
|
-
if (data.path) parts.push(`in ${shortenPath(String(data.path))}`);
|
|
580
|
-
break;
|
|
581
|
-
case "rgrep":
|
|
582
|
-
parts.push(`${data.count} match${(data.count as number) !== 1 ? "es" : ""}`);
|
|
583
|
-
if (data.pattern) parts.push(`for "${truncateToWidth(String(data.pattern), 20)}"`);
|
|
584
|
-
break;
|
|
585
|
-
case "ls":
|
|
586
|
-
parts.push(`${data.count} entr${(data.count as number) !== 1 ? "ies" : "y"}`);
|
|
587
|
-
break;
|
|
588
|
-
case "env":
|
|
589
|
-
if (data.action === "set") {
|
|
590
|
-
parts.push(`set ${data.key}=${truncateToWidth(String(data.value ?? ""), 30)}`);
|
|
591
|
-
} else if (data.action === "get") {
|
|
592
|
-
parts.push(`${data.key}=${truncateToWidth(String(data.value ?? ""), 30)}`);
|
|
593
|
-
} else {
|
|
594
|
-
parts.push(`${data.count} variable${(data.count as number) !== 1 ? "s" : ""}`);
|
|
595
|
-
}
|
|
596
|
-
break;
|
|
597
|
-
case "stat":
|
|
598
|
-
if (data.is_dir) {
|
|
599
|
-
parts.push("directory");
|
|
600
|
-
} else {
|
|
601
|
-
parts.push(`${data.size} bytes`);
|
|
602
|
-
}
|
|
603
|
-
if (data.path) parts.push(shortenPath(String(data.path)));
|
|
604
|
-
break;
|
|
605
|
-
case "replace":
|
|
606
|
-
case "sed":
|
|
607
|
-
parts.push(`${data.count} replacement${(data.count as number) !== 1 ? "s" : ""}`);
|
|
608
|
-
if (data.path) parts.push(`in ${shortenPath(String(data.path))}`);
|
|
609
|
-
break;
|
|
610
|
-
case "rsed":
|
|
611
|
-
parts.push(`${data.count} replacement${(data.count as number) !== 1 ? "s" : ""}`);
|
|
612
|
-
if (data.files) parts.push(`in ${data.files} file${(data.files as number) !== 1 ? "s" : ""}`);
|
|
613
|
-
break;
|
|
614
|
-
case "git_status":
|
|
615
|
-
if (data.clean) {
|
|
616
|
-
parts.push("clean");
|
|
617
|
-
} else {
|
|
618
|
-
const statusParts: string[] = [];
|
|
619
|
-
if (data.staged) statusParts.push(`${data.staged} staged`);
|
|
620
|
-
if (data.modified) statusParts.push(`${data.modified} modified`);
|
|
621
|
-
if (data.untracked) statusParts.push(`${data.untracked} untracked`);
|
|
622
|
-
parts.push(statusParts.join(", ") || "unknown");
|
|
623
|
-
}
|
|
624
|
-
if (data.branch) parts.push(`on ${data.branch}`);
|
|
625
|
-
break;
|
|
626
|
-
case "git_log":
|
|
627
|
-
parts.push(`${data.commits} commit${(data.commits as number) !== 1 ? "s" : ""}`);
|
|
628
|
-
break;
|
|
629
|
-
case "git_diff":
|
|
630
|
-
parts.push(`${data.lines} line${(data.lines as number) !== 1 ? "s" : ""}`);
|
|
631
|
-
if (data.staged) parts.push("(staged)");
|
|
632
|
-
break;
|
|
633
|
-
case "diff":
|
|
634
|
-
if (data.identical) {
|
|
635
|
-
parts.push("files identical");
|
|
636
|
-
} else {
|
|
637
|
-
parts.push("files differ");
|
|
638
|
-
}
|
|
639
|
-
break;
|
|
640
|
-
case "batch":
|
|
641
|
-
parts.push(`${data.files} file${(data.files as number) !== 1 ? "s" : ""} processed`);
|
|
642
|
-
break;
|
|
643
|
-
case "wc":
|
|
644
|
-
parts.push(`${data.lines}L ${data.words}W ${data.chars}C`);
|
|
645
|
-
break;
|
|
646
|
-
case "lines":
|
|
647
|
-
parts.push(`${data.count} line${(data.count as number) !== 1 ? "s" : ""}`);
|
|
648
|
-
if (data.start && data.end) parts.push(`(${data.start}-${data.end})`);
|
|
649
|
-
break;
|
|
650
|
-
case "delete_lines":
|
|
651
|
-
case "delete_matching":
|
|
652
|
-
parts.push(`${data.count} line${(data.count as number) !== 1 ? "s" : ""} deleted`);
|
|
653
|
-
break;
|
|
654
|
-
case "insert_at":
|
|
655
|
-
parts.push(`${data.lines_inserted} line${(data.lines_inserted as number) !== 1 ? "s" : ""} inserted`);
|
|
656
|
-
break;
|
|
657
|
-
case "cd":
|
|
658
|
-
case "pwd":
|
|
659
|
-
case "mkdir":
|
|
660
|
-
case "touch":
|
|
661
|
-
if (data.path) parts.push(shortenPath(String(data.path)));
|
|
662
|
-
break;
|
|
663
|
-
case "rm":
|
|
664
|
-
case "mv":
|
|
665
|
-
case "cp":
|
|
666
|
-
if (data.src) parts.push(`${shortenPath(String(data.src))} → ${shortenPath(String(data.dst))}`);
|
|
667
|
-
else if (data.path) parts.push(shortenPath(String(data.path)));
|
|
668
|
-
break;
|
|
669
|
-
default:
|
|
670
|
-
// Generic formatting for other operations
|
|
671
|
-
if (data.count !== undefined) {
|
|
672
|
-
parts.push(String(data.count));
|
|
673
|
-
}
|
|
674
|
-
if (data.path) {
|
|
675
|
-
parts.push(shortenPath(String(data.path)));
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
const desc = parts.length > 0 ? parts.join(" · ") : "";
|
|
680
|
-
return `${icon} ${theme.fg("muted", op)}${desc ? ` ${theme.fg("dim", desc)}` : ""}`;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
/** Format status event with expanded detail lines. */
|
|
684
|
-
function formatStatusEventExpanded(event: PythonStatusEvent, theme: Theme): string[] {
|
|
685
|
-
const lines: string[] = [];
|
|
686
|
-
const { op, ...data } = event;
|
|
687
|
-
|
|
688
|
-
// Main status line
|
|
689
|
-
lines.push(formatStatusEvent(event, theme));
|
|
690
|
-
|
|
691
|
-
// Add detail lines for operations with list data
|
|
692
|
-
const addItems = (items: unknown[], formatter: (item: unknown) => string, max = 5) => {
|
|
693
|
-
const arr = Array.isArray(items) ? items : [];
|
|
694
|
-
for (let i = 0; i < Math.min(arr.length, max); i++) {
|
|
695
|
-
lines.push(` ${theme.fg("dim", formatter(arr[i]))}`);
|
|
696
|
-
}
|
|
697
|
-
if (arr.length > max) {
|
|
698
|
-
lines.push(` ${theme.fg("dim", `… ${arr.length - max} more`)}`);
|
|
699
|
-
}
|
|
700
|
-
};
|
|
701
|
-
|
|
702
|
-
// Add preview lines (truncated content)
|
|
703
|
-
const addPreview = (preview: string, maxLines = 3) => {
|
|
704
|
-
const previewLines = String(preview).split("\n").slice(0, maxLines);
|
|
705
|
-
for (const line of previewLines) {
|
|
706
|
-
lines.push(` ${theme.fg("toolOutput", truncateToWidth(replaceTabs(line), 80))}`);
|
|
707
|
-
}
|
|
708
|
-
const totalLines = String(preview).split("\n").length;
|
|
709
|
-
if (totalLines > maxLines) {
|
|
710
|
-
lines.push(` ${theme.fg("dim", `… ${totalLines - maxLines} more lines`)}`);
|
|
711
|
-
}
|
|
712
|
-
};
|
|
713
|
-
|
|
714
|
-
switch (op) {
|
|
715
|
-
case "find":
|
|
716
|
-
case "glob":
|
|
717
|
-
if (data.matches) addItems(data.matches as unknown[], m => String(m));
|
|
718
|
-
break;
|
|
719
|
-
case "ls":
|
|
720
|
-
if (data.items) addItems(data.items as unknown[], m => String(m));
|
|
721
|
-
break;
|
|
722
|
-
case "grep":
|
|
723
|
-
if (data.hits) {
|
|
724
|
-
addItems(data.hits as unknown[], h => {
|
|
725
|
-
const hit = h as { line: number; text: string };
|
|
726
|
-
return `${hit.line}: ${truncateToWidth(hit.text, 60)}`;
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
break;
|
|
730
|
-
case "rgrep":
|
|
731
|
-
if (data.hits) {
|
|
732
|
-
addItems(data.hits as unknown[], h => {
|
|
733
|
-
const hit = h as { file: string; line: number; text: string };
|
|
734
|
-
return `${shortenPath(hit.file)}:${hit.line}: ${truncateToWidth(hit.text, 50)}`;
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
break;
|
|
738
|
-
case "rsed":
|
|
739
|
-
if (data.changed) {
|
|
740
|
-
addItems(data.changed as unknown[], c => {
|
|
741
|
-
const change = c as { file: string; count: number };
|
|
742
|
-
return `${shortenPath(change.file)}: ${change.count} replacement${change.count !== 1 ? "s" : ""}`;
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
break;
|
|
746
|
-
case "env":
|
|
747
|
-
if (data.keys) addItems(data.keys as unknown[], k => String(k), 10);
|
|
748
|
-
break;
|
|
749
|
-
case "git_log":
|
|
750
|
-
if (data.entries) {
|
|
751
|
-
addItems(data.entries as unknown[], e => {
|
|
752
|
-
const entry = e as { sha: string; subject: string };
|
|
753
|
-
return `${entry.sha} ${truncateToWidth(entry.subject, 50)}`;
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
break;
|
|
757
|
-
case "git_status":
|
|
758
|
-
if (data.files) addItems(data.files as unknown[], f => String(f));
|
|
759
|
-
break;
|
|
760
|
-
case "git_branch":
|
|
761
|
-
if (data.branches) addItems(data.branches as unknown[], b => String(b));
|
|
762
|
-
break;
|
|
763
|
-
case "read":
|
|
764
|
-
case "cat":
|
|
765
|
-
case "head":
|
|
766
|
-
case "tail":
|
|
767
|
-
case "tree":
|
|
768
|
-
case "diff":
|
|
769
|
-
case "lines":
|
|
770
|
-
case "git_diff":
|
|
771
|
-
case "sh":
|
|
772
|
-
if (data.preview) addPreview(String(data.preview));
|
|
773
|
-
break;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
return lines;
|
|
777
|
-
}
|
|
778
408
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (expanded) {
|
|
793
|
-
// Show expanded details for each event
|
|
794
|
-
const eventLines = formatStatusEventExpanded(events[i], theme);
|
|
795
|
-
lines.push(`${theme.fg("dim", branch)} ${eventLines[0]}`);
|
|
796
|
-
const continueBranch = isLast ? " " : `${theme.tree.vertical} `;
|
|
797
|
-
for (let j = 1; j < eventLines.length; j++) {
|
|
798
|
-
lines.push(`${theme.fg("dim", continueBranch)}${eventLines[j]}`);
|
|
409
|
+
buildRenderContext(info: {
|
|
410
|
+
args: PythonToolParams;
|
|
411
|
+
result?: { content: Array<{ type: string; text?: string }>; details?: PythonToolDetails };
|
|
412
|
+
expanded: boolean;
|
|
413
|
+
getTextOutput: () => string;
|
|
414
|
+
}): Record<string, unknown> {
|
|
415
|
+
const context: Record<string, unknown> = {};
|
|
416
|
+
if (info.result) {
|
|
417
|
+
context.output = info.getTextOutput().trimEnd();
|
|
418
|
+
context.expanded = info.expanded;
|
|
419
|
+
context.previewLines = PYTHON_DEFAULT_PREVIEW_LINES;
|
|
420
|
+
if (typeof info.args.timeout === "number" && Number.isFinite(info.args.timeout)) {
|
|
421
|
+
context.timeout = Math.max(1, Math.min(600, info.args.timeout));
|
|
799
422
|
}
|
|
800
|
-
} else {
|
|
801
|
-
lines.push(`${theme.fg("dim", branch)} ${formatStatusEvent(events[i], theme)}`);
|
|
802
423
|
}
|
|
424
|
+
return context;
|
|
803
425
|
}
|
|
804
426
|
|
|
805
|
-
if (!expanded && events.length > maxCollapsed) {
|
|
806
|
-
lines.push(`${theme.fg("dim", theme.tree.last)} ${theme.fg("dim", `… ${events.length - maxCollapsed} more`)}`);
|
|
807
|
-
} else if (expanded && events.length > maxExpanded) {
|
|
808
|
-
lines.push(`${theme.fg("dim", theme.tree.last)} ${theme.fg("dim", `… ${events.length - maxExpanded} more`)}`);
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
return lines;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
function formatCellOutputLines(
|
|
815
|
-
cell: PythonCellResult,
|
|
816
|
-
expanded: boolean,
|
|
817
|
-
previewLines: number,
|
|
818
|
-
theme: Theme,
|
|
819
|
-
): { lines: string[]; hiddenCount: number } {
|
|
820
|
-
const rawLines = cell.output ? cell.output.split("\n") : [];
|
|
821
|
-
const displayLines = expanded ? rawLines : rawLines.slice(-previewLines);
|
|
822
|
-
const hiddenCount = rawLines.length - displayLines.length;
|
|
823
|
-
const outputLines = displayLines.map(line => {
|
|
824
|
-
const cleaned = replaceTabs(line);
|
|
825
|
-
return cell.status === "error" ? theme.fg("error", cleaned) : theme.fg("toolOutput", cleaned);
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
if (outputLines.length === 0) {
|
|
829
|
-
return { lines: [], hiddenCount: 0 };
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
return { lines: outputLines, hiddenCount };
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
export const pythonToolRenderer = {
|
|
836
427
|
renderCall(args: PythonRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
837
|
-
const ui = new ToolUIKit(uiTheme);
|
|
838
428
|
const cells = args.cells ?? [];
|
|
839
429
|
const cwd = getProjectDir();
|
|
840
430
|
let displayWorkdir = args.cwd;
|
|
@@ -858,7 +448,7 @@ export const pythonToolRenderer = {
|
|
|
858
448
|
if (cells.length === 0) {
|
|
859
449
|
const prompt = uiTheme.fg("accent", ">>>");
|
|
860
450
|
const prefix = workdirLabel ? `${uiTheme.fg("dim", `${workdirLabel} && `)}` : "";
|
|
861
|
-
const text =
|
|
451
|
+
const text = uiTheme.fg("toolTitle", uiTheme.bold(`${prompt} ${prefix}…`));
|
|
862
452
|
return new Text(text, 0, 0);
|
|
863
453
|
}
|
|
864
454
|
|
|
@@ -903,216 +493,75 @@ export const pythonToolRenderer = {
|
|
|
903
493
|
cached = undefined;
|
|
904
494
|
},
|
|
905
495
|
};
|
|
906
|
-
}
|
|
496
|
+
}
|
|
907
497
|
|
|
908
498
|
renderResult(
|
|
909
499
|
result: { content: Array<{ type: string; text?: string }>; details?: PythonToolDetails },
|
|
910
500
|
options: RenderResultOptions & { renderContext?: PythonRenderContext },
|
|
911
501
|
uiTheme: Theme,
|
|
912
502
|
): Component {
|
|
913
|
-
const ui = new ToolUIKit(uiTheme);
|
|
914
503
|
const details = result.details;
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
return [header, ...treeLines];
|
|
924
|
-
});
|
|
925
|
-
|
|
504
|
+
const output = (
|
|
505
|
+
options.renderContext?.output ??
|
|
506
|
+
result.content?.find(c => c.type === "text")?.text ??
|
|
507
|
+
""
|
|
508
|
+
).trimEnd();
|
|
509
|
+
const outputLines = output ? output.split("\n") : [];
|
|
510
|
+
const total = outputLines.length;
|
|
511
|
+
const isError = details?.cells?.some(c => c.status === "error") ?? false;
|
|
926
512
|
const truncation = details?.meta?.truncation;
|
|
927
|
-
const
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
if (
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
}
|
|
938
|
-
if (truncation.truncatedBy === "lines") {
|
|
939
|
-
warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
|
|
940
|
-
} else {
|
|
941
|
-
warnings.push(
|
|
942
|
-
`Truncated: ${truncation.outputLines} lines shown (${ui.formatBytes(truncation.outputBytes)} limit)`,
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
if (warnings.length > 0) {
|
|
946
|
-
warningLine = uiTheme.fg("warning", ui.wrapBrackets(warnings.join(". ")));
|
|
947
|
-
}
|
|
513
|
+
const expanded = options.renderContext?.expanded ?? options.expanded;
|
|
514
|
+
|
|
515
|
+
// Build header
|
|
516
|
+
const cellCount = details?.cells?.length ?? 0;
|
|
517
|
+
const codePreview = details?.cells?.[0]?.code?.split("\n")[0]?.slice(0, 60) ?? "\u2026";
|
|
518
|
+
const meta: string[] = [];
|
|
519
|
+
if (cellCount > 1) meta.push(`${cellCount} cells`);
|
|
520
|
+
if (total > 0) meta.push(`${total} lines`);
|
|
521
|
+
if (details?.cells?.some(c => c.durationMs)) {
|
|
522
|
+
const totalMs = details.cells.reduce((sum, c) => sum + (c.durationMs ?? 0), 0);
|
|
523
|
+
if (totalMs > 0) meta.push(`${(totalMs / 1000).toFixed(1)}s`);
|
|
948
524
|
}
|
|
949
525
|
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
// Cache state following Box pattern
|
|
953
|
-
let cached: { key: string; width: number; result: string[] } | undefined;
|
|
954
|
-
|
|
955
|
-
return {
|
|
956
|
-
render: (width: number): string[] => {
|
|
957
|
-
// Read mutable state at render time
|
|
958
|
-
const expanded = options.renderContext?.expanded ?? options.expanded;
|
|
959
|
-
const previewLines = options.renderContext?.previewLines ?? PYTHON_DEFAULT_PREVIEW_LINES;
|
|
960
|
-
const key = `${expanded}|${previewLines}|${options.spinnerFrame}`;
|
|
961
|
-
if (cached && cached.key === key && cached.width === width) {
|
|
962
|
-
return cached.result;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
const lines: string[] = [];
|
|
966
|
-
for (let i = 0; i < cellResults.length; i++) {
|
|
967
|
-
const cell = cellResults[i];
|
|
968
|
-
const statusLines = renderStatusEvents(cell.statusEvents ?? [], uiTheme, expanded);
|
|
969
|
-
const outputContent = formatCellOutputLines(cell, expanded, previewLines, uiTheme);
|
|
970
|
-
const outputLines = [...outputContent.lines];
|
|
971
|
-
if (!expanded && outputContent.hiddenCount > 0) {
|
|
972
|
-
outputLines.push(
|
|
973
|
-
uiTheme.fg("dim", `… ${outputContent.hiddenCount} more lines (ctrl+o to expand)`),
|
|
974
|
-
);
|
|
975
|
-
}
|
|
976
|
-
if (statusLines.length > 0) {
|
|
977
|
-
if (outputLines.length > 0) {
|
|
978
|
-
outputLines.push(uiTheme.fg("dim", "Status"));
|
|
979
|
-
}
|
|
980
|
-
outputLines.push(...statusLines);
|
|
981
|
-
}
|
|
982
|
-
const cellLines = renderCodeCell(
|
|
983
|
-
{
|
|
984
|
-
code: cell.code,
|
|
985
|
-
language: "python",
|
|
986
|
-
index: i,
|
|
987
|
-
total: cellResults.length,
|
|
988
|
-
title: cell.title,
|
|
989
|
-
status: cell.status,
|
|
990
|
-
spinnerFrame: options.spinnerFrame,
|
|
991
|
-
duration: cell.durationMs,
|
|
992
|
-
output: outputLines.length > 0 ? outputLines.join("\n") : undefined,
|
|
993
|
-
outputMaxLines: outputLines.length,
|
|
994
|
-
codeMaxLines: expanded ? Number.POSITIVE_INFINITY : PYTHON_DEFAULT_PREVIEW_LINES,
|
|
995
|
-
expanded,
|
|
996
|
-
width,
|
|
997
|
-
},
|
|
998
|
-
uiTheme,
|
|
999
|
-
);
|
|
1000
|
-
lines.push(...cellLines);
|
|
1001
|
-
if (i < cellResults.length - 1) {
|
|
1002
|
-
lines.push("");
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
if (jsonLines.length > 0) {
|
|
1006
|
-
if (lines.length > 0) {
|
|
1007
|
-
lines.push("");
|
|
1008
|
-
}
|
|
1009
|
-
lines.push(...jsonLines);
|
|
1010
|
-
}
|
|
1011
|
-
if (timeoutLine) {
|
|
1012
|
-
lines.push(timeoutLine);
|
|
1013
|
-
}
|
|
1014
|
-
if (warningLine) {
|
|
1015
|
-
lines.push(warningLine);
|
|
1016
|
-
}
|
|
1017
|
-
cached = { key, width, result: lines };
|
|
1018
|
-
return lines;
|
|
1019
|
-
},
|
|
1020
|
-
invalidate: () => {
|
|
1021
|
-
cached = undefined;
|
|
1022
|
-
},
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
const displayOutput = output;
|
|
1027
|
-
const combinedOutput = [displayOutput, ...jsonLines].filter(Boolean).join("\n");
|
|
1028
|
-
|
|
1029
|
-
const statusEvents = details?.statusEvents ?? [];
|
|
1030
|
-
const statusLines = renderStatusEvents(
|
|
1031
|
-
statusEvents,
|
|
526
|
+
const header = renderStatusLine(
|
|
527
|
+
{ icon: isError ? "error" : "success", title: "Python", description: `>>> ${codePreview}`, meta },
|
|
1032
528
|
uiTheme,
|
|
1033
|
-
options.renderContext?.expanded ?? options.expanded,
|
|
1034
529
|
);
|
|
1035
530
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
531
|
+
// Tree-style body
|
|
532
|
+
const showAll = isError || expanded;
|
|
533
|
+
const displayLines = showAll ? outputLines : outputLines.slice(-PREVIEW_LIMITS.OUTPUT_COLLAPSED);
|
|
534
|
+
const skipped = total - displayLines.length;
|
|
1040
535
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
) as string[];
|
|
1045
|
-
return new Text(lines.join("\n"), 0, 0);
|
|
536
|
+
const bodyLines: string[] = [];
|
|
537
|
+
if (skipped > 0) {
|
|
538
|
+
bodyLines.push(uiTheme.fg("dim", `\u2026 (${skipped} earlier lines)`));
|
|
1046
539
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
timeoutLine,
|
|
1057
|
-
warningLine,
|
|
1058
|
-
].filter(Boolean) as string[];
|
|
1059
|
-
return new Text(lines.join("\n"), 0, 0);
|
|
540
|
+
const hasTruncation = Boolean(truncation);
|
|
541
|
+
for (let i = 0; i < displayLines.length; i++) {
|
|
542
|
+
bodyLines.push(uiTheme.fg("toolOutput", replaceTabs(displayLines[i])));
|
|
543
|
+
}
|
|
544
|
+
if (hasTruncation) {
|
|
545
|
+
bodyLines.push(uiTheme.fg("warning", "output truncated"));
|
|
546
|
+
}
|
|
547
|
+
if (!showAll && skipped > 0) {
|
|
548
|
+
bodyLines.push(formatClickHint(uiTheme));
|
|
1060
549
|
}
|
|
1061
550
|
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
const textContent = `\n${styledOutput}`;
|
|
551
|
+
const lines = bodyLines.length > 0 ? [header, ...bodyLines] : [header];
|
|
552
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
1067
555
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
556
|
+
interface PythonRenderArgs {
|
|
557
|
+
cells?: Array<{ code: string; title?: string }>;
|
|
558
|
+
timeout?: number;
|
|
559
|
+
cwd?: string;
|
|
560
|
+
}
|
|
1072
561
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
cachedLines = result.visualLines;
|
|
1080
|
-
cachedSkipped = result.skippedCount;
|
|
1081
|
-
cachedWidth = width;
|
|
1082
|
-
cachedPreviewLines = previewLines;
|
|
1083
|
-
}
|
|
1084
|
-
const outputLines: string[] = [];
|
|
1085
|
-
if (cachedSkipped && cachedSkipped > 0) {
|
|
1086
|
-
outputLines.push("");
|
|
1087
|
-
const skippedLine = uiTheme.fg(
|
|
1088
|
-
"dim",
|
|
1089
|
-
`… (${cachedSkipped} earlier lines, showing ${cachedLines.length} of ${cachedSkipped + cachedLines.length}) (ctrl+o to expand)`,
|
|
1090
|
-
);
|
|
1091
|
-
outputLines.push(truncateToWidth(skippedLine, width));
|
|
1092
|
-
}
|
|
1093
|
-
outputLines.push(...cachedLines);
|
|
1094
|
-
if (statusLines.length > 0) {
|
|
1095
|
-
outputLines.push(truncateToWidth(uiTheme.fg("dim", "Status"), width));
|
|
1096
|
-
for (const statusLine of statusLines) {
|
|
1097
|
-
outputLines.push(truncateToWidth(statusLine, width));
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
if (timeoutLine) {
|
|
1101
|
-
outputLines.push(truncateToWidth(timeoutLine, width));
|
|
1102
|
-
}
|
|
1103
|
-
if (warningLine) {
|
|
1104
|
-
outputLines.push(truncateToWidth(warningLine, width));
|
|
1105
|
-
}
|
|
1106
|
-
return outputLines;
|
|
1107
|
-
},
|
|
1108
|
-
invalidate: () => {
|
|
1109
|
-
cachedWidth = undefined;
|
|
1110
|
-
cachedLines = undefined;
|
|
1111
|
-
cachedSkipped = undefined;
|
|
1112
|
-
cachedPreviewLines = undefined;
|
|
1113
|
-
},
|
|
1114
|
-
};
|
|
1115
|
-
},
|
|
1116
|
-
mergeCallAndResult: true,
|
|
1117
|
-
inline: true,
|
|
1118
|
-
};
|
|
562
|
+
interface PythonRenderContext {
|
|
563
|
+
output?: string;
|
|
564
|
+
expanded?: boolean;
|
|
565
|
+
previewLines?: number;
|
|
566
|
+
timeout?: number;
|
|
567
|
+
}
|