oh-my-opencode-slim 2.1.1 → 2.2.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.
Files changed (40) hide show
  1. package/README.ja-JP.md +13 -12
  2. package/README.ko-KR.md +13 -12
  3. package/README.md +29 -16
  4. package/README.zh-CN.md +13 -12
  5. package/dist/agents/index.d.ts +0 -4
  6. package/dist/cli/index.js +49 -6
  7. package/dist/config/constants.d.ts +10 -0
  8. package/dist/config/schema.d.ts +680 -2
  9. package/dist/config/strip-orchestrator-model.d.ts +9 -0
  10. package/dist/hooks/command-hook-utils.d.ts +5 -0
  11. package/dist/hooks/foreground-fallback/index.d.ts +40 -28
  12. package/dist/hooks/image-hook.d.ts +1 -0
  13. package/dist/hooks/index.d.ts +1 -1
  14. package/dist/index.js +1871 -326
  15. package/dist/interview/document.d.ts +2 -1
  16. package/dist/interview/ui.d.ts +0 -1
  17. package/dist/multiplexer/cmux/close-policy.d.ts +20 -0
  18. package/dist/multiplexer/cmux/index.d.ts +102 -0
  19. package/dist/multiplexer/cmux/session-lifecycle.d.ts +92 -0
  20. package/dist/multiplexer/cmux/session-state.d.ts +45 -0
  21. package/dist/multiplexer/factory.d.ts +0 -9
  22. package/dist/multiplexer/herdr/index.d.ts +2 -0
  23. package/dist/multiplexer/index.d.ts +3 -1
  24. package/dist/multiplexer/session-manager.d.ts +10 -2
  25. package/dist/multiplexer/shared.d.ts +8 -1
  26. package/dist/multiplexer/types.d.ts +5 -2
  27. package/dist/tools/smartfetch/utils.d.ts +3 -1
  28. package/dist/tui-state.d.ts +5 -5
  29. package/dist/tui.js +86 -20
  30. package/dist/utils/escape-html.d.ts +1 -0
  31. package/dist/utils/frontmatter.d.ts +6 -0
  32. package/dist/utils/index.d.ts +1 -1
  33. package/dist/utils/logger.d.ts +0 -2
  34. package/oh-my-opencode-slim.schema.json +717 -2
  35. package/package.json +1 -1
  36. package/src/skills/clonedeps/SKILL.md +32 -29
  37. package/src/skills/codemap.md +7 -3
  38. package/src/skills/deepwork/SKILL.md +17 -6
  39. package/src/skills/verification-planning/SKILL.md +103 -0
  40. package/src/skills/worktrees/SKILL.md +14 -7
package/dist/tui.js CHANGED
@@ -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",
@@ -630,6 +674,23 @@ function findConfigPathInDirs(configDirs, baseName) {
630
674
  }
631
675
  return null;
632
676
  }
677
+ function validateFinalImageRouting(config, configPath, options) {
678
+ if (config.image_routing !== "auto")
679
+ return true;
680
+ const disabledAgents = config.disabled_agents ?? DEFAULT_DISABLED_AGENTS;
681
+ if (!disabledAgents.includes("observer"))
682
+ return true;
683
+ const message = 'image_routing "auto" requires observer to be enabled. ' + 'Remove "observer" from disabled_agents.';
684
+ options?.onWarning?.({
685
+ path: configPath,
686
+ kind: "invalid-schema",
687
+ message
688
+ });
689
+ if (!options?.silent) {
690
+ console.warn(`[oh-my-opencode-slim] Invalid config: ${message}`);
691
+ }
692
+ return false;
693
+ }
633
694
  function findPluginConfigPaths(directory) {
634
695
  const userConfigPath = findConfigPathInDirs(getConfigSearchDirs(), "oh-my-opencode-slim");
635
696
  const projectConfigBasePath = path.join(directory, ".opencode", "oh-my-opencode-slim");
@@ -711,6 +772,7 @@ function loadPluginConfig(directory, options) {
711
772
  debug: config.companion.debug ?? false
712
773
  };
713
774
  }
775
+ validateFinalImageRouting(config, projectConfigPath ?? userConfigPath ?? "", options);
714
776
  return config;
715
777
  }
