opencode-orchestrator 0.9.68 → 0.9.70
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.js +148 -150
- package/package.json +1 -7
package/dist/index.js
CHANGED
|
@@ -15429,6 +15429,26 @@ function getBinaryPath() {
|
|
|
15429
15429
|
return binaryPath;
|
|
15430
15430
|
}
|
|
15431
15431
|
|
|
15432
|
+
// src/core/agents/logger.ts
|
|
15433
|
+
import * as fs from "fs";
|
|
15434
|
+
import * as os from "os";
|
|
15435
|
+
import * as path from "path";
|
|
15436
|
+
var DEBUG = process.env.DEBUG_PARALLEL_AGENT === "true";
|
|
15437
|
+
var LOG_FILE = path.join(os.tmpdir(), "opencode-orchestrator.log");
|
|
15438
|
+
function log(...args) {
|
|
15439
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
15440
|
+
const message = `[${timestamp}] [parallel-agent] ${args.map(
|
|
15441
|
+
(a) => typeof a === "object" ? JSON.stringify(a) : String(a)
|
|
15442
|
+
).join(" ")}`;
|
|
15443
|
+
try {
|
|
15444
|
+
fs.appendFileSync(LOG_FILE, message + "\n");
|
|
15445
|
+
} catch {
|
|
15446
|
+
}
|
|
15447
|
+
}
|
|
15448
|
+
function getLogPath() {
|
|
15449
|
+
return LOG_FILE;
|
|
15450
|
+
}
|
|
15451
|
+
|
|
15432
15452
|
// src/tools/rust.ts
|
|
15433
15453
|
async function callRustTool(name, args) {
|
|
15434
15454
|
const binary = getBinaryPath();
|
|
@@ -15443,7 +15463,7 @@ async function callRustTool(name, args) {
|
|
|
15443
15463
|
});
|
|
15444
15464
|
proc.stderr.on("data", (data) => {
|
|
15445
15465
|
const msg = data.toString().trim();
|
|
15446
|
-
if (msg)
|
|
15466
|
+
if (msg) log(`[rust-stderr] ${msg}`);
|
|
15447
15467
|
});
|
|
15448
15468
|
const request = JSON.stringify({
|
|
15449
15469
|
jsonrpc: "2.0",
|
|
@@ -15460,7 +15480,7 @@ async function callRustTool(name, args) {
|
|
|
15460
15480
|
proc.on("close", (code) => {
|
|
15461
15481
|
clearTimeout(timeout);
|
|
15462
15482
|
if (code !== 0 && code !== null) {
|
|
15463
|
-
|
|
15483
|
+
log(`Rust process exited with code ${code}`);
|
|
15464
15484
|
}
|
|
15465
15485
|
try {
|
|
15466
15486
|
const lines = stdout.trim().split("\n");
|
|
@@ -15661,7 +15681,7 @@ var BackgroundTaskManager = class _BackgroundTaskManager {
|
|
|
15661
15681
|
debug(taskId, message) {
|
|
15662
15682
|
if (this.debugMode) {
|
|
15663
15683
|
const ts = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
|
|
15664
|
-
|
|
15684
|
+
log(`[BG ${ts}] ${taskId}: ${message}`);
|
|
15665
15685
|
}
|
|
15666
15686
|
}
|
|
15667
15687
|
run(options) {
|
|
@@ -15920,9 +15940,9 @@ Duration: ${backgroundTaskManager.formatDuration(task)}`;
|
|
|
15920
15940
|
});
|
|
15921
15941
|
|
|
15922
15942
|
// src/core/agents/concurrency.ts
|
|
15923
|
-
var
|
|
15924
|
-
var
|
|
15925
|
-
if (
|
|
15943
|
+
var DEBUG2 = process.env.DEBUG_PARALLEL_AGENT === "true";
|
|
15944
|
+
var log2 = (...args) => {
|
|
15945
|
+
if (DEBUG2) log("[concurrency]", ...args);
|
|
15926
15946
|
};
|
|
15927
15947
|
var ConcurrencyController = class {
|
|
15928
15948
|
counts = /* @__PURE__ */ new Map();
|
|
@@ -15969,10 +15989,10 @@ var ConcurrencyController = class {
|
|
|
15969
15989
|
const current = this.counts.get(key) ?? 0;
|
|
15970
15990
|
if (current < limit) {
|
|
15971
15991
|
this.counts.set(key, current + 1);
|
|
15972
|
-
|
|
15992
|
+
log2(`Acquired ${key}: ${current + 1}/${limit}`);
|
|
15973
15993
|
return;
|
|
15974
15994
|
}
|
|
15975
|
-
|
|
15995
|
+
log2(`Queueing ${key}: ${current}/${limit}`);
|
|
15976
15996
|
return new Promise((resolve) => {
|
|
15977
15997
|
const queue = this.queues.get(key) ?? [];
|
|
15978
15998
|
queue.push(resolve);
|
|
@@ -15985,13 +16005,13 @@ var ConcurrencyController = class {
|
|
|
15985
16005
|
const queue = this.queues.get(key);
|
|
15986
16006
|
if (queue && queue.length > 0) {
|
|
15987
16007
|
const next = queue.shift();
|
|
15988
|
-
|
|
16008
|
+
log2(`Released ${key}: next in queue`);
|
|
15989
16009
|
next();
|
|
15990
16010
|
} else {
|
|
15991
16011
|
const current = this.counts.get(key) ?? 0;
|
|
15992
16012
|
if (current > 0) {
|
|
15993
16013
|
this.counts.set(key, current - 1);
|
|
15994
|
-
|
|
16014
|
+
log2(`Released ${key}: ${current - 1}/${limit}`);
|
|
15995
16015
|
}
|
|
15996
16016
|
}
|
|
15997
16017
|
}
|
|
@@ -16013,8 +16033,8 @@ var ConcurrencyController = class {
|
|
|
16013
16033
|
};
|
|
16014
16034
|
|
|
16015
16035
|
// src/core/agents/task-store.ts
|
|
16016
|
-
import * as
|
|
16017
|
-
import * as
|
|
16036
|
+
import * as fs2 from "node:fs/promises";
|
|
16037
|
+
import * as path2 from "node:path";
|
|
16018
16038
|
var TaskStore = class {
|
|
16019
16039
|
tasks = /* @__PURE__ */ new Map();
|
|
16020
16040
|
pendingByParent = /* @__PURE__ */ new Map();
|
|
@@ -16146,10 +16166,10 @@ var TaskStore = class {
|
|
|
16146
16166
|
*/
|
|
16147
16167
|
async archiveTasks(tasks) {
|
|
16148
16168
|
try {
|
|
16149
|
-
await
|
|
16169
|
+
await fs2.mkdir(PATHS.TASK_ARCHIVE, { recursive: true });
|
|
16150
16170
|
const date5 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
16151
16171
|
const filename = `tasks_${date5}.jsonl`;
|
|
16152
|
-
const filepath =
|
|
16172
|
+
const filepath = path2.join(PATHS.TASK_ARCHIVE, filename);
|
|
16153
16173
|
const lines = tasks.map((task) => JSON.stringify({
|
|
16154
16174
|
id: task.id,
|
|
16155
16175
|
agent: task.agent,
|
|
@@ -16160,10 +16180,9 @@ var TaskStore = class {
|
|
|
16160
16180
|
completedAt: task.completedAt,
|
|
16161
16181
|
parentSessionID: task.parentSessionID
|
|
16162
16182
|
}));
|
|
16163
|
-
await
|
|
16183
|
+
await fs2.appendFile(filepath, lines.join("\n") + "\n");
|
|
16164
16184
|
this.archivedCount += tasks.length;
|
|
16165
16185
|
} catch (error45) {
|
|
16166
|
-
console.error("[TaskStore] Archive failed:", error45);
|
|
16167
16186
|
}
|
|
16168
16187
|
}
|
|
16169
16188
|
/**
|
|
@@ -16183,27 +16202,6 @@ var TaskStore = class {
|
|
|
16183
16202
|
}
|
|
16184
16203
|
};
|
|
16185
16204
|
|
|
16186
|
-
// src/core/agents/logger.ts
|
|
16187
|
-
import * as fs2 from "fs";
|
|
16188
|
-
import * as os from "os";
|
|
16189
|
-
import * as path2 from "path";
|
|
16190
|
-
var DEBUG2 = process.env.DEBUG_PARALLEL_AGENT === "true";
|
|
16191
|
-
var LOG_FILE = path2.join(os.tmpdir(), "opencode-orchestrator.log");
|
|
16192
|
-
function log2(...args) {
|
|
16193
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
16194
|
-
const message = `[${timestamp}] [parallel-agent] ${args.map(
|
|
16195
|
-
(a) => typeof a === "object" ? JSON.stringify(a) : String(a)
|
|
16196
|
-
).join(" ")}`;
|
|
16197
|
-
try {
|
|
16198
|
-
fs2.appendFileSync(LOG_FILE, message + "\n");
|
|
16199
|
-
} catch {
|
|
16200
|
-
}
|
|
16201
|
-
if (DEBUG2) console.log("[parallel-agent]", ...args);
|
|
16202
|
-
}
|
|
16203
|
-
function getLogPath() {
|
|
16204
|
-
return LOG_FILE;
|
|
16205
|
-
}
|
|
16206
|
-
|
|
16207
16205
|
// src/core/agents/format.ts
|
|
16208
16206
|
function formatDuration(start, end) {
|
|
16209
16207
|
const duration3 = (end ?? /* @__PURE__ */ new Date()).getTime() - start.getTime();
|
|
@@ -16682,10 +16680,10 @@ var TaskLauncher = class {
|
|
|
16682
16680
|
this.startPolling = startPolling;
|
|
16683
16681
|
}
|
|
16684
16682
|
async launch(input) {
|
|
16685
|
-
|
|
16683
|
+
log("[task-launcher.ts] launch() called", { agent: input.agent, description: input.description, parent: input.parentSessionID });
|
|
16686
16684
|
const concurrencyKey = input.agent;
|
|
16687
16685
|
await this.concurrency.acquire(concurrencyKey);
|
|
16688
|
-
|
|
16686
|
+
log("[task-launcher.ts] concurrency acquired for", concurrencyKey);
|
|
16689
16687
|
try {
|
|
16690
16688
|
const createResult = await this.client.session.create({
|
|
16691
16689
|
body: { parentID: input.parentSessionID, title: `${PARALLEL_TASK.SESSION_TITLE_PREFIX}: ${input.description}` },
|
|
@@ -16698,7 +16696,7 @@ var TaskLauncher = class {
|
|
|
16698
16696
|
const sessionID = createResult.data.id;
|
|
16699
16697
|
const taskId = `${ID_PREFIX.TASK}${crypto.randomUUID().slice(0, 8)}`;
|
|
16700
16698
|
const depth = (input.depth ?? 0) + 1;
|
|
16701
|
-
|
|
16699
|
+
log("[task-launcher.ts] Creating task with depth", depth);
|
|
16702
16700
|
const task = {
|
|
16703
16701
|
id: taskId,
|
|
16704
16702
|
sessionID,
|
|
@@ -16728,7 +16726,7 @@ var TaskLauncher = class {
|
|
|
16728
16726
|
parts: [{ type: PART_TYPES.TEXT, text: input.prompt }]
|
|
16729
16727
|
}
|
|
16730
16728
|
}).catch((error45) => {
|
|
16731
|
-
|
|
16729
|
+
log(`Prompt error for ${taskId}:`, error45);
|
|
16732
16730
|
this.onTaskError(taskId, error45);
|
|
16733
16731
|
});
|
|
16734
16732
|
const toastManager = getTaskToastManager();
|
|
@@ -16743,7 +16741,7 @@ var TaskLauncher = class {
|
|
|
16743
16741
|
});
|
|
16744
16742
|
}
|
|
16745
16743
|
presets.sessionCreated(sessionID, input.agent);
|
|
16746
|
-
|
|
16744
|
+
log(`Launched ${taskId} in session ${sessionID}`);
|
|
16747
16745
|
return task;
|
|
16748
16746
|
} catch (error45) {
|
|
16749
16747
|
this.concurrency.release(concurrencyKey);
|
|
@@ -16775,7 +16773,7 @@ var TaskResumer = class {
|
|
|
16775
16773
|
existingTask.stablePolls = 0;
|
|
16776
16774
|
this.store.trackPending(input.parentSessionID, existingTask.id);
|
|
16777
16775
|
this.startPolling();
|
|
16778
|
-
|
|
16776
|
+
log(`Resuming task ${existingTask.id} in session ${existingTask.sessionID}`);
|
|
16779
16777
|
this.client.session.prompt({
|
|
16780
16778
|
path: { id: existingTask.sessionID },
|
|
16781
16779
|
body: {
|
|
@@ -16783,7 +16781,7 @@ var TaskResumer = class {
|
|
|
16783
16781
|
parts: [{ type: PART_TYPES.TEXT, text: input.prompt }]
|
|
16784
16782
|
}
|
|
16785
16783
|
}).catch((error45) => {
|
|
16786
|
-
|
|
16784
|
+
log(`Resume prompt error for ${existingTask.id}:`, error45);
|
|
16787
16785
|
existingTask.status = TASK_STATUS.ERROR;
|
|
16788
16786
|
existingTask.error = error45 instanceof Error ? error45.message : String(error45);
|
|
16789
16787
|
existingTask.completedAt = /* @__PURE__ */ new Date();
|
|
@@ -16817,7 +16815,7 @@ var TaskPoller = class {
|
|
|
16817
16815
|
pollingInterval;
|
|
16818
16816
|
start() {
|
|
16819
16817
|
if (this.pollingInterval) return;
|
|
16820
|
-
|
|
16818
|
+
log("[task-poller.ts] start() - polling started");
|
|
16821
16819
|
this.pollingInterval = setInterval(() => this.poll(), CONFIG.POLL_INTERVAL_MS);
|
|
16822
16820
|
this.pollingInterval.unref();
|
|
16823
16821
|
}
|
|
@@ -16837,7 +16835,7 @@ var TaskPoller = class {
|
|
|
16837
16835
|
this.stop();
|
|
16838
16836
|
return;
|
|
16839
16837
|
}
|
|
16840
|
-
|
|
16838
|
+
log("[task-poller.ts] poll() checking", running.length, "running tasks");
|
|
16841
16839
|
try {
|
|
16842
16840
|
const statusResult = await this.client.session.status();
|
|
16843
16841
|
const allStatuses = statusResult.data ?? {};
|
|
@@ -16855,16 +16853,16 @@ var TaskPoller = class {
|
|
|
16855
16853
|
const elapsed = Date.now() - task.startedAt.getTime();
|
|
16856
16854
|
if (elapsed >= CONFIG.MIN_STABILITY_MS && task.stablePolls && task.stablePolls >= 3) {
|
|
16857
16855
|
if (await this.validateSessionHasOutput(task.sessionID)) {
|
|
16858
|
-
|
|
16856
|
+
log(`Task ${task.id} stable for 3 polls, completing...`);
|
|
16859
16857
|
await this.completeTask(task);
|
|
16860
16858
|
}
|
|
16861
16859
|
}
|
|
16862
16860
|
} catch (error45) {
|
|
16863
|
-
|
|
16861
|
+
log(`Poll error for task ${task.id}:`, error45);
|
|
16864
16862
|
}
|
|
16865
16863
|
}
|
|
16866
16864
|
} catch (error45) {
|
|
16867
|
-
|
|
16865
|
+
log("Polling error:", error45);
|
|
16868
16866
|
}
|
|
16869
16867
|
}
|
|
16870
16868
|
async validateSessionHasOutput(sessionID) {
|
|
@@ -16877,7 +16875,7 @@ var TaskPoller = class {
|
|
|
16877
16875
|
}
|
|
16878
16876
|
}
|
|
16879
16877
|
async completeTask(task) {
|
|
16880
|
-
|
|
16878
|
+
log("[task-poller.ts] completeTask() called for", task.id, task.agent);
|
|
16881
16879
|
task.status = TASK_STATUS.COMPLETED;
|
|
16882
16880
|
task.completedAt = /* @__PURE__ */ new Date();
|
|
16883
16881
|
if (task.concurrencyKey) {
|
|
@@ -16890,7 +16888,7 @@ var TaskPoller = class {
|
|
|
16890
16888
|
this.scheduleCleanup(task.id);
|
|
16891
16889
|
const duration3 = formatDuration(task.startedAt, task.completedAt);
|
|
16892
16890
|
presets.sessionCompleted(task.sessionID, duration3);
|
|
16893
|
-
|
|
16891
|
+
log(`Completed ${task.id} (${duration3})`);
|
|
16894
16892
|
}
|
|
16895
16893
|
async updateTaskProgress(task) {
|
|
16896
16894
|
try {
|
|
@@ -16942,7 +16940,7 @@ var TaskCleaner = class {
|
|
|
16942
16940
|
for (const [taskId, task] of this.store.getAll().map((t) => [t.id, t])) {
|
|
16943
16941
|
const age = now - task.startedAt.getTime();
|
|
16944
16942
|
if (age <= CONFIG.TASK_TTL_MS) continue;
|
|
16945
|
-
|
|
16943
|
+
log(`Timeout: ${taskId}`);
|
|
16946
16944
|
if (task.status === TASK_STATUS.RUNNING) {
|
|
16947
16945
|
task.status = TASK_STATUS.TIMEOUT;
|
|
16948
16946
|
task.error = "Task exceeded 30 minute time limit";
|
|
@@ -16977,7 +16975,7 @@ var TaskCleaner = class {
|
|
|
16977
16975
|
}
|
|
16978
16976
|
}
|
|
16979
16977
|
this.store.delete(taskId);
|
|
16980
|
-
|
|
16978
|
+
log(`Cleaned up ${taskId}`);
|
|
16981
16979
|
}, CONFIG.CLEANUP_DELAY_MS);
|
|
16982
16980
|
}
|
|
16983
16981
|
/**
|
|
@@ -17027,9 +17025,9 @@ You will be notified when ALL tasks complete. Continue productive work.`;
|
|
|
17027
17025
|
parts: [{ type: PART_TYPES.TEXT, text: message }]
|
|
17028
17026
|
}
|
|
17029
17027
|
});
|
|
17030
|
-
|
|
17028
|
+
log(`Notified parent ${parentSessionID} (allComplete=${allComplete}, noReply=${!allComplete})`);
|
|
17031
17029
|
} catch (error45) {
|
|
17032
|
-
|
|
17030
|
+
log("Notification error:", error45);
|
|
17033
17031
|
}
|
|
17034
17032
|
this.store.clearNotifications(parentSessionID);
|
|
17035
17033
|
}
|
|
@@ -17058,7 +17056,7 @@ var EventHandler = class {
|
|
|
17058
17056
|
const task = this.findBySession(sessionID);
|
|
17059
17057
|
if (!task || task.status !== TASK_STATUS.RUNNING) return;
|
|
17060
17058
|
this.handleSessionIdle(task).catch((err) => {
|
|
17061
|
-
|
|
17059
|
+
log("Error handling session.idle:", err);
|
|
17062
17060
|
});
|
|
17063
17061
|
}
|
|
17064
17062
|
if (event.type === SESSION_EVENTS.DELETED) {
|
|
@@ -17072,12 +17070,12 @@ var EventHandler = class {
|
|
|
17072
17070
|
async handleSessionIdle(task) {
|
|
17073
17071
|
const elapsed = Date.now() - task.startedAt.getTime();
|
|
17074
17072
|
if (elapsed < CONFIG.MIN_STABILITY_MS) {
|
|
17075
|
-
|
|
17073
|
+
log(`Session idle but too early for ${task.id}, waiting...`);
|
|
17076
17074
|
return;
|
|
17077
17075
|
}
|
|
17078
17076
|
const hasOutput = await this.validateSessionHasOutput(task.sessionID);
|
|
17079
17077
|
if (!hasOutput) {
|
|
17080
|
-
|
|
17078
|
+
log(`Session idle but no output for ${task.id}, waiting...`);
|
|
17081
17079
|
return;
|
|
17082
17080
|
}
|
|
17083
17081
|
task.status = TASK_STATUS.COMPLETED;
|
|
@@ -17090,10 +17088,10 @@ var EventHandler = class {
|
|
|
17090
17088
|
this.store.queueNotification(task);
|
|
17091
17089
|
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
17092
17090
|
this.scheduleCleanup(task.id);
|
|
17093
|
-
|
|
17091
|
+
log(`Task ${task.id} completed via session.idle event (${formatDuration(task.startedAt, task.completedAt)})`);
|
|
17094
17092
|
}
|
|
17095
17093
|
handleSessionDeleted(task) {
|
|
17096
|
-
|
|
17094
|
+
log(`Session deleted event for task ${task.id}`);
|
|
17097
17095
|
if (task.status === TASK_STATUS.RUNNING) {
|
|
17098
17096
|
task.status = TASK_STATUS.ERROR;
|
|
17099
17097
|
task.error = "Session deleted";
|
|
@@ -17106,7 +17104,7 @@ var EventHandler = class {
|
|
|
17106
17104
|
this.store.untrackPending(task.parentSessionID, task.id);
|
|
17107
17105
|
this.store.clearNotificationsForTask(task.id);
|
|
17108
17106
|
this.store.delete(task.id);
|
|
17109
|
-
|
|
17107
|
+
log(`Cleaned up deleted session task: ${task.id}`);
|
|
17110
17108
|
}
|
|
17111
17109
|
};
|
|
17112
17110
|
|
|
@@ -17201,12 +17199,12 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
17201
17199
|
this.store.untrackPending(task.parentSessionID, taskId);
|
|
17202
17200
|
try {
|
|
17203
17201
|
await this.client.session.delete({ path: { id: task.sessionID } });
|
|
17204
|
-
|
|
17202
|
+
log(`Session ${task.sessionID.slice(0, 8)}... deleted`);
|
|
17205
17203
|
} catch {
|
|
17206
|
-
|
|
17204
|
+
log(`Session ${task.sessionID.slice(0, 8)}... already gone`);
|
|
17207
17205
|
}
|
|
17208
17206
|
this.cleaner.scheduleCleanup(taskId);
|
|
17209
|
-
|
|
17207
|
+
log(`Cancelled ${taskId}`);
|
|
17210
17208
|
return true;
|
|
17211
17209
|
}
|
|
17212
17210
|
async getResult(taskId) {
|
|
@@ -17301,7 +17299,7 @@ async function validateSessionHasOutput(session, sessionID) {
|
|
|
17301
17299
|
});
|
|
17302
17300
|
return hasContent;
|
|
17303
17301
|
} catch (error45) {
|
|
17304
|
-
|
|
17302
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Error validating session output:`, error45);
|
|
17305
17303
|
return true;
|
|
17306
17304
|
}
|
|
17307
17305
|
}
|
|
@@ -17315,11 +17313,11 @@ async function pollWithSafetyLimits(session, sessionID, startTime) {
|
|
|
17315
17313
|
pollCount++;
|
|
17316
17314
|
const elapsed = Date.now() - startTime;
|
|
17317
17315
|
if (elapsed >= SYNC_TIMEOUT_MS) {
|
|
17318
|
-
|
|
17316
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Hard timeout reached`, { pollCount, elapsed });
|
|
17319
17317
|
return { success: false, timedOut: true, pollCount, elapsedMs: elapsed };
|
|
17320
17318
|
}
|
|
17321
17319
|
if (Date.now() - lastLogTime > 1e4) {
|
|
17322
|
-
|
|
17320
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Polling...`, {
|
|
17323
17321
|
pollCount,
|
|
17324
17322
|
elapsed: Math.floor(elapsed / 1e3) + "s",
|
|
17325
17323
|
stablePolls,
|
|
@@ -17347,14 +17345,14 @@ async function pollWithSafetyLimits(session, sessionID, startTime) {
|
|
|
17347
17345
|
if (!hasValidOutput) {
|
|
17348
17346
|
continue;
|
|
17349
17347
|
}
|
|
17350
|
-
|
|
17348
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Valid output detected`, { pollCount, elapsed });
|
|
17351
17349
|
}
|
|
17352
17350
|
const msgs = await session.messages({ path: { id: sessionID } });
|
|
17353
17351
|
const count = (msgs.data ?? []).length;
|
|
17354
17352
|
if (count === lastMsgCount) {
|
|
17355
17353
|
stablePolls++;
|
|
17356
17354
|
if (stablePolls >= STABLE_POLLS_REQUIRED) {
|
|
17357
|
-
|
|
17355
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Stable completion`, { pollCount, stablePolls, elapsed });
|
|
17358
17356
|
return { success: true, timedOut: false, pollCount, elapsedMs: elapsed };
|
|
17359
17357
|
}
|
|
17360
17358
|
} else {
|
|
@@ -17362,10 +17360,10 @@ async function pollWithSafetyLimits(session, sessionID, startTime) {
|
|
|
17362
17360
|
lastMsgCount = count;
|
|
17363
17361
|
}
|
|
17364
17362
|
} catch (error45) {
|
|
17365
|
-
|
|
17363
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Poll error (continuing):`, error45);
|
|
17366
17364
|
}
|
|
17367
17365
|
}
|
|
17368
|
-
|
|
17366
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Max poll count reached`, { pollCount, elapsed: Date.now() - startTime });
|
|
17369
17367
|
return {
|
|
17370
17368
|
success: false,
|
|
17371
17369
|
timedOut: true,
|
|
@@ -17382,7 +17380,7 @@ async function extractSessionResult(session, sessionID) {
|
|
|
17382
17380
|
const text = lastMsg?.parts?.filter((p) => p.type === PART_TYPES.TEXT || p.type === PART_TYPES.REASONING).map((p) => p.text ?? "").join("\n") || "";
|
|
17383
17381
|
return text;
|
|
17384
17382
|
} catch (error45) {
|
|
17385
|
-
|
|
17383
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Error extracting result:`, error45);
|
|
17386
17384
|
return "(Error extracting result)";
|
|
17387
17385
|
}
|
|
17388
17386
|
}
|
|
@@ -17415,7 +17413,7 @@ var createDelegateTaskTool = (manager, client) => tool({
|
|
|
17415
17413
|
async execute(args, context) {
|
|
17416
17414
|
const { agent, description, prompt, background, resume } = args;
|
|
17417
17415
|
const ctx = context;
|
|
17418
|
-
|
|
17416
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} execute() called`, { agent, description, background, resume, parentSession: ctx.sessionID });
|
|
17419
17417
|
const sessionClient = client;
|
|
17420
17418
|
if (background === void 0) {
|
|
17421
17419
|
return `${OUTPUT_LABEL.ERROR} 'background' parameter is REQUIRED.`;
|
|
@@ -17434,7 +17432,7 @@ Previous context preserved. Use \`get_task_result({ taskId: "${task.id}" })\` wh
|
|
|
17434
17432
|
}
|
|
17435
17433
|
const startTime = Date.now();
|
|
17436
17434
|
const session = sessionClient.session;
|
|
17437
|
-
|
|
17435
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Resume: starting sync wait`, { taskId: task.id, sessionID: task.sessionID });
|
|
17438
17436
|
const pollResult = await pollWithSafetyLimits(session, task.sessionID, startTime);
|
|
17439
17437
|
if (pollResult.timedOut) {
|
|
17440
17438
|
return `${OUTPUT_LABEL.TIMEOUT} after ${Math.floor(pollResult.elapsedMs / 1e3)}s (${pollResult.pollCount} polls)
|
|
@@ -17474,7 +17472,7 @@ Session: \`${task.sessionID}\` (save for resume)`;
|
|
|
17474
17472
|
}
|
|
17475
17473
|
const sessionID = createResult.data.id;
|
|
17476
17474
|
const startTime = Date.now();
|
|
17477
|
-
|
|
17475
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Sync: starting`, { agent, sessionID });
|
|
17478
17476
|
await session.prompt({
|
|
17479
17477
|
path: { id: sessionID },
|
|
17480
17478
|
body: {
|
|
@@ -17490,18 +17488,18 @@ Session: \`${task.sessionID}\` (save for resume)`;
|
|
|
17490
17488
|
});
|
|
17491
17489
|
const pollResult = await pollWithSafetyLimits(session, sessionID, startTime);
|
|
17492
17490
|
if (pollResult.timedOut) {
|
|
17493
|
-
|
|
17491
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Sync: timed out`, pollResult);
|
|
17494
17492
|
return `${OUTPUT_LABEL.TIMEOUT} after ${Math.floor(pollResult.elapsedMs / 1e3)}s (${pollResult.pollCount} polls)
|
|
17495
17493
|
Session: \`${sessionID}\` - Use get_task_result or resume later.`;
|
|
17496
17494
|
}
|
|
17497
17495
|
const text = await extractSessionResult(session, sessionID);
|
|
17498
|
-
|
|
17496
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Sync: completed`, { sessionID, elapsedMs: pollResult.elapsedMs });
|
|
17499
17497
|
return `${OUTPUT_LABEL.DONE} (${Math.floor(pollResult.elapsedMs / 1e3)}s)
|
|
17500
17498
|
Session: \`${sessionID}\` (save for resume)
|
|
17501
17499
|
|
|
17502
17500
|
${text || "(No output)"}`;
|
|
17503
17501
|
} catch (error45) {
|
|
17504
|
-
|
|
17502
|
+
log(`${PARALLEL_LOG.DELEGATE_TASK} Sync: error`, error45);
|
|
17505
17503
|
return `${OUTPUT_LABEL.ERROR} Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
17506
17504
|
}
|
|
17507
17505
|
}
|
|
@@ -17990,7 +17988,7 @@ async function searchBrave(query) {
|
|
|
17990
17988
|
}
|
|
17991
17989
|
return results;
|
|
17992
17990
|
} catch (error45) {
|
|
17993
|
-
|
|
17991
|
+
log("Brave search error:", error45);
|
|
17994
17992
|
return [];
|
|
17995
17993
|
}
|
|
17996
17994
|
}
|
|
@@ -18039,7 +18037,7 @@ async function searchDuckDuckGo(query) {
|
|
|
18039
18037
|
}
|
|
18040
18038
|
return results;
|
|
18041
18039
|
} catch (error45) {
|
|
18042
|
-
|
|
18040
|
+
log("DuckDuckGo search error:", error45);
|
|
18043
18041
|
return [];
|
|
18044
18042
|
}
|
|
18045
18043
|
}
|
|
@@ -18070,7 +18068,7 @@ async function searchDuckDuckGoHtml(query) {
|
|
|
18070
18068
|
}
|
|
18071
18069
|
return results;
|
|
18072
18070
|
} catch (error45) {
|
|
18073
|
-
|
|
18071
|
+
log("DuckDuckGo HTML search error:", error45);
|
|
18074
18072
|
return [];
|
|
18075
18073
|
}
|
|
18076
18074
|
}
|
|
@@ -18298,7 +18296,7 @@ async function searchGrepApp(query, options) {
|
|
|
18298
18296
|
}
|
|
18299
18297
|
return results;
|
|
18300
18298
|
} catch (error45) {
|
|
18301
|
-
|
|
18299
|
+
log("grep.app search error:", error45);
|
|
18302
18300
|
return [];
|
|
18303
18301
|
}
|
|
18304
18302
|
}
|
|
@@ -18339,7 +18337,7 @@ async function searchGitHub(query, options) {
|
|
|
18339
18337
|
}
|
|
18340
18338
|
return results;
|
|
18341
18339
|
} catch (error45) {
|
|
18342
|
-
|
|
18340
|
+
log("GitHub search error:", error45);
|
|
18343
18341
|
return [];
|
|
18344
18342
|
}
|
|
18345
18343
|
}
|
|
@@ -18660,24 +18658,24 @@ There was a temporary processing issue. Please continue from where you left off.
|
|
|
18660
18658
|
async function handleSessionError(client, sessionID, error45, properties) {
|
|
18661
18659
|
const state2 = getState(sessionID);
|
|
18662
18660
|
if (state2.isRecovering) {
|
|
18663
|
-
|
|
18661
|
+
log("[session-recovery] Already recovering, skipping", { sessionID });
|
|
18664
18662
|
return false;
|
|
18665
18663
|
}
|
|
18666
18664
|
const now = Date.now();
|
|
18667
18665
|
if (now - state2.lastErrorTime < BACKGROUND_TASK.RETRY_COOLDOWN_MS) {
|
|
18668
|
-
|
|
18666
|
+
log("[session-recovery] Too soon since last error, skipping", { sessionID });
|
|
18669
18667
|
return false;
|
|
18670
18668
|
}
|
|
18671
18669
|
state2.lastErrorTime = now;
|
|
18672
18670
|
state2.errorCount++;
|
|
18673
18671
|
const errorType = detectErrorType(error45);
|
|
18674
18672
|
if (!errorType) {
|
|
18675
|
-
|
|
18673
|
+
log("[session-recovery] Unknown error type, using default handler", { sessionID, error: error45 });
|
|
18676
18674
|
return false;
|
|
18677
18675
|
}
|
|
18678
|
-
|
|
18676
|
+
log("[session-recovery] Detected error type", { sessionID, errorType, errorCount: state2.errorCount });
|
|
18679
18677
|
if (state2.errorCount > RECOVERY.MAX_ATTEMPTS) {
|
|
18680
|
-
|
|
18678
|
+
log("[session-recovery] Max recovery attempts exceeded", { sessionID });
|
|
18681
18679
|
presets.warningMaxRetries();
|
|
18682
18680
|
return false;
|
|
18683
18681
|
}
|
|
@@ -18704,7 +18702,7 @@ async function handleSessionError(client, sessionID, error45, properties) {
|
|
|
18704
18702
|
};
|
|
18705
18703
|
const action = handleError(ctx);
|
|
18706
18704
|
if (action.type === "retry" && action.delay) {
|
|
18707
|
-
|
|
18705
|
+
log("[session-recovery] Rate limit, waiting", { delay: action.delay });
|
|
18708
18706
|
await new Promise((r) => setTimeout(r, action.delay));
|
|
18709
18707
|
}
|
|
18710
18708
|
state2.isRecovering = false;
|
|
@@ -18714,7 +18712,7 @@ async function handleSessionError(client, sessionID, error45, properties) {
|
|
|
18714
18712
|
state2.isRecovering = false;
|
|
18715
18713
|
return false;
|
|
18716
18714
|
case ERROR_TYPE.MESSAGE_ABORTED:
|
|
18717
|
-
|
|
18715
|
+
log("[session-recovery] Message aborted by user, not recovering", { sessionID });
|
|
18718
18716
|
state2.isRecovering = false;
|
|
18719
18717
|
return false;
|
|
18720
18718
|
default:
|
|
@@ -18729,14 +18727,14 @@ async function handleSessionError(client, sessionID, error45, properties) {
|
|
|
18729
18727
|
parts: [{ type: PART_TYPES.TEXT, text: recoveryPrompt }]
|
|
18730
18728
|
}
|
|
18731
18729
|
});
|
|
18732
|
-
|
|
18730
|
+
log("[session-recovery] Recovery prompt injected", { sessionID, errorType });
|
|
18733
18731
|
state2.isRecovering = false;
|
|
18734
18732
|
return true;
|
|
18735
18733
|
}
|
|
18736
18734
|
state2.isRecovering = false;
|
|
18737
18735
|
return false;
|
|
18738
18736
|
} catch (injectionError) {
|
|
18739
|
-
|
|
18737
|
+
log("[session-recovery] Failed to inject recovery prompt", { sessionID, error: injectionError });
|
|
18740
18738
|
state2.isRecovering = false;
|
|
18741
18739
|
return false;
|
|
18742
18740
|
}
|
|
@@ -18918,20 +18916,20 @@ async function showCountdownToast(client, secondsRemaining, incompleteCount) {
|
|
|
18918
18916
|
async function injectContinuation(client, sessionID, todos) {
|
|
18919
18917
|
const state2 = getState2(sessionID);
|
|
18920
18918
|
if (state2.isAborting) {
|
|
18921
|
-
|
|
18919
|
+
log("[todo-continuation] Skipped: user is aborting", { sessionID });
|
|
18922
18920
|
return;
|
|
18923
18921
|
}
|
|
18924
18922
|
if (hasRunningBackgroundTasks(sessionID)) {
|
|
18925
|
-
|
|
18923
|
+
log("[todo-continuation] Skipped: background tasks running", { sessionID });
|
|
18926
18924
|
return;
|
|
18927
18925
|
}
|
|
18928
18926
|
if (isSessionRecovering(sessionID)) {
|
|
18929
|
-
|
|
18927
|
+
log("[todo-continuation] Skipped: session is recovering from error", { sessionID });
|
|
18930
18928
|
return;
|
|
18931
18929
|
}
|
|
18932
18930
|
const prompt = generateContinuationPrompt(todos);
|
|
18933
18931
|
if (!prompt) {
|
|
18934
|
-
|
|
18932
|
+
log("[todo-continuation] Skipped: no continuation prompt needed", { sessionID });
|
|
18935
18933
|
return;
|
|
18936
18934
|
}
|
|
18937
18935
|
try {
|
|
@@ -18941,43 +18939,43 @@ async function injectContinuation(client, sessionID, todos) {
|
|
|
18941
18939
|
parts: [{ type: PART_TYPES.TEXT, text: prompt }]
|
|
18942
18940
|
}
|
|
18943
18941
|
});
|
|
18944
|
-
|
|
18942
|
+
log("[todo-continuation] Injected continuation prompt", {
|
|
18945
18943
|
sessionID,
|
|
18946
18944
|
incompleteCount: getIncompleteCount(todos),
|
|
18947
18945
|
progress: formatProgress(todos)
|
|
18948
18946
|
});
|
|
18949
18947
|
} catch (error45) {
|
|
18950
|
-
|
|
18948
|
+
log("[todo-continuation] Failed to inject continuation", { sessionID, error: error45 });
|
|
18951
18949
|
}
|
|
18952
18950
|
}
|
|
18953
18951
|
async function handleSessionIdle(client, sessionID, mainSessionID) {
|
|
18954
18952
|
const state2 = getState2(sessionID);
|
|
18955
18953
|
const now = Date.now();
|
|
18956
18954
|
if (state2.lastIdleTime && now - state2.lastIdleTime < MIN_TIME_BETWEEN_CONTINUATIONS_MS) {
|
|
18957
|
-
|
|
18955
|
+
log("[todo-continuation] Skipped: too soon since last check", { sessionID });
|
|
18958
18956
|
return;
|
|
18959
18957
|
}
|
|
18960
18958
|
state2.lastIdleTime = now;
|
|
18961
18959
|
cancelCountdown(sessionID);
|
|
18962
18960
|
if (mainSessionID && sessionID !== mainSessionID) {
|
|
18963
|
-
|
|
18961
|
+
log("[todo-continuation] Skipped: not main session", { sessionID, mainSessionID });
|
|
18964
18962
|
return;
|
|
18965
18963
|
}
|
|
18966
18964
|
if (isSessionRecovering(sessionID)) {
|
|
18967
|
-
|
|
18965
|
+
log("[todo-continuation] Skipped: in recovery mode", { sessionID });
|
|
18968
18966
|
return;
|
|
18969
18967
|
}
|
|
18970
18968
|
if (state2.abortDetectedAt) {
|
|
18971
18969
|
const timeSinceAbort = Date.now() - state2.abortDetectedAt;
|
|
18972
18970
|
if (timeSinceAbort < ABORT_WINDOW_MS) {
|
|
18973
|
-
|
|
18971
|
+
log("[todo-continuation] Skipped: abort detected recently", { sessionID, timeSinceAbort });
|
|
18974
18972
|
state2.abortDetectedAt = void 0;
|
|
18975
18973
|
return;
|
|
18976
18974
|
}
|
|
18977
18975
|
state2.abortDetectedAt = void 0;
|
|
18978
18976
|
}
|
|
18979
18977
|
if (hasRunningBackgroundTasks(sessionID)) {
|
|
18980
|
-
|
|
18978
|
+
log("[todo-continuation] Skipped: background tasks running", { sessionID });
|
|
18981
18979
|
return;
|
|
18982
18980
|
}
|
|
18983
18981
|
let todos = [];
|
|
@@ -18985,16 +18983,16 @@ async function handleSessionIdle(client, sessionID, mainSessionID) {
|
|
|
18985
18983
|
const response = await client.session.todo({ path: { id: sessionID } });
|
|
18986
18984
|
todos = parseTodos(response.data ?? response);
|
|
18987
18985
|
} catch (error45) {
|
|
18988
|
-
|
|
18986
|
+
log("[todo-continuation] Failed to fetch todos", { sessionID, error: error45 });
|
|
18989
18987
|
return;
|
|
18990
18988
|
}
|
|
18991
18989
|
if (!hasRemainingWork(todos)) {
|
|
18992
|
-
|
|
18990
|
+
log("[todo-continuation] All todos complete", { sessionID });
|
|
18993
18991
|
return;
|
|
18994
18992
|
}
|
|
18995
18993
|
const incompleteCount = getIncompleteCount(todos);
|
|
18996
18994
|
const nextPending = getNextPending(todos);
|
|
18997
|
-
|
|
18995
|
+
log("[todo-continuation] Starting countdown", {
|
|
18998
18996
|
sessionID,
|
|
18999
18997
|
incompleteCount,
|
|
19000
18998
|
nextPending: nextPending?.id
|
|
@@ -19009,10 +19007,10 @@ async function handleSessionIdle(client, sessionID, mainSessionID) {
|
|
|
19009
19007
|
if (hasRemainingWork(freshTodos)) {
|
|
19010
19008
|
await injectContinuation(client, sessionID, freshTodos);
|
|
19011
19009
|
} else {
|
|
19012
|
-
|
|
19010
|
+
log("[todo-continuation] Todos completed during countdown", { sessionID });
|
|
19013
19011
|
}
|
|
19014
19012
|
} catch {
|
|
19015
|
-
|
|
19013
|
+
log("[todo-continuation] Failed to re-fetch todos for continuation", { sessionID });
|
|
19016
19014
|
}
|
|
19017
19015
|
}, COUNTDOWN_SECONDS * TIME.SECOND);
|
|
19018
19016
|
}
|
|
@@ -19021,12 +19019,12 @@ function handleUserMessage(sessionID) {
|
|
|
19021
19019
|
if (state2.countdownStartedAt) {
|
|
19022
19020
|
const elapsed = Date.now() - state2.countdownStartedAt;
|
|
19023
19021
|
if (elapsed < COUNTDOWN_GRACE_PERIOD_MS) {
|
|
19024
|
-
|
|
19022
|
+
log("[todo-continuation] Ignoring message in grace period", { sessionID, elapsed });
|
|
19025
19023
|
return;
|
|
19026
19024
|
}
|
|
19027
19025
|
}
|
|
19028
19026
|
if (state2.countdownTimer) {
|
|
19029
|
-
|
|
19027
|
+
log("[todo-continuation] Cancelled: user interaction", { sessionID });
|
|
19030
19028
|
cancelCountdown(sessionID);
|
|
19031
19029
|
}
|
|
19032
19030
|
state2.isAborting = false;
|
|
@@ -19037,7 +19035,7 @@ function handleSessionError2(sessionID, error45) {
|
|
|
19037
19035
|
const errorObj = error45;
|
|
19038
19036
|
if (errorObj?.name === "MessageAbortedError" || errorObj?.name === "AbortError") {
|
|
19039
19037
|
state2.abortDetectedAt = Date.now();
|
|
19040
|
-
|
|
19038
|
+
log("[todo-continuation] Abort detected", { sessionID, errorName: errorObj.name });
|
|
19041
19039
|
}
|
|
19042
19040
|
cancelCountdown(sessionID);
|
|
19043
19041
|
}
|
|
@@ -19071,7 +19069,7 @@ function readLoopState(directory) {
|
|
|
19071
19069
|
const content = readFileSync(filePath, "utf-8");
|
|
19072
19070
|
return JSON.parse(content);
|
|
19073
19071
|
} catch (error45) {
|
|
19074
|
-
|
|
19072
|
+
log(`[mission-seal] Failed to read state: ${error45}`);
|
|
19075
19073
|
return null;
|
|
19076
19074
|
}
|
|
19077
19075
|
}
|
|
@@ -19085,7 +19083,7 @@ function writeLoopState(directory, state2) {
|
|
|
19085
19083
|
writeFileSync(filePath, JSON.stringify(state2, null, 2), "utf-8");
|
|
19086
19084
|
return true;
|
|
19087
19085
|
} catch (error45) {
|
|
19088
|
-
|
|
19086
|
+
log(`[mission-seal] Failed to write state: ${error45}`);
|
|
19089
19087
|
return false;
|
|
19090
19088
|
}
|
|
19091
19089
|
}
|
|
@@ -19098,7 +19096,7 @@ function clearLoopState(directory) {
|
|
|
19098
19096
|
unlinkSync(filePath);
|
|
19099
19097
|
return true;
|
|
19100
19098
|
} catch (error45) {
|
|
19101
|
-
|
|
19099
|
+
log(`[mission-seal] Failed to clear state: ${error45}`);
|
|
19102
19100
|
return false;
|
|
19103
19101
|
}
|
|
19104
19102
|
}
|
|
@@ -19134,7 +19132,7 @@ async function detectSealInSession(client, sessionID) {
|
|
|
19134
19132
|
}
|
|
19135
19133
|
return false;
|
|
19136
19134
|
} catch (error45) {
|
|
19137
|
-
|
|
19135
|
+
log(`[mission-seal] Failed to check session messages: ${error45}`);
|
|
19138
19136
|
return false;
|
|
19139
19137
|
}
|
|
19140
19138
|
}
|
|
@@ -19149,7 +19147,7 @@ function startMissionLoop(directory, sessionID, prompt, options = {}) {
|
|
|
19149
19147
|
};
|
|
19150
19148
|
const success2 = writeLoopState(directory, state2);
|
|
19151
19149
|
if (success2) {
|
|
19152
|
-
|
|
19150
|
+
log(`[mission-seal] Loop started`, {
|
|
19153
19151
|
sessionID,
|
|
19154
19152
|
maxIterations: state2.maxIterations
|
|
19155
19153
|
});
|
|
@@ -19276,20 +19274,20 @@ async function showMaxIterationsToast(client, state2) {
|
|
|
19276
19274
|
async function injectContinuation2(client, directory, sessionID, loopState) {
|
|
19277
19275
|
const handlerState = getState3(sessionID);
|
|
19278
19276
|
if (handlerState.isAborting) {
|
|
19279
|
-
|
|
19277
|
+
log("[mission-seal-handler] Skipped: user is aborting");
|
|
19280
19278
|
return;
|
|
19281
19279
|
}
|
|
19282
19280
|
if (hasRunningBackgroundTasks2(sessionID)) {
|
|
19283
|
-
|
|
19281
|
+
log("[mission-seal-handler] Skipped: background tasks running");
|
|
19284
19282
|
return;
|
|
19285
19283
|
}
|
|
19286
19284
|
if (isSessionRecovering(sessionID)) {
|
|
19287
|
-
|
|
19285
|
+
log("[mission-seal-handler] Skipped: session recovering");
|
|
19288
19286
|
return;
|
|
19289
19287
|
}
|
|
19290
19288
|
const sealDetected = await detectSealInSession(client, sessionID);
|
|
19291
19289
|
if (sealDetected) {
|
|
19292
|
-
|
|
19290
|
+
log("[mission-seal-handler] Seal detected before injection, completing");
|
|
19293
19291
|
await handleSealDetected(client, directory, loopState);
|
|
19294
19292
|
return;
|
|
19295
19293
|
}
|
|
@@ -19301,18 +19299,18 @@ async function injectContinuation2(client, directory, sessionID, loopState) {
|
|
|
19301
19299
|
parts: [{ type: PART_TYPES.TEXT, text: prompt }]
|
|
19302
19300
|
}
|
|
19303
19301
|
});
|
|
19304
|
-
|
|
19302
|
+
log("[mission-seal-handler] Continuation injected", {
|
|
19305
19303
|
sessionID,
|
|
19306
19304
|
iteration: loopState.iteration
|
|
19307
19305
|
});
|
|
19308
19306
|
} catch (error45) {
|
|
19309
|
-
|
|
19307
|
+
log(`[mission-seal-handler] Failed to inject: ${error45}`);
|
|
19310
19308
|
}
|
|
19311
19309
|
}
|
|
19312
19310
|
async function handleSealDetected(client, directory, loopState) {
|
|
19313
19311
|
clearLoopState(directory);
|
|
19314
19312
|
await showSealedToast(client, loopState);
|
|
19315
|
-
|
|
19313
|
+
log("[mission-seal-handler] Mission sealed!", {
|
|
19316
19314
|
sessionID: loopState.sessionID,
|
|
19317
19315
|
iterations: loopState.iteration
|
|
19318
19316
|
});
|
|
@@ -19320,7 +19318,7 @@ async function handleSealDetected(client, directory, loopState) {
|
|
|
19320
19318
|
async function handleMaxIterations(client, directory, loopState) {
|
|
19321
19319
|
clearLoopState(directory);
|
|
19322
19320
|
await showMaxIterationsToast(client, loopState);
|
|
19323
|
-
|
|
19321
|
+
log("[mission-seal-handler] Max iterations reached", {
|
|
19324
19322
|
sessionID: loopState.sessionID,
|
|
19325
19323
|
iterations: loopState.iteration,
|
|
19326
19324
|
max: loopState.maxIterations
|
|
@@ -19338,11 +19336,11 @@ async function handleMissionSealIdle(client, directory, sessionID, mainSessionID
|
|
|
19338
19336
|
return;
|
|
19339
19337
|
}
|
|
19340
19338
|
if (isSessionRecovering(sessionID)) {
|
|
19341
|
-
|
|
19339
|
+
log("[mission-seal-handler] Skipped: recovering");
|
|
19342
19340
|
return;
|
|
19343
19341
|
}
|
|
19344
19342
|
if (hasRunningBackgroundTasks2(sessionID)) {
|
|
19345
|
-
|
|
19343
|
+
log("[mission-seal-handler] Skipped: background tasks");
|
|
19346
19344
|
return;
|
|
19347
19345
|
}
|
|
19348
19346
|
const loopState = readLoopState(directory);
|
|
@@ -19352,7 +19350,7 @@ async function handleMissionSealIdle(client, directory, sessionID, mainSessionID
|
|
|
19352
19350
|
if (loopState.sessionID !== sessionID) {
|
|
19353
19351
|
return;
|
|
19354
19352
|
}
|
|
19355
|
-
|
|
19353
|
+
log("[mission-seal-handler] Checking for seal", {
|
|
19356
19354
|
sessionID,
|
|
19357
19355
|
iteration: loopState.iteration
|
|
19358
19356
|
});
|
|
@@ -19367,7 +19365,7 @@ async function handleMissionSealIdle(client, directory, sessionID, mainSessionID
|
|
|
19367
19365
|
}
|
|
19368
19366
|
const newState = incrementIteration(directory);
|
|
19369
19367
|
if (!newState) {
|
|
19370
|
-
|
|
19368
|
+
log("[mission-seal-handler] Failed to increment iteration");
|
|
19371
19369
|
return;
|
|
19372
19370
|
}
|
|
19373
19371
|
await showCountdownToast2(client, COUNTDOWN_SECONDS2, newState.iteration, newState.maxIterations);
|
|
@@ -19375,7 +19373,7 @@ async function handleMissionSealIdle(client, directory, sessionID, mainSessionID
|
|
|
19375
19373
|
cancelCountdown2(sessionID);
|
|
19376
19374
|
await injectContinuation2(client, directory, sessionID, newState);
|
|
19377
19375
|
}, COUNTDOWN_SECONDS2 * 1e3);
|
|
19378
|
-
|
|
19376
|
+
log("[mission-seal-handler] Countdown started", {
|
|
19379
19377
|
sessionID,
|
|
19380
19378
|
iteration: newState.iteration,
|
|
19381
19379
|
seconds: COUNTDOWN_SECONDS2
|
|
@@ -19384,7 +19382,7 @@ async function handleMissionSealIdle(client, directory, sessionID, mainSessionID
|
|
|
19384
19382
|
function handleUserMessage2(sessionID) {
|
|
19385
19383
|
const state2 = getState3(sessionID);
|
|
19386
19384
|
if (state2.countdownTimer) {
|
|
19387
|
-
|
|
19385
|
+
log("[mission-seal-handler] Cancelled by user interaction");
|
|
19388
19386
|
cancelCountdown2(sessionID);
|
|
19389
19387
|
}
|
|
19390
19388
|
state2.isAborting = false;
|
|
@@ -19393,7 +19391,7 @@ function handleAbort(sessionID) {
|
|
|
19393
19391
|
const state2 = getState3(sessionID);
|
|
19394
19392
|
state2.isAborting = true;
|
|
19395
19393
|
cancelCountdown2(sessionID);
|
|
19396
|
-
|
|
19394
|
+
log("[mission-seal-handler] Marked as aborting");
|
|
19397
19395
|
}
|
|
19398
19396
|
function cleanupSession2(sessionID) {
|
|
19399
19397
|
cancelCountdown2(sessionID);
|
|
@@ -19408,7 +19406,7 @@ function cleanupSession3(sessionID) {
|
|
|
19408
19406
|
clearInterval(state2.intervalId);
|
|
19409
19407
|
}
|
|
19410
19408
|
sessionStates3.delete(sessionID);
|
|
19411
|
-
|
|
19409
|
+
log("[context-window-monitor] Session cleaned up", { sessionID });
|
|
19412
19410
|
}
|
|
19413
19411
|
|
|
19414
19412
|
// src/plugin-handlers/event-handler.ts
|
|
@@ -19423,7 +19421,7 @@ function createEventHandler(ctx) {
|
|
|
19423
19421
|
}
|
|
19424
19422
|
if (event.type === SESSION_EVENTS.CREATED) {
|
|
19425
19423
|
const sessionID = event.properties?.id || "";
|
|
19426
|
-
|
|
19424
|
+
log("[event-handler] session.created", { sessionID });
|
|
19427
19425
|
presets.missionStarted(`Session ${sessionID.slice(0, 12)}...`);
|
|
19428
19426
|
}
|
|
19429
19427
|
if (event.type === SESSION_EVENTS.DELETED) {
|
|
@@ -19432,7 +19430,7 @@ function createEventHandler(ctx) {
|
|
|
19432
19430
|
if (session) {
|
|
19433
19431
|
const totalTime = Date.now() - session.startTime;
|
|
19434
19432
|
const duration3 = totalTime < 6e4 ? `${Math.round(totalTime / 1e3)}s` : `${Math.round(totalTime / 6e4)}m`;
|
|
19435
|
-
|
|
19433
|
+
log("[event-handler] session.deleted", { sessionID, steps: session.step, duration: duration3 });
|
|
19436
19434
|
sessions.delete(sessionID);
|
|
19437
19435
|
state2.sessions.delete(sessionID);
|
|
19438
19436
|
clearSession(sessionID);
|
|
@@ -19446,7 +19444,7 @@ function createEventHandler(ctx) {
|
|
|
19446
19444
|
if (event.type === SESSION_EVENTS.ERROR) {
|
|
19447
19445
|
const sessionID = event.properties?.sessionId || event.properties?.sessionID || "";
|
|
19448
19446
|
const error45 = event.properties?.error;
|
|
19449
|
-
|
|
19447
|
+
log("[event-handler] session.error", { sessionID, error: error45 });
|
|
19450
19448
|
if (sessionID) {
|
|
19451
19449
|
handleSessionError2(sessionID, error45);
|
|
19452
19450
|
handleAbort(sessionID);
|
|
@@ -19459,7 +19457,7 @@ function createEventHandler(ctx) {
|
|
|
19459
19457
|
event.properties
|
|
19460
19458
|
);
|
|
19461
19459
|
if (recovered) {
|
|
19462
|
-
|
|
19460
|
+
log("[event-handler] auto-recovery initiated", { sessionID });
|
|
19463
19461
|
return;
|
|
19464
19462
|
}
|
|
19465
19463
|
}
|
|
@@ -19491,13 +19489,13 @@ function createEventHandler(ctx) {
|
|
|
19491
19489
|
directory,
|
|
19492
19490
|
sessionID,
|
|
19493
19491
|
sessionID
|
|
19494
|
-
).catch((err) =>
|
|
19492
|
+
).catch((err) => log("[event-handler] mission-seal-handler error", err));
|
|
19495
19493
|
} else {
|
|
19496
19494
|
await handleSessionIdle(
|
|
19497
19495
|
client,
|
|
19498
19496
|
sessionID,
|
|
19499
19497
|
sessionID
|
|
19500
|
-
).catch((err) =>
|
|
19498
|
+
).catch((err) => log("[event-handler] todo-continuation error", err));
|
|
19501
19499
|
}
|
|
19502
19500
|
}
|
|
19503
19501
|
}, 500);
|
|
@@ -19615,7 +19613,7 @@ function createChatMessageHandler(ctx) {
|
|
|
19615
19613
|
const parsed = detectSlashCommand(originalText);
|
|
19616
19614
|
const sessionID = msgInput.sessionID;
|
|
19617
19615
|
const agentName = (msgInput.agent || "").toLowerCase();
|
|
19618
|
-
|
|
19616
|
+
log("[chat-message-handler] hook triggered", { sessionID, agent: agentName, textLength: originalText.length });
|
|
19619
19617
|
if (sessionID) {
|
|
19620
19618
|
handleUserMessage(sessionID);
|
|
19621
19619
|
}
|
|
@@ -19648,7 +19646,7 @@ function createChatMessageHandler(ctx) {
|
|
|
19648
19646
|
userMessage || PROMPTS.CONTINUE
|
|
19649
19647
|
);
|
|
19650
19648
|
startMissionLoop(directory, sessionID, userMessage || originalText);
|
|
19651
|
-
|
|
19649
|
+
log("[chat-message-handler] Auto-applied mission mode + started loop", { originalLength: originalText.length });
|
|
19652
19650
|
}
|
|
19653
19651
|
}
|
|
19654
19652
|
if (parsed) {
|
|
@@ -19678,14 +19676,14 @@ function createChatMessageHandler(ctx) {
|
|
|
19678
19676
|
anomalyCount: 0
|
|
19679
19677
|
});
|
|
19680
19678
|
startSession(sessionID);
|
|
19681
|
-
|
|
19679
|
+
log("[chat-message-handler] Session registered for /task command", { sessionID, agent: agentName });
|
|
19682
19680
|
}
|
|
19683
19681
|
parts[textPartIndex].text = command.template.replace(
|
|
19684
19682
|
/\$ARGUMENTS/g,
|
|
19685
19683
|
parsed.args || PROMPTS.CONTINUE
|
|
19686
19684
|
);
|
|
19687
19685
|
startMissionLoop(directory, sessionID, parsed.args || "continue from where we left off");
|
|
19688
|
-
|
|
19686
|
+
log("[chat-message-handler] /task command: started mission loop", { sessionID, args: parsed.args?.slice(0, 50) });
|
|
19689
19687
|
}
|
|
19690
19688
|
}
|
|
19691
19689
|
};
|
|
@@ -19958,7 +19956,7 @@ function createAssistantDoneHandler(ctx) {
|
|
|
19958
19956
|
state.missionActive = false;
|
|
19959
19957
|
clearLoopState(directory);
|
|
19960
19958
|
presets.missionComplete("Mission Sealed - Explicit completion confirmed");
|
|
19961
|
-
|
|
19959
|
+
log("[assistant-done-handler] Mission sealed detected", { sessionID });
|
|
19962
19960
|
clearSession(sessionID);
|
|
19963
19961
|
sessions.delete(sessionID);
|
|
19964
19962
|
state.sessions.delete(sessionID);
|
|
@@ -19999,7 +19997,7 @@ function createAssistantDoneHandler(ctx) {
|
|
|
19999
19997
|
});
|
|
20000
19998
|
}
|
|
20001
19999
|
} catch (error45) {
|
|
20002
|
-
|
|
20000
|
+
log("[assistant-done-handler] Continuation injection failed, retrying...", { sessionID, error: error45 });
|
|
20003
20001
|
try {
|
|
20004
20002
|
await new Promise((r) => setTimeout(r, 500));
|
|
20005
20003
|
if (client?.session?.prompt) {
|
|
@@ -20009,13 +20007,13 @@ function createAssistantDoneHandler(ctx) {
|
|
|
20009
20007
|
});
|
|
20010
20008
|
}
|
|
20011
20009
|
} catch (retryError) {
|
|
20012
|
-
|
|
20010
|
+
log("[assistant-done-handler] Both continuation attempts failed, waiting for idle handler", {
|
|
20013
20011
|
sessionID,
|
|
20014
20012
|
error: retryError,
|
|
20015
20013
|
loopActive: isLoopActive(directory, sessionID)
|
|
20016
20014
|
});
|
|
20017
20015
|
if (!isLoopActive(directory, sessionID)) {
|
|
20018
|
-
|
|
20016
|
+
log("[assistant-done-handler] No active loop, stopping session", { sessionID });
|
|
20019
20017
|
session.active = false;
|
|
20020
20018
|
state.missionActive = false;
|
|
20021
20019
|
}
|
|
@@ -20029,16 +20027,16 @@ var require2 = createRequire(import.meta.url);
|
|
|
20029
20027
|
var { version: PLUGIN_VERSION } = require2("../package.json");
|
|
20030
20028
|
var OrchestratorPlugin = async (input) => {
|
|
20031
20029
|
const { directory, client } = input;
|
|
20032
|
-
|
|
20033
|
-
|
|
20030
|
+
log(`[orchestrator] v${PLUGIN_VERSION} loaded, log: ${getLogPath()}`);
|
|
20031
|
+
log("[index.ts] Plugin initialized", { version: PLUGIN_VERSION, directory });
|
|
20034
20032
|
initToastClient(client);
|
|
20035
20033
|
const taskToastManager = initTaskToastManager(client);
|
|
20036
|
-
|
|
20034
|
+
log("[index.ts] Toast notifications enabled with TUI and TaskToastManager");
|
|
20037
20035
|
const sessions = /* @__PURE__ */ new Map();
|
|
20038
20036
|
const parallelAgentManager2 = ParallelAgentManager.getInstance(client, directory);
|
|
20039
20037
|
const asyncAgentTools = createAsyncAgentTools(parallelAgentManager2, client);
|
|
20040
20038
|
taskToastManager.setConcurrencyController(parallelAgentManager2.getConcurrency());
|
|
20041
|
-
|
|
20039
|
+
log("[index.ts] ParallelAgentManager initialized with TaskToastManager integration");
|
|
20042
20040
|
const handlerContext = {
|
|
20043
20041
|
client,
|
|
20044
20042
|
directory,
|
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.9.
|
|
5
|
+
"version": "0.9.70",
|
|
6
6
|
"author": "agnusdei1207",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
"build": "shx rm -rf dist && npx esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --format=esm && tsc --emitDeclarationOnly && shx mkdir -p dist/scripts && npx esbuild scripts/postinstall.ts --bundle --outfile=dist/scripts/postinstall.js --platform=node --format=esm && npx esbuild scripts/preuninstall.ts --bundle --outfile=dist/scripts/preuninstall.js --platform=node --format=esm",
|
|
52
52
|
"build:all": "npm run build && npm run rust:dist",
|
|
53
53
|
"test": "vitest run --reporter=verbose",
|
|
54
|
-
"test:watch": "vitest",
|
|
55
54
|
"test:coverage": "vitest run --coverage",
|
|
56
55
|
"test:unit": "vitest run tests/unit --reporter=verbose",
|
|
57
56
|
"test:e2e": "vitest run tests/e2e --reporter=verbose",
|
|
@@ -62,11 +61,6 @@
|
|
|
62
61
|
"release:patch": "npm run build && npm run rust:dist && npm version patch && git push --follow-tags && npm publish --access public",
|
|
63
62
|
"release:minor": "npm run build && npm run rust:dist && npm version minor && git push --follow-tags && npm publish --access public",
|
|
64
63
|
"release:major": "npm run build && npm run rust:dist && npm version major && git push --follow-tags && npm publish --access public",
|
|
65
|
-
"dev:clean": "shx rm -rf $(npm root -g)/opencode-orchestrator /opt/homebrew/lib/node_modules/opencode-orchestrator 2>/dev/null; npm cache clean --force && echo 'Cleaned: NVM + Homebrew + cache'",
|
|
66
|
-
"dev:link": "npm run build && npm link && /opt/homebrew/bin/npm link 2>/dev/null; echo 'SUCCESS: Linked to NVM + Homebrew. Restart OpenCode.'",
|
|
67
|
-
"dev:unlink": "npm unlink -g opencode-orchestrator 2>/dev/null; /opt/homebrew/bin/npm unlink -g opencode-orchestrator 2>/dev/null; npm run dev:clean && echo 'SUCCESS: Unlinked from all'",
|
|
68
|
-
"dev:status": "echo '=== NVM ===' && ls -la $(npm root -g)/opencode-orchestrator 2>/dev/null || echo 'Not linked'; echo '=== Homebrew ===' && ls -la /opt/homebrew/lib/node_modules/opencode-orchestrator 2>/dev/null || echo 'Not linked'",
|
|
69
|
-
"dev:test": "node dist/scripts/postinstall.js && echo '---' && node dist/scripts/preuninstall.js",
|
|
70
64
|
"reset:local": "brew uninstall opencode 2>/dev/null; rm -rf ~/.config/opencode ~/.opencode ~/.local/share/opencode ~/.cache/opencode/node_modules/opencode-orchestrator && echo '=== Clean done ===' && brew install opencode && echo '{\"plugin\": [\"opencode-orchestrator\"], \"$schema\": \"https://opencode.ai/config.json\"}' > ~/.config/opencode/opencode.json && echo '=== Reset (Dev) complete. Run: opencode ==='",
|
|
71
65
|
"reset:prod": "brew uninstall opencode 2>/dev/null; rm -rf ~/.config/opencode ~/.opencode ~/.local/share/opencode ~/.cache/opencode/node_modules/opencode-orchestrator && echo '=== Clean done ===' && brew install opencode && echo '{\"plugin\": [\"opencode-orchestrator\"], \"$schema\": \"https://opencode.ai/config.json\"}' > ~/.config/opencode/opencode.json && npm install -g opencode-orchestrator && echo '=== Reset (Prod) complete. Run: opencode ==='",
|
|
72
66
|
"ginstall": "npm install -g opencode-orchestrator",
|