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/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 userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
16966
- const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
16967
- let config2 = loadConfigFromPath(userConfigPath) ?? {};
16968
- const projectConfig = loadConfigFromPath(projectConfigPath);
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 promptBody = applyAgentVariant(resolvedVariant, {
18141
+ const basePromptBody = applyAgentVariant(resolvedVariant, {
17984
18142
  agent: task.agent,
17985
- tools: { background_task: false, task: false },
18143
+ tools: toolPermissions,
17986
18144
  parts: [{ type: "text", text: task.prompt }]
17987
18145
  });
17988
- await this.client.session.prompt({
17989
- path: { id: session.data.id },
17990
- body: promptBody,
17991
- query: promptQuery
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
- // src/cli/paths.ts
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: toolContext.sessionID
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 (eg. "*.js", "*.{ts,tsx}"). ' + "Returns file paths with matches sorted by modification time.",
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;
@@ -1,4 +1,5 @@
1
1
  export * from './agent-variant';
2
+ export * from './env';
2
3
  export { log } from './logger';
3
4
  export * from './polling';
4
5
  export * from './tmux';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode-slim",
3
- "version": "0.6.4",
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.25.1",
54
- "@opencode-ai/plugin": "^1.1.19",
55
- "@opencode-ai/sdk": "^1.1.19",
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.1.8"
58
+ "zod": "^4.3.6"
59
59
  },
60
60
  "devDependencies": {
61
- "@biomejs/biome": "2.3.11",
62
- "bun-types": "latest",
63
- "typescript": "^5.7.3"
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"