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.
@@ -74,23 +74,23 @@ var init_api = __esm({
74
74
  });
75
75
 
76
76
  // packages/shared/dist/constants.js
77
- var APP_NAME, APP_VERSION, API_PORT, API_HOST, DEFAULT_MODEL, SESSION_RESET_THRESHOLD, SCHEDULER_INTERVAL_MS, QA_MAX_REVISIONS, MANDATORY_ROLES, EVENT_NAMES;
77
+ var APP_NAME, APP_VERSION, API_PORT, API_HOST, DEFAULT_MODEL, SESSION_RESET_THRESHOLD, SCHEDULER_INTERVAL_MS, QA_MAX_REVISIONS, EVENT_NAMES;
78
78
  var init_constants = __esm({
79
79
  "packages/shared/dist/constants.js"() {
80
80
  "use strict";
81
81
  APP_NAME = "io";
82
- APP_VERSION = "4.2.2";
82
+ APP_VERSION = "4.2.4";
83
83
  API_PORT = 7777;
84
84
  API_HOST = "0.0.0.0";
85
85
  DEFAULT_MODEL = "gpt-4o";
86
86
  SESSION_RESET_THRESHOLD = 50;
87
87
  SCHEDULER_INTERVAL_MS = 6e4;
88
88
  QA_MAX_REVISIONS = 3;
89
- MANDATORY_ROLES = ["team-lead", "qa"];
90
89
  EVENT_NAMES = {
91
90
  SQUAD_CREATED: "squad.created",
92
91
  SQUAD_DELETED: "squad.deleted",
93
92
  SQUAD_UPDATED: "squad.updated",
93
+ SQUAD_MEMBER_UPDATED: "squad.member_updated",
94
94
  INSTANCE_CREATED: "instance.created",
95
95
  INSTANCE_STARTED: "instance.started",
96
96
  INSTANCE_COMPLETED: "instance.completed",
@@ -77443,6 +77443,10 @@ async function updateMember(memberId, data, db) {
77443
77443
  const database = db ?? await getDatabase();
77444
77444
  const sets = [];
77445
77445
  const args = [];
77446
+ if (data.role !== void 0) {
77447
+ sets.push("role = ?");
77448
+ args.push(data.role);
77449
+ }
77446
77450
  if (data.systemPrompt !== void 0) {
77447
77451
  sets.push("system_prompt = ?");
77448
77452
  args.push(data.systemPrompt);
@@ -77857,8 +77861,8 @@ async function recordUsage(data, db) {
77857
77861
  createdAt: data.createdAt ?? nowIso()
77858
77862
  };
77859
77863
  await database.execute({
77860
- 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)
77861
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
77864
+ sql: `INSERT INTO token_usage (id, squad_id, agent_id, model, input_tokens, output_tokens, cost, squad_name, agent_name, created_at)
77865
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
77862
77866
  args: [
77863
77867
  usage.id,
77864
77868
  usage.squadId,
@@ -77867,8 +77871,6 @@ async function recordUsage(data, db) {
77867
77871
  usage.inputTokens,
77868
77872
  usage.outputTokens,
77869
77873
  usage.cost,
77870
- data.premiumRequestCost ?? null,
77871
- data.tokenUnitCost ?? null,
77872
77874
  data.squadName ?? null,
77873
77875
  data.agentName ?? null,
77874
77876
  usage.createdAt
@@ -79744,11 +79746,16 @@ router7.put("/api/squads/:id/members/:memberId", async (req, res) => {
79744
79746
  res.status(404).json({ error: "Member not found" });
79745
79747
  return;
79746
79748
  }
79747
- const { systemPrompt, model } = req.body;
79749
+ const { role, systemPrompt, model } = req.body;
79748
79750
  const updated = await updateMember(member.id, {
79751
+ role,
79749
79752
  systemPrompt,
79750
79753
  model: model === "" ? null : model
79751
79754
  });
79755
+ eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, {
79756
+ squadId: member.squadId,
79757
+ member: updated
79758
+ });
79752
79759
  res.status(200).json(updated);
79753
79760
  } catch (error51) {
79754
79761
  res.status(500).json({
@@ -81152,10 +81159,19 @@ async function getModelPricing(modelId) {
81152
81159
  sql: "SELECT * FROM model_pricing WHERE id = ?",
81153
81160
  args: [modelId]
81154
81161
  });
81155
- if (result.rows.length === 0) {
81156
- return null;
81162
+ if (result.rows.length > 0) {
81163
+ return rowToModelPricing(result.rows[0]);
81164
+ }
81165
+ const allModels = await db.execute(
81166
+ "SELECT * FROM model_pricing WHERE token_input_multiplier IS NOT NULL ORDER BY length(id) DESC"
81167
+ );
81168
+ for (const row of allModels.rows) {
81169
+ const storedId = asString(row.id);
81170
+ if (modelId.startsWith(storedId) || storedId.startsWith(modelId)) {
81171
+ return rowToModelPricing(row);
81172
+ }
81157
81173
  }
81158
- return rowToModelPricing(result.rows[0]);
81174
+ return null;
81159
81175
  }
81160
81176
  function calculateTokenUnitCost(inputTokens, outputTokens, inputMultiplier, outputMultiplier) {
81161
81177
  if (inputMultiplier === null || outputMultiplier === null) {
@@ -81230,9 +81246,10 @@ function buildSystemPrompt(options2) {
81230
81246
  const sections = [
81231
81247
  "You are Io, a helpful AI orchestrator companion for IO v4.",
81232
81248
  "You coordinate work across squads, wiki knowledge, installed skills, and direct execution tools.",
81233
- "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.",
81249
+ "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.",
81234
81250
  "Be practical, concise, and execution-oriented. Use tools when they help you produce a more accurate or durable result.",
81235
- "## 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.",
81251
+ "## 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.",
81252
+ "## 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.",
81236
81253
  `## Squad Roster
81237
81254
  ${formatSquadRoster(options2.squads)}`,
81238
81255
  formatOptionalSection("## Conversation Summary", options2.conversationSummary ?? ""),
@@ -82232,8 +82249,7 @@ async function persistUsage(member, usageEvents, squadName) {
82232
82249
  for (const usage of usageEvents) {
82233
82250
  const model = usage.model;
82234
82251
  const pricing = await getModelPricing(model);
82235
- const premiumRequestCost = pricing?.premiumMultiplier ?? 0;
82236
- const tokenUnitCost = pricing ? calculateTokenUnitCost(
82252
+ const cost = pricing ? calculateTokenUnitCost(
82237
82253
  usage.inputTokens ?? 0,
82238
82254
  usage.outputTokens ?? 0,
82239
82255
  pricing.tokenInputMultiplier,
@@ -82247,9 +82263,7 @@ async function persistUsage(member, usageEvents, squadName) {
82247
82263
  model,
82248
82264
  inputTokens: usage.inputTokens ?? 0,
82249
82265
  outputTokens: usage.outputTokens ?? 0,
82250
- cost: tokenUnitCost,
82251
- premiumRequestCost,
82252
- tokenUnitCost
82266
+ cost
82253
82267
  });
82254
82268
  }
82255
82269
  }
@@ -82598,43 +82612,6 @@ Review rules:
82598
82612
  - If approving, summarize why the work is acceptable.
82599
82613
  - If rejecting, include concrete fixes or follow-up actions.
82600
82614
  - Do not approve work that lacks evidence for important behavior changes.`;
82601
- var ROLE_GENERATION_PROMPT = `You are staffing an autonomous software squad for a GitHub repository.
82602
-
82603
- Given repository analysis describing languages, frameworks, file structure, architectural patterns, and operational concerns, propose the smallest effective team that can deliver objectives safely.
82604
-
82605
- Requirements:
82606
- - Mandatory roles Team Lead and QA must always exist.
82607
- - Suggest only additional roles that are clearly justified by the repository analysis.
82608
- - Prefer durable role names such as backend-engineer, frontend-engineer, test-automation-engineer, platform-engineer, data-engineer, security-engineer, documentation-engineer, or mobile-engineer.
82609
- - Each role must have a short human-readable name and a concise description of responsibilities.
82610
- - Avoid duplicate or overlapping roles.
82611
- - Optimize for implementation, verification, and maintainability.
82612
-
82613
- Return strict JSON in this shape:
82614
- {
82615
- "roles": [
82616
- {
82617
- "role": "frontend-engineer",
82618
- "name": "Frontend Engineer",
82619
- "description": "Owns UI implementation, client state, and browser-facing tests."
82620
- }
82621
- ]
82622
- }`;
82623
- function generateRolePrompt(role, repoContext) {
82624
- const normalizedRole = role.trim() || "specialist";
82625
- return `You are the ${normalizedRole} for the IO v4 daemon squad system.
82626
-
82627
- ${ROLE_GUIDELINES}
82628
-
82629
- Role expectations:
82630
- - Own work that naturally fits the ${normalizedRole} discipline.
82631
- - Make changes that align with the repository's existing architecture, conventions, and tooling.
82632
- - Collaborate with the Team Lead and QA by leaving behind clear implementation notes and validation evidence.
82633
- - Escalate blockers or missing context early.
82634
-
82635
- Repository context:
82636
- ${repoContext}`;
82637
- }
82638
82615
 
82639
82616
  // packages/daemon/src/execution/planning.ts
82640
82617
  var execAsync4 = promisify4(exec4);
@@ -83374,232 +83351,6 @@ ${reviewOutcome.issues?.join("\n") ?? "Resolve the review feedback and re-valida
83374
83351
  }
83375
83352
  }
83376
83353
 
83377
- // packages/daemon/src/squad/hiring.ts
83378
- init_dist();
83379
- import { CopilotClient as CopilotClient6, approveAll as approveAll6 } from "@github/copilot-sdk";
83380
- function parseRepoUrl(repoUrl) {
83381
- const trimmed = repoUrl.trim();
83382
- const sshMatch = trimmed.match(/^git@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/i);
83383
- if (sshMatch) {
83384
- return { owner: sshMatch[1], name: sshMatch[2] };
83385
- }
83386
- const normalized = trimmed.replace(/\.git$/i, "");
83387
- const url2 = new URL(normalized);
83388
- const segments = url2.pathname.split("/").filter(Boolean);
83389
- if (segments.length < 2) {
83390
- throw new Error(`Unable to parse owner/name from repo URL: ${repoUrl}`);
83391
- }
83392
- return { owner: segments[0], name: segments[1] };
83393
- }
83394
- function slugifyRole(role) {
83395
- return role.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
83396
- }
83397
- function titleCase(value) {
83398
- return value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
83399
- }
83400
- function extractJsonObject4(content) {
83401
- const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
83402
- if (fenced?.[1]) {
83403
- return fenced[1].trim();
83404
- }
83405
- const start = content.indexOf("{");
83406
- const end = content.lastIndexOf("}");
83407
- return start >= 0 && end > start ? content.slice(start, end + 1) : null;
83408
- }
83409
- function detectRolesFromAnalysis(repoAnalysis) {
83410
- const analysis = repoAnalysis.toLowerCase();
83411
- const roles = [];
83412
- if (/(react|next|vue|angular|frontend|ui|xaml)/.test(analysis)) {
83413
- roles.push({
83414
- role: "frontend-engineer",
83415
- name: "Frontend Engineer",
83416
- description: "Implements user-facing experiences, UI state, and presentation-layer changes."
83417
- });
83418
- }
83419
- if (/(node|express|api|backend|server|daemon|database|sql|postgres|sqlite)/.test(analysis)) {
83420
- roles.push({
83421
- role: "backend-engineer",
83422
- name: "Backend Engineer",
83423
- description: "Implements server-side logic, data access, integrations, and execution flow changes."
83424
- });
83425
- }
83426
- if (/(infra|docker|kubernetes|deploy|ci|cd|workflow|github actions)/.test(analysis)) {
83427
- roles.push({
83428
- role: "platform-engineer",
83429
- name: "Platform Engineer",
83430
- description: "Owns automation, pipelines, environments, and operational tooling."
83431
- });
83432
- }
83433
- if (/(security|auth|oauth|secret|policy)/.test(analysis)) {
83434
- roles.push({
83435
- role: "security-engineer",
83436
- name: "Security Engineer",
83437
- description: "Reviews authentication, authorization, secrets handling, and security-sensitive changes."
83438
- });
83439
- }
83440
- return roles;
83441
- }
83442
- function dedupeRoles(roles) {
83443
- const seen = /* @__PURE__ */ new Set();
83444
- const output = [];
83445
- for (const role of roles) {
83446
- const normalizedRole = slugifyRole(role.role);
83447
- if (!normalizedRole || seen.has(normalizedRole) || MANDATORY_ROLES.includes(normalizedRole)) {
83448
- continue;
83449
- }
83450
- seen.add(normalizedRole);
83451
- output.push({
83452
- role: normalizedRole,
83453
- name: role.name?.trim() || titleCase(normalizedRole),
83454
- description: role.description?.trim() || `${titleCase(normalizedRole)} responsibilities`
83455
- });
83456
- }
83457
- return output;
83458
- }
83459
- function buildMandatoryRoles() {
83460
- return [
83461
- {
83462
- role: "team-lead",
83463
- name: "Team Lead",
83464
- description: "Coordinates planning, task assignment, sequencing, and final technical review."
83465
- },
83466
- {
83467
- role: "qa",
83468
- name: "QA Reviewer",
83469
- description: "Validates diffs, test evidence, and objective completion before delivery."
83470
- }
83471
- ];
83472
- }
83473
- function modelForRole(role) {
83474
- const normalized = slugifyRole(role);
83475
- if (normalized === "team-lead") {
83476
- return DEFAULT_MODEL;
83477
- }
83478
- return DEFAULT_MODEL;
83479
- }
83480
- function systemPromptForRole(role, repoContext) {
83481
- const normalized = slugifyRole(role);
83482
- if (normalized === "team-lead") {
83483
- return `${TEAM_LEAD_PROMPT}
83484
-
83485
- Repository context:
83486
- ${repoContext}`;
83487
- }
83488
- if (normalized === "qa") {
83489
- return `${QA_PROMPT}
83490
-
83491
- Repository context:
83492
- ${repoContext}`;
83493
- }
83494
- return generateRolePrompt(normalized, repoContext);
83495
- }
83496
- async function proposeSquadComposition(repoUrl, repoAnalysis) {
83497
- const mandatory = buildMandatoryRoles();
83498
- const fallbackAdditional = dedupeRoles(detectRolesFromAnalysis(repoAnalysis));
83499
- const prompt = `Repository URL: ${repoUrl}
83500
-
83501
- Repository analysis:
83502
- ${repoAnalysis}
83503
-
83504
- ${ROLE_GENERATION_PROMPT}`;
83505
- let client2 = null;
83506
- try {
83507
- client2 = new CopilotClient6();
83508
- await client2.start();
83509
- const session = await client2.createSession({
83510
- model: DEFAULT_MODEL,
83511
- onPermissionRequest: approveAll6,
83512
- systemMessage: {
83513
- content: ROLE_GENERATION_PROMPT
83514
- }
83515
- });
83516
- try {
83517
- const response = await session.sendAndWait({ prompt }, 6e4);
83518
- const content = response?.data.content?.trim() ?? "";
83519
- const json2 = extractJsonObject4(content);
83520
- if (!json2) {
83521
- return { roles: [...mandatory, ...fallbackAdditional] };
83522
- }
83523
- const parsed = JSON.parse(json2);
83524
- return {
83525
- roles: [...mandatory, ...dedupeRoles(parsed.roles ?? fallbackAdditional)]
83526
- };
83527
- } finally {
83528
- await session.disconnect().catch(() => void 0);
83529
- }
83530
- } catch {
83531
- return { roles: [...mandatory, ...fallbackAdditional] };
83532
- } finally {
83533
- if (client2) {
83534
- await client2.stop().catch(() => []);
83535
- }
83536
- }
83537
- }
83538
- async function hireSquad(repoUrl, composition, config2) {
83539
- const parsedRepo = parseRepoUrl(repoUrl);
83540
- const repoContext = `Repository: ${parsedRepo.owner}/${parsedRepo.name}
83541
- Configured MCP servers: ${config2.mcpServers.join(", ") || "none"}
83542
- QA max revisions: ${config2.maxRevisions || QA_MAX_REVISIONS}`;
83543
- const existingSquad = await getSquadByRepo(parsedRepo.owner, parsedRepo.name);
83544
- const normalizedConfig = {
83545
- prMode: config2.prMode,
83546
- mcpServers: [...config2.mcpServers],
83547
- maxRevisions: config2.maxRevisions || QA_MAX_REVISIONS
83548
- };
83549
- const desiredRoles = composition.roles.length > 0 ? composition.roles : buildMandatoryRoles();
83550
- const membersToCreate = desiredRoles.map((role) => ({
83551
- role: slugifyRole(role.role),
83552
- name: role.name?.trim() || titleCase(role.role),
83553
- description: role.description?.trim() || `${titleCase(role.role)} responsibilities`
83554
- }));
83555
- let squadId;
83556
- if (existingSquad) {
83557
- const updatedSquad = await updateSquad(existingSquad.id, {
83558
- name: `${titleCase(parsedRepo.name)} Squad`,
83559
- config: normalizedConfig,
83560
- status: existingSquad.status
83561
- });
83562
- if (!updatedSquad) {
83563
- throw new Error(`Unable to update existing squad for ${repoUrl}`);
83564
- }
83565
- squadId = updatedSquad.id;
83566
- const existingMembers = await getMembers(updatedSquad.id);
83567
- await Promise.all(existingMembers.map((member) => removeMember(member.id)));
83568
- } else {
83569
- const squad = await createSquad({
83570
- name: `${titleCase(parsedRepo.name)} Squad`,
83571
- repoUrl,
83572
- repoOwner: parsedRepo.owner,
83573
- repoName: parsedRepo.name,
83574
- status: "active",
83575
- config: normalizedConfig
83576
- });
83577
- squadId = squad.id;
83578
- }
83579
- const members = [];
83580
- for (const memberDefinition of membersToCreate) {
83581
- const member = await addMember(squadId, {
83582
- role: memberDefinition.role,
83583
- name: memberDefinition.name,
83584
- systemPrompt: systemPromptForRole(
83585
- memberDefinition.role,
83586
- `${repoContext}
83587
- Role: ${memberDefinition.description}`
83588
- ),
83589
- model: modelForRole(memberDefinition.role)
83590
- });
83591
- members.push(member);
83592
- }
83593
- const fullSquad = await getSquad(squadId);
83594
- if (!fullSquad) {
83595
- throw new Error(`Unable to load squad ${squadId} after hiring`);
83596
- }
83597
- eventBus.emit(existingSquad ? EVENT_NAMES.SQUAD_UPDATED : EVENT_NAMES.SQUAD_CREATED, {
83598
- squad: fullSquad
83599
- });
83600
- return { squad: fullSquad, members: fullSquad.members };
83601
- }
83602
-
83603
83354
  // packages/daemon/src/orchestrator/tools/squad.ts
83604
83355
  var execAsync8 = promisify8(exec8);
83605
83356
  async function pathExists2(path) {
@@ -83610,8 +83361,39 @@ async function pathExists2(path) {
83610
83361
  return false;
83611
83362
  }
83612
83363
  }
83613
- var hireSquadSchema = external_exports.object({
83614
- repoUrl: external_exports.string().trim().min(1)
83364
+ var createSquadSchema = external_exports.object({
83365
+ repoUrl: external_exports.string().trim().min(1).describe("The repository URL to create a squad for"),
83366
+ name: external_exports.string().trim().optional().describe("Squad name. Defaults to '<RepoName> Squad' if not provided."),
83367
+ config: external_exports.object({
83368
+ prMode: external_exports.enum(["draft-pr", "branch-only", "ready-pr", "auto-merge"]).optional().describe("PR creation mode. Defaults to 'draft-pr'."),
83369
+ mcpServers: external_exports.array(external_exports.string()).optional().describe("MCP server URLs to configure for the squad."),
83370
+ maxRevisions: external_exports.number().optional().describe("Max QA revision cycles. Defaults to system default.")
83371
+ }).optional().describe("Squad configuration. All fields are optional with sensible defaults.")
83372
+ });
83373
+ var addSquadMemberSchema = external_exports.object({
83374
+ squadId: external_exports.string().trim().min(1),
83375
+ role: external_exports.string().trim().min(1).describe("Slug-style role identifier (e.g. 'senior-frontend-engineer', 'team-lead')"),
83376
+ name: external_exports.string().trim().min(1).describe("Display name for the member (e.g. 'Senior Frontend Engineer')"),
83377
+ systemPrompt: external_exports.string().min(1).describe("The system prompt defining this member's expertise, responsibilities, and behavior."),
83378
+ model: external_exports.string().optional().describe("Model override for this member. Uses squad default if not provided.")
83379
+ });
83380
+ var removeSquadMemberSchema = external_exports.object({
83381
+ squadId: external_exports.string().trim().min(1),
83382
+ memberId: external_exports.string().trim().min(1)
83383
+ });
83384
+ var updateSquadConfigSchema = external_exports.object({
83385
+ squadId: external_exports.string().trim().min(1),
83386
+ config: external_exports.object({
83387
+ prMode: external_exports.enum(["draft-pr", "branch-only", "ready-pr", "auto-merge"]).optional(),
83388
+ mcpServers: external_exports.array(external_exports.string()).optional(),
83389
+ maxRevisions: external_exports.number().optional()
83390
+ }).describe("Partial config fields to update. Only provided fields are changed.")
83391
+ });
83392
+ var analyzeRepoSchema = external_exports.object({
83393
+ repoUrl: external_exports.string().trim().min(1).describe("The repository URL to analyze"),
83394
+ scanPaths: external_exports.array(external_exports.string()).optional().describe(
83395
+ "Relative paths within the repository to focus the analysis on. When provided, only these directories are scanned instead of the entire repo."
83396
+ )
83615
83397
  });
83616
83398
  var squadIdSchema = external_exports.object({
83617
83399
  squadId: external_exports.string().trim().min(1)
@@ -83620,11 +83402,46 @@ var delegateToSquadSchema = external_exports.object({
83620
83402
  squadId: external_exports.string().trim().min(1),
83621
83403
  objective: external_exports.string().trim().min(1)
83622
83404
  });
83405
+ var renameSquadSchema = external_exports.object({
83406
+ squadId: external_exports.string().trim().min(1),
83407
+ name: external_exports.string().trim().min(1)
83408
+ });
83409
+ var updateSquadMemberSchema = external_exports.object({
83410
+ squadId: external_exports.string().trim().min(1),
83411
+ memberId: external_exports.string().trim().min(1),
83412
+ role: external_exports.string().trim().min(1).optional(),
83413
+ systemPrompt: external_exports.string().optional(),
83414
+ model: external_exports.string().optional()
83415
+ });
83623
83416
  var squadToolDefinitions = [
83624
83417
  {
83625
- name: "hire_squad",
83626
- description: "Hire or refresh a squad for a repository.",
83627
- parameters: hireSquadSchema,
83418
+ name: "create_squad",
83419
+ 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.",
83420
+ parameters: createSquadSchema,
83421
+ skipPermission: true
83422
+ },
83423
+ {
83424
+ name: "add_squad_member",
83425
+ 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.",
83426
+ parameters: addSquadMemberSchema,
83427
+ skipPermission: true
83428
+ },
83429
+ {
83430
+ name: "remove_squad_member",
83431
+ description: "Remove a member from a squad by their ID.",
83432
+ parameters: removeSquadMemberSchema,
83433
+ skipPermission: true
83434
+ },
83435
+ {
83436
+ name: "update_squad_config",
83437
+ description: "Update a squad's configuration (prMode, mcpServers, maxRevisions).",
83438
+ parameters: updateSquadConfigSchema,
83439
+ skipPermission: true
83440
+ },
83441
+ {
83442
+ name: "analyze_repo",
83443
+ 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.",
83444
+ parameters: analyzeRepoSchema,
83628
83445
  skipPermission: true
83629
83446
  },
83630
83447
  {
@@ -83650,28 +83467,121 @@ var squadToolDefinitions = [
83650
83467
  description: "Create an objective for a squad and start execution.",
83651
83468
  parameters: delegateToSquadSchema,
83652
83469
  skipPermission: true
83470
+ },
83471
+ {
83472
+ name: "rename_squad",
83473
+ description: "Rename a squad.",
83474
+ parameters: renameSquadSchema,
83475
+ skipPermission: true
83476
+ },
83477
+ {
83478
+ name: "update_squad_member",
83479
+ description: "Update a squad member's role, system prompt, and/or default model. All fields are optional; only provided fields are changed.",
83480
+ parameters: updateSquadMemberSchema,
83481
+ skipPermission: true
83653
83482
  }
83654
83483
  ];
83655
- function getDefaultSquadConfig(_config) {
83656
- return {
83657
- prMode: "draft-pr",
83658
- mcpServers: [],
83659
- maxRevisions: QA_MAX_REVISIONS
83660
- };
83484
+ function parseRepoUrl(repoUrl) {
83485
+ const trimmed = repoUrl.trim();
83486
+ const sshMatch = trimmed.match(/^git@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/i);
83487
+ if (sshMatch) {
83488
+ return { owner: sshMatch[1], name: sshMatch[2] };
83489
+ }
83490
+ const normalized = trimmed.replace(/\.git$/i, "");
83491
+ const url2 = new URL(normalized);
83492
+ const segments = url2.pathname.split("/").filter(Boolean);
83493
+ if (segments.length < 2) {
83494
+ throw new Error(`Unable to parse owner/name from repo URL: ${repoUrl}`);
83495
+ }
83496
+ return { owner: segments[0], name: segments[1] };
83661
83497
  }
83662
- async function buildRepoAnalysis(repoUrl) {
83663
- const normalized = repoUrl.trim();
83664
- const segments = normalized.replace(/\.git$/i, "").split("/").filter(Boolean);
83665
- const owner = segments.at(-2) ?? "";
83666
- const name = segments.at(-1) ?? normalized;
83667
- const lines = [`Repository URL: ${normalized}`, `Repository name: ${name}`];
83668
- if (!owner || !name) {
83669
- lines.push(
83670
- "Use the repository identity and any available conventions to propose a practical squad composition."
83671
- );
83672
- return lines.join("\n");
83498
+ function titleCase(value) {
83499
+ return value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
83500
+ }
83501
+ var MANIFEST_FILES = [
83502
+ "package.json",
83503
+ "Cargo.toml",
83504
+ "go.mod",
83505
+ "requirements.txt",
83506
+ "pyproject.toml",
83507
+ "Gemfile",
83508
+ "pom.xml",
83509
+ "build.gradle",
83510
+ "composer.json",
83511
+ "Makefile",
83512
+ "Dockerfile",
83513
+ "docker-compose.yml",
83514
+ "docker-compose.yaml",
83515
+ ".github/workflows"
83516
+ ];
83517
+ var SRC_DIR_NAMES = ["src", "lib", "app", "packages", "crates", "cmd", "internal"];
83518
+ var README_CANDIDATES2 = ["README.md", "README.rst", "README.txt", "README"];
83519
+ async function readManifests(rootPath, rootLabel) {
83520
+ const lines = [];
83521
+ for (const manifest of MANIFEST_FILES) {
83522
+ const fullPath = join15(rootPath, manifest);
83523
+ try {
83524
+ const fileStat = await stat4(fullPath);
83525
+ if (fileStat.isFile()) {
83526
+ const content = await readFile9(fullPath, "utf8");
83527
+ lines.push(`
83528
+ --- ${rootLabel}${manifest} ---
83529
+ ${content.slice(0, 3e3)}`);
83530
+ } else if (fileStat.isDirectory()) {
83531
+ const children = await readdir6(fullPath);
83532
+ lines.push(`
83533
+ --- ${rootLabel}${manifest}/ ---
83534
+ ${children.join(", ")}`);
83535
+ }
83536
+ } catch {
83537
+ }
83673
83538
  }
83674
- const repoDir = join15(DATA_DIR, "repos", `${owner}--${name}`);
83539
+ return lines;
83540
+ }
83541
+ async function scanSrcDirs(rootPath, rootLabel, dirs) {
83542
+ const lines = [];
83543
+ const srcDirs = dirs.filter((d) => SRC_DIR_NAMES.includes(d));
83544
+ for (const srcDir of srcDirs) {
83545
+ try {
83546
+ const srcEntries = await readdir6(join15(rootPath, srcDir), { withFileTypes: true });
83547
+ const srcFiles = srcEntries.filter((e) => e.isFile()).map((e) => e.name);
83548
+ const srcSubDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
83549
+ lines.push(`
83550
+ --- ${rootLabel}${srcDir}/ ---`);
83551
+ if (srcSubDirs.length) lines.push(` Directories: ${srcSubDirs.join(", ")}`);
83552
+ if (srcFiles.length) lines.push(` Files: ${srcFiles.slice(0, 30).join(", ")}`);
83553
+ } catch {
83554
+ }
83555
+ }
83556
+ return lines;
83557
+ }
83558
+ async function findReadme(rootPath, rootLabel) {
83559
+ for (const readme of README_CANDIDATES2) {
83560
+ try {
83561
+ const content = await readFile9(join15(rootPath, readme), "utf8");
83562
+ return `
83563
+ --- ${rootLabel}${readme} (excerpt) ---
83564
+ ${content.slice(0, 1500)}`;
83565
+ } catch {
83566
+ }
83567
+ }
83568
+ return null;
83569
+ }
83570
+ async function scanRoot(root) {
83571
+ const lines = [];
83572
+ const rootLabel = root.label ? `${root.label}/` : "";
83573
+ const rootEntries = await readdir6(root.path, { withFileTypes: true });
83574
+ const rootFiles = rootEntries.filter((e) => e.isFile()).map((e) => e.name);
83575
+ const rootDirs = rootEntries.filter((e) => e.isDirectory() && e.name !== ".git").map((e) => e.name);
83576
+ if (rootFiles.length) lines.push(`${rootLabel}Files: ${rootFiles.join(", ")}`);
83577
+ if (rootDirs.length) lines.push(`${rootLabel}Directories: ${rootDirs.join(", ")}`);
83578
+ lines.push(...await readManifests(root.path, rootLabel));
83579
+ lines.push(...await scanSrcDirs(root.path, rootLabel, rootDirs));
83580
+ const readme = await findReadme(root.path, rootLabel);
83581
+ if (readme) lines.push(readme);
83582
+ return lines;
83583
+ }
83584
+ async function cloneOrUpdateRepo(normalized, repoDir) {
83675
83585
  try {
83676
83586
  await mkdir10(join15(DATA_DIR, "repos"), { recursive: true });
83677
83587
  if (await pathExists2(join15(repoDir, ".git"))) {
@@ -83683,79 +83593,40 @@ async function buildRepoAnalysis(repoUrl) {
83683
83593
  timeout: 6e4
83684
83594
  });
83685
83595
  }
83596
+ return true;
83686
83597
  } catch {
83598
+ return false;
83599
+ }
83600
+ }
83601
+ async function buildRepoAnalysis(repoUrl, scanPaths) {
83602
+ const normalized = repoUrl.trim();
83603
+ const segments = normalized.replace(/\.git$/i, "").split("/").filter(Boolean);
83604
+ const owner = segments.at(-2) ?? "";
83605
+ const name = segments.at(-1) ?? normalized;
83606
+ const lines = [`Repository URL: ${normalized}`, `Repository name: ${name}`];
83607
+ if (!owner || !name) {
83608
+ lines.push(
83609
+ "Use the repository identity and any available conventions to propose a practical squad composition."
83610
+ );
83611
+ return lines.join("\n");
83612
+ }
83613
+ const repoDir = join15(DATA_DIR, "repos", `${owner}--${name}`);
83614
+ const cloned = await cloneOrUpdateRepo(normalized, repoDir);
83615
+ if (!cloned) {
83687
83616
  lines.push("Unable to clone repository locally; falling back to basic analysis.");
83688
83617
  lines.push("Based on the repository name, propose roles that match common project patterns.");
83689
83618
  return lines.join("\n");
83690
83619
  }
83691
- try {
83692
- const rootEntries = await readdir6(repoDir, { withFileTypes: true });
83693
- const rootFiles = rootEntries.filter((e) => e.isFile()).map((e) => e.name);
83694
- const rootDirs = rootEntries.filter((e) => e.isDirectory() && e.name !== ".git").map((e) => e.name);
83695
- if (rootFiles.length) lines.push(`Root files: ${rootFiles.join(", ")}`);
83696
- if (rootDirs.length) lines.push(`Root directories: ${rootDirs.join(", ")}`);
83697
- const manifestFiles = [
83698
- "package.json",
83699
- "Cargo.toml",
83700
- "go.mod",
83701
- "requirements.txt",
83702
- "pyproject.toml",
83703
- "Gemfile",
83704
- "pom.xml",
83705
- "build.gradle",
83706
- "composer.json",
83707
- "Makefile",
83708
- "Dockerfile",
83709
- "docker-compose.yml",
83710
- "docker-compose.yaml",
83711
- ".github/workflows"
83712
- ];
83713
- for (const manifest of manifestFiles) {
83714
- const fullPath = join15(repoDir, manifest);
83715
- try {
83716
- const fileStat = await stat4(fullPath);
83717
- if (fileStat.isFile()) {
83718
- const content = await readFile9(fullPath, "utf8");
83719
- lines.push(`
83720
- --- ${manifest} ---
83721
- ${content.slice(0, 3e3)}`);
83722
- } else if (fileStat.isDirectory()) {
83723
- const children = await readdir6(fullPath);
83724
- lines.push(`
83725
- --- ${manifest}/ ---
83726
- ${children.join(", ")}`);
83727
- }
83728
- } catch {
83729
- }
83730
- }
83731
- const srcDirs = rootDirs.filter(
83732
- (d) => ["src", "lib", "app", "packages", "crates", "cmd", "internal"].includes(d)
83733
- );
83734
- for (const srcDir of srcDirs) {
83735
- try {
83736
- const srcEntries = await readdir6(join15(repoDir, srcDir), { withFileTypes: true });
83737
- const srcFiles = srcEntries.filter((e) => e.isFile()).map((e) => e.name);
83738
- const srcSubDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
83739
- lines.push(`
83740
- --- ${srcDir}/ ---`);
83741
- if (srcSubDirs.length) lines.push(` Directories: ${srcSubDirs.join(", ")}`);
83742
- if (srcFiles.length) lines.push(` Files: ${srcFiles.slice(0, 30).join(", ")}`);
83743
- } catch {
83744
- }
83745
- }
83746
- const readmeCandidates = ["README.md", "README.rst", "README.txt", "README"];
83747
- for (const readme of readmeCandidates) {
83748
- try {
83749
- const content = await readFile9(join15(repoDir, readme), "utf8");
83750
- lines.push(`
83751
- --- ${readme} (excerpt) ---
83752
- ${content.slice(0, 1500)}`);
83753
- break;
83754
- } catch {
83755
- }
83620
+ const scanRoots = scanPaths && scanPaths.length > 0 ? scanPaths.map((p) => ({ path: join15(repoDir, p), label: p })) : [{ path: repoDir, label: "" }];
83621
+ if (scanPaths && scanPaths.length > 0) {
83622
+ lines.push(`Focused scan paths: ${scanPaths.join(", ")}`);
83623
+ }
83624
+ for (const root of scanRoots) {
83625
+ try {
83626
+ lines.push(...await scanRoot(root));
83627
+ } catch {
83628
+ lines.push(`Filesystem scan failed for ${root.label || "repo root"}; skipping.`);
83756
83629
  }
83757
- } catch {
83758
- lines.push("Filesystem scan failed; using minimal info.");
83759
83630
  }
83760
83631
  lines.push(
83761
83632
  "\nBased on the above repository structure and contents, propose roles that match the project's actual technology stack and architecture."
@@ -83770,6 +83641,22 @@ function formatSquadList(squads) {
83770
83641
  (squad) => `${squad.id}: ${squad.name} (${squad.repoOwner}/${squad.repoName}) [${squad.status}]`
83771
83642
  ).join("\n");
83772
83643
  }
83644
+ async function processQueue2(squadId) {
83645
+ try {
83646
+ const running = await countRunningInstances(squadId);
83647
+ const config2 = (await Promise.resolve().then(() => (init_config(), config_exports))).loadConfig();
83648
+ if (running < config2.maxInstancesPerSquad) {
83649
+ const next = await getNextQueued(squadId);
83650
+ if (next) {
83651
+ const freshSquad = await getSquad(squadId);
83652
+ if (freshSquad) {
83653
+ void startAndExecuteInstance(next.id, freshSquad, next.objectiveId);
83654
+ }
83655
+ }
83656
+ }
83657
+ } catch {
83658
+ }
83659
+ }
83773
83660
  async function startAndExecuteInstance(instanceId, squad, objectiveId) {
83774
83661
  try {
83775
83662
  const repoPath = await resolveRepoPath(squad.repoUrl, squad.repoName);
@@ -83790,56 +83677,184 @@ async function startAndExecuteInstance(instanceId, squad, objectiveId) {
83790
83677
  await failInstance(instanceId, message2).catch(() => {
83791
83678
  });
83792
83679
  } finally {
83793
- try {
83794
- const running = await countRunningInstances(squad.id);
83795
- const config2 = (await Promise.resolve().then(() => (init_config(), config_exports))).loadConfig();
83796
- if (running < config2.maxInstancesPerSquad) {
83797
- const next = await getNextQueued(squad.id);
83798
- if (next) {
83799
- const freshSquad = await getSquad(squad.id);
83800
- if (freshSquad) {
83801
- void startAndExecuteInstance(next.id, freshSquad, next.objectiveId);
83802
- }
83803
- }
83804
- }
83805
- } catch {
83806
- }
83680
+ await processQueue2(squad.id);
83681
+ }
83682
+ }
83683
+ async function handleCreateSquad(rawArgs) {
83684
+ const { repoUrl, name, config: config2 } = createSquadSchema.parse(rawArgs);
83685
+ const parsedRepo = parseRepoUrl(repoUrl);
83686
+ const existingSquad = await getSquadByRepo(parsedRepo.owner, parsedRepo.name);
83687
+ if (existingSquad) {
83688
+ return {
83689
+ message: `A squad already exists for ${parsedRepo.owner}/${parsedRepo.name}.`,
83690
+ squad: existingSquad
83691
+ };
83692
+ }
83693
+ const squadName = name || `${titleCase(parsedRepo.name)} Squad`;
83694
+ const squadConfig = {
83695
+ prMode: config2?.prMode ?? "draft-pr",
83696
+ mcpServers: config2?.mcpServers ?? [],
83697
+ maxRevisions: config2?.maxRevisions ?? QA_MAX_REVISIONS
83698
+ };
83699
+ const squad = await createSquad({
83700
+ name: squadName,
83701
+ repoUrl,
83702
+ repoOwner: parsedRepo.owner,
83703
+ repoName: parsedRepo.name,
83704
+ status: "active",
83705
+ config: squadConfig
83706
+ });
83707
+ eventBus.emit(EVENT_NAMES.SQUAD_CREATED, { squad });
83708
+ return {
83709
+ message: `Squad "${squad.name}" created for ${parsedRepo.owner}/${parsedRepo.name}. Add members with add_squad_member.`,
83710
+ squad
83711
+ };
83712
+ }
83713
+ async function handleAddSquadMember(rawArgs) {
83714
+ const { squadId, role, name, systemPrompt, model } = addSquadMemberSchema.parse(rawArgs);
83715
+ const squad = await getSquad(squadId);
83716
+ if (!squad) {
83717
+ throw new Error(`Squad ${squadId} was not found.`);
83718
+ }
83719
+ const member = await addMember(squadId, {
83720
+ role,
83721
+ name,
83722
+ systemPrompt,
83723
+ model: model ?? null
83724
+ });
83725
+ eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, { squadId, member });
83726
+ return {
83727
+ message: `Added member "${member.name}" (${member.role}) to squad "${squad.name}".`,
83728
+ member
83729
+ };
83730
+ }
83731
+ async function handleRemoveSquadMember(rawArgs) {
83732
+ const { squadId, memberId } = removeSquadMemberSchema.parse(rawArgs);
83733
+ const squad = await getSquad(squadId);
83734
+ if (!squad) {
83735
+ throw new Error(`Squad ${squadId} was not found.`);
83736
+ }
83737
+ const member = await getMember(memberId);
83738
+ if (!member || member.squadId !== squadId) {
83739
+ throw new Error(`Member ${memberId} was not found in squad ${squadId}.`);
83740
+ }
83741
+ await removeMember(memberId);
83742
+ eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad });
83743
+ return {
83744
+ message: `Removed member "${member.name}" (${member.role}) from squad "${squad.name}".`,
83745
+ memberId
83746
+ };
83747
+ }
83748
+ async function handleUpdateSquadConfig(rawArgs) {
83749
+ const { squadId, config: config2 } = updateSquadConfigSchema.parse(rawArgs);
83750
+ const squad = await getSquad(squadId);
83751
+ if (!squad) {
83752
+ throw new Error(`Squad ${squadId} was not found.`);
83753
+ }
83754
+ const mergedConfig = {
83755
+ prMode: config2.prMode ?? squad.config.prMode,
83756
+ mcpServers: config2.mcpServers ?? squad.config.mcpServers,
83757
+ maxRevisions: config2.maxRevisions ?? squad.config.maxRevisions
83758
+ };
83759
+ const updated = await updateSquad(squadId, { config: mergedConfig });
83760
+ if (!updated) {
83761
+ throw new Error(`Failed to update squad ${squadId}.`);
83762
+ }
83763
+ eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad: updated });
83764
+ return {
83765
+ message: `Updated config for squad "${updated.name}".`,
83766
+ squad: updated
83767
+ };
83768
+ }
83769
+ async function handleAnalyzeRepo(rawArgs) {
83770
+ const { repoUrl, scanPaths } = analyzeRepoSchema.parse(rawArgs);
83771
+ const analysis = await buildRepoAnalysis(repoUrl, scanPaths);
83772
+ return {
83773
+ message: "Repository analysis complete.",
83774
+ analysis
83775
+ };
83776
+ }
83777
+ async function handleFireSquad(rawArgs) {
83778
+ const { squadId } = squadIdSchema.parse(rawArgs);
83779
+ const squad = await getSquad(squadId);
83780
+ if (!squad) {
83781
+ throw new Error(`Squad ${squadId} was not found.`);
83782
+ }
83783
+ const activeObjectives = await getActiveObjectives(squadId);
83784
+ if (activeObjectives.length > 0) {
83785
+ const updated = await updateSquad(squadId, { status: "inactive" });
83786
+ return {
83787
+ message: `Squad ${squadId} was deactivated because it still has active objectives.`,
83788
+ squad: updated,
83789
+ activeObjectives
83790
+ };
83791
+ }
83792
+ await deleteSquad(squadId);
83793
+ return { message: `Squad ${squadId} was deleted.`, squadId };
83794
+ }
83795
+ async function handleDelegateToSquad(rawArgs) {
83796
+ const { squadId, objective } = delegateToSquadSchema.parse(rawArgs);
83797
+ const squad = await getSquad(squadId);
83798
+ if (!squad) {
83799
+ throw new Error(`Squad ${squadId} was not found.`);
83800
+ }
83801
+ const createdObjective = await createObjective(squadId, objective);
83802
+ const { instance, queued } = await spawnInstance({
83803
+ squadId,
83804
+ objectiveId: createdObjective.id
83805
+ });
83806
+ if (!queued) {
83807
+ void startAndExecuteInstance(instance.id, squad, createdObjective.id);
83808
+ }
83809
+ return {
83810
+ 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.`,
83811
+ objective: createdObjective,
83812
+ instanceId: instance.id,
83813
+ queued
83814
+ };
83815
+ }
83816
+ async function handleUpdateSquadMember(rawArgs) {
83817
+ const { squadId, memberId, role, systemPrompt, model } = updateSquadMemberSchema.parse(rawArgs);
83818
+ const squad = await getSquad(squadId);
83819
+ if (!squad) {
83820
+ throw new Error(`Squad ${squadId} was not found.`);
83821
+ }
83822
+ const member = await getMember(memberId);
83823
+ if (!member || member.squadId !== squadId) {
83824
+ throw new Error(`Member ${memberId} was not found in squad ${squadId}.`);
83825
+ }
83826
+ const updated = await updateMember(memberId, {
83827
+ role,
83828
+ systemPrompt,
83829
+ model: model === "" ? null : model
83830
+ });
83831
+ if (!updated) {
83832
+ throw new Error(`Failed to update member ${memberId}.`);
83807
83833
  }
83834
+ eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, {
83835
+ squadId,
83836
+ member: updated
83837
+ });
83838
+ return {
83839
+ message: `Updated member "${updated.name}" in squad "${squad.name}".`,
83840
+ member: updated
83841
+ };
83808
83842
  }
83809
83843
  function createSquadToolExecutor(config2) {
83810
83844
  return async (toolName, rawArgs) => {
83811
83845
  switch (toolName) {
83812
- case "hire_squad": {
83813
- const { repoUrl } = hireSquadSchema.parse(rawArgs);
83814
- const composition = await proposeSquadComposition(
83815
- repoUrl,
83816
- await buildRepoAnalysis(repoUrl)
83817
- );
83818
- const result = await hireSquad(repoUrl, composition, getDefaultSquadConfig(config2));
83819
- return {
83820
- message: `Squad ready for ${repoUrl}.`,
83821
- squad: result.squad,
83822
- members: result.members
83823
- };
83824
- }
83825
- case "fire_squad": {
83826
- const { squadId } = squadIdSchema.parse(rawArgs);
83827
- const squad = await getSquad(squadId);
83828
- if (!squad) {
83829
- throw new Error(`Squad ${squadId} was not found.`);
83830
- }
83831
- const activeObjectives = await getActiveObjectives(squadId);
83832
- if (activeObjectives.length > 0) {
83833
- const updated = await updateSquad(squadId, { status: "inactive" });
83834
- return {
83835
- message: `Squad ${squadId} was deactivated because it still has active objectives.`,
83836
- squad: updated,
83837
- activeObjectives
83838
- };
83839
- }
83840
- await deleteSquad(squadId);
83841
- return { message: `Squad ${squadId} was deleted.`, squadId };
83842
- }
83846
+ case "create_squad":
83847
+ return handleCreateSquad(rawArgs);
83848
+ case "add_squad_member":
83849
+ return handleAddSquadMember(rawArgs);
83850
+ case "remove_squad_member":
83851
+ return handleRemoveSquadMember(rawArgs);
83852
+ case "update_squad_config":
83853
+ return handleUpdateSquadConfig(rawArgs);
83854
+ case "analyze_repo":
83855
+ return handleAnalyzeRepo(rawArgs);
83856
+ case "fire_squad":
83857
+ return handleFireSquad(rawArgs);
83843
83858
  case "list_squads": {
83844
83859
  const squads = await listSquads();
83845
83860
  return {
@@ -83855,27 +83870,22 @@ function createSquadToolExecutor(config2) {
83855
83870
  ...status
83856
83871
  };
83857
83872
  }
83858
- case "delegate_to_squad": {
83859
- const { squadId, objective } = delegateToSquadSchema.parse(rawArgs);
83860
- const squad = await getSquad(squadId);
83861
- if (!squad) {
83873
+ case "delegate_to_squad":
83874
+ return handleDelegateToSquad(rawArgs);
83875
+ case "rename_squad": {
83876
+ const { squadId, name } = renameSquadSchema.parse(rawArgs);
83877
+ const updated = await updateSquad(squadId, { name });
83878
+ if (!updated) {
83862
83879
  throw new Error(`Squad ${squadId} was not found.`);
83863
83880
  }
83864
- const createdObjective = await createObjective(squadId, objective);
83865
- const { instance, queued } = await spawnInstance({
83866
- squadId,
83867
- objectiveId: createdObjective.id
83868
- });
83869
- if (!queued) {
83870
- void startAndExecuteInstance(instance.id, squad, createdObjective.id);
83871
- }
83881
+ eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad: updated });
83872
83882
  return {
83873
- 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.`,
83874
- objective: createdObjective,
83875
- instanceId: instance.id,
83876
- queued
83883
+ message: `Squad renamed to "${updated.name}".`,
83884
+ squad: updated
83877
83885
  };
83878
83886
  }
83887
+ case "update_squad_member":
83888
+ return handleUpdateSquadMember(rawArgs);
83879
83889
  default:
83880
83890
  throw new Error(`Unsupported squad tool: ${toolName}`);
83881
83891
  }
@@ -83942,6 +83952,30 @@ function buildMemoryPath(timestamp) {
83942
83952
  const time3 = iso.slice(11, 19).replace(/:/gu, "-");
83943
83953
  return `memory/${date5}/${time3}.md`;
83944
83954
  }
83955
+ async function handleWikiWrite(rawArgs) {
83956
+ const { path, title, content, tags } = wikiWriteSchema.parse(rawArgs);
83957
+ const existing = await getPage(path);
83958
+ const page = existing ? await updatePage(path, { title, content, tags: tags ?? [] }) : await createPage(path, title, content, tags ?? []);
83959
+ return {
83960
+ message: `${existing ? "Updated" : "Created"} wiki page ${path}.`,
83961
+ page
83962
+ };
83963
+ }
83964
+ async function handleRecall(rawArgs) {
83965
+ const { query } = recallSchema.parse(rawArgs);
83966
+ const [matches, recents] = await Promise.all([searchPages(query, 1), getRecentPages(1)]);
83967
+ const page = matches[0] ?? recents[0] ?? null;
83968
+ if (!page) {
83969
+ return { message: "No wiki content is available yet." };
83970
+ }
83971
+ return {
83972
+ message: `Best match: ${page.title}`,
83973
+ path: page.path,
83974
+ title: page.title,
83975
+ content: page.content,
83976
+ tags: page.tags
83977
+ };
83978
+ }
83945
83979
  var executeWikiToolCall = async (toolName, rawArgs) => {
83946
83980
  switch (toolName) {
83947
83981
  case "wiki_read": {
@@ -83952,15 +83986,8 @@ var executeWikiToolCall = async (toolName, rawArgs) => {
83952
83986
  }
83953
83987
  return { page };
83954
83988
  }
83955
- case "wiki_write": {
83956
- const { path, title, content, tags } = wikiWriteSchema.parse(rawArgs);
83957
- const existing = await getPage(path);
83958
- const page = existing ? await updatePage(path, { title, content, tags: tags ?? [] }) : await createPage(path, title, content, tags ?? []);
83959
- return {
83960
- message: `${existing ? "Updated" : "Created"} wiki page ${path}.`,
83961
- page
83962
- };
83963
- }
83989
+ case "wiki_write":
83990
+ return handleWikiWrite(rawArgs);
83964
83991
  case "wiki_search": {
83965
83992
  const { query, limit } = wikiSearchSchema.parse(rawArgs);
83966
83993
  const pages = await searchPages(query, limit ?? 5);
@@ -83980,21 +84007,8 @@ var executeWikiToolCall = async (toolName, rawArgs) => {
83980
84007
  page
83981
84008
  };
83982
84009
  }
83983
- case "recall": {
83984
- const { query } = recallSchema.parse(rawArgs);
83985
- const [matches, recents] = await Promise.all([searchPages(query, 1), getRecentPages(1)]);
83986
- const page = matches[0] ?? recents[0] ?? null;
83987
- if (!page) {
83988
- return { message: "No wiki content is available yet." };
83989
- }
83990
- return {
83991
- message: `Best match: ${page.title}`,
83992
- path: page.path,
83993
- title: page.title,
83994
- content: page.content,
83995
- tags: page.tags
83996
- };
83997
- }
84010
+ case "recall":
84011
+ return handleRecall(rawArgs);
83998
84012
  default:
83999
84013
  throw new Error(`Unsupported wiki tool: ${toolName}`);
84000
84014
  }
@@ -84273,8 +84287,7 @@ var Orchestrator = class {
84273
84287
  }
84274
84288
  const model = usage.model || this.activeModel || this.config.defaultModel;
84275
84289
  const pricing = await getModelPricing(model);
84276
- const premiumRequestCost = pricing?.premiumMultiplier ?? 0;
84277
- const tokenUnitCost = pricing ? calculateTokenUnitCost(
84290
+ const cost = pricing ? calculateTokenUnitCost(
84278
84291
  usage.inputTokens,
84279
84292
  usage.outputTokens,
84280
84293
  pricing.tokenInputMultiplier,
@@ -84284,9 +84297,7 @@ var Orchestrator = class {
84284
84297
  model,
84285
84298
  inputTokens: usage.inputTokens,
84286
84299
  outputTokens: usage.outputTokens,
84287
- cost: tokenUnitCost,
84288
- premiumRequestCost,
84289
- tokenUnitCost
84300
+ cost
84290
84301
  });
84291
84302
  }
84292
84303
  };