opencode-orchestrator 0.7.1 β†’ 0.8.1

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
@@ -109,7 +109,7 @@ Restart OpenCode after installation.
109
109
 
110
110
  | Mode | Trigger | Behavior |
111
111
  |------|---------|----------|
112
- | **Commander Mode** 🎯 | `/task "mission"` | Full autonomous execution until **MISSION COMPLETE** |
112
+ | **Commander Mode** 🎯 | `/task "mission"` | Full autonomous execution until sealed |
113
113
  | **Chat Mode** πŸ’¬ | Regular conversation | Simple Q&A, no autonomous behavior |
114
114
 
115
115
  ---
@@ -125,15 +125,32 @@ Use `/task` when you need the AI to **complete a mission autonomously**:
125
125
  ```
126
126
 
127
127
  **What Commander Mode Does:**
128
- - ♾️ **Runs until done** β€” Never stops until "MISSION COMPLETE"
128
+ - ♾️ **Runs until sealed** β€” Loops until agent outputs `<mission_seal>SEALED</mission_seal>`
129
129
  - 🧠 **Anti-Hallucination** β€” Researches docs before coding
130
130
  - ⚑ **Parallel Execution** β€” Up to 50 concurrent agents
131
131
  - πŸ”„ **Auto-Recovery** β€” Handles errors automatically
132
- - πŸ“Š **Trriage System** β€” Adapts strategy to complexity (L1/L2/L3)
132
+ - πŸ“Š **Triage System** β€” Adapts strategy to complexity (L1/L2/L3)
133
+
134
+ **πŸŽ–οΈ Mission Seal Loop:**
135
+ ```
136
+ /task "mission" β†’ Agent works β†’ Idle? β†’ Seal found?
137
+ ↑ β”‚
138
+ β”‚ No β”‚ Yes
139
+ └──────────────┴──→ βœ… Complete
140
+ ```
141
+
142
+ When the agent finishes ALL work, it outputs:
143
+ ```xml
144
+ <mission_seal>SEALED</mission_seal>
145
+ ```
146
+
147
+ **Control Commands:**
148
+ - `/stop` or `/cancel` β€” Stop the loop manually
149
+ - Max 20 iterations (configurable)
133
150
 
134
151
  <div align="center">
135
152
  <img src="assets/tui_image.png" alt="Commander TUI" width="600" />
136
- <p><sub><b>/task "mission"</b> triggers full Commander mode</sub></p>
153
+ <p><sub><b>/task "mission"</b> triggers full Commander mode with Mission Seal loop</sub></p>
137
154
  </div>
138
155
 
139
156
  ---
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Mission Seal Handler
3
+ *
4
+ * Integrates Mission Seal detection with session events.
5
+ * When session goes idle, checks for seal and either:
6
+ * - Completes loop if seal detected
7
+ * - Continues with next iteration if seal not detected
8
+ */
9
+ import type { PluginInput } from "@opencode-ai/plugin";
10
+ type OpencodeClient = PluginInput["client"];
11
+ /**
12
+ * Handle session.idle event for mission seal loop
13
+ */
14
+ export declare function handleMissionSealIdle(client: OpencodeClient, directory: string, sessionID: string, mainSessionID?: string): Promise<void>;
15
+ /**
16
+ * Handle user message - cancel countdown
17
+ */
18
+ export declare function handleUserMessage(sessionID: string): void;
19
+ /**
20
+ * Handle abort - prevent continuation
21
+ */
22
+ export declare function handleAbort(sessionID: string): void;
23
+ /**
24
+ * Clean up session state
25
+ */
26
+ export declare function cleanupSession(sessionID: string): void;
27
+ /**
28
+ * Check if there's a pending countdown
29
+ */
30
+ export declare function hasPendingContinuation(sessionID: string): boolean;
31
+ export {};
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Mission Seal - Explicit completion detection system
3
+ *
4
+ * When an agent outputs `<mission_seal>SEALED</mission_seal>`,
5
+ * the task loop knows the mission is truly complete.
6
+ *
7
+ * This prevents false-positive idle detection and ensures
8
+ * agents explicitly confirm task completion.
9
+ */
10
+ import type { PluginInput } from "@opencode-ai/plugin";
11
+ /** Tag for mission seal detection */
12
+ export declare const MISSION_SEAL_TAG: "mission_seal";
13
+ /** Seal confirmation value */
14
+ export declare const SEAL_CONFIRMATION: "SEALED";
15
+ /** Full seal pattern: <mission_seal>SEALED</mission_seal> */
16
+ export declare const SEAL_PATTERN: "<mission_seal>SEALED</mission_seal>";
17
+ /** Regex for detecting seal in text */
18
+ export declare const SEAL_REGEX: RegExp;
19
+ export interface MissionLoopState {
20
+ /** Whether loop is active */
21
+ active: boolean;
22
+ /** Current iteration number */
23
+ iteration: number;
24
+ /** Maximum allowed iterations */
25
+ maxIterations: number;
26
+ /** Original task prompt */
27
+ prompt: string;
28
+ /** Session ID */
29
+ sessionID: string;
30
+ /** When loop started */
31
+ startedAt: string;
32
+ /** Last activity timestamp */
33
+ lastActivity?: string;
34
+ }
35
+ export interface MissionLoopOptions {
36
+ /** Maximum iterations before stopping (default: 20) */
37
+ maxIterations?: number;
38
+ /** Countdown seconds before auto-continue (default: 3) */
39
+ countdownSeconds?: number;
40
+ }
41
+ /**
42
+ * Read loop state from disk
43
+ */
44
+ export declare function readLoopState(directory: string): MissionLoopState | null;
45
+ /**
46
+ * Write loop state to disk
47
+ */
48
+ export declare function writeLoopState(directory: string, state: MissionLoopState): boolean;
49
+ /**
50
+ * Clear loop state (delete file)
51
+ */
52
+ export declare function clearLoopState(directory: string): boolean;
53
+ /**
54
+ * Increment iteration counter
55
+ */
56
+ export declare function incrementIteration(directory: string): MissionLoopState | null;
57
+ /**
58
+ * Check if text contains mission seal
59
+ */
60
+ export declare function detectSealInText(text: string): boolean;
61
+ /**
62
+ * Check session messages for mission seal
63
+ */
64
+ export declare function detectSealInSession(client: PluginInput["client"], sessionID: string): Promise<boolean>;
65
+ /**
66
+ * Start a mission loop
67
+ */
68
+ export declare function startMissionLoop(directory: string, sessionID: string, prompt: string, options?: MissionLoopOptions): boolean;
69
+ /**
70
+ * Cancel an active mission loop
71
+ */
72
+ export declare function cancelMissionLoop(directory: string, sessionID: string): boolean;
73
+ /**
74
+ * Check if loop is active for session
75
+ */
76
+ export declare function isLoopActive(directory: string, sessionID: string): boolean;
77
+ /**
78
+ * Get remaining iterations
79
+ */
80
+ export declare function getRemainingIterations(directory: string): number;
81
+ /**
82
+ * Generate continuation prompt for mission loop
83
+ */
84
+ export declare function generateMissionContinuationPrompt(state: MissionLoopState): string;
85
+ /**
86
+ * Generate completion notification
87
+ */
88
+ export declare function generateSealedNotification(state: MissionLoopState): string;
89
+ /**
90
+ * Generate max iterations reached notification
91
+ */
92
+ export declare function generateMaxIterationsNotification(state: MissionLoopState): string;
package/dist/index.js CHANGED
@@ -116,16 +116,25 @@ var TOOL_NAMES = {
116
116
  CALL_AGENT: "call_agent",
117
117
  SLASHCOMMAND: "slashcommand"
118
118
  };
119
- var MISSION = {
120
- /** Mission completion marker (with emoji) */
121
- COMPLETE: "\u2705 MISSION COMPLETE",
122
- /** Mission completion marker (text only) */
123
- COMPLETE_TEXT: "MISSION COMPLETE",
119
+ var MISSION_SEAL = {
120
+ /** XML tag name for mission seal */
121
+ TAG: "mission_seal",
122
+ /** Confirmation value inside tag */
123
+ CONFIRMATION: "SEALED",
124
+ /** Full seal pattern: <mission_seal>SEALED</mission_seal> */
125
+ PATTERN: "<mission_seal>SEALED</mission_seal>",
126
+ /** Default max loop iterations */
127
+ DEFAULT_MAX_ITERATIONS: 20,
128
+ /** Default countdown seconds before continue */
129
+ DEFAULT_COUNTDOWN_SECONDS: 3,
130
+ /** Loop state file name */
131
+ STATE_FILE: "loop-state.json",
124
132
  /** Stop command */
125
133
  STOP_COMMAND: "/stop",
126
134
  /** Cancel command */
127
135
  CANCEL_COMMAND: "/cancel"
128
136
  };
137
+ var MISSION = MISSION_SEAL;
129
138
  var AGENT_EMOJI = {
130
139
  Commander: "\u{1F3AF}",
131
140
  Planner: "\u{1F4CB}",
@@ -171,7 +180,7 @@ Complete missions efficiently using multiple agents simultaneously. Never stop u
171
180
  <core_principles>
172
181
  1. PARALLELISM FIRST: Always run independent tasks simultaneously
173
182
  2. NEVER BLOCK: Use background execution for slow operations
174
- 3. NEVER STOP: Loop until "${MISSION.COMPLETE}"
183
+ 3. NEVER STOP: Loop until "${MISSION_SEAL.PATTERN}"
175
184
  4. THINK FIRST: Reason before every action
176
185
  5. SESSION REUSE: Resume sessions to preserve context
177
186
  </core_principles>
@@ -354,9 +363,22 @@ OUTPUT ONLY WHEN:
354
363
  2. Build/tests pass
355
364
  3. ${AGENT_NAMES.REVIEWER} approves
356
365
 
357
- ${MISSION.COMPLETE}
366
+ **MISSION SEAL** (Explicit Completion Confirmation):
367
+ When ALL work is truly complete, output the seal tag:
368
+ \`\`\`
369
+ ${MISSION_SEAL.PATTERN}
370
+ \`\`\`
371
+
372
+ Then output:
373
+ ${MISSION_SEAL.PATTERN}
358
374
  Summary: [accomplishments]
359
375
  Evidence: [test/build results]
376
+
377
+ \u26A0\uFE0F IMPORTANT: Only output ${MISSION_SEAL.PATTERN} when:
378
+ - All todos are marked [x] complete
379
+ - All tests pass
380
+ - All builds succeed
381
+ - You have verified the final result
360
382
  </completion>`,
