opencode-orchestrator 1.3.6 → 1.3.8

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.6`
9
+ **Version:** `1.3.8`
10
10
  <!-- VERSION:END -->
11
11
  </div>
12
12
 
@@ -31,14 +31,29 @@ Manual fallback: remove `"opencode-orchestrator"` or `["opencode-orchestrator",
31
31
 
32
32
  ## 2. Configure
33
33
 
34
- OpenCode supports plugin options as `["plugin-name", {...}]` tuples. Use that form for orchestrator-specific settings:
34
+ Tested compatibility:
35
+
36
+ 1. Node.js `24+`
37
+ 2. `@opencode-ai/plugin` `1.17.3`
38
+ 3. `@opencode-ai/sdk` `1.17.3`
39
+
40
+ OpenCode plugin options belong inside the `plugin` array as `["plugin-name", {...}]` tuples. Configure `agentConcurrency` and `missionLoop` there:
35
41
 
36
42
  ```jsonc
37
43
  {
38
44
  "$schema": "https://opencode.ai/config.json",
45
+ "model": "opencode/gpt-5.1-codex",
39
46
  "permission": {
40
47
  "question": "allow"
41
48
  },
49
+ "agent": {
50
+ "commander": {
51
+ "model": "opencode/gpt-5.1-codex"
52
+ },
53
+ "worker": {
54
+ "model": "anthropic/claude-opus-4-5-20251101"
55
+ }
56
+ },
42
57
  "plugin": [
43
58
  [
44
59
  "opencode-orchestrator",
@@ -60,23 +75,14 @@ OpenCode supports plugin options as `["plugin-name", {...}]` tuples. Use that fo
60
75
  }
61
76
  ```
62
77
 
63
- Optional model routing stays in normal OpenCode config. The plugin does not force a model:
78
+ Model selection follows normal OpenCode inheritance. The plugin does not force a model:
64
79
 
65
- ```jsonc
66
- {
67
- "model": "provider/model-id",
68
- "agent": {
69
- "commander": {
70
- "model": "provider/model-id"
71
- },
72
- "worker": {
73
- "model": "provider/stronger-model-id"
74
- }
75
- }
76
- }
77
- ```
80
+ 1. Commander uses the global `model` unless `agent.commander.model` is set.
81
+ 2. Planner, Worker, and Reviewer inherit the invoking primary agent model unless `agent.<name>.model` is set.
82
+ 3. Generated Commander, Planner, Worker, and Reviewer agents inherit global permissions.
83
+ 4. Same-name user agent config can still override model, temperature, and specific permission keys.
78
84
 
79
- Generated Commander, Planner, Worker, and Reviewer agents inherit global permissions. Same-name user agent config can still override specific model or permission keys.
85
+ Legacy top-level concurrency keys (`agentConcurrency`, `providerConcurrency`, `modelConcurrency`, `defaultConcurrency`) are still accepted for backward compatibility, but the plugin tuple is the preferred location.
80
86
 
81
87
  ## 3. Run
82
88
 
@@ -139,6 +145,8 @@ Useful references:
139
145
  3. OpenCode keybinds: https://opencode.ai/docs/keybinds/
140
146
  4. Project issues: https://github.com/agnusdei1207/opencode-orchestrator/issues
141
147
 
148
+ Contributions are welcome: open an issue or pull request when you find a bug, compatibility gap, or focused improvement.
149
+
142
150
  Config logs:
143
151
 
144
152
  | Platform | Path |
@@ -16,7 +16,7 @@ export interface MemoryEntry {
16
16
  level: MemoryLevel;
17
17
  content: string;
18
18
  timestamp: number;
19
- metadata?: Record<string, any>;
19
+ metadata?: Record<string, unknown>;
20
20
  importance: number;
21
21
  }
22
22
  export interface MemorySnapshot {
@@ -12,7 +12,7 @@ export declare class MemoryManager {
12
12
  /**
13
13
  * Add a memory entry
14
14
  */
15
- add(level: MemoryLevel, content: string, importance?: number, metadata?: Record<string, any>): string;
15
+ add(level: MemoryLevel, content: string, importance?: number, metadata?: Record<string, unknown>): string;
16
16
  /**
17
17
  * Retrieve memory for prompt construction
18
18
  */
@@ -1,8 +1,24 @@
1
+ interface ManagedSessionState {
2
+ active: boolean;
3
+ step: number;
4
+ timestamp: number;
5
+ startTime: number;
6
+ lastStepTime: number;
7
+ lastCompletedMessageID?: string;
8
+ lastUserMessageAt?: number;
9
+ lastAssistantCompletedAt?: number;
10
+ lastAbortAt?: number;
11
+ tokens: {
12
+ totalInput: number;
13
+ totalOutput: number;
14
+ estimatedCost: number;
15
+ };
16
+ }
1
17
  /**
2
18
  * Ensures a local session object exists in the plugin context map.
3
19
  * Now WITH disk-based rehydration for robustness.
4
20
  */
5
- export declare function ensureSessionInitialized(sessions: Map<string, any>, sessionID: string, directory?: string): any;
21
+ export declare function ensureSessionInitialized(sessions: Map<string, unknown>, sessionID: string, directory?: string): ManagedSessionState;
6
22
  /**
7
23
  * Activates the global mission state for a specific session.
8
24
  */
@@ -19,7 +35,7 @@ export declare function deactivateMissionState(sessionID: string): void;
19
35
  /**
20
36
  * Updates token usage and estimated cost for a session.
21
37
  */
22
- export declare function updateSessionTokens(sessions: Map<string, any>, sessionID: string, inputLen: number, outputLen: number): void;
38
+ export declare function updateSessionTokens(sessions: Map<string, unknown>, sessionID: string, inputLen: number, outputLen: number): void;
23
39
  /**
24
40
  * Anomaly Management
25
41
  */
@@ -29,3 +45,4 @@ export declare function resetAnomaly(sessionID: string): void;
29
45
  * Task Tracking
30
46
  */
31
47
  export declare function updateCurrentTask(sessionID: string, taskID: string): void;
48
+ export {};
@@ -17,4 +17,4 @@ export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, e
17
17
  /**
18
18
  * Debounce async function
19
19
  */
20
- export declare function debounceAsync<T extends (...args: any[]) => Promise<any>>(fn: T, delayMs: number): T;
20
+ export declare function debounceAsync<TArgs extends unknown[], TResult>(fn: (...args: TArgs) => Promise<TResult>, delayMs: number): (...args: TArgs) => Promise<TResult>;
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Detects if other major plugins are present and ensures orchestrator plays nicely.
5
5
  */
6
- import type { ChatMessageHook, AssistantDoneHook, HookContext } from "../types.js";
7
- export declare class ExternalPluginCompatHook implements ChatMessageHook, AssistantDoneHook {
6
+ import type { ChatMessageHook, ChatMessageResult, HookContext } from "../types.js";
7
+ export declare class ExternalPluginCompatHook implements ChatMessageHook {
8
8
  name: string;
9
- execute(ctx: HookContext, input: any): Promise<any>;
9
+ execute(ctx: HookContext, input: string): Promise<ChatMessageResult>;
10
10
  }
@@ -5,16 +5,8 @@
5
5
  * - Agent headers (e.g. [P] PLANNER Working...)
6
6
  * - Task ID tracking
7
7
  */
8
- import type { PostToolUseHook, HookContext } from "../types.js";
8
+ import type { PostToolUseHook, HookContext, PostToolResult, ToolInput, ToolOutput } from "../types.js";
9
9
  export declare class AgentUIHook implements PostToolUseHook {
10
10
  name: "AgentUI";
11
- execute(ctx: HookContext, tool: string, input: any, output: {
12
- title: string;
13
- output: string;
14
- metadata: any;
15
- }): Promise<{
16
- output?: undefined;
17
- } | {
18
- output: string;
19
- }>;
11
+ execute(ctx: HookContext, tool: string, input: ToolInput, output: ToolOutput): Promise<PostToolResult>;
20
12
  }
@@ -1,15 +1,12 @@
1
1
  /**
2
2
  * MemoryGate Hook - Intercepts tool usage and turn completion to maintain memory.
3
3
  */
4
- import { PostToolUseHook, AssistantDoneHook, HookContext } from "../types.js";
4
+ import type { PostToolUseHook, AssistantDoneHook, HookContext, HookResult, PostToolResult, ToolInput, ToolOutput } from "../types.js";
5
5
  export declare class MemoryGateHook implements PostToolUseHook, AssistantDoneHook {
6
6
  name: "MemoryGate";
7
7
  private memoryManager;
8
- execute(context: HookContext, toolOrText: string, input?: any, output?: {
9
- title: string;
10
- output: string;
11
- metadata: any;
12
- }): Promise<any>;
8
+ execute(context: HookContext, tool: string, input: ToolInput, output: ToolOutput): Promise<PostToolResult>;
9
+ execute(context: HookContext, finalText: string): Promise<HookResult>;
13
10
  /**
14
11
  * Post-Tool: Capture tool outputs to TASK memory
15
12
  */
@@ -1,14 +1,12 @@
1
1
  /**
2
2
  * MetricsHook - Collects telemetry during mission execution
3
3
  */
4
- import { PreToolUseHook, PostToolUseHook, AssistantDoneHook, HookContext } from "../types.js";
4
+ import type { PreToolUseHook, PostToolUseHook, AssistantDoneHook, HookContext, HookResult, PostToolResult, PreToolResult, ToolInput, ToolOutput } from "../types.js";
5
5
  export declare class MetricsHook implements PreToolUseHook, PostToolUseHook, AssistantDoneHook {
6
6
  name: "MetricsTelemetry";
7
7
  private startTimes;
8
8
  private metrics;
9
- execute(context: HookContext, toolOrText: string, input?: any, output?: {
10
- title: string;
11
- output: string;
12
- metadata: any;
13
- }): Promise<any>;
9
+ execute(context: HookContext, tool: string, input: ToolInput): Promise<PreToolResult>;
10
+ execute(context: HookContext, tool: string, input: ToolInput, output: ToolOutput): Promise<PostToolResult>;
11
+ execute(context: HookContext, finalText: string): Promise<HookResult>;
14
12
  }
@@ -4,10 +4,11 @@
4
4
  * Unifies resource tracking, monitoring, and active memory compaction.
5
5
  * Replaces separate ResourceUsageHook and MemoryCompactionHook.
6
6
  */
7
- import type { PostToolUseHook, AssistantDoneHook, HookContext } from "../types.js";
7
+ import type { PostToolUseHook, AssistantDoneHook, HookContext, HookResult, PostToolResult, ToolInput, ToolOutput } from "../types.js";
8
8
  export declare class ResourceControlHook implements PostToolUseHook, AssistantDoneHook {
9
9
  name: "ResourceControl";
10
10
  private lastCompactionTime;
11
- execute(ctx: HookContext, toolOrText: string, input?: any, output?: any): Promise<any>;
11
+ execute(ctx: HookContext, tool: string, input: ToolInput, output: ToolOutput): Promise<PostToolResult>;
12
+ execute(ctx: HookContext, finalText: string): Promise<HookResult>;
12
13
  private checkMemoryHealth;
13
14
  }
@@ -4,16 +4,8 @@
4
4
  * Scans tool outputs for potential secrets (API Keys, etc) and masks them.
5
5
  * This prevents leaking secrets into the context window or logs.
6
6
  */
7
- import type { PostToolUseHook, HookContext } from "../types.js";
7
+ import type { PostToolUseHook, HookContext, PostToolResult, ToolInput, ToolOutput } from "../types.js";
8
8
  export declare class SecretScannerHook implements PostToolUseHook {
9
9
  name: "SecretScanner";
10
- execute(ctx: HookContext, tool: string, input: any, output: {
11
- title: string;
12
- output: string;
13
- metadata: any;
14
- }): Promise<{
15
- output: string;
16
- } | {
17
- output?: undefined;
18
- }>;
10
+ execute(ctx: HookContext, tool: string, input: ToolInput, output: ToolOutput): Promise<PostToolResult>;
19
11
  }
@@ -5,17 +5,8 @@
5
5
  * - Planner: Cannot write code or run commands.
6
6
  * - Reviewer: Cannot write code (only review).
7
7
  */
8
- import type { PreToolUseHook, HookContext } from "../types.js";
8
+ import type { PreToolUseHook, HookContext, PreToolResult, ToolInput } from "../types.js";
9
9
  export declare class StrictRoleGuardHook implements PreToolUseHook {
10
10
  name: "StrictRoleGuard";
11
- execute(ctx: HookContext, tool: string, args: any): Promise<{
12
- action: "block";
13
- reason: "Fork bomb detected.";
14
- } | {
15
- action: "block";
16
- reason: "Root deletion blocked.";
17
- } | {
18
- action: "allow";
19
- reason?: undefined;
20
- }>;
11
+ execute(ctx: HookContext, tool: string, args: ToolInput): Promise<PreToolResult>;
21
12
  }
@@ -4,8 +4,8 @@
4
4
  * Tracks user interaction (chat messages) to pause auto-continuation/loops
5
5
  * and update activity timestamps.
6
6
  */
7
- import type { ChatMessageHook, HookContext } from "../types.js";
7
+ import type { ChatMessageHook, ChatMessageResult, HookContext } from "../types.js";
8
8
  export declare class UserActivityHook implements ChatMessageHook {
9
9
  name: "UserActivity";
10
- execute(ctx: HookContext, message: string): Promise<any>;
10
+ execute(ctx: HookContext, message: string): Promise<ChatMessageResult>;
11
11
  }
@@ -6,10 +6,11 @@
6
6
  * - Auto-continuation injection (Loop)
7
7
  * - User cancellation detection
8
8
  */
9
- import type { AssistantDoneHook, ChatMessageHook, HookContext } from "../types.js";
9
+ import type { AssistantDoneHook, ChatMessageHook, ChatMessageResult, HookContext, HookResult } from "../types.js";
10
10
  export declare class MissionControlHook implements AssistantDoneHook, ChatMessageHook {
11
11
  name: "MissionLoop";
12
- execute(ctx: HookContext, text: string): Promise<any>;
12
+ execute(ctx: HookContext, text: string): Promise<ChatMessageResult>;
13
+ execute(ctx: HookContext, text: string): Promise<HookResult>;
13
14
  private handleChatCommand;
14
15
  private cancelMission;
15
16
  private handleMissionProgress;
@@ -2,14 +2,11 @@
2
2
  * Sanity Check Hook
3
3
  * Implements output anomaly detection.
4
4
  */
5
- import type { PostToolUseHook, AssistantDoneHook, HookContext } from "../types.js";
5
+ import type { PostToolUseHook, AssistantDoneHook, HookContext, HookResult, PostToolResult, ToolInput, ToolOutput } from "../types.js";
6
6
  export declare class SanityCheckHook implements PostToolUseHook, AssistantDoneHook {
7
7
  name: "SanityCheck";
8
- execute(ctx: HookContext, toolOrText: string, input?: any, output?: {
9
- title: string;
10
- output: string;
11
- metadata: any;
12
- }): Promise<any>;
8
+ execute(ctx: HookContext, tool: string, input: ToolInput, output: ToolOutput): Promise<PostToolResult>;
9
+ execute(ctx: HookContext, finalText: string): Promise<HookResult>;
13
10
  private checkToolOutput;
14
11
  private checkFinalText;
15
12
  }
@@ -2,8 +2,7 @@
2
2
  * Hook Registry
3
3
  * Manages registration and execution of hooks with priority and dependency support.
4
4
  */
5
- import type { PreToolUseHook, PostToolUseHook, ChatMessageHook, AssistantDoneHook, HookContext, HookResult, HookMetadata } from "./types.js";
6
- import { HOOK_ACTIONS } from "./constants.js";
5
+ import type { PreToolUseHook, PostToolUseHook, ChatMessageHook, AssistantDoneHook, HookContext, HookResult, HookMetadata, ChatMessageResult, PreToolResult, ToolInput, ToolOutput } from "./types.js";
7
6
  export declare class HookRegistry {
8
7
  private static instance;
9
8
  private preToolHooks;
@@ -19,19 +18,8 @@ export declare class HookRegistry {
19
18
  private prepareMetadata;
20
19
  private sortHooks;
21
20
  private topologicalSort;
22
- executePreTool(ctx: HookContext, tool: string, args: Record<string, unknown>): Promise<{
23
- action: typeof HOOK_ACTIONS.ALLOW | typeof HOOK_ACTIONS.BLOCK | typeof HOOK_ACTIONS.MODIFY;
24
- modifiedArgs?: Record<string, unknown>;
25
- reason?: string;
26
- }>;
27
- executePostTool(ctx: HookContext, tool: string, input: Record<string, unknown>, output: {
28
- title: string;
29
- output: string;
30
- metadata: Record<string, unknown>;
31
- }): Promise<void>;
32
- executeChat(ctx: HookContext, message: string): Promise<{
33
- action: typeof HOOK_ACTIONS.PROCESS | typeof HOOK_ACTIONS.INTERCEPT;
34
- modifiedMessage?: string;
35
- }>;
21
+ executePreTool(ctx: HookContext, tool: string, args: ToolInput): Promise<PreToolResult>;
22
+ executePostTool(ctx: HookContext, tool: string, input: ToolInput, output: ToolOutput): Promise<void>;
23
+ executeChat(ctx: HookContext, message: string): Promise<ChatMessageResult>;
36
24
  executeDone(ctx: HookContext, finalText: string): Promise<HookResult>;
37
25
  }
@@ -25,17 +25,31 @@ export type HookResult = {
25
25
  action: typeof HOOK_ACTIONS.INJECT;
26
26
  prompts: string[];
27
27
  };
28
+ export type ToolInput = Record<string, unknown>;
29
+ export interface ToolOutput {
30
+ title: string;
31
+ output: string;
32
+ metadata: Record<string, unknown>;
33
+ }
34
+ export type PreToolResult = {
35
+ action: typeof HOOK_ACTIONS.ALLOW | typeof HOOK_ACTIONS.BLOCK | typeof HOOK_ACTIONS.MODIFY;
36
+ modifiedArgs?: ToolInput;
37
+ reason?: string;
38
+ };
39
+ export type PostToolResult = {
40
+ output?: string;
41
+ };
42
+ export type ChatMessageResult = {
43
+ action: typeof HOOK_ACTIONS.PROCESS | typeof HOOK_ACTIONS.INTERCEPT;
44
+ modifiedMessage?: string;
45
+ };
28
46
  /**
29
47
  * Pre-Tool Execution Hook
30
48
  * Runs before a tool is executed. Can block execution or modify arguments.
31
49
  */
32
50
  export interface PreToolUseHook {
33
51
  name: string;
34
- execute(context: HookContext, tool: string, args: Record<string, unknown>): Promise<{
35
- action: typeof HOOK_ACTIONS.ALLOW | typeof HOOK_ACTIONS.BLOCK | typeof HOOK_ACTIONS.MODIFY;
36
- modifiedArgs?: Record<string, unknown>;
37
- reason?: string;
38
- }>;
52
+ execute(context: HookContext, tool: string, args: ToolInput): Promise<PreToolResult>;
39
53
  }
40
54
  /**
41
55
  * Post-Tool Execution Hook
@@ -43,13 +57,7 @@ export interface PreToolUseHook {
43
57
  */
44
58
  export interface PostToolUseHook {
45
59
  name: string;
46
- execute(context: HookContext, tool: string, input: Record<string, unknown>, output: {
47
- title: string;
48
- output: string;
49
- metadata: Record<string, unknown>;
50
- }): Promise<{
51
- output?: string;
52
- }>;
60
+ execute(context: HookContext, tool: string, input: ToolInput, output: ToolOutput): Promise<PostToolResult>;
53
61
  }
54
62
  /**
55
63
  * Chat Message Hook
@@ -57,10 +65,7 @@ export interface PostToolUseHook {
57
65
  */
58
66
  export interface ChatMessageHook {
59
67
  name: string;
60
- execute(context: HookContext, message: string): Promise<{
61
- action: typeof HOOK_ACTIONS.PROCESS | typeof HOOK_ACTIONS.INTERCEPT;
62
- modifiedMessage?: string;
63
- }>;
68
+ execute(context: HookContext, message: string): Promise<ChatMessageResult>;
64
69
  }
65
70
  /**
66
71
  * Assistant Done Hook
package/dist/index.js CHANGED
@@ -1037,7 +1037,7 @@ function show(options) {
1037
1037
  } catch {
1038
1038
  }
1039
1039
  }, timeoutMs);
1040
- const promise3 = client.tui.showToast?.({
1040
+ const payload = {
1041
1041
  body: {
1042
1042
  title: toast.title,
1043
1043
  message: toast.message,
@@ -1045,15 +1045,11 @@ function show(options) {
1045
1045
  duration: toast.duration
1046
1046
  },
1047
1047
  signal: ac.signal
1048
+ };
1049
+ Promise.resolve(client.tui.showToast(payload)).finally(() => {
1050
+ clearTimeout(timer);
1051
+ }).catch(() => {
1048
1052
  });
1049
- if (promise3 && typeof promise3.then === "function") {
1050
- Promise.resolve(promise3).finally(() => {
1051
- if (timer) {
1052
- clearTimeout(timer);
1053
- }
1054
- }).catch(() => {
1055
- });
1056
- }
1057
1053
  } catch {
1058
1054
  }
1059
1055
  }
@@ -36891,30 +36887,26 @@ function normalizeContinuationContext(state2, input) {
36891
36887
  // src/core/orchestrator/session-manager.ts
36892
36888
  function ensureSessionInitialized(sessions, sessionID, directory) {
36893
36889
  if (!sessions.has(sessionID)) {
36894
- const now = Date.now();
36895
- const newSession = {
36896
- active: true,
36897
- step: 0,
36898
- timestamp: now,
36899
- startTime: now,
36900
- lastStepTime: now,
36901
- lastCompletedMessageID: void 0,
36902
- lastUserMessageAt: void 0,
36903
- lastAssistantCompletedAt: void 0,
36904
- lastAbortAt: void 0,
36905
- tokens: { totalInput: 0, totalOutput: 0, estimatedCost: 0 }
36906
- };
36890
+ const newSession2 = createManagedSession();
36907
36891
  if (directory) {
36908
36892
  const diskState = readLoopState(directory);
36909
36893
  if (diskState && diskState.sessionID === sessionID) {
36910
- newSession.step = diskState.iteration;
36911
- newSession.startTime = new Date(diskState.startedAt).getTime();
36894
+ newSession2.step = diskState.iteration;
36895
+ newSession2.startTime = new Date(diskState.startedAt).getTime();
36912
36896
  log(`[SessionManager] Rehydrated session from disk: ${sessionID}`);
36913
36897
  }
36914
36898
  }
36915
- sessions.set(sessionID, newSession);
36899
+ sessions.set(sessionID, newSession2);
36900
+ return newSession2;
36916
36901
  }
36917
- return sessions.get(sessionID);
36902
+ const session = sessions.get(sessionID);
36903
+ if (isManagedSessionState(session)) return session;
36904
+ if (isRecord(session)) {
36905
+ return normalizeManagedSession(session);
36906
+ }
36907
+ const newSession = createManagedSession();
36908
+ sessions.set(sessionID, newSession);
36909
+ return newSession;
36918
36910
  }
36919
36911
  function ensureGlobalState(sessionID, directory) {
36920
36912
  let stateSession = state.sessions.get(sessionID);
@@ -36978,6 +36970,43 @@ function updateSessionTokens(sessions, sessionID, inputLen, outputLen) {
36978
36970
  const cost = session.tokens.totalInput / 1e3 * COST_PER_1K_INPUT + session.tokens.totalOutput / 1e3 * COST_PER_1K_OUTPUT;
36979
36971
  session.tokens.estimatedCost = Number(cost.toFixed(4));
36980
36972
  }
36973
+ function createManagedSession() {
36974
+ const now = Date.now();
36975
+ return {
36976
+ active: true,
36977
+ step: 0,
36978
+ timestamp: now,
36979
+ startTime: now,
36980
+ lastStepTime: now,
36981
+ lastCompletedMessageID: void 0,
36982
+ lastUserMessageAt: void 0,
36983
+ lastAssistantCompletedAt: void 0,
36984
+ lastAbortAt: void 0,
36985
+ tokens: { totalInput: 0, totalOutput: 0, estimatedCost: 0 }
36986
+ };
36987
+ }
36988
+ function normalizeManagedSession(session) {
36989
+ const now = Date.now();
36990
+ if (typeof session.active !== "boolean") session.active = true;
36991
+ if (typeof session.step !== "number") session.step = 0;
36992
+ if (typeof session.timestamp !== "number") session.timestamp = now;
36993
+ if (typeof session.startTime !== "number") session.startTime = now;
36994
+ if (typeof session.lastStepTime !== "number") session.lastStepTime = now;
36995
+ if (!isTokenUsage(session.tokens)) {
36996
+ session.tokens = { totalInput: 0, totalOutput: 0, estimatedCost: 0 };
36997
+ }
36998
+ return session;
36999
+ }
37000
+ function isManagedSessionState(value) {
37001
+ return isRecord(value) && typeof value.active === "boolean" && typeof value.step === "number" && typeof value.timestamp === "number" && typeof value.startTime === "number" && typeof value.lastStepTime === "number" && isTokenUsage(value.tokens);
37002
+ }
37003
+ function isRecord(value) {
37004
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37005
+ }
37006
+ function isTokenUsage(value) {
37007
+ if (!isRecord(value)) return false;
37008
+ return typeof value.totalInput === "number" && typeof value.totalOutput === "number" && typeof value.estimatedCost === "number";
37009
+ }
36981
37010
  function recordAnomaly(sessionID) {
36982
37011
  const session = ensureGlobalState(sessionID);
36983
37012
  session.anomalyCount = (session.anomalyCount || 0) + 1;
@@ -37000,6 +37029,7 @@ var SanityCheckHook = class {
37000
37029
  if (toolOrText === TOOL_NAMES.CALL_AGENT) {
37001
37030
  return this.checkToolOutput(ctx, input, output);
37002
37031
  }
37032
+ return {};
37003
37033
  } else {
37004
37034
  return this.checkFinalText(ctx, toolOrText);
37005
37035
  }
@@ -37008,7 +37038,7 @@ var SanityCheckHook = class {
37008
37038
  const sanityResult = checkOutputSanity(toolOutput.output);
37009
37039
  if (!sanityResult.isHealthy) {
37010
37040
  const count = recordAnomaly(ctx.sessionID);
37011
- const agentName = toolInput?.agent || "unknown";
37041
+ const agentName = typeof toolInput?.agent === "string" ? toolInput.agent : "unknown";
37012
37042
  const recoveryText = count >= 2 ? ESCALATION_PROMPT : RECOVERY_PROMPT;
37013
37043
  const errorMsg = MISSION_MESSAGES.ANOMALY_DETECTED_TITLE(agentName.toUpperCase()) + "\n\n" + MISSION_MESSAGES.ANOMALY_DETECTED_BODY(sanityResult.reason || "Unknown anomaly", count, recoveryText);
37014
37044
  return { output: errorMsg };
@@ -37756,7 +37786,7 @@ var MissionControlHook = class {
37756
37786
  deactivateMissionState(sessionID);
37757
37787
  clearSession2(sessionID);
37758
37788
  const session = sessions.get(sessionID);
37759
- if (isRecord(session)) {
37789
+ if (isRecord2(session)) {
37760
37790
  session.active = false;
37761
37791
  }
37762
37792
  }
@@ -37872,11 +37902,11 @@ var MissionControlHook = class {
37872
37902
  }
37873
37903
  }
37874
37904
  };
37875
- function isRecord(value) {
37905
+ function isRecord2(value) {
37876
37906
  return typeof value === "object" && value !== null && !Array.isArray(value);
37877
37907
  }
37878
37908
  function isMissionSessionState(value) {
37879
- return isRecord(value) && typeof value.step === "number" && typeof value.startTime === "number" && typeof value.lastStepTime === "number";
37909
+ return isRecord2(value) && typeof value.step === "number" && typeof value.startTime === "number" && typeof value.lastStepTime === "number";
37880
37910
  }
37881
37911
 
37882
37912
  // src/hooks/custom/strict-role-guard.ts
@@ -37909,7 +37939,7 @@ var StrictRoleGuardHook = class {
37909
37939
  name = HOOK_NAMES2.STRICT_ROLE_GUARD;
37910
37940
  async execute(ctx, tool2, args) {
37911
37941
  if (tool2 === TOOL_NAMES.RUN_COMMAND || tool2 === TOOL_NAMES.RUN_BACKGROUND) {
37912
- const cmd = args?.command;
37942
+ const cmd = typeof args.command === "string" ? args.command : void 0;
37913
37943
  if (cmd) {
37914
37944
  if (cmd.includes(SECURITY_PATTERNS.FORK_BOMB)) {
37915
37945
  return { action: HOOK_ACTIONS.BLOCK, reason: MISSION_MESSAGES.BLOCK_REASON_FORK_BOMB };
@@ -37948,14 +37978,15 @@ var AgentUIHook = class {
37948
37978
  name = HOOK_NAMES2.AGENT_UI;
37949
37979
  async execute(ctx, tool2, input, output) {
37950
37980
  if (tool2 !== TOOL_NAMES.CALL_AGENT) return {};
37951
- if (input?.task) {
37952
- const taskIdMatch = input.task.match(UI_PATTERNS.TASK_ID);
37981
+ const task = typeof input.task === "string" ? input.task : void 0;
37982
+ if (task) {
37983
+ const taskIdMatch = task.match(UI_PATTERNS.TASK_ID);
37953
37984
  if (taskIdMatch) {
37954
37985
  updateCurrentTask(ctx.sessionID, taskIdMatch[1].toUpperCase());
37955
37986
  }
37956
37987
  }
37957
- if (input?.agent) {
37958
- const agentName = input.agent;
37988
+ const agentName = typeof input.agent === "string" ? input.agent : void 0;
37989
+ if (agentName) {
37959
37990
  const indicator = agentName[0].toUpperCase();
37960
37991
  const header = MISSION_MESSAGES.AGENT_HEADER_FORMAT(indicator, agentName.toUpperCase());
37961
37992
  if (!output.output.startsWith("[" + indicator + "]")) {
@@ -38072,25 +38103,21 @@ var ResourceControlHook = class {
38072
38103
  name = HOOK_NAMES2.RESOURCE_CONTROL;
38073
38104
  lastCompactionTime = /* @__PURE__ */ new Map();
38074
38105
  async execute(ctx, toolOrText, input, output) {
38075
- let inputLen = 0;
38076
- let outputLen = 0;
38077
- if (input) {
38078
- const inputStr = typeof input === "string" ? input : JSON.stringify(input);
38079
- inputLen = inputStr.length;
38080
- }
38081
- if (output) {
38082
- const outputStr = typeof output === "string" ? output : JSON.stringify(output);
38083
- outputLen = outputStr.length;
38084
- }
38085
- if (arguments.length === 2 && typeof toolOrText === "string") {
38106
+ const isAssistantDone = input === void 0 && output === void 0;
38107
+ const inputLen = isAssistantDone ? 0 : getSerializedLength(input);
38108
+ let outputLen = getSerializedLength(output);
38109
+ if (isAssistantDone) {
38086
38110
  outputLen = toolOrText.length;
38087
38111
  }
38088
38112
  updateSessionTokens(ctx.sessions, ctx.sessionID, inputLen, outputLen);
38113
+ if (!isAssistantDone) {
38114
+ return {};
38115
+ }
38089
38116
  const session = ctx.sessions.get(ctx.sessionID);
38090
38117
  return this.checkMemoryHealth(ctx, session);
38091
38118
  }
38092
38119
  async checkMemoryHealth(ctx, session) {
38093
- if (!session?.tokens) return { action: HOOK_ACTIONS.CONTINUE };
38120
+ if (!hasTokenUsage(session)) return { action: HOOK_ACTIONS.CONTINUE };
38094
38121
  const totalUsed = session.tokens.totalInput + session.tokens.totalOutput;
38095
38122
  const maxTokens = CONTEXT_MONITOR_CONFIG.DEFAULT_MAX_TOKENS;
38096
38123
  const usage = calculateUsage(totalUsed, maxTokens);
@@ -38112,6 +38139,22 @@ var ResourceControlHook = class {
38112
38139
  };
38113
38140
  }
38114
38141
  };
38142
+ function getSerializedLength(value) {
38143
+ if (value === void 0 || value === null) return 0;
38144
+ if (typeof value === "string") return value.length;
38145
+ try {
38146
+ return JSON.stringify(value)?.length ?? 0;
38147
+ } catch {
38148
+ return String(value).length;
38149
+ }
38150
+ }
38151
+ function hasTokenUsage(session) {
38152
+ if (typeof session !== "object" || session === null || Array.isArray(session)) return false;
38153
+ const tokens = session.tokens;
38154
+ if (typeof tokens !== "object" || tokens === null || Array.isArray(tokens)) return false;
38155
+ const candidate = tokens;
38156
+ return typeof candidate.totalInput === "number" && typeof candidate.totalOutput === "number";
38157
+ }
38115
38158
 
38116
38159
  // src/core/loop/todo-continuation.ts
38117
38160
  init_shared();
@@ -41183,11 +41226,11 @@ var CONCURRENCY_KEYS = [
41183
41226
  "providerConcurrency",
41184
41227
  "modelConcurrency"
41185
41228
  ];
41186
- function isRecord2(value) {
41229
+ function isRecord3(value) {
41187
41230
  return typeof value === "object" && value !== null && !Array.isArray(value);
41188
41231
  }
41189
41232
  function readLimitMap(value) {
41190
- if (!isRecord2(value)) return void 0;
41233
+ if (!isRecord3(value)) return void 0;
41191
41234
  const result = {};
41192
41235
  for (const [key, limit] of Object.entries(value)) {
41193
41236
  if (typeof limit === "number" && Number.isFinite(limit) && limit >= 0) {
@@ -41197,7 +41240,7 @@ function readLimitMap(value) {
41197
41240
  return Object.keys(result).length > 0 ? result : void 0;
41198
41241
  }
41199
41242
  function extractConcurrencyConfig(source) {
41200
- if (!isRecord2(source)) return {};
41243
+ if (!isRecord3(source)) return {};
41201
41244
  const config3 = {};
41202
41245
  if (typeof source.defaultConcurrency === "number" && source.defaultConcurrency >= 0) {
41203
41246
  config3.defaultConcurrency = source.defaultConcurrency;
@@ -41211,7 +41254,7 @@ function extractConcurrencyConfig(source) {
41211
41254
  return config3;
41212
41255
  }
41213
41256
  function hasConcurrencyConfig(source) {
41214
- if (!isRecord2(source)) return false;
41257
+ if (!isRecord3(source)) return false;
41215
41258
  return CONCURRENCY_KEYS.some((key) => source[key] !== void 0);
41216
41259
  }
41217
41260
  function mergeConcurrencyConfig(base, override) {
@@ -41235,14 +41278,14 @@ function mergeConcurrencyConfig(base, override) {
41235
41278
 
41236
41279
  // src/core/config/plugin-options.ts
41237
41280
  function parseOrchestratorPluginOptions(options) {
41238
- const source = isRecord3(options) ? options : {};
41281
+ const source = isRecord4(options) ? options : {};
41239
41282
  return {
41240
41283
  concurrency: extractConcurrencyConfig(source),
41241
41284
  missionLoop: readMissionLoopOptions(source.missionLoop)
41242
41285
  };
41243
41286
  }
41244
41287
  function readMissionLoopOptions(value) {
41245
- if (!isRecord3(value)) return DEFAULT_MISSION_RUNTIME_OPTIONS;
41288
+ if (!isRecord4(value)) return DEFAULT_MISSION_RUNTIME_OPTIONS;
41246
41289
  return {
41247
41290
  ledger: readBoolean(value.ledger, DEFAULT_MISSION_RUNTIME_OPTIONS.ledger),
41248
41291
  markdownMemory: readBoolean(value.markdownMemory, DEFAULT_MISSION_RUNTIME_OPTIONS.markdownMemory),
@@ -41258,7 +41301,7 @@ function readBoolean(value, fallback) {
41258
41301
  function readPositiveInteger(value, fallback) {
41259
41302
  return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : fallback;
41260
41303
  }
41261
- function isRecord3(value) {
41304
+ function isRecord4(value) {
41262
41305
  return typeof value === "object" && value !== null && !Array.isArray(value);
41263
41306
  }
41264
41307
 
@@ -41380,7 +41423,7 @@ This plugin runs in "Claude Code Compatibility Mode".
41380
41423
  }
41381
41424
 
41382
41425
  // src/plugin-handlers/config-handler.ts
41383
- function isRecord4(value) {
41426
+ function isRecord5(value) {
41384
41427
  return typeof value === "object" && value !== null && !Array.isArray(value);
41385
41428
  }
41386
41429
  function isPermissionAction(value) {
@@ -41390,10 +41433,10 @@ function mergePermission(globalPermission, agentPermission) {
41390
41433
  if (agentPermission === void 0) {
41391
41434
  return globalPermission;
41392
41435
  }
41393
- if (isRecord4(globalPermission) && isRecord4(agentPermission)) {
41436
+ if (isRecord5(globalPermission) && isRecord5(agentPermission)) {
41394
41437
  return { ...globalPermission, ...agentPermission };
41395
41438
  }
41396
- if (isPermissionAction(globalPermission) && isRecord4(agentPermission)) {
41439
+ if (isPermissionAction(globalPermission) && isRecord5(agentPermission)) {
41397
41440
  return { "*": globalPermission, ...agentPermission };
41398
41441
  }
41399
41442
  return agentPermission;
@@ -43081,7 +43124,7 @@ Use \`delegate_task\` with background=true for parallel work.
43081
43124
  // src/index.ts
43082
43125
  var require2 = createRequire(import.meta.url);
43083
43126
  var { version: PLUGIN_VERSION } = require2("../package.json");
43084
- function isRecord5(value) {
43127
+ function isRecord6(value) {
43085
43128
  return typeof value === "object" && value !== null && !Array.isArray(value);
43086
43129
  }
43087
43130
  function readStringField(source, key) {
@@ -43089,11 +43132,11 @@ function readStringField(source, key) {
43089
43132
  return typeof value === "string" ? value : void 0;
43090
43133
  }
43091
43134
  function readCreatedSessionID(properties) {
43092
- if (!isRecord5(properties)) return void 0;
43135
+ if (!isRecord6(properties)) return void 0;
43093
43136
  const directID = readStringField(properties, "sessionID") ?? readStringField(properties, "id");
43094
43137
  if (directID) return directID;
43095
43138
  const info = properties.info;
43096
- return isRecord5(info) ? readStringField(info, "sessionID") : void 0;
43139
+ return isRecord6(info) ? readStringField(info, "sessionID") : void 0;
43097
43140
  }
43098
43141
  var OrchestratorPlugin = async (input, options) => {
43099
43142
  const { directory, client } = input;
@@ -1,13 +1,13 @@
1
1
  export interface CachedDiagnostics {
2
- diagnostics: any;
2
+ diagnostics: string;
3
3
  timestamp: number;
4
4
  filesMtime: number;
5
5
  }
6
6
  export declare class DiagnosticsCache {
7
7
  private cache;
8
8
  private defaultTTL;
9
- get(directory: string, file?: string): Promise<any | null>;
10
- set(directory: string, file: string | undefined, diagnostics: any): Promise<void>;
9
+ get(directory: string, file?: string): Promise<string | null>;
10
+ set(directory: string, file: string | undefined, diagnostics: string): Promise<void>;
11
11
  private getCacheKey;
12
12
  private getFilesMtime;
13
13
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
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.6",
5
+ "version": "1.3.8",
6
6
  "author": "agnusdei1207",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -74,8 +74,8 @@
74
74
  "node": ">=24"
75
75
  },
76
76
  "dependencies": {
77
- "@opencode-ai/plugin": "^1.17.1",
78
- "@opencode-ai/sdk": "^1.17.1",
77
+ "@opencode-ai/plugin": "1.17.3",
78
+ "@opencode-ai/sdk": "1.17.3",
79
79
  "jsonc-parser": "^3.3.1",
80
80
  "zod": "^4.3.6"
81
81
  },