oh-my-opencode-slim 2.1.0 → 2.2.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.
Files changed (61) hide show
  1. package/README.ja-JP.md +31 -30
  2. package/README.ko-KR.md +31 -30
  3. package/README.md +57 -33
  4. package/README.zh-CN.md +29 -28
  5. package/dist/agents/index.d.ts +0 -4
  6. package/dist/cli/index.js +90 -16
  7. package/dist/cli/providers.d.ts +7 -7
  8. package/dist/companion/manager.d.ts +3 -0
  9. package/dist/config/constants.d.ts +12 -1
  10. package/dist/config/council-schema.d.ts +37 -12
  11. package/dist/config/schema.d.ts +691 -6
  12. package/dist/config/strip-orchestrator-model.d.ts +9 -0
  13. package/dist/council/council-manager.d.ts +7 -3
  14. package/dist/hooks/command-hook-utils.d.ts +5 -0
  15. package/dist/hooks/foreground-fallback/index.d.ts +53 -7
  16. package/dist/hooks/image-hook.d.ts +1 -0
  17. package/dist/hooks/index.d.ts +2 -1
  18. package/dist/hooks/phase-reminder/index.d.ts +3 -1
  19. package/dist/hooks/post-file-tool-nudge/index.d.ts +15 -9
  20. package/dist/hooks/session-lifecycle.d.ts +11 -0
  21. package/dist/hooks/task-session-manager/index.d.ts +14 -2
  22. package/dist/index.js +2804 -801
  23. package/dist/interview/document.d.ts +2 -1
  24. package/dist/interview/service.d.ts +2 -0
  25. package/dist/interview/ui.d.ts +0 -1
  26. package/dist/multiplexer/cmux/close-policy.d.ts +20 -0
  27. package/dist/multiplexer/cmux/index.d.ts +102 -0
  28. package/dist/multiplexer/cmux/session-lifecycle.d.ts +92 -0
  29. package/dist/multiplexer/cmux/session-state.d.ts +45 -0
  30. package/dist/multiplexer/factory.d.ts +0 -9
  31. package/dist/multiplexer/herdr/index.d.ts +7 -3
  32. package/dist/multiplexer/index.d.ts +3 -1
  33. package/dist/multiplexer/session-manager.d.ts +14 -6
  34. package/dist/multiplexer/shared.d.ts +31 -0
  35. package/dist/multiplexer/tmux/index.d.ts +0 -1
  36. package/dist/multiplexer/types.d.ts +5 -2
  37. package/dist/multiplexer/zellij/index.d.ts +0 -1
  38. package/dist/tools/cancel-task.d.ts +2 -2
  39. package/dist/tools/smartfetch/utils.d.ts +3 -1
  40. package/dist/tui-state.d.ts +5 -5
  41. package/dist/tui.d.ts +1 -0
  42. package/dist/tui.js +130 -29
  43. package/dist/utils/background-job-board.d.ts +5 -1
  44. package/dist/utils/background-job-coordinator.d.ts +72 -0
  45. package/dist/utils/background-job-store.d.ts +46 -0
  46. package/dist/utils/councillor-models.d.ts +20 -0
  47. package/dist/utils/escape-html.d.ts +1 -0
  48. package/dist/utils/frontmatter.d.ts +6 -0
  49. package/dist/utils/index.d.ts +3 -1
  50. package/dist/utils/internal-initiator.d.ts +6 -1
  51. package/dist/utils/logger.d.ts +0 -2
  52. package/dist/utils/session.d.ts +1 -1
  53. package/oh-my-opencode-slim.schema.json +728 -1
  54. package/package.json +1 -1
  55. package/src/skills/clonedeps/SKILL.md +32 -29
  56. package/src/skills/codemap.md +7 -3
  57. package/src/skills/deepwork/SKILL.md +17 -6
  58. package/src/skills/oh-my-opencode-slim/SKILL.md +3 -3
  59. package/src/skills/release-smoke-test/SKILL.md +2 -2
  60. package/src/skills/verification-planning/SKILL.md +103 -0
  61. package/src/skills/worktrees/SKILL.md +14 -7
