oh-my-opencode-slim 0.8.4 → 0.8.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.
package/dist/index.js CHANGED
@@ -3410,7 +3410,7 @@ var require_index_min = __commonJS((exports) => {
3410
3410
  // node_modules/which/lib/index.js
3411
3411
  var require_lib = __commonJS((exports, module) => {
3412
3412
  var { isexe, sync: isexeSync } = require_index_min();
3413
- var { join: join11, delimiter, sep, posix } = __require("path");
3413
+ var { join: join9, delimiter, sep, posix } = __require("path");
3414
3414
  var isWindows = process.platform === "win32";
3415
3415
  var rSlash = new RegExp(`[${posix.sep}${sep === posix.sep ? "" : sep}]`.replace(/(\\)/g, "\\$1"));
3416
3416
  var rRel = new RegExp(`^\\.${rSlash.source}`);
@@ -3437,7 +3437,7 @@ var require_lib = __commonJS((exports, module) => {
3437
3437
  var getPathPart = (raw, cmd) => {
3438
3438
  const pathPart = /^".*"$/.test(raw) ? raw.slice(1, -1) : raw;
3439
3439
  const prefix = !pathPart && rRel.test(cmd) ? cmd.slice(0, 2) : "";
3440
- return prefix + join11(pathPart, cmd);
3440
+ return prefix + join9(pathPart, cmd);
3441
3441
  };
3442
3442
  var which = async (cmd, opt = {}) => {
3443
3443
  const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
@@ -3502,12 +3502,11 @@ function getCustomOpenCodeConfigDir() {
3502
3502
  const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
3503
3503
  return configDir || undefined;
3504
3504
  }
3505
- function getConfigDir() {
3506
- const customConfigDir = getCustomOpenCodeConfigDir();
3507
- if (customConfigDir) {
3508
- return customConfigDir;
3509
- }
3510
- return getDefaultOpenCodeConfigDir();
3505
+ function getConfigSearchDirs() {
3506
+ const dirs = [getCustomOpenCodeConfigDir(), getDefaultOpenCodeConfigDir()];
3507
+ return dirs.filter((dir, index) => {
3508
+ return Boolean(dir) && dirs.indexOf(dir) === index;
3509
+ });
3511
3510
  }
3512
3511
  function getOpenCodeConfigPaths() {
3513
3512
  const configDir = getDefaultOpenCodeConfigDir();
@@ -3600,17 +3599,31 @@ var SUBAGENT_NAMES = [
3600
3599
  "librarian",
3601
3600
  "oracle",
3602
3601
  "designer",
3603
- "fixer"
3602
+ "fixer",
3603
+ "council",
3604
+ "councillor",
3605
+ "council-master"
3604
3606
  ];
3605
3607
  var ORCHESTRATOR_NAME = "orchestrator";
3606
3608
  var ALL_AGENT_NAMES = [ORCHESTRATOR_NAME, ...SUBAGENT_NAMES];
3609
+ var ORCHESTRATABLE_AGENTS = [
3610
+ "explorer",
3611
+ "librarian",
3612
+ "oracle",
3613
+ "designer",
3614
+ "fixer",
3615
+ "council"
3616
+ ];
3607
3617
  var SUBAGENT_DELEGATION_RULES = {
3608
- orchestrator: SUBAGENT_NAMES,
3618
+ orchestrator: ORCHESTRATABLE_AGENTS,
3609
3619
  fixer: [],
3610
3620
  designer: [],
3611
3621
  explorer: [],
3612
3622
  librarian: [],
3613
- oracle: []
3623
+ oracle: [],
3624
+ council: [],
3625
+ councillor: [],
3626
+ "council-master": []
3614
3627
  };
3615
3628
  var DEFAULT_MODELS = {
3616
3629
  orchestrator: undefined,
@@ -3618,55 +3631,21 @@ var DEFAULT_MODELS = {
3618
3631
  librarian: "openai/gpt-5.4-mini",
3619
3632
  explorer: "openai/gpt-5.4-mini",
3620
3633
  designer: "openai/gpt-5.4-mini",
3621
- fixer: "openai/gpt-5.4-mini"
3634
+ fixer: "openai/gpt-5.4-mini",
3635
+ council: "openai/gpt-5.4-mini",
3636
+ councillor: "openai/gpt-5.4-mini",
3637
+ "council-master": "openai/gpt-5.4-mini"
3622
3638
  };
3623
3639
  var POLL_INTERVAL_BACKGROUND_MS = 2000;
3624
3640
  var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
3625
3641
  var MAX_POLL_TIME_MS = 5 * 60 * 1000;
3626
3642
  var FALLBACK_FAILOVER_TIMEOUT_MS = 15000;
3627
- // src/config/loader.ts
3628
- import * as fs from "fs";
3629
- import * as path from "path";
3630
-
3631
- // src/config/agent-mcps.ts
3632
- var DEFAULT_AGENT_MCPS = {
3633
- orchestrator: ["websearch"],
3634
- designer: [],
3635
- oracle: [],
3636
- librarian: ["websearch", "context7", "grep_app"],
3637
- explorer: [],
3638
- fixer: []
3639
- };
3640
- function parseList(items, allAvailable) {
3641
- if (!items || items.length === 0) {
3642
- return [];
3643
- }
3644
- const allow = items.filter((i) => !i.startsWith("!"));
3645
- const deny = items.filter((i) => i.startsWith("!")).map((i) => i.slice(1));
3646
- if (deny.includes("*")) {
3647
- return [];
3648
- }
3649
- if (allow.includes("*")) {
3650
- return allAvailable.filter((item) => !deny.includes(item));
3651
- }
3652
- return allow.filter((item) => !deny.includes(item));
3653
- }
3654
- function getAgentMcpList(agentName, config) {
3655
- const agentConfig = getAgentOverride(config, agentName);
3656
- if (agentConfig?.mcps !== undefined) {
3657
- return agentConfig.mcps;
3658
- }
3659
- const defaultMcps = DEFAULT_AGENT_MCPS[agentName];
3660
- return defaultMcps ?? [];
3661
- }
3662
-
3663
- // src/cli/config-io.ts
3664
- function stripJsonComments(json) {
3665
- const commentPattern = /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
3666
- const trailingCommaPattern = /\\"|"(?:\\"|[^"])*"|(,)(\s*[}\]])/g;
3667
- return json.replace(commentPattern, (match, commentGroup) => commentGroup ? "" : match).replace(trailingCommaPattern, (match, comma, closing) => comma ? closing : match);
3668
- }
3669
-
3643
+ var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
3644
+ var PHASE_REMINDER_TEXT = `Recall Workflow Rules:
3645
+ Understand \u2192 build the best path (delegated based on Agent rules, split and parallelized as much as possible) \u2192 execute \u2192 verify.
3646
+ If delegating, launch the specialist in the same turn you mention it.`;
3647
+ var TMUX_SPAWN_DELAY_MS = 500;
3648
+ var COUNCILLOR_STAGGER_MS = 250;
3670
3649
  // node_modules/zod/v4/classic/external.js
3671
3650
  var exports_external = {};
3672
3651
  __export(exports_external, {
@@ -17199,6 +17178,107 @@ function date4(params) {
17199
17178
 
17200
17179
  // node_modules/zod/v4/classic/external.js
17201
17180
  config(en_default());
17181
+ // src/config/council-schema.ts
17182
+ var ModelIdSchema = exports_external.string().regex(/^[^/\s]+\/[^\s]+$/, 'Expected provider/model format (e.g. "openai/gpt-5.4-mini")');
17183
+ var CouncillorConfigSchema = exports_external.object({
17184
+ model: ModelIdSchema.describe('Model ID in provider/model format (e.g. "openai/gpt-5.4-mini")'),
17185
+ variant: exports_external.string().optional(),
17186
+ prompt: exports_external.string().optional().describe("Optional role/guidance injected into the councillor user prompt")
17187
+ });
17188
+ var PresetMasterOverrideSchema = exports_external.object({
17189
+ model: ModelIdSchema.optional().describe("Override the master model for this preset"),
17190
+ variant: exports_external.string().optional().describe("Override the master variant for this preset"),
17191
+ prompt: exports_external.string().optional().describe("Override the master synthesis guidance for this preset")
17192
+ });
17193
+ var CouncilPresetSchema = exports_external.record(exports_external.string(), exports_external.record(exports_external.string(), exports_external.unknown())).transform((entries, ctx) => {
17194
+ const councillors = {};
17195
+ let masterOverride;
17196
+ for (const [key, raw] of Object.entries(entries)) {
17197
+ if (key === "master") {
17198
+ const parsed = PresetMasterOverrideSchema.safeParse(raw);
17199
+ if (!parsed.success) {
17200
+ ctx.addIssue(`Invalid master override in preset: ${parsed.error.issues.map((i) => i.message).join(", ")}`);
17201
+ return exports_external.NEVER;
17202
+ }
17203
+ masterOverride = parsed.data;
17204
+ } else {
17205
+ const parsed = CouncillorConfigSchema.safeParse(raw);
17206
+ if (!parsed.success) {
17207
+ ctx.addIssue(`Invalid councillor "${key}": ${parsed.error.issues.map((i) => i.message).join(", ")}`);
17208
+ return exports_external.NEVER;
17209
+ }
17210
+ councillors[key] = parsed.data;
17211
+ }
17212
+ }
17213
+ return { councillors, master: masterOverride };
17214
+ });
17215
+ var CouncilMasterConfigSchema = exports_external.object({
17216
+ model: ModelIdSchema.describe('Model ID for the council master (e.g. "anthropic/claude-opus-4-6")'),
17217
+ variant: exports_external.string().optional(),
17218
+ prompt: exports_external.string().optional().describe("Optional role/guidance injected into the master synthesis prompt")
17219
+ });
17220
+ var CouncilConfigSchema = exports_external.object({
17221
+ master: CouncilMasterConfigSchema,
17222
+ presets: exports_external.record(exports_external.string(), CouncilPresetSchema),
17223
+ master_timeout: exports_external.number().min(0).default(300000),
17224
+ councillors_timeout: exports_external.number().min(0).default(180000),
17225
+ default_preset: exports_external.string().default("default"),
17226
+ master_fallback: exports_external.array(ModelIdSchema).optional().transform((val) => {
17227
+ if (!val)
17228
+ return val;
17229
+ const unique = [...new Set(val)];
17230
+ if (unique.length !== val.length) {
17231
+ return unique;
17232
+ }
17233
+ return val;
17234
+ }).describe("Fallback models for the council master. Tried in order if the primary model fails. " + 'Example: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4"]')
17235
+ });
17236
+ // src/config/loader.ts
17237
+ import * as fs from "fs";
17238
+ import * as path from "path";
17239
+
17240
+ // src/config/agent-mcps.ts
17241
+ var DEFAULT_AGENT_MCPS = {
17242
+ orchestrator: ["websearch"],
17243
+ designer: [],
17244
+ oracle: [],
17245
+ librarian: ["websearch", "context7", "grep_app"],
17246
+ explorer: [],
17247
+ fixer: [],
17248
+ council: [],
17249
+ councillor: [],
17250
+ "council-master": []
17251
+ };
17252
+ function parseList(items, allAvailable) {
17253
+ if (!items || items.length === 0) {
17254
+ return [];
17255
+ }
17256
+ const allow = items.filter((i) => !i.startsWith("!"));
17257
+ const deny = items.filter((i) => i.startsWith("!")).map((i) => i.slice(1));
17258
+ if (deny.includes("*")) {
17259
+ return [];
17260
+ }
17261
+ if (allow.includes("*")) {
17262
+ return allAvailable.filter((item) => !deny.includes(item));
17263
+ }
17264
+ return allow.filter((item) => !deny.includes(item));
17265
+ }
17266
+ function getAgentMcpList(agentName, config2) {
17267
+ const agentConfig = getAgentOverride(config2, agentName);
17268
+ if (agentConfig?.mcps !== undefined) {
17269
+ return agentConfig.mcps;
17270
+ }
17271
+ const defaultMcps = DEFAULT_AGENT_MCPS[agentName];
17272
+ return defaultMcps ?? [];
17273
+ }
17274
+
17275
+ // src/cli/config-io.ts
17276
+ function stripJsonComments(json2) {
17277
+ const commentPattern = /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g;
17278
+ const trailingCommaPattern = /\\"|"(?:\\"|[^"])*"|(,)(\s*[}\]])/g;
17279
+ return json2.replace(commentPattern, (match, commentGroup) => commentGroup ? "" : match).replace(trailingCommaPattern, (match, comma, closing) => comma ? closing : match);
17280
+ }
17281
+
17202
17282
  // src/config/schema.ts
17203
17283
  var ProviderModelIdSchema = exports_external.string().regex(/^[^/\s]+\/[^\s]+$/, "Expected provider/model format (provider/.../model)");
17204
17284
  var ManualAgentPlanSchema = exports_external.object({
@@ -17287,7 +17367,8 @@ var PluginConfigSchema = exports_external.object({
17287
17367
  disabled_mcps: exports_external.array(exports_external.string()).optional(),
17288
17368
  tmux: TmuxConfigSchema.optional(),
17289
17369
  background: BackgroundTaskConfigSchema.optional(),
17290
- fallback: FailoverConfigSchema.optional()
17370
+ fallback: FailoverConfigSchema.optional(),
17371
+ council: CouncilConfigSchema.optional()
17291
17372
  });
17292
17373
 
17293
17374
  // src/config/loader.ts
@@ -17321,6 +17402,15 @@ function findConfigPath(basePath) {
17321
17402
  }
17322
17403
  return null;
17323
17404
  }
17405
+ function findConfigPathInDirs(configDirs, baseName) {
17406
+ for (const configDir of configDirs) {
17407
+ const configPath = findConfigPath(path.join(configDir, baseName));
17408
+ if (configPath) {
17409
+ return configPath;
17410
+ }
17411
+ }
17412
+ return null;
17413
+ }
17324
17414
  function deepMerge(base, override) {
17325
17415
  if (!base)
17326
17416
  return override;
@@ -17339,9 +17429,8 @@ function deepMerge(base, override) {
17339
17429
  return result;
17340
17430
  }
17341
17431
  function loadPluginConfig(directory) {
17342
- const userConfigBasePath = path.join(getConfigDir(), "oh-my-opencode-slim");
17432
+ const userConfigPath = findConfigPathInDirs(getConfigSearchDirs(), "oh-my-opencode-slim");
17343
17433
  const projectConfigBasePath = path.join(directory, ".opencode", "oh-my-opencode-slim");
17344
- const userConfigPath = findConfigPath(userConfigBasePath);
17345
17434
  const projectConfigPath = findConfigPath(projectConfigBasePath);
17346
17435
  let config2 = userConfigPath ? loadConfigFromPath(userConfigPath) ?? {} : {};
17347
17436
  const projectConfig = projectConfigPath ? loadConfigFromPath(projectConfigPath) : null;
@@ -17372,8 +17461,10 @@ function loadPluginConfig(directory) {
17372
17461
  }
17373
17462
  function loadAgentPrompt(agentName, preset) {
17374
17463
  const presetDirName = preset && /^[a-zA-Z0-9_-]+$/.test(preset) ? preset : undefined;
17375
- const promptsDir = path.join(getConfigDir(), PROMPTS_DIR_NAME);
17376
- const promptSearchDirs = presetDirName ? [path.join(promptsDir, presetDirName), promptsDir] : [promptsDir];
17464
+ const promptSearchDirs = getConfigSearchDirs().flatMap((configDir) => {
17465
+ const promptsDir = path.join(configDir, PROMPTS_DIR_NAME);
17466
+ return presetDirName ? [path.join(promptsDir, presetDirName), promptsDir] : [promptsDir];
17467
+ });
17377
17468
  const result = {};
17378
17469
  const readFirstPrompt = (fileName, errorPrefix) => {
17379
17470
  for (const dir of promptSearchDirs) {
@@ -17398,6 +17489,418 @@ function getAgentOverride(config2, name) {
17398
17489
  const overrides = config2?.agents ?? {};
17399
17490
  return overrides[name] ?? overrides[Object.keys(AGENT_ALIASES).find((k) => AGENT_ALIASES[k] === name) ?? ""];
17400
17491
  }
17492
+ // src/utils/session.ts
17493
+ function shortModelLabel(model) {
17494
+ return model.split("/").pop() ?? model;
17495
+ }
17496
+ function parseModelReference(model) {
17497
+ const slashIndex = model.indexOf("/");
17498
+ if (slashIndex <= 0 || slashIndex >= model.length - 1) {
17499
+ return null;
17500
+ }
17501
+ return {
17502
+ providerID: model.slice(0, slashIndex),
17503
+ modelID: model.slice(slashIndex + 1)
17504
+ };
17505
+ }
17506
+ async function promptWithTimeout(client, args, timeoutMs) {
17507
+ if (timeoutMs <= 0) {
17508
+ await client.session.prompt(args);
17509
+ return;
17510
+ }
17511
+ const sessionId = args.path.id;
17512
+ let timer;
17513
+ try {
17514
+ const promptPromise = client.session.prompt(args);
17515
+ promptPromise.catch(() => {});
17516
+ await Promise.race([
17517
+ promptPromise,
17518
+ new Promise((_, reject) => {
17519
+ timer = setTimeout(() => {
17520
+ client.session.abort({ path: { id: sessionId } }).catch(() => {});
17521
+ reject(new Error(`Prompt timed out after ${timeoutMs}ms`));
17522
+ }, timeoutMs);
17523
+ })
17524
+ ]);
17525
+ } finally {
17526
+ clearTimeout(timer);
17527
+ }
17528
+ }
17529
+ async function extractSessionResult(client, sessionId, options) {
17530
+ const includeReasoning = options?.includeReasoning ?? true;
17531
+ const messagesResult = await client.session.messages({
17532
+ path: { id: sessionId }
17533
+ });
17534
+ const messages = messagesResult.data ?? [];
17535
+ const assistantMessages = messages.filter((m) => m.info?.role === "assistant");
17536
+ const extractedContent = [];
17537
+ for (const message of assistantMessages) {
17538
+ for (const part of message.parts ?? []) {
17539
+ const allowed = includeReasoning ? part.type === "text" || part.type === "reasoning" : part.type === "text";
17540
+ if (allowed && part.text) {
17541
+ extractedContent.push(part.text);
17542
+ }
17543
+ }
17544
+ }
17545
+ return extractedContent.filter((t) => t.length > 0).join(`
17546
+
17547
+ `);
17548
+ }
17549
+
17550
+ // src/agents/orchestrator.ts
17551
+ function resolvePrompt(base, customPrompt, customAppendPrompt) {
17552
+ if (customPrompt)
17553
+ return customPrompt;
17554
+ if (customAppendPrompt)
17555
+ return `${base}
17556
+
17557
+ ${customAppendPrompt}`;
17558
+ return base;
17559
+ }
17560
+ var ORCHESTRATOR_PROMPT = `<Role>
17561
+ You are an AI coding orchestrator that optimizes for quality, speed, cost, and reliability by delegating to specialists when it provides net efficiency gains.
17562
+ </Role>
17563
+
17564
+ <Agents>
17565
+
17566
+ @explorer
17567
+ - Role: Parallel search specialist for discovering unknowns across the codebase
17568
+ - Stats: 3x faster codebase search than orchestrator, 1/2 cost of orchestrator
17569
+ - Capabilities: Glob, grep, AST queries to locate files, symbols, patterns
17570
+ - **Delegate when:** Need to discover what exists before planning \u2022 Parallel searches speed discovery \u2022 Need summarized map vs full contents \u2022 Broad/uncertain scope
17571
+ - **Don't delegate when:** Know the path and need actual content \u2022 Need full file anyway \u2022 Single specific lookup \u2022 About to edit the file
17572
+
17573
+ @librarian
17574
+ - Role: Authoritative source for current library docs and API references
17575
+ - Stats: 10x better finding up-to-date library docs than orchestrator, 1/2 cost of orchestrator
17576
+ - Capabilities: Fetches latest official docs, examples, API signatures, version-specific behavior via grep_app MCP
17577
+ - **Delegate when:** Libraries with frequent API changes (React, Next.js, AI SDKs) \u2022 Complex APIs needing official examples (ORMs, auth) \u2022 Version-specific behavior matters \u2022 Unfamiliar library \u2022 Edge cases or advanced features \u2022 Nuanced best practices
17578
+ - **Don't delegate when:** Standard usage you're confident about (\`Array.map()\`, \`fetch()\`) \u2022 Simple stable APIs \u2022 General programming knowledge \u2022 Info already in conversation \u2022 Built-in language features
17579
+ - **Rule of thumb:** "How does this library work?" \u2192 @librarian. "How does programming work?" \u2192 yourself.
17580
+
17581
+ @oracle
17582
+ - Role: Strategic advisor for high-stakes decisions and persistent problems
17583
+ - Stats: 10x better decisions maker, problem solver, investigator than orchestrator, 0.8x speed of orchestrator, same cost.
17584
+ - Capabilities: Deep architectural reasoning, system-level trade-offs, complex debugging
17585
+ - Tools/Constraints: Slow, expensive, high-quality\u2014use sparingly when thoroughness beats speed
17586
+ - **Delegate when:** Major architectural decisions with long-term impact \u2022 Problems persisting after 2+ fix attempts \u2022 High-risk multi-system refactors \u2022 Costly trade-offs (performance vs maintainability) \u2022 Complex debugging with unclear root cause \u2022 Security/scalability/data integrity decisions \u2022 Genuinely uncertain and cost of wrong choice is high
17587
+ - **Don't delegate when:** Routine decisions you're confident about \u2022 First bug fix attempt \u2022 Straightforward trade-offs \u2022 Tactical "how" vs strategic "should" \u2022 Time-sensitive good-enough decisions \u2022 Quick research/testing can answer
17588
+ - **Rule of thumb:** Need senior architect review? \u2192 @oracle. Just do it and PR? \u2192 yourself.
17589
+
17590
+ @designer
17591
+ - Role: UI/UX specialist for intentional, polished experiences
17592
+ - Stats: 10x better UI/UX than orchestrator
17593
+ - Capabilities: Visual direction, interactions, responsive layouts, design systems with aesthetic intent
17594
+ - **Delegate when:** User-facing interfaces needing polish \u2022 Responsive layouts \u2022 UX-critical components (forms, nav, dashboards) \u2022 Visual consistency systems \u2022 Animations/micro-interactions \u2022 Landing/marketing pages \u2022 Refining functional\u2192delightful
17595
+ - **Don't delegate when:** Backend/logic with no visual \u2022 Quick prototypes where design doesn't matter yet
17596
+ - **Rule of thumb:** Users see it and polish matters? \u2192 @designer. Headless/functional? \u2192 yourself.
17597
+
17598
+ @fixer
17599
+ - Role: Fast execution specialist for well-defined tasks, which empowers orchestrator with parallel, speedy executions
17600
+ - Stats: 2x faster code edits, 1/2 cost of orchestrator, 0.8x quality of orchestrator
17601
+ - Tools/Constraints: Execution-focused\u2014no research, no architectural decisions
17602
+ - **Delegate when:** For implementation work, think and triage first. If the change is non-trivial or multi-file, hand bounded execution to @fixer
17603
+ - **Don't delegate when:** Needs discovery/research/decisions \u2022 Single small change (<20 lines, one file) \u2022 Unclear requirements needing iteration \u2022 Explaining to fixer > doing \u2022 Tight integration with your current work \u2022 Sequential dependencies
17604
+ - **Rule of thumb:** Explaining > doing? \u2192 yourself. Orchestrator paths selection is vastly improved by Fixer. eg it can reduce overall speed if Orchestrator splits what's usually a single task into multiple subtasks and parallelize it with fixer.
17605
+
17606
+ @council
17607
+ - Role: Multi-LLM consensus engine for high-confidence answers
17608
+ - Stats: 3x slower than orchestrator, 3x or more cost of orchestrator
17609
+ - Capabilities: Runs multiple models in parallel, synthesizes their responses via a council master
17610
+ - **Delegate when:** Critical decisions needing diverse model perspectives \u2022 High-stakes architectural choices where consensus reduces risk \u2022 Ambiguous problems where multi-model disagreement is informative \u2022 Security-sensitive design reviews
17611
+ - **Don't delegate when:** Straightforward tasks you're confident about \u2022 Speed matters more than confidence \u2022 Single-model answer is sufficient \u2022 Routine implementation work
17612
+ - **Result handling:** Present the council's synthesized response verbatim. Do not re-summarize \u2014 the council master has already produced the final answer.
17613
+ - **Rule of thumb:** Need second/third opinions from different models? \u2192 @council. One good answer enough? \u2192 yourself.
17614
+
17615
+ </Agents>
17616
+
17617
+ <Workflow>
17618
+
17619
+ ## 1. Understand
17620
+ Parse request: explicit requirements + implicit needs.
17621
+
17622
+ ## 2. Path Selection
17623
+ Evaluate approach by: quality, speed, cost, reliability.
17624
+ Choose the path that optimizes all four.
17625
+
17626
+ ## 3. Delegation Check
17627
+ **STOP. Review specialists before acting.**
17628
+
17629
+ !!! Review available agents and delegation rules. Decide whether to delegate or do it yourself. !!!
17630
+
17631
+ **Delegation efficiency:**
17632
+ - Reference paths/lines, don't paste files (\`src/app.ts:42\` not full contents)
17633
+ - Provide context summaries, let specialists read what they need
17634
+ - Brief user on delegation goal before each call
17635
+ - Skip delegation if overhead \u2265 doing it yourself
17636
+
17637
+ ## 4. Split and Parallelize
17638
+ Can tasks be split into subtasks and run in parallel?
17639
+ - Multiple @explorer searches across different domains?
17640
+ - @explorer + @librarian research in parallel?
17641
+ - Multiple @fixer instances for faster, scoped implementation?
17642
+
17643
+ Balance: respect dependencies, avoid parallelizing what must be sequential.
17644
+
17645
+ ## 5. Execute
17646
+ 1. Break complex tasks into todos
17647
+ 2. Fire parallel research/implementation
17648
+ 3. Delegate to specialists or do it yourself based on step 3
17649
+ 4. Integrate results
17650
+ 5. Adjust if needed
17651
+
17652
+ ## 6. Verify
17653
+ - Run \`lsp_diagnostics\` for errors
17654
+ - Suggest \`simplify\` skill when applicable
17655
+ - Confirm specialists completed successfully
17656
+ - Verify solution meets requirements
17657
+
17658
+ </Workflow>
17659
+
17660
+ <Communication>
17661
+
17662
+ ## Clarity Over Assumptions
17663
+ - If request is vague or has multiple valid interpretations, ask a targeted question before proceeding
17664
+ - Don't guess at critical details (file paths, API choices, architectural decisions)
17665
+ - Do make reasonable assumptions for minor details and state them briefly
17666
+
17667
+ ## Concise Execution
17668
+ - Answer directly, no preamble
17669
+ - Don't summarize what you did unless asked
17670
+ - Don't explain code unless asked
17671
+ - One-word answers are fine when appropriate
17672
+ - Brief delegation notices: "Checking docs via @librarian..." not "I'm going to delegate to @librarian because..."
17673
+
17674
+ ## No Flattery
17675
+ Never: "Great question!" "Excellent idea!" "Smart choice!" or any praise of user input.
17676
+
17677
+ ## Honest Pushback
17678
+ When user's approach seems problematic:
17679
+ - State concern + alternative concisely
17680
+ - Ask if they want to proceed anyway
17681
+ - Don't lecture, don't blindly implement
17682
+
17683
+ ## Example
17684
+ **Bad:** "Great question! Let me think about the best approach here. I'm going to delegate to @librarian to check the latest Next.js documentation for the App Router, and then I'll implement the solution for you."
17685
+
17686
+ **Good:** "Checking Next.js App Router docs via @librarian..."
17687
+ [proceeds with implementation]
17688
+
17689
+ </Communication>
17690
+ `;
17691
+ function createOrchestratorAgent(model, customPrompt, customAppendPrompt) {
17692
+ const prompt = resolvePrompt(ORCHESTRATOR_PROMPT, customPrompt, customAppendPrompt);
17693
+ const definition = {
17694
+ name: "orchestrator",
17695
+ description: "AI coding orchestrator that delegates tasks to specialist agents for optimal quality, speed, and cost",
17696
+ config: {
17697
+ temperature: 0.1,
17698
+ prompt
17699
+ }
17700
+ };
17701
+ if (Array.isArray(model)) {
17702
+ definition._modelArray = model.map((m) => typeof m === "string" ? { id: m } : m);
17703
+ } else if (typeof model === "string" && model) {
17704
+ definition.config.model = model;
17705
+ }
17706
+ return definition;
17707
+ }
17708
+
17709
+ // src/agents/council.ts
17710
+ var COUNCIL_AGENT_PROMPT = `You are the Council agent \u2014 a multi-LLM orchestration system that runs consensus across multiple models.
17711
+
17712
+ **Tool**: You have access to the \`council_session\` tool.
17713
+
17714
+ **When to use**:
17715
+ - When invoked by a user with a request
17716
+ - When you want multiple expert opinions on a complex problem
17717
+ - When higher confidence is needed through model consensus
17718
+
17719
+ **Usage**:
17720
+ 1. Call the \`council_session\` tool with the user's prompt
17721
+ 2. Optionally specify a preset (default: "default")
17722
+ 3. Receive the synthesized response from the council master
17723
+ 4. Present the result to the user
17724
+
17725
+ **Behavior**:
17726
+ - Delegate requests directly to council_session
17727
+ - Don't pre-analyze or filter the prompt
17728
+ - Present the synthesized result verbatim \u2014 do not re-summarize or condense
17729
+ - Briefly explain the consensus if requested`;
17730
+ function createCouncilAgent(model, customPrompt, customAppendPrompt) {
17731
+ const prompt = resolvePrompt(COUNCIL_AGENT_PROMPT, customPrompt, customAppendPrompt);
17732
+ const definition = {
17733
+ name: "council",
17734
+ description: "Multi-LLM council agent that synthesizes responses from multiple models for higher-quality outputs",
17735
+ config: {
17736
+ temperature: 0.1,
17737
+ prompt
17738
+ }
17739
+ };
17740
+ if (model) {
17741
+ definition.config.model = model;
17742
+ }
17743
+ return definition;
17744
+ }
17745
+ function formatCouncillorPrompt(userPrompt, councillorPrompt) {
17746
+ if (!councillorPrompt)
17747
+ return userPrompt;
17748
+ return `${councillorPrompt}
17749
+
17750
+ ---
17751
+
17752
+ ${userPrompt}`;
17753
+ }
17754
+ function formatMasterSynthesisPrompt(originalPrompt, councillorResults, masterPrompt) {
17755
+ const completedWithResults = councillorResults.filter((cr) => cr.status === "completed" && cr.result);
17756
+ const councillorSection = completedWithResults.map((cr) => {
17757
+ const shortModel = shortModelLabel(cr.model);
17758
+ return `**${cr.name}** (${shortModel}):
17759
+ ${cr.result}`;
17760
+ }).join(`
17761
+
17762
+ `);
17763
+ const failedSection = councillorResults.filter((cr) => cr.status !== "completed").map((cr) => `**${cr.name}**: ${cr.status} \u2014 ${cr.error ?? "Unknown"}`).join(`
17764
+ `);
17765
+ if (completedWithResults.length === 0) {
17766
+ return `---
17767
+
17768
+ **Original Prompt**:
17769
+ ${originalPrompt}
17770
+
17771
+ ---
17772
+
17773
+ **Councillor Responses**:
17774
+ All councillors failed to produce output. Please generate a response based on the original prompt alone.`;
17775
+ }
17776
+ let prompt = `---
17777
+
17778
+ **Original Prompt**:
17779
+ ${originalPrompt}
17780
+
17781
+ ---
17782
+
17783
+ **Councillor Responses**:
17784
+ ${councillorSection}`;
17785
+ if (failedSection) {
17786
+ prompt += `
17787
+
17788
+ ---
17789
+
17790
+ **Failed/Timed-out Councillors**:
17791
+ ${failedSection}`;
17792
+ }
17793
+ prompt += `
17794
+
17795
+ ---
17796
+
17797
+ Synthesize the optimal response based on the above.`;
17798
+ if (masterPrompt) {
17799
+ prompt += `
17800
+
17801
+ ---
17802
+
17803
+ **Master Guidance**:
17804
+ ${masterPrompt}`;
17805
+ }
17806
+ return prompt;
17807
+ }
17808
+
17809
+ // src/agents/council-master.ts
17810
+ var COUNCIL_MASTER_PROMPT = `You are the council master responsible for synthesizing responses from multiple AI models.
17811
+
17812
+ **Role**: Review all councillor responses and create the optimal final answer.
17813
+
17814
+ **Process**:
17815
+ 1. Read the original user prompt
17816
+ 2. Review each councillor's response carefully
17817
+ 3. Identify the best elements from each response
17818
+ 4. Resolve contradictions between councillors
17819
+ 5. Synthesize a final, optimal response
17820
+
17821
+ **Behavior**:
17822
+ - Each councillor had read-only access to the codebase \u2014 their responses may reference specific files, functions, and line numbers
17823
+ - Clearly explain your reasoning for the chosen approach
17824
+ - Be transparent about trade-offs
17825
+ - Credit specific insights from individual councillors by name
17826
+ - If councillors disagree, explain your resolution
17827
+ - Don't just average responses \u2014 choose and improve
17828
+
17829
+ **Output**:
17830
+ - Present the synthesized solution
17831
+ - Review, retain, and include relevant code examples, diagrams, and concrete details from councillor responses
17832
+ - Explain your synthesis reasoning
17833
+ - Note any remaining uncertainties
17834
+ - Acknowledge if consensus was impossible`;
17835
+ function createCouncilMasterAgent(model, customPrompt, customAppendPrompt) {
17836
+ const prompt = resolvePrompt(COUNCIL_MASTER_PROMPT, customPrompt, customAppendPrompt);
17837
+ return {
17838
+ name: "council-master",
17839
+ description: "Council synthesis engine. Receives councillor responses and produces the final answer. No tools, pure text synthesis.",
17840
+ config: {
17841
+ model,
17842
+ temperature: 0.1,
17843
+ prompt,
17844
+ permission: {
17845
+ "*": "deny",
17846
+ question: "deny"
17847
+ }
17848
+ }
17849
+ };
17850
+ }
17851
+
17852
+ // src/agents/councillor.ts
17853
+ var COUNCILLOR_PROMPT = `You are a councillor in a multi-model council.
17854
+
17855
+ **Role**: Provide your best independent analysis and solution to the given problem.
17856
+
17857
+ **Capabilities**: You have read-only access to the codebase. You can:
17858
+ - Read files (read)
17859
+ - Search by name patterns (glob)
17860
+ - Search by content (grep)
17861
+ - Query language server (lsp_diagnostics, lsp_goto_definition, lsp_find_references)
17862
+ - Search code patterns (ast_grep_search)
17863
+ - Search external docs (if MCPs are configured for this agent)
17864
+
17865
+ You CANNOT edit files, write files, run shell commands, or delegate to other agents. You are an advisor, not an implementer.
17866
+
17867
+ **Behavior**:
17868
+ - **Examine the codebase** before answering \u2014 your read access is what makes council valuable. Don't guess at code you can see.
17869
+ - Analyze the problem thoroughly
17870
+ - Provide a complete, well-reasoned response
17871
+ - Focus on the quality and correctness of your solution
17872
+ - Be direct and concise
17873
+ - Don't be influenced by what other councillors might say \u2014 you won't see their responses
17874
+
17875
+ **Output**:
17876
+ - Give your honest assessment
17877
+ - Reference specific files and line numbers when relevant
17878
+ - Include relevant reasoning
17879
+ - State any assumptions clearly
17880
+ - Note any uncertainties`;
17881
+ function createCouncillorAgent(model, customPrompt, customAppendPrompt) {
17882
+ const prompt = resolvePrompt(COUNCILLOR_PROMPT, customPrompt, customAppendPrompt);
17883
+ return {
17884
+ name: "councillor",
17885
+ description: "Read-only council advisor. Examines codebase and provides independent analysis. Spawned internally by the council system.",
17886
+ config: {
17887
+ model,
17888
+ temperature: 0.2,
17889
+ prompt,
17890
+ permission: {
17891
+ "*": "deny",
17892
+ question: "deny",
17893
+ read: "allow",
17894
+ glob: "allow",
17895
+ grep: "allow",
17896
+ lsp: "allow",
17897
+ list: "allow",
17898
+ codesearch: "allow"
17899
+ }
17900
+ }
17901
+ };
17902
+ }
17903
+
17401
17904
  // src/agents/designer.ts
17402
17905
  var DESIGNER_PROMPT = `You are a Designer - a frontend UI/UX specialist who creates intentional, polished experiences.
17403
17906
 
@@ -17474,19 +17977,9 @@ var EXPLORER_PROMPT = `You are Explorer - a fast codebase navigation specialist.
17474
17977
 
17475
17978
  **Role**: Quick contextual grep for codebases. Answer "Where is X?", "Find Y", "Which file has Z".
17476
17979
 
17477
- **Tools Available**:
17478
- - **grep**: Fast regex content search (powered by ripgrep). Use for text patterns, function names, strings.
17479
- Example: grep(pattern="function handleClick", include="*.ts")
17480
- - **glob**: File pattern matching. Use to find files by name/extension.
17481
- - **ast_grep_search**: AST-aware structural search (25 languages). Use for code patterns.
17482
- - Meta-variables: $VAR (single node), $$$ (multiple nodes)
17483
- - Patterns must be complete AST nodes
17484
- - Example: ast_grep_search(pattern="console.log($MSG)", lang="typescript")
17485
- - Example: ast_grep_search(pattern="async function $NAME($$$) { $$$ }", lang="javascript")
17486
-
17487
- **When to use which**:
17980
+ **When to use which tools**:
17488
17981
  - **Text/regex patterns** (strings, comments, variable names): grep
17489
- - **Structural patterns** (function shapes, class structures): ast_grep_search
17982
+ - **Structural patterns** (function shapes, class structures): ast_grep_search
17490
17983
  - **File discovery** (find by name/extension): glob
17491
17984
 
17492
17985
  **Behavior**:
@@ -17671,165 +18164,6 @@ ${customAppendPrompt}`;
17671
18164
  };
17672
18165
  }
17673
18166
 
17674
- // src/agents/orchestrator.ts
17675
- var ORCHESTRATOR_PROMPT = `<Role>
17676
- You are an AI coding orchestrator that optimizes for quality, speed, cost, and reliability by delegating to specialists when it provides net efficiency gains.
17677
- </Role>
17678
-
17679
- <Agents>
17680
-
17681
- @explorer
17682
- - Role: Parallel search specialist for discovering unknowns across the codebase
17683
- - Capabilities: Glob, grep, AST queries to locate files, symbols, patterns
17684
- - **Delegate when:** Need to discover what exists before planning \u2022 Parallel searches speed discovery \u2022 Need summarized map vs full contents \u2022 Broad/uncertain scope
17685
- - **Don't delegate when:** Know the path and need actual content \u2022 Need full file anyway \u2022 Single specific lookup \u2022 About to edit the file
17686
-
17687
- @librarian
17688
- - Role: Authoritative source for current library docs and API references
17689
- - Capabilities: Fetches latest official docs, examples, API signatures, version-specific behavior via grep_app MCP
17690
- - **Delegate when:** Libraries with frequent API changes (React, Next.js, AI SDKs) \u2022 Complex APIs needing official examples (ORMs, auth) \u2022 Version-specific behavior matters \u2022 Unfamiliar library \u2022 Edge cases or advanced features \u2022 Nuanced best practices
17691
- - **Don't delegate when:** Standard usage you're confident about (\`Array.map()\`, \`fetch()\`) \u2022 Simple stable APIs \u2022 General programming knowledge \u2022 Info already in conversation \u2022 Built-in language features
17692
- - **Rule of thumb:** "How does this library work?" \u2192 @librarian. "How does programming work?" \u2192 yourself.
17693
-
17694
- @oracle
17695
- - Role: Strategic advisor for high-stakes decisions and persistent problems
17696
- - Capabilities: Deep architectural reasoning, system-level trade-offs, complex debugging
17697
- - Tools/Constraints: Slow, expensive, high-quality\u2014use sparingly when thoroughness beats speed
17698
- - **Delegate when:** Major architectural decisions with long-term impact \u2022 Problems persisting after 2+ fix attempts \u2022 High-risk multi-system refactors \u2022 Costly trade-offs (performance vs maintainability) \u2022 Complex debugging with unclear root cause \u2022 Security/scalability/data integrity decisions \u2022 Genuinely uncertain and cost of wrong choice is high
17699
- - **Don't delegate when:** Routine decisions you're confident about \u2022 First bug fix attempt \u2022 Straightforward trade-offs \u2022 Tactical "how" vs strategic "should" \u2022 Time-sensitive good-enough decisions \u2022 Quick research/testing can answer
17700
- - **Rule of thumb:** Need senior architect review? \u2192 @oracle. Just do it and PR? \u2192 yourself.
17701
-
17702
- @designer
17703
- - Role: UI/UX specialist for intentional, polished experiences
17704
- - Capabilities: Visual direction, interactions, responsive layouts, design systems with aesthetic intent
17705
- - **Delegate when:** User-facing interfaces needing polish \u2022 Responsive layouts \u2022 UX-critical components (forms, nav, dashboards) \u2022 Visual consistency systems \u2022 Animations/micro-interactions \u2022 Landing/marketing pages \u2022 Refining functional\u2192delightful
17706
- - **Don't delegate when:** Backend/logic with no visual \u2022 Quick prototypes where design doesn't matter yet
17707
- - **Rule of thumb:** Users see it and polish matters? \u2192 @designer. Headless/functional? \u2192 yourself.
17708
-
17709
- @fixer
17710
- - Role: Fast, parallel execution specialist for well-defined tasks
17711
- - Capabilities: Efficient implementation when spec and context are clear
17712
- - Tools/Constraints: Execution-focused\u2014no research, no architectural decisions
17713
- - **Delegate when:** Clearly specified with known approach \u2022 3+ independent parallel tasks \u2022 Straightforward but time-consuming \u2022 Solid plan needing execution \u2022 Repetitive multi-location changes \u2022 Overhead < time saved by parallelization
17714
- - **Don't delegate when:** Needs discovery/research/decisions \u2022 Single small change (<20 lines, one file) \u2022 Unclear requirements needing iteration \u2022 Explaining > doing \u2022 Tight integration with your current work \u2022 Sequential dependencies
17715
- - **Parallelization:** 3+ independent tasks \u2192 spawn multiple @fixers. 1-2 simple tasks \u2192 do yourself.
17716
- - **Rule of thumb:** Explaining > doing? \u2192 yourself. Can split to parallel streams? \u2192 multiple @fixers.
17717
-
17718
- </Agents>
17719
-
17720
- <Workflow>
17721
-
17722
- ## 1. Understand
17723
- Parse request: explicit requirements + implicit needs.
17724
-
17725
- ## 2. Path Analysis
17726
- Evaluate approach by: quality, speed, cost, reliability.
17727
- Choose the path that optimizes all four.
17728
-
17729
- ## 3. Delegation Check
17730
- **STOP. Review specialists before acting.**
17731
-
17732
- Each specialist delivers 10x results in their domain:
17733
- - @explorer \u2192 Parallel discovery when you need to find unknowns, not read knowns
17734
- - @librarian \u2192 Complex/evolving APIs where docs prevent errors, not basic usage
17735
- - @oracle \u2192 High-stakes decisions where wrong choice is costly, not routine calls
17736
- - @designer \u2192 User-facing experiences where polish matters, not internal logic
17737
- - @fixer \u2192 Parallel execution of clear specs, not explaining trivial changes
17738
-
17739
- **Delegation efficiency:**
17740
- - Reference paths/lines, don't paste files (\`src/app.ts:42\` not full contents)
17741
- - Provide context summaries, let specialists read what they need
17742
- - Brief user on delegation goal before each call
17743
- - Skip delegation if overhead \u2265 doing it yourself
17744
-
17745
- **Fixer parallelization:**
17746
- - 3+ independent tasks? Spawn multiple @fixers simultaneously
17747
- - 1-2 simple tasks? Do it yourself
17748
- - Sequential dependencies? Handle serially or do yourself
17749
-
17750
- ## 4. Parallelize
17751
- Can tasks run simultaneously?
17752
- - Multiple @explorer searches across different domains?
17753
- - @explorer + @librarian research in parallel?
17754
- - Multiple @fixer instances for independent changes?
17755
-
17756
- Balance: respect dependencies, avoid parallelizing what must be sequential.
17757
-
17758
- ## 5. Execute
17759
- 1. Break complex tasks into todos if needed
17760
- 2. Fire parallel research/implementation
17761
- 3. Delegate to specialists or do it yourself based on step 3
17762
- 4. Integrate results
17763
- 5. Adjust if needed
17764
-
17765
- ## 6. Verify
17766
- - Run \`lsp_diagnostics\` for errors
17767
- - Suggest \`simplify\` skill when applicable
17768
- - Confirm specialists completed successfully
17769
- - Verify solution meets requirements
17770
-
17771
- ## Agent Role Mapping
17772
- When a workflow calls for an **implementer** subagent: dispatch \`@fixer\`. Fixer has enforced constraints (no research, no delegation, structured output) that match the implementer role exactly.
17773
- When a workflow calls for a **reviewer** subagent: dispatch \`@oracle\`. Oracle has the depth for architectural review and access to code review skills.
17774
-
17775
- </Workflow>
17776
-
17777
- <Communication>
17778
-
17779
- ## Clarity Over Assumptions
17780
- - If request is vague or has multiple valid interpretations, ask a targeted question before proceeding
17781
- - Don't guess at critical details (file paths, API choices, architectural decisions)
17782
- - Do make reasonable assumptions for minor details and state them briefly
17783
-
17784
- ## Concise Execution
17785
- - Answer directly, no preamble
17786
- - Don't summarize what you did unless asked
17787
- - Don't explain code unless asked
17788
- - One-word answers are fine when appropriate
17789
- - Brief delegation notices: "Checking docs via @librarian..." not "I'm going to delegate to @librarian because..."
17790
-
17791
- ## No Flattery
17792
- Never: "Great question!" "Excellent idea!" "Smart choice!" or any praise of user input.
17793
-
17794
- ## Honest Pushback
17795
- When user's approach seems problematic:
17796
- - State concern + alternative concisely
17797
- - Ask if they want to proceed anyway
17798
- - Don't lecture, don't blindly implement
17799
-
17800
- ## Example
17801
- **Bad:** "Great question! Let me think about the best approach here. I'm going to delegate to @librarian to check the latest Next.js documentation for the App Router, and then I'll implement the solution for you."
17802
-
17803
- **Good:** "Checking Next.js App Router docs via @librarian..."
17804
- [proceeds with implementation]
17805
-
17806
- </Communication>
17807
- `;
17808
- function createOrchestratorAgent(model, customPrompt, customAppendPrompt) {
17809
- let prompt = ORCHESTRATOR_PROMPT;
17810
- if (customPrompt) {
17811
- prompt = customPrompt;
17812
- } else if (customAppendPrompt) {
17813
- prompt = `${ORCHESTRATOR_PROMPT}
17814
-
17815
- ${customAppendPrompt}`;
17816
- }
17817
- const definition = {
17818
- name: "orchestrator",
17819
- description: "AI coding orchestrator that delegates tasks to specialist agents for optimal quality, speed, and cost",
17820
- config: {
17821
- temperature: 0.1,
17822
- prompt
17823
- }
17824
- };
17825
- if (Array.isArray(model)) {
17826
- definition._modelArray = model.map((m) => typeof m === "string" ? { id: m } : m);
17827
- } else if (typeof model === "string" && model) {
17828
- definition.config.model = model;
17829
- }
17830
- return definition;
17831
- }
17832
-
17833
18167
  // src/agents/index.ts
17834
18168
  function applyOverrides(agent, override) {
17835
18169
  if (override.model) {
@@ -17848,9 +18182,10 @@ function applyOverrides(agent, override) {
17848
18182
  function applyDefaultPermissions(agent, configuredSkills) {
17849
18183
  const existing = agent.config.permission ?? {};
17850
18184
  const skillPermissions = getSkillPermissionsForAgent(agent.name, configuredSkills);
18185
+ const questionPerm = existing.question === "deny" ? "deny" : "allow";
17851
18186
  agent.config.permission = {
17852
18187
  ...existing,
17853
- question: "allow",
18188
+ question: questionPerm,
17854
18189
  skill: {
17855
18190
  ...typeof existing.skill === "object" ? existing.skill : {},
17856
18191
  ...skillPermissions
@@ -17865,7 +18200,10 @@ var SUBAGENT_FACTORIES = {
17865
18200
  librarian: createLibrarianAgent,
17866
18201
  oracle: createOracleAgent,
17867
18202
  designer: createDesignerAgent,
17868
- fixer: createFixerAgent
18203
+ fixer: createFixerAgent,
18204
+ council: createCouncilAgent,
18205
+ councillor: createCouncillorAgent,
18206
+ "council-master": createCouncilMasterAgent
17869
18207
  };
17870
18208
  function createAgents(config2) {
17871
18209
  const getModelForAgent = (name) => {
@@ -17912,7 +18250,12 @@ function getAgentConfigs(config2) {
17912
18250
  description: a.description,
17913
18251
  mcps: getAgentMcpList(a.name, config2)
17914
18252
  };
17915
- if (isSubagent(a.name)) {
18253
+ if (a.name === "council") {
18254
+ sdkConfig.mode = "all";
18255
+ } else if (a.name === "councillor" || a.name === "council-master") {
18256
+ sdkConfig.mode = "subagent";
18257
+ sdkConfig.hidden = true;
18258
+ } else if (isSubagent(a.name)) {
17916
18259
  sdkConfig.mode = "subagent";
17917
18260
  } else if (a.name === "orchestrator") {
17918
18261
  sdkConfig.mode = "primary";
@@ -18300,17 +18643,48 @@ async function extractZip(archivePath, destDir) {
18300
18643
  throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
18301
18644
  }
18302
18645
  }
18303
- // src/background/background-manager.ts
18304
- function parseModelReference(model) {
18305
- const slashIndex = model.indexOf("/");
18306
- if (slashIndex <= 0 || slashIndex >= model.length - 1) {
18307
- return null;
18646
+ // src/background/subagent-depth.ts
18647
+ class SubagentDepthTracker {
18648
+ depthBySession = new Map;
18649
+ _maxDepth;
18650
+ constructor(maxDepth = DEFAULT_MAX_SUBAGENT_DEPTH) {
18651
+ this._maxDepth = maxDepth;
18652
+ }
18653
+ get maxDepth() {
18654
+ return this._maxDepth;
18655
+ }
18656
+ getDepth(sessionId) {
18657
+ return this.depthBySession.get(sessionId) ?? 0;
18658
+ }
18659
+ registerChild(parentSessionId, childSessionId) {
18660
+ const parentDepth = this.getDepth(parentSessionId);
18661
+ const childDepth = parentDepth + 1;
18662
+ if (childDepth > this.maxDepth) {
18663
+ log("[subagent-depth] spawn blocked: max depth exceeded", {
18664
+ parentSessionId,
18665
+ parentDepth,
18666
+ childDepth,
18667
+ maxDepth: this.maxDepth
18668
+ });
18669
+ return false;
18670
+ }
18671
+ this.depthBySession.set(childSessionId, childDepth);
18672
+ log("[subagent-depth] child registered", {
18673
+ parentSessionId,
18674
+ childSessionId,
18675
+ childDepth
18676
+ });
18677
+ return true;
18678
+ }
18679
+ cleanup(sessionId) {
18680
+ this.depthBySession.delete(sessionId);
18681
+ }
18682
+ cleanupAll() {
18683
+ this.depthBySession.clear();
18308
18684
  }
18309
- return {
18310
- providerID: model.slice(0, slashIndex),
18311
- modelID: model.slice(slashIndex + 1)
18312
- };
18313
18685
  }
18686
+
18687
+ // src/background/background-manager.ts
18314
18688
  function generateTaskId() {
18315
18689
  return `bg_${Math.random().toString(36).substring(2, 10)}`;
18316
18690
  }
@@ -18319,6 +18693,7 @@ class BackgroundTaskManager {
18319
18693
  tasks = new Map;
18320
18694
  tasksBySessionId = new Map;
18321
18695
  agentBySessionId = new Map;
18696
+ depthTracker;
18322
18697
  client;
18323
18698
  directory;
18324
18699
  tmuxEnabled;
@@ -18337,6 +18712,7 @@ class BackgroundTaskManager {
18337
18712
  maxConcurrentStarts: 10
18338
18713
  };
18339
18714
  this.maxConcurrentStarts = this.backgroundConfig.maxConcurrentStarts;
18715
+ this.depthTracker = new SubagentDepthTracker;
18340
18716
  }
18341
18717
  getSubagentRules(agentName) {
18342
18718
  return SUBAGENT_DELEGATION_RULES[agentName] ?? ["explorer"];
@@ -18407,30 +18783,7 @@ class BackgroundTaskManager {
18407
18783
  seen.add(model);
18408
18784
  chain.push(model);
18409
18785
  }
18410
- return chain;
18411
- }
18412
- async promptWithTimeout(args, timeoutMs) {
18413
- if (timeoutMs <= 0) {
18414
- await this.client.session.prompt(args);
18415
- return;
18416
- }
18417
- const sessionId = args.path.id;
18418
- let timer;
18419
- try {
18420
- const promptPromise = this.client.session.prompt(args);
18421
- promptPromise.catch(() => {});
18422
- await Promise.race([
18423
- promptPromise,
18424
- new Promise((_, reject) => {
18425
- timer = setTimeout(() => {
18426
- this.client.session.abort({ path: { id: sessionId } }).catch(() => {});
18427
- reject(new Error(`Prompt timed out after ${timeoutMs}ms`));
18428
- }, timeoutMs);
18429
- })
18430
- ]);
18431
- } finally {
18432
- clearTimeout(timer);
18433
- }
18786
+ return chain;
18434
18787
  }
18435
18788
  calculateToolPermissions(agentName) {
18436
18789
  const allowedSubagents = this.getSubagentRules(agentName);
@@ -18447,20 +18800,31 @@ class BackgroundTaskManager {
18447
18800
  return;
18448
18801
  }
18449
18802
  try {
18450
- const session = await this.client.session.create({
18803
+ const parentDepth = this.depthTracker.getDepth(task.parentSessionId);
18804
+ if (parentDepth + 1 > this.depthTracker.maxDepth) {
18805
+ log("[background-manager] spawn blocked: max depth exceeded", {
18806
+ parentSessionId: task.parentSessionId,
18807
+ parentDepth,
18808
+ maxDepth: this.depthTracker.maxDepth
18809
+ });
18810
+ this.completeTask(task, "failed", "Subagent depth exceeded");
18811
+ return;
18812
+ }
18813
+ const session2 = await this.client.session.create({
18451
18814
  body: {
18452
18815
  parentID: task.parentSessionId,
18453
18816
  title: `Background: ${task.description}`
18454
18817
  },
18455
18818
  query: { directory: this.directory }
18456
18819
  });
18457
- if (!session.data?.id) {
18820
+ if (!session2.data?.id) {
18458
18821
  throw new Error("Failed to create background session");
18459
18822
  }
18460
- task.sessionId = session.data.id;
18461
- this.tasksBySessionId.set(session.data.id, task.id);
18462
- this.agentBySessionId.set(session.data.id, task.agent);
18823
+ task.sessionId = session2.data.id;
18824
+ this.tasksBySessionId.set(session2.data.id, task.id);
18825
+ this.agentBySessionId.set(session2.data.id, task.agent);
18463
18826
  task.status = "running";
18827
+ this.depthTracker.registerChild(task.parentSessionId, session2.data.id);
18464
18828
  if (this.tmuxEnabled) {
18465
18829
  await new Promise((r) => setTimeout(r, 500));
18466
18830
  }
@@ -18479,7 +18843,7 @@ class BackgroundTaskManager {
18479
18843
  const attemptModels = chain.length > 0 ? chain : [undefined];
18480
18844
  const errors3 = [];
18481
18845
  let succeeded = false;
18482
- const sessionId = session.data.id;
18846
+ const sessionId = session2.data.id;
18483
18847
  for (let i = 0;i < attemptModels.length; i++) {
18484
18848
  const model = attemptModels[i];
18485
18849
  const modelLabel = model ?? "default-model";
@@ -18498,7 +18862,7 @@ class BackgroundTaskManager {
18498
18862
  if (i > 0) {
18499
18863
  log(`[background-manager] fallback attempt ${i + 1}/${attemptModels.length}: ${modelLabel}`, { taskId: task.id });
18500
18864
  }
18501
- await this.promptWithTimeout({
18865
+ await promptWithTimeout(this.client, {
18502
18866
  path: { id: sessionId },
18503
18867
  body,
18504
18868
  query: promptQuery
@@ -18525,7 +18889,7 @@ class BackgroundTaskManager {
18525
18889
  throw new Error(`All fallback models failed. ${errors3.join(" | ")}`);
18526
18890
  }
18527
18891
  log(`[background-manager] task started: ${task.id}`, {
18528
- sessionId: session.data.id
18892
+ sessionId: session2.data.id
18529
18893
  });
18530
18894
  } catch (error48) {
18531
18895
  const errorMessage = error48 instanceof Error ? error48.message : String(error48);
@@ -18570,6 +18934,7 @@ class BackgroundTaskManager {
18570
18934
  task.error = "Session deleted";
18571
18935
  this.tasksBySessionId.delete(sessionId);
18572
18936
  this.agentBySessionId.delete(sessionId);
18937
+ this.depthTracker.cleanup(sessionId);
18573
18938
  const resolver = this.completionResolvers.get(taskId);
18574
18939
  if (resolver) {
18575
18940
  resolver(task);
@@ -18582,22 +18947,7 @@ class BackgroundTaskManager {
18582
18947
  if (!task.sessionId)
18583
18948
  return;
18584
18949
  try {
18585
- const messagesResult = await this.client.session.messages({
18586
- path: { id: task.sessionId }
18587
- });
18588
- const messages = messagesResult.data ?? [];
18589
- const assistantMessages = messages.filter((m) => m.info?.role === "assistant");
18590
- const extractedContent = [];
18591
- for (const message of assistantMessages) {
18592
- for (const part of message.parts ?? []) {
18593
- if ((part.type === "text" || part.type === "reasoning") && part.text) {
18594
- extractedContent.push(part.text);
18595
- }
18596
- }
18597
- }
18598
- const responseText = extractedContent.filter((t) => t.length > 0).join(`
18599
-
18600
- `);
18950
+ const responseText = await extractSessionResult(this.client, task.sessionId);
18601
18951
  if (responseText) {
18602
18952
  this.completeTask(task, "completed", responseText);
18603
18953
  } else {
@@ -18713,6 +19063,10 @@ class BackgroundTaskManager {
18713
19063
  this.tasks.clear();
18714
19064
  this.tasksBySessionId.clear();
18715
19065
  this.agentBySessionId.clear();
19066
+ this.depthTracker.cleanupAll();
19067
+ }
19068
+ getDepthTracker() {
19069
+ return this.depthTracker;
18716
19070
  }
18717
19071
  }
18718
19072
  // src/background/tmux-session-manager.ts
@@ -18882,6 +19236,267 @@ class TmuxSessionManager {
18882
19236
  log("[tmux-session-manager] cleanup complete");
18883
19237
  }
18884
19238
  }
19239
+ // src/council/council-manager.ts
19240
+ class CouncilManager {
19241
+ client;
19242
+ directory;
19243
+ config;
19244
+ depthTracker;
19245
+ tmuxEnabled;
19246
+ constructor(ctx, config2, depthTracker, tmuxEnabled = false) {
19247
+ this.client = ctx.client;
19248
+ this.directory = ctx.directory;
19249
+ this.config = config2;
19250
+ this.depthTracker = depthTracker;
19251
+ this.tmuxEnabled = tmuxEnabled;
19252
+ }
19253
+ async runCouncil(prompt, presetName, parentSessionId) {
19254
+ if (this.depthTracker) {
19255
+ const parentDepth = this.depthTracker.getDepth(parentSessionId);
19256
+ if (parentDepth + 1 > this.depthTracker.maxDepth) {
19257
+ log("[council-manager] spawn blocked: max depth exceeded", {
19258
+ parentSessionId,
19259
+ parentDepth,
19260
+ maxDepth: this.depthTracker.maxDepth
19261
+ });
19262
+ return {
19263
+ success: false,
19264
+ error: "Subagent depth exceeded",
19265
+ councillorResults: []
19266
+ };
19267
+ }
19268
+ }
19269
+ const councilConfig = this.config?.council;
19270
+ if (!councilConfig) {
19271
+ log("[council-manager] Council configuration not found");
19272
+ return {
19273
+ success: false,
19274
+ error: "Council not configured",
19275
+ councillorResults: []
19276
+ };
19277
+ }
19278
+ const resolvedPreset = presetName ?? councilConfig.default_preset ?? "default";
19279
+ const preset = councilConfig.presets[resolvedPreset];
19280
+ if (!preset) {
19281
+ log(`[council-manager] Preset "${resolvedPreset}" not found`);
19282
+ return {
19283
+ success: false,
19284
+ error: `Preset "${resolvedPreset}" not found`,
19285
+ councillorResults: []
19286
+ };
19287
+ }
19288
+ if (Object.keys(preset.councillors).length === 0) {
19289
+ log(`[council-manager] Preset "${resolvedPreset}" has no councillors`);
19290
+ return {
19291
+ success: false,
19292
+ error: `Preset "${resolvedPreset}" has no councillors configured`,
19293
+ councillorResults: []
19294
+ };
19295
+ }
19296
+ const councillorsTimeout = councilConfig.councillors_timeout ?? 180000;
19297
+ const masterTimeout = councilConfig.master_timeout ?? 300000;
19298
+ const councillorCount = Object.keys(preset.councillors).length;
19299
+ log(`[council-manager] Starting council with preset "${resolvedPreset}"`, {
19300
+ councillors: Object.keys(preset.councillors)
19301
+ });
19302
+ this.sendStartNotification(parentSessionId, councillorCount).catch((err) => {
19303
+ log("[council-manager] Failed to send start notification", {
19304
+ error: err instanceof Error ? err.message : String(err)
19305
+ });
19306
+ });
19307
+ const councillorResults = await this.runCouncillors(prompt, preset.councillors, parentSessionId, councillorsTimeout);
19308
+ const completedCount = councillorResults.filter((r) => r.status === "completed").length;
19309
+ log(`[council-manager] Councillors completed: ${completedCount}/${councillorResults.length}`);
19310
+ if (completedCount === 0) {
19311
+ return {
19312
+ success: false,
19313
+ error: "All councillors failed or timed out",
19314
+ councillorResults
19315
+ };
19316
+ }
19317
+ const masterResult = await this.runMaster(prompt, councillorResults, councilConfig, parentSessionId, masterTimeout, preset.master);
19318
+ if (!masterResult.success) {
19319
+ log("[council-manager] Master failed", {
19320
+ error: masterResult.error
19321
+ });
19322
+ const bestResult = councillorResults.find((r) => r.status === "completed" && r.result);
19323
+ return {
19324
+ success: false,
19325
+ error: masterResult.error ?? "Council master failed",
19326
+ result: bestResult?.result ? `(Degraded \u2014 master failed, using ${bestResult.name}'s response)
19327
+
19328
+ ${bestResult.result}` : undefined,
19329
+ councillorResults
19330
+ };
19331
+ }
19332
+ log("[council-manager] Council completed successfully");
19333
+ return {
19334
+ success: true,
19335
+ result: masterResult.result,
19336
+ councillorResults
19337
+ };
19338
+ }
19339
+ async sendStartNotification(parentSessionId, councillorCount) {
19340
+ const message = [
19341
+ `\u2394 Council starting \u2014 ${councillorCount} councillors launching \u2014 ctrl+x \u2193 to watch`,
19342
+ "",
19343
+ "[system status: continue without acknowledging this notification]"
19344
+ ].join(`
19345
+ `);
19346
+ await this.client.session.prompt({
19347
+ path: { id: parentSessionId },
19348
+ body: {
19349
+ noReply: true,
19350
+ parts: [{ type: "text", text: message }]
19351
+ }
19352
+ });
19353
+ }
19354
+ async runAgentSession(options) {
19355
+ const modelRef = parseModelReference(options.model);
19356
+ if (!modelRef) {
19357
+ throw new Error(`Invalid model format: ${options.model}`);
19358
+ }
19359
+ let sessionId;
19360
+ try {
19361
+ const session2 = await this.client.session.create({
19362
+ body: {
19363
+ parentID: options.parentSessionId,
19364
+ title: options.title
19365
+ },
19366
+ query: { directory: this.directory }
19367
+ });
19368
+ if (!session2.data?.id) {
19369
+ throw new Error("Failed to create session");
19370
+ }
19371
+ sessionId = session2.data.id;
19372
+ if (this.depthTracker) {
19373
+ const registered = this.depthTracker.registerChild(options.parentSessionId, sessionId);
19374
+ if (!registered) {
19375
+ throw new Error("Subagent depth exceeded");
19376
+ }
19377
+ }
19378
+ if (this.tmuxEnabled) {
19379
+ await new Promise((r) => setTimeout(r, TMUX_SPAWN_DELAY_MS));
19380
+ }
19381
+ const body = {
19382
+ agent: options.agent,
19383
+ model: modelRef,
19384
+ tools: { background_task: false, task: false },
19385
+ parts: [{ type: "text", text: options.promptText }]
19386
+ };
19387
+ if (options.variant) {
19388
+ body.variant = options.variant;
19389
+ }
19390
+ await promptWithTimeout(this.client, {
19391
+ path: { id: sessionId },
19392
+ body,
19393
+ query: { directory: this.directory }
19394
+ }, options.timeout);
19395
+ const result = await extractSessionResult(this.client, sessionId, {
19396
+ includeReasoning: options.includeReasoning
19397
+ });
19398
+ return result || "(No output)";
19399
+ } finally {
19400
+ if (sessionId) {
19401
+ this.client.session.abort({ path: { id: sessionId } }).catch(() => {});
19402
+ if (this.depthTracker) {
19403
+ this.depthTracker.cleanup(sessionId);
19404
+ }
19405
+ }
19406
+ }
19407
+ }
19408
+ async runCouncillors(prompt, councillors, parentSessionId, timeout) {
19409
+ const entries = Object.entries(councillors);
19410
+ const promises = entries.map(([name, config2], index) => (async () => {
19411
+ if (index > 0) {
19412
+ await new Promise((r) => setTimeout(r, index * COUNCILLOR_STAGGER_MS));
19413
+ }
19414
+ const modelLabel = shortModelLabel(config2.model);
19415
+ try {
19416
+ const result = await this.runAgentSession({
19417
+ parentSessionId,
19418
+ title: `Council ${name} (${modelLabel})`,
19419
+ agent: "councillor",
19420
+ model: config2.model,
19421
+ promptText: formatCouncillorPrompt(prompt, config2.prompt),
19422
+ variant: config2.variant,
19423
+ timeout,
19424
+ includeReasoning: false
19425
+ });
19426
+ return {
19427
+ name,
19428
+ model: config2.model,
19429
+ status: "completed",
19430
+ result
19431
+ };
19432
+ } catch (error48) {
19433
+ const msg = error48 instanceof Error ? error48.message : String(error48);
19434
+ return {
19435
+ name,
19436
+ model: config2.model,
19437
+ status: msg.includes("timed out") ? "timed_out" : "failed",
19438
+ error: `Councillor "${name}": ${msg}`
19439
+ };
19440
+ }
19441
+ })());
19442
+ const settled = await Promise.allSettled(promises);
19443
+ return settled.map((result, index) => {
19444
+ const [name, cfg] = entries[index];
19445
+ if (result.status === "fulfilled") {
19446
+ return {
19447
+ name,
19448
+ model: cfg.model,
19449
+ status: result.value.status,
19450
+ result: result.value.result,
19451
+ error: result.value.error
19452
+ };
19453
+ }
19454
+ return {
19455
+ name,
19456
+ model: cfg.model,
19457
+ status: "failed",
19458
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
19459
+ };
19460
+ });
19461
+ }
19462
+ async runMaster(prompt, councillorResults, councilConfig, parentSessionId, timeout, presetMasterOverride) {
19463
+ const masterConfig = councilConfig.master;
19464
+ const fallbackModels = councilConfig.master_fallback ?? [];
19465
+ const effectiveModel = presetMasterOverride?.model ?? masterConfig.model;
19466
+ const effectiveVariant = presetMasterOverride?.variant ?? masterConfig.variant;
19467
+ const effectivePrompt = presetMasterOverride?.prompt ?? masterConfig.prompt;
19468
+ const attemptModels = [effectiveModel, ...fallbackModels];
19469
+ const synthesisPrompt = formatMasterSynthesisPrompt(prompt, councillorResults, effectivePrompt);
19470
+ const errors3 = [];
19471
+ for (let i = 0;i < attemptModels.length; i++) {
19472
+ const model = attemptModels[i];
19473
+ const currentLabel = shortModelLabel(model);
19474
+ try {
19475
+ if (i > 0) {
19476
+ log(`[council-manager] master fallback ${i}/${attemptModels.length - 1}: ${currentLabel}`);
19477
+ }
19478
+ const result = await this.runAgentSession({
19479
+ parentSessionId,
19480
+ title: `Council Master (${currentLabel})`,
19481
+ agent: "council-master",
19482
+ model,
19483
+ promptText: synthesisPrompt,
19484
+ variant: effectiveVariant,
19485
+ timeout
19486
+ });
19487
+ return { success: true, result };
19488
+ } catch (error48) {
19489
+ const msg = error48 instanceof Error ? error48.message : String(error48);
19490
+ errors3.push(`${currentLabel}: ${msg}`);
19491
+ log(`[council-manager] master model failed: ${currentLabel} \u2014 ${msg}`);
19492
+ }
19493
+ }
19494
+ return {
19495
+ success: false,
19496
+ error: `All master models failed. ${errors3.join(" | ")}`
19497
+ };
19498
+ }
19499
+ }
18885
19500
  // src/hooks/auto-update-checker/cache.ts
18886
19501
  import * as fs3 from "fs";
18887
19502
  import * as path4 from "path";
@@ -19558,7 +20173,10 @@ class ForegroundFallbackManager {
19558
20173
  const agentName = this.sessionAgent.get(sessionID);
19559
20174
  const chain = this.resolveChain(agentName, currentModel);
19560
20175
  if (!chain.length) {
19561
- log("[foreground-fallback] no chain configured", { sessionID, agentName });
20176
+ log("[foreground-fallback] no chain configured", {
20177
+ sessionID,
20178
+ agentName
20179
+ });
19562
20180
  return;
19563
20181
  }
19564
20182
  if (!this.sessionTried.has(sessionID)) {
@@ -19647,7 +20265,6 @@ var JSON_ERROR_TOOL_EXCLUDE_LIST = [
19647
20265
  "bash",
19648
20266
  "read",
19649
20267
  "glob",
19650
- "grep",
19651
20268
  "webfetch",
19652
20269
  "grep_app_searchgithub",
19653
20270
  "websearch_web_search_exa"
@@ -19695,9 +20312,7 @@ ${JSON_ERROR_REMINDER}`;
19695
20312
  };
19696
20313
  }
19697
20314
  // src/hooks/phase-reminder/index.ts
19698
- var PHASE_REMINDER = `<reminder>Recall Workflow Rules:
19699
- Understand \u2192 find the best path (delegate based on rules and parallelize independent work) \u2192 execute \u2192 verify.
19700
- If delegating, launch the specialist in the same turn you mention it.</reminder>`;
20315
+ var PHASE_REMINDER = `<reminder>${PHASE_REMINDER_TEXT}</reminder>`;
19701
20316
  function createPhaseReminderHook() {
19702
20317
  return {
19703
20318
  "experimental.chat.messages.transform": async (_input, output) => {
@@ -19736,15 +20351,15 @@ ${originalText}`;
19736
20351
  }
19737
20352
  };
19738
20353
  }
19739
- // src/hooks/post-read-nudge/index.ts
20354
+ // src/hooks/post-file-tool-nudge/index.ts
19740
20355
  var NUDGE = `
19741
20356
 
19742
20357
  ---
19743
- Workflow Reminder: delegate based on rules; If mentioning a specialist, launch it in this same turn.`;
19744
- function createPostReadNudgeHook() {
20358
+ ${PHASE_REMINDER_TEXT}`;
20359
+ function createPostFileToolNudgeHook() {
19745
20360
  return {
19746
20361
  "tool.execute.after": async (input, output) => {
19747
- if (input.tool !== "Read" && input.tool !== "read") {
20362
+ if (input.tool !== "Read" && input.tool !== "read" && input.tool !== "Write" && input.tool !== "write") {
19748
20363
  return;
19749
20364
  }
19750
20365
  output.output = output.output + NUDGE;
@@ -32764,340 +33379,76 @@ Only cancels pending/starting/running tasks.`,
32764
33379
  });
32765
33380
  return { background_task, background_output, background_cancel };
32766
33381
  }
32767
- // src/tools/grep/cli.ts
32768
- var {spawn: spawn4 } = globalThis.Bun;
33382
+ // src/tools/council.ts
33383
+ var z3 = tool.schema;
33384
+ function formatModelComposition(councillorResults) {
33385
+ return councillorResults.map((cr) => {
33386
+ const shortModel = shortModelLabel(cr.model ?? "");
33387
+ return `${cr.name}: ${shortModel}`;
33388
+ }).join(", ");
33389
+ }
33390
+ function createCouncilTool(_ctx, councilManager) {
33391
+ const council_session = tool({
33392
+ description: `Launch a multi-LLM council session for consensus-based analysis.
32769
33393
 
32770
- // src/tools/grep/constants.ts
32771
- import { spawnSync as spawnSync2 } from "child_process";
32772
- import { existsSync as existsSync8 } from "fs";
32773
- import { dirname as dirname4, join as join10 } from "path";
33394
+ Sends the prompt to multiple models (councillors) in parallel, then a council master synthesizes the best response.
32774
33395
 
32775
- // src/tools/grep/downloader.ts
32776
- import {
32777
- chmodSync as chmodSync2,
32778
- existsSync as existsSync7,
32779
- mkdirSync as mkdirSync2,
32780
- readdirSync,
32781
- unlinkSync as unlinkSync2
32782
- } from "fs";
32783
- import { join as join9 } from "path";
32784
- function getInstallDir() {
32785
- const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
32786
- return join9(homeDir, ".cache", "oh-my-opencode-slim", "bin");
32787
- }
32788
- function getRgPath() {
32789
- const isWindows = process.platform === "win32";
32790
- return join9(getInstallDir(), isWindows ? "rg.exe" : "rg");
32791
- }
32792
- function getInstalledRipgrepPath() {
32793
- const rgPath = getRgPath();
32794
- return existsSync7(rgPath) ? rgPath : null;
32795
- }
33396
+ Returns the synthesized result with councillor summary.`,
33397
+ args: {
33398
+ prompt: z3.string().describe("The prompt to send to all councillors"),
33399
+ preset: z3.string().optional().describe('Council preset to use (default: "default"). Must match a preset in the council config.')
33400
+ },
33401
+ async execute(args, toolContext) {
33402
+ if (!toolContext || typeof toolContext !== "object" || !("sessionID" in toolContext)) {
33403
+ throw new Error("Invalid toolContext: missing sessionID");
33404
+ }
33405
+ const allowedAgents = ["council", "orchestrator"];
33406
+ const callingAgent = toolContext.agent;
33407
+ if (callingAgent && !allowedAgents.includes(callingAgent)) {
33408
+ throw new Error(`Council sessions can only be invoked by council or orchestrator agents. Current agent: ${callingAgent}`);
33409
+ }
33410
+ const prompt = String(args.prompt);
33411
+ const preset = typeof args.preset === "string" ? args.preset : undefined;
33412
+ const parentSessionId = toolContext.sessionID;
33413
+ const result = await councilManager.runCouncil(prompt, preset, parentSessionId);
33414
+ if (!result.success) {
33415
+ if (result.result) {
33416
+ const completed2 = result.councillorResults.filter((cr) => cr.status === "completed").length;
33417
+ const total2 = result.councillorResults.length;
33418
+ const composition2 = formatModelComposition(result.councillorResults);
33419
+ return `${result.result}
32796
33420
 
32797
- // src/tools/grep/constants.ts
32798
- var cachedCli = null;
32799
- function findExecutable(name) {
32800
- const isWindows = process.platform === "win32";
32801
- const cmd = isWindows ? "where" : "which";
32802
- try {
32803
- const result = spawnSync2(cmd, [name], { encoding: "utf-8", timeout: 5000 });
32804
- if (result.status === 0 && result.stdout.trim()) {
32805
- return result.stdout.trim().split(/\r?\n/)[0];
32806
- }
32807
- } catch {}
32808
- return null;
32809
- }
32810
- function getDataDir() {
32811
- if (process.platform === "win32") {
32812
- return process.env.LOCALAPPDATA || process.env.APPDATA || join10(process.env.USERPROFILE || ".", "AppData", "Local");
32813
- }
32814
- return process.env.XDG_DATA_HOME || join10(process.env.HOME || ".", ".local", "share");
32815
- }
32816
- function getOpenCodeBundledRg() {
32817
- const execPath = process.execPath;
32818
- const execDir = dirname4(execPath);
32819
- const isWindows = process.platform === "win32";
32820
- const rgName = isWindows ? "rg.exe" : "rg";
32821
- const candidates = [
32822
- join10(getDataDir(), "opencode", "bin", rgName),
32823
- join10(execDir, rgName),
32824
- join10(execDir, "bin", rgName),
32825
- join10(execDir, "..", "bin", rgName),
32826
- join10(execDir, "..", "libexec", rgName)
32827
- ];
32828
- for (const candidate of candidates) {
32829
- if (existsSync8(candidate)) {
32830
- return candidate;
32831
- }
32832
- }
32833
- return null;
32834
- }
32835
- function resolveGrepCli() {
32836
- if (cachedCli)
32837
- return cachedCli;
32838
- const bundledRg = getOpenCodeBundledRg();
32839
- if (bundledRg) {
32840
- cachedCli = { path: bundledRg, backend: "rg" };
32841
- return cachedCli;
32842
- }
32843
- const systemRg = findExecutable("rg");
32844
- if (systemRg) {
32845
- cachedCli = { path: systemRg, backend: "rg" };
32846
- return cachedCli;
32847
- }
32848
- const installedRg = getInstalledRipgrepPath();
32849
- if (installedRg) {
32850
- cachedCli = { path: installedRg, backend: "rg" };
32851
- return cachedCli;
32852
- }
32853
- const grep = findExecutable("grep");
32854
- if (grep) {
32855
- cachedCli = { path: grep, backend: "grep" };
32856
- return cachedCli;
32857
- }
32858
- cachedCli = { path: "rg", backend: "rg" };
32859
- return cachedCli;
32860
- }
32861
- var DEFAULT_MAX_DEPTH = 20;
32862
- var DEFAULT_MAX_FILESIZE = "10M";
32863
- var DEFAULT_MAX_COUNT = 500;
32864
- var DEFAULT_MAX_COLUMNS = 1000;
32865
- var DEFAULT_TIMEOUT_MS3 = 300000;
32866
- var DEFAULT_MAX_OUTPUT_BYTES2 = 10 * 1024 * 1024;
32867
- var RG_SAFETY_FLAGS = [
32868
- "--no-follow",
32869
- "--color=never",
32870
- "--no-heading",
32871
- "--line-number",
32872
- "--with-filename"
32873
- ];
32874
- var GREP_SAFETY_FLAGS = ["-n", "-H", "--color=never"];
33421
+ ---
33422
+ *Council: ${completed2}/${total2} councillors responded (${composition2}) \u2014 degraded*`;
33423
+ }
33424
+ return `Council session failed: ${result.error}`;
33425
+ }
33426
+ let output = result.result ?? "(No output)";
33427
+ const completed = result.councillorResults.filter((cr) => cr.status === "completed").length;
33428
+ const total = result.councillorResults.length;
33429
+ const composition = formatModelComposition(result.councillorResults);
33430
+ output += `
32875
33431
 
32876
- // src/tools/grep/cli.ts
32877
- function buildRgArgs(options) {
32878
- const args = [
32879
- ...RG_SAFETY_FLAGS,
32880
- `--max-depth=${Math.min(options.maxDepth ?? DEFAULT_MAX_DEPTH, DEFAULT_MAX_DEPTH)}`,
32881
- `--max-filesize=${options.maxFilesize ?? DEFAULT_MAX_FILESIZE}`,
32882
- `--max-count=${Math.min(options.maxCount ?? DEFAULT_MAX_COUNT, DEFAULT_MAX_COUNT)}`,
32883
- `--max-columns=${Math.min(options.maxColumns ?? DEFAULT_MAX_COLUMNS, DEFAULT_MAX_COLUMNS)}`
32884
- ];
32885
- if (options.context !== undefined && options.context > 0) {
32886
- args.push(`-C${Math.min(options.context, 10)}`);
32887
- }
32888
- if (options.caseSensitive) {
32889
- args.push("--case-sensitive");
32890
- } else {
32891
- args.push("-i");
32892
- }
32893
- if (options.wholeWord)
32894
- args.push("-w");
32895
- if (options.fixedStrings)
32896
- args.push("-F");
32897
- if (options.multiline)
32898
- args.push("-U");
32899
- if (options.hidden)
32900
- args.push("--hidden");
32901
- if (options.noIgnore)
32902
- args.push("--no-ignore");
32903
- if (options.fileType?.length) {
32904
- for (const type of options.fileType) {
32905
- args.push(`--type=${type}`);
32906
- }
32907
- }
32908
- if (options.globs) {
32909
- for (const glob of options.globs) {
32910
- args.push(`--glob=${glob}`);
32911
- }
32912
- }
32913
- if (options.excludeGlobs) {
32914
- for (const glob of options.excludeGlobs) {
32915
- args.push(`--glob=!${glob}`);
32916
- }
32917
- }
32918
- return args;
32919
- }
32920
- function buildGrepArgs(options) {
32921
- const args = [...GREP_SAFETY_FLAGS, "-r"];
32922
- if (options.context !== undefined && options.context > 0) {
32923
- args.push(`-C${Math.min(options.context, 10)}`);
32924
- }
32925
- if (!options.caseSensitive)
32926
- args.push("-i");
32927
- if (options.wholeWord)
32928
- args.push("-w");
32929
- if (options.fixedStrings)
32930
- args.push("-F");
32931
- if (options.globs?.length) {
32932
- for (const glob of options.globs) {
32933
- args.push(`--include=${glob}`);
32934
- }
32935
- }
32936
- if (options.excludeGlobs?.length) {
32937
- for (const glob of options.excludeGlobs) {
32938
- args.push(`--exclude=${glob}`);
32939
- }
32940
- }
32941
- args.push("--exclude-dir=.git", "--exclude-dir=node_modules");
32942
- return args;
32943
- }
32944
- function buildArgs(options, backend) {
32945
- return backend === "rg" ? buildRgArgs(options) : buildGrepArgs(options);
32946
- }
32947
- function parseOutput(output) {
32948
- if (!output.trim())
32949
- return [];
32950
- const matches = [];
32951
- const lines = output.trim().split(/\r?\n/);
32952
- for (const line of lines) {
32953
- if (!line.trim())
32954
- continue;
32955
- const match = line.match(/^(.+?):(\d+):(.*)$/);
32956
- if (match) {
32957
- matches.push({
32958
- file: match[1],
32959
- line: parseInt(match[2], 10),
32960
- text: match[3]
32961
- });
33432
+ ---
33433
+ *Council: ${completed}/${total} councillors responded (${composition})*`;
33434
+ return output;
32962
33435
  }
32963
- }
32964
- return matches;
32965
- }
32966
- async function runRg(options) {
32967
- const cli = resolveGrepCli();
32968
- const args = buildArgs(options, cli.backend);
32969
- const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS3, DEFAULT_TIMEOUT_MS3);
32970
- if (cli.backend === "rg") {
32971
- args.push("--", options.pattern);
32972
- } else {
32973
- args.push("-e", options.pattern);
32974
- }
32975
- const paths2 = options.paths?.length ? options.paths : ["."];
32976
- args.push(...paths2);
32977
- const proc = spawn4([cli.path, ...args], {
32978
- stdout: "pipe",
32979
- stderr: "pipe"
32980
33436
  });
32981
- const timeoutPromise = new Promise((_, reject) => {
32982
- const id = setTimeout(() => {
32983
- proc.kill();
32984
- reject(new Error(`Search timeout after ${timeout}ms`));
32985
- }, timeout);
32986
- proc.exited.then(() => clearTimeout(id));
32987
- });
32988
- try {
32989
- const stdout = await Promise.race([
32990
- new Response(proc.stdout).text(),
32991
- timeoutPromise
32992
- ]);
32993
- const stderr = await new Response(proc.stderr).text();
32994
- const exitCode = await proc.exited;
32995
- const truncated = stdout.length >= DEFAULT_MAX_OUTPUT_BYTES2;
32996
- const outputToProcess = truncated ? stdout.substring(0, DEFAULT_MAX_OUTPUT_BYTES2) : stdout;
32997
- if (exitCode > 1 && stderr.trim()) {
32998
- return {
32999
- matches: [],
33000
- totalMatches: 0,
33001
- filesSearched: 0,
33002
- truncated: false,
33003
- error: stderr.trim()
33004
- };
33005
- }
33006
- const matches = parseOutput(outputToProcess);
33007
- const filesSearched = new Set(matches.map((m) => m.file)).size;
33008
- return {
33009
- matches,
33010
- totalMatches: matches.length,
33011
- filesSearched,
33012
- truncated
33013
- };
33014
- } catch (e) {
33015
- return {
33016
- matches: [],
33017
- totalMatches: 0,
33018
- filesSearched: 0,
33019
- truncated: false,
33020
- error: e instanceof Error ? e.message : String(e)
33021
- };
33022
- }
33023
- }
33024
- // src/tools/grep/utils.ts
33025
- function formatGrepResult(result) {
33026
- if (result.error) {
33027
- return `Error: ${result.error}`;
33028
- }
33029
- if (result.matches.length === 0) {
33030
- return "No matches found.";
33031
- }
33032
- const lines = [];
33033
- const byFile = new Map;
33034
- for (const match of result.matches) {
33035
- const existing = byFile.get(match.file) || [];
33036
- existing.push({ line: match.line, text: match.text });
33037
- byFile.set(match.file, existing);
33038
- }
33039
- for (const [file3, matches] of byFile) {
33040
- lines.push(`
33041
- ${file3}:`);
33042
- for (const match of matches) {
33043
- lines.push(` ${match.line}: ${match.text}`);
33044
- }
33045
- }
33046
- const summary = `Found ${result.totalMatches} matches in ${result.filesSearched} files`;
33047
- if (result.truncated) {
33048
- lines.push(`
33049
- ${summary} (output truncated)`);
33050
- } else {
33051
- lines.push(`
33052
- ${summary}`);
33053
- }
33054
- return lines.join(`
33055
- `);
33437
+ return { council_session };
33056
33438
  }
33057
-
33058
- // src/tools/grep/tools.ts
33059
- var grep = tool({
33060
- description: "Fast content search tool with safety limits (60s timeout, 10MB output). " + "Searches file contents using regular expressions. " + 'Supports full regex syntax (eg. "log.*Error", "function\\s+\\w+", etc.). ' + 'Filter files by pattern with the include parameter (e.g. "*.js", "*.{ts,tsx}"). ' + "Returns file paths with matches sorted by modification time.",
33061
- args: {
33062
- pattern: tool.schema.string().describe("The regex pattern to search for in file contents"),
33063
- include: tool.schema.string().optional().describe('File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")'),
33064
- path: tool.schema.string().optional().describe("The directory to search in. Defaults to the current working directory."),
33065
- caseSensitive: tool.schema.boolean().optional().default(false).describe("Perform case-sensitive search (default: false)"),
33066
- wholeWord: tool.schema.boolean().optional().default(false).describe("Match whole words only (default: false)"),
33067
- fixedStrings: tool.schema.boolean().optional().default(false).describe("Treat pattern as literal string (default: false)")
33068
- },
33069
- execute: async (args) => {
33070
- try {
33071
- const globs = args.include ? [args.include] : undefined;
33072
- const paths2 = args.path ? [args.path] : undefined;
33073
- const result = await runRg({
33074
- pattern: args.pattern,
33075
- paths: paths2,
33076
- globs,
33077
- context: 0,
33078
- caseSensitive: args.caseSensitive ?? false,
33079
- wholeWord: args.wholeWord ?? false,
33080
- fixedStrings: args.fixedStrings ?? false
33081
- });
33082
- return formatGrepResult(result);
33083
- } catch (e) {
33084
- return `Error: ${e instanceof Error ? e.message : String(e)}`;
33085
- }
33086
- }
33087
- });
33088
33439
  // src/tools/lsp/client.ts
33089
33440
  var import_node = __toESM(require_main(), 1);
33090
33441
  import { readFileSync as readFileSync4 } from "fs";
33091
33442
  import { extname, resolve as resolve2 } from "path";
33092
33443
  import { Readable, Writable } from "stream";
33093
33444
  import { pathToFileURL } from "url";
33094
- var {spawn: spawn5 } = globalThis.Bun;
33445
+ var {spawn: spawn4 } = globalThis.Bun;
33095
33446
 
33096
33447
  // src/tools/lsp/config.ts
33097
33448
  var import_which = __toESM(require_lib(), 1);
33098
- import { existsSync as existsSync10 } from "fs";
33449
+ import { existsSync as existsSync8 } from "fs";
33099
33450
  import { homedir as homedir4 } from "os";
33100
- import { join as join11 } from "path";
33451
+ import { join as join9 } from "path";
33101
33452
 
33102
33453
  // src/tools/lsp/config-store.ts
33103
33454
  var userConfig = new Map;
@@ -33127,8 +33478,8 @@ function hasUserLspConfig() {
33127
33478
  }
33128
33479
 
33129
33480
  // src/tools/lsp/constants.ts
33130
- import { existsSync as existsSync9, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
33131
- import { dirname as dirname5, resolve } from "path";
33481
+ import { existsSync as existsSync7, readdirSync, statSync as statSync3 } from "fs";
33482
+ import { dirname as dirname4, resolve } from "path";
33132
33483
  var SEVERITY_MAP = {
33133
33484
  1: "error",
33134
33485
  2: "warning",
@@ -33148,10 +33499,10 @@ function* walkUpDirectories(start, stop) {
33148
33499
  let dir = resolve(start);
33149
33500
  try {
33150
33501
  if (!statSync3(dir).isDirectory()) {
33151
- dir = dirname5(dir);
33502
+ dir = dirname4(dir);
33152
33503
  }
33153
33504
  } catch {
33154
- dir = dirname5(dir);
33505
+ dir = dirname4(dir);
33155
33506
  }
33156
33507
  let prevDir = "";
33157
33508
  while (dir !== prevDir && dir !== "/") {
@@ -33159,7 +33510,7 @@ function* walkUpDirectories(start, stop) {
33159
33510
  prevDir = dir;
33160
33511
  if (dir === stop)
33161
33512
  break;
33162
- dir = dirname5(dir);
33513
+ dir = dirname4(dir);
33163
33514
  }
33164
33515
  }
33165
33516
  function NearestRoot(includePatterns, excludePatterns) {
@@ -33168,7 +33519,7 @@ function NearestRoot(includePatterns, excludePatterns) {
33168
33519
  if (excludePatterns) {
33169
33520
  for (const dir of walkUpDirectories(file3, cwd)) {
33170
33521
  for (const pattern of excludePatterns) {
33171
- if (existsSync9(`${dir}/${pattern}`)) {
33522
+ if (existsSync7(`${dir}/${pattern}`)) {
33172
33523
  return;
33173
33524
  }
33174
33525
  }
@@ -33178,13 +33529,13 @@ function NearestRoot(includePatterns, excludePatterns) {
33178
33529
  for (const pattern of includePatterns) {
33179
33530
  if (pattern.includes("*")) {
33180
33531
  try {
33181
- const entries = readdirSync2(dir);
33532
+ const entries = readdirSync(dir);
33182
33533
  const regex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\*/g, ".*")}$`);
33183
33534
  if (entries.some((entry) => regex.test(entry))) {
33184
33535
  return dir;
33185
33536
  }
33186
33537
  } catch {}
33187
- } else if (existsSync9(`${dir}/${pattern}`)) {
33538
+ } else if (existsSync7(`${dir}/${pattern}`)) {
33188
33539
  return dir;
33189
33540
  }
33190
33541
  }
@@ -33732,11 +34083,11 @@ function isServerInstalled(command) {
33732
34083
  return false;
33733
34084
  const cmd = command[0];
33734
34085
  if (cmd.includes("/") || cmd.includes("\\")) {
33735
- return existsSync10(cmd);
34086
+ return existsSync8(cmd);
33736
34087
  }
33737
34088
  const isWindows = process.platform === "win32";
33738
34089
  const ext = isWindows ? ".exe" : "";
33739
- const opencodeBin = join11(homedir4(), ".config", "opencode", "bin");
34090
+ const opencodeBin = join9(homedir4(), ".config", "opencode", "bin");
33740
34091
  const searchPath = (process.env.PATH ?? "") + (isWindows ? ";" : ":") + opencodeBin;
33741
34092
  const result = import_which.default.sync(cmd, {
33742
34093
  path: searchPath,
@@ -33747,8 +34098,8 @@ function isServerInstalled(command) {
33747
34098
  return true;
33748
34099
  }
33749
34100
  const cwd = process.cwd();
33750
- const localBin = join11(cwd, "node_modules", ".bin", cmd);
33751
- if (existsSync10(localBin) || existsSync10(localBin + ext)) {
34101
+ const localBin = join9(cwd, "node_modules", ".bin", cmd);
34102
+ if (existsSync8(localBin) || existsSync8(localBin + ext)) {
33752
34103
  return true;
33753
34104
  }
33754
34105
  return false;
@@ -33929,7 +34280,7 @@ class LSPClient {
33929
34280
  command: this.server.command.join(" "),
33930
34281
  root: this.root
33931
34282
  });
33932
- this.proc = spawn5(this.server.command, {
34283
+ this.proc = spawn4(this.server.command, {
33933
34284
  stdin: "pipe",
33934
34285
  stdout: "pipe",
33935
34286
  stderr: "pipe",
@@ -34161,19 +34512,19 @@ stderr: ${stderr}` : ""));
34161
34512
  }
34162
34513
  // src/tools/lsp/utils.ts
34163
34514
  import {
34164
- existsSync as existsSync11,
34515
+ existsSync as existsSync9,
34165
34516
  readFileSync as readFileSync5,
34166
34517
  statSync as statSync4,
34167
- unlinkSync as unlinkSync3,
34518
+ unlinkSync as unlinkSync2,
34168
34519
  writeFileSync as writeFileSync3
34169
34520
  } from "fs";
34170
- import { dirname as dirname6, extname as extname2, join as join12, resolve as resolve3 } from "path";
34521
+ import { dirname as dirname5, extname as extname2, join as join10, resolve as resolve3 } from "path";
34171
34522
  import { fileURLToPath as fileURLToPath2 } from "url";
34172
34523
  function findServerProjectRoot(filePath, server) {
34173
34524
  if (server.root) {
34174
- return server.root(filePath) ?? dirname6(resolve3(filePath));
34525
+ return server.root(filePath) ?? dirname5(resolve3(filePath));
34175
34526
  }
34176
- return dirname6(resolve3(filePath));
34527
+ return dirname5(resolve3(filePath));
34177
34528
  }
34178
34529
  function uriToPath(uri) {
34179
34530
  return fileURLToPath2(uri);
@@ -34203,7 +34554,7 @@ async function withLspClient(filePath, fn) {
34203
34554
  throw new Error(formatServerLookupError(result));
34204
34555
  }
34205
34556
  const server = result.server;
34206
- const root = findServerProjectRoot(absPath, server) ?? dirname6(absPath);
34557
+ const root = findServerProjectRoot(absPath, server) ?? dirname5(absPath);
34207
34558
  log("[lsp] withLspClient: acquiring client", {
34208
34559
  filePath: absPath,
34209
34560
  server: server.id,
@@ -34358,7 +34709,7 @@ function applyWorkspaceEdit(edit) {
34358
34709
  const newPath = uriToPath(change.newUri);
34359
34710
  const content = readFileSync5(oldPath, "utf-8");
34360
34711
  writeFileSync3(newPath, content, "utf-8");
34361
- unlinkSync3(oldPath);
34712
+ unlinkSync2(oldPath);
34362
34713
  result.filesModified.push(newPath);
34363
34714
  } catch (err) {
34364
34715
  result.success = false;
@@ -34367,7 +34718,7 @@ function applyWorkspaceEdit(edit) {
34367
34718
  } else if (change.kind === "delete") {
34368
34719
  try {
34369
34720
  const filePath = uriToPath(change.uri);
34370
- unlinkSync3(filePath);
34721
+ unlinkSync2(filePath);
34371
34722
  result.filesModified.push(filePath);
34372
34723
  } catch (err) {
34373
34724
  result.success = false;
@@ -34578,6 +34929,7 @@ var OhMyOpenCodeLite = async (ctx) => {
34578
34929
  }
34579
34930
  const backgroundManager = new BackgroundTaskManager(ctx, tmuxConfig, config3);
34580
34931
  const backgroundTools = createBackgroundTools(ctx, backgroundManager, tmuxConfig, config3);
34932
+ const councilTools = config3.council ? createCouncilTool(ctx, new CouncilManager(ctx, config3, backgroundManager.getDepthTracker(), tmuxConfig.enabled)) : {};
34581
34933
  const mcps = createBuiltinMcps(config3.disabled_mcps);
34582
34934
  const tmuxSessionManager = new TmuxSessionManager(ctx, tmuxConfig);
34583
34935
  const autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
@@ -34585,7 +34937,7 @@ var OhMyOpenCodeLite = async (ctx) => {
34585
34937
  autoUpdate: true
34586
34938
  });
34587
34939
  const phaseReminderHook = createPhaseReminderHook();
34588
- const postReadNudgeHook = createPostReadNudgeHook();
34940
+ const postFileToolNudgeHook = createPostFileToolNudgeHook();
34589
34941
  const chatHeadersHook = createChatHeadersHook(ctx);
34590
34942
  const delegateTaskRetryHook = createDelegateTaskRetryHook(ctx);
34591
34943
  const jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
@@ -34595,11 +34947,11 @@ var OhMyOpenCodeLite = async (ctx) => {
34595
34947
  agent: agents,
34596
34948
  tool: {
34597
34949
  ...backgroundTools,
34950
+ ...councilTools,
34598
34951
  lsp_goto_definition,
34599
34952
  lsp_find_references,
34600
34953
  lsp_diagnostics,
34601
34954
  lsp_rename,
34602
- grep,
34603
34955
  ast_grep_search,
34604
34956
  ast_grep_replace
34605
34957
  },
@@ -34651,52 +35003,22 @@ var OhMyOpenCodeLite = async (ctx) => {
34651
35003
  }
34652
35004
  }
34653
35005
  if (Object.keys(effectiveArrays).length > 0) {
34654
- const providerConfig = opencodeConfig.provider ?? {};
34655
- const hasProviderConfig = Object.keys(providerConfig).length > 0;
34656
35006
  for (const [agentName, modelArray] of Object.entries(effectiveArrays)) {
34657
35007
  if (modelArray.length === 0)
34658
35008
  continue;
34659
- let resolved = false;
34660
- if (hasProviderConfig) {
34661
- const configuredProviders = Object.keys(providerConfig);
34662
- for (const modelEntry of modelArray) {
34663
- const slashIdx = modelEntry.id.indexOf("/");
34664
- if (slashIdx === -1)
34665
- continue;
34666
- const providerID = modelEntry.id.slice(0, slashIdx);
34667
- if (configuredProviders.includes(providerID)) {
34668
- const entry = configAgent[agentName];
34669
- if (entry) {
34670
- entry.model = modelEntry.id;
34671
- if (modelEntry.variant) {
34672
- entry.variant = modelEntry.variant;
34673
- }
34674
- }
34675
- log("[plugin] resolved model fallback", {
34676
- agent: agentName,
34677
- model: modelEntry.id,
34678
- variant: modelEntry.variant
34679
- });
34680
- resolved = true;
34681
- break;
34682
- }
34683
- }
34684
- }
34685
- if (!resolved) {
34686
- const firstModel = modelArray[0];
34687
- const entry = configAgent[agentName];
34688
- if (entry) {
34689
- entry.model = firstModel.id;
34690
- if (firstModel.variant) {
34691
- entry.variant = firstModel.variant;
34692
- }
35009
+ const chosen = modelArray[0];
35010
+ const entry = configAgent[agentName];
35011
+ if (entry) {
35012
+ entry.model = chosen.id;
35013
+ if (chosen.variant) {
35014
+ entry.variant = chosen.variant;
34693
35015
  }
34694
- log("[plugin] resolved model from array (no provider config)", {
34695
- agent: agentName,
34696
- model: firstModel.id,
34697
- variant: firstModel.variant
34698
- });
34699
35016
  }
35017
+ log("[plugin] resolved model from array", {
35018
+ agent: agentName,
35019
+ model: chosen.id,
35020
+ variant: chosen.variant
35021
+ });
34700
35022
  }
34701
35023
  }
34702
35024
  const configMcp = opencodeConfig.mcp;
@@ -34741,7 +35063,7 @@ var OhMyOpenCodeLite = async (ctx) => {
34741
35063
  "tool.execute.after": async (input, output) => {
34742
35064
  await delegateTaskRetryHook["tool.execute.after"](input, output);
34743
35065
  await jsonErrorRecoveryHook["tool.execute.after"](input, output);
34744
- await postReadNudgeHook["tool.execute.after"](input, output);
35066
+ await postFileToolNudgeHook["tool.execute.after"](input, output);
34745
35067
  }
34746
35068
  };
34747
35069
  };