summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src/agent
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-28 22:51:47 +0900
committerAdam Malczewski <[email protected]>2026-05-28 22:51:47 +0900
commitd6609efd4e14101e77fb35a98ce597a32816862d (patch)
tree09ea404ce0a780ca6b8c380fdd93ad1ae9960986 /packages/core/src/agent
parent2eeabc95b78f6624c187e1e3892f9413266b4b9a (diff)
downloaddispatch-d6609efd4e14101e77fb35a98ce597a32816862d.tar.gz
dispatch-d6609efd4e14101e77fb35a98ce597a32816862d.zip
fix(core): normalize tool schemas for Anthropic, add toolChoice=auto; feat(summon): agent definition support; docs: cc/ research findings
- registry.ts: add normalizeForAnthropic() to strip , additionalProperties, default, nullable from zodToJsonSchema output so Anthropic doesn't silently reject tool definitions - agent.ts: add toolChoice=auto for Claude OAuth to prevent Opus thinking forever without calling tools - summon.ts: add agentSlug parameter, build agents catalog in description, add toAvailableAgents helper - agent-manager.ts: wire agent definition loading into spawnChildAgent, agent model fallback - loader.ts: export loadAgent, expandAgentToolNames, getAgentDirPaths; add getAgentDirPaths for permission gate - agent.ts: auto-allow read-only tools in agent definition directories - packaging/PKGBUILD: exclude ARM64 prebuilds from x86_64 package - cc/: research findings on Claude Opus tool calling issues - tests: loader tests, summon tool tests
Diffstat (limited to 'packages/core/src/agent')
-rw-r--r--packages/core/src/agent/agent.ts33
1 files changed, 32 insertions, 1 deletions
diff --git a/packages/core/src/agent/agent.ts b/packages/core/src/agent/agent.ts
index bb4ee7d..6139dec 100644
--- a/packages/core/src/agent/agent.ts
+++ b/packages/core/src/agent/agent.ts
@@ -2,6 +2,7 @@ import { dirname } from "node:path";
import type { ProviderOptions } from "@ai-sdk/provider-utils";
import type { ModelMessage, SystemModelMessage } from "ai";
import { streamText } from "ai";
+import { getAgentDirPaths } from "../agents/loader.js";
import { appendEventToChunks } from "../chunks/append.js";
import { buildBillingHeaderValue, SYSTEM_IDENTITY } from "../credentials/claude.js";
import { createProvider, prefixToolName, unprefixToolName } from "../llm/provider.js";
@@ -531,7 +532,27 @@ export class Agent {
const isSpillPath =
resolvedPath === resolvedSpillRoot || resolvedPath.startsWith(`${resolvedSpillRoot}/`);
- if (!isUnderWorkdir && !isSpillPath) {
+ // Agent definitions live in well-known directories
+ // (`~/.config/dispatch/agents/` and
+ // `<workdir>/.dispatch/agents/`). Reading those is a
+ // prerequisite for the summon tool's "specify which subagent"
+ // flow — the LLM needs to inspect the TOML to know what each
+ // agent does. We auto-allow READ-ONLY tools under those paths
+ // without prompting the user. Writes (`write_file`) still go
+ // through the normal external_directory gate so an agent can't
+ // quietly overwrite another agent's definition.
+ const isReadOnlyTool =
+ tc.name === "read_file" || tc.name === "read_file_slice" || tc.name === "list_files";
+ let isAgentsDirReadOnly = false;
+ if (isReadOnlyTool) {
+ const agentDirs = getAgentDirPaths(this.config.workingDirectory);
+ const canonicalAgentDirs = await Promise.all(agentDirs.map((d) => canonicalize(d)));
+ isAgentsDirReadOnly = canonicalAgentDirs.some(
+ (d) => resolvedPath === d || resolvedPath.startsWith(`${d}/`),
+ );
+ }
+
+ if (!isUnderWorkdir && !isSpillPath && !isAgentsDirReadOnly) {
const permissionType =
tc.name === "read_file" ? "read" : tc.name === "write_file" ? "edit" : "list";
@@ -715,6 +736,16 @@ export class Agent {
tools,
};
+ // Encourage tool use on Anthropic. Without an explicit
+ // `toolChoice`, Claude (especially Opus 4.7 with adaptive
+ // thinking) can decide to "think forever" instead of calling
+ // the tools it has been given. `"auto"` keeps Claude free to
+ // answer with text when no tool is needed, while making the
+ // availability of tools an explicit signal in the request.
+ if (isClaudeOAuth) {
+ streamOptions.toolChoice = "auto";
+ }
+
if (isClaudeOAuth && effort !== "none") {
// v6 native support for Opus 4.7 adaptive thinking via
// providerOptions. No more rewriteBodyForOpus47 body-