oh-my-opencode-slim 2.1.0 → 2.1.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.
Files changed (38) hide show
  1. package/README.ja-JP.md +18 -18
  2. package/README.ko-KR.md +18 -18
  3. package/README.md +29 -18
  4. package/README.zh-CN.md +16 -16
  5. package/dist/cli/index.js +42 -11
  6. package/dist/cli/providers.d.ts +7 -7
  7. package/dist/companion/manager.d.ts +3 -0
  8. package/dist/config/constants.d.ts +2 -1
  9. package/dist/config/council-schema.d.ts +37 -12
  10. package/dist/config/schema.d.ts +13 -6
  11. package/dist/council/council-manager.d.ts +7 -3
  12. package/dist/hooks/foreground-fallback/index.d.ts +35 -1
  13. package/dist/hooks/index.d.ts +1 -0
  14. package/dist/hooks/phase-reminder/index.d.ts +3 -1
  15. package/dist/hooks/post-file-tool-nudge/index.d.ts +15 -9
  16. package/dist/hooks/session-lifecycle.d.ts +11 -0
  17. package/dist/hooks/task-session-manager/index.d.ts +14 -2
  18. package/dist/index.js +972 -514
  19. package/dist/interview/service.d.ts +2 -0
  20. package/dist/multiplexer/herdr/index.d.ts +5 -3
  21. package/dist/multiplexer/session-manager.d.ts +5 -5
  22. package/dist/multiplexer/shared.d.ts +24 -0
  23. package/dist/multiplexer/tmux/index.d.ts +0 -1
  24. package/dist/multiplexer/zellij/index.d.ts +0 -1
  25. package/dist/tools/cancel-task.d.ts +2 -2
  26. package/dist/tui.d.ts +1 -0
  27. package/dist/tui.js +45 -10
  28. package/dist/utils/background-job-board.d.ts +5 -1
  29. package/dist/utils/background-job-coordinator.d.ts +72 -0
  30. package/dist/utils/background-job-store.d.ts +46 -0
  31. package/dist/utils/councillor-models.d.ts +20 -0
  32. package/dist/utils/index.d.ts +2 -0
  33. package/dist/utils/internal-initiator.d.ts +6 -1
  34. package/dist/utils/session.d.ts +1 -1
  35. package/oh-my-opencode-slim.schema.json +13 -1
  36. package/package.json +1 -1
  37. package/src/skills/oh-my-opencode-slim/SKILL.md +3 -3
  38. package/src/skills/release-smoke-test/SKILL.md +2 -2
@@ -1,4 +1,6 @@
1
1
  import { z } from 'zod';
2
+ import { type CouncillorModelEntry } from '../utils/councillor-models';
3
+ export type { CouncillorModelEntry };
2
4
  /**
3
5
  * Configuration for a single councillor within a preset.
4
6
  * Each councillor is an independent LLM that processes the same prompt.
@@ -6,12 +8,31 @@ import { z } from 'zod';
6
8
  * Councillors run as agent sessions with read-only codebase access
7
9
  * (read, glob, grep, lsp, list). They can examine the codebase but
8
10
  * cannot modify files or spawn subagents.
11
+ *
12
+ * `model` accepts a single ID or an ordered fallback chain. The parsed config
13
+ * exposes `models` (the normalized chain) plus `model` (the primary, for
14
+ * backward compatibility).
9
15
  */
10
- export declare const CouncillorConfigSchema: z.ZodObject<{
11
- model: z.ZodString;
16
+ export declare const CouncillorConfigSchema: z.ZodPipe<z.ZodObject<{
17
+ model: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
18
+ id: z.ZodString;
19
+ variant: z.ZodOptional<z.ZodString>;
20
+ }, z.core.$strip>]>>]>;
12
21
  variant: z.ZodOptional<z.ZodString>;
13
22
  prompt: z.ZodOptional<z.ZodString>;
14
- }, z.core.$strip>;
23
+ }, z.core.$strip>, z.ZodTransform<{
24
+ model: string;
25
+ variant: string | undefined;
26
+ prompt: string | undefined;
27
+ models: CouncillorModelEntry[];
28
+ }, {
29
+ model: string | (string | {
30
+ id: string;
31
+ variant?: string | undefined;
32
+ })[];
33
+ variant?: string | undefined;
34
+ prompt?: string | undefined;
35
+ }>>;
15
36
  export type CouncillorConfig = z.infer<typeof CouncillorConfigSchema>;
16
37
  /**
17
38
  * A named preset grouping several councillors.
@@ -22,8 +43,9 @@ export type CouncillorConfig = z.infer<typeof CouncillorConfigSchema>;
22
43
  */
