oh-my-opencode-slim 2.1.1 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.ja-JP.md +113 -118
  2. package/README.ko-KR.md +141 -123
  3. package/README.md +102 -145
  4. package/README.zh-CN.md +112 -118
  5. package/dist/agents/index.d.ts +0 -4
  6. package/dist/cli/index.js +57 -20
  7. package/dist/cli/providers.d.ts +11 -4
  8. package/dist/config/constants.d.ts +11 -1
  9. package/dist/config/schema.d.ts +680 -2
  10. package/dist/config/strip-orchestrator-model.d.ts +9 -0
  11. package/dist/hooks/command-hook-utils.d.ts +5 -0
  12. package/dist/hooks/foreground-fallback/index.d.ts +40 -28
  13. package/dist/hooks/image-hook.d.ts +1 -0
  14. package/dist/hooks/index.d.ts +1 -1
  15. package/dist/index.js +1992 -455
  16. package/dist/interview/document.d.ts +2 -1
  17. package/dist/interview/ui.d.ts +0 -1
  18. package/dist/multiplexer/cmux/close-policy.d.ts +20 -0
  19. package/dist/multiplexer/cmux/index.d.ts +102 -0
  20. package/dist/multiplexer/cmux/session-lifecycle.d.ts +92 -0
  21. package/dist/multiplexer/cmux/session-state.d.ts +45 -0
  22. package/dist/multiplexer/factory.d.ts +0 -9
  23. package/dist/multiplexer/herdr/index.d.ts +2 -0
  24. package/dist/multiplexer/index.d.ts +3 -1
  25. package/dist/multiplexer/session-manager.d.ts +10 -2
  26. package/dist/multiplexer/shared.d.ts +8 -1
  27. package/dist/multiplexer/types.d.ts +5 -2
  28. package/dist/multiplexer/zellij/index.d.ts +1 -1
  29. package/dist/tools/smartfetch/utils.d.ts +3 -1
  30. package/dist/tui-state.d.ts +5 -5
  31. package/dist/tui.js +87 -27
  32. package/dist/utils/escape-html.d.ts +1 -0
  33. package/dist/utils/frontmatter.d.ts +6 -0
  34. package/dist/utils/index.d.ts +1 -1
  35. package/dist/utils/logger.d.ts +0 -2
  36. package/oh-my-opencode-slim.schema.json +717 -2
  37. package/package.json +1 -1
  38. package/src/skills/clonedeps/SKILL.md +32 -29
  39. package/src/skills/codemap.md +5 -3
  40. package/src/skills/deepwork/SKILL.md +17 -6
  41. package/src/skills/verification-planning/SKILL.md +102 -0
  42. package/src/skills/worktrees/SKILL.md +14 -7
  43. package/src/skills/release-smoke-test/SKILL.md +0 -159
