Jelajahi Sumber

rename handoff feature to subtask

alvinreal 3 bulan lalu
induk
melakukan
952c7091ae

+ 1 - 1
README.md

@@ -490,7 +490,7 @@ Use this section as a map: start with installation, then jump to features, confi
 | **[Session Management](docs/session-management.md)** | Reuse recent child-agent sessions with short aliases instead of starting over |
 | **[Session Management](docs/session-management.md)** | Reuse recent child-agent sessions with short aliases instead of starting over |
 | **[Todo Continuation](docs/todo-continuation.md)** | Auto-continue orchestrator sessions with cooldowns and safety checks |
 | **[Todo Continuation](docs/todo-continuation.md)** | Auto-continue orchestrator sessions with cooldowns and safety checks |
 | **[Preset Switching](docs/preset-switching.md)** | Switch agent model presets at runtime with `/preset` |
 | **[Preset Switching](docs/preset-switching.md)** | Switch agent model presets at runtime with `/preset` |
-| **[Handoff](docs/handoff.md)** | Spawn a child orchestrator with `/handoff`, do the requested work, and return a summary to the main session |
+| **[Subtask](docs/subtask.md)** | Spawn a child orchestrator with `/subtask`, do the requested work, and return a summary to the main session |
 | **[Codemap](docs/codemap.md)** | Generate hierarchical codemaps to understand large codebases faster |
 | **[Codemap](docs/codemap.md)** | Generate hierarchical codemaps to understand large codebases faster |
 | **[Interview](docs/interview.md)** | Turn rough ideas into a structured markdown spec through a browser-based Q&A flow |
 | **[Interview](docs/interview.md)** | Turn rough ideas into a structured markdown spec through a browser-based Q&A flow |
 | **[Divoom Display](docs/divoom.md)** | Mirror orchestrator and specialist-agent activity to a Divoom MiniToo Bluetooth display |
 | **[Divoom Display](docs/divoom.md)** | Mirror orchestrator and specialist-agent activity to a Divoom MiniToo Bluetooth display |

+ 14 - 14
docs/handoff.md → docs/subtask.md

@@ -1,26 +1,26 @@
-# Handoff
+# Subtask
 
 
-`/handoff` starts a boomerang-style worker session for the user’s requested
+`/subtask` starts a boomerang-style worker session for the user’s requested
 goal, then returns a compact completion summary to the original session.
 goal, then returns a compact completion summary to the original session.
 
 
 ## Usage
 ## Usage
 
 
 ```text
 ```text
-/handoff <what the worker should do>
+/subtask <what the worker should do>
 ```
 ```
 
 
-The command asks the current orchestrator to call `handoff_session` with the
+The command asks the current orchestrator to call `subtask` with the
 worker prompt and any clearly relevant files.
 worker prompt and any clearly relevant files.
 
 
 ## Flow
 ## Flow
 
 
-1. The main session calls `handoff_session`.
+1. The main session calls `subtask`.
 2. Slim creates a real child session with `parentID` set to the main session.
 2. Slim creates a real child session with `parentID` set to the main session.
 3. The child runs as `orchestrator`, so it can use the normal specialist-agent
 3. The child runs as `orchestrator`, so it can use the normal specialist-agent
    workflow and delegate through `task` when useful.
    workflow and delegate through `task` when useful.
 4. Referenced files are loaded into the child as synthetic Read-tool context.
 4. Referenced files are loaded into the child as synthetic Read-tool context.
 5. When the child finishes, Slim extracts its assistant output and returns it to
 5. When the child finishes, Slim extracts its assistant output and returns it to
-   the main session inside `<handoff_summary>`.
+   the main session inside `<subtask_summary>`.
 6. The child session is aborted for cleanup after the summary is extracted.
 6. The child session is aborted for cleanup after the summary is extracted.
 
 
 In tmux or zellij, the child appears like other delegated work because it is a
 In tmux or zellij, the child appears like other delegated work because it is a
@@ -31,12 +31,12 @@ real child session. Existing session-depth and pane cleanup handling apply.
 The user prompt controls scope. Keep it direct:
 The user prompt controls scope. Keep it direct:
 
 
 ```text
 ```text
-/handoff finish the docs for handoff and run the relevant checks
-/handoff investigate the flaky auth test and report what changed
-/handoff implement the small UI polish we discussed
+/subtask finish the docs for subtask and run the relevant checks
+/subtask investigate the flaky auth test and report what changed
+/subtask implement the small UI polish we discussed
 ```
 ```
 
 
-The handoff prompt intentionally avoids prescribing extra actions. It should do
+The subtask prompt intentionally avoids prescribing extra actions. It should do
 what the user asks, then summarize what happened, files changed, validation run,
 what the user asks, then summarize what happened, files changed, validation run,
 and any remaining risks or follow-up.
 and any remaining risks or follow-up.
 
 
@@ -44,13 +44,13 @@ and any remaining risks or follow-up.
 
 
 | Tool | Purpose |
 | Tool | Purpose |
 |------|---------|
 |------|---------|
-| `handoff_session` | Creates the child worker session and returns its summary |
-| `read_session` | Lets a handoff worker read details from the parent/source session |
+| `subtask` | Creates the child worker session and returns its summary |
+| `read_session` | Lets a subtask worker read details from the parent/source session |
 
 
 ## Safety
 ## Safety
 
 
-- Nested handoffs are blocked: a handoff worker should finish its current task
-  and return a summary instead of spawning another handoff worker.
+- Nested subtasks are blocked: a subtask worker should finish its current task
+  and return a summary instead of spawning another subtask worker.
 - File context is restricted to the workspace real path, including symlink
 - File context is restricted to the workspace real path, including symlink
   checks.
   checks.
 - Binary files are skipped.
 - Binary files are skipped.

+ 8 - 8
docs/tools.md

