oh-my-opencode-slim 2.0.3 → 2.0.5

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.
@@ -45,6 +45,11 @@ export declare function createDashboardServer(config: DashboardConfig): {
45
45
  answer: string;
46
46
  }> | null;
47
47
  consumeNudgeAction: (interviewId: string) => 'more-questions' | 'confirm-complete' | null;
48
+ consumeBlockComment: (interviewId: string) => {
49
+ section: string;
50
+ comment: string;
51
+ } | null;
52
+ consumeChatMessage: (interviewId: string) => string | null;
48
53
  authToken: string;
49
54
  discoverSessionDirectories: () => Promise<void>;
50
55
  addManualFolder: (dir: string) => void;
@@ -1,4 +1,4 @@
1
- import type { InterviewAnswer, InterviewQuestion, InterviewRecord } from './types';
1
+ import type { InterviewAnswer, InterviewQuestion, InterviewRecord, SpecBlock } from './types';
2
2
  export declare const DEFAULT_OUTPUT_FOLDER = "interview";
3
3
  export declare function normalizeOutputFolder(outputFolder: string): string;
4
4
  export declare function createInterviewDirectoryPath(directory: string, outputFolder: string): string;
@@ -16,6 +16,8 @@ export declare function extractTitle(document: string): string;
16
16
  export declare function buildInterviewDocument(idea: string, summary: string, history: string, meta?: {
17
17
  sessionID?: string;
18
18
  baseMessageCount?: number;
19
+ owner?: string;
20
+ tags?: string[];
19
21
  }): string;
20
22
  /** Parse frontmatter from a .md file. Returns null if no frontmatter. */
21
23
  export declare function parseFrontmatter(content: string): Record<string, string> | null;
@@ -23,3 +25,4 @@ export declare function ensureInterviewFile(record: InterviewRecord): Promise<vo
23
25
  export declare function readInterviewDocument(record: InterviewRecord): Promise<string>;
24
26
  export declare function rewriteInterviewDocument(record: InterviewRecord, summary: string): Promise<string>;
25
27
  export declare function appendInterviewAnswers(record: InterviewRecord, questions: InterviewQuestion[], answers: InterviewAnswer[]): Promise<void>;
