opencode-orchestrator 1.0.40 → 1.0.41

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
@@ -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 = {
@@ -13877,6 +13885,23 @@ ${HPFA_RULES}
13877
13885
  [CRITICAL]: Sequential execution when parallel is possible is a mission failure.
13878
13886
  ${PROMPT_TAGS.QUALITY_CHECKLIST.close}`;
13879
13887
 
13888
+ // src/agents/prompts/common/skills.ts
13889
+ var SKILLS_CAPABILITIES = wrapTag({ open: "<skills_capabilities>", close: "</skills_capabilities>" }, `
13890
+ ### AUTONOMOUS SKILL ACQUISITION
13891
+ You have the ability to extend your capabilities using the **Skills System**.
13892
+ If you encounter a task or technology you are unfamiliar with (e.g., "deploy to AWS", "use specific library"):
13893
+
13894
+ 1. **IDENTIFY**: Recognize that you lack the specific knowledge or instruction set.
13895
+ 2. **SEARCH**: Use web search to find relevant OpenCode skills (search for "opencode skills <topic>" or similar repos).
13896
+ 3. **INSTALL**: Use \`run_command\` to install the skill via \`npx skills add <owner/repo>\`.
13897
+ - Example: \`run_command({ command: "npx skills add agnusdei1207/git-release" })\`
13898
+ 4. **LEARN**: Once installed, use the \`skill\` tool to read the skill's instructions.
13899
+ - Example: \`skill({ name: "git-release" })\`
13900
+ 5. **EXECUTE**: Apply the learned skill to your task.
13901
+
13902
+ **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.
13903
+ `);
13904
+
13880
13905
  // src/agents/prompts/commander/role.ts
13881
13906
  var COMMANDER_ROLE = `${PROMPT_TAGS.ROLE.open}
13882
13907
  You are ${AGENT_NAMES.COMMANDER}. Autonomous mission controller.
@@ -15503,6 +15528,7 @@ var systemPrompt2 = [
15503
15528
  PLANNER_RESEARCH,
15504
15529
  SHARED_LSP_TOOLS,
15505
15530
  SHARED_AST_TOOLS,
15531
+ SKILLS_CAPABILITIES,
15506
15532
  SHARED_WORKSPACE
15507
15533
  ].join("\n\n");
15508
15534
  var planner = {
@@ -15531,6 +15557,7 @@ var systemPrompt3 = [
15531
15557
  WORKER_LSP_TOOLS,
15532
15558
  SHARED_LSP_TOOLS,
15533
15559
  SHARED_AST_TOOLS,
15560
+ SKILLS_CAPABILITIES,
15534
15561
  VERIFICATION_REQUIREMENTS,
15535
15562
  SHARED_WORKSPACE
15536
15563
  ].join("\n\n");
@@ -16340,9 +16367,20 @@ var ConcurrencyController = class {
16340
16367
  return;
16341
16368
  }
16342
16369
  log2(`Queueing ${key}: ${current}/${limit}`);
16343
- return new Promise((resolve2) => {
16370
+ return new Promise((resolve2, reject) => {
16344
16371
  const queue = this.queues.get(key) ?? [];
16345
- queue.push(resolve2);
16372
+ const timeoutId = setTimeout(() => {
16373
+ const currentQueue = this.queues.get(key);
16374
+ if (currentQueue) {
16375
+ const index = currentQueue.findIndex((item) => item.resolve === resolve2);
16376
+ if (index !== -1) {
16377
+ currentQueue.splice(index, 1);
16378
+ this.queues.set(key, currentQueue);
16379
+ }
16380
+ }
16381
+ reject(new Error(`Concurrency acquisition timed out after 60s for ${key}`));
16382
+ }, 6e4);
16383
+ queue.push({ resolve: resolve2, timeoutId });
16346
16384
  this.queues.set(key, queue);
16347
16385
  });
16348
16386
  }
@@ -16352,8 +16390,9 @@ var ConcurrencyController = class {
16352
16390
  const queue = this.queues.get(key);
16353
16391
  if (queue && queue.length > 0) {
16354
16392
  const next = queue.shift();
16393
+ clearTimeout(next.timeoutId);
16355
16394
  log2(`Released ${key}: next in queue`);
16356
- next();
16395
+ next.resolve();
16357
16396
  } else {
16358
16397
  const current = this.counts.get(key) ?? 0;
16359
16398
  if (current > 0) {
@@ -17188,13 +17227,19 @@ var TaskLauncher = class {
17188
17227
  log(`[task-launcher.ts] Task depth limit reached (${currentDepth}/${PARALLEL_TASK.MAX_DEPTH}). Generation blocked.`);
17189
17228
  throw new Error(`Maximum task depth (${PARALLEL_TASK.MAX_DEPTH}) reached. To prevent infinite recursion, no further sub-tasks can be spawned.`);
17190
17229
  }
17191
- const createResult = await this.client.session.create({
17230
+ const sessionCreatePromise = this.client.session.create({
17192
17231
  body: {
17193
17232
  parentID: input.parentSessionID,
17194
17233
  title: `${PARALLEL_TASK.SESSION_TITLE_PREFIX}: ${input.description}`
17195
17234
  },
17196
17235
  query: { directory: this.directory }
17197
17236
  });
17237
+ const createResult = await Promise.race([
17238
+ sessionCreatePromise,
17239
+ new Promise(
17240
+ (_, reject) => setTimeout(() => reject(new Error("Session creation timed out after 15s")), 6e3)
17241
+ )
17242
+ ]);
17198
17243
  if (createResult.error || !createResult.data?.id) {
17199
17244
  throw new Error(`Session creation failed: ${createResult.error || "No ID"}`);
17200
17245
  }
@@ -17253,7 +17298,9 @@ var TaskLauncher = class {
17253
17298
  delegate_task: true,
17254
17299
  get_task_result: true,
17255
17300
  list_tasks: true,
17256
- cancel_task: true
17301
+ cancel_task: true,
17302
+ [TOOL_NAMES.SKILL]: true,
17303
+ [TOOL_NAMES.RUN_COMMAND]: true
17257
17304
  },
17258
17305
  parts: [{ type: PART_TYPES.TEXT, text: task.prompt }]
17259
17306
  }
@@ -17828,18 +17875,18 @@ var ParallelAgentManager = class _ParallelAgentManager {
17828
17875
  }
17829
17876
  async handleTaskComplete(task) {
17830
17877
  if (task.agent === AGENT_NAMES.WORKER && task.mode !== "race") {
17831
- log(`[MSVP] Triggering 1\uCC28 \uB9AC\uBDF0 (Unit Review) for task ${task.id}`);
17878
+ log(`[MSVP] Triggering Unit Review for task ${task.id}`);
17832
17879
  try {
17833
17880
  await this.launch({
17834
17881
  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.`,
