oh-my-opencode-slim 1.0.0 → 1.0.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.
package/README.md CHANGED
@@ -105,7 +105,7 @@ ping all agents
105
105
 
106
106
  <div align="center">
107
107
  <img src="img/ping.png" alt="Ping all agents" width="600">
108
- <p><i>Confirmation that all six agents are online and ready.</i></p>
108
+ <p><i>Confirmation that all configured agents are online and ready.</i></p>
109
109
  </div>
110
110
 
111
111
  If any agent fails to respond, check your provider authentication and config file.
@@ -239,7 +239,7 @@ If any agent fails to respond, check your provider authentication and config fil
239
239
 
240
240
  ---
241
241
 
242
- ### Council: The Chorus of Minds
242
+ ### 04. Council: The Chorus of Minds
243
243
 
244
244
  > [!NOTE]
245
245
  > **Why doesn't Orchestrator auto-call Council more often?** This is intentional. Council runs multiple models at once, so automatic delegation is kept strict because it is usually the highest-cost path in the system. In practice, Council is meant to be used manually when you want it, for example: <code>@council compare these two architectures</code>.
@@ -288,7 +288,7 @@ If any agent fails to respond, check your provider authentication and config fil
288
288
 
289
289
  ---
290
290
 
291
- ### 04. Librarian: The Weaver of Knowledge
291
+ ### 05. Librarian: The Weaver of Knowledge
292
292
 
293
293
  <table>
294
294
  <tr>
@@ -329,7 +329,7 @@ If any agent fails to respond, check your provider authentication and config fil
329
329
 
330
330
  ---
331
331
 
332
- ### 05. Designer: The Guardian of Aesthetics
332
+ ### 06. Designer: The Guardian of Aesthetics
333
333
 
334
334
  <table>
335
335
  <tr>
@@ -370,7 +370,7 @@ If any agent fails to respond, check your provider authentication and config fil
370
370
 
371
371
  ---
372
372
 
373
- ### 06. Fixer: The Last Builder
373
+ ### 07. Fixer: The Last Builder
374
374
 
375
375
  <table>
376
376
  <tr>
@@ -409,18 +409,22 @@ If any agent fails to respond, check your provider authentication and config fil
409
409
  </tr>
410
410
  </table>
411
411
 
412
- ### 07. Observer: The Silent Witness
412
+ ---
413
+
414
+ ## Optional Agents
415
+
416
+ ### Observer: The Silent Witness
413
417
 
414
418
  > [!NOTE]
415
419
  > **Why a separate agent?** If your Orchestrator model is not multimodal, enable Observer to handle images, screenshots, PDFs, and other visual files. Observer is disabled by default and gives the Orchestrator a dedicated multimodal reader without forcing you to change your main reasoning model. Set `disabled_agents: []` and an `observer` model in your configuration.
416
420
 
417
421
  <table>
418
422
  <tr>
419
- <td width="240" valign="top">
420
- <b>Observer</b><br>
421
- <i>Visual & binary analysis</i>
423
+ <td width="30%" align="center" valign="top">
424
+ <img src="img/observer.jpg" width="240" style="border-radius: 10px;">
425
+ <br><sub><i>The eye that reads what others cannot.</i></sub>
422
426
  </td>
423
- <td>
427
+ <td width="70%" valign="top">
424
428
 
425
429
  **Read-only visual analysis** — interprets images, screenshots, PDFs, and diagrams. Returns structured observations to the orchestrator without loading raw file bytes into the main context window.
426
430
 
@@ -469,6 +473,7 @@ Use this section as a map: start with installation, then jump to features, confi
469
473
  | **[Interview](docs/interview.md)** | Turn rough ideas into a structured markdown spec through a browser-based Q&A flow |
470
474
  | **[Multiplexer Integration](docs/multiplexer-integration.md)** | Watch agents work live in Tmux or Zellij panes |
471
475
  | **[Todo Continuation](docs/todo-continuation.md)** | Auto-continue orchestrator sessions with cooldowns and safety checks |
476
+ | **[Preset Switching](docs/preset-switching.md)** | Switch agent model presets at runtime with `/preset` |
472
477
  | **[Codemap](docs/codemap.md)** | Generate hierarchical codemaps to understand large codebases faster |
473
478
 
474
479
  ### ⚙️ Config & Reference
