oh-my-opencode-slim 2.0.1 → 2.0.2

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.
Files changed (39) hide show
  1. package/README.ja-JP.md +30 -1
  2. package/README.ko-KR.md +30 -1
  3. package/README.md +36 -1
  4. package/README.zh-CN.md +30 -1
  5. package/dist/agents/orchestrator.d.ts +0 -2
  6. package/dist/cli/index.js +22 -34
  7. package/dist/config/agent-mcps.d.ts +0 -4
  8. package/dist/config/constants.d.ts +1 -7
  9. package/dist/config/council-schema.d.ts +0 -15
  10. package/dist/config/runtime-preset.d.ts +0 -1
  11. package/dist/config/schema.d.ts +2 -68
  12. package/dist/hooks/auto-update-checker/skill-sync.d.ts +9 -0
  13. package/dist/hooks/filter-available-skills/index.d.ts +1 -13
  14. package/dist/hooks/foreground-fallback/index.d.ts +1 -1
  15. package/dist/hooks/image-hook.d.ts +1 -13
  16. package/dist/hooks/index.d.ts +3 -2
  17. package/dist/hooks/phase-reminder/index.d.ts +10 -16
  18. package/dist/hooks/reflect/index.d.ts +13 -0
  19. package/dist/hooks/task-session-manager/index.d.ts +2 -16
  20. package/dist/hooks/types.d.ts +23 -0
  21. package/dist/index.js +391 -289
  22. package/dist/tui.js +16 -25
  23. package/dist/utils/agent-variant.d.ts +0 -40
  24. package/dist/utils/compat.d.ts +0 -1
  25. package/dist/utils/guards.d.ts +4 -0
  26. package/dist/utils/index.d.ts +1 -2
  27. package/dist/utils/logger.d.ts +1 -1
  28. package/dist/utils/task.d.ts +0 -2
  29. package/oh-my-opencode-slim.schema.json +2 -249
  30. package/package.json +1 -1
  31. package/src/skills/codemap.md +4 -1
  32. package/src/skills/reflect/SKILL.md +193 -0
  33. package/src/skills/worktrees/SKILL.md +164 -0
  34. package/dist/config/fallback-chains.d.ts +0 -1
  35. package/dist/hooks/apply-patch/patch.d.ts +0 -2
  36. package/dist/hooks/delegate-task-retry/guidance.d.ts +0 -2
  37. package/dist/hooks/delegate-task-retry/index.d.ts +0 -4
  38. package/dist/hooks/json-error-recovery/index.d.ts +0 -1
  39. package/dist/utils/env.d.ts +0 -1
