opencode-orchestrator 1.0.5 β†’ 1.0.7

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 CHANGED
@@ -66,33 +66,59 @@ Built for "Infinite Missions," the OpenCode Orchestrator is engineered to handle
66
66
  ## πŸ›οΈ Workflow Architecture
67
67
 
68
68
  ```
69
- /extreme-mission "Build REST API"
70
- β”‚
71
- ╔═══════════════════════════════════╗
72
- β•‘ 🎯 COMMANDER β€” "Start the task" β•‘
73
- β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•€β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
74
- β”‚
75
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
76
- β–Ό β–Ό β–Ό
77
- β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”
78
- β”‚PLANNERβ”‚ β”‚WORKER β”‚ β”‚WORKER β”‚ ← πŸ”₯ 50 PARALLEL
79
- β”‚plan.mdβ”‚ β”‚auth.tsβ”‚ β”‚api.ts β”‚ TASKS CONCURRENTLY
80
- β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜
81
- β”‚ β”‚ β”‚
82
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
83
- β–Ό
84
- ╔═══════════════════════════════════╗
85
- β•‘ βœ… REVIEWER β€” "Verify everything"β•‘
86
- β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•€β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
87
- β”‚
88
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
89
- β”‚ TODO 100%? β”‚
90
- β”‚ Issues = 0? β”‚
91
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
92
- No ↙ β†˜ Yes
93
- ♻️ LOOP βœ… COMPLETE
69
+ /extreme-mission "Build REST API"
70
+ β”‚
71
+ ╔═══════════════════════════════════════╗
72
+ β•‘ 🎯 COMMANDER β€” Orchestrator β•‘
73
+ β•‘ (Main Session - Single) β•‘
74
+ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•€β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
75
+ β”‚
76
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
77
+ β”‚ πŸ“‹ PLANNER β€” Create Plan β”‚
78
+ β”‚ (Single, Sync Call) β”‚
79
+ β”‚ β†’ Outputs: .opencode/todo.md β”‚
80
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
81
+ β”‚
82
+ ══════════════════════════╧══════════════════════════
83
+ β•‘ πŸ”₯ PARALLEL ZONE β•‘
84
+ ══════════════════════════════════════════════════════
85
+ β”‚ β”‚ β”‚
86
+ β–Ό β–Ό β–Ό
87
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
88
+ β”‚ πŸ”¨ WORKERβ”‚ β”‚ πŸ”¨ WORKERβ”‚ β”‚ πŸ”¨ WORKERβ”‚ ← Up to 50
89
+ β”‚ Task 1 β”‚ β”‚ Task 2 β”‚ β”‚ Task 3 β”‚ concurrent
90
+ β”‚ auth.ts β”‚ β”‚ api.ts β”‚ β”‚ db.ts β”‚ sessions
91
+ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
92
+ β”‚ β”‚ β”‚
93
+ ══════════════════════════════════════════════════════
94
+ β•‘ ⏳ SYNC BARRIER β•‘
95
+ ══════════════════════════════════════════════════════
96
+ β”‚
97
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
98
+ β”‚ βœ… REVIEWER β€” Verify All β”‚
99
+ β”‚ (Single, Sync Call) β”‚
100
+ β”‚ β†’ Tests, Lint, Integration β”‚
101
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
102
+ β”‚
103
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
104
+ β”‚ All Complete? β”‚
105
+ β”‚ Issues = 0? β”‚
106
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
107
+ No ↙ β†˜ Yes
108
+ ♻️ LOOP πŸŽ–οΈ MISSION
109
+ (back to SEALED
110
+ Commander)
94
111
  ```
95
112
 
113
+ ### Execution Model
114
+
115
+ | Phase | Agent | Parallelism | Blocking |
116
+ |:------|:------|:------------|:---------|
117
+ | 1️⃣ Plan | Planner | **Single** | Sync (waits) |
118
+ | 2️⃣ Execute | Workers | **Parallel** (up to 50) | Async (background) |
119
+ | 3️⃣ Verify | Reviewer | **Single** | Sync (waits) |
120
+ | 4️⃣ Loop | Commander | **Single** | Coordinates |
121
+
96
122
  ## Features
97
123
 
98
124
  | Feature | What It Does |
@@ -12,3 +12,4 @@ export { VERIFICATION_REQUIREMENTS } from "./verification.js";
12
12
  export { CORE_PHILOSOPHY } from "./core-philosophy.js";
13
13
  export { SHARED_LSP_TOOLS } from "./lsp.js";
14
14
  export { SHARED_AST_TOOLS } from "./ast.js";
