oh-my-claudecode-opencode 0.6.2 → 0.6.4
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/assets/skills/version.md +69 -0
- package/dist/config/model-resolver.d.ts +18 -2
- package/dist/index.js +191 -34
- package/dist/state/session-pause-state.d.ts +33 -0
- package/dist/tools/background-manager.d.ts +8 -2
- package/dist/tools/call-omco-agent.d.ts +2 -1
- package/dist/tools/model-resolution-service.d.ts +38 -0
- package/package.json +4 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: version
|
|
3
|
+
description: Check OMCO plugin version and update status
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Version Check Skill
|
|
8
|
+
|
|
9
|
+
Display the current OMCO plugin version and check for updates.
|
|
10
|
+
|
|
11
|
+
## What to Do
|
|
12
|
+
|
|
13
|
+
When user invokes this skill, run the following checks:
|
|
14
|
+
|
|
15
|
+
### 1. Check Installed Version
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Read version from installed location
|
|
19
|
+
cat ~/.config/opencode/node_modules/oh-my-claudecode-opencode/package.json 2>/dev/null | grep '"version"' | head -1
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. Check Development Version (if in dev directory)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# If in oh-my-claudecode-opencode project directory
|
|
26
|
+
cat package.json 2>/dev/null | grep '"version"' | head -1
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 3. Check Latest Version on npm
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm view oh-my-claudecode-opencode version 2>/dev/null
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Output Format
|
|
36
|
+
|
|
37
|
+
Present the information in a clear table:
|
|
38
|
+
|
|
39
|
+
| Location | Version |
|
|
40
|
+
|----------|---------|
|
|
41
|
+
| Installed | X.X.X |
|
|
42
|
+
| Development | X.X.X (if applicable) |
|
|
43
|
+
| npm Latest | X.X.X |
|
|
44
|
+
|
|
45
|
+
### Status Indicators
|
|
46
|
+
|
|
47
|
+
- **Up to date**: Installed version matches npm latest
|
|
48
|
+
- **Update available**: npm has newer version
|
|
49
|
+
- **Ahead of npm**: Local version is higher (pre-release or unpublished)
|
|
50
|
+
- **Mismatch**: Development and installed versions differ
|
|
51
|
+
|
|
52
|
+
## Update Instructions
|
|
53
|
+
|
|
54
|
+
If update is available:
|
|
55
|
+
```bash
|
|
56
|
+
cd ~/.config/opencode && npm install oh-my-claudecode-opencode@latest
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If development version differs from installed:
|
|
60
|
+
```bash
|
|
61
|
+
# From development directory
|
|
62
|
+
bun run build
|
|
63
|
+
cp -r dist/ ~/.config/opencode/node_modules/oh-my-claudecode-opencode/dist/
|
|
64
|
+
cp package.json ~/.config/opencode/node_modules/oh-my-claudecode-opencode/package.json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## After Update
|
|
68
|
+
|
|
69
|
+
Remind users to restart OpenCode for changes to take effect.
|
|
@@ -22,13 +22,29 @@ export interface ModelResolutionResult {
|
|
|
22
22
|
source: "per-agent-override" | "tier-default" | "hardcoded-fallback";
|
|
23
23
|
originalTier?: ModelTier;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Default tier-to-model mapping.
|
|
27
|
+
* These use generic names - configure model_mapping.tierDefaults in
|
|
28
|
+
* ~/.config/opencode/oh-my-opencode.json for your specific provider.
|
|
29
|
+
*
|
|
30
|
+
* Example config:
|
|
31
|
+
* {
|
|
32
|
+
* "model_mapping": {
|
|
33
|
+
* "tierDefaults": {
|
|
34
|
+
* "haiku": "google/gemini-2.0-flash",
|
|
35
|
+
* "sonnet": "anthropic/claude-sonnet-4",
|
|
36
|
+
* "opus": "anthropic/claude-opus-4"
|
|
37
|
+
* }
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
*/
|
|
25
41
|
export declare const HARDCODED_TIER_DEFAULTS: TierModelMapping;
|
|
26
42
|
/**
|
|
27
|
-
* Check if model follows "provider/model-name" pattern
|
|
43
|
+
* Check if model follows "provider/model-name" pattern or is a simple tier name
|
|
28
44
|
*/
|
|
29
45
|
export declare function isValidModelFormat(model: string): boolean;
|
|
30
46
|
/**
|
|
31
|
-
* Log warning if invalid format
|
|
47
|
+
* Log warning if invalid format (skip for simple tier names)
|
|
32
48
|
*/
|
|
33
49
|
export declare function validateModelFormat(model: string, context: string): void;
|
|
34
50
|
export declare class ModelResolver {
|
package/dist/index.js
CHANGED
|
@@ -9221,7 +9221,7 @@ class Doc {
|
|
|
9221
9221
|
var version = {
|
|
9222
9222
|
major: 4,
|
|
9223
9223
|
minor: 3,
|
|
9224
|
-
patch:
|
|
9224
|
+
patch: 5
|
|
9225
9225
|
};
|
|
9226
9226
|
|
|
9227
9227
|
// node_modules/zod/v4/core/schemas.js
|
|
@@ -10507,7 +10507,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
|
10507
10507
|
if (keyResult instanceof Promise) {
|
|
10508
10508
|
throw new Error("Async schemas not supported in object keys currently");
|
|
10509
10509
|
}
|
|
10510
|
-
const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length;
|
|
10510
|
+
const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length && keyResult.issues.some((iss) => iss.code === "invalid_type" && iss.expected === "number");
|
|
10511
10511
|
if (checkNumericKey) {
|
|
10512
10512
|
const retryResult = def.keyType._zod.run({ value: Number(key), issues: [] }, ctx);
|
|
10513
10513
|
if (retryResult instanceof Promise) {
|
|
@@ -17878,7 +17878,7 @@ function finalize(ctx, schema) {
|
|
|
17878
17878
|
}
|
|
17879
17879
|
}
|
|
17880
17880
|
}
|
|
17881
|
-
if (refSchema.$ref
|
|
17881
|
+
if (refSchema.$ref) {
|
|
17882
17882
|
for (const key in schema2) {
|
|
17883
17883
|
if (key === "$ref" || key === "allOf")
|
|
17884
17884
|
continue;
|
|
@@ -21669,9 +21669,10 @@ function generateTaskId() {
|
|
|
21669
21669
|
taskCounter++;
|
|
21670
21670
|
return `bg_${Date.now().toString(36)}_${taskCounter.toString(36)}`;
|
|
21671
21671
|
}
|
|
21672
|
-
function createBackgroundManager(ctx, config2) {
|
|
21672
|
+
function createBackgroundManager(ctx, config2, modelService) {
|
|
21673
21673
|
const tasks = new Map;
|
|
21674
21674
|
const defaultConcurrency = config2?.defaultConcurrency ?? 5;
|
|
21675
|
+
const modelCache = new Map;
|
|
21675
21676
|
const getRunningCount = (parentSessionID) => {
|
|
21676
21677
|
let count = 0;
|
|
21677
21678
|
for (const task of tasks.values()) {
|
|
@@ -21683,7 +21684,34 @@ function createBackgroundManager(ctx, config2) {
|
|
|
21683
21684
|
}
|
|
21684
21685
|
return count;
|
|
21685
21686
|
};
|
|
21686
|
-
const
|
|
21687
|
+
const getParentSessionModel = async (parentSessionID) => {
|
|
21688
|
+
if (modelCache.has(parentSessionID)) {
|
|
21689
|
+
return modelCache.get(parentSessionID);
|
|
21690
|
+
}
|
|
21691
|
+
try {
|
|
21692
|
+
const messagesResp = await ctx.client.session.messages({
|
|
21693
|
+
path: { id: parentSessionID },
|
|
21694
|
+
query: { directory: ctx.directory }
|
|
21695
|
+
});
|
|
21696
|
+
const messages = messagesResp.data;
|
|
21697
|
+
const assistantMsg = messages?.find((m) => m.info.role === "assistant" && m.info.providerID && m.info.modelID);
|
|
21698
|
+
if (assistantMsg?.info.providerID && assistantMsg?.info.modelID) {
|
|
21699
|
+
const model = {
|
|
21700
|
+
providerID: assistantMsg.info.providerID,
|
|
21701
|
+
modelID: assistantMsg.info.modelID
|
|
21702
|
+
};
|
|
21703
|
+
modelCache.set(parentSessionID, model);
|
|
21704
|
+
log(`Got parent session model from messages`, { parentSessionID, ...model });
|
|
21705
|
+
return model;
|
|
21706
|
+
}
|
|
21707
|
+
log(`Parent session has no assistant messages with model info`, { parentSessionID });
|
|
21708
|
+
return;
|
|
21709
|
+
} catch (err) {
|
|
21710
|
+
log(`Failed to get parent session model`, { parentSessionID, error: String(err) });
|
|
21711
|
+
return;
|
|
21712
|
+
}
|
|
21713
|
+
};
|
|
21714
|
+
const createTask = async (parentSessionID, description, prompt, agent, model) => {
|
|
21687
21715
|
const runningCount = getRunningCount();
|
|
21688
21716
|
if (runningCount >= defaultConcurrency) {
|
|
21689
21717
|
throw new Error(`Max concurrent tasks (${defaultConcurrency}) reached. Wait for some to complete.`);
|
|
@@ -21698,6 +21726,14 @@ function createBackgroundManager(ctx, config2) {
|
|
|
21698
21726
|
};
|
|
21699
21727
|
tasks.set(taskId, task);
|
|
21700
21728
|
log(`Background task created`, { taskId, description, agent });
|
|
21729
|
+
const parentModel = model || await getParentSessionModel(parentSessionID);
|
|
21730
|
+
const resolvedModel = modelService ? modelService.resolveModelForAgent(agent, parentModel) : parentModel;
|
|
21731
|
+
if (resolvedModel && resolvedModel !== parentModel) {
|
|
21732
|
+
log(`[background-manager] Using tier-mapped model for ${agent}`, {
|
|
21733
|
+
providerID: resolvedModel.providerID,
|
|
21734
|
+
modelID: resolvedModel.modelID
|
|
21735
|
+
});
|
|
21736
|
+
}
|
|
21701
21737
|
(async () => {
|
|
21702
21738
|
try {
|
|
21703
21739
|
const sessionResp = await ctx.client.session.create({
|
|
@@ -21717,19 +21753,28 @@ function createBackgroundManager(ctx, config2) {
|
|
|
21717
21753
|
const fullPrompt = systemPrompt ? `${systemPrompt}
|
|
21718
21754
|
|
|
21719
21755
|
${prompt}` : prompt;
|
|
21720
|
-
|
|
21756
|
+
const promptBody = {
|
|
21757
|
+
parts: [{ type: "text", text: fullPrompt }]
|
|
21758
|
+
};
|
|
21759
|
+
if (resolvedModel) {
|
|
21760
|
+
promptBody.model = resolvedModel;
|
|
21761
|
+
log(`Using parent model for subagent`, { taskId, ...resolvedModel });
|
|
21762
|
+
}
|
|
21763
|
+
const promptResp = await ctx.client.session.prompt({
|
|
21721
21764
|
path: { id: sessionID },
|
|
21722
|
-
body:
|
|
21723
|
-
parts: [{ type: "text", text: fullPrompt }]
|
|
21724
|
-
},
|
|
21765
|
+
body: promptBody,
|
|
21725
21766
|
query: { directory: ctx.directory }
|
|
21726
21767
|
});
|
|
21727
|
-
const
|
|
21728
|
-
|
|
21729
|
-
|
|
21730
|
-
|
|
21731
|
-
|
|
21732
|
-
|
|
21768
|
+
const promptData = promptResp.data;
|
|
21769
|
+
if (promptResp.error) {
|
|
21770
|
+
throw new Error(`Prompt failed: ${JSON.stringify(promptResp.error)}`);
|
|
21771
|
+
}
|
|
21772
|
+
if (promptData?.info?.error) {
|
|
21773
|
+
const err = promptData.info.error;
|
|
21774
|
+
const errMsg = err.data?.message || err.name || "Unknown error";
|
|
21775
|
+
throw new Error(`[${err.name}] ${errMsg}`);
|
|
21776
|
+
}
|
|
21777
|
+
const result = promptData?.parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
|
|
21733
21778
|
`) || "";
|
|
21734
21779
|
task.result = result;
|
|
21735
21780
|
task.status = "completed";
|
|
@@ -21770,6 +21815,14 @@ ${prompt}` : prompt;
|
|
|
21770
21815
|
return false;
|
|
21771
21816
|
task.status = "cancelled";
|
|
21772
21817
|
task.completedAt = Date.now();
|
|
21818
|
+
if (task.sessionID) {
|
|
21819
|
+
ctx.client.session.abort({
|
|
21820
|
+
path: { id: task.sessionID },
|
|
21821
|
+
query: { directory: ctx.directory }
|
|
21822
|
+
}).catch((err) => {
|
|
21823
|
+
log(`Failed to abort session for task ${taskId}`, { error: String(err) });
|
|
21824
|
+
});
|
|
21825
|
+
}
|
|
21773
21826
|
log(`Background task cancelled`, { taskId });
|
|
21774
21827
|
return true;
|
|
21775
21828
|
};
|
|
@@ -21781,6 +21834,14 @@ ${prompt}` : prompt;
|
|
|
21781
21834
|
task.status = "cancelled";
|
|
21782
21835
|
task.completedAt = Date.now();
|
|
21783
21836
|
count++;
|
|
21837
|
+
if (task.sessionID) {
|
|
21838
|
+
ctx.client.session.abort({
|
|
21839
|
+
path: { id: task.sessionID },
|
|
21840
|
+
query: { directory: ctx.directory }
|
|
21841
|
+
}).catch((err) => {
|
|
21842
|
+
log(`Failed to abort session for task ${task.id}`, { error: String(err) });
|
|
21843
|
+
});
|
|
21844
|
+
}
|
|
21784
21845
|
}
|
|
21785
21846
|
}
|
|
21786
21847
|
}
|
|
@@ -21808,7 +21869,8 @@ ${prompt}` : prompt;
|
|
|
21808
21869
|
getTasksByParentSession,
|
|
21809
21870
|
cancelTask,
|
|
21810
21871
|
cancelAllTasks,
|
|
21811
|
-
waitForTask
|
|
21872
|
+
waitForTask,
|
|
21873
|
+
getParentSessionModel
|
|
21812
21874
|
};
|
|
21813
21875
|
}
|
|
21814
21876
|
|
|
@@ -34201,7 +34263,7 @@ Use \`background_output\` to get results. Prompts MUST be in English.`,
|
|
|
34201
34263
|
}
|
|
34202
34264
|
|
|
34203
34265
|
// src/tools/call-omco-agent.ts
|
|
34204
|
-
function createCallOmcoAgent(ctx, manager) {
|
|
34266
|
+
function createCallOmcoAgent(ctx, manager, modelService) {
|
|
34205
34267
|
const agentNames = listAgentNames();
|
|
34206
34268
|
const agentList = agentNames.map((name) => {
|
|
34207
34269
|
const agent = getAgent(name);
|
|
@@ -34237,8 +34299,16 @@ Prompts MUST be in English. Use \`background_output\` for async results.`,
|
|
|
34237
34299
|
---
|
|
34238
34300
|
|
|
34239
34301
|
${prompt}`;
|
|
34302
|
+
const parentModel = await manager.getParentSessionModel(context.sessionID);
|
|
34303
|
+
const resolvedModel = modelService ? modelService.resolveModelForAgent(subagent_type, parentModel) : parentModel;
|
|
34304
|
+
if (resolvedModel && resolvedModel !== parentModel) {
|
|
34305
|
+
log(`[call-omco-agent] Using tier-mapped model for ${subagent_type}`, {
|
|
34306
|
+
providerID: resolvedModel.providerID,
|
|
34307
|
+
modelID: resolvedModel.modelID
|
|
34308
|
+
});
|
|
34309
|
+
}
|
|
34240
34310
|
if (run_in_background) {
|
|
34241
|
-
const task = await manager.createTask(context.sessionID, description, enhancedPrompt, subagent_type);
|
|
34311
|
+
const task = await manager.createTask(context.sessionID, description, enhancedPrompt, subagent_type, resolvedModel);
|
|
34242
34312
|
return JSON.stringify({
|
|
34243
34313
|
task_id: task.id,
|
|
34244
34314
|
session_id: task.sessionID,
|
|
@@ -34257,19 +34327,36 @@ ${prompt}`;
|
|
|
34257
34327
|
const sessionID = sessionResp.data?.id ?? sessionResp.id;
|
|
34258
34328
|
if (!sessionID)
|
|
34259
34329
|
throw new Error("Failed to create session");
|
|
34260
|
-
|
|
34330
|
+
const promptBody = {
|
|
34331
|
+
parts: [{ type: "text", text: enhancedPrompt }]
|
|
34332
|
+
};
|
|
34333
|
+
if (resolvedModel) {
|
|
34334
|
+
promptBody.model = resolvedModel;
|
|
34335
|
+
log(`Using resolved model for sync agent call`, { subagent_type, ...resolvedModel });
|
|
34336
|
+
}
|
|
34337
|
+
const promptResp = await ctx.client.session.prompt({
|
|
34261
34338
|
path: { id: sessionID },
|
|
34262
|
-
body:
|
|
34263
|
-
parts: [{ type: "text", text: enhancedPrompt }]
|
|
34264
|
-
},
|
|
34339
|
+
body: promptBody,
|
|
34265
34340
|
query: { directory: ctx.directory }
|
|
34266
34341
|
});
|
|
34267
|
-
|
|
34268
|
-
|
|
34269
|
-
|
|
34270
|
-
|
|
34271
|
-
|
|
34272
|
-
|
|
34342
|
+
if (promptResp.error) {
|
|
34343
|
+
return JSON.stringify({
|
|
34344
|
+
session_id: sessionID,
|
|
34345
|
+
status: "failed",
|
|
34346
|
+
error: `Prompt failed: ${JSON.stringify(promptResp.error)}`
|
|
34347
|
+
});
|
|
34348
|
+
}
|
|
34349
|
+
const promptData = promptResp.data;
|
|
34350
|
+
if (promptData?.info?.error) {
|
|
34351
|
+
const err = promptData.info.error;
|
|
34352
|
+
const errMsg = err.data?.message || err.name || "Unknown error";
|
|
34353
|
+
return JSON.stringify({
|
|
34354
|
+
session_id: sessionID,
|
|
34355
|
+
status: "failed",
|
|
34356
|
+
error: `[${err.name}] ${errMsg}`
|
|
34357
|
+
});
|
|
34358
|
+
}
|
|
34359
|
+
const result = promptData?.parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
|
|
34273
34360
|
`) || "";
|
|
34274
34361
|
return JSON.stringify({
|
|
34275
34362
|
session_id: sessionID,
|
|
@@ -34288,16 +34375,20 @@ ${prompt}`;
|
|
|
34288
34375
|
|
|
34289
34376
|
// src/config/model-resolver.ts
|
|
34290
34377
|
var HARDCODED_TIER_DEFAULTS = {
|
|
34291
|
-
haiku: "
|
|
34292
|
-
sonnet: "
|
|
34293
|
-
opus: "
|
|
34378
|
+
haiku: "haiku",
|
|
34379
|
+
sonnet: "sonnet",
|
|
34380
|
+
opus: "opus"
|
|
34294
34381
|
};
|
|
34382
|
+
var SIMPLE_TIER_NAMES = new Set(["haiku", "sonnet", "opus"]);
|
|
34295
34383
|
function isValidModelFormat(model) {
|
|
34384
|
+
if (SIMPLE_TIER_NAMES.has(model.toLowerCase())) {
|
|
34385
|
+
return true;
|
|
34386
|
+
}
|
|
34296
34387
|
return /^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(model);
|
|
34297
34388
|
}
|
|
34298
34389
|
function validateModelFormat(model, context) {
|
|
34299
34390
|
if (!isValidModelFormat(model)) {
|
|
34300
|
-
warn(`[model-resolver] [${context}] Model "${model}" does not follow "provider/model-name" format`);
|
|
34391
|
+
warn(`[model-resolver] [${context}] Model "${model}" does not follow "provider/model-name" format. Configure model_mapping.tierDefaults in oh-my-opencode.json`);
|
|
34301
34392
|
}
|
|
34302
34393
|
}
|
|
34303
34394
|
|
|
@@ -34361,6 +34452,51 @@ class ModelResolver {
|
|
|
34361
34452
|
}
|
|
34362
34453
|
}
|
|
34363
34454
|
|
|
34455
|
+
// src/tools/model-resolution-service.ts
|
|
34456
|
+
function parseModelString(model) {
|
|
34457
|
+
if (!model.includes("/")) {
|
|
34458
|
+
return;
|
|
34459
|
+
}
|
|
34460
|
+
const [providerID, ...rest] = model.split("/");
|
|
34461
|
+
const modelID = rest.join("/");
|
|
34462
|
+
if (!providerID || !modelID) {
|
|
34463
|
+
return;
|
|
34464
|
+
}
|
|
34465
|
+
return { providerID, modelID };
|
|
34466
|
+
}
|
|
34467
|
+
function createModelResolutionService(modelMappingConfig, agentOverrides) {
|
|
34468
|
+
const resolver = new ModelResolver(modelMappingConfig);
|
|
34469
|
+
const debugLogging = modelMappingConfig?.debugLogging ?? false;
|
|
34470
|
+
const tierDefaults = resolver.getTierDefaults();
|
|
34471
|
+
const hasConfiguredTiers = Object.values(tierDefaults).some((m) => m.includes("/"));
|
|
34472
|
+
const resolveModelForAgent = (agentName, fallbackModel) => {
|
|
34473
|
+
const agentDef = getAgent(agentName);
|
|
34474
|
+
const agentTier = agentDef?.model;
|
|
34475
|
+
const agentOverride = agentOverrides?.[agentName];
|
|
34476
|
+
const resolution = resolver.resolve(agentName, agentTier, agentOverride);
|
|
34477
|
+
const modelConfig = parseModelString(resolution.model);
|
|
34478
|
+
if (modelConfig) {
|
|
34479
|
+
if (debugLogging) {
|
|
34480
|
+
log(`[model-resolution] Resolved ${agentName}: ${resolution.model} (source: ${resolution.source})`);
|
|
34481
|
+
}
|
|
34482
|
+
return modelConfig;
|
|
34483
|
+
}
|
|
34484
|
+
if (debugLogging) {
|
|
34485
|
+
log(`[model-resolution] ${agentName}: No provider mapping for "${resolution.model}", using fallback`, {
|
|
34486
|
+
fallback: fallbackModel ? `${fallbackModel.providerID}/${fallbackModel.modelID}` : "none"
|
|
34487
|
+
});
|
|
34488
|
+
}
|
|
34489
|
+
return fallbackModel;
|
|
34490
|
+
};
|
|
34491
|
+
const isTierMappingConfigured = () => {
|
|
34492
|
+
return hasConfiguredTiers;
|
|
34493
|
+
};
|
|
34494
|
+
return {
|
|
34495
|
+
resolveModelForAgent,
|
|
34496
|
+
isTierMappingConfigured
|
|
34497
|
+
};
|
|
34498
|
+
}
|
|
34499
|
+
|
|
34364
34500
|
// src/skills/index.ts
|
|
34365
34501
|
import { join as join4, dirname as dirname2 } from "path";
|
|
34366
34502
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -38229,13 +38365,30 @@ function getAgentEmoji(agentName) {
|
|
|
38229
38365
|
return emojiMap[agentName] || "\uD83E\uDD16";
|
|
38230
38366
|
}
|
|
38231
38367
|
|
|
38368
|
+
// src/state/session-pause-state.ts
|
|
38369
|
+
var pauseStates = new Map;
|
|
38370
|
+
function isSessionPaused(sessionId) {
|
|
38371
|
+
return pauseStates.get(sessionId)?.isPaused ?? false;
|
|
38372
|
+
}
|
|
38373
|
+
function resumeSession(sessionId) {
|
|
38374
|
+
const state = pauseStates.get(sessionId);
|
|
38375
|
+
if (state?.isPaused) {
|
|
38376
|
+
log(`Session resumed`, { sessionId, wasPausedFor: Date.now() - (state.pausedAt ?? 0) });
|
|
38377
|
+
}
|
|
38378
|
+
pauseStates.delete(sessionId);
|
|
38379
|
+
}
|
|
38380
|
+
|
|
38232
38381
|
// src/index.ts
|
|
38233
38382
|
var OmoOmcsPlugin = async (ctx) => {
|
|
38234
38383
|
const pluginConfig = loadConfig(ctx.directory);
|
|
38235
38384
|
console.log("[omco] Config loaded:", pluginConfig);
|
|
38236
|
-
const
|
|
38385
|
+
const modelService = createModelResolutionService(pluginConfig.model_mapping, pluginConfig.agents);
|
|
38386
|
+
if (modelService.isTierMappingConfigured()) {
|
|
38387
|
+
log("[omco] Model tier mapping configured - agents will use tier-specific models");
|
|
38388
|
+
}
|
|
38389
|
+
const backgroundManager = createBackgroundManager(ctx, pluginConfig.background_task, modelService);
|
|
38237
38390
|
const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
|
|
38238
|
-
const callOmcoAgent = createCallOmcoAgent(ctx, backgroundManager);
|
|
38391
|
+
const callOmcoAgent = createCallOmcoAgent(ctx, backgroundManager, modelService);
|
|
38239
38392
|
const systemPromptInjector = createSystemPromptInjector(ctx);
|
|
38240
38393
|
const skillInjector = createSkillInjector(ctx);
|
|
38241
38394
|
const ralphLoop = createRalphLoopHook(ctx, {
|
|
@@ -38335,6 +38488,10 @@ var OmoOmcsPlugin = async (ctx) => {
|
|
|
38335
38488
|
} else {
|
|
38336
38489
|
systemPromptInjector.clearSkillInjection(input.sessionID);
|
|
38337
38490
|
}
|
|
38491
|
+
if (isSessionPaused(input.sessionID)) {
|
|
38492
|
+
resumeSession(input.sessionID);
|
|
38493
|
+
log("[session] Auto-resumed on user prompt", { sessionID: input.sessionID });
|
|
38494
|
+
}
|
|
38338
38495
|
await autopilot["chat.message"](input, output);
|
|
38339
38496
|
await ultraqaLoop["chat.message"](input, output);
|
|
38340
38497
|
},
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared session pause state for coordinating abort handling across hooks.
|
|
3
|
+
* When user presses ESC (triggers MessageAbortedError), the session is paused
|
|
4
|
+
* and remains paused until explicitly resumed or user sends a new prompt.
|
|
5
|
+
*/
|
|
6
|
+
interface SessionPauseState {
|
|
7
|
+
isPaused: boolean;
|
|
8
|
+
pausedAt: number | null;
|
|
9
|
+
pauseReason: 'user_abort' | 'error' | 'explicit' | null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Pause a session, preventing automatic continuations.
|
|
13
|
+
* Called when MessageAbortedError is detected (user pressed ESC).
|
|
14
|
+
*/
|
|
15
|
+
export declare function pauseSession(sessionId: string, reason?: SessionPauseState['pauseReason']): void;
|
|
16
|
+
/**
|
|
17
|
+
* Check if a session is currently paused.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isSessionPaused(sessionId: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Get the pause state for a session.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getSessionPauseState(sessionId: string): SessionPauseState | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Resume a session, allowing automatic continuations.
|
|
26
|
+
* Called when user sends a new prompt or explicitly resumes.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resumeSession(sessionId: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Clear session state when session is deleted.
|
|
31
|
+
*/
|
|
32
|
+
export declare function clearSessionPauseState(sessionId: string): void;
|
|
33
|
+
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
2
|
import type { BackgroundTaskConfig } from "../config";
|
|
3
|
+
import type { ModelResolutionService } from "./model-resolution-service";
|
|
3
4
|
export interface BackgroundTask {
|
|
4
5
|
id: string;
|
|
5
6
|
status: "running" | "completed" | "failed" | "cancelled";
|
|
@@ -11,12 +12,17 @@ export interface BackgroundTask {
|
|
|
11
12
|
startedAt: number;
|
|
12
13
|
completedAt?: number;
|
|
13
14
|
}
|
|
15
|
+
export interface ModelConfig {
|
|
16
|
+
providerID: string;
|
|
17
|
+
modelID: string;
|
|
18
|
+
}
|
|
14
19
|
export interface BackgroundManager {
|
|
15
|
-
createTask: (parentSessionID: string, description: string, prompt: string, agent: string) => Promise<BackgroundTask>;
|
|
20
|
+
createTask: (parentSessionID: string, description: string, prompt: string, agent: string, model?: ModelConfig) => Promise<BackgroundTask>;
|
|
16
21
|
getTask: (taskId: string) => BackgroundTask | undefined;
|
|
17
22
|
getTasksByParentSession: (sessionID: string) => BackgroundTask[];
|
|
18
23
|
cancelTask: (taskId: string) => boolean;
|
|
19
24
|
cancelAllTasks: (parentSessionID?: string) => number;
|
|
20
25
|
waitForTask: (taskId: string, timeoutMs?: number) => Promise<BackgroundTask>;
|
|
26
|
+
getParentSessionModel: (parentSessionID: string) => Promise<ModelConfig | undefined>;
|
|
21
27
|
}
|
|
22
|
-
export declare function createBackgroundManager(ctx: PluginInput, config?: BackgroundTaskConfig): BackgroundManager;
|
|
28
|
+
export declare function createBackgroundManager(ctx: PluginInput, config?: BackgroundTaskConfig, modelService?: ModelResolutionService): BackgroundManager;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { type PluginInput, type ToolDefinition } from "@opencode-ai/plugin";
|
|
2
2
|
import type { BackgroundManager } from "./background-manager";
|
|
3
|
-
|
|
3
|
+
import type { ModelResolutionService } from "./model-resolution-service";
|
|
4
|
+
export declare function createCallOmcoAgent(ctx: PluginInput, manager: BackgroundManager, modelService?: ModelResolutionService): ToolDefinition;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Resolution Service
|
|
3
|
+
*
|
|
4
|
+
* Centralized model resolution for agent invocations.
|
|
5
|
+
* Connects ModelResolver (config-based tier mapping) to runtime tool calls.
|
|
6
|
+
*
|
|
7
|
+
* Priority chain:
|
|
8
|
+
* 1. Per-agent config model override
|
|
9
|
+
* 2. Per-agent config tier → tierDefaults
|
|
10
|
+
* 3. Agent definition tier → tierDefaults
|
|
11
|
+
* 4. Fallback to sonnet tier
|
|
12
|
+
* 5. If all else fails, use parent session model
|
|
13
|
+
*/
|
|
14
|
+
import { type AgentModelConfig, type ModelMappingConfig } from "../config/model-resolver";
|
|
15
|
+
export interface ModelConfig {
|
|
16
|
+
providerID: string;
|
|
17
|
+
modelID: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ModelResolutionService {
|
|
20
|
+
/**
|
|
21
|
+
* Resolve model for an agent based on tier configuration
|
|
22
|
+
* @param agentName - Name of the agent (canonical or alias)
|
|
23
|
+
* @param fallbackModel - Parent session model to use if resolution fails
|
|
24
|
+
* @returns Resolved ModelConfig or undefined if should use fallback
|
|
25
|
+
*/
|
|
26
|
+
resolveModelForAgent(agentName: string, fallbackModel?: ModelConfig): ModelConfig | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Check if tier mapping is configured (tierDefaults has provider/model format)
|
|
29
|
+
*/
|
|
30
|
+
isTierMappingConfigured(): boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a ModelResolutionService instance
|
|
34
|
+
*
|
|
35
|
+
* @param modelMappingConfig - Config from omco.json model_mapping section
|
|
36
|
+
* @param agentOverrides - Per-agent config overrides from omco.json agents section
|
|
37
|
+
*/
|
|
38
|
+
export declare function createModelResolutionService(modelMappingConfig?: ModelMappingConfig, agentOverrides?: Record<string, AgentModelConfig>): ModelResolutionService;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-claudecode-opencode",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "OpenCode port of oh-my-claudecode - Multi-agent orchestration plugin (omco)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
"prepublishOnly": "bun run clean && bun run build",
|
|
29
29
|
"typecheck": "tsc --noEmit",
|
|
30
30
|
"test": "bun test",
|
|
31
|
+
"test:e2e": "vitest run --config tests/e2e/vitest.config.ts",
|
|
32
|
+
"test:e2e:server": "vitest run --config tests/e2e/vitest.config.ts --testNamePattern 'Server'",
|
|
33
|
+
"test:all": "bun test && vitest run --config tests/e2e/vitest.config.ts",
|
|
31
34
|
"lint": "eslint src --ext .ts"
|
|
32
35
|
},
|
|
33
36
|
"keywords": [
|