oh-my-opencode-slim 2.0.5 → 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 (67) hide show
  1. package/README.ja-JP.md +64 -38
  2. package/README.ko-KR.md +62 -36
  3. package/README.md +82 -47
  4. package/README.zh-CN.md +63 -39
  5. package/dist/agents/council.d.ts +1 -1
  6. package/dist/agents/index.d.ts +7 -2
  7. package/dist/agents/orchestrator.d.ts +1 -1
  8. package/dist/cli/custom-skills-registry.d.ts +18 -0
  9. package/dist/cli/custom-skills.d.ts +3 -19
  10. package/dist/cli/index.js +1130 -195
  11. package/dist/cli/providers.d.ts +12 -16
  12. package/dist/cli/skills.d.ts +2 -2
  13. package/dist/companion/manager.d.ts +10 -0
  14. package/dist/config/constants.d.ts +2 -1
  15. package/dist/config/council-schema.d.ts +37 -12
  16. package/dist/config/loader.d.ts +5 -2
  17. package/dist/config/schema.d.ts +17 -6
  18. package/dist/council/council-manager.d.ts +7 -3
  19. package/dist/hooks/auto-update-checker/skill-sync.d.ts +59 -1
  20. package/dist/hooks/filter-available-skills/index.d.ts +1 -2
  21. package/dist/hooks/foreground-fallback/index.d.ts +40 -2
  22. package/dist/hooks/image-hook.d.ts +1 -1
  23. package/dist/hooks/index.d.ts +2 -0
  24. package/dist/hooks/loop-command/index.d.ts +13 -0
  25. package/dist/hooks/phase-reminder/index.d.ts +4 -3
  26. package/dist/hooks/post-file-tool-nudge/index.d.ts +15 -9
  27. package/dist/hooks/session-lifecycle.d.ts +11 -0
  28. package/dist/hooks/task-session-manager/index.d.ts +15 -4
  29. package/dist/hooks/task-session-manager/pending-call-tracker.d.ts +13 -0
  30. package/dist/hooks/task-session-manager/task-context-tracker.d.ts +14 -0
  31. package/dist/hooks/types.d.ts +3 -1
  32. package/dist/index.js +2890 -986
  33. package/dist/interview/dashboard-manager.d.ts +21 -0
  34. package/dist/interview/manager.d.ts +0 -14
  35. package/dist/interview/service.d.ts +9 -0
  36. package/dist/interview/session-server.d.ts +21 -0
  37. package/dist/interview/types.d.ts +3 -1
  38. package/dist/loop/loop-session.d.ts +64 -0
  39. package/dist/multiplexer/factory.d.ts +5 -5
  40. package/dist/multiplexer/herdr/index.d.ts +33 -0
  41. package/dist/multiplexer/index.d.ts +1 -0
  42. package/dist/multiplexer/session-manager.d.ts +6 -5
  43. package/dist/multiplexer/shared.d.ts +24 -0
  44. package/dist/multiplexer/tmux/index.d.ts +0 -1
  45. package/dist/multiplexer/types.d.ts +4 -4
  46. package/dist/multiplexer/zellij/index.d.ts +0 -1
  47. package/dist/tools/cancel-task.d.ts +2 -2
  48. package/dist/tui.d.ts +1 -0
  49. package/dist/tui.js +123 -45
  50. package/dist/utils/background-job-board.d.ts +22 -2
  51. package/dist/utils/background-job-coordinator.d.ts +72 -0
  52. package/dist/utils/background-job-store.d.ts +46 -0
  53. package/dist/utils/councillor-models.d.ts +20 -0
  54. package/dist/utils/index.d.ts +2 -0
  55. package/dist/utils/internal-initiator.d.ts +6 -1
  56. package/dist/utils/session.d.ts +2 -2
  57. package/oh-my-opencode-slim.schema.json +18 -1
  58. package/package.json +1 -1
  59. package/src/skills/clonedeps/SKILL.md +2 -2
  60. package/src/skills/clonedeps/codemap.md +23 -32
  61. package/src/skills/codemap/SKILL.md +2 -2
  62. package/src/skills/codemap.md +63 -36
  63. package/src/skills/loop-engineering/SKILL.md +30 -0
  64. package/src/skills/oh-my-opencode-slim/SKILL.md +3 -3
  65. package/src/skills/reflect/SKILL.md +133 -0
  66. package/src/skills/release-smoke-test/SKILL.md +159 -0
  67. package/src/skills/simplify/SKILL.md +6 -6
