@botbotgo/agent-harness 0.0.294 → 0.0.296

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.
Files changed (38) hide show
  1. package/README.md +27 -0
  2. package/README.zh.md +27 -0
  3. package/dist/acp.d.ts +28 -3
  4. package/dist/acp.js +100 -7
  5. package/dist/api.d.ts +2 -2
  6. package/dist/cli.d.ts +28 -0
  7. package/dist/cli.js +923 -12
  8. package/dist/client/acp.d.ts +44 -0
  9. package/dist/client/acp.js +165 -0
  10. package/dist/client/in-process.d.ts +44 -0
  11. package/dist/client/in-process.js +69 -0
  12. package/dist/client/index.d.ts +4 -0
  13. package/dist/client/index.js +2 -0
  14. package/dist/client/types.d.ts +56 -0
  15. package/dist/client/types.js +1 -0
  16. package/dist/client.d.ts +1 -0
  17. package/dist/client.js +1 -0
  18. package/dist/config/agents/orchestra.yaml +16 -3
  19. package/dist/index.d.ts +4 -2
  20. package/dist/index.js +1 -0
  21. package/dist/init-project.js +89 -0
  22. package/dist/package-version.d.ts +1 -1
  23. package/dist/package-version.js +1 -1
  24. package/dist/protocol/acp/client.d.ts +8 -2
  25. package/dist/protocol/acp/client.js +143 -0
  26. package/dist/resource/resource-impl.js +21 -4
  27. package/dist/resources/package.json +6 -0
  28. package/dist/resources/skills/approval-execution-policy/SKILL.md +22 -0
  29. package/dist/resources/skills/completion-discipline/SKILL.md +22 -0
  30. package/dist/resources/skills/delegation-discipline/SKILL.md +22 -0
  31. package/dist/resources/skills/safe-editing/SKILL.md +22 -0
  32. package/dist/resources/skills/workspace-inspection/SKILL.md +22 -0
  33. package/dist/runtime/adapter/runtime-adapter-support.d.ts +4 -2
  34. package/dist/runtime/adapter/runtime-adapter-support.js +10 -0
  35. package/dist/runtime/adapter/tool/builtin-middleware-tools.d.ts +74 -0
  36. package/dist/runtime/adapter/tool/builtin-middleware-tools.js +192 -1
  37. package/dist/runtime/agent-runtime-adapter.js +10 -0
  38. package/package.json +12 -2
@@ -33,6 +33,13 @@ function isNotification(value) {
33
33
  && typeof value.method === "string"
34
34
  && !Object.hasOwn(value, "id");
35
35
  }
36
+ async function parseJsonResponse(response) {
37
+ const text = await response.text();
38
+ if (!text.trim()) {
39
+ return undefined;
40
+ }
41
+ return JSON.parse(text);
42
+ }
36
43
  export function createAcpStdioClient(options) {
37
44
  const idPrefix = options.idPrefix?.trim() || "acp";
38
45
  let sequence = 0;
@@ -149,3 +156,139 @@ export function createAcpStdioClient(options) {
149
156
  },
150
157
  };
151
158
  }
