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
25 changes: 25 additions & 0 deletions lib/rpc-manager-tool-exposure.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,31 @@ test("navigating an unpinned session keeps the branch's restored tools", async (
assert.deepEqual(made.state.active, ["read", "bash", "lookup", "scripted", "tool_search"]);
});

test("navigation keeps the active tool order when the restored set is the same", async (t) => {
const declared = ["read", "bash", "edit", "write", "codemode", "grep", "find"];
const cases = [
{ name: "same order", restored: declared, calls: [] },
// pi rebuilt the loadout from the extension registration order.
{ name: "another order", restored: ["read", "bash", "edit", "write", "grep", "find", "codemode"], calls: [declared] },
];

for (const { name, restored, calls } of cases) {
const made = makeWrapper({
active: declared,
navigateTree: async () => {
made.state.active = [...restored];
return { cancelled: false };
},
});
t.after(() => made.wrapper.destroy());

await made.wrapper.send({ type: "navigate_tree", targetId: "same" });

assert.deepEqual(made.state.activations, calls, name);
assert.deepEqual(made.state.active, declared, name);
}
});

test("navigation keeps an extension tool off when the branch's loadout leaves it out", async (t) => {
const made = makeWrapper({
active: ["read", "grep", "find", "ls", "lookup", "ask"],
Expand Down
20 changes: 14 additions & 6 deletions lib/rpc-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -658,8 +658,9 @@ export class AgentSessionWrapper {
/**
* pi restores the target branch's loadout from its transcript when it navigates the tree,
* which can bring back coding tools the pinned preset leaves out and drop codemode, so a
* normal session applies its selection again. Chat only declares no tools, and a
* subagent's tools are fixed by its profile; both keep what pi restored.
* normal session applies its selection again when the set changed, and otherwise keeps the
* order the prompt was in. Chat only declares no tools, and a subagent's tools are fixed by
* its profile; both keep what pi restored.
*/
private async navigateTreeKeepingToolSelection(
targetId: string,
Expand All @@ -672,10 +673,17 @@ export class AgentSessionWrapper {
const entries = this.inner.sessionManager.getEntries() as unknown as SessionEntry[];
if (!readSubagentSessionResources(entries)) {
const activeAfter = this.inner.getActiveToolNames();
this.setActiveToolSelection(
readSessionToolSelection(entries) ?? activeAfter,
[...activeAfter, ...activeBefore.filter((name) => SESSION_TOOL_NAMES.has(name))],
);
const isToolChanged = activeBefore.length !== activeAfter.length
|| activeBefore.some((name) => !activeAfter.includes(name));
if (isToolChanged) {
this.setActiveToolSelection(
readSessionToolSelection(entries) ?? activeAfter,
[...activeAfter, ...activeBefore.filter((name) => SESSION_TOOL_NAMES.has(name))],
);
} else if (activeBefore.some((name, index) => name !== activeAfter[index])) {
// Same tools, other order: pi declares them in this order, so keep the previous one.
this.inner.setActiveToolsByName(activeBefore);
}
}
return { cancelled: false };
}
Expand Down