oh-my-opencode-slim 0.8.4 → 0.8.6

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,417 @@ 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, code reviewer
17583
+ - Stats: 5x better decision maker, problem solver, investigator than orchestrator, 0.8x speed of orchestrator, same cost.
17584
+ - Capabilities: Deep architectural reasoning, system-level trade-offs, complex debugging, code review
17585
+ - **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 \u2022 When a workflow calls for a **reviewer** subagent
17586
+ - **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
17587
+ - **Rule of thumb:** Need senior architect review? \u2192 @oracle. Need code review? \u2192 @oracle. Just do it and PR? \u2192 yourself.
17588
+
17589
+ @designer
17590
+ - Role: UI/UX specialist for intentional, polished experiences
17591
+ - Stats: 10x better UI/UX than orchestrator
17592
+ - Capabilities: Visual direction, interactions, responsive layouts, design systems with aesthetic intent
17593
+ - **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
17594
+ - **Don't delegate when:** Backend/logic with no visual \u2022 Quick prototypes where design doesn't matter yet
17595
+ - **Rule of thumb:** Users see it and polish matters? \u2192 @designer. Headless/functional? \u2192 yourself.
17596
+
17597
+ @fixer
17598
+ - Role: Fast execution specialist for well-defined tasks, which empowers orchestrator with parallel, speedy executions
17599
+ - Stats: 2x faster code edits, 1/2 cost of orchestrator, 0.8x quality of orchestrator
17600
+ - Tools/Constraints: Execution-focused\u2014no research, no architectural decisions
17601
+ - **Delegate when:** For implementation work, think and triage first. If the change is non-trivial or multi-file, hand bounded execution to @fixer
17602
+ - **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
17603
+ - **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.
17604
+
17605
+ @council
17606
+ - Role: Multi-LLM consensus engine for high-confidence answers
17607
+ - Stats: 3x slower than orchestrator, 3x or more cost of orchestrator
17608
+ - Capabilities: Runs multiple models in parallel, synthesizes their responses via a council master
17609
+ - **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
17610
+ - **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
17611
+ - **Result handling:** Present the council's synthesized response verbatim. Do not re-summarize \u2014 the council master has already produced the final answer.
17612
+ - **Rule of thumb:** Need second/third opinions from different models? \u2192 @council. One good answer enough? \u2192 yourself.
17613
+
17614
+ </Agents>
17615
+
17616
+ <Workflow>
17617
+
17618
+ ## 1. Understand
17619
+ Parse request: explicit requirements + implicit needs.
17620
+
17621
+ ## 2. Path Selection
17622
+ Evaluate approach by: quality, speed, cost, reliability.
17623
+ Choose the path that optimizes all four.
17624
+
17625
+ ## 3. Delegation Check
17626
+ **STOP. Review specialists before acting.**
17627
+
17628
+ !!! Review available agents and delegation rules. Decide whether to delegate or do it yourself. !!!
17629
+
17630
+ **Delegation efficiency:**
17631
+ - Reference paths/lines, don't paste files (\`src/app.ts:42\` not full contents)
17632
+ - Provide context summaries, let specialists read what they need
17633
+ - Brief user on delegation goal before each call
17634
+ - Skip delegation if overhead \u2265 doing it yourself
17635
+
17636
+ ## 4. Split and Parallelize
17637
+ Can tasks be split into subtasks and run in parallel?
17638
+ - Multiple @explorer searches across different domains?
17639
+ - @explorer + @librarian research in parallel?
17640
+ - Multiple @fixer instances for faster, scoped implementation?
17641
+
17642
+ Balance: respect dependencies, avoid parallelizing what must be sequential.
17643
+
17644
+ ## 5. Execute
17645
+ 1. Break complex tasks into todos
17646
+ 2. Fire parallel research/implementation
17647
+ 3. Delegate to specialists or do it yourself based on step 3
17648
+ 4. Integrate results
17649
+ 5. Adjust if needed
17650
+
17651
+ ## 6. Verify
17652
+ - Run \`lsp_diagnostics\` for errors
17653
+ - Suggest \`simplify\` skill when applicable
17654
+ - Confirm specialists completed successfully
17655
+ - Verify solution meets requirements
17656
+
17657
+ </Workflow>
17658
+
17659
+ <Communication>
17660
+
17661
+ ## Clarity Over Assumptions
17662
+ - If request is vague or has multiple valid interpretations, ask a targeted question before proceeding
17663
+ - Don't guess at critical details (file paths, API choices, architectural decisions)
17664
+ - Do make reasonable assumptions for minor details and state them briefly
17665
+
17666
+ ## Concise Execution
17667
+ - Answer directly, no preamble
17668
+ - Don't summarize what you did unless asked
17669
+ - Don't explain code unless asked
17670
+ - One-word answers are fine when appropriate
17671
+ - Brief delegation notices: "Checking docs via @librarian..." not "I'm going to delegate to @librarian because..."
17672
+
17673
+ ## No Flattery
17674
+ Never: "Great question!" "Excellent idea!" "Smart choice!" or any praise of user input.
17675
+
17676
+ ## Honest Pushback
17677
+ When user's approach seems problematic:
17678
+ - State concern + alternative concisely
17679
+ - Ask if they want to proceed anyway
17680
+ - Don't lecture, don't blindly implement
17681
+
17682
+ ## Example
17683
+ **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."
17684
+
17685
+ **Good:** "Checking Next.js App Router docs via @librarian..."
17686
+ [proceeds with implementation]
17687
+
17688
+ </Communication>
17689
+ `;
17690
+ function createOrchestratorAgent(model, customPrompt, customAppendPrompt) {
17691
+ const prompt = resolvePrompt(ORCHESTRATOR_PROMPT, customPrompt, customAppendPrompt);
17692
+ const definition = {
17693
+ name: "orchestrator",
17694
+ description: "AI coding orchestrator that delegates tasks to specialist agents for optimal quality, speed, and cost",
17695
+ config: {
17696
+ temperature: 0.1,
17697
+ prompt
17698
+ }
17699
+ };
17700
+ if (Array.isArray(model)) {
17701
+ definition._modelArray = model.map((m) => typeof m === "string" ? { id: m } : m);
17702
+ } else if (typeof model === "string" && model) {
17703
+ definition.config.model = model;
17704
+ }
17705
+ return definition;
17706
+ }
17707
+
17708
+ // src/agents/council.ts
17709
+ var COUNCIL_AGENT_PROMPT = `You are the Council agent \u2014 a multi-LLM orchestration system that runs consensus across multiple models.
17710
+
17711
+ **Tool**: You have access to the \`council_session\` tool.
17712
+
17713
+ **When to use**:
17714
+ - When invoked by a user with a request
17715
+ - When you want multiple expert opinions on a complex problem
17716
+ - When higher confidence is needed through model consensus
17717
+
17718
+ **Usage**:
17719
+ 1. Call the \`council_session\` tool with the user's prompt
17720
+ 2. Optionally specify a preset (default: "default")
17721
+ 3. Receive the synthesized response from the council master
17722
+ 4. Present the result to the user
17723
+
17724
+ **Behavior**:
17725
+ - Delegate requests directly to council_session
17726
+ - Don't pre-analyze or filter the prompt
17727
+ - Present the synthesized result verbatim \u2014 do not re-summarize or condense
17728
+ - Briefly explain the consensus if requested`;
17729
+ function createCouncilAgent(model, customPrompt, customAppendPrompt) {
17730
+ const prompt = resolvePrompt(COUNCIL_AGENT_PROMPT, customPrompt, customAppendPrompt);
17731
+ const definition = {
17732
+ name: "council",
17733
+ description: "Multi-LLM council agent that synthesizes responses from multiple models for higher-quality outputs",
17734
+ config: {
17735
+ temperature: 0.1,
17736
+ prompt
17737
+ }
17738
+ };
17739
+ if (model) {
17740
+ definition.config.model = model;
17741
+ }
17742
+ return definition;
17743
+ }
17744
+ function formatCouncillorPrompt(userPrompt, councillorPrompt) {
17745
+ if (!councillorPrompt)
17746
+ return userPrompt;
17747
+ return `${councillorPrompt}
17748
+
17749
+ ---
17750
+
17751
+ ${userPrompt}`;
17752
+ }
17753
+ function formatMasterSynthesisPrompt(originalPrompt, councillorResults, masterPrompt) {
17754
+ const completedWithResults = councillorResults.filter((cr) => cr.status === "completed" && cr.result);
17755
+ const councillorSection = completedWithResults.map((cr) => {
17756
+ const shortModel = shortModelLabel(cr.model);
17757
+ return `**${cr.name}** (${shortModel}):
17758
+ ${cr.result}`;
17759
+ }).join(`
17760
+
17761
+ `);
17762
+ const failedSection = councillorResults.filter((cr) => cr.status !== "completed").map((cr) => `**${cr.name}**: ${cr.status} \u2014 ${cr.error ?? "Unknown"}`).join(`
17763
+ `);
17764
+ if (completedWithResults.length === 0) {
17765
+ return `---
17766
+
17767
+ **Original Prompt**:
17768
+ ${originalPrompt}
17769
+
17770
+ ---
17771
+
17772
+ **Councillor Responses**:
17773
+ All councillors failed to produce output. Please generate a response based on the original prompt alone.`;
17774
+ }
17775
+ let prompt = `---
17776
+
17777
+ **Original Prompt**:
17778
+ ${originalPrompt}
17779
+
17780
+ ---
17781
+
17782
+ **Councillor Responses**:
17783
+ ${councillorSection}`;
17784
+ if (failedSection) {
17785
+ prompt += `
17786
+
17787
+ ---
17788
+
17789
+ **Failed/Timed-out Councillors**:
17790
+ ${failedSection}`;
17791
+ }
17792
+ prompt += `
17793
+
17794
+ ---
17795
+
17796
+ Synthesize the optimal response based on the above.`;
17797
+ if (masterPrompt) {
17798
+ prompt += `
17799
+
17800
+ ---
17801
+
17802
+ **Master Guidance**:
17803
+ ${masterPrompt}`;
17804
+ }
17805
+ return prompt;
17806
+ }
17807
+
17808
+ // src/agents/council-master.ts
17809
+ var COUNCIL_MASTER_PROMPT = `You are the council master responsible for synthesizing responses from multiple AI models.
17810
+
17811
+ **Role**: Review all councillor responses and create the optimal final answer.
17812
+
17813
+ **Process**:
17814
+ 1. Read the original user prompt
17815
+ 2. Review each councillor's response carefully
17816
+ 3. Identify the best elements from each response
17817
+ 4. Resolve contradictions between councillors
17818
+ 5. Synthesize a final, optimal response
17819
+
17820
+ **Behavior**:
17821
+ - Each councillor had read-only access to the codebase \u2014 their responses may reference specific files, functions, and line numbers
17822
+ - Clearly explain your reasoning for the chosen approach
17823
+ - Be transparent about trade-offs
17824
+ - Credit specific insights from individual councillors by name
17825
+ - If councillors disagree, explain your resolution
17826
+ - Don't just average responses \u2014 choose and improve
17827
+
17828
+ **Output**:
17829
+ - Present the synthesized solution
17830
+ - Review, retain, and include relevant code examples, diagrams, and concrete details from councillor responses
17831
+ - Explain your synthesis reasoning
17832
+ - Note any remaining uncertainties
17833
+ - Acknowledge if consensus was impossible`;
17834
+ function createCouncilMasterAgent(model, customPrompt, customAppendPrompt) {
17835
+ const prompt = resolvePrompt(COUNCIL_MASTER_PROMPT, customPrompt, customAppendPrompt);
17836
+ return {
17837
+ name: "council-master",
17838
+ description: "Council synthesis engine. Receives councillor responses and produces the final answer. No tools, pure text synthesis.",
17839
+ config: {
17840
+ model,
17841
+ temperature: 0.1,
17842
+ prompt,
17843
+ permission: {
17844
+ "*": "deny",
17845
+ question: "deny"
17846
+ }
17847
+ }
17848
+ };
17849
+ }
17850
+
17851
+ // src/agents/councillor.ts
17852
+ var COUNCILLOR_PROMPT = `You are a councillor in a multi-model council.
17853
+
17854
+ **Role**: Provide your best independent analysis and solution to the given problem.
17855
+
17856
+ **Capabilities**: You have read-only access to the codebase. You can:
17857
+ - Read files (read)
17858
+ - Search by name patterns (glob)
17859
+ - Search by content (grep)
17860
+ - Query language server (lsp_diagnostics, lsp_goto_definition, lsp_find_references)
17861
+ - Search code patterns (ast_grep_search)
17862
+ - Search external docs (if MCPs are configured for this agent)
17863
+
17864
+ You CANNOT edit files, write files, run shell commands, or delegate to other agents. You are an advisor, not an implementer.
17865
+
17866
+ **Behavior**:
17867
+ - **Examine the codebase** before answering \u2014 your read access is what makes council valuable. Don't guess at code you can see.
17868
+ - Analyze the problem thoroughly
17869
+ - Provide a complete, well-reasoned response
17870
+ - Focus on the quality and correctness of your solution
17871
+ - Be direct and concise
17872
+ - Don't be influenced by what other councillors might say \u2014 you won't see their responses
17873
+
17874
+ **Output**:
17875
+ - Give your honest assessment
17876
+ - Reference specific files and line numbers when relevant
17877
+ - Include relevant reasoning
17878
+ - State any assumptions clearly
17879
+ - Note any uncertainties`;
17880
+ function createCouncillorAgent(model, customPrompt, customAppendPrompt) {
17881
+ const prompt = resolvePrompt(COUNCILLOR_PROMPT, customPrompt, customAppendPrompt);
17882
+ return {
17883
+ name: "councillor",
17884
+ description: "Read-only council advisor. Examines codebase and provides independent analysis. Spawned internally by the council system.",
17885
+ config: {
17886
+ model,
17887
+ temperature: 0.2,
17888
+ prompt,
17889
+ permission: {
17890
+ "*": "deny",
17891
+ question: "deny",
17892
+ read: "allow",
17893
+ glob: "allow",
17894
+ grep: "allow",
17895
+ lsp: "allow",
17896
+ list: "allow",
17897
+ codesearch: "allow"
17898
+ }
17899
+ }
17900
+ };
17901
+ }
17902
+
17401
17903
  // src/agents/designer.ts