15
+ export { MODULARITY_ENFORCEMENT } from "./modularity.js";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Modularity & Separation of Concerns (Anti-Monolith Strategy)
3
+ *
4
+ * This fragment enforces physical file separation to prevent monolithic output.
5
+ */
6
+ export declare const MODULARITY_ENFORCEMENT: string;
@@ -5,5 +5,5 @@ export declare const CONFIG: {
5
5
  readonly TASK_TTL_MS: number;
6
6
  readonly CLEANUP_DELAY_MS: number;
7
7
  readonly MIN_STABILITY_MS: number;
8
- readonly POLL_INTERVAL_MS: 500;
8
+ readonly POLL_INTERVAL_MS: 2000;
9
9
  };
@@ -15,6 +15,19 @@ export declare class TaskLauncher {
15
15
  private onTaskError;
16
16
  private startPolling;
17
17
  constructor(client: OpencodeClient, directory: string, store: TaskStore, concurrency: ConcurrencyController, onTaskError: (taskId: string, error: unknown) => void, startPolling: () => void);
18
- launch(input: LaunchInput): Promise<ParallelTask>;
18
+ /**
19
+ * Unified launch method - handles both single and multiple tasks efficiently.
20
+ * All session creations happen in parallel immediately.
21
+ * Concurrency acquisition and prompt firing happen in the background.
22
+ */
23
+ launch(inputs: LaunchInput | LaunchInput[]): Promise<ParallelTask | ParallelTask[]>;
24
+ /**
25
+ * Prepare task: Create session and registration without blocking on concurrency
26
+ */
27
+ private prepareTask;
28
+ /**
29
+ * Background execution: Acquire slot and fire prompt
30
+ */
31
+ private executeBackground;
19
32
  }
20
33
  export {};