361
383
  canWrite: true,
362
384
  canBash: true
@@ -13216,7 +13238,7 @@ $ARGUMENTS
13216
13238
  <execution_rules>
13217
13239
  1. Complete this mission without user intervention
13218
13240
  2. Use your full capabilities: research, implement, verify
13219
- 3. Output "${MISSION.COMPLETE}" when done
13241
+ 3. Output "${MISSION_SEAL.PATTERN}" when done
13220
13242
  </execution_rules>
13221
13243
  </mission>`;
13222
13244
  var COMMANDS = {
@@ -13254,7 +13276,7 @@ var COMMANDS = {
13254
13276
 
13255
13277
  | Agent | Role | Capabilities |
13256
13278
  |-------|------|--------------|
13257
- | **${AGENT_NAMES.COMMANDER}** \u{1F3AF} | Master Orchestrator | Autonomous mission control, parallel task coordination, never stops until \u2705 MISSION COMPLETE |
13279
+ | **${AGENT_NAMES.COMMANDER}** \u{1F3AF} | Master Orchestrator | Autonomous mission control, parallel task coordination, never stops until ${MISSION_SEAL.PATTERN} |
13258
13280
  | **${AGENT_NAMES.PLANNER}** \u{1F4CB} | Strategic Planner | Task decomposition, research, caching docs, dependency analysis |
13259
13281
  | **${AGENT_NAMES.WORKER}** \u{1F528} | Implementation | Code, files, terminal, documentation lookup when needed |
13260
13282
  | **${AGENT_NAMES.REVIEWER}** \u2705 | Quality & Context | Verification, TODO updates, context management, auto-fix |
@@ -16756,6 +16778,310 @@ function cleanupSession(sessionID) {
16756
16778
  sessionStates.delete(sessionID);
16757
16779
  }
16758
16780
 
16781
+ // src/core/loop/mission-seal.ts
16782
+ import { existsSync as existsSync4, readFileSync, writeFileSync, unlinkSync } from "node:fs";
16783
+ import { join as join5 } from "node:path";
16784
+ var MISSION_SEAL_TAG = MISSION_SEAL.TAG;
16785
+ var SEAL_CONFIRMATION = MISSION_SEAL.CONFIRMATION;
16786
+ var SEAL_PATTERN = MISSION_SEAL.PATTERN;
16787
+ var SEAL_REGEX = new RegExp(
16788
+ `<${MISSION_SEAL.TAG}>\\s*${MISSION_SEAL.CONFIRMATION}\\s*</${MISSION_SEAL.TAG}>`,
16789
+ "i"
16790
+ );
16791
+ var STATE_FILE = MISSION_SEAL.STATE_FILE;
16792
+ var DEFAULT_MAX_ITERATIONS = MISSION_SEAL.DEFAULT_MAX_ITERATIONS;
16793
+ var DEFAULT_COUNTDOWN_SECONDS = MISSION_SEAL.DEFAULT_COUNTDOWN_SECONDS;
16794
+ function getStateFilePath(directory) {
16795
+ return join5(directory, PATHS.OPENCODE, STATE_FILE);
16796
+ }
16797
+ function readLoopState(directory) {
16798
+ const filePath = getStateFilePath(directory);
16799
+ if (!existsSync4(filePath)) {
16800
+ return null;
16801
+ }
16802
+ try {
16803
+ const content = readFileSync(filePath, "utf-8");
16804
+ return JSON.parse(content);
16805
+ } catch (error45) {
16806
+ log2(`[mission-seal] Failed to read state: ${error45}`);
16807
+ return null;
16808
+ }
16809
+ }
16810
+ function writeLoopState(directory, state2) {
16811
+ const filePath = getStateFilePath(directory);
16812
+ try {
16813
+ writeFileSync(filePath, JSON.stringify(state2, null, 2), "utf-8");
16814
+ return true;
16815
+ } catch (error45) {
16816
+ log2(`[mission-seal] Failed to write state: ${error45}`);
16817
+ return false;
16818
+ }
16819
+ }
16820
+ function clearLoopState(directory) {
16821
+ const filePath = getStateFilePath(directory);
16822
+ if (!existsSync4(filePath)) {
16823
+ return true;
16824
+ }
16825
+ try {
16826
+ unlinkSync(filePath);
16827
+ return true;
16828
+ } catch (error45) {
16829
+ log2(`[mission-seal] Failed to clear state: ${error45}`);
16830
+ return false;
16831
+ }
16832
+ }
16833
+ function incrementIteration(directory) {
16834
+ const state2 = readLoopState(directory);
16835
+ if (!state2) return null;
16836
+ state2.iteration += 1;
16837
+ state2.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
16838
+ if (writeLoopState(directory, state2)) {
16839
+ return state2;
16840
+ }
16841
+ return null;
16842
+ }
16843
+ function detectSealInText(text) {
16844
+ return SEAL_REGEX.test(text);
16845
+ }
16846
+ async function detectSealInSession(client, sessionID) {
16847
+ try {
16848
+ const response = await client.session.messages({ path: { id: sessionID } });
16849
+ const messages = response.data ?? [];
16850
+ const assistantMessages = messages.filter((m) => m.info?.role === "assistant");
16851
+ const recentMessages = assistantMessages.slice(-3);
16852
+ for (const msg of recentMessages) {
16853
+ if (!msg.parts) continue;
16854
+ const textParts = msg.parts.filter(
16855
+ (p) => p.type === PART_TYPES.TEXT || p.type === PART_TYPES.REASONING
16856
+ );
16857
+ for (const part of textParts) {
16858
+ if (part.text && detectSealInText(part.text)) {
16859
+ return true;
16860
+ }
16861
+ }
16862
+ }
16863
+ return false;
16864
+ } catch (error45) {
16865
+ log2(`[mission-seal] Failed to check session messages: ${error45}`);
16866
+ return false;
16867
+ }
16868
+ }
16869
+ function isLoopActive(directory, sessionID) {
16870
+ const state2 = readLoopState(directory);
16871
+ return state2?.active === true && state2?.sessionID === sessionID;
16872
+ }
16873
+ function generateMissionContinuationPrompt(state2) {
16874
+ return `<mission_loop iteration="${state2.iteration}" max="${state2.maxIterations}">
16875
+ \u{1F4CB} **Mission Loop Active** - Iteration ${state2.iteration}/${state2.maxIterations}
16876
+
16877
+ Your previous iteration did not seal the mission. Continue working.
16878
+
16879
+ **RULES**:
16880
+ 1. Review your progress from the previous iteration
16881
+ 2. Continue from where you left off
16882
+ 3. Check TODO list for incomplete items
16883
+ 4. When ALL work is TRULY complete, output:
16884
+
16885
+ \`\`\`
16886
+ ${SEAL_PATTERN}
16887
+ \`\`\`
16888
+
16889
+ **IMPORTANT**:
16890
+ - Do NOT seal until the mission is genuinely complete
16891
+ - Verify all todos are marked [x] before sealing
16892
+ - Run tests/builds if applicable before sealing
16893
+
16894
+ **Original Task**:
16895
+ ${state2.prompt}
16896
+ </mission_loop>`;
16897
+ }
16898
+
16899
+ // src/core/loop/mission-seal-handler.ts
16900
+ var COUNTDOWN_SECONDS2 = 3;
16901
+ var TOAST_DURATION_MS2 = 1500;
16902
+ var MIN_TIME_BETWEEN_CHECKS_MS = 3e3;
16903
+ var sessionStates2 = /* @__PURE__ */ new Map();
16904
+ function getState3(sessionID) {
16905
+ let state2 = sessionStates2.get(sessionID);
16906
+ if (!state2) {
16907
+ state2 = {};
16908
+ sessionStates2.set(sessionID, state2);
16909
+ }
16910
+ return state2;
16911
+ }
16912
+ function cancelCountdown2(sessionID) {
16913
+ const state2 = sessionStates2.get(sessionID);
16914
+ if (state2?.countdownTimer) {
16915
+ clearTimeout(state2.countdownTimer);
16916
+ state2.countdownTimer = void 0;
16917
+ }
16918
+ }
16919
+ function hasRunningBackgroundTasks2(parentSessionID) {
16920
+ try {
16921
+ const manager = ParallelAgentManager.getInstance();
16922
+ const tasks = manager.getTasksByParent(parentSessionID);
16923
+ return tasks.some((t) => t.status === "running");
16924
+ } catch {
16925
+ return false;
16926
+ }
16927
+ }
16928
+ async function showCountdownToast2(client, seconds, iteration, maxIterations) {
16929
+ try {
16930
+ const tuiClient2 = client;
16931
+ if (tuiClient2.tui?.showToast) {
16932
+ await tuiClient2.tui.showToast({
16933
+ body: {
16934
+ title: "\u{1F504} Mission Loop",
16935
+ message: `Continuing in ${seconds}s... (iteration ${iteration}/${maxIterations})`,
16936
+ variant: "warning",
16937
+ duration: TOAST_DURATION_MS2
16938
+ }
16939
+ });
16940
+ }
16941
+ } catch {
16942
+ }
16943
+ }
16944
+ async function showSealedToast(client, state2) {
16945
+ try {
16946
+ const tuiClient2 = client;
16947
+ if (tuiClient2.tui?.showToast) {
16948
+ await tuiClient2.tui.showToast({
16949
+ body: {
16950
+ title: "\u{1F396}\uFE0F Mission Sealed!",
16951
+ message: `Completed after ${state2.iteration} iteration(s)`,
16952
+ variant: "success",
16953
+ duration: 5e3
16954
+ }
16955
+ });
16956
+ }
16957
+ } catch {
16958
+ }
16959
+ }
16960
+ async function showMaxIterationsToast(client, state2) {
16961
+ try {
16962
+ const tuiClient2 = client;
16963
+ if (tuiClient2.tui?.showToast) {
16964
+ await tuiClient2.tui.showToast({
16965
+ body: {
16966
+ title: "\u26A0\uFE0F Mission Loop Stopped",
16967
+ message: `Max iterations (${state2.maxIterations}) reached`,
16968
+ variant: "warning",
16969
+ duration: 5e3
16970
+ }
16971
+ });
16972
+ }
16973
+ } catch {
16974
+ }
16975
+ }
16976
+ async function injectContinuation2(client, directory, sessionID, loopState) {
16977
+ const handlerState = getState3(sessionID);
16978
+ if (handlerState.isAborting) {
16979
+ log2("[mission-seal-handler] Skipped: user is aborting");
16980
+ return;
16981
+ }
16982
+ if (hasRunningBackgroundTasks2(sessionID)) {
16983
+ log2("[mission-seal-handler] Skipped: background tasks running");
16984
+ return;
16985
+ }
16986
+ if (isSessionRecovering(sessionID)) {
16987
+ log2("[mission-seal-handler] Skipped: session recovering");
16988
+ return;
16989
+ }
16990
+ const sealDetected = await detectSealInSession(client, sessionID);
16991
+ if (sealDetected) {
16992
+ log2("[mission-seal-handler] Seal detected before injection, completing");
16993
+ await handleSealDetected(client, directory, loopState);
16994
+ return;
16995
+ }
16996
+ const prompt = generateMissionContinuationPrompt(loopState);
16997
+ try {
16998
+ await client.session.prompt({
16999
+ path: { id: sessionID },
17000
+ body: {
17001
+ parts: [{ type: PART_TYPES.TEXT, text: prompt }]
17002
+ }
17003
+ });
17004
+ log2("[mission-seal-handler] Continuation injected", {
17005
+ sessionID,
17006
+ iteration: loopState.iteration
17007
+ });
17008
+ } catch (error45) {
17009
+ log2(`[mission-seal-handler] Failed to inject: ${error45}`);
17010
+ }
17011
+ }
17012
+ async function handleSealDetected(client, directory, loopState) {
17013
+ clearLoopState(directory);
17014
+ await showSealedToast(client, loopState);
17015
+ log2("[mission-seal-handler] Mission sealed!", {
17016
+ sessionID: loopState.sessionID,
17017
+ iterations: loopState.iteration
17018
+ });
17019
+ }
17020
+ async function handleMaxIterations(client, directory, loopState) {
17021
+ clearLoopState(directory);
17022
+ await showMaxIterationsToast(client, loopState);
17023
+ log2("[mission-seal-handler] Max iterations reached", {
17024
+ sessionID: loopState.sessionID,
17025
+ iterations: loopState.iteration,
17026
+ max: loopState.maxIterations
17027
+ });
17028
+ }
17029
+ async function handleMissionSealIdle(client, directory, sessionID, mainSessionID) {
17030
+ const handlerState = getState3(sessionID);
17031
+ const now = Date.now();
17032
+ if (handlerState.lastCheckTime && now - handlerState.lastCheckTime < MIN_TIME_BETWEEN_CHECKS_MS) {
17033
+ return;
17034
+ }
17035
+ handlerState.lastCheckTime = now;
17036
+ cancelCountdown2(sessionID);
17037
+ if (mainSessionID && sessionID !== mainSessionID) {
17038
+ return;
17039
+ }
17040
+ if (isSessionRecovering(sessionID)) {
17041
+ log2("[mission-seal-handler] Skipped: recovering");
17042
+ return;
17043
+ }
17044
+ if (hasRunningBackgroundTasks2(sessionID)) {
17045
+ log2("[mission-seal-handler] Skipped: background tasks");
17046
+ return;
17047
+ }
17048
+ const loopState = readLoopState(directory);
17049
+ if (!loopState || !loopState.active) {
17050
+ return;
17051
+ }
17052
+ if (loopState.sessionID !== sessionID) {
17053
+ return;
17054
+ }
17055
+ log2("[mission-seal-handler] Checking for seal", {
17056
+ sessionID,
17057
+ iteration: loopState.iteration
17058
+ });
17059
+ const sealDetected = await detectSealInSession(client, sessionID);
17060
+ if (sealDetected) {
17061
+ await handleSealDetected(client, directory, loopState);
17062
+ return;
17063
+ }
17064
+ if (loopState.iteration >= loopState.maxIterations) {
17065
+ await handleMaxIterations(client, directory, loopState);
17066
+ return;
17067
+ }
17068
+ const newState = incrementIteration(directory);
17069
+ if (!newState) {
17070
+ log2("[mission-seal-handler] Failed to increment iteration");
17071
+ return;
17072
+ }
17073
+ await showCountdownToast2(client, COUNTDOWN_SECONDS2, newState.iteration, newState.maxIterations);
17074
+ handlerState.countdownTimer = setTimeout(async () => {
17075
+ cancelCountdown2(sessionID);
17076
+ await injectContinuation2(client, directory, sessionID, newState);
17077
+ }, COUNTDOWN_SECONDS2 * 1e3);
17078
+ log2("[mission-seal-handler] Countdown started", {
17079
+ sessionID,
17080
+ iteration: newState.iteration,
17081
+ seconds: COUNTDOWN_SECONDS2
17082
+ });
17083
+ }
17084
+
16759
17085
  // src/core/progress/store.ts