@@ -34,23 +34,23 @@ Fast, structural code search and refactoring — more powerful than plain text g
 
 
 ---
 ---
 
 
-## Session Handoff
+## Session Subtask
 
 
 Run a boomerang-style worker session and return its summary to the caller.
 Run a boomerang-style worker session and return its summary to the caller.
 
 
 | Command / Tool | Description |
 | Command / Tool | Description |
 |----------------|-------------|
 |----------------|-------------|
-| `/handoff <goal>` | Ask the current agent to summarize context, relevant files, decisions, and next steps for a new session |
-| `handoff_session` | Runs a child handoff worker session and returns its summary to the caller |
-| `read_session` | Reads transcript details from the source session when the handoff summary is missing specifics |
+| `/subtask <goal>` | Ask the current agent to run a focused child worker for the requested goal |
+| `subtask` | Runs a child subtask worker session and returns its summary to the caller |
+| `read_session` | Reads transcript details from the source session when the subtask summary is missing specifics |
 
 
-Handoff prompts include `@file` references. Slim creates a real child session
-with the current session as `parentID`, lets the handoff worker read the provided
-context and files, then returns the worker's `<handoff_summary>` back to the
+Subtask prompts include `@file` references. Slim creates a real child session
+with the current session as `parentID`, lets the subtask worker read the provided
+context and files, then returns the worker's `<subtask_summary>` back to the
 main session as normal tool output. In tmux/zellij this appears like other child
 main session as normal tool output. In tmux/zellij this appears like other child
 agent work: a pane can open for the worker and close when the summary returns.
 agent work: a pane can open for the worker and close when the summary returns.
 
 
-See [Handoff](handoff.md) for the full workflow.
+See [Subtask](subtask.md) for the full workflow.
 
 
 ---
 ---
 
 

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "oh-my-opencode-slim",
   "name": "oh-my-opencode-slim",
-  "version": "1.0.7",
+  "version": "1.0.8",
   "description": "Lightweight agent orchestration plugin for OpenCode - a slimmed-down fork of oh-my-opencode",
   "description": "Lightweight agent orchestration plugin for OpenCode - a slimmed-down fork of oh-my-opencode",
   "main": "dist/index.js",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",
   "types": "dist/index.d.ts",

+ 1 - 1
src/codemap.md

@@ -34,7 +34,7 @@
 
 
 - Connects directly to `@opencode-ai/plugin`: returns the plugin object, mutates runtime agent configuration, handles event hooks, and routes RPC via `ctx.client`/`ctx.client.session`.
 - Connects directly to `@opencode-ai/plugin`: returns the plugin object, mutates runtime agent configuration, handles event hooks, and routes RPC via `ctx.client`/`ctx.client.session`.
 - Integrates with host multiplexer backends through `src/multiplexer`, and with session lifecycle constraints through `SubagentDepthTracker`.
 - Integrates with host multiplexer backends through `src/multiplexer`, and with session lifecycle constraints through `SubagentDepthTracker`.
-- Hooks/handoff integration points now include:
+- Hooks/subtask integration points now include:
   - `createTaskSessionManagerHook` for resumable Task sessions,
   - `createTaskSessionManagerHook` for resumable Task sessions,
   - `createTodoContinuationHook`, `createPhaseReminderHook`, `createFilterAvailableSkillsHook`, and `createPostFileToolNudgeHook` for chat/tool behavior,
   - `createTodoContinuationHook`, `createPhaseReminderHook`, `createFilterAvailableSkillsHook`, and `createPostFileToolNudgeHook` for chat/tool behavior,
   - `createInterviewManager` / `createPresetManager` command handlers.
   - `createInterviewManager` / `createPresetManager` command handlers.

+ 12 - 16
src/index.ts

@@ -41,11 +41,11 @@ import {
   ast_grep_replace,
   ast_grep_replace,
   ast_grep_search,
   ast_grep_search,
   createCouncilTool,
   createCouncilTool,
-  createHandoffCommandManager,
-  createHandoffSessionTool,
-  createHandoffState,
   createPresetManager,
   createPresetManager,
   createReadSessionTool,
   createReadSessionTool,
+  createSubtaskCommandManager,
+  createSubtaskState,
+  createSubtaskTool,
   createWebfetchTool,
   createWebfetchTool,
 } from './tools';
 } from './tools';
 import { recordTuiAgentModel, recordTuiAgentModels } from './tui-state';
 import { recordTuiAgentModel, recordTuiAgentModels } from './tui-state';
@@ -146,8 +146,8 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
   let rewriteDisplayNameMentions: ReturnType<
   let rewriteDisplayNameMentions: ReturnType<
     typeof createDisplayNameMentionRewriter
     typeof createDisplayNameMentionRewriter
   >;
   >;
-  let handoffCommandManager: ReturnType<typeof createHandoffCommandManager>;
-  let handoffState: ReturnType<typeof createHandoffState>;
+  let subtaskCommandManager: ReturnType<typeof createSubtaskCommandManager>;
+  let subtaskState: ReturnType<typeof createSubtaskState>;
 
 
   // Counters for post-init health check (set inside try, checked outside)
   // Counters for post-init health check (set inside try, checked outside)
   let toolCount = 0;
   let toolCount = 0;
@@ -318,15 +318,15 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
     presetManager = createPresetManager(ctx, config);
     presetManager = createPresetManager(ctx, config);
     divoomManager = createDivoomManager(config.divoom);
     divoomManager = createDivoomManager(config.divoom);
 
 
-    handoffState = createHandoffState();
-    handoffCommandManager = createHandoffCommandManager(ctx, handoffState);
+    subtaskState = createSubtaskState();
+    subtaskCommandManager = createSubtaskCommandManager(ctx, subtaskState);
 
 
     toolCount =
     toolCount =
       Object.keys(councilTools).length +
       Object.keys(councilTools).length +
       Object.keys(todoContinuationHook.tool).length +
       Object.keys(todoContinuationHook.tool).length +
       1 + // webfetch
       1 + // webfetch
       2 + // ast_grep_search, ast_grep_replace
       2 + // ast_grep_search, ast_grep_replace
