opencode-orchestrator 0.4.21 → 0.4.23
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 +3 -22
- package/dist/index.js +11 -789
- package/dist/tools/background.d.ts +2 -2
- package/dist/tools/git.d.ts +2 -2
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -13,24 +13,6 @@
|
|
|
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
|
-
};
|
|
34
16
|
call_agent: {
|
|
35
17
|
description: string;
|
|
36
18
|
args: {
|
|
@@ -126,13 +108,13 @@ declare const OrchestratorPlugin: (input: PluginInput) => Promise<{
|
|
|
126
108
|
args: {
|
|
127
109
|
status: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
128
110
|
running: "running";
|
|
129
|
-
all: "all";
|
|
130
111
|
done: "done";
|
|
131
112
|
error: "error";
|
|
113
|
+
all: "all";
|
|
132
114
|
}>>;
|
|
133
115
|
};
|
|
134
116
|
execute(args: {
|
|
135
|
-
status?: "running" | "
|
|
117
|
+
status?: "running" | "done" | "error" | "all" | undefined;
|
|
136
118
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
|
|
137
119
|
};
|
|
138
120
|
kill_background: {
|
|
@@ -157,8 +139,7 @@ declare const OrchestratorPlugin: (input: PluginInput) => Promise<{
|
|
|
157
139
|
output: string;
|
|
158
140
|
metadata: any;
|
|
159
141
|
}) => Promise<void>;
|
|
160
|
-
|
|
161
|
-
handler: ({ event }: {
|
|
142
|
+
event: (input: {
|
|
162
143
|
event: {
|
|
163
144
|
type: string;
|
|
164
145
|
properties?: unknown;
|
package/dist/index.js
CHANGED
|
@@ -13798,206 +13798,6 @@ 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
|
-
|
|
14001
13801
|
// src/core/background.ts
|
|
14002
13802
|
import { spawn as spawn2 } from "child_process";
|
|
14003
13803
|
import { randomBytes } from "crypto";
|
|
@@ -15674,476 +15474,6 @@ function createAsyncAgentTools(manager, client) {
|
|
|
15674
15474
|
};
|
|
15675
15475
|
}
|
|
15676
15476
|
|
|
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
|
-
|
|
16147
15477
|
// src/utils/common.ts
|
|
16148
15478
|
function detectSlashCommand(text) {
|
|
16149
15479
|
const match = text.trim().match(/^\/([a-zA-Z0-9_-]+)(?:\s+(.*))?$/);
|
|
@@ -16178,30 +15508,12 @@ var AGENT_EMOJI2 = {
|
|
|
16178
15508
|
[AGENT_NAMES.RECORDER]: "\u{1F4BE}",
|
|
16179
15509
|
[AGENT_NAMES.COMMANDER]: "\u{1F3AF}"
|
|
16180
15510
|
};
|
|
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>`;
|
|
16197
15511
|
var OrchestratorPlugin = async (input) => {
|
|
16198
15512
|
const { directory, client } = input;
|
|
16199
15513
|
console.log(`[orchestrator] v${PLUGIN_VERSION} loaded`);
|
|
16200
15514
|
const sessions = /* @__PURE__ */ new Map();
|
|
16201
15515
|
const parallelAgentManager2 = ParallelAgentManager.getInstance(client, directory);
|
|
16202
15516
|
const asyncAgentTools = createAsyncAgentTools(parallelAgentManager2, client);
|
|
16203
|
-
const batchTools = createBatchTools(parallelAgentManager2, client);
|
|
16204
|
-
const configTools = createConfigTools(client);
|
|
16205
15517
|
return {
|
|
16206
15518
|
// -----------------------------------------------------------------
|
|
16207
15519
|
// Tools we expose to the LLM
|
|
@@ -16219,13 +15531,13 @@ var OrchestratorPlugin = async (input) => {
|
|
|
16219
15531
|
list_background: listBackgroundTool,
|
|
16220
15532
|
kill_background: killBackgroundTool,
|
|
16221
15533
|
// Async agent tools - spawn agents in parallel sessions
|
|
16222
|
-
...asyncAgentTools
|
|
15534
|
+
...asyncAgentTools
|
|
16223
15535
|
// Git tools - branch info and status
|
|
16224
|
-
git_branch: gitBranchTool(directory),
|
|
15536
|
+
// git_branch: gitBranchTool(directory),
|
|
16225
15537
|
// Smart batch tools - centralized validation and retry
|
|
16226
|
-
...batchTools,
|
|
15538
|
+
// ...batchTools,
|
|
16227
15539
|
// Configuration tools - dynamic runtime settings
|
|
16228
|
-
...configTools
|
|
15540
|
+
// ...configTools,
|
|
16229
15541
|
},
|
|
16230
15542
|
// -----------------------------------------------------------------
|
|
16231
15543
|
// Config hook - registers our commands and agents with OpenCode
|
|
@@ -16436,107 +15748,17 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
16436
15748
|
\u23F1\uFE0F [${currentTime}] Step ${session.step}/${session.maxSteps} | This step: ${stepDuration} | Total: ${totalElapsed}`;
|
|
16437
15749
|
},
|
|
16438
15750
|
// -----------------------------------------------------------------
|
|
16439
|
-
// assistant.done hook
|
|
16440
|
-
//
|
|
16441
|
-
//
|
|
15751
|
+
// NOTE: assistant.done hook has been REMOVED
|
|
15752
|
+
// It was NOT in the official OpenCode plugin API and was causing
|
|
15753
|
+
// UI rendering issues. The "relentless loop" feature needs to be
|
|
15754
|
+
// reimplemented using supported hooks like tool.execute.after
|
|
15755
|
+
// or the event hook.
|
|
16442
15756
|
// -----------------------------------------------------------------
|
|
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
|
-
},
|
|
16536
15757
|
// -----------------------------------------------------------------
|
|
16537
15758
|
// Event handler - cleans up when sessions are deleted
|
|
16538
15759
|
// -----------------------------------------------------------------
|
|
16539
|
-
|
|
15760
|
+
event: async (input2) => {
|
|
15761
|
+
const { event } = input2;
|
|
16540
15762
|
if (event.type === "session.deleted") {
|
|
16541
15763
|
const props = event.properties;
|
|
16542
15764
|
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";
|
|
41
40
|
done: "done";
|
|
42
41
|
error: "error";
|
|
42
|
+
all: "all";
|
|
43
43
|
}>>;
|
|
44
44
|
};
|
|
45
45
|
execute(args: {
|
|
46
|
-
status?: "running" | "
|
|
46
|
+
status?: "running" | "done" | "error" | "all" | 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";
|
|
36
37
|
diff: "diff";
|
|
37
38
|
current: "current";
|
|
38
39
|
list: "list";
|
|
39
40
|
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" | "all" | "diff" | "current" | "list" | "recent";
|
|
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.4.
|
|
5
|
+
"version": "0.4.23",
|
|
6
6
|
"author": "agnusdei1207",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -51,7 +51,8 @@
|
|
|
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
|
-
"setup": "brew install opencode && npm install -g opencode-orchestrator"
|
|
54
|
+
"setup": "brew install opencode && npm install -g opencode-orchestrator",
|
|
55
|
+
"set": "npm run delete && npm run setup"
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
57
58
|
"@opencode-ai/plugin": "^1.1.1",
|