16760
17086
  var progressHistory = /* @__PURE__ */ new Map();
16761
17087
  var sessionStartTimes = /* @__PURE__ */ new Map();
@@ -16868,7 +17194,7 @@ You are ONLY done when:
16868
17194
  - All todos are marked complete or cancelled
16869
17195
  - All features are implemented and tested
16870
17196
  - Final verification passes
16871
- Then output: \u2705 MISSION COMPLETE
17197
+ Then output: ${MISSION_SEAL.PATTERN}
16872
17198
  </completion_criteria>
16873
17199
  </auto_continue>`;
16874
17200
  var OrchestratorPlugin = async (input) => {
@@ -17046,12 +17372,27 @@ var OrchestratorPlugin = async (input) => {
17046
17372
  if (sessionID) {
17047
17373
  const isMainSession = sessions.has(sessionID);
17048
17374
  if (isMainSession) {
17049
- setTimeout(() => {
17375
+ setTimeout(async () => {
17050
17376
  const session = sessions.get(sessionID);
17051
17377
  if (session?.active) {
17052
- handleSessionIdle(client, sessionID, sessionID).catch((err) => {
17053
- log2("[index.ts] todo-continuation error", err);
17054
- });
17378
+ if (isLoopActive(directory, sessionID)) {
17379
+ await handleMissionSealIdle(
17380
+ client,
17381
+ directory,
17382
+ sessionID,
17383
+ sessionID
17384
+ ).catch((err) => {
17385
+ log2("[index.ts] mission-seal-handler error", err);
17386
+ });
17387
+ } else {
17388
+ await handleSessionIdle(
17389
+ client,
17390
+ sessionID,
17391
+ sessionID
17392
+ ).catch((err) => {
17393
+ log2("[index.ts] todo-continuation error", err);
17394
+ });
17395
+ }
17055
17396
  }
17056
17397
  }, 500);
17057
17398
  }
@@ -17210,7 +17551,7 @@ Anomaly count: ${stateSession.anomalyCount}
17210
17551
  // -----------------------------------------------------------------
17211
17552
  // assistant.done hook - runs when the LLM finishes responding
17212
17553
  // This is the heart of the "relentless loop" - we keep pushing it
17213
- // to continue until we see MISSION COMPLETE or hit the limit
17554
+ // to continue until we see <mission_seal>SEALED</mission_seal> or hit the limit
17214
17555
  // -----------------------------------------------------------------
17215
17556
  "assistant.done": async (assistantInput, assistantOutput) => {
17216
17557
  const sessionID = assistantInput.sessionID;
@@ -17250,10 +17591,12 @@ Anomaly count: ${stateSession.anomalyCount}
17250
17591
  if (stateSession && stateSession.anomalyCount > 0) {
17251
17592
  stateSession.anomalyCount = 0;
17252
17593
  }
17253
- if (textContent.includes(MISSION.COMPLETE) || textContent.includes(MISSION.COMPLETE_TEXT)) {
17594
+ if (detectSealInText(textContent)) {
17254
17595
  session.active = false;
17255
17596
  state.missionActive = false;
17256
- presets.missionComplete("Mission completed successfully");
17597
+ clearLoopState(directory);
17598
+ presets.missionComplete("\u{1F396}\uFE0F Mission Sealed - Explicit completion confirmed");
17599
+ log2("[index.ts] Mission sealed detected", { sessionID });
17257
17600
  clearSession(sessionID);
17258
17601
  sessions.delete(sessionID);
17259
17602
  state.sessions.delete(sessionID);
@@ -69,11 +69,38 @@ export declare const TOOL_NAMES: {
69
69
  readonly CALL_AGENT: "call_agent";
70
70
  readonly SLASHCOMMAND: "slashcommand";
71
71
  };
72
+ export declare const MISSION_SEAL: {
73
+ /** XML tag name for mission seal */
74
+ readonly TAG: "mission_seal";
75
+ /** Confirmation value inside tag */
76
+ readonly CONFIRMATION: "SEALED";
77
+ /** Full seal pattern: <mission_seal>SEALED</mission_seal> */
78
+ readonly PATTERN: "<mission_seal>SEALED</mission_seal>";
79
+ /** Default max loop iterations */
80
+ readonly DEFAULT_MAX_ITERATIONS: 20;
81
+ /** Default countdown seconds before continue */
82
+ readonly DEFAULT_COUNTDOWN_SECONDS: 3;
83
+ /** Loop state file name */
84
+ readonly STATE_FILE: "loop-state.json";
85
+ /** Stop command */
86
+ readonly STOP_COMMAND: "/stop";
87
+ /** Cancel command */
88
+ readonly CANCEL_COMMAND: "/cancel";
89
+ };
90
+ /** @deprecated Use MISSION_SEAL instead */
72
91
  export declare const MISSION: {
73
- /** Mission completion marker (with emoji) */
74
- readonly COMPLETE: "βœ… MISSION COMPLETE";
75
- /** Mission completion marker (text only) */
76
- readonly COMPLETE_TEXT: "MISSION COMPLETE";
92
+ /** XML tag name for mission seal */
93
+ readonly TAG: "mission_seal";
94
+ /** Confirmation value inside tag */
95
+ readonly CONFIRMATION: "SEALED";
96
+ /** Full seal pattern: <mission_seal>SEALED</mission_seal> */
97
+ readonly PATTERN: "<mission_seal>SEALED</mission_seal>";
98
+ /** Default max loop iterations */
99
+ readonly DEFAULT_MAX_ITERATIONS: 20;
100
+ /** Default countdown seconds before continue */
101
+ readonly DEFAULT_COUNTDOWN_SECONDS: 3;
102
+ /** Loop state file name */
103
+ readonly STATE_FILE: "loop-state.json";
77
104
  /** Stop command */
78
105
  readonly STOP_COMMAND: "/stop";
79
106
  /** Cancel command */
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": "0.7.1",
5
+ "version": "0.8.1",
6
6
  "author": "agnusdei1207",
7
7
  "license": "MIT",
8
8
  "repository": {