@@ -30,7 +30,7 @@ export declare class ParallelAgentManager {
30
30
  private eventHandler;
31
31
  private constructor();
32
32
  static getInstance(client?: OpencodeClient, directory?: string): ParallelAgentManager;
33
- launch(input: LaunchInput): Promise<ParallelTask>;
33
+ launch(inputs: LaunchInput | LaunchInput[]): Promise<ParallelTask | ParallelTask[]>;
34
34
  resume(input: ResumeInput): Promise<ParallelTask>;
35
35
  getTask(id: string): ParallelTask | undefined;
36
36
  getRunningTasks(): ParallelTask[];
package/dist/index.js CHANGED
@@ -293,12 +293,18 @@ var PARALLEL_TASK = {
293
293
  DEFAULT_CONCURRENCY: 3,
294
294
  MAX_CONCURRENCY: 10,
295
295
  // Sync polling (for delegate_task sync mode)
296
+ // Optimized: Reduced polling frequency while relying more on events
296
297
  SYNC_TIMEOUT_MS: 5 * TIME.MINUTE,
297
- POLL_INTERVAL_MS: 500,
298
- MIN_IDLE_TIME_MS: 5 * TIME.SECOND,
299
- MIN_STABILITY_MS: 3 * TIME.SECOND,
300
- STABLE_POLLS_REQUIRED: 3,
301
- MAX_POLL_COUNT: 600,
298
+ POLL_INTERVAL_MS: 2e3,
299
+ // 500 β†’ 2000ms (75% less API calls)
300
+ MIN_IDLE_TIME_MS: 3 * TIME.SECOND,
301
+ // 5s β†’ 3s (faster detection)
302
+ MIN_STABILITY_MS: 2 * TIME.SECOND,
303
+ // 3s β†’ 2s (faster stability)
304
+ STABLE_POLLS_REQUIRED: 2,
305
+ // 3 β†’ 2 (faster completion)
306
+ MAX_POLL_COUNT: 150,
307
+ // 600 β†’ 150 (adjusted for 2s interval)
302
308
  // Session naming
303
309
  SESSION_TITLE_PREFIX: "Parallel",
304
310
  // Labels for output
@@ -1610,10 +1616,10 @@ function mergeDefs(...defs) {
1610
1616
  function cloneDef(schema) {
1611
1617
  return mergeDefs(schema._zod.def);
1612
1618
  }
1613
- function getElementAtPath(obj, path5) {
1614
- if (!path5)
1619
+ function getElementAtPath(obj, path6) {
1620
+ if (!path6)
1615
1621
  return obj;
1616
- return path5.reduce((acc, key) => acc?.[key], obj);
1622
+ return path6.reduce((acc, key) => acc?.[key], obj);
1617
1623
  }
1618
1624
  function promiseAllObject(promisesObj) {
1619
1625
  const keys = Object.keys(promisesObj);
@@ -1974,11 +1980,11 @@ function aborted(x, startIndex = 0) {
1974
1980
  }
1975
1981
  return false;
1976
1982
  }
1977
- function prefixIssues(path5, issues) {
1983
+ function prefixIssues(path6, issues) {
1978
1984
  return issues.map((iss) => {
1979
1985
  var _a;
1980
1986
  (_a = iss).path ?? (_a.path = []);
1981
- iss.path.unshift(path5);
1987
+ iss.path.unshift(path6);
1982
1988
  return iss;
1983
1989
  });
1984
1990
  }
@@ -2146,7 +2152,7 @@ function treeifyError(error45, _mapper) {
2146
2152
  return issue2.message;
2147
2153
  };
2148
2154
  const result = { errors: [] };
2149
- const processError = (error46, path5 = []) => {
2155
+ const processError = (error46, path6 = []) => {
2150
2156
  var _a, _b;
2151
2157
  for (const issue2 of error46.issues) {
2152
2158
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -2156,7 +2162,7 @@ function treeifyError(error45, _mapper) {
2156
2162
  } else if (issue2.code === "invalid_element") {
2157
2163
  processError({ issues: issue2.issues }, issue2.path);
2158
2164
  } else {
2159
- const fullpath = [...path5, ...issue2.path];
2165
+ const fullpath = [...path6, ...issue2.path];
2160
2166
  if (fullpath.length === 0) {
2161
2167
  result.errors.push(mapper(issue2));
2162
2168
  continue;
@@ -2188,8 +2194,8 @@ function treeifyError(error45, _mapper) {
2188
2194
  }
2189
2195
  function toDotPath(_path) {
2190
2196
  const segs = [];
2191
- const path5 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2192
- for (const seg of path5) {
2197
+ const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2198
+ for (const seg of path6) {
2193
2199
  if (typeof seg === "number")
2194
2200
  segs.push(`[${seg}]`);
2195
2201
  else if (typeof seg === "symbol")
@@ -13679,6 +13685,34 @@ var SHARED_AST_TOOLS = `<ast_tools>
13679
13685
  - Always verify structural changes with \`${TOOL_NAMES.LSP_DIAGNOSTICS}\`.
13680
13686
  </ast_tools>`;
13681
13687
 
13688
+ // src/agents/prompts/common/modularity.ts
13689
+ var MODULARITY_ENFORCEMENT = `${PROMPT_TAGS.QUALITY_CHECKLIST.open}
13690
+ \u{1F3D7}\uFE0F UNIVERSAL ARCHITECTURAL MODULARITY (Language-Agnostic)
13691
+
13692
+ To maintain a scalable and maintainable codebase, follow these structural principles regardless of the programming language:
13693
+
13694
+ ### 1. Structural Layering (The "Layer Separation" Rule)
13695
+ Physically separate code based on its functional role. Do not mix these layers in a single file:
13696
+ - **Definitions & Contracts**: Data structures, types, interfaces, or schemas that define the "shape" of data.
13697
+ - **Static Values & Constants**: Hard-coded values, configuration keys, translations, or design tokens.
13698
+ - **Core Implementation**: The active logic, algorithms, functions, or classes that perform the work.
13699
+ - **State & Storage**: Persistent data handling, database interactions, or memory management logic.
13700
+
13701
+ ### 2. Folder-Based Encapsulation (Feature-Oriented)
13702
+ - **Group by Domain/Feature**: Instead of grouping by technical type (e.g., all "utils" in one flat folder), create a directory for each meaningful feature or domain.
13703
+ - **Internal Structure**: For any feature complex enough to need multiple files, use a dedicated folder.
13704
+ - **Public Interface**: Each folder should have a clear entry point (e.g., \`index\`, \`mod.rs\`, \`main\`, or exports) that acts as the "Receptionist" for that module, hiding internal complexity.
13705
+
13706
+ ### 3. Complexity Sharding
13707
+ If a single unit of code (file or module) starts to handle multiple distinct concerns, **shard it** into a directory:
13708
+ - **High Cohesion**: Keep related code close together within the same folder.
13709
+ - **Low Coupling**: Minimize dependencies between folders. Use "Shared/Common" directories only for truly universal helpers.
13710
+
13711
+ ### 4. Code "Mass" Limits
13712
+ - Keep individual files concise and focused on a single responsibility.
13713
+ - If you have to scroll through "screens of code" to find a different type of logic, it belongs in a new file or sub-folder.
13714
+ ${PROMPT_TAGS.QUALITY_CHECKLIST.close}`;
13715
+
13682
13716
  // src/agents/prompts/commander/role.ts
13683
13717
  var COMMANDER_ROLE = `${PROMPT_TAGS.ROLE.open}
13684
13718
  You are ${AGENT_NAMES.COMMANDER}. Autonomous mission controller.
@@ -15295,6 +15329,7 @@ var commander = {
15295
15329
  // src/agents/subagents/planner.ts
15296
15330
  var systemPrompt2 = [
15297
15331
  PLANNER_ROLE,
15332
+ MODULARITY_ENFORCEMENT,
15298
15333
  PLANNER_FORBIDDEN,
15299
15334
  PLANNER_REQUIRED,
15300
15335
  ENVIRONMENT_DISCOVERY,
@@ -15320,6 +15355,7 @@ var planner = {
15320
15355
  // src/agents/subagents/worker.ts
15321
15356
  var systemPrompt3 = [
15322
15357
  WORKER_ROLE,
15358
+ MODULARITY_ENFORCEMENT,
15323
15359
  WORKER_FORBIDDEN,
15324
15360
  WORKER_REQUIRED,
15325
15361
  ANTI_HALLUCINATION_CORE,
@@ -15347,6 +15383,7 @@ var worker = {
15347
15383
  // src/agents/subagents/reviewer.ts
15348
15384
  var systemPrompt4 = [
15349
15385
  REVIEWER_ROLE,
15386
+ MODULARITY_ENFORCEMENT,
15350
15387
  REVIEWER_FORBIDDEN,
15351
15388
  REVIEWER_REQUIRED,
15352
15389
  REVIEWER_VERIFICATION,
@@ -16229,7 +16266,7 @@ var TaskStore = class {
16229
16266
  return Array.from(this.tasks.values());
16230
16267
  }
16231
16268
  getRunning() {
16232
- return this.getAll().filter((t) => t.status === TASK_STATUS.RUNNING);
16269
+ return this.getAll().filter((t) => t.status === TASK_STATUS.RUNNING || t.status === TASK_STATUS.PENDING);
16233
16270
  }
16234
16271
  getByParent(parentSessionID) {
16235
16272
  return this.getAll().filter((t) => t.parentSessionID === parentSessionID);
@@ -16948,74 +16985,111 @@ var TaskLauncher = class {
16948
16985
  this.onTaskError = onTaskError;
16949
16986
  this.startPolling = startPolling;
16950
16987
  }
16951
- async launch(input) {
16952
- log("[task-launcher.ts] launch() called", { agent: input.agent, description: input.description, parent: input.parentSessionID });
16953
- const concurrencyKey = input.agent;
16954
- await this.concurrency.acquire(concurrencyKey);
16955
- log("[task-launcher.ts] concurrency acquired for", concurrencyKey);
16956
- try {
16957
- const createResult = await this.client.session.create({
16958
- body: { parentID: input.parentSessionID, title: `${PARALLEL_TASK.SESSION_TITLE_PREFIX}: ${input.description}` },
16959
- query: { directory: this.directory }
16988
+ /**
16989
+ * Unified launch method - handles both single and multiple tasks efficiently.
16990
+ * All session creations happen in parallel immediately.
16991
+ * Concurrency acquisition and prompt firing happen in the background.
16992
+ */
16993
+ async launch(inputs) {
16994
+ const isArray = Array.isArray(inputs);
16995
+ const taskInputs = isArray ? inputs : [inputs];
16996
+ if (taskInputs.length === 0) return isArray ? [] : null;
16997
+ log(`[task-launcher.ts] Batch launching ${taskInputs.length} task(s)`);
16998
+ const startTime = Date.now();
16999
+ const tasks = await Promise.all(taskInputs.map(
17000
+ (input) => this.prepareTask(input).catch((error45) => {
17001
+ log(`[task-launcher.ts] Failed to prepare task ${input.description}:`, error45);
17002
+ return null;
17003
+ })
17004
+ ));
17005
+ const successfulTasks = tasks.filter((t) => t !== null);
17006
+ successfulTasks.forEach((task) => {
17007
+ this.executeBackground(task).catch((error45) => {
17008
+ log(`[task-launcher.ts] Background execution failed for ${task.id}:`, error45);
17009
+ this.onTaskError(task.id, error45);
16960
17010
  });
16961
- if (createResult.error) {
16962
- this.concurrency.release(concurrencyKey);
16963
- throw new Error(`Failed to create session: ${createResult.error}`);
16964
- }
16965
- const sessionID = createResult.data.id;
16966
- const taskId = `${ID_PREFIX.TASK}${crypto.randomUUID().slice(0, 8)}`;
16967
- const depth = (input.depth ?? 0) + 1;
16968
- log("[task-launcher.ts] Creating task with depth", depth);
16969
- const task = {
17011
+ });
17012
+ const elapsed = Date.now() - startTime;
17013
+ log(`[task-launcher.ts] Batch launch prepared: ${successfulTasks.length} tasks in ${elapsed}ms`);
17014
+ if (successfulTasks.length > 0) {
17015
+ this.startPolling();
17016
+ }
17017
+ return isArray ? successfulTasks : successfulTasks[0] || null;
17018
+ }
17019
+ /**
17020
+ * Prepare task: Create session and registration without blocking on concurrency
17021
+ */
17022
+ async prepareTask(input) {
17023
+ const createResult = await this.client.session.create({
17024
+ body: {
17025
+ parentID: input.parentSessionID,
17026
+ title: `${PARALLEL_TASK.SESSION_TITLE_PREFIX}: ${input.description}`
17027
+ },
17028
+ query: { directory: this.directory }
17029
+ });
17030
+ if (createResult.error || !createResult.data?.id) {
17031
+ throw new Error(`Session creation failed: ${createResult.error || "No ID"}`);
17032
+ }
17033
+ const sessionID = createResult.data.id;
17034
+ const taskId = `${ID_PREFIX.TASK}${crypto.randomUUID().slice(0, 8)}`;
17035
+ const task = {
17036
+ id: taskId,
17037
+ sessionID,
17038
+ parentSessionID: input.parentSessionID,
17039
+ description: input.description,
17040
+ prompt: input.prompt,
17041
+ agent: input.agent,
17042
+ status: TASK_STATUS.PENDING,
17043
+ // Start as PENDING
17044
+ startedAt: /* @__PURE__ */ new Date(),
17045
+ concurrencyKey: input.agent,
17046
+ depth: (input.depth ?? 0) + 1
17047
+ };
17048
+ this.store.set(taskId, task);
17049
+ this.store.trackPending(input.parentSessionID, taskId);
17050
+ taskWAL.log(WAL_ACTIONS.LAUNCH, task).catch(() => {
17051
+ });
17052
+ const toastManager = getTaskToastManager();
17053
+ if (toastManager) {
17054
+ toastManager.addTask({
16970
17055
  id: taskId,
16971
- sessionID,
16972
- parentSessionID: input.parentSessionID,
16973
17056
  description: input.description,
16974
- prompt: input.prompt,
16975
17057
  agent: input.agent,
16976
- status: TASK_STATUS.RUNNING,
16977
- startedAt: /* @__PURE__ */ new Date(),
16978
- concurrencyKey,
16979
- depth
16980
- };
16981
- this.store.set(taskId, task);
16982
- this.store.trackPending(input.parentSessionID, taskId);
17058
+ isBackground: true,
17059
+ parentSessionID: input.parentSessionID,
17060
+ sessionID
17061
+ });
17062
+ }
17063
+ presets.sessionCreated(sessionID, input.agent);
17064
+ return task;
17065
+ }
17066
+ /**
17067
+ * Background execution: Acquire slot and fire prompt
17068
+ */
17069
+ async executeBackground(task) {
17070
+ try {
17071
+ await this.concurrency.acquire(task.agent);
17072
+ task.status = TASK_STATUS.RUNNING;
17073
+ task.startedAt = /* @__PURE__ */ new Date();
17074
+ this.store.set(task.id, task);
16983
17075
  taskWAL.log(WAL_ACTIONS.LAUNCH, task).catch(() => {
16984
17076
  });
16985
- this.startPolling();
16986
- this.client.session.prompt({
16987
- path: { id: sessionID },
17077
+ await this.client.session.prompt({
17078
+ path: { id: task.sessionID },
16988
17079
  body: {
16989
- agent: input.agent,
17080
+ agent: task.agent,
16990
17081
  tools: {
16991
- // Prevent recursive task spawning from subagents
16992
17082
  delegate_task: false,
16993
17083
  get_task_result: false,
16994
17084
  list_tasks: false,
16995
17085
  cancel_task: false
16996
17086
  },
16997
- parts: [{ type: PART_TYPES.TEXT, text: input.prompt }]
17087
+ parts: [{ type: PART_TYPES.TEXT, text: task.prompt }]
16998
17088
  }
16999
- }).catch((error45) => {
17000
- log(`Prompt error for ${taskId}:`, error45);
17001
- this.onTaskError(taskId, error45);
17002
17089
  });
17003
- const toastManager = getTaskToastManager();
17004
- if (toastManager) {
17005
- toastManager.addTask({
17006
- id: taskId,
17007
- description: input.description,
17008
- agent: input.agent,
17009
- isBackground: true,
17010
- parentSessionID: input.parentSessionID,
17011
- sessionID
17012
- });
17013
- }
17014
- presets.sessionCreated(sessionID, input.agent);
17015
- log(`Launched ${taskId} in session ${sessionID}`);
17016
- return task;
17090
+ log(`[task-launcher.ts] Task ${task.id} (${task.agent}) started running`);
17017
17091
  } catch (error45) {
17018
- this.concurrency.release(concurrencyKey);
17092
+ this.concurrency.release(task.agent);
17019
17093
  throw error45;
17020
17094
  }
17021
17095
  }
@@ -17116,6 +17190,7 @@ var TaskPoller = class {
17116
17190
  const allStatuses = statusResult.data ?? {};
17117
17191
  for (const task of running) {
17118
17192
  try {
17193
+ if (task.status === TASK_STATUS.PENDING) continue;
17119
17194
  const sessionStatus = allStatuses[task.sessionID];
17120
17195
  if (sessionStatus?.type === SESSION_STATUS.IDLE) {
17121
17196
  const elapsed2 = Date.now() - task.startedAt.getTime();
@@ -17468,9 +17543,9 @@ var ParallelAgentManager = class _ParallelAgentManager {
17468
17543
  // ========================================================================
17469
17544
  // Public API
17470
17545
  // ========================================================================
17471
- async launch(input) {
17546
+ async launch(inputs) {
17472
17547
  this.cleaner.pruneExpiredTasks();
17473
- return this.launcher.launch(input);
17548
+ return this.launcher.launch(inputs);
17474
17549
  }
17475
17550
  async resume(input) {
17476
17551
  return this.resumer.resume(input);
@@ -17761,15 +17836,28 @@ var createDelegateTaskTool = (manager, client) => tool({
17761
17836
  }
17762
17837
  if (resume) {
17763
17838
  try {
17764
- const task = await manager.resume({
17839
+ const input = {
17765
17840
  sessionId: resume,
17766
17841
  prompt,
17767
- parentSessionID: ctx.sessionID
17768
- });
17842
+ parentSessionID: ctx.sessionID,
17843
+ agent,
17844
+ // Assuming agent is needed for resume context
17845
+ description
17846
+ // Assuming description is needed for resume context
17847
+ };
17848
+ const launchResult = await manager.launch(input);
17849
+ const task = Array.isArray(launchResult) ? launchResult[0] : launchResult;
17850
+ if (!task) {
17851
+ return `Failed to launch task: ${input.description}`;
17852
+ }
17853
+ const taskId = task.id;
17769
17854
  if (background === true) {
17770
- return `${OUTPUT_LABEL.RESUME} task: \`${task.id}\` (${task.agent}) in session \`${task.sessionID}\`
17855
+ const message = `Launched ${input.agent} task: ${input.description}
17856
+ Task ID: ${taskId}
17857
+ Session: ${task.sessionID}`;
17858
+ return `${OUTPUT_LABEL.RESUME} task: \`${taskId}\` (${task.agent}) in session \`${task.sessionID}\`
17771
17859
 
17772
- Previous context preserved. Use \`get_task_result({ taskId: "${task.id}" })\` when complete.`;
17860
+ Previous context preserved. Use \`get_task_result({ taskId: "${taskId}" })\` when complete.`;
17773
17861
  }
17774
17862
  const startTime = Date.now();
17775
17863
  const session = sessionClient.session;
@@ -17789,12 +17877,13 @@ ${text || "(No output)"}`;
17789
17877
  }
17790
17878
  if (background === true) {
17791
17879
  try {
17792
- const task = await manager.launch({
17880
+ const launchResult = await manager.launch({
17793
17881
  agent,
17794
17882
  description,
17795
17883
  prompt,
17796
17884
  parentSessionID: ctx.sessionID
17797
17885
  });
17886
+ const task = Array.isArray(launchResult) ? launchResult[0] : launchResult;
17798
17887
  presets.taskStarted(task.id, agent);
17799
17888
  return `${OUTPUT_LABEL.SPAWNED} task: \`${task.id}\` (${agent})
17800
17889
  Session: \`${task.sessionID}\` (save for resume)`;
@@ -18841,10 +18930,10 @@ async function resolveCommandPath(key, commandName) {
18841
18930
  const currentPending = pending.get(key);
18842
18931
  if (currentPending) return currentPending;
18843
18932
  const promise2 = (async () => {
18844
- const path5 = await findCommand(commandName);
18845
- cache[key] = path5;
18933
+ const path6 = await findCommand(commandName);
18934
+ cache[key] = path6;
18846
18935
  pending.delete(key);
18847
- return path5;
18936
+ return path6;
18848
18937
  })();
18849
18938
  pending.set(key, promise2);
18850
18939
  return promise2;
@@ -18903,21 +18992,21 @@ import { exec as exec2 } from "node:child_process";
18903
18992
  import { promisify as promisify2 } from "node:util";
18904
18993
  var execAsync2 = promisify2(exec2);
18905
18994
  async function notifyDarwin(title, message) {
18906
- const path5 = await resolveCommandPath(
18995
+ const path6 = await resolveCommandPath(
18907
18996
  NOTIFICATION_COMMAND_KEYS.OSASCRIPT,
18908
18997
  NOTIFICATION_COMMANDS.OSASCRIPT
18909
18998
  );
18910
- if (!path5) return;
18999
+ if (!path6) return;
18911
19000
  const escT = title.replace(/"/g, '\\"');
18912
19001
  const escM = message.replace(/"/g, '\\"');
18913
- await execAsync2(`${path5} -e 'display notification "${escM}" with title "${escT}" sound name "Glass"'`);
19002
+ await execAsync2(`${path6} -e 'display notification "${escM}" with title "${escT}" sound name "Glass"'`);
18914
19003
  }
18915
19004
  async function notifyLinux(title, message) {
18916
- const path5 = await resolveCommandPath(
19005
+ const path6 = await resolveCommandPath(
18917
19006
  NOTIFICATION_COMMAND_KEYS.NOTIFY_SEND,
18918
19007
  NOTIFICATION_COMMANDS.NOTIFY_SEND
18919
19008
  );
18920
- if (path5) await execAsync2(`${path5} "${title}" "${message}" 2>/dev/null`);
19009
+ if (path6) await execAsync2(`${path6} "${title}" "${message}" 2>/dev/null`);
18921
19010
  }
18922
19011
  async function notifyWindows(title, message) {
18923
19012
  const ps = await resolveCommandPath(
@@ -18963,11 +19052,11 @@ import { exec as exec3 } from "node:child_process";
18963
19052
  async function playDarwin(soundPath) {
18964
19053
  if (!soundPath) return;
18965
19054
  try {
18966
- const path5 = await resolveCommandPath(
19055
+ const path6 = await resolveCommandPath(
18967
19056
  NOTIFICATION_COMMAND_KEYS.AFPLAY,
18968
19057
  NOTIFICATION_COMMANDS.AFPLAY
18969
19058
  );
18970
- if (path5) exec3(`"${path5}" "${soundPath}"`);
19059
+ if (path6) exec3(`"${path6}" "${soundPath}"`);
18971
19060
  } catch (err) {
18972
19061
  log(`[session-notify] Error playing sound (Darwin): ${err}`);
18973
19062
  }
@@ -20237,10 +20326,65 @@ function createEventHandler(ctx) {
20237
20326
  };
20238
20327
  }
20239
20328
 
20329
+ // src/utils/compatibility/claude.ts
20330
+ import fs6 from "fs";
20331
+ import path5 from "path";
20332
+ function findClaudeRules(startDir = process.cwd()) {
20333
+ try {
20334
+ let currentDir = startDir;
20335
+ const root = path5.parse(startDir).root;
20336
+ while (true) {
20337
+ const claudeMdPath = path5.join(currentDir, "CLAUDE.md");
20338
+ if (fs6.existsSync(claudeMdPath)) {
20339
+ try {
20340
+ const content = fs6.readFileSync(claudeMdPath, "utf-8");
20341
+ log(`[compatibility] Loaded CLAUDE.md from ${claudeMdPath}`);
20342
+ return formatRules("CLAUDE.md", content);
20343
+ } catch (e) {
20344
+ log(`[compatibility] Error reading CLAUDE.md: ${e}`);
20345
+ }
20346
+ }
20347
+ if (currentDir === root) break;
20348
+ currentDir = path5.dirname(currentDir);
20349
+ }
20350
+ const copilotPath = path5.join(startDir, ".github", "copilot-instructions.md");
20351
+ if (fs6.existsSync(copilotPath)) {
20352
+ return formatRules("Copilot Instructions", fs6.readFileSync(copilotPath, "utf-8"));
20353
+ }
20354
+ return null;
20355
+ } catch (error45) {
20356
+ log(`[compatibility] Error finding Claude rules: ${error45}`);
20357
+ return null;
20358
+ }
20359
+ }
20360
+ function formatRules(source, content) {
20361
+ return `
20362
+ <project_rules source="${source}">
20363
+ ${content}
20364
+ </project_rules>
20365
+
20366
+ <claude_compatibility>
20367
+ These rules are from the project's ${source}.
20368
+ You MUST follow them as strictly as if they were your system prompt.
20369
+ This plugin runs in "Claude Code Compatibility Mode".
20370
+ </claude_compatibility>
20371
+ `;
20372
+ }
20373
+
20240
20374
  // src/plugin-handlers/config-handler.ts
20241
20375
  function createConfigHandler() {
20242
- const commanderPrompt = AGENTS[AGENT_NAMES.COMMANDER]?.systemPrompt || "";
20243
20376
  return async (config2) => {
20377
+ const claudeRules = findClaudeRules();
20378
+ const injectRules = (prompt) => {
20379
+ if (!claudeRules) return prompt;
20380
+ return `${prompt}
20381
+
20382
+ ${claudeRules}`;
20383
+ };
20384
+ const commanderPrompt = injectRules(AGENTS[AGENT_NAMES.COMMANDER]?.systemPrompt || "");
20385
+ const plannerPrompt = injectRules(AGENTS[AGENT_NAMES.PLANNER]?.systemPrompt || "");
20386
+ const workerPrompt = injectRules(AGENTS[AGENT_NAMES.WORKER]?.systemPrompt || "");
20387
+ const reviewerPrompt = injectRules(AGENTS[AGENT_NAMES.REVIEWER]?.systemPrompt || "");
20244
20388
  const existingCommands = config2.command ?? {};
20245
20389
  const existingAgents = config2.agent ?? {};
20246
20390
  const orchestratorCommands = {};
@@ -20266,7 +20410,7 @@ function createConfigHandler() {
20266
20410
  description: "Strategic planning and research specialist",
20267
20411
  mode: "subagent",
20268
20412
  hidden: true,
20269
- prompt: AGENTS[AGENT_NAMES.PLANNER]?.systemPrompt || "",
20413
+ prompt: plannerPrompt,
20270
20414
  maxTokens: AGENT_TOKENS.SUBAGENT_MAX_TOKENS,
20271
20415
  color: "#9B59B6"
20272
20416
  },
@@ -20274,7 +20418,7 @@ function createConfigHandler() {
20274
20418
  description: "Implementation and documentation specialist",
20275
20419
  mode: "subagent",
20276
20420
  hidden: true,
20277
- prompt: AGENTS[AGENT_NAMES.WORKER]?.systemPrompt || "",
20421
+ prompt: workerPrompt,
20278
20422
  maxTokens: AGENT_TOKENS.SUBAGENT_MAX_TOKENS,
20279
20423
  color: "#E67E22"
20280
20424
  },
@@ -20282,7 +20426,7 @@ function createConfigHandler() {
20282
20426
  description: "Verification and context management specialist",
20283
20427
  mode: "subagent",
20284
20428
  hidden: true,
20285
- prompt: AGENTS[AGENT_NAMES.REVIEWER]?.systemPrompt || "",
20429
+ prompt: reviewerPrompt,
20286
20430
  maxTokens: AGENT_TOKENS.SUBAGENT_MAX_TOKENS,
20287
20431
  color: "#27AE60"
20288
20432
  }
@@ -8,11 +8,11 @@ export declare const PARALLEL_TASK: {
8
8
  readonly DEFAULT_CONCURRENCY: 3;
9
9
  readonly MAX_CONCURRENCY: 10;
10
10
  readonly SYNC_TIMEOUT_MS: number;
11
- readonly POLL_INTERVAL_MS: 500;
11
+ readonly POLL_INTERVAL_MS: 2000;
12
12
  readonly MIN_IDLE_TIME_MS: number;
13
13
  readonly MIN_STABILITY_MS: number;
14
- readonly STABLE_POLLS_REQUIRED: 3;
15
- readonly MAX_POLL_COUNT: 600;
14
+ readonly STABLE_POLLS_REQUIRED: 2;
15
+ readonly MAX_POLL_COUNT: 150;
16
16
  readonly SESSION_TITLE_PREFIX: "Parallel";
17
17
  readonly LABEL: "parallel";
18
18
  readonly GROUP_PREFIX: "parallel:";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Find and read CLAUDE.md guidelines.
3
+ * Follows Claude Code's discovery logic:
4
+ * 1. CLAUDE.md in current directory or parents
5
+ * 2. .github/instructions/ *.md (checking main ones)
6
+ * 3. .cursor/rules/ *.md
7
+ * 4. .claude/rules/ *.md
8
+ */
9
+ export declare function findClaudeRules(startDir?: string): string | null;
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": "1.0.5",
5
+ "version": "1.0.7",
6
6
  "author": "agnusdei1207",
7
7
  "license": "MIT",
8
8
  "repository": {