@makefinks/daemon 0.8.0 → 0.9.1
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/system-prompt.ts +47 -41
- package/src/ai/tools/fetch-urls.ts +166 -125
- package/src/components/SettingsMenu.tsx +36 -27
- package/src/components/UrlMenu.tsx +2 -7
- package/src/components/tool-layouts/layouts/subagent.tsx +16 -0
- package/src/components/tool-layouts/layouts/url-tools.ts +145 -81
- package/src/hooks/keyboard-handlers.ts +22 -31
- package/src/types/index.ts +0 -1
- package/src/utils/derive-url-menu-items.ts +198 -37
- package/src/utils/preferences.ts +3 -0
- package/src/utils/tool-output-preview.ts +116 -27
|
@@ -34,6 +34,12 @@ function extractUrl(input: unknown): string | null {
|
|
|
34
34
|
if ("url" in input && typeof input.url === "string") {
|
|
35
35
|
return input.url;
|
|
36
36
|
}
|
|
37
|
+
if ("requests" in input && Array.isArray(input.requests)) {
|
|
38
|
+
const first = input.requests.find((item: unknown) => isRecord(item) && typeof item.url === "string");
|
|
39
|
+
if (isRecord(first) && typeof first.url === "string") {
|
|
40
|
+
return first.url;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
37
43
|
return null;
|
|
38
44
|
}
|
|
39
45
|
|
|
@@ -88,6 +94,16 @@ function formatStepLabel(step: { toolName: string; input?: unknown }): string {
|
|
|
88
94
|
|
|
89
95
|
if (step.toolName === "fetchUrls" || step.toolName === "renderUrl") {
|
|
90
96
|
const url = extractUrl(step.input);
|
|
97
|
+
if (step.toolName === "fetchUrls" && isRecord(step.input) && Array.isArray(step.input.requests)) {
|
|
98
|
+
const count = step.input.requests.filter(
|
|
99
|
+
(item: unknown) => isRecord(item) && typeof item.url === "string"
|
|
100
|
+
).length;
|
|
101
|
+
if (url) {
|
|
102
|
+
const suffix = count > 1 ? ` (+${count - 1})` : "";
|
|
103
|
+
return `${toolLabel}: ${truncateLabel(url, MAX_URL_LENGTH)}${suffix}`;
|
|
104
|
+
}
|
|
105
|
+
return toolLabel;
|
|
106
|
+
}
|
|
91
107
|
if (url) {
|
|
92
108
|
return `${toolLabel}: ${truncateLabel(url, MAX_URL_LENGTH)}`;
|
|
93
109
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { COLORS } from "../../../ui/constants";
|
|
2
2
|
import { registerToolLayout } from "../registry";
|
|
3
|
+
import type { ToolBody } from "../types";
|
|
4
|
+
import type { ToolHeader, ToolLayoutConfig } from "../types";
|
|
3
5
|
|
|
4
6
|
type UnknownRecord = Record<string, unknown>;
|
|
5
7
|
|
|
@@ -7,42 +9,77 @@ function isRecord(value: unknown): value is UnknownRecord {
|
|
|
7
9
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
interface
|
|
12
|
+
interface FetchUrlsRequestInput {
|
|
11
13
|
url: string;
|
|
12
14
|
lineOffset?: number;
|
|
13
15
|
lineLimit?: number;
|
|
14
|
-
highlightQuery?: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
type FetchUrlsResultItem = {
|
|
19
|
+
success?: unknown;
|
|
20
|
+
url?: unknown;
|
|
21
|
+
text?: unknown;
|
|
22
|
+
lineOffset?: unknown;
|
|
23
|
+
lineLimit?: unknown;
|
|
24
|
+
remainingLines?: unknown;
|
|
25
|
+
error?: unknown;
|
|
26
|
+
title?: unknown;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function extractFetchUrlsRequests(input: unknown): FetchUrlsRequestInput[] | null {
|
|
30
|
+
if (!isRecord(input)) return null;
|
|
31
|
+
if (!("requests" in input) || !Array.isArray(input.requests)) return null;
|
|
32
|
+
|
|
33
|
+
const requests: FetchUrlsRequestInput[] = [];
|
|
34
|
+
for (const item of input.requests) {
|
|
35
|
+
if (!isRecord(item)) continue;
|
|
36
|
+
if (!("url" in item) || typeof item.url !== "string") continue;
|
|
37
|
+
const lineOffset =
|
|
38
|
+
"lineOffset" in item && typeof item.lineOffset === "number" ? item.lineOffset : undefined;
|
|
39
|
+
const lineLimit = "lineLimit" in item && typeof item.lineLimit === "number" ? item.lineLimit : undefined;
|
|
40
|
+
requests.push({ url: item.url, lineOffset, lineLimit });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return requests.length > 0 ? requests : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function extractRenderUrlInput(input: unknown): FetchUrlsRequestInput | null {
|
|
47
|
+
if (!input) return null;
|
|
18
48
|
if (!isRecord(input)) return null;
|
|
19
49
|
if (!("url" in input) || typeof input.url !== "string") return null;
|
|
20
50
|
|
|
21
51
|
const lineOffset =
|
|
22
52
|
"lineOffset" in input && typeof input.lineOffset === "number" ? input.lineOffset : undefined;
|
|
23
53
|
const lineLimit = "lineLimit" in input && typeof input.lineLimit === "number" ? input.lineLimit : undefined;
|
|
24
|
-
|
|
25
|
-
"highlightQuery" in input && typeof input.highlightQuery === "string" ? input.highlightQuery : undefined;
|
|
26
|
-
return { url: input.url, lineOffset, lineLimit, highlightQuery };
|
|
54
|
+
return { url: input.url, lineOffset, lineLimit };
|
|
27
55
|
}
|
|
28
56
|
|
|
29
|
-
function
|
|
30
|
-
if (!
|
|
31
|
-
|
|
57
|
+
function extractFetchUrlsResults(result?: unknown): FetchUrlsResultItem[] | null {
|
|
58
|
+
if (!result || typeof result !== "object") return null;
|
|
59
|
+
const record = result as Record<string, unknown>;
|
|
60
|
+
const container = extractToolDataContainer(record);
|
|
61
|
+
if (!isRecord(container)) return null;
|
|
62
|
+
|
|
63
|
+
if (Array.isArray(container.results)) {
|
|
64
|
+
return container.results.filter((item): item is FetchUrlsResultItem => isRecord(item));
|
|
65
|
+
}
|
|
32
66
|
|
|
33
|
-
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function mergeFetchUrlsDefaults(
|
|
71
|
+
input: FetchUrlsRequestInput,
|
|
72
|
+
result?: FetchUrlsResultItem | null
|
|
73
|
+
): FetchUrlsRequestInput {
|
|
74
|
+
if (!result) return input;
|
|
34
75
|
const lineOffset =
|
|
35
|
-
input.lineOffset ?? (typeof
|
|
36
|
-
const lineLimit =
|
|
37
|
-
input.lineLimit ?? (typeof resultRecord.lineLimit === "number" ? resultRecord.lineLimit : undefined);
|
|
76
|
+
input.lineOffset ?? (typeof result.lineOffset === "number" ? result.lineOffset : undefined);
|
|
77
|
+
const lineLimit = input.lineLimit ?? (typeof result.lineLimit === "number" ? result.lineLimit : undefined);
|
|
38
78
|
|
|
39
79
|
return { ...input, lineOffset, lineLimit };
|
|
40
80
|
}
|
|
41
81
|
|
|
42
|
-
function formatFetchUrlsHeader(input:
|
|
43
|
-
if (input.highlightQuery) {
|
|
44
|
-
return `highlight: "${input.highlightQuery}"`;
|
|
45
|
-
}
|
|
82
|
+
function formatFetchUrlsHeader(input: FetchUrlsRequestInput): string {
|
|
46
83
|
const parts: string[] = [];
|
|
47
84
|
if (input.lineOffset !== undefined) {
|
|
48
85
|
parts.push(`lineOffset=${input.lineOffset}`);
|
|
@@ -57,6 +94,10 @@ function normalizeWhitespace(text: string): string {
|
|
|
57
94
|
return text.replace(/\r\n/g, "\n").replace(/\t/g, " ");
|
|
58
95
|
}
|
|
59
96
|
|
|
97
|
+
function escapeXmlAttribute(value: string): string {
|
|
98
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
99
|
+
}
|
|
100
|
+
|
|
60
101
|
type ExaLikeItem = {
|
|
61
102
|
title?: unknown;
|
|
62
103
|
url?: unknown;
|
|
@@ -64,6 +105,9 @@ type ExaLikeItem = {
|
|
|
64
105
|
lineOffset?: unknown;
|
|
65
106
|
lineLimit?: unknown;
|
|
66
107
|
remainingLines?: unknown;
|
|
108
|
+
totalLines?: unknown;
|
|
109
|
+
error?: unknown;
|
|
110
|
+
success?: unknown;
|
|
67
111
|
};
|
|
68
112
|
|
|
69
113
|
function formatExaItemLabel(item: ExaLikeItem): string {
|
|
@@ -78,76 +122,69 @@ function extractToolDataContainer(result: UnknownRecord): unknown {
|
|
|
78
122
|
}
|
|
79
123
|
|
|
80
124
|
function formatFetchUrlsResult(result: unknown): string[] | null {
|
|
125
|
+
if (typeof result === "string") {
|
|
126
|
+
const lines = result.split("\n");
|
|
127
|
+
const MAX_LINES = 8;
|
|
128
|
+
if (lines.length <= MAX_LINES) return lines;
|
|
129
|
+
return [...lines.slice(0, MAX_LINES - 1), " ..."];
|
|
130
|
+
}
|
|
81
131
|
if (!isRecord(result)) return null;
|
|
82
132
|
if (result.success === false && typeof result.error === "string") {
|
|
83
133
|
return [`error: ${result.error}`];
|
|
84
134
|
}
|
|
85
135
|
if (result.success !== true) return null;
|
|
86
136
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
137
|
+
const items = extractFetchUrlsResults(result);
|
|
138
|
+
if (!items) return null;
|
|
90
139
|
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const label = formatExaItemLabel(candidate);
|
|
94
|
-
const url = typeof candidate.url === "string" ? candidate.url : "";
|
|
95
|
-
const title = typeof candidate.title === "string" ? candidate.title : "";
|
|
96
|
-
const lineOffset = typeof candidate.lineOffset === "number" ? candidate.lineOffset : undefined;
|
|
97
|
-
const lineLimit = typeof candidate.lineLimit === "number" ? candidate.lineLimit : undefined;
|
|
98
|
-
const remainingLines =
|
|
99
|
-
typeof candidate.remainingLines === "number" || candidate.remainingLines === null
|
|
100
|
-
? candidate.remainingLines
|
|
101
|
-
: undefined;
|
|
102
|
-
const rangeParts: string[] = [];
|
|
103
|
-
if (lineOffset !== undefined) rangeParts.push(`lineOffset=${lineOffset}`);
|
|
104
|
-
if (lineLimit !== undefined) rangeParts.push(`lineLimit=${lineLimit}`);
|
|
105
|
-
if (remainingLines !== undefined) {
|
|
106
|
-
rangeParts.push(remainingLines === null ? "remainingLines=unknown" : `remainingLines=${remainingLines}`);
|
|
107
|
-
}
|
|
108
|
-
const remainingSuffix = rangeParts.length > 0 ? ` (${rangeParts.join(", ")})` : "";
|
|
109
|
-
|
|
110
|
-
const headerBase = url && title ? `${label} — ${url}` : label;
|
|
111
|
-
const header = `${headerBase}${remainingSuffix}`;
|
|
112
|
-
|
|
113
|
-
const text = typeof candidate.text === "string" ? candidate.text : "";
|
|
114
|
-
if (!text.trim()) return [header];
|
|
115
|
-
|
|
116
|
-
const MAX_LINES = 4;
|
|
140
|
+
const lines: string[] = ["<fetchUrls>"];
|
|
141
|
+
const MAX_LINES = 2;
|
|
117
142
|
const MAX_CHARS = 160;
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
143
|
+
const maxItems = 3;
|
|
144
|
+
|
|
145
|
+
for (const item of items.slice(0, maxItems)) {
|
|
146
|
+
const candidate = item as ExaLikeItem;
|
|
147
|
+
const url = typeof candidate.url === "string" ? candidate.url : "";
|
|
148
|
+
if (!url) continue;
|
|
149
|
+
|
|
150
|
+
const attributes: string[] = [`href="${escapeXmlAttribute(url)}"`];
|
|
151
|
+
if (typeof candidate.lineOffset === "number") attributes.push(`lineOffset="${candidate.lineOffset}"`);
|
|
152
|
+
if (typeof candidate.lineLimit === "number") attributes.push(`lineLimit="${candidate.lineLimit}"`);
|
|
153
|
+
if (typeof candidate.totalLines === "number") attributes.push(`totalLines="${candidate.totalLines}"`);
|
|
154
|
+
if (typeof candidate.remainingLines === "number") {
|
|
155
|
+
attributes.push(`remainingLines="${candidate.remainingLines}"`);
|
|
156
|
+
} else if (candidate.remainingLines === null) {
|
|
157
|
+
attributes.push(`remainingLines="unknown"`);
|
|
158
|
+
}
|
|
127
159
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
160
|
+
if (candidate.success === false && typeof candidate.error === "string") {
|
|
161
|
+
attributes.push(`error="${escapeXmlAttribute(candidate.error)}"`);
|
|
162
|
+
lines.push(` <url ${attributes.join(" ")} />`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
132
165
|
|
|
133
|
-
|
|
166
|
+
const text = typeof candidate.text === "string" ? candidate.text : "";
|
|
167
|
+
if (!text.trim()) {
|
|
168
|
+
lines.push(` <url ${attributes.join(" ")} />`);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
134
171
|
|
|
135
|
-
|
|
136
|
-
|
|
172
|
+
const snippetLines = normalizeWhitespace(text)
|
|
173
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
174
|
+
.trim()
|
|
175
|
+
.split("\n")
|
|
176
|
+
.slice(0, MAX_LINES)
|
|
177
|
+
.map((l) => (l.length > MAX_CHARS ? `${l.slice(0, MAX_CHARS - 1)}…` : l));
|
|
137
178
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const truncated = clean.length > MAX_CHARS ? `${clean.slice(0, MAX_CHARS - 1)}…` : clean;
|
|
142
|
-
lines.push(` ${idx + 1}. "${truncated}"`);
|
|
179
|
+
lines.push(` <url ${attributes.join(" ")}>`);
|
|
180
|
+
for (const line of snippetLines) {
|
|
181
|
+
lines.push(` ${escapeXmlAttribute(line)}`);
|
|
143
182
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (highlights.length > MAX_HIGHLIGHTS) {
|
|
147
|
-
lines.push(` ...and ${highlights.length - MAX_HIGHLIGHTS} more`);
|
|
183
|
+
lines.push(" </url>");
|
|
148
184
|
}
|
|
149
185
|
|
|
150
|
-
|
|
186
|
+
lines.push("</fetchUrls>");
|
|
187
|
+
return lines.length > 2 ? lines : null;
|
|
151
188
|
}
|
|
152
189
|
|
|
153
190
|
function formatRenderUrlResult(result: unknown): string[] | null {
|
|
@@ -188,15 +225,38 @@ export const fetchUrlsLayout: ToolLayoutConfig = {
|
|
|
188
225
|
abbreviation: "fetch",
|
|
189
226
|
|
|
190
227
|
getHeader: (input, result): ToolHeader | null => {
|
|
191
|
-
const
|
|
192
|
-
if (!
|
|
193
|
-
const
|
|
228
|
+
const requests = extractFetchUrlsRequests(input);
|
|
229
|
+
if (!requests) return null;
|
|
230
|
+
const items = extractFetchUrlsResults(result);
|
|
231
|
+
const firstResult = items?.[0] ?? null;
|
|
232
|
+
const first = mergeFetchUrlsDefaults(requests[0] as FetchUrlsRequestInput, firstResult);
|
|
233
|
+
if (requests.length === 1) {
|
|
234
|
+
const headerSuffix = formatFetchUrlsHeader(first);
|
|
235
|
+
return {
|
|
236
|
+
primary: first.url,
|
|
237
|
+
secondary: headerSuffix || undefined,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
194
241
|
return {
|
|
195
|
-
primary:
|
|
196
|
-
secondary: headerSuffix || undefined,
|
|
242
|
+
primary: `${requests.length} urls`,
|
|
197
243
|
};
|
|
198
244
|
},
|
|
199
245
|
|
|
246
|
+
getBody: (input, result): ToolBody | null => {
|
|
247
|
+
const requests = extractFetchUrlsRequests(input);
|
|
248
|
+
if (!requests) return null;
|
|
249
|
+
if (requests.length === 1) return null;
|
|
250
|
+
const items = extractFetchUrlsResults(result) ?? [];
|
|
251
|
+
const lines = requests.map((request, index) => {
|
|
252
|
+
const merged = mergeFetchUrlsDefaults(request, items[index] ?? null);
|
|
253
|
+
const suffix = formatFetchUrlsHeader(merged);
|
|
254
|
+
const text = suffix ? `${merged.url} ${suffix}` : merged.url;
|
|
255
|
+
return { text, color: COLORS.REASONING_DIM };
|
|
256
|
+
});
|
|
257
|
+
return { lines };
|
|
258
|
+
},
|
|
259
|
+
|
|
200
260
|
formatResult: formatFetchUrlsResult,
|
|
201
261
|
};
|
|
202
262
|
|
|
@@ -204,11 +264,15 @@ export const renderUrlLayout: ToolLayoutConfig = {
|
|
|
204
264
|
abbreviation: "render",
|
|
205
265
|
|
|
206
266
|
getHeader: (input, result): ToolHeader | null => {
|
|
207
|
-
const urlInput =
|
|
267
|
+
const urlInput = extractRenderUrlInput(input);
|
|
208
268
|
if (!urlInput) return null;
|
|
209
|
-
const
|
|
269
|
+
const merged = mergeFetchUrlsDefaults(
|
|
270
|
+
urlInput,
|
|
271
|
+
isRecord(result) ? (result as FetchUrlsResultItem) : null
|
|
272
|
+
);
|
|
273
|
+
const headerSuffix = formatFetchUrlsHeader(merged);
|
|
210
274
|
return {
|
|
211
|
-
primary:
|
|
275
|
+
primary: merged.url,
|
|
212
276
|
secondary: headerSuffix || undefined,
|
|
213
277
|
};
|
|
214
278
|
},
|
|
@@ -355,42 +355,33 @@ export function handleSettingsMenuKey(key: KeyEvent, ctx: SettingsMenuContext):
|
|
|
355
355
|
}
|
|
356
356
|
settingIdx++;
|
|
357
357
|
|
|
358
|
-
if (ctx.
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return true;
|
|
366
|
-
}
|
|
367
|
-
settingIdx++;
|
|
368
|
-
|
|
369
|
-
if (ctx.selectedIdx === settingIdx) {
|
|
370
|
-
const speeds: SpeechSpeed[] = [1.0, 1.25, 1.5, 1.75, 2.0];
|
|
371
|
-
const currentSpeed = ctx.manager.speechSpeed;
|
|
372
|
-
const currentIndex = speeds.indexOf(currentSpeed);
|
|
373
|
-
const nextIndex = (currentIndex + 1) % speeds.length;
|
|
374
|
-
const nextSpeed = speeds[nextIndex] ?? 1.0;
|
|
375
|
-
ctx.manager.speechSpeed = nextSpeed;
|
|
376
|
-
ctx.setSpeechSpeed(nextSpeed);
|
|
377
|
-
ctx.persistPreferences({ speechSpeed: nextSpeed });
|
|
378
|
-
key.preventDefault();
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
settingIdx++;
|
|
358
|
+
if (ctx.selectedIdx === settingIdx) {
|
|
359
|
+
const next = !ctx.manager.memoryEnabled;
|
|
360
|
+
ctx.manager.memoryEnabled = next;
|
|
361
|
+
ctx.setMemoryEnabled(next);
|
|
362
|
+
ctx.persistPreferences({ memoryEnabled: next });
|
|
363
|
+
key.preventDefault();
|
|
364
|
+
return true;
|
|
382
365
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
ctx.setMemoryEnabled(next);
|
|
388
|
-
ctx.persistPreferences({ memoryEnabled: next });
|
|
366
|
+
settingIdx++;
|
|
367
|
+
|
|
368
|
+
if (ctx.selectedIdx === settingIdx) {
|
|
369
|
+
if (ctx.interactionMode !== "voice") {
|
|
389
370
|
key.preventDefault();
|
|
390
371
|
return true;
|
|
391
372
|
}
|
|
392
|
-
|
|
373
|
+
const speeds: SpeechSpeed[] = [1.0, 1.25, 1.5, 1.75, 2.0];
|
|
374
|
+
const currentSpeed = ctx.manager.speechSpeed;
|
|
375
|
+
const currentIndex = speeds.indexOf(currentSpeed);
|
|
376
|
+
const nextIndex = (currentIndex + 1) % speeds.length;
|
|
377
|
+
const nextSpeed = speeds[nextIndex] ?? 1.0;
|
|
378
|
+
ctx.manager.speechSpeed = nextSpeed;
|
|
379
|
+
ctx.setSpeechSpeed(nextSpeed);
|
|
380
|
+
ctx.persistPreferences({ speechSpeed: nextSpeed });
|
|
381
|
+
key.preventDefault();
|
|
382
|
+
return true;
|
|
393
383
|
}
|
|
384
|
+
settingIdx++;
|
|
394
385
|
|
|
395
386
|
if (ctx.selectedIdx === settingIdx) {
|
|
396
387
|
const next = !ctx.showFullReasoning;
|