opencode-orchestrator 0.5.1 → 0.5.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/index.d.ts +22 -3
- package/dist/index.js +790 -12
- package/dist/tools/background.d.ts +2 -2
- package/dist/tools/git.d.ts +2 -2
- package/package.json +2 -3
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,24 @@
|
|
|
13
13
|
import type { PluginInput } from "@opencode-ai/plugin";
|
|
14
14
|
declare const OrchestratorPlugin: (input: PluginInput) => Promise<{
|
|
15
15
|
tool: {
|
|
16
|
+
git_branch: {
|
|
17
|
+
description: string;
|
|
18
|
+
args: {
|
|
19
|
+
action: import("zod").ZodEnum<{
|
|
20
|
+
status: "status";
|
|
21
|
+
diff: "diff";
|
|
22
|
+
current: "current";
|
|
23
|
+
list: "list";
|
|
24
|
+
recent: "recent";
|
|
25
|
+
all: "all";
|
|
26
|
+
}>;
|
|
27
|
+
baseBranch: import("zod").ZodOptional<import("zod").ZodString>;
|
|
28
|
+
};
|
|
29
|
+
execute(args: {
|
|
30
|
+
action: "status" | "diff" | "current" | "list" | "recent" | "all";
|
|
31
|
+
baseBranch?: string | undefined;
|
|
32
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
33
|
+
};
|
|
16
34
|
call_agent: {
|
|
17
35
|
description: string;
|
|
18
36
|
args: {
|
|
@@ -108,13 +126,13 @@ declare const OrchestratorPlugin: (input: PluginInput) => Promise<{
|
|
|
108
126
|
args: {
|
|
109
127
|
status: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
110
128
|
running: "running";
|
|
129
|
+
all: "all";
|
|
111
130
|
done: "done";
|
|
112
131
|
error: "error";
|
|
113
|
-
all: "all";
|
|
114
132
|
}>>;
|
|
115
133
|
};
|
|
116
134
|
execute(args: {
|
|
117
|
-
status?: "running" | "
|
|
135
|
+
status?: "running" | "all" | "done" | "error" | undefined;
|
|
118
136
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
119
137
|
};
|
|
120
138
|
kill_background: {
|
|
@@ -139,7 +157,8 @@ declare const OrchestratorPlugin: (input: PluginInput) => Promise<{
|
|
|
139
157
|
output: string;
|
|
140
158
|
metadata: any;
|
|
141
159
|
}) => Promise<void>;
|
|
142
|
-
|
|
160
|
+
"assistant.done": (assistantInput: any, assistantOutput: any) => Promise<void>;
|
|
161
|
+
handler: ({ event }: {
|
|
143
162
|
event: {
|
|
144
163
|
type: string;
|
|
145
164
|
properties?: unknown;
|
package/dist/index.js
CHANGED
|
@@ -13798,6 +13798,206 @@ Returns matches grouped by pattern, with file paths and line numbers.
|
|
|
13798
13798
|
}
|
|
13799
13799
|
});
|
|
13800
13800
|
|
|
13801
|
+
// src/tools/git.ts
|
|
13802
|
+
var gitBranchTool = (directory) => tool({
|
|
13803
|
+
description: `Get Git branch information and status.
|
|
13804
|
+
|
|
13805
|
+
<purpose>
|
|
13806
|
+
Analyze current Git workspace context including:
|
|
13807
|
+
- Current branch name
|
|
13808
|
+
- All branches (local and remote)
|
|
13809
|
+
- Branch relationships (upstream, ahead/behind)
|
|
13810
|
+
- Staged, unstaged, and untracked files
|
|
13811
|
+
- Recent commits
|
|
13812
|
+
</purpose>
|
|
13813
|
+
|
|
13814
|
+
<examples>
|
|
13815
|
+
- Get current branch: Returns branch name and status
|
|
13816
|
+
- List all branches: Shows local and remote branches
|
|
13817
|
+
- Check status: Shows modified files
|
|
13818
|
+
- Recent commits: Last 5 commits with file changes
|
|
13819
|
+
</examples>
|
|
13820
|
+
|
|
13821
|
+
<output>
|
|
13822
|
+
Returns structured Git information for better code decisions.
|
|
13823
|
+
</output>`,
|
|
13824
|
+
args: {
|
|
13825
|
+
action: tool.schema.enum([
|
|
13826
|
+
"current",
|
|
13827
|
+
"list",
|
|
13828
|
+
"status",
|
|
13829
|
+
"diff",
|
|
13830
|
+
"recent",
|
|
13831
|
+
"all"
|
|
13832
|
+
]).describe("Action to perform: current, list, status, diff, recent, all"),
|
|
13833
|
+
baseBranch: tool.schema.string().optional().describe("Base branch for comparison (e.g., 'main', 'develop'). Required for diff action.")
|
|
13834
|
+
},
|
|
13835
|
+
async execute(args) {
|
|
13836
|
+
const { action, baseBranch } = args;
|
|
13837
|
+
try {
|
|
13838
|
+
switch (action) {
|
|
13839
|
+
case "current":
|
|
13840
|
+
return await getCurrentBranch(directory);
|
|
13841
|
+
case "list":
|
|
13842
|
+
return await listBranches(directory);
|
|
13843
|
+
case "status":
|
|
13844
|
+
return await getGitStatus(directory);
|
|
13845
|
+
case "diff":
|
|
13846
|
+
return await getDiff(directory, baseBranch);
|
|
13847
|
+
case "recent":
|
|
13848
|
+
return await getRecentCommits(directory, 5);
|
|
13849
|
+
case "all":
|
|
13850
|
+
return await getAllInfo(directory);
|
|
13851
|
+
default:
|
|
13852
|
+
return await getCurrentBranch(directory);
|
|
13853
|
+
}
|
|
13854
|
+
} catch (error45) {
|
|
13855
|
+
return "\u274C Git error: " + (error45 instanceof Error ? error45.message : String(error45));
|
|
13856
|
+
}
|
|
13857
|
+
}
|
|
13858
|
+
});
|
|
13859
|
+
async function execGit(directory, args) {
|
|
13860
|
+
const { execSync } = await import("child_process");
|
|
13861
|
+
try {
|
|
13862
|
+
return execSync("git " + args.join(" "), {
|
|
13863
|
+
cwd: directory,
|
|
13864
|
+
encoding: "utf-8",
|
|
13865
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
13866
|
+
maxBuffer: 10 * 1024 * 1024
|
|
13867
|
+
// 10MB
|
|
13868
|
+
}).toString();
|
|
13869
|
+
} catch (error45) {
|
|
13870
|
+
if (error45.status === 128) {
|
|
13871
|
+
throw new Error("Not a git repository");
|
|
13872
|
+
}
|
|
13873
|
+
throw error45;
|
|
13874
|
+
}
|
|
13875
|
+
}
|
|
13876
|
+
async function getAheadBehind(directory, branch) {
|
|
13877
|
+
try {
|
|
13878
|
+
const output = await execGit(directory, ["rev-list", "--left-right", "--count", branch + "...@{u}"]);
|
|
13879
|
+
const parts = output.trim().split(/\s+/);
|
|
13880
|
+
const ahead = parseInt(parts[0], 10);
|
|
13881
|
+
const behind = parseInt(parts[1] || "0", 10);
|
|
13882
|
+
const resultParts = [];
|
|
13883
|
+
if (ahead > 0) resultParts.push(ahead + " ahead");
|
|
13884
|
+
if (behind > 0) resultParts.push(behind + " behind");
|
|
13885
|
+
return resultParts.length > 0 ? "| **Sync** | " + resultParts.join(", ") + " |" : "";
|
|
13886
|
+
} catch {
|
|
13887
|
+
return "";
|
|
13888
|
+
}
|
|
13889
|
+
}
|
|
13890
|
+
async function getCurrentBranch(directory) {
|
|
13891
|
+
const output = await execGit(directory, ["branch", "--show-current"]);
|
|
13892
|
+
const current = output.trim() || "HEAD (detached)";
|
|
13893
|
+
const upstream = await execGit(directory, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]).catch(() => "");
|
|
13894
|
+
return "\u{1F33F} **Current Branch**: `" + current + "`\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n" + (upstream ? "| **Upstream** | `" + upstream + "` |" : "") + await getAheadBehind(directory, current);
|
|
13895
|
+
}
|
|
13896
|
+
async function listBranches(directory) {
|
|
13897
|
+
const branches = await execGit(directory, ["branch", "-vv"]);
|
|
13898
|
+
const branchList = branches.split("\n").filter((b) => b.trim()).map((b) => {
|
|
13899
|
+
const isCurrent = b.startsWith("*");
|
|
13900
|
+
const name = isCurrent ? b.substring(2).trim() : b.trim();
|
|
13901
|
+
const parts = name.split(/\s+/);
|
|
13902
|
+
const branchName = parts[0];
|
|
13903
|
+
const upstream = parts[1] ? parts[1].match(/\[([^\]]+)\]/)?.[1] : void 0;
|
|
13904
|
+
const icon = isCurrent ? "\u{1F33F}" : "\u{1F4C2}";
|
|
13905
|
+
const status = isCurrent ? "(current)" : "";
|
|
13906
|
+
const upstreamInfo = upstream ? "\u2192 " + upstream : "";
|
|
13907
|
+
return icon + " `" + branchName + "` " + status + " " + upstreamInfo;
|
|
13908
|
+
}).join("\n");
|
|
13909
|
+
return "\u{1F33F} **All Branches**\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n" + branchList;
|
|
13910
|
+
}
|
|
13911
|
+
async function getGitStatus(directory) {
|
|
13912
|
+
const status = await execGit(directory, ["status", "--porcelain"]);
|
|
13913
|
+
const lines = status.split("\n").filter((l) => l.trim());
|
|
13914
|
+
if (lines.length === 0) {
|
|
13915
|
+
return "\u2705 Working directory clean";
|
|
13916
|
+
}
|
|
13917
|
+
const staged = [];
|
|
13918
|
+
const unstaged = [];
|
|
13919
|
+
const untracked = [];
|
|
13920
|
+
const conflicted = [];
|
|
13921
|
+
for (const line of lines) {
|
|
13922
|
+
if (!line || line.length < 2) continue;
|
|
13923
|
+
const statusCode = line.substring(0, 2);
|
|
13924
|
+
const filename = line.substring(3);
|
|
13925
|
+
if (statusCode === "??") {
|
|
13926
|
+
untracked.push(filename);
|
|
13927
|
+
} else if (statusCode.startsWith("U") || statusCode === "AA") {
|
|
13928
|
+
conflicted.push(filename);
|
|
13929
|
+
} else if (statusCode[0] !== " ") {
|
|
13930
|
+
staged.push(filename);
|
|
13931
|
+
}
|
|
13932
|
+
if (statusCode[1] !== " " && statusCode[1] !== "?") {
|
|
13933
|
+
unstaged.push(filename);
|
|
13934
|
+
}
|
|
13935
|
+
}
|
|
13936
|
+
const current = await execGit(directory, ["branch", "--show-current"]).catch(() => "unknown");
|
|
13937
|
+
let result = "\u{1F33F} **Branch**: `" + current + "`\n\n";
|
|
13938
|
+
if (staged.length > 0) {
|
|
13939
|
+
result += "\u2705 **Staged** (" + staged.length + ")\n" + staged.map((f) => " + " + f).join("\n") + "\n\n";
|
|
13940
|
+
}
|
|
13941
|
+
if (unstaged.length > 0) {
|
|
13942
|
+
result += "\u{1F4DD} **Modified** (" + unstaged.length + ")\n" + unstaged.map((f) => " ~ " + f).join("\n") + "\n\n";
|
|
13943
|
+
}
|
|
13944
|
+
if (untracked.length > 0) {
|
|
13945
|
+
result += "\u2795 **Untracked** (" + untracked.length + ")\n" + untracked.slice(0, 10).map((f) => " ? " + f).join("\n") + (untracked.length > 10 ? "\n ... and " + (untracked.length - 10) + " more" : "") + "\n\n";
|
|
13946
|
+
}
|
|
13947
|
+
if (conflicted.length > 0) {
|
|
13948
|
+
result += "\u26A0\uFE0F **Conflicts** (" + conflicted.length + ")\n" + conflicted.map((f) => " ! " + f).join("\n") + "\n\n";
|
|
13949
|
+
}
|
|
13950
|
+
return result.trim();
|
|
13951
|
+
}
|
|
13952
|
+
async function getDiff(directory, baseBranch) {
|
|
13953
|
+
const current = await execGit(directory, ["branch", "--show-current"]).catch(() => "HEAD");
|
|
13954
|
+
const base = baseBranch || "main";
|
|
13955
|
+
const diff = await execGit(directory, ["diff", base + "..." + current, "--stat"]);
|
|
13956
|
+
if (!diff.trim()) {
|
|
13957
|
+
return "\u2705 No differences between `" + current + "` and `" + base + "`";
|
|
13958
|
+
}
|
|
13959
|
+
const files = await execGit(directory, ["diff", base + "..." + current, "--name-only"]);
|
|
13960
|
+
const fileList = files.split("\n").filter((f) => f.trim());
|
|
13961
|
+
return "\u{1F4CA} **Diff**: `" + current + "` vs `" + base + "`\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n" + diff + "\n\n\u{1F4C1} **Changed Files** (" + fileList.length + "):\n" + fileList.map((f, i) => " " + (i + 1) + ". " + f).join("\n");
|
|
13962
|
+
}
|
|
13963
|
+
async function getRecentCommits(directory, count = 5) {
|
|
13964
|
+
const log2 = await execGit(directory, [
|
|
13965
|
+
"log",
|
|
13966
|
+
"-" + count,
|
|
13967
|
+
"--pretty=format:%H|%an|%ad|%s",
|
|
13968
|
+
"--date=short",
|
|
13969
|
+
"--name-only"
|
|
13970
|
+
]);
|
|
13971
|
+
const blocks = log2.split("\n\n").filter((b) => b.trim());
|
|
13972
|
+
const commits = [];
|
|
13973
|
+
for (const block of blocks) {
|
|
13974
|
+
const lines = block.split("\n").filter((l) => l.trim());
|
|
13975
|
+
if (lines.length < 2) continue;
|
|
13976
|
+
const [hash2, author, date5, message] = lines[0].split("|");
|
|
13977
|
+
const files = lines.slice(1);
|
|
13978
|
+
commits.push({
|
|
13979
|
+
hash: hash2.substring(0, 7),
|
|
13980
|
+
author,
|
|
13981
|
+
date: date5,
|
|
13982
|
+
message,
|
|
13983
|
+
files
|
|
13984
|
+
});
|
|
13985
|
+
}
|
|
13986
|
+
let result = "\u{1F4DC} **Recent Commits** (last " + commits.length + ")\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n";
|
|
13987
|
+
for (const commit of commits) {
|
|
13988
|
+
result += "\n\u{1F539} `" + commit.hash + "` - " + commit.message + "\n";
|
|
13989
|
+
result += " \u{1F464} " + commit.author + " | \u{1F4C5} " + commit.date + "\n";
|
|
13990
|
+
result += " \u{1F4C1} " + commit.files.length + " file" + (commit.files.length > 1 ? "s" : "") + "\n";
|
|
13991
|
+
}
|
|
13992
|
+
return result;
|
|
13993
|
+
}
|
|
13994
|
+
async function getAllInfo(directory) {
|
|
13995
|
+
const current = await getCurrentBranch(directory);
|
|
13996
|
+
const status = await getGitStatus(directory);
|
|
13997
|
+
const recent = await getRecentCommits(directory, 3);
|
|
13998
|
+
return "\u{1F50D} **Full Git Context**\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n" + current + "\n\n" + status + "\n\n" + recent;
|
|
13999
|
+
}
|
|
14000
|
+
|
|
13801
14001
|
// src/core/background.ts
|
|
13802
14002
|
import { spawn as spawn2 } from "child_process";
|
|
13803
14003
|
import { randomBytes } from "crypto";
|
|
@@ -15474,6 +15674,476 @@ function createAsyncAgentTools(manager, client) {
|
|
|
15474
15674
|
};
|
|
15475
15675
|
}
|
|
15476
15676
|
|
|
15677
|
+
// src/core/batch-processor.ts
|
|
15678
|
+
var SmartBatchProcessor = class {
|
|
15679
|
+
parallelAgentManager;
|
|
15680
|
+
tasks = /* @__PURE__ */ new Map();
|
|
15681
|
+
constructor(parallelAgentManager2) {
|
|
15682
|
+
this.parallelAgentManager = parallelAgentManager2;
|
|
15683
|
+
}
|
|
15684
|
+
/**
|
|
15685
|
+
* Process a batch of tasks with smart validation
|
|
15686
|
+
*/
|
|
15687
|
+
async processBatch(tasks, options = {
|
|
15688
|
+
concurrency: 3,
|
|
15689
|
+
maxRetries: 2,
|
|
15690
|
+
validateAfterEach: false,
|
|
15691
|
+
continueOnError: true
|
|
15692
|
+
}) {
|
|
15693
|
+
const startTime = Date.now();
|
|
15694
|
+
for (const task of tasks) {
|
|
15695
|
+
this.tasks.set(task.id, {
|
|
15696
|
+
...task,
|
|
15697
|
+
status: "pending",
|
|
15698
|
+
attempts: 0
|
|
15699
|
+
});
|
|
15700
|
+
}
|
|
15701
|
+
console.log(`
|
|
15702
|
+
\u{1F4E6} [Smart Batch] Starting ${tasks.length} tasks with concurrency ${options.concurrency}`);
|
|
15703
|
+
console.log(` Strategy: ${options.validateAfterEach ? "Validate each" : "Centralized validation"}`);
|
|
15704
|
+
await this.executePhase(tasks, options, "initial");
|
|
15705
|
+
const failedTasks = Array.from(this.tasks.values()).filter((t) => t.status === "failed" || t.status === "pending");
|
|
15706
|
+
if (failedTasks.length === 0) {
|
|
15707
|
+
console.log(`
|
|
15708
|
+
\u2705 [Smart Batch] All ${tasks.length} tasks succeeded!`);
|
|
15709
|
+
return this.buildResult(startTime, tasks);
|
|
15710
|
+
}
|
|
15711
|
+
console.log(`
|
|
15712
|
+
\u{1F50D} [Smart Batch] Validation complete: ${failedTasks.length}/${tasks.length} tasks need retry`);
|
|
15713
|
+
let retryCount = 0;
|
|
15714
|
+
while (failedTasks.length > 0 && retryCount < options.maxRetries) {
|
|
15715
|
+
retryCount++;
|
|
15716
|
+
console.log(`
|
|
15717
|
+
\u{1F504} [Smart Batch] Retry round ${retryCount}/${options.maxRetries} for ${failedTasks.length} tasks`);
|
|
15718
|
+
await this.executePhase(failedTasks, options, `retry-${retryCount}`);
|
|
15719
|
+
const stillFailed = Array.from(this.tasks.values()).filter((t) => t.status === "failed");
|
|
15720
|
+
if (stillFailed.length === failedTasks.length) {
|
|
15721
|
+
console.log(`\u26A0\uFE0F [Smart Batch] No progress in retry round ${retryCount}, stopping`);
|
|
15722
|
+
break;
|
|
15723
|
+
}
|
|
15724
|
+
}
|
|
15725
|
+
return this.buildResult(startTime, tasks);
|
|
15726
|
+
}
|
|
15727
|
+
/**
|
|
15728
|
+
* Execute a phase with concurrency control
|
|
15729
|
+
*/
|
|
15730
|
+
async executePhase(tasks, options, phase) {
|
|
15731
|
+
for (const task of tasks) {
|
|
15732
|
+
this.parallelAgentManager.setConcurrencyLimit(task.agent, options.concurrency);
|
|
15733
|
+
}
|
|
15734
|
+
const batches = [];
|
|
15735
|
+
for (let i = 0; i < tasks.length; i += options.concurrency) {
|
|
15736
|
+
batches.push(tasks.slice(i, i + options.concurrency));
|
|
15737
|
+
}
|
|
15738
|
+
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
|
|
15739
|
+
const batch = batches[batchIndex];
|
|
15740
|
+
console.log(` [${phase}] Processing batch ${batchIndex + 1}/${batches.length} (${batch.length} tasks)`);
|
|
15741
|
+
const batchPromises = batch.map((task) => this.executeTask(task, options));
|
|
15742
|
+
await Promise.all(batchPromises);
|
|
15743
|
+
if (!options.validateAfterEach) {
|
|
15744
|
+
continue;
|
|
15745
|
+
}
|
|
15746
|
+
const failedInBatch = batch.filter((t) => t.status === "failed");
|
|
15747
|
+
if (failedInBatch.length > 0) {
|
|
15748
|
+
console.log(` [${phase}] ${failedInBatch.length} failed in batch, immediate retry`);
|
|
15749
|
+
const retryPromises = failedInBatch.map((t) => this.executeTask(t, options));
|
|
15750
|
+
await Promise.all(retryPromises);
|
|
15751
|
+
}
|
|
15752
|
+
}
|
|
15753
|
+
}
|
|
15754
|
+
/**
|
|
15755
|
+
* Execute a single task
|
|
15756
|
+
*/
|
|
15757
|
+
async executeTask(task, options) {
|
|
15758
|
+
const currentTask = this.tasks.get(task.id);
|
|
15759
|
+
if (!options.continueOnError && currentTask.status === "failed") {
|
|
15760
|
+
return;
|
|
15761
|
+
}
|
|
15762
|
+
try {
|
|
15763
|
+
currentTask.attempts++;
|
|
15764
|
+
currentTask.status = "pending";
|
|
15765
|
+
const launched = await this.parallelAgentManager.launch({
|
|
15766
|
+
agent: task.agent,
|
|
15767
|
+
description: task.description,
|
|
15768
|
+
prompt: task.prompt,
|
|
15769
|
+
parentSessionID: ""
|
|
15770
|
+
// Would need actual parent ID
|
|
15771
|
+
});
|
|
15772
|
+
const maxWaitTime = 5 * 60 * 1e3;
|
|
15773
|
+
const startTime = Date.now();
|
|
15774
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
15775
|
+
const taskData = this.parallelAgentManager.getTask(launched.id);
|
|
15776
|
+
if (!taskData) break;
|
|
15777
|
+
if (taskData.status === "completed") {
|
|
15778
|
+
currentTask.status = "success";
|
|
15779
|
+
console.log(` \u2705 [${task.id}] Success on attempt ${currentTask.attempts}`);
|
|
15780
|
+
return;
|
|
15781
|
+
}
|
|
15782
|
+
if (taskData.status === "error" || taskData.status === "timeout") {
|
|
15783
|
+
currentTask.status = "failed";
|
|
15784
|
+
currentTask.error = taskData.error || "Unknown error";
|
|
15785
|
+
console.log(` \u274C [${task.id}] Failed: ${currentTask.error}`);
|
|
15786
|
+
return;
|
|
15787
|
+
}
|
|
15788
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
15789
|
+
}
|
|
15790
|
+
currentTask.status = "failed";
|
|
15791
|
+
currentTask.error = "Timeout";
|
|
15792
|
+
} catch (error45) {
|
|
15793
|
+
currentTask.status = "failed";
|
|
15794
|
+
currentTask.error = error45 instanceof Error ? error45.message : String(error45);
|
|
15795
|
+
console.log(` \u274C [${task.id}] Exception: ${currentTask.error}`);
|
|
15796
|
+
}
|
|
15797
|
+
}
|
|
15798
|
+
/**
|
|
15799
|
+
* Build result summary
|
|
15800
|
+
*/
|
|
15801
|
+
buildResult(startTime, tasks) {
|
|
15802
|
+
const allTasks = Array.from(this.tasks.values());
|
|
15803
|
+
const successCount = allTasks.filter((t) => t.status === "success").length;
|
|
15804
|
+
const failedCount = allTasks.filter((t) => t.status === "failed").length;
|
|
15805
|
+
const retriedCount = allTasks.filter((t) => t.attempts > 1).length;
|
|
15806
|
+
return {
|
|
15807
|
+
total: tasks.length,
|
|
15808
|
+
success: successCount,
|
|
15809
|
+
failed: failedCount,
|
|
15810
|
+
retried: retriedCount,
|
|
15811
|
+
duration: Date.now() - startTime,
|
|
15812
|
+
tasks: allTasks
|
|
15813
|
+
};
|
|
15814
|
+
}
|
|
15815
|
+
/**
|
|
15816
|
+
* Export failed tasks for manual review
|
|
15817
|
+
*/
|
|
15818
|
+
exportFailedTasks() {
|
|
15819
|
+
const failedTasks = Array.from(this.tasks.values()).filter((t) => t.status === "failed");
|
|
15820
|
+
if (failedTasks.length === 0) {
|
|
15821
|
+
return "\u2705 No failed tasks to export.";
|
|
15822
|
+
}
|
|
15823
|
+
const output = failedTasks.map((task) => {
|
|
15824
|
+
return `
|
|
15825
|
+
---
|
|
15826
|
+
Task ID: ${task.id}
|
|
15827
|
+
Agent: ${task.agent}
|
|
15828
|
+
Attempts: ${task.attempts}
|
|
15829
|
+
Error: ${task.error}
|
|
15830
|
+
Description: ${task.description}
|
|
15831
|
+
---
|
|
15832
|
+
`.trim();
|
|
15833
|
+
}).join("\n");
|
|
15834
|
+
return `\u274C ${failedTasks.length} failed tasks:
|
|
15835
|
+
${output}`;
|
|
15836
|
+
}
|
|
15837
|
+
/**
|
|
15838
|
+
* Clear all tasks
|
|
15839
|
+
*/
|
|
15840
|
+
clear() {
|
|
15841
|
+
this.tasks.clear();
|
|
15842
|
+
}
|
|
15843
|
+
};
|
|
15844
|
+
|
|
15845
|
+
// src/tools/batch.ts
|
|
15846
|
+
var createProcessBatchTool = (parallelAgentManager2, client) => tool({
|
|
15847
|
+
description: `Execute a batch of tasks with intelligent validation and retry.
|
|
15848
|
+
|
|
15849
|
+
<strategy>
|
|
15850
|
+
1. Execute all tasks in parallel (respecting concurrency limits)
|
|
15851
|
+
2. Centralized validation: Identify ALL failed tasks at once
|
|
15852
|
+
3. Retry ONLY failed tasks (not everything)
|
|
15853
|
+
</strategy>
|
|
15854
|
+
|
|
15855
|
+
<benefits>
|
|
15856
|
+
- Faster than naive retry: Failed tasks batch-identified
|
|
15857
|
+
- More efficient: No redundant work on successful tasks
|
|
15858
|
+
- Controlled: Concurrency limits prevent API overload
|
|
15859
|
+
</benefits>
|
|
15860
|
+
|
|
15861
|
+
<examples>
|
|
15862
|
+
process_batch({
|
|
15863
|
+
concurrency: 10,
|
|
15864
|
+
tasks: [
|
|
15865
|
+
{ id: "task1", agent: "builder", description: "Test A", prompt: "..." },
|
|
15866
|
+
{ id: "task2", agent: "inspector", description: "Test B", prompt: "..." }
|
|
15867
|
+
]
|
|
15868
|
+
})
|
|
15869
|
+
</examples>`,
|
|
15870
|
+
args: {
|
|
15871
|
+
concurrency: tool.schema.string().describe("Concurrency limit (default: 3, max: 10)"),
|
|
15872
|
+
maxRetries: tool.schema.string().optional().describe("Maximum retry rounds (default: 2)"),
|
|
15873
|
+
validateAfterEach: tool.schema.boolean().optional().describe("Validate after each task (default: false = centralized validation)"),
|
|
15874
|
+
tasks: tool.schema.string().describe("Array of task objects in JSON format")
|
|
15875
|
+
},
|
|
15876
|
+
async execute(args) {
|
|
15877
|
+
const { concurrency, maxRetries = "2", validateAfterEach = false, tasks } = args;
|
|
15878
|
+
let taskList;
|
|
15879
|
+
try {
|
|
15880
|
+
taskList = JSON.parse(tasks);
|
|
15881
|
+
} catch {
|
|
15882
|
+
return "\u274C Invalid tasks JSON. Must be valid array.";
|
|
15883
|
+
}
|
|
15884
|
+
if (!Array.isArray(taskList)) {
|
|
15885
|
+
return "\u274C tasks must be an array of task objects.";
|
|
15886
|
+
}
|
|
15887
|
+
for (const task of taskList) {
|
|
15888
|
+
if (!task.id || !task.agent || !task.description || !task.prompt) {
|
|
15889
|
+
return `\u274C Task missing required fields (id, agent, description, prompt)`;
|
|
15890
|
+
}
|
|
15891
|
+
}
|
|
15892
|
+
const numConcurrency = parseInt(concurrency, 10);
|
|
15893
|
+
const numRetries = parseInt(maxRetries, 10);
|
|
15894
|
+
if (isNaN(numConcurrency) || numConcurrency < 1 || numConcurrency > 10) {
|
|
15895
|
+
return "\u274C Invalid concurrency. Must be 1-10.";
|
|
15896
|
+
}
|
|
15897
|
+
if (isNaN(numRetries) || numRetries < 0 || numRetries > 5) {
|
|
15898
|
+
return "\u274C Invalid maxRetries. Must be 0-5.";
|
|
15899
|
+
}
|
|
15900
|
+
const processor = new SmartBatchProcessor(parallelAgentManager2);
|
|
15901
|
+
const result = await processor.processBatch(taskList, {
|
|
15902
|
+
concurrency: numConcurrency,
|
|
15903
|
+
maxRetries: numRetries,
|
|
15904
|
+
validateAfterEach,
|
|
15905
|
+
continueOnError: true
|
|
15906
|
+
});
|
|
15907
|
+
const durationSecs = Math.floor(result.duration / 1e3);
|
|
15908
|
+
const successRate = (result.success / result.total * 100).toFixed(1);
|
|
15909
|
+
let output = `\u2705 **Batch Processing Complete**
|
|
15910
|
+
|
|
15911
|
+
| Metric | Value |
|
|
15912
|
+
|---------|--------|
|
|
15913
|
+
| **Total Tasks** | ${result.total} |
|
|
15914
|
+
| **Successful** | ${result.success} (${successRate}%) |
|
|
15915
|
+
| **Failed** | ${result.failed} |
|
|
15916
|
+
| **Retried** | ${result.retried} |
|
|
15917
|
+
| **Duration** | ${durationSecs}s |
|
|
15918
|
+
|
|
15919
|
+
`;
|
|
15920
|
+
if (result.failed > 0) {
|
|
15921
|
+
output += `\u26A0\uFE0F **Failed Tasks**
|
|
15922
|
+
|
|
15923
|
+
Use \`export_failed_tasks()\` to review failed tasks and manually fix issues.
|
|
15924
|
+
|
|
15925
|
+
---
|
|
15926
|
+
|
|
15927
|
+
Failed Task IDs:
|
|
15928
|
+
${result.tasks.filter((t) => t.status === "failed").map((t) => ` - ${t.id}`).join("\n")}
|
|
15929
|
+
---
|
|
15930
|
+
`;
|
|
15931
|
+
}
|
|
15932
|
+
return output;
|
|
15933
|
+
}
|
|
15934
|
+
});
|
|
15935
|
+
var createExportFailedTasksTool = (parallelAgentManager2) => tool({
|
|
15936
|
+
description: `Export failed tasks from the last batch for manual review.`,
|
|
15937
|
+
args: {},
|
|
15938
|
+
async execute() {
|
|
15939
|
+
const processor = new SmartBatchProcessor(parallelAgentManager2);
|
|
15940
|
+
return processor.exportFailedTasks();
|
|
15941
|
+
}
|
|
15942
|
+
});
|
|
15943
|
+
var createCompareStrategiesTool = (parallelAgentManager2) => tool({
|
|
15944
|
+
description: `Compare naive retry vs smart batch validation performance.
|
|
15945
|
+
|
|
15946
|
+
<comparison>
|
|
15947
|
+
**Naive Strategy** (current):
|
|
15948
|
+
- Concurrency: 3 fixed
|
|
15949
|
+
- Retry: Per-task immediate retry
|
|
15950
|
+
- Issue: Slow for large batches, redundant work
|
|
15951
|
+
|
|
15952
|
+
**Smart Batch Strategy** (new):
|
|
15953
|
+
- Concurrency: Configurable (up to 10)
|
|
15954
|
+
- Validation: Centralized, batch-identify failures
|
|
15955
|
+
- Retry: Only failed tasks
|
|
15956
|
+
- Benefit: Faster, less redundant work
|
|
15957
|
+
</comparison>`,
|
|
15958
|
+
args: {
|
|
15959
|
+
taskCount: tool.schema.string().describe("Number of simulated tasks (default: 100)"),
|
|
15960
|
+
concurrency1: tool.schema.string().optional().describe("Strategy 1 concurrency (default: 3)"),
|
|
15961
|
+
concurrency2: tool.schema.string().optional().describe("Strategy 2 concurrency (default: 10)")
|
|
15962
|
+
},
|
|
15963
|
+
async execute(args) {
|
|
15964
|
+
const taskCount = parseInt(args.taskCount || "100", 10);
|
|
15965
|
+
const concurrency1 = parseInt(args.concurrency1 || "3", 10);
|
|
15966
|
+
const concurrency2 = parseInt(args.concurrency2 || "10", 10);
|
|
15967
|
+
const naiveBatches = Math.ceil(taskCount / concurrency1);
|
|
15968
|
+
const naiveTime = naiveBatches * 60;
|
|
15969
|
+
const smartBatches = Math.ceil(taskCount / concurrency2);
|
|
15970
|
+
const smartTime = smartBatches * 60 + 30;
|
|
15971
|
+
const timeDiff = naiveTime - smartTime;
|
|
15972
|
+
const improvement = (timeDiff / naiveTime * 100).toFixed(1);
|
|
15973
|
+
return `\u{1F4CA} **Strategy Comparison for ${taskCount} tasks**
|
|
15974
|
+
|
|
15975
|
+
**Strategy 1: Naive (Current)**
|
|
15976
|
+
| Metric | Value |
|
|
15977
|
+
|---------|--------|
|
|
15978
|
+
| Concurrency | ${concurrency1} |
|
|
15979
|
+
| Batches | ${naiveBatches} |
|
|
15980
|
+
| Est. Time | ${naiveTime}s |
|
|
15981
|
+
|
|
15982
|
+
**Strategy 2: Smart Batch (Proposed)**
|
|
15983
|
+
| Metric | Value |
|
|
15984
|
+
|---------|--------|
|
|
15985
|
+
| Concurrency | ${concurrency2} |
|
|
15986
|
+
| Batches | ${smartBatches} |
|
|
15987
|
+
| Est. Time | ${smartTime}s |
|
|
15988
|
+
|
|
15989
|
+
**Summary**
|
|
15990
|
+
| Metric | Value |
|
|
15991
|
+
|---------|--------|
|
|
15992
|
+
| Time Saved | ${timeDiff}s (${improvement}%) |
|
|
15993
|
+
| Batches Saved | ${naiveBatches - smartBatches} |
|
|
15994
|
+
|
|
15995
|
+
Recommendation: Use **Smart Batch** with concurrency ${concurrency2} for ${timeDiff}s improvement.`;
|
|
15996
|
+
}
|
|
15997
|
+
});
|
|
15998
|
+
function createBatchTools(parallelAgentManager2, client) {
|
|
15999
|
+
return {
|
|
16000
|
+
process_batch: createProcessBatchTool(parallelAgentManager2, client),
|
|
16001
|
+
export_failed_tasks: createExportFailedTasksTool(parallelAgentManager2),
|
|
16002
|
+
compare_strategies: createCompareStrategiesTool(parallelAgentManager2)
|
|
16003
|
+
};
|
|
16004
|
+
}
|
|
16005
|
+
|
|
16006
|
+
// src/tools/config.ts
|
|
16007
|
+
var createShowConfigTool = () => tool({
|
|
16008
|
+
description: `Display current OpenCode Orchestrator configuration.
|
|
16009
|
+
|
|
16010
|
+
Shows all dynamic settings including timeouts, concurrency limits, and debug flags.`,
|
|
16011
|
+
args: {},
|
|
16012
|
+
async execute() {
|
|
16013
|
+
configManager.exportConfigs();
|
|
16014
|
+
return "";
|
|
16015
|
+
}
|
|
16016
|
+
});
|
|
16017
|
+
var createSetConcurrencyTool = (client) => tool({
|
|
16018
|
+
description: `Update concurrency limit for a specific agent type.
|
|
16019
|
+
|
|
16020
|
+
<examples>
|
|
16021
|
+
set_concurrency({ agent: "builder", limit: 5 })
|
|
16022
|
+
set_concurrency({ agent: "inspector", limit: 2 })
|
|
16023
|
+
</examples>
|
|
16024
|
+
|
|
16025
|
+
<notes>
|
|
16026
|
+
- Changes take effect immediately
|
|
16027
|
+
- Queued tasks will start when slots become available
|
|
16028
|
+
- Limit cannot exceed global max (10)
|
|
16029
|
+
</notes>`,
|
|
16030
|
+
args: {
|
|
16031
|
+
agent: tool.schema.string().describe('Agent type (e.g., "builder", "inspector", "architect")'),
|
|
16032
|
+
limit: tool.schema.string().describe("New concurrency limit (number)")
|
|
16033
|
+
},
|
|
16034
|
+
async execute(args) {
|
|
16035
|
+
const { agent, limit } = args;
|
|
16036
|
+
const numLimit = parseInt(limit, 10);
|
|
16037
|
+
if (isNaN(numLimit) || numLimit < 1) {
|
|
16038
|
+
return `\u274C Invalid limit: "${limit}". Must be a number >= 1.`;
|
|
16039
|
+
}
|
|
16040
|
+
const maxLimit = configManager.getParallelAgentConfig().maxConcurrency;
|
|
16041
|
+
if (numLimit > maxLimit) {
|
|
16042
|
+
return `\u274C Limit ${numLimit} exceeds global max ${maxLimit}. Using ${maxLimit}.`;
|
|
16043
|
+
}
|
|
16044
|
+
configManager.updateParallelAgentConfig({
|
|
16045
|
+
defaultConcurrency: numLimit
|
|
16046
|
+
});
|
|
16047
|
+
return `\u2705 **Concurrency Updated**
|
|
16048
|
+
|
|
16049
|
+
| Property | Value |
|
|
16050
|
+
|----------|-------|
|
|
16051
|
+
| **Agent** | ${agent} |
|
|
16052
|
+
| **New Limit** | ${numLimit} parallel tasks |
|
|
16053
|
+
|
|
16054
|
+
Changes take effect immediately. New tasks will respect to the new limit.`;
|
|
16055
|
+
}
|
|
16056
|
+
});
|
|
16057
|
+
var createSetTimeoutTool = () => tool({
|
|
16058
|
+
description: `Update task timeout duration.
|
|
16059
|
+
|
|
16060
|
+
<examples>
|
|
16061
|
+
set_timeout({ taskTtlMinutes: 45 })
|
|
16062
|
+
set_timeout({ cleanupDelayMinutes: 2 })
|
|
16063
|
+
</examples>`,
|
|
16064
|
+
args: {
|
|
16065
|
+
taskTtlMinutes: tool.schema.string().optional().describe("Task timeout in minutes (default: 30)"),
|
|
16066
|
+
cleanupDelayMinutes: tool.schema.string().optional().describe("Cleanup delay after completion in minutes (default: 5)")
|
|
16067
|
+
},
|
|
16068
|
+
async execute(args) {
|
|
16069
|
+
const { taskTtlMinutes, cleanupDelayMinutes } = args;
|
|
16070
|
+
const updates = {};
|
|
16071
|
+
if (taskTtlMinutes) {
|
|
16072
|
+
const ttl = parseInt(taskTtlMinutes, 10);
|
|
16073
|
+
if (isNaN(ttl) || ttl < 1) {
|
|
16074
|
+
return `\u274C Invalid taskTtlMinutes: "${taskTtlMinutes}". Must be number >= 1.`;
|
|
16075
|
+
}
|
|
16076
|
+
updates.taskTtlMs = ttl * 60 * 1e3;
|
|
16077
|
+
}
|
|
16078
|
+
if (cleanupDelayMinutes) {
|
|
16079
|
+
const delay = parseInt(cleanupDelayMinutes, 10);
|
|
16080
|
+
if (isNaN(delay) || delay < 0) {
|
|
16081
|
+
return `\u274C Invalid cleanupDelayMinutes: "${cleanupDelayMinutes}". Must be number >= 0.`;
|
|
16082
|
+
}
|
|
16083
|
+
updates.cleanupDelayMs = delay * 60 * 1e3;
|
|
16084
|
+
}
|
|
16085
|
+
if (Object.keys(updates).length === 0) {
|
|
16086
|
+
return "\u26A0\uFE0F No changes specified. Provide at least one parameter.";
|
|
16087
|
+
}
|
|
16088
|
+
configManager.updateParallelAgentConfig(updates);
|
|
16089
|
+
let output = "\u2705 **Timeout Configuration Updated**\n\n";
|
|
16090
|
+
if (updates.taskTtlMs) {
|
|
16091
|
+
output += `| Task TTL | ${updates.taskTtlMs / 60 / 1e3} minutes |
|
|
16092
|
+
`;
|
|
16093
|
+
}
|
|
16094
|
+
if (updates.cleanupDelayMs) {
|
|
16095
|
+
output += `| Cleanup Delay | ${updates.cleanupDelayMs / 60 / 1e3} minutes |
|
|
16096
|
+
`;
|
|
16097
|
+
}
|
|
16098
|
+
output += "\nChanges take effect immediately.";
|
|
16099
|
+
return output;
|
|
16100
|
+
}
|
|
16101
|
+
});
|
|
16102
|
+
var createSetDebugTool = () => tool({
|
|
16103
|
+
description: `Enable or disable debug logging.
|
|
16104
|
+
|
|
16105
|
+
<examples>
|
|
16106
|
+
set_debug({ component: "parallel_agent", enable: true })
|
|
16107
|
+
set_debug({ component: "background_task", enable: false })
|
|
16108
|
+
</examples>`,
|
|
16109
|
+
args: {
|
|
16110
|
+
component: tool.schema.string().describe('Component to debug: "parallel_agent", "background_task", or "all"'),
|
|
16111
|
+
enable: tool.schema.boolean().describe("Enable (true) or disable (false) debug logs")
|
|
16112
|
+
},
|
|
16113
|
+
async execute(args) {
|
|
16114
|
+
const { component, enable } = args;
|
|
16115
|
+
const enableValue = enable ? "true" : "false";
|
|
16116
|
+
if (component === "parallel_agent" || component === "all") {
|
|
16117
|
+
process.env.OPENCODE_DEBUG_PARALLEL = enableValue;
|
|
16118
|
+
configManager.updateParallelAgentConfig({
|
|
16119
|
+
enableDebug: enable
|
|
16120
|
+
});
|
|
16121
|
+
}
|
|
16122
|
+
if (component === "background_task" || component === "all") {
|
|
16123
|
+
process.env.OPENCODE_DEBUG_BACKGROUND = enableValue;
|
|
16124
|
+
configManager.updateBackgroundTaskConfig({
|
|
16125
|
+
enableDebug: enable
|
|
16126
|
+
});
|
|
16127
|
+
}
|
|
16128
|
+
return `\u2705 **Debug Logging ${enable ? "Enabled" : "Disabled"}**
|
|
16129
|
+
|
|
16130
|
+
| Component | Debug Status |
|
|
16131
|
+
|-----------|--------------|
|
|
16132
|
+
| ${component === "all" ? "parallel_agent" : component} | ${enable ? "\u{1F527} Enabled" : "\u{1F507} Disabled"} |
|
|
16133
|
+
${component === "all" ? "| background_task | \u{1F527} Enabled |" : ""}
|
|
16134
|
+
|
|
16135
|
+
Changes take effect immediately.`;
|
|
16136
|
+
}
|
|
16137
|
+
});
|
|
16138
|
+
function createConfigTools(client) {
|
|
16139
|
+
return {
|
|
16140
|
+
show_config: createShowConfigTool(),
|
|
16141
|
+
set_concurrency: createSetConcurrencyTool(client),
|
|
16142
|
+
set_timeout: createSetTimeoutTool(),
|
|
16143
|
+
set_debug: createSetDebugTool()
|
|
16144
|
+
};
|
|
16145
|
+
}
|
|
16146
|
+
|
|
15477
16147
|
// src/utils/common.ts
|
|
15478
16148
|
function detectSlashCommand(text) {
|
|
15479
16149
|
const match = text.trim().match(/^\/([a-zA-Z0-9_-]+)(?:\s+(.*))?$/);
|
|
@@ -15498,7 +16168,7 @@ function formatElapsedTime(startMs, endMs = Date.now()) {
|
|
|
15498
16168
|
}
|
|
15499
16169
|
|
|
15500
16170
|
// src/index.ts
|
|
15501
|
-
var PLUGIN_VERSION = "0.4
|
|
16171
|
+
var PLUGIN_VERSION = "0.2.4";
|
|
15502
16172
|
var DEFAULT_MAX_STEPS = 500;
|
|
15503
16173
|
var TASK_COMMAND_MAX_STEPS = 1e3;
|
|
15504
16174
|
var AGENT_EMOJI2 = {
|
|
@@ -15508,12 +16178,30 @@ var AGENT_EMOJI2 = {
|
|
|
15508
16178
|
[AGENT_NAMES.RECORDER]: "\u{1F4BE}",
|
|
15509
16179
|
[AGENT_NAMES.COMMANDER]: "\u{1F3AF}"
|
|
15510
16180
|
};
|
|
16181
|
+
var CONTINUE_INSTRUCTION = `<auto_continue>
|
|
16182
|
+
<status>Mission not complete. Keep executing.</status>
|
|
16183
|
+
|
|
16184
|
+
<rules>
|
|
16185
|
+
1. DO NOT stop - mission is incomplete
|
|
16186
|
+
2. DO NOT wait for user input
|
|
16187
|
+
3. If previous action failed, try different approach
|
|
16188
|
+
4. If agent returned nothing, proceed to next step
|
|
16189
|
+
</rules>
|
|
16190
|
+
|
|
16191
|
+
<next_step>
|
|
16192
|
+
What is the current state?
|
|
16193
|
+
What is the next action?
|
|
16194
|
+
Execute it NOW.
|
|
16195
|
+
</next_step>
|
|
16196
|
+
</auto_continue>`;
|
|
15511
16197
|
var OrchestratorPlugin = async (input) => {
|
|
15512
16198
|
const { directory, client } = input;
|
|
15513
16199
|
console.log(`[orchestrator] v${PLUGIN_VERSION} loaded`);
|
|
15514
16200
|
const sessions = /* @__PURE__ */ new Map();
|
|
15515
16201
|
const parallelAgentManager2 = ParallelAgentManager.getInstance(client, directory);
|
|
15516
16202
|
const asyncAgentTools = createAsyncAgentTools(parallelAgentManager2, client);
|
|
16203
|
+
const batchTools = createBatchTools(parallelAgentManager2, client);
|
|
16204
|
+
const configTools = createConfigTools(client);
|
|
15517
16205
|
return {
|
|
15518
16206
|
// -----------------------------------------------------------------
|
|
15519
16207
|
// Tools we expose to the LLM
|
|
@@ -15531,13 +16219,13 @@ var OrchestratorPlugin = async (input) => {
|
|
|
15531
16219
|
list_background: listBackgroundTool,
|
|
15532
16220
|
kill_background: killBackgroundTool,
|
|
15533
16221
|
// Async agent tools - spawn agents in parallel sessions
|
|
15534
|
-
...asyncAgentTools
|
|
16222
|
+
...asyncAgentTools,
|
|
15535
16223
|
// Git tools - branch info and status
|
|
15536
|
-
|
|
16224
|
+
git_branch: gitBranchTool(directory),
|
|
15537
16225
|
// Smart batch tools - centralized validation and retry
|
|
15538
|
-
|
|
16226
|
+
...batchTools,
|
|
15539
16227
|
// Configuration tools - dynamic runtime settings
|
|
15540
|
-
|
|
16228
|
+
...configTools
|
|
15541
16229
|
},
|
|
15542
16230
|
// -----------------------------------------------------------------
|
|
15543
16231
|
// Config hook - registers our commands and agents with OpenCode
|
|
@@ -15748,17 +16436,107 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
15748
16436
|
\u23F1\uFE0F [${currentTime}] Step ${session.step}/${session.maxSteps} | This step: ${stepDuration} | Total: ${totalElapsed}`;
|
|
15749
16437
|
},
|
|
15750
16438
|
// -----------------------------------------------------------------
|
|
15751
|
-
//
|
|
15752
|
-
//
|
|
15753
|
-
//
|
|
15754
|
-
// reimplemented using supported hooks like tool.execute.after
|
|
15755
|
-
// or the event hook.
|
|
16439
|
+
// assistant.done hook - runs when the LLM finishes responding
|
|
16440
|
+
// This is the heart of the "relentless loop" - we keep pushing it
|
|
16441
|
+
// to continue until we see MISSION COMPLETE or hit the limit
|
|
15756
16442
|
// -----------------------------------------------------------------
|
|
16443
|
+
"assistant.done": async (assistantInput, assistantOutput) => {
|
|
16444
|
+
const sessionID = assistantInput.sessionID;
|
|
16445
|
+
const session = sessions.get(sessionID);
|
|
16446
|
+
if (!session?.active) return;
|
|
16447
|
+
const parts = assistantOutput.parts;
|
|
16448
|
+
const textContent = parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text || "").join("\n") || "";
|
|
16449
|
+
const stateSession = state.sessions.get(sessionID);
|
|
16450
|
+
const sanityResult = checkOutputSanity(textContent);
|
|
16451
|
+
if (!sanityResult.isHealthy && stateSession) {
|
|
16452
|
+
stateSession.anomalyCount = (stateSession.anomalyCount || 0) + 1;
|
|
16453
|
+
session.step++;
|
|
16454
|
+
session.timestamp = Date.now();
|
|
16455
|
+
const recoveryText = stateSession.anomalyCount >= 2 ? ESCALATION_PROMPT : RECOVERY_PROMPT;
|
|
16456
|
+
try {
|
|
16457
|
+
if (client?.session?.prompt) {
|
|
16458
|
+
await client.session.prompt({
|
|
16459
|
+
path: { id: sessionID },
|
|
16460
|
+
body: {
|
|
16461
|
+
parts: [{
|
|
16462
|
+
type: "text",
|
|
16463
|
+
text: `\u26A0\uFE0F ANOMALY #${stateSession.anomalyCount}: ${sanityResult.reason}
|
|
16464
|
+
|
|
16465
|
+
` + recoveryText + `
|
|
16466
|
+
|
|
16467
|
+
[Recovery Step ${session.step}/${session.maxSteps}]`
|
|
16468
|
+
}]
|
|
16469
|
+
}
|
|
16470
|
+
});
|
|
16471
|
+
}
|
|
16472
|
+
} catch {
|
|
16473
|
+
session.active = false;
|
|
16474
|
+
state.missionActive = false;
|
|
16475
|
+
}
|
|
16476
|
+
return;
|
|
16477
|
+
}
|
|
16478
|
+
if (stateSession && stateSession.anomalyCount > 0) {
|
|
16479
|
+
stateSession.anomalyCount = 0;
|
|
16480
|
+
}
|
|
16481
|
+
if (textContent.includes("\u2705 MISSION COMPLETE") || textContent.includes("MISSION COMPLETE")) {
|
|
16482
|
+
session.active = false;
|
|
16483
|
+
state.missionActive = false;
|
|
16484
|
+
sessions.delete(sessionID);
|
|
16485
|
+
state.sessions.delete(sessionID);
|
|
16486
|
+
return;
|
|
16487
|
+
}
|
|
16488
|
+
if (textContent.includes("/stop") || textContent.includes("/cancel")) {
|
|
16489
|
+
session.active = false;
|
|
16490
|
+
state.missionActive = false;
|
|
16491
|
+
sessions.delete(sessionID);
|
|
16492
|
+
state.sessions.delete(sessionID);
|
|
16493
|
+
return;
|
|
16494
|
+
}
|
|
16495
|
+
const now = Date.now();
|
|
16496
|
+
const stepDuration = formatElapsedTime(session.lastStepTime, now);
|
|
16497
|
+
const totalElapsed = formatElapsedTime(session.startTime, now);
|
|
16498
|
+
session.step++;
|
|
16499
|
+
session.timestamp = now;
|
|
16500
|
+
session.lastStepTime = now;
|
|
16501
|
+
const currentTime = formatTimestamp();
|
|
16502
|
+
if (session.step >= session.maxSteps) {
|
|
16503
|
+
session.active = false;
|
|
16504
|
+
state.missionActive = false;
|
|
16505
|
+
return;
|
|
16506
|
+
}
|
|
16507
|
+
try {
|
|
16508
|
+
if (client?.session?.prompt) {
|
|
16509
|
+
await client.session.prompt({
|
|
16510
|
+
path: { id: sessionID },
|
|
16511
|
+
body: {
|
|
16512
|
+
parts: [{
|
|
16513
|
+
type: "text",
|
|
16514
|
+
text: CONTINUE_INSTRUCTION + `
|
|
16515
|
+
|
|
16516
|
+
\u23F1\uFE0F [${currentTime}] Step ${session.step}/${session.maxSteps} | This step: ${stepDuration} | Total: ${totalElapsed}`
|
|
16517
|
+
}]
|
|
16518
|
+
}
|
|
16519
|
+
});
|
|
16520
|
+
}
|
|
16521
|
+
} catch {
|
|
16522
|
+
try {
|
|
16523
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
16524
|
+
if (client?.session?.prompt) {
|
|
16525
|
+
await client.session.prompt({
|
|
16526
|
+
path: { id: sessionID },
|
|
16527
|
+
body: { parts: [{ type: "text", text: "continue" }] }
|
|
16528
|
+
});
|
|
16529
|
+
}
|
|
16530
|
+
} catch {
|
|
16531
|
+
session.active = false;
|
|
16532
|
+
state.missionActive = false;
|
|
16533
|
+
}
|
|
16534
|
+
}
|
|
16535
|
+
},
|
|
15757
16536
|
// -----------------------------------------------------------------
|
|
15758
16537
|
// Event handler - cleans up when sessions are deleted
|
|
15759
16538
|
// -----------------------------------------------------------------
|
|
15760
|
-
|
|
15761
|
-
const { event } = input2;
|
|
16539
|
+
handler: async ({ event }) => {
|
|
15762
16540
|
if (event.type === "session.deleted") {
|
|
15763
16541
|
const props = event.properties;
|
|
15764
16542
|
if (props?.info?.id) {
|
|
@@ -37,13 +37,13 @@ export declare const listBackgroundTool: {
|
|
|
37
37
|
args: {
|
|
38
38
|
status: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
39
39
|
running: "running";
|
|
40
|
+
all: "all";
|
|
40
41
|
done: "done";
|
|
41
42
|
error: "error";
|
|
42
|
-
all: "all";
|
|
43
43
|
}>>;
|
|
44
44
|
};
|
|
45
45
|
execute(args: {
|
|
46
|
-
status?: "running" | "
|
|
46
|
+
status?: "running" | "all" | "done" | "error" | undefined;
|
|
47
47
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
48
48
|
};
|
|
49
49
|
export declare const killBackgroundTool: {
|
package/dist/tools/git.d.ts
CHANGED
|
@@ -33,16 +33,16 @@ export declare const gitBranchTool: (directory: string) => {
|
|
|
33
33
|
args: {
|
|
34
34
|
action: import("zod").ZodEnum<{
|
|
35
35
|
status: "status";
|
|
36
|
-
all: "all";
|
|
37
36
|
diff: "diff";
|
|
38
37
|
current: "current";
|
|
39
38
|
list: "list";
|
|
40
39
|
recent: "recent";
|
|
40
|
+
all: "all";
|
|
41
41
|
}>;
|
|
42
42
|
baseBranch: import("zod").ZodOptional<import("zod").ZodString>;
|
|
43
43
|
};
|
|
44
44
|
execute(args: {
|
|
45
|
-
action: "status" | "
|
|
45
|
+
action: "status" | "diff" | "current" | "list" | "recent" | "all";
|
|
46
46
|
baseBranch?: string | undefined;
|
|
47
47
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
48
48
|
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "opencode-orchestrator",
|
|
3
3
|
"displayName": "OpenCode Orchestrator",
|
|
4
4
|
"description": "Distributed Cognitive Architecture for OpenCode. Turns simple prompts into specialized multi-agent workflows (Planner, Coder, Reviewer).",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.3",
|
|
6
6
|
"author": "agnusdei1207",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -51,8 +51,7 @@
|
|
|
51
51
|
"dev:status": "echo '=== Global Link Status ===' && shx ls -l $(npm root -g)/opencode-orchestrator || echo 'Not linked'",
|
|
52
52
|
"dev:test": "node dist/scripts/postinstall.js && echo '---' && node dist/scripts/preuninstall.js",
|
|
53
53
|
"delete": "brew uninstall opencode && rm -rf ~/.config/opencode && npm uninstall -g opencode-orchestrator",
|
|
54
|
-
"
|
|
55
|
-
"set": "npm run delete && npm run setup"
|
|
54
|
+
"install": "brew install opencode && npm install -g opencode-orchestrator"
|
|
56
55
|
},
|
|
57
56
|
"dependencies": {
|
|
58
57
|
"@opencode-ai/plugin": "^1.1.1",
|