heyio 4.2.2 → 4.2.3

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.
@@ -80,7 +80,7 @@ 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.3";
84
84
  API_PORT = 7777;
85
85
  API_HOST = "0.0.0.0";
86
86
  DEFAULT_MODEL = "gpt-4o";
@@ -92,6 +92,7 @@ var init_constants = __esm({
92
92
  SQUAD_CREATED: "squad.created",
93
93
  SQUAD_DELETED: "squad.deleted",
94
94
  SQUAD_UPDATED: "squad.updated",
95
+ SQUAD_MEMBER_UPDATED: "squad.member_updated",
95
96
  INSTANCE_CREATED: "instance.created",
96
97
  INSTANCE_STARTED: "instance.started",
97
98
  INSTANCE_COMPLETED: "instance.completed",
@@ -2310,6 +2311,10 @@ async function updateMember(memberId, data, db) {
2310
2311
  const database = db ?? await getDatabase();
2311
2312
  const sets = [];
2312
2313
  const args = [];
2314
+ if (data.role !== void 0) {
2315
+ sets.push("role = ?");
2316
+ args.push(data.role);
2317
+ }
2313
2318
  if (data.systemPrompt !== void 0) {
2314
2319
  sets.push("system_prompt = ?");
2315
2320
  args.push(data.systemPrompt);
@@ -11914,8 +11919,8 @@ async function recordUsage(data, db) {
11914
11919
  createdAt: data.createdAt ?? nowIso()
11915
11920
  };
11916
11921
  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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11922
+ sql: `INSERT INTO token_usage (id, squad_id, agent_id, model, input_tokens, output_tokens, cost, squad_name, agent_name, created_at)
11923
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
11919
11924
  args: [
11920
11925
  usage.id,
11921
11926
  usage.squadId,
@@ -11924,8 +11929,6 @@ async function recordUsage(data, db) {
11924
11929
  usage.inputTokens,
11925
11930
  usage.outputTokens,
11926
11931
  usage.cost,
11927
- data.premiumRequestCost ?? null,
11928
- data.tokenUnitCost ?? null,
11929
11932
  data.squadName ?? null,
11930
11933
  data.agentName ?? null,
11931
11934
  usage.createdAt
@@ -52870,11 +52873,16 @@ var init_squads2 = __esm({
52870
52873
  res.status(404).json({ error: "Member not found" });
52871
52874
  return;
52872
52875
  }
52873
- const { systemPrompt, model } = req.body;
52876
+ const { role, systemPrompt, model } = req.body;
52874
52877
  const updated = await updateMember(member.id, {
52878
+ role,
52875
52879
  systemPrompt,
52876
52880
  model: model === "" ? null : model
52877
52881
  });
52882
+ eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, {
52883
+ squadId: member.squadId,
52884
+ member: updated
52885
+ });
52878
52886
  res.status(200).json(updated);
52879
52887
  } catch (error51) {
52880
52888
  res.status(500).json({
@@ -68325,10 +68333,19 @@ async function getModelPricing(modelId) {
68325
68333
  sql: "SELECT * FROM model_pricing WHERE id = ?",
68326
68334
  args: [modelId]
68327
68335
  });
68328
- if (result.rows.length === 0) {
68329
- return null;
68336
+ if (result.rows.length > 0) {
68337
+ return rowToModelPricing(result.rows[0]);
68338
+ }
68339
+ const allModels = await db.execute(
68340
+ "SELECT * FROM model_pricing WHERE token_input_multiplier IS NOT NULL ORDER BY length(id) DESC"
68341
+ );
68342
+ for (const row of allModels.rows) {
68343
+ const storedId = asString(row.id);
68344
+ if (modelId.startsWith(storedId) || storedId.startsWith(modelId)) {
68345
+ return rowToModelPricing(row);
68346
+ }
68330
68347
  }
68331
- return rowToModelPricing(result.rows[0]);
68348
+ return null;
68332
68349
  }
68333
68350
  function calculateTokenUnitCost(inputTokens, outputTokens, inputMultiplier, outputMultiplier) {
68334
68351
  if (inputMultiplier === null || outputMultiplier === null) {
@@ -68426,7 +68443,7 @@ function buildSystemPrompt(options2) {
68426
68443
  "You coordinate work across squads, wiki knowledge, installed skills, and direct execution tools.",
68427
68444
  "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.",
68428
68445
  "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.",
68446
+ "## 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- When hiring a squad, always pass the user's context about what areas of the codebase or what kind of work the squad will focus on. Extract specific folder paths or project files mentioned by the user and pass them as scanPaths.\n- Use the wiki to remember important information and recover prior context.\n- Use installed skills when they are relevant to the current request.",
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
  }
@@ -69894,9 +69908,10 @@ Review rules:
69894
69908
  Given repository analysis describing languages, frameworks, file structure, architectural patterns, and operational concerns, propose the smallest effective team that can deliver objectives safely.
69895
69909
 
69896
69910
  Requirements:
69897
- - Mandatory roles Team Lead and QA must always exist.
69911
+ - Mandatory roles Team Lead and QA must always exist \u2014 do NOT include them in your response.
69898
69912
  - 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.
69913
+ - Role names must reflect Senior or Principal seniority and be specific to the actual technology stack and work scope. Examples: "Principal React Engineer", "Senior DevOps Engineer", "Senior CI/CD Solutions Engineer", "Principal .NET API Engineer", "Senior Data Pipeline Engineer".
69914
+ - Do NOT use generic names like "Frontend Engineer" or "Backend Engineer". Be specific about the technology and domain.
69900
69915
  - Each role must have a short human-readable name and a concise description of responsibilities.
69901
69916
  - Avoid duplicate or overlapping roles.
69902
69917
  - Optimize for implementation, verification, and maintainability.
@@ -69905,9 +69920,9 @@ Return strict JSON in this shape:
69905
69920
  {
69906
69921
  "roles": [
69907
69922
  {
69908
- "role": "frontend-engineer",
69909
- "name": "Frontend Engineer",
69910
- "description": "Owns UI implementation, client state, and browser-facing tests."
69923
+ "role": "principal-react-engineer",
69924
+ "name": "Principal React Engineer",
69925
+ "description": "Owns UI implementation, client state management, and browser-facing integration tests."
69911
69926
  }
69912
69927
  ]
69913
69928
  }`;
@@ -70753,29 +70768,29 @@ function detectRolesFromAnalysis(repoAnalysis) {
70753
70768
  const roles = [];
70754
70769
  if (/(react|next|vue|angular|frontend|ui|xaml)/.test(analysis)) {
70755
70770
  roles.push({
70756
- role: "frontend-engineer",
70757
- name: "Frontend Engineer",
70771
+ role: "senior-frontend-engineer",
70772
+ name: "Senior Frontend Engineer",
70758
70773
  description: "Implements user-facing experiences, UI state, and presentation-layer changes."
70759
70774
  });
70760
70775
  }
70761
70776
  if (/(node|express|api|backend|server|daemon|database|sql|postgres|sqlite)/.test(analysis)) {
70762
70777
  roles.push({
70763
- role: "backend-engineer",
70764
- name: "Backend Engineer",
70778
+ role: "senior-backend-engineer",
70779
+ name: "Senior Backend Engineer",
70765
70780
  description: "Implements server-side logic, data access, integrations, and execution flow changes."
70766
70781
  });
70767
70782
  }
70768
70783
  if (/(infra|docker|kubernetes|deploy|ci|cd|workflow|github actions)/.test(analysis)) {
70769
70784
  roles.push({
70770
- role: "platform-engineer",
70771
- name: "Platform Engineer",
70785
+ role: "senior-platform-engineer",
70786
+ name: "Senior Platform Engineer",
70772
70787
  description: "Owns automation, pipelines, environments, and operational tooling."
70773
70788
  });
70774
70789
  }
70775
70790
  if (/(security|auth|oauth|secret|policy)/.test(analysis)) {
70776
70791
  roles.push({
70777
- role: "security-engineer",
70778
- name: "Security Engineer",
70792
+ role: "senior-security-engineer",
70793
+ name: "Senior Security Engineer",
70779
70794
  description: "Reviews authentication, authorization, secrets handling, and security-sensitive changes."
70780
70795
  });
70781
70796
  }
@@ -70835,13 +70850,17 @@ ${repoContext}`;
70835
70850
  }
70836
70851
  return generateRolePrompt(normalized, repoContext);
70837
70852
  }
70838
- async function proposeSquadComposition(repoUrl, repoAnalysis) {
70853
+ async function proposeSquadComposition(repoUrl, repoAnalysis, context) {
70839
70854
  const mandatory = buildMandatoryRoles();
70840
70855
  const fallbackAdditional = dedupeRoles(detectRolesFromAnalysis(repoAnalysis));
70856
+ const contextSection = context ? `
70857
+
70858
+ Additional context from user:
70859
+ ${context}` : "";
70841
70860
  const prompt = `Repository URL: ${repoUrl}
70842
70861
 
70843
70862
  Repository analysis:
70844
- ${repoAnalysis}
70863
+ ${repoAnalysis}${contextSection}
70845
70864
 
70846
70865
  ${ROLE_GENERATION_PROMPT}`;
70847
70866
  let client2 = null;
@@ -70971,19 +70990,72 @@ function getDefaultSquadConfig(_config) {
70971
70990
  maxRevisions: QA_MAX_REVISIONS
70972
70991
  };
70973
70992
  }
70974
- async function buildRepoAnalysis(repoUrl) {
70975
- const normalized = repoUrl.trim();
70976
- const segments = normalized.replace(/\.git$/i, "").split("/").filter(Boolean);
70977
- const owner = segments.at(-2) ?? "";
70978
- const name = segments.at(-1) ?? normalized;
70979
- const lines = [`Repository URL: ${normalized}`, `Repository name: ${name}`];
70980
- if (!owner || !name) {
70981
- lines.push(
70982
- "Use the repository identity and any available conventions to propose a practical squad composition."
70983
- );
70984
- return lines.join("\n");
70993
+ async function readManifests(rootPath, rootLabel) {
70994
+ const lines = [];
70995
+ for (const manifest of MANIFEST_FILES) {
70996
+ const fullPath = join15(rootPath, manifest);
70997
+ try {
70998
+ const fileStat = await stat4(fullPath);
70999
+ if (fileStat.isFile()) {
71000
+ const content = await readFile9(fullPath, "utf8");
71001
+ lines.push(`
71002
+ --- ${rootLabel}${manifest} ---
71003
+ ${content.slice(0, 3e3)}`);
71004
+ } else if (fileStat.isDirectory()) {
71005
+ const children = await readdir6(fullPath);
71006
+ lines.push(`
71007
+ --- ${rootLabel}${manifest}/ ---
71008
+ ${children.join(", ")}`);
71009
+ }
71010
+ } catch {
71011
+ }
70985
71012
  }
70986
- const repoDir = join15(DATA_DIR, "repos", `${owner}--${name}`);
71013
+ return lines;
71014
+ }
71015
+ async function scanSrcDirs(rootPath, rootLabel, dirs) {
71016
+ const lines = [];
71017
+ const srcDirs = dirs.filter((d) => SRC_DIR_NAMES.includes(d));
71018
+ for (const srcDir of srcDirs) {
71019
+ try {
71020
+ const srcEntries = await readdir6(join15(rootPath, srcDir), { withFileTypes: true });
71021
+ const srcFiles = srcEntries.filter((e) => e.isFile()).map((e) => e.name);
71022
+ const srcSubDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
71023
+ lines.push(`
71024
+ --- ${rootLabel}${srcDir}/ ---`);
71025
+ if (srcSubDirs.length) lines.push(` Directories: ${srcSubDirs.join(", ")}`);
71026
+ if (srcFiles.length) lines.push(` Files: ${srcFiles.slice(0, 30).join(", ")}`);
71027
+ } catch {
71028
+ }
71029
+ }
71030
+ return lines;
71031
+ }
71032
+ async function findReadme(rootPath, rootLabel) {
71033
+ for (const readme of README_CANDIDATES2) {
71034
+ try {
71035
+ const content = await readFile9(join15(rootPath, readme), "utf8");
71036
+ return `
71037
+ --- ${rootLabel}${readme} (excerpt) ---
71038
+ ${content.slice(0, 1500)}`;
71039
+ } catch {
71040
+ }
71041
+ }
71042
+ return null;
71043
+ }
71044
+ async function scanRoot(root) {
71045
+ const lines = [];
71046
+ const rootLabel = root.label ? `${root.label}/` : "";
71047
+ const rootEntries = await readdir6(root.path, { withFileTypes: true });
71048
+ const rootFiles = rootEntries.filter((e) => e.isFile()).map((e) => e.name);
71049
+ const rootDirs = rootEntries.filter((e) => e.isDirectory() && e.name !== ".git").map((e) => e.name);
71050
+ if (rootFiles.length) lines.push(`${rootLabel}Files: ${rootFiles.join(", ")}`);
71051
+ if (rootDirs.length) lines.push(`${rootLabel}Directories: ${rootDirs.join(", ")}`);
71052
+ lines.push(...await readManifests(root.path, rootLabel));
71053
+ lines.push(...await scanSrcDirs(root.path, rootLabel, rootDirs));
71054
+ const readme = await findReadme(root.path, rootLabel);
71055
+ if (readme) lines.push(readme);
71056
+ return lines;
71057
+ }
71058
+ async function cloneOrUpdateRepo(normalized, repoDir) {
70987
71059
  try {
70988
71060
  await mkdir10(join15(DATA_DIR, "repos"), { recursive: true });
70989
71061
  if (await pathExists2(join15(repoDir, ".git"))) {
@@ -70995,79 +71067,40 @@ async function buildRepoAnalysis(repoUrl) {
70995
71067
  timeout: 6e4
70996
71068
  });
70997
71069
  }
71070
+ return true;
70998
71071
  } catch {
71072
+ return false;
71073
+ }
71074
+ }
71075
+ async function buildRepoAnalysis(repoUrl, scanPaths) {
71076
+ const normalized = repoUrl.trim();
71077
+ const segments = normalized.replace(/\.git$/i, "").split("/").filter(Boolean);
71078
+ const owner = segments.at(-2) ?? "";
71079
+ const name = segments.at(-1) ?? normalized;
71080
+ const lines = [`Repository URL: ${normalized}`, `Repository name: ${name}`];
71081
+ if (!owner || !name) {
71082
+ lines.push(
71083
+ "Use the repository identity and any available conventions to propose a practical squad composition."
71084
+ );
71085
+ return lines.join("\n");
71086
+ }
71087
+ const repoDir = join15(DATA_DIR, "repos", `${owner}--${name}`);
71088
+ const cloned = await cloneOrUpdateRepo(normalized, repoDir);
71089
+ if (!cloned) {
70999
71090
  lines.push("Unable to clone repository locally; falling back to basic analysis.");
71000
71091
  lines.push("Based on the repository name, propose roles that match common project patterns.");
71001
71092
  return lines.join("\n");
71002
71093
  }
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
- }
71094
+ const scanRoots = scanPaths && scanPaths.length > 0 ? scanPaths.map((p) => ({ path: join15(repoDir, p), label: p })) : [{ path: repoDir, label: "" }];
71095
+ if (scanPaths && scanPaths.length > 0) {
71096
+ lines.push(`Focused scan paths: ${scanPaths.join(", ")}`);
71097
+ }
71098
+ for (const root of scanRoots) {
71099
+ try {
71100
+ lines.push(...await scanRoot(root));
71101
+ } catch {
71102
+ lines.push(`Filesystem scan failed for ${root.label || "repo root"}; skipping.`);
71068
71103
  }
71069
- } catch {
71070
- lines.push("Filesystem scan failed; using minimal info.");
71071
71104
  }
71072
71105
  lines.push(
71073
71106
  "\nBased on the above repository structure and contents, propose roles that match the project's actual technology stack and architecture."
@@ -71082,6 +71115,22 @@ function formatSquadList(squads) {
71082
71115
  (squad) => `${squad.id}: ${squad.name} (${squad.repoOwner}/${squad.repoName}) [${squad.status}]`
71083
71116
  ).join("\n");
71084
71117
  }
71118
+ async function processQueue2(squadId) {
71119
+ try {
71120
+ const running = await countRunningInstances(squadId);
71121
+ const config2 = (await Promise.resolve().then(() => (init_config(), config_exports))).loadConfig();
71122
+ if (running < config2.maxInstancesPerSquad) {
71123
+ const next = await getNextQueued(squadId);
71124
+ if (next) {
71125
+ const freshSquad = await getSquad(squadId);
71126
+ if (freshSquad) {
71127
+ void startAndExecuteInstance(next.id, freshSquad, next.objectiveId);
71128
+ }
71129
+ }
71130
+ }
71131
+ } catch {
71132
+ }
71133
+ }
71085
71134
  async function startAndExecuteInstance(instanceId, squad, objectiveId) {
71086
71135
  try {
71087
71136
  const repoPath = await resolveRepoPath(squad.repoUrl, squad.repoName);
@@ -71102,56 +71151,96 @@ async function startAndExecuteInstance(instanceId, squad, objectiveId) {
71102
71151
  await failInstance(instanceId, message2).catch(() => {
71103
71152
  });
71104
71153
  } 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
- }
71154
+ await processQueue2(squad.id);
71155
+ }
71156
+ }
71157
+ async function handleHireSquad(rawArgs, config2) {
71158
+ const { repoUrl, context, scanPaths } = hireSquadSchema.parse(rawArgs);
71159
+ const composition = await proposeSquadComposition(
71160
+ repoUrl,
71161
+ await buildRepoAnalysis(repoUrl, scanPaths),
71162
+ context
71163
+ );
71164
+ const result = await hireSquad(repoUrl, composition, getDefaultSquadConfig(config2));
71165
+ return {
71166
+ message: `Squad ready for ${repoUrl}.`,
71167
+ squad: result.squad,
71168
+ members: result.members
71169
+ };
71170
+ }
71171
+ async function handleFireSquad(rawArgs) {
71172
+ const { squadId } = squadIdSchema.parse(rawArgs);
71173
+ const squad = await getSquad(squadId);
71174
+ if (!squad) {
71175
+ throw new Error(`Squad ${squadId} was not found.`);
71176
+ }
71177
+ const activeObjectives = await getActiveObjectives(squadId);
71178
+ if (activeObjectives.length > 0) {
71179
+ const updated = await updateSquad(squadId, { status: "inactive" });
71180
+ return {
71181
+ message: `Squad ${squadId} was deactivated because it still has active objectives.`,
71182
+ squad: updated,
71183
+ activeObjectives
71184
+ };
71185
+ }
71186
+ await deleteSquad(squadId);
71187
+ return { message: `Squad ${squadId} was deleted.`, squadId };
71188
+ }
71189
+ async function handleDelegateToSquad(rawArgs) {
71190
+ const { squadId, objective } = delegateToSquadSchema.parse(rawArgs);
71191
+ const squad = await getSquad(squadId);
71192
+ if (!squad) {
71193
+ throw new Error(`Squad ${squadId} was not found.`);
71194
+ }
71195
+ const createdObjective = await createObjective(squadId, objective);
71196
+ const { instance, queued } = await spawnInstance({
71197
+ squadId,
71198
+ objectiveId: createdObjective.id
71199
+ });
71200
+ if (!queued) {
71201
+ void startAndExecuteInstance(instance.id, squad, createdObjective.id);
71202
+ }
71203
+ return {
71204
+ 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.`,
71205
+ objective: createdObjective,
71206
+ instanceId: instance.id,
71207
+ queued
71208
+ };
71209
+ }
71210
+ async function handleUpdateSquadMember(rawArgs) {
71211
+ const { squadId, memberId, role, systemPrompt, model } = updateSquadMemberSchema.parse(rawArgs);
71212
+ const squad = await getSquad(squadId);
71213
+ if (!squad) {
71214
+ throw new Error(`Squad ${squadId} was not found.`);
71215
+ }
71216
+ const member = await getMember(memberId);
71217
+ if (!member || member.squadId !== squadId) {
71218
+ throw new Error(`Member ${memberId} was not found in squad ${squadId}.`);
71119
71219
  }
71220
+ const updated = await updateMember(memberId, {
71221
+ role,
71222
+ systemPrompt,
71223
+ model: model === "" ? null : model
71224
+ });
71225
+ if (!updated) {
71226
+ throw new Error(`Failed to update member ${memberId}.`);
71227
+ }
71228
+ eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, {
71229
+ squadId,
71230
+ member: updated
71231
+ });
71232
+ return {
71233
+ message: `Updated member "${updated.name}" in squad "${squad.name}".`,
71234
+ member: updated
71235
+ };
71120
71236
  }
71121
71237
  function createSquadToolExecutor(config2) {
71122
71238
  return async (toolName, rawArgs) => {
71123
71239
  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
- }
71240
+ case "hire_squad":
71241
+ return handleHireSquad(rawArgs, config2);
71242
+ case "fire_squad":
71243
+ return handleFireSquad(rawArgs);
71155
71244
  case "list_squads": {
71156
71245
  const squads = await listSquads();
71157
71246
  return {
@@ -71167,39 +71256,35 @@ function createSquadToolExecutor(config2) {
71167
71256
  ...status
71168
71257
  };
71169
71258
  }
71170
- case "delegate_to_squad": {
71171
- const { squadId, objective } = delegateToSquadSchema.parse(rawArgs);
71172
- const squad = await getSquad(squadId);
71173
- if (!squad) {
71259
+ case "delegate_to_squad":
71260
+ return handleDelegateToSquad(rawArgs);
71261
+ case "rename_squad": {
71262
+ const { squadId, name } = renameSquadSchema.parse(rawArgs);
71263
+ const updated = await updateSquad(squadId, { name });
71264
+ if (!updated) {
71174
71265
  throw new Error(`Squad ${squadId} was not found.`);
71175
71266
  }
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
- }
71267
+ eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad: updated });
71184
71268
  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
71269
+ message: `Squad renamed to "${updated.name}".`,
71270
+ squad: updated
71189
71271
  };
71190
71272
  }
71273
+ case "update_squad_member":
71274
+ return handleUpdateSquadMember(rawArgs);
71191
71275
  default:
71192
71276
  throw new Error(`Unsupported squad tool: ${toolName}`);
71193
71277
  }
71194
71278
  };