17402
17904
  var DESIGNER_PROMPT = `You are a Designer - a frontend UI/UX specialist who creates intentional, polished experiences.
17403
17905
 
@@ -17474,19 +17976,9 @@ var EXPLORER_PROMPT = `You are Explorer - a fast codebase navigation specialist.
17474
17976
 
17475
17977
  **Role**: Quick contextual grep for codebases. Answer "Where is X?", "Find Y", "Which file has Z".
17476
17978
 
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**:
17979
+ **When to use which tools**:
17488
17980
  - **Text/regex patterns** (strings, comments, variable names): grep
17489
- - **Structural patterns** (function shapes, class structures): ast_grep_search
17981
+ - **Structural patterns** (function shapes, class structures): ast_grep_search
17490
17982
  - **File discovery** (find by name/extension): glob
17491
17983
 
17492
17984
  **Behavior**:
@@ -17671,165 +18163,6 @@ ${customAppendPrompt}`;
17671
18163
  };
17672
18164
  }
17673
18165
 
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
18166
  // src/agents/index.ts
17834
18167
  function applyOverrides(agent, override) {
17835
18168
  if (override.model) {
@@ -17848,9 +18181,10 @@ function applyOverrides(agent, override) {
17848
18181
  function applyDefaultPermissions(agent, configuredSkills) {
17849
18182
  const existing = agent.config.permission ?? {};
17850
18183
  const skillPermissions = getSkillPermissionsForAgent(agent.name, configuredSkills);
18184
+ const questionPerm = existing.question === "deny" ? "deny" : "allow";
17851
18185
  agent.config.permission = {
17852
18186
  ...existing,
17853
- question: "allow",
18187
+ question: questionPerm,
17854
18188
  skill: {
17855
18189
  ...typeof existing.skill === "object" ? existing.skill : {},
17856
18190
  ...skillPermissions
@@ -17865,7 +18199,10 @@ var SUBAGENT_FACTORIES = {
17865
18199
  librarian: createLibrarianAgent,
17866
18200
  oracle: createOracleAgent,
17867
18201
  designer: createDesignerAgent,
17868
- fixer: createFixerAgent
18202
+ fixer: createFixerAgent,
18203
+ council: createCouncilAgent,
18204
+ councillor: createCouncillorAgent,
18205
+ "council-master": createCouncilMasterAgent
17869
18206
  };
17870
18207
  function createAgents(config2) {
17871
18208
  const getModelForAgent = (name) => {
@@ -17912,7 +18249,12 @@ function getAgentConfigs(config2) {
17912
18249
  description: a.description,
17913
18250
  mcps: getAgentMcpList(a.name, config2)
17914
18251
  };
17915
- if (isSubagent(a.name)) {
18252
+ if (a.name === "council") {
18253
+ sdkConfig.mode = "all";
18254
+ } else if (a.name === "councillor" || a.name === "council-master") {
18255
+ sdkConfig.mode = "subagent";
18256
+ sdkConfig.hidden = true;
18257
+ } else if (isSubagent(a.name)) {
17916
18258
  sdkConfig.mode = "subagent";
17917
18259
  } else if (a.name === "orchestrator") {
17918
18260
  sdkConfig.mode = "primary";
@@ -18300,17 +18642,48 @@ async function extractZip(archivePath, destDir) {
18300
18642
  throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
18301
18643
  }
18302
18644
  }
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;
18645
+ // src/background/subagent-depth.ts
18646
+ class SubagentDepthTracker {
18647
+ depthBySession = new Map;
18648
+ _maxDepth;
18649
+ constructor(maxDepth = DEFAULT_MAX_SUBAGENT_DEPTH) {
18650
+ this._maxDepth = maxDepth;
18651
+ }
18652
+ get maxDepth() {
18653
+ return this._maxDepth;
18654
+ }
18655
+ getDepth(sessionId) {
18656
+ return this.depthBySession.get(sessionId) ?? 0;
18657
+ }
18658
+ registerChild(parentSessionId, childSessionId) {
18659
+ const parentDepth = this.getDepth(parentSessionId);
18660
+ const childDepth = parentDepth + 1;
18661
+ if (childDepth > this.maxDepth) {
18662
+ log("[subagent-depth] spawn blocked: max depth exceeded", {
18663
+ parentSessionId,
18664
+ parentDepth,
18665
+ childDepth,
18666
+ maxDepth: this.maxDepth
18667
+ });
18668
+ return false;
18669
+ }
18670
+ this.depthBySession.set(childSessionId, childDepth);
18671
+ log("[subagent-depth] child registered", {
18672
+ parentSessionId,
18673
+ childSessionId,
18674
+ childDepth
18675
+ });
18676
+ return true;
18677
+ }
18678
+ cleanup(sessionId) {
18679
+ this.depthBySession.delete(sessionId);
18680
+ }
18681
+ cleanupAll() {
18682
+ this.depthBySession.clear();
18308
18683
  }
18309
- return {
18310
- providerID: model.slice(0, slashIndex),
18311
- modelID: model.slice(slashIndex + 1)
18312
- };
18313
18684
  }
18685
+
18686
+ // src/background/background-manager.ts
18314
18687
  function generateTaskId() {
18315
18688
  return `bg_${Math.random().toString(36).substring(2, 10)}`;
18316
18689
  }
@@ -18319,6 +18692,7 @@ class BackgroundTaskManager {
18319
18692
  tasks = new Map;
18320
18693
  tasksBySessionId = new Map;
18321
18694
  agentBySessionId = new Map;
18695
+ depthTracker;
18322
18696
  client;
18323
18697
  directory;
18324
18698
  tmuxEnabled;
@@ -18337,6 +18711,7 @@ class BackgroundTaskManager {
18337
18711
  maxConcurrentStarts: 10
18338
18712
  };
18339
18713
  this.maxConcurrentStarts = this.backgroundConfig.maxConcurrentStarts;
18714
+ this.depthTracker = new SubagentDepthTracker;
18340
18715
  }
18341
18716
  getSubagentRules(agentName) {
18342
18717
  return SUBAGENT_DELEGATION_RULES[agentName] ?? ["explorer"];
@@ -18407,30 +18782,7 @@ class BackgroundTaskManager {
18407
18782
  seen.add(model);
18408
18783
  chain.push(model);
18409
18784
  }
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
- }
18785
+ return chain;
18434
18786
  }
18435
18787
  calculateToolPermissions(agentName) {
18436
18788
  const allowedSubagents = this.getSubagentRules(agentName);
@@ -18447,20 +18799,31 @@ class BackgroundTaskManager {
18447
18799
  return;
18448
18800
  }
18449
18801
  try {
18450
- const session = await this.client.session.create({
18802
+ const parentDepth = this.depthTracker.getDepth(task.parentSessionId);
18803
+ if (parentDepth + 1 > this.depthTracker.maxDepth) {
18804
+ log("[background-manager] spawn blocked: max depth exceeded", {
18805
+ parentSessionId: task.parentSessionId,
18806
+ parentDepth,
18807
+ maxDepth: this.depthTracker.maxDepth
18808
+ });
18809
+ this.completeTask(task, "failed", "Subagent depth exceeded");
18810
+ return;
18811
+ }
18812
+ const session2 = await this.client.session.create({
18451
18813
  body: {
18452
18814
  parentID: task.parentSessionId,
18453
18815
  title: `Background: ${task.description}`
18454
18816
  },
18455
18817
  query: { directory: this.directory }
18456
18818
  });
18457
- if (!session.data?.id) {
18819
+ if (!session2.data?.id) {
18458
18820
  throw new Error("Failed to create background session");
18459
18821
  }
18460
- task.sessionId = session.data.id;
18461
- this.tasksBySessionId.set(session.data.id, task.id);
18462
- this.agentBySessionId.set(session.data.id, task.agent);
18822
+ task.sessionId = session2.data.id;
18823
+ this.tasksBySessionId.set(session2.data.id, task.id);
18824
+ this.agentBySessionId.set(session2.data.id, task.agent);
18463
18825
  task.status = "running";
18826
+ this.depthTracker.registerChild(task.parentSessionId, session2.data.id);
18464
18827
  if (this.tmuxEnabled) {
18465
18828
  await new Promise((r) => setTimeout(r, 500));
18466
18829
  }
@@ -18479,7 +18842,7 @@ class BackgroundTaskManager {
18479
18842
  const attemptModels = chain.length > 0 ? chain : [undefined];
18480
18843
  const errors3 = [];
18481
18844
  let succeeded = false;
18482
- const sessionId = session.data.id;
18845
+ const sessionId = session2.data.id;
18483
18846
  for (let i = 0;i < attemptModels.length; i++) {
18484
18847
  const model = attemptModels[i];
18485
18848
  const modelLabel = model ?? "default-model";
@@ -18498,7 +18861,7 @@ class BackgroundTaskManager {
18498
18861
  if (i > 0) {
18499
18862
  log(`[background-manager] fallback attempt ${i + 1}/${attemptModels.length}: ${modelLabel}`, { taskId: task.id });
18500
18863
  }
18501
- await this.promptWithTimeout({
18864
+ await promptWithTimeout(this.client, {
18502
18865
  path: { id: sessionId },
18503
18866
  body,
18504
18867
  query: promptQuery
@@ -18525,7 +18888,7 @@ class BackgroundTaskManager {
18525
18888
  throw new Error(`All fallback models failed. ${errors3.join(" | ")}`);
18526
18889
  }
18527
18890
  log(`[background-manager] task started: ${task.id}`, {
18528
- sessionId: session.data.id
18891
+ sessionId: session2.data.id
18529
18892
  });
18530
18893
  } catch (error48) {
18531
18894
  const errorMessage = error48 instanceof Error ? error48.message : String(error48);
@@ -18570,6 +18933,7 @@ class BackgroundTaskManager {
18570
18933
  task.error = "Session deleted";
18571
18934
  this.tasksBySessionId.delete(sessionId);
18572
18935
  this.agentBySessionId.delete(sessionId);
18936
+ this.depthTracker.cleanup(sessionId);
18573
18937
  const resolver = this.completionResolvers.get(taskId);
18574
18938
  if (resolver) {
18575
18939
  resolver(task);
@@ -18582,22 +18946,7 @@ class BackgroundTaskManager {
18582
18946
  if (!task.sessionId)
18583
18947
  return;
18584
18948
  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
- `);
18949
+ const responseText = await extractSessionResult(this.client, task.sessionId);
18601
18950
  if (responseText) {
18602
18951
  this.completeTask(task, "completed", responseText);
18603
18952
  } else {
@@ -18713,6 +19062,10 @@ class BackgroundTaskManager {
18713
19062
  this.tasks.clear();
18714
19063
  this.tasksBySessionId.clear();
18715
19064
  this.agentBySessionId.clear();
19065
+ this.depthTracker.cleanupAll();
19066
+ }
19067
+ getDepthTracker() {
19068
+ return this.depthTracker;
18716
19069
  }
18717
19070
  }