23
44
  export declare const CouncilPresetSchema: z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>, z.ZodTransform<Record<string, {
24
45
  model: string;
25
- variant?: string | undefined;
26
- prompt?: string | undefined;
46
+ variant: string | undefined;
47
+ prompt: string | undefined;
48
+ models: CouncillorModelEntry[];
27
49
  }>, Record<string, Record<string, unknown>>>>;
28
50
  export type CouncilPreset = z.infer<typeof CouncilPresetSchema>;
29
51
  /**
@@ -44,7 +66,7 @@ export declare const CouncillorExecutionModeSchema: z.ZodDefault<z.ZodEnum<{
44
66
  * "council": {
45
67
  * "presets": {
46
68
  * "default": {
47
- * "alpha": { "model": "openai/gpt-5.4-mini" },
69
+ * "alpha": { "model": "openai/gpt-5.6-luna" },
48
70
  * "beta": { "model": "openai/gpt-5.3-codex" },
49
71
  * "gamma": { "model": "google/gemini-3-pro" }
50
72
  * }
@@ -58,8 +80,9 @@ export declare const CouncillorExecutionModeSchema: z.ZodDefault<z.ZodEnum<{
58
80
  export declare const CouncilConfigSchema: z.ZodPipe<z.ZodObject<{
59
81
  presets: z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>, z.ZodTransform<Record<string, {
60
82
  model: string;
61
- variant?: string | undefined;
62
- prompt?: string | undefined;
83
+ variant: string | undefined;
84
+ prompt: string | undefined;
85
+ models: CouncillorModelEntry[];
63
86
  }>, Record<string, Record<string, unknown>>>>>;
64
87
  timeout: z.ZodDefault<z.ZodNumber>;
65
88
  default_preset: z.ZodDefault<z.ZodString>;
@@ -72,8 +95,9 @@ export declare const CouncilConfigSchema: z.ZodPipe<z.ZodObject<{
72
95
  }, z.core.$strip>, z.ZodTransform<{
73
96
  presets: Record<string, Record<string, {
74
97
  model: string;
75
- variant?: string | undefined;
76
- prompt?: string | undefined;
98
+ variant: string | undefined;
99
+ prompt: string | undefined;
100
+ models: CouncillorModelEntry[];
77
101
  }>>;
78
102
  timeout: number;
79
103
  default_preset: string;
@@ -84,8 +108,9 @@ export declare const CouncilConfigSchema: z.ZodPipe<z.ZodObject<{
84
108
  }, {
85
109
  presets: Record<string, Record<string, {
86
110
  model: string;
87
- variant?: string | undefined;
88
- prompt?: string | undefined;
111
+ variant: string | undefined;
112
+ prompt: string | undefined;
113
+ models: CouncillorModelEntry[];
89
114
  }>>;
90
115
  timeout: number;
91
116
  default_preset: string;
@@ -177,7 +177,9 @@ export declare const FailoverConfigSchema: z.ZodObject<{
177
177
  enabled: z.ZodDefault<z.ZodBoolean>;
178
178
  timeoutMs: z.ZodDefault<z.ZodNumber>;
179
179
  retryDelayMs: z.ZodDefault<z.ZodNumber>;
180
+ maxRetries: z.ZodDefault<z.ZodNumber>;
180
181
  retry_on_empty: z.ZodDefault<z.ZodBoolean>;
182
+ runtimeOverride: z.ZodDefault<z.ZodBoolean>;
181
183
  }, z.core.$strict>;
182
184
  export type FailoverConfig = z.infer<typeof FailoverConfigSchema>;
183
185
  export declare const CompanionConfigSchema: z.ZodObject<{
@@ -337,13 +339,16 @@ export declare const PluginConfigSchema: z.ZodObject<{
337
339
  enabled: z.ZodDefault<z.ZodBoolean>;
338
340
  timeoutMs: z.ZodDefault<z.ZodNumber>;
339
341
  retryDelayMs: z.ZodDefault<z.ZodNumber>;
342
+ maxRetries: z.ZodDefault<z.ZodNumber>;
340
343
  retry_on_empty: z.ZodDefault<z.ZodBoolean>;
344
+ runtimeOverride: z.ZodDefault<z.ZodBoolean>;
341
345
  }, z.core.$strict>>;
342
346
  council: z.ZodOptional<z.ZodPipe<z.ZodObject<{
343
347
  presets: z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>, z.ZodTransform<Record<string, {
344
348
  model: string;
345
- variant?: string | undefined;
346
- prompt?: string | undefined;
349
+ variant: string | undefined;
350
+ prompt: string | undefined;
351
+ models: import("./council-schema").CouncillorModelEntry[];
347
352
  }>, Record<string, Record<string, unknown>>>>>;
348
353
  timeout: z.ZodDefault<z.ZodNumber>;
349
354
  default_preset: z.ZodDefault<z.ZodString>;
@@ -356,8 +361,9 @@ export declare const PluginConfigSchema: z.ZodObject<{
356
361
  }, z.core.$strip>, z.ZodTransform<{
357
362
  presets: Record<string, Record<string, {
358
363
  model: string;
359
- variant?: string | undefined;
360
- prompt?: string | undefined;
364
+ variant: string | undefined;
365
+ prompt: string | undefined;
366
+ models: import("./council-schema").CouncillorModelEntry[];
361
367
  }>>;
362
368
  timeout: number;
363
369
  default_preset: string;
@@ -368,8 +374,9 @@ export declare const PluginConfigSchema: z.ZodObject<{
368
374
  }, {
369
375
  presets: Record<string, Record<string, {
370
376
  model: string;
371
- variant?: string | undefined;
372
- prompt?: string | undefined;
377
+ variant: string | undefined;
378
+ prompt: string | undefined;
379
+ models: import("./council-schema").CouncillorModelEntry[];
373
380
  }>>;
374
381
  timeout: number;
375
382
  default_preset: string;
@@ -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
  }
@@ -14,6 +14,7 @@
14
14
  * try/catch, which is not possible for interactive (foreground) sessions.
15
15
  */