@@ -3,28 +3,28 @@ export declare const GENERATED_PRESETS: readonly ["openai", "opencode-go"];
3
3
  export declare const MODEL_MAPPINGS: {
4
4
  readonly openai: {
5
5
  readonly orchestrator: {
6
- readonly model: "openai/gpt-5.5";
6
+ readonly model: "openai/gpt-5.6-terra";
7
7
  readonly variant: "medium";
8
8
  };
9
9
  readonly oracle: {
10
- readonly model: "openai/gpt-5.5";
10
+ readonly model: "openai/gpt-5.6-sol";
11
11
  readonly variant: "high";
12
12
  };
13
13
  readonly librarian: {
14
- readonly model: "openai/gpt-5.4-mini";
14
+ readonly model: "openai/gpt-5.6-luna";
15
15
  readonly variant: "low";
16
16
  };
17
17
  readonly explorer: {
18
- readonly model: "openai/gpt-5.4-mini";
18
+ readonly model: "openai/gpt-5.6-luna";
19
19
  readonly variant: "low";
20
20
  };
21
21
  readonly designer: {
22
- readonly model: "openai/gpt-5.4-mini";
22
+ readonly model: "openai/gpt-5.6-luna";
23
23
  readonly variant: "medium";
24
24
  };
25
25
  readonly fixer: {
26
- readonly model: "openai/gpt-5.5";
27
- readonly variant: "low";
26
+ readonly model: "openai/gpt-5.6-luna";
27
+ readonly variant: "medium";
28
28
  };
29
29
  };
30
30
  readonly kimi: {
@@ -104,24 +104,20 @@ export declare const MODEL_MAPPINGS: {
104
104
  };
105
105
  readonly 'opencode-go': {
106
106
  readonly orchestrator: {
107
- readonly model: "opencode-go/glm-5.1";
107
+ readonly model: "opencode-go/glm-5.2";
108
108
  };
109
109
  readonly oracle: {
110
- readonly model: "opencode-go/deepseek-v4-pro";
110
+ readonly model: "opencode-go/qwen3.7-max";
111
111
  readonly variant: "max";
112
112
  };
113
- readonly council: {
114
- readonly model: "opencode-go/deepseek-v4-pro";
115
- readonly variant: "high";
116
- };
117
113
  readonly librarian: {
118
- readonly model: "opencode-go/minimax-m2.7";
114
+ readonly model: "opencode-go/deepseek-v4-flash";
119
115
  };
120
116
  readonly explorer: {
121
- readonly model: "opencode-go/minimax-m2.7";
117
+ readonly model: "opencode-go/deepseek-v4-flash";
122
118
  };
123
119
  readonly designer: {
124
- readonly model: "opencode-go/kimi-k2.6";
120
+ readonly model: "opencode-go/kimi-k2.7-code";
125
121
  readonly variant: "medium";
126
122
  };
127
123
  readonly fixer: {
@@ -3,7 +3,7 @@
3
3
  * permission grants but is NOT installed by this plugin's CLI.
4
4
  */
5
5
  export interface PermissionOnlySkill {
6
- /** Skill name must match the name OpenCode uses for permission checks */
6
+ /** Skill name - must match the name OpenCode uses for permission checks */
7
7
  name: string;
8
8
  /** List of agents that should auto-allow this skill */
9
9
  allowedAgents: string[];
@@ -12,7 +12,7 @@ export interface PermissionOnlySkill {
12
12
  }
13
13
  /**
14
14
  * Skills managed externally (not installed by this plugin's CLI).
15
- * Entries here only affect agent permission grants nothing is installed.
15
+ * Entries here only affect agent permission grants - nothing is installed.
16
16
  */
17
17
  export declare const PERMISSION_ONLY_SKILLS: PermissionOnlySkill[];
18
18
  /**
@@ -17,8 +17,17 @@ export declare class CompanionManager {
17
17
  private readonly busyAgentSessions;
18
18
  private readonly config?;
19
19
  private companionProcess;
20
+ private wasSpawner;
21
+ private spawnedCompanionPid;
20
22
  constructor(sessionId: string, cwd: string, config?: CompanionConfig);
21
23
  onLoad(): void;
24
+ /**
25
+ * Register this manager behind a single process `exit` listener. Re-inits for
26
+ * the same OpenCode session dispose the superseded manager immediately so its
27
+ * detached child does not survive until process exit.
28
+ */
29
+ private registerActiveManager;
30
+ private static disposeActiveManagers;
22
31
  /**
23
32
  * Feed every session.status event here, with the agent name resolved
24
33
  * from sessionAgentMap. Orchestrator sessions drive overall status;
@@ -33,6 +42,7 @@ export declare class CompanionManager {
33
42
  onWaitingInput(): void;
34
43
  onInputResolved(): void;
35
44
  onExit(): void;
45
+ private removeOwnedPidFileIfNoSessionsRemain;
36
46
  /** One entry per running agent instance (two fixers → two cells). */
37
47
  private activeAgents;
38
48
  private flush;
@@ -15,7 +15,8 @@ export declare const POLL_INTERVAL_BACKGROUND_MS = 2000;
15
15
  export declare const MAX_POLL_TIME_MS: number;
16
16
  export declare const DEFAULT_MAX_SUBAGENT_DEPTH = 3;
17
17
  export declare const PHASE_REMINDER_TEXT = "!IMPORTANT! Scheduler workflow: plan lanes/dependencies \u2192 dispatch background specialists \u2192 track task IDs \u2192 wait for hook-driven completion \u2192 reconcile terminal results \u2192 verify. Do not poll running jobs, consume running-job output, or advance dependent work. !END!";
18
- export declare const PHASE_REMINDER = "<internal_reminder>!IMPORTANT! Scheduler workflow: plan lanes/dependencies \u2192 dispatch background specialists \u2192 track task IDs \u2192 wait for hook-driven completion \u2192 reconcile terminal results \u2192 verify. Do not poll running jobs, consume running-job output, or advance dependent work. !END!</internal_reminder>";
18
+ export declare function formatSystemReminder(text: string): string;
19
+ export declare const PHASE_REMINDER: string;
19
20
  export declare const WRITABLE_FILE_OPERATIONS_RULES = "**File Operations Rules**:\n- 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.\n- Use bash for execution and automation: git, package managers, tests, builds, scripts, diagnostics, and shell-native filesystem operations.\n- Shell is acceptable for bulk or mechanical filesystem changes when it is clearer or safer than many individual edits (for example: truncate generated logs, remove build artifacts, batch rename/move files), especially when the user explicitly asks for that shell operation.\n- Before destructive or broad shell operations, verify the target set and quote paths. Prefer a dry-run/listing first when practical.\n- Do not use cat/head/tail/sed/awk only to read code into context; use read/grep unless a shell pipeline is genuinely the better diagnostic.";
20
21
  export declare const READONLY_FILE_OPERATIONS_RULES = "**File Operations Rules**:\n- READ-ONLY: inspect and report; do not modify files.\n- Prefer dedicated file tools for codebase inspection: glob/grep/ast_grep_search for discovery and read for file contents.\n- Bash is allowed for non-mutating diagnostics and shell-native inspection when it is the clearest tool, but not for modifying files.\n- Do not use cat/head/tail/sed/awk only to read code into context; use read/grep unless a shell pipeline is genuinely the better diagnostic.";
21
22
  export declare const NO_SHELL_READONLY_FILE_OPERATIONS_RULES = "**File Operations Rules**:\n- READ-ONLY: inspect and report; do not modify files.\n- Use glob/grep/ast_grep_search for discovery and read for file contents.\n- Do not use bash or shell commands.";
@@ -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;
@@ -76,10 +76,13 @@ export declare function loadPluginConfig(directory: string, options?: LoadPlugin
76
76
  * then falls back to the root prompts directory.
77
77
  *
78
78
  * @param agentName - Name of the agent (e.g., "orchestrator", "explorer")
79
- * @param preset - Optional preset name for preset-scoped prompt lookup
79
+ * @param optionsOrPreset - Optional preset name or options configuration
80
80
  * @returns Object with prompt and/or appendPrompt if files exist
81
81
  */
82
- export declare function loadAgentPrompt(agentName: string, preset?: string): {
82
+ export declare function loadAgentPrompt(agentName: string, optionsOrPreset?: string | {
83
+ preset?: string;
84
+ projectDirectory?: string;
85
+ }): {
83
86
  prompt?: string;
84
87
  appendPrompt?: string;
85
88
  };
@@ -66,6 +66,7 @@ export declare const MultiplexerTypeSchema: z.ZodEnum<{
66
66
  auto: "auto";
67
67
  tmux: "tmux";
68
68
  zellij: "zellij";
69
+ herdr: "herdr";
69
70
  none: "none";
70
71
  }>;
71
72
  export type MultiplexerType = z.infer<typeof MultiplexerTypeSchema>;
@@ -95,6 +96,7 @@ export declare const MultiplexerConfigSchema: z.ZodObject<{
95
96
  auto: "auto";
96
97
  tmux: "tmux";
97
98
  zellij: "zellij";
99
+ herdr: "herdr";
98
100
  none: "none";
99
101
  }>>;
100
102
  layout: z.ZodDefault<z.ZodEnum<{
@@ -175,7 +177,9 @@ export declare const FailoverConfigSchema: z.ZodObject<{
175
177
  enabled: z.ZodDefault<z.ZodBoolean>;
176
178
  timeoutMs: z.ZodDefault<z.ZodNumber>;
177
179
  retryDelayMs: z.ZodDefault<z.ZodNumber>;
180
+ maxRetries: z.ZodDefault<z.ZodNumber>;
178
181
  retry_on_empty: z.ZodDefault<z.ZodBoolean>;
182
+ runtimeOverride: z.ZodDefault<z.ZodBoolean>;
179
183
  }, z.core.$strict>;
180
184
  export type FailoverConfig = z.infer<typeof FailoverConfigSchema>;
181
185
  export declare const CompanionConfigSchema: z.ZodObject<{
@@ -247,6 +251,7 @@ export type AcpAgentsConfig = z.infer<typeof AcpAgentsConfigSchema>;
247
251
  export declare const PluginConfigSchema: z.ZodObject<{
248
252
  preset: z.ZodOptional<z.ZodString>;
249
253
  setDefaultAgent: z.ZodOptional<z.ZodBoolean>;
254
+ compactSidebar: z.ZodOptional<z.ZodBoolean>;
250
255
  autoUpdate: z.ZodOptional<z.ZodBoolean>;
251
256
  presets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodObject<{
252
257
  model: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
@@ -285,6 +290,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
285
290
  auto: "auto";
286
291
  tmux: "tmux";
287
292
  zellij: "zellij";
293
+ herdr: "herdr";
288
294
  none: "none";
289
295
  }>>;
290
296
  layout: z.ZodDefault<z.ZodEnum<{
@@ -333,13 +339,16 @@ export declare const PluginConfigSchema: z.ZodObject<{
333
339
  enabled: z.ZodDefault<z.ZodBoolean>;
334
340
  timeoutMs: z.ZodDefault<z.ZodNumber>;
335
341
  retryDelayMs: z.ZodDefault<z.ZodNumber>;
342
+ maxRetries: z.ZodDefault<z.ZodNumber>;
336
343
  retry_on_empty: z.ZodDefault<z.ZodBoolean>;
344
+ runtimeOverride: z.ZodDefault<z.ZodBoolean>;
337
345
  }, z.core.$strict>>;
338
346
  council: z.ZodOptional<z.ZodPipe<z.ZodObject<{
339
347
  presets: z.ZodRecord<z.ZodString, z.ZodPipe<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>, z.ZodTransform<Record<string, {
340
348
  model: string;
341
- variant?: string | undefined;
342
- prompt?: string | undefined;
349
+ variant: string | undefined;
350
+ prompt: string | undefined;
351
+ models: import("./council-schema").CouncillorModelEntry[];
343
352
  }>, Record<string, Record<string, unknown>>>>>;
344
353
  timeout: z.ZodDefault<z.ZodNumber>;
345
354
  default_preset: z.ZodDefault<z.ZodString>;
@@ -352,8 +361,9 @@ export declare const PluginConfigSchema: z.ZodObject<{
352
361
  }, z.core.$strip>, z.ZodTransform<{
353
362
  presets: Record<string, Record<string, {
354
363
  model: string;
355
- variant?: string | undefined;
356
- prompt?: string | undefined;
364
+ variant: string | undefined;
365
+ prompt: string | undefined;
366
+ models: import("./council-schema").CouncillorModelEntry[];
357
367
  }>>;
358
368
  timeout: number;
359
369
  default_preset: string;
@@ -364,8 +374,9 @@ export declare const PluginConfigSchema: z.ZodObject<{
364
374
  }, {
365
375
  presets: Record<string, Record<string, {
366
376
  model: string;
367
- variant?: string | undefined;
368
- prompt?: string | undefined;
377
+ variant: string | undefined;
378
+ prompt: string | undefined;
379
+ models: import("./council-schema").CouncillorModelEntry[];
369
380
  }>>;
370
381
  timeout: number;
371
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
  }
@@ -2,8 +2,66 @@ export interface SkillSyncResult {
2
2
  installed: string[];
3
3
  skippedExisting: string[];
4
4
  failed: string[];
5
+ staged: string[];
6
+ adopted: string[];
7
+ customized: string[];
5
8
  }
9
+ export interface SkillManifestEntry {
10
+ status: 'managed' | 'customized' | 'deleted' | 'conflict';
11
+ packageVersion: string;
12
+ sourceHash: string;
13
+ lastManagedHash: string;
14
+ lastSeenHash: string;
15
+ stagedPath?: string;
16
+ updatedAt: string;
17
+ }
18
+ export interface SkillsManifest {
19
+ schemaVersion: number;
20
+ updatedAt: string;
21
+ skills: Record<string, SkillManifestEntry>;
22
+ }
23
+ interface ManagedSkillSource {
24
+ name: string;
25
+ sourcePath: string;
26
+ }
27
+ interface SkillSyncOptions {
28
+ skills?: ManagedSkillSource[];
29
+ }
30
+ /**
31
+ * Hashes of historically managed versions of skills.
32
+ * When a release changes skill content, this table must be populated
33
+ * from the published npm package tarballs to allow upgrading existing users.
34
+ *
35
+ * How to populate:
36
+ * 1. Download previous releases of the npm package: `npm pack oh-my-opencode-slim@<version>`
37
+ * 2. Compute directory hash for each legacy skill directory inside the unpacked tarball:
38
+ * `import { computeDirectoryHash } from './skill-sync';`
39
+ * `const hash = computeDirectoryHash('path/to/extracted/package/src/skills/<skill-name>');`
40
+ * 3. Append the hash to the skill's string array below:
41
+ * ```typescript
42
+ * export const LEGACY_MANAGED_SKILL_HASHES: Record<string, string[]> = {
43
+ * 'simplify': ['hash1', 'hash2'],
44
+ * 'codemap': ['hash3']
45
+ * };
46
+ * ```
47
+ */
48
+ export declare const LEGACY_MANAGED_SKILL_HASHES: Record<string, string[]>;
49
+ /**
50
+ * Computes a deterministic SHA-256 hash of a directory's files.
51
+ */
52
+ export declare function computeDirectoryHash(dirPath: string): string;
53
+ /**
54
+ * Acquires a simple lock under .oh-my-opencode-slim.
55
+ * Avoids stealing active locks purely by time; writes owner metadata
56
+ * and only steals dead same-host pid if detectable.
57
+ */
58
+ export declare function acquireLock(lockDir: string): boolean;
59
+ /**
60
+ * Releases the lock.
61
+ */
62
+ export declare function releaseLock(lockDir: string): void;
6
63
  /**
7
64
  * Synchronizes bundled skills from the newly installed package root to OpenCode config skills directory.
8
65
  */
9
- export declare function syncBundledSkillsFromPackage(packageRoot: string): SkillSyncResult;
66
+ export declare function syncBundledSkillsFromPackage(packageRoot: string, options?: SkillSyncOptions): SkillSyncResult;
67
+ export {};
@@ -5,7 +5,6 @@
5
5
  */
6
6
  import type { PluginInput } from '@opencode-ai/plugin';
7
7
  import { type PluginConfig } from '../../config';
8
- import type { MessageWithParts } from '../types';
9
8
  type SkillRule = 'allow' | 'ask' | 'deny';
10
9
  declare function filterAvailableSkillsText(text: string, permissionRules: Record<string, SkillRule>): string;
11
10
  /**
@@ -14,7 +13,7 @@ declare function filterAvailableSkillsText(text: string, permissionRules: Record
14
13
  */
15
14
  export declare function createFilterAvailableSkillsHook(_ctx: PluginInput, config: PluginConfig): {
16
15
  'experimental.chat.messages.transform': (_input: Record<string, never>, output: {
17
- messages: MessageWithParts[];
16
+ messages?: unknown;
18
17
  }) => Promise<void>;
19
18
  };
20
19
  export { filterAvailableSkillsText };
@@ -6,7 +6,7 @@
6
6
  * 1. Looks up the next untried model in the agent's configured chain
7
7
  * 2. Aborts the rate-limited prompt via client.session.abort()
8
8
  * 3. Re-queues the last user message via client.session.promptAsync()
9
- * with the new model promptAsync returns immediately so we never
9
+ * with the new model - promptAsync returns immediately so we never
10
10
  * block the event handler waiting for a full LLM response.
11
11
  *
12
12
  * This mirrors the same fallback loop used for delegated sessions, but operates
@@ -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) */
@@ -41,18 +51,46 @@ export declare class ForegroundFallbackManager {
41
51
  private readonly inProgress;
42
52
  /** sessionID → timestamp of last trigger (for deduplication) */
43
53
  private readonly lastTrigger;
54
+ /** sessionID → model in use when lastTrigger was set; dedup is bypassed
55
+ * when the model has changed, allowing the cascade to continue when a
56
+ * new fallback model also fails within the dedup window. */
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;
44
64
  constructor(client: OpencodeClient,
45
65
  /**
46
66
  * Ordered fallback chains per agent.
47
67
  * e.g. { orchestrator: ['anthropic/claude-opus-4-5', 'openai/gpt-4o'] }
48
68
  * The first model that hasn't been tried yet is selected on each fallback.
49
69
  */
50
- 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);
51
80
  /**
52
81
  * Process an OpenCode plugin event.
53
82
  * Call this from the plugin's `event` hook for every event received.
54
83
  */
55
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;
56
94
  private tryFallback;
57
95
  /**
58
96
  * Determine the fallback chain to use for a session.
@@ -1,4 +1,4 @@
1
- import type { MessageWithParts } from './types';
1
+ import { type MessageWithParts } from './types';
2
2
  export declare function processImageAttachments(args: {
3
3
  messages: MessageWithParts[];
4
4
  workDir: string;
@@ -8,7 +8,9 @@ export { createFilterAvailableSkillsHook } from './filter-available-skills';
8
8
  export { ForegroundFallbackManager, isRateLimitError, } from './foreground-fallback';
9
9
  export { processImageAttachments } from './image-hook';
10
10
  export { createJsonErrorRecoveryHook } from './json-error-recovery/hook';
11
+ export { createLoopCommandHook } from './loop-command';
11
12
  export { createPhaseReminderHook } from './phase-reminder';
12
13
  export { createPostFileToolNudgeHook } from './post-file-tool-nudge';
13
14
  export { createReflectCommandHook } from './reflect';
15
+ export { SessionLifecycle } from './session-lifecycle';
14
16
  export { createTaskSessionManagerHook } from './task-session-manager';
@@ -0,0 +1,13 @@
1
+ export declare function createLoopCommandHook(): {
2
+ registerCommand: (config: Record<string, unknown>) => void;
3
+ handleCommandExecuteBefore: (input: {
4
+ command: string;
5
+ sessionID: string;
6
+ arguments: string;
7
+ }, output: {
8
+ parts: Array<{
9
+ type: string;
10
+ text?: string;
11
+ }>;
12
+ }) => Promise<void>;
13
+ };
@@ -6,15 +6,16 @@
6
6
  * of the user's actual turn.
7
7
  */
8
8
  import { PHASE_REMINDER } from '../../config/constants';
9
- import type { MessageWithParts } from '../types';
9
+ import type { SessionLifecycle } from '../session-lifecycle';
10
10
  export { PHASE_REMINDER };
11
+ export declare const PHASE_REMINDER_METADATA_KEY = "oh-my-opencode-slim.phaseReminder";
11
12
  /**
12
13
  * Creates the experimental.chat.messages.transform hook for phase reminder injection.
13
14
  * This hook runs right before sending to API, so it doesn't affect UI display.
14
15
  * Only injects for the orchestrator agent.
15
16
  */
16
- export declare function createPhaseReminderHook(): {
17
+ export declare function createPhaseReminderHook(coordinator?: SessionLifecycle): {
17
18
  'experimental.chat.messages.transform': (_input: Record<string, never>, output: {
18
- messages: MessageWithParts[];
19
+ messages?: unknown;
19
20
  }) => Promise<void>;
20
21
  };
@@ -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,12 +1,23 @@
1
1
  import type { PluginInput } from '@opencode-ai/plugin';
2
- import { BackgroundJobBoard } from '../../utils';
3
- import type { MessageWithParts } from '../types';
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";
4
5
  export declare function createTaskSessionManagerHook(_ctx: PluginInput, options: {
5
6
  maxSessionsPerAgent: number;
6
7
  readContextMinLines?: number;
7
8
  readContextMaxFiles?: number;
8
- backgroundJobBoard?: BackgroundJobBoard;
9
+ backgroundJobBoard?: BackgroundJobStore;
9
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;
10
21
  }): {
11
22
  'tool.execute.before': (input: {
12
23
  tool: string;
@@ -24,7 +35,7 @@ export declare function createTaskSessionManagerHook(_ctx: PluginInput, options:
24
35
  metadata?: unknown;
25
36
  }) => Promise<void>;
26
37
  'experimental.chat.messages.transform': (_input: Record<string, never>, output: {
27
- messages: MessageWithParts[];
38
+ messages?: unknown;
28
39
  }) => Promise<void>;
29
40
  event: (input: {
30
41
  event: {