-      2; // handoff_session, read_session
+      2; // subtask, read_session
   } catch (err) {
   } catch (err) {
     // Plugin init failed: log visibly before re-throwing so the user
     // Plugin init failed: log visibly before re-throwing so the user
     // sees something actionable instead of a silent "loaded but empty".
     // sees something actionable instead of a silent "loaded but empty".
@@ -396,12 +396,8 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
       ...todoContinuationHook.tool,
       ...todoContinuationHook.tool,
       ast_grep_search,
       ast_grep_search,
       ast_grep_replace,
       ast_grep_replace,
-      handoff_session: createHandoffSessionTool(
-        ctx,
-        handoffState,
-        depthTracker,
-      ),
-      read_session: createReadSessionTool(ctx.client, handoffState),
+      subtask: createSubtaskTool(ctx, subtaskState, depthTracker),
+      read_session: createReadSessionTool(ctx.client, subtaskState),
     },
     },
 
 
     mcp: mcps,
     mcp: mcps,
@@ -737,7 +733,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
 
 
       interviewManager.registerCommand(opencodeConfig);
       interviewManager.registerCommand(opencodeConfig);
       presetManager.registerCommand(opencodeConfig);
       presetManager.registerCommand(opencodeConfig);
-      handoffCommandManager.registerCommand(opencodeConfig);
+      subtaskCommandManager.registerCommand(opencodeConfig);
     },
     },
 
 
     event: async (input) => {
     event: async (input) => {
@@ -815,7 +811,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
         },
         },
       );
       );
 
 
