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