heyio 4.2.1 → 4.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/daemon/cli.js +336 -204
- package/dist/daemon/index.js +334 -203
- package/dist/web/assets/{index-C-ghqdil.js → index-58mhEI4z.js} +126 -126
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
package/dist/daemon/index.js
CHANGED
|
@@ -79,7 +79,7 @@ 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.3";
|
|
83
83
|
API_PORT = 7777;
|
|
84
84
|
API_HOST = "0.0.0.0";
|
|
85
85
|
DEFAULT_MODEL = "gpt-4o";
|
|
@@ -91,6 +91,7 @@ var init_constants = __esm({
|
|
|
91
91
|
SQUAD_CREATED: "squad.created",
|
|
92
92
|
SQUAD_DELETED: "squad.deleted",
|
|
93
93
|
SQUAD_UPDATED: "squad.updated",
|
|
94
|
+
SQUAD_MEMBER_UPDATED: "squad.member_updated",
|
|
94
95
|
INSTANCE_CREATED: "instance.created",
|
|
95
96
|
INSTANCE_STARTED: "instance.started",
|
|
96
97
|
INSTANCE_COMPLETED: "instance.completed",
|
|
@@ -77443,6 +77444,10 @@ async function updateMember(memberId, data, db) {
|
|
|
77443
77444
|
const database = db ?? await getDatabase();
|
|
77444
77445
|
const sets = [];
|
|
77445
77446
|
const args = [];
|
|
77447
|
+
if (data.role !== void 0) {
|
|
77448
|
+
sets.push("role = ?");
|
|
77449
|
+
args.push(data.role);
|
|
77450
|
+
}
|
|
77446
77451
|
if (data.systemPrompt !== void 0) {
|
|
77447
77452
|
sets.push("system_prompt = ?");
|
|
77448
77453
|
args.push(data.systemPrompt);
|
|
@@ -77857,8 +77862,8 @@ async function recordUsage(data, db) {
|
|
|
77857
77862
|
createdAt: data.createdAt ?? nowIso()
|
|
77858
77863
|
};
|
|
77859
77864
|
await database.execute({
|
|
77860
|
-
sql: `INSERT INTO token_usage (id, squad_id, agent_id, model, input_tokens, output_tokens, cost,
|
|
77861
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
77865
|
+
sql: `INSERT INTO token_usage (id, squad_id, agent_id, model, input_tokens, output_tokens, cost, squad_name, agent_name, created_at)
|
|
77866
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
77862
77867
|
args: [
|
|
77863
77868
|
usage.id,
|
|
77864
77869
|
usage.squadId,
|
|
@@ -77867,8 +77872,6 @@ async function recordUsage(data, db) {
|
|
|
77867
77872
|
usage.inputTokens,
|
|
77868
77873
|
usage.outputTokens,
|
|
77869
77874
|
usage.cost,
|
|
77870
|
-
data.premiumRequestCost ?? null,
|
|
77871
|
-
data.tokenUnitCost ?? null,
|
|
77872
77875
|
data.squadName ?? null,
|
|
77873
77876
|
data.agentName ?? null,
|
|
77874
77877
|
usage.createdAt
|
|
@@ -79744,11 +79747,16 @@ router7.put("/api/squads/:id/members/:memberId", async (req, res) => {
|
|
|
79744
79747
|
res.status(404).json({ error: "Member not found" });
|
|
79745
79748
|
return;
|
|
79746
79749
|
}
|
|
79747
|
-
const { systemPrompt, model } = req.body;
|
|
79750
|
+
const { role, systemPrompt, model } = req.body;
|
|
79748
79751
|
const updated = await updateMember(member.id, {
|
|
79752
|
+
role,
|
|
79749
79753
|
systemPrompt,
|
|
79750
79754
|
model: model === "" ? null : model
|
|
79751
79755
|
});
|
|
79756
|
+
eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, {
|
|
79757
|
+
squadId: member.squadId,
|
|
79758
|
+
member: updated
|
|
79759
|
+
});
|
|
79752
79760
|
res.status(200).json(updated);
|
|
79753
79761
|
} catch (error51) {
|
|
79754
79762
|
res.status(500).json({
|
|
@@ -80028,8 +80036,7 @@ var rootLogger = null;
|
|
|
80028
80036
|
function createConsoleStream() {
|
|
80029
80037
|
return (0, import_pino_pretty.default)({
|
|
80030
80038
|
colorize: process.stdout.isTTY ?? false,
|
|
80031
|
-
|
|
80032
|
-
ignore: "pid,hostname",
|
|
80039
|
+
ignore: "pid,hostname,name,time",
|
|
80033
80040
|
singleLine: true
|
|
80034
80041
|
});
|
|
80035
80042
|
}
|
|
@@ -81153,10 +81160,19 @@ async function getModelPricing(modelId) {
|
|
|
81153
81160
|
sql: "SELECT * FROM model_pricing WHERE id = ?",
|
|
81154
81161
|
args: [modelId]
|
|
81155
81162
|
});
|
|
81156
|
-
if (result.rows.length
|
|
81157
|
-
return
|
|
81163
|
+
if (result.rows.length > 0) {
|
|
81164
|
+
return rowToModelPricing(result.rows[0]);
|
|
81158
81165
|
}
|
|
81159
|
-
|
|
81166
|
+
const allModels = await db.execute(
|
|
81167
|
+
"SELECT * FROM model_pricing WHERE token_input_multiplier IS NOT NULL ORDER BY length(id) DESC"
|
|
81168
|
+
);
|
|
81169
|
+
for (const row of allModels.rows) {
|
|
81170
|
+
const storedId = asString(row.id);
|
|
81171
|
+
if (modelId.startsWith(storedId) || storedId.startsWith(modelId)) {
|
|
81172
|
+
return rowToModelPricing(row);
|
|
81173
|
+
}
|
|
81174
|
+
}
|
|
81175
|
+
return null;
|
|
81160
81176
|
}
|
|
81161
81177
|
function calculateTokenUnitCost(inputTokens, outputTokens, inputMultiplier, outputMultiplier) {
|
|
81162
81178
|
if (inputMultiplier === null || outputMultiplier === null) {
|
|
@@ -81233,7 +81249,7 @@ function buildSystemPrompt(options2) {
|
|
|
81233
81249
|
"You coordinate work across squads, wiki knowledge, installed skills, and direct execution tools.",
|
|
81234
81250
|
"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.",
|
|
81235
81251
|
"Be practical, concise, and execution-oriented. Use tools when they help you produce a more accurate or durable result.",
|
|
81236
|
-
"## 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.",
|
|
81252
|
+
"## Delegation Rules\n- Prefer delegation for project-specific work when a matching squad exists.\n- If no squad exists for the target repository or project, use direct coding and knowledge tools yourself.\n- When hiring a squad, always pass the user's context about what areas of the codebase or what kind of work the squad will focus on. Extract specific folder paths or project files mentioned by the user and pass them as scanPaths.\n- Use the wiki to remember important information and recover prior context.\n- Use installed skills when they are relevant to the current request.",
|
|
81237
81253
|
`## Squad Roster
|
|
81238
81254
|
${formatSquadRoster(options2.squads)}`,
|
|
81239
81255
|
formatOptionalSection("## Conversation Summary", options2.conversationSummary ?? ""),
|
|
@@ -82233,8 +82249,7 @@ async function persistUsage(member, usageEvents, squadName) {
|
|
|
82233
82249
|
for (const usage of usageEvents) {
|
|
82234
82250
|
const model = usage.model;
|
|
82235
82251
|
const pricing = await getModelPricing(model);
|
|
82236
|
-
const
|
|
82237
|
-
const tokenUnitCost = pricing ? calculateTokenUnitCost(
|
|
82252
|
+
const cost = pricing ? calculateTokenUnitCost(
|
|
82238
82253
|
usage.inputTokens ?? 0,
|
|
82239
82254
|
usage.outputTokens ?? 0,
|
|
82240
82255
|
pricing.tokenInputMultiplier,
|
|
@@ -82248,9 +82263,7 @@ async function persistUsage(member, usageEvents, squadName) {
|
|
|
82248
82263
|
model,
|
|
82249
82264
|
inputTokens: usage.inputTokens ?? 0,
|
|
82250
82265
|
outputTokens: usage.outputTokens ?? 0,
|
|
82251
|
-
cost
|
|
82252
|
-
premiumRequestCost,
|
|
82253
|
-
tokenUnitCost
|
|
82266
|
+
cost
|
|
82254
82267
|
});
|
|
82255
82268
|
}
|
|
82256
82269
|
}
|
|
@@ -82604,9 +82617,10 @@ var ROLE_GENERATION_PROMPT = `You are staffing an autonomous software squad for
|
|
|
82604
82617
|
Given repository analysis describing languages, frameworks, file structure, architectural patterns, and operational concerns, propose the smallest effective team that can deliver objectives safely.
|
|
82605
82618
|
|
|
82606
82619
|
Requirements:
|
|
82607
|
-
- Mandatory roles Team Lead and QA must always exist.
|
|
82620
|
+
- Mandatory roles Team Lead and QA must always exist \u2014 do NOT include them in your response.
|
|
82608
82621
|
- Suggest only additional roles that are clearly justified by the repository analysis.
|
|
82609
|
-
-
|
|
82622
|
+
- Role names must reflect Senior or Principal seniority and be specific to the actual technology stack and work scope. Examples: "Principal React Engineer", "Senior DevOps Engineer", "Senior CI/CD Solutions Engineer", "Principal .NET API Engineer", "Senior Data Pipeline Engineer".
|
|
82623
|
+
- Do NOT use generic names like "Frontend Engineer" or "Backend Engineer". Be specific about the technology and domain.
|
|
82610
82624
|
- Each role must have a short human-readable name and a concise description of responsibilities.
|
|
82611
82625
|
- Avoid duplicate or overlapping roles.
|
|
82612
82626
|
- Optimize for implementation, verification, and maintainability.
|
|
@@ -82615,9 +82629,9 @@ Return strict JSON in this shape:
|
|
|
82615
82629
|
{
|
|
82616
82630
|
"roles": [
|
|
82617
82631
|
{
|
|
82618
|
-
"role": "
|
|
82619
|
-
"name": "
|
|
82620
|
-
"description": "Owns UI implementation, client state, and browser-facing tests."
|
|
82632
|
+
"role": "principal-react-engineer",
|
|
82633
|
+
"name": "Principal React Engineer",
|
|
82634
|
+
"description": "Owns UI implementation, client state management, and browser-facing integration tests."
|
|
82621
82635
|
}
|
|
82622
82636
|
]
|
|
82623
82637
|
}`;
|
|
@@ -83412,29 +83426,29 @@ function detectRolesFromAnalysis(repoAnalysis) {
|
|
|
83412
83426
|
const roles = [];
|
|
83413
83427
|
if (/(react|next|vue|angular|frontend|ui|xaml)/.test(analysis)) {
|
|
83414
83428
|
roles.push({
|
|
83415
|
-
role: "frontend-engineer",
|
|
83416
|
-
name: "Frontend Engineer",
|
|
83429
|
+
role: "senior-frontend-engineer",
|
|
83430
|
+
name: "Senior Frontend Engineer",
|
|
83417
83431
|
description: "Implements user-facing experiences, UI state, and presentation-layer changes."
|
|
83418
83432
|
});
|
|
83419
83433
|
}
|
|
83420
83434
|
if (/(node|express|api|backend|server|daemon|database|sql|postgres|sqlite)/.test(analysis)) {
|
|
83421
83435
|
roles.push({
|
|
83422
|
-
role: "backend-engineer",
|
|
83423
|
-
name: "Backend Engineer",
|
|
83436
|
+
role: "senior-backend-engineer",
|
|
83437
|
+
name: "Senior Backend Engineer",
|
|
83424
83438
|
description: "Implements server-side logic, data access, integrations, and execution flow changes."
|
|
83425
83439
|
});
|
|
83426
83440
|
}
|
|
83427
83441
|
if (/(infra|docker|kubernetes|deploy|ci|cd|workflow|github actions)/.test(analysis)) {
|
|
83428
83442
|
roles.push({
|
|
83429
|
-
role: "platform-engineer",
|
|
83430
|
-
name: "Platform Engineer",
|
|
83443
|
+
role: "senior-platform-engineer",
|
|
83444
|
+
name: "Senior Platform Engineer",
|
|
83431
83445
|
description: "Owns automation, pipelines, environments, and operational tooling."
|
|
83432
83446
|
});
|
|
83433
83447
|
}
|
|
83434
83448
|
if (/(security|auth|oauth|secret|policy)/.test(analysis)) {
|
|
83435
83449
|
roles.push({
|
|
83436
|
-
role: "security-engineer",
|
|
83437
|
-
name: "Security Engineer",
|
|
83450
|
+
role: "senior-security-engineer",
|
|
83451
|
+
name: "Senior Security Engineer",
|
|
83438
83452
|
description: "Reviews authentication, authorization, secrets handling, and security-sensitive changes."
|
|
83439
83453
|
});
|
|
83440
83454
|
}
|
|
@@ -83494,13 +83508,17 @@ ${repoContext}`;
|
|
|
83494
83508
|
}
|
|
83495
83509
|
return generateRolePrompt(normalized, repoContext);
|
|
83496
83510
|
}
|
|
83497
|
-
async function proposeSquadComposition(repoUrl, repoAnalysis) {
|
|
83511
|
+
async function proposeSquadComposition(repoUrl, repoAnalysis, context) {
|
|
83498
83512
|
const mandatory = buildMandatoryRoles();
|
|
83499
83513
|
const fallbackAdditional = dedupeRoles(detectRolesFromAnalysis(repoAnalysis));
|
|
83514
|
+
const contextSection = context ? `
|
|
83515
|
+
|
|
83516
|
+
Additional context from user:
|
|
83517
|
+
${context}` : "";
|
|
83500
83518
|
const prompt = `Repository URL: ${repoUrl}
|
|
83501
83519
|
|
|
83502
83520
|
Repository analysis:
|
|
83503
|
-
${repoAnalysis}
|
|
83521
|
+
${repoAnalysis}${contextSection}
|
|
83504
83522
|
|
|
83505
83523
|
${ROLE_GENERATION_PROMPT}`;
|
|
83506
83524
|
let client2 = null;
|
|
@@ -83612,7 +83630,13 @@ async function pathExists2(path) {
|
|
|
83612
83630
|
}
|
|
83613
83631
|
}
|
|
83614
83632
|
var hireSquadSchema = external_exports.object({
|
|
83615
|
-
repoUrl: external_exports.string().trim().min(1)
|
|
83633
|
+
repoUrl: external_exports.string().trim().min(1).describe("The repository URL to hire a squad for"),
|
|
83634
|
+
context: external_exports.string().optional().describe(
|
|
83635
|
+
"Additional context from the user to guide squad composition and role selection. Include any specifics about what areas of the codebase the squad will focus on, what technologies matter most, or what kind of work they will do."
|
|
83636
|
+
),
|
|
83637
|
+
scanPaths: external_exports.array(external_exports.string()).optional().describe(
|
|
83638
|
+
"Relative paths within the repository to focus the analysis on. When provided, only these directories are scanned for technology detection instead of the entire repo. Extract these from the user's context when they mention specific folders, solution files, or project subsets."
|
|
83639
|
+
)
|
|
83616
83640
|
});
|
|
83617
83641
|
var squadIdSchema = external_exports.object({
|
|
83618
83642
|
squadId: external_exports.string().trim().min(1)
|
|
@@ -83621,10 +83645,21 @@ var delegateToSquadSchema = external_exports.object({
|
|
|
83621
83645
|
squadId: external_exports.string().trim().min(1),
|
|
83622
83646
|
objective: external_exports.string().trim().min(1)
|
|
83623
83647
|
});
|
|
83648
|
+
var renameSquadSchema = external_exports.object({
|
|
83649
|
+
squadId: external_exports.string().trim().min(1),
|
|
83650
|
+
name: external_exports.string().trim().min(1)
|
|
83651
|
+
});
|
|
83652
|
+
var updateSquadMemberSchema = external_exports.object({
|
|
83653
|
+
squadId: external_exports.string().trim().min(1),
|
|
83654
|
+
memberId: external_exports.string().trim().min(1),
|
|
83655
|
+
role: external_exports.string().trim().min(1).optional(),
|
|
83656
|
+
systemPrompt: external_exports.string().optional(),
|
|
83657
|
+
model: external_exports.string().optional()
|
|
83658
|
+
});
|
|
83624
83659
|
var squadToolDefinitions = [
|
|
83625
83660
|
{
|
|
83626
83661
|
name: "hire_squad",
|
|
83627
|
-
description: "Hire or refresh a squad for a repository.",
|
|
83662
|
+
description: "Hire or refresh a squad for a repository. Optionally provide context to guide role selection and scanPaths to focus the repo analysis on specific directories.",
|
|
83628
83663
|
parameters: hireSquadSchema,
|
|
83629
83664
|
skipPermission: true
|
|
83630
83665
|
},
|
|
@@ -83651,6 +83686,18 @@ var squadToolDefinitions = [
|
|
|
83651
83686
|
description: "Create an objective for a squad and start execution.",
|
|
83652
83687
|
parameters: delegateToSquadSchema,
|
|
83653
83688
|
skipPermission: true
|
|
83689
|
+
},
|
|
83690
|
+
{
|
|
83691
|
+
name: "rename_squad",
|
|
83692
|
+
description: "Rename a squad.",
|
|
83693
|
+
parameters: renameSquadSchema,
|
|
83694
|
+
skipPermission: true
|
|
83695
|
+
},
|
|
83696
|
+
{
|
|
83697
|
+
name: "update_squad_member",
|
|
83698
|
+
description: "Update a squad member's role, system prompt, and/or default model. All fields are optional; only provided fields are changed.",
|
|
83699
|
+
parameters: updateSquadMemberSchema,
|
|
83700
|
+
skipPermission: true
|
|
83654
83701
|
}
|
|
83655
83702
|
];
|
|
83656
83703
|
function getDefaultSquadConfig(_config) {
|
|
@@ -83660,19 +83707,90 @@ function getDefaultSquadConfig(_config) {
|
|
|
83660
83707
|
maxRevisions: QA_MAX_REVISIONS
|
|
83661
83708
|
};
|
|
83662
83709
|
}
|
|
83663
|
-
|
|
83664
|
-
|
|
83665
|
-
|
|
83666
|
-
|
|
83667
|
-
|
|
83668
|
-
|
|
83669
|
-
|
|
83670
|
-
|
|
83671
|
-
|
|
83672
|
-
|
|
83673
|
-
|
|
83710
|
+
var MANIFEST_FILES = [
|
|
83711
|
+
"package.json",
|
|
83712
|
+
"Cargo.toml",
|
|
83713
|
+
"go.mod",
|
|
83714
|
+
"requirements.txt",
|
|
83715
|
+
"pyproject.toml",
|
|
83716
|
+
"Gemfile",
|
|
83717
|
+
"pom.xml",
|
|
83718
|
+
"build.gradle",
|
|
83719
|
+
"composer.json",
|
|
83720
|
+
"Makefile",
|
|
83721
|
+
"Dockerfile",
|
|
83722
|
+
"docker-compose.yml",
|
|
83723
|
+
"docker-compose.yaml",
|
|
83724
|
+
".github/workflows"
|
|
83725
|
+
];
|
|
83726
|
+
var SRC_DIR_NAMES = ["src", "lib", "app", "packages", "crates", "cmd", "internal"];
|
|
83727
|
+
var README_CANDIDATES2 = ["README.md", "README.rst", "README.txt", "README"];
|
|
83728
|
+
async function readManifests(rootPath, rootLabel) {
|
|
83729
|
+
const lines = [];
|
|
83730
|
+
for (const manifest of MANIFEST_FILES) {
|
|
83731
|
+
const fullPath = join15(rootPath, manifest);
|
|
83732
|
+
try {
|
|
83733
|
+
const fileStat = await stat4(fullPath);
|
|
83734
|
+
if (fileStat.isFile()) {
|
|
83735
|
+
const content = await readFile9(fullPath, "utf8");
|
|
83736
|
+
lines.push(`
|
|
83737
|
+
--- ${rootLabel}${manifest} ---
|
|
83738
|
+
${content.slice(0, 3e3)}`);
|
|
83739
|
+
} else if (fileStat.isDirectory()) {
|
|
83740
|
+
const children = await readdir6(fullPath);
|
|
83741
|
+
lines.push(`
|
|
83742
|
+
--- ${rootLabel}${manifest}/ ---
|
|
83743
|
+
${children.join(", ")}`);
|
|
83744
|
+
}
|
|
83745
|
+
} catch {
|
|
83746
|
+
}
|
|
83674
83747
|
}
|
|
83675
|
-
|
|
83748
|
+
return lines;
|
|
83749
|
+
}
|
|
83750
|
+
async function scanSrcDirs(rootPath, rootLabel, dirs) {
|
|
83751
|
+
const lines = [];
|
|
83752
|
+
const srcDirs = dirs.filter((d) => SRC_DIR_NAMES.includes(d));
|
|
83753
|
+
for (const srcDir of srcDirs) {
|
|
83754
|
+
try {
|
|
83755
|
+
const srcEntries = await readdir6(join15(rootPath, srcDir), { withFileTypes: true });
|
|
83756
|
+
const srcFiles = srcEntries.filter((e) => e.isFile()).map((e) => e.name);
|
|
83757
|
+
const srcSubDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
83758
|
+
lines.push(`
|
|
83759
|
+
--- ${rootLabel}${srcDir}/ ---`);
|
|
83760
|
+
if (srcSubDirs.length) lines.push(` Directories: ${srcSubDirs.join(", ")}`);
|
|
83761
|
+
if (srcFiles.length) lines.push(` Files: ${srcFiles.slice(0, 30).join(", ")}`);
|
|
83762
|
+
} catch {
|
|
83763
|
+
}
|
|
83764
|
+
}
|
|
83765
|
+
return lines;
|
|
83766
|
+
}
|
|
83767
|
+
async function findReadme(rootPath, rootLabel) {
|
|
83768
|
+
for (const readme of README_CANDIDATES2) {
|
|
83769
|
+
try {
|
|
83770
|
+
const content = await readFile9(join15(rootPath, readme), "utf8");
|
|
83771
|
+
return `
|
|
83772
|
+
--- ${rootLabel}${readme} (excerpt) ---
|
|
83773
|
+
${content.slice(0, 1500)}`;
|
|
83774
|
+
} catch {
|
|
83775
|
+
}
|
|
83776
|
+
}
|
|
83777
|
+
return null;
|
|
83778
|
+
}
|
|
83779
|
+
async function scanRoot(root) {
|
|
83780
|
+
const lines = [];
|
|
83781
|
+
const rootLabel = root.label ? `${root.label}/` : "";
|
|
83782
|
+
const rootEntries = await readdir6(root.path, { withFileTypes: true });
|
|
83783
|
+
const rootFiles = rootEntries.filter((e) => e.isFile()).map((e) => e.name);
|
|
83784
|
+
const rootDirs = rootEntries.filter((e) => e.isDirectory() && e.name !== ".git").map((e) => e.name);
|
|
83785
|
+
if (rootFiles.length) lines.push(`${rootLabel}Files: ${rootFiles.join(", ")}`);
|
|
83786
|
+
if (rootDirs.length) lines.push(`${rootLabel}Directories: ${rootDirs.join(", ")}`);
|
|
83787
|
+
lines.push(...await readManifests(root.path, rootLabel));
|
|
83788
|
+
lines.push(...await scanSrcDirs(root.path, rootLabel, rootDirs));
|
|
83789
|
+
const readme = await findReadme(root.path, rootLabel);
|
|
83790
|
+
if (readme) lines.push(readme);
|
|
83791
|
+
return lines;
|
|
83792
|
+
}
|
|
83793
|
+
async function cloneOrUpdateRepo(normalized, repoDir) {
|
|
83676
83794
|
try {
|
|
83677
83795
|
await mkdir10(join15(DATA_DIR, "repos"), { recursive: true });
|
|
83678
83796
|
if (await pathExists2(join15(repoDir, ".git"))) {
|
|
@@ -83684,79 +83802,40 @@ async function buildRepoAnalysis(repoUrl) {
|
|
|
83684
83802
|
timeout: 6e4
|
|
83685
83803
|
});
|
|
83686
83804
|
}
|
|
83805
|
+
return true;
|
|
83687
83806
|
} catch {
|
|
83807
|
+
return false;
|
|
83808
|
+
}
|
|
83809
|
+
}
|
|
83810
|
+
async function buildRepoAnalysis(repoUrl, scanPaths) {
|
|
83811
|
+
const normalized = repoUrl.trim();
|
|
83812
|
+
const segments = normalized.replace(/\.git$/i, "").split("/").filter(Boolean);
|
|
83813
|
+
const owner = segments.at(-2) ?? "";
|
|
83814
|
+
const name = segments.at(-1) ?? normalized;
|
|
83815
|
+
const lines = [`Repository URL: ${normalized}`, `Repository name: ${name}`];
|
|
83816
|
+
if (!owner || !name) {
|
|
83817
|
+
lines.push(
|
|
83818
|
+
"Use the repository identity and any available conventions to propose a practical squad composition."
|
|
83819
|
+
);
|
|
83820
|
+
return lines.join("\n");
|
|
83821
|
+
}
|
|
83822
|
+
const repoDir = join15(DATA_DIR, "repos", `${owner}--${name}`);
|
|
83823
|
+
const cloned = await cloneOrUpdateRepo(normalized, repoDir);
|
|
83824
|
+
if (!cloned) {
|
|
83688
83825
|
lines.push("Unable to clone repository locally; falling back to basic analysis.");
|
|
83689
83826
|
lines.push("Based on the repository name, propose roles that match common project patterns.");
|
|
83690
83827
|
return lines.join("\n");
|
|
83691
83828
|
}
|
|
83692
|
-
|
|
83693
|
-
|
|
83694
|
-
|
|
83695
|
-
|
|
83696
|
-
|
|
83697
|
-
|
|
83698
|
-
|
|
83699
|
-
|
|
83700
|
-
|
|
83701
|
-
"go.mod",
|
|
83702
|
-
"requirements.txt",
|
|
83703
|
-
"pyproject.toml",
|
|
83704
|
-
"Gemfile",
|
|
83705
|
-
"pom.xml",
|
|
83706
|
-
"build.gradle",
|
|
83707
|
-
"composer.json",
|
|
83708
|
-
"Makefile",
|
|
83709
|
-
"Dockerfile",
|
|
83710
|
-
"docker-compose.yml",
|
|
83711
|
-
"docker-compose.yaml",
|
|
83712
|
-
".github/workflows"
|
|
83713
|
-
];
|
|
83714
|
-
for (const manifest of manifestFiles) {
|
|
83715
|
-
const fullPath = join15(repoDir, manifest);
|
|
83716
|
-
try {
|
|
83717
|
-
const fileStat = await stat4(fullPath);
|
|
83718
|
-
if (fileStat.isFile()) {
|
|
83719
|
-
const content = await readFile9(fullPath, "utf8");
|
|
83720
|
-
lines.push(`
|
|
83721
|
-
--- ${manifest} ---
|
|
83722
|
-
${content.slice(0, 3e3)}`);
|
|
83723
|
-
} else if (fileStat.isDirectory()) {
|
|
83724
|
-
const children = await readdir6(fullPath);
|
|
83725
|
-
lines.push(`
|
|
83726
|
-
--- ${manifest}/ ---
|
|
83727
|
-
${children.join(", ")}`);
|
|
83728
|
-
}
|
|
83729
|
-
} catch {
|
|
83730
|
-
}
|
|
83731
|
-
}
|
|
83732
|
-
const srcDirs = rootDirs.filter(
|
|
83733
|
-
(d) => ["src", "lib", "app", "packages", "crates", "cmd", "internal"].includes(d)
|
|
83734
|
-
);
|
|
83735
|
-
for (const srcDir of srcDirs) {
|
|
83736
|
-
try {
|
|
83737
|
-
const srcEntries = await readdir6(join15(repoDir, srcDir), { withFileTypes: true });
|
|
83738
|
-
const srcFiles = srcEntries.filter((e) => e.isFile()).map((e) => e.name);
|
|
83739
|
-
const srcSubDirs = srcEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
83740
|
-
lines.push(`
|
|
83741
|
-
--- ${srcDir}/ ---`);
|
|
83742
|
-
if (srcSubDirs.length) lines.push(` Directories: ${srcSubDirs.join(", ")}`);
|
|
83743
|
-
if (srcFiles.length) lines.push(` Files: ${srcFiles.slice(0, 30).join(", ")}`);
|
|
83744
|
-
} catch {
|
|
83745
|
-
}
|
|
83746
|
-
}
|
|
83747
|
-
const readmeCandidates = ["README.md", "README.rst", "README.txt", "README"];
|
|
83748
|
-
for (const readme of readmeCandidates) {
|
|
83749
|
-
try {
|
|
83750
|
-
const content = await readFile9(join15(repoDir, readme), "utf8");
|
|
83751
|
-
lines.push(`
|
|
83752
|
-
--- ${readme} (excerpt) ---
|
|
83753
|
-
${content.slice(0, 1500)}`);
|
|
83754
|
-
break;
|
|
83755
|
-
} catch {
|
|
83756
|
-
}
|
|
83829
|
+
const scanRoots = scanPaths && scanPaths.length > 0 ? scanPaths.map((p) => ({ path: join15(repoDir, p), label: p })) : [{ path: repoDir, label: "" }];
|
|
83830
|
+
if (scanPaths && scanPaths.length > 0) {
|
|
83831
|
+
lines.push(`Focused scan paths: ${scanPaths.join(", ")}`);
|
|
83832
|
+
}
|
|
83833
|
+
for (const root of scanRoots) {
|
|
83834
|
+
try {
|
|
83835
|
+
lines.push(...await scanRoot(root));
|
|
83836
|
+
} catch {
|
|
83837
|
+
lines.push(`Filesystem scan failed for ${root.label || "repo root"}; skipping.`);
|
|
83757
83838
|
}
|
|
83758
|
-
} catch {
|
|
83759
|
-
lines.push("Filesystem scan failed; using minimal info.");
|
|
83760
83839
|
}
|
|
83761
83840
|
lines.push(
|
|
83762
83841
|
"\nBased on the above repository structure and contents, propose roles that match the project's actual technology stack and architecture."
|
|
@@ -83771,6 +83850,22 @@ function formatSquadList(squads) {
|
|
|
83771
83850
|
(squad) => `${squad.id}: ${squad.name} (${squad.repoOwner}/${squad.repoName}) [${squad.status}]`
|
|
83772
83851
|
).join("\n");
|
|
83773
83852
|
}
|
|
83853
|
+
async function processQueue2(squadId) {
|
|
83854
|
+
try {
|
|
83855
|
+
const running = await countRunningInstances(squadId);
|
|
83856
|
+
const config2 = (await Promise.resolve().then(() => (init_config(), config_exports))).loadConfig();
|
|
83857
|
+
if (running < config2.maxInstancesPerSquad) {
|
|
83858
|
+
const next = await getNextQueued(squadId);
|
|
83859
|
+
if (next) {
|
|
83860
|
+
const freshSquad = await getSquad(squadId);
|
|
83861
|
+
if (freshSquad) {
|
|
83862
|
+
void startAndExecuteInstance(next.id, freshSquad, next.objectiveId);
|
|
83863
|
+
}
|
|
83864
|
+
}
|
|
83865
|
+
}
|
|
83866
|
+
} catch {
|
|
83867
|
+
}
|
|
83868
|
+
}
|
|
83774
83869
|
async function startAndExecuteInstance(instanceId, squad, objectiveId) {
|
|
83775
83870
|
try {
|
|
83776
83871
|
const repoPath = await resolveRepoPath(squad.repoUrl, squad.repoName);
|
|
@@ -83791,56 +83886,96 @@ async function startAndExecuteInstance(instanceId, squad, objectiveId) {
|
|
|
83791
83886
|
await failInstance(instanceId, message2).catch(() => {
|
|
83792
83887
|
});
|
|
83793
83888
|
} finally {
|
|
83794
|
-
|
|
83795
|
-
|
|
83796
|
-
|
|
83797
|
-
|
|
83798
|
-
|
|
83799
|
-
|
|
83800
|
-
|
|
83801
|
-
|
|
83802
|
-
|
|
83803
|
-
|
|
83804
|
-
|
|
83805
|
-
|
|
83806
|
-
|
|
83807
|
-
|
|
83889
|
+
await processQueue2(squad.id);
|
|
83890
|
+
}
|
|
83891
|
+
}
|
|
83892
|
+
async function handleHireSquad(rawArgs, config2) {
|
|
83893
|
+
const { repoUrl, context, scanPaths } = hireSquadSchema.parse(rawArgs);
|
|
83894
|
+
const composition = await proposeSquadComposition(
|
|
83895
|
+
repoUrl,
|
|
83896
|
+
await buildRepoAnalysis(repoUrl, scanPaths),
|
|
83897
|
+
context
|
|
83898
|
+
);
|
|
83899
|
+
const result = await hireSquad(repoUrl, composition, getDefaultSquadConfig(config2));
|
|
83900
|
+
return {
|
|
83901
|
+
message: `Squad ready for ${repoUrl}.`,
|
|
83902
|
+
squad: result.squad,
|
|
83903
|
+
members: result.members
|
|
83904
|
+
};
|
|
83905
|
+
}
|
|
83906
|
+
async function handleFireSquad(rawArgs) {
|
|
83907
|
+
const { squadId } = squadIdSchema.parse(rawArgs);
|
|
83908
|
+
const squad = await getSquad(squadId);
|
|
83909
|
+
if (!squad) {
|
|
83910
|
+
throw new Error(`Squad ${squadId} was not found.`);
|
|
83911
|
+
}
|
|
83912
|
+
const activeObjectives = await getActiveObjectives(squadId);
|
|
83913
|
+
if (activeObjectives.length > 0) {
|
|
83914
|
+
const updated = await updateSquad(squadId, { status: "inactive" });
|
|
83915
|
+
return {
|
|
83916
|
+
message: `Squad ${squadId} was deactivated because it still has active objectives.`,
|
|
83917
|
+
squad: updated,
|
|
83918
|
+
activeObjectives
|
|
83919
|
+
};
|
|
83920
|
+
}
|
|
83921
|
+
await deleteSquad(squadId);
|
|
83922
|
+
return { message: `Squad ${squadId} was deleted.`, squadId };
|
|
83923
|
+
}
|
|
83924
|
+
async function handleDelegateToSquad(rawArgs) {
|
|
83925
|
+
const { squadId, objective } = delegateToSquadSchema.parse(rawArgs);
|
|
83926
|
+
const squad = await getSquad(squadId);
|
|
83927
|
+
if (!squad) {
|
|
83928
|
+
throw new Error(`Squad ${squadId} was not found.`);
|
|
83929
|
+
}
|
|
83930
|
+
const createdObjective = await createObjective(squadId, objective);
|
|
83931
|
+
const { instance, queued } = await spawnInstance({
|
|
83932
|
+
squadId,
|
|
83933
|
+
objectiveId: createdObjective.id
|
|
83934
|
+
});
|
|
83935
|
+
if (!queued) {
|
|
83936
|
+
void startAndExecuteInstance(instance.id, squad, createdObjective.id);
|
|
83937
|
+
}
|
|
83938
|
+
return {
|
|
83939
|
+
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.`,
|
|
83940
|
+
objective: createdObjective,
|
|
83941
|
+
instanceId: instance.id,
|
|
83942
|
+
queued
|
|
83943
|
+
};
|
|
83944
|
+
}
|
|
83945
|
+
async function handleUpdateSquadMember(rawArgs) {
|
|
83946
|
+
const { squadId, memberId, role, systemPrompt, model } = updateSquadMemberSchema.parse(rawArgs);
|
|
83947
|
+
const squad = await getSquad(squadId);
|
|
83948
|
+
if (!squad) {
|
|
83949
|
+
throw new Error(`Squad ${squadId} was not found.`);
|
|
83950
|
+
}
|
|
83951
|
+
const member = await getMember(memberId);
|
|
83952
|
+
if (!member || member.squadId !== squadId) {
|
|
83953
|
+
throw new Error(`Member ${memberId} was not found in squad ${squadId}.`);
|
|
83808
83954
|
}
|
|
83955
|
+
const updated = await updateMember(memberId, {
|
|
83956
|
+
role,
|
|
83957
|
+
systemPrompt,
|
|
83958
|
+
model: model === "" ? null : model
|
|
83959
|
+
});
|
|
83960
|
+
if (!updated) {
|
|
83961
|
+
throw new Error(`Failed to update member ${memberId}.`);
|
|
83962
|
+
}
|
|
83963
|
+
eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, {
|
|
83964
|
+
squadId,
|
|
83965
|
+
member: updated
|
|
83966
|
+
});
|
|
83967
|
+
return {
|
|
83968
|
+
message: `Updated member "${updated.name}" in squad "${squad.name}".`,
|
|
83969
|
+
member: updated
|
|
83970
|
+
};
|
|
83809
83971
|
}
|
|
83810
83972
|
function createSquadToolExecutor(config2) {
|
|
83811
83973
|
return async (toolName, rawArgs) => {
|
|
83812
83974
|
switch (toolName) {
|
|
83813
|
-
case "hire_squad":
|
|
83814
|
-
|
|
83815
|
-
|
|
83816
|
-
|
|
83817
|
-
await buildRepoAnalysis(repoUrl)
|
|
83818
|
-
);
|
|
83819
|
-
const result = await hireSquad(repoUrl, composition, getDefaultSquadConfig(config2));
|
|
83820
|
-
return {
|
|
83821
|
-
message: `Squad ready for ${repoUrl}.`,
|
|
83822
|
-
squad: result.squad,
|
|
83823
|
-
members: result.members
|
|
83824
|
-
};
|
|
83825
|
-
}
|
|
83826
|
-
case "fire_squad": {
|
|
83827
|
-
const { squadId } = squadIdSchema.parse(rawArgs);
|
|
83828
|
-
const squad = await getSquad(squadId);
|
|
83829
|
-
if (!squad) {
|
|
83830
|
-
throw new Error(`Squad ${squadId} was not found.`);
|
|
83831
|
-
}
|
|
83832
|
-
const activeObjectives = await getActiveObjectives(squadId);
|
|
83833
|
-
if (activeObjectives.length > 0) {
|
|
83834
|
-
const updated = await updateSquad(squadId, { status: "inactive" });
|
|
83835
|
-
return {
|
|
83836
|
-
message: `Squad ${squadId} was deactivated because it still has active objectives.`,
|
|
83837
|
-
squad: updated,
|
|
83838
|
-
activeObjectives
|
|
83839
|
-
};
|
|
83840
|
-
}
|
|
83841
|
-
await deleteSquad(squadId);
|
|
83842
|
-
return { message: `Squad ${squadId} was deleted.`, squadId };
|
|
83843
|
-
}
|
|
83975
|
+
case "hire_squad":
|
|
83976
|
+
return handleHireSquad(rawArgs, config2);
|
|
83977
|
+
case "fire_squad":
|
|
83978
|
+
return handleFireSquad(rawArgs);
|
|
83844
83979
|
case "list_squads": {
|
|
83845
83980
|
const squads = await listSquads();
|
|
83846
83981
|
return {
|
|
@@ -83856,27 +83991,22 @@ function createSquadToolExecutor(config2) {
|
|
|
83856
83991
|
...status
|
|
83857
83992
|
};
|
|
83858
83993
|
}
|
|
83859
|
-
case "delegate_to_squad":
|
|
83860
|
-
|
|
83861
|
-
|
|
83862
|
-
|
|
83994
|
+
case "delegate_to_squad":
|
|
83995
|
+
return handleDelegateToSquad(rawArgs);
|
|
83996
|
+
case "rename_squad": {
|
|
83997
|
+
const { squadId, name } = renameSquadSchema.parse(rawArgs);
|
|
83998
|
+
const updated = await updateSquad(squadId, { name });
|
|
83999
|
+
if (!updated) {
|
|
83863
84000
|
throw new Error(`Squad ${squadId} was not found.`);
|
|
83864
84001
|
}
|
|
83865
|
-
|
|
83866
|
-
const { instance, queued } = await spawnInstance({
|
|
83867
|
-
squadId,
|
|
83868
|
-
objectiveId: createdObjective.id
|
|
83869
|
-
});
|
|
83870
|
-
if (!queued) {
|
|
83871
|
-
void startAndExecuteInstance(instance.id, squad, createdObjective.id);
|
|
83872
|
-
}
|
|
84002
|
+
eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad: updated });
|
|
83873
84003
|
return {
|
|
83874
|
-
message:
|
|
83875
|
-
|
|
83876
|
-
instanceId: instance.id,
|
|
83877
|
-
queued
|
|
84004
|
+
message: `Squad renamed to "${updated.name}".`,
|
|
84005
|
+
squad: updated
|
|
83878
84006
|
};
|
|
83879
84007
|
}
|
|
84008
|
+
case "update_squad_member":
|
|
84009
|
+
return handleUpdateSquadMember(rawArgs);
|
|
83880
84010
|
default:
|
|
83881
84011
|
throw new Error(`Unsupported squad tool: ${toolName}`);
|
|
83882
84012
|
}
|
|
@@ -83943,6 +84073,30 @@ function buildMemoryPath(timestamp) {
|
|
|
83943
84073
|
const time3 = iso.slice(11, 19).replace(/:/gu, "-");
|
|
83944
84074
|
return `memory/${date5}/${time3}.md`;
|
|
83945
84075
|
}
|
|
84076
|
+
async function handleWikiWrite(rawArgs) {
|
|
84077
|
+
const { path, title, content, tags } = wikiWriteSchema.parse(rawArgs);
|
|
84078
|
+
const existing = await getPage(path);
|
|
84079
|
+
const page = existing ? await updatePage(path, { title, content, tags: tags ?? [] }) : await createPage(path, title, content, tags ?? []);
|
|
84080
|
+
return {
|
|
84081
|
+
message: `${existing ? "Updated" : "Created"} wiki page ${path}.`,
|
|
84082
|
+
page
|
|
84083
|
+
};
|
|
84084
|
+
}
|
|
84085
|
+
async function handleRecall(rawArgs) {
|
|
84086
|
+
const { query } = recallSchema.parse(rawArgs);
|
|
84087
|
+
const [matches, recents] = await Promise.all([searchPages(query, 1), getRecentPages(1)]);
|
|
84088
|
+
const page = matches[0] ?? recents[0] ?? null;
|
|
84089
|
+
if (!page) {
|
|
84090
|
+
return { message: "No wiki content is available yet." };
|
|
84091
|
+
}
|
|
84092
|
+
return {
|
|
84093
|
+
message: `Best match: ${page.title}`,
|
|
84094
|
+
path: page.path,
|
|
84095
|
+
title: page.title,
|
|
84096
|
+
content: page.content,
|
|
84097
|
+
tags: page.tags
|
|
84098
|
+
};
|
|
84099
|
+
}
|
|
83946
84100
|
var executeWikiToolCall = async (toolName, rawArgs) => {
|
|
83947
84101
|
switch (toolName) {
|
|
83948
84102
|
case "wiki_read": {
|
|
@@ -83953,15 +84107,8 @@ var executeWikiToolCall = async (toolName, rawArgs) => {
|
|
|
83953
84107
|
}
|
|
83954
84108
|
return { page };
|
|
83955
84109
|
}
|
|
83956
|
-
case "wiki_write":
|
|
83957
|
-
|
|
83958
|
-
const existing = await getPage(path);
|
|
83959
|
-
const page = existing ? await updatePage(path, { title, content, tags: tags ?? [] }) : await createPage(path, title, content, tags ?? []);
|
|
83960
|
-
return {
|
|
83961
|
-
message: `${existing ? "Updated" : "Created"} wiki page ${path}.`,
|
|
83962
|
-
page
|
|
83963
|
-
};
|
|
83964
|
-
}
|
|
84110
|
+
case "wiki_write":
|
|
84111
|
+
return handleWikiWrite(rawArgs);
|
|
83965
84112
|
case "wiki_search": {
|
|
83966
84113
|
const { query, limit } = wikiSearchSchema.parse(rawArgs);
|
|
83967
84114
|
const pages = await searchPages(query, limit ?? 5);
|
|
@@ -83981,21 +84128,8 @@ var executeWikiToolCall = async (toolName, rawArgs) => {
|
|
|
83981
84128
|
page
|
|
83982
84129
|
};
|
|
83983
84130
|
}
|
|
83984
|
-
case "recall":
|
|
83985
|
-
|
|
83986
|
-
const [matches, recents] = await Promise.all([searchPages(query, 1), getRecentPages(1)]);
|
|
83987
|
-
const page = matches[0] ?? recents[0] ?? null;
|
|
83988
|
-
if (!page) {
|
|
83989
|
-
return { message: "No wiki content is available yet." };
|
|
83990
|
-
}
|
|
83991
|
-
return {
|
|
83992
|
-
message: `Best match: ${page.title}`,
|
|
83993
|
-
path: page.path,
|
|
83994
|
-
title: page.title,
|
|
83995
|
-
content: page.content,
|
|
83996
|
-
tags: page.tags
|
|
83997
|
-
};
|
|
83998
|
-
}
|
|
84131
|
+
case "recall":
|
|
84132
|
+
return handleRecall(rawArgs);
|
|
83999
84133
|
default:
|
|
84000
84134
|
throw new Error(`Unsupported wiki tool: ${toolName}`);
|
|
84001
84135
|
}
|
|
@@ -84274,8 +84408,7 @@ var Orchestrator = class {
|
|
|
84274
84408
|
}
|
|
84275
84409
|
const model = usage.model || this.activeModel || this.config.defaultModel;
|
|
84276
84410
|
const pricing = await getModelPricing(model);
|
|
84277
|
-
const
|
|
84278
|
-
const tokenUnitCost = pricing ? calculateTokenUnitCost(
|
|
84411
|
+
const cost = pricing ? calculateTokenUnitCost(
|
|
84279
84412
|
usage.inputTokens,
|
|
84280
84413
|
usage.outputTokens,
|
|
84281
84414
|
pricing.tokenInputMultiplier,
|
|
@@ -84285,9 +84418,7 @@ var Orchestrator = class {
|
|
|
84285
84418
|
model,
|
|
84286
84419
|
inputTokens: usage.inputTokens,
|
|
84287
84420
|
outputTokens: usage.outputTokens,
|
|
84288
|
-
cost
|
|
84289
|
-
premiumRequestCost,
|
|
84290
|
-
tokenUnitCost
|
|
84421
|
+
cost
|
|
84291
84422
|
});
|
|
84292
84423
|
}
|
|
84293
84424
|
};
|