opencode-orchestrator 1.0.40 → 1.0.42

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.
@@ -14,3 +14,4 @@ export { SHARED_LSP_TOOLS } from "./lsp.js";
14
14
  export { SHARED_AST_TOOLS } from "./ast.js";
15
15
  export { MODULARITY_ENFORCEMENT } from "./modularity.js";
16
16
  export { HYPER_PARALLEL_ENFORCEMENT } from "./hyper-parallel.js";
17
+ export { SKILLS_CAPABILITIES } from "./skills.js";
@@ -0,0 +1 @@
1
+ export declare const SKILLS_CAPABILITIES: string;
package/dist/index.js CHANGED
@@ -272,7 +272,7 @@ var WAL_ACTIONS = {
272
272
  var PHASES = {
273
273
  PHASE_0: {
274
274
  ID: "PHASE_0",
275
- NAME: "DISCOVERY SWARM",
275
+ NAME: "DISCOVERY",
276
276
  DESCRIPTION: "Parallel intelligence gathering and project mapping",
277
277
  MANDATORY: true
278
278
  },
@@ -577,6 +577,8 @@ var TOOL_NAMES = {
577
577
  CHECK_BACKGROUND: "check_background",
578
578
  LIST_BACKGROUND: "list_background",
579
579
  KILL_BACKGROUND: "kill_background",
580
+ // Command tools
581
+ RUN_COMMAND: "run_command",
580
582
  // Search tools
581
583
  GREP_SEARCH: "grep_search",
582
584
  GLOB_SEARCH: "glob_search",
@@ -605,7 +607,8 @@ var TOOL_NAMES = {
605
607
  AST_REPLACE: "ast_replace",
606
608
  // Other tools
607
609
  CALL_AGENT: "call_agent",
608
- SLASHCOMMAND: "slashcommand"
610
+ SLASHCOMMAND: "slashcommand",
611
+ SKILL: "skill"
609
612
  };
610
613
 
611
614
  // src/shared/tool/constants/lsp/lsp-severity.ts
@@ -793,6 +796,11 @@ var PROMPT_TAGS = {
793
796
  MISSION_LOOP: { open: "<mission_loop>", close: "</mission_loop>" },
794
797
  AUTONOMOUS_MODE: { open: "<autonomous_mode>", close: "</autonomous_mode>" }
795
798
  };
799
+ var wrapTag = (tag, content) => {
800
+ return `${tag.open}
801
+ ${content}
802
+ ${tag.close}`;
803
+ };
796
804
 
797
805
  // src/shared/prompt/constants/status.ts
798
806
  var WORK_STATUS = {
@@ -916,14 +924,15 @@ var PHILOSOPHY_LEARN_PRINCIPLE = "LEARN = DOCUMENT: What you discover, you recor
916
924
 
917
925
  // src/shared/prompt/constants/mandates.ts
918
926
  var PHASE_0_DIRECT_DISCOVERY = `**Direct Project Discovery**: Read the project directly to understand it.
919
-
920
- 1. **STRUCTURE**: Run \`ls -la\` and \`find . -maxdepth 2 -type d | head -30\` to map the project layout.
921
- 2. **STACK**: Read config files (package.json, Cargo.toml, go.mod, etc.) to identify build/test commands.
922
- 3. **DOCS**: Read README.md and key documentation to understand the architecture.
923
- 4. **INFRA**: Check for Dockerfile, CI/CD configs, and infrastructure files.
924
- 5. **CONSOLIDATE**: Save findings to \`${PATHS.CONTEXT}\`.
925
-
926
- [EFFICIENT]: Direct reading is faster and cheaper than delegating to parallel scouts.`;
927
+
928
+ 0. **FRESH START**: If ${PATHS.TODO} or ${PATHS.CONTEXT} exist, assume they are from a previous task. Archive or overwrite them.
929
+ 1. **STRUCTURE**: Run \`ls -la\` and \`find . -maxdepth 2 -type d | head -30\` to map the project layout.
930
+ 2. **STACK**: Read config files (package.json, Cargo.toml, go.mod, etc.) to identify build/test commands.
931
+ 3. **DOCS**: Read README.md and key documentation to understand the architecture.
932
+ 4. **INFRA**: Check for Dockerfile, CI/CD configs, and infrastructure files.
933
+ 5. **CONSOLIDATE**: FORCE OVERWRITE \`${PATHS.CONTEXT}\` with fresh findings. Do NOT read existing content; assume it is stale.
934
+
935
+ [EFFICIENT]: Direct reading is faster and cheaper than delegating to parallel scouts.`;
927
936
  var PHASE_1_THINK_ANALYSIS = `### 1.1 ANALYZE & SCOPE (INPUT)
928
937
  - **Review consolidated ${PATHS.CONTEXT} from Phase 0.**
929
938
  - Map discovered files to the user's request.
@@ -13877,6 +13886,23 @@ ${HPFA_RULES}
13877
13886
  [CRITICAL]: Sequential execution when parallel is possible is a mission failure.
13878
13887
  ${PROMPT_TAGS.QUALITY_CHECKLIST.close}`;
13879
13888
 
13889
+ // src/agents/prompts/common/skills.ts
13890
+ var SKILLS_CAPABILITIES = wrapTag({ open: "<skills_capabilities>", close: "</skills_capabilities>" }, `
13891
+ ### AUTONOMOUS SKILL ACQUISITION
13892
+ You have the ability to extend your capabilities using the **Skills System**.
13893
+ If you encounter a task or technology you are unfamiliar with (e.g., "deploy to AWS", "use specific library"):
13894
+
13895
+ 1. **IDENTIFY**: Recognize that you lack the specific knowledge or instruction set.
13896
+ 2. **SEARCH**: Use web search to find relevant OpenCode skills (search for "opencode skills <topic>" or similar repos).
13897
+ 3. **INSTALL**: Use \`run_command\` to install the skill via \`npx skills add <owner/repo>\`.
13898
+ - Example: \`run_command({ command: "npx skills add agnusdei1207/git-release" })\`
13899
+ 4. **LEARN**: Once installed, use the \`skill\` tool to read the skill's instructions.
13900
+ - Example: \`skill({ name: "git-release" })\`
13901
+ 5. **EXECUTE**: Apply the learned skill to your task.
13902
+
13903
+ **pro-tip**: You do NOT need to ask the user for permission to install standard skills. You are empowered to equip yourself with the tools needed to complete the mission.
13904
+ `);
13905
+
13880
13906
  // src/agents/prompts/commander/role.ts
13881
13907
  var COMMANDER_ROLE = `${PROMPT_TAGS.ROLE.open}
13882
13908
  You are ${AGENT_NAMES.COMMANDER}. Autonomous mission controller.
@@ -15503,6 +15529,7 @@ var systemPrompt2 = [
15503
15529
  PLANNER_RESEARCH,
15504
15530
  SHARED_LSP_TOOLS,
15505
15531
  SHARED_AST_TOOLS,
15532
+ SKILLS_CAPABILITIES,
15506
15533
  SHARED_WORKSPACE
15507
15534
  ].join("\n\n");
15508
15535
  var planner = {
@@ -15531,6 +15558,7 @@ var systemPrompt3 = [
15531
15558
  WORKER_LSP_TOOLS,
15532
15559
  SHARED_LSP_TOOLS,
15533
15560
  SHARED_AST_TOOLS,
15561
+ SKILLS_CAPABILITIES,
15534
15562
  VERIFICATION_REQUIREMENTS,
15535
15563
  SHARED_WORKSPACE
15536
15564
  ].join("\n\n");
@@ -16340,9 +16368,20 @@ var ConcurrencyController = class {
16340
16368
  return;
16341
16369
  }
16342
16370
  log2(`Queueing ${key}: ${current}/${limit}`);
16343
- return new Promise((resolve2) => {
16371
+ return new Promise((resolve2, reject) => {
16344
16372
  const queue = this.queues.get(key) ?? [];
16345
- queue.push(resolve2);
16373
+ const timeoutId = setTimeout(() => {
16374
+ const currentQueue = this.queues.get(key);
16375
+ if (currentQueue) {
16376
+ const index = currentQueue.findIndex((item) => item.resolve === resolve2);
16377
+ if (index !== -1) {
16378
+ currentQueue.splice(index, 1);
16379
+ this.queues.set(key, currentQueue);
16380
+ }
16381
+ }
16382
+ reject(new Error(`Concurrency acquisition timed out after 60s for ${key}`));
16383
+ }, 6e4);
16384
+ queue.push({ resolve: resolve2, timeoutId });
16346
16385
  this.queues.set(key, queue);
16347
16386
  });
16348
16387
  }
@@ -16352,8 +16391,9 @@ var ConcurrencyController = class {
16352
16391
  const queue = this.queues.get(key);
16353
16392
  if (queue && queue.length > 0) {
16354
16393
  const next = queue.shift();
16394
+ clearTimeout(next.timeoutId);
16355
16395
  log2(`Released ${key}: next in queue`);
16356
- next();
16396
+ next.resolve();
16357
16397
  } else {
16358
16398
  const current = this.counts.get(key) ?? 0;
16359
16399
  if (current > 0) {
@@ -17188,13 +17228,19 @@ var TaskLauncher = class {
17188
17228
  log(`[task-launcher.ts] Task depth limit reached (${currentDepth}/${PARALLEL_TASK.MAX_DEPTH}). Generation blocked.`);
17189
17229
  throw new Error(`Maximum task depth (${PARALLEL_TASK.MAX_DEPTH}) reached. To prevent infinite recursion, no further sub-tasks can be spawned.`);
17190
17230
  }
17191
- const createResult = await this.client.session.create({
17231
+ const sessionCreatePromise = this.client.session.create({
17192
17232
  body: {
17193
17233
  parentID: input.parentSessionID,
17194
17234
  title: `${PARALLEL_TASK.SESSION_TITLE_PREFIX}: ${input.description}`
17195
17235
  },
17196
17236
  query: { directory: this.directory }
17197
17237
  });
17238
+ const createResult = await Promise.race([
17239
+ sessionCreatePromise,
17240
+ new Promise(
17241
+ (_, reject) => setTimeout(() => reject(new Error("Session creation timed out after 15s")), 6e3)
17242
+ )
17243
+ ]);
17198
17244
  if (createResult.error || !createResult.data?.id) {
17199
17245
  throw new Error(`Session creation failed: ${createResult.error || "No ID"}`);
17200
17246
  }
@@ -17253,7 +17299,9 @@ var TaskLauncher = class {
17253
17299
  delegate_task: true,
17254
17300
  get_task_result: true,
17255
17301
  list_tasks: true,
17256
- cancel_task: true
17302
+ cancel_task: true,
17303
+ [TOOL_NAMES.SKILL]: true,
17304
+ [TOOL_NAMES.RUN_COMMAND]: true
17257
17305
  },
17258
17306
  parts: [{ type: PART_TYPES.TEXT, text: task.prompt }]
17259
17307
  }
@@ -17828,18 +17876,18 @@ var ParallelAgentManager = class _ParallelAgentManager {
17828
17876
  }
17829
17877
  async handleTaskComplete(task) {
17830
17878
  if (task.agent === AGENT_NAMES.WORKER && task.mode !== "race") {
17831
- log(`[MSVP] Triggering 1\uCC28 \uB9AC\uBDF0 (Unit Review) for task ${task.id}`);
17879
+ log(`[MSVP] Triggering Unit Review for task ${task.id}`);
17832
17880
  try {
17833
17881
  await this.launch({
17834
17882
  agent: AGENT_NAMES.REVIEWER,
17835
- description: `1\uCC28 \uB9AC\uBDF0: ${task.description}`,
17836
- prompt: `\uC9C4\uD589\uB41C \uC791\uC5C5(\`${task.description}\`)\uC5D0 \uB300\uD574 1\uCC28 \uB9AC\uBDF0(\uC720\uB2DB \uAC80\uC99D)\uB97C \uC218\uD589\uD558\uC138\uC694.
17837
- \uC8FC\uC694 \uC810\uAC80 \uC0AC\uD56D:
17838
- 1. \uD574\uB2F9 \uBAA8\uB4C8\uC758 \uC720\uB2DB \uD14C\uC2A4\uD2B8 \uCF54\uB4DC \uC791\uC131 \uC5EC\uBD80 \uBC0F \uD1B5\uACFC \uD655\uC778
17839
- 2. \uCF54\uB4DC \uD488\uC9C8 \uBC0F \uBAA8\uB4C8\uC131 \uC900\uC218 \uC5EC\uBD80
17840
- 3. \uBC1C\uACAC\uB41C \uACB0\uD568 \uC989\uC2DC \uC218\uC815 \uC9C0\uC2DC \uB610\uB294 \uB9AC\uD3EC\uD2B8
17841
-
17842
- \uC774 \uC791\uC5C5\uC740 \uC804\uCCB4 \uD1B5\uD569 \uC804 \uBD80\uD488 \uB2E8\uC704\uC758 \uC644\uACB0\uC131\uC744 \uBCF4\uC7A5\uD558\uAE30 \uC704\uD568\uC785\uB2C8\uB2E4.`,
17883
+ description: `Unit Review: ${task.description}`,
17884
+ prompt: `Perform a Unit Review (verification) for the completed task (\`${task.description}\`).
17885
+ Key Checklist:
17886
+ 1. Verify if unit test code for the module is written and passes.
17887
+ 2. Check for code quality and modularity compliance.
17888
+ 3. Instruct immediate correction of found defects or report them.
17889
+
17890
+ This task ensures the completeness of the unit before global integration.`,
17843
17891
  parentSessionID: task.parentSessionID,
17844
17892
  depth: task.depth,
17845
17893
  groupID: task.groupID || task.id
@@ -19351,6 +19399,239 @@ async function hasIncompleteTodos(client, sessionID) {
19351
19399
  }
19352
19400
  }
19353
19401
 
19402
+ // src/core/recovery/constants.ts
19403
+ var MAX_RETRIES = RECOVERY.MAX_ATTEMPTS;
19404
+ var BASE_DELAY = RECOVERY.BASE_DELAY_MS;
19405
+ var MAX_HISTORY = HISTORY.MAX_RECOVERY;
19406
+
19407
+ // src/core/recovery/patterns.ts
19408
+ var errorPatterns = [
19409
+ // Rate limiting
19410
+ {
19411
+ pattern: /rate.?limit|too.?many.?requests|429/i,
19412
+ category: "rate_limit",
19413
+ handler: (ctx) => {
19414
+ const delay = BASE_DELAY * Math.pow(2, ctx.attempt);
19415
+ presets.warningRateLimited();
19416
+ return { type: "retry", delay, attempt: ctx.attempt + 1 };
19417
+ }
19418
+ },
19419
+ // Context overflow
19420
+ {
19421
+ pattern: /context.?length|token.?limit|maximum.?context/i,
19422
+ category: "context_overflow",
19423
+ handler: () => {
19424
+ presets.errorRecovery("Compacting context");
19425
+ return { type: "compact", reason: "Context limit reached" };
19426
+ }
19427
+ },
19428
+ // Network errors
19429
+ {
19430
+ pattern: /ECONNREFUSED|ETIMEDOUT|network|fetch.?failed/i,
19431
+ category: "network",
19432
+ handler: (ctx) => {
19433
+ if (ctx.attempt >= MAX_RETRIES) {
19434
+ return { type: "abort", reason: "Network unavailable after retries" };
19435
+ }
19436
+ return { type: "retry", delay: BASE_DELAY * (ctx.attempt + 1), attempt: ctx.attempt + 1 };
19437
+ }
19438
+ },
19439
+ // Session errors
19440
+ {
19441
+ pattern: /session.?not.?found|session.?expired/i,
19442
+ category: "session",
19443
+ handler: () => {
19444
+ return { type: "abort", reason: "Session no longer available" };
19445
+ }
19446
+ },
19447
+ // Tool errors
19448
+ {
19449
+ pattern: /tool.?not.?found|unknown.?tool/i,
19450
+ category: "tool",
19451
+ handler: (ctx) => {
19452
+ return { type: "escalate", to: "Reviewer", reason: `Unknown tool used by ${ctx.agent}` };
19453
+ }
19454
+ },
19455
+ // Parse errors
19456
+ {
19457
+ pattern: /parse.?error|invalid.?json|syntax.?error/i,
19458
+ category: "parse",
19459
+ handler: (ctx) => {
19460
+ if (ctx.attempt >= 2) {
19461
+ return { type: "skip", reason: "Persistent parse error" };
19462
+ }
19463
+ return { type: "retry", delay: 500, attempt: ctx.attempt + 1 };
19464
+ }
19465
+ },
19466
+ // Gibberish / hallucination
19467
+ {
19468
+ pattern: /gibberish|hallucination|mixed.?language/i,
19469
+ category: "gibberish",
19470
+ handler: () => {
19471
+ presets.errorRecovery("Retrying with clean context");
19472
+ return { type: "retry", delay: 1e3, attempt: 1 };
19473
+ }
19474
+ }
19475
+ ];
19476
+
19477
+ // src/core/recovery/handler.ts
19478
+ var recoveryHistory = [];
19479
+ function handleError(context) {
19480
+ const errorMessage = context.error.message || String(context.error);
19481
+ for (const pattern of errorPatterns) {
19482
+ const matches = typeof pattern.pattern === "string" ? errorMessage.includes(pattern.pattern) : pattern.pattern.test(errorMessage);
19483
+ if (matches) {
19484
+ const action = pattern.handler(context);
19485
+ recoveryHistory.push({
19486
+ context,
19487
+ action,
19488
+ timestamp: /* @__PURE__ */ new Date()
19489
+ });
19490
+ if (recoveryHistory.length > MAX_HISTORY) {
19491
+ recoveryHistory.shift();
19492
+ }
19493
+ return action;
19494
+ }
19495
+ }
19496
+ if (context.attempt < MAX_RETRIES) {
19497
+ return {
19498
+ type: "retry",
19499
+ delay: BASE_DELAY * Math.pow(2, context.attempt),
19500
+ attempt: context.attempt + 1
19501
+ };
19502
+ }
19503
+ return { type: "abort", reason: `Unknown error after ${MAX_RETRIES} retries` };
19504
+ }
19505
+
19506
+ // src/core/recovery/session-recovery.ts
19507
+ var recoveryState = /* @__PURE__ */ new Map();
19508
+ function getState(sessionID) {
19509
+ let state2 = recoveryState.get(sessionID);
19510
+ if (!state2) {
19511
+ state2 = { isRecovering: false, lastErrorTime: 0, errorCount: 0 };
19512
+ recoveryState.set(sessionID, state2);
19513
+ }
19514
+ return state2;
19515
+ }
19516
+ var TOOL_CRASH_RECOVERY_PROMPT = `<recovery type="tool_crash">
19517
+ The previous tool execution failed. This is a system-level issue, not your fault.
19518
+
19519
+ <action>
19520
+ 1. Acknowledge the tool failure
19521
+ 2. Try an alternative approach using different tools
19522
+ 3. If the same tool is needed, retry with modified parameters
19523
+ 4. Continue with the original mission
19524
+ </action>
19525
+
19526
+ Do NOT apologize excessively. Just proceed.
19527
+ </recovery>`;
19528
+ var THINKING_RECOVERY_PROMPT = `<recovery type="thinking_block">
19529
+ There was a temporary processing issue. Please continue from where you left off.
19530
+
19531
+ <action>
19532
+ 1. Review your current progress
19533
+ 2. Identify the next pending task
19534
+ 3. Continue execution
19535
+ </action>
19536
+ </recovery>`;
19537
+ async function handleSessionError(client, sessionID, error45, properties) {
19538
+ const state2 = getState(sessionID);
19539
+ if (state2.isRecovering) {
19540
+ log("[session-recovery] Already recovering, skipping", { sessionID });
19541
+ return false;
19542
+ }
19543
+ const now = Date.now();
19544
+ if (now - state2.lastErrorTime < BACKGROUND_TASK.RETRY_COOLDOWN_MS) {
19545
+ log("[session-recovery] Too soon since last error, skipping", { sessionID });
19546
+ return false;
19547
+ }
19548
+ state2.lastErrorTime = now;
19549
+ state2.errorCount++;
19550
+ const errorType = detectErrorType(error45);
19551
+ if (!errorType) {
19552
+ log("[session-recovery] Unknown error type, using default handler", { sessionID, error: error45 });
19553
+ return false;
19554
+ }
19555
+ log("[session-recovery] Detected error type", { sessionID, errorType, errorCount: state2.errorCount });
19556
+ if (state2.errorCount > RECOVERY.MAX_ATTEMPTS) {
19557
+ log("[session-recovery] Max recovery attempts exceeded", { sessionID });
19558
+ presets.warningMaxRetries();
19559
+ return false;
19560
+ }
19561
+ state2.isRecovering = true;
19562
+ try {
19563
+ let recoveryPrompt = null;
19564
+ let toastMessage = null;
19565
+ switch (errorType) {
19566
+ case ERROR_TYPE.TOOL_RESULT_MISSING:
19567
+ recoveryPrompt = TOOL_CRASH_RECOVERY_PROMPT;
19568
+ toastMessage = "Tool Crash Recovery";
19569
+ break;
19570
+ case ERROR_TYPE.THINKING_BLOCK_ORDER:
19571
+ case ERROR_TYPE.THINKING_DISABLED:
19572
+ recoveryPrompt = THINKING_RECOVERY_PROMPT;
19573
+ toastMessage = "Thinking Block Recovery";
19574
+ break;
19575
+ case ERROR_TYPE.RATE_LIMIT:
19576
+ const ctx = {
19577
+ sessionId: sessionID,
19578
+ error: error45 instanceof Error ? error45 : new Error(String(error45)),
19579
+ attempt: state2.errorCount,
19580
+ timestamp: /* @__PURE__ */ new Date()
19581
+ };
19582
+ const action = handleError(ctx);
19583
+ if (action.type === "retry" && action.delay) {
19584
+ log("[session-recovery] Rate limit, waiting", { delay: action.delay });
19585
+ await new Promise((r) => setTimeout(r, action.delay));
19586
+ }
19587
+ state2.isRecovering = false;
19588
+ return true;
19589
+ case ERROR_TYPE.CONTEXT_OVERFLOW:
19590
+ toastMessage = "Context Overflow - Consider compaction";
19591
+ state2.isRecovering = false;
19592
+ return false;
19593
+ case ERROR_TYPE.MESSAGE_ABORTED:
19594
+ log("[session-recovery] Message aborted by user, not recovering", { sessionID });
19595
+ state2.isRecovering = false;
19596
+ return false;
19597
+ default:
19598
+ state2.isRecovering = false;
19599
+ return false;
19600
+ }
19601
+ if (recoveryPrompt && toastMessage) {
19602
+ presets.errorRecovery(toastMessage);
19603
+ await client.session.prompt({
19604
+ path: { id: sessionID },
19605
+ body: {
19606
+ parts: [{ type: PART_TYPES.TEXT, text: recoveryPrompt }]
19607
+ }
19608
+ });
19609
+ log("[session-recovery] Recovery prompt injected", { sessionID, errorType });
19610
+ state2.isRecovering = false;
19611
+ return true;
19612
+ }
19613
+ state2.isRecovering = false;
19614
+ return false;
19615
+ } catch (injectionError) {
19616
+ log("[session-recovery] Failed to inject recovery prompt", { sessionID, error: injectionError });
19617
+ state2.isRecovering = false;
19618
+ return false;
19619
+ }
19620
+ }
19621
+ function markRecoveryComplete(sessionID) {
19622
+ const state2 = recoveryState.get(sessionID);
19623
+ if (state2) {
19624
+ state2.isRecovering = false;
19625
+ state2.errorCount = 0;
19626
+ }
19627
+ }
19628
+ function cleanupSessionRecovery(sessionID) {
19629
+ recoveryState.delete(sessionID);
19630
+ }
19631
+ function isSessionRecovering(sessionID) {
19632
+ return recoveryState.get(sessionID)?.isRecovering ?? false;
19633
+ }
19634
+
19354
19635
  // src/core/notification/os-notify/handler.ts
19355
19636
  function createSessionNotificationHandler(client, config2 = {}) {
19356
19637
  const currentPlatform = detectPlatform();
@@ -19395,6 +19676,15 @@ function createSessionNotificationHandler(client, config2 = {}) {
19395
19676
  state2.notificationVersions.set(sessionID, (state2.notificationVersions.get(sessionID) ?? 0) + 1);
19396
19677
  state2.notifiedSessions.delete(sessionID);
19397
19678
  }
19679
+ function hasRunningBackgroundTasks3(parentSessionID) {
19680
+ try {
19681
+ const manager = ParallelAgentManager.getInstance();
19682
+ const tasks = manager.getTasksByParent(parentSessionID);
19683
+ return tasks.some((t) => t.status === STATUS_LABEL.RUNNING);
19684
+ } catch {
19685
+ return false;
19686
+ }
19687
+ }
19398
19688
  async function executeNotification(sessionID, version2) {
19399
19689
  if (state2.executingNotifications.has(sessionID)) {
19400
19690
  state2.pendingTimers.delete(sessionID);
@@ -19404,6 +19694,16 @@ function createSessionNotificationHandler(client, config2 = {}) {
19404
19694
  state2.pendingTimers.delete(sessionID);
19405
19695
  return;
19406
19696
  }
19697
+ if (isSessionRecovering(sessionID)) {
19698
+ log(`[session-notify] Skipping notification for ${sessionID} - session is recovering`);
19699
+ state2.pendingTimers.delete(sessionID);
19700
+ return;
19701
+ }
19702
+ if (hasRunningBackgroundTasks3(sessionID)) {
19703
+ log(`[session-notify] Skipping notification for ${sessionID} - background tasks running`);
19704
+ state2.pendingTimers.delete(sessionID);
19705
+ return;
19706
+ }
19407
19707
  state2.executingNotifications.add(sessionID);
19408
19708
  try {
19409
19709
  if (mergedConfig.skipIfIncompleteTodos) {
@@ -20233,7 +20533,7 @@ var UI_PATTERNS = {
20233
20533
  var StrictRoleGuardHook = class {
20234
20534
  name = HOOK_NAMES.STRICT_ROLE_GUARD;
20235
20535
  async execute(ctx, tool2, args) {
20236
- if (tool2 === "run_command" || tool2 === TOOL_NAMES.RUN_BACKGROUND) {
20536
+ if (tool2 === TOOL_NAMES.RUN_COMMAND || tool2 === TOOL_NAMES.RUN_BACKGROUND) {
20237
20537
  const cmd = args?.command;
20238
20538
  if (cmd) {
20239
20539
  if (cmd.includes(SECURITY_PATTERNS.FORK_BOMB)) {
@@ -20308,7 +20608,7 @@ var CONTEXT_MONITOR_CONFIG = {
20308
20608
  ALERT_COOLDOWN_MS: 6e4
20309
20609
  };
20310
20610
  var sessionStates = /* @__PURE__ */ new Map();
20311
- function getState(sessionID) {
20611
+ function getState2(sessionID) {
20312
20612
  let state2 = sessionStates.get(sessionID);
20313
20613
  if (!state2) {
20314
20614
  state2 = {
@@ -20363,7 +20663,7 @@ function checkContextWindow(sessionID, usedTokens, maxTokens = CONTEXT_MONITOR_C
20363
20663
  const usage = calculateUsage(usedTokens, maxTokens);
20364
20664
  const level = getAlertLevel(usage);
20365
20665
  if (!level) return;
20366
- const state2 = getState(sessionID);
20666
+ const state2 = getState2(sessionID);
20367
20667
  const now = Date.now();
20368
20668
  if (now - state2.lastAlertTime < CONTEXT_MONITOR_CONFIG.ALERT_COOLDOWN_MS) {
20369
20669
  if (state2.lastAlertLevel === level) return;
@@ -20533,239 +20833,6 @@ After launching, use list_tasks to monitor progress.
20533
20833
  return prompt;
20534
20834
  }
20535
20835
 
20536
- // src/core/recovery/constants.ts
20537
- var MAX_RETRIES = RECOVERY.MAX_ATTEMPTS;
20538
- var BASE_DELAY = RECOVERY.BASE_DELAY_MS;
20539
- var MAX_HISTORY = HISTORY.MAX_RECOVERY;
20540
-
20541
- // src/core/recovery/patterns.ts
20542
- var errorPatterns = [
20543
- // Rate limiting
20544
- {
20545
- pattern: /rate.?limit|too.?many.?requests|429/i,
20546
- category: "rate_limit",
20547
- handler: (ctx) => {
20548
- const delay = BASE_DELAY * Math.pow(2, ctx.attempt);
20549
- presets.warningRateLimited();
20550
- return { type: "retry", delay, attempt: ctx.attempt + 1 };
20551
- }
20552
- },
20553
- // Context overflow
20554
- {
20555
- pattern: /context.?length|token.?limit|maximum.?context/i,
20556
- category: "context_overflow",
20557
- handler: () => {
20558
- presets.errorRecovery("Compacting context");
20559
- return { type: "compact", reason: "Context limit reached" };
20560
- }
20561
- },
20562
- // Network errors
20563
- {
20564
- pattern: /ECONNREFUSED|ETIMEDOUT|network|fetch.?failed/i,
20565
- category: "network",
20566
- handler: (ctx) => {
20567
- if (ctx.attempt >= MAX_RETRIES) {
20568
- return { type: "abort", reason: "Network unavailable after retries" };
20569
- }
20570
- return { type: "retry", delay: BASE_DELAY * (ctx.attempt + 1), attempt: ctx.attempt + 1 };
20571
- }
20572
- },
20573
- // Session errors
20574
- {
20575
- pattern: /session.?not.?found|session.?expired/i,
20576
- category: "session",
20577
- handler: () => {
20578
- return { type: "abort", reason: "Session no longer available" };
20579
- }
20580
- },
20581
- // Tool errors
20582
- {
20583
- pattern: /tool.?not.?found|unknown.?tool/i,
20584
- category: "tool",
20585
- handler: (ctx) => {
20586
- return { type: "escalate", to: "Reviewer", reason: `Unknown tool used by ${ctx.agent}` };
20587
- }
20588
- },
20589
- // Parse errors
20590
- {
20591
- pattern: /parse.?error|invalid.?json|syntax.?error/i,
20592
- category: "parse",
20593
- handler: (ctx) => {
20594
- if (ctx.attempt >= 2) {
20595
- return { type: "skip", reason: "Persistent parse error" };
20596
- }
20597
- return { type: "retry", delay: 500, attempt: ctx.attempt + 1 };
20598
- }
20599
- },
20600
- // Gibberish / hallucination
20601
- {
20602
- pattern: /gibberish|hallucination|mixed.?language/i,
20603
- category: "gibberish",
20604
- handler: () => {
20605
- presets.errorRecovery("Retrying with clean context");
20606
- return { type: "retry", delay: 1e3, attempt: 1 };
20607
- }
20608
- }
20609
- ];
20610
-
20611
- // src/core/recovery/handler.ts
20612
- var recoveryHistory = [];
20613
- function handleError(context) {
20614
- const errorMessage = context.error.message || String(context.error);
20615
- for (const pattern of errorPatterns) {
20616
- const matches = typeof pattern.pattern === "string" ? errorMessage.includes(pattern.pattern) : pattern.pattern.test(errorMessage);
20617
- if (matches) {
20618
- const action = pattern.handler(context);
20619
- recoveryHistory.push({
20620
- context,
20621
- action,
20622
- timestamp: /* @__PURE__ */ new Date()
20623
- });
20624
- if (recoveryHistory.length > MAX_HISTORY) {
20625
- recoveryHistory.shift();
20626
- }
20627
- return action;
20628
- }
20629
- }
20630
- if (context.attempt < MAX_RETRIES) {
20631
- return {
20632
- type: "retry",
20633
- delay: BASE_DELAY * Math.pow(2, context.attempt),
20634
- attempt: context.attempt + 1
20635
- };
20636
- }
20637
- return { type: "abort", reason: `Unknown error after ${MAX_RETRIES} retries` };
20638
- }
20639
-
20640
- // src/core/recovery/session-recovery.ts
20641
- var recoveryState = /* @__PURE__ */ new Map();
20642
- function getState2(sessionID) {
20643
- let state2 = recoveryState.get(sessionID);
20644
- if (!state2) {
20645
- state2 = { isRecovering: false, lastErrorTime: 0, errorCount: 0 };
20646
- recoveryState.set(sessionID, state2);
20647
- }
20648
- return state2;
20649
- }
20650
- var TOOL_CRASH_RECOVERY_PROMPT = `<recovery type="tool_crash">
20651
- The previous tool execution failed. This is a system-level issue, not your fault.
20652
-
20653
- <action>
20654
- 1. Acknowledge the tool failure
20655
- 2. Try an alternative approach using different tools
20656
- 3. If the same tool is needed, retry with modified parameters
20657
- 4. Continue with the original mission
20658
- </action>
20659
-
20660
- Do NOT apologize excessively. Just proceed.
20661
- </recovery>`;
20662
- var THINKING_RECOVERY_PROMPT = `<recovery type="thinking_block">
20663
- There was a temporary processing issue. Please continue from where you left off.
20664
-
20665
- <action>
20666
- 1. Review your current progress
20667
- 2. Identify the next pending task
20668
- 3. Continue execution
20669
- </action>
20670
- </recovery>`;
20671
- async function handleSessionError(client, sessionID, error45, properties) {
20672
- const state2 = getState2(sessionID);
20673
- if (state2.isRecovering) {
20674
- log("[session-recovery] Already recovering, skipping", { sessionID });
20675
- return false;
20676
- }
20677
- const now = Date.now();
20678
- if (now - state2.lastErrorTime < BACKGROUND_TASK.RETRY_COOLDOWN_MS) {
20679
- log("[session-recovery] Too soon since last error, skipping", { sessionID });
20680
- return false;
20681
- }
20682
- state2.lastErrorTime = now;
20683
- state2.errorCount++;
20684
- const errorType = detectErrorType(error45);
20685
- if (!errorType) {
20686
- log("[session-recovery] Unknown error type, using default handler", { sessionID, error: error45 });
20687
- return false;
20688
- }
20689
- log("[session-recovery] Detected error type", { sessionID, errorType, errorCount: state2.errorCount });
20690
- if (state2.errorCount > RECOVERY.MAX_ATTEMPTS) {
20691
- log("[session-recovery] Max recovery attempts exceeded", { sessionID });
20692
- presets.warningMaxRetries();
20693
- return false;
20694
- }
20695
- state2.isRecovering = true;
20696
- try {
20697
- let recoveryPrompt = null;
20698
- let toastMessage = null;
20699
- switch (errorType) {
20700
- case ERROR_TYPE.TOOL_RESULT_MISSING:
20701
- recoveryPrompt = TOOL_CRASH_RECOVERY_PROMPT;
20702
- toastMessage = "Tool Crash Recovery";
20703
- break;
20704
- case ERROR_TYPE.THINKING_BLOCK_ORDER:
20705
- case ERROR_TYPE.THINKING_DISABLED:
20706
- recoveryPrompt = THINKING_RECOVERY_PROMPT;
20707
- toastMessage = "Thinking Block Recovery";
20708
- break;
20709
- case ERROR_TYPE.RATE_LIMIT:
20710
- const ctx = {
20711
- sessionId: sessionID,
20712
- error: error45 instanceof Error ? error45 : new Error(String(error45)),
20713
- attempt: state2.errorCount,
20714
- timestamp: /* @__PURE__ */ new Date()
20715
- };
20716
- const action = handleError(ctx);
20717
- if (action.type === "retry" && action.delay) {
20718
- log("[session-recovery] Rate limit, waiting", { delay: action.delay });
20719
- await new Promise((r) => setTimeout(r, action.delay));
20720
- }
20721
- state2.isRecovering = false;
20722
- return true;
20723
- case ERROR_TYPE.CONTEXT_OVERFLOW:
20724
- toastMessage = "Context Overflow - Consider compaction";
20725
- state2.isRecovering = false;
20726
- return false;
20727
- case ERROR_TYPE.MESSAGE_ABORTED:
20728
- log("[session-recovery] Message aborted by user, not recovering", { sessionID });
20729
- state2.isRecovering = false;
20730
- return false;
20731
- default:
20732
- state2.isRecovering = false;
20733
- return false;
20734
- }
20735
- if (recoveryPrompt && toastMessage) {
20736
- presets.errorRecovery(toastMessage);
20737
- await client.session.prompt({
20738
- path: { id: sessionID },
20739
- body: {
20740
- parts: [{ type: PART_TYPES.TEXT, text: recoveryPrompt }]
20741
- }
20742
- });
20743
- log("[session-recovery] Recovery prompt injected", { sessionID, errorType });
20744
- state2.isRecovering = false;
20745
- return true;
20746
- }
20747
- state2.isRecovering = false;
20748
- return false;
20749
- } catch (injectionError) {
20750
- log("[session-recovery] Failed to inject recovery prompt", { sessionID, error: injectionError });
20751
- state2.isRecovering = false;
20752
- return false;
20753
- }
20754
- }
20755
- function markRecoveryComplete(sessionID) {
20756
- const state2 = recoveryState.get(sessionID);
20757
- if (state2) {
20758
- state2.isRecovering = false;
20759
- state2.errorCount = 0;
20760
- }
20761
- }
20762
- function cleanupSessionRecovery(sessionID) {
20763
- recoveryState.delete(sessionID);
20764
- }
20765
- function isSessionRecovering(sessionID) {
20766
- return recoveryState.get(sessionID)?.isRecovering ?? false;
20767
- }
20768
-
20769
20836
  // src/core/loop/todo-continuation.ts
20770
20837
  var sessionStates2 = /* @__PURE__ */ new Map();
20771
20838
  var COUNTDOWN_SECONDS = 2;
@@ -4,7 +4,7 @@
4
4
  export declare const PHASES: {
5
5
  readonly PHASE_0: {
6
6
  readonly ID: "PHASE_0";
7
- readonly NAME: "DISCOVERY SWARM";
7
+ readonly NAME: "DISCOVERY";
8
8
  readonly DESCRIPTION: "Parallel intelligence gathering and project mapping";
9
9
  readonly MANDATORY: true;
10
10
  };
@@ -10,6 +10,7 @@ export declare const TOOL_NAMES: {
10
10
  readonly CHECK_BACKGROUND: "check_background";
11
11
  readonly LIST_BACKGROUND: "list_background";
12
12
  readonly KILL_BACKGROUND: "kill_background";
13
+ readonly RUN_COMMAND: "run_command";
13
14
  readonly GREP_SEARCH: "grep_search";
14
15
  readonly GLOB_SEARCH: "glob_search";
15
16
  readonly MGREP: "mgrep";
@@ -29,4 +30,5 @@ export declare const TOOL_NAMES: {
29
30
  readonly AST_REPLACE: "ast_replace";
30
31
  readonly CALL_AGENT: "call_agent";
31
32
  readonly SLASHCOMMAND: "slashcommand";
33
+ readonly SKILL: "skill";
32
34
  };
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.40",
5
+ "version": "1.0.42",
6
6
  "author": "agnusdei1207",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -62,7 +62,7 @@
62
62
  "release:minor": "npm run build && npm run rust:dist && npm version minor && git push --follow-tags && npm publish --access public",
63
63
  "release:major": "npm run build && npm run rust:dist && npm version major && git push --follow-tags && npm publish --access public",
64
64
  "reset:local": "brew uninstall opencode 2>/dev/null; rm -rf ~/.config/opencode ~/.opencode ~/.local/share/opencode ~/.cache/opencode/node_modules/opencode-orchestrator && echo '=== Clean done ===' && brew install opencode && echo '{\"plugin\": [\"opencode-orchestrator\"], \"$schema\": \"https://opencode.ai/config.json\"}' > ~/.config/opencode/opencode.json && echo '=== Reset (Dev) complete. Run: opencode ==='",
65
- "reset:prod": "brew uninstall opencode 2>/dev/null; rm -rf ~/.config/opencode ~/.opencode ~/.local/share/opencode ~/.cache/opencode/node_modules/opencode-orchestrator && echo '=== Clean done ===' && brew install opencode && echo '{\"plugin\": [\"opencode-orchestrator\"], \"$schema\": \"https://opencode.ai/config.json\"}' > ~/.config/opencode/opencode.json && npm uninstall -g opencode-orchestrator && echo '=== Reset (Prod) complete. Run: opencode ==='",
65
+ "reset:prod": "brew uninstall opencode 2>/dev/null; rm -rf ~/.config/opencode ~/.opencode ~/.local/share/opencode ~/.cache/opencode/node_modules/opencode-orchestrator && echo '=== Clean done ===' && brew install opencode && echo '{\"plugin\": [\"opencode-orchestrator\"], \"$schema\": \"https://opencode.ai/config.json\"}' > ~/.config/opencode/opencode.json && npm uninstall -g opencode-orchestrator && npm install -g opencode-orchestrator && echo '=== Reset (Prod) complete. Run: opencode ==='",
66
66
  "ginstall": "npm install -g opencode-orchestrator",
67
67
  "log": "tail -f \"$(node -e 'console.log(require(\"os\").tmpdir())')/opencode-orchestrator.log\""
68
68
  },