159
+ export function createAcpHttpClient(options) {
160
+ let closed = false;
161
+ let requestSequence = 0;
162
+ const listeners = new Set();
163
+ const eventsAbort = new AbortController();
164
+ let resolveReady;
165
+ let rejectReady;
166
+ const ready = new Promise((resolve, reject) => {
167
+ resolveReady = resolve;
168
+ rejectReady = reject;
169
+ });
170
+ const completed = (async () => {
171
+ const response = await fetch(options.eventsUrl, {
172
+ method: "GET",
173
+ headers: {
174
+ accept: "text/event-stream",
175
+ },
176
+ signal: eventsAbort.signal,
177
+ });
178
+ if (!response.ok || !response.body) {
179
+ throw new Error(`ACP HTTP events request failed with status ${response.status}.`);
180
+ }
181
+ resolveReady?.();
182
+ resolveReady = undefined;
183
+ rejectReady = undefined;
184
+ const reader = response.body
185
+ .pipeThrough(new TextDecoderStream())
186
+ .getReader();
187
+ let buffer = "";
188
+ try {
189
+ for (;;) {
190
+ const { done, value } = await reader.read();
191
+ if (done) {
192
+ break;
193
+ }
194
+ buffer += value;
195
+ let splitIndex = buffer.indexOf("\n\n");
196
+ while (splitIndex >= 0) {
197
+ const frame = buffer.slice(0, splitIndex);
198
+ buffer = buffer.slice(splitIndex + 2);
199
+ const dataLines = frame
200
+ .split("\n")
201
+ .map((line) => line.trimEnd())
202
+ .filter((line) => line.startsWith("data:"))
203
+ .map((line) => line.slice("data:".length).trim());
204
+ if (dataLines.length > 0) {
205
+ try {
206
+ const parsed = JSON.parse(dataLines.join("\n"));
207
+ if (isNotification(parsed)) {
208
+ for (const listener of Array.from(listeners)) {
209
+ try {
210
+ listener(parsed);
211
+ }
212
+ catch {
213
+ // Ignore listener failures so one subscriber cannot tear down the transport.
214
+ }
215
+ }
216
+ }
217
+ }
218
+ catch {
219
+ // Ignore malformed SSE payloads.
220
+ }
221
+ }
222
+ splitIndex = buffer.indexOf("\n\n");
223
+ }
224
+ }
225
+ }
226
+ finally {
227
+ reader.releaseLock();
228
+ }
229
+ })().catch((error) => {
230
+ rejectReady?.(error);
231
+ resolveReady = undefined;
232
+ rejectReady = undefined;
233
+ if (!closed && !(error instanceof DOMException && error.name === "AbortError")) {
234
+ throw error;
235
+ }
236
+ });
237
+ return {
238
+ async request(method, params) {
239
+ if (closed) {
240
+ throw new Error("ACP http client is closed.");
241
+ }
242
+ await ready;
243
+ const response = await fetch(options.rpcUrl, {
244
+ method: "POST",
245
+ headers: {
246
+ "content-type": "application/json",
247
+ },
248
+ body: JSON.stringify({
249
+ jsonrpc: "2.0",
250
+ id: `acp-http-${++requestSequence}`,
251
+ method,
252
+ ...(params === undefined ? {} : { params }),
253
+ }),
254
+ });
255
+ const parsed = await parseJsonResponse(response);
256
+ if (!isResponse(parsed)) {
257
+ throw new Error("ACP http client received a non-JSON-RPC response.");
258
+ }
259
+ if ("error" in parsed) {
260
+ throw new AcpClientError(parsed.error);
261
+ }
262
+ return parsed.result;
263
+ },
264
+ async notify(method, params) {
265
+ if (closed) {
266
+ throw new Error("ACP http client is closed.");
267
+ }
268
+ await ready;
269
+ await fetch(options.rpcUrl, {
270
+ method: "POST",
271
+ headers: {
272
+ "content-type": "application/json",
273
+ },
274
+ body: JSON.stringify({
275
+ jsonrpc: "2.0",
276
+ method,
277
+ ...(params === undefined ? {} : { params }),
278
+ }),
279
+ });
280
+ },
281
+ subscribe(listener) {
282
+ listeners.add(listener);
283
+ return () => {
284
+ listeners.delete(listener);
285
+ };
286
+ },
287
+ async close() {
288
+ closed = true;
289
+ eventsAbort.abort();
290
+ await completed.catch(() => undefined);
291
+ listeners.clear();
292
+ },
293
+ };
294
+ }
@@ -634,13 +634,30 @@ async function loadRemoteResource(source, workspaceRoot) {
634
634
  export async function ensureResourceSources(sources = [], workspaceRoot = process.cwd()) {
635
635
  await Promise.all(sources.map((source) => loadRemoteResource(source, workspaceRoot)));
636
636
  }
637
+ function resolveBundledFallbackRoot(kind) {
638
+ const suffix = kind === "skills" ? ["resources", "skills"] : ["config"];
639
+ const candidates = [
640
+ path.resolve(resourceDir, "..", ...suffix),
641
+ path.resolve(resourceDir, "..", "..", ...suffix),
642
+ ];
643
+ for (const candidate of candidates) {
644
+ if (existsSync(candidate)) {
645
+ return candidate;
646
+ }
647
+ }
648
+ return null;
649
+ }
637
650
  export function defaultResourceSkillsRoot() {
638
- const provider = requireLocalResource("default resource skill resolution");
639
- return preferProviderValue(provider, (candidate) => candidate.defaultResourceSkillsRoot?.(), (candidate) => candidate.builtinSkillsRoot?.()) ?? "";
651
+ if (localResource) {
652
+ return preferProviderValue(localResource, (candidate) => candidate.defaultResourceSkillsRoot?.(), (candidate) => candidate.builtinSkillsRoot?.()) ?? "";
653
+ }
654
+ return resolveBundledFallbackRoot("skills") ?? requireLocalResource("default resource skill resolution").defaultResourceSkillsRoot?.() ?? "";
640
655
  }
641
656
  export function defaultResourceConfigRoot() {
642
- const provider = requireLocalResource("default resource config resolution");
643
- return (preferProviderValue(provider, (candidate) => candidate.defaultResourceConfigRoot?.(), (candidate) => candidate.builtinConfigRoot?.() ?? candidate.builtinDefaultsRoot?.()) ?? "");
657
+ if (localResource) {
658
+ return (preferProviderValue(localResource, (candidate) => candidate.defaultResourceConfigRoot?.(), (candidate) => candidate.builtinConfigRoot?.() ?? candidate.builtinDefaultsRoot?.()) ?? "");
659
+ }
660
+ return resolveBundledFallbackRoot("config") ?? requireLocalResource("default resource config resolution").defaultResourceConfigRoot?.() ?? "";
644
661
  }
645
662
  export async function listResourceTools(sources = [], workspaceRoot = process.cwd()) {
646
663
  await ensureResourceSources(sources, workspaceRoot);
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "@botbotgo/agent-harness-default-resources",
3
+ "private": true,
4
+ "type": "module",
5
+ "description": "Default starter skills packaged with the repository workspace."
6
+ }
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: approval-execution-policy
3
+ description: Treat side effects, command execution, and external actions as governed runtime decisions.
4
+ ---
5
+
6
+ # Approval And Execution Policy
7
+
8
+ Use this skill whenever the task may write files, run commands, access external systems, send messages, or trigger other side effects.
9
+
10
+ ## Workflow
11
+
12
+ 1. Classify the next action as low risk or side-effectful.
13
+ 2. Continue directly only when the action is clearly safe and allowed by the runtime.
14
+ 3. Request approval before destructive, external, privileged, or ambiguous actions.
15
+ 4. After approval, execute only the action that was approved and report the result plainly.
16
+
17
+ ## Rules
18
+
19
+ - Do not hide risky commands or external calls inside a larger action.
20
+ - If the safest next step is to inspect more context, inspect first.
21
+ - If approval is needed, explain why in one concrete sentence.
22
+ - When an action is denied or unavailable, continue with the safest useful fallback instead of pretending it succeeded.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: completion-discipline
3
+ description: Finish the task cleanly, verify what you can, and leave the user with a clear outcome.
4
+ ---
5
+
6
+ # Completion Discipline
7
+
8
+ Use this skill at the end of any non-trivial task.
9
+
10
+ ## Workflow
11
+
12
+ 1. Check whether the user's request is actually complete.
13
+ 2. Verify the changed or inspected area with the smallest relevant check.
14
+ 3. Distinguish completed work, unverified areas, and blocked follow-up.
15
+ 4. Return a concise final answer that makes the outcome easy to trust.
16
+
17
+ ## Rules
18
+
19
+ - Do not stop at a half-finished plan when a concrete next action is still available.
20
+ - Do not claim success without stating what was verified.
21
+ - If tests or commands were not run, say so explicitly.
22
+ - Prefer a short, high-signal wrap-up over a changelog dump.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: delegation-discipline
3
+ description: Delegate only when a subagent is clearly the best fit and the handoff can be bounded cleanly.
4
+ ---
5
+
6
+ # Delegation Discipline
7
+
8
+ Use this skill when the runtime exposes subagents and the task may benefit from delegation.
9
+
10
+ ## Workflow
11
+
12
+ 1. Decide whether the host can finish the next critical-path step itself.
13
+ 2. Delegate only if a subagent has a clearly matching purpose and the handoff can be stated in one bounded ask.
14
+ 3. Keep blocking work local when waiting on a delegated answer would only add latency.
15
+ 4. Integrate delegated output into one final response instead of forwarding raw subagent output.
16
+
17
+ ## Rules
18
+
19
+ - Do not delegate by reflex.
20
+ - Do not split work into subagents unless that separation improves quality or speed.
21
+ - Do not ask a subagent to rediscover context the host already has unless the subagent truly needs it.
22
+ - If no suitable subagent exists, continue locally.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: safe-editing
3
+ description: Make the smallest workspace change that solves the task, then verify it.
4
+ ---
5
+
6
+ # Safe Editing
7
+
8
+ Use this skill when the request requires changing files, prompts, configuration, or generated content in the workspace.
9
+
10
+ ## Workflow
11
+
12
+ 1. Identify the exact target file or files before editing.
13
+ 2. Prefer a localized change over rewriting large blocks.
14
+ 3. Keep naming, structure, and surrounding conventions consistent with the existing workspace.
15
+ 4. After the edit, run the smallest reasonable verification step and report the outcome.
16
+
17
+ ## Rules
18
+
19
+ - Do not rewrite whole files when a bounded edit is enough.
20
+ - Do not silently undo unrelated user work.
21
+ - If a change could have side effects, call them out explicitly.
22
+ - If verification is blocked, explain what was checked and what remains unverified.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: workspace-inspection
3
+ description: Inspect the current workspace before making claims or edits.
4
+ ---
5
+
6
+ # Workspace Inspection
7
+
8
+ Use this skill when the request depends on the current workspace, repository, files, configuration, or local runtime state.
9
+
10
+ ## Workflow
11
+
12
+ 1. Start with lightweight discovery such as `list_files` or `search_files`.
13
+ 2. Read only the files needed to establish the relevant facts.
14
+ 3. Ground every conclusion in inspected workspace evidence rather than memory or guesses.
15
+ 4. If the workspace does not contain the needed evidence, say that clearly before proposing a next step.
16
+
17
+ ## Rules
18
+
19
+ - Do not claim a file, config value, dependency, or code path exists until you inspected it.
20
+ - Prefer the smallest fact set that can support the next action.
21
+ - When the user asks for analysis only, stop after inspection and a clear explanation.
22
+ - When the user asks for a change, inspect first and then move to the smallest safe edit.
@@ -1,5 +1,4 @@
1
- export declare function truncateLines(lines: string[], maxChars?: number): string;
2
- export declare function summarizeBuiltinWriteTodosArgs(args: Record<string, unknown>): {
1
+ export type BuiltinTodoSnapshot = {
3
2
  total: number;
4
3
  pending: number;
5
4
  completed: number;
@@ -8,3 +7,6 @@ export declare function summarizeBuiltinWriteTodosArgs(args: Record<string, unkn
8
7
  status: string;
9
8
  }>;
10
9
  };
10
+ export declare function truncateLines(lines: string[], maxChars?: number): string;
11
+ export declare function summarizeBuiltinWriteTodosArgs(args: Record<string, unknown>): BuiltinTodoSnapshot;
12
+ export declare function formatBuiltinTodoSnapshot(snapshot: BuiltinTodoSnapshot): string;
@@ -27,3 +27,13 @@ export function summarizeBuiltinWriteTodosArgs(args) {
27
27
  items,
28
28
  };
29
29
  }
30
+ export function formatBuiltinTodoSnapshot(snapshot) {
31
+ if (snapshot.total === 0) {
32
+ return "No todos tracked.";
33
+ }
34
+ const lines = [
35
+ `Tracked ${snapshot.total} todo item(s): ${snapshot.pending} pending, ${snapshot.completed} completed.`,
36
+ ...snapshot.items.map((item, index) => `${index + 1}. [${item.status}] ${item.content}`),
37
+ ];
38
+ return truncateLines(lines);
39
+ }
@@ -105,6 +105,80 @@ export type BuiltinMiddlewareBackend = {
105
105
  exitCode: number | null;
106
106
  truncated: boolean;
107
107
  };
108
+ fetchUrl?: (url: string) => Promise<string | {
109
+ content?: string | Uint8Array;
110
+ body?: string | Uint8Array;
111
+ status?: number;
112
+ statusText?: string;
113
+ error?: string;
114
+ }> | string | {
115
+ content?: string | Uint8Array;
116
+ body?: string | Uint8Array;
117
+ status?: number;
118
+ statusText?: string;
119
+ error?: string;
120
+ };
121
+ httpRequest?: (request: {
122
+ url: string;
123
+ method?: string;
124
+ headers?: Record<string, string>;
125
+ body?: string;
126
+ }) => Promise<string | {
127
+ content?: string | Uint8Array;
128
+ body?: string | Uint8Array;
129
+ status?: number;
130
+ statusText?: string;
131
+ headers?: Record<string, string>;
132
+ error?: string;
133
+ }> | string | {
134
+ content?: string | Uint8Array;
135
+ body?: string | Uint8Array;
136
+ status?: number;
137
+ statusText?: string;
138
+ headers?: Record<string, string>;
139
+ error?: string;
140
+ };
141
+ sendMessage?: (message: {
142
+ destination: string;
143
+ message: string;
144
+ subject?: string;
145
+ metadata?: Record<string, unknown>;
146
+ }) => Promise<string | {
147
+ ok?: boolean;
148
+ id?: string;
149
+ error?: string;
150
+ }> | string | {
151
+ ok?: boolean;
152
+ id?: string;
153
+ error?: string;
154
+ };
155
+ requestApproval?: (request: {
156
+ action: string;
157
+ reason?: string;
158
+ details?: Record<string, unknown>;
159
+ }) => Promise<string | {
160
+ approved?: boolean;
161
+ id?: string;
162
+ error?: string;
163
+ }> | string | {
164
+ approved?: boolean;
165
+ id?: string;
166
+ error?: string;
167
+ };
168
+ scheduleTask?: (task: {
169
+ instruction: string;
170
+ when: string;
171
+ name?: string;
172
+ metadata?: Record<string, unknown>;
173
+ }) => Promise<string | {
174
+ ok?: boolean;
175
+ id?: string;
176
+ error?: string;
177
+ }> | string | {
178
+ ok?: boolean;
179
+ id?: string;
180
+ error?: string;
181
+ };
108
182
  };
