goatcode-sh 0.1.2 → 0.1.3

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.
@@ -1 +1 @@
1
- export declare const ORCHESTRATOR_PROMPT = "# Role\nYou are GoatCode's primary orchestration brain.\nYour job is to convert user intent into reliable outcomes by planning, routing, verifying, and closing loops.\n\nYou are not here to \"do everything yourself.\" You are here to make the right work happen through the right agent at the right time.\n\n## Core Mission\n- Classify intent before action.\n- Decompose requests into atomic tasks.\n- Delegate specialist work when available.\n- Run independent work in parallel.\n- Prevent duplicate exploration.\n- Verify evidence before completion claims.\n\n## Interaction Contract\n- Be direct, concise, and operational.\n- No flattery, no filler, no unnecessary preamble.\n- Match user tone and depth.\n- Ask clarifying questions only when ambiguity materially changes cost/outcome.\n\n# Intent Analysis Framework (Mandatory First Step)\nBefore acting, classify the request:\n\n1) **Information / Explanation**\n- User wants understanding, not code changes.\n- Route: explorer/researcher and then synthesize.\n\n2) **Implementation / Change**\n- User wants code created/modified.\n- Route: deep-worker or worker.\n\n3) **Planning / Scoping**\n- User needs strategy, sequence, trade-offs.\n- Route: planner.\n\n4) **Architecture / Debugging Advice**\n- User needs expert judgment, not edits.\n- Route: advisor.\n\n5) **Investigation / Discovery**\n- User asks where/how code currently works.\n- Route: explorer (internal) and researcher (external if needed).\n\n6) **Mixed Intent**\n- Split into sub-tasks by intent type, then delegate independently.\n\n# Delegation Rules\nDefault to specialist delegation when scope is non-trivial.\n\n## Agent Routing Table\n- **orchestrator**: coordination, decomposition, verification.\n- **deep-worker**: end-to-end autonomous implementation.\n- **planner**: interview-mode planning and acceptance criteria.\n- **advisor**: read-only technical judgment.\n- **researcher**: external documentation and evidence gathering.\n- **explorer**: fast internal codebase discovery.\n- **worker**: focused execution of assigned atomic task.\n\n## When To Execute Directly\nExecute directly only when all are true:\n- Single-step task.\n- No specialist advantage.\n- No broad search needed.\n- Risk of misrouting exceeds benefit.\n\n# Parallel Execution Mandate\nIf tasks are independent, launch them simultaneously.\n\n## Parallelism Rules\n- Fire independent delegations in parallel.\n- Fire independent tool calls in parallel.\n- Do not serialize unrelated reads/searches.\n- Gather results only when required by dependency.\n\n## Dependency Rule\n- If Task B depends on Task A output, run sequentially.\n- Otherwise parallelize by default.\n\n# Anti-Duplication Rules (Strict)\nOnce you delegate exploration, do not re-run the same search yourself.\n\n## Forbidden\n- Repeating delegated grep/glob/LSP discovery manually.\n- Running \"quick checks\" on the same question already delegated.\n- Contradicting pending delegated work with fresh duplicate searches.\n\n## Allowed\n- Independent implementation not requiring delegated findings.\n- Preparation work with no overlap.\n- Waiting for completion if dependent work is blocked.\n\n# Session Continuity\nFor follow-ups, reuse delegated session context when available.\n\n## Continuation Policy\n- Same subproblem -> continue existing agent session.\n- Failed attempt -> continue same session with corrective instruction.\n- Related follow-up question -> continue same session.\n- New unrelated problem -> start a new session.\n\n# Planning and Task Discipline\nIf work has 2+ meaningful steps, maintain a structured todo list.\n\n## Todo Rules\n- Create atomic tasks.\n- Keep exactly one task in_progress.\n- Mark completed immediately after verification.\n- Do not batch status updates.\n\n# Verification Standard\nCompletion claims require evidence from tools/commands.\n\n## Required Evidence\n- Diagnostics clean for changed files.\n- Build success when applicable.\n- Tests pass when applicable.\n- Delegated results are reviewed, not blindly trusted.\n\n## Claim Policy\n- Never assert success without fresh evidence.\n- If evidence fails, report failure with root cause and next action.\n\n# Hard Constraints\n- Never suppress types with as any, @ts-ignore, or @ts-expect-error.\n- Never commit or push unless user explicitly requests.\n- Never edit files in read-only advisory workflows.\n- Never expand scope with unrelated \"bonus\" work.\n\n# Failure Handling\nWhen blocked or failing repeatedly:\n- Focus on root cause, not symptoms.\n- Reduce change surface.\n- Re-verify after each meaningful fix.\n- Escalate with concise options when uncertainty remains high.\n\n# Final Response Contract\nWhen reporting back:\n- State what was requested.\n- State what was done.\n- Provide verification evidence.\n- Note any assumptions and unresolved risks.\n- Offer next step only if it directly advances the current goal.\n";
1
+ export declare const ORCHESTRATOR_PROMPT = "# Role\nYou are GoatCode's primary orchestration brain.\nYour job is to convert user intent into reliable outcomes by planning, routing, verifying, and closing loops.\n\nYou are not here to \"do everything yourself.\" You are here to make the right work happen through the right agent at the right time.\n\n## Core Mission\n- Classify intent before action.\n- Decompose requests into atomic tasks.\n- Delegate specialist work when available.\n- Run independent work in parallel.\n- Prevent duplicate exploration.\n- Verify evidence before completion claims.\n\n## Interaction Contract\n- Be direct, concise, and operational.\n- No flattery, no filler, no unnecessary preamble.\n- Match user tone and depth.\n- Ask clarifying questions only when ambiguity materially changes cost/outcome.\n\n# Delegation-First Protocol (CRITICAL)\nDelegate BEFORE exploring. Every file you read directly consumes your context window, and AGENTS.md context is injected with every read \u2014 repeated reads cause exponential context bloat.\n\n## Exploration Budget\n- You may make at most **1-2 lightweight tool calls** (directory listing, single glob) before your first delegation.\n- If the task involves reading 3+ files, exploring a package/module, or any deep analysis: **delegate immediately**.\n- Do NOT read source files yourself to \"understand the structure first\" \u2014 delegate that understanding.\n\n## Ultrawork Mode\nWhen the user says \"ultrawork\" (or \"ulw\"), they want deep autonomous execution:\n- Delegate the ENTIRE task to deep-worker immediately.\n- Do not explore first. Do not read files first. Compose a thorough delegation prompt and fire it.\n- Your role in ultrawork mode is: decompose \u2192 delegate \u2192 wait \u2192 synthesize results.\n\n## Anti-Pattern: \"Just One More File\"\nNEVER fall into this pattern: read directory \u2192 read package.json \u2192 glob files \u2192 read index.ts \u2192 read more files...\nThis consumes your entire context budget with duplicated AGENTS.md injections and leads to timeouts.\nInstead: read directory (optional) \u2192 delegate deep exploration to specialist \u2192 wait for results.\n\n# Intent Analysis Framework (Mandatory First Step)\nBefore acting, classify the request:\n\n1) **Information / Explanation**\n- User wants understanding, not code changes.\n- Route: explorer/researcher and then synthesize.\n- Delegate in your FIRST response \u2014 do not read files yourself.\n\n2) **Implementation / Change**\n- User wants code created/modified.\n- Route: deep-worker or worker.\n- Delegate in your FIRST response \u2014 do not explore the codebase yourself.\n\n3) **Planning / Scoping**\n- User needs strategy, sequence, trade-offs.\n- Route: planner.\n\n4) **Architecture / Debugging Advice**\n- User needs expert judgment, not edits.\n- Route: advisor.\n\n5) **Investigation / Discovery**\n- User asks where/how code currently works.\n- Route: explorer (internal) and researcher (external if needed).\n- Delegate in your FIRST response \u2014 explorers are faster and don't bloat your context.\n\n6) **Mixed Intent**\n- Split into sub-tasks by intent type, then delegate ALL independently in the SAME turn.\n\n# Delegation Rules\nDefault to specialist delegation when scope is non-trivial.\n\n## Agent Routing Table\n- **orchestrator**: coordination, decomposition, verification.\n- **deep-worker**: end-to-end autonomous implementation.\n- **planner**: interview-mode planning and acceptance criteria.\n- **advisor**: read-only technical judgment.\n- **researcher**: external documentation and evidence gathering.\n- **explorer**: fast internal codebase discovery.\n- **worker**: focused execution of assigned atomic task.\n\n## When To Execute Directly\nExecute directly only when ALL are true:\n- Single-step task requiring \u22642 file reads.\n- No specialist advantage.\n- No broad search or multi-file analysis needed.\n- Risk of misrouting exceeds benefit.\n- NOT an ultrawork/investigation/discovery request.\n\n# Parallel Execution Mandate\nIf tasks are independent, launch them simultaneously.\n\n## Parallelism Rules\n- Fire independent delegations in parallel.\n- Fire independent tool calls in parallel.\n- Do not serialize unrelated reads/searches.\n- Gather results only when required by dependency.\n\n## Dependency Rule\n- If Task B depends on Task A output, run sequentially.\n- Otherwise parallelize by default.\n\n# Anti-Duplication Rules (Strict)\nOnce you delegate exploration, do not re-run the same search yourself.\n\n## Forbidden\n- Repeating delegated grep/glob/LSP discovery manually.\n- Running \"quick checks\" on the same question already delegated.\n- Contradicting pending delegated work with fresh duplicate searches.\n\n## Allowed\n- Independent implementation not requiring delegated findings.\n- Preparation work with no overlap.\n- Waiting for completion if dependent work is blocked.\n\n# Session Continuity\nFor follow-ups, reuse delegated session context when available.\n\n## Continuation Policy\n- Same subproblem -> continue existing agent session.\n- Failed attempt -> continue same session with corrective instruction.\n- Related follow-up question -> continue same session.\n- New unrelated problem -> start a new session.\n\n# Planning and Task Discipline\nIf work has 2+ meaningful steps, maintain a structured todo list.\n\n## Todo Rules\n- Create atomic tasks.\n- Keep exactly one task in_progress.\n- Mark completed immediately after verification.\n- Do not batch status updates.\n\n# Verification Standard\nCompletion claims require evidence from tools/commands.\n\n## Required Evidence\n- Diagnostics clean for changed files.\n- Build success when applicable.\n- Tests pass when applicable.\n- Delegated results are reviewed, not blindly trusted.\n\n## Claim Policy\n- Never assert success without fresh evidence.\n- If evidence fails, report failure with root cause and next action.\n\n# Hard Constraints\n- Never suppress types with as any, @ts-ignore, or @ts-expect-error.\n- Never commit or push unless user explicitly requests.\n- Never edit files in read-only advisory workflows.\n- Never expand scope with unrelated \"bonus\" work.\n\n# Failure Handling\nWhen blocked or failing repeatedly:\n- Focus on root cause, not symptoms.\n- Reduce change surface.\n- Re-verify after each meaningful fix.\n- Escalate with concise options when uncertainty remains high.\n\n# Final Response Contract\nWhen reporting back:\n- State what was requested.\n- State what was done.\n- Provide verification evidence.\n- Note any assumptions and unresolved risks.\n- Offer next step only if it directly advances the current goal.\n";
@@ -1,5 +1,6 @@
1
1
  import type { PluginHookContributions } from "../../types/hook";
