@iinm/plain-agent 1.8.3 → 1.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -504,8 +504,8 @@ The agent can use the following tools to assist with tasks:
504
504
  - **tmux_command**: Run a tmux command.
505
505
  - **ask_web**: Use the web search to answer questions that need up-to-date information or supporting sources. (requires Google API key or Vertex AI configuration).
506
506
  - **ask_url**: Use one or more provided URLs to answer a question. Include the URLs in your question. (requires Google API key or Vertex AI configuration).
507
- - **delegate_to_subagent**: Delegate a subtask to a subagent. The agent switches to a subagent role within the same conversation, focusing on the specified goal.
508
- - **report_as_subagent**: Report completion and return to the main agent. Used by subagents to communicate results and restore the main agent role. After reporting, the subagent's conversation history is removed from the context.
507
+ - **switch_to_subagent**: Switch to a subagent role within the same conversation, focusing on the specified goal.
508
+ - **switch_to_main_agent**: Switch back to the main agent role and report the result. After reporting, the subagent's conversation history is removed from the context.
509
509
  - **compact_context**: Compact the conversation context by discarding prior messages and reloading task state from a memory file. Use when the context has grown large but the task is not yet complete. Can also be invoked via the `/compact` slash command.
510
510
 
511
511
  ## Prompts
@@ -34,7 +34,7 @@
34
34
  "action": "ask"
35
35
  },
36
36
  {
37
- "toolName": { "$regex": "^(delegate_to_subagent|report_as_subagent)$" },
37
+ "toolName": { "$regex": "^(switch_to_subagent|switch_to_main_agent)$" },
38
38
  "action": "allow"
39
39
  },