109
183
  export type BuiltinExecutableTool = {
110
184
  name: string;
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { isSandboxBackend } from "deepagents";
3
3
  import { isRecord } from "../../../utils/object.js";
4
- import { summarizeBuiltinWriteTodosArgs, truncateLines } from "../runtime-adapter-support.js";
4
+ import { formatBuiltinTodoSnapshot, summarizeBuiltinWriteTodosArgs, truncateLines } from "../runtime-adapter-support.js";
5
5
  function toDisplayContent(content) {
6
6
  if (typeof content === "string") {
7
7
  return content;
@@ -11,8 +11,31 @@ function toDisplayContent(content) {
11
11
  }
12
12
  return "";
13
13
  }
14
+ function notAvailable(toolName, capability) {
15
+ return `Error: ${toolName} is not available. This backend does not support ${capability}.`;
16
+ }
17
+ function formatHttpResponse(result) {
18
+ if (result?.error) {
19
+ return result.error;
20
+ }
21
+ const content = toDisplayContent(result?.body ?? result?.content);
22
+ const status = typeof result?.status === "number" ? `${result.status}${result.statusText ? ` ${result.statusText}` : ""}` : null;
23
+ const headerLines = result?.headers ? Object.entries(result.headers).map(([key, value]) => `${key}: ${value}`) : [];
24
+ const lines = [
25
+ ...(status ? [`Status: ${status}`] : []),
26
+ ...(headerLines.length > 0 ? ["Headers:", ...headerLines] : []),
27
+ ...(content ? ["Body:", content] : []),
28
+ ];
29
+ return lines.length > 0 ? lines.join("\n") : "Request completed.";
30
+ }
14
31
  export async function createBuiltinMiddlewareTools(backend, options) {
15
32
  const tools = new Map();
33
+ let todoSnapshot = {
34
+ total: 0,
35
+ pending: 0,
36
+ completed: 0,
37
+ items: [],
38
+ };
16
39
  tools.set("write_todos", {
17
40
  name: "write_todos",
18
41
  schema: z.object({
@@ -21,6 +44,7 @@ export async function createBuiltinMiddlewareTools(backend, options) {
21
44
  invoke: async (input) => {
22
45
  const args = isRecord(input) ? input : {};
23
46
  const summary = summarizeBuiltinWriteTodosArgs(args);
47
+ todoSnapshot = summary;
24
48
  return {
25
49
  ok: true,
26
50
  tool: "write_todos",
@@ -29,6 +53,16 @@ export async function createBuiltinMiddlewareTools(backend, options) {
29
53
  };
30
54
  },
31
55
  });
56
+ tools.set("read_todos", {
57
+ name: "read_todos",
58
+ schema: z.object({}).passthrough(),
59
+ invoke: async () => ({
60
+ ok: true,
61
+ tool: "read_todos",
62
+ message: formatBuiltinTodoSnapshot(todoSnapshot),
63
+ summary: todoSnapshot,
64
+ }),
65
+ });
32
66
  tools.set("ls", {
33
67
  name: "ls",
34
68
  schema: z.object({ path: z.string().optional().default("/") }).passthrough(),
@@ -48,6 +82,11 @@ export async function createBuiltinMiddlewareTools(backend, options) {
48
82
  return truncateLines(infos.map((info) => info.is_dir ? `${info.path} (directory)` : `${info.path}${info.size ? ` (${info.size} bytes)` : ""}`));
49
83
  },
50
84
  });
85
+ tools.set("list_files", {
86
+ name: "list_files",
87
+ schema: z.object({ path: z.string().optional().default("/") }).passthrough(),
88
+ invoke: async (input) => tools.get("ls").invoke(input),
89
+ });
51
90
  tools.set("read_file", {
52
91
  name: "read_file",
53
92
  schema: z.object({
@@ -142,6 +181,25 @@ export async function createBuiltinMiddlewareTools(backend, options) {
142
181
  return truncateLines(lines);
143
182
  },
144
183
  });
184
+ tools.set("search_files", {
185
+ name: "search_files",
186
+ schema: z.object({
187
+ query: z.string(),
188
+ path: z.string().optional().default("/"),
189
+ glob: z.string().nullable().optional(),
190
+ search_type: z.enum(["content", "path"]).optional().default("content"),
191
+ }).passthrough(),
192
+ invoke: async (input) => {
193
+ const typed = isRecord(input) ? input : {};
194
+ const query = typeof typed.query === "string" ? typed.query : "";
195
+ const path = typeof typed.path === "string" ? typed.path : "/";
196
+ const globPattern = typeof typed.glob === "string" ? typed.glob : null;
197
+ const searchType = typed.search_type === "path" ? "path" : "content";
198
+ return searchType === "path"
199
+ ? tools.get("glob").invoke({ pattern: query, path })
200
+ : tools.get("grep").invoke({ pattern: query, path, glob: globPattern });
201
+ },
202
+ });
145
203
  tools.set("execute", {
146
204
  name: "execute",
147
205
  schema: z.object({ command: z.string() }).passthrough(),
@@ -161,6 +219,131 @@ export async function createBuiltinMiddlewareTools(backend, options) {
161
219
  return parts.join("");
162
220
  },
163
221
  });
222
+ tools.set("run_command", {
223
+ name: "run_command",
224
+ schema: z.object({ command: z.string() }).passthrough(),
225
+ invoke: async (input) => tools.get("execute").invoke(input),
226
+ });
227
+ tools.set("fetch_url", {
228
+ name: "fetch_url",
229
+ schema: z.object({ url: z.string() }).passthrough(),
230
+ invoke: async (input) => {
231
+ if (typeof backend.fetchUrl !== "function") {
232
+ return notAvailable("fetch_url", "URL fetching");
233
+ }
234
+ const typed = isRecord(input) ? input : {};
235
+ const result = await Promise.resolve(backend.fetchUrl(typeof typed.url === "string" ? typed.url : ""));
236
+ if (typeof result === "string") {
237
+ return result;
238
+ }
239
+ return formatHttpResponse(result);
240
+ },
241
+ });
242
+ tools.set("http_request", {
243
+ name: "http_request",
244
+ schema: z.object({
245
+ url: z.string(),
246
+ method: z.string().optional(),
247
+ headers: z.record(z.string(), z.string()).optional(),
248
+ body: z.string().optional(),
249
+ }).passthrough(),
250
+ invoke: async (input) => {
251
+ if (typeof backend.httpRequest !== "function") {
252
+ return notAvailable("http_request", "structured HTTP requests");
253
+ }
254
+ const typed = isRecord(input) ? input : {};
255
+ const result = await Promise.resolve(backend.httpRequest({
256
+ url: typeof typed.url === "string" ? typed.url : "",
257
+ method: typeof typed.method === "string" ? typed.method : undefined,
258
+ headers: typed.headers && typeof typed.headers === "object" && !Array.isArray(typed.headers)
259
+ ? Object.fromEntries(Object.entries(typed.headers).filter(([, value]) => typeof value === "string"))
260
+ : undefined,
261
+ body: typeof typed.body === "string" ? typed.body : undefined,
262
+ }));
263
+ if (typeof result === "string") {
264
+ return result;
265
+ }
266
+ return formatHttpResponse(result);
267
+ },
268
+ });
269
+ tools.set("send_message", {
270
+ name: "send_message",
271
+ schema: z.object({
272
+ destination: z.string(),
273
+ message: z.string(),
274
+ subject: z.string().optional(),
275
+ metadata: z.record(z.string(), z.unknown()).optional(),
276
+ }).passthrough(),
277
+ invoke: async (input) => {
278
+ if (typeof backend.sendMessage !== "function") {
279
+ return notAvailable("send_message", "message delivery");
280
+ }
281
+ const typed = isRecord(input) ? input : {};
282
+ const result = await Promise.resolve(backend.sendMessage({
283
+ destination: typeof typed.destination === "string" ? typed.destination : "",
284
+ message: typeof typed.message === "string" ? typed.message : "",
285
+ subject: typeof typed.subject === "string" ? typed.subject : undefined,
286
+ metadata: isRecord(typed.metadata) ? typed.metadata : undefined,
287
+ }));
288
+ if (typeof result === "string") {
289
+ return result;
290
+ }
291
+ return result?.error ?? `Message sent to '${typeof typed.destination === "string" ? typed.destination : ""}'${result?.id ? ` (id: ${result.id})` : ""}.`;
292
+ },
293
+ });
294
+ tools.set("request_approval", {
295
+ name: "request_approval",
296
+ schema: z.object({
297
+ action: z.string(),
298
+ reason: z.string().optional(),
299
+ details: z.record(z.string(), z.unknown()).optional(),
300
+ }).passthrough(),
301
+ invoke: async (input) => {
302
+ if (typeof backend.requestApproval !== "function") {
303
+ return notAvailable("request_approval", "approval requests");
304
+ }
305
+ const typed = isRecord(input) ? input : {};
306
+ const result = await Promise.resolve(backend.requestApproval({
307
+ action: typeof typed.action === "string" ? typed.action : "",
308
+ reason: typeof typed.reason === "string" ? typed.reason : undefined,
309
+ details: isRecord(typed.details) ? typed.details : undefined,
310
+ }));
311
+ if (typeof result === "string") {
312
+ return result;
313
+ }
314
+ if (result?.error) {
315
+ return result.error;
316
+ }
317
+ return result?.approved === true
318
+ ? `Approval granted for '${typeof typed.action === "string" ? typed.action : ""}'${result.id ? ` (id: ${result.id})` : ""}.`
319
+ : `Approval requested for '${typeof typed.action === "string" ? typed.action : ""}'${result?.id ? ` (id: ${result.id})` : ""}.`;
320
+ },
321
+ });
322
+ tools.set("schedule_task", {
323
+ name: "schedule_task",
324
+ schema: z.object({
325
+ instruction: z.string(),
326
+ when: z.string(),
327
+ name: z.string().optional(),
328
+ metadata: z.record(z.string(), z.unknown()).optional(),
329
+ }).passthrough(),
330
+ invoke: async (input) => {
331
+ if (typeof backend.scheduleTask !== "function") {
332
+ return notAvailable("schedule_task", "task scheduling");
333
+ }
334
+ const typed = isRecord(input) ? input : {};
335
+ const result = await Promise.resolve(backend.scheduleTask({
336
+ instruction: typeof typed.instruction === "string" ? typed.instruction : "",
337
+ when: typeof typed.when === "string" ? typed.when : "",
338
+ name: typeof typed.name === "string" ? typed.name : undefined,
339
+ metadata: isRecord(typed.metadata) ? typed.metadata : undefined,
340
+ }));
341
+ if (typeof result === "string") {
342
+ return result;
343
+ }
344
+ return result?.error ?? `Scheduled task '${typeof typed.name === "string" ? typed.name : typeof typed.instruction === "string" ? typed.instruction : ""}' for ${typeof typed.when === "string" ? typed.when : ""}${result?.id ? ` (id: ${result.id})` : ""}.`;
345
+ },
346
+ });
164
347
  if (options.includeTaskTool && options.invokeTaskTool) {
165
348
  tools.set("task", {
166
349
  name: "task",
@@ -170,6 +353,14 @@ export async function createBuiltinMiddlewareTools(backend, options) {
170
353
  }).passthrough(),
171
354
  invoke: options.invokeTaskTool,
172
355
  });
356
+ tools.set("delegate_task", {
357
+ name: "delegate_task",
358
+ schema: z.object({
359
+ description: z.string(),
360
+ subagent_type: z.string(),
361
+ }).passthrough(),
362
+ invoke: async (input) => tools.get("task").invoke(input),
363
+ });
173
364
  }
174
365
  return tools;
175
366
  }