16
16
  import type { PluginInput } from '@opencode-ai/plugin';
17
+ import type { SessionLifecycle } from '../session-lifecycle';
17
18
  type OpencodeClient = PluginInput['client'];
18
19
  export declare function isRateLimitError(error: unknown): boolean;
19
20
  /**
@@ -31,6 +32,15 @@ export declare class ForegroundFallbackManager {
31
32
  */
32
33
  private readonly chains;
33
34
  private readonly enabled;
35
+ /** Consecutive 429s tolerated on the same model before swap/abort. */
36
+ private readonly maxRetries;
37
+ /**
38
+ * When true (default), a runtime model outside the configured chain
39
+ * still triggers fallback on rate-limit errors. When false, out-of-chain
40
+ * runtime picks are respected and the error surfaces instead. Models
41
+ * that are members of the chain always fall back regardless.
42
+ */
43
+ private readonly runtimeOverride;
34
44
  /** sessionID → last observed model string ("providerID/modelID") */
35
45
  private readonly sessionModel;
36
46
  /** sessionID → agent name (populated from message.updated info.agent field) */
@@ -45,18 +55,42 @@ export declare class ForegroundFallbackManager {
45
55
  * when the model has changed, allowing the cascade to continue when a
46
56
  * new fallback model also fails within the dedup window. */
47
57
  private readonly lastTriggerModel;
58
+ /** sessionID → consecutive 429 count for the current model.
59
+ * Reset on model swap or session deletion. */
60
+ private readonly sessionRetries;
61
+ /** Exposed for task-session-manager: prevents idle reconciliation
62
+ * while a fallback abort/re-prompt is in flight for this session. */
63
+ isFallbackInProgress(sessionID: string): boolean;
48
64
  constructor(client: OpencodeClient,
49
65
  /**
50
66
  * Ordered fallback chains per agent.
51
67
  * e.g. { orchestrator: ['anthropic/claude-opus-4-5', 'openai/gpt-4o'] }
52
68
  * The first model that hasn't been tried yet is selected on each fallback.
53
69
  */
54
- chains: Record<string, string[]>, enabled: boolean);
70
+ chains: Record<string, string[]>, enabled: boolean,
71
+ /** Consecutive 429s tolerated on the same model before swap/abort. */
72
+ maxRetries?: number, coordinator?: SessionLifecycle,
73
+ /**
74
+ * When true (default), a runtime model outside the configured chain
75
+ * still triggers fallback on rate-limit errors. When false, out-of-chain
76
+ * runtime picks are respected and the error surfaces instead. Models
77
+ * that are members of the chain always fall back regardless.
78
+ */
79
+ runtimeOverride?: boolean);
55
80
  /**
56
81
  * Process an OpenCode plugin event.
57
82
  * Call this from the plugin's `event` hook for every event received.
58
83
  */
59
84
  handleEvent(rawEvent: unknown): Promise<void>;
85
+ /** Increment retry counter and return true when the budget is exhausted.
86
+ * Used by the session.status retry path — each retry counts toward the
87
+ * budget and only triggers fallback after maxRetries - 1 absorptions.
88
+ * Non-retry paths (session.error / message.updated) use shouldIntervene(),
89
+ * which bypasses the counter on first occurrence. */
90
+ private checkRetryBudget;
91
+ /** For non-retry paths (session.error, message.updated): intervene immediately
92
+ * unless the session is already in a retry window (has prior retries). */
93
+ private shouldIntervene;
60
94
  private tryFallback;
61
95
  /**
62
96
  * Determine the fallback chain to use for a session.
@@ -12,4 +12,5 @@ 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;