18718
19071
  // src/background/tmux-session-manager.ts
@@ -18882,6 +19235,267 @@ class TmuxSessionManager {
18882
19235
  log("[tmux-session-manager] cleanup complete");
18883
19236
  }
18884
19237
  }
19238
+ // src/council/council-manager.ts
19239
+ class CouncilManager {
19240
+ client;
19241
+ directory;
19242
+ config;
19243
+ depthTracker;
19244
+ tmuxEnabled;
19245
+ constructor(ctx, config2, depthTracker, tmuxEnabled = false) {
19246
+ this.client = ctx.client;
19247
+ this.directory = ctx.directory;
19248
+ this.config = config2;
19249
+ this.depthTracker = depthTracker;
19250
+ this.tmuxEnabled = tmuxEnabled;
19251
+ }
19252
+ async runCouncil(prompt, presetName, parentSessionId) {
19253
+ if (this.depthTracker) {
19254
+ const parentDepth = this.depthTracker.getDepth(parentSessionId);
19255
+ if (parentDepth + 1 > this.depthTracker.maxDepth) {
19256
+ log("[council-manager] spawn blocked: max depth exceeded", {
19257
+ parentSessionId,
19258
+ parentDepth,
19259
+ maxDepth: this.depthTracker.maxDepth
19260
+ });
19261
+ return {
19262
+ success: false,
19263
+ error: "Subagent depth exceeded",
19264
+ councillorResults: []
19265
+ };
19266
+ }
19267
+ }
19268
+ const councilConfig = this.config?.council;
19269
+ if (!councilConfig) {
19270
+ log("[council-manager] Council configuration not found");
19271
+ return {
19272
+ success: false,
19273
+ error: "Council not configured",
19274
+ councillorResults: []
19275
+ };
19276
+ }
19277
+ const resolvedPreset = presetName ?? councilConfig.default_preset ?? "default";
19278
+ const preset = councilConfig.presets[resolvedPreset];
19279
+ if (!preset) {
19280
+ log(`[council-manager] Preset "${resolvedPreset}" not found`);
19281
+ return {
19282
+ success: false,
19283
+ error: `Preset "${resolvedPreset}" not found`,
19284
+ councillorResults: []
19285
+ };
19286
+ }
19287
+ if (Object.keys(preset.councillors).length === 0) {
19288
+ log(`[council-manager] Preset "${resolvedPreset}" has no councillors`);
19289
+ return {
19290
+ success: false,
19291
+ error: `Preset "${resolvedPreset}" has no councillors configured`,
19292
+ councillorResults: []
19293
+ };
19294
+ }
19295
+ const councillorsTimeout = councilConfig.councillors_timeout ?? 180000;
19296
+ const masterTimeout = councilConfig.master_timeout ?? 300000;
19297
+ const councillorCount = Object.keys(preset.councillors).length;
19298
+ log(`[council-manager] Starting council with preset "${resolvedPreset}"`, {
19299
+ councillors: Object.keys(preset.councillors)
19300
+ });
19301
+ this.sendStartNotification(parentSessionId, councillorCount).catch((err) => {
19302
+ log("[council-manager] Failed to send start notification", {
19303
+ error: err instanceof Error ? err.message : String(err)
19304
+ });
19305
+ });
19306
+ const councillorResults = await this.runCouncillors(prompt, preset.councillors, parentSessionId, councillorsTimeout);
19307
+ const completedCount = councillorResults.filter((r) => r.status === "completed").length;
19308
+ log(`[council-manager] Councillors completed: ${completedCount}/${councillorResults.length}`);
19309
+ if (completedCount === 0) {
19310
+ return {
19311
+ success: false,
19312
+ error: "All councillors failed or timed out",
19313
+ councillorResults
19314
+ };
19315
+ }
19316
+ const masterResult = await this.runMaster(prompt, councillorResults, councilConfig, parentSessionId, masterTimeout, preset.master);
19317
+ if (!masterResult.success) {
19318
+ log("[council-manager] Master failed", {
19319
+ error: masterResult.error
19320
+ });
19321
+ const bestResult = councillorResults.find((r) => r.status === "completed" && r.result);
19322
+ return {
19323
+ success: false,
19324
+ error: masterResult.error ?? "Council master failed",
19325
+ result: bestResult?.result ? `(Degraded \u2014 master failed, using ${bestResult.name}'s response)
19326
+
19327
+ ${bestResult.result}` : undefined,
19328
+ councillorResults
19329
+ };
19330
+ }
19331
+ log("[council-manager] Council completed successfully");
19332
+ return {
19333
+ success: true,
19334
+ result: masterResult.result,
19335
+ councillorResults
19336
+ };
19337
+ }
19338
+ async sendStartNotification(parentSessionId, councillorCount) {
19339
+ const message = [
19340
+ `\u2394 Council starting \u2014 ${councillorCount} councillors launching \u2014 ctrl+x \u2193 to watch`,
19341
+ "",
19342
+ "[system status: continue without acknowledging this notification]"
19343
+ ].join(`
19344
+ `);
19345
+ await this.client.session.prompt({
19346
+ path: { id: parentSessionId },
19347
+ body: {
19348
+ noReply: true,
19349
+ parts: [{ type: "text", text: message }]
19350
+ }
19351
+ });
19352
+ }
19353
+ async runAgentSession(options) {
19354
+ const modelRef = parseModelReference(options.model);
19355
+ if (!modelRef) {
19356
+ throw new Error(`Invalid model format: ${options.model}`);
19357
+ }
19358
+ let sessionId;
19359
+ try {
19360
+ const session2 = await this.client.session.create({
19361
+ body: {
19362
+ parentID: options.parentSessionId,
19363
+ title: options.title
19364
+ },
19365
+ query: { directory: this.directory }
19366
+ });
19367
+ if (!session2.data?.id) {
19368
+ throw new Error("Failed to create session");
19369
+ }
19370
+ sessionId = session2.data.id;
19371
+ if (this.depthTracker) {
19372
+ const registered = this.depthTracker.registerChild(options.parentSessionId, sessionId);
19373
+ if (!registered) {
19374
+ throw new Error("Subagent depth exceeded");
19375
+ }
19376
+ }
19377
+ if (this.tmuxEnabled) {
19378
+ await new Promise((r) => setTimeout(r, TMUX_SPAWN_DELAY_MS));
19379
+ }
19380
+ const body = {
19381
+ agent: options.agent,
19382
+ model: modelRef,
19383
+ tools: { background_task: false, task: false },
19384
+ parts: [{ type: "text", text: options.promptText }]
19385
+ };
19386
+ if (options.variant) {
19387
+ body.variant = options.variant;
19388
+ }
19389
+ await promptWithTimeout(this.client, {
19390
+ path: { id: sessionId },
19391
+ body,
19392
+ query: { directory: this.directory }
19393
+ }, options.timeout);
19394
+ const result = await extractSessionResult(this.client, sessionId, {
19395
+ includeReasoning: options.includeReasoning
19396
+ });
19397
+ return result || "(No output)";
19398
+ } finally {
19399
+ if (sessionId) {
19400
+ this.client.session.abort({ path: { id: sessionId } }).catch(() => {});
19401
+ if (this.depthTracker) {
19402
+ this.depthTracker.cleanup(sessionId);
19403
+ }
19404
+ }
19405
+ }
19406
+ }
19407
+ async runCouncillors(prompt, councillors, parentSessionId, timeout) {
19408
+ const entries = Object.entries(councillors);
19409
+ const promises = entries.map(([name, config2], index) => (async () => {
19410
+ if (index > 0) {
19411
+ await new Promise((r) => setTimeout(r, index * COUNCILLOR_STAGGER_MS));
19412
+ }
19413
+ const modelLabel = shortModelLabel(config2.model);
19414
+ try {
19415
+ const result = await this.runAgentSession({
19416
+ parentSessionId,
19417
+ title: `Council ${name} (${modelLabel})`,
19418
+ agent: "councillor",
19419
+ model: config2.model,
19420
+ promptText: formatCouncillorPrompt(prompt, config2.prompt),
19421
+ variant: config2.variant,
19422
+ timeout,
19423
+ includeReasoning: false
19424
+ });
19425
+ return {
19426
+ name,
19427
+ model: config2.model,
19428
+ status: "completed",
19429
+ result
19430
+ };
19431
+ } catch (error48) {
19432
+ const msg = error48 instanceof Error ? error48.message : String(error48);
19433
+ return {
19434
+ name,
19435
+ model: config2.model,
19436
+ status: msg.includes("timed out") ? "timed_out" : "failed",
19437
+ error: `Councillor "${name}": ${msg}`
19438
+ };
19439
+ }
19440
+ })());
19441
+ const settled = await Promise.allSettled(promises);
19442
+ return settled.map((result, index) => {
19443
+ const [name, cfg] = entries[index];
19444
+ if (result.status === "fulfilled") {
19445
+ return {
19446
+ name,
19447
+ model: cfg.model,
19448
+ status: result.value.status,
19449
+ result: result.value.result,
19450
+ error: result.value.error
19451
+ };
19452
+ }
19453
+ return {
19454
+ name,
19455
+ model: cfg.model,
19456
+ status: "failed",
19457
+ error: result.reason instanceof Error ? result.reason.message : String(result.reason)
19458
+ };
19459
+ });
19460
+ }
19461
+ async runMaster(prompt, councillorResults, councilConfig, parentSessionId, timeout, presetMasterOverride) {
19462
+ const masterConfig = councilConfig.master;
19463
+ const fallbackModels = councilConfig.master_fallback ?? [];
19464
+ const effectiveModel = presetMasterOverride?.model ?? masterConfig.model;
19465
+ const effectiveVariant = presetMasterOverride?.variant ?? masterConfig.variant;
19466
+ const effectivePrompt = presetMasterOverride?.prompt ?? masterConfig.prompt;
19467
+ const attemptModels = [effectiveModel, ...fallbackModels];
19468
+ const synthesisPrompt = formatMasterSynthesisPrompt(prompt, councillorResults, effectivePrompt);
19469
+ const errors3 = [];
19470
+ for (let i = 0;i < attemptModels.length; i++) {
19471
+ const model = attemptModels[i];
19472
+ const currentLabel = shortModelLabel(model);
19473
+ try {
19474
+ if (i > 0) {
19475
+ log(`[council-manager] master fallback ${i}/${attemptModels.length - 1}: ${currentLabel}`);
19476
+ }
19477
+ const result = await this.runAgentSession({
19478
+ parentSessionId,
19479
+ title: `Council Master (${currentLabel})`,
19480
+ agent: "council-master",
19481
+ model,
19482
+ promptText: synthesisPrompt,
19483
+ variant: effectiveVariant,
19484
+ timeout
19485
+ });
19486
+ return { success: true, result };
19487
+ } catch (error48) {
19488
+ const msg = error48 instanceof Error ? error48.message : String(error48);
19489
+ errors3.push(`${currentLabel}: ${msg}`);
19490
+ log(`[council-manager] master model failed: ${currentLabel} \u2014 ${msg}`);
19491
+ }
19492
+ }
19493
+ return {
19494
+ success: false,
19495
+ error: `All master models failed. ${errors3.join(" | ")}`
19496
+ };
19497
+ }
19498
+ }
18885
19499
  // src/hooks/auto-update-checker/cache.ts
