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.
- package/dist/agents/orchestrator/prompt.d.ts +1 -1
- package/dist/hooks/post-read-nudge/handler.d.ts +1 -0
- package/dist/hooks/post-read-nudge/index.d.ts +1 -1
- package/dist/index.js +88 -12
- package/dist/tools/delegate-task/handler.d.ts +2 -1
- package/dist/tools/delegate-task/plugin.d.ts +2 -0
- package/package.json +1 -1
- package/src/agents/orchestrator/prompt.ts +27 -4
- package/src/hooks/context-injector/handlers/agents.test.ts +50 -0
- package/src/hooks/context-injector/handlers/agents.ts +20 -0
- package/src/hooks/post-read-nudge/handler.test.ts +53 -0
- package/src/hooks/post-read-nudge/handler.ts +36 -0
- package/src/hooks/post-read-nudge/index.ts +5 -1
- package/src/tools/delegate-task/handler.ts +35 -2
- package/src/tools/delegate-task/plugin.ts +11 -2
|
@@ -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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
7213
|
+
var storedContext2;
|
|
7138
7214
|
function getSessionManagerContext() {
|
|
7139
|
-
if (!
|
|
7215
|
+
if (!storedContext2) {
|
|
7140
7216
|
throw new Error("Session manager context not initialized. Call initSessionManagerContext first.");
|
|
7141
7217
|
}
|
|
7142
|
-
return
|
|
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.
|
|
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
|
-
|
|
3
|
+
import type { OpenCodeContext } from "../../types/plugin";
|
|
4
|
+
export declare function createTaskTool(getManager: () => BackgroundAgentManager, getStoredContext?: () => OpenCodeContext | undefined): ToolDefinition;
|
package/package.json
CHANGED
|
@@ -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
|
|
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,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
|
-
|
|
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 =
|
|
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
|
});
|