Files
AgentCompany/src/main/agent-runtime/internal-tools/router.ts
T
ZacharyZhang-NY b839ce4f23 Inject hire_agent and file_approval into CEO agent (C4 part 3/5)
Wire the two internal tools from parts 1-2 end-to-end:

- `invokeInternalTool` RPC method added to RuntimeBridge; worker tool
  `execute` calls route here, handler dispatches by tool name via the
  new `agent-runtime/internal-tools/router.ts`.
- `InternalToolCallbacks` (`onApprovalCreated` / `onApprovalResolved`
  / `onDomainChanged`) are late-bound on the bridge via
  `setInternalToolCallbacks`; `wire-api-server` now cross-wires the
  same callbacks used by the HTTP approval route so thread integration
  / automation fire identically across both entry points.
- `RunJob.internalTools` (new field) carries the per-run tool list
  main decides at dispatch time via `orchestration/internal-tool-
  selector.ts`. Selection rule: `hire_agent` → any agent with
  `canRequestHire` authority; `file_approval` → CEOs only (scoped to
  the `approve_ceo_strategy` use case).
- Worker `createPiAgent` call site now assembles `codingTools +
  internalTools` before handing to pi. Exhaustiveness guard on the
  switch catches new tool names at compile time.

No prompt-level wiring yet — the `initial-user-turn` and
`team-building.en.md` updates ship in parts 4 and 5 so this commit
only adds the tool surface without changing the CEO's observable
behaviour (the existing HTTP path is still the one the prompt tells
the CEO to call).

tests: pnpm typecheck + pnpm test:unit (529) + pnpm test:eval (8/8) green
2026-04-20 14:25:44 -04:00

65 lines
2.3 KiB
TypeScript

/**
* Dispatch router for main-side internal-tool handlers (C4 part 3/5).
*
* The worker RPC method `invokeInternalTool` lands here; this module
* picks the right handler by `toolName` and delegates. Adding a new
* internal tool means: (a) new worker-side tool definition under
* `src/main/worker/runtime/internal-tools/`, (b) new handler under
* `src/main/agent-runtime/internal-tools/`, (c) one new `case` in the
* switch below. No third file to update, no registry lookup — the
* compile-time `never`-exhaustiveness on the default case catches a
* missing branch at build time.
*
* The router never catches: every handler throws a classified
* `AppError` on failure and the caller (`RuntimeBridge`) translates the
* throw into the `{ ok: false, error }` RPC envelope. Silent swallow
* would collapse tool-error plus tool-success onto the same success
* wire shape, which is the exact failure mode the tool-error
* convention exists to prevent.
*/
import { HIRE_AGENT_TOOL_NAME } from "@main/worker/runtime/internal-tools/hire-agent-tool";
import { FILE_APPROVAL_TOOL_NAME } from "@main/worker/runtime/internal-tools/file-approval-tool";
import type {
InvokeInternalToolRpcPayload,
InvokeInternalToolRpcResult,
} from "@main/worker/runtime/internal-tools/internal-tool-rpc";
import {
handleHireAgentToolCall,
type HireAgentHandlerDeps,
} from "./hire-agent-handler";
import {
handleFileApprovalToolCall,
type FileApprovalHandlerDeps,
} from "./file-approval-handler";
import { UserInputError } from "@main/errors";
export interface InternalToolRouterDeps {
hireAgent: HireAgentHandlerDeps;
fileApproval: FileApprovalHandlerDeps;
}
export interface InternalToolRouterContext {
companyId: string;
agentId: string;
}
export function routeInternalToolCall(
deps: InternalToolRouterDeps,
ctx: InternalToolRouterContext,
payload: InvokeInternalToolRpcPayload,
): InvokeInternalToolRpcResult {
switch (payload.toolName) {
case HIRE_AGENT_TOOL_NAME:
return handleHireAgentToolCall(deps.hireAgent, ctx, payload.args);
case FILE_APPROVAL_TOOL_NAME:
return handleFileApprovalToolCall(deps.fileApproval, ctx, payload.args);
default:
throw new UserInputError({
code: "internal_tool.unknown",
message: `Unknown internal tool: ${payload.toolName}`,
context: { toolName: payload.toolName },
});
}
}