-      handoffCommandManager.handleEvent(
+      subtaskCommandManager.handleEvent(
         input as {
         input as {
           event: {
           event: {
             type: string;
             type: string;

+ 16 - 0
src/tools/codemap.md

@@ -7,6 +7,7 @@
 - AST-aware search/replace via `ast-grep` stack.
 - AST-aware search/replace via `ast-grep` stack.
 - Remote fetch/transform utility via `smartfetch` (`webfetch` tool).
 - Remote fetch/transform utility via `smartfetch` (`webfetch` tool).
 - Council orchestration via `createCouncilTool` (`council.ts`).
 - Council orchestration via `createCouncilTool` (`council.ts`).
+- Child-session subtasks via `subtask` and `/subtask` (`subtask/`).
 - Runtime preset switching via `/preset` hook via `createPresetManager` (`preset-manager.ts`).
 - Runtime preset switching via `/preset` hook via `createPresetManager` (`preset-manager.ts`).
 
 
 It is the bridge between plugin runtime integration (`src/index.ts`) and the lower-level
 It is the bridge between plugin runtime integration (`src/index.ts`) and the lower-level
@@ -17,6 +18,8 @@ implementations in feature folders.
 - `ast_grep_search`, `ast_grep_replace` from `./ast-grep`
 - `ast_grep_search`, `ast_grep_replace` from `./ast-grep`
 - `createWebfetchTool`, `WEBFETCH_DESCRIPTION`, and related types from `./smartfetch`
 - `createWebfetchTool`, `WEBFETCH_DESCRIPTION`, and related types from `./smartfetch`
 - `createCouncilTool`
 - `createCouncilTool`
+- `createSubtaskTool`, `createSubtaskCommandManager`, `createSubtaskState`, and
+  `createReadSessionTool` from `./subtask`
 - `createPresetManager` and `PresetManager` type
 - `createPresetManager` and `PresetManager` type
 
 
 ## Design patterns
 ## Design patterns
@@ -57,6 +60,18 @@ implementations in feature folders.
   `displayName`).
   `displayName`).
 - In-memory `activePreset` supports immediate status display and updates after successful switches.
 - In-memory `activePreset` supports immediate status display and updates after successful switches.
 
 
+### Subtask path
+
+- `createSubtaskCommandManager` registers `/subtask` and asks the current
+  agent to call the `subtask` tool with the worker prompt and relevant files.
+- `createSubtaskTool` creates a real child session with `parentID`, injects
+  referenced files as synthetic Read-tool context, waits for the worker to
+  finish, returns `<subtask_summary>`, then aborts the child for cleanup.
+- `createReadSessionTool` lets a subtask worker read only the source session
+  that spawned it when the summary prompt lacks details.
+- `SubtaskState` marks child sessions so nested subtasks can be blocked and
+  session.deleted events can clear stale markers.
+
 ### Smartfetch path
 ### Smartfetch path
 
 
 - `createWebfetchTool` owns fetch orchestration, permission prompts, cache checks,
 - `createWebfetchTool` owns fetch orchestration, permission prompts, cache checks,
@@ -83,6 +98,7 @@ implementations in feature folders.
 - Tool registration:
 - Tool registration:
   - `council` tools (only when `config.council` exists),
   - `council` tools (only when `config.council` exists),
   - `webfetch`,
   - `webfetch`,
+  - `subtask`, `read_session`,
   - AST tools.
   - AST tools.
 - `presetManager` is initialized in plugin init and:
 - `presetManager` is initialized in plugin init and:
   - calls `registerCommand` during config hook,
   - calls `registerCommand` during config hook,

+ 7 - 7
src/tools/index.ts

@@ -1,13 +1,13 @@
 // AST-grep tools
 // AST-grep tools
 export { ast_grep_replace, ast_grep_search } from './ast-grep';
 export { ast_grep_replace, ast_grep_search } from './ast-grep';
 export { createCouncilTool } from './council';
 export { createCouncilTool } from './council';
-export type { HandoffCommandManager } from './handoff';
-export {
-  createHandoffCommandManager,
-  createHandoffSessionTool,
-  createHandoffState,
-  createReadSessionTool,
-} from './handoff';
 export type { PresetManager } from './preset-manager';
 export type { PresetManager } from './preset-manager';
 export { createPresetManager } from './preset-manager';
 export { createPresetManager } from './preset-manager';
 export { createWebfetchTool } from './smartfetch';
 export { createWebfetchTool } from './smartfetch';
+export type { SubtaskCommandManager } from './subtask';
+export {
+  createReadSessionTool,
+  createSubtaskCommandManager,
+  createSubtaskState,
+  createSubtaskTool,
+} from './subtask';

+ 19 - 19
src/tools/handoff/command.test.ts → src/tools/subtask/command.test.ts

@@ -1,6 +1,6 @@
 import { describe, expect, test } from 'bun:test';
 import { describe, expect, test } from 'bun:test';
-import { createHandoffCommandManager } from './command';
-import { createHandoffState } from './state';
+import { createSubtaskCommandManager } from './command';
+import { createSubtaskState } from './state';
 
 
 function createContext() {
 function createContext() {
   return {
   return {
@@ -9,26 +9,26 @@ function createContext() {
   } as any;
   } as any;
 }
 }
 
 
-describe('createHandoffCommandManager', () => {
-  test('registers the /handoff command', () => {
-    const manager = createHandoffCommandManager(
+describe('createSubtaskCommandManager', () => {
+  test('registers the /subtask command', () => {
+    const manager = createSubtaskCommandManager(
       createContext(),
       createContext(),
-      createHandoffState(),
+      createSubtaskState(),
     );
     );
     const config: Record<string, unknown> = {};
     const config: Record<string, unknown> = {};
 
 
     manager.registerCommand(config);
     manager.registerCommand(config);
 
 
     const commands = config.command as Record<string, { template: string }>;
     const commands = config.command as Record<string, { template: string }>;
-    expect(commands.handoff).toBeDefined();
-    expect(commands.handoff.template).toContain('handoff_session');
-    expect(commands.handoff.template).toContain('$ARGUMENTS');
+    expect(commands.subtask).toBeDefined();
+    expect(commands.subtask.template).toContain('subtask');
+    expect(commands.subtask.template).toContain('$ARGUMENTS');
   });
   });
 
 
-  test('marks child sessions of handoff workers with the same source', () => {
-    const state = createHandoffState();
+  test('marks child sessions of subtask workers with the same source', () => {
+    const state = createSubtaskState();
     state.markSession('ses_worker', 'ses_source');
     state.markSession('ses_worker', 'ses_source');
-    const manager = createHandoffCommandManager(createContext(), state);
+    const manager = createSubtaskCommandManager(createContext(), state);
 
 
     manager.handleEvent({
     manager.handleEvent({
       event: {
       event: {
@@ -41,8 +41,8 @@ describe('createHandoffCommandManager', () => {
   });
   });
 
 
   test('does not mark unrelated child sessions', () => {
   test('does not mark unrelated child sessions', () => {
-    const state = createHandoffState();
-    const manager = createHandoffCommandManager(createContext(), state);
+    const state = createSubtaskState();
+    const manager = createSubtaskCommandManager(createContext(), state);
 
 
     manager.handleEvent({
     manager.handleEvent({
       event: {
       event: {
@@ -51,13 +51,13 @@ describe('createHandoffCommandManager', () => {
       },
       },
     });
     });
 
 
-    expect(state.isHandoffSession('ses_child')).toBe(false);
+    expect(state.isSubtaskSession('ses_child')).toBe(false);
   });
   });
 
 
-  test('unmarks deleted handoff sessions', () => {
-    const state = createHandoffState();
+  test('unmarks deleted subtask sessions', () => {
+    const state = createSubtaskState();
     state.markSession('ses_worker', 'ses_source');
     state.markSession('ses_worker', 'ses_source');
-    const manager = createHandoffCommandManager(createContext(), state);
+    const manager = createSubtaskCommandManager(createContext(), state);
 
 
     manager.handleEvent({
     manager.handleEvent({
       event: {
       event: {
@@ -66,6 +66,6 @@ describe('createHandoffCommandManager', () => {
       },
       },
     });
     });
 
 
-    expect(state.isHandoffSession('ses_worker')).toBe(false);
+    expect(state.isSubtaskSession('ses_worker')).toBe(false);
   });
   });
 });
 });

+ 19 - 19
src/tools/handoff/command.ts → src/tools/subtask/command.ts

@@ -1,41 +1,41 @@
 /**
 /**
- * Command registration manager for handoff functionality.
+ * Command registration manager for subtask functionality.
  *
  *
- * Manages the /handoff slash command registration and the HANDOFF_COMMAND
- * template that guides the AI in generating handoff prompts.
+ * Manages the /subtask slash command registration and the SUBTASK_COMMAND
+ * template that guides the AI in generating subtask prompts.
  */
  */
 
 
 import type { PluginInput } from '@opencode-ai/plugin';
 import type { PluginInput } from '@opencode-ai/plugin';
-import type { HandoffState } from './state';
+import type { SubtaskState } from './state';
 
 
-const COMMAND_NAME = 'handoff';
+const COMMAND_NAME = 'subtask';
 
 
 /**
 /**
- * The handoff command template that guides the AI in generating handoff
+ * The subtask command template that guides the AI in generating subtask
  * prompts.
  * prompts.
  */
  */
-const HANDOFF_COMMAND_TEMPLATE = `Start a handoff worker session.
+const SUBTASK_COMMAND_TEMPLATE = `Start a subtask worker session.
 
 
 Use the user's request below as the source of truth for what the worker should do. Keep scope and emphasis exactly aligned with the user's request.
 Use the user's request below as the source of truth for what the worker should do. Keep scope and emphasis exactly aligned with the user's request.
 
 
 USER: $ARGUMENTS
 USER: $ARGUMENTS
 
 
-Call handoff_session with the worker prompt and any clearly relevant files:
-\`handoff_session(prompt="...", files=["src/foo.ts", "src/bar.ts", ...])\``;
+Call subtask with the worker prompt and any clearly relevant files:
+\`subtask(prompt="...", files=["src/foo.ts", "src/bar.ts", ...])\``;
 
 
 /**
 /**
- * Creates a handoff command manager.
+ * Creates a subtask command manager.
  *
  *
- * Handles registration of the /handoff command and processing of chat
- * messages to inject synthetic file parts for handoff sessions.
+ * Handles registration of the /subtask command and processing of chat
+ * messages to inject synthetic file parts for subtask sessions.
  */
  */
-export function createHandoffCommandManager(
+export function createSubtaskCommandManager(
   _ctx: PluginInput,
   _ctx: PluginInput,
-  state: HandoffState,
+  state: SubtaskState,
   _processedSessions?: Set<string>,
   _processedSessions?: Set<string>,
 ) {
 ) {
   /**
   /**
-   * Register the /handoff command in the OpenCode config.
+   * Register the /subtask command in the OpenCode config.
    */
    */
   function registerCommand(opencodeConfig: Record<string, unknown>): void {
   function registerCommand(opencodeConfig: Record<string, unknown>): void {
     const configCommand = opencodeConfig.command as
     const configCommand = opencodeConfig.command as
@@ -46,8 +46,8 @@ export function createHandoffCommandManager(
         opencodeConfig.command = {};
         opencodeConfig.command = {};
       }
       }
       (opencodeConfig.command as Record<string, unknown>)[COMMAND_NAME] = {
       (opencodeConfig.command as Record<string, unknown>)[COMMAND_NAME] = {
-        description: 'Create a focused handoff prompt for a new session',
-        template: HANDOFF_COMMAND_TEMPLATE,
+        description: 'Create a focused subtask prompt for a new session',
+        template: SUBTASK_COMMAND_TEMPLATE,
       };
       };
     }
     }
   }
   }
@@ -80,6 +80,6 @@ export function createHandoffCommandManager(
   };
   };
 }
 }
 
 
-export type HandoffCommandManager = ReturnType<
-  typeof createHandoffCommandManager
+export type SubtaskCommandManager = ReturnType<
+  typeof createSubtaskCommandManager
 >;
 >;

+ 5 - 5
src/tools/handoff/files.test.ts → src/tools/subtask/files.test.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Tests for handoff file reference parsing.
+ * Tests for subtask file reference parsing.
  */
  */
 
 
 import { describe, expect, it } from 'bun:test';
 import { describe, expect, it } from 'bun:test';
@@ -77,7 +77,7 @@ describe('parseFileReferences', () => {
 
 
   it('should handle references with trailing punctuation', () => {
   it('should handle references with trailing punctuation', () => {
     // Note: The regex includes trailing punctuation as part of the path
     // Note: The regex includes trailing punctuation as part of the path
-    // This is the vendored behavior from opencode-handoff
+    // This is the vendored behavior from opencode-subtask
     const text = 'See @src/file.ts, @src/other.ts. And @src/more.ts!';
     const text = 'See @src/file.ts, @src/other.ts. And @src/more.ts!';
     const refs = parseFileReferences(text);
     const refs = parseFileReferences(text);
     // The regex captures the trailing punctuation, so these won't match
     // The regex captures the trailing punctuation, so these won't match
@@ -109,7 +109,7 @@ describe('FILE_REGEX', () => {
 
 
 describe('buildSyntheticFileParts', () => {
 describe('buildSyntheticFileParts', () => {
   it('loads readable files inside the workspace', async () => {
   it('loads readable files inside the workspace', async () => {
-    const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'omos-handoff-files-'));
+    const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'omos-subtask-files-'));
     try {
     try {
       fs.writeFileSync(path.join(dir, 'file.ts'), 'const x = 1;\n');
       fs.writeFileSync(path.join(dir, 'file.ts'), 'const x = 1;\n');
 
 
@@ -124,9 +124,9 @@ describe('buildSyntheticFileParts', () => {
   });
   });
 
 
   it('skips path traversal and symlinks outside the workspace', async () => {
   it('skips path traversal and symlinks outside the workspace', async () => {
-    const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'omos-handoff-files-'));
+    const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'omos-subtask-files-'));
     const outside = fs.mkdtempSync(
     const outside = fs.mkdtempSync(
-      path.join(os.tmpdir(), 'omos-handoff-outside-'),
+      path.join(os.tmpdir(), 'omos-subtask-outside-'),
     );
     );
     try {
     try {
       fs.writeFileSync(path.join(outside, 'secret.txt'), 'secret\n');
       fs.writeFileSync(path.join(outside, 'secret.txt'), 'secret\n');

+ 2 - 2
src/tools/handoff/files.ts → src/tools/subtask/files.ts

@@ -1,7 +1,7 @@
 /**
 /**
- * File reference parsing and synthetic file parts for handoff sessions.
+ * File reference parsing and synthetic file parts for subtask sessions.
  *
  *
- * Handles extraction of @file references from handoff prompts and
+ * Handles extraction of @file references from subtask prompts and
  * building synthetic text parts that match OpenCode's Read tool output
  * building synthetic text parts that match OpenCode's Read tool output
  * format.
  * format.
  */
  */

+ 6 - 6
src/tools/handoff/index.ts → src/tools/subtask/index.ts

@@ -1,23 +1,23 @@
 /**
 /**
- * Handoff functionality for session continuation.
+ * Subtask functionality for session continuation.
  *
  *
- * Provides tools and commands for creating handoff prompts that allow
+ * Provides tools and commands for creating subtask prompts that allow
  * work to continue seamlessly in new sessions with preloaded context.
  * work to continue seamlessly in new sessions with preloaded context.
  */
  */
 
 
 export {
 export {
-  createHandoffCommandManager,
-  type HandoffCommandManager,
+  createSubtaskCommandManager,
+  type SubtaskCommandManager,
 } from './command';
 } from './command';
 export {
 export {
   buildSyntheticFileParts,
   buildSyntheticFileParts,
   FILE_REGEX,
   FILE_REGEX,
   parseFileReferences,
   parseFileReferences,
 } from './files';
 } from './files';
-export { createHandoffState, type HandoffState } from './state';
+export { createSubtaskState, type SubtaskState } from './state';
 export {
 export {
-  createHandoffSessionTool,
   createReadSessionTool,
   createReadSessionTool,
+  createSubtaskTool,
   type OpencodeClient,
   type OpencodeClient,
 } from './tools';
 } from './tools';
 export {
 export {

+ 4 - 4
src/tools/handoff/state.ts → src/tools/subtask/state.ts

@@ -1,11 +1,11 @@
-export interface HandoffState {
+export interface SubtaskState {
   markSession(sessionID: string, sourceSessionID: string): void;
   markSession(sessionID: string, sourceSessionID: string): void;
   unmarkSession(sessionID: string): void;
   unmarkSession(sessionID: string): void;
-  isHandoffSession(sessionID: string): boolean;
+  isSubtaskSession(sessionID: string): boolean;
   sourceFor(sessionID: string): string | undefined;
   sourceFor(sessionID: string): string | undefined;
 }
 }
 
 
-export function createHandoffState(): HandoffState {
+export function createSubtaskState(): SubtaskState {
   const sourceBySession = new Map<string, string>();
   const sourceBySession = new Map<string, string>();
 
 
   return {
   return {
@@ -15,7 +15,7 @@ export function createHandoffState(): HandoffState {
     unmarkSession(sessionID: string): void {
     unmarkSession(sessionID: string): void {
       sourceBySession.delete(sessionID);
       sourceBySession.delete(sessionID);
     },
     },
-    isHandoffSession(sessionID: string): boolean {
+    isSubtaskSession(sessionID: string): boolean {
       return sourceBySession.has(sessionID);
       return sourceBySession.has(sessionID);
     },
     },
     sourceFor(sessionID: string): string | undefined {
     sourceFor(sessionID: string): string | undefined {

+ 19 - 19
src/tools/handoff/tools.test.ts → src/tools/subtask/tools.test.ts

@@ -3,15 +3,15 @@ import * as fs from 'node:fs';
 import * as os from 'node:os';
 import * as os from 'node:os';
 import * as path from 'node:path';
 import * as path from 'node:path';
 import { SubagentDepthTracker } from '../../utils/subagent-depth';
 import { SubagentDepthTracker } from '../../utils/subagent-depth';
-import { createHandoffState } from './state';
-import { createHandoffSessionTool, createReadSessionTool } from './tools';
+import { createSubtaskState } from './state';
+import { createReadSessionTool, createSubtaskTool } from './tools';
 
 
 function makeTempDir() {
 function makeTempDir() {
-  return fs.mkdtempSync(path.join(os.tmpdir(), 'omos-handoff-tool-'));
+  return fs.mkdtempSync(path.join(os.tmpdir(), 'omos-subtask-tool-'));
 }
 }
 
 
-describe('handoff_session tool', () => {
-  test('runs a worker child session and returns its handoff summary', async () => {
+describe('subtask tool', () => {
+  test('runs a worker child session and returns its subtask summary', async () => {
     const directory = makeTempDir();
     const directory = makeTempDir();
     try {
     try {
       fs.mkdirSync(path.join(directory, 'src'));
       fs.mkdirSync(path.join(directory, 'src'));
@@ -28,8 +28,8 @@ describe('handoff_session tool', () => {
         ],
         ],
       }));
       }));
       const sessionAbort = mock(async () => ({}));
       const sessionAbort = mock(async () => ({}));
-      const state = createHandoffState();
-      const tool = createHandoffSessionTool(
+      const state = createSubtaskState();
+      const tool = createSubtaskTool(
         {
         {
           directory,
           directory,
           client: {
           client: {
@@ -51,7 +51,7 @@ describe('handoff_session tool', () => {
       );
       );
 
 
       expect(result).toContain('task_id: ses_new');
       expect(result).toContain('task_id: ses_new');
-      expect(result).toContain('<handoff_summary>');
+      expect(result).toContain('<subtask_summary>');
       expect(result).toContain('Summary from worker');
       expect(result).toContain('Summary from worker');
       expect(sessionCreate).toHaveBeenCalledWith({
       expect(sessionCreate).toHaveBeenCalledWith({
         responseStyle: 'data',
         responseStyle: 'data',
@@ -59,7 +59,7 @@ describe('handoff_session tool', () => {
         query: { directory },
         query: { directory },
         body: {
         body: {
           parentID: 'ses_old',
           parentID: 'ses_old',
-          title: 'Handoff worker from ses_old',
+          title: 'Subtask worker from ses_old',
         },
         },
       });
       });
       expect(sessionPrompt).toHaveBeenCalledTimes(1);
       expect(sessionPrompt).toHaveBeenCalledTimes(1);
@@ -96,18 +96,18 @@ describe('handoff_session tool', () => {
     }
     }
   });
   });
 
 
-  test('blocks nested handoff calls from a handoff worker', async () => {
+  test('blocks nested subtask calls from a subtask worker', async () => {
     const directory = makeTempDir();
     const directory = makeTempDir();
     try {
     try {
       let nestedResult = '';
       let nestedResult = '';
-      const state = createHandoffState();
-      const tool = createHandoffSessionTool(
+      const state = createSubtaskState();
+      const tool = createSubtaskTool(
         {
         {
           directory,
           directory,
           client: {
           client: {
             session: {
             session: {
               abort: mock(async () => ({})),
               abort: mock(async () => ({})),
-              create: mock(async () => ({ data: { id: 'ses_handoff' } })),
+              create: mock(async () => ({ data: { id: 'ses_subtask' } })),
               messages: mock(async () => ({
               messages: mock(async () => ({
                 data: [
                 data: [
                   {
                   {
@@ -118,8 +118,8 @@ describe('handoff_session tool', () => {
               })),
               })),
               prompt: mock(async () => {
               prompt: mock(async () => {
                 nestedResult = String(
                 nestedResult = String(
-                  await tool.execute({ prompt: 'nested handoff' }, {
-                    sessionID: 'ses_handoff',
+                  await tool.execute({ prompt: 'nested subtask' }, {
+                    sessionID: 'ses_subtask',
                   } as any),
                   } as any),
                 );
                 );
               }),
               }),
@@ -130,11 +130,11 @@ describe('handoff_session tool', () => {
         new SubagentDepthTracker(),
         new SubagentDepthTracker(),
       );
       );
 
 
-      await tool.execute({ prompt: 'outer handoff' }, {
+      await tool.execute({ prompt: 'outer subtask' }, {
         sessionID: 'ses_old',
         sessionID: 'ses_old',
       } as any);
       } as any);
 
 
-      expect(nestedResult).toContain('Nested handoff is disabled');
+      expect(nestedResult).toContain('Nested subtask is disabled');
     } finally {
     } finally {
       fs.rmSync(directory, { recursive: true, force: true });
       fs.rmSync(directory, { recursive: true, force: true });
     }
     }
@@ -159,7 +159,7 @@ describe('read_session tool', () => {
         },
         },
       ],
       ],
     }));
     }));
-    const state = createHandoffState();
+    const state = createSubtaskState();
     state.markSession('ses_worker', 'ses_old');
     state.markSession('ses_worker', 'ses_old');
 
 
     const result = await createReadSessionTool(
     const result = await createReadSessionTool(
@@ -174,7 +174,7 @@ describe('read_session tool', () => {
   });
   });
 
 
   test('blocks reads outside the source session', async () => {
   test('blocks reads outside the source session', async () => {
-    const state = createHandoffState();
+    const state = createSubtaskState();
     state.markSession('ses_worker', 'ses_old');
     state.markSession('ses_worker', 'ses_old');
     const messages = mock(async () => ({ data: [] }));
     const messages = mock(async () => ({ data: [] }));
 
 

+ 24 - 24
src/tools/handoff/tools.ts → src/tools/subtask/tools.ts

@@ -1,8 +1,8 @@
 /**
 /**
- * Tool definitions for handoff functionality.
+ * Tool definitions for subtask functionality.
  *
  *
  * Factory functions that create tool definitions with injected dependencies:
  * Factory functions that create tool definitions with injected dependencies:
- * - createHandoffSessionTool: Create a new session with handoff prompt
+ * - createSubtaskTool: Create a new session with subtask prompt
  * - createReadSessionTool: Read conversation transcript from a session
  * - createReadSessionTool: Read conversation transcript from a session
  */
  */
 
 
@@ -11,19 +11,19 @@ import { tool } from '@opencode-ai/plugin';
 import { extractSessionResult, promptWithTimeout } from '../../utils/session';
 import { extractSessionResult, promptWithTimeout } from '../../utils/session';
 import type { SubagentDepthTracker } from '../../utils/subagent-depth';
 import type { SubagentDepthTracker } from '../../utils/subagent-depth';
 import { buildSyntheticFileParts, parseFileReferences } from './files';
 import { buildSyntheticFileParts, parseFileReferences } from './files';
-import type { HandoffState } from './state';
+import type { SubtaskState } from './state';
 
 
 export type OpencodeClient = PluginInput['client'];
 export type OpencodeClient = PluginInput['client'];
-const HANDOFF_TIMEOUT_MS = 5 * 60 * 1000;
+const SUBTASK_TIMEOUT_MS = 5 * 60 * 1000;
 
 
 /**
 /**
- * Create the handoff_session tool.
+ * Create the subtask tool.
  *
  *
  * Takes the OpenCode client as a dependency for TUI and session operations.
  * Takes the OpenCode client as a dependency for TUI and session operations.
  */
  */
-export function createHandoffSessionTool(
+export function createSubtaskTool(
   ctx: PluginInput,
   ctx: PluginInput,
-  state: HandoffState,
+  state: SubtaskState,
   depthTracker?: SubagentDepthTracker,
   depthTracker?: SubagentDepthTracker,
 ): ToolDefinition {
 ): ToolDefinition {
   const client = ctx.client;
   const client = ctx.client;
@@ -32,7 +32,7 @@ export function createHandoffSessionTool(
     description:
     description:
       'Run a child worker session and return its completion summary to the caller',
       'Run a child worker session and return its completion summary to the caller',
     args: {
     args: {
-      prompt: tool.schema.string().describe('The generated handoff prompt'),
+      prompt: tool.schema.string().describe('The generated subtask prompt'),
       files: tool.schema
       files: tool.schema
         .array(tool.schema.string())
         .array(tool.schema.string())
         .optional()
         .optional()
@@ -50,15 +50,15 @@ export function createHandoffSessionTool(
         context && typeof context === 'object' && 'sessionID' in context
         context && typeof context === 'object' && 'sessionID' in context
           ? (context as { sessionID: string }).sessionID
           ? (context as { sessionID: string }).sessionID
           : 'unknown';
           : 'unknown';
-      if (state.isHandoffSession(sessionID)) {
-        return 'Nested handoff is disabled: this session is already a handoff worker. Finish this worker and return its summary to the parent session instead.';
+      if (state.isSubtaskSession(sessionID)) {
+        return 'Nested subtask is disabled: this session is already a subtask worker. Finish this worker and return its summary to the parent session instead.';
       }
       }
       if (
       if (
         sessionID !== 'unknown' &&
         sessionID !== 'unknown' &&
         depthTracker &&
         depthTracker &&
         depthTracker.getDepth(sessionID) + 1 > depthTracker.maxDepth
         depthTracker.getDepth(sessionID) + 1 > depthTracker.maxDepth
       ) {
       ) {
-        return `Handoff worker blocked: max subagent depth ${depthTracker.maxDepth} would be exceeded.`;
+        return `Subtask worker blocked: max subagent depth ${depthTracker.maxDepth} would be exceeded.`;
       }
       }
 
 
       const sessionReference = `Work on behalf of parent session ${sessionID}. When you lack specific information you can use read_session to get it.`;
       const sessionReference = `Work on behalf of parent session ${sessionID}. When you lack specific information you can use read_session to get it.`;
@@ -80,7 +80,7 @@ export function createHandoffSessionTool(
           query: { directory },
           query: { directory },
           body: {
           body: {
             parentID: sessionID === 'unknown' ? undefined : sessionID,
             parentID: sessionID === 'unknown' ? undefined : sessionID,
-            title: `Handoff worker from ${sessionID}`,
+            title: `Subtask worker from ${sessionID}`,
           },
           },
         });
         });
 
 
@@ -88,7 +88,7 @@ export function createHandoffSessionTool(
           (session as { data?: { id?: string }; id?: string })?.data?.id ??
           (session as { data?: { id?: string }; id?: string })?.data?.id ??
           (session as { data?: { id?: string }; id?: string })?.id;
           (session as { data?: { id?: string }; id?: string })?.id;
         if (!childSessionID) {
         if (!childSessionID) {
-          throw new Error('Handoff worker session did not return an id');
+          throw new Error('Subtask worker session did not return an id');
         }
         }
         if (sessionID !== 'unknown' && depthTracker) {
         if (sessionID !== 'unknown' && depthTracker) {
           const registered = depthTracker.registerChild(
           const registered = depthTracker.registerChild(
@@ -97,7 +97,7 @@ export function createHandoffSessionTool(
           );
           );
           if (!registered) {
           if (!registered) {
             throw new Error(
             throw new Error(
-              'Handoff worker blocked: max subagent depth exceeded',
+              'Subtask worker blocked: max subagent depth exceeded',
             );
             );
           }
           }
         }
         }
@@ -121,7 +121,7 @@ export function createHandoffSessionTool(
               ],
               ],
             },
             },
           },
           },
-          HANDOFF_TIMEOUT_MS,
+          SUBTASK_TIMEOUT_MS,
         );
         );
 
 
         const extraction = await extractSessionResult(client, childSessionID, {
         const extraction = await extractSessionResult(client, childSessionID, {
@@ -129,15 +129,15 @@ export function createHandoffSessionTool(
           includeReasoning: false,
           includeReasoning: false,
         });
         });
         if (extraction.empty) {
         if (extraction.empty) {
-          throw new Error('Handoff worker returned no summary');
+          throw new Error('Subtask worker returned no summary');
         }
         }
 
 
         return [
         return [
           `task_id: ${childSessionID}`,
           `task_id: ${childSessionID}`,
           '',
           '',
-          '<handoff_summary>',
+          '<subtask_summary>',
           extraction.text,
           extraction.text,
-          '</handoff_summary>',
+          '</subtask_summary>',
         ].join('\n');
         ].join('\n');
       } finally {
       } finally {
         if (childSessionID) {
         if (childSessionID) {
@@ -148,7 +148,7 @@ export function createHandoffSessionTool(
             });
             });
             state.unmarkSession(childSessionID);
             state.unmarkSession(childSessionID);
           } catch {
           } catch {
-            // Keep the handoff marker if abort fails; session.deleted cleanup
+            // Keep the subtask marker if abort fails; session.deleted cleanup
             // will remove it when OpenCode eventually deletes the session.
             // will remove it when OpenCode eventually deletes the session.
           }
           }
         }
         }
@@ -235,11 +235,11 @@ function formatTranscript(
  */
  */
 export function createReadSessionTool(
 export function createReadSessionTool(
   client: OpencodeClient,
   client: OpencodeClient,
-  state: HandoffState,
+  state: SubtaskState,
 ): ToolDefinition {
 ): ToolDefinition {
   return tool({
   return tool({
     description:
     description:
-      "Read the conversation transcript from a previous session. Use this when you need specific information from the source session that wasn't included in the handoff summary.",
+      "Read the conversation transcript from a previous session. Use this when you need specific information from the source session that wasn't included in the subtask summary.",
     args: {
     args: {
       sessionID: tool.schema
       sessionID: tool.schema
         .string()
         .string()
@@ -264,11 +264,11 @@ export function createReadSessionTool(
         context && typeof context === 'object' && 'sessionID' in context
         context && typeof context === 'object' && 'sessionID' in context
           ? (context as { sessionID?: string }).sessionID
           ? (context as { sessionID?: string }).sessionID
           : undefined;
           : undefined;
-      if (!callerSessionID || !state.isHandoffSession(callerSessionID)) {
-        return 'read_session is only available from handoff worker sessions.';
+      if (!callerSessionID || !state.isSubtaskSession(callerSessionID)) {
+        return 'read_session is only available from subtask worker sessions.';
       }
       }
       if (state.sourceFor(callerSessionID) !== args.sessionID) {
       if (state.sourceFor(callerSessionID) !== args.sessionID) {
-        return 'read_session can only read the source session for this handoff worker.';
+        return 'read_session can only read the source session for this subtask worker.';
       }
       }
 
 
       try {
       try {

+ 1 - 1
src/tools/handoff/vendor.test.ts → src/tools/subtask/vendor.test.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Tests for handoff vendor helpers.
+ * Tests for subtask vendor helpers.
  */
  */
 
 
 import { describe, expect, it } from 'bun:test';
 import { describe, expect, it } from 'bun:test';

+ 0 - 0
src/tools/handoff/vendor.ts → src/tools/subtask/vendor.ts