@makefinks/daemon 0.9.0 → 0.10.0
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/package.json +11 -3
- package/src/ai/daemon-ai.ts +3 -3
- package/src/ai/memory/memory-manager.ts +5 -1
- package/src/ai/model-config.ts +2 -2
- package/src/ai/system-prompt.ts +16 -0
- package/src/ai/tools/fetch-urls.ts +20 -7
- package/src/ai/tools/tool-registry.ts +6 -0
- package/src/ai/tools/write-file.ts +51 -0
- package/src/components/tool-layouts/layouts/index.ts +1 -0
- package/src/components/tool-layouts/layouts/url-tools.ts +3 -1
- package/src/components/tool-layouts/layouts/write-file.tsx +117 -0
- package/src/types/index.ts +2 -0
- package/src/utils/derive-url-menu-items.ts +1 -0
- package/src/utils/tool-output-preview.ts +7 -2
package/package.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"module": "src/index.tsx",
|
|
30
30
|
"type": "module",
|
|
31
|
-
"version": "0.
|
|
31
|
+
"version": "0.10.0",
|
|
32
32
|
"bin": {
|
|
33
33
|
"daemon": "dist/cli.js"
|
|
34
34
|
},
|
|
@@ -77,13 +77,21 @@
|
|
|
77
77
|
"@ai-sdk/openai": "^3.0.0",
|
|
78
78
|
"@openrouter/ai-sdk-provider": "^2.1.0",
|
|
79
79
|
"@opentui-ui/toast": "^0.0.3",
|
|
80
|
-
"@opentui/core": "
|
|
81
|
-
"@opentui/react": "
|
|
80
|
+
"@opentui/core": "0.1.75",
|
|
81
|
+
"@opentui/react": "0.1.75",
|
|
82
82
|
"ai": "^6.0.0",
|
|
83
83
|
"exa-js": "^2.0.12",
|
|
84
84
|
"mem0ai": "^2.2.1",
|
|
85
85
|
"openai": "^6.16.0",
|
|
86
86
|
"opentui-spinner": "^0.0.6",
|
|
87
87
|
"react": "^19.2.3"
|
|
88
|
+
},
|
|
89
|
+
"optionalDependencies": {
|
|
90
|
+
"@opentui/core-win32-x64": "0.1.75",
|
|
91
|
+
"@opentui/core-win32-arm64": "0.1.75",
|
|
92
|
+
"@opentui/core-darwin-x64": "0.1.75",
|
|
93
|
+
"@opentui/core-darwin-arm64": "0.1.75",
|
|
94
|
+
"@opentui/core-linux-x64": "0.1.75",
|
|
95
|
+
"@opentui/core-linux-arm64": "0.1.75"
|
|
88
96
|
}
|
|
89
97
|
}
|
package/src/ai/daemon-ai.ts
CHANGED
|
@@ -15,14 +15,14 @@ import {
|
|
|
15
15
|
import { getDaemonManager } from "../state/daemon-state";
|
|
16
16
|
import { getRuntimeContext } from "../state/runtime-context";
|
|
17
17
|
import type {
|
|
18
|
+
MemoryToastOperation,
|
|
19
|
+
MemoryToastPreview,
|
|
18
20
|
ReasoningEffort,
|
|
19
21
|
StreamCallbacks,
|
|
20
22
|
TokenUsage,
|
|
21
23
|
ToolApprovalRequest,
|
|
22
24
|
ToolApprovalResponse,
|
|
23
25
|
TranscriptionResult,
|
|
24
|
-
MemoryToastPreview,
|
|
25
|
-
MemoryToastOperation,
|
|
26
26
|
} from "../types";
|
|
27
27
|
import { debug, toolDebug } from "../utils/debug-logger";
|
|
28
28
|
import { getOpenRouterReportedCost } from "../utils/openrouter-reported-cost";
|
|
@@ -321,7 +321,7 @@ export async function generateResponse(
|
|
|
321
321
|
async function persistConversationMemory(
|
|
322
322
|
userMessage: string,
|
|
323
323
|
assistantMessage: string
|
|
324
|
-
): Promise<
|
|
324
|
+
): Promise<MemoryToastPreview | null> {
|
|
325
325
|
const userTextForMemory = userMessage.trim();
|
|
326
326
|
const assistantTextForMemory = assistantMessage.trim();
|
|
327
327
|
|
|
@@ -302,9 +302,13 @@ Rules:
|
|
|
302
302
|
})) as Mem0RawAddResult;
|
|
303
303
|
|
|
304
304
|
const extracted = result.results.map((r) => {
|
|
305
|
-
const
|
|
305
|
+
const rawEvent =
|
|
306
306
|
(r as unknown as { metadata?: { event?: string } }).metadata?.event ??
|
|
307
307
|
(r as { event?: string }).event;
|
|
308
|
+
const validEvents = ["ADD", "UPDATE", "DELETE", "NONE"] as const;
|
|
309
|
+
const event = validEvents.includes(rawEvent as (typeof validEvents)[number])
|
|
310
|
+
? (rawEvent as (typeof validEvents)[number])
|
|
311
|
+
: "NONE";
|
|
308
312
|
return {
|
|
309
313
|
id: r.id,
|
|
310
314
|
memory: r.memory,
|
package/src/ai/model-config.ts
CHANGED
|
@@ -9,14 +9,14 @@ import { loadManualConfig } from "../utils/config";
|
|
|
9
9
|
// Available models for selection (OpenRouter format)
|
|
10
10
|
export const AVAILABLE_MODELS: ModelOption[] = [
|
|
11
11
|
{ id: "x-ai/grok-4.1-fast", name: "Grok 4.1 Fast" },
|
|
12
|
+
{ id: "arcee-ai/trinity-large-preview:free", name: "Trinity Large Preview" },
|
|
12
13
|
{ id: "z-ai/glm-4.7", name: "GLM 4.7" },
|
|
13
14
|
{ id: "minimax/minimax-m2.1", name: "Minimax M2.1" },
|
|
14
15
|
{ id: "google/gemini-3-flash-preview", name: "Gemini 3 Flash" },
|
|
15
16
|
{ id: "google/gemini-3-pro-preview", name: "Gemini 3 Pro" },
|
|
16
17
|
{ id: "openai/gpt-5.2", name: "GPT 5.2" },
|
|
17
|
-
{ id: "moonshotai/kimi-k2
|
|
18
|
+
{ id: "moonshotai/kimi-k2.5", name: "Kimi K2.5" },
|
|
18
19
|
{ id: "openai/gpt-oss-120b:exacto", name: "GPT-OSS-120" },
|
|
19
|
-
{ id: "mistralai/devstral-2512:free", name: "Mistral Devstral" },
|
|
20
20
|
{ id: "nvidia/nemotron-3-nano-30b-a3b:free", name: "Nemotron 3 Nano" },
|
|
21
21
|
];
|
|
22
22
|
|
package/src/ai/system-prompt.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type InteractionMode = "text" | "voice";
|
|
|
9
9
|
|
|
10
10
|
export interface ToolAvailability {
|
|
11
11
|
readFile: boolean;
|
|
12
|
+
writeFile: boolean;
|
|
12
13
|
runBash: boolean;
|
|
13
14
|
webSearch: boolean;
|
|
14
15
|
fetchUrls: boolean;
|
|
@@ -64,6 +65,7 @@ export function buildDaemonSystemPrompt(options: SystemPromptOptions = {}): stri
|
|
|
64
65
|
function normalizeToolAvailability(toolAvailability?: Partial<ToolAvailability>): ToolAvailability {
|
|
65
66
|
return {
|
|
66
67
|
readFile: toolAvailability?.readFile ?? true,
|
|
68
|
+
writeFile: toolAvailability?.writeFile ?? true,
|
|
67
69
|
runBash: toolAvailability?.runBash ?? true,
|
|
68
70
|
webSearch: toolAvailability?.webSearch ?? true,
|
|
69
71
|
fetchUrls: toolAvailability?.fetchUrls ?? true,
|
|
@@ -243,6 +245,19 @@ Fetch multiple URLs in one call:
|
|
|
243
245
|
By default it reads up to 2000 lines from the start when no offset/limit are provided.
|
|
244
246
|
For partial reads, you must provide both a 0-based line offset and a line limit.
|
|
245
247
|
`,
|
|
248
|
+
writeFile: `
|
|
249
|
+
### 'writeFile' (local file writer)
|
|
250
|
+
Use this to write content to files. Creates new files or overwrites existing ones.
|
|
251
|
+
Automatically creates parent directories if they don't exist.
|
|
252
|
+
|
|
253
|
+
**CRITICAL: Always report the correct file location to the user**
|
|
254
|
+
- When you write a file, explicitly tell the user the full path where it was saved
|
|
255
|
+
- If the file is in the workspace, say "I have saved it to my workspace at: [full path]"
|
|
256
|
+
- If the file is in the current working directory, say "I have saved it to: [path]"
|
|
257
|
+
- Do NOT give commands like "cat filename" or "open filename" unless the file is actually in the current working directory
|
|
258
|
+
- For files in the workspace, give the full path: "cat /full/path/to/file" or tell the user to navigate there first
|
|
259
|
+
`,
|
|
260
|
+
|
|
246
261
|
subagent: `
|
|
247
262
|
### 'subagent'
|
|
248
263
|
Call this tool to spawn subagents for specific tasks.
|
|
@@ -260,6 +275,7 @@ function buildToolDefinitions(availability: ToolAvailability): string {
|
|
|
260
275
|
if (availability.groundingManager) blocks.push(TOOL_SECTIONS.groundingManager);
|
|
261
276
|
if (availability.runBash) blocks.push(TOOL_SECTIONS.runBash);
|
|
262
277
|
if (availability.readFile) blocks.push(TOOL_SECTIONS.readFile);
|
|
278
|
+
if (availability.writeFile) blocks.push(TOOL_SECTIONS.writeFile);
|
|
263
279
|
if (availability.subagent) blocks.push(TOOL_SECTIONS.subagent);
|
|
264
280
|
|
|
265
281
|
const webNote =
|
|
@@ -63,15 +63,28 @@ export const fetchUrls = tool({
|
|
|
63
63
|
return `<fetchUrls error="${escapeXmlAttribute(exaClientResult.error)}" />`;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
interface NormalizedRequest {
|
|
67
|
+
url: string;
|
|
68
|
+
lineOffset?: number;
|
|
69
|
+
lineLimit?: number;
|
|
70
|
+
invalidPagination: boolean;
|
|
71
|
+
effectiveLineOffset: number;
|
|
72
|
+
effectiveLineLimit: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const normalizedRequests = requests.map((request): NormalizedRequest => {
|
|
76
|
+
const lineOffset = request.lineOffset;
|
|
77
|
+
const lineLimit = request.lineLimit;
|
|
78
|
+
const hasLineOffset = typeof lineOffset === "number";
|
|
79
|
+
const hasLineLimit = typeof lineLimit === "number";
|
|
80
|
+
const invalidPagination = hasLineOffset && !hasLineLimit && (lineOffset ?? 0) > 0;
|
|
70
81
|
return {
|
|
71
|
-
|
|
82
|
+
url: request.url,
|
|
83
|
+
lineOffset,
|
|
84
|
+
lineLimit,
|
|
72
85
|
invalidPagination,
|
|
73
|
-
effectiveLineOffset: hasLineOffset ?
|
|
74
|
-
effectiveLineLimit: hasLineLimit ?
|
|
86
|
+
effectiveLineOffset: hasLineOffset ? lineOffset : 0,
|
|
87
|
+
effectiveLineLimit: hasLineLimit ? lineLimit : DEFAULT_LINE_LIMIT,
|
|
75
88
|
};
|
|
76
89
|
});
|
|
77
90
|
|
|
@@ -8,6 +8,7 @@ import { runBash } from "./run-bash";
|
|
|
8
8
|
import { subagent } from "./subagents";
|
|
9
9
|
import { todoManager } from "./todo-manager";
|
|
10
10
|
import { webSearch } from "./web-search";
|
|
11
|
+
import { writeFile } from "./write-file";
|
|
11
12
|
|
|
12
13
|
import type { ToolToggleId, ToolToggles } from "../../types";
|
|
13
14
|
import { detectLocalPlaywrightChromium } from "../../utils/js-rendering";
|
|
@@ -40,6 +41,7 @@ type ToolGateResult = {
|
|
|
40
41
|
|
|
41
42
|
const TOOL_REGISTRY: ToolEntry[] = [
|
|
42
43
|
{ id: "readFile", toggleKey: "readFile", tool: readFile },
|
|
44
|
+
{ id: "writeFile", toggleKey: "writeFile", tool: writeFile },
|
|
43
45
|
{ id: "runBash", toggleKey: "runBash", tool: runBash },
|
|
44
46
|
{ id: "webSearch", toggleKey: "webSearch", tool: webSearch, gate: gateExa },
|
|
45
47
|
{ id: "fetchUrls", toggleKey: "fetchUrls", tool: fetchUrls, gate: gateExa },
|
|
@@ -68,6 +70,7 @@ async function gateRenderUrl(): Promise<ToolGateResult> {
|
|
|
68
70
|
function normalizeToggles(toggles?: ToolToggles): ToolToggles {
|
|
69
71
|
return {
|
|
70
72
|
readFile: toggles?.readFile ?? true,
|
|
73
|
+
writeFile: toggles?.writeFile ?? true,
|
|
71
74
|
runBash: toggles?.runBash ?? true,
|
|
72
75
|
webSearch: toggles?.webSearch ?? true,
|
|
73
76
|
fetchUrls: toggles?.fetchUrls ?? true,
|
|
@@ -166,6 +169,7 @@ export async function buildToolSet(
|
|
|
166
169
|
export function getToolLabels(): Record<ToolId, string> {
|
|
167
170
|
return {
|
|
168
171
|
readFile: "readFile",
|
|
172
|
+
writeFile: "writeFile",
|
|
169
173
|
runBash: "runBash",
|
|
170
174
|
webSearch: "webSearch",
|
|
171
175
|
fetchUrls: "fetchUrls",
|
|
@@ -179,6 +183,7 @@ export function getToolLabels(): Record<ToolId, string> {
|
|
|
179
183
|
export function getDefaultToolOrder(): ToolId[] {
|
|
180
184
|
return [
|
|
181
185
|
"readFile",
|
|
186
|
+
"writeFile",
|
|
182
187
|
"runBash",
|
|
183
188
|
"webSearch",
|
|
184
189
|
"fetchUrls",
|
|
@@ -192,6 +197,7 @@ export function getDefaultToolOrder(): ToolId[] {
|
|
|
192
197
|
export function createToolAvailabilitySnapshot(availability: ToolAvailabilityMap): Record<ToolId, boolean> {
|
|
193
198
|
return {
|
|
194
199
|
readFile: availability.readFile?.enabled ?? false,
|
|
200
|
+
writeFile: availability.writeFile?.enabled ?? false,
|
|
195
201
|
runBash: availability.runBash?.enabled ?? false,
|
|
196
202
|
webSearch: availability.webSearch?.enabled ?? false,
|
|
197
203
|
fetchUrls: availability.fetchUrls?.enabled ?? false,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { tool } from "ai";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
export const writeFile = tool({
|
|
7
|
+
description:
|
|
8
|
+
"Write content to a file. Creates the file if it doesn't exist, or overwrites it if it does. Supports append mode to add content to existing files. Use this to create scripts, save outputs, write configuration files, or generate any text-based file.",
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
path: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("Path to the file to write. Can be absolute or relative to the current working directory."),
|
|
13
|
+
content: z.string().describe("The content to write to the file."),
|
|
14
|
+
append: z
|
|
15
|
+
.boolean()
|
|
16
|
+
.optional()
|
|
17
|
+
.default(false)
|
|
18
|
+
.describe("If true, append to the file instead of overwriting. Creates the file if it doesn't exist."),
|
|
19
|
+
}),
|
|
20
|
+
execute: async ({ path: filePath, content, append }) => {
|
|
21
|
+
try {
|
|
22
|
+
const resolvedPath = path.resolve(filePath);
|
|
23
|
+
const dir = path.dirname(resolvedPath);
|
|
24
|
+
|
|
25
|
+
// Create parent directories if they don't exist
|
|
26
|
+
if (!fs.existsSync(dir)) {
|
|
27
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Write or append to the file
|
|
31
|
+
if (append) {
|
|
32
|
+
fs.appendFileSync(resolvedPath, content, "utf8");
|
|
33
|
+
} else {
|
|
34
|
+
fs.writeFileSync(resolvedPath, content, "utf8");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
success: true,
|
|
39
|
+
path: resolvedPath,
|
|
40
|
+
bytesWritten: Buffer.byteLength(content, "utf8"),
|
|
41
|
+
};
|
|
42
|
+
} catch (error: unknown) {
|
|
43
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
path: filePath,
|
|
47
|
+
error: err.message,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { ToolLayoutConfig, ToolHeader } from "../types";
|
|
2
1
|
import { COLORS } from "../../../ui/constants";
|
|
3
2
|
import { registerToolLayout } from "../registry";
|
|
3
|
+
import type { ToolBody } from "../types";
|
|
4
|
+
import type { ToolHeader, ToolLayoutConfig } from "../types";
|
|
4
5
|
|
|
5
6
|
type UnknownRecord = Record<string, unknown>;
|
|
6
7
|
|
|
@@ -106,6 +107,7 @@ type ExaLikeItem = {
|
|
|
106
107
|
remainingLines?: unknown;
|
|
107
108
|
totalLines?: unknown;
|
|
108
109
|
error?: unknown;
|
|
110
|
+
success?: unknown;
|
|
109
111
|
};
|
|
110
112
|
|
|
111
113
|
function formatExaItemLabel(item: ExaLikeItem): string {
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { pathToFiletype } from "@opentui/core";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { COLORS, REASONING_MARKDOWN_STYLE } from "../../../ui/constants";
|
|
4
|
+
import { registerToolLayout } from "../registry";
|
|
5
|
+
import type { ToolHeader, ToolLayoutConfig, ToolLayoutRenderProps } from "../types";
|
|
6
|
+
|
|
7
|
+
type UnknownRecord = Record<string, unknown>;
|
|
8
|
+
|
|
9
|
+
function isRecord(value: unknown): value is UnknownRecord {
|
|
10
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function extractPath(input: unknown): string | null {
|
|
14
|
+
if (!isRecord(input)) return null;
|
|
15
|
+
if ("path" in input && typeof input.path === "string") {
|
|
16
|
+
return input.path;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function extractContent(input: unknown): string | null {
|
|
22
|
+
if (!isRecord(input)) return null;
|
|
23
|
+
if ("content" in input && typeof input.content === "string") {
|
|
24
|
+
return input.content;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function extractAppend(input: unknown): boolean {
|
|
30
|
+
if (!isRecord(input)) return false;
|
|
31
|
+
if ("append" in input && typeof input.append === "boolean") {
|
|
32
|
+
return input.append;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function WriteFileBody({ call, result }: ToolLayoutRenderProps) {
|
|
38
|
+
if (!isRecord(result)) return null;
|
|
39
|
+
if (result.success === false && typeof result.error === "string") {
|
|
40
|
+
return (
|
|
41
|
+
<box paddingLeft={2}>
|
|
42
|
+
<text>
|
|
43
|
+
<span fg={COLORS.STATUS_FAILED}>{`error: ${result.error}`}</span>
|
|
44
|
+
</text>
|
|
45
|
+
</box>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (result.success !== true) return null;
|
|
49
|
+
|
|
50
|
+
const content = extractContent(call.input) ?? "";
|
|
51
|
+
const path = extractPath(call.input) ?? "";
|
|
52
|
+
|
|
53
|
+
// Detect filetype from path for syntax highlighting
|
|
54
|
+
const filetype = pathToFiletype(path);
|
|
55
|
+
|
|
56
|
+
// Format content preview
|
|
57
|
+
let previewContent = "";
|
|
58
|
+
if (content.trim()) {
|
|
59
|
+
const MAX_LINES = 4;
|
|
60
|
+
const MAX_CHARS = 160;
|
|
61
|
+
const contentLines = content
|
|
62
|
+
.split("\n")
|
|
63
|
+
.slice(0, MAX_LINES)
|
|
64
|
+
.map((line) => (line.length > MAX_CHARS ? `${line.slice(0, MAX_CHARS - 1)}…` : line));
|
|
65
|
+
|
|
66
|
+
const totalLines = content.split("\n").length;
|
|
67
|
+
if (totalLines > MAX_LINES) {
|
|
68
|
+
contentLines.push(`... (${totalLines - MAX_LINES} more lines)`);
|
|
69
|
+
}
|
|
70
|
+
previewContent = contentLines.join("\n");
|
|
71
|
+
} else {
|
|
72
|
+
previewContent = "(empty file)";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<box flexDirection="column" paddingLeft={2} marginTop={0}>
|
|
77
|
+
<box
|
|
78
|
+
borderStyle="single"
|
|
79
|
+
borderColor={COLORS.TOOL_INPUT_BORDER}
|
|
80
|
+
paddingLeft={1}
|
|
81
|
+
paddingRight={1}
|
|
82
|
+
paddingTop={0}
|
|
83
|
+
paddingBottom={0}
|
|
84
|
+
>
|
|
85
|
+
<code
|
|
86
|
+
content={previewContent}
|
|
87
|
+
filetype={filetype}
|
|
88
|
+
syntaxStyle={REASONING_MARKDOWN_STYLE}
|
|
89
|
+
conceal={true}
|
|
90
|
+
drawUnstyledText={false}
|
|
91
|
+
/>
|
|
92
|
+
</box>
|
|
93
|
+
</box>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const writeFileLayout: ToolLayoutConfig = {
|
|
98
|
+
abbreviation: "write",
|
|
99
|
+
|
|
100
|
+
getHeader: (input): ToolHeader | null => {
|
|
101
|
+
const path = extractPath(input);
|
|
102
|
+
if (!path) return null;
|
|
103
|
+
const append = extractAppend(input);
|
|
104
|
+
const filetype = pathToFiletype(path);
|
|
105
|
+
|
|
106
|
+
const parts: string[] = [];
|
|
107
|
+
if (filetype) parts.push(filetype);
|
|
108
|
+
if (append) parts.push("append");
|
|
109
|
+
|
|
110
|
+
const secondary = parts.length > 0 ? parts.join(" · ") : undefined;
|
|
111
|
+
return { primary: path, secondary, secondaryStyle: "dim" };
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
renderBody: WriteFileBody,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
registerToolLayout("writeFile", writeFileLayout);
|
package/src/types/index.ts
CHANGED
|
@@ -269,6 +269,7 @@ export type VoiceInteractionType = "direct" | "review";
|
|
|
269
269
|
|
|
270
270
|
export type ToolToggleId =
|
|
271
271
|
| "readFile"
|
|
272
|
+
| "writeFile"
|
|
272
273
|
| "runBash"
|
|
273
274
|
| "webSearch"
|
|
274
275
|
| "fetchUrls"
|
|
@@ -281,6 +282,7 @@ export type ToolToggles = Record<ToolToggleId, boolean>;
|
|
|
281
282
|
|
|
282
283
|
export const DEFAULT_TOOL_TOGGLES: ToolToggles = {
|
|
283
284
|
readFile: true,
|
|
285
|
+
writeFile: true,
|
|
284
286
|
runBash: true,
|
|
285
287
|
webSearch: true,
|
|
286
288
|
fetchUrls: true,
|
|
@@ -53,6 +53,7 @@ function parseFetchUrlsXml(result: string): {
|
|
|
53
53
|
const attrs: Record<string, string> = {};
|
|
54
54
|
for (const attr of attrText.matchAll(/(\w+)="([^"]*)"/g)) {
|
|
55
55
|
const key = attr[1];
|
|
56
|
+
if (!key) continue;
|
|
56
57
|
const value = attr[2] ?? "";
|
|
57
58
|
attrs[key] = unescapeXmlAttribute(value);
|
|
58
59
|
}
|
|
@@ -65,6 +65,10 @@ type ExaLikeItem = {
|
|
|
65
65
|
text?: unknown;
|
|
66
66
|
lineOffset?: unknown;
|
|
67
67
|
lineLimit?: unknown;
|
|
68
|
+
success?: unknown;
|
|
69
|
+
error?: unknown;
|
|
70
|
+
remainingLines?: unknown;
|
|
71
|
+
totalLines?: unknown;
|
|
68
72
|
};
|
|
69
73
|
|
|
70
74
|
function extractExaItems(data: unknown): ExaLikeItem[] | null {
|
|
@@ -146,8 +150,9 @@ function formatExaFetchResult(result: unknown): string | null {
|
|
|
146
150
|
if (headerLine) lines.push(headerLine);
|
|
147
151
|
if (normalLines[0]) lines.push(normalLines[0]);
|
|
148
152
|
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
const firstErrorLine = errorLines[0];
|
|
154
|
+
if (firstErrorLine) {
|
|
155
|
+
lines.push(firstErrorLine);
|
|
151
156
|
} else if (contentLine) {
|
|
152
157
|
lines.push(contentLine);
|
|
153
158
|
}
|