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
|
-
|
|
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
|
|
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
|
|
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: `
|
|
17836
|
-
prompt:
|
|
17837
|
-
|
|
17838
|
-
1.
|
|
17839
|
-
2.
|
|
17840
|
-
3.
|
|
17841
|
-
|
|
17842
|
-
|
|
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 ===
|
|
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
|
|
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 =
|
|
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.
|
|
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
|
},
|