@@ -0,0 +1,9 @@
1
+ import type { PluginConfig, Preset } from './schema';
2
+ export declare function stripOrchestratorModel(agents: Record<string, unknown>, enabled: boolean | undefined, preset: Preset | undefined): void;
3
+ export declare function applyOrchestratorModelConfig(input: {
4
+ agents: Record<string, unknown>;
5
+ enabled: boolean | undefined;
6
+ presets: PluginConfig['presets'];
7
+ configPreset: string | undefined;
8
+ runtimePreset: string | null;
9
+ }): void;
@@ -41,9 +41,13 @@ export declare class CouncilManager {
41
41
  private runAgentSession;
42
42
  private runCouncillors;
43
43
  /**
44
- * Run a single councillor with retry logic for empty responses.
45
- * Only retries on "Empty response from provider" errors - timeouts
46
- * and other failures are returned immediately.
44
+ * Run a single councillor across its configured model chain.
45
+ *
46
+ * For each model in the chain, empty responses are retried up to
47
+ * `maxRetries` times (providers that silently rate-limit). Any other
48
+ * failure or timeout advances to the next model in the chain. The
49
+ * councillor only fails once every model has been exhausted; the reported
50
+ * `model` and `error` reflect the last model tried.
47
51
  */
48
52
  private runCouncillorWithRetry;
49
53
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Register a command hook in the OpenCode config if it doesn't already exist.
3
+ * Returns true if the command was registered, false if it already existed.
4
+ */
5
+ export declare function registerCommandHook(opencodeConfig: Record<string, unknown>, commandName: string, template: string, description: string): boolean;
@@ -2,9 +2,12 @@
2
2
  * Runtime model fallback for foreground (interactive) agent sessions.
3
3
  *
4
4
  * When OpenCode fires a session.error, message.updated, or session.status
5
- * event containing a rate-limit signal, this manager:
5
+ * event containing a transient error (rate-limit, 403/Forbidden, etc.), this
6
+ * manager:
6
7
  * 1. Looks up the next untried model in the agent's configured chain
7
- * 2. Aborts the rate-limited prompt via client.session.abort()
8
+ * 2. Aborts the rate-limited prompt via client.session.abort() on the
9
+ * session.status retry path; session.error and message.updated paths
10
+ * re-prompt directly without abort.
8
11
  * 3. Re-queues the last user message via client.session.promptAsync()
9
12
  * with the new model - promptAsync returns immediately so we never
10
13
  * block the event handler waiting for a full LLM response.
@@ -14,7 +17,15 @@
14
17
  * try/catch, which is not possible for interactive (foreground) sessions.
15
18
  */
16
19
  import type { PluginInput } from '@opencode-ai/plugin';
20
+ import type { SessionLifecycle } from '../session-lifecycle';
17
21
  type OpencodeClient = PluginInput['client'];
22
+ export declare function isFailoverError(error: unknown): boolean;
23
+ /**
24
+ * Checks whether an error is a transient/retryable error (rate-limit,
25
+ * 403/Forbidden, etc.) that should trigger model fallback.
26
+ */
27
+ export declare function isRetryableError(error: unknown): boolean;
28
+ /** @deprecated Use isRetryableError instead. */
18
29
  export declare function isRateLimitError(error: unknown): boolean;
19
30
  /**
20
31
  * Manages runtime model fallback for foreground agent sessions.
@@ -29,8 +40,10 @@ export declare class ForegroundFallbackManager {
29
40
  * e.g. { orchestrator: ['anthropic/claude-opus-4-5', 'openai/gpt-4o'] }
30
41
  * The first model that hasn't been tried yet is selected on each fallback.
31
42
  */
32
- private readonly chains;
43
+ private chains;
33
44
  private readonly enabled;
45
+ /** Consecutive 429s tolerated on the same model before swap/abort. */
46
+ private readonly maxRetries;
34
47
  /** sessionID → last observed model string ("providerID/modelID") */
35
48
  private readonly sessionModel;
36
49
  /** sessionID → agent name (populated from message.updated info.agent field) */
@@ -45,27 +58,60 @@ export declare class ForegroundFallbackManager {
45
58
  * when the model has changed, allowing the cascade to continue when a
46
59
  * new fallback model also fails within the dedup window. */
47
60
  private readonly lastTriggerModel;
61
+ /** sessionID → consecutive 429 count for the current model.
62
+ * Reset on model swap or session deletion. */
63
+ private readonly sessionRetries;
64
+ /** Exposed for task-session-manager: prevents idle reconciliation
65
+ * while a fallback abort/re-prompt is in flight for this session. */
66
+ isFallbackInProgress(sessionID: string): boolean;
67
+ /**
68
+ * Disable the fallback chain for a specific agent.
69
+ * After calling this, rate-limit errors for that agent surface instead of
70
+ * silently falling back through the chain.
71
+ */
72
+ disableChain(agentName: string): void;
73
+ registerSessionAgent(sessionID: string, agentName: string): void;
48
74
  constructor(client: OpencodeClient,
49
75
  /**
50
76
  * Ordered fallback chains per agent.
51
77
  * e.g. { orchestrator: ['anthropic/claude-opus-4-5', 'openai/gpt-4o'] }
52
78
  * The first model that hasn't been tried yet is selected on each fallback.
53
79
  */
54
- chains: Record<string, string[]>, enabled: boolean);
80
+ chains: Record<string, string[]>, enabled: boolean,
81
+ /** Consecutive 429s tolerated on the same model before swap/abort. */
82
+ maxRetries?: number, coordinator?: SessionLifecycle);
55
83
  /**
56
84
  * Process an OpenCode plugin event.
57
85
  * Call this from the plugin's `event` hook for every event received.
58
86
  */
59
87
  handleEvent(rawEvent: unknown): Promise<void>;
88
+ /** Increment retry counter and return true when the budget is exhausted.
89
+ * Used by shouldIntervene when tried > 0 — each retry counts toward the
90
+ * budget and only triggers fallback after maxRetries - 1 absorptions.
91
+ * First failover retry (tried === 0) bypasses the counter via shouldIntervene. */
92
+ private consumeRetryBudget;
93
+ /** Intervene immediately on first occurrence (tried === 0), otherwise
94
+ * delegate to retry budget. Used by all three event paths. */
95
+ private shouldTriggerFallback;
96
+ private isRecoveredStatus;
60
97
  private tryFallback;
98
+ /**
99
+ * Fallback path for session.status retry events. Aborts the retry loop
100
+ * before falling back because promptAsync alone is ignored while the
101
+ * session is in retry mode. inProgress is set first so the
102
+ * task-session-manager sees isFallbackInProgress()=true during the
103
+ * abort idle window and does not cancel the pending task call.
104
+ */
105
+ private tryFallbackWithAbort;
106
+ private isDeduped;
107
+ private execFallback;
61
108
  /**
62
109
  * Determine the fallback chain to use for a session.
63
110
  *
64
111
  * Priority:
65
112
  * 1. Agent name known AND has a configured chain → return it directly
66
- * 2. Agent name known but NO chain configured → return [] (no fallback;
67
- * do NOT bleed into other agents' chains which would re-prompt the
68
- * session with a model belonging to a completely different agent)
113
+ * 2. Agent name known but NO chain → return [] (no fallback; never
114
+ * bleed into other agents' chains)
69
115
  * 3. Agent name unknown, current model known → search all chains for
70
116
  * the model to infer which chain to use
71
117
  * 4. Nothing matches → flatten all chains as a last resort (only
@@ -2,6 +2,7 @@ import { type MessageWithParts } from './types';
2
2
  export declare function processImageAttachments(args: {
3
3
  messages: MessageWithParts[];
4
4
  workDir: string;
5
+ imageRouting: 'auto' | 'direct';
5
6
  disabledAgents: Set<string>;
6
7
  log: (msg: string) => void;
7
8
  }): void;
@@ -5,11 +5,12 @@ export { createChatHeadersHook } from './chat-headers';
5
5
  export { createDeepworkCommandHook } from './deepwork';
6
6
  export { createDelegateTaskRetryHook } from './delegate-task-retry/hook';
7
7
  export { createFilterAvailableSkillsHook } from './filter-available-skills';
8
- export { ForegroundFallbackManager, isRateLimitError, } from './foreground-fallback';
8
+ export { ForegroundFallbackManager, isFailoverError, isRetryableError, } from './foreground-fallback';
9
9
  export { processImageAttachments } from './image-hook';
10
10
  export { createJsonErrorRecoveryHook } from './json-error-recovery/hook';
11
11
  export { createLoopCommandHook } from './loop-command';
12
12
  export { createPhaseReminderHook } from './phase-reminder';
13
13
  export { createPostFileToolNudgeHook } from './post-file-tool-nudge';
14
14
  export { createReflectCommandHook } from './reflect';
15
+ export { SessionLifecycle } from './session-lifecycle';
15
16
  export { createTaskSessionManagerHook } from './task-session-manager';
@@ -6,13 +6,15 @@
6
6
  * of the user's actual turn.
7
7
  */
8
8
  import { PHASE_REMINDER } from '../../config/constants';
9
+ import type { SessionLifecycle } from '../session-lifecycle';
9
10
  export { PHASE_REMINDER };
11
+ export declare const PHASE_REMINDER_METADATA_KEY = "oh-my-opencode-slim.phaseReminder";
10
12
  /**
11
13
  * Creates the experimental.chat.messages.transform hook for phase reminder injection.
12
14
  * This hook runs right before sending to API, so it doesn't affect UI display.
13
15
  * Only injects for the orchestrator agent.
14
16
  */
15
- export declare function createPhaseReminderHook(): {
17
+ export declare function createPhaseReminderHook(coordinator?: SessionLifecycle): {
16
18
  'experimental.chat.messages.transform': (_input: Record<string, never>, output: {
17
19
  messages?: unknown;
18
20
  }) => Promise<void>;
@@ -1,19 +1,25 @@
1
1
  /**
2
2
  * Post-tool nudge - queues a delegation reminder after file reads/writes.
3
3
  * Catches the "inspect/edit files → implement myself" anti-pattern.
4
+ *
5
+ * The reminder is ephemeral: recorded on tool execution, injected via
6
+ * system.transform, and consumed once. File tool output stays clean.
4
7
  */
5
- interface ToolExecuteAfterInput {
6
- tool: string;
7
- sessionID?: string;
8
- callID?: string;
9
- }
10
- interface ToolExecuteAfterOutput {
11
- output?: unknown;
12
- }
8
+ import type { SessionLifecycle } from '../session-lifecycle';
13
9
  interface PostFileToolNudgeOptions {
14
10
  shouldInject?: (sessionID: string) => boolean;
11
+ coordinator?: SessionLifecycle;
15
12
  }
16
13
  export declare function createPostFileToolNudgeHook(options?: PostFileToolNudgeOptions): {
17
- 'tool.execute.after': (input: ToolExecuteAfterInput, output: ToolExecuteAfterOutput) => Promise<void>;
14
+ 'tool.execute.after': (input: {
15
+ tool: string;
16
+ sessionID?: string;
17
+ callID?: string;
18
+ }, _output: unknown) => Promise<void>;
19
+ 'experimental.chat.system.transform': (input: {
20
+ sessionID?: string;
21
+ }, output: {
22
+ system: string[];
23
+ }) => Promise<void>;
18
24
  };
19
25
  export {};
@@ -0,0 +1,11 @@
1
+ export declare class SessionLifecycle {
2
+ #private;
3
+ constructor(log: (msg: string, meta?: Record<string, unknown>) => void);
4
+ onSessionDeleted(callback: (sessionId: string) => void): void;
5
+ dispatchSessionDeleted(sessionId: string): void;
6
+ markPending(sessionId: string): void;
7
+ /** Atomic — only one caller gets true per markPending call. */
8
+ consumePending(sessionId: string): boolean;
9
+ hasPendingSession(sessionId: string): boolean;
10
+ clearSession(sessionId: string): void;
11
+ }
@@ -1,11 +1,23 @@
1
1
  import type { PluginInput } from '@opencode-ai/plugin';
2
- import { BackgroundJobBoard } from '../../utils';
2
+ import { type BackgroundJobStore } from '../../utils';
3
+ import type { SessionLifecycle } from '../session-lifecycle';
4
+ export declare const BACKGROUND_JOB_BOARD_METADATA_KEY = "oh-my-opencode-slim.backgroundJobBoard";
3
5
  export declare function createTaskSessionManagerHook(_ctx: PluginInput, options: {
4
6
  maxSessionsPerAgent: number;
5
7
  readContextMinLines?: number;
6
8
  readContextMaxFiles?: number;
7
- backgroundJobBoard?: BackgroundJobBoard;
9
+ backgroundJobBoard?: BackgroundJobStore;
8
10
  shouldManageSession: (sessionID: string) => boolean;
11
+ /** Register a session as orchestrator when the transform hook detects
12
+ * an orchestrator message but the session isn't in the agent map yet. */
13
+ registerSessionAsOrchestrator?: (sessionID: string) => void;
14
+ /** Optional guard: when provided, idle events for a session that is
15
+ * currently undergoing a foreground-fallback abort/re-prompt cycle
16
+ * will NOT trigger idle reconciliation. prevents marking a still-
17
+ * active child job as completed when the session was aborted for
18
+ * model fallback rather than natural completion. */
19
+ isFallbackInProgress?: (sessionID: string) => boolean;
20
+ coordinator?: SessionLifecycle;
9
21
  }): {
10
22
  'tool.execute.before': (input: {
11
23
  tool: string;