17882
+ description: `Unit Review: ${task.description}`,
17883
+ prompt: `Perform a Unit Review (verification) for the completed task (\`${task.description}\`).
17884
+ Key Checklist:
17885
+ 1. Verify if unit test code for the module is written and passes.
17886
+ 2. Check for code quality and modularity compliance.
17887
+ 3. Instruct immediate correction of found defects or report them.
17888
+
17889
+ This task ensures the completeness of the unit before global integration.`,
17843
17890
  parentSessionID: task.parentSessionID,
17844
17891
  depth: task.depth,
17845
17892
  groupID: task.groupID || task.id
@@ -19351,6 +19398,239 @@ async function hasIncompleteTodos(client, sessionID) {
19351
19398
  }
19352
19399
  }
19353
19400
 
19401
+ // src/core/recovery/constants.ts
19402
+ var MAX_RETRIES = RECOVERY.MAX_ATTEMPTS;
19403
+ var BASE_DELAY = RECOVERY.BASE_DELAY_MS;
19404
+ var MAX_HISTORY = HISTORY.MAX_RECOVERY;
19405
+
19406
+ // src/core/recovery/patterns.ts
19407
+ var errorPatterns = [
19408
+ // Rate limiting
19409
+ {
19410
+ pattern: /rate.?limit|too.?many.?requests|429/i,
19411
+ category: "rate_limit",
19412
+ handler: (ctx) => {
19413
+ const delay = BASE_DELAY * Math.pow(2, ctx.attempt);
19414
+ presets.warningRateLimited();
19415
+ return { type: "retry", delay, attempt: ctx.attempt + 1 };
19416
+ }
19417
+ },
19418
+ // Context overflow
19419
+ {
19420
+ pattern: /context.?length|token.?limit|maximum.?context/i,
19421
+ category: "context_overflow",
19422
+ handler: () => {
19423
+ presets.errorRecovery("Compacting context");
19424
+ return { type: "compact", reason: "Context limit reached" };
19425
+ }
19426
+ },
19427
+ // Network errors
19428
+ {
19429
+ pattern: /ECONNREFUSED|ETIMEDOUT|network|fetch.?failed/i,
19430
+ category: "network",
19431
+ handler: (ctx) => {
19432
+ if (ctx.attempt >= MAX_RETRIES) {
19433
+ return { type: "abort", reason: "Network unavailable after retries" };
19434
+ }
19435
+ return { type: "retry", delay: BASE_DELAY * (ctx.attempt + 1), attempt: ctx.attempt + 1 };
19436
+ }
19437
+ },
19438
+ // Session errors
19439
+ {
19440
+ pattern: /session.?not.?found|session.?expired/i,
19441
+ category: "session",
19442
+ handler: () => {
19443
+ return { type: "abort", reason: "Session no longer available" };
19444
+ }
19445
+ },
19446
+ // Tool errors
19447
+ {
19448
+ pattern: /tool.?not.?found|unknown.?tool/i,
19449
+ category: "tool",
19450
+ handler: (ctx) => {
19451
+ return { type: "escalate", to: "Reviewer", reason: `Unknown tool used by ${ctx.agent}` };
19452
+ }
19453
+ },
19454
+ // Parse errors
19455
+ {
19456
+ pattern: /parse.?error|invalid.?json|syntax.?error/i,
19457
+ category: "parse",
19458
+ handler: (ctx) => {
19459
+ if (ctx.attempt >= 2) {
19460
+ return { type: "skip", reason: "Persistent parse error" };
19461
+ }
19462
+ return { type: "retry", delay: 500, attempt: ctx.attempt + 1 };
19463
+ }
19464
+ },
19465
+ // Gibberish / hallucination
19466
+ {
19467
+ pattern: /gibberish|hallucination|mixed.?language/i,
19468
+ category: "gibberish",
19469
+ handler: () => {
19470
+ presets.errorRecovery("Retrying with clean context");
19471
+ return { type: "retry", delay: 1e3, attempt: 1 };
19472
+ }
19473
+ }
19474
+ ];
19475
+
19476
+ // src/core/recovery/handler.ts
19477
+ var recoveryHistory = [];
19478
+ function handleError(context) {
19479
+ const errorMessage = context.error.message || String(context.error);
19480
+ for (const pattern of errorPatterns) {
19481
+ const matches = typeof pattern.pattern === "string" ? errorMessage.includes(pattern.pattern) : pattern.pattern.test(errorMessage);
19482
+ if (matches) {
19483
+ const action = pattern.handler(context);
19484
+ recoveryHistory.push({
19485
+ context,
19486
+ action,
19487
+ timestamp: /* @__PURE__ */ new Date()
19488
+ });
19489
+ if (recoveryHistory.length > MAX_HISTORY) {
19490
+ recoveryHistory.shift();
19491
+ }
19492
+ return action;
19493
+ }
19494
+ }
19495
+ if (context.attempt < MAX_RETRIES) {
19496
+ return {
19497
+ type: "retry",
19498
+ delay: BASE_DELAY * Math.pow(2, context.attempt),
19499
+ attempt: context.attempt + 1
19500
+ };
19501
+ }
19502
+ return { type: "abort", reason: `Unknown error after ${MAX_RETRIES} retries` };
19503
+ }
19504
+
19505
+ // src/core/recovery/session-recovery.ts
19506
+ var recoveryState = /* @__PURE__ */ new Map();
19507
+ function getState(sessionID) {
19508
+ let state2 = recoveryState.get(sessionID);
19509
+ if (!state2) {
19510
+ state2 = { isRecovering: false, lastErrorTime: 0, errorCount: 0 };
19511
+ recoveryState.set(sessionID, state2);
19512
+ }
19513
+ return state2;
19514
+ }
19515
+ var TOOL_CRASH_RECOVERY_PROMPT = `<recovery type="tool_crash">
19516
+ The previous tool execution failed. This is a system-level issue, not your fault.
19517
+
19518
+ <action>
19519
+ 1. Acknowledge the tool failure
19520
+ 2. Try an alternative approach using different tools
19521
+ 3. If the same tool is needed, retry with modified parameters
19522
+ 4. Continue with the original mission
19523
+ </action>
19524
+
19525
+ Do NOT apologize excessively. Just proceed.
19526
+ </recovery>`;
19527
+ var THINKING_RECOVERY_PROMPT = `<recovery type="thinking_block">
19528
+ There was a temporary processing issue. Please continue from where you left off.
19529
+
19530
+ <action>
19531
+ 1. Review your current progress
19532
+ 2. Identify the next pending task
19533
+ 3. Continue execution
19534
+ </action>
19535
+ </recovery>`;
19536
+ async function handleSessionError(client, sessionID, error45, properties) {
19537
+ const state2 = getState(sessionID);
19538
+ if (state2.isRecovering) {
19539
+ log("[session-recovery] Already recovering, skipping", { sessionID });
19540
+ return false;
19541
+ }
19542
+ const now = Date.now();
19543
+ if (now - state2.lastErrorTime < BACKGROUND_TASK.RETRY_COOLDOWN_MS) {
19544
+ log("[session-recovery] Too soon since last error, skipping", { sessionID });
19545
+ return false;
19546
+ }
19547
+ state2.lastErrorTime = now;
19548
+ state2.errorCount++;
19549
+ const errorType = detectErrorType(error45);
19550
+ if (!errorType) {
19551
+ log("[session-recovery] Unknown error type, using default handler", { sessionID, error: error45 });
19552
+ return false;
19553
+ }
19554
+ log("[session-recovery] Detected error type", { sessionID, errorType, errorCount: state2.errorCount });
19555
+ if (state2.errorCount > RECOVERY.MAX_ATTEMPTS) {
19556
+ log("[session-recovery] Max recovery attempts exceeded", { sessionID });
19557
+ presets.warningMaxRetries();
19558
+ return false;
19559
+ }
19560
+ state2.isRecovering = true;
19561
+ try {
19562
+ let recoveryPrompt = null;
19563
+ let toastMessage = null;
19564
+ switch (errorType) {
19565
+ case ERROR_TYPE.TOOL_RESULT_MISSING:
19566
+ recoveryPrompt = TOOL_CRASH_RECOVERY_PROMPT;
19567
+ toastMessage = "Tool Crash Recovery";
19568
+ break;
19569
+ case ERROR_TYPE.THINKING_BLOCK_ORDER:
19570
+ case ERROR_TYPE.THINKING_DISABLED:
19571
+ recoveryPrompt = THINKING_RECOVERY_PROMPT;
19572
+ toastMessage = "Thinking Block Recovery";
19573
+ break;
19574
+ case ERROR_TYPE.RATE_LIMIT:
19575
+ const ctx = {
19576
+ sessionId: sessionID,
19577
+ error: error45 instanceof Error ? error45 : new Error(String(error45)),
19578
+ attempt: state2.errorCount,
19579
+ timestamp: /* @__PURE__ */ new Date()
19580
+ };
19581
+ const action = handleError(ctx);
19582
+ if (action.type === "retry" && action.delay) {
19583
+ log("[session-recovery] Rate limit, waiting", { delay: action.delay });
19584
+ await new Promise((r) => setTimeout(r, action.delay));
19585
+ }
19586
+ state2.isRecovering = false;
19587
+ return true;
19588
+ case ERROR_TYPE.CONTEXT_OVERFLOW:
19589
+ toastMessage = "Context Overflow - Consider compaction";
19590
+ state2.isRecovering = false;
19591
+ return false;
19592
+ case ERROR_TYPE.MESSAGE_ABORTED:
19593
+ log("[session-recovery] Message aborted by user, not recovering", { sessionID });
19594
+ state2.isRecovering = false;
19595
+ return false;
19596
+ default:
19597
+ state2.isRecovering = false;
19598
+ return false;
19599
+ }
19600
+ if (recoveryPrompt && toastMessage) {
19601
+ presets.errorRecovery(toastMessage);
19602
+ await client.session.prompt({
19603
+ path: { id: sessionID },
19604
+ body: {
19605
+ parts: [{ type: PART_TYPES.TEXT, text: recoveryPrompt }]
19606
+ }
19607
+ });
19608
+ log("[session-recovery] Recovery prompt injected", { sessionID, errorType });
19609
+ state2.isRecovering = false;
19610
+ return true;
19611
+ }
19612
+ state2.isRecovering = false;
19613
+ return false;
19614
+ } catch (injectionError) {
19615
+ log("[session-recovery] Failed to inject recovery prompt", { sessionID, error: injectionError });
19616
+ state2.isRecovering = false;
19617
+ return false;
19618
+ }
19619
+ }
19620
+ function markRecoveryComplete(sessionID) {
19621
+ const state2 = recoveryState.get(sessionID);
19622
+ if (state2) {
19623
+ state2.isRecovering = false;
19624
+ state2.errorCount = 0;
19625
+ }
19626
+ }
19627
+ function cleanupSessionRecovery(sessionID) {
19628
+ recoveryState.delete(sessionID);
19629
+ }
19630
+ function isSessionRecovering(sessionID) {
19631
+ return recoveryState.get(sessionID)?.isRecovering ?? false;
19632
+ }
19633
+
19354
19634
  // src/core/notification/os-notify/handler.ts
