Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/coding-agent/src/discovery/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ export interface ParsedAgentFields {
prewalk?: boolean | string;
/** `true` = advise with the default advisor-role model; string = advise with that model pattern. */
advisor?: boolean | string;
/** `true` = keep the parent-owned `todo` tool in spawned sessions when the tools grant includes it. */
todo?: boolean;
}

/**
Expand Down Expand Up @@ -392,6 +394,7 @@ export function parseAgentFields(frontmatter: Record<string, unknown>): ParsedAg
readSummarize,
prewalk,
advisor,
todo: parseBoolean(frontmatter.todo) ?? undefined,
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/coding-agent/src/task/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ interface AgentFrontmatter {
blocking?: boolean;
prewalk?: boolean | string;
advisor?: boolean | string;
todo?: boolean;
}

interface EmbeddedAgentDef {
Expand Down
7 changes: 5 additions & 2 deletions packages/coding-agent/src/task/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3923,8 +3923,11 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes

// Todos are parent-owned bookkeeping and stripped from subagents —
// except under prewalk, whose plan nudge + todo gate require the
// subagent to commit its own todo list before the hand-off.
const isParentOwnedTool = (name: string): boolean => !prewalk && name === "todo";
// subagent to commit its own todo list before the hand-off, or when
// the agent definition opts in via `todo: true` frontmatter and its
// tools grant includes `todo` (issue #12575).
const keepsTodo = agent.todo === true && toolNames?.includes("todo") === true;
const isParentOwnedTool = (name: string): boolean => !prewalk && !keepsTodo && name === "todo";
const subagentToolNames = session.getEnabledToolNames();
const filteredSubagentTools = subagentToolNames.filter(name => !isParentOwnedTool(name));
if (filteredSubagentTools.length !== subagentToolNames.length) {
Expand Down
6 changes: 6 additions & 0 deletions packages/coding-agent/src/task/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ export interface AgentDefinition {
readSummarize?: boolean;
/** Prewalk hand-off for the spawned session: `true` = switch to the default prewalk target at the first edit/write, string = custom target model pattern. */
prewalk?: boolean | string;
/**
* Opt-in to the `todo` tool for spawned sessions of this agent. The todo
* list is parent-owned by default and stripped from subagents; `true`
* keeps it when the agent's `tools` grant includes `todo` (issue #12575).
*/
todo?: boolean;
/** Advisor for spawned sessions of this agent: `true` = advise with the default advisor-role model, string = advise with that model pattern (optional `:level` suffix). Absent/`false` = no advisor. */
advisor?: boolean | string;
source: AgentSource;
Expand Down
5 changes: 4 additions & 1 deletion packages/coding-agent/src/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,10 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
if (name === "eval") return allowEval;
if (name === "debug") return session.settings.get("debug.enabled");
if (name === "todo")
return (!includeYield || session.prewalkArmed === true) && session.settings.get("todo.enabled");
return (
(!includeYield || session.prewalkArmed === true || requestedTools?.includes("todo") === true) &&
session.settings.get("todo.enabled")
);
if (name === "glob") return session.settings.get("glob.enabled");
if (name === "grep") return session.settings.get("grep.enabled");
if (name === "github") return session.settings.get("github.enabled");
Expand Down
10 changes: 10 additions & 0 deletions packages/coding-agent/test/tools/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,16 @@ describe("createTools", () => {
expect(prewalkSubagent.map(t => t.name)).toContain("todo");
});

it("keeps todo for yield sessions with an explicit todo grant (issue #12575)", async () => {
// Explicit `tools: [read, todo]` frontmatter opts the subagent into its
// own list; the default stays parent-owned.
const granted = await createTools(createTestSession({ requireYieldTool: true }), ["read", "todo"]);
expect(granted.map(t => t.name)).toContain("todo");

const ungranted = await createTools(createTestSession({ requireYieldTool: true }), ["read"]);
expect(ungranted.map(t => t.name)).not.toContain("todo");
});

it("excludes ask tool when hasUI is false", async () => {
const session = createTestSession({ hasUI: false });
const tools = await createTools(session);
Expand Down