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.
- package/dist/daemon/cli.js +457 -454
- package/dist/daemon/index.js +472 -461
- package/dist/web/assets/{index-N8lsPMIu.js → index-58mhEI4z.js} +126 -126
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
package/dist/daemon/index.js
CHANGED
|
@@ -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,
|
|
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.
|
|
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,
|
|
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
|
|
81156
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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: "
|
|
83626
|
-
description: "
|
|
83627
|
-
parameters:
|
|
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
|
|
83656
|
-
|
|
83657
|
-
|
|
83658
|
-
|
|
83659
|
-
|
|
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
|
-
|
|
83663
|
-
|
|
83664
|
-
|
|
83665
|
-
|
|
83666
|
-
|
|
83667
|
-
|
|
83668
|
-
|
|
83669
|
-
|
|
83670
|
-
|
|
83671
|
-
|
|
83672
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83692
|
-
|
|
83693
|
-
|
|
83694
|
-
|
|
83695
|
-
|
|
83696
|
-
|
|
83697
|
-
|
|
83698
|
-
|
|
83699
|
-
|
|
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
|
-
|
|
83794
|
-
|
|
83795
|
-
|
|
83796
|
-
|
|
83797
|
-
|
|
83798
|
-
|
|
83799
|
-
|
|
83800
|
-
|
|
83801
|
-
|
|
83802
|
-
|
|
83803
|
-
|
|
83804
|
-
|
|
83805
|
-
|
|
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 "
|
|
83813
|
-
|
|
83814
|
-
|
|
83815
|
-
|
|
83816
|
-
|
|
83817
|
-
);
|
|
83818
|
-
|
|
83819
|
-
return
|
|
83820
|
-
|
|
83821
|
-
|
|
83822
|
-
|
|
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
|
-
|
|
83860
|
-
|
|
83861
|
-
|
|
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
|
-
|
|
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:
|
|
83874
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
84288
|
-
premiumRequestCost,
|
|
84289
|
-
tokenUnitCost
|
|
84300
|
+
cost
|
|
84290
84301
|
});
|
|
84291
84302
|
}
|
|
84292
84303
|
};
|