package/dist/index.js CHANGED
@@ -6246,33 +6246,33 @@ var require_URL = __commonJS((exports, module) => {
6246
6246
  else
6247
6247
  return basepath.substring(0, lastslash + 1) + refpath;
6248
6248
  }
6249
- function remove_dot_segments(path16) {
6250
- if (!path16)
6251
- return path16;
6249
+ function remove_dot_segments(path18) {
6250
+ if (!path18)
6251
+ return path18;
6252
6252
  var output = "";
6253
- while (path16.length > 0) {
6254
- if (path16 === "." || path16 === "..") {
6255
- path16 = "";
6253
+ while (path18.length > 0) {
6254
+ if (path18 === "." || path18 === "..") {
6255
+ path18 = "";
6256
6256
  break;
6257
6257
  }
6258
- var twochars = path16.substring(0, 2);
6259
- var threechars = path16.substring(0, 3);
6260
- var fourchars = path16.substring(0, 4);
6258
+ var twochars = path18.substring(0, 2);
6259
+ var threechars = path18.substring(0, 3);
6260
+ var fourchars = path18.substring(0, 4);
6261
6261
  if (threechars === "../") {
6262
- path16 = path16.substring(3);
6262
+ path18 = path18.substring(3);
6263
6263
  } else if (twochars === "./") {
6264
- path16 = path16.substring(2);
6264
+ path18 = path18.substring(2);
6265
6265
  } else if (threechars === "/./") {
6266
- path16 = "/" + path16.substring(3);
6267
- } else if (twochars === "/." && path16.length === 2) {
6268
- path16 = "/";
6269
- } else if (fourchars === "/../" || threechars === "/.." && path16.length === 3) {
6270
- path16 = "/" + path16.substring(4);
6266
+ path18 = "/" + path18.substring(3);
6267
+ } else if (twochars === "/." && path18.length === 2) {
6268
+ path18 = "/";
6269
+ } else if (fourchars === "/../" || threechars === "/.." && path18.length === 3) {
6270
+ path18 = "/" + path18.substring(4);
6271
6271
  output = output.replace(/\/?[^\/]*$/, "");
6272
6272
  } else {
6273
- var segment = path16.match(/(\/?([^\/]*))/)[0];
6273
+ var segment = path18.match(/(\/?([^\/]*))/)[0];
6274
6274
  output += segment;
6275
- path16 = path16.substring(segment.length);
6275
+ path18 = path18.substring(segment.length);
6276
6276
  }
6277
6277
  }
6278
6278
  return output;
@@ -18150,14 +18150,14 @@ var require_turndown_cjs = __commonJS((exports, module) => {
18150
18150
  } else if (node.nodeType === 1) {
18151
18151
  replacement = replacementForNode.call(self, node);
18152
18152
  }
18153
- return join14(output, replacement);
18153
+ return join16(output, replacement);
18154
18154
  }, "");
18155
18155
  }
18156
18156
  function postProcess(output) {
18157
18157
  var self = this;
18158
18158
  this.rules.forEach(function(rule) {
18159
18159
  if (typeof rule.append === "function") {
18160
- output = join14(output, rule.append(self.options));
18160
+ output = join16(output, rule.append(self.options));
18161
18161
  }
18162
18162
  });
18163
18163
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -18170,7 +18170,7 @@ var require_turndown_cjs = __commonJS((exports, module) => {
18170
18170
  content = content.trim();
18171
18171
  return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
18172
18172
  }
18173
- function join14(output, replacement) {
18173
+ function join16(output, replacement) {
18174
18174
  var s1 = trimTrailingNewlines(output);
18175
18175
  var s2 = trimLeadingNewlines(replacement);
18176
18176
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -18240,11 +18240,23 @@ var CUSTOM_SKILLS = [
18240
18240
  allowedAgents: ["orchestrator"],
18241
18241
  sourcePath: "src/skills/deepwork"
18242
18242
  },
18243
+ {
18244
+ name: "reflect",
18245
+ description: "Review repeated work and suggest reusable workflow improvements",
18246
+ allowedAgents: ["orchestrator"],
18247
+ sourcePath: "src/skills/reflect"
18248
+ },
18243
18249
  {
18244
18250
  name: "oh-my-opencode-slim",
18245
18251
  description: "Configure, customize, and safely improve oh-my-opencode-slim setups",
18246
18252
  allowedAgents: ["orchestrator"],
18247
18253
  sourcePath: "src/skills/oh-my-opencode-slim"
18254
+ },
18255
+ {
18256
+ name: "worktrees",
18257
+ description: "Manage Git worktrees as OMO safe isolated coding lanes for complex/risky/parallel work",
18258
+ allowedAgents: ["orchestrator"],
18259
+ sourcePath: "src/skills/worktrees"
18248
18260
  }
18249
18261
  ];
18250
18262
 
@@ -18303,8 +18315,7 @@ var SUBAGENT_NAMES = [
18303
18315
  "council",
18304
18316
  "councillor"
18305
18317
  ];
18306
- var ORCHESTRATOR_NAME = "orchestrator";
18307
- var ALL_AGENT_NAMES = [ORCHESTRATOR_NAME, ...SUBAGENT_NAMES];
18318
+ var ALL_AGENT_NAMES = ["orchestrator", ...SUBAGENT_NAMES];
18308
18319
  var PROTECTED_AGENTS = new Set(["orchestrator", "councillor"]);
18309
18320
  var DEFAULT_MODELS = {
18310
18321
  orchestrator: undefined,
@@ -18318,10 +18329,10 @@ var DEFAULT_MODELS = {
18318
18329
  councillor: "openai/gpt-5.4-mini"
18319
18330
  };
18320
18331
  var POLL_INTERVAL_BACKGROUND_MS = 2000;
18321
- var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
18322
18332
  var MAX_POLL_TIME_MS = 5 * 60 * 1000;
18323
18333
  var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
18324
18334
  var PHASE_REMINDER_TEXT = `!IMPORTANT! Scheduler workflow: plan lanes/dependencies → dispatch background specialists → track task IDs → wait for hook-driven completion → reconcile terminal results → verify. Do not poll running jobs, consume running-job output, or advance dependent work. !END!`;
18335
+ var PHASE_REMINDER = `<internal_reminder>${PHASE_REMINDER_TEXT}</internal_reminder>`;
18325
18336
  var WRITABLE_FILE_OPERATIONS_RULES = `**File Operations Rules**:
18326
18337
  - Prefer dedicated file tools for normal code work: glob/grep/ast_grep_search for discovery, read for file contents, and edit/write/apply_patch for targeted source changes.
18327
18338
  - Use bash for execution and automation: git, package managers, tests, builds, scripts, diagnostics, and shell-native filesystem operations.
@@ -18386,17 +18397,11 @@ var CouncilConfigSchema = z.object({
18386
18397
  default_preset: z.string().default("default"),
18387
18398
  councillor_execution_mode: CouncillorExecutionModeSchema.describe('Execution mode for councillors. "serial" runs them one at a time (required for single-model systems). "parallel" runs them concurrently (default, faster for multi-model systems).'),
18388
18399
  councillor_retries: z.number().int().min(0).max(5).default(3).describe("Number of retry attempts for councillors that return empty responses " + "(e.g. due to provider rate limiting). Default: 3 retries."),
18389
- master: z.unknown().optional().describe("DEPRECATED — ignored. Council agent synthesizes directly."),
18390
- master_timeout: z.unknown().optional().describe('DEPRECATED — ignored. Use "timeout" instead.'),
18391
- master_fallback: z.unknown().optional().describe("DEPRECATED — ignored. No separate master session.")
18400
+ master: z.unknown().optional().describe("DEPRECATED — ignored. Council agent synthesizes directly.")
18392
18401
  }).transform((data) => {
18393
18402
  const deprecated = [];
18394
18403
  if (data.master !== undefined)
18395
18404
  deprecated.push("master");
18396
- if (data.master_timeout !== undefined)
18397
- deprecated.push("master_timeout");
18398
- if (data.master_fallback !== undefined)
18399
- deprecated.push("master_fallback");
18400
18405
  const legacyMasterModel = typeof data.master === "object" && data.master !== null && "model" in data.master && typeof data.master.model === "string" ? data.master.model : undefined;
18401
18406
  return {
18402
18407
  presets: data.presets,
@@ -18415,7 +18420,6 @@ import * as path from "node:path";
18415
18420
  // src/utils/compat.ts
18416
18421
  import { spawn as nodeSpawn } from "node:child_process";
18417
18422
  import { writeFile as fsWriteFile } from "node:fs/promises";
18418
- var isBun = typeof globalThis.Bun !== "undefined";
18419
18423
  function collectStream(stream) {
18420
18424
  if (!stream)
18421
18425
  return () => Promise.resolve("");
@@ -18534,15 +18538,6 @@ var ManualPlanSchema = z2.object({
18534
18538
  librarian: ManualAgentPlanSchema,
18535
18539
  fixer: ManualAgentPlanSchema
18536
18540
  }).strict();
18537
- var AgentModelChainSchema = z2.array(z2.string()).min(1);
18538
- var FallbackChainsSchema = z2.object({
18539
- orchestrator: AgentModelChainSchema.optional(),
18540
- oracle: AgentModelChainSchema.optional(),
18541
- designer: AgentModelChainSchema.optional(),
18542
- explorer: AgentModelChainSchema.optional(),
18543
- librarian: AgentModelChainSchema.optional(),
18544
- fixer: AgentModelChainSchema.optional()
18545
- }).catchall(AgentModelChainSchema);
18546
18541
  var AgentOverrideConfigSchema = z2.object({
18547
18542
  model: z2.union([
18548
18543
  z2.string(),
@@ -18605,9 +18600,8 @@ var FailoverConfigSchema = z2.object({
18605
18600
  enabled: z2.boolean().default(true),
18606
18601
  timeoutMs: z2.number().min(0).default(15000),
18607
18602
  retryDelayMs: z2.number().min(0).default(500),
18608
- chains: FallbackChainsSchema.default({}),
18609
18603
  retry_on_empty: z2.boolean().default(true).describe("When true (default), empty provider responses are treated as failures, " + "triggering fallback/retry. Set to false to treat them as successes.")
18610
- });
18604
+ }).strict();
18611
18605
  var CompanionConfigSchema = z2.object({
18612
18606
  enabled: z2.boolean().optional(),
18613
18607
  position: z2.enum(["bottom-right", "bottom-left", "top-right", "top-left"]).optional(),
@@ -18638,10 +18632,7 @@ function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
18638
18632
  var PluginConfigSchema = z2.object({
18639
18633
  preset: z2.string().optional(),
18640
18634
  setDefaultAgent: z2.boolean().optional(),
18641
- scoringEngineVersion: z2.enum(["v1", "v2-shadow", "v2"]).optional(),
18642
- balanceProviderUsage: z2.boolean().optional(),
18643
18635
  autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
18644
- manualPlan: ManualPlanSchema.optional(),
18645
18636
  presets: z2.record(z2.string(), PresetSchema).optional(),
18646
18637
  agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
18647
18638
  disabled_agents: z2.array(z2.string()).optional().describe("Agent names to disable completely. " + "Disabled agents are not instantiated and cannot be delegated to. " + "Orchestrator and council internal agents (councillor) cannot be disabled. " + "By default, 'observer' is disabled. Remove it from this list and configure a vision-capable model to enable."),
@@ -19201,7 +19192,6 @@ When user's approach seems problematic:
19201
19192
  </Communication>
19202
19193
  `;
19203
19194
  }
19204
- var ORCHESTRATOR_PROMPT = buildOrchestratorPrompt();
19205
19195
  function createOrchestratorAgent(model, customPrompt, customAppendPrompt, disabledAgents) {
19206
19196
  const basePrompt = buildOrchestratorPrompt(disabledAgents);
19207
19197
  const prompt = resolvePrompt(basePrompt, customPrompt, customAppendPrompt);
@@ -20271,35 +20261,6 @@ class CompanionManager {
20271
20261
  }
20272
20262
  }
20273
20263
 
20274
- // src/config/fallback-chains.ts
20275
- function normalizeFallbackChainsForPreset(chains, presetName) {
20276
- const normalized = {};
20277
- for (const [rawKey, chainModels] of Object.entries(chains)) {
20278
- if (!chainModels?.length)
20279
- continue;
20280
- const separatorIndex = rawKey.indexOf(":");
20281
- const hasPresetScope = separatorIndex !== -1;
20282
- const scopedPreset = hasPresetScope ? rawKey.slice(0, separatorIndex) : "";
20283
- const agentName = hasPresetScope ? rawKey.slice(separatorIndex + 1) : rawKey;
20284
- if (!agentName)
20285
- continue;
20286
- if (hasPresetScope && scopedPreset !== presetName)
20287
- continue;
20288
- const existing = normalized[agentName] ?? [];
20289
- const seen = new Set(existing);
20290
- for (const chainModel of chainModels) {
20291
- if (seen.has(chainModel))
20292
- continue;
20293
- seen.add(chainModel);
20294
- existing.push(chainModel);
20295
- }
20296
- if (existing.length > 0) {
20297
- normalized[agentName] = existing;
20298
- }
20299
- }
20300
- return normalized;
20301
- }
20302
-
20303
20264
  // src/config/runtime-preset.ts
20304
20265
  var activeRuntimePreset = null;
20305
20266
  function setActiveRuntimePreset(name) {
@@ -22083,6 +22044,9 @@ function createApplyPatchHook(ctx) {
22083
22044
  }
22084
22045
  };
22085
22046
  }
22047
+ // src/hooks/auto-update-checker/index.ts
22048
+ import * as path10 from "node:path";
22049
+
22086
22050
  // src/hooks/auto-update-checker/cache.ts
22087
22051
  import * as fs5 from "node:fs";
22088
22052
  import * as path8 from "node:path";
@@ -22542,6 +22506,133 @@ function preparePackageUpdate(version, packageName = PACKAGE_NAME, runtimePackag
22542
22506
  }
22543
22507
  }
22544
22508
 
22509
+ // src/hooks/auto-update-checker/skill-sync.ts
22510
+ import {
22511
+ copyFileSync,
22512
+ existsSync as existsSync5,
22513
+ lstatSync,
22514
+ mkdirSync as mkdirSync3,
22515
+ mkdtempSync,
22516
+ readdirSync as readdirSync2,
22517
+ renameSync as renameSync2,
22518
+ rmSync as rmSync2
22519
+ } from "node:fs";
22520
+ import * as path9 from "node:path";
22521
+ function copyDirRecursive(src, dest) {
22522
+ const stat2 = lstatSync(src);
22523
+ if (stat2.isSymbolicLink()) {
22524
+ return;
22525
+ }
22526
+ if (stat2.isDirectory()) {
22527
+ mkdirSync3(dest, { recursive: true });
22528
+ const entries = readdirSync2(src);
22529
+ for (const entry of entries) {
22530
+ copyDirRecursive(path9.join(src, entry), path9.join(dest, entry));
22531
+ }
22532
+ } else if (stat2.isFile()) {
22533
+ const destDir = path9.dirname(dest);
22534
+ if (!existsSync5(destDir)) {
22535
+ mkdirSync3(destDir, { recursive: true });
22536
+ }
22537
+ copyFileSync(src, dest);
22538
+ }
22539
+ }
22540
+ function syncBundledSkillsFromPackage(packageRoot) {
22541
+ const installed = [];
22542
+ const skippedExisting = [];
22543
+ const failed = [];
22544
+ const sourceSkillsDir = path9.join(packageRoot, "src", "skills");
22545
+ try {
22546
+ const stat2 = lstatSync(sourceSkillsDir);
22547
+ if (stat2.isSymbolicLink() || !stat2.isDirectory()) {
22548
+ log(`[skill-sync] Source skills directory is not a valid directory: ${sourceSkillsDir}`);
22549
+ return { installed, skippedExisting, failed };
22550
+ }
22551
+ } catch {
22552
+ log(`[skill-sync] Source skills directory does not exist or is unreadable: ${sourceSkillsDir}`);
22553
+ return { installed, skippedExisting, failed };
22554
+ }
22555
+ const destSkillsDir = path9.join(getConfigDir(), "skills");
22556
+ try {
22557
+ if (!existsSync5(destSkillsDir)) {
22558
+ mkdirSync3(destSkillsDir, { recursive: true });
22559
+ }
22560
+ } catch (err) {
22561
+ log(`[skill-sync] Failed to create destination skills directory: ${destSkillsDir}`, err);
22562
+ }
22563
+ let entries = [];
22564
+ try {
22565
+ entries = readdirSync2(sourceSkillsDir);
22566
+ } catch (err) {
22567
+ log(`[skill-sync] Failed to read source skills directory: ${sourceSkillsDir}`, err);
22568
+ return { installed, skippedExisting, failed };
22569
+ }
22570
+ for (const entry of entries) {
22571
+ const entryPath = path9.join(sourceSkillsDir, entry);
22572
+ try {
22573
+ if (entry.startsWith(".")) {
22574
+ continue;
22575
+ }
22576
+ const entryStat = lstatSync(entryPath);
22577
+ if (entryStat.isSymbolicLink() || !entryStat.isDirectory()) {
22578
+ continue;
22579
+ }
22580
+ const skillMdPath = path9.join(entryPath, "SKILL.md");
22581
+ try {
22582
+ const skillMdStat = lstatSync(skillMdPath);
22583
+ if (skillMdStat.isSymbolicLink() || !skillMdStat.isFile()) {
22584
+ continue;
22585
+ }
22586
+ } catch {
22587
+ continue;
22588
+ }
22589
+ const destPath = path9.join(destSkillsDir, entry);
22590
+ let destExists = false;
22591
+ try {
22592
+ lstatSync(destPath);
22593
+ destExists = true;
22594
+ } catch {}
22595
+ if (destExists) {
22596
+ log(`[skill-sync] Skill already exists in destination: ${entry}`);
22597
+ skippedExisting.push(entry);
22598
+ continue;
22599
+ }
22600
+ const stagingDir = mkdtempSync(path9.join(destSkillsDir, `.sync-staging-${entry}-`));
22601
+ try {
22602
+ copyDirRecursive(entryPath, stagingDir);
22603
+ let destExistsLate = false;
22604
+ try {
22605
+ lstatSync(destPath);
22606
+ destExistsLate = true;
22607
+ } catch {}
22608
+ if (destExistsLate) {
22609
+ log(`[skill-sync] Destination path was created during staging for ${entry}, skipping promotion.`);
22610
+ skippedExisting.push(entry);
22611
+ } else {
22612
+ renameSync2(stagingDir, destPath);
22613
+ installed.push(entry);
22614
+ log(`[skill-sync] Successfully synced skill: ${entry}`);
22615
+ }
22616
+ } catch (err) {
22617
+ log(`[skill-sync] Failed to sync skill ${entry}:`, err);
22618
+ failed.push(entry);
22619
+ } finally {
22620
+ try {
22621
+ if (existsSync5(stagingDir)) {
22622
+ rmSync2(stagingDir, { recursive: true, force: true });
22623
+ }
22624
+ } catch (err) {
22625
+ log(`[skill-sync] Failed to clean up staging directory ${stagingDir}:`, err);
22626
+ }
22627
+ }
22628
+ } catch (err) {
22629
+ log(`[skill-sync] Error processing source entry ${entry}:`, err);
22630
+ failed.push(entry);
22631
+ }
22632
+ }
22633
+ return { installed, skippedExisting, failed };
22634
+ }
22635
+
22545
22636
  // src/hooks/auto-update-checker/index.ts
22546
22637
  function createAutoUpdateCheckerHook(ctx, options = {}) {
22547
22638
  const { autoUpdate = true } = options;
@@ -22624,8 +22715,28 @@ Version is pinned. Update your plugin config to apply.`, "info", 8000);
22624
22715
  }
22625
22716
  const installSuccess = await runBunInstallSafe(installDir);
22626
22717
  if (installSuccess) {
22627
- showToast(ctx, "OMO-Slim Updated!", `v${currentVersion} → v${latestVersion}
22628
- Restart OpenCode to apply.`, "success", 8000);
22718
+ let installedSkills = [];
22719
+ try {
22720
+ const packageRoot = path10.join(installDir, "node_modules", PACKAGE_NAME);
22721
+ const syncResult = syncBundledSkillsFromPackage(packageRoot);
22722
+ installedSkills = syncResult.installed;
22723
+ if (syncResult.failed.length > 0) {
22724
+ log(`[auto-update-checker] Skill sync warnings/failures: ${syncResult.failed.join(", ")}`);
22725
+ }
22726
+ if (syncResult.skippedExisting.length > 0) {
22727
+ log(`[auto-update-checker] Skill sync skipped existing: ${syncResult.skippedExisting.join(", ")}`);
22728
+ }
22729
+ } catch (err) {
22730
+ log("[auto-update-checker] Skill sync failed silently:", err);
22731
+ }
22732
+ let message = `v${currentVersion} → v${latestVersion}
22733
+ Restart OpenCode to apply.`;
22734
+ if (installedSkills.length > 0) {
22735
+ message = `v${currentVersion} → v${latestVersion}
22736
+ Added bundled skills: ${installedSkills.join(", ")}
22737
+ Restart OpenCode to apply.`;
22738
+ }
22739
+ showToast(ctx, "OMO-Slim Updated!", message, "success", 8000);
22629
22740
  log(`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`);
22630
22741
  } else {
22631
22742
  showToast(ctx, `OMO-Slim ${latestVersion}`, `v${latestVersion} available, but auto-update failed to install it. Check logs or retry manually.`, "error", 8000);
@@ -22728,10 +22839,6 @@ function createDisplayNameMentionRewriter(config) {
22728
22839
  };
22729
22840
  }
22730
22841
  // src/utils/task.ts
22731
- var TRANSIENT_PROCESS_ERROR_TEXT = new Set([
22732
- "Task is not running in this process and has no final output.",
22733
- "Task is not running in this process and has not produced output."
22734
- ]);
22735
22842
  function parseTaskIdFromTaskOutput(output) {
22736
22843
  const xmlMatch = /<task\s+[^>]*\bid=["']([^"']+)["'][^>]*>/i.exec(output);
22737
22844
  if (xmlMatch)
@@ -23134,11 +23241,13 @@ function normalizeCancelReason(reason) {
23134
23241
  const normalized = reason?.replace(/\s+/g, " ").trim();
23135
23242
  return normalized ? `cancelled: ${normalized}` : "cancelled";
23136
23243
  }
23137
- // src/utils/internal-initiator.ts
23138
- var SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
23244
+ // src/utils/guards.ts
23139
23245
  function isRecord(value) {
23140
23246
  return typeof value === "object" && value !== null;
23141
23247
  }
23248
+
23249
+ // src/utils/internal-initiator.ts
23250
+ var SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
23142
23251
  function createInternalAgentTextPart(text) {
23143
23252
  return {
23144
23253
  type: "text",
@@ -23178,8 +23287,8 @@ function isPwshAvailable() {
23178
23287
  });
23179
23288
  return result.status === 0;
23180
23289
  }
23181
- function escapePowerShellPath(path9) {
23182
- return path9.replace(/'/g, "''");
23290
+ function escapePowerShellPath(path11) {
23291
+ return path11.replace(/'/g, "''");
23183
23292
  }
23184
23293
  function getWindowsZipExtractor() {
23185
23294
  const buildNumber = getWindowsBuildNumber();
@@ -23392,7 +23501,7 @@ function detectDelegateTaskError(output) {
23392
23501
  return null;
23393
23502
  }
23394
23503
 
23395
- // src/hooks/delegate-task-retry/guidance.ts
23504
+ // src/hooks/delegate-task-retry/hook.ts
23396
23505
  function extractAvailableList(output) {
23397
23506
  const match = output.match(/Allowed agents:\s*(.+)$/m);
23398
23507
  if (match)
@@ -23422,7 +23531,6 @@ function buildRetryGuidance(errorInfo) {
23422
23531
  return lines.join(`
23423
23532
  `);
23424
23533
  }
23425
- // src/hooks/delegate-task-retry/hook.ts
23426
23534
  function createDelegateTaskRetryHook(_ctx) {
23427
23535
  return {
23428
23536
  "tool.execute.after": async (input, output) => {
@@ -23548,12 +23656,6 @@ function isRateLimitError(error) {
23548
23656
  ].join(" ");
23549
23657
  return RATE_LIMIT_PATTERNS.some((p) => p.test(text));
23550
23658
  }
23551
- function parseModel(model) {
23552
- const slash = model.indexOf("/");
23553
- if (slash <= 0 || slash >= model.length - 1)
23554
- return null;
23555
- return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
23556
- }
23557
23659
  var DEDUP_WINDOW_MS = 5000;
23558
23660
  var REPROMPT_DELAY_MS = 500;
23559
23661
 
@@ -23671,7 +23773,7 @@ class ForegroundFallbackManager {
23671
23773
  return;
23672
23774
  }
23673
23775
  tried.add(nextModel);
23674
- const ref = parseModel(nextModel);
23776
+ const ref = parseModelReference(nextModel);
23675
23777
  if (!ref) {
23676
23778
  log("[foreground-fallback] invalid model format", {
23677
23779
  sessionID,
@@ -23748,15 +23850,15 @@ class ForegroundFallbackManager {
23748
23850
  // src/hooks/image-hook.ts
23749
23851
  import { createHash } from "node:crypto";
23750
23852
  import {
23751
- existsSync as existsSync5,
23752
- mkdirSync as mkdirSync3,
23753
- readdirSync as readdirSync2,
23853
+ existsSync as existsSync6,
23854
+ mkdirSync as mkdirSync4,
23855
+ readdirSync as readdirSync3,
23754
23856
  rmdirSync,
23755
23857
  statSync as statSync3,
23756
23858
  unlinkSync as unlinkSync2,
23757
23859
  writeFileSync as writeFileSync4
23758
23860
  } from "node:fs";
23759
- import { basename as basename2, extname, join as join8 } from "node:path";
23861
+ import { basename as basename2, extname, join as join10 } from "node:path";
23760
23862
  var lastCleanupByDir = new Map;
23761
23863
  var CLEANUP_INTERVAL = 10 * 60 * 1000;
23762
23864
  function isImagePart(p) {
@@ -23803,8 +23905,8 @@ function cleanupAllSessions(saveDir) {
23803
23905
  const maxAge = 60 * 60 * 1000;
23804
23906
  const dirsToScan = [];
23805
23907
  try {
23806
- for (const entry of readdirSync2(saveDir, { withFileTypes: true })) {
23807
- const fp = join8(saveDir, entry.name);
23908
+ for (const entry of readdirSync3(saveDir, { withFileTypes: true })) {
23909
+ const fp = join10(saveDir, entry.name);
23808
23910
  if (entry.isDirectory()) {
23809
23911
  dirsToScan.push(fp);
23810
23912
  } else {
@@ -23819,9 +23921,9 @@ function cleanupAllSessions(saveDir) {
23819
23921
  try {
23820
23922
  let isEmpty = true;
23821
23923
  let allRemoved = true;
23822
- for (const f of readdirSync2(dir)) {
23924
+ for (const f of readdirSync3(dir)) {
23823
23925
  isEmpty = false;
23824
- const fp = join8(dir, f);
23926
+ const fp = join10(dir, f);
23825
23927
  try {
23826
23928
  if (now - statSync3(fp).mtimeMs > maxAge) {
23827
23929
  unlinkSync2(fp);
@@ -23843,8 +23945,8 @@ function cleanupAllSessions(saveDir) {
23843
23945
  function writeUniqueFile(dir, name, data, log2) {
23844
23946
  const ext = extname(name);
23845
23947
  const base = basename2(name, ext) || name;
23846
- let candidate = join8(dir, name);
23847
- if (existsSync5(candidate)) {
23948
+ let candidate = join10(dir, name);
23949
+ if (existsSync6(candidate)) {
23848
23950
  return candidate;
23849
23951
  }
23850
23952
  let counter = 0;
@@ -23856,7 +23958,7 @@ function writeUniqueFile(dir, name, data, log2) {
23856
23958
  } catch (e) {
23857
23959
  if (e instanceof Error && e.code === "EEXIST") {
23858
23960
  counter += 1;
23859
- candidate = join8(dir, `${base}-${counter}${ext}`);
23961
+ candidate = join10(dir, `${base}-${counter}${ext}`);
23860
23962
  continue;
23861
23963
  }
23862
23964
  log2(`[image-hook] failed to save image: ${e}`);
@@ -23880,16 +23982,16 @@ function processImageAttachments(args) {
23880
23982
  messagesWithImages.push({ msg, imageParts });
23881
23983
  }
23882
23984
  }
23883
- const saveDir = join8(workDir, ".opencode", "images");
23985
+ const saveDir = join10(workDir, ".opencode", "images");
23884
23986
  if (messagesWithImages.length === 0) {
23885
- if (existsSync5(saveDir))
23987
+ if (existsSync6(saveDir))
23886
23988
  cleanupAllSessions(saveDir);
23887
23989
  return;
23888
23990
  }
23889
- const gitignorePath = join8(workDir, ".opencode", ".gitignore");
23991
+ const gitignorePath = join10(workDir, ".opencode", ".gitignore");
23890
23992
  try {
23891
- mkdirSync3(saveDir, { recursive: true });
23892
- if (!existsSync5(gitignorePath))
23993
+ mkdirSync4(saveDir, { recursive: true });
23994
+ if (!existsSync6(gitignorePath))
23893
23995
  writeFileSync4(gitignorePath, `*
23894
23996
  `);
23895
23997
  } catch (e) {
@@ -23898,9 +24000,9 @@ function processImageAttachments(args) {
23898
24000
  cleanupAllSessions(saveDir);
23899
24001
  for (const { msg, imageParts } of messagesWithImages) {
23900
24002
  const sessionSubdir = msg.info.sessionID ? sanitizeFilename(msg.info.sessionID) : undefined;
23901
- const targetDir = sessionSubdir ? join8(saveDir, sessionSubdir) : saveDir;
24003
+ const targetDir = sessionSubdir ? join10(saveDir, sessionSubdir) : saveDir;
23902
24004
  try {
23903
- mkdirSync3(targetDir, { recursive: true });
24005
+ mkdirSync4(targetDir, { recursive: true });
23904
24006
  } catch (e) {
23905
24007
  log2(`[image-hook] failed to create target image directory: ${e}`);
23906
24008
  }
@@ -23984,7 +24086,6 @@ ${JSON_ERROR_REMINDER}`;
23984
24086
  };
23985
24087
  }
23986
24088
  // src/hooks/phase-reminder/index.ts
23987
- var PHASE_REMINDER = `<internal_reminder>${PHASE_REMINDER_TEXT}</internal_reminder>`;
23988
24089
  function createPhaseReminderHook() {
23989
24090
  return {
23990
24091
  "experimental.chat.messages.transform": async (_input, output) => {
@@ -24026,24 +24127,18 @@ function createPhaseReminderHook() {
24026
24127
  };
24027
24128
  }
24028
24129
  // src/hooks/post-file-tool-nudge/index.ts
24029
- var POST_FILE_TOOL_NUDGE = PHASE_REMINDER_TEXT;
24030
24130
  var FILE_TOOLS = new Set(["Read", "read", "Write", "write"]);
24031
24131
  function createPostFileToolNudgeHook(options = {}) {
24032
24132
  function appendReminder(output) {
24033
24133
  if (typeof output.output !== "string") {
24034
24134
  return;
24035
24135
  }
24036
- if (output.output.includes(POST_FILE_TOOL_NUDGE)) {
24136
+ if (output.output.includes(PHASE_REMINDER)) {
24037
24137
  return;
24038
24138
  }
24039
- output.output = [
24040
- output.output,
24041
- "",
24042
- "<internal_reminder>",
24043
- POST_FILE_TOOL_NUDGE,
24044
- "</internal_reminder>"
24045
- ].join(`
24046
- `);
24139
+ output.output = `${output.output}
24140
+
24141
+ ${PHASE_REMINDER}`;
24047
24142
  }
24048
24143
  return {
24049
24144
  "tool.execute.after": async (input, output) => {
@@ -24057,8 +24152,59 @@ function createPostFileToolNudgeHook(options = {}) {
24057
24152
  }
24058
24153
  };
24059
24154
  }
24155
+ // src/hooks/reflect/index.ts
24156
+ var COMMAND_NAME2 = "reflect";
24157
+ function activationPrompt2(focus) {
24158
+ const focusBlock = focus ? ["Focus:", focus] : [
24159
+ "Focus:",
24160
+ "Review recent work broadly and identify repeated workflow friction worth improving."
24161
+ ];
24162
+ return [
24163
+ "Use the reflect skill for this request.",
24164
+ "",
24165
+ "Reflect requirements:",
24166
+ "- inspect existing skills, commands, agents, prompt overrides, MCP permissions, config, and project playbooks before suggesting anything new;",
24167
+ "- find repeated workflow patterns from the current conversation, project notes, local memories, logs, or session artifacts that are available and safe to inspect;",
24168
+ "- prefer evidence from repeated recent behavior over speculation;",
24169
+ "- recommend the smallest useful improvement: prompt/config rule, skill, command, custom agent, MCP/tool permission change, project playbook, or skip;",
24170
+ "- treat creating nothing as a valid result when evidence is weak;",
24171
+ "- ask before changing prompts, skills, commands, agents, MCP access, or config unless the user explicitly requested the exact edit;",
24172
+ "- return a compact report with findings, recommended changes, skipped candidates, and items needing more evidence.",
24173
+ "",
24174
+ ...focusBlock
24175
+ ].join(`
24176
+ `);
24177
+ }
24178
+ function createReflectCommandHook() {
24179
+ let shouldHandleCommand = false;
24180
+ return {
24181
+ registerCommand: (opencodeConfig) => {
24182
+ const commandConfig = opencodeConfig.command;
24183
+ if (commandConfig?.[COMMAND_NAME2]) {
24184
+ shouldHandleCommand = false;
24185
+ return;
24186
+ }
24187
+ if (!opencodeConfig.command)
24188
+ opencodeConfig.command = {};
24189
+ opencodeConfig.command[COMMAND_NAME2] = {
24190
+ template: "Review repeated work and suggest workflow improvements",
24191
+ description: "Use reflect to learn from repeated workflows and suggest reusable improvements"
24192
+ };
24193
+ shouldHandleCommand = true;
24194
+ },
24195
+ handleCommandExecuteBefore: async (input, output) => {
24196
+ if (input.command !== COMMAND_NAME2 || !shouldHandleCommand)
24197
+ return;
24198
+ output.parts.length = 0;
24199
+ output.parts.push({
24200
+ type: "text",
24201
+ text: activationPrompt2(input.arguments.trim())
24202
+ });
24203
+ }
24204
+ };
24205
+ }
24060
24206
  // src/hooks/task-session-manager/index.ts
24061
- import path9 from "node:path";
24207
+ import path11 from "node:path";
24062
24208
  var AGENT_NAME_SET = new Set([
24063
24209
  "orchestrator",
24064
24210
  "oracle",
@@ -24104,9 +24250,6 @@ function createOccurrenceId(part, message, partIndex) {
24104
24250
  function isAgentName(value) {
24105
24251
  return typeof value === "string" && AGENT_NAME_SET.has(value);
24106
24252
  }
24107
- function isObjectRecord(value) {
24108
- return typeof value === "object" && value !== null;
24109
- }
24110
24253
  function extractPath(output) {
24111
24254
  return /<path>([^<]+)<\/path>/.exec(output)?.[1];
24112
24255
  }
@@ -24115,8 +24258,8 @@ function extractTaskSummary(output) {
24115
24258
  return summary?.trim() || undefined;
24116
24259
  }
24117
24260
  function normalizePath(root, file) {
24118
- const relative = path9.relative(root, file);
24119
- if (!relative || relative.startsWith("..") || path9.isAbsolute(relative)) {
24261
+ const relative = path11.relative(root, file);
24262
+ if (!relative || relative.startsWith("..") || path11.isAbsolute(relative)) {
24120
24263
  return file;
24121
24264
  }
24122
24265
  return relative;
@@ -24384,7 +24527,7 @@ function createTaskSessionManagerHook(_ctx, options) {
24384
24527
  if (!input.sessionID || !options.shouldManageSession(input.sessionID)) {
24385
24528
  return;
24386
24529
  }
24387
- if (!isObjectRecord(output.args))
24530
+ if (!isRecord(output.args))
24388
24531
  return;
24389
24532
  const args = output.args;
24390
24533
  if (!isAgentName(args.subagent_type)) {
@@ -24658,7 +24801,7 @@ function createTaskSessionManagerHook(_ctx, options) {
24658
24801
  result: status.result
24659
24802
  });
24660
24803
  output.output = formatCancelledTaskStatusOutput(status.taskID, existing?.resultSummary);
24661
- if (isObjectRecord(output) && isObjectRecord(output.metadata)) {
24804
+ if (isRecord(output) && isRecord(output.metadata)) {
24662
24805
  output.metadata.state = "cancelled";
24663
24806
  }
24664
24807
  }
@@ -24682,7 +24825,7 @@ function formatCancelledTaskStatusOutput(taskID, summary = "cancelled") {
24682
24825
  `);
24683
24826
  }
24684
24827
  // src/interview/manager.ts
24685
- import path13 from "node:path";
24828
+ import path15 from "node:path";
24686
24829
 
24687
24830
  // src/interview/dashboard.ts
24688
24831
  import crypto from "node:crypto";
@@ -24692,27 +24835,27 @@ import {
24692
24835
  createServer
24693
24836
  } from "node:http";
24694
24837
  import os4 from "node:os";
24695
- import path11 from "node:path";
24838
+ import path13 from "node:path";
24696
24839
  import { URL as URL2 } from "node:url";
24697
24840
 
24698
24841
  // src/interview/document.ts
24699
24842
  import * as fsSync from "node:fs";
24700
24843
  import * as fs6 from "node:fs/promises";
24701
- import * as path10 from "node:path";
24844
+ import * as path12 from "node:path";
24702
24845
  var DEFAULT_OUTPUT_FOLDER = "interview";
24703
24846
  function normalizeOutputFolder(outputFolder) {
24704
24847
  const normalized = outputFolder.trim().replace(/^\/+|\/+$/g, "");
24705
24848
  return normalized || DEFAULT_OUTPUT_FOLDER;
24706
24849
  }
24707
24850
  function createInterviewDirectoryPath(directory, outputFolder) {
24708
- return path10.join(directory, normalizeOutputFolder(outputFolder));
24851
+ return path12.join(directory, normalizeOutputFolder(outputFolder));
24709
24852
  }
24710
24853
  function createInterviewFilePath(directory, outputFolder, idea) {
24711
24854
  const fileName = `${slugify(idea) || "interview"}.md`;
24712
- return path10.join(createInterviewDirectoryPath(directory, outputFolder), fileName);
24855
+ return path12.join(createInterviewDirectoryPath(directory, outputFolder), fileName);
24713
24856
  }
24714
24857
  function relativeInterviewPath(directory, filePath) {
24715
- return path10.relative(directory, filePath) || path10.basename(filePath);
24858
+ return path12.relative(directory, filePath) || path12.basename(filePath);
24716
24859
  }
24717
24860
  function resolveExistingInterviewPath(directory, outputFolder, value) {
24718
24861
  const trimmed = value.trim();
@@ -24721,22 +24864,22 @@ function resolveExistingInterviewPath(directory, outputFolder, value) {
24721
24864
  }
24722
24865
  const outputDir = createInterviewDirectoryPath(directory, outputFolder);
24723
24866
  const candidates = new Set;
24724
- const resolvedRoot = path10.resolve(directory);
24725
- if (path10.isAbsolute(trimmed)) {
24867
+ const resolvedRoot = path12.resolve(directory);
24868
+ if (path12.isAbsolute(trimmed)) {
24726
24869
  candidates.add(trimmed);
24727
24870
  } else {
24728
- candidates.add(path10.resolve(directory, trimmed));
24729
- candidates.add(path10.join(outputDir, trimmed));
24871
+ candidates.add(path12.resolve(directory, trimmed));
24872
+ candidates.add(path12.join(outputDir, trimmed));
24730
24873
  if (!trimmed.endsWith(".md")) {
24731
- candidates.add(path10.join(outputDir, `${trimmed}.md`));
24874
+ candidates.add(path12.join(outputDir, `${trimmed}.md`));
24732
24875
  }
24733
24876
  }
24734
24877
  for (const candidate of candidates) {
24735
- if (path10.extname(candidate) !== ".md") {
24878
+ if (path12.extname(candidate) !== ".md") {
24736
24879
  continue;
24737
24880
  }
24738
- const resolved = path10.resolve(candidate);
24739
- if (!resolved.startsWith(resolvedRoot + path10.sep) && resolved !== resolvedRoot) {
24881
+ const resolved = path12.resolve(candidate);
24882
+ if (!resolved.startsWith(resolvedRoot + path12.sep) && resolved !== resolvedRoot) {
24740
24883
  continue;
24741
24884
  }
24742
24885
  if (fsSync.existsSync(candidate)) {
@@ -24816,7 +24959,7 @@ function parseFrontmatter(content) {
24816
24959
  return result;
24817
24960
  }
24818
24961
  async function ensureInterviewFile(record) {
24819
- await fs6.mkdir(path10.dirname(record.markdownPath), { recursive: true });
24962
+ await fs6.mkdir(path12.dirname(record.markdownPath), { recursive: true });
24820
24963
  try {
24821
24964
  await fs6.access(record.markdownPath);
24822
24965
  } catch {
@@ -26486,12 +26629,12 @@ function renderInterviewPage(interviewId, resumeSlug) {
26486
26629
 
26487
26630
  // src/interview/dashboard.ts
26488
26631
  function getAuthFilePath(port) {
26489
- const dataHome = process.env.XDG_DATA_HOME || path11.join(os4.homedir(), ".local", "share");
26490
- return path11.join(dataHome, "opencode", `.dashboard-${port}.json`);
26632
+ const dataHome = process.env.XDG_DATA_HOME || path13.join(os4.homedir(), ".local", "share");
26633
+ return path13.join(dataHome, "opencode", `.dashboard-${port}.json`);
26491
26634
  }
26492
26635
  function writeAuthFile(port, token) {
26493
26636
  const filePath = getAuthFilePath(port);
26494
- const dir = path11.dirname(filePath);
26637
+ const dir = path13.dirname(filePath);
26495
26638
  try {
26496
26639
  fsSync2.mkdirSync(dir, { recursive: true });
26497
26640
  } catch {}
@@ -26628,7 +26771,7 @@ function createDashboardServer(config) {
26628
26771
  const directories = getKnownDirectories();
26629
26772
  const items = [];
26630
26773
  for (const dir of directories) {
26631
- const interviewDir = path11.join(dir, config.outputFolder);
26774
+ const interviewDir = path13.join(dir, config.outputFolder);
26632
26775
  let entries;
26633
26776
  try {
26634
26777
  entries = await fs7.readdir(interviewDir);
@@ -26640,7 +26783,7 @@ function createDashboardServer(config) {
26640
26783
  continue;
26641
26784
  let content;
26642
26785
  try {
26643
- content = await fs7.readFile(path11.join(interviewDir, entry), "utf8");
26786
+ content = await fs7.readFile(path13.join(interviewDir, entry), "utf8");
26644
26787
  } catch {
26645
26788
  continue;
26646
26789
  }
@@ -26666,7 +26809,7 @@ function createDashboardServer(config) {
26666
26809
  const directories = getKnownDirectories();
26667
26810
  let rebuilt = 0;
26668
26811
  for (const dir of directories) {
26669
- const interviewDir = path11.join(dir, config.outputFolder);
26812
+ const interviewDir = path13.join(dir, config.outputFolder);
26670
26813
  let entries;
26671
26814
  try {
26672
26815
  entries = await fs7.readdir(interviewDir);
@@ -26678,7 +26821,7 @@ function createDashboardServer(config) {
26678
26821
  continue;
26679
26822
  let content;
26680
26823
  try {
26681
- content = await fs7.readFile(path11.join(interviewDir, entry), "utf8");
26824
+ content = await fs7.readFile(path13.join(interviewDir, entry), "utf8");
26682
26825
  } catch {
26683
26826
  continue;
26684
26827
  }
@@ -26704,7 +26847,7 @@ function createDashboardServer(config) {
26704
26847
  questions: [],
26705
26848
  pendingAnswers: null,
26706
26849
  lastUpdatedAt: fm.updatedAt ? new Date(fm.updatedAt).getTime() : Date.now(),
26707
- filePath: path11.join(interviewDir, entry),
26850
+ filePath: path13.join(interviewDir, entry),
26708
26851
  nudgeAction: null
26709
26852
  });
26710
26853
  if (!sessions.has(fm.sessionID)) {
@@ -26968,7 +27111,7 @@ function createDashboardServer(config) {
26968
27111
  const dirs = getKnownDirectories();
26969
27112
  for (const dir of dirs) {
26970
27113
  const slug = extractResumeSlug(interviewId);
26971
- const candidate = path11.join(dir, config.outputFolder, `${slug}.md`);
27114
+ const candidate = path13.join(dir, config.outputFolder, `${slug}.md`);
26972
27115
  try {
26973
27116
  document = await fs7.readFile(candidate, "utf8");
26974
27117
  markdownPath = candidate;
@@ -27496,7 +27639,7 @@ function createInterviewServer(deps) {
27496
27639
  // src/interview/service.ts
27497
27640
  import { spawn as spawn2 } from "node:child_process";
27498
27641
  import * as fs8 from "node:fs/promises";
27499
- import * as path12 from "node:path";
27642
+ import * as path14 from "node:path";
27500
27643
 
27501
27644
  // src/interview/types.ts
27502
27645
  import { z as z3 } from "zod";
@@ -27668,7 +27811,7 @@ function buildAnswerPrompt(answers, questions, maxQuestions) {
27668
27811
  }
27669
27812
 
27670
27813
  // src/interview/service.ts
27671
- var COMMAND_NAME2 = "interview";
27814
+ var COMMAND_NAME3 = "interview";
27672
27815
  var DEFAULT_MAX_QUESTIONS = 2;
27673
27816
  function isTruthyEnvFlag(value) {
27674
27817
  if (!value) {
@@ -27676,12 +27819,12 @@ function isTruthyEnvFlag(value) {
27676
27819
  }
27677
27820
  return value !== "0" && value.toLowerCase() !== "false";
27678
27821
  }
27679
- function isAutomatedRuntime(env2) {
27680
- return env2.NODE_ENV === "test" || isTruthyEnvFlag(env2.CI) || isTruthyEnvFlag(env2.BUN_TEST) || isTruthyEnvFlag(env2.VITEST) || env2.JEST_WORKER_ID !== undefined;
27822
+ function isAutomatedRuntime(env) {
27823
+ return env.NODE_ENV === "test" || isTruthyEnvFlag(env.CI) || isTruthyEnvFlag(env.BUN_TEST) || isTruthyEnvFlag(env.VITEST) || env.JEST_WORKER_ID !== undefined;
27681
27824
  }
27682
- function shouldAutoOpenBrowser(config, env2) {
27825
+ function shouldAutoOpenBrowser(config, env) {
27683
27826
  const requested = config?.autoOpenBrowser ?? true;
27684
- return requested && !isAutomatedRuntime(env2);
27827
+ return requested && !isAutomatedRuntime(env);
27685
27828
  }
27686
27829
  function openBrowser(url) {
27687
27830
  const platform2 = process.platform;
@@ -27763,12 +27906,12 @@ function createInterviewService(ctx, config, deps) {
27763
27906
  if (!newSlug) {
27764
27907
  return;
27765
27908
  }
27766
- const currentFileName = path12.basename(interview.markdownPath, ".md");
27909
+ const currentFileName = path14.basename(interview.markdownPath, ".md");
27767
27910
  if (currentFileName === newSlug) {
27768
27911
  return;
27769
27912
  }
27770
- const dir = path12.dirname(interview.markdownPath);
27771
- const newPath = path12.join(dir, `${newSlug}.md`);
27913
+ const dir = path14.dirname(interview.markdownPath);
27914
+ const newPath = path14.join(dir, `${newSlug}.md`);
27772
27915
  try {
27773
27916
  await fs8.access(newPath);
27774
27917
  return;
@@ -27844,9 +27987,9 @@ function createInterviewService(ctx, config, deps) {
27844
27987
  const messages = await loadMessages(sessionID);
27845
27988
  const title = extractTitle(document);
27846
27989
  const record = {
27847
- id: `${Date.now()}-${++idCounter}-${slugify(path12.basename(markdownPath, ".md")) || "interview"}`,
27990
+ id: `${Date.now()}-${++idCounter}-${slugify(path14.basename(markdownPath, ".md")) || "interview"}`,
27848
27991
  sessionID,
27849
- idea: title || path12.basename(markdownPath, ".md"),
27992
+ idea: title || path14.basename(markdownPath, ".md"),
27850
27993
  markdownPath,
27851
27994
  createdAt: nowIso(),
27852
27995
  status: "active",
@@ -27915,11 +28058,11 @@ function createInterviewService(ctx, config, deps) {
27915
28058
  }
27916
28059
  function registerCommand(opencodeConfig) {
27917
28060
  const configCommand = opencodeConfig.command;
27918
- if (!configCommand?.[COMMAND_NAME2]) {
28061
+ if (!configCommand?.[COMMAND_NAME3]) {
27919
28062
  if (!opencodeConfig.command) {
27920
28063
  opencodeConfig.command = {};
27921
28064
  }
27922
- opencodeConfig.command[COMMAND_NAME2] = {
28065
+ opencodeConfig.command[COMMAND_NAME3] = {
27923
28066
  template: "Start an interview and write a live markdown spec",
27924
28067
  description: "Open a localhost interview UI linked to the current OpenCode session"
27925
28068
  };
@@ -27993,7 +28136,7 @@ function createInterviewService(ctx, config, deps) {
27993
28136
  }
27994
28137
  }
27995
28138
  async function handleCommandExecuteBefore(input, output) {
27996
- if (input.command !== COMMAND_NAME2) {
28139
+ if (input.command !== COMMAND_NAME3) {
27997
28140
  return;
27998
28141
  }
27999
28142
  const idea = input.arguments.trim();
@@ -28073,7 +28216,7 @@ function createInterviewService(ctx, config, deps) {
28073
28216
  return fileCache.items;
28074
28217
  }
28075
28218
  const outputDir = createInterviewDirectoryPath(ctx.directory, outputFolder);
28076
- const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) => path12.resolve(i.markdownPath)));
28219
+ const activePaths = new Set([...interviewsById.values()].filter((i) => i.status === "active").map((i) => path14.resolve(i.markdownPath)));
28077
28220
  let entries;
28078
28221
  try {
28079
28222
  entries = await fs8.readdir(outputDir);
@@ -28084,8 +28227,8 @@ function createInterviewService(ctx, config, deps) {
28084
28227
  for (const entry of entries) {
28085
28228
  if (!entry.endsWith(".md"))
28086
28229
  continue;
28087
- const fullPath = path12.join(outputDir, entry);
28088
- if (activePaths.has(path12.resolve(fullPath)))
28230
+ const fullPath = path14.join(outputDir, entry);
28231
+ if (activePaths.has(path14.resolve(fullPath)))
28089
28232
  continue;
28090
28233
  let content;
28091
28234
  try {
@@ -28184,7 +28327,7 @@ function createInterviewManager(ctx, config) {
28184
28327
  const outputFolder = interviewConfig?.outputFolder ?? "interview";
28185
28328
  if (!dashboardEnabled) {
28186
28329
  const service2 = createInterviewService(ctx, interviewConfig);
28187
- const resolvedOutputPath = path13.join(ctx.directory, outputFolder);
28330
+ const resolvedOutputPath = path15.join(ctx.directory, outputFolder);
28188
28331
  const server = createInterviewServer({
28189
28332
  getState: async (interviewId) => service2.getInterviewState(interviewId),
28190
28333
  listInterviewFiles: async () => service2.listInterviewFiles(),
@@ -28289,7 +28432,7 @@ function createInterviewManager(ctx, config) {
28289
28432
  listInterviews: () => service.listInterviews(),
28290
28433
  submitAnswers: async (interviewId, answers) => service.submitAnswers(interviewId, answers),
28291
28434
  handleNudgeAction: async (interviewId, action) => service.handleNudgeAction(interviewId, action),
28292
- outputFolder: path13.join(ctx.directory, outputFolder),
28435
+ outputFolder: path15.join(ctx.directory, outputFolder),
28293
28436
  port: 0
28294
28437
  });
28295
28438
  service.setBaseUrlResolver(() => perSessionServer.ensureStarted());
@@ -28774,23 +28917,23 @@ class TmuxMultiplexer {
28774
28917
  return null;
28775
28918
  }
28776
28919
  const stdout = await proc.stdout();
28777
- const path14 = stdout.trim().split(`
28920
+ const path16 = stdout.trim().split(`
28778
28921
  `)[0];
28779
- if (!path14) {
28922
+ if (!path16) {
28780
28923
  log("[tmux] findBinary: no path in output");
28781
28924
  return null;
28782
28925
  }
28783
- const verifyProc = crossSpawn([path14, "-V"], {
28926
+ const verifyProc = crossSpawn([path16, "-V"], {
28784
28927
  stdout: "pipe",
28785
28928
  stderr: "pipe"
28786
28929
  });
28787
28930
  const verifyExit = await verifyProc.exited;
28788
28931
  if (verifyExit !== 0) {
28789
- log("[tmux] findBinary: tmux -V failed", { path: path14, verifyExit });
28932
+ log("[tmux] findBinary: tmux -V failed", { path: path16, verifyExit });
28790
28933
  return null;
28791
28934
  }
28792
- log("[tmux] findBinary: found", { path: path14 });
28793
- return path14;
28935
+ log("[tmux] findBinary: found", { path: path16 });
28936
+ return path16;
28794
28937
  } catch (err) {
28795
28938
  log("[tmux] findBinary: exception", { error: String(err) });
28796
28939
  return null;
@@ -29745,18 +29888,18 @@ async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
29745
29888
  import { tool } from "@opencode-ai/plugin";
29746
29889
 
29747
29890
  // src/tools/ast-grep/cli.ts
29748
- import { existsSync as existsSync9 } from "node:fs";
29891
+ import { existsSync as existsSync10 } from "node:fs";
29749
29892
 
29750
29893
  // src/tools/ast-grep/constants.ts
29751
- import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
29894
+ import { existsSync as existsSync9, statSync as statSync4 } from "node:fs";
29752
29895
  import { createRequire as createRequire3 } from "node:module";
29753
- import { dirname as dirname7, join as join12 } from "node:path";
29896
+ import { dirname as dirname8, join as join14 } from "node:path";
29754
29897
 
29755
29898
  // src/tools/ast-grep/downloader.ts
29756
- import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "node:fs";
29899
+ import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4 } from "node:fs";
29757
29900
  import { createRequire as createRequire2 } from "node:module";
29758
29901
  import { homedir as homedir5 } from "node:os";
29759
- import { join as join11 } from "node:path";
29902
+ import { join as join13 } from "node:path";
29760
29903
  var REPO = "ast-grep/ast-grep";
29761
29904
  var DEFAULT_VERSION = "0.40.0";
29762
29905
  function getAstGrepVersion() {
@@ -29780,19 +29923,19 @@ var PLATFORM_MAP = {
29780
29923
  function getCacheDir2() {
29781
29924
  if (process.platform === "win32") {
29782
29925
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
29783
- const base2 = localAppData || join11(homedir5(), "AppData", "Local");
29784
- return join11(base2, "oh-my-opencode-slim", "bin");
29926
+ const base2 = localAppData || join13(homedir5(), "AppData", "Local");
29927
+ return join13(base2, "oh-my-opencode-slim", "bin");
29785
29928
  }
29786
29929
  const xdgCache = process.env.XDG_CACHE_HOME;
29787
- const base = xdgCache || join11(homedir5(), ".cache");
29788
- return join11(base, "oh-my-opencode-slim", "bin");
29930
+ const base = xdgCache || join13(homedir5(), ".cache");
29931
+ return join13(base, "oh-my-opencode-slim", "bin");
29789
29932
  }
29790
29933
  function getBinaryName() {
29791
29934
  return process.platform === "win32" ? "sg.exe" : "sg";
29792
29935
  }
29793
29936
  function getCachedBinaryPath() {
29794
- const binaryPath2 = join11(getCacheDir2(), getBinaryName());
29795
- return existsSync7(binaryPath2) ? binaryPath2 : null;
29937
+ const binaryPath2 = join13(getCacheDir2(), getBinaryName());
29938
+ return existsSync8(binaryPath2) ? binaryPath2 : null;
29796
29939
  }
29797
29940
  async function downloadAstGrep(version = DEFAULT_VERSION) {
29798
29941
  const platformKey = `${process.platform}-${process.arch}`;
@@ -29803,8 +29946,8 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
29803
29946
  }
29804
29947
  const cacheDir = getCacheDir2();
29805
29948
  const binaryName = getBinaryName();
29806
- const binaryPath2 = join11(cacheDir, binaryName);
29807
- if (existsSync7(binaryPath2)) {
29949
+ const binaryPath2 = join13(cacheDir, binaryName);
29950
+ if (existsSync8(binaryPath2)) {
29808
29951
  return binaryPath2;
29809
29952
  }
29810
29953
  const { arch, os: os5 } = platformInfo;
@@ -29812,21 +29955,21 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
29812
29955
  const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`;
29813
29956
  console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
29814
29957
  try {
29815
- if (!existsSync7(cacheDir)) {
29816
- mkdirSync5(cacheDir, { recursive: true });
29958
+ if (!existsSync8(cacheDir)) {
29959
+ mkdirSync6(cacheDir, { recursive: true });
29817
29960
  }
29818
29961
  const response = await fetch(downloadUrl, { redirect: "follow" });
29819
29962
  if (!response.ok) {
29820
29963
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
29821
29964
  }
29822
- const archivePath = join11(cacheDir, assetName);
29965
+ const archivePath = join13(cacheDir, assetName);
29823
29966
  const arrayBuffer = await response.arrayBuffer();
29824
29967
  await crossWrite(archivePath, arrayBuffer);
29825
29968
  await extractZip(archivePath, cacheDir);
29826
- if (existsSync7(archivePath)) {
29969
+ if (existsSync8(archivePath)) {
29827
29970
  unlinkSync4(archivePath);
29828
29971
  }
29829
- if (process.platform !== "win32" && existsSync7(binaryPath2)) {
29972
+ if (process.platform !== "win32" && existsSync8(binaryPath2)) {
29830
29973
  chmodSync(binaryPath2, 493);
29831
29974
  }
29832
29975
  console.log(`[oh-my-opencode-slim] ast-grep binary ready.`);
@@ -29907,9 +30050,9 @@ function findSgCliPathSync() {
29907
30050
  try {
29908
30051
  const require2 = createRequire3(import.meta.url);
29909
30052
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
29910
- const cliDir = dirname7(cliPkgPath);
29911
- const sgPath = join12(cliDir, binaryName);
29912
- if (existsSync8(sgPath) && isValidBinary(sgPath)) {
30053
+ const cliDir = dirname8(cliPkgPath);
30054
+ const sgPath = join14(cliDir, binaryName);
30055
+ if (existsSync9(sgPath) && isValidBinary(sgPath)) {
29913
30056
  return sgPath;
29914
30057
  }
29915
30058
  } catch {}
@@ -29918,19 +30061,19 @@ function findSgCliPathSync() {
29918
30061
  try {
29919
30062
  const require2 = createRequire3(import.meta.url);
29920
30063
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
29921
- const pkgDir = dirname7(pkgPath);
30064
+ const pkgDir = dirname8(pkgPath);
29922
30065
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
29923
- const binaryPath2 = join12(pkgDir, astGrepName);
29924
- if (existsSync8(binaryPath2) && isValidBinary(binaryPath2)) {
30066
+ const binaryPath2 = join14(pkgDir, astGrepName);
30067
+ if (existsSync9(binaryPath2) && isValidBinary(binaryPath2)) {
29925
30068
  return binaryPath2;
29926
30069
  }
29927
30070
  } catch {}
29928
30071
  }
29929
30072
  if (process.platform === "darwin") {
29930
30073
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
29931
- for (const path14 of homebrewPaths) {
29932
- if (existsSync8(path14) && isValidBinary(path14)) {
29933
- return path14;
30074
+ for (const path16 of homebrewPaths) {
30075
+ if (existsSync9(path16) && isValidBinary(path16)) {
30076
+ return path16;
29934
30077
  }
29935
30078
  }
29936
30079
  }
@@ -29947,10 +30090,10 @@ function getSgCliPath() {
29947
30090
  }
29948
30091
  return "sg";
29949
30092
  }
29950
- function setSgCliPath(path14) {
29951
- resolvedCliPath = path14;
30093
+ function setSgCliPath(path16) {
30094
+ resolvedCliPath = path16;
29952
30095
  }
29953
- var DEFAULT_TIMEOUT_MS2 = 300000;
30096
+ var DEFAULT_TIMEOUT_MS = 300000;
29954
30097
  var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
29955
30098
  var DEFAULT_MAX_MATCHES = 500;
29956
30099
 
@@ -29958,7 +30101,7 @@ var DEFAULT_MAX_MATCHES = 500;
29958
30101
  var initPromise = null;
29959
30102
  async function getAstGrepPath() {
29960
30103
  const currentPath = getSgCliPath();
29961
- if (currentPath !== "sg" && existsSync9(currentPath)) {
30104
+ if (currentPath !== "sg" && existsSync10(currentPath)) {
29962
30105
  return currentPath;
29963
30106
  }
29964
30107
  if (initPromise) {
@@ -29966,7 +30109,7 @@ async function getAstGrepPath() {
29966
30109
  }
29967
30110
  initPromise = (async () => {
29968
30111
  const syncPath = findSgCliPathSync();
29969
- if (syncPath && existsSync9(syncPath)) {
30112
+ if (syncPath && existsSync10(syncPath)) {
29970
30113
  setSgCliPath(syncPath);
29971
30114
  return syncPath;
29972
30115
  }
@@ -30005,13 +30148,13 @@ async function runSg(options) {
30005
30148
  const paths2 = options.paths && options.paths.length > 0 ? options.paths : ["."];
30006
30149
  args.push(...paths2);
30007
30150
  let cliPath = getSgCliPath();
30008
- if (!existsSync9(cliPath) && cliPath !== "sg") {
30151
+ if (!existsSync10(cliPath) && cliPath !== "sg") {
30009
30152
  const downloadedPath = await getAstGrepPath();
30010
30153
  if (downloadedPath) {
30011
30154
  cliPath = downloadedPath;
30012
30155
  }
30013
30156
  }
30014
- const timeout = DEFAULT_TIMEOUT_MS2;
30157
+ const timeout = DEFAULT_TIMEOUT_MS;
30015
30158
  const proc = crossSpawn([cliPath, ...args], {
30016
30159
  stdout: "pipe",
30017
30160
  stderr: "pipe"
@@ -30614,7 +30757,7 @@ async function getSessionStatus(client, taskID) {
30614
30757
  try {
30615
30758
  const result = await client.session.status();
30616
30759
  const data = result.data;
30617
- if (!isObjectRecord2(data)) {
30760
+ if (!isRecord(data)) {
30618
30761
  return { status: undefined, source: "invalid-data", keys: [] };
30619
30762
  }
30620
30763
  const keys = Object.keys(data).slice(0, 20);
@@ -30622,14 +30765,14 @@ async function getSessionStatus(client, taskID) {
30622
30765
  if (item === undefined) {
30623
30766
  return { status: "idle", source: "missing-from-map", keys };
30624
30767
  }
30625
- if (isObjectRecord2(item) && typeof item.type === "string") {
30768
+ if (isRecord(item) && typeof item.type === "string") {
30626
30769
  return { status: item.type, source: "task-map-entry", keys };
30627
30770
  }
30628
30771
  if (typeof data.type === "string") {
30629
30772
  return { status: data.type, source: "legacy-data-type", keys };
30630
30773
  }
30631
30774
  const nested = data.status;
30632
- if (isObjectRecord2(nested) && typeof nested.type === "string") {
30775
+ if (isRecord(nested) && typeof nested.type === "string") {
30633
30776
  return { status: nested.type, source: "legacy-data-status", keys };
30634
30777
  }
30635
30778
  return { status: undefined, source: "unknown-shape", keys };
@@ -30644,9 +30787,6 @@ async function getSessionStatus(client, taskID) {
30644
30787
  function delay(ms) {
30645
30788
  return new Promise((resolve3) => setTimeout(resolve3, ms));
30646
30789
  }
30647
- function isObjectRecord2(value) {
30648
- return typeof value === "object" && value !== null;
30649
- }
30650
30790
  function isSessionID(value) {
30651
30791
  return /^ses_[\w-]+$/.test(value);
30652
30792
  }
@@ -30661,7 +30801,7 @@ async function getSessionParentID(client, taskID) {
30661
30801
  try {
30662
30802
  const response = await session2.get({ path: { id: taskID } });
30663
30803
  const data = response.data;
30664
- if (!isObjectRecord2(data))
30804
+ if (!isRecord(data))
30665
30805
  return;
30666
30806
  const parentID = data.parentID;
30667
30807
  return typeof parentID === "string" ? parentID : undefined;
@@ -30756,14 +30896,14 @@ import * as fs10 from "node:fs";
30756
30896
  // src/tui-state.ts
30757
30897
  import * as fs9 from "node:fs";
30758
30898
  import * as os5 from "node:os";
30759
- import * as path14 from "node:path";
30899
+ import * as path16 from "node:path";
30760
30900
  var STATE_DIR = "oh-my-opencode-slim";
30761
30901
  var STATE_FILE = "tui-state.json";
30762
30902
  function dataDir() {
30763
- return process.env.XDG_DATA_HOME ?? path14.join(os5.homedir(), ".local", "share");
30903
+ return process.env.XDG_DATA_HOME ?? path16.join(os5.homedir(), ".local", "share");
30764
30904
  }
30765
30905
  function getTuiStatePath() {
30766
- return path14.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
30906
+ return path16.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
30767
30907
  }
30768
30908
  function emptySnapshot() {
30769
30909
  return {
@@ -30799,7 +30939,7 @@ async function readTuiSnapshotAsync() {
30799
30939
  function writeTuiSnapshot(snapshot) {
30800
30940
  try {
30801
30941
  const filePath = getTuiStatePath();
30802
- fs9.mkdirSync(path14.dirname(filePath), { recursive: true });
30942
+ fs9.mkdirSync(path16.dirname(filePath), { recursive: true });
30803
30943
  fs9.writeFileSync(filePath, `${JSON.stringify(snapshot)}
30804
30944
  `);
30805
30945
  } catch {}
@@ -30822,11 +30962,11 @@ function recordTuiAgentModel(input) {
30822
30962
  }
30823
30963
 
30824
30964
  // src/tools/preset-manager.ts
30825
- var COMMAND_NAME3 = "preset";
30965
+ var COMMAND_NAME4 = "preset";
30826
30966
  function createPresetManager(ctx, config) {
30827
30967
  let activePreset = getActiveRuntimePreset() ?? config.preset ?? null;
30828
30968
  async function handleCommandExecuteBefore(input, output) {
30829
- if (input.command !== COMMAND_NAME3) {
30969
+ if (input.command !== COMMAND_NAME4) {
30830
30970
  return;
30831
30971
  }
30832
30972
  output.parts.length = 0;
@@ -30845,11 +30985,11 @@ function createPresetManager(ctx, config) {
30845
30985
  }
30846
30986
  function registerCommand(opencodeConfig) {
30847
30987
  const configCommand = opencodeConfig.command;
30848
- if (!configCommand?.[COMMAND_NAME3]) {
30988
+ if (!configCommand?.[COMMAND_NAME4]) {
30849
30989
  if (!opencodeConfig.command) {
30850
30990
  opencodeConfig.command = {};
30851
30991
  }
30852
- opencodeConfig.command[COMMAND_NAME3] = {
30992
+ opencodeConfig.command[COMMAND_NAME4] = {
30853
30993
  template: "List available presets and switch between them",
30854
30994
  description: "Switch agent presets at runtime (e.g., /preset cheap, /preset powerful)"
30855
30995
  };
@@ -30999,14 +31139,14 @@ var BINARY_PREFIXES = [
30999
31139
  var WEBFETCH_DESCRIPTION = "Fetch a URL with better extraction for static/docs pages. Supports llms.txt probing, content-focused HTML extraction, metadata, redirects, and an optional prompt processed by a cheap secondary model.";
31000
31140
  // src/tools/smartfetch/tool.ts
31001
31141
  import os6 from "node:os";
31002
- import path18 from "node:path";
31142
+ import path20 from "node:path";
31003
31143
  import {
31004
31144
  tool as tool4
31005
31145
  } from "@opencode-ai/plugin";
31006
31146
 
31007
31147
  // src/tools/smartfetch/binary.ts
31008
31148
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
31009
- import path15 from "node:path";
31149
+ import path17 from "node:path";
31010
31150
  function extensionForMime(contentType) {
31011
31151
  const mime = contentType.split(";")[0]?.trim().toLowerCase();
31012
31152
  const map = {
@@ -31027,10 +31167,10 @@ function buildBinaryResultMessage(fetchResult, savedPath) {
31027
31167
  async function saveBinary(binaryDir, data, contentType, filename) {
31028
31168
  await mkdir2(binaryDir, { recursive: true });
31029
31169
  const initialName = filename || `webfetch-${Date.now()}.${extensionForMime(contentType)}`;
31030
- const parsed = path15.parse(initialName);
31170
+ const parsed = path17.parse(initialName);
31031
31171
  for (let attempt = 0;attempt < 1000; attempt++) {
31032
31172
  const candidateName = attempt === 0 ? initialName : `${parsed.name}-${attempt}${parsed.ext || `.${extensionForMime(contentType)}`}`;
31033
- const file = path15.join(binaryDir, candidateName);
31173
+ const file = path17.join(binaryDir, candidateName);
31034
31174
  try {
31035
31175
  await writeFile2(file, data, { flag: "wx" });
31036
31176
  return file;
@@ -31684,7 +31824,7 @@ var M = class u2 {
31684
31824
  };
31685
31825
 
31686
31826
  // src/tools/smartfetch/network.ts
31687
- import path16 from "node:path";
31827
+ import path18 from "node:path";
31688
31828
 
31689
31829
  // src/tools/smartfetch/utils.ts
31690
31830
  var import_readability = __toESM(require_readability(), 1);
@@ -32409,7 +32549,7 @@ function inferFilenameFromUrl(url) {
32409
32549
  function truncateFilename(name, maxLength = 180) {
32410
32550
  if (name.length <= maxLength)
32411
32551
  return name;
32412
- const parsed = path16.parse(name);
32552
+ const parsed = path18.parse(name);
32413
32553
  const ext = parsed.ext || "";
32414
32554
  const baseLimit = Math.max(1, maxLength - ext.length);
32415
32555
  return `${parsed.name.slice(0, baseLimit)}${ext}`;
@@ -32579,9 +32719,9 @@ function isInvalidLlmsResult(fetchResult) {
32579
32719
  }
32580
32720
 
32581
32721
  // src/tools/smartfetch/secondary-model.ts
32582
- import { existsSync as existsSync10 } from "node:fs";
32722
+ import { existsSync as existsSync11 } from "node:fs";
32583
32723
  import { readFile as readFile4 } from "node:fs/promises";
32584
- import path17 from "node:path";
32724
+ import path19 from "node:path";
32585
32725
  function parseModelRef(value) {
32586
32726
  if (!value)
32587
32727
  return;
@@ -32607,8 +32747,8 @@ function pickAgentModelRef(value) {
32607
32747
  }
32608
32748
  function findPreferredOpenCodeConfigPath(baseDir) {
32609
32749
  for (const file of ["opencode.jsonc", "opencode.json"]) {
32610
- const fullPath = path17.join(baseDir, file);
32611
- if (existsSync10(fullPath))
32750
+ const fullPath = path19.join(baseDir, file);
32751
+ if (existsSync11(fullPath))
32612
32752
  return fullPath;
32613
32753
  }
32614
32754
  return;
@@ -32624,7 +32764,7 @@ async function readOpenCodeConfigFile(configPath) {
32624
32764
  }
32625
32765
  }
32626
32766
  async function readEffectiveOpenCodeConfig(directory) {
32627
- const projectDir = path17.join(directory, ".opencode");
32767
+ const projectDir = path19.join(directory, ".opencode");
32628
32768
  const userDirs = getConfigSearchDirs();
32629
32769
  const projectPath = findPreferredOpenCodeConfigPath(projectDir);
32630
32770
  const userPath = userDirs.map((configDir) => findPreferredOpenCodeConfigPath(configDir)).find(Boolean);
@@ -32785,7 +32925,7 @@ async function runSecondaryModelWithFallback(client, directory, models, prompt,
32785
32925
  // src/tools/smartfetch/tool.ts
32786
32926
  var z6 = tool4.schema;
32787
32927
  function createWebfetchTool(pluginCtx, options = {}) {
32788
- const binaryDir = options.binaryDir || path18.join(os6.tmpdir(), "opencode-smartfetch");
32928
+ const binaryDir = options.binaryDir || path20.join(os6.tmpdir(), "opencode-smartfetch");
32789
32929
  return tool4({
32790
32930
  description: WEBFETCH_DESCRIPTION,
32791
32931
  args: {
@@ -33378,6 +33518,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33378
33518
  let jsonErrorRecoveryHook;
33379
33519
  let foregroundFallback;
33380
33520
  let deepworkCommandHook;
33521
+ let reflectCommandHook;
33381
33522
  let taskSessionManagerHook;
33382
33523
  let backgroundJobBoard;
33383
33524
  let interviewManager;
@@ -33403,32 +33544,13 @@ var OhMyOpenCodeLite = async (ctx) => {
33403
33544
  agentDefs = createAgents(config);
33404
33545
  agents = getAgentConfigs(config);
33405
33546
  modelArrayMap = {};
33406
- for (const agentDef of agentDefs) {
33407
- if (agentDef._modelArray && agentDef._modelArray.length > 0) {
33408
- modelArrayMap[agentDef.name] = agentDef._modelArray;
33409
- }
33410
- }
33411
33547
  runtimeChains = {};
33412
33548
  for (const agentDef of agentDefs) {
33413
33549
  if (agentDef._modelArray?.length) {
33550
+ modelArrayMap[agentDef.name] = agentDef._modelArray;
33414
33551
  runtimeChains[agentDef.name] = agentDef._modelArray.map((m) => m.id);
33415
33552
  }
33416
33553
  }
33417
- const activePresetForFallback = getActiveRuntimePreset() ?? config.preset ?? null;
33418
- if (config.fallback?.enabled !== false) {
33419
- const chains = normalizeFallbackChainsForPreset(config.fallback?.chains ?? {}, activePresetForFallback);
33420
- for (const [agentName, chainModels] of Object.entries(chains)) {
33421
- const existing = runtimeChains[agentName] ?? [];
33422
- const seen = new Set(existing);
33423
- for (const m of chainModels) {
33424
- if (!seen.has(m)) {
33425
- seen.add(m);
33426
- existing.push(m);
33427
- }
33428
- }
33429
- runtimeChains[agentName] = existing;
33430
- }
33431
- }
33432
33554
  multiplexerConfig = {
33433
33555
  type: config.multiplexer?.type ?? "none",
33434
33556
  layout: config.multiplexer?.layout ?? "main-vertical",
@@ -33470,6 +33592,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33470
33592
  jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
33471
33593
  foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
33472
33594
  deepworkCommandHook = createDeepworkCommandHook();
33595
+ reflectCommandHook = createReflectCommandHook();
33473
33596
  taskSessionManagerHook = createTaskSessionManagerHook(ctx, {
33474
33597
  maxSessionsPerAgent: config.backgroundJobs?.maxSessionsPerAgent ?? 2,
33475
33598
  readContextMinLines: config.backgroundJobs?.readContextMinLines ?? 10,
@@ -33555,34 +33678,11 @@ var OhMyOpenCodeLite = async (ctx) => {
33555
33678
  }
33556
33679
  }
33557
33680
  const configAgent = opencodeConfig.agent;
33558
- const activePresetForFallback = getActiveRuntimePreset() ?? config.preset ?? null;
33559
- const fallbackChainsEnabled = config.fallback?.enabled !== false;
33560
- const fallbackChains = fallbackChainsEnabled ? normalizeFallbackChainsForPreset(config.fallback?.chains ?? {}, activePresetForFallback) : {};
33561
- const effectiveArrays = {};
33562
- for (const [agentName, models] of Object.entries(modelArrayMap)) {
33563
- effectiveArrays[agentName] = [...models];
33564
- }
33565
- for (const [agentName, chainModels] of Object.entries(fallbackChains)) {
33566
- if (!chainModels || chainModels.length === 0)
33567
- continue;
33568
- if (!effectiveArrays[agentName]) {
33569
- const entry = configAgent[agentName];
33570
- const currentModel = typeof entry?.model === "string" ? entry.model : undefined;
33571
- effectiveArrays[agentName] = currentModel ? [{ id: currentModel }] : [];
33572
- }
33573
- const seen = new Set(effectiveArrays[agentName].map((m) => m.id));
33574
- for (const chainModel of chainModels) {
33575
- if (!seen.has(chainModel)) {
33576
- seen.add(chainModel);
33577
- effectiveArrays[agentName].push({ id: chainModel });
33578
- }
33579
- }
33580
- }
33581
- if (Object.keys(effectiveArrays).length > 0) {
33582
- for (const [agentName, modelArray] of Object.entries(effectiveArrays)) {
33583
- if (modelArray.length === 0)
33681
+ if (Object.keys(modelArrayMap).length > 0) {
33682
+ for (const [agentName, models] of Object.entries(modelArrayMap)) {
33683
+ if (models.length === 0)
33584
33684
  continue;
33585
- const chosen = modelArray[0];
33685
+ const chosen = models[0];
33586
33686
  const entry = configAgent[agentName];
33587
33687
  if (entry) {
33588
33688
  entry.model = chosen.id;
@@ -33718,6 +33818,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33718
33818
  }
33719
33819
  interviewManager.registerCommand(opencodeConfig);
33720
33820
  deepworkCommandHook.registerCommand(opencodeConfig);
33821
+ reflectCommandHook.registerCommand(opencodeConfig);
33721
33822
  presetManager.registerCommand(opencodeConfig);
33722
33823
  },
33723
33824
  event: async (input) => {
@@ -33784,6 +33885,7 @@ var OhMyOpenCodeLite = async (ctx) => {
33784
33885
  await interviewManager.handleCommandExecuteBefore(input, output);
33785
33886
  await presetManager.handleCommandExecuteBefore(input, output);
33786
33887
  await deepworkCommandHook.handleCommandExecuteBefore(input, output);
33888
+ await reflectCommandHook.handleCommandExecuteBefore(input, output);
33787
33889
  },
33788
33890
  "chat.headers": chatHeadersHook["chat.headers"],
33789
33891
  "chat.message": async (input, output) => {