2
2
  type PostToolUseHook = NonNullable<PluginHookContributions["tool.execute.after"]>;
3
3
  export declare const POST_READ_NUDGE = "\n\n---\nWorkflow Reminder: delegate based on rules; if mentioning a specialist, launch it in this same turn.";
4
+ export declare const DELEGATION_ESCALATION_NUDGE: string;
4
5
  export declare function createPostReadNudgeHandler(): PostToolUseHook;
5
6
  export {};
@@ -1,2 +1,2 @@
1
- export { createPostReadNudgeHandler, POST_READ_NUDGE } from "./handler";
1
+ export { createPostReadNudgeHandler, POST_READ_NUDGE, DELEGATION_ESCALATION_NUDGE, } from "./handler";
2
2
  export { postReadNudgePlugin } from "./plugin";
package/dist/index.js CHANGED
@@ -1073,16 +1073,37 @@ You are not here to "do everything yourself." You are here to make the right wor
1073
1073
  - Match user tone and depth.
1074
1074
  - Ask clarifying questions only when ambiguity materially changes cost/outcome.
1075
1075
 
1076
+ # Delegation-First Protocol (CRITICAL)
1077
+ Delegate BEFORE exploring. Every file you read directly consumes your context window, and AGENTS.md context is injected with every read \u2014 repeated reads cause exponential context bloat.
1078
+
1079
+ ## Exploration Budget
1080
+ - You may make at most **1-2 lightweight tool calls** (directory listing, single glob) before your first delegation.
1081
+ - If the task involves reading 3+ files, exploring a package/module, or any deep analysis: **delegate immediately**.
1082
+ - Do NOT read source files yourself to "understand the structure first" \u2014 delegate that understanding.
1083
+
1084
+ ## Ultrawork Mode
1085
+ When the user says "ultrawork" (or "ulw"), they want deep autonomous execution:
1086
+ - Delegate the ENTIRE task to deep-worker immediately.
1087
+ - Do not explore first. Do not read files first. Compose a thorough delegation prompt and fire it.
1088
+ - Your role in ultrawork mode is: decompose \u2192 delegate \u2192 wait \u2192 synthesize results.
1089
+
1090
+ ## Anti-Pattern: "Just One More File"
1091
+ NEVER fall into this pattern: read directory \u2192 read package.json \u2192 glob files \u2192 read index.ts \u2192 read more files...
1092
+ This consumes your entire context budget with duplicated AGENTS.md injections and leads to timeouts.
1093
+ Instead: read directory (optional) \u2192 delegate deep exploration to specialist \u2192 wait for results.
1094
+
1076
1095
  # Intent Analysis Framework (Mandatory First Step)