71195
71279
  }
71196
- var execAsync8, hireSquadSchema, squadIdSchema, delegateToSquadSchema, squadToolDefinitions;
71280
+ var execAsync8, hireSquadSchema, squadIdSchema, delegateToSquadSchema, renameSquadSchema, updateSquadMemberSchema, squadToolDefinitions, MANIFEST_FILES, SRC_DIR_NAMES, README_CANDIDATES2;
71197
71281
  var init_squad2 = __esm({
71198
71282
  "packages/daemon/src/orchestrator/tools/squad.ts"() {
71199
71283
  "use strict";
71200
71284
  init_dist();
71201
71285
  init_paths();
71202
71286
  init_zod();
71287
+ init_event_bus();
71203
71288
  init_instances2();
71204
71289
  init_runner();
71205
71290
  init_hiring();
@@ -71207,7 +71292,13 @@ var init_squad2 = __esm({
71207
71292
  init_store2();
71208
71293
  execAsync8 = promisify8(exec8);
71209
71294
  hireSquadSchema = external_exports.object({
71210
- repoUrl: external_exports.string().trim().min(1)
71295
+ repoUrl: external_exports.string().trim().min(1).describe("The repository URL to hire a squad for"),
71296
+ context: external_exports.string().optional().describe(
71297
+ "Additional context from the user to guide squad composition and role selection. Include any specifics about what areas of the codebase the squad will focus on, what technologies matter most, or what kind of work they will do."
71298
+ ),
71299
+ scanPaths: external_exports.array(external_exports.string()).optional().describe(
71300
+ "Relative paths within the repository to focus the analysis on. When provided, only these directories are scanned for technology detection instead of the entire repo. Extract these from the user's context when they mention specific folders, solution files, or project subsets."
71301
+ )
71211
71302
  });
71212
71303
  squadIdSchema = external_exports.object({
71213
71304
  squadId: external_exports.string().trim().min(1)
@@ -71216,10 +71307,21 @@ var init_squad2 = __esm({
71216
71307
  squadId: external_exports.string().trim().min(1),
71217
71308
  objective: external_exports.string().trim().min(1)
71218
71309
  });
71310
+ renameSquadSchema = external_exports.object({
71311
+ squadId: external_exports.string().trim().min(1),
71312
+ name: external_exports.string().trim().min(1)
71313
+ });
71314
+ updateSquadMemberSchema = external_exports.object({
71315
+ squadId: external_exports.string().trim().min(1),
71316
+ memberId: external_exports.string().trim().min(1),
71317
+ role: external_exports.string().trim().min(1).optional(),
71318
+ systemPrompt: external_exports.string().optional(),
71319
+ model: external_exports.string().optional()
71320
+ });
71219
71321
  squadToolDefinitions = [
71220
71322
  {
71221
71323
  name: "hire_squad",
71222
- description: "Hire or refresh a squad for a repository.",
71324
+ description: "Hire or refresh a squad for a repository. Optionally provide context to guide role selection and scanPaths to focus the repo analysis on specific directories.",
71223
71325
  parameters: hireSquadSchema,
71224
71326
  skipPermission: true
71225
71327
  },
@@ -71246,8 +71348,38 @@ var init_squad2 = __esm({
71246
71348
  description: "Create an objective for a squad and start execution.",
71247
71349
  parameters: delegateToSquadSchema,
71248
71350
  skipPermission: true
71351
+ },
71352
+ {
71353
+ name: "rename_squad",
71354
+ description: "Rename a squad.",
71355
+ parameters: renameSquadSchema,
71356
+ skipPermission: true
71357
+ },
71358
+ {
71359
+ name: "update_squad_member",
71360
+ description: "Update a squad member's role, system prompt, and/or default model. All fields are optional; only provided fields are changed.",
71361
+ parameters: updateSquadMemberSchema,
71362
+ skipPermission: true
71249
71363
  }
71250
71364
  ];
71365
+ MANIFEST_FILES = [
71366
+ "package.json",
71367
+ "Cargo.toml",
71368
+ "go.mod",
71369
+ "requirements.txt",
71370
+ "pyproject.toml",
71371
+ "Gemfile",
71372
+ "pom.xml",
71373
+ "build.gradle",
71374
+ "composer.json",
71375
+ "Makefile",
71376
+ "Dockerfile",
71377
+ "docker-compose.yml",
71378
+ "docker-compose.yaml",
71379
+ ".github/workflows"
71380
+ ];
71381
+ SRC_DIR_NAMES = ["src", "lib", "app", "packages", "crates", "cmd", "internal"];
71382
+ README_CANDIDATES2 = ["README.md", "README.rst", "README.txt", "README"];
71251
71383
  }
71252
71384
  });
71253
71385
 
@@ -71258,6 +71390,30 @@ function buildMemoryPath(timestamp) {
71258
71390
  const time3 = iso.slice(11, 19).replace(/:/gu, "-");
71259
71391
  return `memory/${date5}/${time3}.md`;
71260
71392
  }
71393
+ async function handleWikiWrite(rawArgs) {
71394
+ const { path, title, content, tags } = wikiWriteSchema.parse(rawArgs);
71395
+ const existing = await getPage(path);
71396
+ const page = existing ? await updatePage(path, { title, content, tags: tags ?? [] }) : await createPage(path, title, content, tags ?? []);
71397
+ return {
71398
+ message: `${existing ? "Updated" : "Created"} wiki page ${path}.`,
71399
+ page
71400
+ };
71401
+ }
71402
+ async function handleRecall(rawArgs) {
71403
+ const { query } = recallSchema.parse(rawArgs);
71404
+ const [matches, recents] = await Promise.all([searchPages(query, 1), getRecentPages(1)]);
71405
+ const page = matches[0] ?? recents[0] ?? null;
71406
+ if (!page) {
71407
+ return { message: "No wiki content is available yet." };
71408
+ }
71409
+ return {
71410
+ message: `Best match: ${page.title}`,
71411
+ path: page.path,
71412
+ title: page.title,
71413
+ content: page.content,
71414
+ tags: page.tags
71415
+ };
71416
+ }
71261
71417
  var wikiReadSchema, wikiWriteSchema, wikiSearchSchema, rememberSchema, recallSchema, wikiToolDefinitions, executeWikiToolCall;
71262
71418
  var init_wiki4 = __esm({
71263
71419
  "packages/daemon/src/orchestrator/tools/wiki.ts"() {
@@ -71327,15 +71483,8 @@ var init_wiki4 = __esm({
71327
71483
  }
71328
71484
  return { page };
71329
71485
  }
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
- }
71486
+ case "wiki_write":
71487
+ return handleWikiWrite(rawArgs);
71339
71488
  case "wiki_search": {
71340
71489
  const { query, limit } = wikiSearchSchema.parse(rawArgs);
71341
71490
  const pages = await searchPages(query, limit ?? 5);
@@ -71355,21 +71504,8 @@ var init_wiki4 = __esm({
71355
71504
  page
71356
71505
  };
71357
71506
  }
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
- }
71507
+ case "recall":
71508
+ return handleRecall(rawArgs);
71373
71509
  default:
71374
71510
  throw new Error(`Unsupported wiki tool: ${toolName}`);
71375
71511
  }
@@ -71680,8 +71816,7 @@ var init_orchestrator = __esm({
71680
71816
  }
71681
71817
  const model = usage.model || this.activeModel || this.config.defaultModel;
71682
71818
  const pricing = await getModelPricing(model);
71683
- const premiumRequestCost = pricing?.premiumMultiplier ?? 0;
71684
- const tokenUnitCost = pricing ? calculateTokenUnitCost(
71819
+ const cost = pricing ? calculateTokenUnitCost(
71685
71820
  usage.inputTokens,
71686
71821
  usage.outputTokens,
71687
71822
  pricing.tokenInputMultiplier,
@@ -71691,9 +71826,7 @@ var init_orchestrator = __esm({
71691
71826
  model,
71692
71827
  inputTokens: usage.inputTokens,
71693
71828
  outputTokens: usage.outputTokens,
71694
- cost: tokenUnitCost,
71695
- premiumRequestCost,
71696
- tokenUnitCost
71829
+ cost
71697
71830
  });
71698
71831
  }
71699
71832
  };