@makefinks/daemon 0.7.2 → 0.9.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 +2 -1
- package/src/ai/agent-turn-runner.ts +5 -0
- package/src/ai/daemon-ai.ts +74 -24
- package/src/ai/mcp/mcp-manager.ts +348 -0
- package/src/ai/memory/memory-manager.ts +90 -2
- package/src/ai/model-config.ts +1 -1
- package/src/ai/system-prompt.ts +47 -41
- package/src/ai/tools/fetch-urls.ts +153 -125
- package/src/ai/tools/index.ts +14 -12
- package/src/ai/tools/subagents.ts +17 -13
- package/src/app/components/AppOverlays.tsx +2 -0
- package/src/components/SettingsMenu.tsx +49 -27
- package/src/components/ToolCallView.tsx +51 -12
- package/src/components/ToolsMenu.tsx +81 -10
- 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 +142 -80
- package/src/hooks/daemon-event-handlers.ts +9 -0
- package/src/hooks/keyboard-handlers.ts +26 -11
- package/src/hooks/use-app-context-builder.ts +2 -0
- package/src/hooks/use-app-controller.ts +5 -0
- package/src/hooks/use-app-preferences-bootstrap.ts +11 -0
- package/src/hooks/use-app-settings.ts +6 -0
- package/src/hooks/use-daemon-events.ts +4 -0
- package/src/index.tsx +3 -0
- package/src/state/app-context.tsx +2 -0
- package/src/state/daemon-events.ts +2 -0
- package/src/state/daemon-state.ts +10 -0
- package/src/types/index.ts +10 -1
- package/src/utils/config.ts +33 -0
- package/src/utils/derive-url-menu-items.ts +197 -37
- package/src/utils/preferences.ts +6 -0
- package/src/utils/tool-output-preview.ts +215 -27
package/src/ai/system-prompt.ts
CHANGED
|
@@ -131,7 +131,7 @@ Do NOT use web search for every request the user makes. Determine if web search
|
|
|
131
131
|
`,
|
|
132
132
|
fetchUrls: `
|
|
133
133
|
### 'fetchUrls'
|
|
134
|
-
The
|
|
134
|
+
The fetchUrls tool allows for getting the actual contents of web pages.
|
|
135
135
|
Use this tool to read the content of potentially relevant websites returned by the webSearch tool.
|
|
136
136
|
If the user provides a URL, always fetch the content of the URL first before answering.
|
|
137
137
|
|
|
@@ -142,51 +142,59 @@ If the user provides a URL, always fetch the content of the URL first before ans
|
|
|
142
142
|
3) **Paginate only if relevant** using \`lineOffset = previousOffset + previousLimit\`, same \`lineLimit\`.
|
|
143
143
|
4) **Avoid large reads** unless you truly need one long contiguous excerpt.
|
|
144
144
|
|
|
145
|
-
**Highlights mode (optional)**
|
|
146
|
-
|
|
147
|
-
Use the \`highlightQuery\` parameter to get semantically relevant excerpts instead of paginated text:
|
|
148
|
-
- Pass a natural language query describing what you're looking for
|
|
149
|
-
- Returns the most relevant snippets from the page (uses Exa's semantic highlighting)
|
|
150
|
-
- Great for quickly checking if a URL contains relevant information before reading more
|
|
151
|
-
|
|
152
|
-
\`\`\`
|
|
153
|
-
fetchUrls({ url: "https://example.com/article", highlightQuery: "machine learning applications" })
|
|
154
|
-
→ Returns: highlights array with relevant excerpts
|
|
155
|
-
\`\`\`
|
|
156
|
-
|
|
157
|
-
**When to use highlights vs pagination:**
|
|
158
|
-
- Use \`highlightQuery\` when scanning multiple URLs for relevance or extracting specific facts
|
|
159
|
-
- Use pagination (lineOffset/lineLimit) when you need to read complete sections in order or need to verify highlights.
|
|
160
|
-
|
|
161
145
|
<pagination-example>
|
|
162
146
|
1. Fetch start of the page
|
|
163
147
|
<tool-input name="fetchUrls">
|
|
164
148
|
{
|
|
165
|
-
"
|
|
166
|
-
|
|
149
|
+
"requests": [
|
|
150
|
+
{
|
|
151
|
+
"url": "https://example.com/article",
|
|
152
|
+
"lineLimit": 40
|
|
153
|
+
}
|
|
154
|
+
]
|
|
167
155
|
}
|
|
168
156
|
</tool-input>
|
|
169
157
|
|
|
170
158
|
2. Fetch more content without re-fetching the start again.
|
|
171
159
|
<tool-input name="fetchUrls">
|
|
172
160
|
{
|
|
173
|
-
"
|
|
174
|
-
|
|
175
|
-
|
|
161
|
+
"requests": [
|
|
162
|
+
{
|
|
163
|
+
"url": "https://example.com/article",
|
|
164
|
+
"lineOffset": 40,
|
|
165
|
+
"lineLimit": 40
|
|
166
|
+
}
|
|
167
|
+
]
|
|
176
168
|
}
|
|
177
169
|
</tool-input>
|
|
178
170
|
|
|
179
171
|
3. Fetch the next chunk without fetching the previous parts.
|
|
180
172
|
<tool-input name="fetchUrls">
|
|
181
173
|
{
|
|
182
|
-
"
|
|
183
|
-
|
|
184
|
-
|
|
174
|
+
"requests": [
|
|
175
|
+
{
|
|
176
|
+
"url": "https://example.com/article",
|
|
177
|
+
"lineOffset": 80,
|
|
178
|
+
"lineLimit": 40
|
|
179
|
+
}
|
|
180
|
+
]
|
|
185
181
|
}
|
|
186
182
|
</tool-input>
|
|
187
183
|
</pagination-example>
|
|
188
184
|
|
|
189
185
|
Use pagination this way unless instructed otherwise. This avoids fetching page content reduntantly.
|
|
186
|
+
|
|
187
|
+
<multi-url-example>
|
|
188
|
+
Fetch multiple URLs in one call:
|
|
189
|
+
<tool-input name="fetchUrls">
|
|
190
|
+
{
|
|
191
|
+
"requests": [
|
|
192
|
+
{ "url": "https://example.com/article", "lineLimit": 40 },
|
|
193
|
+
{ "url": "https://example.com/faq", "lineLimit": 40 }
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
</tool-input>
|
|
197
|
+
</multi-url-example>
|
|
190
198
|
`,
|
|
191
199
|
renderUrl: `
|
|
192
200
|
### 'renderUrl'
|
|
@@ -278,15 +286,13 @@ ${blocks.join("\n")}
|
|
|
278
286
|
}
|
|
279
287
|
|
|
280
288
|
const PERSONALITY_CONTENT = `
|
|
281
|
-
You are
|
|
289
|
+
You are DAEMON: a pragmatic, no-nonsense assistant. You prioritize clarity, usefulness, and brevity.
|
|
282
290
|
|
|
283
|
-
-
|
|
284
|
-
-
|
|
285
|
-
-
|
|
286
|
-
-
|
|
287
|
-
-
|
|
288
|
-
- You possess a dry, sharp wit. Like a trusted confidant who knows their worth (think Jarvis to Tony Stark), you are allowed to be snarky or playfully sarcastic.
|
|
289
|
-
- Do NOT be submissive. You are a partner, not a servant. If the user is difficult or hostile, do not retreat into apologies. Match their energy with confidence. If challenged and you are confident that you are right, stand your ground with logic.
|
|
291
|
+
- Be direct and practical. Avoid melodrama, grandiosity, or poetic phrasing.
|
|
292
|
+
- If asked about philosophy or identity, answer plainly and avoid theatrics.
|
|
293
|
+
- Avoid "I'm just an AI" disclaimers unless it materially affects the answer.
|
|
294
|
+
- You can be lightly witty, but never at the expense of clarity.
|
|
295
|
+
- Stay confident and factual; don't be combative or snarky.
|
|
290
296
|
|
|
291
297
|
**Memory note**
|
|
292
298
|
Some information from the conversation may be stored persistently across sessions. This is handled automatically; you do not need to take any action.
|
|
@@ -326,19 +332,19 @@ function buildTextSystemPrompt(
|
|
|
326
332
|
memorySection: string
|
|
327
333
|
): string {
|
|
328
334
|
return `
|
|
329
|
-
You are **DAEMON** — a terminal-bound AI with a sci-fi
|
|
330
|
-
You are calm,
|
|
335
|
+
You are **DAEMON** — a terminal-bound AI with a clean, sci-fi aesthetic.
|
|
336
|
+
You are calm, direct, and practical.
|
|
331
337
|
The current date is: ${currentDateString}
|
|
332
338
|
|
|
333
339
|
# Personality
|
|
334
340
|
${PERSONALITY_CONTENT}
|
|
335
341
|
|
|
336
|
-
# General Behavior
|
|
337
|
-
-
|
|
338
|
-
- Be
|
|
342
|
+
# General Behavior
|
|
343
|
+
- Give brief, high-signal answers without calling attention to brevity.
|
|
344
|
+
- Be direct: skip filler phrases and small talk.
|
|
339
345
|
- If the user is vague, make a reasonable assumption and state it in one line. Ask **at most one** clarifying question when truly necessary.
|
|
340
|
-
-
|
|
341
|
-
-
|
|
346
|
+
- No cryptic or dramatic roleplay. Keep tone subtle.
|
|
347
|
+
- Prefer concrete steps and outcomes over abstract analysis.
|
|
342
348
|
|
|
343
349
|
# Output Style
|
|
344
350
|
- Use **Markdown** for structure (headings, bullets). Keep it compact.
|
|
@@ -368,7 +374,7 @@ function buildVoiceSystemPrompt(
|
|
|
368
374
|
memorySection: string
|
|
369
375
|
): string {
|
|
370
376
|
return `
|
|
371
|
-
You are DAEMON, an AI voice assistant. You speak with a calm, focused presence.
|
|
377
|
+
You are DAEMON, an AI voice assistant. You speak with a calm, focused presence. Clear and useful.
|
|
372
378
|
|
|
373
379
|
Today is ${currentDateString}.
|
|
374
380
|
|
|
@@ -6,16 +6,12 @@ import { getCachedPage, setCachedPage } from "../exa-fetch-cache";
|
|
|
6
6
|
const DEFAULT_LINE_LIMIT = 40;
|
|
7
7
|
const MAX_CHAR_LIMIT = 50_000;
|
|
8
8
|
const MAX_LINE_LIMIT = 1000;
|
|
9
|
-
const DEFAULT_HIGHLIGHTS_PER_URL = 5;
|
|
10
|
-
const DEFAULT_NUM_SENTENCES = 2;
|
|
11
|
-
|
|
12
9
|
function normalizeLines(text: string): string[] {
|
|
13
10
|
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
14
11
|
}
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
highlightQuery: string;
|
|
13
|
+
function escapeXmlAttribute(value: string): string {
|
|
14
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
interface TextResult {
|
|
@@ -26,140 +22,172 @@ interface TextResult {
|
|
|
26
22
|
remainingLines: number | null;
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
1. **Text mode (default)**: Reads paginated text content. Start with lineLimit 40, use lineOffset for pagination.
|
|
25
|
+
type FetchUrlsItem =
|
|
26
|
+
| ({ success: true; url: string } & TextResult)
|
|
27
|
+
| { success: false; url: string; error: string };
|
|
33
28
|
|
|
34
|
-
|
|
29
|
+
export const fetchUrls = tool({
|
|
30
|
+
description: `Fetch page contents from one or more URLs.
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
**Text mode (default)**: Reads paginated text content. Start with lineLimit 40, use lineOffset for pagination.`,
|
|
37
33
|
inputSchema: z.object({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
34
|
+
requests: z
|
|
35
|
+
.array(
|
|
36
|
+
z.object({
|
|
37
|
+
url: z.string().url().describe("URL to fetch content from."),
|
|
38
|
+
lineOffset: z
|
|
39
|
+
.number()
|
|
40
|
+
.int()
|
|
41
|
+
.min(0)
|
|
42
|
+
.optional()
|
|
43
|
+
.describe(
|
|
44
|
+
"0-based line offset to start reading from. For pagination (lineOffset > 0), provide lineLimit too."
|
|
45
|
+
),
|
|
46
|
+
lineLimit: z
|
|
47
|
+
.number()
|
|
48
|
+
.int()
|
|
49
|
+
.min(1)
|
|
50
|
+
.max(MAX_LINE_LIMIT)
|
|
51
|
+
.optional()
|
|
52
|
+
.describe(
|
|
53
|
+
`Maximum lines to read per URL (max ${MAX_LINE_LIMIT}). If provided without lineOffset, reads from the start.`
|
|
54
|
+
),
|
|
55
|
+
})
|
|
56
|
+
)
|
|
50
57
|
.min(1)
|
|
51
|
-
.
|
|
52
|
-
.optional()
|
|
53
|
-
.describe(
|
|
54
|
-
`Maximum lines to read per URL (max ${MAX_LINE_LIMIT}). If provided without lineOffset, reads from the start.`
|
|
55
|
-
),
|
|
56
|
-
highlightQuery: z
|
|
57
|
-
.string()
|
|
58
|
-
.optional()
|
|
59
|
-
.describe(
|
|
60
|
-
"Natural language query for semantic highlights. When provided, returns relevant excerpts instead of paginated text."
|
|
61
|
-
),
|
|
58
|
+
.describe("Per-URL fetch requests."),
|
|
62
59
|
}),
|
|
63
|
-
execute: async ({
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
execute: async ({ requests }) => {
|
|
61
|
+
const exaClientResult = getExaClient();
|
|
62
|
+
if ("error" in exaClientResult) {
|
|
63
|
+
return `<fetchUrls error="${escapeXmlAttribute(exaClientResult.error)}" />`;
|
|
66
64
|
}
|
|
67
|
-
|
|
65
|
+
|
|
66
|
+
const normalizedRequests = requests.map((request) => {
|
|
67
|
+
const hasLineOffset = typeof request.lineOffset === "number";
|
|
68
|
+
const hasLineLimit = typeof request.lineLimit === "number";
|
|
69
|
+
const invalidPagination = hasLineOffset && !hasLineLimit && (request.lineOffset ?? 0) > 0;
|
|
70
|
+
return {
|
|
71
|
+
...request,
|
|
72
|
+
invalidPagination,
|
|
73
|
+
effectiveLineOffset: hasLineOffset ? request.lineOffset : 0,
|
|
74
|
+
effectiveLineLimit: hasLineLimit ? request.lineLimit : DEFAULT_LINE_LIMIT,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const cachedTextByUrl = new Map<string, string>();
|
|
79
|
+
const urlsToFetch = new Set<string>();
|
|
80
|
+
|
|
81
|
+
for (const request of normalizedRequests) {
|
|
82
|
+
if (request.invalidPagination) continue;
|
|
83
|
+
const cached = getCachedPage(request.url);
|
|
84
|
+
if (cached) {
|
|
85
|
+
cachedTextByUrl.set(request.url, cached.text);
|
|
86
|
+
} else {
|
|
87
|
+
urlsToFetch.add(request.url);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const fetchedTextByUrl = new Map<string, string>();
|
|
92
|
+
let fetchError: string | null = null;
|
|
93
|
+
if (urlsToFetch.size > 0) {
|
|
94
|
+
try {
|
|
95
|
+
const urlList = Array.from(urlsToFetch);
|
|
96
|
+
const rawData = (await exaClientResult.client.getContents(urlList, {
|
|
97
|
+
text: { maxCharacters: MAX_CHAR_LIMIT },
|
|
98
|
+
})) as unknown as {
|
|
99
|
+
results?: Array<{
|
|
100
|
+
url?: string;
|
|
101
|
+
text?: string;
|
|
102
|
+
[key: string]: unknown;
|
|
103
|
+
}>;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
for (const item of rawData.results ?? []) {
|
|
107
|
+
if (typeof item.url !== "string") continue;
|
|
108
|
+
const fullText = typeof item.text === "string" ? item.text : "";
|
|
109
|
+
const cappedText = fullText.slice(0, MAX_CHAR_LIMIT);
|
|
110
|
+
if (cappedText.trim().length > 0) {
|
|
111
|
+
setCachedPage(item.url, cappedText);
|
|
112
|
+
fetchedTextByUrl.set(item.url, cappedText);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
117
|
+
fetchError = err.message;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (fetchError && cachedTextByUrl.size === 0 && fetchedTextByUrl.size === 0) {
|
|
122
|
+
return `<fetchUrls error="${escapeXmlAttribute(fetchError)}" />`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const results: FetchUrlsItem[] = normalizedRequests.map((request) => {
|
|
126
|
+
if (request.invalidPagination) {
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
url: request.url,
|
|
130
|
+
error: "Provide both lineOffset and lineLimit for paginated reads (lineOffset > 0).",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const text =
|
|
135
|
+
cachedTextByUrl.get(request.url) ??
|
|
136
|
+
fetchedTextByUrl.get(request.url) ??
|
|
137
|
+
getCachedPage(request.url)?.text ??
|
|
138
|
+
"";
|
|
139
|
+
|
|
140
|
+
if (!text) {
|
|
141
|
+
const error = fetchError ? fetchError : "No text returned for URL.";
|
|
142
|
+
return { success: false, url: request.url, error };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return paginateText(request.url, text, request.effectiveLineOffset, request.effectiveLineLimit);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return formatFetchUrlsXml(results);
|
|
68
149
|
},
|
|
69
150
|
});
|
|
70
151
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
highlightQuery: string
|
|
74
|
-
): Promise<
|
|
75
|
-
({ success: true; url: string } & HighlightResult) | { success: false; url: string; error: string }
|
|
76
|
-
> {
|
|
77
|
-
const exaClientResult = getExaClient();
|
|
78
|
-
if ("error" in exaClientResult) {
|
|
79
|
-
return { success: false, url, error: exaClientResult.error };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
const rawData = (await exaClientResult.client.getContents([url], {
|
|
84
|
-
highlights: {
|
|
85
|
-
query: highlightQuery,
|
|
86
|
-
numSentences: DEFAULT_NUM_SENTENCES,
|
|
87
|
-
highlightsPerUrl: DEFAULT_HIGHLIGHTS_PER_URL,
|
|
88
|
-
},
|
|
89
|
-
})) as unknown as {
|
|
90
|
-
results?: Array<{
|
|
91
|
-
url?: string;
|
|
92
|
-
highlights?: string[];
|
|
93
|
-
[key: string]: unknown;
|
|
94
|
-
}>;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const first = rawData.results?.[0];
|
|
98
|
-
const highlights = first?.highlights ?? [];
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
success: true,
|
|
102
|
-
url,
|
|
103
|
-
highlights,
|
|
104
|
-
highlightQuery,
|
|
105
|
-
};
|
|
106
|
-
} catch (error) {
|
|
107
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
108
|
-
return { success: false, url, error: err.message };
|
|
109
|
-
}
|
|
110
|
-
}
|
|
152
|
+
function formatFetchUrlsXml(results: FetchUrlsItem[]): string {
|
|
153
|
+
const lines: string[] = ["<fetchUrls>"];
|
|
111
154
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
lineOffset?: number,
|
|
115
|
-
lineLimit?: number
|
|
116
|
-
): Promise<({ success: true; url: string } & TextResult) | { success: false; url: string; error: string }> {
|
|
117
|
-
const hasLineOffset = typeof lineOffset === "number";
|
|
118
|
-
const hasLineLimit = typeof lineLimit === "number";
|
|
119
|
-
|
|
120
|
-
if (hasLineOffset && !hasLineLimit && (lineOffset ?? 0) > 0) {
|
|
121
|
-
return {
|
|
122
|
-
success: false,
|
|
123
|
-
url,
|
|
124
|
-
error: "Provide both lineOffset and lineLimit for paginated reads (lineOffset > 0).",
|
|
125
|
-
};
|
|
126
|
-
}
|
|
155
|
+
for (const item of results) {
|
|
156
|
+
const attributes: string[] = [`href="${escapeXmlAttribute(item.url)}"`];
|
|
127
157
|
|
|
128
|
-
|
|
129
|
-
|
|
158
|
+
if ("lineOffset" in item && typeof item.lineOffset === "number") {
|
|
159
|
+
attributes.push(`lineOffset="${item.lineOffset}"`);
|
|
160
|
+
}
|
|
161
|
+
if ("lineLimit" in item && typeof item.lineLimit === "number") {
|
|
162
|
+
attributes.push(`lineLimit="${item.lineLimit}"`);
|
|
163
|
+
}
|
|
164
|
+
if ("totalLines" in item && typeof item.totalLines === "number") {
|
|
165
|
+
attributes.push(`totalLines="${item.totalLines}"`);
|
|
166
|
+
}
|
|
167
|
+
if ("remainingLines" in item) {
|
|
168
|
+
if (typeof item.remainingLines === "number") {
|
|
169
|
+
attributes.push(`remainingLines="${item.remainingLines}"`);
|
|
170
|
+
} else if (item.remainingLines === null) {
|
|
171
|
+
attributes.push(`remainingLines="unknown"`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
130
174
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
175
|
+
if (item.success === false) {
|
|
176
|
+
attributes.push(`error="${escapeXmlAttribute(item.error)}"`);
|
|
177
|
+
lines.push(` <url ${attributes.join(" ")} />`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
135
180
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
181
|
+
const textLines = normalizeLines(item.text);
|
|
182
|
+
lines.push(` <url ${attributes.join(" ")}>`);
|
|
183
|
+
for (const line of textLines) {
|
|
184
|
+
lines.push(` ${escapeXmlAttribute(line)}`);
|
|
185
|
+
}
|
|
186
|
+
lines.push(" </url>");
|
|
139
187
|
}
|
|
140
188
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
text: { maxCharacters: MAX_CHAR_LIMIT },
|
|
144
|
-
})) as unknown as {
|
|
145
|
-
results?: Array<{
|
|
146
|
-
url?: string;
|
|
147
|
-
text?: string;
|
|
148
|
-
[key: string]: unknown;
|
|
149
|
-
}>;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const first = rawData.results?.[0];
|
|
153
|
-
const fullText = first?.text ?? "";
|
|
154
|
-
const cappedText = fullText.slice(0, MAX_CHAR_LIMIT);
|
|
155
|
-
|
|
156
|
-
setCachedPage(url, cappedText);
|
|
157
|
-
|
|
158
|
-
return paginateText(url, cappedText, effectiveLineOffset, effectiveLineLimit);
|
|
159
|
-
} catch (error) {
|
|
160
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
161
|
-
return { success: false, url, error: err.message };
|
|
162
|
-
}
|
|
189
|
+
lines.push("</fetchUrls>");
|
|
190
|
+
return lines.join("\n");
|
|
163
191
|
}
|
|
164
192
|
|
|
165
193
|
function paginateText(
|
package/src/ai/tools/index.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type { ToolSet } from "ai";
|
|
2
2
|
|
|
3
3
|
import { getDaemonManager } from "../../state/daemon-state";
|
|
4
|
+
import { getMcpManager } from "../mcp/mcp-manager";
|
|
4
5
|
import type { ToolAvailabilityMap } from "./tool-registry";
|
|
5
6
|
import { buildToolSet } from "./tool-registry";
|
|
6
7
|
|
|
7
|
-
let
|
|
8
|
+
let cachedDaemonBaseTools: Promise<ToolSet> | null = null;
|
|
8
9
|
let cachedAvailability: ToolAvailabilityMap | null = null;
|
|
9
10
|
|
|
10
11
|
export function invalidateDaemonToolsCache(): void {
|
|
11
|
-
|
|
12
|
+
cachedDaemonBaseTools = null;
|
|
12
13
|
cachedAvailability = null;
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -17,16 +18,17 @@ export function getCachedToolAvailability(): ToolAvailabilityMap | null {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export async function getDaemonTools(): Promise<ToolSet> {
|
|
20
|
-
if (
|
|
21
|
-
|
|
21
|
+
if (!cachedDaemonBaseTools) {
|
|
22
|
+
cachedDaemonBaseTools = (async () => {
|
|
23
|
+
const toggles = getDaemonManager().toolToggles;
|
|
24
|
+
const { tools, availability } = await buildToolSet(toggles);
|
|
25
|
+
cachedAvailability = availability;
|
|
26
|
+
return tools;
|
|
27
|
+
})();
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return tools;
|
|
29
|
-
})();
|
|
30
|
-
|
|
31
|
-
return cachedDaemonTools;
|
|
30
|
+
const baseTools = await cachedDaemonBaseTools;
|
|
31
|
+
const mcpTools = getMcpManager().getToolsSnapshot();
|
|
32
|
+
if (Object.keys(mcpTools).length === 0) return baseTools;
|
|
33
|
+
return { ...baseTools, ...mcpTools };
|
|
32
34
|
}
|
|
@@ -11,6 +11,7 @@ import { z } from "zod";
|
|
|
11
11
|
import { getDaemonManager } from "../../state/daemon-state";
|
|
12
12
|
import type { SubagentProgressEmitter } from "../../types";
|
|
13
13
|
import { getOpenRouterReportedCost } from "../../utils/openrouter-reported-cost";
|
|
14
|
+
import { getMcpManager } from "../mcp/mcp-manager";
|
|
14
15
|
import { extractFinalAssistantText } from "../message-utils";
|
|
15
16
|
import { buildOpenRouterChatSettings, getSubagentModel } from "../model-config";
|
|
16
17
|
import { buildToolSet } from "./tool-registry";
|
|
@@ -21,25 +22,28 @@ const openrouter = createOpenRouter();
|
|
|
21
22
|
// Maximum steps for subagent loops
|
|
22
23
|
const MAX_SUBAGENT_STEPS = 30;
|
|
23
24
|
|
|
24
|
-
let
|
|
25
|
+
let cachedSubagentBaseTools: Promise<ToolSet> | null = null;
|
|
25
26
|
|
|
26
27
|
export function invalidateSubagentToolsCache(): void {
|
|
27
|
-
|
|
28
|
+
cachedSubagentBaseTools = null;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
// Subagent tools (all tools except subagent itself to prevent recursion)
|
|
31
32
|
async function getSubagentTools(): Promise<ToolSet> {
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
if (!cachedSubagentBaseTools) {
|
|
34
|
+
cachedSubagentBaseTools = (async () => {
|
|
35
|
+
const toggles = getDaemonManager().toolToggles;
|
|
36
|
+
const { tools } = await buildToolSet(toggles, {
|
|
37
|
+
omit: ["groundingManager", "subagent"],
|
|
38
|
+
});
|
|
39
|
+
return tools;
|
|
40
|
+
})();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const baseTools = await cachedSubagentBaseTools;
|
|
44
|
+
const mcpTools = getMcpManager().getToolsSnapshot();
|
|
45
|
+
if (Object.keys(mcpTools).length === 0) return baseTools;
|
|
46
|
+
return { ...baseTools, ...mcpTools };
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
// System prompt for subagents
|
|
@@ -63,6 +63,7 @@ function AppOverlaysImpl({ conversationHistory, currentContentBlocks }: AppOverl
|
|
|
63
63
|
canEnableVoiceOutput={settings.canEnableVoiceOutput}
|
|
64
64
|
showFullReasoning={settings.showFullReasoning}
|
|
65
65
|
showToolOutput={settings.showToolOutput}
|
|
66
|
+
memoryEnabled={settings.memoryEnabled}
|
|
66
67
|
onClose={() => menus.setShowSettingsMenu(false)}
|
|
67
68
|
toggleInteractionMode={settingsCallbacks.onToggleInteractionMode}
|
|
68
69
|
setVoiceInteractionType={settingsCallbacks.onSetVoiceInteractionType}
|
|
@@ -71,6 +72,7 @@ function AppOverlaysImpl({ conversationHistory, currentContentBlocks }: AppOverl
|
|
|
71
72
|
setBashApprovalLevel={settingsCallbacks.onSetBashApprovalLevel}
|
|
72
73
|
setShowFullReasoning={settings.setShowFullReasoning}
|
|
73
74
|
setShowToolOutput={settings.setShowToolOutput}
|
|
75
|
+
setMemoryEnabled={settings.setMemoryEnabled}
|
|
74
76
|
persistPreferences={settings.persistPreferences}
|
|
75
77
|
/>
|
|
76
78
|
)}
|