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 +96 -108
- package/dist/config/schema.d.ts +2 -0
- package/dist/features/background-manager.d.ts +3 -1
- package/dist/index.js +213 -168
- package/dist/tools/background.d.ts +2 -1
- package/dist/utils/agent-variant.d.ts +6 -0
- package/dist/utils/index.d.ts +1 -0
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
42
|
-
- [
|
|
43
|
-
- [
|
|
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
|
-
##
|
|
414
|
+
## ⚙️ Configuration Guide
|
|
491
415
|
|
|
492
|
-
|
|
416
|
+
The Pantheon listens to your commands through sacred JSON scriptures. Here is how you shape their behavior.
|
|
493
417
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
|
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
|
-
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
### General Settings
|
|
501
432
|
|
|
502
|
-
|
|
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
|
-
"
|
|
439
|
+
"server": {
|
|
440
|
+
"port": 4096
|
|
441
|
+
}
|
|
507
442
|
}
|
|
508
443
|
```
|
|
509
444
|
|
|
510
445
|
---
|
|
511
446
|
|
|
512
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
505
|
+
---
|
|
519
506
|
|
|
520
|
-
|
|
521
|
-
2. **Project Local**: `./.opencode/oh-my-opencode-slim.json`
|
|
507
|
+
### MCP Management
|
|
522
508
|
|
|
523
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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)**:
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/utils/index.d.ts
CHANGED
package/package.json
CHANGED