@makefinks/daemon 0.2.0 → 0.3.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.
@@ -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 tools: ToolSet = {
38
- readFile,
39
- runBash,
40
- todoManager,
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 = isWebSearchAvailable();
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
+ }