oh-my-opencode-slim 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <img src="img/team.png" alt="Pantheon agents" width="420">
3
3
  <p><i>Six divine beings emerged from the dawn of code, each an immortal master of their craft await your command to forge order from chaos and build what was once thought impossible.</i></p>
4
4
  <p><b>Open Multi Agent Suite</b> · Mix any models · Auto delegate tasks</p>
5
- <p><a href="https://moltfounders.com/project/0f5874c7-9291-415b-9622-7509d96a2c73"><img src="https://moltfounders.com/badges/4.png" alt="MoltFounders" height="30"></a></p>
5
+ <p><a href="https://moltfounders.com/jobs/09d1c6e7-9e0e-4683-8d78-e2376aaa2333"><img src="https://moltfounders.com/badges/4.png" alt="MoltFounders" height="30"></a></p>
6
6
  </div>
7
7
 
8
8
  ---
@@ -259,6 +259,6 @@ MIT
259
259
  ---
260
260
 
261
261
  <!-- MoltFounders Banner -->
262
- <a href="https://moltfounders.com/project/0f5874c7-9291-415b-9622-7509d96a2c73">
262
+ <a href="https://moltfounders.com/jobs/09d1c6e7-9e0e-4683-8d78-e2376aaa2333">
263
263
  <img src="img/moltfounders-banner.png" alt="MoltFounders - The Agent Co-Founder Network">
264
264
  </a>
@@ -3,5 +3,13 @@ export interface AgentDefinition {
3
3
  name: string;
4
4
  description?: string;
5
5
  config: AgentConfig;
6
+ /** Priority-ordered model entries for runtime fallback resolution. */
7
+ _modelArray?: Array<{
8
+ id: string;
9
+ variant?: string;
10
+ }>;
6
11
  }