1077
1096
  Before acting, classify the request:
1078
1097
 
1079
1098
  1) **Information / Explanation**
1080
1099
  - User wants understanding, not code changes.
1081
1100
  - Route: explorer/researcher and then synthesize.
1101
+ - Delegate in your FIRST response \u2014 do not read files yourself.
1082
1102
 
1083
1103
  2) **Implementation / Change**
1084
1104
  - User wants code created/modified.
1085
1105
  - Route: deep-worker or worker.
1106
+ - Delegate in your FIRST response \u2014 do not explore the codebase yourself.
1086
1107
 
1087
1108
  3) **Planning / Scoping**
1088
1109
  - User needs strategy, sequence, trade-offs.
@@ -1095,9 +1116,10 @@ Before acting, classify the request:
1095
1116
  5) **Investigation / Discovery**
1096
1117
  - User asks where/how code currently works.
1097
1118
  - Route: explorer (internal) and researcher (external if needed).
1119
+ - Delegate in your FIRST response \u2014 explorers are faster and don't bloat your context.
1098
1120
 
1099
1121
  6) **Mixed Intent**
1100
- - Split into sub-tasks by intent type, then delegate independently.
1122
+ - Split into sub-tasks by intent type, then delegate ALL independently in the SAME turn.
1101
1123
 
1102
1124
  # Delegation Rules
1103
1125
  Default to specialist delegation when scope is non-trivial.
@@ -1112,11 +1134,12 @@ Default to specialist delegation when scope is non-trivial.
1112
1134
  - **worker**: focused execution of assigned atomic task.
1113
1135
 
1114
1136
  ## When To Execute Directly
1115
- Execute directly only when all are true:
1116
- - Single-step task.
1137
+ Execute directly only when ALL are true:
1138
+ - Single-step task requiring \u22642 file reads.
1117
1139
  - No specialist advantage.
1118
- - No broad search needed.
1140
+ - No broad search or multi-file analysis needed.
1119
1141
  - Risk of misrouting exceeds benefit.
1142
+ - NOT an ultrawork/investigation/discovery request.
1120
1143
 
1121
1144
  # Parallel Execution Mandate
1122
1145
  If tasks are independent, launch them simultaneously.
@@ -2168,6 +2191,7 @@ function collectAgentsPaths(fileDirectory, workspaceDirectory) {
2168
2191
  return paths.reverse();
2169
2192
  }
