oh-my-opencode-slim 2.0.4 → 2.1.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 (61) hide show
  1. package/README.ja-JP.md +61 -21
  2. package/README.ko-KR.md +59 -19
  3. package/README.md +73 -20
  4. package/README.zh-CN.md +62 -24
  5. package/dist/agents/council.d.ts +1 -1
  6. package/dist/agents/index.d.ts +7 -2
  7. package/dist/agents/orchestrator.d.ts +1 -1
  8. package/dist/agents/permissions.d.ts +10 -0
  9. package/dist/cli/custom-skills-registry.d.ts +18 -0
  10. package/dist/cli/custom-skills.d.ts +3 -19
  11. package/dist/cli/index.js +1093 -186
  12. package/dist/cli/providers.d.ts +5 -9
  13. package/dist/cli/skills.d.ts +3 -3
  14. package/dist/companion/manager.d.ts +7 -0
  15. package/dist/config/constants.d.ts +3 -2
  16. package/dist/config/loader.d.ts +5 -2
  17. package/dist/config/schema.d.ts +7 -0
  18. package/dist/council/council-manager.d.ts +1 -1
  19. package/dist/hooks/auto-update-checker/skill-sync.d.ts +59 -1
  20. package/dist/hooks/filter-available-skills/index.d.ts +1 -2
  21. package/dist/hooks/foreground-fallback/index.d.ts +5 -1
  22. package/dist/hooks/image-hook.d.ts +1 -1
  23. package/dist/hooks/index.d.ts +1 -0
  24. package/dist/hooks/loop-command/index.d.ts +13 -0
  25. package/dist/hooks/phase-reminder/index.d.ts +1 -2
  26. package/dist/hooks/task-session-manager/index.d.ts +1 -2
  27. package/dist/hooks/task-session-manager/pending-call-tracker.d.ts +13 -0
  28. package/dist/hooks/task-session-manager/task-context-tracker.d.ts +14 -0
  29. package/dist/hooks/types.d.ts +3 -1
  30. package/dist/index.js +3491 -876
  31. package/dist/interview/dashboard-manager.d.ts +21 -0
  32. package/dist/interview/dashboard.d.ts +5 -0
  33. package/dist/interview/document.d.ts +4 -1
  34. package/dist/interview/manager.d.ts +0 -14
  35. package/dist/interview/server.d.ts +2 -0
  36. package/dist/interview/service.d.ts +9 -0
  37. package/dist/interview/session-server.d.ts +21 -0
  38. package/dist/interview/types.d.ts +16 -1
  39. package/dist/loop/loop-session.d.ts +64 -0
  40. package/dist/multiplexer/factory.d.ts +5 -5
  41. package/dist/multiplexer/herdr/index.d.ts +31 -0
  42. package/dist/multiplexer/index.d.ts +1 -0
  43. package/dist/multiplexer/session-manager.d.ts +3 -0
  44. package/dist/multiplexer/types.d.ts +4 -4
  45. package/dist/tools/acp-run.d.ts +1 -1
  46. package/dist/tui-state.d.ts +3 -0
  47. package/dist/tui.d.ts +4 -1
  48. package/dist/tui.js +142 -55
  49. package/dist/utils/background-job-board.d.ts +20 -0
  50. package/dist/utils/env.d.ts +3 -0
  51. package/dist/utils/session.d.ts +1 -1
  52. package/oh-my-opencode-slim.schema.json +24 -4
  53. package/package.json +1 -1
  54. package/src/skills/clonedeps/SKILL.md +2 -2
  55. package/src/skills/clonedeps/codemap.md +23 -32
  56. package/src/skills/codemap/SKILL.md +2 -2
  57. package/src/skills/codemap.md +63 -36
  58. package/src/skills/loop-engineering/SKILL.md +30 -0
  59. package/src/skills/reflect/SKILL.md +133 -0
  60. package/src/skills/release-smoke-test/SKILL.md +159 -0
  61. package/src/skills/simplify/SKILL.md +6 -6
package/dist/tui.js CHANGED
@@ -118,14 +118,14 @@ var ALL_AGENT_NAMES = ["orchestrator", ...SUBAGENT_NAMES];
118
118
  var PROTECTED_AGENTS = new Set(["orchestrator", "councillor"]);
119
119
  var DEFAULT_MODELS = {
120
120
  orchestrator: undefined,
121
- oracle: "openai/gpt-5.5",
122
- librarian: "openai/gpt-5.4-mini",
123
- explorer: "openai/gpt-5.4-mini",
124
- designer: "openai/gpt-5.4-mini",
125
- fixer: "openai/gpt-5.4-mini",
126
- observer: "openai/gpt-5.4-mini",
127
- council: "openai/gpt-5.4-mini",
128
- councillor: "openai/gpt-5.4-mini"
121
+ oracle: undefined,
122
+ librarian: undefined,
123
+ explorer: undefined,
124
+ designer: undefined,
125
+ fixer: undefined,
126
+ observer: undefined,
127
+ council: undefined,
128
+ councillor: undefined
129
129
  };
