oh-my-opencode-slim 0.6.4 → 0.8.0
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 +29 -2
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/orchestrator.d.ts +1 -1
- package/dist/background/background-manager.d.ts +44 -0
- package/dist/background/tmux-session-manager.d.ts +5 -0
- package/dist/cli/chutes-selection.d.ts +3 -0
- package/dist/cli/config-io.d.ts +5 -0
- package/dist/cli/config-manager.d.ts +8 -0
- package/dist/cli/dynamic-model-selection.d.ts +14 -0
- package/dist/cli/external-rankings.d.ts +8 -0
- package/dist/cli/index.js +2888 -166
- package/dist/cli/model-key-normalization.d.ts +1 -0
- package/dist/cli/model-selection.d.ts +30 -0
- package/dist/cli/opencode-models.d.ts +18 -0
- package/dist/cli/opencode-selection.d.ts +3 -0
- package/dist/cli/paths.d.ts +2 -0
- package/dist/cli/precedence-resolver.d.ts +16 -0
- package/dist/cli/providers.d.ts +127 -2
- package/dist/cli/scoring-v2/engine.d.ts +4 -0
- package/dist/cli/scoring-v2/features.d.ts +3 -0
- package/dist/cli/scoring-v2/index.d.ts +4 -0
- package/dist/cli/scoring-v2/types.d.ts +17 -0
- package/dist/cli/scoring-v2/weights.d.ts +2 -0
- package/dist/cli/system.d.ts +2 -0
- package/dist/cli/types.d.ts +104 -0
- package/dist/config/constants.d.ts +2 -0
- package/dist/config/loader.d.ts +3 -2
- package/dist/config/schema.d.ts +119 -0
- package/dist/index.js +305 -71
- package/dist/utils/env.d.ts +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -3338,6 +3338,14 @@ var SUBAGENT_NAMES = [
|
|
|
3338
3338
|
];
|
|
3339
3339
|
var ORCHESTRATOR_NAME = "orchestrator";
|
|
3340
3340
|
var ALL_AGENT_NAMES = [ORCHESTRATOR_NAME, ...SUBAGENT_NAMES];
|
|
3341
|
+
var SUBAGENT_DELEGATION_RULES = {
|
|
3342
|
+
orchestrator: SUBAGENT_NAMES,
|
|
3343
|
+
fixer: ["explorer"],
|
|
3344
|
+
designer: ["explorer"],
|
|
3345
|
+
explorer: [],
|
|
3346
|
+
librarian: [],
|
|
3347
|
+
oracle: []
|
|
3348
|
+
};
|
|
3341
3349
|
var DEFAULT_MODELS = {
|
|
3342
3350
|
orchestrator: "kimi-for-coding/k2p5",
|
|
3343
3351
|
oracle: "openai/gpt-5.2-codex",
|
|
@@ -3349,11 +3357,63 @@ var DEFAULT_MODELS = {
|
|
|
3349
3357
|
var POLL_INTERVAL_BACKGROUND_MS = 2000;
|
|
3350
3358
|
var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
|
|
3351
3359
|
var MAX_POLL_TIME_MS = 5 * 60 * 1000;
|
|
3360
|
+
var FALLBACK_FAILOVER_TIMEOUT_MS = 15000;
|
|
3352
3361
|
// src/config/loader.ts
|
|
3353
3362
|
import * as fs from "fs";
|
|
3354
3363
|
import * as os from "os";
|
|
3355
3364
|
import * as path from "path";
|
|
3356
3365
|
|
|
3366
|
+
// src/cli/paths.ts
|
|
3367
|
+
import { homedir } from "os";
|
|
3368
|
+
import { join } from "path";
|
|
3369
|
+
function getConfigDir() {
|
|
3370
|
+
const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join(homedir(), ".config");
|
|
3371
|
+
return join(userConfigDir, "opencode");
|
|
3372
|
+
}
|
|
3373
|
+
function getOpenCodeConfigPaths() {
|
|
3374
|
+
const configDir = getConfigDir();
|
|
3375
|
+
return [join(configDir, "opencode.json"), join(configDir, "opencode.jsonc")];
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
// src/config/agent-mcps.ts
|
|
3379
|
+
var DEFAULT_AGENT_MCPS = {
|
|
3380
|
+
orchestrator: ["websearch"],
|
|
3381
|
+
designer: [],
|
|
3382
|
+
oracle: [],
|
|
3383
|
+
librarian: ["websearch", "context7", "grep_app"],
|
|
3384
|
+
explorer: [],
|
|
3385
|
+
fixer: []
|
|
3386
|
+
};
|
|
3387
|
+
function parseList(items, allAvailable) {
|
|
3388
|
+
if (!items || items.length === 0) {
|
|
3389
|
+
return [];
|
|
3390
|
+
}
|
|
3391
|
+
const allow = items.filter((i) => !i.startsWith("!"));
|
|
3392
|
+
const deny = items.filter((i) => i.startsWith("!")).map((i) => i.slice(1));
|
|
3393
|
+
if (deny.includes("*")) {
|
|
3394
|
+
return [];
|
|
3395
|
+
}
|
|
3396
|
+
if (allow.includes("*")) {
|
|
3397
|
+
return allAvailable.filter((item) => !deny.includes(item));
|
|
3398
|
+
}
|
|
3399
|
+
return allow.filter((item) => !deny.includes(item));
|
|
3400
|
+
}
|
|
3401
|
+
function getAgentMcpList(agentName, config) {
|
|
3402
|
+
const agentConfig = getAgentOverride(config, agentName);
|
|
3403
|
+
if (agentConfig?.mcps !== undefined) {
|
|
3404
|
+
return agentConfig.mcps;
|
|
3405
|
+
}
|
|
3406
|
+
const defaultMcps = DEFAULT_AGENT_MCPS[agentName];
|
|
3407
|
+
return defaultMcps ?? [];
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
// src/cli/config-io.ts
|
|
3411
|
+
function stripJsonComments(json) {
|
|
3412
|
+
const commentPattern = /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
|
|
3413
|
+
const trailingCommaPattern = /\\"|"(?:\\"|[^"])*"|(,)(\s*[}\]])/g;
|
|
3414
|
+
return json.replace(commentPattern, (match, commentGroup) => commentGroup ? "" : match).replace(trailingCommaPattern, (match, comma, closing) => comma ? closing : match);
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3357
3417
|
// node_modules/zod/v4/classic/external.js
|
|
3358
3418
|
var exports_external = {};
|
|
3359
3419
|
__export(exports_external, {
|
|
@@ -16887,6 +16947,43 @@ function date4(params) {
|
|
|
16887
16947
|
// node_modules/zod/v4/classic/external.js
|
|
16888
16948
|
config(en_default());
|
|
16889
16949
|
// src/config/schema.ts
|
|
16950
|
+
var ProviderModelIdSchema = exports_external.string().regex(/^[^/\s]+\/[^\s]+$/, "Expected provider/model format (provider/.../model)");
|
|
16951
|
+
var ManualAgentPlanSchema = exports_external.object({
|
|
16952
|
+
primary: ProviderModelIdSchema,
|
|
16953
|
+
fallback1: ProviderModelIdSchema,
|
|
16954
|
+
fallback2: ProviderModelIdSchema,
|
|
16955
|
+
fallback3: ProviderModelIdSchema
|
|
16956
|
+
}).superRefine((value, ctx) => {
|
|
16957
|
+
const unique = new Set([
|
|
16958
|
+
value.primary,
|
|
16959
|
+
value.fallback1,
|
|
16960
|
+
value.fallback2,
|
|
16961
|
+
value.fallback3
|
|
16962
|
+
]);
|
|
16963
|
+
if (unique.size !== 4) {
|
|
16964
|
+
ctx.addIssue({
|
|
16965
|
+
code: exports_external.ZodIssueCode.custom,
|
|
16966
|
+
message: "primary and fallbacks must be unique per agent"
|
|
16967
|
+
});
|
|
16968
|
+
}
|
|
16969
|
+
});
|
|
16970
|
+
var ManualPlanSchema = exports_external.object({
|
|
16971
|
+
orchestrator: ManualAgentPlanSchema,
|
|
16972
|
+
oracle: ManualAgentPlanSchema,
|
|
16973
|
+
designer: ManualAgentPlanSchema,
|
|
16974
|
+
explorer: ManualAgentPlanSchema,
|
|
16975
|
+
librarian: ManualAgentPlanSchema,
|
|
16976
|
+
fixer: ManualAgentPlanSchema
|
|
16977
|
+
}).strict();
|
|
16978
|
+
var AgentModelChainSchema = exports_external.array(exports_external.string()).min(1);
|
|
16979
|
+
var FallbackChainsSchema = exports_external.object({
|
|
16980
|
+
orchestrator: AgentModelChainSchema.optional(),
|
|
16981
|
+
oracle: AgentModelChainSchema.optional(),
|
|
16982
|
+
designer: AgentModelChainSchema.optional(),
|
|
16983
|
+
explorer: AgentModelChainSchema.optional(),
|
|
16984
|
+
librarian: AgentModelChainSchema.optional(),
|
|
16985
|
+
fixer: AgentModelChainSchema.optional()
|
|
16986
|
+
}).catchall(AgentModelChainSchema);
|
|
16890
16987
|
var AgentOverrideConfigSchema = exports_external.object({
|
|
16891
16988
|
model: exports_external.string().optional(),
|
|
16892
16989
|
temperature: exports_external.number().min(0).max(2).optional(),
|
|
@@ -16911,17 +17008,25 @@ var McpNameSchema = exports_external.enum(["websearch", "context7", "grep_app"])
|
|
|
16911
17008
|
var BackgroundTaskConfigSchema = exports_external.object({
|
|
16912
17009
|
maxConcurrentStarts: exports_external.number().min(1).max(50).default(10)
|
|
16913
17010
|
});
|
|
17011
|
+
var FailoverConfigSchema = exports_external.object({
|
|
17012
|
+
enabled: exports_external.boolean().default(true),
|
|
17013
|
+
timeoutMs: exports_external.number().min(1000).max(120000).default(15000),
|
|
17014
|
+
chains: FallbackChainsSchema.default({})
|
|
17015
|
+
});
|
|
16914
17016
|
var PluginConfigSchema = exports_external.object({
|
|
16915
17017
|
preset: exports_external.string().optional(),
|
|
17018
|
+
scoringEngineVersion: exports_external.enum(["v1", "v2-shadow", "v2"]).optional(),
|
|
17019
|
+
balanceProviderUsage: exports_external.boolean().optional(),
|
|
17020
|
+
manualPlan: ManualPlanSchema.optional(),
|
|
16916
17021
|
presets: exports_external.record(exports_external.string(), PresetSchema).optional(),
|
|
16917
17022
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
16918
17023
|
disabled_mcps: exports_external.array(exports_external.string()).optional(),
|
|
16919
17024
|
tmux: TmuxConfigSchema.optional(),
|
|
16920
|
-
background: BackgroundTaskConfigSchema.optional()
|
|
17025
|
+
background: BackgroundTaskConfigSchema.optional(),
|
|
17026
|
+
fallback: FailoverConfigSchema.optional()
|
|
16921
17027
|
});
|
|
16922
17028
|
|
|
16923
17029
|
// src/config/loader.ts
|
|
16924
|
-
var CONFIG_FILENAME = "oh-my-opencode-slim.json";
|
|
16925
17030
|
var PROMPTS_DIR_NAME = "oh-my-opencode-slim";
|
|
16926
17031
|
function getUserConfigDir() {
|
|
16927
17032
|
return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
@@ -16929,7 +17034,7 @@ function getUserConfigDir() {
|
|
|
16929
17034
|
function loadConfigFromPath(configPath) {
|
|
16930
17035
|
try {
|
|
16931
17036
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
16932
|
-
const rawConfig = JSON.parse(content);
|
|
17037
|
+
const rawConfig = JSON.parse(stripJsonComments(content));
|
|
16933
17038
|
const result = PluginConfigSchema.safeParse(rawConfig);
|
|
16934
17039
|
if (!result.success) {
|
|
16935
17040
|
console.warn(`[oh-my-opencode-slim] Invalid config at ${configPath}:`);
|
|
@@ -16944,6 +17049,17 @@ function loadConfigFromPath(configPath) {
|
|
|
16944
17049
|
return null;
|
|
16945
17050
|
}
|
|
16946
17051
|
}
|
|
17052
|
+
function findConfigPath(basePath) {
|
|
17053
|
+
const jsoncPath = `${basePath}.jsonc`;
|
|
17054
|
+
const jsonPath = `${basePath}.json`;
|
|
17055
|
+
if (fs.existsSync(jsoncPath)) {
|
|
17056
|
+
return jsoncPath;
|
|
17057
|
+
}
|
|
17058
|
+
if (fs.existsSync(jsonPath)) {
|
|
17059
|
+
return jsonPath;
|
|
17060
|
+
}
|
|
17061
|
+
return null;
|
|
17062
|
+
}
|
|
16947
17063
|
function deepMerge(base, override) {
|
|
16948
17064
|
if (!base)
|
|
16949
17065
|
return override;
|
|
@@ -16962,16 +17078,19 @@ function deepMerge(base, override) {
|
|
|
16962
17078
|
return result;
|
|
16963
17079
|
}
|
|
16964
17080
|
function loadPluginConfig(directory) {
|
|
16965
|
-
const
|
|
16966
|
-
const
|
|
16967
|
-
|
|
16968
|
-
const
|
|
17081
|
+
const userConfigBasePath = path.join(getUserConfigDir(), "opencode", "oh-my-opencode-slim");
|
|
17082
|
+
const projectConfigBasePath = path.join(directory, ".opencode", "oh-my-opencode-slim");
|
|
17083
|
+
const userConfigPath = findConfigPath(userConfigBasePath);
|
|
17084
|
+
const projectConfigPath = findConfigPath(projectConfigBasePath);
|
|
17085
|
+
let config2 = userConfigPath ? loadConfigFromPath(userConfigPath) ?? {} : {};
|
|
17086
|
+
const projectConfig = projectConfigPath ? loadConfigFromPath(projectConfigPath) : null;
|
|
16969
17087
|
if (projectConfig) {
|
|
16970
17088
|
config2 = {
|
|
16971
17089
|
...config2,
|
|
16972
17090
|
...projectConfig,
|
|
16973
17091
|
agents: deepMerge(config2.agents, projectConfig.agents),
|
|
16974
|
-
tmux: deepMerge(config2.tmux, projectConfig.tmux)
|
|
17092
|
+
tmux: deepMerge(config2.tmux, projectConfig.tmux),
|
|
17093
|
+
fallback: deepMerge(config2.fallback, projectConfig.fallback)
|
|
16975
17094
|
};
|
|
16976
17095
|
}
|
|
16977
17096
|
const envPreset = process.env.OH_MY_OPENCODE_SLIM_PRESET;
|
|
@@ -17016,38 +17135,6 @@ function getAgentOverride(config2, name) {
|
|
|
17016
17135
|
const overrides = config2?.agents ?? {};
|
|
17017
17136
|
return overrides[name] ?? overrides[Object.keys(AGENT_ALIASES).find((k) => AGENT_ALIASES[k] === name) ?? ""];
|
|
17018
17137
|
}
|
|
17019
|
-
// src/config/agent-mcps.ts
|
|
17020
|
-
var DEFAULT_AGENT_MCPS = {
|
|
17021
|
-
orchestrator: ["websearch"],
|
|
17022
|
-
designer: [],
|
|
17023
|
-
oracle: [],
|
|
17024
|
-
librarian: ["websearch", "context7", "grep_app"],
|
|
17025
|
-
explorer: [],
|
|
17026
|
-
fixer: []
|
|
17027
|
-
};
|
|
17028
|
-
function parseList(items, allAvailable) {
|
|
17029
|
-
if (!items || items.length === 0) {
|
|
17030
|
-
return [];
|
|
17031
|
-
}
|
|
17032
|
-
const allow = items.filter((i) => !i.startsWith("!"));
|
|
17033
|
-
const deny = items.filter((i) => i.startsWith("!")).map((i) => i.slice(1));
|
|
17034
|
-
if (deny.includes("*")) {
|
|
17035
|
-
return [];
|
|
17036
|
-
}
|
|
17037
|
-
if (allow.includes("*")) {
|
|
17038
|
-
return allAvailable.filter((item) => !deny.includes(item));
|
|
17039
|
-
}
|
|
17040
|
-
return allow.filter((item) => !deny.includes(item));
|
|
17041
|
-
}
|
|
17042
|
-
function getAgentMcpList(agentName, config2) {
|
|
17043
|
-
const agentConfig = getAgentOverride(config2, agentName);
|
|
17044
|
-
if (agentConfig?.mcps !== undefined) {
|
|
17045
|
-
return agentConfig.mcps;
|
|
17046
|
-
}
|
|
17047
|
-
const defaultMcps = DEFAULT_AGENT_MCPS[agentName];
|
|
17048
|
-
return defaultMcps ?? [];
|
|
17049
|
-
}
|
|
17050
|
-
|
|
17051
17138
|
// src/agents/designer.ts
|
|
17052
17139
|
var DESIGNER_PROMPT = `You are a Designer - a frontend UI/UX specialist who creates intentional, polished experiences.
|
|
17053
17140
|
|
|
@@ -17382,7 +17469,7 @@ Each specialist delivers 10x results in their domain:
|
|
|
17382
17469
|
- @explorer \u2192 Parallel discovery when you need to find unknowns, not read knowns
|
|
17383
17470
|
- @librarian \u2192 Complex/evolving APIs where docs prevent errors, not basic usage
|
|
17384
17471
|
- @oracle \u2192 High-stakes decisions where wrong choice is costly, not routine calls
|
|
17385
|
-
- @designer \u2192 User-facing experiences where polish matters, not internal logic
|
|
17472
|
+
- @designer \u2192 User-facing experiences where polish matters, not internal logic
|
|
17386
17473
|
- @fixer \u2192 Parallel execution of clear specs, not explaining trivial changes
|
|
17387
17474
|
|
|
17388
17475
|
**Delegation efficiency:**
|
|
@@ -17474,6 +17561,8 @@ ${customAppendPrompt}`;
|
|
|
17474
17561
|
function applyOverrides(agent, override) {
|
|
17475
17562
|
if (override.model)
|
|
17476
17563
|
agent.config.model = override.model;
|
|
17564
|
+
if (override.variant)
|
|
17565
|
+
agent.config.variant = override.variant;
|
|
17477
17566
|
if (override.temperature !== undefined)
|
|
17478
17567
|
agent.config.temperature = override.temperature;
|
|
17479
17568
|
}
|
|
@@ -17781,6 +17870,16 @@ async function closeTmuxPane(paneId) {
|
|
|
17781
17870
|
return false;
|
|
17782
17871
|
}
|
|
17783
17872
|
try {
|
|
17873
|
+
log("[tmux] closeTmuxPane: sending Ctrl+C for graceful shutdown", {
|
|
17874
|
+
paneId
|
|
17875
|
+
});
|
|
17876
|
+
const ctrlCProc = spawn([tmux, "send-keys", "-t", paneId, "C-c"], {
|
|
17877
|
+
stdout: "pipe",
|
|
17878
|
+
stderr: "pipe"
|
|
17879
|
+
});
|
|
17880
|
+
await ctrlCProc.exited;
|
|
17881
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
17882
|
+
log("[tmux] closeTmuxPane: killing pane", { paneId });
|
|
17784
17883
|
const proc = spawn([tmux, "kill-pane", "-t", paneId], {
|
|
17785
17884
|
stdout: "pipe",
|
|
17786
17885
|
stderr: "pipe"
|
|
@@ -17894,6 +17993,16 @@ async function extractZip(archivePath, destDir) {
|
|
|
17894
17993
|
}
|
|
17895
17994
|
}
|
|
17896
17995
|
// src/background/background-manager.ts
|
|
17996
|
+
function parseModelReference(model) {
|
|
17997
|
+
const slashIndex = model.indexOf("/");
|
|
17998
|
+
if (slashIndex <= 0 || slashIndex >= model.length - 1) {
|
|
17999
|
+
return null;
|
|
18000
|
+
}
|
|
18001
|
+
return {
|
|
18002
|
+
providerID: model.slice(0, slashIndex),
|
|
18003
|
+
modelID: model.slice(slashIndex + 1)
|
|
18004
|
+
};
|
|
18005
|
+
}
|
|
17897
18006
|
function generateTaskId() {
|
|
17898
18007
|
return `bg_${Math.random().toString(36).substring(2, 10)}`;
|
|
17899
18008
|
}
|
|
@@ -17901,6 +18010,7 @@ function generateTaskId() {
|
|
|
17901
18010
|
class BackgroundTaskManager {
|
|
17902
18011
|
tasks = new Map;
|
|
17903
18012
|
tasksBySessionId = new Map;
|
|
18013
|
+
agentBySessionId = new Map;
|
|
17904
18014
|
client;
|
|
17905
18015
|
directory;
|
|
17906
18016
|
tmuxEnabled;
|
|
@@ -17920,6 +18030,20 @@ class BackgroundTaskManager {
|
|
|
17920
18030
|
};
|
|
17921
18031
|
this.maxConcurrentStarts = this.backgroundConfig.maxConcurrentStarts;
|
|
17922
18032
|
}
|
|
18033
|
+
getSubagentRules(agentName) {
|
|
18034
|
+
return SUBAGENT_DELEGATION_RULES[agentName] ?? ["explorer"];
|
|
18035
|
+
}
|
|
18036
|
+
isAgentAllowed(parentSessionId, requestedAgent) {
|
|
18037
|
+
const parentAgentName = this.agentBySessionId.get(parentSessionId) ?? "orchestrator";
|
|
18038
|
+
const allowedSubagents = this.getSubagentRules(parentAgentName);
|
|
18039
|
+
if (allowedSubagents.length === 0)
|
|
18040
|
+
return false;
|
|
18041
|
+
return allowedSubagents.includes(requestedAgent);
|
|
18042
|
+
}
|
|
18043
|
+
getAllowedSubagents(parentSessionId) {
|
|
18044
|
+
const parentAgentName = this.agentBySessionId.get(parentSessionId) ?? "orchestrator";
|
|
18045
|
+
return this.getSubagentRules(parentAgentName);
|
|
18046
|
+
}
|
|
17923
18047
|
launch(opts) {
|
|
17924
18048
|
const task = {
|
|
17925
18049
|
id: generateTaskId(),
|
|
@@ -17954,6 +18078,38 @@ class BackgroundTaskManager {
|
|
|
17954
18078
|
this.startTask(task);
|
|
17955
18079
|
}
|
|
17956
18080
|
}
|
|
18081
|
+
resolveFallbackChain(agentName) {
|
|
18082
|
+
const fallback = this.config?.fallback;
|
|
18083
|
+
const chains = fallback?.chains;
|
|
18084
|
+
const configuredChain = chains?.[agentName] ?? [];
|
|
18085
|
+
const primary = this.config?.agents?.[agentName]?.model;
|
|
18086
|
+
const chain = [];
|
|
18087
|
+
const seen = new Set;
|
|
18088
|
+
for (const model of [primary, ...configuredChain]) {
|
|
18089
|
+
if (!model || seen.has(model))
|
|
18090
|
+
continue;
|
|
18091
|
+
seen.add(model);
|
|
18092
|
+
chain.push(model);
|
|
18093
|
+
}
|
|
18094
|
+
return chain;
|
|
18095
|
+
}
|
|
18096
|
+
async promptWithTimeout(args, timeoutMs) {
|
|
18097
|
+
await Promise.race([
|
|
18098
|
+
this.client.session.prompt(args),
|
|
18099
|
+
new Promise((_, reject) => {
|
|
18100
|
+
setTimeout(() => {
|
|
18101
|
+
reject(new Error(`Prompt timed out after ${timeoutMs}ms`));
|
|
18102
|
+
}, timeoutMs);
|
|
18103
|
+
})
|
|
18104
|
+
]);
|
|
18105
|
+
}
|
|
18106
|
+
calculateToolPermissions(agentName) {
|
|
18107
|
+
const allowedSubagents = this.getSubagentRules(agentName);
|
|
18108
|
+
if (allowedSubagents.length === 0) {
|
|
18109
|
+
return { background_task: false, task: false };
|
|
18110
|
+
}
|
|
18111
|
+
return { background_task: true, task: true };
|
|
18112
|
+
}
|
|
17957
18113
|
async startTask(task) {
|
|
17958
18114
|
task.status = "starting";
|
|
17959
18115
|
this.activeStarts++;
|
|
@@ -17974,22 +18130,57 @@ class BackgroundTaskManager {
|
|
|
17974
18130
|
}
|
|
17975
18131
|
task.sessionId = session.data.id;
|
|
17976
18132
|
this.tasksBySessionId.set(session.data.id, task.id);
|
|
18133
|
+
this.agentBySessionId.set(session.data.id, task.agent);
|
|
17977
18134
|
task.status = "running";
|
|
17978
18135
|
if (this.tmuxEnabled) {
|
|
17979
18136
|
await new Promise((r) => setTimeout(r, 500));
|
|
17980
18137
|
}
|
|
18138
|
+
const toolPermissions = this.calculateToolPermissions(task.agent);
|
|
17981
18139
|
const promptQuery = { directory: this.directory };
|
|
17982
18140
|
const resolvedVariant = resolveAgentVariant(this.config, task.agent);
|
|
17983
|
-
const
|
|
18141
|
+
const basePromptBody = applyAgentVariant(resolvedVariant, {
|
|
17984
18142
|
agent: task.agent,
|
|
17985
|
-
tools:
|
|
18143
|
+
tools: toolPermissions,
|
|
17986
18144
|
parts: [{ type: "text", text: task.prompt }]
|
|
17987
18145
|
});
|
|
17988
|
-
|
|
17989
|
-
|
|
17990
|
-
|
|
17991
|
-
|
|
17992
|
-
|
|
18146
|
+
const timeoutMs = this.config?.fallback?.timeoutMs ?? FALLBACK_FAILOVER_TIMEOUT_MS;
|
|
18147
|
+
const fallbackEnabled = this.config?.fallback?.enabled ?? true;
|
|
18148
|
+
const chain = fallbackEnabled ? this.resolveFallbackChain(task.agent) : [];
|
|
18149
|
+
const attemptModels = chain.length > 0 ? chain : [undefined];
|
|
18150
|
+
const errors3 = [];
|
|
18151
|
+
let succeeded = false;
|
|
18152
|
+
for (const model of attemptModels) {
|
|
18153
|
+
try {
|
|
18154
|
+
const body = {
|
|
18155
|
+
...basePromptBody,
|
|
18156
|
+
model: undefined
|
|
18157
|
+
};
|
|
18158
|
+
if (model) {
|
|
18159
|
+
const ref = parseModelReference(model);
|
|
18160
|
+
if (!ref) {
|
|
18161
|
+
throw new Error(`Invalid fallback model format: ${model}`);
|
|
18162
|
+
}
|
|
18163
|
+
body.model = ref;
|
|
18164
|
+
}
|
|
18165
|
+
await this.promptWithTimeout({
|
|
18166
|
+
path: { id: session.data.id },
|
|
18167
|
+
body,
|
|
18168
|
+
query: promptQuery
|
|
18169
|
+
}, timeoutMs);
|
|
18170
|
+
succeeded = true;
|
|
18171
|
+
break;
|
|
18172
|
+
} catch (error48) {
|
|
18173
|
+
const msg = error48 instanceof Error ? error48.message : String(error48);
|
|
18174
|
+
if (model) {
|
|
18175
|
+
errors3.push(`${model}: ${msg}`);
|
|
18176
|
+
} else {
|
|
18177
|
+
errors3.push(`default-model: ${msg}`);
|
|
18178
|
+
}
|
|
18179
|
+
}
|
|
18180
|
+
}
|
|
18181
|
+
if (!succeeded) {
|
|
18182
|
+
throw new Error(`All fallback models failed. ${errors3.join(" | ")}`);
|
|
18183
|
+
}
|
|
17993
18184
|
log(`[background-manager] task started: ${task.id}`, {
|
|
17994
18185
|
sessionId: session.data.id
|
|
17995
18186
|
});
|
|
@@ -18017,6 +18208,33 @@ class BackgroundTaskManager {
|
|
|
18017
18208
|
await this.extractAndCompleteTask(task);
|
|
18018
18209
|
}
|
|
18019
18210
|
}
|
|
18211
|
+
async handleSessionDeleted(event) {
|
|
18212
|
+
if (event.type !== "session.deleted")
|
|
18213
|
+
return;
|
|
18214
|
+
const sessionId = event.properties?.info?.id ?? event.properties?.sessionID;
|
|
18215
|
+
if (!sessionId)
|
|
18216
|
+
return;
|
|
18217
|
+
const taskId = this.tasksBySessionId.get(sessionId);
|
|
18218
|
+
if (!taskId)
|
|
18219
|
+
return;
|
|
18220
|
+
const task = this.tasks.get(taskId);
|
|
18221
|
+
if (!task)
|
|
18222
|
+
return;
|
|
18223
|
+
if (task.status === "running" || task.status === "pending") {
|
|
18224
|
+
log(`[background-manager] Session deleted, cancelling task: ${task.id}`);
|
|
18225
|
+
task.status = "cancelled";
|
|
18226
|
+
task.completedAt = new Date;
|
|
18227
|
+
task.error = "Session deleted";
|
|
18228
|
+
this.tasksBySessionId.delete(sessionId);
|
|
18229
|
+
this.agentBySessionId.delete(sessionId);
|
|
18230
|
+
const resolver = this.completionResolvers.get(taskId);
|
|
18231
|
+
if (resolver) {
|
|
18232
|
+
resolver(task);
|
|
18233
|
+
this.completionResolvers.delete(taskId);
|
|
18234
|
+
}
|
|
18235
|
+
log(`[background-manager] Task cancelled due to session deletion: ${task.id}`);
|
|
18236
|
+
}
|
|
18237
|
+
}
|
|
18020
18238
|
async extractAndCompleteTask(task) {
|
|
18021
18239
|
if (!task.sessionId)
|
|
18022
18240
|
return;
|
|
@@ -18059,6 +18277,12 @@ class BackgroundTaskManager {
|
|
|
18059
18277
|
}
|
|
18060
18278
|
if (task.sessionId) {
|
|
18061
18279
|
this.tasksBySessionId.delete(task.sessionId);
|
|
18280
|
+
this.agentBySessionId.delete(task.sessionId);
|
|
18281
|
+
}
|
|
18282
|
+
if (task.sessionId) {
|
|
18283
|
+
this.client.session.abort({
|
|
18284
|
+
path: { id: task.sessionId }
|
|
18285
|
+
}).catch(() => {});
|
|
18062
18286
|
}
|
|
18063
18287
|
if (task.parentSessionId) {
|
|
18064
18288
|
this.sendCompletionNotification(task).catch((err) => {
|
|
@@ -18145,6 +18369,7 @@ class BackgroundTaskManager {
|
|
|
18145
18369
|
this.completionResolvers.clear();
|
|
18146
18370
|
this.tasks.clear();
|
|
18147
18371
|
this.tasksBySessionId.clear();
|
|
18372
|
+
this.agentBySessionId.clear();
|
|
18148
18373
|
}
|
|
18149
18374
|
}
|
|
18150
18375
|
// src/background/tmux-session-manager.ts
|
|
@@ -18226,6 +18451,19 @@ class TmuxSessionManager {
|
|
|
18226
18451
|
await this.closeSession(sessionId);
|
|
18227
18452
|
}
|
|
18228
18453
|
}
|
|
18454
|
+
async onSessionDeleted(event) {
|
|
18455
|
+
if (!this.enabled)
|
|
18456
|
+
return;
|
|
18457
|
+
if (event.type !== "session.deleted")
|
|
18458
|
+
return;
|
|
18459
|
+
const sessionId = event.properties?.sessionID;
|
|
18460
|
+
if (!sessionId)
|
|
18461
|
+
return;
|
|
18462
|
+
log("[tmux-session-manager] session deleted, closing pane", {
|
|
18463
|
+
sessionId
|
|
18464
|
+
});
|
|
18465
|
+
await this.closeSession(sessionId);
|
|
18466
|
+
}
|
|
18229
18467
|
startPolling() {
|
|
18230
18468
|
if (this.pollInterval)
|
|
18231
18469
|
return;
|
|
@@ -18304,25 +18542,8 @@ class TmuxSessionManager {
|
|
|
18304
18542
|
// src/hooks/auto-update-checker/cache.ts
|
|
18305
18543
|
import * as fs3 from "fs";
|
|
18306
18544
|
import * as path4 from "path";
|
|
18307
|
-
|
|
18308
|
-
|
|
18309
|
-
import { homedir as homedir2 } from "os";
|
|
18310
|
-
import { join as join3 } from "path";
|
|
18311
|
-
function getConfigDir() {
|
|
18312
|
-
const userConfigDir = process.env.XDG_CONFIG_HOME ? process.env.XDG_CONFIG_HOME : join3(homedir2(), ".config");
|
|
18313
|
-
return join3(userConfigDir, "opencode");
|
|
18314
|
-
}
|
|
18315
|
-
function getOpenCodeConfigPaths() {
|
|
18316
|
-
const configDir = getConfigDir();
|
|
18317
|
-
return [join3(configDir, "opencode.json"), join3(configDir, "opencode.jsonc")];
|
|
18318
|
-
}
|
|
18319
|
-
|
|
18320
|
-
// src/cli/config-io.ts
|
|
18321
|
-
function stripJsonComments(json2) {
|
|
18322
|
-
const commentPattern = /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
|
|
18323
|
-
const trailingCommaPattern = /\\"|"(?:\\"|[^"])*"|(,)(\s*[}\]])/g;
|
|
18324
|
-
return json2.replace(commentPattern, (match, commentGroup) => commentGroup ? "" : match).replace(trailingCommaPattern, (match, comma, closing) => comma ? closing : match);
|
|
18325
|
-
}
|
|
18545
|
+
// src/cli/dynamic-model-selection.ts
|
|
18546
|
+
var FREE_BIASED_PROVIDERS = new Set(["opencode"]);
|
|
18326
18547
|
// src/hooks/auto-update-checker/constants.ts
|
|
18327
18548
|
import * as os3 from "os";
|
|
18328
18549
|
import * as path3 from "path";
|
|
@@ -31692,11 +31913,16 @@ Key behaviors:
|
|
|
31692
31913
|
const agent = String(args.agent);
|
|
31693
31914
|
const prompt = String(args.prompt);
|
|
31694
31915
|
const description = String(args.description);
|
|
31916
|
+
const parentSessionId = toolContext.sessionID;
|
|
31917
|
+
if (!manager.isAgentAllowed(parentSessionId, agent)) {
|
|
31918
|
+
const allowed = manager.getAllowedSubagents(parentSessionId);
|
|
31919
|
+
return `Agent '${agent}' is not allowed. Allowed agents: ${allowed.join(", ")}`;
|
|
31920
|
+
}
|
|
31695
31921
|
const task = manager.launch({
|
|
31696
31922
|
agent,
|
|
31697
31923
|
prompt,
|
|
31698
31924
|
description,
|
|
31699
|
-
parentSessionId
|
|
31925
|
+
parentSessionId
|
|
31700
31926
|
});
|
|
31701
31927
|
return `Background task launched.
|
|
31702
31928
|
|
|
@@ -32067,11 +32293,14 @@ ${summary}`);
|
|
|
32067
32293
|
|
|
32068
32294
|
// src/tools/grep/tools.ts
|
|
32069
32295
|
var grep = tool({
|
|
32070
|
-
description: "Fast content search tool with safety limits (60s timeout, 10MB output). " + "Searches file contents using regular expressions. " + 'Supports full regex syntax (eg. "log.*Error", "function\\s+\\w+", etc.). ' + 'Filter files by pattern with the include parameter (
|
|
32296
|
+
description: "Fast content search tool with safety limits (60s timeout, 10MB output). " + "Searches file contents using regular expressions. " + 'Supports full regex syntax (eg. "log.*Error", "function\\s+\\w+", etc.). ' + 'Filter files by pattern with the include parameter (e.g. "*.js", "*.{ts,tsx}"). ' + "Returns file paths with matches sorted by modification time.",
|
|
32071
32297
|
args: {
|
|
32072
32298
|
pattern: tool.schema.string().describe("The regex pattern to search for in file contents"),
|
|
32073
32299
|
include: tool.schema.string().optional().describe('File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")'),
|
|
32074
|
-
path: tool.schema.string().optional().describe("The directory to search in. Defaults to the current working directory.")
|
|
32300
|
+
path: tool.schema.string().optional().describe("The directory to search in. Defaults to the current working directory."),
|
|
32301
|
+
caseSensitive: tool.schema.boolean().optional().default(false).describe("Perform case-sensitive search (default: false)"),
|
|
32302
|
+
wholeWord: tool.schema.boolean().optional().default(false).describe("Match whole words only (default: false)"),
|
|
32303
|
+
fixedStrings: tool.schema.boolean().optional().default(false).describe("Treat pattern as literal string (default: false)")
|
|
32075
32304
|
},
|
|
32076
32305
|
execute: async (args) => {
|
|
32077
32306
|
try {
|
|
@@ -32081,7 +32310,10 @@ var grep = tool({
|
|
|
32081
32310
|
pattern: args.pattern,
|
|
32082
32311
|
paths: paths2,
|
|
32083
32312
|
globs,
|
|
32084
|
-
context: 0
|
|
32313
|
+
context: 0,
|
|
32314
|
+
caseSensitive: args.caseSensitive ?? false,
|
|
32315
|
+
wholeWord: args.wholeWord ?? false,
|
|
32316
|
+
fixedStrings: args.fixedStrings ?? false
|
|
32085
32317
|
});
|
|
32086
32318
|
return formatGrepResult(result);
|
|
32087
32319
|
} catch (e) {
|
|
@@ -33065,6 +33297,8 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
33065
33297
|
await tmuxSessionManager.onSessionCreated(input.event);
|
|
33066
33298
|
await backgroundManager.handleSessionStatus(input.event);
|
|
33067
33299
|
await tmuxSessionManager.onSessionStatus(input.event);
|
|
33300
|
+
await backgroundManager.handleSessionDeleted(input.event);
|
|
33301
|
+
await tmuxSessionManager.onSessionDeleted(input.event);
|
|
33068
33302
|
},
|
|
33069
33303
|
"experimental.chat.messages.transform": phaseReminderHook["experimental.chat.messages.transform"],
|
|
33070
33304
|
"tool.execute.after": postReadNudgeHook["tool.execute.after"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getEnv(name: string): string | undefined;
|
package/dist/utils/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-opencode-slim",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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",
|
|
@@ -50,17 +50,17 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@ast-grep/cli": "^0.40.0",
|
|
53
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
54
|
-
"@opencode-ai/plugin": "^1.
|
|
55
|
-
"@opencode-ai/sdk": "^1.
|
|
53
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
54
|
+
"@opencode-ai/plugin": "^1.2.6",
|
|
55
|
+
"@opencode-ai/sdk": "^1.2.6",
|
|
56
56
|
"vscode-jsonrpc": "^8.2.0",
|
|
57
57
|
"vscode-languageserver-protocol": "^3.17.5",
|
|
58
|
-
"zod": "^4.
|
|
58
|
+
"zod": "^4.3.6"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@biomejs/biome": "2.
|
|
62
|
-
"bun-types": "
|
|
63
|
-
"typescript": "^5.
|
|
61
|
+
"@biomejs/biome": "2.4.2",
|
|
62
|
+
"bun-types": "1.3.9",
|
|
63
|
+
"typescript": "^5.9.3"
|
|
64
64
|
},
|
|
65
65
|
"trustedDependencies": [
|
|
66
66
|
"@ast-grep/cli"
|