@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.
@@ -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 type { ToolLayoutConfig, ToolHeader } from "../types";
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 FetchUrlsInput {
12
+ interface FetchUrlsRequestInput {
11
13
  url: string;
12
14
  lineOffset?: number;
13
15
  lineLimit?: number;
14
- highlightQuery?: string;
15
16
  }
16
17
 
17
- function extractFetchUrlsInput(input: unknown): FetchUrlsInput | null {
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
- const highlightQuery =
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 mergeFetchUrlsDefaults(input: FetchUrlsInput | null, result?: unknown): FetchUrlsInput | null {
30
- if (!input) return null;
31
- if (!result || typeof result !== "object") return input;
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
- const resultRecord = result as Record<string, unknown>;
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 resultRecord.lineOffset === "number" ? resultRecord.lineOffset : undefined);
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: FetchUrlsInput): string {
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, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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
- if (Array.isArray(result.highlights)) {
88
- return formatHighlightsResult(result);
89
- }
137
+ const items = extractFetchUrlsResults(result);
138
+ if (!items) return null;
90
139
 
91
- const data = extractToolDataContainer(result);
92
- const candidate = isRecord(data) ? (data as ExaLikeItem) : {};
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 snippet = normalizeWhitespace(text)
119
- .replace(/\n{3,}/g, "\n\n")
120
- .trim()
121
- .split("\n")
122
- .slice(0, MAX_LINES)
123
- .map((l) => (l.length > MAX_CHARS ? `${l.slice(0, MAX_CHARS - 1)}…` : l));
124
-
125
- return [header, ...snippet];
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
- function formatHighlightsResult(result: UnknownRecord): string[] {
129
- const highlights = result.highlights as unknown[];
130
- const highlightQuery = typeof result.highlightQuery === "string" ? result.highlightQuery : "";
131
- const count = highlights.length;
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
- const lines: string[] = [`${count} highlight${count !== 1 ? "s" : ""} for "${highlightQuery}"`];
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
- const MAX_HIGHLIGHTS = 3;
136
- const MAX_CHARS = 120;
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
- highlights.slice(0, MAX_HIGHLIGHTS).forEach((h, idx) => {
139
- if (typeof h === "string") {
140
- const clean = h.replace(/\n+/g, " ").trim();
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
- return lines;
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 urlInput = mergeFetchUrlsDefaults(extractFetchUrlsInput(input), result);
192
- if (!urlInput) return null;
193
- const headerSuffix = formatFetchUrlsHeader(urlInput);
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: urlInput.url,
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 = mergeFetchUrlsDefaults(extractFetchUrlsInput(input), result);
267
+ const urlInput = extractRenderUrlInput(input);
208
268
  if (!urlInput) return null;
209
- const headerSuffix = formatFetchUrlsHeader(urlInput);
269
+ const merged = mergeFetchUrlsDefaults(
270
+ urlInput,
271
+ isRecord(result) ? (result as FetchUrlsResultItem) : null
272
+ );
273
+ const headerSuffix = formatFetchUrlsHeader(merged);
210
274
  return {
211
- primary: urlInput.url,
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.interactionMode === "voice") {
359
- if (ctx.selectedIdx === settingIdx) {
360
- const next = !ctx.manager.memoryEnabled;
361
- ctx.manager.memoryEnabled = next;
362
- ctx.setMemoryEnabled(next);
363
- ctx.persistPreferences({ memoryEnabled: next });
364
- key.preventDefault();
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
- if (ctx.interactionMode !== "voice") {
384
- if (ctx.selectedIdx === settingIdx) {
385
- const next = !ctx.manager.memoryEnabled;
386
- ctx.manager.memoryEnabled = next;
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
- settingIdx++;
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;
@@ -447,7 +447,6 @@ export interface UrlMenuItem {
447
447
  url: string;
448
448
  groundedCount: number;
449
449
  readPercent?: number;
450
- highlightsCount?: number;
451
450
  status: "ok" | "error";
452
451
  error?: string;
453
452
  lastSeenIndex: number;