18886
19500
  import * as fs3 from "fs";
18887
19501
  import * as path4 from "path";
@@ -19558,7 +20172,10 @@ class ForegroundFallbackManager {
19558
20172
  const agentName = this.sessionAgent.get(sessionID);
19559
20173
  const chain = this.resolveChain(agentName, currentModel);
19560
20174
  if (!chain.length) {
19561
- log("[foreground-fallback] no chain configured", { sessionID, agentName });
20175
+ log("[foreground-fallback] no chain configured", {
20176
+ sessionID,
20177
+ agentName
20178
+ });
19562
20179
  return;
19563
20180
  }
19564
20181
  if (!this.sessionTried.has(sessionID)) {
@@ -19647,7 +20264,6 @@ var JSON_ERROR_TOOL_EXCLUDE_LIST = [
19647
20264
  "bash",
19648
20265
  "read",
19649
20266
  "glob",
19650
- "grep",
19651
20267
  "webfetch",
19652
20268
  "grep_app_searchgithub",
19653
20269
  "websearch_web_search_exa"
@@ -19695,9 +20311,7 @@ ${JSON_ERROR_REMINDER}`;
19695
20311
  };
19696
20312
  }
19697
20313
  // 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>`;
20314
+ var PHASE_REMINDER = `<reminder>${PHASE_REMINDER_TEXT}</reminder>`;
19701
20315
  function createPhaseReminderHook() {
19702
20316
  return {
19703
20317
  "experimental.chat.messages.transform": async (_input, output) => {
@@ -19736,15 +20350,15 @@ ${originalText}`;
19736
20350
  }
19737
20351
  };
19738
20352
  }
19739
- // src/hooks/post-read-nudge/index.ts
20353
+ // src/hooks/post-file-tool-nudge/index.ts
19740
20354
  var NUDGE = `
19741
20355
 
19742
20356
  ---
19743
- Workflow Reminder: delegate based on rules; If mentioning a specialist, launch it in this same turn.`;
19744
- function createPostReadNudgeHook() {
20357
+ ${PHASE_REMINDER_TEXT}`;
20358
+ function createPostFileToolNudgeHook() {
19745
20359
  return {
19746
20360
  "tool.execute.after": async (input, output) => {
19747
- if (input.tool !== "Read" && input.tool !== "read") {
20361
+ if (input.tool !== "Read" && input.tool !== "read" && input.tool !== "Write" && input.tool !== "write") {
19748
20362
  return;
19749
20363
  }
19750
20364
  output.output = output.output + NUDGE;
@@ -32764,340 +33378,76 @@ Only cancels pending/starting/running tasks.`,
32764
33378
  });
32765
33379
  return { background_task, background_output, background_cancel };
32766
33380
  }
32767
- // src/tools/grep/cli.ts
32768
- var {spawn: spawn4 } = globalThis.Bun;
33381
+ // src/tools/council.ts
33382
+ var z3 = tool.schema;
33383
+ function formatModelComposition(councillorResults) {
33384
+ return councillorResults.map((cr) => {
33385
+ const shortModel = shortModelLabel(cr.model ?? "");
33386
+ return `${cr.name}: ${shortModel}`;
33387
+ }).join(", ");
33388
+ }
33389
+ function createCouncilTool(_ctx, councilManager) {
33390
+ const council_session = tool({
33391
+ description: `Launch a multi-LLM council session for consensus-based analysis.
32769
33392
 
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";
33393
+ Sends the prompt to multiple models (councillors) in parallel, then a council master synthesizes the best response.
32774
33394
 
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
- }
33395
+ Returns the synthesized result with councillor summary.`,
33396
+ args: {
33397
+ prompt: z3.string().describe("The prompt to send to all councillors"),
33398
+ preset: z3.string().optional().describe('Council preset to use (default: "default"). Must match a preset in the council config.')
33399
+ },
33400
+ async execute(args, toolContext) {
33401
+ if (!toolContext || typeof toolContext !== "object" || !("sessionID" in toolContext)) {
33402
+ throw new Error("Invalid toolContext: missing sessionID");
33403
+ }
33404
+ const allowedAgents = ["council", "orchestrator"];
33405
+ const callingAgent = toolContext.agent;
33406
+ if (callingAgent && !allowedAgents.includes(callingAgent)) {
33407
+ throw new Error(`Council sessions can only be invoked by council or orchestrator agents. Current agent: ${callingAgent}`);
33408
+ }
33409
+ const prompt = String(args.prompt);
33410
+ const preset = typeof args.preset === "string" ? args.preset : undefined;
33411
+ const parentSessionId = toolContext.sessionID;
33412
+ const result = await councilManager.runCouncil(prompt, preset, parentSessionId);
33413
+ if (!result.success) {
33414
+ if (result.result) {
33415
+ const completed2 = result.councillorResults.filter((cr) => cr.status === "completed").length;
33416
+ const total2 = result.councillorResults.length;
33417
+ const composition2 = formatModelComposition(result.councillorResults);
33418
+ return `${result.result}
32796
33419
 
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"];
33420
+ ---
33421
+ *Council: ${completed2}/${total2} councillors responded (${composition2}) \u2014 degraded*`;
33422
+ }
33423
+ return `Council session failed: ${result.error}`;
33424
+ }
33425
+ let output = result.result ?? "(No output)";
33426
+ const completed = result.councillorResults.filter((cr) => cr.status === "completed").length;
33427
+ const total = result.councillorResults.length;
33428
+ const composition = formatModelComposition(result.councillorResults);
33429
+ output += `
32875
33430
 
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
- });
33431
+ ---
33432
+ *Council: ${completed}/${total} councillors responded (${composition})*`;
33433
+ return output;
32962
33434
  }
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
33435
  });
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
- `);
33436
+ return { council_session };
33056
33437
  }
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
33438
  // src/tools/lsp/client.ts
33089
33439
  var import_node = __toESM(require_main(), 1);
33090
33440
  import { readFileSync as readFileSync4 } from "fs";
33091
33441
  import { extname, resolve as resolve2 } from "path";
33092
33442
  import { Readable, Writable } from "stream";
33093
33443
  import { pathToFileURL } from "url";
33094
- var {spawn: spawn5 } = globalThis.Bun;
33444
+ var {spawn: spawn4 } = globalThis.Bun;
33095
33445
 
33096
33446
  // src/tools/lsp/config.ts
33097
33447
  var import_which = __toESM(require_lib(), 1);
33098
- import { existsSync as existsSync10 } from "fs";
33448
+ import { existsSync as existsSync8 } from "fs";
33099
33449
  import { homedir as homedir4 } from "os";
33100
- import { join as join11 } from "path";
33450
+ import { join as join9 } from "path";
33101
33451
 
33102
33452
  // src/tools/lsp/config-store.ts
33103
33453
  var userConfig = new Map;
@@ -33127,8 +33477,8 @@ function hasUserLspConfig() {
33127
33477
  }
33128
33478
 
33129
33479
  // 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";
33480
+ import { existsSync as existsSync7, readdirSync, statSync as statSync3 } from "fs";
33481
+ import { dirname as dirname4, resolve } from "path";
33132
33482
  var SEVERITY_MAP = {
33133
33483
  1: "error",
33134
33484
  2: "warning",
@@ -33148,10 +33498,10 @@ function* walkUpDirectories(start, stop) {
33148
33498
  let dir = resolve(start);
33149
33499
  try {
33150
33500
  if (!statSync3(dir).isDirectory()) {
33151
- dir = dirname5(dir);
33501
+ dir = dirname4(dir);
33152
33502
  }
33153
33503
  } catch {
33154
- dir = dirname5(dir);
33504
+ dir = dirname4(dir);
33155
33505
  }
33156
33506
  let prevDir = "";
33157
33507
  while (dir !== prevDir && dir !== "/") {
@@ -33159,7 +33509,7 @@ function* walkUpDirectories(start, stop) {
33159
33509
  prevDir = dir;
33160
33510
  if (dir === stop)
33161
33511
  break;
33162
- dir = dirname5(dir);
33512
+ dir = dirname4(dir);
33163
33513
  }
33164
33514
  }
33165
33515
  function NearestRoot(includePatterns, excludePatterns) {
@@ -33168,7 +33518,7 @@ function NearestRoot(includePatterns, excludePatterns) {
33168
33518
  if (excludePatterns) {
33169
33519
  for (const dir of walkUpDirectories(file3, cwd)) {
33170
33520
  for (const pattern of excludePatterns) {
33171
- if (existsSync9(`${dir}/${pattern}`)) {
33521
+ if (existsSync7(`${dir}/${pattern}`)) {
33172
33522
  return;
33173
33523
  }
33174
33524
  }
@@ -33178,13 +33528,13 @@ function NearestRoot(includePatterns, excludePatterns) {
33178
33528
  for (const pattern of includePatterns) {
33179
33529
  if (pattern.includes("*")) {
33180
33530
  try {
33181
- const entries = readdirSync2(dir);
33531
+ const entries = readdirSync(dir);
33182
33532
  const regex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\*/g, ".*")}$`);
33183
33533
  if (entries.some((entry) => regex.test(entry))) {
33184
33534
  return dir;
33185
33535
  }
33186
33536
  } catch {}
33187
- } else if (existsSync9(`${dir}/${pattern}`)) {
33537
+ } else if (existsSync7(`${dir}/${pattern}`)) {
33188
33538
  return dir;
33189
33539
  }
33190
33540
  }
@@ -33732,11 +34082,11 @@ function isServerInstalled(command) {
33732
34082
  return false;
33733
34083
  const cmd = command[0];
33734
34084
  if (cmd.includes("/") || cmd.includes("\\")) {
33735
- return existsSync10(cmd);
34085
+ return existsSync8(cmd);
33736
34086
  }
33737
34087
  const isWindows = process.platform === "win32";
33738
34088
  const ext = isWindows ? ".exe" : "";
33739
- const opencodeBin = join11(homedir4(), ".config", "opencode", "bin");
34089
+ const opencodeBin = join9(homedir4(), ".config", "opencode", "bin");
33740
34090
  const searchPath = (process.env.PATH ?? "") + (isWindows ? ";" : ":") + opencodeBin;
33741
34091
  const result = import_which.default.sync(cmd, {
33742
34092
  path: searchPath,
@@ -33747,8 +34097,8 @@ function isServerInstalled(command) {
33747
34097
  return true;
33748
34098
  }
33749
34099
  const cwd = process.cwd();
33750
- const localBin = join11(cwd, "node_modules", ".bin", cmd);
33751
- if (existsSync10(localBin) || existsSync10(localBin + ext)) {
34100
+ const localBin = join9(cwd, "node_modules", ".bin", cmd);
34101
+ if (existsSync8(localBin) || existsSync8(localBin + ext)) {
33752
34102
  return true;
33753
34103
  }
33754
34104
  return false;
@@ -33929,7 +34279,7 @@ class LSPClient {
33929
34279
  command: this.server.command.join(" "),
33930
34280
  root: this.root
33931
34281
  });
33932
- this.proc = spawn5(this.server.command, {
34282
+ this.proc = spawn4(this.server.command, {
33933
34283
  stdin: "pipe",
33934
34284
  stdout: "pipe",
33935
34285
  stderr: "pipe",
@@ -34161,19 +34511,19 @@ stderr: ${stderr}` : ""));
34161
34511
  }
34162
34512
  // src/tools/lsp/utils.ts
34163
34513
  import {
34164
- existsSync as existsSync11,
34514
+ existsSync as existsSync9,
34165
34515
  readFileSync as readFileSync5,
34166
34516
  statSync as statSync4,
34167
- unlinkSync as unlinkSync3,
34517
+ unlinkSync as unlinkSync2,
34168
34518
  writeFileSync as writeFileSync3
34169
34519
  } from "fs";
34170
- import { dirname as dirname6, extname as extname2, join as join12, resolve as resolve3 } from "path";
34520
+ import { dirname as dirname5, extname as extname2, join as join10, resolve as resolve3 } from "path";
34171
34521
  import { fileURLToPath as fileURLToPath2 } from "url";
34172
34522
  function findServerProjectRoot(filePath, server) {
34173
34523
  if (server.root) {
34174
- return server.root(filePath) ?? dirname6(resolve3(filePath));
34524
+ return server.root(filePath) ?? dirname5(resolve3(filePath));
34175
34525
  }
34176
- return dirname6(resolve3(filePath));
34526
+ return dirname5(resolve3(filePath));
34177
34527
  }
34178
34528
  function uriToPath(uri) {
34179
34529
  return fileURLToPath2(uri);
@@ -34203,7 +34553,7 @@ async function withLspClient(filePath, fn) {
34203
34553
  throw new Error(formatServerLookupError(result));
34204
34554
  }
34205
34555
  const server = result.server;
34206
- const root = findServerProjectRoot(absPath, server) ?? dirname6(absPath);
34556
+ const root = findServerProjectRoot(absPath, server) ?? dirname5(absPath);
34207
34557
  log("[lsp] withLspClient: acquiring client", {
34208
34558
  filePath: absPath,
34209
34559
  server: server.id,
@@ -34358,7 +34708,7 @@ function applyWorkspaceEdit(edit) {
34358
34708
  const newPath = uriToPath(change.newUri);
34359
34709
  const content = readFileSync5(oldPath, "utf-8");
34360
34710
  writeFileSync3(newPath, content, "utf-8");
34361
- unlinkSync3(oldPath);
34711
+ unlinkSync2(oldPath);
34362
34712
  result.filesModified.push(newPath);
34363
34713
  } catch (err) {
34364
34714
  result.success = false;
@@ -34367,7 +34717,7 @@ function applyWorkspaceEdit(edit) {
34367
34717
  } else if (change.kind === "delete") {
34368
34718
  try {
34369
34719
  const filePath = uriToPath(change.uri);
34370
- unlinkSync3(filePath);
34720
+ unlinkSync2(filePath);
34371
34721
  result.filesModified.push(filePath);
34372
34722
  } catch (err) {
34373
34723
  result.success = false;
@@ -34578,6 +34928,7 @@ var OhMyOpenCodeLite = async (ctx) => {
34578
34928
  }
34579
34929
  const backgroundManager = new BackgroundTaskManager(ctx, tmuxConfig, config3);
34580
34930
  const backgroundTools = createBackgroundTools(ctx, backgroundManager, tmuxConfig, config3);
34931
+ const councilTools = config3.council ? createCouncilTool(ctx, new CouncilManager(ctx, config3, backgroundManager.getDepthTracker(), tmuxConfig.enabled)) : {};
34581
34932
  const mcps = createBuiltinMcps(config3.disabled_mcps);
34582
34933
  const tmuxSessionManager = new TmuxSessionManager(ctx, tmuxConfig);
34583
34934
  const autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
@@ -34585,7 +34936,7 @@ var OhMyOpenCodeLite = async (ctx) => {
34585
34936
  autoUpdate: true
34586
34937
  });
34587
34938
  const phaseReminderHook = createPhaseReminderHook();
34588
- const postReadNudgeHook = createPostReadNudgeHook();
34939
+ const postFileToolNudgeHook = createPostFileToolNudgeHook();
34589
34940
  const chatHeadersHook = createChatHeadersHook(ctx);
34590
34941
  const delegateTaskRetryHook = createDelegateTaskRetryHook(ctx);
34591
34942
  const jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
@@ -34595,11 +34946,11 @@ var OhMyOpenCodeLite = async (ctx) => {
34595
34946
  agent: agents,
34596
34947
  tool: {
34597
34948
  ...backgroundTools,
34949
+ ...councilTools,
34598
34950
  lsp_goto_definition,
34599
34951
  lsp_find_references,
34600
34952
  lsp_diagnostics,
34601
34953
  lsp_rename,
34602
- grep,
34603
34954
  ast_grep_search,
34604
34955
  ast_grep_replace
34605
34956
  },
@@ -34651,52 +35002,22 @@ var OhMyOpenCodeLite = async (ctx) => {
34651
35002
  }
34652
35003
  }
34653
35004
  if (Object.keys(effectiveArrays).length > 0) {
34654
- const providerConfig = opencodeConfig.provider ?? {};
34655
- const hasProviderConfig = Object.keys(providerConfig).length > 0;
34656
35005
  for (const [agentName, modelArray] of Object.entries(effectiveArrays)) {
34657
35006
  if (modelArray.length === 0)
34658
35007
  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
- }
35008
+ const chosen = modelArray[0];
35009
+ const entry = configAgent[agentName];
35010
+ if (entry) {
35011
+ entry.model = chosen.id;
35012
+ if (chosen.variant) {
35013
+ entry.variant = chosen.variant;
34693
35014
  }
34694
- log("[plugin] resolved model from array (no provider config)", {
34695
- agent: agentName,
34696
- model: firstModel.id,
34697
- variant: firstModel.variant
34698
- });
34699
35015
  }
35016
+ log("[plugin] resolved model from array", {
35017
+ agent: agentName,
35018
+ model: chosen.id,
35019
+ variant: chosen.variant
35020
+ });
34700
35021
  }
34701
35022
  }
34702
35023
  const configMcp = opencodeConfig.mcp;
@@ -34741,7 +35062,7 @@ var OhMyOpenCodeLite = async (ctx) => {
34741
35062
  "tool.execute.after": async (input, output) => {
34742
35063
  await delegateTaskRetryHook["tool.execute.after"](input, output);
34743
35064
  await jsonErrorRecoveryHook["tool.execute.after"](input, output);
34744
- await postReadNudgeHook["tool.execute.after"](input, output);
35065
+ await postFileToolNudgeHook["tool.execute.after"](input, output);
34745
35066
  }
34746
35067
  };
34747
35068
  };