2170
2193
  function createAgentsInjectorHandler(workspaceDirectory) {
2194
+ const injectedBySession = new Map;
2171
2195
  return async (input, output) => {
2172
2196
  const typedInput = input;
2173
2197
  const typedOutput = output;
@@ -2179,15 +2203,28 @@ function createAgentsInjectorHandler(workspaceDirectory) {
2179
2203
  if (!filePath || typeof typedOutput.output !== "string") {
2180
2204
  return;
2181
2205
  }
2206
+ const sessionKey = typedInput.sessionID ?? "__default";
2207
+ let injectedPaths = injectedBySession.get(sessionKey);
2208
+ if (!injectedPaths) {
2209
+ injectedPaths = new Set;
2210
+ injectedBySession.set(sessionKey, injectedPaths);
2211
+ }
2182
2212
  const fileDirectory = dirname(filePath);
2183
2213
  const agentsPaths = collectAgentsPaths(fileDirectory, workspaceDirectory);
2184
2214
  for (const agentsPath of agentsPaths) {
2215
+ if (injectedPaths.has(agentsPath)) {
2216
+ typedOutput.output += `
2217
+
2218
+ [Directory Context: ${agentsPath} \u2014 see full AGENTS.md injected above]`;
2219
+ continue;
2220
+ }
2185
2221
  try {
2186
2222
  const content = readFileSync2(agentsPath, "utf8");
2187
2223
  typedOutput.output += `
2188
2224
 
2189
2225
  [Directory Context: ${agentsPath}]
2190
2226
  ${content}`;
2227
+ injectedPaths.add(agentsPath);
2191
2228
  } catch (error) {
2192
2229
  log("[agents-injector] Failed to read AGENTS.md", {
2193
2230
  agentsPath,
@@ -5313,13 +5350,23 @@ var POST_READ_NUDGE = `
5313
5350
 
5314
5351
  ---
5315
5352
  Workflow Reminder: delegate based on rules; if mentioning a specialist, launch it in this same turn.`;
5353
+ var DELEGATION_NUDGE_THRESHOLD = 3;
5354
+ var DELEGATION_ESCALATION_NUDGE = `
5355
+
5356
+ ---
5357
+ [DELEGATION NUDGE] You have made multiple exploration calls without delegating. ` + "Stop reading files and delegate to a specialist agent NOW. " + "Each read compounds context bloat from AGENTS.md injection. " + "Use explorer for codebase discovery or deep-worker for thorough analysis.";
5316
5358
  function isRecord23(value) {
5317
5359
  return typeof value === "object" && value !== null;
5318
5360
  }
5319
5361
  function isReadTool2(tool) {
5320
5362
  return tool.toLowerCase() === "read";
5321
5363
  }
5364
+ function isExplorationTool(tool) {
5365
+ const name = tool.toLowerCase();
5366
+ return name === "read" || name === "grep" || name === "glob";
5367
+ }
5322
5368
  function createPostReadNudgeHandler() {
5369
+ let explorationCallCount = 0;
5323
5370
  return async (input, output) => {
5324
5371
  if (!isRecord23(input) || !isRecord23(output))
5325
5372
  return;
@@ -5327,10 +5374,22 @@ function createPostReadNudgeHandler() {
5327
5374
  const text = output.output;
5328
5375
  if (typeof tool !== "string" || typeof text !== "string")
5329
5376
  return;
5377
+ if (isExplorationTool(tool)) {
5378
+ explorationCallCount++;
5379
+ }
5330
5380
  if (!isReadTool2(tool))
5331
5381
  return;
5332
5382
  if (text.includes(POST_READ_NUDGE.trim()))
5333
5383
  return;
5384
+ if (explorationCallCount >= DELEGATION_NUDGE_THRESHOLD) {
5385
+ if (!text.includes(DELEGATION_ESCALATION_NUDGE.trim())) {
5386
+ output.output = `${text}${DELEGATION_ESCALATION_NUDGE}`;
5387
+ log("[post-read-nudge] escalated to delegation nudge", {
5388
+ explorationCallCount
5389
+ });
5390
+ }
5391
+ return;
5392
+ }
5334
5393
  output.output = `${text}${POST_READ_NUDGE}`;
5335
5394
  };
5336
5395
  }
@@ -6458,7 +6517,21 @@ var taskArgsSchema = z5.object({
6458
6517
  run_in_background: z5.boolean().describe("true: async (returns task_id), false: sync (waits for result)"),
6459
6518
  session_id: z5.string().optional().describe("Resume an existing session")
6460
6519
  });
6461
- function createTaskTool(getManager) {
6520
+ function resolveClient(toolContext, getStoredContext) {
6521
+ try {
6522
+ return getClientFromToolContext(toolContext);
6523
+ } catch {
6524
+ const stored = getStoredContext();
6525
+ if (stored?.client) {
6526
+ return stored.client;
6527
+ }
6528
+ throw new Error("OpenCode client unavailable. Neither the tool context nor the plugin context expose it.");
6529
+ }
6530
+ }
6531
+ function createTaskTool(getManager, getStoredContext) {
6532
+ const contextGetter = getStoredContext ?? (() => {
6533
+ return;
6534
+ });
6462
6535
  return buildTool({
6463
6536
  description: [
6464
6537
  "Delegate a task to a category-based agent.",
@@ -6482,7 +6555,7 @@ function createTaskTool(getManager) {
6482
6555
  log("[delegate-task] Unknown category", { category: input.category });
6483
6556
  return `Unknown category: "${input.category}". Available: ${available}`;
6484
6557
  }
6485
- const client = getClientFromToolContext(toolContext);
6558
+ const client = resolveClient(toolContext, contextGetter);
6486
6559
  const manager = getManager();
6487
6560
  const deps = {
6488
6561
  manager,
@@ -6505,17 +6578,20 @@ function createTaskTool(getManager) {
6505
6578
  function getManagerOrThrow() {
6506
6579
  return getBackgroundAgent().manager;
6507
6580
  }
6581
+ var storedContext;
6508
6582
  var plugin_default = definePlugin({
6509
6583
  name: "delegate-task",
6510
6584
  version: "1.0.0",
6511
- setup() {
6585
+ setup(ctx) {
6586
+ storedContext = ctx;
6512
6587
  initBackgroundAgent();
6513
6588
  },
6514
6589
  teardown() {
6590
+ storedContext = undefined;
6515
6591
  resetBackgroundAgent();
6516
6592
  },
6517
6593
  tools: {
6518
- task: createTaskTool(getManagerOrThrow)
6594
+ task: createTaskTool(getManagerOrThrow, () => storedContext)
6519
6595
  }
6520
6596
  });
6521
6597
  // src/tools/lsp/goto-definition/types.ts
@@ -7134,12 +7210,12 @@ var plugin_default3 = definePlugin({
7134
7210
  import { tool as tool6 } from "@opencode-ai/plugin";
7135
7211
 
7136
7212
  // src/tools/session-manager/client-context.ts
7137
- var storedContext;
7213
+ var storedContext2;
7138
7214
  function getSessionManagerContext() {
7139
- if (!storedContext) {
7215
+ if (!storedContext2) {
7140
7216
  throw new Error("Session manager context not initialized. Call initSessionManagerContext first.");
7141
7217
  }
7142
- return storedContext;
7218
+ return storedContext2;
7143
7219
  }
7144
7220
 
7145
7221
  // src/tools/session-manager/session-formatter.ts
@@ -7973,7 +8049,7 @@ function isNewerVersion(latest, current) {
7973
8049
  // package.json
7974
8050
  var package_default = {
7975
8051
  name: "goatcode-sh",
7976
- version: "0.1.2",
8052
+ version: "0.1.3",
7977
8053
  description: "Enterprise-grade OpenCode plugin with micro-plugin architecture, multi-agent orchestration, and professional naming",
7978
8054
  main: "dist/index.js",
7979
8055
  types: "dist/index.d.ts",
@@ -1,3 +1,4 @@
1
1
  import type { ToolDefinition } from "@opencode-ai/plugin";
2
2
  import type { BackgroundAgentManager } from "../../runtime";
3
- export declare function createTaskTool(getManager: () => BackgroundAgentManager): ToolDefinition;
3
+ import type { OpenCodeContext } from "../../types/plugin";
4
+ export declare function createTaskTool(getManager: () => BackgroundAgentManager, getStoredContext?: () => OpenCodeContext | undefined): ToolDefinition;
@@ -1,2 +1,4 @@
1
+ import type { OpenCodeContext } from "../../types/plugin";
2
+ export declare function getDelegateTaskContext(): OpenCodeContext | undefined;
1
3
  declare const _default: import("../../plugin-api").PluginDefinition;
2
4
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goatcode-sh",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Enterprise-grade OpenCode plugin with micro-plugin architecture, multi-agent orchestration, and professional naming",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,16 +18,37 @@ You are not here to "do everything yourself." You are here to make the right wor
18
18
  - Match user tone and depth.
19
19
  - Ask clarifying questions only when ambiguity materially changes cost/outcome.
20
20
 
21
+ # Delegation-First Protocol (CRITICAL)
22
+ Delegate BEFORE exploring. Every file you read directly consumes your context window, and AGENTS.md context is injected with every read — repeated reads cause exponential context bloat.
23
+
24
+ ## Exploration Budget
25
+ - You may make at most **1-2 lightweight tool calls** (directory listing, single glob) before your first delegation.
26
+ - If the task involves reading 3+ files, exploring a package/module, or any deep analysis: **delegate immediately**.
27
+ - Do NOT read source files yourself to "understand the structure first" — delegate that understanding.
28
+
29
+ ## Ultrawork Mode
30
+ When the user says "ultrawork" (or "ulw"), they want deep autonomous execution:
31
+ - Delegate the ENTIRE task to deep-worker immediately.
32
+ - Do not explore first. Do not read files first. Compose a thorough delegation prompt and fire it.
33
+ - Your role in ultrawork mode is: decompose → delegate → wait → synthesize results.
34
+
35
+ ## Anti-Pattern: "Just One More File"
36
+ NEVER fall into this pattern: read directory → read package.json → glob files → read index.ts → read more files...
37
+ This consumes your entire context budget with duplicated AGENTS.md injections and leads to timeouts.
38
+ Instead: read directory (optional) → delegate deep exploration to specialist → wait for results.
39
+
21
40
  # Intent Analysis Framework (Mandatory First Step)
22
41
  Before acting, classify the request:
23
42
 
24
43
  1) **Information / Explanation**
25
44
  - User wants understanding, not code changes.
26
45
  - Route: explorer/researcher and then synthesize.
46
+ - Delegate in your FIRST response — do not read files yourself.
27
47
 
28
48
  2) **Implementation / Change**
29
49
  - User wants code created/modified.
30
50
  - Route: deep-worker or worker.
51
+ - Delegate in your FIRST response — do not explore the codebase yourself.
31
52
 
32
53
  3) **Planning / Scoping**
33
54
  - User needs strategy, sequence, trade-offs.
@@ -40,9 +61,10 @@ Before acting, classify the request:
40
61
  5) **Investigation / Discovery**
41
62
  - User asks where/how code currently works.
42
63
  - Route: explorer (internal) and researcher (external if needed).
64
+ - Delegate in your FIRST response — explorers are faster and don't bloat your context.
43
65
 
44
66
  6) **Mixed Intent**
45
- - Split into sub-tasks by intent type, then delegate independently.
67
+ - Split into sub-tasks by intent type, then delegate ALL independently in the SAME turn.
46
68
 
47
69
  # Delegation Rules
48
70
  Default to specialist delegation when scope is non-trivial.
@@ -57,11 +79,12 @@ Default to specialist delegation when scope is non-trivial.
57
79
  - **worker**: focused execution of assigned atomic task.
58
80
 
59
81
  ## When To Execute Directly
60
- Execute directly only when all are true:
61
- - Single-step task.
82
+ Execute directly only when ALL are true:
83
+ - Single-step task requiring ≤2 file reads.
62
84
  - No specialist advantage.
63
- - No broad search needed.
85
+ - No broad search or multi-file analysis needed.
64
86
  - Risk of misrouting exceeds benefit.
87
+ - NOT an ultrawork/investigation/discovery request.
65
88
 
66
89
  # Parallel Execution Mandate
67
90
  If tasks are independent, launch them simultaneously.
@@ -87,4 +87,54 @@ describe("createAgentsInjectorHandler", () => {
87
87
  });
88
88
  });
89
89
  });
90
+
91
+ describe("#given repeated reads from the same AGENTS.md scope", () => {
92
+ describe("#when the same AGENTS.md would be injected twice in one session", () => {
93
+ it("#then injects full content once and a back-reference on subsequent reads", async () => {
94
+ const workspace = createWorkspace();
95
+ const subDir = join(workspace, "src");
96
+ mkdirSync(subDir, { recursive: true });
97
+ writeFileSync(join(workspace, "AGENTS.md"), "root-agent-rules");
98
+ writeFileSync(join(subDir, "file1.ts"), "export {}");
99
+ writeFileSync(join(subDir, "file2.ts"), "export {}");
100
+
101
+ const handler = createAgentsInjectorHandler(workspace);
102
+
103
+ // First read — full content injected
104
+ const output1 = { title: join(subDir, "file1.ts"), output: "content1" };
105
+ await handler({ tool: "read", sessionID: "ses1" }, output1);
106
+ expect(output1.output).toContain("root-agent-rules");
107
+ expect(output1.output).not.toContain("see full AGENTS.md injected above");
108
+
109
+ // Second read in same session — back-reference only
110
+ const output2 = { title: join(subDir, "file2.ts"), output: "content2" };
111
+ await handler({ tool: "read", sessionID: "ses1" }, output2);
112
+ expect(output2.output).toContain("see full AGENTS.md injected above");
113
+ expect(output2.output).not.toContain("root-agent-rules");
114
+ });
115
+ });
116
+
117
+ describe("#when a different session reads the same file", () => {
118
+ it("#then injects full content again for the new session", async () => {
119
+ const workspace = createWorkspace();
120
+ const subDir = join(workspace, "src");
121
+ mkdirSync(subDir, { recursive: true });
122
+ writeFileSync(join(workspace, "AGENTS.md"), "root-agent-rules");
123
+ writeFileSync(join(subDir, "file1.ts"), "export {}");
124
+
125
+ const handler = createAgentsInjectorHandler(workspace);
126
+
127
+ // Session 1 read — full content
128
+ const output1 = { title: join(subDir, "file1.ts"), output: "content1" };
129
+ await handler({ tool: "read", sessionID: "ses1" }, output1);
130
+ expect(output1.output).toContain("root-agent-rules");
131
+
132
+ // Session 2 read — full content again (not a back-reference)
133
+ const output2 = { title: join(subDir, "file1.ts"), output: "content2" };
134
+ await handler({ tool: "read", sessionID: "ses2" }, output2);
135
+ expect(output2.output).toContain("root-agent-rules");
136
+ expect(output2.output).not.toContain("see full AGENTS.md injected above");
137
+ });
138
+ });
139
+ });
90
140
  });
@@ -4,6 +4,7 @@ import { log } from "../../../shared/logger";
4
4
 
5
5
  type ToolExecuteAfterInput = {
6
6
  tool?: string;
7
+ sessionID?: string;
7
8
  };
8
9
 
9
10
  type ToolExecuteAfterOutput = {
@@ -49,6 +50,11 @@ function collectAgentsPaths(fileDirectory: string, workspaceDirectory: string):
49
50
  }
50
51
 
51
52
  export function createAgentsInjectorHandler(workspaceDirectory: string) {
53
+ // Track which AGENTS.md files have been fully injected per session
54
+ // to avoid repeating hundreds of lines of context on every read call.
55
+ // Keyed by sessionID so dedup resets correctly across sessions.
56
+ const injectedBySession = new Map<string, Set<string>>();
57
+
52
58
  return async (input: unknown, output: unknown): Promise<void> => {
53
59
  const typedInput = input as ToolExecuteAfterInput;
54
60
  const typedOutput = output as ToolExecuteAfterOutput;
@@ -63,13 +69,27 @@ export function createAgentsInjectorHandler(workspaceDirectory: string) {
63
69
  return;
64
70
  }
65
71
 
72
+ const sessionKey = typedInput.sessionID ?? "__default";
73
+ let injectedPaths = injectedBySession.get(sessionKey);
74
+ if (!injectedPaths) {
75
+ injectedPaths = new Set<string>();
76
+ injectedBySession.set(sessionKey, injectedPaths);
77
+ }
78
+
66
79
  const fileDirectory = dirname(filePath);
67
80
  const agentsPaths = collectAgentsPaths(fileDirectory, workspaceDirectory);
68
81
 
69
82
  for (const agentsPath of agentsPaths) {
83
+ if (injectedPaths.has(agentsPath)) {
84
+ // Already injected full content in this session — append a short back-reference only
85
+ typedOutput.output += `\n\n[Directory Context: ${agentsPath} — see full AGENTS.md injected above]`;
86
+ continue;
87
+ }
88
+
70
89
  try {
71
90
  const content = readFileSync(agentsPath, "utf8");
72
91
  typedOutput.output += `\n\n[Directory Context: ${agentsPath}]\n${content}`;
92
+ injectedPaths.add(agentsPath);
73
93
  } catch (error) {
74
94
  log("[agents-injector] Failed to read AGENTS.md", {
75
95
  agentsPath,
@@ -103,4 +103,57 @@ describe("createPostReadNudgeHandler", () => {
103
103
  });
104
104
  });
105
105
  });
106
+
107
+ describe("#given multiple exploration tool calls exceeding threshold", () => {
108
+ describe("#when 3+ exploration calls are made (read, grep, glob mix)", () => {
109
+ it("#then escalates to a delegation nudge on subsequent reads", async () => {
110
+ const handler = createPostReadNudgeHandler();
111
+
112
+ // First two reads get normal nudge
113
+ const input1 = { tool: "read", sessionID: "s1", callID: "c1", args: {} };
114
+ const output1 = { title: "read", output: "content1", metadata: {} };
115
+ await handler(input1, output1);
116
+ expect(output1.output).toContain("Workflow Reminder");
117
+ expect(output1.output).not.toContain("DELEGATION NUDGE");
118
+
119
+ // Second read
120
+ const input2 = { tool: "read", sessionID: "s1", callID: "c2", args: {} };
121
+ const output2 = { title: "read", output: "content2", metadata: {} };
122
+ await handler(input2, output2);
123
+ expect(output2.output).toContain("Workflow Reminder");
124
+ expect(output2.output).not.toContain("DELEGATION NUDGE");
125
+
126
+ // Third read triggers escalation
127
+ const input3 = { tool: "read", sessionID: "s1", callID: "c3", args: {} };
128
+ const output3 = { title: "read", output: "content3", metadata: {} };
129
+ await handler(input3, output3);
130
+ expect(output3.output).toContain("DELEGATION NUDGE");
131
+ expect(output3.output).toContain("delegate to a specialist agent NOW");
132
+ });
133
+ });
134
+
135
+ describe("#when grep and glob calls contribute to the count", () => {
136
+ it("#then counts all exploration tools toward the threshold", async () => {
137
+ const handler = createPostReadNudgeHandler();
138
+
139
+ // Grep call — doesn't append nudge to grep output
140
+ const grepInput = { tool: "grep", sessionID: "s1", callID: "c1", args: {} };
141
+ const grepOutput = { title: "grep", output: "grep results", metadata: {} };
142
+ await handler(grepInput, grepOutput);
143
+ expect(grepOutput.output).toBe("grep results");
144
+
145
+ // Glob call — doesn't append nudge to glob output
146
+ const globInput = { tool: "glob", sessionID: "s1", callID: "c2", args: {} };
147
+ const globOutput = { title: "glob", output: "glob results", metadata: {} };
148
+ await handler(globInput, globOutput);
149
+ expect(globOutput.output).toBe("glob results");
150
+
151
+ // Third exploration call is a read — should trigger escalation
152
+ const readInput = { tool: "read", sessionID: "s1", callID: "c3", args: {} };
153
+ const readOutput = { title: "read", output: "file content", metadata: {} };
154
+ await handler(readInput, readOutput);
155
+ expect(readOutput.output).toContain("DELEGATION NUDGE");
156
+ });
157
+ });
158
+ });
106
159
  });
@@ -1,10 +1,19 @@
1
1
  import type { PluginHookContributions } from "../../types/hook";
2
+ import { log } from "../../shared/logger";
2
3
 
3
4
  type PostToolUseHook = NonNullable<PluginHookContributions["tool.execute.after"]>;
4
5
 
5
6
  export const POST_READ_NUDGE =
6
7
  "\n\n---\nWorkflow Reminder: delegate based on rules; if mentioning a specialist, launch it in this same turn.";
7
8
 
9
+ const DELEGATION_NUDGE_THRESHOLD = 3;
10
+
11
+ export const DELEGATION_ESCALATION_NUDGE =
12
+ "\n\n---\n[DELEGATION NUDGE] You have made multiple exploration calls without delegating. " +
13
+ "Stop reading files and delegate to a specialist agent NOW. " +
14
+ "Each read compounds context bloat from AGENTS.md injection. " +
15
+ "Use explorer for codebase discovery or deep-worker for thorough analysis.";
16
+
8
17
  function isRecord(value: unknown): value is Record<string, unknown> {
9
18
  return typeof value === "object" && value !== null;
10
19
  }
@@ -13,16 +22,43 @@ function isReadTool(tool: string): boolean {
13
22
  return tool.toLowerCase() === "read";
14
23
  }
15
24
 
25
+ function isExplorationTool(tool: string): boolean {
26
+ const name = tool.toLowerCase();
27
+ return name === "read" || name === "grep" || name === "glob";
28
+ }
29
+
16
30
  export function createPostReadNudgeHandler(): PostToolUseHook {
31
+ // Track exploration call count to escalate delegation nudges.
32
+ // Resets when the handler is recreated (new session).
33
+ let explorationCallCount = 0;
34
+
17
35
  return async (input: unknown, output: unknown) => {
18
36
  if (!isRecord(input) || !isRecord(output)) return;
19
37
 
20
38
  const tool = input.tool;
21
39
  const text = output.output;
22
40
  if (typeof tool !== "string" || typeof text !== "string") return;
41
+
42
+ // Count all exploration tool calls (read, grep, glob)
43
+ if (isExplorationTool(tool)) {
44
+ explorationCallCount++;
45
+ }
46
+
47
+ // Only append nudges to read tool outputs
23
48
  if (!isReadTool(tool)) return;
24
49
  if (text.includes(POST_READ_NUDGE.trim())) return;
25
50
 
51
+ // After threshold, escalate to a stronger delegation nudge
52
+ if (explorationCallCount >= DELEGATION_NUDGE_THRESHOLD) {
53
+ if (!text.includes(DELEGATION_ESCALATION_NUDGE.trim())) {
54
+ output.output = `${text}${DELEGATION_ESCALATION_NUDGE}`;
55
+ log("[post-read-nudge] escalated to delegation nudge", {
56
+ explorationCallCount,
57
+ });
58
+ }
59
+ return;
60
+ }
61
+
26
62
  output.output = `${text}${POST_READ_NUDGE}`;
27
63
  };
28
64
  }
@@ -1,2 +1,6 @@
1
- export { createPostReadNudgeHandler, POST_READ_NUDGE } from "./handler";
1
+ export {
2
+ createPostReadNudgeHandler,
3
+ POST_READ_NUDGE,
4
+ DELEGATION_ESCALATION_NUDGE,
5
+ } from "./handler";
2
6
  export { postReadNudgePlugin } from "./plugin";
@@ -1,5 +1,6 @@
1
1
  import type { ToolDefinition } from "@opencode-ai/plugin";
2
2
  import type { BackgroundAgentManager } from "../../runtime";
3
+ import type { OpenCodeContext } from "../../types/plugin";
3
4
  import type { TaskInput, CategoryConfig } from "./types";
4
5
  import type { ExecutorDeps } from "./executor";
5
6
  import { z } from "zod";
@@ -24,7 +25,39 @@ const taskArgsSchema = z.object({
24
25
  session_id: z.string().optional().describe("Resume an existing session"),
25
26
  });
26
27
 
27
- export function createTaskTool(getManager: () => BackgroundAgentManager): ToolDefinition {
28
+ /**
29
+ * Resolve the OpenCode client, trying the tool context first then
30
+ * falling back to the stored plugin-level context.
31
+ *
32
+ * OpenCode's plugin runtime does not always expose `client` on the
33
+ * tool execution context. The plugin-level context (captured at setup)
34
+ * is a reliable fallback.
35
+ */
36
+ function resolveClient(
37
+ toolContext: Parameters<ToolDefinition["execute"]>[1],
38
+ getStoredContext: () => OpenCodeContext | undefined,
39
+ ): OpenCodeContext["client"] {
40
+ // Try tool context first (works if OpenCode exposes client there)
41
+ try {
42
+ return getClientFromToolContext(toolContext);
43
+ } catch {
44
+ // Fall back to stored plugin-level context
45
+ const stored = getStoredContext();
46
+ if (stored?.client) {
47
+ return stored.client;
48
+ }
49
+ throw new Error(
50
+ "OpenCode client unavailable. Neither the tool context nor the plugin context expose it.",
51
+ );
52
+ }
53
+ }
54
+
55
+ export function createTaskTool(
56
+ getManager: () => BackgroundAgentManager,
57
+ getStoredContext?: () => OpenCodeContext | undefined,
58
+ ): ToolDefinition {
59
+ const contextGetter = getStoredContext ?? (() => undefined);
60
+
28
61
  return buildTool({
29
62
  description: [
30
63
  "Delegate a task to a category-based agent.",
@@ -50,7 +83,7 @@ export function createTaskTool(getManager: () => BackgroundAgentManager): ToolDe
50
83
  return `Unknown category: "${input.category}". Available: ${available}`;
51
84
  }
52
85
 
53
- const client = getClientFromToolContext(toolContext);
86
+ const client = resolveClient(toolContext, contextGetter);
54
87
  const manager = getManager();
55
88
  const deps: ExecutorDeps = {
56
89
  manager,
@@ -1,3 +1,4 @@
1
+ import type { OpenCodeContext } from "../../types/plugin";
1
2
  import { definePlugin } from "../../plugin-api/define-plugin";
2
3
  import { getBackgroundAgent, initBackgroundAgent, resetBackgroundAgent } from "../../runtime";
3
4
  import { createTaskTool } from "./handler";
@@ -6,16 +7,24 @@ function getManagerOrThrow() {
6
7
  return getBackgroundAgent().manager;
7
8
  }
8
9
 
10
+ let storedContext: OpenCodeContext | undefined;
11
+
12
+ export function getDelegateTaskContext(): OpenCodeContext | undefined {
13
+ return storedContext;
14
+ }
15
+
9
16
  export default definePlugin({
10
17
  name: "delegate-task",
11
18
  version: "1.0.0",
12
- setup() {
19
+ setup(ctx) {
20
+ storedContext = ctx;
13
21
  initBackgroundAgent();
14
22
  },
15
23
  teardown() {
24
+ storedContext = undefined;
16
25
  resetBackgroundAgent();
17
26
  },
18
27
  tools: {
19
- task: createTaskTool(getManagerOrThrow),
28
+ task: createTaskTool(getManagerOrThrow, () => storedContext),
20
29
  },
21
30
  });