28
+ export declare function parseSpecBlocks(markdown: string): SpecBlock[];
@@ -4,6 +4,8 @@ export declare function createInterviewServer(deps: {
4
4
  listInterviewFiles: () => Promise<InterviewFileItem[]>;
5
5
  listInterviews: () => InterviewListItem[];
6
6
  submitAnswers: (interviewId: string, answers: InterviewAnswer[]) => Promise<void>;
7
+ submitBlockComment: (interviewId: string, section: string, comment: string) => Promise<void>;
8
+ submitChat: (interviewId: string, message: string) => Promise<void>;
7
9
  handleNudgeAction: (interviewId: string, action: 'more-questions' | 'confirm-complete') => Promise<void>;
8
10
  outputFolder: string;
9
11
  port: number;
@@ -30,5 +30,7 @@ export declare function createInterviewService(ctx: PluginInput, config?: Interv
30
30
  listInterviewFiles: () => Promise<InterviewFileItem[]>;
31
31
  listInterviews: () => InterviewListItem[];
32
32
  submitAnswers: (interviewId: string, answers: InterviewAnswer[]) => Promise<void>;
33
+ submitBlockComment: (interviewId: string, section: string, comment: string) => Promise<void>;
34
+ submitChat: (interviewId: string, message: string) => Promise<void>;
33
35
  handleNudgeAction: (interviewId: string, action: 'more-questions' | 'confirm-complete') => Promise<void>;
34
36
  };
@@ -61,6 +61,11 @@ export interface InterviewFileItem {
61
61
  sessionID?: string;
62
62
  directory?: string;
63
63
  }
64
+ export interface SpecBlock {
65
+ id: string;
66
+ title: string;
67
+ content: string;
68
+ }
64
69
  export interface InterviewState {
65
70
  interview: InterviewRecord;
66
71
  url: string;
@@ -71,6 +76,7 @@ export interface InterviewState {
71
76
  summary: string;
72
77
  questions: InterviewQuestion[];
73
78
  document: string;
79
+ blocks: SpecBlock[];
74
80
  }
75
81
  /** Wire format for dashboard state cache entries. */
76
82
  export interface InterviewStateEntry {
@@ -93,4 +99,11 @@ export interface InterviewStateEntry {
93
99
  lastUpdatedAt: number;
94
100
  filePath: string;
95
101
  nudgeAction: 'more-questions' | 'confirm-complete' | null;
102
+ pendingBlockComment: {
103
+ section: string;
104
+ comment: string;
105
+ } | null;
106
+ pendingChatMessage: string | null;
107
+ document?: string;
108
+ blocks?: SpecBlock[];
96
109
  }
@@ -33,6 +33,7 @@ export declare class MultiplexerSessionManager {
33
33
  private knownSessions;
34
34
  private spawningSessions;
35
35
  private closingSessions;
36
+ private deferredIdleCloses;
36
37
  private pollInterval?;
37
38
  private enabled;
38
39
  constructor(ctx: PluginInput, config: MultiplexerConfig, backgroundJobBoard?: BackgroundJobBoard | undefined);
@@ -49,6 +50,7 @@ export declare class MultiplexerSessionManager {
49
50
  private updatePolling;
50
51
  private getSessionId;
51
52
  private isRunningBackgroundJob;
53
+ retryDeferredIdleClose(sessionId: string): Promise<void>;
52
54
  cleanup(): Promise<void>;
53
55
  }
54
56
  /**
@@ -1,3 +1,3 @@
1
1
  import { type ToolDefinition } from '@opencode-ai/plugin';
2
- import type { AcpAgentsConfig } from '../config';
2
+ import { type AcpAgentsConfig } from '../config';
3
3
  export declare function createAcpRunTool(agents?: AcpAgentsConfig): ToolDefinition;
@@ -2,14 +2,17 @@ export interface TuiSnapshot {
2
2
  version: 1;
3
3
  updatedAt: number;
4
4
  agentModels: Record<string, string>;
5
+ agentVariants: Record<string, string>;
5
6
  }
6
7
  export declare function getTuiStatePath(): string;
7
8
  export declare function readTuiSnapshot(): TuiSnapshot;
8
9
  export declare function readTuiSnapshotAsync(): Promise<TuiSnapshot>;
9
10
  export declare function recordTuiAgentModels(input: {
10
11
  agentModels: Record<string, string>;
12
+ agentVariants?: Record<string, string>;
11
13
  }): void;
12
14
  export declare function recordTuiAgentModel(input: {
13
15
  agentName: string;
14
16
  model: string;
17
+ variant?: string | null;
15
18
  }): void;
package/dist/tui.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import type { TuiPluginModule } from '@opencode-ai/plugin/tui';
2
2
  import { type TuiSnapshot } from './tui-state';
3
- export declare function formatSidebarModelName(model: string): string;
3
+ export declare function splitSidebarModelId(model: string): {
4
+ provider?: string;
5
+ model: string;
6
+ };
4
7
  export declare function getSidebarAgentNames(snapshot: TuiSnapshot): string[];
5
8
  export declare function readConfigInvalid(directory: string): boolean;
6
9
  declare const plugin: TuiPluginModule & {
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;
@@ -353,6 +353,7 @@ var CompanionConfigSchema = z2.object({
353
353
  debug: z2.boolean().optional().describe("Enable verbose native companion debug logs.")
354
354
  });
355
355
  var AcpAgentPermissionModeSchema = z2.enum(["ask", "allow", "reject"]);
356
+ var MAX_ACP_TIMEOUT_MS = 2147483647;
356
357
  var AcpAgentConfigSchema = z2.object({
357
358
  command: z2.string().min(1),
358
359
  args: z2.array(z2.string()).default([]),
@@ -362,7 +363,7 @@ var AcpAgentConfigSchema = z2.object({
362
363
  prompt: z2.string().min(1).optional(),
363
364
  orchestratorPrompt: z2.string().min(1).optional(),
364
365
  wrapperModel: ProviderModelIdSchema.optional(),
365
- timeoutMs: z2.number().int().min(1000).max(900000).default(300000),
366
+ 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
367
  permissionMode: AcpAgentPermissionModeSchema.default("ask")
367
368
  }).strict();
368
369
  var AcpAgentsConfigSchema = z2.record(z2.string(), AcpAgentConfigSchema);
@@ -396,6 +397,8 @@ var PluginConfigSchema = z2.object({
396
397
  agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
397
398
  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
399
  disabled_mcps: z2.array(z2.string()).optional(),
400
+ 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."),
401
+ 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
402
  multiplexer: MultiplexerConfigSchema.optional(),
400
403
  tmux: TmuxConfigSchema.optional(),
401
404
  websearch: WebsearchConfigSchema.optional(),
@@ -732,7 +735,8 @@ function emptySnapshot() {
732
735
  return {
733
736
  version: 1,
734
737
  updatedAt: Date.now(),
735
- agentModels: {}
738
+ agentModels: {},
739
+ agentVariants: {}
736
740
  };
737
741
  }
738
742
  function parseSnapshot(value) {
@@ -742,7 +746,8 @@ function parseSnapshot(value) {
742
746
  return {
743
747
  version: 1,
744
748
  updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : Date.now(),
745
- agentModels: parsed.agentModels ?? {}
749
+ agentModels: parsed.agentModels ?? {},
750
+ agentVariants: parsed.agentVariants ?? {}
746
751
  };
747
752
  }
748
753
  function readTuiSnapshot() {
@@ -776,14 +781,32 @@ function updateSnapshot(mutator) {
776
781
  function recordTuiAgentModels(input) {
777
782
  updateSnapshot((snapshot) => {
778
783
  snapshot.agentModels = { ...input.agentModels };
784
+ snapshot.agentVariants = { ...input.agentVariants ?? {} };
779
785
  });
780
786
  }
781
787
  function recordTuiAgentModel(input) {
782
788
  updateSnapshot((snapshot) => {
783
789
  snapshot.agentModels[input.agentName] = input.model;
790
+ if (input.variant !== undefined) {
791
+ if (input.variant === null) {
792
+ delete snapshot.agentVariants[input.agentName];
793
+ } else {
794
+ snapshot.agentVariants[input.agentName] = input.variant;
795
+ }
796
+ }
784
797
  });
785
798
  }
786
799
 
800
+ // src/utils/env.ts
801
+ var TRUTHY_ENV_VALUES = new Set(["1", "true", "yes", "on"]);
802
+ var PLUGIN_DISABLE_ENV = "OH_MY_OPENCODE_SLIM_DISABLE";
803
+ function isTruthyEnvValue(value) {
804
+ return TRUTHY_ENV_VALUES.has(value?.trim().toLowerCase() ?? "");
805
+ }
806
+ function isPluginDisabledByEnv(env = process.env) {
807
+ return isTruthyEnvValue(env[PLUGIN_DISABLE_ENV]);
808
+ }
809
+
787
810
  // src/tui.ts
788
811
  var PLUGIN_NAME = "oh-my-opencode-slim";
789
812
  var CONFIG_WARNING_COLOR = "orange";
@@ -816,24 +839,42 @@ function text(props, children) {
816
839
  function box(props, children = []) {
817
840
  return element("box", props, children);
818
841
  }
819
- function truncate(value, max = 24) {
820
- return value.length > max ? `${value.slice(0, max - 1)}…` : value;
821
- }
822
842
  function getTuiDirectory(api) {
823
843
  return api.state?.path?.directory ?? process.cwd();
824
844
  }
825
- function formatSidebarModelName(model) {
826
- const lastSlash = model.lastIndexOf("/");
827
- return lastSlash === -1 ? model : model.slice(lastSlash + 1);
845
+ function splitSidebarModelId(model) {
846
+ const slashIndex = model.indexOf("/");
847
+ if (slashIndex === -1) {
848
+ return { model };
849
+ }
850
+ return {
851
+ provider: model.slice(0, slashIndex),
852
+ model: model.slice(slashIndex + 1)
853
+ };
828
854
  }
829
855
  function getSidebarAgentNames(snapshot) {
830
856
  const configuredAgents = Object.keys(snapshot.agentModels);
831
857
  return configuredAgents.length > 0 ? configuredAgents : FALLBACK_SIDEBAR_AGENTS;
832
858
  }
833
- function row(label, value, theme, valueColor) {
834
- return box({ width: "100%", flexDirection: "row", justifyContent: "space-between" }, [
859
+ function agentRow(label, model, variant, theme) {
860
+ const modelParts = splitSidebarModelId(model);
861
+ const detailRows = [];
862
+ if (modelParts.provider) {
863
+ detailRows.push(agentDetailRow("provider", modelParts.provider, theme));
864
+ }
865
+ detailRows.push(agentDetailRow("model", modelParts.model, theme));
866
+ if (variant) {
867
+ detailRows.push(agentDetailRow("variant", variant, theme));
868
+ }
869
+ return box({ width: "100%", flexDirection: "column", marginBottom: 1 }, [
835
870
  text({ fg: theme.textMuted }, [label]),
836
- text({ fg: valueColor ?? theme.text }, [value])
871
+ ...detailRows
872
+ ]);
873
+ }
874
+ function agentDetailRow(label, value, theme) {
875
+ return box({ width: "100%", flexDirection: "row", paddingLeft: 2 }, [
876
+ text({ fg: theme.textMuted, width: 9 }, [label]),
877
+ text({ fg: theme.textMuted }, [value])
837
878
  ]);
838
879
  }
839
880
  function renderSidebar(snapshot, version, theme, configInvalid) {
@@ -863,7 +904,8 @@ function renderSidebar(snapshot, version, theme, configInvalid) {
863
904
  ]),
864
905
  ...getSidebarAgentNames(snapshot).map((agentName) => {
865
906
  const model = snapshot.agentModels[agentName] ?? "pending";
866
- return row(agentName, truncate(formatSidebarModelName(model), 26), theme, theme.textMuted);
907
+ const variant = snapshot.agentVariants[agentName];
908
+ return agentRow(agentName, model, variant, theme);
867
909
  })
868
910
  ]);
869
911
  }
@@ -893,6 +935,8 @@ function readConfigInvalid(directory) {
893
935
  var plugin = {
894
936
  id: `${PLUGIN_NAME}:tui`,
895
937
  tui: async (api, _options, meta) => {
938
+ if (isPluginDisabledByEnv())
939
+ return;
896
940
  const version = meta.version ?? await readPackageVersion() ?? "dev";
897
941
  let configDirectory = getTuiDirectory(api);
898
942
  let configInvalid = readConfigInvalid(configDirectory);
@@ -923,8 +967,8 @@ var plugin = {
923
967
  };
924
968
  var tui_default = plugin;
925
969
  export {
970
+ splitSidebarModelId,
926
971
  readConfigInvalid,
927
972
  getSidebarAgentNames,
928
- formatSidebarModelName,
929
973
  tui_default as default
930
974
  };
@@ -51,13 +51,16 @@ export interface BackgroundJobStatusInput {
51
51
  lastStatusError?: string;
52
52
  now?: number;
53
53
  }
54
+ type TerminalStateListener = (taskID: string) => void;
54
55
  export declare class BackgroundJobBoard {
55
56
  private readonly jobs;
56
57
  private readonly counters;
58
+ private terminalStateListener?;
57
59
  private readonly maxReusablePerAgent;
58
60
  private readonly readContextMinLines;
59
61
  private readonly readContextMaxFiles;
60
62
  constructor(options?: BackgroundJobBoardOptions);
63
+ setTerminalStateListener(listener?: TerminalStateListener): void;
61
64
  registerLaunch(input: BackgroundJobLaunchInput): BackgroundJobRecord;
62
65
  updateStatus(input: BackgroundJobStatusInput): BackgroundJobRecord | undefined;
63
66
  updateFromStatusOutput(output: string): BackgroundJobRecord | undefined;
@@ -87,3 +90,4 @@ export declare function deriveTaskSessionLabel(input: {
87
90
  prompt?: string;
88
91
  agentType: string;
89
92
  }): string;
93
+ 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;
@@ -197,6 +197,20 @@
197
197
  "type": "string"
198
198
  }
199
199
  },
200
+ "disabled_tools": {
201
+ "description": "Tool names to disable completely. Disabled tools are not registered with OpenCode and cannot be used by agents.",
202
+ "type": "array",
203
+ "items": {
204
+ "type": "string"
205
+ }
206
+ },
207
+ "disabled_skills": {
208
+ "description": "Skill names to disable completely. Disabled skills are not granted to agents, even when referenced by presets or agent overrides.",
209
+ "type": "array",
210
+ "items": {
211
+ "type": "string"
212
+ }
213
+ },
200
214
  "multiplexer": {
201
215
  "type": "object",
202
216
  "properties": {
@@ -515,10 +529,11 @@
515
529
  "pattern": "^[^/\\s]+\\/[^\\s]+$"
516
530
  },
517
531
  "timeoutMs": {
518
- "default": 300000,
532
+ "default": 0,
533
+ "description": "Timeout for a single ACP run in milliseconds. Set to 0 to disable the timeout.",
519
534
  "type": "integer",
520
- "minimum": 1000,
521
- "maximum": 900000
535
+ "minimum": 0,
536
+ "maximum": 2147483647
522
537
  },
523
538
  "permissionMode": {
524
539
  "default": "ask",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode-slim",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
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",