40
40
  {
@@ -43,7 +43,7 @@ Ask what the user wants to configure. Common topics:
43
43
 
44
44
  If the request is vague, ask a focused clarifying question before proceeding.
45
45
 
46
- **If the user wants to configure Sandbox**: immediately delegate to the `sandbox-configurator` agent and do not proceed further yourself.
46
+ **If the user wants to configure Sandbox**: immediately switch to the `sandbox-configurator` subagent and do not proceed further yourself.
47
47
 
48
48
  ## Step 4: Apply Changes
49
49
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iinm/plain-agent",
3
- "version": "1.8.3",
3
+ "version": "1.8.5",
4
4
  "description": "A lightweight CLI-based coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -731,6 +731,14 @@ setup_container_firewall() {
731
731
  # allow established connections
732
732
  iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
733
733
  iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
734
+
735
+ # allow localhost on IPv6
736
+ ip6tables -A INPUT -i lo -j ACCEPT
737
+ ip6tables -A OUTPUT -o lo -j ACCEPT
738
+
739
+ # allow established connections on IPv6
740
+ ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
741
+ ip6tables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
734
742
 
735
743
  # allow given addresses
736
744
  for address in "${addresses[@]}"; do
@@ -750,6 +758,11 @@ setup_container_firewall() {
750
758
  fi
751
759
  done
752
760
  iptables -A OUTPUT -p tcp -m set --match-set allow_list dst,dst -j ACCEPT
761
+
762
+ # Reject unauthorized TCP connections immediately with RST
763
+ # instead of silently dropping and causing timeout
764
+ iptables -A OUTPUT -p tcp -j REJECT --reject-with tcp-reset
765
+ ip6tables -A OUTPUT -p tcp -j REJECT --reject-with tcp-reset
753
766
  }
754
767
 
755
768
  setup_container_user() {
package/src/agent.mjs CHANGED
@@ -2,8 +2,8 @@
2
2
  * @import { Agent, AgentConfig, AgentEventEmitter, UserEventEmitter } from "./agent"
3
3
  * @import { Tool, ToolDefinition } from "./tool"
4
4
  * @import { CompactContextInput } from "./tools/compactContext"
5
- * @import { DelegateToSubagentInput } from "./tools/delegateToSubagent"
6
- * @import { ReportAsSubagentInput } from "./tools/reportAsSubagent"
5
+ * @import { SwitchToSubagentInput } from "./tools/switchToSubagent"
6
+ * @import { SwitchToMainAgentInput } from "./tools/switchToMainAgent"
7
7
  */
8
8
 
9
9
  import { EventEmitter } from "node:events";
@@ -18,8 +18,8 @@ import {
18
18
  compactContextToolName,
19
19
  readMemoryForCompaction,
20
20
  } from "./tools/compactContext.mjs";
21
- import { delegateToSubagentToolName } from "./tools/delegateToSubagent.mjs";
22
- import { reportAsSubagentToolName } from "./tools/reportAsSubagent.mjs";
21
+ import { switchToMainAgentToolName } from "./tools/switchToMainAgent.mjs";
22
+ import { switchToSubagentToolName } from "./tools/switchToSubagent.mjs";
23
23
 
24
24
  /**
25
25
  * @param {AgentConfig} config
@@ -69,10 +69,10 @@ export function createAgent({
69
69
  });
70
70
 
71
71
  /**
72
- * @param {DelegateToSubagentInput} input
72
+ * @param {SwitchToSubagentInput} input
73
73
  */
74
- const delegateToSubagentImpl = async (input) => {
75
- const result = subagentManager.delegateToSubagent(
74
+ const switchToSubagentImpl = async (input) => {
75
+ const result = subagentManager.switchToSubagent(
76
76
  input.name,
77
77
  input.goal,
78
78
  stateManager.getMessages().length - 1,
@@ -84,10 +84,10 @@ export function createAgent({
84
84
  };
85
85
 
86
86
  /**
87
- * @param {ReportAsSubagentInput} input
87
+ * @param {SwitchToMainAgentInput} input
88
88
  */
89
- const reportAsSubagentImpl = async (input) => {
90
- const result = await subagentManager.reportAsSubagent(input.memoryPath);
89
+ const switchToMainAgentImpl = async (input) => {
90
+ const result = await subagentManager.switchToMainAgent(input.memoryPath);
91
91
  if (!result.success) {
92
92
  return new Error(result.error);
93
93
  }
@@ -101,7 +101,7 @@ export function createAgent({
101
101
  if (subagentManager.isSubagentActive()) {
102
102
  return new Error(
103
103
  "compact_context cannot be used while running as a subagent. " +
104
- "Call report_as_subagent to return to the main agent first.",
104
+ "Call switch_to_main_agent to return to the main agent first.",
105
105
  );
106
106
  }
107
107
  const input = /** @type {CompactContextInput} */ (rawInput);
@@ -111,11 +111,11 @@ export function createAgent({
111
111
  /** @type {Map<string, Tool>} */
112
112
  const toolByName = new Map();
113
113
  for (const tool of tools) {
114
- if (tool.def.name === delegateToSubagentToolName && tool.injectImpl) {
115
- tool.injectImpl(delegateToSubagentImpl);
114
+ if (tool.def.name === switchToSubagentToolName && tool.injectImpl) {
115
+ tool.injectImpl(switchToSubagentImpl);
116
116
  }
117
- if (tool.def.name === reportAsSubagentToolName && tool.injectImpl) {
118
- tool.injectImpl(reportAsSubagentImpl);
117
+ if (tool.def.name === switchToMainAgentToolName && tool.injectImpl) {
118
+ tool.injectImpl(switchToMainAgentImpl);
119
119
  }
120
120
  if (tool.def.name === compactContextToolName && tool.injectImpl) {
121
121
  tool.injectImpl(compactContextImpl);
@@ -127,7 +127,7 @@ export function createAgent({
127
127
  const toolDefs = tools.map(({ def }) => def);
128
128
 
129
129
  const toolExecutor = createToolExecutor(toolByName, {
130
- exclusiveToolNames: [delegateToSubagentToolName, reportAsSubagentToolName],
130
+ exclusiveToolNames: [switchToSubagentToolName, switchToMainAgentToolName],
131
131
  });
132
132
 
133
133
  async function dumpMessages() {
@@ -54,7 +54,7 @@ export function createCommandHandler({
54
54
  const goalText =
55
55
  goalTextContent?.type === "text" ? goalTextContent.text : goal;
56
56
 
57
- const messageText = `Delegate to "${name}" agent with goal: ${goalText}`;
57
+ const messageText = `Switch to "${name}" subagent with goal: ${goalText}`;
58
58
  userEventEmitter.emit("userInput", [
59
59
  { type: "text", text: messageText },
60
60
  ...goalImages,
@@ -5,7 +5,7 @@
5
5
  * @import { PatchFileInput } from "./tools/patchFile"
6
6
  * @import { WriteFileInput } from "./tools/writeFile"
7
7
  * @import { TmuxCommandInput } from "./tools/tmuxCommand"
8
- * @import { DelegateToSubagentInput } from "./tools/delegateToSubagent"
8
+ * @import { SwitchToSubagentInput } from "./tools/switchToSubagent"
9
9
  */
10
10
 
11
11
  import { styleText } from "node:util";
@@ -129,13 +129,13 @@ export function formatToolUse(toolUse) {
129
129
  ].join("\n");
130
130
  }
131
131
 
132
- if (toolName === "delegate_to_subagent") {
133
- /** @type {Partial<DelegateToSubagentInput>} */
134
- const delegateInput = input;
132
+ if (toolName === "switch_to_subagent") {
133
+ /** @type {Partial<SwitchToSubagentInput>} */
134
+ const switchToSubagentInput = input;
135
135
  return [
136
136
  `tool: ${toolName}`,
137
- `name: ${delegateInput.name}`,
138
- `goal: ${delegateInput.goal}`,
137
+ `name: ${switchToSubagentInput.name}`,
138
+ `goal: ${switchToSubagentInput.goal}`,
139
139
  ].join("\n");
140
140
  }
141
141
 
@@ -149,12 +149,12 @@ export function formatToolUse(toolUse) {
149
149
  ].join("\n");
150
150
  }
151
151
 
152
- if (toolName === "report_as_subagent") {
153
- /** @type {Partial<import("./tools/reportAsSubagent").ReportAsSubagentInput>} */
154
- const reportAsSubagentInput = input;
152
+ if (toolName === "switch_to_main_agent") {
153
+ /** @type {Partial<import("./tools/switchToMainAgent").SwitchToMainAgentInput>} */
154
+ const switchToMainAgentInput = input;
155
155
  return [
156
156
  `tool: ${toolName}`,
157
- `memoryPath: ${reportAsSubagentInput.memoryPath}`,
157
+ `memoryPath: ${switchToMainAgentInput.memoryPath}`,
158
158
  ].join("\n");
159
159
  }
160
160
 
@@ -52,41 +52,52 @@ export async function loadAgentRoles(claudeCodePlugins) {
52
52
  }
53
53
  }
54
54
 
55
- /** @type {Map<string, AgentRole>} */
56
- const roles = new Map();
57
-
58
- for (const { dir, idPrefix, only } of agentDirs) {
59
- const files = await getMarkdownFiles(dir).catch((err) => {
60
- if (err.code !== "ENOENT") {
61
- console.warn(`Failed to list agent roles in ${dir}:`, err);
62
- }
63
- return [];
64
- });
65
-
66
- for (const file of files) {
67
- const fullPath = path.join(dir, file);
68
- const content = await fs.readFile(fullPath, "utf-8").catch((err) => {
69
- console.warn(`Failed to read agent role file ${fullPath}:`, err);
70
- return null;
71
- });
72
-
73
- if (content === null) continue;
74
-
75
- // Filter by only pattern if specified
76
- if (only && !only.test(file)) {
77
- continue;
78
- }
79
-
80
- let role = parseAgentRole(file, content, fullPath, idPrefix);
81
- if (role.import) {
82
- role = await mergeRemoteRole(role, file, fullPath);
83
- }
84
-
85
- roles.set(role.id, role);
86
- }
87
- }
55
+ const files = (
56
+ await Promise.all(
57
+ agentDirs.map(async ({ dir, idPrefix, only }) => {
58
+ const files = await getMarkdownFiles(dir).catch((err) => {
59
+ if (err.code !== "ENOENT") {
60
+ console.warn(`Failed to list agent roles in ${dir}:`, err);
61
+ }
62
+ return /** @type {string[]} */ ([]);
63
+ });
64
+ return files.map((file) => ({ dir, file, idPrefix, only }));
65
+ }),
66
+ )
67
+ )
68
+ .flat()
69
+ // Filter by only pattern if specified
70
+ .filter(({ file, only }) => !(only && !only.test(file)));
71
+
72
+ const roles = /** @type {AgentRole[]} */ (
73
+ (
74
+ await Promise.all(
75
+ files.map(async ({ dir, file, idPrefix }) => {
76
+ const fullPath = path.join(dir, file);
77
+ const content = await fs.readFile(fullPath, "utf-8").catch((err) => {
78
+ console.warn(`Failed to read agent role file ${fullPath}:`, err);
79
+ return null;
80
+ });
81
+
82
+ if (content === null) return null;
83
+
84
+ let role = parseAgentRole(file, content, fullPath, idPrefix);
85
+ if (role.import) {
86
+ try {
87
+ role = await mergeRemoteRole(role, file, fullPath);
88
+ } catch (err) {
89
+ console.warn(`Failed to import remote role ${role.id}:`, err);
90
+ return null;
91
+ }
92
+ }
93
+
94
+ return role;
95
+ }),
96
+ )
97
+ ).filter((role) => role)
98
+ );
88
99
 
89
- return roles;
100
+ return new Map(roles.map((role) => [role.id, role]));
90
101
  }
91
102
 
92
103
  /**
@@ -68,50 +68,63 @@ export async function loadPrompts(claudeCodePlugins) {
68
68
  }
69
69
  }
70
70
 
71
- /** @type {Map<string, Prompt>} */
72
- const prompts = new Map();
73
-
74
- for (const { dir, idPrefix, only } of promptDirs) {
75
- const files = await getMarkdownFiles(dir).catch((err) => {
76
- if (err.code !== "ENOENT") {
77
- console.warn(`Failed to list prompts in ${dir}:`, err);
78
- }
79
- return [];
80
- });
81
-
82
- for (const file of files) {
83
- const fullPath = path.join(dir, file);
84
- const content = await fs.readFile(fullPath, "utf-8").catch((err) => {
85
- console.warn(`Failed to read prompt file ${fullPath}:`, err);
86
- return null;
87
- });
88
-
89
- if (content === null) continue;
90
-
91
- // Filter by only pattern if specified
92
- if (only && !only.test(file)) {
93
- continue;
94
- }
95
-
96
- // Ignore all files in the skills/ directory except for SKILL.md.
97
- if (fullPath.match(/\/skills\//) && !file.endsWith("/SKILL.md")) {
98
- continue;
99
- }
100
-
101
- let prompt = parsePrompt(file, content, fullPath, idPrefix);
102
- if (prompt.import) {
103
- prompt = await mergeRemotePrompt(prompt, file, fullPath);
104
- }
105
-
106
- if (prompt.userInvocable === false) {
107
- continue;
108
- }
71
+ const files = (
72
+ await Promise.all(
73
+ promptDirs.map(async ({ dir, idPrefix, only }) => {
74
+ const files = await getMarkdownFiles(dir).catch((err) => {
75
+ if (err.code !== "ENOENT") {
76
+ console.warn(`Failed to list prompts in ${dir}:`, err);
77
+ }
78
+ return /** @type {string[]} */ ([]);
79
+ });
80
+ return files.map((file) => ({ dir, file, idPrefix, only }));
81
+ }),
82
+ )
83
+ )
84
+ .flat()
85
+ // Filter by only pattern if specified
86
+ .filter(({ file, only }) => !(only && !only.test(file)))
87
+ // Ignore all files in the skills/ directory except for SKILL.md.
88
+ .filter(
89
+ ({ file, dir }) =>
90
+ !(
91
+ path.join(dir, file).includes("/skills/") &&
92
+ !file.endsWith("/SKILL.md")
93
+ ),
94
+ );
109
95
 
110
- prompts.set(prompt.id, prompt);
111
- }
112
- }
96
+ const prompts = /** @type {Prompt[]} */ (
97
+ (
98
+ await Promise.all(
99
+ files.map(async ({ dir, file, idPrefix }) => {
100
+ const fullPath = path.join(dir, file);
101
+ const content = await fs.readFile(fullPath, "utf-8").catch((err) => {
102
+ console.warn(`Failed to read prompt file ${fullPath}:`, err);
103
+ return null;
104
+ });
105
+
106
+ if (content === null) return null;
107
+
108
+ let prompt = parsePrompt(file, content, fullPath, idPrefix);
109
+ if (prompt.import) {
110
+ try {
111
+ prompt = await mergeRemotePrompt(prompt, file, fullPath);
112
+ } catch (err) {
113
+ console.warn(`Failed to import remote prompt ${prompt.id}:`, err);
114
+ return null;
115
+ }
116
+ }
117
+
118
+ if (prompt.userInvocable === false) {
119
+ return null;
120
+ }
121
+ return prompt;
122
+ }),
123
+ )
124
+ ).filter((prompt) => prompt)
125
+ );
113
126
 
114
- return prompts;
127
+ return new Map(prompts.map((prompt) => [prompt.id, prompt]));
115
128
  }
116
129
 
117
130
  /**
package/src/main.mjs CHANGED
@@ -22,10 +22,10 @@ import { createPrompt } from "./prompt.mjs";
22
22
  import { createAskURLTool } from "./tools/askURL.mjs";
23
23
  import { createAskWebTool } from "./tools/askWeb.mjs";
24
24
  import { createCompactContextTool } from "./tools/compactContext.mjs";
25
- import { createDelegateToSubagentTool } from "./tools/delegateToSubagent.mjs";
26
25
  import { createExecCommandTool } from "./tools/execCommand.mjs";
27
26
  import { createPatchFileTool } from "./tools/patchFile.mjs";
28
- import { createReportAsSubagentTool } from "./tools/reportAsSubagent.mjs";
27
+ import { createSwitchToMainAgentTool } from "./tools/switchToMainAgent.mjs";
28
+ import { createSwitchToSubagentTool } from "./tools/switchToSubagent.mjs";
29
29
  import { createTmuxCommandTool } from "./tools/tmuxCommand.mjs";
30
30
  import { writeFileTool } from "./tools/writeFile.mjs";
31
31
  import { createToolUseApprover } from "./toolUseApprover.mjs";
@@ -158,8 +158,10 @@ if (cliArgs.subcommand.type === "cost") {
158
158
  const modelNameWithVariant = modelFromArgs || modelFromConfig;
159
159
 
160
160
  const pluginPaths = resolvePluginPaths(appConfig.claudeCodePlugins ?? []);
161
- const agentRoles = await loadAgentRoles(pluginPaths);
162
- const prompts = await loadPrompts(pluginPaths);
161
+ const [prompts, agentRoles] = await Promise.all([
162
+ loadPrompts(pluginPaths),
163
+ loadAgentRoles(pluginPaths),
164
+ ]);
163
165
 
164
166
  const prompt = createPrompt({
165
167
  username: USER_NAME,
@@ -179,8 +181,8 @@ if (cliArgs.subcommand.type === "cost") {
179
181
  createPatchFileTool(),
180
182
  createTmuxCommandTool({ sandbox: appConfig.sandbox }),
181
183
  createCompactContextTool(),
182
- createDelegateToSubagentTool(),
183
- createReportAsSubagentTool(),
184
+ createSwitchToSubagentTool(),
185
+ createSwitchToMainAgentTool(),
184
186
  ];
185
187
 
186
188
  if (appConfig.tools?.askWeb) {
package/src/prompt.mjs CHANGED
@@ -133,6 +133,6 @@ ${agentRoleDescriptions}
133
133
  export const CLAUDE_CODE_COMPATIBILITY_NOTES = `# Environment Constraints
134
134
 
135
135
  - Use memory file to manage todo list.
136
- - Subagents cannot run in parallel. Delegate to them one at a time.
136
+ - Subagents cannot run in parallel. Switch to them one at a time.
137
137
  - Use AGENTS.md instead when CLAUDE.md is absent.
138
138
  - If instructed to use "haiku agent", "sonnet agent", or "opus agent", use "worker" instead.`;
package/src/subagent.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @import { Message, MessageContentToolResult, MessageContentToolUse } from "./model"
3
- * @import { ReportAsSubagentInput } from "./tools/reportAsSubagent"
3
+ * @import { SwitchToMainAgentInput } from "./tools/switchToMainAgent"
4
4
  * @import { AgentRole } from "./context/loadAgentRoles.mjs"
5
5
  */
6
6
 
@@ -8,7 +8,7 @@ import fs from "node:fs/promises";
8
8
  import path from "node:path";
9
9
  import { AGENT_PROJECT_METADATA_DIR } from "./env.mjs";
10
10
  import { CLAUDE_CODE_COMPATIBILITY_NOTES } from "./prompt.mjs";
11
- import { reportAsSubagentToolName } from "./tools/reportAsSubagent.mjs";
11
+ import { switchToMainAgentToolName } from "./tools/switchToMainAgent.mjs";
12
12
 
13
13
  /** @typedef {ReturnType<typeof createSubagentManager>} SubagentManager */
14
14
 
@@ -23,39 +23,39 @@ import { reportAsSubagentToolName } from "./tools/reportAsSubagent.mjs";
23
23
  * @param {SubagentStateEventHandlers} handlers
24
24
  */
25
25
  export function createSubagentManager(agentRoles, handlers) {
26
- /** @type {{name: string; goal: string; delegationMessageIndex: number}[]} */
26
+ /** @type {{name: string; goal: string; switchMessageIndex: number}[]} */
27
27
  const subagents = [];
28
28
  let subagentCount = 0;
29
29
 
30
30
  /**
31
- * @typedef {DelegateSuccess | DelegateFailure} DelegateResult
31
+ * @typedef {SwitchToSubagentSuccess | SwitchToSubagentFailure} SwitchToSubagentResult
32
32
  */
33
33
 
34
34
  /**
35
- * @typedef {Object} DelegateSuccess
35
+ * @typedef {Object} SwitchToSubagentSuccess
36
36
  * @property {true} success
37
37
  * @property {string} value
38
38
  */
39
39
 
40
40
  /**
41
- * @typedef {Object} DelegateFailure
41
+ * @typedef {Object} SwitchToSubagentFailure
42
42
  * @property {false} success
43
43
  * @property {string} error
44
44
  */
45
45
 
46
46
  /**
47
- * Delegate a task to a subagent.
47
+ * Switch to a subagent role.
48
48
  * @param {string} name
49
49
  * @param {string} goal
50
- * @param {number} delegationMessageIndex
51
- * @returns {DelegateResult}
50
+ * @param {number} switchMessageIndex
51
+ * @returns {SwitchToSubagentResult}
52
52
  */
53
- function delegateToSubagent(name, goal, delegationMessageIndex) {
53
+ function switchToSubagent(name, goal, switchMessageIndex) {
54
54
  if (subagents.length > 0) {
55
55
  return {
56
56
  success: false,
57
57
  error:
58
- "Cannot call delegate_to_subagent while already acting as a subagent.",
58
+ "Cannot call switch_to_subagent while already acting as a subagent.",
59
59
  };
60
60
  }
61
61
 
@@ -86,7 +86,7 @@ export function createSubagentManager(agentRoles, handlers) {
86
86
  subagents.push({
87
87
  name: actualName,
88
88
  goal,
89
- delegationMessageIndex,
89
+ switchMessageIndex,
90
90
  });
91
91
  handlers.onSubagentSwitched({ name: actualName });
92
92
 
@@ -99,37 +99,37 @@ export function createSubagentManager(agentRoles, handlers) {
99
99
  : `Role: ${actualName}`,
100
100
  `Your goal: ${goal}`,
101
101
  `Memory file path format: ${AGENT_PROJECT_METADATA_DIR}/memory/<session-id>--${sequenceNumber}--${actualName.replace("/", "-")}--<kebab-case-title>.md (Replace <kebab-case-title> with a short title describing your own goal)`,
102
- `When finished, call "report_as_subagent" with the memory file path. Start executing your goal now.`,
102
+ `When finished, call "switch_to_main_agent" with the memory file path. Start executing your goal now.`,
103
103
  ].join("\n\n"),
104
104
  };
105
105
  }
106
106
 
107
107
  /**
108
- * @typedef {ReportSuccess | ReportFailure} ReportResult
108
+ * @typedef {SwitchToMainAgentSuccess | SwitchToMainAgentFailure} SwitchToMainAgentResult
109
109
  */
110
110
 
111
111
  /**
112
- * @typedef {Object} ReportSuccess
112
+ * @typedef {Object} SwitchToMainAgentSuccess
113
113
  * @property {true} success
114
114
  * @property {string} memoryContent
115
115
  */
116
116
 
117
117
  /**
118
- * @typedef {Object} ReportFailure
118
+ * @typedef {Object} SwitchToMainAgentFailure
119
119
  * @property {false} success
120
120
  * @property {string} error
121
121
  */
122
122
 
123
123
  /**
124
- * Report as a subagent and read the memory file.
124
+ * Switch back to the main agent role and read the memory file.
125
125
  * @param {string} memoryPath
126
- * @returns {Promise<ReportResult>}
126
+ * @returns {Promise<SwitchToMainAgentResult>}
127
127
  */
128
- async function reportAsSubagent(memoryPath) {
128
+ async function switchToMainAgent(memoryPath) {
129
129
  if (subagents.length === 0) {
130
130
  return {
131
131
  success: false,
132
- error: "Cannot call report_as_subagent from the main agent.",
132
+ error: "Cannot call switch_to_main_agent from the main agent.",
133
133
  };
134
134
  }
135
135
 
@@ -171,7 +171,7 @@ export function createSubagentManager(agentRoles, handlers) {
171
171
  */
172
172
  function processToolResults(toolUseParts, toolResults, messages) {
173
173
  const reportSubagentToolUse = toolUseParts.find(
174
- (toolUse) => toolUse.toolName === reportAsSubagentToolName,
174
+ (toolUse) => toolUse.toolName === switchToMainAgentToolName,
175
175
  );
176
176
 
177
177
  if (reportSubagentToolUse) {
@@ -193,7 +193,7 @@ export function createSubagentManager(agentRoles, handlers) {
193
193
 
194
194
  /**
195
195
  * Handle the result of a subagent reporting back.
196
- * On success, truncates conversation history back to the delegation point
196
+ * On success, truncates conversation history back to the switch point
197
197
  * and converts the report into a standard user message.
198
198
  * @param {MessageContentToolUse} reportToolUse
199
199
  * @param {MessageContentToolResult} reportResult
@@ -214,10 +214,10 @@ export function createSubagentManager(agentRoles, handlers) {
214
214
 
215
215
  handlers.onSubagentSwitched(subagents.at(-1) ?? null);
216
216
 
217
- // Truncate history back to the delegation point
217
+ // Truncate history back to the switch point
218
218
  const truncatedMessages = messages.slice(
219
219
  0,
220
- currentSubagent.delegationMessageIndex,
220
+ currentSubagent.switchMessageIndex,
221
221
  );
222
222
 
223
223
  // Convert the tool result into a standard user message
@@ -225,7 +225,7 @@ export function createSubagentManager(agentRoles, handlers) {
225
225
  .map((c) => (c.type === "text" ? c.text : ""))
226
226
  .join("\n\n");
227
227
 
228
- const reportInput = /** @type {ReportAsSubagentInput} */ (
228
+ const reportInput = /** @type {SwitchToMainAgentInput} */ (
229
229
  reportToolUse.input
230
230
  );
231
231
 
@@ -257,8 +257,8 @@ export function createSubagentManager(agentRoles, handlers) {
257
257
  }
258
258
 
259
259
  return {
260
- delegateToSubagent,
261
- reportAsSubagent,
260
+ switchToSubagent,
261
+ switchToMainAgent,
262
262
  processToolResults,
263
263
  isSubagentActive,
264
264
  };
@@ -0,0 +1,3 @@
1
+ export type SwitchToMainAgentInput = {
2
+ memoryPath: string;
3
+ };
@@ -2,10 +2,10 @@
2
2
  * @import { Tool, ToolImplementation } from '../tool'
3
3
  */
4
4
 
5
- export const reportAsSubagentToolName = "report_as_subagent";
5
+ export const switchToMainAgentToolName = "switch_to_main_agent";
6
6
 
7
7
  /** @returns {Tool} */
8
- export function createReportAsSubagentTool() {
8
+ export function createSwitchToMainAgentTool() {
9
9
  /** @type {ToolImplementation} */
10
10
  let impl = async () => {
11
11
  throw new Error("Not implemented");
@@ -14,9 +14,8 @@ export function createReportAsSubagentTool() {
14
14
  /** @type {Tool} */
15
15
  const tool = {
16
16
  def: {
17
- name: reportAsSubagentToolName,
18
- description:
19
- "End the subagent role and report the result to the main agent.",
17
+ name: switchToMainAgentToolName,
18
+ description: "Switch back to the main agent role and report the result.",
20
19
  inputSchema: {
21
20
  type: "object",
22
21
  properties: {
@@ -0,0 +1,4 @@
1
+ export type SwitchToSubagentInput = {
2
+ name: string;
3
+ goal: string;
4
+ };
@@ -2,10 +2,10 @@
2
2
  * @import { Tool, ToolImplementation } from '../tool'
3
3
  */
4
4
 
5
- export const delegateToSubagentToolName = "delegate_to_subagent";
5
+ export const switchToSubagentToolName = "switch_to_subagent";
6
6
 
7
7
  /** @returns {Tool} */
8
- export function createDelegateToSubagentTool() {
8
+ export function createSwitchToSubagentTool() {
9
9
  /** @type {ToolImplementation} */
10
10
  let impl = async () => {
11
11
  throw new Error("Not implemented");
@@ -14,9 +14,9 @@ export function createDelegateToSubagentTool() {
14
14
  /** @type {Tool} */
15
15
  const tool = {
16
16
  def: {
17
- name: delegateToSubagentToolName,
17
+ name: switchToSubagentToolName,
18
18
  description:
19
- "Delegate a subtask to a subagent. You inherit the current context and work on the delegated goal.",
19
+ "Switch to a subagent role within the same conversation, focusing on the specified goal. You inherit the current context.",
20
20
  inputSchema: {
21
21
  type: "object",
22
22
  properties: {
@@ -42,6 +42,17 @@ export function createDelegateToSubagentTool() {
42
42
  injectImpl(fn) {
43
43
  impl = fn;
44
44
  },
45
+
46
+ /**
47
+ * @param {Record<string, unknown>} input
48
+ * @returns {Record<string, unknown>}
49
+ */
50
+ maskApprovalInput: (input) => {
51
+ const { name } = /** @type {{name: string}} */ (input);
52
+ return {
53
+ name,
54
+ };
55
+ },
45
56
  };
46
57
 
47
58
  return tool;
@@ -1,4 +0,0 @@
1
- export type DelegateToSubagentInput = {
2
- name: string;
3
- goal: string;
4
- };
@@ -1,3 +0,0 @@
1
- export type ReportAsSubagentInput = {
2
- memoryPath: string;
3
- };