130
130
  var POLL_INTERVAL_BACKGROUND_MS = 2000;
131
131
  var MAX_POLL_TIME_MS = 5 * 60 * 1000;
@@ -232,7 +232,7 @@ var CouncilConfigSchema = z.object({
232
232
  default_preset: z.string().default("default"),
233
233
  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).'),
234
234
  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."),
235
- master: z.unknown().optional().describe("DEPRECATED ignored. Council agent synthesizes directly.")
235
+ master: z.unknown().optional().describe("DEPRECATED - ignored. Council agent synthesizes directly.")
236
236
  }).transform((data) => {
237
237
  const deprecated = [];
238
238
  if (data.master !== undefined)
@@ -298,7 +298,13 @@ var AgentOverrideConfigSchema = z2.object({
298
298
  options: z2.record(z2.string(), z2.unknown()).optional(),
299
299
  displayName: z2.string().min(1).optional()
300
300
  }).strict();
301
- var MultiplexerTypeSchema = z2.enum(["auto", "tmux", "zellij", "none"]);
301
+ var MultiplexerTypeSchema = z2.enum([
302
+ "auto",
303
+ "tmux",
304
+ "zellij",
305
+ "herdr",
306
+ "none"
307
+ ]);
302
308
  var MultiplexerLayoutSchema = z2.enum([
303
309
  "main-horizontal",
304
310
  "main-vertical",
@@ -353,6 +359,7 @@ var CompanionConfigSchema = z2.object({
353
359
  debug: z2.boolean().optional().describe("Enable verbose native companion debug logs.")
354
360
  });
355
361
  var AcpAgentPermissionModeSchema = z2.enum(["ask", "allow", "reject"]);
362
+ var MAX_ACP_TIMEOUT_MS = 2147483647;
356
363
  var AcpAgentConfigSchema = z2.object({
357
364
  command: z2.string().min(1),
358
365
  args: z2.array(z2.string()).default([]),
@@ -362,28 +369,17 @@ var AcpAgentConfigSchema = z2.object({
362
369
  prompt: z2.string().min(1).optional(),
363
370
  orchestratorPrompt: z2.string().min(1).optional(),
364
371
  wrapperModel: ProviderModelIdSchema.optional(),
365
- timeoutMs: z2.number().int().min(1000).max(900000).default(300000),
372
+ timeoutMs: z2.number().int().min(0).max(MAX_ACP_TIMEOUT_MS).default(0).describe("Timeout for a single ACP run in milliseconds. Set to 0 to disable the timeout."),
366
373
  permissionMode: AcpAgentPermissionModeSchema.default("ask")
367
374
  }).strict();
368
375
  var AcpAgentsConfigSchema = z2.record(z2.string(), AcpAgentConfigSchema);
369
- function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
376
+ function rejectOrchestratorPromptOnOrchestrator(overrides, ctx, pathPrefix) {
370
377
  for (const [name, override] of Object.entries(overrides)) {
371
- const isBuiltInOrAlias = ALL_AGENT_NAMES.includes(name) || AGENT_ALIASES[name] !== undefined;
372
- if (!isBuiltInOrAlias) {
373
- continue;
374
- }
375
- if (override.prompt !== undefined) {
376
- ctx.addIssue({
377
- code: z2.ZodIssueCode.custom,
378
- path: [...pathPrefix, name, "prompt"],
379
- message: "prompt is only supported for custom agents"
380
- });
381
- }
382
- if (override.orchestratorPrompt !== undefined) {
378
+ if (name === "orchestrator" && override.orchestratorPrompt !== undefined) {
383
379
  ctx.addIssue({
384
380
  code: z2.ZodIssueCode.custom,
385
381
  path: [...pathPrefix, name, "orchestratorPrompt"],
386
- message: "orchestratorPrompt is only supported for custom agents"
382
+ message: "orchestratorPrompt is not supported for the orchestrator agent"
387
383
  });
388
384
  }
389
385
  }
@@ -391,11 +387,14 @@ function validateCustomOnlyPromptFields(overrides, ctx, pathPrefix) {
391
387
  var PluginConfigSchema = z2.object({
392
388
  preset: z2.string().optional(),
393
389
  setDefaultAgent: z2.boolean().optional(),
390
+ compactSidebar: z2.boolean().optional().describe("Use the compact TUI sidebar layout when enabled."),
394
391
  autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
395
392
  presets: z2.record(z2.string(), PresetSchema).optional(),
396
393
  agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
397
394
  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."),
398
395
  disabled_mcps: z2.array(z2.string()).optional(),
396
+ 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."),
397
+ 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."),
399
398
  multiplexer: MultiplexerConfigSchema.optional(),
400
399
  tmux: TmuxConfigSchema.optional(),
401
400
  websearch: WebsearchConfigSchema.optional(),
@@ -407,11 +406,14 @@ var PluginConfigSchema = z2.object({
407
406
  acpAgents: AcpAgentsConfigSchema.optional()
408
407
  }).superRefine((value, ctx) => {
409
408
  if (value.agents) {
410
- validateCustomOnlyPromptFields(value.agents, ctx, ["agents"]);
409
+ rejectOrchestratorPromptOnOrchestrator(value.agents, ctx, ["agents"]);
411
410
  }
412
411
  if (value.presets) {
413
412
  for (const [presetName, preset] of Object.entries(value.presets)) {
414
- validateCustomOnlyPromptFields(preset, ctx, ["presets", presetName]);
413
+ rejectOrchestratorPromptOnOrchestrator(preset, ctx, [
414
+ "presets",
415
+ presetName
416
+ ]);
415
417
  }
416
418
  }
417
419
  });
@@ -467,7 +469,7 @@ function getAgentMcpList(agentName, config) {
467
469
  return defaultMcps ?? [];
468
470
  }
469
471
 
470
- // src/cli/custom-skills.ts
472
+ // src/cli/custom-skills-registry.ts
471
473
  var CUSTOM_SKILLS = [
472
474
  {
473
475
  name: "simplify",
@@ -505,6 +507,12 @@ var CUSTOM_SKILLS = [
505
507
  allowedAgents: ["orchestrator"],
506
508
  sourcePath: "src/skills/oh-my-opencode-slim"
507
509
  },
510
+ {
511
+ name: "release-smoke-test",
512
+ description: "Validate packed release candidates and bugfixes before public publish",
513
+ allowedAgents: ["orchestrator"],
514
+ sourcePath: "src/skills/release-smoke-test"
515
+ },
508
516
  {
509
517
  name: "worktrees",
510
518
  description: "Manage Git worktrees as OMO safe isolated coding lanes for complex/risky/parallel work",
@@ -602,6 +610,7 @@ function mergePluginConfigs(base, override) {
602
610
  ...base,
603
611
  ...override,
604
612
  agents: deepMerge(base.agents, override.agents),
613
+ presets: deepMerge(base.presets, override.presets),
605
614
  tmux: deepMerge(base.tmux, override.tmux),
606
615
  multiplexer: deepMerge(base.multiplexer, override.multiplexer),
607
616
  interview: deepMerge(base.interview, override.interview),
@@ -673,15 +682,33 @@ function loadPluginConfig(directory, options) {
673
682
  }
674
683
  return config;
675
684
  }
676
- function loadAgentPrompt(agentName, preset) {
685
+ function loadAgentPrompt(agentName, optionsOrPreset) {
686
+ let preset;
687
+ let projectDirectory;
688
+ if (typeof optionsOrPreset === "string") {
689
+ preset = optionsOrPreset;
690
+ } else if (optionsOrPreset && typeof optionsOrPreset === "object") {
691
+ preset = optionsOrPreset.preset;
692
+ projectDirectory = optionsOrPreset.projectDirectory;
693
+ }
677
694
  const presetDirName = preset && /^[a-zA-Z0-9_-]+$/.test(preset) ? preset : undefined;
678
- const promptSearchDirs = getConfigSearchDirs().flatMap((configDir) => {
679
- const promptsDir = path.join(configDir, PROMPTS_DIR_NAME);
680
- return presetDirName ? [path.join(promptsDir, presetDirName), promptsDir] : [promptsDir];
681
- });
682
- const result = {};
695
+ const searchDirs = [];
696
+ if (projectDirectory && presetDirName) {
697
+ searchDirs.push(path.join(projectDirectory, ".opencode", PROMPTS_DIR_NAME, presetDirName));
698
+ }
699
+ if (projectDirectory) {
700
+ searchDirs.push(path.join(projectDirectory, ".opencode", PROMPTS_DIR_NAME));
701
+ }
702
+ if (presetDirName) {
703
+ for (const userDir of getConfigSearchDirs()) {
704
+ searchDirs.push(path.join(userDir, PROMPTS_DIR_NAME, presetDirName));
705
+ }
706
+ }
707
+ for (const userDir of getConfigSearchDirs()) {
708
+ searchDirs.push(path.join(userDir, PROMPTS_DIR_NAME));
709
+ }
683
710
  const readFirstPrompt = (fileName, errorPrefix) => {
684
- for (const dir of promptSearchDirs) {
711
+ for (const dir of searchDirs) {
685
712
  const promptPath = path.join(dir, fileName);
686
713
  if (!fs.existsSync(promptPath)) {
687
714
  continue;
@@ -694,6 +721,7 @@ function loadAgentPrompt(agentName, preset) {
694
721
  }
695
722
  return;
696
723
  };
724
+ const result = {};
697
725
  result.prompt = readFirstPrompt(`${agentName}.md`, "Error reading prompt file");
698
726
  result.appendPrompt = readFirstPrompt(`${agentName}_append.md`, "Error reading append prompt file");
699
727
  return result;
@@ -732,7 +760,8 @@ function emptySnapshot() {
732
760
  return {
733
761
  version: 1,
734
762
  updatedAt: Date.now(),
735
- agentModels: {}
763
+ agentModels: {},
764
+ agentVariants: {}
736
765
  };
737
766
  }
738
767
  function parseSnapshot(value) {
@@ -742,7 +771,8 @@ function parseSnapshot(value) {
742
771
  return {
743
772
  version: 1,
744
773
  updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : Date.now(),
745
- agentModels: parsed.agentModels ?? {}
774
+ agentModels: parsed.agentModels ?? {},
775
+ agentVariants: parsed.agentVariants ?? {}
746
776
  };
747
777
  }
748
778
  function readTuiSnapshot() {
@@ -776,14 +806,32 @@ function updateSnapshot(mutator) {
776
806
  function recordTuiAgentModels(input) {
777
807
  updateSnapshot((snapshot) => {
778
808
  snapshot.agentModels = { ...input.agentModels };
809
+ snapshot.agentVariants = { ...input.agentVariants ?? {} };
779
810
  });
780
811
  }
781
812
  function recordTuiAgentModel(input) {
782
813
  updateSnapshot((snapshot) => {
783
814
  snapshot.agentModels[input.agentName] = input.model;
815
+ if (input.variant !== undefined) {
816
+ if (input.variant === null) {
817
+ delete snapshot.agentVariants[input.agentName];
818
+ } else {
819
+ snapshot.agentVariants[input.agentName] = input.variant;
820
+ }
821
+ }
784
822
  });
785
823
  }
786
824
 
825
+ // src/utils/env.ts
826
+ var TRUTHY_ENV_VALUES = new Set(["1", "true", "yes", "on"]);
827
+ var PLUGIN_DISABLE_ENV = "OH_MY_OPENCODE_SLIM_DISABLE";
828
+ function isTruthyEnvValue(value) {
829
+ return TRUTHY_ENV_VALUES.has(value?.trim().toLowerCase() ?? "");
830
+ }
831
+ function isPluginDisabledByEnv(env = process.env) {
832
+ return isTruthyEnvValue(env[PLUGIN_DISABLE_ENV]);
833
+ }
834
+
787
835
  // src/tui.ts
788
836
  var PLUGIN_NAME = "oh-my-opencode-slim";
789
837
  var CONFIG_WARNING_COLOR = "orange";
@@ -816,27 +864,56 @@ function text(props, children) {
816
864
  function box(props, children = []) {
817
865
  return element("box", props, children);
818
866
  }
819
- function truncate(value, max = 24) {
820
- return value.length > max ? `${value.slice(0, max - 1)}…` : value;
821
- }
822
867
  function getTuiDirectory(api) {
823
868
  return api.state?.path?.directory ?? process.cwd();
824
869
  }
825
- function formatSidebarModelName(model) {
826
- const lastSlash = model.lastIndexOf("/");
827
- return lastSlash === -1 ? model : model.slice(lastSlash + 1);
870
+ function splitSidebarModelId(model) {
871
+ const slashIndex = model.indexOf("/");
872
+ if (slashIndex === -1) {
873
+ return { model };
874
+ }
875
+ return {
876
+ provider: model.slice(0, slashIndex),
877
+ model: model.slice(slashIndex + 1)
878
+ };
828
879
  }
829
880
  function getSidebarAgentNames(snapshot) {
830
881
  const configuredAgents = Object.keys(snapshot.agentModels);
831
882
  return configuredAgents.length > 0 ? configuredAgents : FALLBACK_SIDEBAR_AGENTS;
832
883
  }
833
- function row(label, value, theme, valueColor) {
834
- return box({ width: "100%", flexDirection: "row", justifyContent: "space-between" }, [
884
+ function agentRow(label, model, variant, theme) {
885
+ const modelParts = splitSidebarModelId(model);
886
+ const detailRows = [];
887
+ function detailRow(fieldLabel, value) {
888
+ return box({ width: "100%", flexDirection: "row", paddingLeft: 2 }, [
889
+ text({ fg: theme.textMuted, width: 9 }, [fieldLabel]),
890
+ text({ fg: theme.textMuted }, [value])
891
+ ]);
892
+ }
893
+ if (modelParts.provider) {
894
+ detailRows.push(detailRow("provider", modelParts.provider));
895
+ }
896
+ detailRows.push(detailRow("model", modelParts.model));
897
+ if (variant) {
898
+ detailRows.push(detailRow("variant", variant));
899
+ }
900
+ return box({ width: "100%", flexDirection: "column", marginBottom: 1 }, [
835
901
  text({ fg: theme.textMuted }, [label]),
836
- text({ fg: valueColor ?? theme.text }, [value])
902
+ ...detailRows
837
903
  ]);
838
904
  }
839
- function renderSidebar(snapshot, version, theme, configInvalid) {
905
+ function compactAgentRow(label, model, variant, theme) {
906
+ const value = variant ? `${model} (${variant})` : model;
907
+ return box({
908
+ width: "100%",
909
+ flexDirection: "row",
910
+ justifyContent: "space-between"
911
+ }, [
912
+ text({ fg: theme.textMuted, width: 14 }, [label]),
913
+ text({ fg: theme.textMuted }, [value])
914
+ ]);
915
+ }
916
+ function renderSidebar(snapshot, version, theme, configInvalid, compactSidebar) {
840
917
  const configStatusRow = buildConfigStatusRow(configInvalid, theme);
841
918
  return box({
842
919
  width: "100%",
@@ -863,7 +940,11 @@ function renderSidebar(snapshot, version, theme, configInvalid) {
863
940
  ]),
864
941
  ...getSidebarAgentNames(snapshot).map((agentName) => {
865
942
  const model = snapshot.agentModels[agentName] ?? "pending";
866
- return row(agentName, truncate(formatSidebarModelName(model), 26), theme, theme.textMuted);
943
+ const variant = snapshot.agentVariants[agentName];
944
+ if (compactSidebar) {
945
+ return compactAgentRow(agentName, model, variant, theme);
946
+ }
947
+ return agentRow(agentName, model, variant, theme);
867
948
  })
868
949
  ]);
869
950
  }
@@ -880,22 +961,28 @@ function buildConfigStatusRow(configInvalid, theme) {
880
961
  text({ fg: theme.textMuted }, ["Run doctor for details"])
881
962
  ]);
882
963
  }
883
- function readConfigInvalid(directory) {
964
+ function readConfigState(directory) {
884
965
  let configInvalid = false;
885
- loadPluginConfig(directory, {
966
+ const config = loadPluginConfig(directory, {
886
967
  silent: true,
887
968
  onWarning: () => {
888
969
  configInvalid = true;
889
970
  }
890
971
  });
891
- return configInvalid;
972
+ const compactSidebar = config.compactSidebar ?? false;
973
+ return { configInvalid, compactSidebar };
974
+ }
975
+ function readConfigInvalid(directory) {
976
+ return readConfigState(directory).configInvalid;
892
977
  }
893
978
  var plugin = {
894
979
  id: `${PLUGIN_NAME}:tui`,
895
980
  tui: async (api, _options, meta) => {
981
+ if (isPluginDisabledByEnv())
982
+ return;
896
983
  const version = meta.version ?? await readPackageVersion() ?? "dev";
897
984
  let configDirectory = getTuiDirectory(api);
898
- let configInvalid = readConfigInvalid(configDirectory);
985
+ let { configInvalid, compactSidebar } = readConfigState(configDirectory);
899
986
  let snapshot = readTuiSnapshot();
900
987
  const renderTimer = setInterval(async () => {
901
988
  try {
@@ -903,7 +990,7 @@ var plugin = {
903
990
  const currentDirectory = getTuiDirectory(api);
904
991
  if (currentDirectory !== configDirectory) {
905
992
  configDirectory = currentDirectory;
906
- configInvalid = readConfigInvalid(configDirectory);
993
+ ({ configInvalid, compactSidebar } = readConfigState(configDirectory));
907
994
  }
908
995
  api.renderer.requestRender();
909
996
  } catch {}
@@ -915,7 +1002,7 @@ var plugin = {
915
1002
  order: 900,
916
1003
  slots: {
917
1004
  sidebar_content() {
918
- return renderSidebar(snapshot, version, api.theme.current, configInvalid);
1005
+ return renderSidebar(snapshot, version, api.theme.current, configInvalid, compactSidebar);
919
1006
  }
920
1007
  }
921
1008
  });
@@ -923,8 +1010,8 @@ var plugin = {
923
1010
  };
924
1011
  var tui_default = plugin;
925
1012
  export {
1013
+ splitSidebarModelId,
926
1014
  readConfigInvalid,
927
1015
  getSidebarAgentNames,
928
- formatSidebarModelName,
929
1016
  tui_default as default
930
1017
  };
@@ -14,6 +14,7 @@ export interface BackgroundJobRecord {
14
14
  objective?: string;
15
15
  state: BackgroundJobState;
16
16
  timedOut: boolean;
17
+ recoverableAfterLiveBusy: boolean;
17
18
  statusUncertain: boolean;
18
19
  cancellationRequested: boolean;
19
20
  terminalUnreconciled: boolean;
@@ -28,6 +29,9 @@ export interface BackgroundJobRecord {
28
29
  lastUsedAt: number;
29
30
  terminalState?: TaskOutputState;
30
31
  contextFiles: ContextFile[];
32
+ totalErrors?: number;
33
+ timeoutCount?: number;
34
+ lastErrorAt?: number;
31
35
  }
32
36
  export interface BackgroundJobBoardOptions {
33
37
  maxReusablePerAgent?: number;
@@ -51,13 +55,19 @@ export interface BackgroundJobStatusInput {
51
55
  lastStatusError?: string;
52
56
  now?: number;
53
57
  }
58
+ type TerminalStateListener = (taskID: string) => void;
54
59
  export declare class BackgroundJobBoard {
55
60
  private readonly jobs;
56
61
  private readonly counters;
62
+ private terminalStateListeners;
57
63
  private readonly maxReusablePerAgent;
58
64
  private readonly readContextMinLines;
59
65
  private readonly readContextMaxFiles;
60
66
  constructor(options?: BackgroundJobBoardOptions);
67
+ addTerminalStateListener(listener: TerminalStateListener): void;
68
+ removeTerminalStateListener(listener: TerminalStateListener): void;
69
+ setTerminalStateListener(listener?: TerminalStateListener): void;
70
+ private notifyTerminalStateListeners;
61
71
  registerLaunch(input: BackgroundJobLaunchInput): BackgroundJobRecord;
62
72
  updateStatus(input: BackgroundJobStatusInput): BackgroundJobRecord | undefined;
63
73
  updateFromStatusOutput(output: string): BackgroundJobRecord | undefined;
@@ -67,14 +77,23 @@ export declare class BackgroundJobBoard {
67
77
  force?: boolean;
68
78
  }): BackgroundJobRecord | undefined;
69
79
  get(taskID: string): BackgroundJobRecord | undefined;
80
+ field<K extends keyof BackgroundJobRecord>(taskID: string, key: K): BackgroundJobRecord[K] | undefined;
81
+ isRunning(taskID: string): boolean;
82
+ isTerminalUnreconciled(taskID: string): boolean;
83
+ getResultSummary(taskID: string): string | undefined;
84
+ getLastLiveBusyAt(taskID: string): number | undefined;
85
+ getParentSessionID(taskID: string): string | undefined;
86
+ getState(taskID: string): BackgroundJobState | undefined;
70
87
  resolve(parentSessionID: string, taskIDOrAlias: string): BackgroundJobRecord | undefined;
71
88
  resolveReusable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
89
+ resolveRecoverable(parentSessionID: string, taskIDOrAlias: string, agent?: string): BackgroundJobRecord | undefined;
72
90
  markUsed(parentSessionID: string, key: string, now?: number): void;
73
91
  taskIDs(): Set<string>;
74
92
  addContext(taskID: string, files: ContextFile[]): void;
75
93
  list(parentSessionID?: string): BackgroundJobRecord[];
76
94
  hasRunning(parentSessionID: string): boolean;
77
95
  hasTerminalUnreconciled(parentSessionID: string): boolean;
96
+ hasConvergenceSignals(taskID: string, threshold?: number): boolean;
78
97
  formatForPrompt(parentSessionID: string, now?: number): string | undefined;
79
98
  clearParent(parentSessionID: string): void;
80
99
  drop(taskID: string): void;
@@ -87,3 +106,4 @@ export declare function deriveTaskSessionLabel(input: {
87
106
  prompt?: string;
88
107
  agentType: string;
89
108
  }): string;
109
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare const PLUGIN_DISABLE_ENV = "OH_MY_OPENCODE_SLIM_DISABLE";
2
+ export declare function isTruthyEnvValue(value: string | undefined): boolean;
3
+ export declare function isPluginDisabledByEnv(env?: NodeJS.ProcessEnv): boolean;
@@ -52,7 +52,7 @@ export declare function parseModelReference(model: string): {
52
52
  export declare function promptWithTimeout(client: OpencodeClient, args: Parameters<OpencodeClient['session']['prompt']>[0], timeoutMs: number, signal?: AbortSignal): Promise<void>;
53
53
  /**
54
54
  * Result of extracting session content.
55
- * `empty` is true when the assistant produced zero text content
55
+ * `empty` is true when the assistant produced zero text content -
56
56
  * the provider returned an empty response (e.g. rate-limited silently).
57
57
  */
58
58
  export interface SessionExtractionResult {
@@ -8,6 +8,10 @@
8
8
  "setDefaultAgent": {
9
9
  "type": "boolean"
10
10
  },
11
+ "compactSidebar": {
12
+ "description": "Use the compact TUI sidebar layout when enabled.",
13
+ "type": "boolean"
14
+ },
11
15
  "autoUpdate": {
12
16
  "description": "Disable automatic installation of plugin updates when false. Defaults to true.",
13
17
  "type": "boolean"
@@ -197,6 +201,20 @@
197
201
  "type": "string"
198
202
  }
199
203
  },
204
+ "disabled_tools": {
205
+ "description": "Tool names to disable completely. Disabled tools are not registered with OpenCode and cannot be used by agents.",
206
+ "type": "array",
207
+ "items": {
208
+ "type": "string"
209
+ }
210
+ },
211
+ "disabled_skills": {
212
+ "description": "Skill names to disable completely. Disabled skills are not granted to agents, even when referenced by presets or agent overrides.",
213
+ "type": "array",
214
+ "items": {
215
+ "type": "string"
216
+ }
217
+ },
200
218
  "multiplexer": {
201
219
  "type": "object",
202
220
  "properties": {
@@ -207,6 +225,7 @@
207
225
  "auto",
208
226
  "tmux",
209
227
  "zellij",
228
+ "herdr",
210
229
  "none"
211
230
  ]
212
231
  },
@@ -403,7 +422,7 @@
403
422
  "maximum": 5
404
423
  },
405
424
  "master": {
406
- "description": "DEPRECATED ignored. Council agent synthesizes directly."
425
+ "description": "DEPRECATED - ignored. Council agent synthesizes directly."
407
426
  }
408
427
  },
409
428
  "required": [
@@ -515,10 +534,11 @@
515
534
  "pattern": "^[^/\\s]+\\/[^\\s]+$"
516
535
  },
517
536
  "timeoutMs": {
518
- "default": 300000,
537
+ "default": 0,
538
+ "description": "Timeout for a single ACP run in milliseconds. Set to 0 to disable the timeout.",
519
539
  "type": "integer",
520
- "minimum": 1000,
521
- "maximum": 900000
540
+ "minimum": 0,
541
+ "maximum": 2147483647
522
542
  },
523
543
  "permissionMode": {
524
544
  "default": "ask",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode-slim",
3
- "version": "2.0.4",
3
+ "version": "2.1.0",
4
4
  "description": "Lightweight agent orchestration plugin for OpenCode - a slimmed-down fork of oh-my-opencode",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -217,9 +217,9 @@ sentence so future agents do not need an extra read just to know what is there:
217
217
  Read-only dependency source repositories are available under
218
218
  `.slim/clonedeps/repos/` for inspection. Do not edit these clones.
219
219
 
220
- - `.slim/clonedeps/repos/<safe-name>/` `<repo>` at `<ref>`; <one sentence on
220
+ - `.slim/clonedeps/repos/<safe-name>/` - `<repo>` at `<ref>`; <one sentence on
221
221
  why this source is useful>.
222
- - `.slim/clonedeps/repos/<safe-name-2>/` `<repo>` at `<ref>`; <one sentence on
222
+ - `.slim/clonedeps/repos/<safe-name-2>/` - `<repo>` at `<ref>`; <one sentence on
223
223
  why this source is useful>.
224
224
  ```
225
225
 
@@ -1,41 +1,32 @@
1
1
  # src/skills/clonedeps/
2
2
 
3
3
  ## Responsibility
4
-
5
- Workflow-only bundled OpenCode skill for local dependency source mirroring. It
6
- instructs the orchestrator to use `@librarian` for dependency discovery and
7
- source URL/ref resolution, then perform approved git/filesystem operations
8
- directly.
4
+ Manages the cloning and management of read-only dependency source repositories into `.slim/clonedeps/repos/` for offline inspection. This skill provides a workflow (not a command wrapper) that guides the orchestrator and `@librarian` through cloning dependency sources so agents can inspect library internals without requiring network access.
9
5
 
10
6
  ## Design
11
-
12
- - `SKILL.md` is the prompt contract loaded by OpenCode and assigned only to the
13
- orchestrator.
14
- - No helper script is bundled. The skill avoids brittle cross-ecosystem parsing
15
- and keeps repo-specific judgment in librarian/orchestrator.
16
- - State is trackable project metadata stored in `.slim/clonedeps.json`; clone
17
- contents live under `.slim/clonedeps/repos/<safe-dependency-name>/` and are
18
- ignored by git.
19
- - The workflow updates `.gitignore`, `.ignore`, and root `AGENTS.md` with
20
- concise marker sections so cloned source stays out of git but visible to
21
- OpenCode and discoverable by future agents.
7
+ - **Workflow skill, not a command wrapper**: No helper scripts or TypeScript utility functions. The orchestrator owns the decision-making; `@librarian` recommends sources; the orchestrator performs filesystem/git operations directly.
8
+ - **Read-only clones**: Dependencies are cloned into `.slim/clonedeps/repos/` and should not be modified.
9
+ - **Cache strategy**: Existing clones are reused when they satisfy the task. Only fetch when the manifest entry is missing or stale.
10
+ - **Agent-driven**: No runtime TS helpers (no `getClonedDepPath` utility). Agents resolve paths from `.slim/clonedeps.json` if needed.
11
+ - **Configuration**: Uses `.slim/clonedeps.json` in the project root to define which repositories are cloned and their checked-out refs.
22
12
 
23
13
  ## Flow
24
-
25
- 1. Orchestrator checks `.slim/clonedeps.json` first and reuses existing clones
26
- when they satisfy the current task.
27
- 2. Orchestrator asks librarian for a small source-resolution plan across the
28
- repository's actual languages/ecosystems.
29
- 3. Orchestrator verifies refs where possible and asks the user to approve.
30
- 4. Orchestrator clones/fetches each approved source repo once into
31
- `.slim/clonedeps/repos/<safe-repo-name>/`.
32
- 5. Orchestrator writes `.slim/clonedeps.json` with paths, refs, and reasons.
33
- 6. Orchestrator updates `.gitignore`, `.ignore`, and root `AGENTS.md`; the
34
- AGENTS section lists each read-only clone path directly with a one-sentence
35
- purpose.
14
+ 1. **Check existing state**: Read `.slim/clonedeps.json` (if it exists). Check whether each listed `path` exists under `.slim/clonedeps/repos/`.
15
+ 2. **Ask librarian for plan**: Delegate dependency discovery and source resolution to `@librarian`, who returns a small plan (dependency name, repo URL, ref, package subdirectory, reason).
16
+ 3. **Verify and confirm**: Orchestrator verifies refs with `git ls-remote`, avoids unsafe URLs, presents plan to user, and gets approval before cloning.
17
+ 4. **Clone sources**: Orchestrator runs git commands directly. Creates one folder per source under `.slim/clonedeps/repos/<safe-repo-name>/`. Prefers shallow clones, pinned tags, HTTPS URLs.
18
+ 5. **Write local state**: Writes `.slim/clonedeps.json` with structured manifest (version, updatedAt, dependencies array).
19
+ 6. **Update ignore files**: Adds idempotent marker blocks to `.gitignore` and `.ignore`.
20
+ 7. **Register in AGENTS.md**: Appends a `## Cloned Dependency Source` section so future agents know what exists.
36
21
 
37
22
  ## Integration
38
-
39
- - Registered in `src/cli/custom-skills.ts` with orchestrator-only permission.
40
- - Included in release verification via `scripts/verify-release-artifact.ts`.
41
- - Documented in `docs/skills.md` and included in `src/skills/codemap.md`.
23
+ - **Consumed by**: Agents that need to inspect dependency internals (e.g., `@librarian`, `@explorer`) via the registered paths in AGENTS.md.
24
+ - **Depends on**: Git CLI (for `git clone`, `git ls-remote`), `.slim/clonedeps.json` manifest.
25
+ - **Outputs**: Local filesystem paths under `.slim/clonedeps/repos/` for agent inspection.
26
+
27
+ ## Notes
28
+ - Cloned repositories are read-only and should not be edited.
29
+ - The cache directory (`.slim/clonedeps/repos/`) is in the **project directory**, not the user's home directory.
30
+ - This skill is for development and debugging; it does not affect runtime behavior.
31
+ - `.slim/clonedeps.json` is small structured metadata that can be committed.
32
+ - Only `.slim/clonedeps/repos/` is gitignored.
@@ -87,7 +87,7 @@ Once all specific directories are mapped, the Orchestrator must create or update
87
87
 
88
88
  **OpenCode auto-loads `AGENTS.md` into agent context on every session.** To ensure agents automatically discover and use the codemap, update (or create) `AGENTS.md` at the repo root:
89
89
 
90
- 1. If `AGENTS.md` already exists and already contains a `## Repository Map` section, **skip this step** the reference is already set up.
90
+ 1. If `AGENTS.md` already exists and already contains a `## Repository Map` section, **skip this step** - the reference is already set up.
91
91
  2. If `AGENTS.md` exists but has no `## Repository Map` section, **append** the section below.
92
92
  3. If `AGENTS.md` doesn't exist, **create** it with the section below.
93
93
 
@@ -104,7 +104,7 @@ Before working on any task, read `codemap.md` to understand:
104
104
  For deep work on a specific folder, also read that folder's `codemap.md`.
105
105
  ```
106
106
 
107
- This is idempotent repeated codemap runs will detect the existing section and skip. No duplication.
107
+ This is idempotent - repeated codemap runs will detect the existing section and skip. No duplication.
108
108
 
109
109
  ## Codemap Content
110
110