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
@@ -25,6 +25,8 @@ export declare function createInterviewService(ctx: PluginInput, config?: Interv
25
25
  parts: Array<{
26
26
  type: string;
27
27
  text?: string;
28
+ synthetic?: boolean;
29
+ metadata?: Record<string, unknown>;
28
30
  }>;
29
31
  }) => Promise<void>;
30
32
  handleEvent: (input: {
@@ -18,14 +18,16 @@ export declare class HerdrMultiplexer implements Multiplexer {
18
18
  private binaryPath;
19
19
  private hasChecked;
20
20
  private readonly parentPaneId;
21
- private readonly paneDirection;
21
+ private layout;
22
+ private paneDirection;
23
+ private agentAreaPaneId;
22
24
  constructor(layout?: MultiplexerLayout, mainPaneSize?: number);
23
25
  isAvailable(): Promise<boolean>;
24
26
  isInsideSession(): boolean;
25
27
  spawnPane(sessionId: string, description: string, serverUrl: string, directory: string): Promise<PaneResult>;
26
28
  closePane(paneId: string): Promise<boolean>;
27
- applyLayout(_layout: MultiplexerLayout, _mainPaneSize: number): Promise<void>;
29
+ applyLayout(layout: MultiplexerLayout, _mainPaneSize: number): Promise<void>;
30
+ private runSplit;
28
31
  private targetPaneArg;
29
32
  private getBinary;
30
- private findBinary;
31
33
  }
@@ -1,6 +1,7 @@
1
1
  import type { PluginInput } from '@opencode-ai/plugin';
2
2
  import type { MultiplexerConfig } from '../config/schema';
3
- import type { BackgroundJobBoard } from '../utils/background-job-board';
3
+ import type { BackgroundJobStore } from '../utils/background-job-store';
4
+ type BackgroundJobReader = Pick<BackgroundJobStore, 'getState' | 'deferIfRunning' | 'clearDeferredClose'>;
4
5
  interface SessionEvent {
5
6
  type: string;
6
7
  properties?: {
@@ -33,10 +34,9 @@ export declare class MultiplexerSessionManager {
33
34
  private knownSessions;
34
35
  private spawningSessions;
35
36
  private closingSessions;
36
- private deferredIdleCloses;
37
37
  private pollInterval?;
38
38
  private enabled;
39
- constructor(ctx: PluginInput, config: MultiplexerConfig, backgroundJobBoard?: BackgroundJobBoard | undefined);
39
+ constructor(ctx: PluginInput, config: MultiplexerConfig, backgroundJobBoard?: BackgroundJobReader | undefined);
40
40
  onSessionCreated(event: SessionEvent): Promise<void>;
41
41
  onSessionStatus(event: SessionEvent): Promise<void>;
42
42
  onSessionDeleted(event: SessionEvent): Promise<void>;
@@ -50,8 +50,8 @@ export declare class MultiplexerSessionManager {
50
50
  private updatePolling;
51
51
  private getSessionId;
52
52
  private backgroundJobState;
53
- private isRunningBackgroundJob;
54
- retryDeferredIdleClose(sessionId: string): Promise<void>;
53
+ private shouldCloseNow;
54
+ closeSessionFromCoordinator(sessionId: string): Promise<void>;
55
55
  cleanup(): Promise<void>;
56
56
  }
57
57
  /**
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared multiplexer infrastructure
3
+ *
4
+ * Functions used across tmux, zellij, and herdr backend adapters.
5
+ * Extracted to eliminate copy-paste duplication and prevent drift.
6
+ */
7
+ export declare function quoteShellArg(value: string): string;
8
+ /** Normalize Windows backslashes to / so sh -lc (MSYS2/Git Bash) doesn't treat them as escape chars. */
9
+ export declare function normalizePathForShell(directory: string): string;
10
+ export declare function buildOpencodeAttachCommand(sessionId: string, serverUrl: string, directory: string): string;
11
+ export declare function findBinary(binaryName: string, options?: {
12
+ verify?: boolean;
13
+ }): Promise<string | null>;
14
+ export interface GracefulClosePaneOptions {
15
+ /** Backend-specific Ctrl+C command args (binary prepended by caller). */
16
+ ctrlC: string[];
17
+ /** Backend-specific close/kill command args (binary prepended by caller). */
18
+ close: string[];
19
+ /** Accept exit code 1 as success (zellij/herdr treat "already closed" as 1). */
20
+ acceptExitCode1?: boolean;
21
+ /** Return true for empty/unknown paneId instead of false (zellij/herdr behavior). */
22
+ emptyPaneReturnsTrue?: boolean;
23
+ }
24
+ export declare function gracefulClosePane(binary: string | null, paneId: string, options: GracefulClosePaneOptions): Promise<boolean>;
@@ -23,5 +23,4 @@ export declare class TmuxMultiplexer implements Multiplexer {
23
23
  private runTmux;
24
24
  private getBinary;
25
25
  private targetArgs;
26
- private findBinary;
27
26
  }
@@ -44,5 +44,4 @@ export declare class ZellijMultiplexer implements Multiplexer {
44
44
  private getParentTabId;
45
45
  private findTabIdForPane;
46
46
  private getBinary;
47
- private findBinary;
48
47
  }
@@ -1,8 +1,8 @@
1
1
  import { type PluginInput, type ToolDefinition } from '@opencode-ai/plugin';
2
- import type { BackgroundJobBoard } from '../utils/background-job-board';
2
+ import type { BackgroundJobStore } from '../utils/background-job-store';
3
3
  interface CancelTaskToolOptions {
4
4
  client: PluginInput['client'];
5
- backgroundJobBoard: BackgroundJobBoard;
5
+ backgroundJobBoard: BackgroundJobStore;
6
6
  shouldManageSession: (sessionID: string) => boolean;
7
7
  abortTimeoutMs?: number;
8
8
  verifyAbortMs?: number;
package/dist/tui.d.ts CHANGED
@@ -6,6 +6,7 @@ export declare function splitSidebarModelId(model: string): {
6
6
  };
7
7
  export declare function getSidebarAgentNames(snapshot: TuiSnapshot): string[];
8
8
  export declare function readConfigInvalid(directory: string): boolean;
9
+ export declare function readCompactSidebar(directory: string): boolean;
9
10
  declare const plugin: TuiPluginModule & {
10
11
  id: string;
11
12
  };
package/dist/tui.js CHANGED
@@ -131,7 +131,12 @@ var POLL_INTERVAL_BACKGROUND_MS = 2000;
131
131
  var MAX_POLL_TIME_MS = 5 * 60 * 1000;
132
132
  var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
133
133
  var PHASE_REMINDER_TEXT = `!IMPORTANT! Scheduler workflow: plan lanes/dependencies → dispatch background specialists → track task IDs → wait for hook-driven completion → reconcile terminal results → verify. Do not poll running jobs, consume running-job output, or advance dependent work. !END!`;
134
- var PHASE_REMINDER = `<internal_reminder>${PHASE_REMINDER_TEXT}</internal_reminder>`;
134
+ function formatSystemReminder(text) {
135
+ return `<system-reminder>
136
+ ${text}
137
+ </system-reminder>`;
138
+ }
139
+ var PHASE_REMINDER = formatSystemReminder(PHASE_REMINDER_TEXT);
135
140
  var WRITABLE_FILE_OPERATIONS_RULES = `**File Operations Rules**:
136
141
  - Prefer dedicated file tools for normal code work: glob/grep/ast_grep_search for discovery, read for file contents, and edit/write/apply_patch for targeted source changes.
137
142
  - Use bash for execution and automation: git, package managers, tests, builds, scripts, diagnostics, and shell-native filesystem operations.
@@ -188,11 +193,35 @@ function getOpenCodeConfigPaths() {
188
193
  }
189
194
  // src/config/council-schema.ts
190
195
  import { z } from "zod";
191
- var ModelIdSchema = z.string().regex(/^[^/\s]+\/[^\s]+$/, 'Expected provider/model format (e.g. "openai/gpt-5.4-mini")');
196
+
197
+ // src/utils/councillor-models.ts
198
+ function normalizeCouncillorModels(model, fallbackVariant) {
199
+ const raw = Array.isArray(model) ? model : [model];
200
+ return raw.map((entry) => typeof entry === "string" ? { id: entry, variant: fallbackVariant } : { id: entry.id, variant: entry.variant ?? fallbackVariant });
201
+ }
202
+
203
+ // src/config/council-schema.ts
204
+ var ModelIdSchema = z.string().regex(/^[^/\s]+\/[^\s]+$/, 'Expected provider/model format (e.g. "openai/gpt-5.6-luna")');
205
+ var CouncillorModelEntrySchema = z.object({
206
+ id: ModelIdSchema,
207
+ variant: z.string().optional()
208
+ });
209
+ var CouncillorModelSchema = z.union([
210
+ ModelIdSchema,
211
+ z.array(z.union([ModelIdSchema, CouncillorModelEntrySchema])).min(1)
212
+ ]).describe('Model ID in provider/model format (e.g. "openai/gpt-5.6-luna"), or an ' + "ordered fallback chain (array of model IDs or { id, variant } entries) " + "tried in order until one responds.");
192
213
  var CouncillorConfigSchema = z.object({
193
- model: ModelIdSchema.describe('Model ID in provider/model format (e.g. "openai/gpt-5.4-mini")'),
214
+ model: CouncillorModelSchema,
194
215
  variant: z.string().optional(),
195
216
  prompt: z.string().optional().describe("Optional role/guidance injected into the councillor user prompt")
217
+ }).transform((c) => {
218
+ const models = normalizeCouncillorModels(c.model, c.variant);
219
+ return {
220
+ model: models[0].id,
221
+ variant: c.variant,
222
+ prompt: c.prompt,
223
+ models
224
+ };
196
225
  });
197
226
  var CouncilPresetSchema = z.record(z.string(), z.record(z.string(), z.unknown())).transform((entries, ctx) => {
198
227
  const councillors = {};
@@ -346,7 +375,9 @@ var FailoverConfigSchema = z2.object({
346
375
  enabled: z2.boolean().default(true),
347
376
  timeoutMs: z2.number().min(0).default(15000),
348
377
  retryDelayMs: z2.number().min(0).default(500),
349
- retry_on_empty: z2.boolean().default(true).describe("When true (default), empty provider responses are treated as failures, " + "triggering fallback/retry. Set to false to treat them as successes.")
378
+ maxRetries: z2.number().int().min(0).default(3).describe("Number of consecutive 429/rate-limit responses tolerated on the " + "same model before aborting (or swapping to the next fallback " + "model when a chain is configured)."),
379
+ retry_on_empty: z2.boolean().default(true).describe("When true (default), empty provider responses are treated as failures, " + "triggering fallback/retry. Set to false to treat them as successes."),
380
+ runtimeOverride: z2.boolean().default(true).describe("When true (default), a runtime model selected via /model that is " + "outside the configured fallback chain will still trigger the chain " + "on rate-limit errors. When false, out-of-chain runtime picks are " + "respected and the error surfaces instead of silently falling back " + "to the chain. Models that are members of the chain always fall back " + "regardless of this setting.")
350
381
  }).strict();
351
382
  var CompanionConfigSchema = z2.object({
352
383
  enabled: z2.boolean().optional(),
@@ -387,7 +418,7 @@ function rejectOrchestratorPromptOnOrchestrator(overrides, ctx, pathPrefix) {
387
418
  var PluginConfigSchema = z2.object({
388
419
  preset: z2.string().optional(),
389
420
  setDefaultAgent: z2.boolean().optional(),
390
- compactSidebar: z2.boolean().optional().describe("Use the compact TUI sidebar layout when enabled."),
421
+ compactSidebar: z2.boolean().optional().describe("Use the compact TUI sidebar layout. Defaults to true; set false to use the expanded layout."),
391
422
  autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
392
423
  presets: z2.record(z2.string(), PresetSchema).optional(),
393
424
  agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
@@ -902,15 +933,15 @@ function agentRow(label, model, variant, theme) {
902
933
  ...detailRows
903
934
  ]);
904
935
  }
905
- function compactAgentRow(label, model, variant, theme) {
906
- const value = variant ? `${model} (${variant})` : model;
936
+ function compactAgentRow(label, model, _variant, theme) {
937
+ const modelName = splitSidebarModelId(model).model;
907
938
  return box({
908
939
  width: "100%",
909
940
  flexDirection: "row",
910
941
  justifyContent: "space-between"
911
942
  }, [
912
943
  text({ fg: theme.textMuted, width: 14 }, [label]),
913
- text({ fg: theme.textMuted }, [value])
944
+ text({ fg: theme.textMuted }, [modelName])
914
945
  ]);
915
946
  }
916
947
  function renderSidebar(snapshot, version, theme, configInvalid, compactSidebar) {
@@ -931,7 +962,7 @@ function renderSidebar(snapshot, version, theme, configInvalid, compactSidebar)
931
962
  justifyContent: "space-between",
932
963
  alignItems: "center"
933
964
  }, [
934
- box({ paddingLeft: 1, paddingRight: 1, backgroundColor: theme.accent }, [text({ fg: theme.background }, ["OMO-Slim"])]),
965
+ box({ paddingLeft: 1, paddingRight: 1, backgroundColor: theme.accent }, [text({ fg: theme.text }, ["OMO-Slim"])]),
935
966
  text({ fg: theme.textMuted }, [`v${version}`])
936
967
  ]),
937
968
  configStatusRow,
@@ -969,12 +1000,15 @@ function readConfigState(directory) {
969
1000
  configInvalid = true;
970
1001
  }
971
1002
  });
972
- const compactSidebar = config.compactSidebar ?? false;
1003
+ const compactSidebar = config.compactSidebar ?? true;
973
1004
  return { configInvalid, compactSidebar };
974
1005
  }
975
1006
  function readConfigInvalid(directory) {
976
1007
  return readConfigState(directory).configInvalid;
977
1008
  }
1009
+ function readCompactSidebar(directory) {
1010
+ return readConfigState(directory).compactSidebar;
1011
+ }
978
1012
  var plugin = {
979
1013
  id: `${PLUGIN_NAME}:tui`,
980
1014
  tui: async (api, _options, meta) => {
@@ -1012,6 +1046,7 @@ var tui_default = plugin;
1012
1046
  export {
1013
1047
  splitSidebarModelId,
1014
1048
  readConfigInvalid,
1049
+ readCompactSidebar,
1015
1050
  getSidebarAgentNames,
1016
1051
  tui_default as default
1017
1052
  };
@@ -1,3 +1,4 @@
1
+ import type { BackgroundJobStore } from './background-job-store';
1
2
  import { type TaskOutputState } from './task';
2
3
  export interface ContextFile {
3
4
  path: string;
@@ -56,7 +57,7 @@ export interface BackgroundJobStatusInput {
56
57
  now?: number;
57
58
  }
58
59
  type TerminalStateListener = (taskID: string) => void;
59
- export declare class BackgroundJobBoard {
60
+ export declare class BackgroundJobBoard implements BackgroundJobStore {
60
61
  private readonly jobs;
61
62
  private readonly counters;
62
63
  private terminalStateListeners;
@@ -97,6 +98,9 @@ export declare class BackgroundJobBoard {
97
98
  formatForPrompt(parentSessionID: string, now?: number): string | undefined;
98
99
  clearParent(parentSessionID: string): void;
99
100
  drop(taskID: string): void;
101
+ deferIfRunning(_sessionId: string): boolean;
102
+ retryDeferredClose(_sessionId: string): boolean;
103
+ clearDeferredClose(_sessionId: string): void;
100
104
  private trimReusable;
101
105
  private formatReusableJob;
102
106
  private nextAlias;
@@ -0,0 +1,72 @@
1
+ import type { BackgroundJobBoard, BackgroundJobLaunchInput, BackgroundJobRecord, BackgroundJobStatusInput, ContextFile } from './background-job-board';
2
+ import type { BackgroundJobStore } from './background-job-store';
3
+ import type { TaskOutputState } from './task';
4
+ type TerminalStateListener = (taskID: string) => void;
5
+ /**
6
+ * BackgroundJobCoordinator owns the lifecycle policy for background jobs.
7
+ * It sits between the board and its consumers, providing:
8
+ * - Subscription interface for terminal state notifications (replaces fire-and-forget)
9
+ * - Lifecycle policy: determines when jobs are terminal, when closes should be deferred
10
+ * - Single-writer contract: coordinator is the sole writer to the board
11
+ *
12
+ * The board's guards prevent silent overwrites. The coordinator adds:
13
+ * - Centralized notification with guaranteed delivery
14
+ * - Re-checks board state before notifying (handles races)
15
+ */
16
+ export declare class BackgroundJobCoordinator implements BackgroundJobStore {
17
+ private readonly board;
18
+ private terminalStateListeners;
19
+ private readonly deferredIdleCloses;
20
+ constructor(board: BackgroundJobBoard);
21
+ addTerminalStateListener(listener: TerminalStateListener): void;
22
+ removeTerminalStateListener(listener: TerminalStateListener): void;
23
+ /**
24
+ * Handle terminal state from board. Re-checks board state to handle races.
25
+ * This is the centralized lifecycle policy.
26
+ */
27
+ private handleTerminalState;
28
+ /**
29
+ * Evaluate close policy. Returns true if session should close now.
30
+ * Mutates deferred state: adds to deferred set if running, removes if not.
31
+ */
32
+ deferIfRunning(sessionId: string): boolean;
33
+ /**
34
+ * Retry closing a deferred session. Called when a background job completes.
35
+ * Returns true if the session should now close.
36
+ */
37
+ retryDeferredClose(sessionId: string): boolean;
38
+ /**
39
+ * Clear deferred close state for a session being deleted.
40
+ */
41
+ clearDeferredClose(sessionId: string): void;
42
+ registerLaunch(input: BackgroundJobLaunchInput): BackgroundJobRecord;
43
+ updateStatus(input: BackgroundJobStatusInput): BackgroundJobRecord | undefined;
44
+ updateFromStatusOutput(output: string): BackgroundJobRecord | undefined;
45
+ markRunningFromLiveSession(taskID: string, now?: number): BackgroundJobRecord | undefined;
46
+ markReconciled(taskID: string, now?: number): BackgroundJobRecord | undefined;
47
+ markCancelled(taskID: string, reason?: string, now?: number, options?: {
48
+ force?: boolean;
49
+ }): BackgroundJobRecord | undefined;
50
+ get(taskID: string): BackgroundJobRecord | undefined;
51
+ field<K extends keyof BackgroundJobRecord>(taskID: string, key: K): BackgroundJobRecord[K] | undefined;
52
+ isRunning(taskID: string): boolean;
53
+ isTerminalUnreconciled(taskID: string): boolean;
54
+ getResultSummary(taskID: string): string | undefined;
55
+ getLastLiveBusyAt(taskID: string): number | undefined;
56
+ getParentSessionID(taskID: string): string | undefined;
57
+ getState(taskID: string): TaskOutputState | 'reconciled' | undefined;
58
+ resolve(parentSessionID: string, taskIDOrAlias: string): BackgroundJobRecord | undefined;
59
+ resolveReusable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
60
+ resolveRecoverable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
61
+ markUsed(parentSessionID: string, key: string, now?: number): void;
62
+ taskIDs(): Set<string>;
63
+ addContext(taskID: string, files: ContextFile[]): void;
64
+ list(parentSessionID?: string): BackgroundJobRecord[];
65
+ hasRunning(parentSessionID: string): boolean;
66
+ hasTerminalUnreconciled(parentSessionID: string): boolean;
67
+ hasConvergenceSignals(taskID: string, threshold?: number): boolean;
68
+ formatForPrompt(parentSessionID: string, now?: number): string | undefined;
69
+ clearParent(parentSessionID: string): void;
70
+ drop(taskID: string): void;
71
+ }
72
+ export {};
@@ -0,0 +1,46 @@
1
+ import type { BackgroundJobLaunchInput, BackgroundJobRecord, BackgroundJobStatusInput, ContextFile } from './background-job-board';
2
+ import type { TaskOutputState } from './task';
3
+ /**
4
+ * Unified interface for background job operations.
5
+ * Both BackgroundJobBoard and BackgroundJobCoordinator satisfy this.
6
+ *
7
+ * ponytail: single interface, both board and coordinator implement it.
8
+ */
9
+ export interface BackgroundJobStore {
10
+ registerLaunch(input: BackgroundJobLaunchInput): BackgroundJobRecord;
11
+ updateStatus(input: BackgroundJobStatusInput): BackgroundJobRecord | undefined;
12
+ updateFromStatusOutput(output: string): BackgroundJobRecord | undefined;
13
+ markRunningFromLiveSession(taskID: string, now?: number): BackgroundJobRecord | undefined;
14
+ markReconciled(taskID: string, now?: number): BackgroundJobRecord | undefined;
15
+ markCancelled(taskID: string, reason?: string, now?: number, options?: {
16
+ force?: boolean;
17
+ }): BackgroundJobRecord | undefined;
18
+ clearParent(parentSessionID: string): void;
19
+ drop(taskID: string): void;
20
+ addContext(taskID: string, files: ContextFile[]): void;
21
+ markUsed(parentSessionID: string, key: string, now?: number): void;
22
+ get(taskID: string): BackgroundJobRecord | undefined;
23
+ field<K extends keyof BackgroundJobRecord>(taskID: string, key: K): BackgroundJobRecord[K] | undefined;
24
+ isRunning(taskID: string): boolean;
25
+ isTerminalUnreconciled(taskID: string): boolean;
26
+ getResultSummary(taskID: string): string | undefined;
27
+ getLastLiveBusyAt(taskID: string): number | undefined;
28
+ getParentSessionID(taskID: string): string | undefined;
29
+ getState(taskID: string): TaskOutputState | 'reconciled' | undefined;
30
+ resolve(parentSessionID: string, taskIDOrAlias: string): BackgroundJobRecord | undefined;
31
+ resolveReusable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
32
+ resolveRecoverable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
33
+ taskIDs(): Set<string>;
34
+ list(parentSessionID?: string): BackgroundJobRecord[];
35
+ hasRunning(parentSessionID: string): boolean;
36
+ hasTerminalUnreconciled(parentSessionID: string): boolean;
37
+ hasConvergenceSignals(taskID: string, threshold?: number): boolean;
38
+ formatForPrompt(parentSessionID: string, now?: number): string | undefined;
39
+ /** Evaluate close policy. Returns true if session should close now.
40
+ * Mutates deferred state: adds to deferred set if running, removes if not. */
41
+ deferIfRunning(sessionId: string): boolean;
42
+ /** Retry closing a deferred session. Returns true if session should now close. */
43
+ retryDeferredClose(sessionId: string): boolean;
44
+ /** Clear deferred close state for a session being deleted. */
45
+ clearDeferredClose(sessionId: string): void;
46
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Pure helpers for councillor model fallback chains.
3
+ *
4
+ * Kept free of any schema/validation library import so that runtime consumers
5
+ * (e.g. `CouncilManager`) can resolve a councillor's ordered model chain
6
+ * without pulling in zod or the config schema module.
7
+ */
8
+ /** A single model in a councillor fallback chain, with optional variant. */
9
+ export type CouncillorModelEntry = {
10
+ id: string;
11
+ variant?: string;
12
+ };
13
+ /**
14
+ * Flatten a councillor model config into an ordered list of model entries.
15
+ *
16
+ * Accepts either a single "provider/model" string or an ordered fallback
17
+ * chain (array of strings and/or `{ id, variant }` entries). Entries that
18
+ * don't carry their own variant fall back to the shared `fallbackVariant`.
19
+ */
20
+ export declare function normalizeCouncillorModels(model: string | Array<string | CouncillorModelEntry>, fallbackVariant?: string): CouncillorModelEntry[];
@@ -1,5 +1,7 @@
1
1
  export * from './agent-variant';
2
2
  export * from './background-job-board';
3
+ export * from './background-job-coordinator';
4
+ export * from './background-job-store';
3
5
  export * from './internal-initiator';
4
6
  export { getLogDir, initLogger, log } from './logger';
5
7
  export * from './polling';
@@ -1,6 +1,11 @@
1
1
  export declare const SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
2
+ export declare const INTERNAL_INITIATOR_METADATA_KEY = "oh-my-opencode-slim.internalInitiator";
2
3
  export declare function createInternalAgentTextPart(text: string): {
3
4
  type: 'text';
4
5
  text: string;
6
+ synthetic: true;
7
+ metadata: {
8
+ 'oh-my-opencode-slim.internalInitiator': true;
9
+ };
5
10
  };
6
- export declare function hasInternalInitiatorMarker(part: unknown): boolean;
11
+ export declare function isInternalInitiatorPart(part: unknown): boolean;
@@ -11,7 +11,7 @@ export declare function withTimeout<T>(operation: Promise<T>, timeoutMs: number,
11
11
  export declare function abortSessionWithTimeout(client: OpencodeClient, sessionId: string, timeoutMs?: number): Promise<void>;
12
12
  /**
13
13
  * Extract the short model label from a "provider/model" string.
14
- * E.g. "openai/gpt-5.4-mini" → "gpt-5.4-mini"
14
+ * E.g. "openai/gpt-5.6-luna" → "gpt-5.6-luna"
15
15
  */
16
16
  export declare function shortModelLabel(model: string): string;
17
17
  export type PromptBody = {
@@ -9,7 +9,7 @@
9
9
  "type": "boolean"
10
10
  },
11
11
  "compactSidebar": {
12
- "description": "Use the compact TUI sidebar layout when enabled.",
12
+ "description": "Use the compact TUI sidebar layout. Defaults to true; set false to use the expanded layout.",
13
13
  "type": "boolean"
14
14
  },
15
15
  "autoUpdate": {
@@ -366,10 +366,22 @@
366
366
  "type": "number",
367
367
  "minimum": 0
368
368
  },
369
+ "maxRetries": {
370
+ "default": 3,
371
+ "description": "Number of consecutive 429/rate-limit responses tolerated on the same model before aborting (or swapping to the next fallback model when a chain is configured).",
372
+ "type": "integer",
373
+ "minimum": 0,
374
+ "maximum": 9007199254740991
375
+ },
369
376
  "retry_on_empty": {
370
377
  "default": true,
371
378
  "description": "When true (default), empty provider responses are treated as failures, triggering fallback/retry. Set to false to treat them as successes.",
372
379
  "type": "boolean"
380
+ },
381
+ "runtimeOverride": {
382
+ "default": true,
383
+ "description": "When true (default), a runtime model selected via /model that is outside the configured fallback chain will still trigger the chain on rate-limit errors. When false, out-of-chain runtime picks are respected and the error surfaces instead of silently falling back to the chain. Models that are members of the chain always fall back regardless of this setting.",
384
+ "type": "boolean"
373
385
  }
374
386
  },
375
387
  "additionalProperties": false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode-slim",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Lightweight agent orchestration plugin for OpenCode - a slimmed-down fork of oh-my-opencode",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -122,13 +122,13 @@ Edit the active preset under `presets.<preset>.<agent>`:
122
122
  "presets": {
123
123
  "openai": {
124
124
  "orchestrator": {
125
- "model": "openai/gpt-5.5",
125
+ "model": "openai/gpt-5.6-terra",
126
126
  "variant": "high",
127
127
  "skills": ["*"],
128
128
  "mcps": ["*", "!context7"]
129
129
  },
130
130
  "librarian": {
131
- "model": "openai/gpt-5.4-mini",
131
+ "model": "openai/gpt-5.6-luna",
132
132
  "variant": "low",
133
133
  "skills": [],
134
134
  "mcps": ["websearch", "context7", "gh_grep"]
@@ -233,7 +233,7 @@ Use this shape as a starting point:
233
233
  {
234
234
  "agents": {
235
235
  "api-reviewer": {
236
- "model": "openai/gpt-5.5",
236
+ "model": "openai/gpt-5.6",
237
237
  "variant": "high",
238
238
  "prompt": "You review API design, compatibility, error semantics, and migration risk. Return concise findings with file references.",
239
239
  "orchestratorPrompt": "Delegate to @api-reviewer for API contract changes, public SDK changes, backwards-compatibility questions, or migration-risk review. Do not use it for routine implementation.",
@@ -119,13 +119,13 @@ tarball install.
119
119
  mkdir -p "$SMOKE/config"
120
120
  cat > "$SMOKE/config/opencode.json" <<EOF
121
121
  {
122
- "model": "openai/gpt-5.5-fast",
122
+ "model": "openai/gpt-5.6-fast",
123
123
  "plugin": [
124
124
  "file://$SMOKE/app/node_modules/oh-my-opencode-slim/dist/index.js"
125
125
  ],
126
126
  "agent": {
127
127
  "orchestrator": {
128
- "model": "openai/gpt-5.5-fast"
128
+ "model": "openai/gpt-5.6-fast"
129
129
  }
130
130
  }
131
131
  }