@makefinks/daemon 0.2.0 → 0.3.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 +1 -1
- package/src/ai/daemon-ai.ts +30 -85
- package/src/ai/system-prompt.ts +134 -111
- package/src/ai/tool-approval-coordinator.ts +113 -0
- package/src/ai/tools/index.ts +12 -32
- package/src/ai/tools/subagents.ts +16 -30
- package/src/ai/tools/tool-registry.ts +203 -0
- package/src/app/App.tsx +23 -641
- package/src/app/components/AppOverlays.tsx +8 -0
- package/src/components/ContentBlockView.tsx +5 -11
- package/src/components/HotkeysPane.tsx +2 -1
- package/src/components/ToolsMenu.tsx +235 -0
- package/src/hooks/use-app-context-builder.ts +2 -0
- package/src/hooks/use-app-controller.ts +546 -0
- package/src/hooks/use-app-menus.ts +6 -0
- package/src/hooks/use-app-preferences-bootstrap.ts +9 -0
- package/src/hooks/use-bootstrap-controller.ts +92 -0
- package/src/hooks/use-daemon-keyboard.ts +28 -8
- package/src/hooks/use-daemon-runtime-controller.ts +147 -0
- package/src/hooks/use-overlay-controller.ts +6 -0
- package/src/hooks/use-reasoning-animation.ts +8 -3
- package/src/hooks/use-session-controller.ts +79 -0
- package/src/state/app-context.tsx +2 -0
- package/src/state/daemon-state.ts +19 -8
- package/src/types/index.ts +25 -0
- package/src/ui/constants.ts +5 -3
- package/src/ui/reasoning-ticker.tsx +38 -0
- package/src/utils/preferences.ts +10 -0
|
@@ -3,23 +3,16 @@
|
|
|
3
3
|
* DAEMON can call this tool multiple times in parallel to spawn concurrent subagents.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { tool } from "ai";
|
|
7
|
-
import { z } from "zod";
|
|
8
6
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
7
|
+
import { tool } from "ai";
|
|
9
8
|
import { ToolLoopAgent, stepCountIs } from "ai";
|
|
10
|
-
import { fetchUrls } from "./fetch-urls";
|
|
11
|
-
import { renderUrl } from "./render-url";
|
|
12
|
-
import { webSearch } from "./web-search";
|
|
13
|
-
|
|
14
|
-
import { readFile } from "./read-file";
|
|
15
|
-
import { runBash } from "./run-bash";
|
|
16
|
-
import { todoManager } from "./todo-manager";
|
|
17
|
-
import { buildOpenRouterChatSettings, getSubagentModel } from "../model-config";
|
|
18
|
-
import { isWebSearchAvailable } from "./index";
|
|
19
9
|
import type { ToolSet } from "ai";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { getDaemonManager } from "../../state/daemon-state";
|
|
20
12
|
import type { SubagentProgressEmitter } from "../../types";
|
|
21
|
-
import { detectLocalPlaywrightChromium } from "../../utils/js-rendering";
|
|
22
13
|
import { getOpenRouterReportedCost } from "../../utils/openrouter-reported-cost";
|
|
14
|
+
import { buildOpenRouterChatSettings, getSubagentModel } from "../model-config";
|
|
15
|
+
import { buildToolSet } from "./tool-registry";
|
|
23
16
|
|
|
24
17
|
// OpenRouter client for subagents
|
|
25
18
|
const openrouter = createOpenRouter();
|
|
@@ -29,27 +22,19 @@ const MAX_SUBAGENT_STEPS = 30;
|
|
|
29
22
|
|
|
30
23
|
let cachedSubagentTools: Promise<ToolSet> | null = null;
|
|
31
24
|
|
|
25
|
+
export function invalidateSubagentToolsCache(): void {
|
|
26
|
+
cachedSubagentTools = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
32
29
|
// Subagent tools (all tools except subagent itself to prevent recursion)
|
|
33
30
|
async function getSubagentTools(): Promise<ToolSet> {
|
|
34
31
|
if (cachedSubagentTools) return cachedSubagentTools;
|
|
35
32
|
|
|
36
33
|
cachedSubagentTools = (async () => {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
if (isWebSearchAvailable()) {
|
|
44
|
-
(tools as ToolSet & { webSearch: typeof webSearch }).webSearch = webSearch;
|
|
45
|
-
(tools as ToolSet & { fetchUrls: typeof fetchUrls }).fetchUrls = fetchUrls;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const jsRendering = await detectLocalPlaywrightChromium();
|
|
49
|
-
if (jsRendering.available) {
|
|
50
|
-
return { ...tools, renderUrl };
|
|
51
|
-
}
|
|
52
|
-
|
|
34
|
+
const toggles = getDaemonManager().toolToggles;
|
|
35
|
+
const { tools } = await buildToolSet(toggles, {
|
|
36
|
+
omit: ["groundingManager", "subagent"],
|
|
37
|
+
});
|
|
53
38
|
return tools;
|
|
54
39
|
})();
|
|
55
40
|
|
|
@@ -113,12 +98,13 @@ Provide a concise summary for display and a very specific task description (espe
|
|
|
113
98
|
execute: async ({ summary, task }, { toolCallId }) => {
|
|
114
99
|
try {
|
|
115
100
|
const tools = await getSubagentTools();
|
|
116
|
-
const webSearchAvailable =
|
|
101
|
+
const webSearchAvailable = Boolean((tools as Record<string, unknown>).webSearch);
|
|
102
|
+
const fetchUrlsAvailable = Boolean((tools as Record<string, unknown>).fetchUrls);
|
|
117
103
|
|
|
118
104
|
// Create ephemeral subagent
|
|
119
105
|
const subagent = new ToolLoopAgent({
|
|
120
106
|
model: openrouter.chat(getSubagentModel(), buildOpenRouterChatSettings()),
|
|
121
|
-
instructions: buildSubagentSystemPrompt(webSearchAvailable),
|
|
107
|
+
instructions: buildSubagentSystemPrompt(webSearchAvailable || fetchUrlsAvailable),
|
|
122
108
|
tools,
|
|
123
109
|
stopWhen: stepCountIs(MAX_SUBAGENT_STEPS),
|
|
124
110
|
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { ToolSet } from "ai";
|
|
2
|
+
|
|
3
|
+
import { fetchUrls } from "./fetch-urls";
|
|
4
|
+
import { groundingManager } from "./grounding-manager";
|
|
5
|
+
import { readFile } from "./read-file";
|
|
6
|
+
import { renderUrl } from "./render-url";
|
|
7
|
+
import { runBash } from "./run-bash";
|
|
8
|
+
import { subagent } from "./subagents";
|
|
9
|
+
import { todoManager } from "./todo-manager";
|
|
10
|
+
import { webSearch } from "./web-search";
|
|
11
|
+
|
|
12
|
+
import type { ToolToggleId, ToolToggles } from "../../types";
|
|
13
|
+
import { detectLocalPlaywrightChromium } from "../../utils/js-rendering";
|
|
14
|
+
|
|
15
|
+
export type ToolId = ToolToggleId;
|
|
16
|
+
|
|
17
|
+
export interface ToolAvailabilityStatus {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
envAvailable: boolean;
|
|
20
|
+
disabledReason?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ToolAvailabilityMap = Record<ToolId, ToolAvailabilityStatus>;
|
|
24
|
+
|
|
25
|
+
type ToolEntry = {
|
|
26
|
+
id: ToolId;
|
|
27
|
+
tool: ToolSet[keyof ToolSet];
|
|
28
|
+
toggleKey: ToolToggleId;
|
|
29
|
+
gate?: (context: ToolGateContext) => Promise<ToolGateResult>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type ToolGateContext = {
|
|
33
|
+
toggles: ToolToggles;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type ToolGateResult = {
|
|
37
|
+
envAvailable: boolean;
|
|
38
|
+
disabledReason?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const TOOL_REGISTRY: ToolEntry[] = [
|
|
42
|
+
{ id: "readFile", toggleKey: "readFile", tool: readFile },
|
|
43
|
+
{ id: "runBash", toggleKey: "runBash", tool: runBash },
|
|
44
|
+
{ id: "webSearch", toggleKey: "webSearch", tool: webSearch, gate: gateExa },
|
|
45
|
+
{ id: "fetchUrls", toggleKey: "fetchUrls", tool: fetchUrls, gate: gateExa },
|
|
46
|
+
{ id: "renderUrl", toggleKey: "renderUrl", tool: renderUrl, gate: gateRenderUrl },
|
|
47
|
+
{ id: "todoManager", toggleKey: "todoManager", tool: todoManager },
|
|
48
|
+
{ id: "groundingManager", toggleKey: "groundingManager", tool: groundingManager },
|
|
49
|
+
{ id: "subagent", toggleKey: "subagent", tool: subagent },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
function gateExa(): Promise<ToolGateResult> {
|
|
53
|
+
const hasKey = Boolean(process.env.EXA_API_KEY);
|
|
54
|
+
return Promise.resolve({
|
|
55
|
+
envAvailable: hasKey,
|
|
56
|
+
disabledReason: hasKey ? undefined : "EXA_API_KEY not configured",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function gateRenderUrl(): Promise<ToolGateResult> {
|
|
61
|
+
const capability = await detectLocalPlaywrightChromium();
|
|
62
|
+
return {
|
|
63
|
+
envAvailable: capability.available,
|
|
64
|
+
disabledReason: capability.available ? undefined : capability.reason,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeToggles(toggles?: ToolToggles): ToolToggles {
|
|
69
|
+
return {
|
|
70
|
+
readFile: toggles?.readFile ?? true,
|
|
71
|
+
runBash: toggles?.runBash ?? true,
|
|
72
|
+
webSearch: toggles?.webSearch ?? true,
|
|
73
|
+
fetchUrls: toggles?.fetchUrls ?? true,
|
|
74
|
+
renderUrl: toggles?.renderUrl ?? true,
|
|
75
|
+
todoManager: toggles?.todoManager ?? true,
|
|
76
|
+
groundingManager: toggles?.groundingManager ?? true,
|
|
77
|
+
subagent: toggles?.subagent ?? true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function selectRegistryTools(only: ToolId[] | null): ToolEntry[] {
|
|
82
|
+
if (!only) return TOOL_REGISTRY;
|
|
83
|
+
return TOOL_REGISTRY.filter((entry) => only.includes(entry.id));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function omitRegistryTools(omit: ToolId[] | null): ToolEntry[] {
|
|
87
|
+
if (!omit) return TOOL_REGISTRY;
|
|
88
|
+
return TOOL_REGISTRY.filter((entry) => !omit.includes(entry.id));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface BuildToolsOptions {
|
|
92
|
+
only?: ToolId[];
|
|
93
|
+
omit?: ToolId[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function resolveToolAvailability(
|
|
97
|
+
toggles: ToolToggles,
|
|
98
|
+
options: BuildToolsOptions = {}
|
|
99
|
+
): Promise<ToolAvailabilityMap> {
|
|
100
|
+
const normalizedToggles = normalizeToggles(toggles);
|
|
101
|
+
const entries = options.only ? selectRegistryTools(options.only) : omitRegistryTools(options.omit ?? null);
|
|
102
|
+
const results: ToolAvailabilityMap = {} as ToolAvailabilityMap;
|
|
103
|
+
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
const toggleEnabled = Boolean(normalizedToggles[entry.toggleKey]);
|
|
106
|
+
const gateResult = entry.gate ? await entry.gate({ toggles: normalizedToggles }) : { envAvailable: true };
|
|
107
|
+
results[entry.id] = {
|
|
108
|
+
enabled: toggleEnabled && gateResult.envAvailable,
|
|
109
|
+
envAvailable: gateResult.envAvailable,
|
|
110
|
+
disabledReason: gateResult.disabledReason,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return results;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function buildMenuItems(availability: ToolAvailabilityMap): Record<
|
|
118
|
+
ToolId,
|
|
119
|
+
{
|
|
120
|
+
id: ToolId;
|
|
121
|
+
label: string;
|
|
122
|
+
envAvailable: boolean;
|
|
123
|
+
disabledReason?: string;
|
|
124
|
+
}
|
|
125
|
+
> {
|
|
126
|
+
const labels = getToolLabels();
|
|
127
|
+
const ordered = getDefaultToolOrder();
|
|
128
|
+
const entries = ordered.map((id) => {
|
|
129
|
+
const status = availability[id];
|
|
130
|
+
return {
|
|
131
|
+
id,
|
|
132
|
+
label: labels[id],
|
|
133
|
+
envAvailable: status?.envAvailable ?? true,
|
|
134
|
+
disabledReason: status?.disabledReason,
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return Object.fromEntries(entries.map((entry) => [entry.id, entry])) as Record<
|
|
139
|
+
ToolId,
|
|
140
|
+
{
|
|
141
|
+
id: ToolId;
|
|
142
|
+
label: string;
|
|
143
|
+
envAvailable: boolean;
|
|
144
|
+
disabledReason?: string;
|
|
145
|
+
}
|
|
146
|
+
>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function buildToolSet(
|
|
150
|
+
toggles: ToolToggles,
|
|
151
|
+
options: BuildToolsOptions = {}
|
|
152
|
+
): Promise<{ tools: ToolSet; availability: ToolAvailabilityMap }> {
|
|
153
|
+
const availability = await resolveToolAvailability(toggles, options);
|
|
154
|
+
const entries = options.only ? selectRegistryTools(options.only) : omitRegistryTools(options.omit ?? null);
|
|
155
|
+
const tools: ToolSet = {};
|
|
156
|
+
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
const status = availability[entry.id];
|
|
159
|
+
if (!status?.enabled) continue;
|
|
160
|
+
(tools as ToolSet & Record<ToolId, ToolSet[keyof ToolSet]>)[entry.id] = entry.tool;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { tools, availability };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function getToolLabels(): Record<ToolId, string> {
|
|
167
|
+
return {
|
|
168
|
+
readFile: "readFile",
|
|
169
|
+
runBash: "runBash",
|
|
170
|
+
webSearch: "webSearch",
|
|
171
|
+
fetchUrls: "fetchUrls",
|
|
172
|
+
renderUrl: "renderUrl",
|
|
173
|
+
todoManager: "todoManager",
|
|
174
|
+
groundingManager: "groundingManager",
|
|
175
|
+
subagent: "subagent",
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function getDefaultToolOrder(): ToolId[] {
|
|
180
|
+
return [
|
|
181
|
+
"readFile",
|
|
182
|
+
"runBash",
|
|
183
|
+
"webSearch",
|
|
184
|
+
"fetchUrls",
|
|
185
|
+
"renderUrl",
|
|
186
|
+
"todoManager",
|
|
187
|
+
"groundingManager",
|
|
188
|
+
"subagent",
|
|
189
|
+
];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function createToolAvailabilitySnapshot(availability: ToolAvailabilityMap): Record<ToolId, boolean> {
|
|
193
|
+
return {
|
|
194
|
+
readFile: availability.readFile?.enabled ?? false,
|
|
195
|
+
runBash: availability.runBash?.enabled ?? false,
|
|
196
|
+
webSearch: availability.webSearch?.enabled ?? false,
|
|
197
|
+
fetchUrls: availability.fetchUrls?.enabled ?? false,
|
|
198
|
+
renderUrl: availability.renderUrl?.enabled ?? false,
|
|
199
|
+
todoManager: availability.todoManager?.enabled ?? false,
|
|
200
|
+
groundingManager: availability.groundingManager?.enabled ?? false,
|
|
201
|
+
subagent: availability.subagent?.enabled ?? false,
|
|
202
|
+
};
|
|
203
|
+
}
|