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
|
|
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
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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: `
|
|
17836
|
-
prompt:
|
|
17837
|
-
|
|
17838
|
-
1.
|
|
17839
|
-
2.
|
|
17840
|
-
3.
|
|
17841
|
-
|
|
17842
|
-
|
|
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 ===
|
|
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
|
|
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 =
|
|
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;
|
|
@@ -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.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
|
},
|