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 +15 -10
- package/dist/cli/index.js +3 -1
- package/dist/config/council-schema.d.ts +1 -0
- package/dist/config/schema.d.ts +1 -0
- package/dist/council/council-manager.d.ts +3 -0
- package/dist/index.js +209 -22
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/preset-manager.d.ts +27 -0
- package/package.json +1 -1
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
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
|
|
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="
|
|
420
|
-
<
|
|
421
|
-
<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;
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -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
|
|
22630
|
+
function cleanupAllSessions(saveDir) {
|
|
22617
22631
|
const now = Date.now();
|
|
22618
|
-
|
|
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(
|
|
22635
|
+
lastCleanupByDir.set(saveDir, now);
|
|
22636
|
+
const maxAge = 60 * 60 * 1000;
|
|
22637
|
+
const dirsToScan = [];
|
|
22625
22638
|
try {
|
|
22626
|
-
const
|
|
22627
|
-
|
|
22628
|
-
|
|
22629
|
-
|
|
22630
|
-
|
|
22631
|
-
|
|
22632
|
-
|
|
22633
|
-
|
|
22634
|
-
|
|
22635
|
-
|
|
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: ${
|
|
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) => {
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -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