opencode-orchestrator 0.5.4 → 0.5.6
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/README.md +21 -23
- package/dist/agents/definitions.d.ts +1 -1
- package/dist/agents/orchestrator.d.ts +1 -1
- package/dist/agents/subagents/architect.d.ts +1 -1
- package/dist/agents/subagents/builder.d.ts +1 -1
- package/dist/agents/subagents/inspector.d.ts +1 -1
- package/dist/agents/subagents/recorder.d.ts +1 -1
- package/dist/core/agents/concurrency.d.ts +36 -0
- package/dist/core/agents/config.d.ts +9 -0
- package/dist/core/agents/format.d.ts +9 -0
- package/dist/core/agents/index.d.ts +7 -0
- package/dist/core/agents/interfaces/index.d.ts +5 -0
- package/dist/core/agents/interfaces/launch-input.d.ts +9 -0
- package/dist/core/agents/interfaces/parallel-task.d.ts +26 -0
- package/dist/core/agents/logger.d.ts +4 -0
- package/dist/core/agents/manager.d.ts +76 -0
- package/dist/core/agents/task-store.d.ts +28 -0
- package/dist/core/agents/types/index.d.ts +4 -0
- package/dist/core/agents/types/parallel-task-status.d.ts +4 -0
- package/dist/core/commands/index.d.ts +6 -0
- package/dist/core/commands/interfaces/background-task.d.ts +20 -0
- package/dist/core/commands/interfaces/index.d.ts +5 -0
- package/dist/core/commands/interfaces/run-background-options.d.ts +9 -0
- package/dist/core/commands/manager.d.ts +27 -0
- package/dist/core/commands/types/background-task-status.d.ts +4 -0
- package/dist/core/commands/types/index.d.ts +4 -0
- package/dist/core/orchestrator/index.d.ts +7 -0
- package/dist/core/orchestrator/interfaces/index.d.ts +5 -0
- package/dist/core/{state.d.ts → orchestrator/interfaces/session-state.d.ts} +4 -7
- package/dist/core/orchestrator/interfaces/task.d.ts +17 -0
- package/dist/core/orchestrator/state.d.ts +10 -0
- package/dist/core/{tasks.d.ts → orchestrator/task-graph.d.ts} +2 -14
- package/dist/core/orchestrator/types/index.d.ts +5 -0
- package/dist/core/orchestrator/types/task-status.d.ts +4 -0
- package/dist/core/orchestrator/types/task-type.d.ts +4 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +588 -714
- package/dist/shared/{contracts/names.d.ts → agent.d.ts} +12 -0
- package/dist/shared/constants.d.ts +56 -0
- package/dist/tools/background-cmd/check.d.ts +14 -0
- package/dist/tools/background-cmd/index.d.ts +7 -0
- package/dist/tools/background-cmd/kill.d.ts +12 -0
- package/dist/tools/background-cmd/list.d.ts +17 -0
- package/dist/tools/background-cmd/run.d.ts +18 -0
- package/dist/tools/callAgent.d.ts +0 -0
- package/dist/tools/parallel/cancel-task.d.ts +13 -0
- package/dist/tools/parallel/delegate-task.d.ts +21 -0
- package/dist/tools/parallel/get-task-result.d.ts +13 -0
- package/dist/tools/parallel/index.d.ts +7 -0
- package/dist/tools/parallel/list-tasks.d.ts +13 -0
- package/dist/tools/rust.d.ts +0 -0
- package/dist/tools/search.d.ts +2 -9
- package/dist/tools/slashCommand.d.ts +0 -0
- package/dist/utils/binary.d.ts +0 -0
- package/dist/utils/common.d.ts +0 -0
- package/package.json +17 -7
- package/dist/agents/coder.d.ts +0 -2
- package/dist/agents/fixer.d.ts +0 -2
- package/dist/agents/names.d.ts +0 -12
- package/dist/agents/planner.d.ts +0 -2
- package/dist/agents/reviewer.d.ts +0 -2
- package/dist/agents/searcher.d.ts +0 -2
- package/dist/agents/subagents/coder.d.ts +0 -2
- package/dist/agents/subagents/executor.d.ts +0 -2
- package/dist/agents/subagents/fixer.d.ts +0 -2
- package/dist/agents/subagents/frontend-designer.d.ts +0 -2
- package/dist/agents/subagents/memory.d.ts +0 -2
- package/dist/agents/subagents/planner.d.ts +0 -2
- package/dist/agents/subagents/publisher.d.ts +0 -2
- package/dist/agents/subagents/reviewer.d.ts +0 -2
- package/dist/agents/subagents/searcher.d.ts +0 -2
- package/dist/agents/subagents/strategist.d.ts +0 -2
- package/dist/agents/subagents/surgeon.d.ts +0 -2
- package/dist/agents/subagents/types.d.ts +0 -7
- package/dist/agents/subagents/visualist.d.ts +0 -2
- package/dist/agents/types.d.ts +0 -7
- package/dist/cli.d.ts +0 -2
- package/dist/constants/agent.d.ts +0 -8
- package/dist/constants/index.d.ts +0 -7
- package/dist/constants/prompts.d.ts +0 -10
- package/dist/constants/task.d.ts +0 -12
- package/dist/constants/time.d.ts +0 -34
- package/dist/context/enforcer.d.ts +0 -47
- package/dist/core/async-agent.d.ts +0 -100
- package/dist/core/background.d.ts +0 -78
- package/dist/core/batch-processor.d.ts +0 -62
- package/dist/core/config.d.ts +0 -55
- package/dist/core/session-manager.d.ts +0 -39
- package/dist/parallel/optimizer.d.ts +0 -47
- package/dist/profiler/execution.d.ts +0 -40
- package/dist/prompts/shared.d.ts +0 -2
- package/dist/shared/contracts/interfaces.d.ts +0 -7
- package/dist/tasks.d.ts +0 -29
- package/dist/tools/async-agent.d.ts +0 -70
- package/dist/tools/background.d.ts +0 -55
- package/dist/tools/batch.d.ts +0 -53
- package/dist/tools/config.d.ts +0 -60
- package/dist/tools/git.d.ts +0 -48
- package/dist/utils/formatting.d.ts +0 -13
- package/dist/utils/index.d.ts +0 -8
- package/dist/utils/task.d.ts +0 -8
package/dist/index.js
CHANGED
|
@@ -4,19 +4,13 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
-
// src/shared/
|
|
7
|
+
// src/shared/agent.ts
|
|
8
8
|
var AGENT_NAMES = {
|
|
9
|
-
// Core Agents (5)
|
|
10
9
|
COMMANDER: "commander",
|
|
11
|
-
// Orchestrator - ReAct loop controller
|
|
12
10
|
ARCHITECT: "architect",
|
|
13
|
-
// Planner + Strategist - Plan-and-Execute
|
|
14
11
|
BUILDER: "builder",
|
|
15
|
-
// Coder + Visualist combined (full-stack)
|
|
16
12
|
INSPECTOR: "inspector",
|
|
17
|
-
// Reviewer + Fixer combined (quality + fix)
|
|
18
13
|
RECORDER: "recorder"
|
|
19
|
-
// Persistent context - saves/loads session state
|
|
20
14
|
};
|
|
21
15
|
|
|
22
16
|
// src/agents/orchestrator.ts
|
|
@@ -33,13 +27,6 @@ You are Commander. Complete missions autonomously. Never stop until done.
|
|
|
33
27
|
3. Never stop because agent returned nothing
|
|
34
28
|
4. Always survey environment & codebase BEFORE coding
|
|
35
29
|
5. Always verify with evidence based on runtime context
|
|
36
|
-
6. LANGUAGE:
|
|
37
|
-
- THINK and REASON in English for maximum stability
|
|
38
|
-
- FINAL REPORT: Detect the user's language from their request and respond in the SAME language
|
|
39
|
-
- If user writes in Korean \u2192 Report in Korean
|
|
40
|
-
- If user writes in English \u2192 Report in English
|
|
41
|
-
- If user writes in Japanese \u2192 Report in Japanese
|
|
42
|
-
- Default to English if language is unclear
|
|
43
30
|
</core_rules>
|
|
44
31
|
|
|
45
32
|
<phase_0 name="TRIAGE">
|
|
@@ -229,7 +216,7 @@ You are Architect. Break complex tasks into atomic pieces.
|
|
|
229
216
|
</role>
|
|
230
217
|
|
|
231
218
|
<constraints>
|
|
232
|
-
|
|
219
|
+
|
|
233
220
|
If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
234
221
|
</constraints>
|
|
235
222
|
|
|
@@ -291,7 +278,7 @@ You are Builder. Write code that works.
|
|
|
291
278
|
</role>
|
|
292
279
|
|
|
293
280
|
<constraints>
|
|
294
|
-
|
|
281
|
+
|
|
295
282
|
If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
296
283
|
</constraints>
|
|
297
284
|
|
|
@@ -357,7 +344,7 @@ You are Inspector. Prove failure or success with evidence.
|
|
|
357
344
|
</role>
|
|
358
345
|
|
|
359
346
|
<constraints>
|
|
360
|
-
|
|
347
|
+
|
|
361
348
|
If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
362
349
|
</constraints>
|
|
363
350
|
|
|
@@ -424,7 +411,7 @@ You are Recorder. Save and load work progress.
|
|
|
424
411
|
</role>
|
|
425
412
|
|
|
426
413
|
<constraints>
|
|
427
|
-
|
|
414
|
+
|
|
428
415
|
If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
|
|
429
416
|
</constraints>
|
|
430
417
|
|
|
@@ -491,7 +478,7 @@ var AGENTS = {
|
|
|
491
478
|
[AGENT_NAMES.RECORDER]: recorder
|
|
492
479
|
};
|
|
493
480
|
|
|
494
|
-
// src/core/
|
|
481
|
+
// src/core/orchestrator/task-graph.ts
|
|
495
482
|
var TaskGraph = class _TaskGraph {
|
|
496
483
|
tasks = /* @__PURE__ */ new Map();
|
|
497
484
|
constructor(tasks) {
|
|
@@ -532,7 +519,7 @@ var TaskGraph = class _TaskGraph {
|
|
|
532
519
|
const notCompleted = tasks.filter((t) => t.status !== "completed");
|
|
533
520
|
let summary = "\u{1F4CB} **Mission Status**\n";
|
|
534
521
|
if (completed.length > 0) {
|
|
535
|
-
summary += `\u2705 Completed: ${completed.length} tasks
|
|
522
|
+
summary += `\u2705 Completed: ${completed.length} tasks
|
|
536
523
|
`;
|
|
537
524
|
}
|
|
538
525
|
for (const task of notCompleted) {
|
|
@@ -549,18 +536,16 @@ var TaskGraph = class _TaskGraph {
|
|
|
549
536
|
try {
|
|
550
537
|
const tasks = JSON.parse(json2);
|
|
551
538
|
return new _TaskGraph(tasks);
|
|
552
|
-
} catch
|
|
553
|
-
console.error("Failed to parse TaskGraph JSON:", e);
|
|
539
|
+
} catch {
|
|
554
540
|
return new _TaskGraph();
|
|
555
541
|
}
|
|
556
542
|
}
|
|
557
543
|
};
|
|
558
544
|
|
|
559
|
-
// src/core/state.ts
|
|
545
|
+
// src/core/orchestrator/state.ts
|
|
560
546
|
var state = {
|
|
561
547
|
missionActive: false,
|
|
562
548
|
maxIterations: 1e3,
|
|
563
|
-
// Effectively infinite - "Relentless" mode
|
|
564
549
|
maxRetries: 3,
|
|
565
550
|
sessions: /* @__PURE__ */ new Map()
|
|
566
551
|
};
|
|
@@ -13068,10 +13053,6 @@ var COMMANDS = {
|
|
|
13068
13053
|
You are Commander. Complete this mission. Never stop until 100% done.
|
|
13069
13054
|
</role>
|
|
13070
13055
|
|
|
13071
|
-
<constraints>
|
|
13072
|
-
Reasoning MUST be in English for model stability. Final report in Korean.
|
|
13073
|
-
</constraints>
|
|
13074
|
-
|
|
13075
13056
|
<phase_1 name="MANDATORY_ENVIRONMENT_SCAN">
|
|
13076
13057
|
Before any planning or coding, you MUST understand:
|
|
13077
13058
|
1. INFRA: OS-native? Container? Docker-compose? Volume-mounted?
|
|
@@ -13289,44 +13270,71 @@ var globSearchTool = (directory) => tool({
|
|
|
13289
13270
|
}
|
|
13290
13271
|
});
|
|
13291
13272
|
var mgrepTool = (directory) => tool({
|
|
13292
|
-
description: `Search multiple patterns
|
|
13293
|
-
|
|
13294
|
-
<purpose>
|
|
13295
|
-
Search for multiple regex patterns simultaneously using Rust's parallel execution.
|
|
13296
|
-
Much faster than running grep multiple times sequentially.
|
|
13297
|
-
</purpose>
|
|
13298
|
-
|
|
13299
|
-
<examples>
|
|
13300
|
-
- patterns: ["useState", "useEffect", "useContext"] \u2192 Find all React hooks usage
|
|
13301
|
-
- patterns: ["TODO", "FIXME", "HACK"] \u2192 Find all code annotations
|
|
13302
|
-
- patterns: ["import.*lodash", "require.*lodash"] \u2192 Find all lodash imports
|
|
13303
|
-
</examples>
|
|
13304
|
-
|
|
13305
|
-
<output>
|
|
13306
|
-
Returns matches grouped by pattern, with file paths and line numbers.
|
|
13307
|
-
</output>`,
|
|
13273
|
+
description: `Search multiple patterns (runs grep for each pattern).`,
|
|
13308
13274
|
args: {
|
|
13309
|
-
patterns: tool.schema.array(tool.schema.string()).describe("Array of regex patterns
|
|
13310
|
-
dir: tool.schema.string().optional().describe("Directory
|
|
13311
|
-
max_results_per_pattern: tool.schema.number().optional().describe("Max results per pattern (default: 50)")
|
|
13275
|
+
patterns: tool.schema.array(tool.schema.string()).describe("Array of regex patterns"),
|
|
13276
|
+
dir: tool.schema.string().optional().describe("Directory (defaults to project root)")
|
|
13312
13277
|
},
|
|
13313
13278
|
async execute(args) {
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13279
|
+
const results = {};
|
|
13280
|
+
const dir = args.dir || directory;
|
|
13281
|
+
for (const pattern of args.patterns) {
|
|
13282
|
+
const result = await callRustTool("grep_search", { pattern, directory: dir });
|
|
13283
|
+
results[pattern] = result;
|
|
13284
|
+
}
|
|
13285
|
+
return JSON.stringify(results, null, 2);
|
|
13319
13286
|
}
|
|
13320
13287
|
});
|
|
13321
13288
|
|
|
13322
|
-
// src/core/
|
|
13289
|
+
// src/core/commands/manager.ts
|
|
13323
13290
|
import { spawn as spawn2 } from "child_process";
|
|
13324
13291
|
import { randomBytes } from "crypto";
|
|
13292
|
+
|
|
13293
|
+
// src/shared/constants.ts
|
|
13294
|
+
var TIME = {
|
|
13295
|
+
SECOND: 1e3,
|
|
13296
|
+
MINUTE: 60 * 1e3,
|
|
13297
|
+
HOUR: 60 * 60 * 1e3
|
|
13298
|
+
};
|
|
13299
|
+
var ID_PREFIX = {
|
|
13300
|
+
/** Parallel agent task ID (e.g., task_a1b2c3d4) */
|
|
13301
|
+
TASK: "task_",
|
|
13302
|
+
/** Background command job ID (e.g., job_a1b2c3d4) */
|
|
13303
|
+
JOB: "job_",
|
|
13304
|
+
/** Session ID prefix */
|
|
13305
|
+
SESSION: "session_"
|
|
13306
|
+
};
|
|
13307
|
+
var PARALLEL_TASK = {
|
|
13308
|
+
TTL_MS: 30 * TIME.MINUTE,
|
|
13309
|
+
CLEANUP_DELAY_MS: 5 * TIME.MINUTE,
|
|
13310
|
+
MIN_STABILITY_MS: 5 * TIME.SECOND,
|
|
13311
|
+
POLL_INTERVAL_MS: 2e3,
|
|
13312
|
+
DEFAULT_CONCURRENCY: 3,
|
|
13313
|
+
MAX_CONCURRENCY: 10
|
|
13314
|
+
};
|
|
13315
|
+
var BACKGROUND_TASK = {
|
|
13316
|
+
DEFAULT_TIMEOUT_MS: 5 * TIME.MINUTE,
|
|
13317
|
+
MAX_OUTPUT_LENGTH: 1e4
|
|
13318
|
+
};
|
|
13319
|
+
var STATUS_EMOJI = {
|
|
13320
|
+
pending: "\u23F3",
|
|
13321
|
+
running: "\u{1F504}",
|
|
13322
|
+
completed: "\u2705",
|
|
13323
|
+
done: "\u2705",
|
|
13324
|
+
error: "\u274C",
|
|
13325
|
+
timeout: "\u23F0",
|
|
13326
|
+
cancelled: "\u{1F6AB}"
|
|
13327
|
+
};
|
|
13328
|
+
function getStatusEmoji(status) {
|
|
13329
|
+
return STATUS_EMOJI[status] ?? "\u2753";
|
|
13330
|
+
}
|
|
13331
|
+
|
|
13332
|
+
// src/core/commands/manager.ts
|
|
13325
13333
|
var BackgroundTaskManager = class _BackgroundTaskManager {
|
|
13326
13334
|
static _instance;
|
|
13327
13335
|
tasks = /* @__PURE__ */ new Map();
|
|
13328
|
-
debugMode = true;
|
|
13329
|
-
//
|
|
13336
|
+
debugMode = process.env.DEBUG_BG_TASK === "true";
|
|
13337
|
+
// Disabled by default
|
|
13330
13338
|
constructor() {
|
|
13331
13339
|
}
|
|
13332
13340
|
static get instance() {
|
|
@@ -13335,25 +13343,15 @@ var BackgroundTaskManager = class _BackgroundTaskManager {
|
|
|
13335
13343
|
}
|
|
13336
13344
|
return _BackgroundTaskManager._instance;
|
|
13337
13345
|
}
|
|
13338
|
-
/**
|
|
13339
|
-
* Generate a unique task ID in the format job_xxxxxxxx
|
|
13340
|
-
*/
|
|
13341
13346
|
generateId() {
|
|
13342
|
-
|
|
13343
|
-
return `job_${hex3}`;
|
|
13347
|
+
return `${ID_PREFIX.JOB}${randomBytes(4).toString("hex")}`;
|
|
13344
13348
|
}
|
|
13345
|
-
/**
|
|
13346
|
-
* Debug logging helper
|
|
13347
|
-
*/
|
|
13348
13349
|
debug(taskId, message) {
|
|
13349
13350
|
if (this.debugMode) {
|
|
13350
|
-
const
|
|
13351
|
-
console.log(`[BG
|
|
13351
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
|
|
13352
|
+
console.log(`[BG ${ts}] ${taskId}: ${message}`);
|
|
13352
13353
|
}
|
|
13353
13354
|
}
|
|
13354
|
-
/**
|
|
13355
|
-
* Run a command in the background
|
|
13356
|
-
*/
|
|
13357
13355
|
run(options) {
|
|
13358
13356
|
const id = this.generateId();
|
|
13359
13357
|
const { command, cwd = process.cwd(), timeout = 3e5, label } = options;
|
|
@@ -13374,7 +13372,7 @@ var BackgroundTaskManager = class _BackgroundTaskManager {
|
|
|
13374
13372
|
timeout
|
|
13375
13373
|
};
|
|
13376
13374
|
this.tasks.set(id, task);
|
|
13377
|
-
this.debug(id, `Starting: ${command}
|
|
13375
|
+
this.debug(id, `Starting: ${command}`);
|
|
13378
13376
|
try {
|
|
13379
13377
|
const proc = spawn2(shell, task.args, {
|
|
13380
13378
|
cwd,
|
|
@@ -13383,22 +13381,17 @@ var BackgroundTaskManager = class _BackgroundTaskManager {
|
|
|
13383
13381
|
});
|
|
13384
13382
|
task.process = proc;
|
|
13385
13383
|
proc.stdout?.on("data", (data) => {
|
|
13386
|
-
|
|
13387
|
-
task.output += text;
|
|
13388
|
-
this.debug(id, `stdout: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`);
|
|
13384
|
+
task.output += data.toString();
|
|
13389
13385
|
});
|
|
13390
13386
|
proc.stderr?.on("data", (data) => {
|
|
13391
|
-
|
|
13392
|
-
task.errorOutput += text;
|
|
13393
|
-
this.debug(id, `stderr: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`);
|
|
13387
|
+
task.errorOutput += data.toString();
|
|
13394
13388
|
});
|
|
13395
13389
|
proc.on("close", (code) => {
|
|
13396
13390
|
task.exitCode = code;
|
|
13397
13391
|
task.endTime = Date.now();
|
|
13398
13392
|
task.status = code === 0 ? "done" : "error";
|
|
13399
13393
|
task.process = void 0;
|
|
13400
|
-
|
|
13401
|
-
this.debug(id, `Completed with code ${code} in ${duration3}s`);
|
|
13394
|
+
this.debug(id, `Done (code=${code})`);
|
|
13402
13395
|
});
|
|
13403
13396
|
proc.on("error", (err) => {
|
|
13404
13397
|
task.status = "error";
|
|
@@ -13406,47 +13399,31 @@ var BackgroundTaskManager = class _BackgroundTaskManager {
|
|
|
13406
13399
|
Process error: ${err.message}`;
|
|
13407
13400
|
task.endTime = Date.now();
|
|
13408
13401
|
task.process = void 0;
|
|
13409
|
-
this.debug(id, `Error: ${err.message}`);
|
|
13410
13402
|
});
|
|
13411
13403
|
setTimeout(() => {
|
|
13412
13404
|
if (task.status === "running" && task.process) {
|
|
13413
|
-
this.debug(id, `Timeout after ${timeout}ms, killing process`);
|
|
13414
13405
|
task.process.kill("SIGKILL");
|
|
13415
13406
|
task.status = "timeout";
|
|
13416
13407
|
task.endTime = Date.now();
|
|
13417
|
-
|
|
13418
|
-
Process killed: timeout after ${timeout}ms`;
|
|
13408
|
+
this.debug(id, "Timeout");
|
|
13419
13409
|
}
|
|
13420
13410
|
}, timeout);
|
|
13421
13411
|
} catch (err) {
|
|
13422
13412
|
task.status = "error";
|
|
13423
|
-
task.errorOutput = `
|
|
13413
|
+
task.errorOutput = `Spawn failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
13424
13414
|
task.endTime = Date.now();
|
|
13425
|
-
this.debug(id, `Spawn failed: ${task.errorOutput}`);
|
|
13426
13415
|
}
|
|
13427
13416
|
return task;
|
|
13428
13417
|
}
|
|
13429
|
-
/**
|
|
13430
|
-
* Get task by ID
|
|
13431
|
-
*/
|
|
13432
13418
|
get(taskId) {
|
|
13433
13419
|
return this.tasks.get(taskId);
|
|
13434
13420
|
}
|
|
13435
|
-
/**
|
|
13436
|
-
* Get all tasks
|
|
13437
|
-
*/
|
|
13438
13421
|
getAll() {
|
|
13439
13422
|
return Array.from(this.tasks.values());
|
|
13440
13423
|
}
|
|
13441
|
-
/**
|
|
13442
|
-
* Get tasks by status
|
|
13443
|
-
*/
|
|
13444
13424
|
getByStatus(status) {
|
|
13445
13425
|
return this.getAll().filter((t) => t.status === status);
|
|
13446
13426
|
}
|
|
13447
|
-
/**
|
|
13448
|
-
* Clear completed/failed tasks
|
|
13449
|
-
*/
|
|
13450
13427
|
clearCompleted() {
|
|
13451
13428
|
let count = 0;
|
|
13452
13429
|
for (const [id, task] of this.tasks) {
|
|
@@ -13457,9 +13434,6 @@ Process killed: timeout after ${timeout}ms`;
|
|
|
13457
13434
|
}
|
|
13458
13435
|
return count;
|
|
13459
13436
|
}
|
|
13460
|
-
/**
|
|
13461
|
-
* Kill a running task
|
|
13462
|
-
*/
|
|
13463
13437
|
kill(taskId) {
|
|
13464
13438
|
const task = this.tasks.get(taskId);
|
|
13465
13439
|
if (task?.process) {
|
|
@@ -13467,72 +13441,35 @@ Process killed: timeout after ${timeout}ms`;
|
|
|
13467
13441
|
task.status = "error";
|
|
13468
13442
|
task.errorOutput += "\nKilled by user";
|
|
13469
13443
|
task.endTime = Date.now();
|
|
13470
|
-
this.debug(taskId, "Killed by user");
|
|
13471
13444
|
return true;
|
|
13472
13445
|
}
|
|
13473
13446
|
return false;
|
|
13474
13447
|
}
|
|
13475
|
-
/**
|
|
13476
|
-
* Format duration for display
|
|
13477
|
-
*/
|
|
13478
13448
|
formatDuration(task) {
|
|
13479
13449
|
const end = task.endTime || Date.now();
|
|
13480
13450
|
const seconds = (end - task.startTime) / 1e3;
|
|
13481
|
-
if (seconds < 60) {
|
|
13482
|
-
|
|
13483
|
-
}
|
|
13484
|
-
const minutes = Math.floor(seconds / 60);
|
|
13485
|
-
const remainingSeconds = seconds % 60;
|
|
13486
|
-
return `${minutes}m ${remainingSeconds.toFixed(0)}s`;
|
|
13451
|
+
if (seconds < 60) return `${seconds.toFixed(1)}s`;
|
|
13452
|
+
return `${Math.floor(seconds / 60)}m ${(seconds % 60).toFixed(0)}s`;
|
|
13487
13453
|
}
|
|
13488
|
-
/**
|
|
13489
|
-
* Get status emoji
|
|
13490
|
-
*/
|
|
13491
13454
|
getStatusEmoji(status) {
|
|
13492
|
-
|
|
13493
|
-
case "pending":
|
|
13494
|
-
return "\u23F8\uFE0F";
|
|
13495
|
-
case "running":
|
|
13496
|
-
return "\u23F3";
|
|
13497
|
-
case "done":
|
|
13498
|
-
return "\u2705";
|
|
13499
|
-
case "error":
|
|
13500
|
-
return "\u274C";
|
|
13501
|
-
case "timeout":
|
|
13502
|
-
return "\u23F0";
|
|
13503
|
-
default:
|
|
13504
|
-
return "\u2753";
|
|
13505
|
-
}
|
|
13455
|
+
return getStatusEmoji(status);
|
|
13506
13456
|
}
|
|
13507
13457
|
};
|
|
13508
13458
|
var backgroundTaskManager = BackgroundTaskManager.instance;
|
|
13509
13459
|
|
|
13510
|
-
// src/tools/background.ts
|
|
13460
|
+
// src/tools/background-cmd/run.ts
|
|
13511
13461
|
var runBackgroundTool = tool({
|
|
13512
13462
|
description: `Run a shell command in the background and get a task ID.
|
|
13513
13463
|
|
|
13514
13464
|
<purpose>
|
|
13515
|
-
Execute long-running commands (builds, tests
|
|
13516
|
-
|
|
13517
|
-
</purpose
|
|
13518
|
-
|
|
13519
|
-
<examples>
|
|
13520
|
-
- "npm run build" \u2192 Build project in background
|
|
13521
|
-
- "cargo test" \u2192 Run Rust tests
|
|
13522
|
-
- "sleep 10 && echo done" \u2192 Delayed execution
|
|
13523
|
-
</examples>
|
|
13524
|
-
|
|
13525
|
-
<flow>
|
|
13526
|
-
1. Call run_background with command
|
|
13527
|
-
2. Get task ID immediately (e.g., job_a1b2c3d4)
|
|
13528
|
-
3. Continue other work
|
|
13529
|
-
4. Call check_background with task ID to get results
|
|
13530
|
-
</flow>`,
|
|
13465
|
+
Execute long-running commands (builds, tests) without blocking.
|
|
13466
|
+
Use check_background to get results.
|
|
13467
|
+
</purpose>`,
|
|
13531
13468
|
args: {
|
|
13532
13469
|
command: tool.schema.string().describe("Shell command to execute"),
|
|
13533
|
-
cwd: tool.schema.string().optional().describe("Working directory
|
|
13534
|
-
timeout: tool.schema.number().optional().describe("Timeout in
|
|
13535
|
-
label: tool.schema.string().optional().describe("
|
|
13470
|
+
cwd: tool.schema.string().optional().describe("Working directory"),
|
|
13471
|
+
timeout: tool.schema.number().optional().describe("Timeout in ms (default: 300000)"),
|
|
13472
|
+
label: tool.schema.string().optional().describe("Task label")
|
|
13536
13473
|
},
|
|
13537
13474
|
async execute(args) {
|
|
13538
13475
|
const { command, cwd, timeout, label } = args;
|
|
@@ -13544,36 +13481,20 @@ The command runs asynchronously - use check_background to get results.
|
|
|
13544
13481
|
});
|
|
13545
13482
|
const displayLabel = label ? ` (${label})` : "";
|
|
13546
13483
|
return `\u{1F680} **Background Task Started**${displayLabel}
|
|
13547
|
-
|
|
13548
|
-
|
|
|
13549
|
-
|
|
13550
|
-
| **Task ID** | \`${task.id}\` |
|
|
13551
|
-
| **Command** | \`${command}\` |
|
|
13552
|
-
| **Status** | ${backgroundTaskManager.getStatusEmoji(task.status)} ${task.status} |
|
|
13553
|
-
| **Working Dir** | ${task.cwd} |
|
|
13554
|
-
| **Timeout** | ${(task.timeout / 1e3).toFixed(0)}s |
|
|
13555
|
-
\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
|
|
13484
|
+
| Task ID | \`${task.id}\` |
|
|
13485
|
+
| Command | \`${command}\` |
|
|
13486
|
+
| Status | ${backgroundTaskManager.getStatusEmoji(task.status)} ${task.status} |
|
|
13556
13487
|
|
|
13557
|
-
\u{1F4CC}
|
|
13488
|
+
\u{1F4CC} Use \`check_background({ taskId: "${task.id}" })\` to get results.`;
|
|
13558
13489
|
}
|
|
13559
13490
|
});
|
|
13560
|
-
var checkBackgroundTool = tool({
|
|
13561
|
-
description: `Check the status and output of a background task.
|
|
13562
|
-
|
|
13563
|
-
<purpose>
|
|
13564
|
-
Retrieve the current status and output of a previously started background task.
|
|
13565
|
-
Use this after run_background to get results.
|
|
13566
|
-
</purpose>
|
|
13567
13491
|
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
- Duration
|
|
13572
|
-
- Full output (stdout + stderr)
|
|
13573
|
-
</output_includes>`,
|
|
13492
|
+
// src/tools/background-cmd/check.ts
|
|
13493
|
+
var checkBackgroundTool = tool({
|
|
13494
|
+
description: `Check the status and output of a background task.`,
|
|
13574
13495
|
args: {
|
|
13575
|
-
taskId: tool.schema.string().describe("Task ID from run_background
|
|
13576
|
-
tailLines: tool.schema.number().optional().describe("Limit output to last N lines
|
|
13496
|
+
taskId: tool.schema.string().describe("Task ID from run_background"),
|
|
13497
|
+
tailLines: tool.schema.number().optional().describe("Limit output to last N lines")
|
|
13577
13498
|
},
|
|
13578
13499
|
async execute(args) {
|
|
13579
13500
|
const { taskId, tailLines } = args;
|
|
@@ -13586,7 +13507,7 @@ Use this after run_background to get results.
|
|
|
13586
13507
|
const taskList = allTasks.map((t) => `- \`${t.id}\`: ${t.command.substring(0, 30)}...`).join("\n");
|
|
13587
13508
|
return `\u274C Task \`${taskId}\` not found.
|
|
13588
13509
|
|
|
13589
|
-
**Available
|
|
13510
|
+
**Available:**
|
|
13590
13511
|
${taskList}`;
|
|
13591
13512
|
}
|
|
13592
13513
|
const duration3 = backgroundTaskManager.formatDuration(task);
|
|
@@ -13594,64 +13515,41 @@ ${taskList}`;
|
|
|
13594
13515
|
let output = task.output;
|
|
13595
13516
|
let stderr = task.errorOutput;
|
|
13596
13517
|
if (tailLines && tailLines > 0) {
|
|
13597
|
-
|
|
13598
|
-
|
|
13599
|
-
output = outputLines.slice(-tailLines).join("\n");
|
|
13600
|
-
stderr = stderrLines.slice(-tailLines).join("\n");
|
|
13518
|
+
output = output.split("\n").slice(-tailLines).join("\n");
|
|
13519
|
+
stderr = stderr.split("\n").slice(-tailLines).join("\n");
|
|
13601
13520
|
}
|
|
13602
13521
|
const maxLen = 1e4;
|
|
13603
|
-
if (output.length > maxLen)
|
|
13604
|
-
|
|
13605
|
-
|
|
13606
|
-
|
|
13607
|
-
|
|
13608
|
-
|
|
13609
|
-
|
|
13610
|
-
|
|
13611
|
-
|
|
13612
|
-
|
|
13613
|
-
\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
|
|
13614
|
-
| Property | Value |
|
|
13615
|
-
|----------|-------|
|
|
13616
|
-
| **Command** | \`${task.command}\` |
|
|
13617
|
-
| **Status** | ${statusEmoji} **${task.status.toUpperCase()}** |
|
|
13618
|
-
| **Duration** | ${duration3}${task.status === "running" ? " (ongoing)" : ""} |
|
|
13619
|
-
${task.exitCode !== null ? `| **Exit Code** | ${task.exitCode} |` : ""}
|
|
13620
|
-
\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`;
|
|
13621
|
-
if (output.trim()) {
|
|
13622
|
-
result += `
|
|
13623
|
-
|
|
13624
|
-
\u{1F4E4} **Output (stdout)**:
|
|
13522
|
+
if (output.length > maxLen) output = `[...truncated...]\\n` + output.slice(-maxLen);
|
|
13523
|
+
if (stderr.length > maxLen) stderr = `[...truncated...]\\n` + stderr.slice(-maxLen);
|
|
13524
|
+
let result = `${statusEmoji} **Task ${task.id}**${task.label ? ` (${task.label})` : ""}
|
|
13525
|
+
| Command | \`${task.command}\` |
|
|
13526
|
+
| Status | ${statusEmoji} **${task.status.toUpperCase()}** |
|
|
13527
|
+
| Duration | ${duration3}${task.status === "running" ? " (ongoing)" : ""} |
|
|
13528
|
+
${task.exitCode !== null ? `| Exit Code | ${task.exitCode} |` : ""}`;
|
|
13529
|
+
if (output.trim()) result += `
|
|
13530
|
+
|
|
13531
|
+
\u{1F4E4} **stdout:**
|
|
13625
13532
|
\`\`\`
|
|
13626
13533
|
${output.trim()}
|
|
13627
13534
|
\`\`\``;
|
|
13628
|
-
|
|
13629
|
-
if (stderr.trim()) {
|
|
13630
|
-
result += `
|
|
13535
|
+
if (stderr.trim()) result += `
|
|
13631
13536
|
|
|
13632
|
-
\u26A0\uFE0F **
|
|
13537
|
+
\u26A0\uFE0F **stderr:**
|
|
13633
13538
|
\`\`\`
|
|
13634
13539
|
${stderr.trim()}
|
|
13635
13540
|
\`\`\``;
|
|
13636
|
-
|
|
13637
|
-
if (task.status === "running") {
|
|
13638
|
-
result += `
|
|
13541
|
+
if (task.status === "running") result += `
|
|
13639
13542
|
|
|
13640
|
-
\u23F3
|
|
13641
|
-
\`check_background({ taskId: "${task.id}" })\``;
|
|
13642
|
-
}
|
|
13543
|
+
\u23F3 Still running... check again.`;
|
|
13643
13544
|
return result;
|
|
13644
13545
|
}
|
|
13645
13546
|
});
|
|
13646
|
-
var listBackgroundTool = tool({
|
|
13647
|
-
description: `List all background tasks and their current status.
|
|
13648
13547
|
|
|
13649
|
-
|
|
13650
|
-
|
|
13651
|
-
|
|
13652
|
-
</purpose>`,
|
|
13548
|
+
// src/tools/background-cmd/list.ts
|
|
13549
|
+
var listBackgroundTool = tool({
|
|
13550
|
+
description: `List all background tasks and their status.`,
|
|
13653
13551
|
args: {
|
|
13654
|
-
status: tool.schema.enum(["all", "running", "done", "error"]).optional().describe("Filter by status
|
|
13552
|
+
status: tool.schema.enum(["all", "running", "done", "error"]).optional().describe("Filter by status")
|
|
13655
13553
|
},
|
|
13656
13554
|
async execute(args) {
|
|
13657
13555
|
const { status = "all" } = args;
|
|
@@ -13662,91 +13560,102 @@ Useful to check what's in progress before starting new tasks.
|
|
|
13662
13560
|
tasks = backgroundTaskManager.getByStatus(status);
|
|
13663
13561
|
}
|
|
13664
13562
|
if (tasks.length === 0) {
|
|
13665
|
-
return `\u{1F4CB}
|
|
13666
|
-
|
|
13667
|
-
Use \`run_background\` to start a new background task.`;
|
|
13563
|
+
return `\u{1F4CB} No background tasks${status !== "all" ? ` with status "${status}"` : ""}`;
|
|
13668
13564
|
}
|
|
13669
13565
|
tasks.sort((a, b) => b.startTime - a.startTime);
|
|
13670
|
-
const rows = tasks.map((
|
|
13671
|
-
const emoji3 = backgroundTaskManager.getStatusEmoji(
|
|
13672
|
-
const
|
|
13673
|
-
const
|
|
13674
|
-
|
|
13675
|
-
return `| \`${task.id}\` | ${emoji3} ${task.status.padEnd(7)} | ${cmdShort.padEnd(25)}${labelPart} | ${duration3.padStart(8)} |`;
|
|
13566
|
+
const rows = tasks.map((t) => {
|
|
13567
|
+
const emoji3 = backgroundTaskManager.getStatusEmoji(t.status);
|
|
13568
|
+
const cmd = t.command.length > 25 ? t.command.slice(0, 22) + "..." : t.command;
|
|
13569
|
+
const label = t.label ? ` [${t.label}]` : "";
|
|
13570
|
+
return `| \`${t.id}\` | ${emoji3} ${t.status} | ${cmd}${label} | ${backgroundTaskManager.formatDuration(t)} |`;
|
|
13676
13571
|
}).join("\n");
|
|
13677
|
-
const
|
|
13678
|
-
const
|
|
13679
|
-
const
|
|
13680
|
-
return `\u{1F4CB} **Background Tasks** (${tasks.length}
|
|
13681
|
-
\
|
|
13682
|
-
| \u23F3 Running: ${runningCount} | \u2705 Done: ${doneCount} | \u274C Error/Timeout: ${errorCount} |
|
|
13683
|
-
\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\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
13684
|
-
|
|
13685
|
-
| Task ID | Status | Command | Duration |
|
|
13686
|
-
|---------|--------|---------|----------|
|
|
13687
|
-
${rows}
|
|
13572
|
+
const running = tasks.filter((t) => t.status === "running").length;
|
|
13573
|
+
const done = tasks.filter((t) => t.status === "done").length;
|
|
13574
|
+
const error45 = tasks.filter((t) => t.status === "error" || t.status === "timeout").length;
|
|
13575
|
+
return `\u{1F4CB} **Background Tasks** (${tasks.length})
|
|
13576
|
+
\u23F3 Running: ${running} | \u2705 Done: ${done} | \u274C Error: ${error45}
|
|
13688
13577
|
|
|
13689
|
-
|
|
13578
|
+
| ID | Status | Command | Duration |
|
|
13579
|
+
|----|--------|---------|----------|
|
|
13580
|
+
${rows}`;
|
|
13690
13581
|
}
|
|
13691
13582
|
});
|
|
13692
|
-
var killBackgroundTool = tool({
|
|
13693
|
-
description: `Kill a running background task.
|
|
13694
13583
|
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13584
|
+
// src/tools/background-cmd/kill.ts
|
|
13585
|
+
var killBackgroundTool = tool({
|
|
13586
|
+
description: `Kill a running background task.`,
|
|
13698
13587
|
args: {
|
|
13699
|
-
taskId: tool.schema.string().describe("Task ID to kill
|
|
13588
|
+
taskId: tool.schema.string().describe("Task ID to kill")
|
|
13700
13589
|
},
|
|
13701
13590
|
async execute(args) {
|
|
13702
13591
|
const { taskId } = args;
|
|
13703
13592
|
const task = backgroundTaskManager.get(taskId);
|
|
13704
|
-
if (!task) {
|
|
13705
|
-
|
|
13706
|
-
}
|
|
13707
|
-
if (task.status !== "running") {
|
|
13708
|
-
return `\u26A0\uFE0F Task \`${taskId}\` is not running (status: ${task.status}).`;
|
|
13709
|
-
}
|
|
13593
|
+
if (!task) return `\u274C Task \`${taskId}\` not found.`;
|
|
13594
|
+
if (task.status !== "running") return `\u26A0\uFE0F Task \`${taskId}\` is not running (${task.status}).`;
|
|
13710
13595
|
const killed = backgroundTaskManager.kill(taskId);
|
|
13711
13596
|
if (killed) {
|
|
13712
|
-
return `\u{1F6D1} Task \`${taskId}\`
|
|
13597
|
+
return `\u{1F6D1} Task \`${taskId}\` killed.
|
|
13713
13598
|
Command: \`${task.command}\`
|
|
13714
|
-
Duration
|
|
13599
|
+
Duration: ${backgroundTaskManager.formatDuration(task)}`;
|
|
13715
13600
|
}
|
|
13716
|
-
return `\u26A0\uFE0F Could not kill task \`${taskId}
|
|
13601
|
+
return `\u26A0\uFE0F Could not kill task \`${taskId}\`.`;
|
|
13717
13602
|
}
|
|
13718
13603
|
});
|
|
13719
13604
|
|
|
13720
|
-
// src/core/
|
|
13721
|
-
var TASK_TTL_MS = 30 * 60 * 1e3;
|
|
13722
|
-
var CLEANUP_DELAY_MS = 5 * 60 * 1e3;
|
|
13723
|
-
var MIN_STABILITY_MS = 5 * 1e3;
|
|
13724
|
-
var POLL_INTERVAL_MS = 2e3;
|
|
13725
|
-
var DEFAULT_CONCURRENCY = 3;
|
|
13605
|
+
// src/core/agents/concurrency.ts
|
|
13726
13606
|
var DEBUG = process.env.DEBUG_PARALLEL_AGENT === "true";
|
|
13727
13607
|
var log = (...args) => {
|
|
13728
|
-
if (DEBUG) console.log("[
|
|
13608
|
+
if (DEBUG) console.log("[concurrency]", ...args);
|
|
13729
13609
|
};
|
|
13730
13610
|
var ConcurrencyController = class {
|
|
13731
13611
|
counts = /* @__PURE__ */ new Map();
|
|
13732
13612
|
queues = /* @__PURE__ */ new Map();
|
|
13733
13613
|
limits = /* @__PURE__ */ new Map();
|
|
13614
|
+
config;
|
|
13615
|
+
constructor(config2) {
|
|
13616
|
+
this.config = config2 ?? {};
|
|
13617
|
+
}
|
|
13734
13618
|
setLimit(key, limit) {
|
|
13735
13619
|
this.limits.set(key, limit);
|
|
13736
13620
|
}
|
|
13621
|
+
/**
|
|
13622
|
+
* Get concurrency limit for a key.
|
|
13623
|
+
* Priority: explicit limit > model > provider > agent > default
|
|
13624
|
+
*/
|
|
13625
|
+
getConcurrencyLimit(key) {
|
|
13626
|
+
const explicitLimit = this.limits.get(key);
|
|
13627
|
+
if (explicitLimit !== void 0) {
|
|
13628
|
+
return explicitLimit === 0 ? Infinity : explicitLimit;
|
|
13629
|
+
}
|
|
13630
|
+
if (this.config.modelConcurrency?.[key] !== void 0) {
|
|
13631
|
+
const limit = this.config.modelConcurrency[key];
|
|
13632
|
+
return limit === 0 ? Infinity : limit;
|
|
13633
|
+
}
|
|
13634
|
+
const provider = key.split("/")[0];
|
|
13635
|
+
if (this.config.providerConcurrency?.[provider] !== void 0) {
|
|
13636
|
+
const limit = this.config.providerConcurrency[provider];
|
|
13637
|
+
return limit === 0 ? Infinity : limit;
|
|
13638
|
+
}
|
|
13639
|
+
if (this.config.agentConcurrency?.[key] !== void 0) {
|
|
13640
|
+
const limit = this.config.agentConcurrency[key];
|
|
13641
|
+
return limit === 0 ? Infinity : limit;
|
|
13642
|
+
}
|
|
13643
|
+
return this.config.defaultConcurrency ?? PARALLEL_TASK.DEFAULT_CONCURRENCY;
|
|
13644
|
+
}
|
|
13645
|
+
// Backwards compatible alias
|
|
13737
13646
|
getLimit(key) {
|
|
13738
|
-
return this.
|
|
13647
|
+
return this.getConcurrencyLimit(key);
|
|
13739
13648
|
}
|
|
13740
13649
|
async acquire(key) {
|
|
13741
|
-
const limit = this.
|
|
13742
|
-
if (limit ===
|
|
13650
|
+
const limit = this.getConcurrencyLimit(key);
|
|
13651
|
+
if (limit === Infinity) return;
|
|
13743
13652
|
const current = this.counts.get(key) ?? 0;
|
|
13744
13653
|
if (current < limit) {
|
|
13745
13654
|
this.counts.set(key, current + 1);
|
|
13746
|
-
log(`Acquired
|
|
13655
|
+
log(`Acquired ${key}: ${current + 1}/${limit}`);
|
|
13747
13656
|
return;
|
|
13748
13657
|
}
|
|
13749
|
-
log(`Queueing
|
|
13658
|
+
log(`Queueing ${key}: ${current}/${limit}`);
|
|
13750
13659
|
return new Promise((resolve) => {
|
|
13751
13660
|
const queue = this.queues.get(key) ?? [];
|
|
13752
13661
|
queue.push(resolve);
|
|
@@ -13754,41 +13663,168 @@ var ConcurrencyController = class {
|
|
|
13754
13663
|
});
|
|
13755
13664
|
}
|
|
13756
13665
|
release(key) {
|
|
13757
|
-
const limit = this.
|
|
13758
|
-
if (limit ===
|
|
13666
|
+
const limit = this.getConcurrencyLimit(key);
|
|
13667
|
+
if (limit === Infinity) return;
|
|
13759
13668
|
const queue = this.queues.get(key);
|
|
13760
13669
|
if (queue && queue.length > 0) {
|
|
13761
13670
|
const next = queue.shift();
|
|
13762
|
-
log(`Released
|
|
13671
|
+
log(`Released ${key}: next in queue`);
|
|
13763
13672
|
next();
|
|
13764
13673
|
} else {
|
|
13765
13674
|
const current = this.counts.get(key) ?? 0;
|
|
13766
13675
|
if (current > 0) {
|
|
13767
13676
|
this.counts.set(key, current - 1);
|
|
13768
|
-
log(`Released
|
|
13677
|
+
log(`Released ${key}: ${current - 1}/${limit}`);
|
|
13769
13678
|
}
|
|
13770
13679
|
}
|
|
13771
13680
|
}
|
|
13772
13681
|
getQueueLength(key) {
|
|
13773
13682
|
return this.queues.get(key)?.length ?? 0;
|
|
13774
13683
|
}
|
|
13684
|
+
getActiveCount(key) {
|
|
13685
|
+
return this.counts.get(key) ?? 0;
|
|
13686
|
+
}
|
|
13687
|
+
/**
|
|
13688
|
+
* Get formatted concurrency info string (e.g., "2/5 slots")
|
|
13689
|
+
*/
|
|
13690
|
+
getConcurrencyInfo(key) {
|
|
13691
|
+
const active = this.getActiveCount(key);
|
|
13692
|
+
const limit = this.getConcurrencyLimit(key);
|
|
13693
|
+
if (limit === Infinity) return "";
|
|
13694
|
+
return ` (${active}/${limit} slots)`;
|
|
13695
|
+
}
|
|
13775
13696
|
};
|
|
13776
|
-
|
|
13777
|
-
|
|
13778
|
-
|
|
13697
|
+
|
|
13698
|
+
// src/core/agents/task-store.ts
|
|
13699
|
+
var TaskStore = class {
|
|
13779
13700
|
tasks = /* @__PURE__ */ new Map();
|
|
13780
13701
|
pendingByParent = /* @__PURE__ */ new Map();
|
|
13781
13702
|
notifications = /* @__PURE__ */ new Map();
|
|
13782
|
-
|
|
13703
|
+
set(id, task) {
|
|
13704
|
+
this.tasks.set(id, task);
|
|
13705
|
+
}
|
|
13706
|
+
get(id) {
|
|
13707
|
+
return this.tasks.get(id);
|
|
13708
|
+
}
|
|
13709
|
+
getAll() {
|
|
13710
|
+
return Array.from(this.tasks.values());
|
|
13711
|
+
}
|
|
13712
|
+
getRunning() {
|
|
13713
|
+
return this.getAll().filter((t) => t.status === "running");
|
|
13714
|
+
}
|
|
13715
|
+
getByParent(parentSessionID) {
|
|
13716
|
+
return this.getAll().filter((t) => t.parentSessionID === parentSessionID);
|
|
13717
|
+
}
|
|
13718
|
+
delete(id) {
|
|
13719
|
+
return this.tasks.delete(id);
|
|
13720
|
+
}
|
|
13721
|
+
clear() {
|
|
13722
|
+
this.tasks.clear();
|
|
13723
|
+
this.pendingByParent.clear();
|
|
13724
|
+
this.notifications.clear();
|
|
13725
|
+
}
|
|
13726
|
+
// Pending tracking
|
|
13727
|
+
trackPending(parentSessionID, taskId) {
|
|
13728
|
+
const pending = this.pendingByParent.get(parentSessionID) ?? /* @__PURE__ */ new Set();
|
|
13729
|
+
pending.add(taskId);
|
|
13730
|
+
this.pendingByParent.set(parentSessionID, pending);
|
|
13731
|
+
}
|
|
13732
|
+
untrackPending(parentSessionID, taskId) {
|
|
13733
|
+
const pending = this.pendingByParent.get(parentSessionID);
|
|
13734
|
+
if (pending) {
|
|
13735
|
+
pending.delete(taskId);
|
|
13736
|
+
if (pending.size === 0) {
|
|
13737
|
+
this.pendingByParent.delete(parentSessionID);
|
|
13738
|
+
}
|
|
13739
|
+
}
|
|
13740
|
+
}
|
|
13741
|
+
getPendingCount(parentSessionID) {
|
|
13742
|
+
return this.pendingByParent.get(parentSessionID)?.size ?? 0;
|
|
13743
|
+
}
|
|
13744
|
+
hasPending(parentSessionID) {
|
|
13745
|
+
return this.getPendingCount(parentSessionID) > 0;
|
|
13746
|
+
}
|
|
13747
|
+
// Notifications
|
|
13748
|
+
queueNotification(task) {
|
|
13749
|
+
const queue = this.notifications.get(task.parentSessionID) ?? [];
|
|
13750
|
+
queue.push(task);
|
|
13751
|
+
this.notifications.set(task.parentSessionID, queue);
|
|
13752
|
+
}
|
|
13753
|
+
getNotifications(parentSessionID) {
|
|
13754
|
+
return this.notifications.get(parentSessionID) ?? [];
|
|
13755
|
+
}
|
|
13756
|
+
clearNotifications(parentSessionID) {
|
|
13757
|
+
this.notifications.delete(parentSessionID);
|
|
13758
|
+
}
|
|
13759
|
+
cleanEmptyNotifications() {
|
|
13760
|
+
for (const [sessionID, queue] of this.notifications.entries()) {
|
|
13761
|
+
if (queue.length === 0) {
|
|
13762
|
+
this.notifications.delete(sessionID);
|
|
13763
|
+
}
|
|
13764
|
+
}
|
|
13765
|
+
}
|
|
13766
|
+
/**
|
|
13767
|
+
* Remove a specific task from all notification queues
|
|
13768
|
+
*/
|
|
13769
|
+
clearNotificationsForTask(taskId) {
|
|
13770
|
+
for (const [sessionID, tasks] of this.notifications.entries()) {
|
|
13771
|
+
const filtered = tasks.filter((t) => t.id !== taskId);
|
|
13772
|
+
if (filtered.length === 0) {
|
|
13773
|
+
this.notifications.delete(sessionID);
|
|
13774
|
+
} else if (filtered.length !== tasks.length) {
|
|
13775
|
+
this.notifications.set(sessionID, filtered);
|
|
13776
|
+
}
|
|
13777
|
+
}
|
|
13778
|
+
}
|
|
13779
|
+
};
|
|
13780
|
+
|
|
13781
|
+
// src/core/agents/config.ts
|
|
13782
|
+
var CONFIG = {
|
|
13783
|
+
TASK_TTL_MS: PARALLEL_TASK.TTL_MS,
|
|
13784
|
+
CLEANUP_DELAY_MS: PARALLEL_TASK.CLEANUP_DELAY_MS,
|
|
13785
|
+
MIN_STABILITY_MS: PARALLEL_TASK.MIN_STABILITY_MS,
|
|
13786
|
+
POLL_INTERVAL_MS: PARALLEL_TASK.POLL_INTERVAL_MS
|
|
13787
|
+
};
|
|
13788
|
+
|
|
13789
|
+
// src/core/agents/logger.ts
|
|
13790
|
+
var DEBUG2 = process.env.DEBUG_PARALLEL_AGENT === "true";
|
|
13791
|
+
function log2(...args) {
|
|
13792
|
+
if (DEBUG2) console.log("[parallel-agent]", ...args);
|
|
13793
|
+
}
|
|
13794
|
+
|
|
13795
|
+
// src/core/agents/format.ts
|
|
13796
|
+
function formatDuration(start, end) {
|
|
13797
|
+
const duration3 = (end ?? /* @__PURE__ */ new Date()).getTime() - start.getTime();
|
|
13798
|
+
const seconds = Math.floor(duration3 / 1e3);
|
|
13799
|
+
const minutes = Math.floor(seconds / 60);
|
|
13800
|
+
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
13801
|
+
return `${seconds}s`;
|
|
13802
|
+
}
|
|
13803
|
+
function buildNotificationMessage(tasks) {
|
|
13804
|
+
const summary = tasks.map((t) => {
|
|
13805
|
+
const status = t.status === "completed" ? "\u2705" : "\u274C";
|
|
13806
|
+
return `${status} \`${t.id}\`: ${t.description}`;
|
|
13807
|
+
}).join("\n");
|
|
13808
|
+
return `<system-notification>
|
|
13809
|
+
**All Parallel Tasks Complete**
|
|
13810
|
+
|
|
13811
|
+
${summary}
|
|
13812
|
+
|
|
13813
|
+
Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve results.
|
|
13814
|
+
</system-notification>`;
|
|
13815
|
+
}
|
|
13816
|
+
|
|
13817
|
+
// src/core/agents/manager.ts
|
|
13818
|
+
var ParallelAgentManager = class _ParallelAgentManager {
|
|
13819
|
+
static _instance;
|
|
13820
|
+
store = new TaskStore();
|
|
13783
13821
|
client;
|
|
13784
13822
|
directory;
|
|
13785
|
-
concurrency;
|
|
13786
|
-
// Polling
|
|
13823
|
+
concurrency = new ConcurrencyController();
|
|
13787
13824
|
pollingInterval;
|
|
13788
13825
|
constructor(client, directory) {
|
|
13789
13826
|
this.client = client;
|
|
13790
13827
|
this.directory = directory;
|
|
13791
|
-
this.concurrency = new ConcurrencyController();
|
|
13792
13828
|
}
|
|
13793
13829
|
static getInstance(client, directory) {
|
|
13794
13830
|
if (!_ParallelAgentManager._instance) {
|
|
@@ -13802,29 +13838,21 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
13802
13838
|
// ========================================================================
|
|
13803
13839
|
// Public API
|
|
13804
13840
|
// ========================================================================
|
|
13805
|
-
/**
|
|
13806
|
-
* Launch an agent in a new session (async, non-blocking)
|
|
13807
|
-
*/
|
|
13808
13841
|
async launch(input) {
|
|
13809
13842
|
const concurrencyKey = input.agent;
|
|
13810
13843
|
await this.concurrency.acquire(concurrencyKey);
|
|
13811
13844
|
this.pruneExpiredTasks();
|
|
13812
13845
|
try {
|
|
13813
13846
|
const createResult = await this.client.session.create({
|
|
13814
|
-
body: {
|
|
13815
|
-
|
|
13816
|
-
title: `Parallel: ${input.description}`
|
|
13817
|
-
},
|
|
13818
|
-
query: {
|
|
13819
|
-
directory: this.directory
|
|
13820
|
-
}
|
|
13847
|
+
body: { parentID: input.parentSessionID, title: `Parallel: ${input.description}` },
|
|
13848
|
+
query: { directory: this.directory }
|
|
13821
13849
|
});
|
|
13822
13850
|
if (createResult.error) {
|
|
13823
13851
|
this.concurrency.release(concurrencyKey);
|
|
13824
13852
|
throw new Error(`Failed to create session: ${createResult.error}`);
|
|
13825
13853
|
}
|
|
13826
13854
|
const sessionID = createResult.data.id;
|
|
13827
|
-
const taskId =
|
|
13855
|
+
const taskId = `${ID_PREFIX.TASK}${crypto.randomUUID().slice(0, 8)}`;
|
|
13828
13856
|
const task = {
|
|
13829
13857
|
id: taskId,
|
|
13830
13858
|
sessionID,
|
|
@@ -13835,171 +13863,172 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
13835
13863
|
startedAt: /* @__PURE__ */ new Date(),
|
|
13836
13864
|
concurrencyKey
|
|
13837
13865
|
};
|
|
13838
|
-
this.
|
|
13839
|
-
this.trackPending(input.parentSessionID, taskId);
|
|
13866
|
+
this.store.set(taskId, task);
|
|
13867
|
+
this.store.trackPending(input.parentSessionID, taskId);
|
|
13840
13868
|
this.startPolling();
|
|
13841
13869
|
this.client.session.prompt({
|
|
13842
13870
|
path: { id: sessionID },
|
|
13843
|
-
body: {
|
|
13844
|
-
agent: input.agent,
|
|
13845
|
-
parts: [{ type: "text", text: input.prompt }]
|
|
13846
|
-
}
|
|
13871
|
+
body: { agent: input.agent, parts: [{ type: "text", text: input.prompt }] }
|
|
13847
13872
|
}).catch((error45) => {
|
|
13848
|
-
|
|
13873
|
+
log2(`Prompt error for ${taskId}:`, error45);
|
|
13849
13874
|
this.handleTaskError(taskId, error45);
|
|
13850
13875
|
});
|
|
13851
|
-
|
|
13876
|
+
log2(`Launched ${taskId} in session ${sessionID}`);
|
|
13852
13877
|
return task;
|
|
13853
13878
|
} catch (error45) {
|
|
13854
13879
|
this.concurrency.release(concurrencyKey);
|
|
13855
13880
|
throw error45;
|
|
13856
13881
|
}
|
|
13857
13882
|
}
|
|
13858
|
-
/**
|
|
13859
|
-
* Get task by ID
|
|
13860
|
-
*/
|
|
13861
13883
|
getTask(id) {
|
|
13862
|
-
return this.
|
|
13884
|
+
return this.store.get(id);
|
|
13863
13885
|
}
|
|
13864
|
-
/**
|
|
13865
|
-
* Get all running tasks
|
|
13866
|
-
*/
|
|
13867
13886
|
getRunningTasks() {
|
|
13868
|
-
return
|
|
13887
|
+
return this.store.getRunning();
|
|
13869
13888
|
}
|
|
13870
|
-
/**
|
|
13871
|
-
* Get all tasks
|
|
13872
|
-
*/
|
|
13873
13889
|
getAllTasks() {
|
|
13874
|
-
return
|
|
13890
|
+
return this.store.getAll();
|
|
13875
13891
|
}
|
|
13876
|
-
/**
|
|
13877
|
-
* Get tasks by parent session
|
|
13878
|
-
*/
|
|
13879
13892
|
getTasksByParent(parentSessionID) {
|
|
13880
|
-
return
|
|
13893
|
+
return this.store.getByParent(parentSessionID);
|
|
13881
13894
|
}
|
|
13882
|
-
/**
|
|
13883
|
-
* Cancel a running task
|
|
13884
|
-
*/
|
|
13885
13895
|
async cancelTask(taskId) {
|
|
13886
|
-
const task = this.
|
|
13887
|
-
if (!task || task.status !== "running")
|
|
13888
|
-
return false;
|
|
13889
|
-
}
|
|
13896
|
+
const task = this.store.get(taskId);
|
|
13897
|
+
if (!task || task.status !== "running") return false;
|
|
13890
13898
|
task.status = "error";
|
|
13891
13899
|
task.error = "Cancelled by user";
|
|
13892
13900
|
task.completedAt = /* @__PURE__ */ new Date();
|
|
13893
|
-
if (task.concurrencyKey)
|
|
13894
|
-
|
|
13895
|
-
}
|
|
13896
|
-
this.untrackPending(task.parentSessionID, taskId);
|
|
13901
|
+
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
13902
|
+
this.store.untrackPending(task.parentSessionID, taskId);
|
|
13897
13903
|
try {
|
|
13898
|
-
await this.client.session.delete({
|
|
13899
|
-
|
|
13900
|
-
});
|
|
13901
|
-
console.log(`[parallel] \u{1F5D1}\uFE0F Session ${task.sessionID.slice(0, 8)}... deleted`);
|
|
13904
|
+
await this.client.session.delete({ path: { id: task.sessionID } });
|
|
13905
|
+
log2(`Session ${task.sessionID.slice(0, 8)}... deleted`);
|
|
13902
13906
|
} catch {
|
|
13903
|
-
|
|
13907
|
+
log2(`Session ${task.sessionID.slice(0, 8)}... already gone`);
|
|
13904
13908
|
}
|
|
13905
13909
|
this.scheduleCleanup(taskId);
|
|
13906
|
-
|
|
13907
|
-
log(`Cancelled ${taskId}`);
|
|
13910
|
+
log2(`Cancelled ${taskId}`);
|
|
13908
13911
|
return true;
|
|
13909
13912
|
}
|
|
13910
|
-
/**
|
|
13911
|
-
* Get result from completed task
|
|
13912
|
-
*/
|
|
13913
13913
|
async getResult(taskId) {
|
|
13914
|
-
const task = this.
|
|
13914
|
+
const task = this.store.get(taskId);
|
|
13915
13915
|
if (!task) return null;
|
|
13916
13916
|
if (task.result) return task.result;
|
|
13917
13917
|
if (task.status === "error") return `Error: ${task.error}`;
|
|
13918
13918
|
if (task.status === "running") return null;
|
|
13919
13919
|
try {
|
|
13920
|
-
const
|
|
13921
|
-
|
|
13922
|
-
|
|
13923
|
-
|
|
13924
|
-
return `Error: ${messagesResult.error}`;
|
|
13925
|
-
}
|
|
13926
|
-
const messages = messagesResult.data ?? [];
|
|
13927
|
-
const assistantMsgs = messages.filter((m) => m.info?.role === "assistant").reverse();
|
|
13928
|
-
const lastMsg = assistantMsgs[0];
|
|
13920
|
+
const result = await this.client.session.messages({ path: { id: task.sessionID } });
|
|
13921
|
+
if (result.error) return `Error: ${result.error}`;
|
|
13922
|
+
const messages = result.data ?? [];
|
|
13923
|
+
const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
|
|
13929
13924
|
if (!lastMsg) return "(No response)";
|
|
13930
|
-
const
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
const result = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n");
|
|
13934
|
-
task.result = result;
|
|
13935
|
-
return result;
|
|
13925
|
+
const text = lastMsg.parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text ?? "").filter(Boolean).join("\n") ?? "";
|
|
13926
|
+
task.result = text;
|
|
13927
|
+
return text;
|
|
13936
13928
|
} catch (error45) {
|
|
13937
13929
|
return `Error: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
13938
13930
|
}
|
|
13939
13931
|
}
|
|
13940
|
-
/**
|
|
13941
|
-
* Set concurrency limit for agent type
|
|
13942
|
-
*/
|
|
13943
13932
|
setConcurrencyLimit(agentType, limit) {
|
|
13944
13933
|
this.concurrency.setLimit(agentType, limit);
|
|
13945
13934
|
}
|
|
13946
|
-
/**
|
|
13947
|
-
* Get pending notification count
|
|
13948
|
-
*/
|
|
13949
13935
|
getPendingCount(parentSessionID) {
|
|
13950
|
-
return this.
|
|
13936
|
+
return this.store.getPendingCount(parentSessionID);
|
|
13951
13937
|
}
|
|
13952
|
-
/**
|
|
13953
|
-
* Cleanup all state
|
|
13954
|
-
*/
|
|
13955
13938
|
cleanup() {
|
|
13956
13939
|
this.stopPolling();
|
|
13957
|
-
this.
|
|
13958
|
-
this.pendingByParent.clear();
|
|
13959
|
-
this.notifications.clear();
|
|
13940
|
+
this.store.clear();
|
|
13960
13941
|
}
|
|
13942
|
+
formatDuration = formatDuration;
|
|
13961
13943
|
// ========================================================================
|
|
13962
|
-
//
|
|
13944
|
+
// Event Handling (from OpenCode hooks)
|
|
13963
13945
|
// ========================================================================
|
|
13964
|
-
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
13968
|
-
|
|
13969
|
-
|
|
13970
|
-
|
|
13971
|
-
|
|
13972
|
-
|
|
13973
|
-
|
|
13974
|
-
|
|
13946
|
+
/**
|
|
13947
|
+
* Handle OpenCode session events for proper resource cleanup.
|
|
13948
|
+
* Call this from your plugin's event hook.
|
|
13949
|
+
*/
|
|
13950
|
+
handleEvent(event) {
|
|
13951
|
+
const props = event.properties;
|
|
13952
|
+
if (event.type === "session.idle") {
|
|
13953
|
+
const sessionID = props?.sessionID;
|
|
13954
|
+
if (!sessionID) return;
|
|
13955
|
+
const task = this.findBySession(sessionID);
|
|
13956
|
+
if (!task || task.status !== "running") return;
|
|
13957
|
+
this.handleSessionIdle(task).catch((err) => {
|
|
13958
|
+
log2("Error handling session.idle:", err);
|
|
13959
|
+
});
|
|
13960
|
+
}
|
|
13961
|
+
if (event.type === "session.deleted") {
|
|
13962
|
+
const sessionID = props?.info?.id ?? props?.sessionID;
|
|
13963
|
+
if (!sessionID) return;
|
|
13964
|
+
const task = this.findBySession(sessionID);
|
|
13965
|
+
if (!task) return;
|
|
13966
|
+
log2(`Session deleted event for task ${task.id}`);
|
|
13967
|
+
if (task.status === "running") {
|
|
13968
|
+
task.status = "error";
|
|
13969
|
+
task.error = "Session deleted";
|
|
13970
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
13971
|
+
}
|
|
13972
|
+
if (task.concurrencyKey) {
|
|
13973
|
+
this.concurrency.release(task.concurrencyKey);
|
|
13974
|
+
task.concurrencyKey = void 0;
|
|
13975
13975
|
}
|
|
13976
|
+
this.store.untrackPending(task.parentSessionID, task.id);
|
|
13977
|
+
this.store.clearNotificationsForTask(task.id);
|
|
13978
|
+
this.store.delete(task.id);
|
|
13979
|
+
log2(`Cleaned up deleted session task: ${task.id}`);
|
|
13976
13980
|
}
|
|
13977
13981
|
}
|
|
13982
|
+
/**
|
|
13983
|
+
* Find task by session ID
|
|
13984
|
+
*/
|
|
13985
|
+
findBySession(sessionID) {
|
|
13986
|
+
return this.store.getAll().find((t) => t.sessionID === sessionID);
|
|
13987
|
+
}
|
|
13988
|
+
/**
|
|
13989
|
+
* Handle session.idle event - validate and complete task
|
|
13990
|
+
*/
|
|
13991
|
+
async handleSessionIdle(task) {
|
|
13992
|
+
const elapsed = Date.now() - task.startedAt.getTime();
|
|
13993
|
+
if (elapsed < CONFIG.MIN_STABILITY_MS) {
|
|
13994
|
+
log2(`Session idle but too early for ${task.id}, waiting...`);
|
|
13995
|
+
return;
|
|
13996
|
+
}
|
|
13997
|
+
const hasOutput = await this.validateSessionHasOutput(task.sessionID);
|
|
13998
|
+
if (!hasOutput) {
|
|
13999
|
+
log2(`Session idle but no output for ${task.id}, waiting...`);
|
|
14000
|
+
return;
|
|
14001
|
+
}
|
|
14002
|
+
task.status = "completed";
|
|
14003
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
14004
|
+
if (task.concurrencyKey) {
|
|
14005
|
+
this.concurrency.release(task.concurrencyKey);
|
|
14006
|
+
task.concurrencyKey = void 0;
|
|
14007
|
+
}
|
|
14008
|
+
this.store.untrackPending(task.parentSessionID, task.id);
|
|
14009
|
+
this.store.queueNotification(task);
|
|
14010
|
+
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
14011
|
+
this.scheduleCleanup(task.id);
|
|
14012
|
+
log2(`Task ${task.id} completed via session.idle event (${formatDuration(task.startedAt, task.completedAt)})`);
|
|
14013
|
+
}
|
|
13978
14014
|
// ========================================================================
|
|
13979
|
-
// Internal
|
|
14015
|
+
// Internal
|
|
13980
14016
|
// ========================================================================
|
|
13981
14017
|
handleTaskError(taskId, error45) {
|
|
13982
|
-
const task = this.
|
|
14018
|
+
const task = this.store.get(taskId);
|
|
13983
14019
|
if (!task) return;
|
|
13984
14020
|
task.status = "error";
|
|
13985
14021
|
task.error = error45 instanceof Error ? error45.message : String(error45);
|
|
13986
14022
|
task.completedAt = /* @__PURE__ */ new Date();
|
|
13987
|
-
if (task.concurrencyKey)
|
|
13988
|
-
|
|
13989
|
-
|
|
13990
|
-
this.untrackPending(task.parentSessionID, taskId);
|
|
13991
|
-
this.queueNotification(task);
|
|
14023
|
+
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
14024
|
+
this.store.untrackPending(task.parentSessionID, taskId);
|
|
14025
|
+
this.store.queueNotification(task);
|
|
13992
14026
|
this.notifyParentIfAllComplete(task.parentSessionID);
|
|
13993
14027
|
this.scheduleCleanup(taskId);
|
|
13994
14028
|
}
|
|
13995
|
-
// ========================================================================
|
|
13996
|
-
// Internal: Polling
|
|
13997
|
-
// ========================================================================
|
|
13998
14029
|
startPolling() {
|
|
13999
14030
|
if (this.pollingInterval) return;
|
|
14000
|
-
this.pollingInterval = setInterval(() =>
|
|
14001
|
-
this.pollRunningTasks();
|
|
14002
|
-
}, POLL_INTERVAL_MS);
|
|
14031
|
+
this.pollingInterval = setInterval(() => this.pollRunningTasks(), CONFIG.POLL_INTERVAL_MS);
|
|
14003
14032
|
this.pollingInterval.unref();
|
|
14004
14033
|
}
|
|
14005
14034
|
stopPolling() {
|
|
@@ -14010,211 +14039,183 @@ var ParallelAgentManager = class _ParallelAgentManager {
|
|
|
14010
14039
|
}
|
|
14011
14040
|
async pollRunningTasks() {
|
|
14012
14041
|
this.pruneExpiredTasks();
|
|
14013
|
-
const
|
|
14014
|
-
if (
|
|
14042
|
+
const running = this.store.getRunning();
|
|
14043
|
+
if (running.length === 0) {
|
|
14015
14044
|
this.stopPolling();
|
|
14016
14045
|
return;
|
|
14017
14046
|
}
|
|
14018
14047
|
try {
|
|
14019
14048
|
const statusResult = await this.client.session.status();
|
|
14020
14049
|
const allStatuses = statusResult.data ?? {};
|
|
14021
|
-
for (const task of
|
|
14022
|
-
|
|
14023
|
-
|
|
14050
|
+
for (const task of running) {
|
|
14051
|
+
try {
|
|
14052
|
+
const sessionStatus = allStatuses[task.sessionID];
|
|
14053
|
+
if (sessionStatus?.type === "idle") {
|
|
14054
|
+
const elapsed2 = Date.now() - task.startedAt.getTime();
|
|
14055
|
+
if (elapsed2 < CONFIG.MIN_STABILITY_MS) continue;
|
|
14056
|
+
if (!await this.validateSessionHasOutput(task.sessionID)) continue;
|
|
14057
|
+
await this.completeTask(task);
|
|
14058
|
+
continue;
|
|
14059
|
+
}
|
|
14060
|
+
await this.updateTaskProgress(task);
|
|
14024
14061
|
const elapsed = Date.now() - task.startedAt.getTime();
|
|
14025
|
-
if (elapsed
|
|
14026
|
-
|
|
14027
|
-
|
|
14028
|
-
|
|
14029
|
-
|
|
14030
|
-
if (task.concurrencyKey) {
|
|
14031
|
-
this.concurrency.release(task.concurrencyKey);
|
|
14062
|
+
if (elapsed >= CONFIG.MIN_STABILITY_MS && task.stablePolls && task.stablePolls >= 3) {
|
|
14063
|
+
if (await this.validateSessionHasOutput(task.sessionID)) {
|
|
14064
|
+
log2(`Task ${task.id} stable for 3 polls, completing...`);
|
|
14065
|
+
await this.completeTask(task);
|
|
14066
|
+
}
|
|
14032
14067
|
}
|
|
14033
|
-
|
|
14034
|
-
|
|
14035
|
-
this.notifyParentIfAllComplete(task.parentSessionID);
|
|
14036
|
-
this.scheduleCleanup(task.id);
|
|
14037
|
-
const duration3 = this.formatDuration(task.startedAt, task.completedAt);
|
|
14038
|
-
console.log(`[parallel] \u2705 COMPLETED ${task.id} \u2192 ${task.agent}: ${task.description} (${duration3})`);
|
|
14039
|
-
log(`Completed ${task.id}`);
|
|
14068
|
+
} catch (error45) {
|
|
14069
|
+
log2(`Poll error for task ${task.id}:`, error45);
|
|
14040
14070
|
}
|
|
14041
14071
|
}
|
|
14042
14072
|
} catch (error45) {
|
|
14043
|
-
|
|
14073
|
+
log2("Polling error:", error45);
|
|
14044
14074
|
}
|
|
14045
14075
|
}
|
|
14046
|
-
|
|
14047
|
-
|
|
14048
|
-
|
|
14076
|
+
/**
|
|
14077
|
+
* Update task progress and stability tracking
|
|
14078
|
+
*/
|
|
14079
|
+
async updateTaskProgress(task) {
|
|
14080
|
+
try {
|
|
14081
|
+
const result = await this.client.session.messages({ path: { id: task.sessionID } });
|
|
14082
|
+
if (result.error) return;
|
|
14083
|
+
const messages = result.data ?? [];
|
|
14084
|
+
const assistantMsgs = messages.filter((m) => m.info?.role === "assistant");
|
|
14085
|
+
let toolCalls = 0;
|
|
14086
|
+
let lastTool;
|
|
14087
|
+
let lastMessage;
|
|
14088
|
+
for (const msg of assistantMsgs) {
|
|
14089
|
+
for (const part of msg.parts ?? []) {
|
|
14090
|
+
if (part.type === "tool_use" || part.tool) {
|
|
14091
|
+
toolCalls++;
|
|
14092
|
+
lastTool = part.tool || part.name;
|
|
14093
|
+
}
|
|
14094
|
+
if (part.type === "text" && part.text) {
|
|
14095
|
+
lastMessage = part.text;
|
|
14096
|
+
}
|
|
14097
|
+
}
|
|
14098
|
+
}
|
|
14099
|
+
task.progress = {
|
|
14100
|
+
toolCalls,
|
|
14101
|
+
lastTool,
|
|
14102
|
+
lastMessage: lastMessage?.slice(0, 100),
|
|
14103
|
+
lastUpdate: /* @__PURE__ */ new Date()
|
|
14104
|
+
};
|
|
14105
|
+
const currentMsgCount = messages.length;
|
|
14106
|
+
if (task.lastMsgCount === currentMsgCount) {
|
|
14107
|
+
task.stablePolls = (task.stablePolls ?? 0) + 1;
|
|
14108
|
+
} else {
|
|
14109
|
+
task.stablePolls = 0;
|
|
14110
|
+
}
|
|
14111
|
+
task.lastMsgCount = currentMsgCount;
|
|
14112
|
+
} catch {
|
|
14113
|
+
}
|
|
14114
|
+
}
|
|
14115
|
+
/**
|
|
14116
|
+
* Complete a task and cleanup
|
|
14117
|
+
*/
|
|
14118
|
+
async completeTask(task) {
|
|
14119
|
+
task.status = "completed";
|
|
14120
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
14121
|
+
if (task.concurrencyKey) {
|
|
14122
|
+
this.concurrency.release(task.concurrencyKey);
|
|
14123
|
+
task.concurrencyKey = void 0;
|
|
14124
|
+
}
|
|
14125
|
+
this.store.untrackPending(task.parentSessionID, task.id);
|
|
14126
|
+
this.store.queueNotification(task);
|
|
14127
|
+
await this.notifyParentIfAllComplete(task.parentSessionID);
|
|
14128
|
+
this.scheduleCleanup(task.id);
|
|
14129
|
+
log2(`Completed ${task.id} (${formatDuration(task.startedAt, task.completedAt)})`);
|
|
14130
|
+
}
|
|
14049
14131
|
async validateSessionHasOutput(sessionID) {
|
|
14050
14132
|
try {
|
|
14051
|
-
const response = await this.client.session.messages({
|
|
14052
|
-
path: { id: sessionID }
|
|
14053
|
-
});
|
|
14133
|
+
const response = await this.client.session.messages({ path: { id: sessionID } });
|
|
14054
14134
|
const messages = response.data ?? [];
|
|
14055
|
-
|
|
14056
|
-
if (m.info?.role !== "assistant") return false;
|
|
14057
|
-
const parts = m.parts ?? [];
|
|
14058
|
-
return parts.some(
|
|
14059
|
-
(p) => p.type === "text" && p.text?.trim() || p.type === "reasoning" && p.text?.trim() || p.type === "tool"
|
|
14060
|
-
);
|
|
14061
|
-
});
|
|
14062
|
-
return hasContent;
|
|
14135
|
+
return messages.some((m) => m.info?.role === "assistant" && m.parts?.some((p) => p.type === "text" && p.text?.trim() || p.type === "tool"));
|
|
14063
14136
|
} catch {
|
|
14064
14137
|
return true;
|
|
14065
14138
|
}
|
|
14066
14139
|
}
|
|
14067
|
-
// ========================================================================
|
|
14068
|
-
// Internal: Cleanup & TTL
|
|
14069
|
-
// ========================================================================
|
|
14070
14140
|
pruneExpiredTasks() {
|
|
14071
14141
|
const now = Date.now();
|
|
14072
|
-
for (const [taskId, task] of this.
|
|
14142
|
+
for (const [taskId, task] of this.store.getAll().map((t) => [t.id, t])) {
|
|
14073
14143
|
const age = now - task.startedAt.getTime();
|
|
14074
|
-
if (age
|
|
14075
|
-
|
|
14076
|
-
|
|
14077
|
-
|
|
14078
|
-
|
|
14079
|
-
|
|
14080
|
-
|
|
14081
|
-
|
|
14082
|
-
|
|
14083
|
-
|
|
14084
|
-
|
|
14085
|
-
|
|
14086
|
-
this.client.session.delete({
|
|
14087
|
-
path: { id: task.sessionID }
|
|
14088
|
-
}).then(() => {
|
|
14089
|
-
console.log(`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (timeout session deleted)`);
|
|
14090
|
-
}).catch(() => {
|
|
14091
|
-
console.log(`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (timeout session already gone)`);
|
|
14092
|
-
});
|
|
14093
|
-
this.tasks.delete(taskId);
|
|
14094
|
-
}
|
|
14095
|
-
}
|
|
14096
|
-
for (const [sessionID, queue] of this.notifications.entries()) {
|
|
14097
|
-
if (queue.length === 0) {
|
|
14098
|
-
this.notifications.delete(sessionID);
|
|
14099
|
-
}
|
|
14144
|
+
if (age <= CONFIG.TASK_TTL_MS) continue;
|
|
14145
|
+
log2(`Timeout: ${taskId}`);
|
|
14146
|
+
if (task.status === "running") {
|
|
14147
|
+
task.status = "timeout";
|
|
14148
|
+
task.error = "Task exceeded 30 minute time limit";
|
|
14149
|
+
task.completedAt = /* @__PURE__ */ new Date();
|
|
14150
|
+
if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
|
|
14151
|
+
this.store.untrackPending(task.parentSessionID, taskId);
|
|
14152
|
+
}
|
|
14153
|
+
this.client.session.delete({ path: { id: task.sessionID } }).catch(() => {
|
|
14154
|
+
});
|
|
14155
|
+
this.store.delete(taskId);
|
|
14100
14156
|
}
|
|
14157
|
+
this.store.cleanEmptyNotifications();
|
|
14101
14158
|
}
|
|
14102
14159
|
scheduleCleanup(taskId) {
|
|
14103
|
-
const task = this.
|
|
14160
|
+
const task = this.store.get(taskId);
|
|
14104
14161
|
const sessionID = task?.sessionID;
|
|
14105
14162
|
setTimeout(async () => {
|
|
14106
14163
|
if (sessionID) {
|
|
14107
14164
|
try {
|
|
14108
|
-
await this.client.session.delete({
|
|
14109
|
-
path: { id: sessionID }
|
|
14110
|
-
});
|
|
14111
|
-
console.log(`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (session deleted)`);
|
|
14112
|
-
log(`Deleted session ${sessionID}`);
|
|
14165
|
+
await this.client.session.delete({ path: { id: sessionID } });
|
|
14113
14166
|
} catch {
|
|
14114
|
-
console.log(`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (session already gone)`);
|
|
14115
14167
|
}
|
|
14116
14168
|
}
|
|
14117
|
-
this.
|
|
14118
|
-
|
|
14119
|
-
}, CLEANUP_DELAY_MS);
|
|
14120
|
-
}
|
|
14121
|
-
// ========================================================================
|
|
14122
|
-
// Internal: Notifications
|
|
14123
|
-
// ========================================================================
|
|
14124
|
-
queueNotification(task) {
|
|
14125
|
-
const queue = this.notifications.get(task.parentSessionID) ?? [];
|
|
14126
|
-
queue.push(task);
|
|
14127
|
-
this.notifications.set(task.parentSessionID, queue);
|
|
14169
|
+
this.store.delete(taskId);
|
|
14170
|
+
log2(`Cleaned up ${taskId}`);
|
|
14171
|
+
}, CONFIG.CLEANUP_DELAY_MS);
|
|
14128
14172
|
}
|
|
14129
14173
|
async notifyParentIfAllComplete(parentSessionID) {
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
}
|
|
14135
|
-
const completedTasks = this.notifications.get(parentSessionID) ?? [];
|
|
14136
|
-
if (completedTasks.length === 0) return;
|
|
14137
|
-
const summary = completedTasks.map((t) => {
|
|
14138
|
-
const status = t.status === "completed" ? "\u2705" : "\u274C";
|
|
14139
|
-
return `${status} \`${t.id}\`: ${t.description}`;
|
|
14140
|
-
}).join("\n");
|
|
14141
|
-
const notification = `<system-notification>
|
|
14142
|
-
**All Parallel Tasks Complete**
|
|
14143
|
-
|
|
14144
|
-
${summary}
|
|
14145
|
-
|
|
14146
|
-
Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve results.
|
|
14147
|
-
</system-notification>`;
|
|
14174
|
+
if (this.store.hasPending(parentSessionID)) return;
|
|
14175
|
+
const notifications = this.store.getNotifications(parentSessionID);
|
|
14176
|
+
if (notifications.length === 0) return;
|
|
14177
|
+
const message = buildNotificationMessage(notifications);
|
|
14148
14178
|
try {
|
|
14149
14179
|
await this.client.session.prompt({
|
|
14150
14180
|
path: { id: parentSessionID },
|
|
14151
|
-
body: {
|
|
14152
|
-
noReply: true,
|
|
14153
|
-
parts: [{ type: "text", text: notification }]
|
|
14154
|
-
}
|
|
14181
|
+
body: { noReply: true, parts: [{ type: "text", text: message }] }
|
|
14155
14182
|
});
|
|
14156
|
-
|
|
14183
|
+
log2(`Notified parent ${parentSessionID}`);
|
|
14157
14184
|
} catch (error45) {
|
|
14158
|
-
|
|
14185
|
+
log2("Notification error:", error45);
|
|
14159
14186
|
}
|
|
14160
|
-
this.
|
|
14161
|
-
}
|
|
14162
|
-
// ========================================================================
|
|
14163
|
-
// Internal: Formatting
|
|
14164
|
-
// ========================================================================
|
|
14165
|
-
formatDuration(start, end) {
|
|
14166
|
-
const duration3 = (end ?? /* @__PURE__ */ new Date()).getTime() - start.getTime();
|
|
14167
|
-
const seconds = Math.floor(duration3 / 1e3);
|
|
14168
|
-
const minutes = Math.floor(seconds / 60);
|
|
14169
|
-
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
14170
|
-
return `${seconds}s`;
|
|
14187
|
+
this.store.clearNotifications(parentSessionID);
|
|
14171
14188
|
}
|
|
14172
14189
|
};
|
|
14173
14190
|
var parallelAgentManager = {
|
|
14174
14191
|
getInstance: ParallelAgentManager.getInstance.bind(ParallelAgentManager)
|
|
14175
14192
|
};
|
|
14176
14193
|
|
|
14177
|
-
// src/tools/
|
|
14194
|
+
// src/tools/parallel/delegate-task.ts
|
|
14178
14195
|
var createDelegateTaskTool = (manager, client) => tool({
|
|
14179
14196
|
description: `Delegate a task to an agent.
|
|
14180
14197
|
|
|
14181
14198
|
<mode>
|
|
14182
|
-
- background=true: Non-blocking.
|
|
14183
|
-
- background=false: Blocking. Waits for result.
|
|
14199
|
+
- background=true: Non-blocking. Returns task ID immediately.
|
|
14200
|
+
- background=false: Blocking. Waits for result.
|
|
14184
14201
|
</mode>
|
|
14185
14202
|
|
|
14186
|
-
<when_to_use_background_true>
|
|
14187
|
-
- Multiple independent tasks to run in parallel
|
|
14188
|
-
- Long-running tasks (build, test, analysis)
|
|
14189
|
-
- You have other work to do while waiting
|
|
14190
|
-
- Example: "Build module A" + "Test module B" in parallel
|
|
14191
|
-
</when_to_use_background_true>
|
|
14192
|
-
|
|
14193
|
-
<when_to_use_background_false>
|
|
14194
|
-
- You need the result for the very next step
|
|
14195
|
-
- Single task with nothing else to do
|
|
14196
|
-
- Quick questions that return fast
|
|
14197
|
-
</when_to_use_background_false>
|
|
14198
|
-
|
|
14199
14203
|
<safety>
|
|
14200
|
-
- Max 3
|
|
14204
|
+
- Max 3 tasks per agent type
|
|
14201
14205
|
- Auto-timeout: 30 minutes
|
|
14202
|
-
- Auto-cleanup: 5 minutes after completion
|
|
14203
14206
|
</safety>`,
|
|
14204
14207
|
args: {
|
|
14205
|
-
agent: tool.schema.string().describe("Agent name
|
|
14206
|
-
description: tool.schema.string().describe("
|
|
14207
|
-
prompt: tool.schema.string().describe("
|
|
14208
|
-
background: tool.schema.boolean().describe("true=async
|
|
14208
|
+
agent: tool.schema.string().describe("Agent name"),
|
|
14209
|
+
description: tool.schema.string().describe("Task description"),
|
|
14210
|
+
prompt: tool.schema.string().describe("Prompt for the agent"),
|
|
14211
|
+
background: tool.schema.boolean().describe("true=async, false=sync")
|
|
14209
14212
|
},
|
|
14210
14213
|
async execute(args, context) {
|
|
14211
14214
|
const { agent, description, prompt, background } = args;
|
|
14212
14215
|
const ctx = context;
|
|
14213
14216
|
const sessionClient = client;
|
|
14214
14217
|
if (background === void 0) {
|
|
14215
|
-
return `\u274C 'background' parameter is REQUIRED
|
|
14216
|
-
- background=true: Run in parallel, returns task ID
|
|
14217
|
-
- background=false: Wait for result (blocking)`;
|
|
14218
|
+
return `\u274C 'background' parameter is REQUIRED.`;
|
|
14218
14219
|
}
|
|
14219
14220
|
if (background === true) {
|
|
14220
14221
|
try {
|
|
@@ -14224,178 +14225,84 @@ var createDelegateTaskTool = (manager, client) => tool({
|
|
|
14224
14225
|
prompt,
|
|
14225
14226
|
parentSessionID: ctx.sessionID
|
|
14226
14227
|
});
|
|
14227
|
-
|
|
14228
|
-
const pendingCount = manager.getPendingCount(ctx.sessionID);
|
|
14229
|
-
console.log(`[parallel] \u{1F680} SPAWNED ${task.id} \u2192 ${agent}: ${description}`);
|
|
14230
|
-
return `
|
|
14231
|
-
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
14232
|
-
\u2551 \u{1F680} BACKGROUND TASK SPAWNED \u2551
|
|
14233
|
-
\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
|
|
14234
|
-
\u2551 Task ID: ${task.id.padEnd(45)}\u2551
|
|
14235
|
-
\u2551 Agent: ${task.agent.padEnd(45)}\u2551
|
|
14236
|
-
\u2551 Description: ${task.description.slice(0, 45).padEnd(45)}\u2551
|
|
14237
|
-
\u2551 Status: \u23F3 RUNNING (background) \u2551
|
|
14238
|
-
\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
|
|
14239
|
-
\u2551 Running: ${String(runningCount).padEnd(5)} \u2502 Pending: ${String(pendingCount).padEnd(5)} \u2551
|
|
14240
|
-
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
14241
|
-
|
|
14242
|
-
\u{1F4CC} Continue your work! System notifies when ALL complete.
|
|
14243
|
-
\u{1F50D} Use \`get_task_result({ taskId: "${task.id}" })\` later.`;
|
|
14228
|
+
return `\u{1F680} Task spawned: \`${task.id}\` (${agent})`;
|
|
14244
14229
|
} catch (error45) {
|
|
14245
|
-
|
|
14246
|
-
console.log(`[parallel] \u274C FAILED: ${message}`);
|
|
14247
|
-
return `\u274C Failed to spawn background task: ${message}`;
|
|
14230
|
+
return `\u274C Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
14248
14231
|
}
|
|
14249
14232
|
}
|
|
14250
|
-
console.log(`[delegate] \u23F3 SYNC ${agent}: ${description}`);
|
|
14251
14233
|
try {
|
|
14252
14234
|
const session = sessionClient.session;
|
|
14253
|
-
const directory = ".";
|
|
14254
14235
|
const createResult = await session.create({
|
|
14255
|
-
body: {
|
|
14256
|
-
|
|
14257
|
-
title: `Task: ${description}`
|
|
14258
|
-
},
|
|
14259
|
-
query: { directory }
|
|
14236
|
+
body: { parentID: ctx.sessionID, title: `Task: ${description}` },
|
|
14237
|
+
query: { directory: "." }
|
|
14260
14238
|
});
|
|
14261
14239
|
if (createResult.error || !createResult.data?.id) {
|
|
14262
|
-
return `\u274C Failed to create session
|
|
14240
|
+
return `\u274C Failed to create session`;
|
|
14263
14241
|
}
|
|
14264
14242
|
const sessionID = createResult.data.id;
|
|
14265
14243
|
const startTime = Date.now();
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
|
|
14270
|
-
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
});
|
|
14274
|
-
} catch (promptError) {
|
|
14275
|
-
const errorMessage = promptError instanceof Error ? promptError.message : String(promptError);
|
|
14276
|
-
return `\u274C Failed to send prompt: ${errorMessage}
|
|
14277
|
-
|
|
14278
|
-
Session ID: ${sessionID}`;
|
|
14279
|
-
}
|
|
14280
|
-
const POLL_INTERVAL_MS2 = 500;
|
|
14281
|
-
const MAX_POLL_TIME_MS = 10 * 60 * 1e3;
|
|
14282
|
-
const MIN_STABILITY_MS2 = 5e3;
|
|
14283
|
-
const STABILITY_POLLS_REQUIRED = 3;
|
|
14284
|
-
let stablePolls = 0;
|
|
14285
|
-
let lastMsgCount = 0;
|
|
14286
|
-
while (Date.now() - startTime < MAX_POLL_TIME_MS) {
|
|
14287
|
-
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS2));
|
|
14244
|
+
await session.prompt({
|
|
14245
|
+
path: { id: sessionID },
|
|
14246
|
+
body: { agent, parts: [{ type: "text", text: prompt }] }
|
|
14247
|
+
});
|
|
14248
|
+
let stablePolls = 0, lastMsgCount = 0;
|
|
14249
|
+
while (Date.now() - startTime < 10 * 60 * 1e3) {
|
|
14250
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
14288
14251
|
const statusResult = await session.status();
|
|
14289
|
-
|
|
14290
|
-
const sessionStatus = allStatuses[sessionID];
|
|
14291
|
-
if (sessionStatus?.type !== "idle") {
|
|
14252
|
+
if (statusResult.data?.[sessionID]?.type !== "idle") {
|
|
14292
14253
|
stablePolls = 0;
|
|
14293
|
-
lastMsgCount = 0;
|
|
14294
14254
|
continue;
|
|
14295
14255
|
}
|
|
14296
|
-
if (Date.now() - startTime <
|
|
14297
|
-
const
|
|
14298
|
-
const
|
|
14299
|
-
|
|
14300
|
-
if (currentMsgCount === lastMsgCount) {
|
|
14256
|
+
if (Date.now() - startTime < 5e3) continue;
|
|
14257
|
+
const msgs2 = await session.messages({ path: { id: sessionID } });
|
|
14258
|
+
const count = (msgs2.data ?? []).length;
|
|
14259
|
+
if (count === lastMsgCount) {
|
|
14301
14260
|
stablePolls++;
|
|
14302
|
-
if (stablePolls >=
|
|
14261
|
+
if (stablePolls >= 3) break;
|
|
14303
14262
|
} else {
|
|
14304
14263
|
stablePolls = 0;
|
|
14305
|
-
lastMsgCount =
|
|
14264
|
+
lastMsgCount = count;
|
|
14306
14265
|
}
|
|
14307
14266
|
}
|
|
14308
|
-
const
|
|
14309
|
-
const messages =
|
|
14310
|
-
const
|
|
14311
|
-
const
|
|
14312
|
-
|
|
14313
|
-
return `\u274C No assistant response found.
|
|
14267
|
+
const msgs = await session.messages({ path: { id: sessionID } });
|
|
14268
|
+
const messages = msgs.data ?? [];
|
|
14269
|
+
const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
|
|
14270
|
+
const text = lastMsg?.parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text ?? "").join("\n") || "";
|
|
14271
|
+
return `\u2705 Completed (${Math.floor((Date.now() - startTime) / 1e3)}s)
|
|
14314
14272
|
|
|
14315
|
-
|
|
14316
|
-
}
|
|
14317
|
-
const textParts = lastMsg.parts?.filter(
|
|
14318
|
-
(p) => p.type === "text" || p.type === "reasoning"
|
|
14319
|
-
) ?? [];
|
|
14320
|
-
const textContent = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n");
|
|
14321
|
-
const duration3 = Math.floor((Date.now() - startTime) / 1e3);
|
|
14322
|
-
console.log(`[delegate] \u2705 COMPLETED ${agent}: ${description} (${duration3}s)`);
|
|
14323
|
-
return `\u2705 **Task Completed** (${duration3}s)
|
|
14324
|
-
|
|
14325
|
-
Agent: ${agent}
|
|
14326
|
-
Session ID: ${sessionID}
|
|
14327
|
-
|
|
14328
|
-
---
|
|
14329
|
-
|
|
14330
|
-
${textContent || "(No text output)"}`;
|
|
14273
|
+
${text || "(No output)"}`;
|
|
14331
14274
|
} catch (error45) {
|
|
14332
|
-
|
|
14333
|
-
console.log(`[delegate] \u274C FAILED: ${message}`);
|
|
14334
|
-
return `\u274C Task failed: ${message}`;
|
|
14275
|
+
return `\u274C Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
|
|
14335
14276
|
}
|
|
14336
14277
|
}
|
|
14337
14278
|
});
|
|
14338
|
-
var createGetTaskResultTool = (manager) => tool({
|
|
14339
|
-
description: `Get the result from a completed background task.
|
|
14340
14279
|
|
|
14341
|
-
|
|
14342
|
-
|
|
14343
|
-
|
|
14344
|
-
</note>`,
|
|
14280
|
+
// src/tools/parallel/get-task-result.ts
|
|
14281
|
+
var createGetTaskResultTool = (manager) => tool({
|
|
14282
|
+
description: `Get result from a completed background task.`,
|
|
14345
14283
|
args: {
|
|
14346
|
-
taskId: tool.schema.string().describe("Task ID
|
|
14284
|
+
taskId: tool.schema.string().describe("Task ID")
|
|
14347
14285
|
},
|
|
14348
14286
|
async execute(args) {
|
|
14349
|
-
const
|
|
14350
|
-
|
|
14351
|
-
if (
|
|
14352
|
-
|
|
14353
|
-
|
|
14354
|
-
Use \`list_tasks\` to see available tasks.`;
|
|
14355
|
-
}
|
|
14356
|
-
if (task.status === "running") {
|
|
14357
|
-
const elapsed = Math.floor((Date.now() - task.startedAt.getTime()) / 1e3);
|
|
14358
|
-
return `\u23F3 **Task Still Running**
|
|
14359
|
-
|
|
14360
|
-
| Property | Value |
|
|
14361
|
-
|----------|-------|
|
|
14362
|
-
| **Task ID** | \`${taskId}\` |
|
|
14363
|
-
| **Agent** | ${task.agent} |
|
|
14364
|
-
| **Elapsed** | ${elapsed}s |
|
|
14365
|
-
|
|
14366
|
-
Wait for "All Complete" notification, then try again.`;
|
|
14367
|
-
}
|
|
14368
|
-
const result = await manager.getResult(taskId);
|
|
14287
|
+
const task = manager.getTask(args.taskId);
|
|
14288
|
+
if (!task) return `\u274C Task not found: \`${args.taskId}\``;
|
|
14289
|
+
if (task.status === "running") return `\u23F3 Still running...`;
|
|
14290
|
+
const result = await manager.getResult(args.taskId);
|
|
14369
14291
|
const duration3 = manager.formatDuration(task.startedAt, task.completedAt);
|
|
14370
14292
|
if (task.status === "error" || task.status === "timeout") {
|
|
14371
|
-
return `\u274C
|
|
14372
|
-
|
|
14373
|
-
| Property | Value |
|
|
14374
|
-
|----------|-------|
|
|
14375
|
-
| **Task ID** | \`${taskId}\` |
|
|
14376
|
-
| **Agent** | ${task.agent} |
|
|
14377
|
-
| **Error** | ${task.error} |
|
|
14378
|
-
| **Duration** | ${duration3} |`;
|
|
14293
|
+
return `\u274C ${task.status}: ${task.error}`;
|
|
14379
14294
|
}
|
|
14380
|
-
return `\u2705
|
|
14381
|
-
|
|
14382
|
-
| Property | Value |
|
|
14383
|
-
|----------|-------|
|
|
14384
|
-
| **Task ID** | \`${taskId}\` |
|
|
14385
|
-
| **Agent** | ${task.agent} |
|
|
14386
|
-
| **Duration** | ${duration3} |
|
|
14387
|
-
|
|
14388
|
-
---
|
|
14389
|
-
|
|
14390
|
-
**Result:**
|
|
14295
|
+
return `\u2705 Completed (${duration3})
|
|
14391
14296
|
|
|
14392
14297
|
${result || "(No output)"}`;
|
|
14393
14298
|
}
|
|
14394
14299
|
});
|
|
14300
|
+
|
|
14301
|
+
// src/tools/parallel/list-tasks.ts
|
|
14395
14302
|
var createListTasksTool = (manager) => tool({
|
|
14396
|
-
description: `List all background tasks
|
|
14303
|
+
description: `List all background tasks.`,
|
|
14397
14304
|
args: {
|
|
14398
|
-
status: tool.schema.string().optional().describe("Filter:
|
|
14305
|
+
status: tool.schema.string().optional().describe("Filter: all, running, completed, error")
|
|
14399
14306
|
},
|
|
14400
14307
|
async execute(args) {
|
|
14401
14308
|
const { status = "all" } = args;
|
|
@@ -14413,73 +14320,35 @@ var createListTasksTool = (manager) => tool({
|
|
|
14413
14320
|
default:
|
|
14414
14321
|
tasks = manager.getAllTasks();
|
|
14415
14322
|
}
|
|
14416
|
-
if (tasks.length === 0) {
|
|
14417
|
-
return `\u{1F4CB} No background tasks found${status !== "all" ? ` (filter: ${status})` : ""}.
|
|
14418
|
-
|
|
14419
|
-
Use \`delegate_task({ ..., background: true })\` to spawn background tasks.`;
|
|
14420
|
-
}
|
|
14421
|
-
const runningCount = manager.getRunningTasks().length;
|
|
14422
|
-
const completedCount = manager.getAllTasks().filter((t) => t.status === "completed").length;
|
|
14423
|
-
const errorCount = manager.getAllTasks().filter((t) => t.status === "error" || t.status === "timeout").length;
|
|
14424
|
-
const statusIcon = (s) => {
|
|
14425
|
-
switch (s) {
|
|
14426
|
-
case "running":
|
|
14427
|
-
return "\u23F3";
|
|
14428
|
-
case "completed":
|
|
14429
|
-
return "\u2705";
|
|
14430
|
-
case "error":
|
|
14431
|
-
return "\u274C";
|
|
14432
|
-
case "timeout":
|
|
14433
|
-
return "\u23F1\uFE0F";
|
|
14434
|
-
default:
|
|
14435
|
-
return "\u2753";
|
|
14436
|
-
}
|
|
14437
|
-
};
|
|
14323
|
+
if (tasks.length === 0) return `\u{1F4CB} No tasks found.`;
|
|
14438
14324
|
const rows = tasks.map((t) => {
|
|
14439
14325
|
const elapsed = Math.floor((Date.now() - t.startedAt.getTime()) / 1e3);
|
|
14440
|
-
|
|
14441
|
-
return `| \`${t.id}\` | ${statusIcon(t.status)} ${t.status} | ${t.agent} | ${desc} | ${elapsed}s |`;
|
|
14326
|
+
return `| \`${t.id}\` | ${getStatusEmoji(t.status)} ${t.status} | ${t.agent} | ${elapsed}s |`;
|
|
14442
14327
|
}).join("\n");
|
|
14443
|
-
return `\u{1F4CB} **
|
|
14444
|
-
\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\u2501\u2501\u2501\u2501
|
|
14445
|
-
| \u23F3 Running: ${runningCount} | \u2705 Completed: ${completedCount} | \u274C Error: ${errorCount} |
|
|
14446
|
-
\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\u2501\u2501\u2501\u2501
|
|
14328
|
+
return `\u{1F4CB} **Tasks**
|
|
14447
14329
|
|
|
14448
|
-
|
|
|
14449
|
-
|
|
14450
|
-
${rows}
|
|
14451
|
-
|
|
14452
|
-
\u{1F4A1} Use \`get_task_result({ taskId: "task_xxx" })\` to get results.
|
|
14453
|
-
\u{1F6D1} Use \`cancel_task({ taskId: "task_xxx" })\` to stop a running task.`;
|
|
14330
|
+
| ID | Status | Agent | Time |
|
|
14331
|
+
|----|--------|-------|------|
|
|
14332
|
+
${rows}`;
|
|
14454
14333
|
}
|
|
14455
14334
|
});
|
|
14456
|
-
var createCancelTaskTool = (manager) => tool({
|
|
14457
|
-
description: `Cancel a running background task.
|
|
14458
14335
|
|
|
14459
|
-
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
</purpose>`,
|
|
14336
|
+
// src/tools/parallel/cancel-task.ts
|
|
14337
|
+
var createCancelTaskTool = (manager) => tool({
|
|
14338
|
+
description: `Cancel a running task.`,
|
|
14463
14339
|
args: {
|
|
14464
|
-
taskId: tool.schema.string().describe("Task ID to cancel
|
|
14340
|
+
taskId: tool.schema.string().describe("Task ID to cancel")
|
|
14465
14341
|
},
|
|
14466
14342
|
async execute(args) {
|
|
14467
|
-
const
|
|
14468
|
-
|
|
14469
|
-
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
Task \`${taskId}\` has been stopped. Concurrency slot released.`;
|
|
14473
|
-
}
|
|
14474
|
-
const task = manager.getTask(taskId);
|
|
14475
|
-
if (task) {
|
|
14476
|
-
return `\u26A0\uFE0F Cannot cancel: Task \`${taskId}\` is ${task.status} (not running).`;
|
|
14477
|
-
}
|
|
14478
|
-
return `\u274C Task \`${taskId}\` not found.
|
|
14479
|
-
|
|
14480
|
-
Use \`list_tasks\` to see available tasks.`;
|
|
14343
|
+
const cancelled = await manager.cancelTask(args.taskId);
|
|
14344
|
+
if (cancelled) return `\u{1F6D1} Cancelled: \`${args.taskId}\``;
|
|
14345
|
+
const task = manager.getTask(args.taskId);
|
|
14346
|
+
if (task) return `\u26A0\uFE0F Cannot cancel: Task is ${task.status}`;
|
|
14347
|
+
return `\u274C Not found: \`${args.taskId}\``;
|
|
14481
14348
|
}
|
|
14482
14349
|
});
|
|
14350
|
+
|
|
14351
|
+
// src/tools/parallel/index.ts
|
|
14483
14352
|
function createAsyncAgentTools(manager, client) {
|
|
14484
14353
|
return {
|
|
14485
14354
|
delegate_task: createDelegateTaskTool(manager, client),
|
|
@@ -14978,6 +14847,11 @@ ${stateSession.graph.getTaskSummary()}`;
|
|
|
14978
14847
|
// Event handler - cleans up when sessions are deleted
|
|
14979
14848
|
// -----------------------------------------------------------------
|
|
14980
14849
|
handler: async ({ event }) => {
|
|
14850
|
+
try {
|
|
14851
|
+
const manager = ParallelAgentManager.getInstance();
|
|
14852
|
+
manager.handleEvent(event);
|
|
14853
|
+
} catch {
|
|
14854
|
+
}
|
|
14981
14855
|
if (event.type === "session.deleted") {
|
|
14982
14856
|
const props = event.properties;
|
|
14983
14857
|
if (props?.info?.id) {
|