@aexol/spectral 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/kanban-bridge.js +668 -0
- package/dist/extensions/spectral-vision-fallback.js +3 -2
- package/dist/mcp/init.js +1 -9
- package/dist/memory/index.js +2 -0
- package/dist/memory/tools/write-project-observation.js +60 -0
- package/dist/relay/auto-research.js +34 -0
- package/dist/sdk/ai/env-api-keys.js +9 -49
- package/dist/sdk/ai/utils/oauth/anthropic.js +1 -1
- package/dist/sdk/ai/utils/oauth/openai-codex.js +1 -1
- package/dist/sdk/coding-agent/config.js +2 -69
- package/dist/sdk/coding-agent/core/extensions/loader.js +2 -35
- package/dist/sdk/coding-agent/core/extensions/runner.js +1 -2
- package/dist/sdk/coding-agent/core/model-resolver-utils.js +8 -0
- package/dist/sdk/coding-agent/core/model-resolver.js +1 -1
- package/dist/sdk/coding-agent/core/resource-loader.js +1 -1
- package/dist/sdk/coding-agent/core/settings-manager.js +1 -170
- package/dist/sdk/coding-agent/core/system-prompt.js +3 -1
- package/dist/sdk/coding-agent/core/theme.js +202 -0
- package/dist/sdk/coding-agent/core/tools/bash.js +17 -18
- package/dist/sdk/coding-agent/core/tools/edit.js +7 -8
- package/dist/sdk/coding-agent/core/tools/find.js +9 -13
- package/dist/sdk/coding-agent/core/tools/grep.js +10 -14
- package/dist/sdk/coding-agent/core/tools/ls.js +9 -10
- package/dist/sdk/coding-agent/core/tools/read.js +15 -25
- package/dist/sdk/coding-agent/{modes/interactive/components/diff.js → core/tools/render-diff.js} +18 -31
- package/dist/sdk/coding-agent/core/tools/write.js +10 -11
- package/dist/sdk/coding-agent/index.js +7 -5
- package/dist/sdk/coding-agent/modes/index.js +0 -1
- package/dist/sdk/coding-agent/modes/rpc/rpc-mode.js +2 -2
- package/dist/sdk/coding-agent/utils/photon.js +2 -10
- package/dist/sdk/coding-agent/utils/pi-user-agent.js +1 -2
- package/dist/server/agent-bridge.js +2 -1
- package/package.json +1 -1
- package/dist/sdk/coding-agent/bun/cli.js +0 -7
- package/dist/sdk/coding-agent/bun/restore-sandbox-env.js +0 -31
- package/dist/sdk/coding-agent/cli/args.js +0 -340
- package/dist/sdk/coding-agent/cli/file-processor.js +0 -82
- package/dist/sdk/coding-agent/cli/initial-message.js +0 -21
- package/dist/sdk/coding-agent/core/footer-data-provider.js +0 -309
- package/dist/sdk/coding-agent/modes/interactive/components/keybinding-hints.js +0 -35
- package/dist/sdk/coding-agent/modes/interactive/components/visual-truncate.js +0 -26
- package/dist/sdk/coding-agent/modes/interactive/interactive-mode.js +0 -3
- package/dist/sdk/coding-agent/modes/interactive/theme/theme.js +0 -1022
|
@@ -3,10 +3,9 @@ import { spawn } from "child_process";
|
|
|
3
3
|
import { readFileSync, statSync } from "fs";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { Type } from "typebox";
|
|
6
|
-
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
7
6
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
8
7
|
import { resolveToCwd } from "./path-utils.js";
|
|
9
|
-
import { getTextOutput,
|
|
8
|
+
import { getTextOutput, shortenPath, str } from "./render-utils.js";
|
|
10
9
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
11
10
|
import { DEFAULT_MAX_BYTES, formatSize, GREP_MAX_LINE_LENGTH, truncateHead, truncateLine, } from "./truncate.js";
|
|
12
11
|
const grepSchema = Type.Object({
|
|
@@ -23,24 +22,21 @@ const defaultGrepOperations = {
|
|
|
23
22
|
isDirectory: (p) => statSync(p).isDirectory(),
|
|
24
23
|
readFile: (p) => readFileSync(p, "utf-8"),
|
|
25
24
|
};
|
|
26
|
-
function formatGrepCall(args,
|
|
25
|
+
function formatGrepCall(args, _theme) {
|
|
27
26
|
const pattern = str(args?.pattern);
|
|
28
27
|
const rawPath = str(args?.path);
|
|
29
28
|
const path = rawPath !== null ? shortenPath(rawPath || ".") : null;
|
|
30
29
|
const glob = str(args?.glob);
|
|
31
30
|
const limit = args?.limit;
|
|
32
|
-
const invalidArg =
|
|
33
|
-
let text =
|
|
34
|
-
" " +
|
|
35
|
-
(pattern === null ? invalidArg : theme.fg("accent", `/${pattern || ""}/`)) +
|
|
36
|
-
theme.fg("toolOutput", ` in ${path === null ? invalidArg : path}`);
|
|
31
|
+
const invalidArg = "[invalid]";
|
|
32
|
+
let text = `grep ${pattern === null ? invalidArg : `/${pattern || ""}/`} in ${path === null ? invalidArg : path}`;
|
|
37
33
|
if (glob)
|
|
38
|
-
text +=
|
|
34
|
+
text += ` (${glob})`;
|
|
39
35
|
if (limit !== undefined)
|
|
40
|
-
text +=
|
|
36
|
+
text += ` limit ${limit}`;
|
|
41
37
|
return text;
|
|
42
38
|
}
|
|
43
|
-
function formatGrepResult(result, options,
|
|
39
|
+
function formatGrepResult(result, options, _theme, showImages) {
|
|
44
40
|
const output = getTextOutput(result, showImages).trim();
|
|
45
41
|
let text = "";
|
|
46
42
|
if (output) {
|
|
@@ -48,9 +44,9 @@ function formatGrepResult(result, options, theme, showImages) {
|
|
|
48
44
|
const maxLines = options.expanded ? lines.length : 15;
|
|
49
45
|
const displayLines = lines.slice(0, maxLines);
|
|
50
46
|
const remaining = lines.length - maxLines;
|
|
51
|
-
text += `\n${displayLines.
|
|
47
|
+
text += `\n${displayLines.join("\n")}`;
|
|
52
48
|
if (remaining > 0) {
|
|
53
|
-
text +=
|
|
49
|
+
text += `\n... (${remaining} more lines)`;
|
|
54
50
|
}
|
|
55
51
|
}
|
|
56
52
|
const matchLimit = result.details?.matchLimitReached;
|
|
@@ -64,7 +60,7 @@ function formatGrepResult(result, options, theme, showImages) {
|
|
|
64
60
|
warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
|
|
65
61
|
if (linesTruncated)
|
|
66
62
|
warnings.push("some lines truncated");
|
|
67
|
-
text += `\n
|
|
63
|
+
text += `\n[Truncated: ${warnings.join(", ")}]`;
|
|
68
64
|
}
|
|
69
65
|
return text;
|
|
70
66
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { existsSync, readdirSync, statSync } from "fs";
|
|
2
2
|
import nodePath from "path";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
|
-
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
|
|
5
4
|
import { resolveToCwd } from "./path-utils.js";
|
|
6
|
-
import { getTextOutput,
|
|
5
|
+
import { getTextOutput, shortenPath, str } from "./render-utils.js";
|
|
7
6
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
8
7
|
import { DEFAULT_MAX_BYTES, formatSize, truncateHead } from "./truncate.js";
|
|
9
8
|
const lsSchema = Type.Object({
|
|
@@ -16,18 +15,18 @@ const defaultLsOperations = {
|
|
|
16
15
|
stat: statSync,
|
|
17
16
|
readdir: readdirSync,
|
|
18
17
|
};
|
|
19
|
-
function formatLsCall(args,
|
|
18
|
+
function formatLsCall(args, _theme) {
|
|
20
19
|
const rawPath = str(args?.path);
|
|
21
20
|
const path = rawPath !== null ? shortenPath(rawPath || ".") : null;
|
|
22
21
|
const limit = args?.limit;
|
|
23
|
-
const invalidArg =
|
|
24
|
-
let text =
|
|
22
|
+
const invalidArg = "[invalid]";
|
|
23
|
+
let text = `ls ${path === null ? invalidArg : path}`;
|
|
25
24
|
if (limit !== undefined) {
|
|
26
|
-
text +=
|
|
25
|
+
text += ` (limit ${limit})`;
|
|
27
26
|
}
|
|
28
27
|
return text;
|
|
29
28
|
}
|
|
30
|
-
function formatLsResult(result, options,
|
|
29
|
+
function formatLsResult(result, options, _theme, showImages) {
|
|
31
30
|
const output = getTextOutput(result, showImages).trim();
|
|
32
31
|
let text = "";
|
|
33
32
|
if (output) {
|
|
@@ -35,9 +34,9 @@ function formatLsResult(result, options, theme, showImages) {
|
|
|
35
34
|
const maxLines = options.expanded ? lines.length : 20;
|
|
36
35
|
const displayLines = lines.slice(0, maxLines);
|
|
37
36
|
const remaining = lines.length - maxLines;
|
|
38
|
-
text += `\n${displayLines.
|
|
37
|
+
text += `\n${displayLines.join("\n")}`;
|
|
39
38
|
if (remaining > 0) {
|
|
40
|
-
text +=
|
|
39
|
+
text += `\n... (${remaining} more lines)`;
|
|
41
40
|
}
|
|
42
41
|
}
|
|
43
42
|
const entryLimit = result.details?.entryLimitReached;
|
|
@@ -48,7 +47,7 @@ function formatLsResult(result, options, theme, showImages) {
|
|
|
48
47
|
warnings.push(`${entryLimit} entries limit`);
|
|
49
48
|
if (truncation?.truncated)
|
|
50
49
|
warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
|
|
51
|
-
text += `\n
|
|
50
|
+
text += `\n[Truncated: ${warnings.join(", ")}]`;
|
|
52
51
|
}
|
|
53
52
|
return text;
|
|
54
53
|
}
|
|
@@ -3,13 +3,12 @@ import { constants } from "fs";
|
|
|
3
3
|
import { access as fsAccess, readFile as fsReadFile } from "fs/promises";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { getReadmePath } from "../../config.js";
|
|
6
|
-
import {
|
|
7
|
-
import { getLanguageFromPath, highlightCode } from "../../modes/interactive/theme/theme.js";
|
|
6
|
+
import { getLanguageFromPath, highlightCode } from "../theme.js";
|
|
8
7
|
import { formatDimensionNote, resizeImage } from "../../utils/image-resize.js";
|
|
9
8
|
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js";
|
|
10
9
|
import { formatPathRelativeToCwdOrAbsolute } from "../../utils/paths.js";
|
|
11
10
|
import { resolveReadPath } from "./path-utils.js";
|
|
12
|
-
import { getTextOutput,
|
|
11
|
+
import { getTextOutput, replaceTabs, shortenPath, str } from "./render-utils.js";
|
|
13
12
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
14
13
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead } from "./truncate.js";
|
|
15
14
|
const readSchema = Type.Object({
|
|
@@ -23,19 +22,18 @@ const defaultReadOperations = {
|
|
|
23
22
|
access: (path) => fsAccess(path, constants.R_OK),
|
|
24
23
|
detectImageMimeType: detectSupportedImageMimeTypeFromFile,
|
|
25
24
|
};
|
|
26
|
-
function formatReadLineRange(args,
|
|
25
|
+
function formatReadLineRange(args, _theme) {
|
|
27
26
|
if (args?.offset === undefined && args?.limit === undefined)
|
|
28
27
|
return "";
|
|
29
28
|
const startLine = args.offset ?? 1;
|
|
30
29
|
const endLine = args.limit !== undefined ? startLine + args.limit - 1 : "";
|
|
31
|
-
return
|
|
30
|
+
return `:${startLine}${endLine ? `-${endLine}` : ""}`;
|
|
32
31
|
}
|
|
33
|
-
function formatReadCall(args,
|
|
32
|
+
function formatReadCall(args, _theme) {
|
|
34
33
|
const rawPath = str(args?.file_path ?? args?.path);
|
|
35
34
|
const path = rawPath !== null ? shortenPath(rawPath) : null;
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
return `${theme.fg("toolTitle", theme.bold("read"))} ${pathDisplay}${formatReadLineRange(args, theme)}`;
|
|
35
|
+
const pathDisplay = path === null ? "[invalid]" : path || "...";
|
|
36
|
+
return `read ${pathDisplay}${formatReadLineRange(args)}`;
|
|
39
37
|
}
|
|
40
38
|
function trimTrailingEmptyLines(lines) {
|
|
41
39
|
let end = lines.length;
|
|
@@ -85,19 +83,11 @@ function getCompactReadClassification(args, cwd) {
|
|
|
85
83
|
}
|
|
86
84
|
return undefined;
|
|
87
85
|
}
|
|
88
|
-
function formatCompactReadCall(classification, args,
|
|
89
|
-
const expandHint = theme.fg("dim", ` (${keyText("app.tools.expand")} to expand)`);
|
|
86
|
+
function formatCompactReadCall(classification, args, _theme) {
|
|
90
87
|
if (classification.kind === "skill") {
|
|
91
|
-
return
|
|
92
|
-
theme.fg("customMessageText", classification.label) +
|
|
93
|
-
formatReadLineRange(args, theme) +
|
|
94
|
-
expandHint);
|
|
88
|
+
return `[skill] ${classification.label}${formatReadLineRange(args)}`;
|
|
95
89
|
}
|
|
96
|
-
return
|
|
97
|
-
" " +
|
|
98
|
-
theme.fg("accent", classification.label) +
|
|
99
|
-
formatReadLineRange(args, theme) +
|
|
100
|
-
expandHint);
|
|
90
|
+
return `read ${classification.kind} ${classification.label}${formatReadLineRange(args)}`;
|
|
101
91
|
}
|
|
102
92
|
function formatReadResult(args, result, options, theme, showImages, cwd, isError) {
|
|
103
93
|
if (!options.expanded && !isError && getCompactReadClassification(args, cwd)) {
|
|
@@ -111,20 +101,20 @@ function formatReadResult(args, result, options, theme, showImages, cwd, isError
|
|
|
111
101
|
const maxLines = options.expanded ? lines.length : 10;
|
|
112
102
|
const displayLines = lines.slice(0, maxLines);
|
|
113
103
|
const remaining = lines.length - maxLines;
|
|
114
|
-
let text = `\n${displayLines.map((line) =>
|
|
104
|
+
let text = `\n${displayLines.map((line) => replaceTabs(line)).join("\n")}`;
|
|
115
105
|
if (remaining > 0) {
|
|
116
|
-
text +=
|
|
106
|
+
text += `\n... (${remaining} more lines)`;
|
|
117
107
|
}
|
|
118
108
|
const truncation = result.details?.truncation;
|
|
119
109
|
if (truncation?.truncated) {
|
|
120
110
|
if (truncation.firstLineExceedsLimit) {
|
|
121
|
-
text += `\n
|
|
111
|
+
text += `\n[First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`;
|
|
122
112
|
}
|
|
123
113
|
else if (truncation.truncatedBy === "lines") {
|
|
124
|
-
text += `\n
|
|
114
|
+
text += `\n[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)]`;
|
|
125
115
|
}
|
|
126
116
|
else {
|
|
127
|
-
text += `\n
|
|
117
|
+
text += `\n[Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)]`;
|
|
128
118
|
}
|
|
129
119
|
}
|
|
130
120
|
return text;
|
package/dist/sdk/coding-agent/{modes/interactive/components/diff.js → core/tools/render-diff.js}
RENAMED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain-text diff rendering for LLM consumption.
|
|
3
|
+
* Extracted from the TUI interactive mode diff renderer, with all ANSI
|
|
4
|
+
* styling removed — the output is plain text suitable for LLM input.
|
|
5
|
+
*/
|
|
1
6
|
import * as Diff from "diff";
|
|
2
|
-
import { theme } from "../theme/theme.js";
|
|
3
7
|
/**
|
|
4
8
|
* Parse diff line to extract prefix, line number, and content.
|
|
5
9
|
* Format: "+123 content" or "-123 content" or " 123 content" or " ..."
|
|
@@ -10,17 +14,9 @@ function parseDiffLine(line) {
|
|
|
10
14
|
return null;
|
|
11
15
|
return { prefix: match[1], lineNum: match[2], content: match[3] };
|
|
12
16
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Replace tabs with spaces for consistent rendering.
|
|
15
|
-
*/
|
|
16
17
|
function replaceTabs(text) {
|
|
17
18
|
return text.replace(/\t/g, " ");
|
|
18
19
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Compute word-level diff and render with inverse on changed parts.
|
|
21
|
-
* Uses diffWords which groups whitespace with adjacent words for cleaner highlighting.
|
|
22
|
-
* Strips leading whitespace from inverse to avoid highlighting indentation.
|
|
23
|
-
*/
|
|
24
20
|
function renderIntraLineDiff(oldContent, newContent) {
|
|
25
21
|
const wordDiff = Diff.diffWords(oldContent, newContent);
|
|
26
22
|
let removedLine = "";
|
|
@@ -30,7 +26,6 @@ function renderIntraLineDiff(oldContent, newContent) {
|
|
|
30
26
|
for (const part of wordDiff) {
|
|
31
27
|
if (part.removed) {
|
|
32
28
|
let value = part.value;
|
|
33
|
-
// Strip leading whitespace from the first removed part
|
|
34
29
|
if (isFirstRemoved) {
|
|
35
30
|
const leadingWs = value.match(/^(\s*)/)?.[1] || "";
|
|
36
31
|
value = value.slice(leadingWs.length);
|
|
@@ -38,12 +33,11 @@ function renderIntraLineDiff(oldContent, newContent) {
|
|
|
38
33
|
isFirstRemoved = false;
|
|
39
34
|
}
|
|
40
35
|
if (value) {
|
|
41
|
-
removedLine +=
|
|
36
|
+
removedLine += `<REMOVED>${value}</REMOVED>`;
|
|
42
37
|
}
|
|
43
38
|
}
|
|
44
39
|
else if (part.added) {
|
|
45
40
|
let value = part.value;
|
|
46
|
-
// Strip leading whitespace from the first added part
|
|
47
41
|
if (isFirstAdded) {
|
|
48
42
|
const leadingWs = value.match(/^(\s*)/)?.[1] || "";
|
|
49
43
|
value = value.slice(leadingWs.length);
|
|
@@ -51,7 +45,7 @@ function renderIntraLineDiff(oldContent, newContent) {
|
|
|
51
45
|
isFirstAdded = false;
|
|
52
46
|
}
|
|
53
47
|
if (value) {
|
|
54
|
-
addedLine +=
|
|
48
|
+
addedLine += `<ADDED>${value}</ADDED>`;
|
|
55
49
|
}
|
|
56
50
|
}
|
|
57
51
|
else {
|
|
@@ -62,10 +56,10 @@ function renderIntraLineDiff(oldContent, newContent) {
|
|
|
62
56
|
return { removedLine, addedLine };
|
|
63
57
|
}
|
|
64
58
|
/**
|
|
65
|
-
* Render a diff string
|
|
66
|
-
* - Context lines:
|
|
67
|
-
* - Removed lines:
|
|
68
|
-
* - Added lines:
|
|
59
|
+
* Render a diff string as plain text with markers.
|
|
60
|
+
* - Context lines: unchanged
|
|
61
|
+
* - Removed lines: prefixed with "-"
|
|
62
|
+
* - Added lines: prefixed with "+"
|
|
69
63
|
*/
|
|
70
64
|
export function renderDiff(diffText, _options = {}) {
|
|
71
65
|
const lines = diffText.split("\n");
|
|
@@ -75,12 +69,11 @@ export function renderDiff(diffText, _options = {}) {
|
|
|
75
69
|
const line = lines[i];
|
|
76
70
|
const parsed = parseDiffLine(line);
|
|
77
71
|
if (!parsed) {
|
|
78
|
-
result.push(
|
|
72
|
+
result.push(line);
|
|
79
73
|
i++;
|
|
80
74
|
continue;
|
|
81
75
|
}
|
|
82
76
|
if (parsed.prefix === "-") {
|
|
83
|
-
// Collect consecutive removed lines
|
|
84
77
|
const removedLines = [];
|
|
85
78
|
while (i < lines.length) {
|
|
86
79
|
const p = parseDiffLine(lines[i]);
|
|
@@ -89,7 +82,6 @@ export function renderDiff(diffText, _options = {}) {
|
|
|
89
82
|
removedLines.push({ lineNum: p.lineNum, content: p.content });
|
|
90
83
|
i++;
|
|
91
84
|
}
|
|
92
|
-
// Collect consecutive added lines
|
|
93
85
|
const addedLines = [];
|
|
94
86
|
while (i < lines.length) {
|
|
95
87
|
const p = parseDiffLine(lines[i]);
|
|
@@ -98,33 +90,28 @@ export function renderDiff(diffText, _options = {}) {
|
|
|
98
90
|
addedLines.push({ lineNum: p.lineNum, content: p.content });
|
|
99
91
|
i++;
|
|
100
92
|
}
|
|
101
|
-
// Only do intra-line diffing when there's exactly one removed and one added line
|
|
102
|
-
// (indicating a single line modification). Otherwise, show lines as-is.
|
|
103
93
|
if (removedLines.length === 1 && addedLines.length === 1) {
|
|
104
94
|
const removed = removedLines[0];
|
|
105
95
|
const added = addedLines[0];
|
|
106
96
|
const { removedLine, addedLine } = renderIntraLineDiff(replaceTabs(removed.content), replaceTabs(added.content));
|
|
107
|
-
result.push(
|
|
108
|
-
result.push(
|
|
97
|
+
result.push(`-${removed.lineNum} ${removedLine}`);
|
|
98
|
+
result.push(`+${added.lineNum} ${addedLine}`);
|
|
109
99
|
}
|
|
110
100
|
else {
|
|
111
|
-
// Show all removed lines first, then all added lines
|
|
112
101
|
for (const removed of removedLines) {
|
|
113
|
-
result.push(
|
|
102
|
+
result.push(`-${removed.lineNum} ${replaceTabs(removed.content)}`);
|
|
114
103
|
}
|
|
115
104
|
for (const added of addedLines) {
|
|
116
|
-
result.push(
|
|
105
|
+
result.push(`+${added.lineNum} ${replaceTabs(added.content)}`);
|
|
117
106
|
}
|
|
118
107
|
}
|
|
119
108
|
}
|
|
120
109
|
else if (parsed.prefix === "+") {
|
|
121
|
-
|
|
122
|
-
result.push(theme.fg("toolDiffAdded", `+${parsed.lineNum} ${replaceTabs(parsed.content)}`));
|
|
110
|
+
result.push(`+${parsed.lineNum} ${replaceTabs(parsed.content)}`);
|
|
123
111
|
i++;
|
|
124
112
|
}
|
|
125
113
|
else {
|
|
126
|
-
|
|
127
|
-
result.push(theme.fg("toolDiffContext", ` ${parsed.lineNum} ${replaceTabs(parsed.content)}`));
|
|
114
|
+
result.push(` ${parsed.lineNum} ${replaceTabs(parsed.content)}`);
|
|
128
115
|
i++;
|
|
129
116
|
}
|
|
130
117
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { mkdir as fsMkdir, writeFile as fsWriteFile } from "fs/promises";
|
|
2
2
|
import { dirname } from "path";
|
|
3
3
|
import { Type } from "typebox";
|
|
4
|
-
import {
|
|
5
|
-
import { getLanguageFromPath, highlightCode } from "../../modes/interactive/theme/theme.js";
|
|
4
|
+
import { getLanguageFromPath, highlightCode } from "../theme.js";
|
|
6
5
|
import { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
7
6
|
import { resolveToCwd } from "./path-utils.js";
|
|
8
|
-
import {
|
|
7
|
+
import { normalizeDisplayText, replaceTabs, shortenPath, str } from "./render-utils.js";
|
|
9
8
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
10
9
|
const writeSchema = Type.Object({
|
|
11
10
|
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
|
@@ -22,14 +21,14 @@ function trimTrailingEmptyLines(lines) {
|
|
|
22
21
|
}
|
|
23
22
|
return lines.slice(0, end);
|
|
24
23
|
}
|
|
25
|
-
function formatWriteCall(args, options,
|
|
24
|
+
function formatWriteCall(args, options, _theme) {
|
|
26
25
|
const rawPath = str(args?.file_path ?? args?.path);
|
|
27
26
|
const fileContent = str(args?.content);
|
|
28
27
|
const path = rawPath !== null ? shortenPath(rawPath) : null;
|
|
29
|
-
const
|
|
30
|
-
let text =
|
|
28
|
+
const pathDisplay = path === null ? "[invalid]" : path || "...";
|
|
29
|
+
let text = `write ${pathDisplay}`;
|
|
31
30
|
if (fileContent === null) {
|
|
32
|
-
text += `\n\n
|
|
31
|
+
text += `\n\n[invalid content arg - expected string]`;
|
|
33
32
|
}
|
|
34
33
|
else if (fileContent) {
|
|
35
34
|
const lang = rawPath ? getLanguageFromPath(rawPath) : undefined;
|
|
@@ -41,14 +40,14 @@ function formatWriteCall(args, options, theme) {
|
|
|
41
40
|
const maxLines = options.expanded ? lines.length : 10;
|
|
42
41
|
const displayLines = lines.slice(0, maxLines);
|
|
43
42
|
const remaining = lines.length - maxLines;
|
|
44
|
-
text += `\n\n${displayLines.
|
|
43
|
+
text += `\n\n${displayLines.join("\n")}`;
|
|
45
44
|
if (remaining > 0) {
|
|
46
|
-
text +=
|
|
45
|
+
text += `\n... (${remaining} more lines, ${totalLines} total)`;
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
return text;
|
|
50
49
|
}
|
|
51
|
-
function formatWriteResult(result,
|
|
50
|
+
function formatWriteResult(result, _theme) {
|
|
52
51
|
if (!result.isError) {
|
|
53
52
|
return undefined;
|
|
54
53
|
}
|
|
@@ -59,7 +58,7 @@ function formatWriteResult(result, theme) {
|
|
|
59
58
|
if (!output) {
|
|
60
59
|
return undefined;
|
|
61
60
|
}
|
|
62
|
-
return `\n${
|
|
61
|
+
return `\n${output}`;
|
|
63
62
|
}
|
|
64
63
|
export function createWriteToolDefinition(cwd, options) {
|
|
65
64
|
const ops = options?.operations ?? defaultWriteOperations;
|
|
@@ -8,6 +8,8 @@ export { AuthStorage, FileAuthStorageBackend, InMemoryAuthStorageBackend, } from
|
|
|
8
8
|
export { calculateContextTokens, collectEntriesForBranchSummary, compact, DEFAULT_COMPACTION_SETTINGS, estimateTokens, findCutPoint, findTurnStartIndex, generateBranchSummary, generateSummary, getLastAssistantUsage, prepareBranchEntries, serializeConversation, shouldCompact, } from "./core/compaction/index.js";
|
|
9
9
|
export { createEventBus } from "./core/event-bus.js";
|
|
10
10
|
export { createExtensionRuntime, defineTool, discoverAndLoadExtensions, ExtensionRunner, isBashToolResult, isEditToolResult, isFindToolResult, isGrepToolResult, isLsToolResult, isReadToolResult, isToolCallEventType, isWriteToolResult, wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.js";
|
|
11
|
+
// Footer data provider — no longer exported (TUI-only, serve/relay uses no-ops)
|
|
12
|
+
// export type { ReadonlyFooterDataProvider } from "./core/footer-data-provider.js";
|
|
11
13
|
export { convertToLlm } from "./core/messages.js";
|
|
12
14
|
export { ModelRegistry } from "./core/model-registry.js";
|
|
13
15
|
export { DefaultPackageManager } from "./core/package-manager.js";
|
|
@@ -28,11 +30,11 @@ export { createBashToolDefinition, createEditToolDefinition, createFindToolDefin
|
|
|
28
30
|
// Main entry point
|
|
29
31
|
// main.ts removed (interactive-only, not used in SDK mode)
|
|
30
32
|
// Run modes for programmatic SDK usage
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Theme utilities for
|
|
35
|
-
export { getLanguageFromPath,
|
|
33
|
+
// InteractiveMode removed (TUI-only, not used in serve/relay mode)
|
|
34
|
+
export { RpcClient, runRpcMode, } from "./modes/index.js";
|
|
35
|
+
export { runPrintMode } from "./modes/print-mode.js";
|
|
36
|
+
// Theme utilities for syntax highlighting (serve/relay compatible)
|
|
37
|
+
export { getLanguageFromPath, highlightCode, loadThemeFromPath } from "./core/theme.js";
|
|
36
38
|
// Clipboard utilities
|
|
37
39
|
export { copyToClipboard } from "./utils/clipboard.js";
|
|
38
40
|
export { parseFrontmatter, stripFrontmatter } from "./utils/frontmatter.js";
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
import * as crypto from "node:crypto";
|
|
14
14
|
import { takeOverStdout, writeRawStdout } from "../../core/output-guard.js";
|
|
15
15
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
16
|
-
import { theme } from "../interactive/theme/theme.js";
|
|
17
16
|
import { attachJsonlLineReader, serializeJsonLine } from "./jsonl.js";
|
|
18
17
|
/**
|
|
19
18
|
* Run in RPC mode.
|
|
@@ -200,7 +199,8 @@ export async function runRpcMode(runtimeHost) {
|
|
|
200
199
|
return undefined;
|
|
201
200
|
},
|
|
202
201
|
get theme() {
|
|
203
|
-
return
|
|
202
|
+
// Theme not available in RPC mode; return a stub.
|
|
203
|
+
return {};
|
|
204
204
|
},
|
|
205
205
|
getAllThemes() {
|
|
206
206
|
return [];
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Photon image processing wrapper.
|
|
3
3
|
*
|
|
4
|
-
* This module provides a unified interface to @silvia-odwyer/photon-node
|
|
5
|
-
*
|
|
6
|
-
* 2. Bun compiled binaries (standalone distribution)
|
|
7
|
-
*
|
|
8
|
-
* The challenge: photon-node's CJS entry uses fs.readFileSync(__dirname + '/photon_rs_bg.wasm')
|
|
9
|
-
* which bakes the build machine's absolute path into Bun compiled binaries.
|
|
10
|
-
*
|
|
11
|
-
* Solution:
|
|
12
|
-
* 1. Patch fs.readFileSync to redirect missing photon_rs_bg.wasm reads
|
|
13
|
-
* 2. Copy photon_rs_bg.wasm next to the executable in build:binary
|
|
4
|
+
* This module provides a unified interface to @silvia-odwyer/photon-node for
|
|
5
|
+
* use in Node.js (development, npm run build).
|
|
14
6
|
*/
|
|
15
7
|
import { createRequire } from "module";
|
|
16
8
|
import * as path from "path";
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
export function getPiUserAgent(version) {
|
|
2
|
-
|
|
3
|
-
return `pi/${version} (${process.platform}; ${runtime}; ${process.arch})`;
|
|
2
|
+
return `pi/${version} (${process.platform}; node/${process.version}; ${process.arch})`;
|
|
4
3
|
}
|
|
@@ -55,6 +55,7 @@ import { existsSync, statSync } from "node:fs";
|
|
|
55
55
|
import { dirname, join, resolve } from "node:path";
|
|
56
56
|
import { fileURLToPath } from "node:url";
|
|
57
57
|
import aexolMcpExtension from "../extensions/aexol-mcp.js";
|
|
58
|
+
import kanbanBridgeExtension from "../extensions/kanban-bridge.js";
|
|
58
59
|
import spectralVisionExtension from "../extensions/spectral-vision-fallback.js";
|
|
59
60
|
import subagentExt from "../agent/index.js";
|
|
60
61
|
import designerExtension from "../designer/index.js";
|
|
@@ -418,7 +419,7 @@ export class AgentBridge {
|
|
|
418
419
|
async start() {
|
|
419
420
|
if (this.disposed)
|
|
420
421
|
throw new Error("AgentBridge already disposed");
|
|
421
|
-
const extensionFactories = [aexolMcpExtension, async (pi) => { spectralVisionExtension(pi); }, async (pi) => { subagentExt(pi); }, async (pi) => { designerExtension(pi); }, async (pi) => { observationalMemory(pi); }];
|
|
422
|
+
const extensionFactories = [aexolMcpExtension, kanbanBridgeExtension, async (pi) => { spectralVisionExtension(pi); }, async (pi) => { subagentExt(pi); }, async (pi) => { designerExtension(pi); }, async (pi) => { observationalMemory(pi); }];
|
|
422
423
|
// Load pi-mcp-adapter via jiti so tsc never crawls its .ts files in
|
|
423
424
|
// node_modules. The static `import` was causing tsc to type-check
|
|
424
425
|
// pi-mcp-adapter's source and fail the build on its type errors.
|
package/package.json
CHANGED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workaround for https://github.com/oven-sh/bun/issues/27802
|
|
3
|
-
*
|
|
4
|
-
* Bun compiled binaries have an empty `process.env` when running inside
|
|
5
|
-
* sandbox environments (e.g. nono on Linux/macOS). On Linux we can recover
|
|
6
|
-
* the environment from `/proc/self/environ`.
|
|
7
|
-
*/
|
|
8
|
-
import { readFileSync } from "node:fs";
|
|
9
|
-
/**
|
|
10
|
-
* Restore environment variables from `/proc/self/environ` when running
|
|
11
|
-
* inside a sandbox where Bun's `process.env` is empty.
|
|
12
|
-
*/
|
|
13
|
-
export function restoreSandboxEnv() {
|
|
14
|
-
if (!process.versions?.bun)
|
|
15
|
-
return;
|
|
16
|
-
// If process.env already has entries, nothing to fix.
|
|
17
|
-
if (Object.keys(process.env).length > 0)
|
|
18
|
-
return;
|
|
19
|
-
try {
|
|
20
|
-
const data = readFileSync("/proc/self/environ", "utf-8");
|
|
21
|
-
for (const entry of data.split("\0")) {
|
|
22
|
-
const idx = entry.indexOf("=");
|
|
23
|
-
if (idx > 0) {
|
|
24
|
-
process.env[entry.slice(0, idx)] = entry.slice(idx + 1);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
// /proc/self/environ may not be readable; ignore.
|
|
30
|
-
}
|
|
31
|
-
}
|