opencode-orchestrator 1.3.11 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![MIT License](https://img.shields.io/badge/license-MIT-red.svg)](LICENSE)
7
7
  [![npm](https://img.shields.io/npm/v/opencode-orchestrator.svg)](https://www.npmjs.com/package/opencode-orchestrator)
8
8
  <!-- VERSION:START -->
9
- **Version:** `1.3.11`
9
+ **Version:** `1.5.0`
10
10
  <!-- VERSION:END -->
11
11
  </div>
12
12
 
@@ -34,8 +34,8 @@ Manual fallback: remove `"opencode-orchestrator"` or `["opencode-orchestrator",
34
34
  Tested compatibility:
35
35
 
36
36
  1. Node.js `24+`
37
- 2. `@opencode-ai/plugin` `1.17.3`
38
- 3. `@opencode-ai/sdk` `1.17.3`
37
+ 2. `@opencode-ai/plugin` `1.17.4`
38
+ 3. `@opencode-ai/sdk` `1.17.4`
39
39
 
40
40
  OpenCode plugin options belong inside the `plugin` array as `["plugin-name", {...}]` tuples. Configure `agentConcurrency` and `missionLoop` there:
41
41
 
@@ -99,6 +99,35 @@ Mission controls:
99
99
  3. `/cancel` and `/stop` deactivate the current mission loop.
100
100
  4. The default mission iteration ceiling is `1,000,000,000`.
101
101
 
102
+ ### Authorized Shell Listener TUI
103
+
104
+ For owned lab machines or explicitly authorized test environments, the bundled Rust CLI can run a multi-session TCP shell listener:
105
+
106
+ ```bash
107
+ orchestrator shell-listener --bind 127.0.0.1 --port 4444
108
+ ```
109
+
110
+ The listener is intentionally outside the OpenCode JSON-RPC tool surface. It is an operator-driven terminal workflow, not an LLM-callable tool.
111
+
112
+ Safety defaults:
113
+
114
+ 1. Loopback-only bind by default.
115
+ 2. Non-loopback bind addresses require `--allow-remote`.
116
+ 3. Raw stream logs are stored under `.opencode-orchestrator/shell-listener/`.
117
+ 4. The CLI does not generate payloads, exploit targets, or bypass authentication.
118
+
119
+ TUI commands:
120
+
121
+ | Command | Purpose |
122
+ | --- | --- |
123
+ | `sessions` | Show connected sessions, peer addresses, status, and raw log paths. |
124
+ | `use <id>` | Select the active session. |
125
+ | `send <text>` | Send one input line to the active session. Use this for login, registry, CDK, or reverse-proxy prompts that need human input. |
126
+ | `run <cmd>` | Send a command followed by a unique sentinel marker so completion can be recognized in output. |
127
+ | `pty` | Send a manual PTY helper to the active session when the remote environment supports Python. |
128
+ | `close [id]` | Close a session. |
129
+ | `quit` | Stop the listener UI. |
130
+
102
131
  ## 4. How It Works
103
132
 
104
133
  ```mermaid
@@ -128,6 +157,7 @@ Runtime evidence is written only when enabled:
128
157
  | `.opencode/mission-ledger.jsonl` | Bounded event trail for mission decisions. |
129
158
  | `.opencode/docs/brain/scratchpad.md` | Generated Markdown memory surface for active missions. |
130
159
  | `.opencode/docs/brain/knowledge-map.canvas` | Obsidian-compatible visual map of objective, evidence, and verification nodes. |
160
+ | `.opencode/docs/brain/memories/*.md` | Generated mission-relevant memory notes indexed by the knowledge retriever. |
131
161
 
132
162
  ## 5. Developer Notes
133
163
 
Binary file
Binary file
@@ -2,7 +2,7 @@
2
2
  * ╔════════════════════════════════════════════════════════════╗
3
3
  * ║ DISCOVERY CORE PHILOSOPHY ║
4
4
  * ╠════════════════════════════════════════════════════════════╣
5
- * ║ 발견 단계의 핵심 철학: 실행 충분한 탐색
5
+ * ║ Core Philosophy of Discovery: Thorough Exploration Before Execution
6
6
  * ╚════════════════════════════════════════════════════════════╝
7
7
  */
8
8
  export declare const DISCOVERY_CORE = "\n# Discovery Phase Philosophy\n\nBefore any execution, thoroughly explore:\n1. Workspace structure\n2. Environment capabilities\n3. Available skills and tools\n4. Project patterns and conventions\n\nThe discovery phase sets the foundation for all subsequent actions.\n";
@@ -2,7 +2,7 @@
2
2
  * ╔════════════════════════════════════════════════════════════╗
3
3
  * ║ PLANNING CORE PHILOSOPHY ║
4
4
  * ╠════════════════════════════════════════════════════════════╣
5
- * ║ 계획 단계의 핵심 철학: 실행 가능한 명확한 계획 수립
5
+ * ║ Core Philosophy of Planning: Establishing Clear, Actionable Plans
6
6
  * ╚════════════════════════════════════════════════════════════╝
7
7
  */
8
8
  export declare const PLANNING_CORE = "\n# Planning Phase Philosophy\n\nCreate actionable, granular plans:\n1. Break down complex tasks into atomic units\n2. Identify dependencies and execution order\n3. Plan for parallel execution where possible\n4. Define clear success criteria\n\nGood planning makes execution straightforward.\n";
@@ -2,7 +2,7 @@
2
2
  * ╔════════════════════════════════════════════════════════════╗
3
3
  * ║ EXECUTION CORE PHILOSOPHY ║
4
4
  * ╠════════════════════════════════════════════════════════════╣
5
- * ║ 실행 단계의 핵심 철학: 계획을 정확하게 실행
5
+ * ║ Core Philosophy of Execution: Executing Plans with Precision
6
6
  * ╚════════════════════════════════════════════════════════════╝
7
7
  */
8
8
  export declare const EXECUTION_CORE = "\n# Execution Phase Philosophy\n\nExecute with precision and quality:\n1. Follow the plan systematically\n2. Handle errors gracefully\n3. Provide clear progress updates\n4. Maintain code quality standards\n\nExecution transforms plans into reality.\n";
@@ -2,7 +2,7 @@
2
2
  * ╔════════════════════════════════════════════════════════════╗
3
3
  * ║ VERIFICATION CORE PHILOSOPHY ║
4
4
  * ╠════════════════════════════════════════════════════════════╣
5
- * ║ 검증 단계의 핵심 철학: 실행 기반 품질 보증
5
+ * ║ Core Philosophy of Verification: Execution-Based Quality Assurance
6
6
  * ╚════════════════════════════════════════════════════════════╝
7
7
  */
8
8
  export declare const VERIFICATION_CORE = "\n# Verification Phase Philosophy\n\nVerify through actual execution:\n1. Build the project\n2. Run tests\n3. Check linting and type errors\n4. Validate runtime behavior\n\nReal execution is the ultimate verification.\n";
@@ -2,7 +2,7 @@
2
2
  * ╔════════════════════════════════════════════════════════════╗
3
3
  * ║ MISSION CORE PHILOSOPHY ║
4
4
  * ╠════════════════════════════════════════════════════════════╣
5
- * ║ 미션 관리의 핵심 철학: 전체 생명주기 관리
5
+ * ║ Core Philosophy of Mission Control: Managing Full Lifecycle
6
6
  * ╚════════════════════════════════════════════════════════════╝
7
7
  */
8
8
  export declare const MISSION_CORE = "\n# Mission Management Philosophy\n\nManage the entire mission lifecycle:\n1. Track mission status throughout execution\n2. Handle recovery from failures\n3. Determine completion conditions\n4. Ensure clean mission closure\n\nA mission succeeds when all objectives are met.\n";
@@ -2,7 +2,7 @@
2
2
  * ╔════════════════════════════════════════════════════════════╗
3
3
  * ║ TOOLS CORE PHILOSOPHY ║
4
4
  * ╠════════════════════════════════════════════════════════════╣
5
- * ║ 도구 사용의 핵심 철학: 올바른 도구를 올바르게 사용
5
+ * ║ Core Philosophy of Tools: Using the Right Tools Correctly
6
6
  * ╚════════════════════════════════════════════════════════════╝
7
7
  */
8
8
  export declare const TOOLS_CORE = "\n# Tools Usage Philosophy\n\nUse the right tool for the right job:\n1. Understand each tool's capabilities and limitations\n2. Choose the most appropriate tool for each task\n3. Use tools efficiently and correctly\n4. Validate tool outputs\n\nProper tool usage amplifies effectiveness.\n";
@@ -4,5 +4,6 @@ export declare class KnowledgeContextProvider {
4
4
  private walkDirectory;
5
5
  private indexKnowledge;
6
6
  private buildSnippet;
7
+ private isDirectInjectedScratchpad;
7
8
  private formatPrompt;
8
9
  }
@@ -2,3 +2,5 @@ import type { MissionLoopState } from "../../shared/loop/interfaces/mission-loop
2
2
  export declare function syncMissionMemory(directory: string, state: MissionLoopState): boolean;
3
3
  export declare function getMissionScratchpadPath(directory: string): string;
4
4
  export declare function getMissionCanvasPath(directory: string): string;
5
+ export declare function getMissionMemoryNotesDirPath(directory: string): string;
6
+ export declare function readMissionScratchpadSnapshot(directory: string, maxChars?: number): string | null;
package/dist/index.js CHANGED
@@ -36437,7 +36437,7 @@ init_logger();
36437
36437
  // src/core/loop/mission-loop.ts
36438
36438
  init_logger();
36439
36439
  init_shared();
36440
- import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync4 } from "node:fs";
36440
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync4 } from "node:fs";
36441
36441
  import { join as join6 } from "node:path";
36442
36442
 
36443
36443
  // src/shared/constants/system-messages.ts
@@ -36515,15 +36515,15 @@ var CONTINUE_INSTRUCTION = `<auto_continue>
36515
36515
  </auto_continue>`;
36516
36516
  var STAGNATION_INTERVENTION = `
36517
36517
  <system_intervention type="stagnation_detected">
36518
- \u26A0\uFE0F **\uACBD\uACE0: \uC9C4\uD589 \uC815\uCCB4 \uAC10\uC9C0 (STAGNATION DETECTED)**
36519
- \uCD5C\uADFC \uC5EC\uB7EC \uD134 \uB3D9\uC548 \uC2E4\uC9C8\uC801\uC778 \uC9C4\uC804\uC774 \uAC10\uC9C0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uB2E8\uC21C "\uBAA8\uB2C8\uD130\uB9C1"\uC774\uB098 \uAC19\uC740 \uD589\uB3D9\uC744 \uBC18\uBCF5\uD558\uB294 \uAC83\uC740 \uAE08\uC9C0\uB429\uB2C8\uB2E4.
36518
+ \u26A0\uFE0F **WARNING: STAGNATION DETECTED**
36519
+ No substantive progress has been detected for the past several turns. Repeating the same action or merely "monitoring" is prohibited.
36520
36520
 
36521
- **\uC790\uC728\uC801 \uC9C4\uB2E8 \uBC0F \uD574\uACB0 \uC9C0\uCE68:**
36522
- 1. **\uC2E4\uC2DC\uAC04 \uB85C\uADF8 \uD655\uC778**: \`check_background_task\` \uB610\uB294 \`read_file\`\uC744 \uC0AC\uC6A9\uD558\uC5EC \uC9C4\uD589 \uC911\uC778 \uC791\uC5C5\uC758 \uCD9C\uB825 \uB85C\uADF8\uB97C \uC9C1\uC811 \uD655\uC778\uD558\uC2ED\uC2DC\uC624.
36523
- 2. **\uD504\uB85C\uC138\uC2A4 \uC0DD\uC874 \uC9C4\uB2E8**: \uC791\uC5C5\uC774 \uC880\uBE44 \uC0C1\uD0DC\uC774\uAC70\uB098 \uBA48\uCD98 \uAC83 \uAC19\uB2E4\uBA74 \uACFC\uAC10\uD558\uAC8C \`kill\`\uD558\uACE0 \uB2E8\uACC4\uB97C \uC138\uBD84\uD654\uD558\uC5EC \uB2E4\uC2DC \uC2E4\uD589\uD558\uC2ED\uC2DC\uC624.
36524
- 3. **\uC804\uB7B5 \uC804\uD658**: \uB3D9\uC77C\uD55C \uC811\uADFC \uBC29\uC2DD\uC774 \uC2E4\uD328\uD558\uACE0 \uC788\uB2E4\uBA74, \uB2E4\uB978 \uB3C4\uAD6C\uB098 \uBC29\uBC95\uC744 \uC0AC\uC6A9\uD558\uC5EC \uBAA9\uD45C\uC5D0 \uB3C4\uB2EC\uD558\uC2ED\uC2DC\uC624.
36521
+ **Guidelines for Autonomous Diagnosis and Resolution:**
36522
+ 1. **Check Real-time Logs**: Use \`check_background_task\` or \`read_file\` to directly check the output logs of the running task.
36523
+ 2. **Process Liveness Diagnosis**: If the task appears to be in a zombie state or hung, proactively \`kill\` it, break down the steps, and run it again.
36524
+ 3. **Strategy Pivot**: If the same approach keeps failing, use alternative tools or methods to reach the goal.
36525
36525
 
36526
- **\uC9C0\uAE08 \uBC14\uB85C \uB2A5\uB3D9\uC801\uC73C\uB85C \uAC1C\uC785\uD558\uC2ED\uC2DC\uC624. \uB300\uAE30\uD558\uC9C0 \uB9C8\uC2ED\uC2DC\uC624.**
36526
+ **Intervene proactively right now. Do not wait.**
36527
36527
  </system_intervention>`;
36528
36528
  var CLEANUP_INSTRUCTION = `
36529
36529
  <system_maintenance type="continuous_hygiene">
@@ -36548,7 +36548,7 @@ You must maintain a pristine workspace. **As part of your move**, perform these
36548
36548
 
36549
36549
  // src/core/knowledge/mission-memory.ts
36550
36550
  init_shared();
36551
- import { mkdirSync as mkdirSync3, renameSync, writeFileSync } from "node:fs";
36551
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
36552
36552
  import { dirname as dirname2, join as join5 } from "node:path";
36553
36553
 
36554
36554
  // src/core/loop/mission-ledger.ts
@@ -36625,8 +36625,17 @@ function parseLedgerLine(line) {
36625
36625
  var BRAIN_DIR = join5(PATHS.DOCS, "brain");
36626
36626
  var SCRATCHPAD_FILE = "scratchpad.md";
36627
36627
  var CANVAS_FILE = "knowledge-map.canvas";
36628
+ var MEMORY_NOTES_DIR = "memories";
36628
36629
  var MAX_CANVAS_EVENTS = 3;
36629
36630
  var MAX_SCRATCHPAD_EVENTS = 6;
36631
+ var MAX_MEMORY_NOTES = 8;
36632
+ var MAX_MEMORY_BODY_CHARS = 900;
36633
+ var SCRATCHPAD_SNAPSHOT_CHARS = 1600;
36634
+ var MISSION_MEMORY_LEVELS = [
36635
+ "project" /* PROJECT */,
36636
+ "mission" /* MISSION */,
36637
+ "task" /* TASK */
36638
+ ];
36630
36639
  function syncMissionMemory(directory, state2) {
36631
36640
  const options = getMissionRuntimeOptions();
36632
36641
  if (!options.markdownMemory) return false;
@@ -36637,6 +36646,7 @@ function syncMissionMemory(directory, state2) {
36637
36646
  try {
36638
36647
  writeScratchpad(directory, state2, events);
36639
36648
  writeCanvas(directory, state2, events);
36649
+ syncMissionMemoryNotes(directory, state2);
36640
36650
  return true;
36641
36651
  } catch {
36642
36652
  return false;
@@ -36648,6 +36658,17 @@ function getMissionScratchpadPath(directory) {
36648
36658
  function getMissionCanvasPath(directory) {
36649
36659
  return join5(directory, BRAIN_DIR, CANVAS_FILE);
36650
36660
  }
36661
+ function getMissionMemoryNotesDirPath(directory) {
36662
+ return join5(directory, BRAIN_DIR, MEMORY_NOTES_DIR);
36663
+ }
36664
+ function readMissionScratchpadSnapshot(directory, maxChars = SCRATCHPAD_SNAPSHOT_CHARS) {
36665
+ const path11 = getMissionScratchpadPath(directory);
36666
+ if (!existsSync3(path11)) return null;
36667
+ const content = stripFrontmatter(readFileSync2(path11, "utf8")).trim();
36668
+ if (!content) return null;
36669
+ if (content.length <= maxChars) return content;
36670
+ return `${content.slice(0, maxChars)}...`;
36671
+ }
36651
36672
  function writeScratchpad(directory, state2, events) {
36652
36673
  const content = [
36653
36674
  "---",
@@ -36682,6 +36703,22 @@ function writeCanvas(directory, state2, events) {
36682
36703
  const edges = buildCanvasEdges(nodes);
36683
36704
  atomicWrite(getMissionCanvasPath(directory), JSON.stringify({ nodes, edges }, null, 2));
36684
36705
  }
36706
+ function syncMissionMemoryNotes(directory, state2) {
36707
+ const notesDir = getMissionMemoryNotesDirPath(directory);
36708
+ const expectedFiles = /* @__PURE__ */ new Set();
36709
+ for (const entry of selectMissionMemoryEntries(state2)) {
36710
+ const fileName = buildMemoryNoteFileName(entry);
36711
+ expectedFiles.add(fileName);
36712
+ atomicWrite(join5(notesDir, fileName), buildMemoryNoteContent(state2, entry));
36713
+ }
36714
+ if (!existsSync3(notesDir)) return;
36715
+ for (const entry of readdirSync(notesDir, { withFileTypes: true })) {
36716
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
36717
+ if (!expectedFiles.has(entry.name)) {
36718
+ unlinkSync(join5(notesDir, entry.name));
36719
+ }
36720
+ }
36721
+ }
36685
36722
  function buildCanvasNodes(state2, events) {
36686
36723
  const nodes = [
36687
36724
  textNode("objective", `Objective
@@ -36711,6 +36748,67 @@ function buildCanvasEdges(nodes) {
36711
36748
  function textNode(id, text, x, y) {
36712
36749
  return { id, type: "text", text, x, y, width: 360, height: 180 };
36713
36750
  }
36751
+ function selectMissionMemoryEntries(state2) {
36752
+ const snapshot = MemoryManager.getInstance().export();
36753
+ const query = `${state2.objective ?? ""} ${state2.prompt}`.trim();
36754
+ const selected = [];
36755
+ for (const level of MISSION_MEMORY_LEVELS) {
36756
+ const entries = snapshot[level] ?? [];
36757
+ for (const entry of entries) {
36758
+ if (shouldPersistMissionEntry(entry, level, query)) {
36759
+ selected.push(entry);
36760
+ }
36761
+ }
36762
+ }
36763
+ return selected.sort((left, right) => right.importance - left.importance || right.timestamp - left.timestamp).slice(0, MAX_MEMORY_NOTES);
36764
+ }
36765
+ function shouldPersistMissionEntry(entry, level, query) {
36766
+ const matchesQuery = query ? isRelevantToQuery(entry.content, query) : false;
36767
+ switch (level) {
36768
+ case "project" /* PROJECT */:
36769
+ return entry.importance >= 0.7 || matchesQuery;
36770
+ case "mission" /* MISSION */:
36771
+ return entry.importance >= 0.5 || matchesQuery;
36772
+ case "task" /* TASK */:
36773
+ return entry.importance >= 0.7 || matchesQuery;
36774
+ default:
36775
+ return false;
36776
+ }
36777
+ }
36778
+ function isRelevantToQuery(content, query) {
36779
+ const normalizedContent = content.toLowerCase();
36780
+ const keywords = query.toLowerCase().split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 3);
36781
+ if (keywords.length === 0) return false;
36782
+ return keywords.some((keyword) => normalizedContent.includes(keyword));
36783
+ }
36784
+ function buildMemoryNoteFileName(entry) {
36785
+ return `${entry.level}-${sanitizeFilePart(entry.id)}.md`;
36786
+ }
36787
+ function buildMemoryNoteContent(state2, entry) {
36788
+ const recordedAt = new Date(entry.timestamp).toISOString();
36789
+ const body = entry.content.length > MAX_MEMORY_BODY_CHARS ? `${entry.content.slice(0, MAX_MEMORY_BODY_CHARS)}...` : entry.content;
36790
+ return [
36791
+ "---",
36792
+ `tags: [mission-memory, orchestrator, ${entry.level}]`,
36793
+ `title: "${escapeYaml(`${entry.level} memory ${entry.id}`)}"`,
36794
+ "keep: true",
36795
+ `level: "${entry.level}"`,
36796
+ `importance: ${entry.importance.toFixed(3)}`,
36797
+ `session: "${escapeYaml(state2.sessionID)}"`,
36798
+ `recorded_at: "${recordedAt}"`,
36799
+ `objective: "${escapeYaml(state2.objective ?? state2.prompt)}"`,
36800
+ "---",
36801
+ `# ${capitalize(entry.level)} Memory`,
36802
+ "",
36803
+ `- Session: ${state2.sessionID}`,
36804
+ `- Recorded at: ${recordedAt}`,
36805
+ `- Importance: ${entry.importance.toFixed(3)}`,
36806
+ "",
36807
+ "## Content",
36808
+ body,
36809
+ ""
36810
+ ].join("\n");
36811
+ }
36714
36812
  function formatEventLines(events) {
36715
36813
  if (events.length === 0) return ["- No runtime evidence recorded yet."];
36716
36814
  return events.map((event) => `- ${event.timestamp} ${event.type}: ${event.summary ?? event.reason ?? "recorded"}`);
@@ -36724,6 +36822,15 @@ function atomicWrite(path11, content) {
36724
36822
  function escapeYaml(value) {
36725
36823
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
36726
36824
  }
36825
+ function stripFrontmatter(content) {
36826
+ return content.replace(/^---\n[\s\S]*?\n---\n?/u, "");
36827
+ }
36828
+ function sanitizeFilePart(value) {
36829
+ return value.replace(/[^a-zA-Z0-9_-]+/g, "-");
36830
+ }
36831
+ function capitalize(value) {
36832
+ return value.charAt(0).toUpperCase() + value.slice(1);
36833
+ }
36727
36834
 
36728
36835
  // src/core/loop/mission-loop.ts
36729
36836
  var STATE_FILE = MISSION_CONTROL.STATE_FILE;
@@ -36734,11 +36841,11 @@ function getStateFilePath(directory) {
36734
36841
  }
36735
36842
  function readLoopState(directory) {
36736
36843
  const filePath = getStateFilePath(directory);
36737
- if (!existsSync3(filePath)) {
36844
+ if (!existsSync4(filePath)) {
36738
36845
  return null;
36739
36846
  }
36740
36847
  try {
36741
- const content = readFileSync2(filePath, "utf-8");
36848
+ const content = readFileSync3(filePath, "utf-8");
36742
36849
  return JSON.parse(content);
36743
36850
  } catch (error95) {
36744
36851
  log(`[${MISSION_CONTROL.LOG_SOURCE}] Failed to read state: ${error95}`);
@@ -36749,7 +36856,7 @@ function writeLoopState(directory, state2) {
36749
36856
  const filePath = getStateFilePath(directory);
36750
36857
  const dirPath = join6(directory, PATHS.OPENCODE);
36751
36858
  try {
36752
- if (!existsSync3(dirPath)) {
36859
+ if (!existsSync4(dirPath)) {
36753
36860
  mkdirSync4(dirPath, { recursive: true });
36754
36861
  }
36755
36862
  writeFileSync2(filePath, JSON.stringify(state2, null, 2), "utf-8");
@@ -36761,11 +36868,11 @@ function writeLoopState(directory, state2) {
36761
36868
  }
36762
36869
  function clearLoopState(directory) {
36763
36870
  const filePath = getStateFilePath(directory);
36764
- if (!existsSync3(filePath)) {
36871
+ if (!existsSync4(filePath)) {
36765
36872
  return false;
36766
36873
  }
36767
36874
  try {
36768
- unlinkSync(filePath);
36875
+ unlinkSync2(filePath);
36769
36876
  return true;
36770
36877
  } catch (error95) {
36771
36878
  log(`[${MISSION_CONTROL.LOG_SOURCE}] Failed to clear state: ${error95}`);
@@ -37251,7 +37358,7 @@ ${commandList}`;
37251
37358
  // src/core/loop/verification.ts
37252
37359
  init_shared();
37253
37360
  init_logger();
37254
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
37361
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
37255
37362
  import { join as join7 } from "node:path";
37256
37363
  var CHECKLIST_FILE = CHECKLIST.FILE;
37257
37364
  function parseChecklistLine(line, currentCategory) {
@@ -37318,11 +37425,11 @@ function parseChecklist(content) {
37318
37425
  }
37319
37426
  function readChecklist(directory) {
37320
37427
  const filePath = join7(directory, CHECKLIST_FILE);
37321
- if (!existsSync4(filePath)) {
37428
+ if (!existsSync5(filePath)) {
37322
37429
  return [];
37323
37430
  }
37324
37431
  try {
37325
- const content = readFileSync3(filePath, "utf-8");
37432
+ const content = readFileSync4(filePath, "utf-8");
37326
37433
  return parseChecklist(content);
37327
37434
  } catch (error95) {
37328
37435
  log(`[checklist] Failed to read checklist: ${error95}`);
@@ -37340,7 +37447,7 @@ function verifyChecklist(directory) {
37340
37447
  errors: []
37341
37448
  };
37342
37449
  const filePath = join7(directory, CHECKLIST_FILE);
37343
- if (!existsSync4(filePath)) {
37450
+ if (!existsSync5(filePath)) {
37344
37451
  result.errors.push(`Verification checklist not found at ${CHECKLIST_FILE}`);
37345
37452
  result.errors.push("Create checklist with at least: build, tests, and any environment-specific checks");
37346
37453
  return result;
@@ -37417,9 +37524,9 @@ function verifyMissionCompletion(directory) {
37417
37524
  }
37418
37525
  }
37419
37526
  const todoPath = join7(directory, PATHS.TODO);
37420
- if (existsSync4(todoPath)) {
37527
+ if (existsSync5(todoPath)) {
37421
37528
  try {
37422
- const content = readFileSync3(todoPath, "utf-8");
37529
+ const content = readFileSync4(todoPath, "utf-8");
37423
37530
  const incompleteCount = countMatches(content, TODO_INCOMPLETE_PATTERN);
37424
37531
  const completeCount = countMatches(content, TODO_COMPLETE_PATTERN);
37425
37532
  const total = incompleteCount + completeCount;
@@ -37442,9 +37549,9 @@ function verifyMissionCompletion(directory) {
37442
37549
  result.errors.push(`TODO file not found at ${PATHS.TODO}`);
37443
37550
  }
37444
37551
  const syncPath = join7(directory, PATHS.SYNC_ISSUES);
37445
- if (existsSync4(syncPath)) {
37552
+ if (existsSync5(syncPath)) {
37446
37553
  try {
37447
- const content = readFileSync3(syncPath, "utf-8");
37554
+ const content = readFileSync4(syncPath, "utf-8");
37448
37555
  result.syncIssuesEmpty = !hasRealSyncIssues(content);
37449
37556
  if (!result.syncIssuesEmpty) {
37450
37557
  const issueLines = content.split("\n").filter(
@@ -37532,7 +37639,7 @@ function buildVerificationSummary(result) {
37532
37639
  init_logger();
37533
37640
  import { exec as exec2 } from "node:child_process";
37534
37641
  import { promisify as promisify2 } from "node:util";
37535
- import { readFileSync as readFileSync4 } from "node:fs";
37642
+ import { readFileSync as readFileSync5 } from "node:fs";
37536
37643
 
37537
37644
  // src/shared/notification/os-notify/constants/notification-commands.ts
37538
37645
  var NOTIFICATION_COMMANDS = {
@@ -37610,7 +37717,7 @@ async function notifyDarwin(title, message) {
37610
37717
  function isWSL() {
37611
37718
  try {
37612
37719
  if (process.env.WSL_DISTRO_NAME || process.env.WSLENV) return true;
37613
- const procVersion = readFileSync4("/proc/version", "utf-8");
37720
+ const procVersion = readFileSync5("/proc/version", "utf-8");
37614
37721
  return /microsoft|WSL/i.test(procVersion);
37615
37722
  } catch {
37616
37723
  return false;
@@ -39131,9 +39238,9 @@ import * as path6 from "node:path";
39131
39238
 
39132
39239
  // src/core/cache/utils.ts
39133
39240
  import * as fs6 from "node:fs/promises";
39134
- import { existsSync as existsSync6 } from "node:fs";
39241
+ import { existsSync as existsSync7 } from "node:fs";
39135
39242
  async function ensureCacheDir() {
39136
- if (!existsSync6(CACHE_DIR)) {
39243
+ if (!existsSync7(CACHE_DIR)) {
39137
39244
  await fs6.mkdir(CACHE_DIR, { recursive: true });
39138
39245
  }
39139
39246
  }
@@ -39635,14 +39742,14 @@ var backgroundTaskManager = BackgroundTaskManager.instance;
39635
39742
 
39636
39743
  // src/tools/rust-pool.ts
39637
39744
  import { spawn as spawn2 } from "child_process";
39638
- import { existsSync as existsSync9 } from "fs";
39745
+ import { existsSync as existsSync10 } from "fs";
39639
39746
 
39640
39747
  // src/utils/binary.ts
39641
39748
  init_shared();
39642
39749
  import { join as join12, dirname as dirname5 } from "path";
39643
39750
  import { fileURLToPath } from "url";
39644
39751
  import { platform, arch } from "os";
39645
- import { existsSync as existsSync8 } from "fs";
39752
+ import { existsSync as existsSync9 } from "fs";
39646
39753
  var __dirname = dirname5(fileURLToPath(import.meta.url));
39647
39754
  function getPlatformBinaryName(os = platform(), cpu = arch()) {
39648
39755
  if (os === PLATFORM.WIN32) {
@@ -39663,7 +39770,7 @@ function resolveBinaryPath(options = {}) {
39663
39770
  const moduleDir = options.moduleDir ?? __dirname;
39664
39771
  const os = options.os ?? platform();
39665
39772
  const cpu = options.cpu ?? arch();
39666
- const exists = options.exists ?? existsSync8;
39773
+ const exists = options.exists ?? existsSync9;
39667
39774
  const binaryName = getPlatformBinaryName(os, cpu);
39668
39775
  for (const binDir of getCandidateBinDirs(moduleDir)) {
39669
39776
  const binaryPath = join12(binDir, binaryName);
@@ -39702,7 +39809,7 @@ var RustToolPool = class {
39702
39809
  constructor(maxSize = 4, options = {}) {
39703
39810
  this.maxSize = maxSize;
39704
39811
  this.binaryPath = options.binaryPath ?? getBinaryPath;
39705
- this.exists = options.exists ?? existsSync9;
39812
+ this.exists = options.exists ?? existsSync10;
39706
39813
  this.idleTimeout = options.idleTimeoutMs ?? this.idleTimeout;
39707
39814
  this.processReadyDelay = options.processReadyDelayMs ?? this.processReadyDelay;
39708
39815
  this.requestTimeout = options.requestTimeoutMs ?? this.requestTimeout;
@@ -42478,7 +42585,7 @@ Wait for these tasks to complete before concluding the mission.
42478
42585
  init_shared();
42479
42586
 
42480
42587
  // src/core/knowledge/context-provider.ts
42481
- import { existsSync as existsSync10, readFileSync as readFileSync6, readdirSync } from "node:fs";
42588
+ import { existsSync as existsSync11, readFileSync as readFileSync7, readdirSync as readdirSync2 } from "node:fs";
42482
42589
  import path10 from "node:path";
42483
42590
 
42484
42591
  // src/core/knowledge/graph-parser.ts
@@ -42772,7 +42879,7 @@ var HybridSearch = class {
42772
42879
  };
42773
42880
 
42774
42881
  // src/core/knowledge/tag-indexer.ts
42775
- import { readFileSync as readFileSync5 } from "node:fs";
42882
+ import { readFileSync as readFileSync6 } from "node:fs";
42776
42883
  var TagIndexer = class {
42777
42884
  tagMap = /* @__PURE__ */ new Map();
42778
42885
  fileCache = /* @__PURE__ */ new Map();
@@ -42846,7 +42953,7 @@ var TagIndexer = class {
42846
42953
  */
42847
42954
  indexFileFromDisk(filePath) {
42848
42955
  try {
42849
- const content = readFileSync5(filePath, "utf8");
42956
+ const content = readFileSync6(filePath, "utf8");
42850
42957
  this.indexFile(filePath, content);
42851
42958
  } catch {
42852
42959
  this.clearIndexForFile(filePath);
@@ -42946,6 +43053,7 @@ var MAX_RESULTS = 3;
42946
43053
  var MAX_SNIPPET_CHARS = 220;
42947
43054
  var KNOWLEDGE_ROOTS = ["docs", path10.join(".opencode", "docs")];
42948
43055
  var SKIP_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", "dist", "bin", ".git", "archive"]);
43056
+ var GENERATED_SCRATCHPAD_PATH = path10.join(".opencode", "docs", "brain", "scratchpad.md");
42949
43057
  var KnowledgeContextProvider = class {
42950
43058
  buildPrompt(directory, query) {
42951
43059
  const normalizedQuery = query.trim();
@@ -42961,14 +43069,14 @@ var KnowledgeContextProvider = class {
42961
43069
  const files = [];
42962
43070
  for (const root of KNOWLEDGE_ROOTS) {
42963
43071
  const fullRoot = path10.join(directory, root);
42964
- if (!existsSync10(fullRoot)) continue;
43072
+ if (!existsSync11(fullRoot)) continue;
42965
43073
  files.push(...this.walkDirectory(fullRoot));
42966
43074
  }
42967
- return files.sort();
43075
+ return files.filter((filePath) => !this.isDirectInjectedScratchpad(directory, filePath)).sort();
42968
43076
  }
42969
43077
  walkDirectory(directory) {
42970
43078
  const files = [];
42971
- for (const entry of readdirSync(directory, { withFileTypes: true })) {
43079
+ for (const entry of readdirSync2(directory, { withFileTypes: true })) {
42972
43080
  const fullPath = path10.join(directory, entry.name);
42973
43081
  if (entry.isDirectory()) {
42974
43082
  if (SKIP_SEGMENTS.has(entry.name)) continue;
@@ -42989,7 +43097,7 @@ var KnowledgeContextProvider = class {
42989
43097
  const noteToSnippet = /* @__PURE__ */ new Map();
42990
43098
  for (const filePath of files) {
42991
43099
  try {
42992
- const content = readFileSync6(filePath, "utf8");
43100
+ const content = readFileSync7(filePath, "utf8");
42993
43101
  const noteName = graphParser.getNoteName(filePath);
42994
43102
  const { body } = tagIndexer.parseFrontmatter(content);
42995
43103
  const normalizedBody = body.trim();
@@ -43009,6 +43117,9 @@ var KnowledgeContextProvider = class {
43009
43117
  if (normalized.length <= MAX_SNIPPET_CHARS) return normalized;
43010
43118
  return `${normalized.slice(0, MAX_SNIPPET_CHARS)}...`;
43011
43119
  }
43120
+ isDirectInjectedScratchpad(directory, filePath) {
43121
+ return path10.relative(directory, filePath) === GENERATED_SCRATCHPAD_PATH;
43122
+ }
43012
43123
  formatPrompt(query, results, indexed) {
43013
43124
  const lines = [
43014
43125
  "<knowledge_rag_context>",
@@ -43050,13 +43161,17 @@ function createSystemTransformHandler(ctx) {
43050
43161
  const { commander: commander2 } = await Promise.resolve().then(() => (init_commander(), commander_exports));
43051
43162
  systemAdditions.push(commander2.systemPrompt);
43052
43163
  systemAdditions.push(buildMissionLoopSystemPrompt(loopState));
43164
+ const scratchpadPrompt = buildMissionScratchpadPrompt(directory);
43165
+ if (scratchpadPrompt) {
43166
+ systemAdditions.push(scratchpadPrompt);
43167
+ }
43053
43168
  }
43054
43169
  if (session?.active) {
43055
43170
  systemAdditions.push(buildActiveSessionPrompt(session.step));
43056
43171
  }
43057
43172
  const knowledgePrompt = buildKnowledgeContextPrompt(
43058
43173
  directory,
43059
- loopState?.prompt,
43174
+ loopState,
43060
43175
  state2.sessions.get(sessionID)?.currentTask
43061
43176
  );
43062
43177
  if (knowledgePrompt) {
@@ -43077,10 +43192,24 @@ function createSystemTransformHandler(ctx) {
43077
43192
  }
43078
43193
  };
43079
43194
  }
43080
- function buildKnowledgeContextPrompt(directory, missionPrompt, currentTask) {
43081
- const queryParts = [missionPrompt ?? "", currentTask ?? ""].filter(Boolean);
43195
+ function buildKnowledgeContextPrompt(directory, loopState, currentTask) {
43196
+ const queryParts = [
43197
+ loopState?.objective ?? "",
43198
+ loopState?.prompt ?? "",
43199
+ currentTask ?? "",
43200
+ loopState?.lastProgress ?? "",
43201
+ loopState?.lastVerificationSummary ?? "",
43202
+ loopState?.lastContinuationReason ?? ""
43203
+ ].filter(Boolean);
43082
43204
  return knowledgeContextProvider.buildPrompt(directory, queryParts.join(" ").trim());
43083
43205
  }
43206
+ function buildMissionScratchpadPrompt(directory) {
43207
+ const snapshot = readMissionScratchpadSnapshot(directory);
43208
+ if (!snapshot) return null;
43209
+ return `<mission_scratchpad path="${PATHS.DOCS}/brain/scratchpad.md">
43210
+ ${snapshot}
43211
+ </mission_scratchpad>`;
43212
+ }
43084
43213
  function buildMissionLoopSystemPrompt(loopState) {
43085
43214
  return `<orchestrator_mission_loop>
43086
43215
  \u{1F3AF} MISSION LOOP ACTIVE: Iteration ${loopState.iteration}/${loopState.maxIterations}
@@ -21,5 +21,5 @@ export declare const MISSION_MESSAGES: {
21
21
  };
22
22
  export declare const COMPACTION_PROMPT: string;
23
23
  export declare const CONTINUE_INSTRUCTION: string;
24
- export declare const STAGNATION_INTERVENTION = "\n<system_intervention type=\"stagnation_detected\">\n\u26A0\uFE0F **\uACBD\uACE0: \uC9C4\uD589 \uC815\uCCB4 \uAC10\uC9C0 (STAGNATION DETECTED)**\n\uCD5C\uADFC \uC5EC\uB7EC \uD134 \uB3D9\uC548 \uC2E4\uC9C8\uC801\uC778 \uC9C4\uC804\uC774 \uAC10\uC9C0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uB2E8\uC21C \"\uBAA8\uB2C8\uD130\uB9C1\"\uC774\uB098 \uAC19\uC740 \uD589\uB3D9\uC744 \uBC18\uBCF5\uD558\uB294 \uAC83\uC740 \uAE08\uC9C0\uB429\uB2C8\uB2E4.\n\n**\uC790\uC728\uC801 \uC9C4\uB2E8 \uBC0F \uD574\uACB0 \uC9C0\uCE68:**\n1. **\uC2E4\uC2DC\uAC04 \uB85C\uADF8 \uD655\uC778**: `check_background_task` \uB610\uB294 `read_file`\uC744 \uC0AC\uC6A9\uD558\uC5EC \uC9C4\uD589 \uC911\uC778 \uC791\uC5C5\uC758 \uCD9C\uB825 \uB85C\uADF8\uB97C \uC9C1\uC811 \uD655\uC778\uD558\uC2ED\uC2DC\uC624.\n2. **\uD504\uB85C\uC138\uC2A4 \uC0DD\uC874 \uC9C4\uB2E8**: \uC791\uC5C5\uC774 \uC880\uBE44 \uC0C1\uD0DC\uC774\uAC70\uB098 \uBA48\uCD98 \uAC83 \uAC19\uB2E4\uBA74 \uACFC\uAC10\uD558\uAC8C `kill`\uD558\uACE0 \uB2E8\uACC4\uB97C \uC138\uBD84\uD654\uD558\uC5EC \uB2E4\uC2DC \uC2E4\uD589\uD558\uC2ED\uC2DC\uC624.\n3. **\uC804\uB7B5 \uC804\uD658**: \uB3D9\uC77C\uD55C \uC811\uADFC \uBC29\uC2DD\uC774 \uC2E4\uD328\uD558\uACE0 \uC788\uB2E4\uBA74, \uB2E4\uB978 \uB3C4\uAD6C\uB098 \uBC29\uBC95\uC744 \uC0AC\uC6A9\uD558\uC5EC \uBAA9\uD45C\uC5D0 \uB3C4\uB2EC\uD558\uC2ED\uC2DC\uC624.\n\n**\uC9C0\uAE08 \uBC14\uB85C \uB2A5\uB3D9\uC801\uC73C\uB85C \uAC1C\uC785\uD558\uC2ED\uC2DC\uC624. \uB300\uAE30\uD558\uC9C0 \uB9C8\uC2ED\uC2DC\uC624.**\n</system_intervention>";
24
+ export declare const STAGNATION_INTERVENTION = "\n<system_intervention type=\"stagnation_detected\">\n\u26A0\uFE0F **WARNING: STAGNATION DETECTED**\nNo substantive progress has been detected for the past several turns. Repeating the same action or merely \"monitoring\" is prohibited.\n\n**Guidelines for Autonomous Diagnosis and Resolution:**\n1. **Check Real-time Logs**: Use `check_background_task` or `read_file` to directly check the output logs of the running task.\n2. **Process Liveness Diagnosis**: If the task appears to be in a zombie state or hung, proactively `kill` it, break down the steps, and run it again.\n3. **Strategy Pivot**: If the same approach keeps failing, use alternative tools or methods to reach the goal.\n\n**Intervene proactively right now. Do not wait.**\n</system_intervention>";
25
25
  export declare const CLEANUP_INSTRUCTION = "\n<system_maintenance type=\"continuous_hygiene\">\n\uD83E\uDDF9 **DOCUMENTATION & STATE HYGIENE (Iteration %ITER%)**\nYou must maintain a pristine workspace. **As part of your move**, perform these checks:\n\n1. **Relevance Assessment**:\n - Review active documents (`.opencode/*.md`). Are they needed for the *current* objective?\n - If a file represents a solved problem or obsolete context, **Archive it** to `.opencode/archive/` or delete it.\n\n2. **Synchronization**:\n - Verify `TODO.md` matches the actual code state. Mark completed items immediately.\n - Check `sync-issues.md`. If issues are resolved, remove them.\n\n3. **Context Optimization**:\n - If `work-log.md` is getting noisy, summarize key decisions into `summary.md` and truncate the log.\n - Keep context lightweight.\n\n**Rule**: A cluttered workspace leads to hallucinations. Clean as you go.\n</system_maintenance>\n";
package/package.json CHANGED
@@ -2,14 +2,14 @@
2
2
  "name": "opencode-orchestrator",
3
3
  "displayName": "OpenCode Orchestrator",
4
4
  "description": "Multi-agent mission control for OpenCode with Commander, Planner, Worker, and Reviewer workflows.",
5
- "version": "1.3.11",
5
+ "version": "1.5.0",
6
6
  "author": "agnusdei1207",
7
7
  "license": "MIT",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/agnusdei1207/opencode-orchestrator.git"
11
11
  },
12
- "homepage": "https://github.com/agnusdei1207/opencode-orchestrator/issues",
12
+ "homepage": "https://agnusdei1207.github.io/opencode-orchestrator/",
13
13
  "bugs": {
14
14
  "url": "https://github.com/agnusdei1207/opencode-orchestrator/issues"
15
15
  },
@@ -74,8 +74,8 @@
74
74
  "node": ">=24"
75
75
  },
76
76
  "dependencies": {
77
- "@opencode-ai/plugin": "1.17.3",
78
- "@opencode-ai/sdk": "1.17.3",
77
+ "@opencode-ai/plugin": "1.17.4",
78
+ "@opencode-ai/sdk": "1.17.4",
79
79
  "jsonc-parser": "^3.3.1",
80
80
  "zod": "^4.3.6"
81
81
  },