package/dist/cli/index.js CHANGED
@@ -158,13 +158,15 @@ var CouncilConfigSchema = z.object({
158
158
  deprecated.push("master_timeout");
159
159
  if (data.master_fallback !== undefined)
160
160
  deprecated.push("master_fallback");
161
+ const legacyMasterModel = typeof data.master === "object" && data.master !== null && "model" in data.master && typeof data.master.model === "string" ? data.master.model : undefined;
161
162
  return {
162
163
  presets: data.presets,
163
164
  timeout: data.timeout,
164
165
  default_preset: data.default_preset,
165
166
  councillor_execution_mode: data.councillor_execution_mode,
166
167
  councillor_retries: data.councillor_retries,
167
- _deprecated: deprecated.length > 0 ? deprecated : undefined
168
+ _deprecated: deprecated.length > 0 ? deprecated : undefined,
169
+ _legacyMasterModel: legacyMasterModel
168
170
  };
169
171
  });
170
172
  // src/config/schema.ts
@@ -82,6 +82,7 @@ export declare const CouncilConfigSchema: z.ZodPipe<z.ZodObject<{
82
82
  councillor_execution_mode: "parallel" | "serial";
83
83
  councillor_retries: number;
84
84
  _deprecated: string[] | undefined;
85
+ _legacyMasterModel: string | undefined;
85
86
  }, {
86
87
  presets: Record<string, Record<string, {
87
88
  model: string;
@@ -345,6 +345,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
345
345
  councillor_execution_mode: "parallel" | "serial";
346
346
  councillor_retries: number;
347
347
  _deprecated: string[] | undefined;
348
+ _legacyMasterModel: string | undefined;
348
349
  }, {
349
350
  presets: Record<string, Record<string, {
350
351
  model: string;
@@ -15,9 +15,12 @@ export declare class CouncilManager {
15
15
  private depthTracker?;
16
16
  private tmuxEnabled;
17
17
  private deprecatedFields?;
18
+ private legacyMasterModel?;
18
19
  constructor(ctx: PluginInput, config?: PluginConfig, depthTracker?: SubagentDepthTracker, tmuxEnabled?: boolean);
19
20
  /** Return deprecated config fields detected during parsing (for tool warnings). */
20
21
  getDeprecatedFields(): string[] | undefined;
22
+ /** Return the legacy master.model if it was used as fallback. */
23
+ getLegacyMasterModel(): string | undefined;
21
24
  /**
22
25
  * Run a full council session.
23
26
  *
package/dist/index.js CHANGED
@@ -18378,13 +18378,15 @@ var CouncilConfigSchema = z.object({
18378
18378
  deprecated.push("master_timeout");
18379
18379
  if (data.master_fallback !== undefined)
18380
18380
  deprecated.push("master_fallback");
18381
+ const legacyMasterModel = typeof data.master === "object" && data.master !== null && "model" in data.master && typeof data.master.model === "string" ? data.master.model : undefined;
18381
18382
  return {
18382
18383
  presets: data.presets,
18383
18384
  timeout: data.timeout,
18384
18385
  default_preset: data.default_preset,
18385
18386
  councillor_execution_mode: data.councillor_execution_mode,
18386
18387
  councillor_retries: data.councillor_retries,
18387
- _deprecated: deprecated.length > 0 ? deprecated : undefined
18388
+ _deprecated: deprecated.length > 0 ? deprecated : undefined,
18389
+ _legacyMasterModel: legacyMasterModel
18388
18390
  };
18389
18391
  });
18390
18392
  // src/config/loader.ts
@@ -19630,6 +19632,13 @@ function createAgents(config) {
19630
19632
  applyDefaultPermissions(agent, override?.skills);
19631
19633
  return agent;
19632
19634
  });
19635
+ const legacyMasterModel = config?.council?._legacyMasterModel;
19636
+ if (legacyMasterModel) {
19637
+ const councilAgent = builtInSubAgents.find((a) => a.name === "council");
19638
+ if (councilAgent && !getAgentOverride(config, "council")?.model && councilAgent.config.model === DEFAULT_MODELS.council) {
19639
+ councilAgent.config.model = legacyMasterModel;
19640
+ }
19641
+ }
19633
19642
  const customSubAgents = protoCustomAgents.map((agent) => {
19634
19643
  const override = getAgentOverride(config, agent.name);
19635
19644
  if (override) {
@@ -19819,17 +19828,22 @@ class CouncilManager {
19819
19828
  depthTracker;
19820
19829
  tmuxEnabled;
19821
19830
  deprecatedFields;
19831
+ legacyMasterModel;
19822
19832
  constructor(ctx, config, depthTracker, tmuxEnabled = false) {
19823
19833
  this.client = ctx.client;
19824
19834
  this.directory = ctx.directory;
19825
19835
  this.config = config;
19826
19836
  this.deprecatedFields = config?.council?._deprecated;
19837
+ this.legacyMasterModel = config?.council?._legacyMasterModel;
19827
19838
  this.depthTracker = depthTracker;
19828
19839
  this.tmuxEnabled = tmuxEnabled;
19829
19840
  }
19830
19841
  getDeprecatedFields() {
19831
19842
  return this.deprecatedFields;
19832
19843
  }
19844
+ getLegacyMasterModel() {
19845
+ return this.legacyMasterModel;
19846
+ }
19833
19847
  async runCouncil(prompt, presetName, parentSessionId) {
19834
19848
  if (this.depthTracker) {
19835
19849
  const parentDepth = this.depthTracker.getDepth(parentSessionId);
@@ -22613,31 +22627,51 @@ function extFromMime(mime) {
22613
22627
  function sanitizeFilename(name) {
22614
22628
  return name.replace(/[^a-zA-Z0-9._-]/g, "_");
22615
22629
  }
22616
- function cleanupOldImages(dir, saveDir) {
22630
+ function cleanupAllSessions(saveDir) {
22617
22631
  const now = Date.now();
22618
- if (!lastCleanupByDir.has(dir) && existsSync4(dir)) {
22619
- lastCleanupByDir.set(dir, now);
22620
- }
22621
- const lastCleanup = lastCleanupByDir.get(dir) ?? 0;
22632
+ const lastCleanup = lastCleanupByDir.get(saveDir) ?? 0;
22622
22633
  if (now - lastCleanup < CLEANUP_INTERVAL)
22623
22634
  return;
22624
- lastCleanupByDir.set(dir, now);
22635
+ lastCleanupByDir.set(saveDir, now);
22636
+ const maxAge = 60 * 60 * 1000;
22637
+ const dirsToScan = [];
22625
22638
  try {
22626
- const maxAge = 60 * 60 * 1000;
22627
- for (const f of readdirSync2(dir)) {
22628
- const fp = join7(dir, f);
22629
- try {
22630
- if (now - statSync3(fp).mtimeMs > maxAge)
22631
- unlinkSync2(fp);
22632
- } catch {}
22633
- }
22634
- if (dir !== saveDir) {
22635
- try {
22636
- rmdirSync(dir);
22637
- lastCleanupByDir.delete(dir);
22638
- } catch {}
22639
+ for (const entry of readdirSync2(saveDir, { withFileTypes: true })) {
22640
+ const fp = join7(saveDir, entry.name);
22641
+ if (entry.isDirectory()) {
22642
+ dirsToScan.push(fp);
22643
+ } else {
22644
+ try {
22645
+ if (now - statSync3(fp).mtimeMs > maxAge)
22646
+ unlinkSync2(fp);
22647
+ } catch {}
22648
+ }
22639
22649
  }
22640
22650
  } catch {}
22651
+ for (const dir of dirsToScan) {
22652
+ try {
22653
+ let isEmpty = true;
22654
+ let allRemoved = true;
22655
+ for (const f of readdirSync2(dir)) {
22656
+ isEmpty = false;
22657
+ const fp = join7(dir, f);
22658
+ try {
22659
+ if (now - statSync3(fp).mtimeMs > maxAge) {
22660
+ unlinkSync2(fp);
22661
+ } else {
22662
+ allRemoved = false;
22663
+ }
22664
+ } catch {
22665
+ allRemoved = false;
22666
+ }
22667
+ }
22668
+ if (!isEmpty && allRemoved) {
22669
+ try {
22670
+ rmdirSync(dir);
22671
+ } catch {}
22672
+ }
22673
+ } catch {}
22674
+ }
22641
22675
  }
22642
22676
  function writeUniqueFile(dir, name, data, log2) {
22643
22677
  const ext = extname(name);
@@ -22680,6 +22714,7 @@ function processImageAttachments(args) {
22680
22714
  } catch (e) {
22681
22715
  log2(`[image-hook] failed to create image directory: ${e}`);
22682
22716
  }
22717
+ cleanupAllSessions(saveDir);
22683
22718
  for (const msg of messages) {
22684
22719
  if (msg.info.role !== "user")
22685
22720
  continue;
@@ -22693,7 +22728,6 @@ function processImageAttachments(args) {
22693
22728
  } catch (e) {
22694
22729
  log2(`[image-hook] failed to create target image directory: ${e}`);
22695
22730
  }
22696
- cleanupOldImages(targetDir, saveDir);
22697
22731
  const savedPaths = [];
22698
22732
  for (const p of imageParts) {
22699
22733
  const url = p.url;
@@ -25055,6 +25089,10 @@ function renderInterviewPage(interviewId, resumeSlug) {
25055
25089
  render(data);
25056
25090
  }
25057
25091
 
25092
+ function scrollToTop() {
25093
+ window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
25094
+ }
25095
+
25058
25096
  document.getElementById('submitButton').addEventListener('click', async () => {
25059
25097
  if (!state.data) return;
25060
25098
  const answers = (state.data.questions || []).map((question) => {
@@ -25068,6 +25106,7 @@ function renderInterviewPage(interviewId, resumeSlug) {
25068
25106
  const overlayText = document.getElementById('loadingText');
25069
25107
  overlay.classList.add('active');
25070
25108
  overlayText.textContent = "Submitting Answers...";
25109
+ scrollToTop();
25071
25110
 
25072
25111
  try {
25073
25112
  const response = await fetch('/api/interviews/' + encodeURIComponent(interviewId) + '/answers', {
@@ -28571,14 +28610,158 @@ Returns the councillor responses with a summary footer.`,
28571
28610
  *Council: ${completed}/${total} councillors responded (${composition})*`;
28572
28611
  const deprecated = councilManager.getDeprecatedFields();
28573
28612
  if (deprecated && deprecated.length > 0) {
28613
+ const legacyMasterModel = councilManager.getLegacyMasterModel();
28614
+ const hasMaster = deprecated.includes("master");
28615
+ const trulyIgnored = hasMaster && !legacyMasterModel ? deprecated : deprecated.filter((f) => f !== "master");
28616
+ const parts = [];
28617
+ if (hasMaster && legacyMasterModel) {
28618
+ parts.push(`\`council.master\` is deprecated and will be removed in a future version. Its \`model\` is currently used as a fallback for the council agent — add a \`council\` entry to your preset to make this explicit.`);
28619
+ }
28620
+ if (trulyIgnored.length > 0) {
28621
+ parts.push(`${trulyIgnored.map((f) => `\`council.${f}\``).join(", ")} ${trulyIgnored.length === 1 ? "is" : "are"} deprecated and ignored — remove ${trulyIgnored.length === 1 ? "it" : "them"} from your config.`);
28622
+ }
28574
28623
  output += `
28575
- ⚠ Config warning: ${deprecated.map((f) => `\`council.${f}\``).join(", ")} ${deprecated.length === 1 ? "is" : "are"} deprecated and ignored. The council agent synthesizes directly — remove ${deprecated.length === 1 ? "it" : "them"} from your config.`;
28624
+ ⚠ Config warning: ${parts.join(" ")}`;
28576
28625
  }
28577
28626
  return output;
28578
28627
  }
28579
28628
  });
28580
28629
  return { council_session };
28581
28630
  }
28631
+ // src/tools/preset-manager.ts
28632
+ var COMMAND_NAME3 = "preset";
28633
+ function createPresetManager(ctx, config) {
28634
+ let activePreset = config.preset ?? null;
28635
+ async function handleCommandExecuteBefore(input, output) {
28636
+ if (input.command !== COMMAND_NAME3) {
28637
+ return;
28638
+ }
28639
+ output.parts.length = 0;
28640
+ const arg = input.arguments.trim();
28641
+ const presets = config.presets ?? {};
28642
+ if (!arg) {
28643
+ output.parts.push(createInternalAgentTextPart(formatPresetList(presets)));
28644
+ return;
28645
+ }
28646
+ if (/\s/.test(arg)) {
28647
+ const suggestion = arg.split(/\s+/)[0];
28648
+ output.parts.push(createInternalAgentTextPart(`Preset names cannot contain spaces. Did you mean: /preset ${suggestion}?`));
28649
+ return;
28650
+ }
28651
+ await switchPreset(arg, presets, output);
28652
+ }
28653
+ function registerCommand(opencodeConfig) {
28654
+ const configCommand = opencodeConfig.command;
28655
+ if (!configCommand?.[COMMAND_NAME3]) {
28656
+ if (!opencodeConfig.command) {
28657
+ opencodeConfig.command = {};
28658
+ }
28659
+ opencodeConfig.command[COMMAND_NAME3] = {
28660
+ template: "List available presets and switch between them",
28661
+ description: "Switch agent presets at runtime (e.g., /preset cheap, /preset powerful)"
28662
+ };
28663
+ }
28664
+ }
28665
+ async function switchPreset(presetName, presets, output) {
28666
+ const preset = presets[presetName];
28667
+ if (!preset) {
28668
+ const available = Object.keys(presets);
28669
+ const hint = available.length > 0 ? `Available presets: ${available.join(", ")}` : "No presets configured. Define presets in oh-my-opencode-slim.jsonc.";
28670
+ output.parts.push(createInternalAgentTextPart(`Preset "${presetName}" not found. ${hint}`));
28671
+ return;
28672
+ }
28673
+ const agentUpdates = {};
28674
+ for (const [agentName, override] of Object.entries(preset)) {
28675
+ const agentConfig = mapOverrideToAgentConfig(override);
28676
+ if (Object.keys(agentConfig).length > 0) {
28677
+ agentUpdates[agentName] = agentConfig;
28678
+ }
28679
+ }
28680
+ if (Object.keys(agentUpdates).length === 0) {
28681
+ output.parts.push(createInternalAgentTextPart(`Preset "${presetName}" is empty (no agent overrides defined).`));
28682
+ return;
28683
+ }
28684
+ try {
28685
+ await ctx.client.config.update({
28686
+ body: { agent: agentUpdates }
28687
+ });
28688
+ activePreset = presetName;
28689
+ const summary = Object.entries(agentUpdates).map(([name, cfg]) => {
28690
+ const parts = [name];
28691
+ if (cfg.model)
28692
+ parts.push(`model: ${cfg.model}`);
28693
+ if (cfg.variant)
28694
+ parts.push(`variant: ${cfg.variant}`);
28695
+ if (cfg.temperature !== undefined)
28696
+ parts.push(`temp: ${cfg.temperature}`);
28697
+ if (cfg.options)
28698
+ parts.push("options: yes");
28699
+ return parts.join(" → ");
28700
+ }).join(`
28701
+ `);
28702
+ output.parts.push(createInternalAgentTextPart(`Switched to preset "${presetName}":
28703
+ ${summary}`));
28704
+ } catch (err) {
28705
+ output.parts.push(createInternalAgentTextPart(`Failed to switch preset "${presetName}": ${String(err)}`));
28706
+ }
28707
+ }
28708
+ function mapOverrideToAgentConfig(override) {
28709
+ const agentConfig = {};
28710
+ if (typeof override.model === "string") {
28711
+ agentConfig.model = override.model;
28712
+ } else if (Array.isArray(override.model) && override.model.length > 0) {
28713
+ const first = override.model[0];
28714
+ agentConfig.model = typeof first === "string" ? first : first.id;
28715
+ if (typeof first !== "string" && first.variant) {
28716
+ agentConfig.variant = first.variant;
28717
+ }
28718
+ }
28719
+ if (typeof override.temperature === "number") {
28720
+ agentConfig.temperature = override.temperature;
28721
+ }
28722
+ if (typeof override.variant === "string") {
28723
+ agentConfig.variant = override.variant;
28724
+ }
28725
+ if (override.options && typeof override.options === "object" && !Array.isArray(override.options)) {
28726
+ agentConfig.options = override.options;
28727
+ }
28728
+ return agentConfig;
28729
+ }
28730
+ function formatPresetList(presets) {
28731
+ const names = Object.keys(presets);
28732
+ if (names.length === 0) {
28733
+ return 'No presets configured. Define presets in oh-my-opencode-slim.jsonc under the "presets" field.';
28734
+ }
28735
+ const lines = ["Available presets:"];
28736
+ for (const name of names) {
28737
+ const marker = name === activePreset ? " ← active" : "";
28738
+ const preset = presets[name];
28739
+ const agentNames = Object.keys(preset);
28740
+ const models = agentNames.map((a) => {
28741
+ const cfg = preset[a];
28742
+ const modelStr = typeof cfg.model === "string" ? cfg.model : Array.isArray(cfg.model) && cfg.model.length > 0 ? resolveFirstModel(cfg.model) : undefined;
28743
+ return modelStr ? ` ${a} → ${modelStr}` : ` ${a}`;
28744
+ }).join(`
28745
+ `);
28746
+ lines.push(` ${name}${marker}`);
28747
+ lines.push(models);
28748
+ }
28749
+ lines.push(`
28750
+ Usage: /preset <name> to switch.`);
28751
+ return lines.join(`
28752
+ `);
28753
+ }
28754
+ function resolveFirstModel(models) {
28755
+ if (models.length === 0)
28756
+ return;
28757
+ const first = models[0];
28758
+ return typeof first === "string" ? first : first.id;
28759
+ }
28760
+ return {
28761
+ handleCommandExecuteBefore,
28762
+ registerCommand
28763
+ };
28764
+ }
28582
28765
  // src/tools/smartfetch/constants.ts
28583
28766
  var DOCS_HOST_SUFFIXES = [
28584
28767
  ".readthedocs.io",
@@ -30966,6 +31149,7 @@ var OhMyOpenCodeLite = async (ctx) => {
30966
31149
  let foregroundFallback;
30967
31150
  let todoContinuationHook;
30968
31151
  let interviewManager;
31152
+ let presetManager;
30969
31153
  let councilTools;
30970
31154
  let webfetch;
30971
31155
  let toolCount = 0;
@@ -31043,6 +31227,7 @@ var OhMyOpenCodeLite = async (ctx) => {
31043
31227
  autoEnableThreshold: config.todoContinuation?.autoEnableThreshold ?? 4
31044
31228
  });
31045
31229
  interviewManager = createInterviewManager(ctx, config);
31230
+ presetManager = createPresetManager(ctx, config);
31046
31231
  toolCount = Object.keys(councilTools).length + Object.keys(todoContinuationHook.tool).length + 1 + 2;
31047
31232
  } catch (err) {
31048
31233
  log("[plugin] FATAL: init failed", String(err));
@@ -31197,6 +31382,7 @@ var OhMyOpenCodeLite = async (ctx) => {
31197
31382
  };
31198
31383
  }
31199
31384
  interviewManager.registerCommand(opencodeConfig);
31385
+ presetManager.registerCommand(opencodeConfig);
31200
31386
  },
31201
31387
  event: async (input) => {
31202
31388
  const event = input.event;
@@ -31232,6 +31418,7 @@ var OhMyOpenCodeLite = async (ctx) => {
31232
31418
  "command.execute.before": async (input, output) => {
31233
31419
  await todoContinuationHook.handleCommandExecuteBefore(input, output);
31234
31420
  await interviewManager.handleCommandExecuteBefore(input, output);
31421
+ await presetManager.handleCommandExecuteBefore(input, output);
31235
31422
  },
31236
31423
  "chat.headers": chatHeadersHook["chat.headers"],
31237
31424
  "chat.message": async (input, output) => {
@@ -1,3 +1,5 @@
1
1
  export { ast_grep_replace, ast_grep_search } from './ast-grep';
2
2
  export { createCouncilTool } from './council';
3
+ export type { PresetManager } from './preset-manager';
4
+ export { createPresetManager } from './preset-manager';
3
5
  export { createWebfetchTool } from './smartfetch';
@@ -0,0 +1,27 @@
1
+ import type { PluginInput } from '@opencode-ai/plugin';
2
+ import type { PluginConfig } from '../config';
3
+ /**
4
+ * Creates a preset manager for the /preset slash command.
5
+ *
6
+ * Uses the OpenCode SDK's client.config.update() to change agent models
7
+ * and temperatures without restarting. The server invalidates its agent
8
+ * cache and re-reads config on the next prompt.
9
+ *
10
+ * Note: activePreset is tracked in-memory only and resets on plugin reload.
11
+ * If the user manually edits config or another mechanism changes agents,
12
+ * this tracker may become stale until the next /preset call.
13
+ */
14
+ export declare function createPresetManager(ctx: PluginInput, config: PluginConfig): {
15
+ handleCommandExecuteBefore: (input: {
16
+ command: string;
17
+ sessionID: string;
18
+ arguments: string;
19
+ }, output: {
20
+ parts: Array<{
21
+ type: string;
22
+ text?: string;
23
+ }>;
24
+ }) => Promise<void>;
25
+ registerCommand: (opencodeConfig: Record<string, unknown>) => void;
26
+ };
27
+ export type PresetManager = ReturnType<typeof createPresetManager>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode-slim",
3
- "version": "1.0.0",
3
+ "version": "1.0.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",