heyio 4.2.2 → 4.2.4

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.
@@ -75,23 +75,23 @@ var init_api = __esm({
75
75
  });
76
76
 
77
77
  // packages/shared/dist/constants.js
78
- var APP_NAME, APP_VERSION, API_PORT, API_HOST, DEFAULT_MODEL, SESSION_RESET_THRESHOLD, SCHEDULER_INTERVAL_MS, QA_MAX_REVISIONS, MANDATORY_ROLES, EVENT_NAMES;
78
+ var APP_NAME, APP_VERSION, API_PORT, API_HOST, DEFAULT_MODEL, SESSION_RESET_THRESHOLD, SCHEDULER_INTERVAL_MS, QA_MAX_REVISIONS, EVENT_NAMES;
79
79
  var init_constants = __esm({
80
80
  "packages/shared/dist/constants.js"() {
81
81
  "use strict";
82
82
  APP_NAME = "io";
83
- APP_VERSION = "4.2.2";
83
+ APP_VERSION = "4.2.4";
84
84
  API_PORT = 7777;
85
85
  API_HOST = "0.0.0.0";
86
86
  DEFAULT_MODEL = "gpt-4o";
87
87
  SESSION_RESET_THRESHOLD = 50;
88
88
  SCHEDULER_INTERVAL_MS = 6e4;
89
89
  QA_MAX_REVISIONS = 3;
90
- MANDATORY_ROLES = ["team-lead", "qa"];
91
90
  EVENT_NAMES = {
92
91
  SQUAD_CREATED: "squad.created",
93
92
  SQUAD_DELETED: "squad.deleted",
94
93
  SQUAD_UPDATED: "squad.updated",
94
+ SQUAD_MEMBER_UPDATED: "squad.member_updated",
95
95
  INSTANCE_CREATED: "instance.created",
96
96
  INSTANCE_STARTED: "instance.started",
97
97
  INSTANCE_COMPLETED: "instance.completed",
@@ -2310,6 +2310,10 @@ async function updateMember(memberId, data, db) {
2310
2310
  const database = db ?? await getDatabase();
2311
2311
  const sets = [];
2312
2312
  const args = [];
2313
+ if (data.role !== void 0) {
2314
+ sets.push("role = ?");
2315
+ args.push(data.role);
2316
+ }
2313
2317
  if (data.systemPrompt !== void 0) {
2314
2318
  sets.push("system_prompt = ?");
2315
2319
  args.push(data.systemPrompt);
@@ -11914,8 +11918,8 @@ async function recordUsage(data, db) {
11914
11918
  createdAt: data.createdAt ?? nowIso()
11915
11919
  };
11916
11920
  await database.execute({
11917
- sql: `INSERT INTO token_usage (id, squad_id, agent_id, model, input_tokens, output_tokens, cost, premium_request_cost, token_unit_cost, squad_name, agent_name, created_at)
11918
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11921
+ sql: `INSERT INTO token_usage (id, squad_id, agent_id, model, input_tokens, output_tokens, cost, squad_name, agent_name, created_at)
11922
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11919
11923
  args: [
11920
11924
  usage.id,
11921
11925
  usage.squadId,
@@ -11924,8 +11928,6 @@ async function recordUsage(data, db) {
11924
11928
  usage.inputTokens,
11925
11929
  usage.outputTokens,
11926
11930
  usage.cost,
11927
- data.premiumRequestCost ?? null,
11928
- data.tokenUnitCost ?? null,
11929
11931
  data.squadName ?? null,
11930
11932
  data.agentName ?? null,
11931
11933
  usage.createdAt
@@ -52870,11 +52872,16 @@ var init_squads2 = __esm({
52870
52872
  res.status(404).json({ error: "Member not found" });
52871
52873
  return;
52872
52874
  }
52873
- const { systemPrompt, model } = req.body;
52875
+ const { role, systemPrompt, model } = req.body;
52874
52876
  const updated = await updateMember(member.id, {
52877
+ role,
52875
52878
  systemPrompt,
52876
52879
  model: model === "" ? null : model
52877
52880
  });
52881
+ eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, {
52882
+ squadId: member.squadId,
52883
+ member: updated
52884
+ });
52878
52885
  res.status(200).json(updated);
52879
52886
  } catch (error51) {
52880
52887
  res.status(500).json({
@@ -68325,10 +68332,19 @@ async function getModelPricing(modelId) {
68325
68332
  sql: "SELECT * FROM model_pricing WHERE id = ?",
68326
68333
  args: [modelId]
68327
68334
  });
68328
- if (result.rows.length === 0) {
68329
- return null;
68335
+ if (result.rows.length > 0) {
68336
+ return rowToModelPricing(result.rows[0]);
68337
+ }
68338
+ const allModels = await db.execute(
68339
+ "SELECT * FROM model_pricing WHERE token_input_multiplier IS NOT NULL ORDER BY length(id) DESC"
68340
+ );
68341
+ for (const row of allModels.rows) {
68342
+ const storedId = asString(row.id);
68343
+ if (modelId.startsWith(storedId) || storedId.startsWith(modelId)) {
68344
+ return rowToModelPricing(row);
68345
+ }
68330
68346
  }
68331
- return rowToModelPricing(result.rows[0]);
68347
+ return null;
68332
68348
  }
68333
68349
  function calculateTokenUnitCost(inputTokens, outputTokens, inputMultiplier, outputMultiplier) {
68334
68350
  if (inputMultiplier === null || outputMultiplier === null) {
@@ -68424,9 +68440,10 @@ function buildSystemPrompt(options2) {
68424
68440
  const sections = [
68425
68441
  "You are Io, a helpful AI orchestrator companion for IO v4.",
68426
68442
  "You coordinate work across squads, wiki knowledge, installed skills, and direct execution tools.",
68427
- "When a user asks about a project that has a squad, delegate to that squad. When no squad exists, you can do the work directly.",
68443
+ "When a user asks about a project that has a squad, delegate to that squad. When no squad exists, you can do the work directly or create a squad for ongoing project work.",
68428
68444
  "Be practical, concise, and execution-oriented. Use tools when they help you produce a more accurate or durable result.",
68429
- "## Delegation Rules\n- Prefer delegation for project-specific work when a matching squad exists.\n- If no squad exists for the target repository or project, use direct coding and knowledge tools yourself.\n- Use the wiki to remember important information and recover prior context.\n- Use installed skills when they are relevant to the current request.",
68445
+ "## Delegation Rules\n- Prefer delegation for project-specific work when a matching squad exists.\n- If no squad exists for the target repository or project, use direct coding and knowledge tools yourself for one-off tasks.\n- For ongoing project work where no squad exists, create one with `create_squad` and then add members with `add_squad_member`.\n- Use the wiki to remember important information and recover prior context.\n- Use installed skills when they are relevant to the current request.",
68446
+ "## Squad Creation Guidelines\n- Use `analyze_repo` first to understand the repository's tech stack and structure.\n- Always add a `team-lead` member (coordinates planning, task assignment, and technical review) and a `qa` member (validates diffs, tests, and objective completion).\n- Add additional role-specific members based on the repository analysis (e.g. senior-frontend-engineer, senior-backend-engineer, senior-platform-engineer).\n- Write detailed system prompts for each member that define their expertise, responsibilities, and behavioral guidelines.\n- Use `remove_squad_member` and `add_squad_member` to restructure existing squads when the user requests changes.",
68430
68447
  `## Squad Roster
68431
68448
  ${formatSquadRoster(options2.squads)}`,
68432
68449
  formatOptionalSection("## Conversation Summary", options2.conversationSummary ?? ""),
@@ -69492,8 +69509,7 @@ async function persistUsage(member, usageEvents, squadName) {
69492
69509
  for (const usage of usageEvents) {
69493
69510
  const model = usage.model;
69494
69511
  const pricing = await getModelPricing(model);
69495
- const premiumRequestCost = pricing?.premiumMultiplier ?? 0;
69496
- const tokenUnitCost = pricing ? calculateTokenUnitCost(
69512
+ const cost = pricing ? calculateTokenUnitCost(
69497
69513
  usage.inputTokens ?? 0,
69498
69514
  usage.outputTokens ?? 0,
69499
69515
  pricing.tokenInputMultiplier,
@@ -69507,9 +69523,7 @@ async function persistUsage(member, usageEvents, squadName) {
69507
69523
  model,
69508
69524
  inputTokens: usage.inputTokens ?? 0,
69509
69525
  outputTokens: usage.outputTokens ?? 0,
69510
- cost: tokenUnitCost,
69511
- premiumRequestCost,
69512
- tokenUnitCost
69526
+ cost
69513
69527
  });
69514
69528
  }
69515
69529
  }
@@ -69828,22 +69842,7 @@ var init_instance_context = __esm({
69828
69842
  });
69829
69843
 
69830
69844
  // packages/daemon/src/squad/roles.ts
69831
- function generateRolePrompt(role, repoContext) {
69832
- const normalizedRole = role.trim() || "specialist";
69833
- return `You are the ${normalizedRole} for the IO v4 daemon squad system.
69834
-
69835
- ${ROLE_GUIDELINES}
69836
-
69837
- Role expectations:
69838
- - Own work that naturally fits the ${normalizedRole} discipline.
69839
- - Make changes that align with the repository's existing architecture, conventions, and tooling.
69840
- - Collaborate with the Team Lead and QA by leaving behind clear implementation notes and validation evidence.
69841
- - Escalate blockers or missing context early.
69842
-
69843
- Repository context:
69844
- ${repoContext}`;
69845
- }
69846
- var ROLE_GUIDELINES, TEAM_LEAD_PROMPT, QA_PROMPT, ROLE_GENERATION_PROMPT;
69845
+ var ROLE_GUIDELINES, TEAM_LEAD_PROMPT, QA_PROMPT;
69847
69846
  var init_roles = __esm({
69848
69847
  "packages/daemon/src/squad/roles.ts"() {
69849
69848
  "use strict";
@@ -69889,28 +69888,6 @@ Review rules:
69889
69888
  - If approving, summarize why the work is acceptable.
69890
69889
  - If rejecting, include concrete fixes or follow-up actions.
69891
69890
  - Do not approve work that lacks evidence for important behavior changes.`;
69892
- ROLE_GENERATION_PROMPT = `You are staffing an autonomous software squad for a GitHub repository.
69893
-
69894
- Given repository analysis describing languages, frameworks, file structure, architectural patterns, and operational concerns, propose the smallest effective team that can deliver objectives safely.
69895
-
69896
- Requirements:
69897
- - Mandatory roles Team Lead and QA must always exist.
69898
- - Suggest only additional roles that are clearly justified by the repository analysis.
69899
- - Prefer durable role names such as backend-engineer, frontend-engineer, test-automation-engineer, platform-engineer, data-engineer, security-engineer, documentation-engineer, or mobile-engineer.
69900
- - Each role must have a short human-readable name and a concise description of responsibilities.
69901
- - Avoid duplicate or overlapping roles.
69902
- - Optimize for implementation, verification, and maintainability.
69903
-
69904
- Return strict JSON in this shape:
69905
- {
69906
- "roles": [
69907
- {
69908
- "role": "frontend-engineer",
69909
- "name": "Frontend Engineer",
69910
- "description": "Owns UI implementation, client state, and browser-facing tests."
69911
- }
69912
- ]
69913
- }`;
69914
69891
  }
69915
69892
  });
69916
69893
 
@@ -70717,8 +70694,19 @@ var init_runner = __esm({
70717
70694
  }
70718
70695
  });
70719
70696
 
70720
- // packages/daemon/src/squad/hiring.ts
70721
- import { CopilotClient as CopilotClient6, approveAll as approveAll6 } from "@github/copilot-sdk";
70697
+ // packages/daemon/src/orchestrator/tools/squad.ts
70698
+ import { exec as exec8 } from "node:child_process";
70699
+ import { mkdir as mkdir10, readFile as readFile9, readdir as readdir6, stat as stat4 } from "node:fs/promises";
70700
+ import { join as join15 } from "node:path";
70701
+ import { promisify as promisify8 } from "node:util";
70702
+ async function pathExists2(path) {
70703
+ try {
70704
+ await stat4(path);
70705
+ return true;
70706
+ } catch {
70707
+ return false;
70708
+ }
70709
+ }
70722
70710
  function parseRepoUrl(repoUrl) {
70723
70711
  const trimmed = repoUrl.trim();
70724
70712
  const sshMatch = trimmed.match(/^git@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/i);
@@ -70733,245 +70721,92 @@ function parseRepoUrl(repoUrl) {
70733
70721
  }
70734
70722
  return { owner: segments[0], name: segments[1] };
70735
70723
  }
70736
- function slugifyRole(role) {
70737
- return role.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
70738
- }
70739
70724
  function titleCase(value) {
70740
70725
  return value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
70741
70726
  }
70742
- function extractJsonObject4(content) {
70743
- const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
70744
- if (fenced?.[1]) {
70745
- return fenced[1].trim();
70746
- }
70747
- const start = content.indexOf("{");
70748
- const end = content.lastIndexOf("}");
70749
- return start >= 0 && end > start ? content.slice(start, end + 1) : null;
70750
- }
70751
- function detectRolesFromAnalysis(repoAnalysis) {
70752
- const analysis = repoAnalysis.toLowerCase();
70753
- const roles = [];
70754
- if (/(react|next|vue|angular|frontend|ui|xaml)/.test(analysis)) {
70755
- roles.push({
70756
- role: "frontend-engineer",
70757
- name: "Frontend Engineer",
70758
- description: "Implements user-facing experiences, UI state, and presentation-layer changes."
70759
- });
70760
- }
70761
- if (/(node|express|api|backend|server|daemon|database|sql|postgres|sqlite)/.test(analysis)) {
70762
- roles.push({
70763
- role: "backend-engineer",
70764
- name: "Backend Engineer",
70765
- description: "Implements server-side logic, data access, integrations, and execution flow changes."
70766
- });
70767
- }
70768
- if (/(infra|docker|kubernetes|deploy|ci|cd|workflow|github actions)/.test(analysis)) {
70769
- roles.push({
70770
- role: "platform-engineer",
70771
- name: "Platform Engineer",
70772
- description: "Owns automation, pipelines, environments, and operational tooling."
70773
- });
70774
- }
70775
- if (/(security|auth|oauth|secret|policy)/.test(analysis)) {
70776
- roles.push({
70777
- role: "security-engineer",
70778
- name: "Security Engineer",
70779
- description: "Reviews authentication, authorization, secrets handling, and security-sensitive changes."
70780
- });
70781
- }
70782
- return roles;
70783
- }
70784
- function dedupeRoles(roles) {
70785
- const seen = /* @__PURE__ */ new Set();
70786
- const output2 = [];
70787
- for (const role of roles) {
70788
- const normalizedRole = slugifyRole(role.role);
70789
- if (!normalizedRole || seen.has(normalizedRole) || MANDATORY_ROLES.includes(normalizedRole)) {
70790
- continue;
70791
- }
70792
- seen.add(normalizedRole);
70793
- output2.push({
70794
- role: normalizedRole,
70795
- name: role.name?.trim() || titleCase(normalizedRole),
70796
- description: role.description?.trim() || `${titleCase(normalizedRole)} responsibilities`
70797
- });
70798
- }
70799
- return output2;
70800
- }
70801
- function buildMandatoryRoles() {
70802
- return [
70803
- {
70804
- role: "team-lead",
70805
- name: "Team Lead",
70806
- description: "Coordinates planning, task assignment, sequencing, and final technical review."
70807
- },
70808
- {
70809
- role: "qa",
70810
- name: "QA Reviewer",
70811
- description: "Validates diffs, test evidence, and objective completion before delivery."
70727
+ async function readManifests(rootPath, rootLabel) {
70728
+ const lines = [];
70729
+ for (const manifest of MANIFEST_FILES) {
70730
+ const fullPath = join15(rootPath, manifest);
70731
+ try {
70732
+ const fileStat = await stat4(fullPath);
70733
+ if (fileStat.isFile()) {
70734
+ const content = await readFile9(fullPath, "utf8");
70735
+ lines.push(`
70736
+ --- ${rootLabel}${manifest} ---
70737
+ ${content.slice(0, 3e3)}`);
70738
+ } else if (fileStat.isDirectory()) {
70739
+ const children = await readdir6(fullPath);
70740
+ lines.push(`
70741
+ --- ${rootLabel}${manifest}/ ---
70742
+ ${children.join(", ")}`);
70743
+ }
70744
+ } catch {
70812
70745
  }
70813
- ];
70814
- }
70815
- function modelForRole(role) {
70816
- const normalized = slugifyRole(role);
70817
- if (normalized === "team-lead") {
70818
- return DEFAULT_MODEL;
70819
70746
  }
70820
- return DEFAULT_MODEL;
70747
+ return lines;
70821
70748
  }
70822
- function systemPromptForRole(role, repoContext) {
70823
- const normalized = slugifyRole(role);
70824
- if (normalized === "team-lead") {
70825
- return `${TEAM_LEAD_PROMPT}
70826
-
70827
- Repository context:
70828
- ${repoContext}`;
70829
- }
70830
- if (normalized === "qa") {
70831
- return `${QA_PROMPT}
70832
-
70833
- Repository context:
70834
- ${repoContext}`;
70835
- }
70836
- return generateRolePrompt(normalized, repoContext);
70837
- }
70838
- async function proposeSquadComposition(repoUrl, repoAnalysis) {
70839
- const mandatory = buildMandatoryRoles();
70840
- const fallbackAdditional = dedupeRoles(detectRolesFromAnalysis(repoAnalysis));
70841
- const prompt = `Repository URL: ${repoUrl}
70842
-
70843
- Repository analysis:
70844
- ${repoAnalysis}
70845
-
70846
- ${ROLE_GENERATION_PROMPT}`;
70847
- let client2 = null;
70848
- try {
70849
- client2 = new CopilotClient6();
70850
- await client2.start();
70851
- const session = await client2.createSession({
70852
- model: DEFAULT_MODEL,
70853
- onPermissionRequest: approveAll6,
70854
- systemMessage: {
70855
- content: ROLE_GENERATION_PROMPT
70856
- }
70857
- });
70749
+ async function scanSrcDirs(rootPath, rootLabel, dirs) {
70750
+ const lines = [];
70751
+ const srcDirs = dirs.filter((d) => SRC_DIR_NAMES.includes(d));
70752
+ for (const srcDir of srcDirs) {
70858
70753
  try {
70859
- const response = await session.sendAndWait({ prompt }, 6e4);
70860
- const content = response?.data.content?.trim() ?? "";
70861
- const json2 = extractJsonObject4(content);
70862
- if (!json2) {
70863
- return { roles: [...mandatory, ...fallbackAdditional] };
70864
- }
70865
- const parsed = JSON.parse(json2);
70866
- return {
70867
- roles: [...mandatory, ...dedupeRoles(parsed.roles ?? fallbackAdditional)]
70868
- };
70869
- } finally {
70870
- await session.disconnect().catch(() => void 0);
70871
- }
70872
- } catch {
70873
- return { roles: [...mandatory, ...fallbackAdditional] };
70874
- } finally {
70875
- if (client2) {
70876
- await client2.stop().catch(() => []);
70754
+ const srcEntries = await readdir6(join15(rootPath, srcDir), { withFileTypes: true });
70755
+ const srcFiles = srcEntries.filter((e) => e.isFile()).map((e) => e.name);
70756
+ const srcSubDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
70757
+ lines.push(`
70758
+ --- ${rootLabel}${srcDir}/ ---`);
70759
+ if (srcSubDirs.length) lines.push(` Directories: ${srcSubDirs.join(", ")}`);
70760
+ if (srcFiles.length) lines.push(` Files: ${srcFiles.slice(0, 30).join(", ")}`);
70761
+ } catch {
70877
70762
  }
70878
70763
  }
70764
+ return lines;
70879
70765
  }
70880
- async function hireSquad(repoUrl, composition, config2) {
70881
- const parsedRepo = parseRepoUrl(repoUrl);
70882
- const repoContext = `Repository: ${parsedRepo.owner}/${parsedRepo.name}
70883
- Configured MCP servers: ${config2.mcpServers.join(", ") || "none"}
70884
- QA max revisions: ${config2.maxRevisions || QA_MAX_REVISIONS}`;
70885
- const existingSquad = await getSquadByRepo(parsedRepo.owner, parsedRepo.name);
70886
- const normalizedConfig = {
70887
- prMode: config2.prMode,
70888
- mcpServers: [...config2.mcpServers],
70889
- maxRevisions: config2.maxRevisions || QA_MAX_REVISIONS
70890
- };
70891
- const desiredRoles = composition.roles.length > 0 ? composition.roles : buildMandatoryRoles();
70892
- const membersToCreate = desiredRoles.map((role) => ({
70893
- role: slugifyRole(role.role),
70894
- name: role.name?.trim() || titleCase(role.role),
70895
- description: role.description?.trim() || `${titleCase(role.role)} responsibilities`
70896
- }));
70897
- let squadId;
70898
- if (existingSquad) {
70899
- const updatedSquad = await updateSquad(existingSquad.id, {
70900
- name: `${titleCase(parsedRepo.name)} Squad`,
70901
- config: normalizedConfig,
70902
- status: existingSquad.status
70903
- });
70904
- if (!updatedSquad) {
70905
- throw new Error(`Unable to update existing squad for ${repoUrl}`);
70766
+ async function findReadme(rootPath, rootLabel) {
70767
+ for (const readme of README_CANDIDATES2) {
70768
+ try {
70769
+ const content = await readFile9(join15(rootPath, readme), "utf8");
70770
+ return `
70771
+ --- ${rootLabel}${readme} (excerpt) ---
70772
+ ${content.slice(0, 1500)}`;
70773
+ } catch {
70906
70774
  }
70907
- squadId = updatedSquad.id;
70908
- const existingMembers = await getMembers(updatedSquad.id);
70909
- await Promise.all(existingMembers.map((member) => removeMember(member.id)));
70910
- } else {
70911
- const squad = await createSquad({
70912
- name: `${titleCase(parsedRepo.name)} Squad`,
70913
- repoUrl,
70914
- repoOwner: parsedRepo.owner,
70915
- repoName: parsedRepo.name,
70916
- status: "active",
70917
- config: normalizedConfig
70918
- });
70919
- squadId = squad.id;
70920
- }
70921
- const members = [];
70922
- for (const memberDefinition of membersToCreate) {
70923
- const member = await addMember(squadId, {
70924
- role: memberDefinition.role,
70925
- name: memberDefinition.name,
70926
- systemPrompt: systemPromptForRole(
70927
- memberDefinition.role,
70928
- `${repoContext}
70929
- Role: ${memberDefinition.description}`
70930
- ),
70931
- model: modelForRole(memberDefinition.role)
70932
- });
70933
- members.push(member);
70934
70775
  }
70935
- const fullSquad = await getSquad(squadId);
70936
- if (!fullSquad) {
70937
- throw new Error(`Unable to load squad ${squadId} after hiring`);
70938
- }
70939
- eventBus.emit(existingSquad ? EVENT_NAMES.SQUAD_UPDATED : EVENT_NAMES.SQUAD_CREATED, {
70940
- squad: fullSquad
70941
- });
70942
- return { squad: fullSquad, members: fullSquad.members };
70776
+ return null;
70943
70777
  }
70944
- var init_hiring = __esm({
70945
- "packages/daemon/src/squad/hiring.ts"() {
70946
- "use strict";
70947
- init_dist();
70948
- init_event_bus();
70949
- init_store2();
70950
- init_roles();
70951
- }
70952
- });
70953
-
70954
- // packages/daemon/src/orchestrator/tools/squad.ts
70955
- import { exec as exec8 } from "node:child_process";
70956
- import { mkdir as mkdir10, readFile as readFile9, readdir as readdir6, stat as stat4 } from "node:fs/promises";
70957
- import { join as join15 } from "node:path";
70958
- import { promisify as promisify8 } from "node:util";
70959
- async function pathExists2(path) {
70778
+ async function scanRoot(root) {
70779
+ const lines = [];
70780
+ const rootLabel = root.label ? `${root.label}/` : "";
70781
+ const rootEntries = await readdir6(root.path, { withFileTypes: true });
70782
+ const rootFiles = rootEntries.filter((e) => e.isFile()).map((e) => e.name);
70783
+ const rootDirs = rootEntries.filter((e) => e.isDirectory() && e.name !== ".git").map((e) => e.name);
70784
+ if (rootFiles.length) lines.push(`${rootLabel}Files: ${rootFiles.join(", ")}`);
70785
+ if (rootDirs.length) lines.push(`${rootLabel}Directories: ${rootDirs.join(", ")}`);
70786
+ lines.push(...await readManifests(root.path, rootLabel));
70787
+ lines.push(...await scanSrcDirs(root.path, rootLabel, rootDirs));
70788
+ const readme = await findReadme(root.path, rootLabel);
70789
+ if (readme) lines.push(readme);
70790
+ return lines;
70791
+ }
70792
+ async function cloneOrUpdateRepo(normalized, repoDir) {
70960
70793
  try {
70961
- await stat4(path);
70794
+ await mkdir10(join15(DATA_DIR, "repos"), { recursive: true });
70795
+ if (await pathExists2(join15(repoDir, ".git"))) {
70796
+ await execAsync8("git pull --ff-only", { cwd: repoDir, timeout: 3e4 }).catch(
70797
+ () => void 0
70798
+ );
70799
+ } else {
70800
+ await execAsync8(`git clone --depth 50 ${normalized} "${repoDir}"`, {
70801
+ timeout: 6e4
70802
+ });
70803
+ }
70962
70804
  return true;
70963
70805
  } catch {
70964
70806
  return false;
70965
70807
  }
70966
70808
  }
70967
- function getDefaultSquadConfig(_config) {
70968
- return {
70969
- prMode: "draft-pr",
70970
- mcpServers: [],
70971
- maxRevisions: QA_MAX_REVISIONS
70972
- };
70973
- }
70974
- async function buildRepoAnalysis(repoUrl) {
70809
+ async function buildRepoAnalysis(repoUrl, scanPaths) {
70975
70810
  const normalized = repoUrl.trim();
70976
70811
  const segments = normalized.replace(/\.git$/i, "").split("/").filter(Boolean);
70977
70812
  const owner = segments.at(-2) ?? "";
@@ -70984,90 +70819,22 @@ async function buildRepoAnalysis(repoUrl) {
70984
70819
  return lines.join("\n");
70985
70820
  }
70986
70821
  const repoDir = join15(DATA_DIR, "repos", `${owner}--${name}`);
70987
- try {
70988
- await mkdir10(join15(DATA_DIR, "repos"), { recursive: true });
70989
- if (await pathExists2(join15(repoDir, ".git"))) {
70990
- await execAsync8("git pull --ff-only", { cwd: repoDir, timeout: 3e4 }).catch(
70991
- () => void 0
70992
- );
70993
- } else {
70994
- await execAsync8(`git clone --depth 50 ${normalized} "${repoDir}"`, {
70995
- timeout: 6e4
70996
- });
70997
- }
70998
- } catch {
70822
+ const cloned = await cloneOrUpdateRepo(normalized, repoDir);
70823
+ if (!cloned) {
70999
70824
  lines.push("Unable to clone repository locally; falling back to basic analysis.");
71000
70825
  lines.push("Based on the repository name, propose roles that match common project patterns.");
71001
70826
  return lines.join("\n");
71002
70827
  }
71003
- try {
71004
- const rootEntries = await readdir6(repoDir, { withFileTypes: true });
71005
- const rootFiles = rootEntries.filter((e) => e.isFile()).map((e) => e.name);
71006
- const rootDirs = rootEntries.filter((e) => e.isDirectory() && e.name !== ".git").map((e) => e.name);
71007
- if (rootFiles.length) lines.push(`Root files: ${rootFiles.join(", ")}`);
71008
- if (rootDirs.length) lines.push(`Root directories: ${rootDirs.join(", ")}`);
71009
- const manifestFiles = [
71010
- "package.json",
71011
- "Cargo.toml",
71012
- "go.mod",
71013
- "requirements.txt",
71014
- "pyproject.toml",
71015
- "Gemfile",
71016
- "pom.xml",
71017
- "build.gradle",
71018
- "composer.json",
71019
- "Makefile",
71020
- "Dockerfile",
71021
- "docker-compose.yml",
71022
- "docker-compose.yaml",
71023
- ".github/workflows"
71024
- ];
71025
- for (const manifest of manifestFiles) {
71026
- const fullPath = join15(repoDir, manifest);
71027
- try {
71028
- const fileStat = await stat4(fullPath);
71029
- if (fileStat.isFile()) {
71030
- const content = await readFile9(fullPath, "utf8");
71031
- lines.push(`
71032
- --- ${manifest} ---
71033
- ${content.slice(0, 3e3)}`);
71034
- } else if (fileStat.isDirectory()) {
71035
- const children = await readdir6(fullPath);
71036
- lines.push(`
71037
- --- ${manifest}/ ---
71038
- ${children.join(", ")}`);
71039
- }
71040
- } catch {
71041
- }
71042
- }
71043
- const srcDirs = rootDirs.filter(
71044
- (d) => ["src", "lib", "app", "packages", "crates", "cmd", "internal"].includes(d)
71045
- );
71046
- for (const srcDir of srcDirs) {
71047
- try {
71048
- const srcEntries = await readdir6(join15(repoDir, srcDir), { withFileTypes: true });
71049
- const srcFiles = srcEntries.filter((e) => e.isFile()).map((e) => e.name);
71050
- const srcSubDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
71051
- lines.push(`
71052
- --- ${srcDir}/ ---`);
71053
- if (srcSubDirs.length) lines.push(` Directories: ${srcSubDirs.join(", ")}`);
71054
- if (srcFiles.length) lines.push(` Files: ${srcFiles.slice(0, 30).join(", ")}`);
71055
- } catch {
71056
- }
71057
- }
71058
- const readmeCandidates = ["README.md", "README.rst", "README.txt", "README"];
71059
- for (const readme of readmeCandidates) {
71060
- try {
71061
- const content = await readFile9(join15(repoDir, readme), "utf8");
71062
- lines.push(`
71063
- --- ${readme} (excerpt) ---
71064
- ${content.slice(0, 1500)}`);
71065
- break;
71066
- } catch {
71067
- }
70828
+ const scanRoots = scanPaths && scanPaths.length > 0 ? scanPaths.map((p) => ({ path: join15(repoDir, p), label: p })) : [{ path: repoDir, label: "" }];
70829
+ if (scanPaths && scanPaths.length > 0) {
70830
+ lines.push(`Focused scan paths: ${scanPaths.join(", ")}`);
70831
+ }
70832
+ for (const root of scanRoots) {
70833
+ try {
70834
+ lines.push(...await scanRoot(root));
70835
+ } catch {
70836
+ lines.push(`Filesystem scan failed for ${root.label || "repo root"}; skipping.`);
71068
70837
  }
71069
- } catch {
71070
- lines.push("Filesystem scan failed; using minimal info.");
71071
70838
  }
71072
70839
  lines.push(
71073
70840
  "\nBased on the above repository structure and contents, propose roles that match the project's actual technology stack and architecture."
@@ -71082,6 +70849,22 @@ function formatSquadList(squads) {
71082
70849
  (squad) => `${squad.id}: ${squad.name} (${squad.repoOwner}/${squad.repoName}) [${squad.status}]`
71083
70850
  ).join("\n");
71084
70851
  }
70852
+ async function processQueue2(squadId) {
70853
+ try {
70854
+ const running = await countRunningInstances(squadId);
70855
+ const config2 = (await Promise.resolve().then(() => (init_config(), config_exports))).loadConfig();
70856
+ if (running < config2.maxInstancesPerSquad) {
70857
+ const next = await getNextQueued(squadId);
70858
+ if (next) {
70859
+ const freshSquad = await getSquad(squadId);
70860
+ if (freshSquad) {
70861
+ void startAndExecuteInstance(next.id, freshSquad, next.objectiveId);
70862
+ }
70863
+ }
70864
+ }
70865
+ } catch {
70866
+ }
70867
+ }
71085
70868
  async function startAndExecuteInstance(instanceId, squad, objectiveId) {
71086
70869
  try {
71087
70870
  const repoPath = await resolveRepoPath(squad.repoUrl, squad.repoName);
@@ -71102,56 +70885,184 @@ async function startAndExecuteInstance(instanceId, squad, objectiveId) {
71102
70885
  await failInstance(instanceId, message2).catch(() => {
71103
70886
  });
71104
70887
  } finally {
71105
- try {
71106
- const running = await countRunningInstances(squad.id);
71107
- const config2 = (await Promise.resolve().then(() => (init_config(), config_exports))).loadConfig();
71108
- if (running < config2.maxInstancesPerSquad) {
71109
- const next = await getNextQueued(squad.id);
71110
- if (next) {
71111
- const freshSquad = await getSquad(squad.id);
71112
- if (freshSquad) {
71113
- void startAndExecuteInstance(next.id, freshSquad, next.objectiveId);
71114
- }
71115
- }
71116
- }
71117
- } catch {
71118
- }
70888
+ await processQueue2(squad.id);
71119
70889
  }
71120
70890
  }
70891
+ async function handleCreateSquad(rawArgs) {
70892
+ const { repoUrl, name, config: config2 } = createSquadSchema.parse(rawArgs);
70893
+ const parsedRepo = parseRepoUrl(repoUrl);
70894
+ const existingSquad = await getSquadByRepo(parsedRepo.owner, parsedRepo.name);
70895
+ if (existingSquad) {
70896
+ return {
70897
+ message: `A squad already exists for ${parsedRepo.owner}/${parsedRepo.name}.`,
70898
+ squad: existingSquad
70899
+ };
70900
+ }
70901
+ const squadName = name || `${titleCase(parsedRepo.name)} Squad`;
70902
+ const squadConfig = {
70903
+ prMode: config2?.prMode ?? "draft-pr",
70904
+ mcpServers: config2?.mcpServers ?? [],
70905
+ maxRevisions: config2?.maxRevisions ?? QA_MAX_REVISIONS
70906
+ };
70907
+ const squad = await createSquad({
70908
+ name: squadName,
70909
+ repoUrl,
70910
+ repoOwner: parsedRepo.owner,
70911
+ repoName: parsedRepo.name,
70912
+ status: "active",
70913
+ config: squadConfig
70914
+ });
70915
+ eventBus.emit(EVENT_NAMES.SQUAD_CREATED, { squad });
70916
+ return {
70917
+ message: `Squad "${squad.name}" created for ${parsedRepo.owner}/${parsedRepo.name}. Add members with add_squad_member.`,
70918
+ squad
70919
+ };
70920
+ }
70921
+ async function handleAddSquadMember(rawArgs) {
70922
+ const { squadId, role, name, systemPrompt, model } = addSquadMemberSchema.parse(rawArgs);
70923
+ const squad = await getSquad(squadId);
70924
+ if (!squad) {
70925
+ throw new Error(`Squad ${squadId} was not found.`);
70926
+ }
70927
+ const member = await addMember(squadId, {
70928
+ role,
70929
+ name,
70930
+ systemPrompt,
70931
+ model: model ?? null
70932
+ });
70933
+ eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, { squadId, member });
70934
+ return {
70935
+ message: `Added member "${member.name}" (${member.role}) to squad "${squad.name}".`,
70936
+ member
70937
+ };
70938
+ }
70939
+ async function handleRemoveSquadMember(rawArgs) {
70940
+ const { squadId, memberId } = removeSquadMemberSchema.parse(rawArgs);
70941
+ const squad = await getSquad(squadId);
70942
+ if (!squad) {
70943
+ throw new Error(`Squad ${squadId} was not found.`);
70944
+ }
70945
+ const member = await getMember(memberId);
70946
+ if (!member || member.squadId !== squadId) {
70947
+ throw new Error(`Member ${memberId} was not found in squad ${squadId}.`);
70948
+ }
70949
+ await removeMember(memberId);
70950
+ eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad });
70951
+ return {
70952
+ message: `Removed member "${member.name}" (${member.role}) from squad "${squad.name}".`,
70953
+ memberId
70954
+ };
70955
+ }
70956
+ async function handleUpdateSquadConfig(rawArgs) {
70957
+ const { squadId, config: config2 } = updateSquadConfigSchema.parse(rawArgs);
70958
+ const squad = await getSquad(squadId);
70959
+ if (!squad) {
70960
+ throw new Error(`Squad ${squadId} was not found.`);
70961
+ }
70962
+ const mergedConfig = {
70963
+ prMode: config2.prMode ?? squad.config.prMode,
70964
+ mcpServers: config2.mcpServers ?? squad.config.mcpServers,
70965
+ maxRevisions: config2.maxRevisions ?? squad.config.maxRevisions
70966
+ };
70967
+ const updated = await updateSquad(squadId, { config: mergedConfig });
70968
+ if (!updated) {
70969
+ throw new Error(`Failed to update squad ${squadId}.`);
70970
+ }
70971
+ eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad: updated });
70972
+ return {
70973
+ message: `Updated config for squad "${updated.name}".`,
70974
+ squad: updated
70975
+ };
70976
+ }
70977
+ async function handleAnalyzeRepo(rawArgs) {
70978
+ const { repoUrl, scanPaths } = analyzeRepoSchema.parse(rawArgs);
70979
+ const analysis = await buildRepoAnalysis(repoUrl, scanPaths);
70980
+ return {
70981
+ message: "Repository analysis complete.",
70982
+ analysis
70983
+ };
70984
+ }
70985
+ async function handleFireSquad(rawArgs) {
70986
+ const { squadId } = squadIdSchema.parse(rawArgs);
70987
+ const squad = await getSquad(squadId);
70988
+ if (!squad) {
70989
+ throw new Error(`Squad ${squadId} was not found.`);
70990
+ }
70991
+ const activeObjectives = await getActiveObjectives(squadId);
70992
+ if (activeObjectives.length > 0) {
70993
+ const updated = await updateSquad(squadId, { status: "inactive" });
70994
+ return {
70995
+ message: `Squad ${squadId} was deactivated because it still has active objectives.`,
70996
+ squad: updated,
70997
+ activeObjectives
70998
+ };
70999
+ }
71000
+ await deleteSquad(squadId);
71001
+ return { message: `Squad ${squadId} was deleted.`, squadId };
71002
+ }
71003
+ async function handleDelegateToSquad(rawArgs) {
71004
+ const { squadId, objective } = delegateToSquadSchema.parse(rawArgs);
71005
+ const squad = await getSquad(squadId);
71006
+ if (!squad) {
71007
+ throw new Error(`Squad ${squadId} was not found.`);
71008
+ }
71009
+ const createdObjective = await createObjective(squadId, objective);
71010
+ const { instance, queued } = await spawnInstance({
71011
+ squadId,
71012
+ objectiveId: createdObjective.id
71013
+ });
71014
+ if (!queued) {
71015
+ void startAndExecuteInstance(instance.id, squad, createdObjective.id);
71016
+ }
71017
+ return {
71018
+ message: queued ? `Objective queued for squad ${squad.name} (at capacity). Instance ${instance.id} will start when a slot opens.` : `Delegated objective to squad ${squad.name}. Instance ${instance.id} started.`,
71019
+ objective: createdObjective,
71020
+ instanceId: instance.id,
71021
+ queued
71022
+ };
71023
+ }
71024
+ async function handleUpdateSquadMember(rawArgs) {
71025
+ const { squadId, memberId, role, systemPrompt, model } = updateSquadMemberSchema.parse(rawArgs);
71026
+ const squad = await getSquad(squadId);
71027
+ if (!squad) {
71028
+ throw new Error(`Squad ${squadId} was not found.`);
71029
+ }
71030
+ const member = await getMember(memberId);
71031
+ if (!member || member.squadId !== squadId) {
71032
+ throw new Error(`Member ${memberId} was not found in squad ${squadId}.`);
71033
+ }
71034
+ const updated = await updateMember(memberId, {
71035
+ role,
71036
+ systemPrompt,
71037
+ model: model === "" ? null : model
71038
+ });
71039
+ if (!updated) {
71040
+ throw new Error(`Failed to update member ${memberId}.`);
71041
+ }
71042
+ eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, {
71043
+ squadId,
71044
+ member: updated
71045
+ });
71046
+ return {
71047
+ message: `Updated member "${updated.name}" in squad "${squad.name}".`,
71048
+ member: updated
71049
+ };
71050
+ }
71121
71051
  function createSquadToolExecutor(config2) {
71122
71052
  return async (toolName, rawArgs) => {
71123
71053
  switch (toolName) {
71124
- case "hire_squad": {
71125
- const { repoUrl } = hireSquadSchema.parse(rawArgs);
71126
- const composition = await proposeSquadComposition(
71127
- repoUrl,
71128
- await buildRepoAnalysis(repoUrl)
71129
- );
71130
- const result = await hireSquad(repoUrl, composition, getDefaultSquadConfig(config2));
71131
- return {
71132
- message: `Squad ready for ${repoUrl}.`,
71133
- squad: result.squad,
71134
- members: result.members
71135
- };
71136
- }
71137
- case "fire_squad": {
71138
- const { squadId } = squadIdSchema.parse(rawArgs);
71139
- const squad = await getSquad(squadId);
71140
- if (!squad) {
71141
- throw new Error(`Squad ${squadId} was not found.`);
71142
- }
71143
- const activeObjectives = await getActiveObjectives(squadId);
71144
- if (activeObjectives.length > 0) {
71145
- const updated = await updateSquad(squadId, { status: "inactive" });
71146
- return {
71147
- message: `Squad ${squadId} was deactivated because it still has active objectives.`,
71148
- squad: updated,
71149
- activeObjectives
71150
- };
71151
- }
71152
- await deleteSquad(squadId);
71153
- return { message: `Squad ${squadId} was deleted.`, squadId };
71154
- }
71054
+ case "create_squad":
71055
+ return handleCreateSquad(rawArgs);
71056
+ case "add_squad_member":
71057
+ return handleAddSquadMember(rawArgs);
71058
+ case "remove_squad_member":
71059
+ return handleRemoveSquadMember(rawArgs);
71060
+ case "update_squad_config":
71061
+ return handleUpdateSquadConfig(rawArgs);
71062
+ case "analyze_repo":
71063
+ return handleAnalyzeRepo(rawArgs);
71064
+ case "fire_squad":
71065
+ return handleFireSquad(rawArgs);
71155
71066
  case "list_squads": {
71156
71067
  const squads = await listSquads();
71157
71068
  return {
@@ -71167,47 +71078,73 @@ function createSquadToolExecutor(config2) {
71167
71078
  ...status
71168
71079
  };
71169
71080
  }
71170
- case "delegate_to_squad": {
71171
- const { squadId, objective } = delegateToSquadSchema.parse(rawArgs);
71172
- const squad = await getSquad(squadId);
71173
- if (!squad) {
71081
+ case "delegate_to_squad":
71082
+ return handleDelegateToSquad(rawArgs);
71083
+ case "rename_squad": {
71084
+ const { squadId, name } = renameSquadSchema.parse(rawArgs);
71085
+ const updated = await updateSquad(squadId, { name });
71086
+ if (!updated) {
71174
71087
  throw new Error(`Squad ${squadId} was not found.`);
71175
71088
  }
71176
- const createdObjective = await createObjective(squadId, objective);
71177
- const { instance, queued } = await spawnInstance({
71178
- squadId,
71179
- objectiveId: createdObjective.id
71180
- });
71181
- if (!queued) {
71182
- void startAndExecuteInstance(instance.id, squad, createdObjective.id);
71183
- }
71089
+ eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad: updated });
71184
71090
  return {
71185
- message: queued ? `Objective queued for squad ${squad.name} (at capacity). Instance ${instance.id} will start when a slot opens.` : `Delegated objective to squad ${squad.name}. Instance ${instance.id} started.`,
71186
- objective: createdObjective,
71187
- instanceId: instance.id,
71188
- queued
71091
+ message: `Squad renamed to "${updated.name}".`,
71092
+ squad: updated
71189
71093
  };
71190
71094
  }
71095
+ case "update_squad_member":
71096
+ return handleUpdateSquadMember(rawArgs);
71191
71097
  default:
71192
71098
  throw new Error(`Unsupported squad tool: ${toolName}`);
71193
71099
  }
71194
71100
  };
71195
71101
  }
71196
- var execAsync8, hireSquadSchema, squadIdSchema, delegateToSquadSchema, squadToolDefinitions;
71102
+ var execAsync8, createSquadSchema, addSquadMemberSchema, removeSquadMemberSchema, updateSquadConfigSchema, analyzeRepoSchema, squadIdSchema, delegateToSquadSchema, renameSquadSchema, updateSquadMemberSchema, squadToolDefinitions, MANIFEST_FILES, SRC_DIR_NAMES, README_CANDIDATES2;
71197
71103
  var init_squad2 = __esm({
71198
71104
  "packages/daemon/src/orchestrator/tools/squad.ts"() {
71199
71105
  "use strict";
71200
71106
  init_dist();
71201
71107
  init_paths();
71202
71108
  init_zod();
71109
+ init_event_bus();
71203
71110
  init_instances2();
71204
71111
  init_runner();
71205
- init_hiring();
71206
71112
  init_manager2();
71207
71113
  init_store2();
71208
71114
  execAsync8 = promisify8(exec8);
71209
- hireSquadSchema = external_exports.object({
71210
- repoUrl: external_exports.string().trim().min(1)
71115
+ createSquadSchema = external_exports.object({
71116
+ repoUrl: external_exports.string().trim().min(1).describe("The repository URL to create a squad for"),
71117
+ name: external_exports.string().trim().optional().describe("Squad name. Defaults to '<RepoName> Squad' if not provided."),
71118
+ config: external_exports.object({
71119
+ prMode: external_exports.enum(["draft-pr", "branch-only", "ready-pr", "auto-merge"]).optional().describe("PR creation mode. Defaults to 'draft-pr'."),
71120
+ mcpServers: external_exports.array(external_exports.string()).optional().describe("MCP server URLs to configure for the squad."),
71121
+ maxRevisions: external_exports.number().optional().describe("Max QA revision cycles. Defaults to system default.")
71122
+ }).optional().describe("Squad configuration. All fields are optional with sensible defaults.")
71123
+ });
71124
+ addSquadMemberSchema = external_exports.object({
71125
+ squadId: external_exports.string().trim().min(1),
71126
+ role: external_exports.string().trim().min(1).describe("Slug-style role identifier (e.g. 'senior-frontend-engineer', 'team-lead')"),
71127
+ name: external_exports.string().trim().min(1).describe("Display name for the member (e.g. 'Senior Frontend Engineer')"),
71128
+ systemPrompt: external_exports.string().min(1).describe("The system prompt defining this member's expertise, responsibilities, and behavior."),
71129
+ model: external_exports.string().optional().describe("Model override for this member. Uses squad default if not provided.")
71130
+ });
71131
+ removeSquadMemberSchema = external_exports.object({
71132
+ squadId: external_exports.string().trim().min(1),
71133
+ memberId: external_exports.string().trim().min(1)
71134
+ });
71135
+ updateSquadConfigSchema = external_exports.object({
71136
+ squadId: external_exports.string().trim().min(1),
71137
+ config: external_exports.object({
71138
+ prMode: external_exports.enum(["draft-pr", "branch-only", "ready-pr", "auto-merge"]).optional(),
71139
+ mcpServers: external_exports.array(external_exports.string()).optional(),
71140
+ maxRevisions: external_exports.number().optional()
71141
+ }).describe("Partial config fields to update. Only provided fields are changed.")
71142
+ });
71143
+ analyzeRepoSchema = external_exports.object({
71144
+ repoUrl: external_exports.string().trim().min(1).describe("The repository URL to analyze"),
71145
+ scanPaths: external_exports.array(external_exports.string()).optional().describe(
71146
+ "Relative paths within the repository to focus the analysis on. When provided, only these directories are scanned instead of the entire repo."
71147
+ )
71211
71148
  });
71212
71149
  squadIdSchema = external_exports.object({
71213
71150
  squadId: external_exports.string().trim().min(1)
@@ -71216,11 +71153,46 @@ var init_squad2 = __esm({
71216
71153
  squadId: external_exports.string().trim().min(1),
71217
71154
  objective: external_exports.string().trim().min(1)
71218
71155
  });
71156
+ renameSquadSchema = external_exports.object({
71157
+ squadId: external_exports.string().trim().min(1),
71158
+ name: external_exports.string().trim().min(1)
71159
+ });
71160
+ updateSquadMemberSchema = external_exports.object({
71161
+ squadId: external_exports.string().trim().min(1),
71162
+ memberId: external_exports.string().trim().min(1),
71163
+ role: external_exports.string().trim().min(1).optional(),
71164
+ systemPrompt: external_exports.string().optional(),
71165
+ model: external_exports.string().optional()
71166
+ });
71219
71167
  squadToolDefinitions = [
71220
71168
  {
71221
- name: "hire_squad",
71222
- description: "Hire or refresh a squad for a repository.",
71223
- parameters: hireSquadSchema,
71169
+ name: "create_squad",
71170
+ description: "Create a new squad for a repository. After creating, use add_squad_member to populate it with agents. Always include team-lead and qa roles.",
71171
+ parameters: createSquadSchema,
71172
+ skipPermission: true
71173
+ },
71174
+ {
71175
+ name: "add_squad_member",
71176
+ description: "Add a member (agent) to an existing squad. Provide a role slug, display name, and a detailed system prompt defining the agent's expertise and responsibilities.",
71177
+ parameters: addSquadMemberSchema,
71178
+ skipPermission: true
71179
+ },
71180
+ {
71181
+ name: "remove_squad_member",
71182
+ description: "Remove a member from a squad by their ID.",
71183
+ parameters: removeSquadMemberSchema,
71184
+ skipPermission: true
71185
+ },
71186
+ {
71187
+ name: "update_squad_config",
71188
+ description: "Update a squad's configuration (prMode, mcpServers, maxRevisions).",
71189
+ parameters: updateSquadConfigSchema,
71190
+ skipPermission: true
71191
+ },
71192
+ {
71193
+ name: "analyze_repo",
71194
+ description: "Analyze a repository's structure and tech stack. Returns information about frameworks, languages, directory layout, and config files. Use this before creating a squad to inform role decisions.",
71195
+ parameters: analyzeRepoSchema,
71224
71196
  skipPermission: true
71225
71197
  },
71226
71198
  {
@@ -71246,8 +71218,38 @@ var init_squad2 = __esm({
71246
71218
  description: "Create an objective for a squad and start execution.",
71247
71219
  parameters: delegateToSquadSchema,
71248
71220
  skipPermission: true
71221
+ },
71222
+ {
71223
+ name: "rename_squad",
71224
+ description: "Rename a squad.",
71225
+ parameters: renameSquadSchema,
71226
+ skipPermission: true
71227
+ },
71228
+ {
71229
+ name: "update_squad_member",
71230
+ description: "Update a squad member's role, system prompt, and/or default model. All fields are optional; only provided fields are changed.",
71231
+ parameters: updateSquadMemberSchema,
71232
+ skipPermission: true
71249
71233
  }
71250
71234
  ];
71235
+ MANIFEST_FILES = [
71236
+ "package.json",
71237
+ "Cargo.toml",
71238
+ "go.mod",
71239
+ "requirements.txt",
71240
+ "pyproject.toml",
71241
+ "Gemfile",
71242
+ "pom.xml",
71243
+ "build.gradle",
71244
+ "composer.json",
71245
+ "Makefile",
71246
+ "Dockerfile",
71247
+ "docker-compose.yml",
71248
+ "docker-compose.yaml",
71249
+ ".github/workflows"
71250
+ ];
71251
+ SRC_DIR_NAMES = ["src", "lib", "app", "packages", "crates", "cmd", "internal"];
71252
+ README_CANDIDATES2 = ["README.md", "README.rst", "README.txt", "README"];
71251
71253
  }
71252
71254
  });
71253
71255
 
@@ -71258,6 +71260,30 @@ function buildMemoryPath(timestamp) {
71258
71260
  const time3 = iso.slice(11, 19).replace(/:/gu, "-");
71259
71261
  return `memory/${date5}/${time3}.md`;
71260
71262
  }
71263
+ async function handleWikiWrite(rawArgs) {
71264
+ const { path, title, content, tags } = wikiWriteSchema.parse(rawArgs);
71265
+ const existing = await getPage(path);
71266
+ const page = existing ? await updatePage(path, { title, content, tags: tags ?? [] }) : await createPage(path, title, content, tags ?? []);
71267
+ return {
71268
+ message: `${existing ? "Updated" : "Created"} wiki page ${path}.`,
71269
+ page
71270
+ };
71271
+ }
71272
+ async function handleRecall(rawArgs) {
71273
+ const { query } = recallSchema.parse(rawArgs);
71274
+ const [matches, recents] = await Promise.all([searchPages(query, 1), getRecentPages(1)]);
71275
+ const page = matches[0] ?? recents[0] ?? null;
71276
+ if (!page) {
71277
+ return { message: "No wiki content is available yet." };
71278
+ }
71279
+ return {
71280
+ message: `Best match: ${page.title}`,
71281
+ path: page.path,
71282
+ title: page.title,
71283
+ content: page.content,
71284
+ tags: page.tags
71285
+ };
71286
+ }
71261
71287
  var wikiReadSchema, wikiWriteSchema, wikiSearchSchema, rememberSchema, recallSchema, wikiToolDefinitions, executeWikiToolCall;
71262
71288
  var init_wiki4 = __esm({
71263
71289
  "packages/daemon/src/orchestrator/tools/wiki.ts"() {
@@ -71327,15 +71353,8 @@ var init_wiki4 = __esm({
71327
71353
  }
71328
71354
  return { page };
71329
71355
  }
71330
- case "wiki_write": {
71331
- const { path, title, content, tags } = wikiWriteSchema.parse(rawArgs);
71332
- const existing = await getPage(path);
71333
- const page = existing ? await updatePage(path, { title, content, tags: tags ?? [] }) : await createPage(path, title, content, tags ?? []);
71334
- return {
71335
- message: `${existing ? "Updated" : "Created"} wiki page ${path}.`,
71336
- page
71337
- };
71338
- }
71356
+ case "wiki_write":
71357
+ return handleWikiWrite(rawArgs);
71339
71358
  case "wiki_search": {
71340
71359
  const { query, limit } = wikiSearchSchema.parse(rawArgs);
71341
71360
  const pages = await searchPages(query, limit ?? 5);
@@ -71355,21 +71374,8 @@ var init_wiki4 = __esm({
71355
71374
  page
71356
71375
  };
71357
71376
  }
71358
- case "recall": {
71359
- const { query } = recallSchema.parse(rawArgs);
71360
- const [matches, recents] = await Promise.all([searchPages(query, 1), getRecentPages(1)]);
71361
- const page = matches[0] ?? recents[0] ?? null;
71362
- if (!page) {
71363
- return { message: "No wiki content is available yet." };
71364
- }
71365
- return {
71366
- message: `Best match: ${page.title}`,
71367
- path: page.path,
71368
- title: page.title,
71369
- content: page.content,
71370
- tags: page.tags
71371
- };
71372
- }
71377
+ case "recall":
71378
+ return handleRecall(rawArgs);
71373
71379
  default:
71374
71380
  throw new Error(`Unsupported wiki tool: ${toolName}`);
71375
71381
  }
@@ -71680,8 +71686,7 @@ var init_orchestrator = __esm({
71680
71686
  }
71681
71687
  const model = usage.model || this.activeModel || this.config.defaultModel;
71682
71688
  const pricing = await getModelPricing(model);
71683
- const premiumRequestCost = pricing?.premiumMultiplier ?? 0;
71684
- const tokenUnitCost = pricing ? calculateTokenUnitCost(
71689
+ const cost = pricing ? calculateTokenUnitCost(
71685
71690
  usage.inputTokens,
71686
71691
  usage.outputTokens,
71687
71692
  pricing.tokenInputMultiplier,
@@ -71691,9 +71696,7 @@ var init_orchestrator = __esm({
71691
71696
  model,
71692
71697
  inputTokens: usage.inputTokens,
71693
71698
  outputTokens: usage.outputTokens,
71694
- cost: tokenUnitCost,
71695
- premiumRequestCost,
71696
- tokenUnitCost
71699
+ cost
71697
71700
  });
71698
71701
  }
71699
71702
  };