feat(p2): subagents can use code mode

codemode is choosable for subagents and in their default tools while the
codemode setting is on; one-shot agents never get it. A subagent with code
mode gets the script-only tools (MCP, terminal, agent, models.*) by name in
its Durable tools array, at spawn/configure and from the worker when the
registry's script-only tools change.
This commit is contained in:
Armin Ronacher 2026-11-04 21:53:05 +01:00
parent 617d0ef549
commit 474416d70b
7 changed files with 317 additions and 24 deletions

View file

@ -2,7 +2,7 @@ import { setTimeout as sleep } from "node:timers/promises";
import type { Models } from "@earendil-works/pi-ai/models";
import { type ConversationId, ROOT_CONVERSATION_ID } from "@earendil-works/pi-durable";
import { canSend, contribute, defineFacet, expectVariant, forbidden, signalOf } from "@p2/app/v1";
import { durableContext } from "@p2/app/v1/node";
import { durableContext, ExtensionSwitches } from "@p2/app/v1/node";
import {
CommitSignal,
ConversationConfigured,
@ -21,8 +21,10 @@ import { type Child, SubagentControl, Subagents } from "./contract.ts";
import { ALWAYS, Subagents as Registry } from "./contract.worker.ts";
import {
createSubagentExtension,
enableScriptTools,
ownLocation,
placeSubagents,
scriptOnly,
setArchived,
spawnEnvironments,
teamExtension,
@ -60,6 +62,18 @@ export default defineFacet((host, self) => {
directory: (target, from, context) => resolveDirectory(harness().environments, target, from, context),
});
self.contribute(Extensions, { [extension.name]: extension, [teamExtension.name]: teamExtension });
// The tools behind code mode, as the Harness's registry installs them (switched-off extensions left out).
const scriptTools = self.derive(
[self.registry(Extensions), self.registry(ExtensionSwitches)],
([extensions, switches]) =>
[...extensions]
.filter(([name]) => switches.get(name) !== false)
.flatMap(([, each]) => (each.tools ?? []).filter(scriptOnly).map((tool) => tool.name))
.sort(),
);
self.on(scriptTools, async ({ value }, ctx) => {
await enableScriptTools(harness().durable, value, durableContext(ctx));
});
self.contribute(ConversationConfigured, {
subagents: async (conversation, ctx) => {
if (conversation.id === ROOT_CONVERSATION_ID) return;