package/dist/tui.js CHANGED
@@ -130,7 +130,7 @@ var DEFAULT_MODELS = {
130
130
  var POLL_INTERVAL_BACKGROUND_MS = 2000;
131
131
  var MAX_POLL_TIME_MS = 5 * 60 * 1000;
132
132
  var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
133
- 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!`;
133
+ var PHASE_REMINDER_TEXT = `!IMPORTANT! Scheduler workflow: First choose the lightest workflow that fits the work. If direct execution is justified, complete it and verify proportionately. Otherwise: 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!`;
134
134
  function formatSystemReminder(text) {
135
135
  return `<system-reminder>
136
136
  ${text}
@@ -155,6 +155,13 @@ var NO_SHELL_READONLY_FILE_OPERATIONS_RULES = `**File Operations Rules**:
155
155
  var TMUX_SPAWN_DELAY_MS = 500;
156
156
  var COUNCILLOR_STAGGER_MS = 250;
157
157
  var DEFAULT_DISABLED_AGENTS = ["observer"];
158
+ var DEFAULT_MAX_SESSIONS_PER_AGENT = 2;
159
+ var DEFAULT_READ_CONTEXT_MIN_LINES = 10;
160
+ var DEFAULT_READ_CONTEXT_MAX_FILES = 8;
161
+ var DEFAULT_IMAGE_ROUTING = "auto";
162
+ function resolveImageRouting(imageRouting) {
163
+ return imageRouting ?? DEFAULT_IMAGE_ROUTING;
164
+ }
158
165
 
159
166
  // src/config/loader.ts
160
167
  import * as fs from "node:fs";
@@ -307,6 +314,33 @@ var ManualPlanSchema = z2.object({
307
314
  librarian: ManualAgentPlanSchema,
308
315
  fixer: ManualAgentPlanSchema
309
316
  }).strict();
317
+ var PermissionActionSchema = z2.enum(["ask", "allow", "deny"]);
318
+ var PermissionRuleSchema = z2.union([
319
+ PermissionActionSchema,
320
+ z2.record(z2.string(), PermissionActionSchema)
321
+ ]);
322
+ var PermissionObjectSchema = z2.object({
323
+ read: PermissionRuleSchema.optional(),
324
+ edit: PermissionRuleSchema.optional(),
325
+ glob: PermissionRuleSchema.optional(),
326
+ grep: PermissionRuleSchema.optional(),
327
+ list: PermissionRuleSchema.optional(),
328
+ bash: PermissionRuleSchema.optional(),
329
+ task: PermissionRuleSchema.optional(),
330
+ external_directory: PermissionRuleSchema.optional(),
331
+ lsp: PermissionRuleSchema.optional(),
332
+ skill: PermissionRuleSchema.optional(),
333
+ todowrite: PermissionActionSchema.optional(),
334
+ question: PermissionActionSchema.optional(),
335
+ webfetch: PermissionActionSchema.optional(),
336
+ websearch: PermissionActionSchema.optional(),
337
+ codesearch: PermissionActionSchema.optional(),
338
+ doom_loop: PermissionActionSchema.optional()
339
+ }).catchall(PermissionRuleSchema);
340
+ var PermissionConfigSchema = z2.union([
341
+ PermissionActionSchema,
342
+ PermissionObjectSchema
343
+ ]);
310
344
  var AgentOverrideConfigSchema = z2.object({
311
345
  model: z2.union([
312
346
  z2.string(),
@@ -325,13 +359,15 @@ var AgentOverrideConfigSchema = z2.object({
325
359
  prompt: z2.string().min(1).optional(),
326
360
  orchestratorPrompt: z2.string().min(1).optional(),
327
361
  options: z2.record(z2.string(), z2.unknown()).optional(),
328
- displayName: z2.string().min(1).optional()
362
+ displayName: z2.string().min(1).optional(),
363
+ permission: PermissionConfigSchema.optional()
329
364
  }).strict();
330
365
  var MultiplexerTypeSchema = z2.enum([
331
366
  "auto",
332
367
  "tmux",
333
368
  "zellij",
334
369
  "herdr",
370
+ "cmux",
335
371
  "none"
336
372
  ]);
337
373
  var MultiplexerLayoutSchema = z2.enum([
@@ -377,7 +413,7 @@ var FailoverConfigSchema = z2.object({
377
413
  retryDelayMs: z2.number().min(0).default(500),
378
414
  maxRetries: z2.number().int().min(0).default(3).describe("Number of consecutive 429/rate-limit responses tolerated on the " + "same model before aborting (or swapping to the next fallback " + "model when a chain is configured)."),
379
415
  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."),
380
- runtimeOverride: z2.boolean().default(true).describe("When true (default), a runtime model selected via /model that is " + "outside the configured fallback chain will still trigger the chain " + "on rate-limit errors. When false, out-of-chain runtime picks are " + "respected and the error surfaces instead of silently falling back " + "to the chain. Models that are members of the chain always fall back " + "regardless of this setting.")
416
+ runtimeOverride: z2.boolean().optional().describe("DEPRECATED: no longer used. Previously controlled whether out-of-chain " + "runtime model picks triggered fallback. Fallback is now always " + "disabled when a user explicitly selects a model via /model.")
381
417
  }).strict();
382
418
  var CompanionConfigSchema = z2.object({
383
419
  enabled: z2.boolean().optional(),
@@ -419,11 +455,13 @@ var PluginConfigSchema = z2.object({
419
455
  preset: z2.string().optional(),
420
456
  setDefaultAgent: z2.boolean().optional(),
421
457
  compactSidebar: z2.boolean().optional().describe("Use the compact TUI sidebar layout. Defaults to true; set false to use the expanded layout."),
458
+ stripOrchestratorModel: z2.boolean().optional().describe("When true, omit orchestrator.model and orchestrator.variant from the SDK config so OpenCode uses the session model selected with /model after subagent dispatch. An explicitly selected preset that sets orchestrator.model is preserved. Defaults to false."),
422
459
  autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
423
460
  presets: z2.record(z2.string(), PresetSchema).optional(),
424
461
  agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
425
462
  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."),
426
- disabled_mcps: z2.array(z2.string()).optional(),
463
+ image_routing: z2.enum(["auto", "direct"]).optional().describe("How image attachments are handled. " + "When omitted, preserves legacy conditional behavior: intercept " + 'attachments only when observer is enabled. "auto": requires ' + "observer to be enabled and saves attachments to disk before " + 'nudging delegation to @observer. "direct": always passes ' + "attachments to the orchestrator untouched."),
464
+ disabled_mcps: z2.array(z2.string()).optional().describe("MCP server names to disable completely. Disabled servers are not " + "started and cannot be used by agents."),
427
465
  disabled_tools: z2.array(z2.string()).optional().describe("Tool names to disable completely. Disabled tools are not registered with OpenCode and cannot be used by agents."),
428
466
  disabled_skills: z2.array(z2.string()).optional().describe("Skill names to disable completely. Disabled skills are not granted to agents, even when referenced by presets or agent overrides."),
429
467
  multiplexer: MultiplexerConfigSchema.optional(),
@@ -526,6 +564,12 @@ var CUSTOM_SKILLS = [
526
564
  allowedAgents: ["orchestrator"],
527
565
  sourcePath: "src/skills/deepwork"
528
566
  },
567
+ {
568
+ name: "verification-planning",
569
+ description: "Plan credible, proportionate evidence before non-trivial implementation",
570
+ allowedAgents: ["orchestrator"],
571
+ sourcePath: "src/skills/verification-planning"
572
+ },
529
573
  {
530
574
  name: "reflect",
531
575
  description: "Review repeated work and suggest reusable workflow improvements",
@@ -538,12 +582,6 @@ var CUSTOM_SKILLS = [
538
582
  allowedAgents: ["orchestrator"],
539
583
  sourcePath: "src/skills/oh-my-opencode-slim"
540
584
  },
541
- {
542
- name: "release-smoke-test",
543
- description: "Validate packed release candidates and bugfixes before public publish",
544
- allowedAgents: ["orchestrator"],
545
- sourcePath: "src/skills/release-smoke-test"
546
- },
547
585
  {
548
586
  name: "worktrees",
549
587
  description: "Manage Git worktrees as OMO safe isolated coding lanes for complex/risky/parallel work",
@@ -630,6 +668,23 @@ function findConfigPathInDirs(configDirs, baseName) {
630
668
  }
631
669
  return null;
632
670
  }
671
+ function validateFinalImageRouting(config, configPath, options) {
672
+ if (config.image_routing !== "auto")
673
+ return true;
674
+ const disabledAgents = config.disabled_agents ?? DEFAULT_DISABLED_AGENTS;
675
+ if (!disabledAgents.includes("observer"))
676
+ return true;
677
+ const message = 'image_routing "auto" requires observer to be enabled. ' + 'Remove "observer" from disabled_agents.';
678
+ options?.onWarning?.({
679
+ path: configPath,
680
+ kind: "invalid-schema",
681
+ message
682
+ });
683
+ if (!options?.silent) {
684
+ console.warn(`[oh-my-opencode-slim] Invalid config: ${message}`);
685
+ }
686
+ return false;
687
+ }
633
688
  function findPluginConfigPaths(directory) {
634
689
  const userConfigPath = findConfigPathInDirs(getConfigSearchDirs(), "oh-my-opencode-slim");
635
690
  const projectConfigBasePath = path.join(directory, ".opencode", "oh-my-opencode-slim");
@@ -711,6 +766,7 @@ function loadPluginConfig(directory, options) {
711
766
  debug: config.companion.debug ?? false
712
767
  };
713
768
  }
769
+ validateFinalImageRouting(config, projectConfigPath ?? userConfigPath ?? "", options);
714
770
  return config;
715
771
  }
716
772
  function loadAgentPrompt(agentName, optionsOrPreset) {
@@ -776,6 +832,7 @@ function migrateTmuxToMultiplexer(config) {
776
832
  }
777
833
 
778
834
  // src/tui-state.ts
835
+ import { createHash } from "node:crypto";
779
836
  import * as fs2 from "node:fs";
780
837
  import * as os from "node:os";
781
838
  import * as path2 from "node:path";
@@ -784,8 +841,11 @@ var STATE_FILE = "tui-state.json";
784
841
  function dataDir() {
785
842
  return process.env.XDG_DATA_HOME ?? path2.join(os.homedir(), ".local", "share");
786
843
  }
787
- function getTuiStatePath() {
788
- return path2.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
844
+ function projectScope(projectDir) {
845
+ return createHash("sha256").update(path2.resolve(projectDir)).digest("hex").slice(0, 12);
846
+ }
847
+ function getTuiStatePath(projectDir) {
848
+ return path2.join(dataDir(), "opencode", "storage", STATE_DIR, projectScope(projectDir), STATE_FILE);
789
849
  }
790
850
  function emptySnapshot() {
791
851
  return {
@@ -806,42 +866,42 @@ function parseSnapshot(value) {
806
866
  agentVariants: parsed.agentVariants ?? {}
807
867
  };
808
868
  }
809
- function readTuiSnapshot() {
869
+ function readTuiSnapshot(projectDir) {
810
870
  try {
811
- return parseSnapshot(fs2.readFileSync(getTuiStatePath(), "utf8"));
871
+ return parseSnapshot(fs2.readFileSync(getTuiStatePath(projectDir), "utf8"));
812
872
  } catch {
813
873
  return emptySnapshot();
814
874
  }
815
875
  }
816
- async function readTuiSnapshotAsync() {
876
+ async function readTuiSnapshotAsync(projectDir) {
817
877
  try {
818
- return parseSnapshot(await fs2.promises.readFile(getTuiStatePath(), "utf8"));
878
+ return parseSnapshot(await fs2.promises.readFile(getTuiStatePath(projectDir), "utf8"));
819
879
  } catch {
820
880
  return emptySnapshot();
821
881
  }
822
882
  }
823
- function writeTuiSnapshot(snapshot) {
883
+ function writeTuiSnapshot(snapshot, projectDir) {
824
884
  try {
825
- const filePath = getTuiStatePath();
885
+ const filePath = getTuiStatePath(projectDir);
826
886
  fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
827
887
  fs2.writeFileSync(filePath, `${JSON.stringify(snapshot)}
828
888
  `);
829
889
  } catch {}
830
890
  }
831
- function updateSnapshot(mutator) {
832
- const snapshot = readTuiSnapshot();
891
+ function updateSnapshot(projectDir, mutator) {
892
+ const snapshot = readTuiSnapshot(projectDir);
833
893
  mutator(snapshot);
834
894
  snapshot.updatedAt = Date.now();
835
- writeTuiSnapshot(snapshot);
895
+ writeTuiSnapshot(snapshot, projectDir);
836
896
  }
837
- function recordTuiAgentModels(input) {
838
- updateSnapshot((snapshot) => {
897
+ function recordTuiAgentModels(input, projectDir) {
898
+ updateSnapshot(projectDir, (snapshot) => {
839
899
  snapshot.agentModels = { ...input.agentModels };
840
900
  snapshot.agentVariants = { ...input.agentVariants ?? {} };
841
901
  });
842
902
  }
843
- function recordTuiAgentModel(input) {
844
- updateSnapshot((snapshot) => {
903
+ function recordTuiAgentModel(input, projectDir) {
904
+ updateSnapshot(projectDir, (snapshot) => {
845
905
  snapshot.agentModels[input.agentName] = input.model;
846
906
  if (input.variant !== undefined) {
847
907
  if (input.variant === null) {
@@ -1017,11 +1077,11 @@ var plugin = {
1017
1077
  const version = meta.version ?? await readPackageVersion() ?? "dev";
1018
1078
  let configDirectory = getTuiDirectory(api);
1019
1079
  let { configInvalid, compactSidebar } = readConfigState(configDirectory);
1020
- let snapshot = readTuiSnapshot();
1080
+ let snapshot = readTuiSnapshot(configDirectory);
1021
1081
  const renderTimer = setInterval(async () => {
1022
1082
  try {
1023
- snapshot = await readTuiSnapshotAsync();
1024
1083
  const currentDirectory = getTuiDirectory(api);
1084
+ snapshot = await readTuiSnapshotAsync(currentDirectory);
1025
1085
  if (currentDirectory !== configDirectory) {
1026
1086
  configDirectory = currentDirectory;
1027
1087
  ({ configInvalid, compactSidebar } = readConfigState(configDirectory));
@@ -0,0 +1 @@
1
+ export declare function escapeHtml(value: string): string;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Parse `---` delimited frontmatter from a string.
3
+ * Handles both `\n` and `\r\n` line endings.
4
+ * Returns null if no frontmatter is found.
5
+ */
6
+ export declare function parseFrontmatter(content: string): Record<string, string> | null;
@@ -3,7 +3,7 @@ export * from './background-job-board';
3
3
  export * from './background-job-coordinator';
4
4
  export * from './background-job-store';
5
5
  export * from './internal-initiator';
6
- export { getLogDir, initLogger, log } from './logger';
6
+ export { initLogger, log } from './logger';
7
7
  export * from './polling';
8
8
  export * from './session';
9
9
  export * from './task';
@@ -1,6 +1,4 @@
1
- declare function getLogDir(): string;
2
1
  export declare function initLogger(sessionId: string): void;
3
- export { getLogDir };
4
2
  /** @internal Reset logger state for testing */
5
3
  export declare function resetLogger(): void;
6
4
  /** @internal Wait for queued log writes in tests. */