agent-sh 0.12.17 → 0.12.19
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/agent/agent-loop.js +1 -2
- package/dist/agent/conversation-state.d.ts +3 -0
- package/dist/agent/conversation-state.js +25 -4
- package/dist/agent/tools/edit-file.js +6 -22
- package/dist/agent/tools/write-file.js +6 -22
- package/dist/agent/types.d.ts +2 -0
- package/dist/extensions/tui-renderer.js +21 -10
- package/dist/settings.d.ts +2 -0
- package/dist/settings.js +1 -0
- package/package.json +1 -1
package/dist/agent/agent-loop.js
CHANGED
|
@@ -995,8 +995,7 @@ export class AgentLoop {
|
|
|
995
995
|
const absPath = path.resolve(process.cwd(), args.path);
|
|
996
996
|
this.fileReadCache.delete(absPath);
|
|
997
997
|
}
|
|
998
|
-
|
|
999
|
-
const resultDisplay = tool.formatResult?.(args, result);
|
|
998
|
+
const resultDisplay = result.display ?? tool.formatResult?.(args, result);
|
|
1000
999
|
// Emit completion events (via transform pipe so extensions can override)
|
|
1001
1000
|
this.bus.emitTransform("agent:tool-completed", {
|
|
1002
1001
|
toolCallId: id, exitCode: result.exitCode,
|
|
@@ -59,6 +59,9 @@ export declare class ConversationState {
|
|
|
59
59
|
private hasOpenToolCalls;
|
|
60
60
|
private flushPendingNotes;
|
|
61
61
|
getMessages(): ChatCompletionMessageParam[];
|
|
62
|
+
/** Drop tool messages with no matching preceding tool_call — strict
|
|
63
|
+
* providers (DeepSeek) 400, and compaction can leave such orphans. */
|
|
64
|
+
private dropOrphanToolMessages;
|
|
62
65
|
/**
|
|
63
66
|
* If a stream was interrupted mid-tool-execution, an assistant message
|
|
64
67
|
* with tool_calls can land in history without matching tool results.
|
|
@@ -163,7 +163,24 @@ export class ConversationState {
|
|
|
163
163
|
this.invalidateMessagesCache();
|
|
164
164
|
}
|
|
165
165
|
getMessages() {
|
|
166
|
-
return this.normalizeReasoningConsistency(this.stubDanglingToolCalls(this.messages));
|
|
166
|
+
return this.normalizeReasoningConsistency(this.stubDanglingToolCalls(this.dropOrphanToolMessages(this.messages)));
|
|
167
|
+
}
|
|
168
|
+
/** Drop tool messages with no matching preceding tool_call — strict
|
|
169
|
+
* providers (DeepSeek) 400, and compaction can leave such orphans. */
|
|
170
|
+
dropOrphanToolMessages(messages) {
|
|
171
|
+
const knownIds = new Set();
|
|
172
|
+
const result = [];
|
|
173
|
+
for (const msg of messages) {
|
|
174
|
+
if (msg.role === "assistant" && "tool_calls" in msg && msg.tool_calls) {
|
|
175
|
+
for (const tc of msg.tool_calls)
|
|
176
|
+
knownIds.add(tc.id);
|
|
177
|
+
}
|
|
178
|
+
if (msg.role === "tool" && !knownIds.has(msg.tool_call_id)) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
result.push(msg);
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
167
184
|
}
|
|
168
185
|
/**
|
|
169
186
|
* If a stream was interrupted mid-tool-execution, an assistant message
|
|
@@ -582,19 +599,23 @@ export class ConversationState {
|
|
|
582
599
|
slimTurn(messages) {
|
|
583
600
|
const MAX_RESULT_LEN = 1500;
|
|
584
601
|
const result = [];
|
|
585
|
-
const
|
|
602
|
+
const droppedToolIds = new Set();
|
|
586
603
|
for (const msg of messages) {
|
|
587
604
|
if (msg.role === "assistant" && "tool_calls" in msg && msg.tool_calls) {
|
|
588
605
|
const kept = msg.tool_calls.filter((tc) => {
|
|
589
606
|
if (!("function" in tc))
|
|
590
607
|
return true;
|
|
591
608
|
if (READ_ONLY_TOOLS.has(tc.function.name)) {
|
|
592
|
-
|
|
609
|
+
droppedToolIds.add(tc.id);
|
|
593
610
|
return false;
|
|
594
611
|
}
|
|
595
612
|
return true;
|
|
596
613
|
});
|
|
597
614
|
if (kept.length === 0) {
|
|
615
|
+
// No content + no tool_calls is malformed (DeepSeek 400); drop the husk.
|
|
616
|
+
const text = typeof msg.content === "string" ? msg.content.trim() : "";
|
|
617
|
+
if (!text)
|
|
618
|
+
continue;
|
|
598
619
|
const { tool_calls: _, ...rest } = msg;
|
|
599
620
|
result.push(rest);
|
|
600
621
|
}
|
|
@@ -604,7 +625,7 @@ export class ConversationState {
|
|
|
604
625
|
continue;
|
|
605
626
|
}
|
|
606
627
|
if (msg.role === "tool") {
|
|
607
|
-
if (
|
|
628
|
+
if (droppedToolIds.has(msg.tool_call_id))
|
|
608
629
|
continue;
|
|
609
630
|
const content = typeof msg.content === "string" ? msg.content : "";
|
|
610
631
|
if (content.length > MAX_RESULT_LEN) {
|
|
@@ -69,13 +69,7 @@ export function createEditFileTool(getCwd) {
|
|
|
69
69
|
icon: "✎",
|
|
70
70
|
locations: [{ path: args.path }],
|
|
71
71
|
}),
|
|
72
|
-
|
|
73
|
-
if (result.isError)
|
|
74
|
-
return {};
|
|
75
|
-
const m = result.content.match(/\((\+\d+(?:\s-\d+)?)\)/);
|
|
76
|
-
return m ? { summary: m[1] } : {};
|
|
77
|
-
},
|
|
78
|
-
async execute(args, onChunk) {
|
|
72
|
+
async execute(args) {
|
|
79
73
|
const filePath = expandHome(args.path);
|
|
80
74
|
const oldText = args.old_text;
|
|
81
75
|
const newText = args.new_text;
|
|
@@ -120,26 +114,16 @@ export function createEditFileTool(getCwd) {
|
|
|
120
114
|
? newContent.replace(/\n/g, "\r\n")
|
|
121
115
|
: newContent;
|
|
122
116
|
await fs.writeFile(absPath, finalContent);
|
|
123
|
-
// Compute and stream diff for display. Batch into one onChunk —
|
|
124
|
-
// per-line emits trigger N TUI renders for large hunks.
|
|
125
117
|
const diff = computeEditDiff(normalized, normalizedOld, normalizedNew, replaceAll);
|
|
126
|
-
|
|
127
|
-
const parts = [];
|
|
128
|
-
for (const hunk of diff.hunks) {
|
|
129
|
-
for (const line of hunk.lines) {
|
|
130
|
-
const prefix = line.type === "added" ? "+" : line.type === "removed" ? "-" : " ";
|
|
131
|
-
parts.push(`${prefix}${line.text}\n`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
onChunk(parts.join(""));
|
|
135
|
-
}
|
|
136
|
-
const stats = diff.isNewFile
|
|
137
|
-
? `+${diff.added}`
|
|
138
|
-
: `+${diff.added} -${diff.removed}`;
|
|
118
|
+
const stats = diff.isNewFile ? `+${diff.added}` : `+${diff.added} -${diff.removed}`;
|
|
139
119
|
return {
|
|
140
120
|
content: `Edited ${absPath} (${stats})`,
|
|
141
121
|
exitCode: 0,
|
|
142
122
|
isError: false,
|
|
123
|
+
display: {
|
|
124
|
+
summary: stats,
|
|
125
|
+
body: { kind: "diff", diff, filePath: absPath },
|
|
126
|
+
},
|
|
143
127
|
};
|
|
144
128
|
}
|
|
145
129
|
catch (err) {
|
|
@@ -29,13 +29,7 @@ export function createWriteFileTool(getCwd) {
|
|
|
29
29
|
icon: "✎",
|
|
30
30
|
locations: [{ path: args.path }],
|
|
31
31
|
}),
|
|
32
|
-
|
|
33
|
-
if (result.isError)
|
|
34
|
-
return {};
|
|
35
|
-
const m = result.content.match(/\((\+\d+(?:\s-\d+)?)\)/);
|
|
36
|
-
return m ? { summary: m[1] } : {};
|
|
37
|
-
},
|
|
38
|
-
async execute(args, onChunk) {
|
|
32
|
+
async execute(args) {
|
|
39
33
|
const filePath = expandHome(args.path);
|
|
40
34
|
const content = args.content;
|
|
41
35
|
const absPath = path.resolve(getCwd(), filePath);
|
|
@@ -49,28 +43,18 @@ export function createWriteFileTool(getCwd) {
|
|
|
49
43
|
}
|
|
50
44
|
await fs.mkdir(path.dirname(absPath), { recursive: true });
|
|
51
45
|
await fs.writeFile(absPath, content);
|
|
52
|
-
// Compute and stream diff for display. Batch into one onChunk —
|
|
53
|
-
// per-line emits trigger N TUI renders for large files.
|
|
54
46
|
const diff = computeDiff(oldContent, content);
|
|
55
|
-
|
|
56
|
-
const parts = [];
|
|
57
|
-
for (const hunk of diff.hunks) {
|
|
58
|
-
for (const line of hunk.lines) {
|
|
59
|
-
const prefix = line.type === "added" ? "+" : line.type === "removed" ? "-" : " ";
|
|
60
|
-
parts.push(`${prefix}${line.text}\n`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
onChunk(parts.join(""));
|
|
64
|
-
}
|
|
65
|
-
const stats = diff.isNewFile
|
|
66
|
-
? `+${diff.added}`
|
|
67
|
-
: `+${diff.added} -${diff.removed}`;
|
|
47
|
+
const stats = diff.isNewFile ? `+${diff.added}` : `+${diff.added} -${diff.removed}`;
|
|
68
48
|
return {
|
|
69
49
|
content: oldContent === null
|
|
70
50
|
? `Created ${absPath} (${stats})`
|
|
71
51
|
: `Wrote ${absPath} (${stats})`,
|
|
72
52
|
exitCode: 0,
|
|
73
53
|
isError: false,
|
|
54
|
+
display: {
|
|
55
|
+
summary: stats,
|
|
56
|
+
body: { kind: "diff", diff, filePath: absPath },
|
|
57
|
+
},
|
|
74
58
|
};
|
|
75
59
|
}
|
|
76
60
|
catch (err) {
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface ToolResult {
|
|
|
19
19
|
content: string;
|
|
20
20
|
exitCode: number | null;
|
|
21
21
|
isError: boolean;
|
|
22
|
+
/** When set, takes precedence over `tool.formatResult()`. */
|
|
23
|
+
display?: ToolResultDisplay;
|
|
22
24
|
}
|
|
23
25
|
/** Structured result display — returned by formatResult or computed by defaults. */
|
|
24
26
|
export interface ToolResultDisplay {
|
|
@@ -652,22 +652,33 @@ export default function activate(ctx) {
|
|
|
652
652
|
function renderDiffBody(diff, filePath, width) {
|
|
653
653
|
if (diff.isIdentical)
|
|
654
654
|
return [];
|
|
655
|
-
const boxW = Math.min(120, width - 2);
|
|
655
|
+
const boxW = Math.min(120, width - 2);
|
|
656
656
|
const contentW = boxW - 4;
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
657
|
+
let body;
|
|
658
|
+
if (diff.isNewFile) {
|
|
659
|
+
const lines = diff.hunks.flatMap(h => h.lines.map(l => l.text));
|
|
660
|
+
const preview = getSettings().newFilePreviewLines;
|
|
661
|
+
const head = lines.slice(0, preview);
|
|
662
|
+
const truncated = head.map(l => l.length > contentW ? l.slice(0, contentW - 1) + "…" : l);
|
|
663
|
+
const more = lines.length > preview
|
|
664
|
+
? [`${p.dim}… ${lines.length - preview} more lines${p.reset}`]
|
|
665
|
+
: [];
|
|
666
|
+
body = ["", ...truncated, ...more, ""];
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
const diffLines = renderDiff(diff, {
|
|
670
|
+
width: contentW,
|
|
671
|
+
filePath,
|
|
672
|
+
maxLines: getSettings().diffMaxLines,
|
|
673
|
+
trueColor: true,
|
|
674
|
+
});
|
|
675
|
+
body = diffLines.length > 1 ? ["", ...diffLines.slice(1), ""] : diffLines;
|
|
676
|
+
}
|
|
665
677
|
return renderBoxFrame(body, {
|
|
666
678
|
width: boxW,
|
|
667
679
|
style: "rounded",
|
|
668
680
|
borderColor: p.dim,
|
|
669
681
|
title: diffTitle(filePath, diff),
|
|
670
|
-
footer,
|
|
671
682
|
});
|
|
672
683
|
}
|
|
673
684
|
/** Render output lines with truncation. */
|
package/dist/settings.d.ts
CHANGED
|
@@ -67,6 +67,8 @@ export interface Settings {
|
|
|
67
67
|
readOutputMaxLines?: number;
|
|
68
68
|
/** Max diff lines rendered in the TUI (Infinity = no limit). */
|
|
69
69
|
diffMaxLines?: number;
|
|
70
|
+
/** Lines of head content shown when a brand-new file is created. */
|
|
71
|
+
newFilePreviewLines?: number;
|
|
70
72
|
/** Tool protocol:
|
|
71
73
|
* "api" — all tools sent with full schema.
|
|
72
74
|
* "deferred" — extensions dispatched through `use_extension(name, args)` meta-tool.
|
package/dist/settings.js
CHANGED