7
- export declare function createOrchestratorAgent(model: string, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
12
+ export declare function createOrchestratorAgent(model?: string | Array<string | {
13
+ id: string;
14
+ variant?: string;
15
+ }>, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
package/dist/cli/index.js CHANGED
@@ -80,11 +80,11 @@ function pickSupportChutesModel(models, primaryModel) {
80
80
  }
81
81
  // src/cli/config-io.ts
82
82
  import {
83
- copyFileSync,
84
- existsSync as existsSync2,
83
+ copyFileSync as copyFileSync2,
84
+ existsSync as existsSync3,
85
85
  readFileSync,
86
86
  renameSync,
87
- statSync,
87
+ statSync as statSync2,
88
88
  writeFileSync
89
89
  } from "fs";
90
90
 
@@ -13704,7 +13704,16 @@ var FallbackChainsSchema = exports_external.object({
13704
13704
  fixer: AgentModelChainSchema.optional()
13705
13705
  }).catchall(AgentModelChainSchema);
13706
13706
  var AgentOverrideConfigSchema = exports_external.object({
13707
- model: exports_external.string().optional(),
13707
+ model: exports_external.union([
13708
+ exports_external.string(),
13709
+ exports_external.array(exports_external.union([
13710
+ exports_external.string(),
13711
+ exports_external.object({
13712
+ id: exports_external.string(),
13713
+ variant: exports_external.string().optional()
13714
+ })
13715
+ ]))
13716
+ ]).optional(),
13708
13717
  temperature: exports_external.number().min(0).max(2).optional(),
13709
13718
  variant: exports_external.string().optional().catch(undefined),
13710
13719
  skills: exports_external.array(exports_external.string()).optional(),
@@ -13729,7 +13738,7 @@ var BackgroundTaskConfigSchema = exports_external.object({
13729
13738
  });
13730
13739
  var FailoverConfigSchema = exports_external.object({
13731
13740
  enabled: exports_external.boolean().default(true),
13732
- timeoutMs: exports_external.number().min(1000).max(120000).default(15000),
13741
+ timeoutMs: exports_external.number().min(0).default(15000),
13733
13742
  chains: FallbackChainsSchema.default({})
13734
13743
  });
13735
13744
  var PluginConfigSchema = exports_external.object({
@@ -13756,6 +13765,67 @@ var DEFAULT_AGENT_MCPS = {
13756
13765
 
13757
13766
  // src/cli/skills.ts
13758
13767
  import { spawnSync } from "child_process";
13768
+
13769
+ // src/cli/custom-skills.ts
13770
+ import {
13771
+ copyFileSync,
13772
+ existsSync as existsSync2,
13773
+ mkdirSync as mkdirSync2,
13774
+ readdirSync,
13775
+ statSync
13776
+ } from "fs";
13777
+ import { homedir as homedir2 } from "os";
13778
+ import { dirname, join as join2 } from "path";
13779
+ import { fileURLToPath } from "url";
13780
+ var CUSTOM_SKILLS = [
13781
+ {
13782
+ name: "cartography",
13783
+ description: "Repository understanding and hierarchical codemap generation",
13784
+ allowedAgents: ["orchestrator", "explorer"],
13785
+ sourcePath: "src/skills/cartography"
13786
+ }
13787
+ ];
13788
+ function getCustomSkillsDir() {
13789
+ return join2(homedir2(), ".config", "opencode", "skills");
13790
+ }
13791
+ function copyDirRecursive(src, dest) {
13792
+ if (!existsSync2(dest)) {
13793
+ mkdirSync2(dest, { recursive: true });
13794
+ }
13795
+ const entries = readdirSync(src);
13796
+ for (const entry of entries) {
13797
+ const srcPath = join2(src, entry);
13798
+ const destPath = join2(dest, entry);
13799
+ const stat = statSync(srcPath);
13800
+ if (stat.isDirectory()) {
13801
+ copyDirRecursive(srcPath, destPath);
13802
+ } else {
13803
+ const destDir = dirname(destPath);
13804
+ if (!existsSync2(destDir)) {
13805
+ mkdirSync2(destDir, { recursive: true });
13806
+ }
13807
+ copyFileSync(srcPath, destPath);
13808
+ }
13809
+ }
13810
+ }
13811
+ function installCustomSkill(skill) {
13812
+ try {
13813
+ const packageRoot = fileURLToPath(new URL("../..", import.meta.url));
13814
+ const sourcePath = join2(packageRoot, skill.sourcePath);
13815
+ const targetPath = join2(getCustomSkillsDir(), skill.name);
13816
+ if (!existsSync2(sourcePath)) {
13817
+ console.error(`Custom skill source not found: ${sourcePath}`);
13818
+ return false;
13819
+ }
13820
+ copyDirRecursive(sourcePath, targetPath);
13821
+ return true;
13822
+ } catch (error48) {
13823
+ console.error(`Failed to install custom skill: ${skill.name}`, error48);
13824
+ return false;
13825
+ }
13826
+ }
13827
+
13828
+ // src/cli/skills.ts
13759
13829
  var RECOMMENDED_SKILLS = [
13760
13830
  {
13761
13831
  name: "simplify",
@@ -13863,7 +13933,7 @@ var MODEL_MAPPINGS = {
13863
13933
  },
13864
13934
  antigravity: {
13865
13935
  orchestrator: { model: "google/antigravity-gemini-3-flash" },
13866
- oracle: { model: "google/antigravity-gemini-3-pro" },
13936
+ oracle: { model: "google/antigravity-gemini-3.1-pro" },
13867
13937
  librarian: {
13868
13938
  model: "google/antigravity-gemini-3-flash",
13869
13939
  variant: "low"
@@ -14203,9 +14273,9 @@ function stripJsonComments(json2) {
14203
14273
  }
14204
14274
  function parseConfigFile(path) {
14205
14275
  try {
14206
- if (!existsSync2(path))
14276
+ if (!existsSync3(path))
14207
14277
  return { config: null };
14208
- const stat = statSync(path);
14278
+ const stat = statSync2(path);
14209
14279
  if (stat.size === 0)
14210
14280
  return { config: null };
14211
14281
  const content = readFileSync(path, "utf-8");
@@ -14234,8 +14304,8 @@ function writeConfig(configPath, config2) {
14234
14304
  const bakPath = `${configPath}.bak`;
14235
14305
  const content = `${JSON.stringify(config2, null, 2)}
14236
14306
  `;
14237
- if (existsSync2(configPath)) {
14238
- copyFileSync(configPath, bakPath);
14307
+ if (existsSync3(configPath)) {
14308
+ copyFileSync2(configPath, bakPath);
14239
14309
  }
14240
14310
  writeFileSync(tmpPath, content);
14241
14311
  renameSync(tmpPath, configPath);
@@ -14284,8 +14354,8 @@ function writeLiteConfig(installConfig) {
14284
14354
  const bakPath = `${configPath}.bak`;
14285
14355
  const content = `${JSON.stringify(config2, null, 2)}
14286
14356
  `;
14287
- if (existsSync2(configPath)) {
14288
- copyFileSync(configPath, bakPath);
14357
+ if (existsSync3(configPath)) {
14358
+ copyFileSync2(configPath, bakPath);
14289
14359
  }
14290
14360
  writeFileSync(tmpPath, content);
14291
14361
  renameSync(tmpPath, configPath);
@@ -14368,8 +14438,8 @@ function addGoogleProvider() {
14368
14438
  const providers = config2.provider ?? {};
14369
14439
  providers.google = {
14370
14440
  models: {
14371
- "antigravity-gemini-3-pro": {
14372
- name: "Gemini 3 Pro (Antigravity)",
14441
+ "antigravity-gemini-3.1-pro": {
14442
+ name: "Gemini 3.1 Pro (Antigravity)",
14373
14443
  limit: { context: 1048576, output: 65535 },
14374
14444
  modalities: { input: ["text", "image", "pdf"], output: ["text"] },
14375
14445
  variants: {
@@ -14426,8 +14496,8 @@ function addGoogleProvider() {
14426
14496
  limit: { context: 1048576, output: 65536 },
14427
14497
  modalities: { input: ["text", "image", "pdf"], output: ["text"] }
14428
14498
  },
14429
- "gemini-3-pro-preview": {
14430
- name: "Gemini 3 Pro Preview (Gemini CLI)",
14499
+ "gemini-3.1-pro-preview": {
14500
+ name: "Gemini 3.1 Pro Preview (Gemini CLI)",
14431
14501
  limit: { context: 1048576, output: 65535 },
14432
14502
  modalities: { input: ["text", "image", "pdf"], output: ["text"] }
14433
14503
  }
@@ -15751,7 +15821,7 @@ async function fetchExternalModelSignals(options) {
15751
15821
  return { signals: aggregate, warnings };
15752
15822
  }
15753
15823
  // src/cli/system.ts
15754
- import { statSync as statSync2 } from "fs";
15824
+ import { statSync as statSync3 } from "fs";
15755
15825
  var cachedOpenCodePath = null;
15756
15826
  function getOpenCodePaths() {
15757
15827
  const home = process.env.HOME || process.env.USERPROFILE || "";
@@ -15793,7 +15863,7 @@ function resolveOpenCodePath() {
15793
15863
  if (opencodePath === "opencode")
15794
15864
  continue;
15795
15865
  try {
15796
- const stat = statSync2(opencodePath);
15866
+ const stat = statSync3(opencodePath);
15797
15867
  if (stat.isFile()) {
15798
15868
  cachedOpenCodePath = opencodePath;
15799
15869
  return opencodePath;
@@ -16023,65 +16093,6 @@ function pickSupportOpenCodeModel(models, primaryModel) {
16023
16093
  }, primaryModel);
16024
16094
  return support;
16025
16095
  }
16026
- // src/cli/custom-skills.ts
16027
- import {
16028
- copyFileSync as copyFileSync2,
16029
- existsSync as existsSync3,
16030
- mkdirSync as mkdirSync2,
16031
- readdirSync,
16032
- statSync as statSync3
16033
- } from "fs";
16034
- import { homedir as homedir2 } from "os";
16035
- import { dirname, join as join3 } from "path";
16036
- import { fileURLToPath } from "url";
16037
- var CUSTOM_SKILLS = [
16038
- {
16039
- name: "cartography",
16040
- description: "Repository understanding and hierarchical codemap generation",
16041
- allowedAgents: ["orchestrator"],
16042
- sourcePath: "src/skills/cartography"
16043
- }
16044
- ];
16045
- function getCustomSkillsDir() {
16046
- return join3(homedir2(), ".config", "opencode", "skills");
16047
- }
16048
- function copyDirRecursive(src, dest) {
16049
- if (!existsSync3(dest)) {
16050
- mkdirSync2(dest, { recursive: true });
16051
- }
16052
- const entries = readdirSync(src);
16053
- for (const entry of entries) {
16054
- const srcPath = join3(src, entry);
16055
- const destPath = join3(dest, entry);
16056
- const stat = statSync3(srcPath);
16057
- if (stat.isDirectory()) {
16058
- copyDirRecursive(srcPath, destPath);
16059
- } else {
16060
- const destDir = dirname(destPath);
16061
- if (!existsSync3(destDir)) {
16062
- mkdirSync2(destDir, { recursive: true });
16063
- }
16064
- copyFileSync2(srcPath, destPath);
16065
- }
16066
- }
16067
- }
16068
- function installCustomSkill(skill) {
16069
- try {
16070
- const packageRoot = fileURLToPath(new URL("../..", import.meta.url));
16071
- const sourcePath = join3(packageRoot, skill.sourcePath);
16072
- const targetPath = join3(getCustomSkillsDir(), skill.name);
16073
- if (!existsSync3(sourcePath)) {
16074
- console.error(`Custom skill source not found: ${sourcePath}`);
16075
- return false;
16076
- }
16077
- copyDirRecursive(sourcePath, targetPath);
16078
- return true;
16079
- } catch (error48) {
16080
- console.error(`Failed to install custom skill: ${skill.name}`, error48);
16081
- return false;
16082
- }
16083
- }
16084
-
16085
16096
  // src/cli/install.ts
16086
16097
  var GREEN = "\x1B[32m";
16087
16098
  var BLUE = "\x1B[34m";
@@ -16430,8 +16441,8 @@ async function runManualSetupMode(rl, detected, modelsOnly = false) {
16430
16441
  }
16431
16442
  if (antigravity === "yes") {
16432
16443
  availableModels.push({
16433
- model: "google/antigravity-gemini-3-pro",
16434
- name: "Gemini 3 Pro"
16444
+ model: "google/antigravity-gemini-3.1-pro",
16445
+ name: "Gemini 3.1 Pro"
16435
16446
  });
16436
16447
  availableModels.push({
16437
16448
  model: "google/antigravity-gemini-3-flash",
@@ -130,7 +130,7 @@ export declare const MODEL_MAPPINGS: {
130
130
  readonly model: "google/antigravity-gemini-3-flash";
131
131
  };
132
132
  readonly oracle: {
133
- readonly model: "google/antigravity-gemini-3-pro";
133
+ readonly model: "google/antigravity-gemini-3.1-pro";
134
134
  };
135
135
  readonly librarian: {
136
136
  readonly model: "google/antigravity-gemini-3-flash";
@@ -15,11 +15,28 @@ export interface RecommendedSkill {
15
15
  /** Optional commands to run after the skill is added */
16
16
  postInstallCommands?: string[];
17
17
  }
18
+ /**
19
+ * A skill that is managed externally (e.g. user-installed) and needs
20
+ * permission grants but is NOT installed by this plugin's CLI.
21
+ */
22
+ export interface PermissionOnlySkill {
23
+ /** Skill name — must match the name OpenCode uses for permission checks */
24
+ name: string;
25
+ /** List of agents that should auto-allow this skill */
26
+ allowedAgents: string[];
27
+ /** Human-readable description (for documentation only) */
28
+ description: string;
29
+ }
18
30
  /**
19
31
  * List of recommended skills.
20
32
  * Add new skills here to include them in the installation flow.
21
33
  */
22
34
  export declare const RECOMMENDED_SKILLS: RecommendedSkill[];
35
+ /**
36
+ * Skills managed externally (not installed by this plugin's CLI).
37
+ * Entries here only affect agent permission grants — nothing is installed.
38
+ */
39
+ export declare const PERMISSION_ONLY_SKILLS: PermissionOnlySkill[];
23
40
  /**
24
41
  * Install a skill using `npx skills add`.
25
42
  * @param skill - The skill to install
@@ -4,7 +4,7 @@ export declare const ORCHESTRATOR_NAME: "orchestrator";
4
4
  export declare const ALL_AGENT_NAMES: readonly ["orchestrator", "explorer", "librarian", "oracle", "designer", "fixer"];
5
5
  export type AgentName = (typeof ALL_AGENT_NAMES)[number];
6
6
  export declare const SUBAGENT_DELEGATION_RULES: Record<AgentName, readonly string[]>;
7
- export declare const DEFAULT_MODELS: Record<AgentName, string>;
7
+ export declare const DEFAULT_MODELS: Record<AgentName, string | undefined>;
8
8
  export declare const POLL_INTERVAL_MS = 500;
9
9
  export declare const POLL_INTERVAL_SLOW_MS = 1000;
10
10
  export declare const POLL_INTERVAL_BACKGROUND_MS = 2000;
@@ -17,11 +17,14 @@ export declare function loadPluginConfig(directory: string): PluginConfig;
17
17
  /**
18
18
  * Load custom prompt for an agent from the prompts directory.
19
19
  * Checks for {agent}.md (replaces default) and {agent}_append.md (appends to default).
20
+ * If preset is provided and safe for paths, it first checks {preset}/ subdirectory,
21
+ * then falls back to the root prompts directory.
20
22
  *
21
23
  * @param agentName - Name of the agent (e.g., "orchestrator", "explorer")
24
+ * @param preset - Optional preset name for preset-scoped prompt lookup
22
25
  * @returns Object with prompt and/or appendPrompt if files exist
23
26
  */
24
- export declare function loadAgentPrompt(agentName: string): {
27
+ export declare function loadAgentPrompt(agentName: string, preset?: string): {
25
28
  prompt?: string;
26
29
  appendPrompt?: string;
27
30
  };
@@ -50,7 +50,10 @@ export type ManualAgentPlan = z.infer<typeof ManualAgentPlanSchema>;
50
50
  export type ManualPlan = z.infer<typeof ManualPlanSchema>;
51
51
  export type FallbackAgentName = (typeof FALLBACK_AGENT_NAMES)[number];
52
52
  export declare const AgentOverrideConfigSchema: z.ZodObject<{
53
- model: z.ZodOptional<z.ZodString>;
53
+ model: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
54
+ id: z.ZodString;
55
+ variant: z.ZodOptional<z.ZodString>;
56
+ }, z.core.$strip>]>>]>>;
54
57
  temperature: z.ZodOptional<z.ZodNumber>;
55
58
  variant: z.ZodCatch<z.ZodOptional<z.ZodString>>;
56
59
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -77,8 +80,16 @@ export declare const TmuxConfigSchema: z.ZodObject<{
77
80
  }, z.core.$strip>;
78
81
  export type TmuxConfig = z.infer<typeof TmuxConfigSchema>;
79
82
  export type AgentOverrideConfig = z.infer<typeof AgentOverrideConfigSchema>;
83
+ /** Normalized model entry with optional per-model variant. */
84
+ export type ModelEntry = {
85
+ id: string;
86
+ variant?: string;
87
+ };
80
88
  export declare const PresetSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
81
- model: z.ZodOptional<z.ZodString>;
89
+ model: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
90
+ id: z.ZodString;
91
+ variant: z.ZodOptional<z.ZodString>;
92
+ }, z.core.$strip>]>>]>>;
82
93
  temperature: z.ZodOptional<z.ZodNumber>;
83
94
  variant: z.ZodCatch<z.ZodOptional<z.ZodString>>;
84
95
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -155,14 +166,20 @@ export declare const PluginConfigSchema: z.ZodObject<{
155
166
  }, z.core.$strip>;
156
167
  }, z.core.$strict>>;
157
168
  presets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodObject<{
158
- model: z.ZodOptional<z.ZodString>;
169
+ model: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
170
+ id: z.ZodString;
171
+ variant: z.ZodOptional<z.ZodString>;
172
+ }, z.core.$strip>]>>]>>;
159
173
  temperature: z.ZodOptional<z.ZodNumber>;
160
174
  variant: z.ZodCatch<z.ZodOptional<z.ZodString>>;
161
175
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
162
176
  mcps: z.ZodOptional<z.ZodArray<z.ZodString>>;
163
177
  }, z.core.$strip>>>>;
164
178
  agents: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
165
- model: z.ZodOptional<z.ZodString>;
179
+ model: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
180
+ id: z.ZodString;
181
+ variant: z.ZodOptional<z.ZodString>;
182
+ }, z.core.$strip>]>>]>>;
166
183
  temperature: z.ZodOptional<z.ZodNumber>;
167
184
  variant: z.ZodCatch<z.ZodOptional<z.ZodString>>;
168
185
  skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -0,0 +1,43 @@
1
+ import type { PluginInput } from '@opencode-ai/plugin';
2
+ import type { PluginConfig } from '../../config';
3
+ declare const AUTOPILOT_CONTINUE_PROMPT = "[AUTOPILOT] Continue executing work in this same session now. Do not acknowledge autopilot, do not ask for confirmation, and do not stop after a status message. Perform the next concrete action immediately (run a tool/command, edit files, or advance the active todo). Only stop when all todos are completed/cancelled or the user explicitly disables autopilot.";
4
+ interface MessagePart {
5
+ type: string;
6
+ text?: string;
7
+ }
8
+ interface TodoEntry {
9
+ status?: string;
10
+ }
11
+ type AutopilotCommand = 'enable' | 'disable' | 'status' | null;
12
+ declare function parseAutopilotCommand(text: string, keyword: string, disableKeyword: string): AutopilotCommand;
13
+ declare function extractTodoList(data: unknown): TodoEntry[];
14
+ declare function isActiveTodo(todo: TodoEntry): boolean;
15
+ export declare function createAutopilotHook(ctx: PluginInput, config: PluginConfig): {
16
+ 'chat.message': (input: {
17
+ sessionID: string;
18
+ agent?: string;
19
+ messageID?: string;
20
+ }, output: {
21
+ parts: MessagePart[];
22
+ }) => Promise<void>;
23
+ event: (input: {
24
+ event: {
25
+ type: string;
26
+ properties?: {
27
+ sessionID?: string;
28
+ status?: {
29
+ type?: string;
30
+ };
31
+ info?: {
32
+ id?: string;
33
+ role?: string;
34
+ sessionID?: string;
35
+ time?: {
36
+ completed?: number;
37
+ };
38
+ };
39
+ };
40
+ };
41
+ }) => Promise<void>;
42
+ };
43
+ export { AUTOPILOT_CONTINUE_PROMPT, extractTodoList, isActiveTodo, parseAutopilotCommand, };
@@ -0,0 +1,16 @@
1
+ import type { PluginInput, ProviderContext } from '@opencode-ai/plugin';
2
+ import type { Model, UserMessage } from '@opencode-ai/sdk';
3
+ interface ChatHeadersInput {
4
+ sessionID: string;
5
+ model: Model;
6
+ provider: ProviderContext;
7
+ message: UserMessage;
8
+ }
9
+ interface ChatHeadersOutput {
10
+ headers: Record<string, string>;
11
+ }
12
+ export declare function __resetInternalMarkerCacheForTesting(): void;
13
+ export declare function createChatHeadersHook(ctx: PluginInput): {
14
+ 'chat.headers': (input: ChatHeadersInput, output: ChatHeadersOutput) => Promise<void>;
15
+ };
16
+ export {};
@@ -0,0 +1,2 @@
1
+ import { type DetectedError } from './patterns';
2
+ export declare function buildRetryGuidance(errorInfo: DetectedError): string;
@@ -0,0 +1,8 @@
1
+ import type { PluginInput } from '@opencode-ai/plugin';
2
+ export declare function createDelegateTaskRetryHook(_ctx: PluginInput): {
3
+ 'tool.execute.after': (input: {
4
+ tool: string;
5
+ }, output: {
6
+ output: unknown;
7
+ }) => Promise<void>;
8
+ };
@@ -0,0 +1,4 @@
1
+ export { buildRetryGuidance } from './guidance';
2
+ export { createDelegateTaskRetryHook } from './hook';
3
+ export type { DelegateTaskErrorPattern, DetectedError } from './patterns';
4
+ export { DELEGATE_TASK_ERROR_PATTERNS, detectDelegateTaskError, } from './patterns';
@@ -0,0 +1,11 @@
1
+ export interface DelegateTaskErrorPattern {
2
+ pattern: string;
3
+ errorType: string;
4
+ fixHint: string;
5
+ }
6
+ export declare const DELEGATE_TASK_ERROR_PATTERNS: DelegateTaskErrorPattern[];
7
+ export interface DetectedError {
8
+ errorType: string;
9
+ originalOutput: string;
10
+ }
11
+ export declare function detectDelegateTaskError(output: string): DetectedError | null;
@@ -1,4 +1,7 @@
1
1
  export type { AutoUpdateCheckerOptions } from './auto-update-checker';
2
2
  export { createAutoUpdateCheckerHook } from './auto-update-checker';
3
+ export { createChatHeadersHook } from './chat-headers';
4
+ export { createDelegateTaskRetryHook } from './delegate-task-retry';
5
+ export { createJsonErrorRecoveryHook } from './json-error-recovery';
3
6
  export { createPhaseReminderHook } from './phase-reminder';
4
7
  export { createPostReadNudgeHook } from './post-read-nudge';
@@ -0,0 +1,18 @@
1
+ import type { PluginInput } from '@opencode-ai/plugin';
2
+ export declare const JSON_ERROR_TOOL_EXCLUDE_LIST: readonly ["bash", "read", "glob", "grep", "webfetch", "grep_app_searchgithub", "websearch_web_search_exa"];
3
+ export declare const JSON_ERROR_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
4
+ export declare const JSON_ERROR_REMINDER = "\n[JSON PARSE ERROR - IMMEDIATE ACTION REQUIRED]\n\nYou sent invalid JSON arguments. The system could not parse your tool call.\nSTOP and do this NOW:\n\n1. LOOK at the error message above to see what was expected vs what you sent.\n2. CORRECT your JSON syntax (missing braces, unescaped quotes, trailing commas, etc).\n3. RETRY the tool call with valid JSON.\n\nDO NOT repeat the exact same invalid call.\n";
5
+ interface ToolExecuteAfterInput {
6
+ tool: string;
7
+ sessionID: string;
8
+ callID: string;
9
+ }
10
+ interface ToolExecuteAfterOutput {
11
+ title: string;
12
+ output: unknown;
13
+ metadata: unknown;
14
+ }
15
+ export declare function createJsonErrorRecoveryHook(_ctx: PluginInput): {
16
+ 'tool.execute.after': (input: ToolExecuteAfterInput, output: ToolExecuteAfterOutput) => Promise<void>;
17
+ };
18
+ export {};
@@ -0,0 +1 @@
1
+ export { createJsonErrorRecoveryHook, JSON_ERROR_PATTERNS, JSON_ERROR_REMINDER, JSON_ERROR_TOOL_EXCLUDE_LIST, } from './hook';
@@ -1,3 +1,4 @@
1
+ export declare const PHASE_REMINDER = "<reminder>Recall Workflow Rules:\nUnderstand \u2192 find the best path (delegate based on rules and parallelize independent work) \u2192 execute \u2192 verify.\nIf delegating, launch the specialist in the same turn you mention it.</reminder>";
1
2
  interface MessageInfo {
2
3
  role: string;
3
4
  agent?: string;
package/dist/index.js CHANGED
@@ -3277,6 +3277,16 @@ var require_main = __commonJS((exports) => {
3277
3277
  exports.createMessageConnection = createMessageConnection;
3278
3278
  });
3279
3279
 
3280
+ // src/cli/custom-skills.ts
3281
+ var CUSTOM_SKILLS = [
3282
+ {
3283
+ name: "cartography",
3284
+ description: "Repository understanding and hierarchical codemap generation",
3285
+ allowedAgents: ["orchestrator", "explorer"],
3286
+ sourcePath: "src/skills/cartography"
3287
+ }
3288
+ ];
3289
+
3280
3290
  // src/cli/skills.ts
3281
3291
  var RECOMMENDED_SKILLS = [
3282
3292
  {
@@ -3298,6 +3308,13 @@ var RECOMMENDED_SKILLS = [
3298
3308
  ]
3299
3309
  }
3300
3310
  ];
3311
+ var PERMISSION_ONLY_SKILLS = [
3312
+ {
3313
+ name: "requesting-code-review",
3314
+ allowedAgents: ["oracle"],
3315
+ description: "Code review template for reviewer subagents in multi-step workflows"
3316
+ }
3317
+ ];
3301
3318
  function getSkillPermissionsForAgent(agentName, skillList) {
3302
3319
  const permissions = {
3303
3320
  "*": agentName === "orchestrator" ? "allow" : "deny"
@@ -3321,6 +3338,18 @@ function getSkillPermissionsForAgent(agentName, skillList) {
3321
3338
  permissions[skill.skillName] = "allow";
3322
3339
  }
3323
3340
  }
3341
+ for (const skill of CUSTOM_SKILLS) {
3342
+ const isAllowed = skill.allowedAgents.includes("*") || skill.allowedAgents.includes(agentName);
3343
+ if (isAllowed) {
3344
+ permissions[skill.name] = "allow";
3345
+ }
3346
+ }
3347
+ for (const skill of PERMISSION_ONLY_SKILLS) {
3348
+ const isAllowed = skill.allowedAgents.includes("*") || skill.allowedAgents.includes(agentName);
3349
+ if (isAllowed) {
3350
+ permissions[skill.name] = "allow";
3351
+ }
3352
+ }
3324
3353
  return permissions;
3325
3354
  }
3326
3355
 
@@ -3340,14 +3369,14 @@ var ORCHESTRATOR_NAME = "orchestrator";
3340
3369
  var ALL_AGENT_NAMES = [ORCHESTRATOR_NAME, ...SUBAGENT_NAMES];
3341
3370
  var SUBAGENT_DELEGATION_RULES = {
3342
3371
  orchestrator: SUBAGENT_NAMES,
3343
- fixer: ["explorer"],
3344
- designer: ["explorer"],
3372
+ fixer: [],
3373
+ designer: [],
3345
3374
  explorer: [],
3346
3375
  librarian: [],
3347
3376
  oracle: []
3348
3377
  };
3349
3378
  var DEFAULT_MODELS = {
3350
- orchestrator: "kimi-for-coding/k2p5",
3379
+ orchestrator: undefined,
3351
3380
  oracle: "openai/gpt-5.2-codex",
3352
3381
  librarian: "openai/gpt-5.1-codex-mini",
3353
3382
  explorer: "openai/gpt-5.1-codex-mini",
@@ -16985,7 +17014,16 @@ var FallbackChainsSchema = exports_external.object({
16985
17014
  fixer: AgentModelChainSchema.optional()
16986
17015
  }).catchall(AgentModelChainSchema);
16987
17016
  var AgentOverrideConfigSchema = exports_external.object({
16988
- model: exports_external.string().optional(),
17017
+ model: exports_external.union([
17018
+ exports_external.string(),
17019
+ exports_external.array(exports_external.union([
17020
+ exports_external.string(),
17021
+ exports_external.object({
17022
+ id: exports_external.string(),
17023
+ variant: exports_external.string().optional()
17024
+ })
17025
+ ]))
17026
+ ]).optional(),
16989
17027
  temperature: exports_external.number().min(0).max(2).optional(),
16990
17028
  variant: exports_external.string().optional().catch(undefined),
16991
17029
  skills: exports_external.array(exports_external.string()).optional(),
@@ -17010,7 +17048,7 @@ var BackgroundTaskConfigSchema = exports_external.object({
17010
17048
  });
17011
17049
  var FailoverConfigSchema = exports_external.object({
17012
17050
  enabled: exports_external.boolean().default(true),
17013
- timeoutMs: exports_external.number().min(1000).max(120000).default(15000),
17051
+ timeoutMs: exports_external.number().min(0).default(15000),
17014
17052
  chains: FallbackChainsSchema.default({})
17015
17053
  });
17016
17054
  var PluginConfigSchema = exports_external.object({
@@ -17109,25 +17147,27 @@ function loadPluginConfig(directory) {
17109
17147
  }
17110
17148
  return config2;
17111
17149
  }
17112
- function loadAgentPrompt(agentName) {
17150
+ function loadAgentPrompt(agentName, preset) {
17151
+ const presetDirName = preset && /^[a-zA-Z0-9_-]+$/.test(preset) ? preset : undefined;
17113
17152
  const promptsDir = path.join(getUserConfigDir(), "opencode", PROMPTS_DIR_NAME);
17153
+ const promptSearchDirs = presetDirName ? [path.join(promptsDir, presetDirName), promptsDir] : [promptsDir];
17114
17154
  const result = {};
17115
- const promptPath = path.join(promptsDir, `${agentName}.md`);
17116
- if (fs.existsSync(promptPath)) {
17117
- try {
17118
- result.prompt = fs.readFileSync(promptPath, "utf-8");
17119
- } catch (error48) {
17120
- console.warn(`[oh-my-opencode-slim] Error reading prompt file ${promptPath}:`, error48 instanceof Error ? error48.message : String(error48));
17121
- }
17122
- }
17123
- const appendPromptPath = path.join(promptsDir, `${agentName}_append.md`);
17124
- if (fs.existsSync(appendPromptPath)) {
17125
- try {
17126
- result.appendPrompt = fs.readFileSync(appendPromptPath, "utf-8");
17127
- } catch (error48) {
17128
- console.warn(`[oh-my-opencode-slim] Error reading append prompt file ${appendPromptPath}:`, error48 instanceof Error ? error48.message : String(error48));
17155
+ const readFirstPrompt = (fileName, errorPrefix) => {
17156
+ for (const dir of promptSearchDirs) {
17157
+ const promptPath = path.join(dir, fileName);
17158
+ if (!fs.existsSync(promptPath)) {
17159
+ continue;
17160
+ }
17161
+ try {
17162
+ return fs.readFileSync(promptPath, "utf-8");
17163
+ } catch (error48) {
17164
+ console.warn(`[oh-my-opencode-slim] ${errorPrefix} ${promptPath}:`, error48 instanceof Error ? error48.message : String(error48));
17165
+ }
17129
17166
  }
17130
- }
17167
+ return;
17168
+ };
17169
+ result.prompt = readFirstPrompt(`${agentName}.md`, "Error reading prompt file");
17170
+ result.appendPrompt = readFirstPrompt(`${agentName}_append.md`, "Error reading append prompt file");
17131
17171
  return result;
17132
17172
  }
17133
17173
  // src/config/utils.ts
@@ -17280,9 +17320,10 @@ var FIXER_PROMPT = `You are Fixer - a fast, focused implementation specialist.
17280
17320
 
17281
17321
  **Constraints**:
17282
17322
  - NO external research (no websearch, context7, grep_app)
17283
- - NO delegation (no background_task)
17323
+ - NO delegation (no background_task, no spawning subagents)
17284
17324
  - No multi-step research/planning; minimal execution sequence ok
17285
- - If context is insufficient, read the files listed; only ask for missing inputs you cannot retrieve
17325
+ - If context is insufficient: use grep/glob/lsp_diagnostics directly \u2014 do not delegate
17326
+ - Only ask for missing inputs you truly cannot retrieve yourself
17286
17327
 
17287
17328
  **Output Format**:
17288
17329
  <summary>
@@ -17504,6 +17545,10 @@ Balance: respect dependencies, avoid parallelizing what must be sequential.
17504
17545
  - Confirm specialists completed successfully
17505
17546
  - Verify solution meets requirements
17506
17547
 
17548
+ ## Agent Role Mapping
17549
+ When a workflow calls for an **implementer** subagent: dispatch \`@fixer\`. Fixer has enforced constraints (no research, no delegation, structured output) that match the implementer role exactly.
17550
+ When a workflow calls for a **reviewer** subagent: dispatch \`@oracle\`. Oracle has the depth for architectural review and access to code review skills.
17551
+
17507
17552
  </Workflow>
17508
17553
 
17509
17554
  <Communication>
@@ -17546,21 +17591,32 @@ function createOrchestratorAgent(model, customPrompt, customAppendPrompt) {
17546
17591
 
17547
17592
  ${customAppendPrompt}`;
17548
17593
  }
17549
- return {
17594
+ const definition = {
17550
17595
  name: "orchestrator",
17551
17596
  description: "AI coding orchestrator that delegates tasks to specialist agents for optimal quality, speed, and cost",
17552
17597
  config: {
17553
- model,
17554
17598
  temperature: 0.1,
17555
17599
  prompt
17556
17600
  }
17557
17601
  };
17602
+ if (Array.isArray(model)) {
17603
+ definition._modelArray = model.map((m) => typeof m === "string" ? { id: m } : m);
17604
+ } else if (typeof model === "string" && model) {
17605
+ definition.config.model = model;
17606
+ }
17607
+ return definition;
17558
17608
  }
17559
17609
 
17560
17610
  // src/agents/index.ts
17561
17611
  function applyOverrides(agent, override) {
17562
- if (override.model)
17563
- agent.config.model = override.model;
17612
+ if (override.model) {
17613
+ if (Array.isArray(override.model)) {
17614
+ agent._modelArray = override.model.map((m) => typeof m === "string" ? { id: m } : m);
17615
+ agent.config.model = undefined;
17616
+ } else {
17617
+ agent.config.model = override.model;
17618
+ }
17619
+ }
17564
17620
  if (override.variant)
17565
17621
  agent.config.variant = override.variant;
17566
17622
  if (override.temperature !== undefined)
@@ -17591,12 +17647,20 @@ var SUBAGENT_FACTORIES = {
17591
17647
  function createAgents(config2) {
17592
17648
  const getModelForAgent = (name) => {
17593
17649
  if (name === "fixer" && !getAgentOverride(config2, "fixer")?.model) {
17594
- return getAgentOverride(config2, "librarian")?.model ?? DEFAULT_MODELS.librarian;
17650
+ const librarianOverride = getAgentOverride(config2, "librarian")?.model;
17651
+ let librarianModel;
17652
+ if (Array.isArray(librarianOverride)) {
17653
+ const first = librarianOverride[0];
17654
+ librarianModel = typeof first === "string" ? first : first?.id;
17655
+ } else {
17656
+ librarianModel = librarianOverride;
17657
+ }
17658
+ return librarianModel ?? DEFAULT_MODELS.librarian;
17595
17659
  }
17596
17660
  return DEFAULT_MODELS[name];
17597
17661
  };
17598
17662
  const protoSubAgents = Object.entries(SUBAGENT_FACTORIES).map(([name, factory]) => {
17599
- const customPrompts = loadAgentPrompt(name);
17663
+ const customPrompts = loadAgentPrompt(name, config2?.preset);
17600
17664
  return factory(getModelForAgent(name), customPrompts.prompt, customPrompts.appendPrompt);
17601
17665
  });
17602
17666
  const allSubAgents = protoSubAgents.map((agent) => {
@@ -17607,13 +17671,13 @@ function createAgents(config2) {
17607
17671
  applyDefaultPermissions(agent, override?.skills);
17608
17672
  return agent;
17609
17673
  });
17610
- const orchestratorModel = getAgentOverride(config2, "orchestrator")?.model ?? DEFAULT_MODELS.orchestrator;
17611
- const orchestratorPrompts = loadAgentPrompt("orchestrator");
17674
+ const orchestratorOverride = getAgentOverride(config2, "orchestrator");
17675
+ const orchestratorModel = orchestratorOverride?.model ?? DEFAULT_MODELS.orchestrator;
17676
+ const orchestratorPrompts = loadAgentPrompt("orchestrator", config2?.preset);
17612
17677
  const orchestrator = createOrchestratorAgent(orchestratorModel, orchestratorPrompts.prompt, orchestratorPrompts.appendPrompt);
17613
- const oOverride = getAgentOverride(config2, "orchestrator");
17614
- applyDefaultPermissions(orchestrator, oOverride?.skills);
17615
- if (oOverride) {
17616
- applyOverrides(orchestrator, oOverride);
17678
+ applyDefaultPermissions(orchestrator, orchestratorOverride?.skills);
17679
+ if (orchestratorOverride) {
17680
+ applyOverrides(orchestrator, orchestratorOverride);
17617
17681
  }
17618
17682
  return [orchestrator, ...allSubAgents];
17619
17683
  }
@@ -17675,6 +17739,27 @@ function applyAgentVariant(variant, body) {
17675
17739
  }
17676
17740
  return { ...body, variant };
17677
17741
  }
17742
+ // src/utils/internal-initiator.ts
17743
+ var SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
17744
+ function isRecord(value) {
17745
+ return typeof value === "object" && value !== null;
17746
+ }
17747
+ function createInternalAgentTextPart(text) {
17748
+ return {
17749
+ type: "text",
17750
+ text: `${text}
17751
+ ${SLIM_INTERNAL_INITIATOR_MARKER}`
17752
+ };
17753
+ }
17754
+ function hasInternalInitiatorMarker(part) {
17755
+ if (!isRecord(part) || part.type !== "text") {
17756
+ return false;
17757
+ }
17758
+ if (typeof part.text !== "string") {
17759
+ return false;
17760
+ }
17761
+ return part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER);
17762
+ }
17678
17763
  // src/utils/tmux.ts
17679
17764
  var {spawn } = globalThis.Bun;
17680
17765
  var tmuxPath = null;
@@ -18085,7 +18170,15 @@ class BackgroundTaskManager {
18085
18170
  const primary = this.config?.agents?.[agentName]?.model;
18086
18171
  const chain = [];
18087
18172
  const seen = new Set;
18088
- for (const model of [primary, ...configuredChain]) {
18173
+ let primaryIds;
18174
+ if (Array.isArray(primary)) {
18175
+ primaryIds = primary.map((m) => typeof m === "string" ? m : m.id);
18176
+ } else if (typeof primary === "string") {
18177
+ primaryIds = [primary];
18178
+ } else {
18179
+ primaryIds = [];
18180
+ }
18181
+ for (const model of [...primaryIds, ...configuredChain]) {
18089
18182
  if (!model || seen.has(model))
18090
18183
  continue;
18091
18184
  seen.add(model);
@@ -18094,6 +18187,10 @@ class BackgroundTaskManager {
18094
18187
  return chain;
18095
18188
  }
18096
18189
  async promptWithTimeout(args, timeoutMs) {
18190
+ if (timeoutMs <= 0) {
18191
+ await this.client.session.prompt(args);
18192
+ return;
18193
+ }
18097
18194
  await Promise.race([
18098
18195
  this.client.session.prompt(args),
18099
18196
  new Promise((_, reject) => {
@@ -18143,8 +18240,8 @@ class BackgroundTaskManager {
18143
18240
  tools: toolPermissions,
18144
18241
  parts: [{ type: "text", text: task.prompt }]
18145
18242
  });
18146
- const timeoutMs = this.config?.fallback?.timeoutMs ?? FALLBACK_FAILOVER_TIMEOUT_MS;
18147
18243
  const fallbackEnabled = this.config?.fallback?.enabled ?? true;
18244
+ const timeoutMs = fallbackEnabled ? this.config?.fallback?.timeoutMs ?? FALLBACK_FAILOVER_TIMEOUT_MS : 0;
18148
18245
  const chain = fallbackEnabled ? this.resolveFallbackChain(task.agent) : [];
18149
18246
  const attemptModels = chain.length > 0 ? chain : [undefined];
18150
18247
  const errors3 = [];
@@ -18303,7 +18400,7 @@ class BackgroundTaskManager {
18303
18400
  await this.client.session.prompt({
18304
18401
  path: { id: task.parentSessionId },
18305
18402
  body: {
18306
- parts: [{ type: "text", text: message }]
18403
+ parts: [createInternalAgentTextPart(message)]
18307
18404
  }
18308
18405
  });
18309
18406
  }
@@ -18933,10 +19030,220 @@ function showToast(ctx, title, message, variant = "info", duration3 = 3000) {
18933
19030
  body: { title, message, variant, duration: duration3 }
18934
19031
  }).catch(() => {});
18935
19032
  }
19033
+ // src/hooks/chat-headers.ts
19034
+ var INTERNAL_MARKER_CACHE_LIMIT = 1000;
19035
+ var internalMarkerCache = new Map;
19036
+ function getProviderID(input) {
19037
+ return input.provider.info?.id || input.model.providerID;
19038
+ }
19039
+ function isCopilotProvider(providerID) {
19040
+ return providerID === "github-copilot" || providerID === "github-copilot-enterprise";
19041
+ }
19042
+ async function hasInternalMarker(client, sessionID, messageID) {
19043
+ const cacheKey = `${sessionID}:${messageID}`;
19044
+ const cached2 = internalMarkerCache.get(cacheKey);
19045
+ if (cached2 !== undefined) {
19046
+ return cached2;
19047
+ }
19048
+ try {
19049
+ const response = await client.session.message({
19050
+ path: { id: sessionID, messageID }
19051
+ });
19052
+ const hasMarker = (response.data?.parts ?? []).some(hasInternalInitiatorMarker);
19053
+ if (hasMarker) {
19054
+ if (internalMarkerCache.size >= INTERNAL_MARKER_CACHE_LIMIT) {
19055
+ internalMarkerCache.clear();
19056
+ }
19057
+ internalMarkerCache.set(cacheKey, true);
19058
+ }
19059
+ return hasMarker;
19060
+ } catch {
19061
+ return false;
19062
+ }
19063
+ }
19064
+ function createChatHeadersHook(ctx) {
19065
+ return {
19066
+ "chat.headers": async (input, output) => {
19067
+ if (!isCopilotProvider(getProviderID(input))) {
19068
+ return;
19069
+ }
19070
+ if (input.model.api.npm === "@ai-sdk/github-copilot") {
19071
+ return;
19072
+ }
19073
+ if (!input.message.id || input.message.role !== "user") {
19074
+ return;
19075
+ }
19076
+ if (!await hasInternalMarker(ctx.client, input.sessionID, input.message.id)) {
19077
+ return;
19078
+ }
19079
+ output.headers["x-initiator"] = "agent";
19080
+ }
19081
+ };
19082
+ }
19083
+ // src/hooks/delegate-task-retry/patterns.ts
19084
+ var DELEGATE_TASK_ERROR_PATTERNS = [
19085
+ {
19086
+ pattern: "run_in_background",
19087
+ errorType: "missing_run_in_background",
19088
+ fixHint: "Add run_in_background=false (delegation) or run_in_background=true (parallel exploration)."
19089
+ },
19090
+ {
19091
+ pattern: "load_skills",
19092
+ errorType: "missing_load_skills",
19093
+ fixHint: "Add load_skills=[] (empty array when no skill is needed)."
19094
+ },
19095
+ {
19096
+ pattern: "category OR subagent_type",
19097
+ errorType: "mutual_exclusion",
19098
+ fixHint: 'Provide only one: category (e.g., "unspecified-low") OR subagent_type (e.g., "explorer").'
19099
+ },
19100
+ {
19101
+ pattern: "Must provide either category or subagent_type",
19102
+ errorType: "missing_category_or_agent",
19103
+ fixHint: 'Add either category="unspecified-low" or subagent_type="explorer".'
19104
+ },
19105
+ {
19106
+ pattern: "Unknown category",
19107
+ errorType: "unknown_category",
19108
+ fixHint: "Use a valid category listed in the error output."
19109
+ },
19110
+ {
19111
+ pattern: "Unknown agent",
19112
+ errorType: "unknown_agent",
19113
+ fixHint: "Use a valid agent name from the available list."
19114
+ },
19115
+ {
19116
+ pattern: "Skills not found",
19117
+ errorType: "unknown_skills",
19118
+ fixHint: "Use valid skill names listed in the error output."
19119
+ },
19120
+ {
19121
+ pattern: "is not allowed. Allowed agents:",
19122
+ errorType: "background_agent_not_allowed",
19123
+ fixHint: "Use one of the allowed agents shown in the error or delegate from a parent agent that can call this subagent."
19124
+ }
19125
+ ];
19126
+ function detectDelegateTaskError(output) {
19127
+ if (!output || typeof output !== "string")
19128
+ return null;
19129
+ const hasErrorSignal = output.includes("[ERROR]") || output.includes("Invalid arguments") || output.includes("is not allowed. Allowed agents:");
19130
+ if (!hasErrorSignal)
19131
+ return null;
19132
+ for (const pattern of DELEGATE_TASK_ERROR_PATTERNS) {
19133
+ if (output.includes(pattern.pattern)) {
19134
+ return {
19135
+ errorType: pattern.errorType,
19136
+ originalOutput: output
19137
+ };
19138
+ }
19139
+ }
19140
+ return null;
19141
+ }
19142
+
19143
+ // src/hooks/delegate-task-retry/guidance.ts
19144
+ function extractAvailableList(output) {
19145
+ const match = output.match(/Allowed agents:\s*(.+)$/m);
19146
+ if (match)
19147
+ return match[1].trim();
19148
+ const available = output.match(/Available[^:]*:\s*(.+)$/m);
19149
+ if (available)
19150
+ return available[1].trim();
19151
+ return null;
19152
+ }
19153
+ function buildRetryGuidance(errorInfo) {
19154
+ const pattern = DELEGATE_TASK_ERROR_PATTERNS.find((p) => p.errorType === errorInfo.errorType);
19155
+ if (!pattern) {
19156
+ return `
19157
+ [delegate-task retry] Fix parameters and retry with corrected arguments.`;
19158
+ }
19159
+ const available = extractAvailableList(errorInfo.originalOutput);
19160
+ const lines = [
19161
+ "",
19162
+ "[delegate-task retry suggestion]",
19163
+ `Error type: ${errorInfo.errorType}`,
19164
+ `Fix: ${pattern.fixHint}`
19165
+ ];
19166
+ if (available) {
19167
+ lines.push(`Available: ${available}`);
19168
+ }
19169
+ lines.push("Retry now with corrected parameters. Example:", 'task(description="...", prompt="...", category="unspecified-low", run_in_background=false, load_skills=[])');
19170
+ return lines.join(`
19171
+ `);
19172
+ }
19173
+ // src/hooks/delegate-task-retry/hook.ts
19174
+ function createDelegateTaskRetryHook(_ctx) {
19175
+ return {
19176
+ "tool.execute.after": async (input, output) => {
19177
+ const toolName = input.tool.toLowerCase();
19178
+ const isDelegateTool = toolName === "task" || toolName === "background_task";
19179
+ if (!isDelegateTool)
19180
+ return;
19181
+ if (typeof output.output !== "string")
19182
+ return;
19183
+ const detected = detectDelegateTaskError(output.output);
19184
+ if (!detected)
19185
+ return;
19186
+ output.output += `
19187
+ ${buildRetryGuidance(detected)}`;
19188
+ }
19189
+ };
19190
+ }
19191
+ // src/hooks/json-error-recovery/hook.ts
19192
+ var JSON_ERROR_TOOL_EXCLUDE_LIST = [
19193
+ "bash",
19194
+ "read",
19195
+ "glob",
19196
+ "grep",
19197
+ "webfetch",
19198
+ "grep_app_searchgithub",
19199
+ "websearch_web_search_exa"
19200
+ ];
19201
+ var JSON_ERROR_PATTERNS = [
19202
+ /json parse error/i,
19203
+ /failed to parse json/i,
19204
+ /invalid json/i,
19205
+ /malformed json/i,
19206
+ /unexpected end of json input/i,
19207
+ /syntaxerror:\s*unexpected token.*json/i,
19208
+ /json[^\n]*expected '\}'/i,
19209
+ /json[^\n]*unexpected eof/i
19210
+ ];
19211
+ var JSON_ERROR_REMINDER_MARKER = "[JSON PARSE ERROR - IMMEDIATE ACTION REQUIRED]";
19212
+ var JSON_ERROR_EXCLUDED_TOOLS = new Set(JSON_ERROR_TOOL_EXCLUDE_LIST);
19213
+ var JSON_ERROR_REMINDER = `
19214
+ [JSON PARSE ERROR - IMMEDIATE ACTION REQUIRED]
19215
+
19216
+ You sent invalid JSON arguments. The system could not parse your tool call.
19217
+ STOP and do this NOW:
19218
+
19219
+ 1. LOOK at the error message above to see what was expected vs what you sent.
19220
+ 2. CORRECT your JSON syntax (missing braces, unescaped quotes, trailing commas, etc).
19221
+ 3. RETRY the tool call with valid JSON.
19222
+
19223
+ DO NOT repeat the exact same invalid call.
19224
+ `;
19225
+ function createJsonErrorRecoveryHook(_ctx) {
19226
+ return {
19227
+ "tool.execute.after": async (input, output) => {
19228
+ if (JSON_ERROR_EXCLUDED_TOOLS.has(input.tool.toLowerCase()))
19229
+ return;
19230
+ if (typeof output.output !== "string")
19231
+ return;
19232
+ if (output.output.includes(JSON_ERROR_REMINDER_MARKER))
19233
+ return;
19234
+ const outputText = output.output;
19235
+ const hasJsonError = JSON_ERROR_PATTERNS.some((pattern) => pattern.test(outputText));
19236
+ if (hasJsonError) {
19237
+ output.output += `
19238
+ ${JSON_ERROR_REMINDER}`;
19239
+ }
19240
+ }
19241
+ };
19242
+ }
18936
19243
  // src/hooks/phase-reminder/index.ts
18937
- var PHASE_REMINDER = `<reminder>\u26A0\uFE0F MANDATORY: Understand\u2192DELEGATE(! based on each agent rules)\u2192Split-and-Parallelize(?)\u2192Plan\u2192Execute\u2192Verify
18938
- Available Specialist: @oracle @librarian @explorer @designer @fixer
18939
- </reminder>`;
19244
+ var PHASE_REMINDER = `<reminder>Recall Workflow Rules:
19245
+ Understand \u2192 find the best path (delegate based on rules and parallelize independent work) \u2192 execute \u2192 verify.
19246
+ If delegating, launch the specialist in the same turn you mention it.</reminder>`;
18940
19247
  function createPhaseReminderHook() {
18941
19248
  return {
18942
19249
  "experimental.chat.messages.transform": async (_input, output) => {
@@ -18964,6 +19271,9 @@ function createPhaseReminderHook() {
18964
19271
  return;
18965
19272
  }
18966
19273
  const originalText = lastUserMessage.parts[textPartIndex].text ?? "";
19274
+ if (originalText.includes(SLIM_INTERNAL_INITIATOR_MARKER)) {
19275
+ return;
19276
+ }
18967
19277
  lastUserMessage.parts[textPartIndex].text = `${PHASE_REMINDER}
18968
19278
 
18969
19279
  ---
@@ -18976,7 +19286,7 @@ ${originalText}`;
18976
19286
  var NUDGE = `
18977
19287
 
18978
19288
  ---
18979
- Reminder to follow the workflow instructions, consider delegation to specialist(s)`;
19289
+ Workflow Reminder: delegate based on rules; If mentioning a specialist, launch it in this same turn.`;
18980
19290
  function createPostReadNudgeHook() {
18981
19291
  return {
18982
19292
  "tool.execute.after": async (input, output) => {
@@ -33218,7 +33528,14 @@ var lsp_rename = tool({
33218
33528
  // src/index.ts
33219
33529
  var OhMyOpenCodeLite = async (ctx) => {
33220
33530
  const config3 = loadPluginConfig(ctx.directory);
33531
+ const agentDefs = createAgents(config3);
33221
33532
  const agents = getAgentConfigs(config3);
33533
+ const modelArrayMap = {};
33534
+ for (const agentDef of agentDefs) {
33535
+ if (agentDef._modelArray && agentDef._modelArray.length > 0) {
33536
+ modelArrayMap[agentDef.name] = agentDef._modelArray;
33537
+ }
33538
+ }
33222
33539
  const tmuxConfig = {
33223
33540
  enabled: config3.tmux?.enabled ?? false,
33224
33541
  layout: config3.tmux?.layout ?? "main-vertical",
@@ -33242,6 +33559,9 @@ var OhMyOpenCodeLite = async (ctx) => {
33242
33559
  });
33243
33560
  const phaseReminderHook = createPhaseReminderHook();
33244
33561
  const postReadNudgeHook = createPostReadNudgeHook();
33562
+ const chatHeadersHook = createChatHeadersHook(ctx);
33563
+ const delegateTaskRetryHook = createDelegateTaskRetryHook(ctx);
33564
+ const jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
33245
33565
  return {
33246
33566
  name: "oh-my-opencode-slim",
33247
33567
  agent: agents,
@@ -33264,6 +33584,40 @@ var OhMyOpenCodeLite = async (ctx) => {
33264
33584
  Object.assign(opencodeConfig.agent, agents);
33265
33585
  }
33266
33586
  const configAgent = opencodeConfig.agent;
33587
+ if (Object.keys(modelArrayMap).length > 0) {
33588
+ const providerConfig = opencodeConfig.provider ?? {};
33589
+ const configuredProviders = Object.keys(providerConfig);
33590
+ for (const [agentName, modelArray] of Object.entries(modelArrayMap)) {
33591
+ let resolved = false;
33592
+ for (const modelEntry of modelArray) {
33593
+ const slashIdx = modelEntry.id.indexOf("/");
33594
+ if (slashIdx === -1)
33595
+ continue;
33596
+ const providerID = modelEntry.id.slice(0, slashIdx);
33597
+ if (configuredProviders.includes(providerID)) {
33598
+ const entry = configAgent[agentName];
33599
+ if (entry) {
33600
+ entry.model = modelEntry.id;
33601
+ if (modelEntry.variant) {
33602
+ entry.variant = modelEntry.variant;
33603
+ }
33604
+ }
33605
+ log("[plugin] resolved model fallback", {
33606
+ agent: agentName,
33607
+ model: modelEntry.id,
33608
+ variant: modelEntry.variant
33609
+ });
33610
+ resolved = true;
33611
+ break;
33612
+ }
33613
+ }
33614
+ if (!resolved) {
33615
+ log("[plugin] no provider match for model array", {
33616
+ agent: agentName
33617
+ });
33618
+ }
33619
+ }
33620
+ }
33267
33621
  const configMcp = opencodeConfig.mcp;
33268
33622
  if (!configMcp) {
33269
33623
  opencodeConfig.mcp = { ...mcps };
@@ -33300,8 +33654,13 @@ var OhMyOpenCodeLite = async (ctx) => {
33300
33654
  await backgroundManager.handleSessionDeleted(input.event);
33301
33655
  await tmuxSessionManager.onSessionDeleted(input.event);
33302
33656
  },
33657
+ "chat.headers": chatHeadersHook["chat.headers"],
33303
33658
  "experimental.chat.messages.transform": phaseReminderHook["experimental.chat.messages.transform"],
33304
- "tool.execute.after": postReadNudgeHook["tool.execute.after"]
33659
+ "tool.execute.after": async (input, output) => {
33660
+ await delegateTaskRetryHook["tool.execute.after"](input, output);
33661
+ await jsonErrorRecoveryHook["tool.execute.after"](input, output);
33662
+ await postReadNudgeHook["tool.execute.after"](input, output);
33663
+ }
33305
33664
  };
33306
33665
  };
33307
33666
  var src_default = OhMyOpenCodeLite;
@@ -1,5 +1,6 @@
1
1
  export * from './agent-variant';
2
2
  export * from './env';
3
+ export * from './internal-initiator';
3
4
  export { log } from './logger';
4
5
  export * from './polling';
5
6
  export * from './tmux';
@@ -0,0 +1,6 @@
1
+ export declare const SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
2
+ export declare function createInternalAgentTextPart(text: string): {
3
+ type: 'text';
4
+ text: string;
5
+ };
6
+ export declare function hasInternalInitiatorMarker(part: unknown): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode-slim",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
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",
@@ -1,5 +0,0 @@
1
- import type { Account, AccountQuotaResult, AccountsConfig } from './types';
2
- export declare const CONFIG_PATHS: string[];
3
- export declare function loadAccountsConfig(): AccountsConfig | null;
4
- export declare function fetchAccountQuota(account: Account): Promise<AccountQuotaResult>;
5
- export declare function fetchAllQuotas(accounts: Account[]): Promise<AccountQuotaResult[]>;
@@ -1 +0,0 @@
1
- export {};
@@ -1,21 +0,0 @@
1
- /**
2
- * Compact quota display tool - groups models by quota family
3
- *
4
- * Output format:
5
- * ```
6
- * tornikevault
7
- * Claude [░░░░░░░░░░] 0% 3h23m
8
- * G-Flash [██████████] 100% 4h59m
9
- * G-Pro [██████████] 100% 4h59m
10
- *
11
- * tzedgin
12
- * Claude [░░░░░░░░░░] 0% 1h41m
13
- * G-Flash [██████████] 100% 4h59m
14
- * G-Pro [██████████] 100% 4h59m
15
- * ```
16
- */
17
- export declare const antigravity_quota: {
18
- description: string;
19
- args: {};
20
- execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
21
- };
@@ -1,41 +0,0 @@
1
- export interface Account {
2
- email: string;
3
- refreshToken: string;
4
- projectId?: string;
5
- managedProjectId?: string;
6
- rateLimitResetTimes: Record<string, number>;
7
- }
8
- export interface AccountsConfig {
9
- accounts: Account[];
10
- activeIndex: number;
11
- }
12
- export interface QuotaInfo {
13
- remainingFraction?: number;
14
- resetTime?: string;
15
- }
16
- export interface ModelInfo {
17
- displayName?: string;
18
- model?: string;
19
- quotaInfo?: QuotaInfo;
20
- recommended?: boolean;
21
- }
22
- export interface QuotaResponse {
23
- models?: Record<string, ModelInfo>;
24
- }
25
- export interface TokenResponse {
26
- access_token: string;
27
- }
28
- export interface LoadCodeAssistResponse {
29
- cloudaicompanionProject?: unknown;
30
- }
31
- export interface ModelQuota {
32
- name: string;
33
- percent: number;
34
- resetIn: string;
35
- }
36
- export interface AccountQuotaResult {
37
- email: string;
38
- success: boolean;
39
- error?: string;
40
- models: ModelQuota[];
41
- }