716
778
  function loadAgentPrompt(agentName, optionsOrPreset) {
@@ -776,6 +838,7 @@ function migrateTmuxToMultiplexer(config) {
776
838
  }
777
839
 
778
840
  // src/tui-state.ts
841
+ import { createHash } from "node:crypto";
779
842
  import * as fs2 from "node:fs";
780
843
  import * as os from "node:os";
781
844
  import * as path2 from "node:path";
@@ -784,8 +847,11 @@ var STATE_FILE = "tui-state.json";
784
847
  function dataDir() {
785
848
  return process.env.XDG_DATA_HOME ?? path2.join(os.homedir(), ".local", "share");
786
849
  }
787
- function getTuiStatePath() {
788
- return path2.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
850
+ function projectScope(projectDir) {
851
+ return createHash("sha256").update(path2.resolve(projectDir)).digest("hex").slice(0, 12);
852
+ }
853
+ function getTuiStatePath(projectDir) {
854
+ return path2.join(dataDir(), "opencode", "storage", STATE_DIR, projectScope(projectDir), STATE_FILE);
789
855
  }
790
856
  function emptySnapshot() {
791
857
  return {
@@ -806,42 +872,42 @@ function parseSnapshot(value) {
806
872
  agentVariants: parsed.agentVariants ?? {}
807
873
  };
808
874
  }
809
- function readTuiSnapshot() {
875
+ function readTuiSnapshot(projectDir) {
810
876
  try {
811
- return parseSnapshot(fs2.readFileSync(getTuiStatePath(), "utf8"));
877
+ return parseSnapshot(fs2.readFileSync(getTuiStatePath(projectDir), "utf8"));
812
878
  } catch {
813
879
  return emptySnapshot();
814
880
  }
815
881
  }
816
- async function readTuiSnapshotAsync() {
882
+ async function readTuiSnapshotAsync(projectDir) {
817
883
  try {
818
- return parseSnapshot(await fs2.promises.readFile(getTuiStatePath(), "utf8"));
884
+ return parseSnapshot(await fs2.promises.readFile(getTuiStatePath(projectDir), "utf8"));
819
885
  } catch {
820
886
  return emptySnapshot();
821
887
  }
822
888
  }
823
- function writeTuiSnapshot(snapshot) {
889
+ function writeTuiSnapshot(snapshot, projectDir) {
824
890
  try {
825
- const filePath = getTuiStatePath();
891
+ const filePath = getTuiStatePath(projectDir);
826
892
  fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
827
893
  fs2.writeFileSync(filePath, `${JSON.stringify(snapshot)}
828
894
  `);
829
895
  } catch {}
830
896
  }
831
- function updateSnapshot(mutator) {
832
- const snapshot = readTuiSnapshot();
897
+ function updateSnapshot(projectDir, mutator) {
898
+ const snapshot = readTuiSnapshot(projectDir);
833
899
  mutator(snapshot);
834
900
  snapshot.updatedAt = Date.now();
835
- writeTuiSnapshot(snapshot);
901
+ writeTuiSnapshot(snapshot, projectDir);
836
902
  }
837
- function recordTuiAgentModels(input) {
838
- updateSnapshot((snapshot) => {
903
+ function recordTuiAgentModels(input, projectDir) {
904
+ updateSnapshot(projectDir, (snapshot) => {
839
905
  snapshot.agentModels = { ...input.agentModels };
840
906
  snapshot.agentVariants = { ...input.agentVariants ?? {} };
841
907
  });
842
908
  }
843
- function recordTuiAgentModel(input) {
844
- updateSnapshot((snapshot) => {
909
+ function recordTuiAgentModel(input, projectDir) {
910
+ updateSnapshot(projectDir, (snapshot) => {
845
911
  snapshot.agentModels[input.agentName] = input.model;
846
912
  if (input.variant !== undefined) {
847
913
  if (input.variant === null) {
@@ -1017,11 +1083,11 @@ var plugin = {
1017
1083
  const version = meta.version ?? await readPackageVersion() ?? "dev";
1018
1084
  let configDirectory = getTuiDirectory(api);
1019
1085
  let { configInvalid, compactSidebar } = readConfigState(configDirectory);
1020
- let snapshot = readTuiSnapshot();
1086
+ let snapshot = readTuiSnapshot(configDirectory);
1021
1087
  const renderTimer = setInterval(async () => {
1022
1088
  try {
1023
- snapshot = await readTuiSnapshotAsync();
1024
1089
  const currentDirectory = getTuiDirectory(api);
1090
+ snapshot = await readTuiSnapshotAsync(currentDirectory);
1025
1091
  if (currentDirectory !== configDirectory) {
1026
1092
  configDirectory = currentDirectory;
1027
1093
  ({ 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. */