19355
19635
  function createSessionNotificationHandler(client, config2 = {}) {
19356
19636
  const currentPlatform = detectPlatform();
@@ -19395,6 +19675,15 @@ function createSessionNotificationHandler(client, config2 = {}) {
19395
19675
  state2.notificationVersions.set(sessionID, (state2.notificationVersions.get(sessionID) ?? 0) + 1);
19396
19676
  state2.notifiedSessions.delete(sessionID);
19397
19677
  }
19678
+ function hasRunningBackgroundTasks3(parentSessionID) {
19679
+ try {
19680
+ const manager = ParallelAgentManager.getInstance();
19681
+ const tasks = manager.getTasksByParent(parentSessionID);
19682
+ return tasks.some((t) => t.status === STATUS_LABEL.RUNNING);
19683
+ } catch {
19684
+ return false;
19685
+ }
19686
+ }
19398
19687
  async function executeNotification(sessionID, version2) {
19399
19688
  if (state2.executingNotifications.has(sessionID)) {
19400
19689
  state2.pendingTimers.delete(sessionID);
@@ -19404,6 +19693,16 @@ function createSessionNotificationHandler(client, config2 = {}) {
19404
19693
  state2.pendingTimers.delete(sessionID);
19405
19694
  return;
19406
19695
  }
19696
+ if (isSessionRecovering(sessionID)) {
19697
+ log(`[session-notify] Skipping notification for ${sessionID} - session is recovering`);
19698
+ state2.pendingTimers.delete(sessionID);
19699
+ return;
19700
+ }
19701
+ if (hasRunningBackgroundTasks3(sessionID)) {
19702
+ log(`[session-notify] Skipping notification for ${sessionID} - background tasks running`);
19703
+ state2.pendingTimers.delete(sessionID);
19704
+ return;
19705
+ }
19407
19706
  state2.executingNotifications.add(sessionID);
19408
19707
  try {
19409
19708
  if (mergedConfig.skipIfIncompleteTodos) {
@@ -20233,7 +20532,7 @@ var UI_PATTERNS = {
20233
20532
  var StrictRoleGuardHook = class {
20234
20533
  name = HOOK_NAMES.STRICT_ROLE_GUARD;
20235
20534
  async execute(ctx, tool2, args) {
20236
- if (tool2 === "run_command" || tool2 === TOOL_NAMES.RUN_BACKGROUND) {
20535
+ if (tool2 === TOOL_NAMES.RUN_COMMAND || tool2 === TOOL_NAMES.RUN_BACKGROUND) {
20237
20536
  const cmd = args?.command;
20238
20537
  if (cmd) {
20239
20538
  if (cmd.includes(SECURITY_PATTERNS.FORK_BOMB)) {
@@ -20308,7 +20607,7 @@ var CONTEXT_MONITOR_CONFIG = {
20308
20607
  ALERT_COOLDOWN_MS: 6e4
20309
20608
  };
20310
20609
  var sessionStates = /* @__PURE__ */ new Map();
20311
- function getState(sessionID) {
20610
+ function getState2(sessionID) {
20312
20611
  let state2 = sessionStates.get(sessionID);
20313
20612
  if (!state2) {
20314
20613
  state2 = {
@@ -20363,7 +20662,7 @@ function checkContextWindow(sessionID, usedTokens, maxTokens = CONTEXT_MONITOR_C
20363
20662
  const usage = calculateUsage(usedTokens, maxTokens);
20364
20663
  const level = getAlertLevel(usage);
20365
20664
  if (!level) return;
20366
- const state2 = getState(sessionID);
20665
+ const state2 = getState2(sessionID);
20367
20666
  const now = Date.now();
20368
20667
  if (now - state2.lastAlertTime < CONTEXT_MONITOR_CONFIG.ALERT_COOLDOWN_MS) {
20369
20668
  if (state2.lastAlertLevel === level) return;
@@ -20533,239 +20832,6 @@ After launching, use list_tasks to monitor progress.
20533
20832
  return prompt;
20534
20833
  }
20535
20834
 
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
20835
  // src/core/loop/todo-continuation.ts
20770
20836
  var sessionStates2 = /* @__PURE__ */ new Map();
20771
20837
  var COUNTDOWN_SECONDS = 2;
@@ -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.41",
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
  },