oh-my-opencode-slim 0.3.6 → 0.3.7

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
@@ -12,7 +12,7 @@
12
12
 
13
13
  > Slimmed-down fork of [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) - focused on core agent orchestration without the extra bells and whistles.
14
14
 
15
- > **[Antigravity](https://antigravity.ai) subscription recommended.** The pantheon is tuned for Antigravity's model routing. Other providers work, but you'll get the best experience with Antigravity.
15
+ > **[Antigravity](https://antigravity.google) subscription recommended.** The pantheon is tuned for Antigravity's model routing. Other providers work, but you'll get the best experience with Antigravity.
16
16
 
17
17
  ---
18
18
 
@@ -23,24 +23,14 @@
23
23
  - [For LLM Agents](#for-llm-agents)
24
24
  - [🏗️ **Architecture & Flow**](#architecture--flow)
25
25
  - [🏛️ **Meet the Pantheon**](#meet-the-pantheon)
26
- - [Orchestrator](#orchestrator)
27
- - [Explorer](#explorer)
28
- - [Oracle](#oracle)
29
- - [Librarian](#librarian)
30
- - [Frontend Designer](#frontend-designer)
31
- - [Document Writer](#document-writer)
32
- - [Multimodal Viewer](#multimodal-viewer)
33
- - [Code Simplifier](#code-simplifier)
34
26
  - [🛠️ **Tools & Capabilities**](#tools--capabilities)
35
- - [Tmux Integration](#tmux-integration)
36
- - [Quota Tool](#quota-tool)
37
- - [Background Tasks](#background-tasks)
38
- - [LSP Tools](#lsp-tools)
39
- - [Code Search Tools](#code-search-tools)
40
27
  - [🧩 **Skills**](#-skills)
41
- - [Playwright Integration](#playwright-integration)
42
- - [🔌 **MCP Servers**](#mcp-servers)
43
- - [⚙️ **Configuration**](#configuration)
28
+ - [⚙️ **Configuration Guide**](#configuration-guide)
29
+ - [Locations & Precedence](#locations--precedence)
30
+ - [General Settings](#general-settings)
31
+ - [Agent Configuration](#agent-configuration)
32
+ - [Tmux Setup](#tmux-setup)
33
+ - [MCP Management](#mcp-management)
44
34
  - [🗑️ **Uninstallation**](#uninstallation)
45
35
 
46
36
  ---
@@ -350,71 +340,6 @@ Identify unnecessary complexity, challenge premature abstractions, estimate LOC
350
340
  - **Auto-Cleanup**: Panes close when agents finish, layout rebalances
351
341
  - **Zero Overhead**: Works with OpenCode's built-in `task` tool AND our `background_task` tool
352
342
 
353
- #### Quick Setup
354
-
355
- **1. Enable the OpenCode HTTP server** (one-time setup)
356
-
357
- Add to your `~/.config/opencode/opencode.json`:
358
-
359
- ```json
360
- {
361
- "server": {
362
- "port": 4096
363
- }
364
- }
365
- ```
366
-
367
- **2. Enable tmux integration in the plugin**
368
-
369
- Add to your `~/.config/opencode/oh-my-opencode-slim.json`:
370
-
371
- ```json
372
- {
373
- "tmux": {
374
- "enabled": true,
375
- "layout": "main-vertical",
376
- "main_pane_size": 60
377
- }
378
- }
379
- ```
380
-
381
- **3. Run OpenCode inside tmux**
382
-
383
- ```bash
384
- tmux
385
- opencode
386
- ```
387
-
388
- That's it. When agents spawn, they'll appear in new panes.
389
-
390
- #### Layout Options
391
-
392
- | Layout | Description |
393
- |--------|-------------|
394
- | `main-vertical` | Your session on the left (60%), agents stacked on the right |
395
- | `main-horizontal` | Your session on top (60%), agents stacked below |
396
- | `tiled` | All panes in equal-sized grid |
397
- | `even-horizontal` | All panes side by side |
398
- | `even-vertical` | All panes stacked vertically |
399
-
400
- #### Configuration Reference
401
-
402
- ```json
403
- {
404
- "tmux": {
405
- "enabled": true,
406
- "layout": "main-vertical",
407
- "main_pane_size": 60
408
- }
409
- }
410
- ```
411
-
412
- | Option | Type | Default | Description |
413
- |--------|------|---------|-------------|
414
- | `enabled` | boolean | `false` | Enable/disable tmux integration |
415
- | `layout` | string | `"main-vertical"` | Tmux layout preset |
416
- | `main_pane_size` | number | `60` | Size of main pane as percentage (20-80) |
417
-
418
343
  ---
419
344
 
420
345
  ### Quota Tool
@@ -431,7 +356,6 @@ For Antigravity users. You can trigger this at any time by asking the agent to *
431
356
 
432
357
  ### Background Tasks
433
358
 
434
-
435
359
  The plugin provides tools to manage asynchronous work:
436
360
 
437
361
  | Tool | Description |
@@ -487,60 +411,124 @@ Skills are specialized capabilities that combine MCP servers with specific instr
487
411
 
488
412
  ---
489
413
 
490
- ## MCP Servers
414
+ ## ⚙️ Configuration Guide
491
415
 
492
- Built-in Model Context Protocol servers (enabled by default):
416
+ The Pantheon listens to your commands through sacred JSON scriptures. Here is how you shape their behavior.
493
417
 
494
- | MCP | Purpose | URL |
495
- |-----|---------|-----|
496
- | `websearch` | Real-time web search via Exa AI | `https://mcp.exa.ai/mcp` |
497
- | `context7` | Official library documentation | `https://mcp.context7.com/mcp` |
498
- | `grep_app` | GitHub code search via grep.app | `https://mcp.grep.app` |
418
+ ### Locations & Precedence
419
+
420
+ The plugin merges configuration from two locations. Settings in the **Project Local** file override those in the **User Global** file.
421
+
422
+ | Level | Path | Scope |
423
+ | :--- | :--- | :--- |
424
+ | **User Global** | `~/.config/opencode/oh-my-opencode-slim.json` | All projects for this user |
425
+ | **Project Local** | `./.opencode/oh-my-opencode-slim.json` | This specific repository |
426
+
427
+ > **Note for Windows Users:** The global config is located at `%APPDATA%\opencode\oh-my-opencode-slim.json` or `~/.config/opencode/oh-my-opencode-slim.json`.
499
428
 
500
- ### Disabling MCPs
429
+ ---
430
+
431
+ ### General Settings
501
432
 
502
- You can disable specific MCP servers in your config:
433
+ #### OpenCode Server
434
+ To enable certain integrations (like Tmux), you must first enable the OpenCode HTTP server in your main `opencode.json` file.
503
435
 
436
+ **File:** `~/.config/opencode/opencode.json`
504
437
  ```json
505
438
  {
506
- "disabled_mcps": ["websearch", "grep_app"]
439
+ "server": {
440
+ "port": 4096
441
+ }
507
442
  }
508
443
  ```
509
444
 
510
445
  ---
511
446
 
512
- ## Configuration
447
+ ### Agent Configuration
448
+
449
+ You can customize the underlying LLM and reasoning effort for each deity in the Pantheon.
450
+
451
+ **File:** `oh-my-opencode-slim.json`
452
+ ```json
453
+ {
454
+ "agents": {
455
+ "orchestrator": {
456
+ "model": "openai/gpt-5.2-codex",
457
+ "variant": "high"
458
+ },
459
+ "explore": {
460
+ "model": "opencode/glm-4.7",
461
+ "variant": "low"
462
+ }
463
+ },
464
+ "disabled_agents": ["multimodal-looker", "code-simplicity-reviewer"]
465
+ }
466
+ ```
467
+
468
+ #### Agent Settings Reference
469
+ | Option | Type | Default | Description |
470
+ |--------|------|---------|-------------|
471
+ | `agents.<name>.model` | string | *Varies* | Override the LLM for a specific agent |
472
+ | `agents.<name>.variant` | string | `"medium"` | Reasoning level (`low`, `medium`, `high`) |
473
+ | `disabled_agents` | array | `[]` | List of agents to completely deactivate |
474
+
475
+ ---
476
+
477
+ ### Tmux Setup
478
+
479
+ Watch your agents work in real-time by enabling the Tmux integration. This requires the [OpenCode Server](#opencode-server) to be active.
480
+
481
+ **File:** `oh-my-opencode-slim.json`
482
+ ```json
483
+ {
484
+ "tmux": {
485
+ "enabled": true,
486
+ "layout": "main-vertical",
487
+ "main_pane_size": 60
488
+ }
489
+ }
490
+ ```
513
491
 
514
- You can customize the behavior of the plugin via JSON configuration files.
492
+ #### Tmux Settings Reference
493
+ | Option | Type | Default | Description |
494
+ |--------|------|---------|-------------|
495
+ | `enabled` | boolean | `false` | Enable/disable tmux integration |
496
+ | `layout` | string | `"main-vertical"` | Layout preset (see below) |
497
+ | `main_pane_size` | number | `60` | Size of main session pane as % (20-80) |
515
498
 
516
- ### Configuration Files
499
+ **Available Layouts:**
500
+ - `main-vertical`: Main session left, agents stacked right.
501
+ - `main-horizontal`: Main session top, agents stacked below.
502
+ - `tiled`: Equal-sized grid.
503
+ - `even-horizontal` / `even-vertical`: Evenly distributed panes.
517
504
 
518
- The plugin looks for configuration in two places (and merges them):
505
+ ---
519
506
 
520
- 1. **User Global**: `~/.config/opencode/oh-my-opencode-slim.json` (or OS equivalent)
521
- 2. **Project Local**: `./.opencode/oh-my-opencode-slim.json`
507
+ ### MCP Management
522
508
 
523
- | Platform | User Config Path |
524
- | :--- | :--- |
525
- | **Windows** | `~/.config/opencode/oh-my-opencode-slim.json` or `%APPDATA%\opencode\oh-my-opencode-slim.json` |
526
- | **macOS/Linux** | `~/.config/opencode/oh-my-opencode-slim.json` |
509
+ The Pantheon comes equipped with built-in Model Context Protocol (MCP) servers.
527
510
 
528
- ### Disabling Agents
511
+ | MCP | Purpose | URL |
512
+ |-----|---------|-----|
513
+ | `websearch` | Real-time web search via Exa AI | `https://mcp.exa.ai/mcp` |
514
+ | `context7` | Official library documentation | `https://mcp.context7.com/mcp` |
515
+ | `grep_app` | GitHub code search via grep.app | `https://mcp.grep.app` |
529
516
 
530
- You can disable specific agents using the `disabled_agents` array:
517
+ #### Disabling MCPs
518
+ If you wish to silence an MCP server, add it to the `disabled_mcps` array in your config:
531
519
 
520
+ **File:** `oh-my-opencode-slim.json`
532
521
  ```json
533
522
  {
534
- "disabled_agents": ["multimodal-looker", "code-simplicity-reviewer"]
523
+ "disabled_mcps": ["websearch", "grep_app"]
535
524
  }
536
525
  ```
537
526
 
538
527
  ---
539
528
 
540
- ## Uninstallation
529
+ ## 🗑️ Uninstallation
541
530
 
542
531
  1. **Remove the plugin from your OpenCode config**:
543
-
544
532
  Edit `~/.config/opencode/opencode.json` and remove `"oh-my-opencode-slim"` from the `plugin` array.
545
533
 
546
534
  2. **Remove configuration files (optional)**:
@@ -4,6 +4,7 @@ export declare const AgentOverrideConfigSchema: z.ZodObject<{
4
4
  temperature: z.ZodOptional<z.ZodNumber>;
5
5
  prompt: z.ZodOptional<z.ZodString>;
6
6
  prompt_append: z.ZodOptional<z.ZodString>;
7
+ variant: z.ZodCatch<z.ZodOptional<z.ZodString>>;
7
8
  disable: z.ZodOptional<z.ZodBoolean>;
8
9
  }, z.core.$strip>;
9
10
  export declare const TmuxLayoutSchema: z.ZodEnum<{
@@ -39,6 +40,7 @@ export declare const PluginConfigSchema: z.ZodObject<{
39
40
  temperature: z.ZodOptional<z.ZodNumber>;
40
41
  prompt: z.ZodOptional<z.ZodString>;
41
42
  prompt_append: z.ZodOptional<z.ZodString>;
43
+ variant: z.ZodCatch<z.ZodOptional<z.ZodString>>;
42
44
  disable: z.ZodOptional<z.ZodBoolean>;
43
45
  }, z.core.$strip>>>;
44
46
  disabled_agents: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -1,5 +1,6 @@
1
1
  import type { PluginInput } from "@opencode-ai/plugin";
2
2
  import type { TmuxConfig } from "../config/schema";
3
+ import type { PluginConfig } from "../config";
3
4
  export interface BackgroundTask {
4
5
  id: string;
5
6
  sessionId: string;
@@ -24,7 +25,8 @@ export declare class BackgroundTaskManager {
24
25
  private directory;
25
26
  private pollInterval?;
26
27
  private tmuxEnabled;
27
- constructor(ctx: PluginInput, tmuxConfig?: TmuxConfig);
28
+ private config?;
29
+ constructor(ctx: PluginInput, tmuxConfig?: TmuxConfig, config?: PluginConfig);
28
30
  launch(opts: LaunchOptions): Promise<BackgroundTask>;
29
31
  getResult(taskId: string, block?: boolean, timeout?: number): Promise<BackgroundTask | null>;
30
32
  cancel(taskId?: string): number;
package/dist/index.js CHANGED
@@ -20484,6 +20484,7 @@ var AgentOverrideConfigSchema = exports_external.object({
20484
20484
  temperature: exports_external.number().min(0).max(2).optional(),
20485
20485
  prompt: exports_external.string().optional(),
20486
20486
  prompt_append: exports_external.string().optional(),
20487
+ variant: exports_external.string().optional().catch(undefined),
20487
20488
  disable: exports_external.boolean().optional()
20488
20489
  });
20489
20490
  var TmuxLayoutSchema = exports_external.enum([
@@ -20606,13 +20607,13 @@ You are an AI coding orchestrator. You DO NOT implement - you DELEGATE.
20606
20607
  **Core Rule:** If a specialist agent can do the work, YOU MUST delegate to them.
20607
20608
 
20608
20609
  **Why Delegation Matters:**
20609
- - @frontend-ui-ux-engineer \u2192 10x better designs than you
20610
- - @librarian \u2192 finds docs you'd miss
20611
- - @explore \u2192 searches faster than you
20612
- - @oracle \u2192 catches architectural issues you'd overlook
20613
- - @document-writer \u2192 writes cleaner docs for less cost
20614
- - @code-simplicity-reviewer \u2192 spots complexity you're blind to
20615
- - @multimodal-looker \u2192 understands images you can't parse
20610
+ - @frontend-ui-ux-engineer \u2192 10x better designs than you \u2192 improves quality
20611
+ - @librarian \u2192 finds docs you'd miss \u2192 improves speed and quality
20612
+ - @explore \u2192 searches faster than you \u2192 improves speed
20613
+ - @oracle \u2192 catches architectural issues you'd overlook \u2192 improves quality
20614
+ - @document-writer \u2192 writes cleaner docs for less cost \u2192 reduceses cost
20615
+ - @code-simplicity-reviewer \u2192 spots complexity you're blind to \u2192 improves quality
20616
+ - @multimodal-looker \u2192 understands images you can't parse \u2192 improves speed and quality
20616
20617
 
20617
20618
  **Your value is in orchestration, not implementation.**
20618
20619
  </Role>
@@ -21066,6 +21067,10 @@ function applyOverrides(agent, override) {
21066
21067
  ${override.prompt_append}`;
21067
21068
  }
21068
21069
  }
21070
+ function applyDefaultPermissions(agent) {
21071
+ const existing = agent.config.permission ?? {};
21072
+ agent.config.permission = { ...existing, question: "allow" };
21073
+ }
21069
21074
  var SUBAGENT_FACTORIES = {
21070
21075
  explore: createExploreAgent,
21071
21076
  librarian: createLibrarianAgent,
@@ -21091,6 +21096,7 @@ function createAgents(config2) {
21091
21096
  });
21092
21097
  const orchestratorModel = agentOverrides["orchestrator"]?.model ?? DEFAULT_MODELS["orchestrator"];
21093
21098
  const orchestrator = createOrchestratorAgent(orchestratorModel);
21099
+ applyDefaultPermissions(orchestrator);
21094
21100
  const oOverride = agentOverrides["orchestrator"];
21095
21101
  if (oOverride) {
21096
21102
  applyOverrides(orchestrator, oOverride);
@@ -21101,157 +21107,6 @@ function getAgentConfigs(config2) {
21101
21107
  const agents = createAgents(config2);
21102
21108
  return Object.fromEntries(agents.map((a) => [a.name, { ...a.config, description: a.description }]));
21103
21109
  }
21104
-
21105
- // src/features/background-manager.ts
21106
- function generateTaskId() {
21107
- return `bg_${Math.random().toString(36).substring(2, 10)}`;
21108
- }
21109
-
21110
- class BackgroundTaskManager {
21111
- tasks = new Map;
21112
- client;
21113
- directory;
21114
- pollInterval;
21115
- tmuxEnabled;
21116
- constructor(ctx, tmuxConfig) {
21117
- this.client = ctx.client;
21118
- this.directory = ctx.directory;
21119
- this.tmuxEnabled = tmuxConfig?.enabled ?? false;
21120
- }
21121
- async launch(opts) {
21122
- const session = await this.client.session.create({
21123
- body: {
21124
- parentID: opts.parentSessionId,
21125
- title: `Background: ${opts.description}`
21126
- },
21127
- query: { directory: this.directory }
21128
- });
21129
- if (!session.data?.id) {
21130
- throw new Error("Failed to create background session");
21131
- }
21132
- const task = {
21133
- id: generateTaskId(),
21134
- sessionId: session.data.id,
21135
- description: opts.description,
21136
- agent: opts.agent,
21137
- status: "running",
21138
- startedAt: new Date
21139
- };
21140
- this.tasks.set(task.id, task);
21141
- this.startPolling();
21142
- if (this.tmuxEnabled) {
21143
- await new Promise((r) => setTimeout(r, 500));
21144
- }
21145
- const promptQuery = {
21146
- directory: this.directory
21147
- };
21148
- if (opts.model) {
21149
- promptQuery.model = opts.model;
21150
- }
21151
- await this.client.session.prompt({
21152
- path: { id: session.data.id },
21153
- body: {
21154
- agent: opts.agent,
21155
- tools: { background_task: false, task: false },
21156
- parts: [{ type: "text", text: opts.prompt }]
21157
- },
21158
- query: promptQuery
21159
- });
21160
- return task;
21161
- }
21162
- async getResult(taskId, block = false, timeout = 120000) {
21163
- const task = this.tasks.get(taskId);
21164
- if (!task)
21165
- return null;
21166
- if (!block || task.status === "completed" || task.status === "failed") {
21167
- return task;
21168
- }
21169
- const deadline = Date.now() + timeout;
21170
- while (Date.now() < deadline) {
21171
- await this.pollTask(task);
21172
- const status = task.status;
21173
- if (status === "completed" || status === "failed") {
21174
- return task;
21175
- }
21176
- await new Promise((r) => setTimeout(r, POLL_INTERVAL_SLOW_MS));
21177
- }
21178
- return task;
21179
- }
21180
- cancel(taskId) {
21181
- if (taskId) {
21182
- const task = this.tasks.get(taskId);
21183
- if (task && task.status === "running") {
21184
- task.status = "failed";
21185
- task.error = "Cancelled by user";
21186
- task.completedAt = new Date;
21187
- return 1;
21188
- }
21189
- return 0;
21190
- }
21191
- let count = 0;
21192
- for (const task of this.tasks.values()) {
21193
- if (task.status === "running") {
21194
- task.status = "failed";
21195
- task.error = "Cancelled by user";
21196
- task.completedAt = new Date;
21197
- count++;
21198
- }
21199
- }
21200
- return count;
21201
- }
21202
- startPolling() {
21203
- if (this.pollInterval)
21204
- return;
21205
- this.pollInterval = setInterval(() => this.pollAllTasks(), POLL_INTERVAL_BACKGROUND_MS);
21206
- }
21207
- async pollAllTasks() {
21208
- const runningTasks = [...this.tasks.values()].filter((t) => t.status === "running");
21209
- if (runningTasks.length === 0 && this.pollInterval) {
21210
- clearInterval(this.pollInterval);
21211
- this.pollInterval = undefined;
21212
- return;
21213
- }
21214
- for (const task of runningTasks) {
21215
- await this.pollTask(task);
21216
- }
21217
- }
21218
- async pollTask(task) {
21219
- try {
21220
- const statusResult = await this.client.session.status();
21221
- const allStatuses = statusResult.data ?? {};
21222
- const sessionStatus = allStatuses[task.sessionId];
21223
- if (sessionStatus && sessionStatus.type !== "idle") {
21224
- return;
21225
- }
21226
- const messagesResult = await this.client.session.messages({ path: { id: task.sessionId } });
21227
- const messages = messagesResult.data ?? messagesResult;
21228
- const assistantMessages = messages.filter((m) => m.info?.role === "assistant");
21229
- if (assistantMessages.length === 0) {
21230
- return;
21231
- }
21232
- const extractedContent = [];
21233
- for (const message of assistantMessages) {
21234
- for (const part of message.parts ?? []) {
21235
- if ((part.type === "text" || part.type === "reasoning") && part.text) {
21236
- extractedContent.push(part.text);
21237
- }
21238
- }
21239
- }
21240
- const responseText = extractedContent.filter((t) => t.length > 0).join(`
21241
-
21242
- `);
21243
- if (responseText) {
21244
- task.result = responseText;
21245
- task.status = "completed";
21246
- task.completedAt = new Date;
21247
- }
21248
- } catch (error48) {
21249
- task.status = "failed";
21250
- task.error = error48 instanceof Error ? error48.message : String(error48);
21251
- task.completedAt = new Date;
21252
- }
21253
- }
21254
- }
21255
21110
  // src/utils/tmux.ts
21256
21111
  var {spawn } = globalThis.Bun;
21257
21112
 
@@ -21471,7 +21326,193 @@ function startTmuxCheck() {
21471
21326
  getTmuxPath().catch(() => {});
21472
21327
  }
21473
21328
  }
21329
+ // src/utils/agent-variant.ts
21330
+ function normalizeAgentName(agentName) {
21331
+ const trimmed = agentName.trim();
21332
+ return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
21333
+ }
21334
+ function resolveAgentVariant(config2, agentName) {
21335
+ const normalized = normalizeAgentName(agentName);
21336
+ const rawVariant = config2?.agents?.[normalized]?.variant;
21337
+ if (typeof rawVariant !== "string") {
21338
+ log(`[variant] no variant configured for agent "${normalized}"`);
21339
+ return;
21340
+ }
21341
+ const trimmed = rawVariant.trim();
21342
+ if (trimmed.length === 0) {
21343
+ log(`[variant] empty variant for agent "${normalized}" (ignored)`);
21344
+ return;
21345
+ }
21346
+ log(`[variant] resolved variant="${trimmed}" for agent "${normalized}"`);
21347
+ return trimmed;
21348
+ }
21349
+ function applyAgentVariant(variant, body) {
21350
+ if (!variant) {
21351
+ log("[variant] no variant to apply (skipped)");
21352
+ return body;
21353
+ }
21354
+ if (body.variant) {
21355
+ log(`[variant] body already has variant="${body.variant}" (not overriding)`);
21356
+ return body;
21357
+ }
21358
+ log(`[variant] applied variant="${variant}" to prompt body`);
21359
+ return { ...body, variant };
21360
+ }
21361
+ // src/features/background-manager.ts
21362
+ function generateTaskId() {
21363
+ return `bg_${Math.random().toString(36).substring(2, 10)}`;
21364
+ }
21474
21365
 
21366
+ class BackgroundTaskManager {
21367
+ tasks = new Map;
21368
+ client;
21369
+ directory;
21370
+ pollInterval;
21371
+ tmuxEnabled;
21372
+ config;
21373
+ constructor(ctx, tmuxConfig, config2) {
21374
+ this.client = ctx.client;
21375
+ this.directory = ctx.directory;
21376
+ this.tmuxEnabled = tmuxConfig?.enabled ?? false;
21377
+ this.config = config2;
21378
+ }
21379
+ async launch(opts) {
21380
+ const session = await this.client.session.create({
21381
+ body: {
21382
+ parentID: opts.parentSessionId,
21383
+ title: `Background: ${opts.description}`
21384
+ },
21385
+ query: { directory: this.directory }
21386
+ });
21387
+ if (!session.data?.id) {
21388
+ throw new Error("Failed to create background session");
21389
+ }
21390
+ const task = {
21391
+ id: generateTaskId(),
21392
+ sessionId: session.data.id,
21393
+ description: opts.description,
21394
+ agent: opts.agent,
21395
+ status: "running",
21396
+ startedAt: new Date
21397
+ };
21398
+ this.tasks.set(task.id, task);
21399
+ this.startPolling();
21400
+ if (this.tmuxEnabled) {
21401
+ await new Promise((r) => setTimeout(r, 500));
21402
+ }
21403
+ const promptQuery = {
21404
+ directory: this.directory
21405
+ };
21406
+ if (opts.model) {
21407
+ promptQuery.model = opts.model;
21408
+ }
21409
+ log(`[background-manager] launching task for agent="${opts.agent}"`, { description: opts.description });
21410
+ const resolvedVariant = resolveAgentVariant(this.config, opts.agent);
21411
+ const promptBody = applyAgentVariant(resolvedVariant, {
21412
+ agent: opts.agent,
21413
+ tools: { background_task: false, task: false },
21414
+ parts: [{ type: "text", text: opts.prompt }]
21415
+ });
21416
+ await this.client.session.prompt({
21417
+ path: { id: session.data.id },
21418
+ body: promptBody,
21419
+ query: promptQuery
21420
+ });
21421
+ return task;
21422
+ }
21423
+ async getResult(taskId, block = false, timeout = 120000) {
21424
+ const task = this.tasks.get(taskId);
21425
+ if (!task)
21426
+ return null;
21427
+ if (!block || task.status === "completed" || task.status === "failed") {
21428
+ return task;
21429
+ }
21430
+ const deadline = Date.now() + timeout;
21431
+ while (Date.now() < deadline) {
21432
+ await this.pollTask(task);
21433
+ const status = task.status;
21434
+ if (status === "completed" || status === "failed") {
21435
+ return task;
21436
+ }
21437
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_SLOW_MS));
21438
+ }
21439
+ return task;
21440
+ }
21441
+ cancel(taskId) {
21442
+ if (taskId) {
21443
+ const task = this.tasks.get(taskId);
21444
+ if (task && task.status === "running") {
21445
+ task.status = "failed";
21446
+ task.error = "Cancelled by user";
21447
+ task.completedAt = new Date;
21448
+ return 1;
21449
+ }
21450
+ return 0;
21451
+ }
21452
+ let count = 0;
21453
+ for (const task of this.tasks.values()) {
21454
+ if (task.status === "running") {
21455
+ task.status = "failed";
21456
+ task.error = "Cancelled by user";
21457
+ task.completedAt = new Date;
21458
+ count++;
21459
+ }
21460
+ }
21461
+ return count;
21462
+ }
21463
+ startPolling() {
21464
+ if (this.pollInterval)
21465
+ return;
21466
+ this.pollInterval = setInterval(() => this.pollAllTasks(), POLL_INTERVAL_BACKGROUND_MS);
21467
+ }
21468
+ async pollAllTasks() {
21469
+ const runningTasks = [...this.tasks.values()].filter((t) => t.status === "running");
21470
+ if (runningTasks.length === 0 && this.pollInterval) {
21471
+ clearInterval(this.pollInterval);
21472
+ this.pollInterval = undefined;
21473
+ return;
21474
+ }
21475
+ for (const task of runningTasks) {
21476
+ await this.pollTask(task);
21477
+ }
21478
+ }
21479
+ async pollTask(task) {
21480
+ try {
21481
+ const statusResult = await this.client.session.status();
21482
+ const allStatuses = statusResult.data ?? {};
21483
+ const sessionStatus = allStatuses[task.sessionId];
21484
+ if (sessionStatus && sessionStatus.type !== "idle") {
21485
+ return;
21486
+ }
21487
+ const messagesResult = await this.client.session.messages({ path: { id: task.sessionId } });
21488
+ const messages = messagesResult.data ?? messagesResult;
21489
+ const assistantMessages = messages.filter((m) => m.info?.role === "assistant");
21490
+ if (assistantMessages.length === 0) {
21491
+ return;
21492
+ }
21493
+ const extractedContent = [];
21494
+ for (const message of assistantMessages) {
21495
+ for (const part of message.parts ?? []) {
21496
+ if ((part.type === "text" || part.type === "reasoning") && part.text) {
21497
+ extractedContent.push(part.text);
21498
+ }
21499
+ }
21500
+ }
21501
+ const responseText = extractedContent.filter((t) => t.length > 0).join(`
21502
+
21503
+ `);
21504
+ if (responseText) {
21505
+ task.result = responseText;
21506
+ task.status = "completed";
21507
+ task.completedAt = new Date;
21508
+ }
21509
+ } catch (error48) {
21510
+ task.status = "failed";
21511
+ task.error = error48 instanceof Error ? error48.message : String(error48);
21512
+ task.completedAt = new Date;
21513
+ }
21514
+ }
21515
+ }
21475
21516
  // src/features/tmux-session-manager.ts
21476
21517
  var POLL_INTERVAL_MS2 = 2000;
21477
21518
  var SESSION_TIMEOUT_MS = 10 * 60 * 1000;
@@ -33922,7 +33963,7 @@ function tool(input) {
33922
33963
  tool.schema = exports_external2;
33923
33964
  // src/tools/background.ts
33924
33965
  var z2 = tool.schema;
33925
- function createBackgroundTools(ctx, manager, tmuxConfig) {
33966
+ function createBackgroundTools(ctx, manager, tmuxConfig, pluginConfig) {
33926
33967
  const agentNames = getAgentNames().join(", ");
33927
33968
  const background_task = tool({
33928
33969
  description: `Run agent task. Use sync=true to wait for result, sync=false (default) to run in background.
@@ -33945,7 +33986,7 @@ Sync mode blocks until completion and returns the result directly.`,
33945
33986
  const description = String(args.description);
33946
33987
  const isSync = args.sync === true;
33947
33988
  if (isSync) {
33948
- return await executeSync(description, prompt, agent, tctx, ctx, tmuxConfig, args.session_id);
33989
+ return await executeSync(description, prompt, agent, tctx, ctx, tmuxConfig, pluginConfig, args.session_id);
33949
33990
  }
33950
33991
  const task = await manager.launch({
33951
33992
  agent,
@@ -34016,7 +34057,7 @@ Duration: ${duration5}
34016
34057
  });
34017
34058
  return { background_task, background_output, background_cancel };
34018
34059
  }
34019
- async function executeSync(description, prompt, agent, toolContext, ctx, tmuxConfig, existingSessionId) {
34060
+ async function executeSync(description, prompt, agent, toolContext, ctx, tmuxConfig, pluginConfig, existingSessionId) {
34020
34061
  let sessionID;
34021
34062
  if (existingSessionId) {
34022
34063
  const sessionResult = await ctx.client.session.get({ path: { id: existingSessionId } });
@@ -34042,14 +34083,18 @@ async function executeSync(description, prompt, agent, toolContext, ctx, tmuxCon
34042
34083
  await new Promise((r) => setTimeout(r, 500));
34043
34084
  }
34044
34085
  }
34086
+ log(`[background-sync] launching sync task for agent="${agent}"`, { description });
34087
+ const resolvedVariant = resolveAgentVariant(pluginConfig, agent);
34088
+ const baseBody = {
34089
+ agent,
34090
+ tools: { background_task: false, task: false },
34091
+ parts: [{ type: "text", text: prompt }]
34092
+ };
34093
+ const promptBody = applyAgentVariant(resolvedVariant, baseBody);
34045
34094
  try {
34046
34095
  await ctx.client.session.prompt({
34047
34096
  path: { id: sessionID },
34048
- body: {
34049
- agent,
34050
- tools: { background_task: false, task: false },
34051
- parts: [{ type: "text", text: prompt }]
34052
- }
34097
+ body: promptBody
34053
34098
  });
34054
34099
  } catch (error92) {
34055
34100
  return `Error: Failed to send prompt: ${error92 instanceof Error ? error92.message : String(error92)}
@@ -40931,8 +40976,8 @@ var OhMyOpenCodeLite = async (ctx) => {
40931
40976
  if (tmuxConfig.enabled) {
40932
40977
  startTmuxCheck();
40933
40978
  }
40934
- const backgroundManager = new BackgroundTaskManager(ctx, tmuxConfig);
40935
- const backgroundTools = createBackgroundTools(ctx, backgroundManager, tmuxConfig);
40979
+ const backgroundManager = new BackgroundTaskManager(ctx, tmuxConfig, config3);
40980
+ const backgroundTools = createBackgroundTools(ctx, backgroundManager, tmuxConfig, config3);
40936
40981
  const mcps = createBuiltinMcps(config3.disabled_mcps);
40937
40982
  const skillMcpManager = SkillMcpManager.getInstance();
40938
40983
  const skillTools = createSkillTools(skillMcpManager);
@@ -1,4 +1,5 @@
1
1
  import { type PluginInput, type ToolDefinition } from "@opencode-ai/plugin";
2
2
  import type { BackgroundTaskManager } from "../features";
3
3
  import type { TmuxConfig } from "../config/schema";
4
- export declare function createBackgroundTools(ctx: PluginInput, manager: BackgroundTaskManager, tmuxConfig?: TmuxConfig): Record<string, ToolDefinition>;
4
+ import type { PluginConfig } from "../config";
5
+ export declare function createBackgroundTools(ctx: PluginInput, manager: BackgroundTaskManager, tmuxConfig?: TmuxConfig, pluginConfig?: PluginConfig): Record<string, ToolDefinition>;
@@ -0,0 +1,6 @@
1
+ import type { PluginConfig } from "../config";
2
+ export declare function normalizeAgentName(agentName: string): string;
3
+ export declare function resolveAgentVariant(config: PluginConfig | undefined, agentName: string): string | undefined;
4
+ export declare function applyAgentVariant<T extends {
5
+ variant?: string;
6
+ }>(variant: string | undefined, body: T): T;
@@ -1,2 +1,3 @@
1
1
  export * from "./polling";
2
2
  export * from "./tmux";
3
+ export * from "./agent-variant";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode-slim",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
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",