heyio 4.2.3 → 4.2.5
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 +437 -366
- package/dist/daemon/index.js +437 -357
- package/package.json +1 -1
package/dist/daemon/index.js
CHANGED
|
@@ -74,19 +74,18 @@ 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.5";
|
|
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",
|
|
@@ -80829,10 +80828,16 @@ async function fetchModelCatalog() {
|
|
|
80829
80828
|
// packages/daemon/src/models/pricing-scraper.ts
|
|
80830
80829
|
var TOKEN_UNIT_COSTS_URL = "https://docs.github.com/en/billing/reference/costs-for-github-models";
|
|
80831
80830
|
var PREMIUM_MULTIPLIERS_URL = "https://docs.github.com/en/copilot/reference/copilot-billing/request-based-billing-legacy/model-multipliers-for-annual-plans";
|
|
80831
|
+
var COPILOT_PRICING_URL = "https://docs.github.com/api/article/body?pathname=/en/copilot/reference/copilot-billing/models-and-pricing";
|
|
80832
|
+
var PRICE_TO_MULTIPLIER = 0.1;
|
|
80832
80833
|
async function scrapeTokenUnitPricing() {
|
|
80833
80834
|
const html = await fetchPage(TOKEN_UNIT_COSTS_URL);
|
|
80834
80835
|
return parseTokenUnitTable(html);
|
|
80835
80836
|
}
|
|
80837
|
+
async function scrapeCopilotPricing() {
|
|
80838
|
+
const markdown = await fetchMarkdown(COPILOT_PRICING_URL);
|
|
80839
|
+
return parseCopilotPricingMarkdown(markdown);
|
|
80840
|
+
}
|
|
80836
80841
|
async function scrapePremiumRequestPricing() {
|
|
80837
80842
|
const html = await fetchPage(PREMIUM_MULTIPLIERS_URL);
|
|
80838
80843
|
return parsePremiumMultiplierTable(html);
|
|
@@ -80850,6 +80855,19 @@ async function fetchPage(url2) {
|
|
|
80850
80855
|
}
|
|
80851
80856
|
return response.text();
|
|
80852
80857
|
}
|
|
80858
|
+
async function fetchMarkdown(url2) {
|
|
80859
|
+
const response = await fetch(url2, {
|
|
80860
|
+
headers: {
|
|
80861
|
+
Accept: "text/markdown, text/plain, */*",
|
|
80862
|
+
"User-Agent": "IO-Daemon/4.0 (pricing-refresh)"
|
|
80863
|
+
},
|
|
80864
|
+
redirect: "follow"
|
|
80865
|
+
});
|
|
80866
|
+
if (!response.ok) {
|
|
80867
|
+
throw new Error(`Failed to fetch ${url2}: ${response.status} ${response.statusText}`);
|
|
80868
|
+
}
|
|
80869
|
+
return response.text();
|
|
80870
|
+
}
|
|
80853
80871
|
function parseTokenUnitTable(html) {
|
|
80854
80872
|
const results = [];
|
|
80855
80873
|
const tableRowPattern = /<tr[^>]*>\s*<td[^>]*>([^<]+)<\/td>\s*<td[^>]*>([^<]+)<\/td>\s*<td[^>]*>([^<]*)<\/td>\s*<td[^>]*>([^<]+)<\/td>/gi;
|
|
@@ -80884,6 +80902,74 @@ function parsePremiumMultiplierTable(html) {
|
|
|
80884
80902
|
function cleanCellText(text) {
|
|
80885
80903
|
return text.replace(/<[^>]*>/g, "").replace(/&[^;]+;/g, " ").trim();
|
|
80886
80904
|
}
|
|
80905
|
+
function parseCopilotPricingMarkdown(markdown) {
|
|
80906
|
+
const results = [];
|
|
80907
|
+
const seenModels = /* @__PURE__ */ new Set();
|
|
80908
|
+
const lines = markdown.split("\n");
|
|
80909
|
+
let columnIndices = null;
|
|
80910
|
+
for (const line of lines) {
|
|
80911
|
+
const trimmed = line.trim();
|
|
80912
|
+
if (!trimmed.startsWith("|")) continue;
|
|
80913
|
+
if (/\|\s*Model\s/i.test(trimmed)) {
|
|
80914
|
+
columnIndices = parseHeaderColumns(trimmed);
|
|
80915
|
+
continue;
|
|
80916
|
+
}
|
|
80917
|
+
if (/^[\s|:-]+$/.test(trimmed) || !columnIndices) continue;
|
|
80918
|
+
const entry = parseDataRow(trimmed, columnIndices);
|
|
80919
|
+
if (entry && !seenModels.has(entry.modelName.toLowerCase())) {
|
|
80920
|
+
seenModels.add(entry.modelName.toLowerCase());
|
|
80921
|
+
results.push(entry);
|
|
80922
|
+
}
|
|
80923
|
+
}
|
|
80924
|
+
return results;
|
|
80925
|
+
}
|
|
80926
|
+
function parseDataRow(line, columnIndices) {
|
|
80927
|
+
const cells = splitMarkdownRow(line);
|
|
80928
|
+
if (cells.length <= columnIndices.output) return null;
|
|
80929
|
+
const modelName = cells[columnIndices.model].trim();
|
|
80930
|
+
const inputPrice = parseDollarValue(cells[columnIndices.input]);
|
|
80931
|
+
const cachedPrice = columnIndices.cached >= 0 ? parseDollarValue(cells[columnIndices.cached]) : null;
|
|
80932
|
+
const outputPrice = parseDollarValue(cells[columnIndices.output]);
|
|
80933
|
+
if (!modelName || inputPrice === null || outputPrice === null) return null;
|
|
80934
|
+
return {
|
|
80935
|
+
modelName,
|
|
80936
|
+
inputMultiplier: inputPrice * PRICE_TO_MULTIPLIER,
|
|
80937
|
+
cachedInputMultiplier: cachedPrice !== null ? cachedPrice * PRICE_TO_MULTIPLIER : null,
|
|
80938
|
+
outputMultiplier: outputPrice * PRICE_TO_MULTIPLIER
|
|
80939
|
+
};
|
|
80940
|
+
}
|
|
80941
|
+
function parseHeaderColumns(headerLine) {
|
|
80942
|
+
const cells = splitMarkdownRow(headerLine).map((c) => c.trim().toLowerCase());
|
|
80943
|
+
const model = cells.findIndex((c) => c === "model" || c === "model name");
|
|
80944
|
+
const input = cells.findIndex(
|
|
80945
|
+
(c) => (c === "input" || c === "input multiplier") && !c.includes("cached")
|
|
80946
|
+
);
|
|
80947
|
+
const cached2 = cells.findIndex(
|
|
80948
|
+
(c) => c.includes("cached input") || c === "cached input multiplier"
|
|
80949
|
+
);
|
|
80950
|
+
const output = cells.findIndex(
|
|
80951
|
+
(c) => (c === "output" || c === "output multiplier") && !c.includes("cached")
|
|
80952
|
+
);
|
|
80953
|
+
if (model < 0 || input < 0 || output < 0) {
|
|
80954
|
+
return null;
|
|
80955
|
+
}
|
|
80956
|
+
return { model, input, cached: cached2 >= 0 ? cached2 : -1, output };
|
|
80957
|
+
}
|
|
80958
|
+
function splitMarkdownRow(line) {
|
|
80959
|
+
const parts = line.split("|");
|
|
80960
|
+
if (parts[0].trim() === "") parts.shift();
|
|
80961
|
+
if (parts[parts.length - 1]?.trim() === "") parts.pop();
|
|
80962
|
+
return parts;
|
|
80963
|
+
}
|
|
80964
|
+
function parseDollarValue(cell) {
|
|
80965
|
+
if (!cell) return null;
|
|
80966
|
+
const cleaned = cell.trim().replace(/[$,]/g, "");
|
|
80967
|
+
if (cleaned.toLowerCase() === "n/a" || cleaned === "" || cleaned === "-") {
|
|
80968
|
+
return null;
|
|
80969
|
+
}
|
|
80970
|
+
const value = Number.parseFloat(cleaned);
|
|
80971
|
+
return Number.isNaN(value) ? null : value;
|
|
80972
|
+
}
|
|
80887
80973
|
|
|
80888
80974
|
// packages/daemon/src/models/seed.ts
|
|
80889
80975
|
var SEED_MODELS = [
|
|
@@ -80911,79 +80997,99 @@ var SEED_MODELS = [
|
|
|
80911
80997
|
id: "gpt-5-mini",
|
|
80912
80998
|
displayName: "GPT-5 mini",
|
|
80913
80999
|
premiumMultiplier: 0.33,
|
|
80914
|
-
tokenInputMultiplier:
|
|
80915
|
-
tokenOutputMultiplier:
|
|
80916
|
-
cachedInputMultiplier:
|
|
81000
|
+
tokenInputMultiplier: 0.025,
|
|
81001
|
+
tokenOutputMultiplier: 0.2,
|
|
81002
|
+
cachedInputMultiplier: 25e-4,
|
|
80917
81003
|
tier: "trivial",
|
|
80918
81004
|
available: true
|
|
80919
81005
|
},
|
|
80920
81006
|
{
|
|
80921
|
-
id: "
|
|
80922
|
-
displayName: "
|
|
81007
|
+
id: "gpt-5.3-codex",
|
|
81008
|
+
displayName: "GPT-5.3-Codex",
|
|
80923
81009
|
premiumMultiplier: 6,
|
|
80924
|
-
tokenInputMultiplier: 0.
|
|
80925
|
-
tokenOutputMultiplier:
|
|
80926
|
-
cachedInputMultiplier:
|
|
81010
|
+
tokenInputMultiplier: 0.175,
|
|
81011
|
+
tokenOutputMultiplier: 1.4,
|
|
81012
|
+
cachedInputMultiplier: 0.0175,
|
|
80927
81013
|
tier: "premium",
|
|
80928
81014
|
available: true
|
|
80929
81015
|
},
|
|
80930
81016
|
{
|
|
80931
|
-
id: "
|
|
80932
|
-
displayName: "
|
|
80933
|
-
premiumMultiplier:
|
|
80934
|
-
tokenInputMultiplier:
|
|
80935
|
-
tokenOutputMultiplier:
|
|
80936
|
-
cachedInputMultiplier:
|
|
80937
|
-
tier: "
|
|
81017
|
+
id: "gpt-5.4",
|
|
81018
|
+
displayName: "GPT-5.4",
|
|
81019
|
+
premiumMultiplier: 6,
|
|
81020
|
+
tokenInputMultiplier: 0.25,
|
|
81021
|
+
tokenOutputMultiplier: 1.5,
|
|
81022
|
+
cachedInputMultiplier: 0.025,
|
|
81023
|
+
tier: "premium",
|
|
80938
81024
|
available: true
|
|
80939
81025
|
},
|
|
80940
81026
|
{
|
|
80941
|
-
id: "
|
|
80942
|
-
displayName: "
|
|
80943
|
-
premiumMultiplier:
|
|
80944
|
-
tokenInputMultiplier:
|
|
80945
|
-
tokenOutputMultiplier:
|
|
80946
|
-
cachedInputMultiplier:
|
|
80947
|
-
tier: "
|
|
81027
|
+
id: "gpt-5.4-mini",
|
|
81028
|
+
displayName: "GPT-5.4 mini",
|
|
81029
|
+
premiumMultiplier: 6,
|
|
81030
|
+
tokenInputMultiplier: 0.075,
|
|
81031
|
+
tokenOutputMultiplier: 0.45,
|
|
81032
|
+
cachedInputMultiplier: 75e-4,
|
|
81033
|
+
tier: "premium",
|
|
80948
81034
|
available: true
|
|
80949
81035
|
},
|
|
80950
81036
|
{
|
|
80951
|
-
id: "gpt-5.
|
|
80952
|
-
displayName: "GPT-5.
|
|
80953
|
-
premiumMultiplier:
|
|
80954
|
-
tokenInputMultiplier:
|
|
80955
|
-
tokenOutputMultiplier:
|
|
80956
|
-
cachedInputMultiplier:
|
|
80957
|
-
tier: "
|
|
81037
|
+
id: "gpt-5.5",
|
|
81038
|
+
displayName: "GPT-5.5",
|
|
81039
|
+
premiumMultiplier: 57,
|
|
81040
|
+
tokenInputMultiplier: 0.5,
|
|
81041
|
+
tokenOutputMultiplier: 3,
|
|
81042
|
+
cachedInputMultiplier: 0.05,
|
|
81043
|
+
tier: "ultra",
|
|
80958
81044
|
available: true
|
|
80959
81045
|
},
|
|
80960
81046
|
{
|
|
80961
|
-
id: "
|
|
80962
|
-
displayName: "
|
|
80963
|
-
premiumMultiplier:
|
|
80964
|
-
tokenInputMultiplier:
|
|
80965
|
-
tokenOutputMultiplier:
|
|
80966
|
-
cachedInputMultiplier:
|
|
80967
|
-
tier: "
|
|
81047
|
+
id: "claude-sonnet-4",
|
|
81048
|
+
displayName: "Claude Sonnet 4",
|
|
81049
|
+
premiumMultiplier: 6,
|
|
81050
|
+
tokenInputMultiplier: 0.3,
|
|
81051
|
+
tokenOutputMultiplier: 1.5,
|
|
81052
|
+
cachedInputMultiplier: 0.03,
|
|
81053
|
+
tier: "premium",
|
|
80968
81054
|
available: true
|
|
80969
81055
|
},
|
|
80970
81056
|
{
|
|
80971
|
-
id: "
|
|
80972
|
-
displayName: "
|
|
81057
|
+
id: "claude-sonnet-4.5",
|
|
81058
|
+
displayName: "Claude Sonnet 4.5",
|
|
80973
81059
|
premiumMultiplier: 6,
|
|
80974
|
-
tokenInputMultiplier:
|
|
80975
|
-
tokenOutputMultiplier:
|
|
80976
|
-
cachedInputMultiplier:
|
|
81060
|
+
tokenInputMultiplier: 0.3,
|
|
81061
|
+
tokenOutputMultiplier: 1.5,
|
|
81062
|
+
cachedInputMultiplier: 0.03,
|
|
81063
|
+
tier: "premium",
|
|
81064
|
+
available: true
|
|
81065
|
+
},
|
|
81066
|
+
{
|
|
81067
|
+
id: "claude-sonnet-4.6",
|
|
81068
|
+
displayName: "Claude Sonnet 4.6",
|
|
81069
|
+
premiumMultiplier: 9,
|
|
81070
|
+
tokenInputMultiplier: 0.3,
|
|
81071
|
+
tokenOutputMultiplier: 1.5,
|
|
81072
|
+
cachedInputMultiplier: 0.03,
|
|
80977
81073
|
tier: "premium",
|
|
80978
81074
|
available: true
|
|
80979
81075
|
},
|
|
81076
|
+
{
|
|
81077
|
+
id: "claude-haiku-4.5",
|
|
81078
|
+
displayName: "Claude Haiku 4.5",
|
|
81079
|
+
premiumMultiplier: 0.33,
|
|
81080
|
+
tokenInputMultiplier: 0.1,
|
|
81081
|
+
tokenOutputMultiplier: 0.5,
|
|
81082
|
+
cachedInputMultiplier: 0.01,
|
|
81083
|
+
tier: "trivial",
|
|
81084
|
+
available: true
|
|
81085
|
+
},
|
|
80980
81086
|
{
|
|
80981
81087
|
id: "claude-opus-4.5",
|
|
80982
81088
|
displayName: "Claude Opus 4.5",
|
|
80983
81089
|
premiumMultiplier: 15,
|
|
80984
|
-
tokenInputMultiplier:
|
|
80985
|
-
tokenOutputMultiplier:
|
|
80986
|
-
cachedInputMultiplier:
|
|
81090
|
+
tokenInputMultiplier: 0.5,
|
|
81091
|
+
tokenOutputMultiplier: 2.5,
|
|
81092
|
+
cachedInputMultiplier: 0.05,
|
|
80987
81093
|
tier: "premium",
|
|
80988
81094
|
available: true
|
|
80989
81095
|
},
|
|
@@ -80991,9 +81097,9 @@ var SEED_MODELS = [
|
|
|
80991
81097
|
id: "claude-opus-4.6",
|
|
80992
81098
|
displayName: "Claude Opus 4.6",
|
|
80993
81099
|
premiumMultiplier: 27,
|
|
80994
|
-
tokenInputMultiplier:
|
|
80995
|
-
tokenOutputMultiplier:
|
|
80996
|
-
cachedInputMultiplier:
|
|
81100
|
+
tokenInputMultiplier: 0.5,
|
|
81101
|
+
tokenOutputMultiplier: 2.5,
|
|
81102
|
+
cachedInputMultiplier: 0.05,
|
|
80997
81103
|
tier: "ultra",
|
|
80998
81104
|
available: true
|
|
80999
81105
|
},
|
|
@@ -81001,21 +81107,81 @@ var SEED_MODELS = [
|
|
|
81001
81107
|
id: "claude-opus-4.7",
|
|
81002
81108
|
displayName: "Claude Opus 4.7",
|
|
81003
81109
|
premiumMultiplier: 27,
|
|
81004
|
-
tokenInputMultiplier:
|
|
81005
|
-
tokenOutputMultiplier:
|
|
81006
|
-
cachedInputMultiplier:
|
|
81110
|
+
tokenInputMultiplier: 0.5,
|
|
81111
|
+
tokenOutputMultiplier: 2.5,
|
|
81112
|
+
cachedInputMultiplier: 0.05,
|
|
81007
81113
|
tier: "ultra",
|
|
81008
81114
|
available: true
|
|
81009
81115
|
},
|
|
81010
81116
|
{
|
|
81011
|
-
id: "
|
|
81012
|
-
displayName: "
|
|
81013
|
-
premiumMultiplier:
|
|
81014
|
-
tokenInputMultiplier:
|
|
81015
|
-
tokenOutputMultiplier:
|
|
81016
|
-
cachedInputMultiplier:
|
|
81117
|
+
id: "claude-opus-4.8",
|
|
81118
|
+
displayName: "Claude Opus 4.8",
|
|
81119
|
+
premiumMultiplier: 27,
|
|
81120
|
+
tokenInputMultiplier: 0.5,
|
|
81121
|
+
tokenOutputMultiplier: 2.5,
|
|
81122
|
+
cachedInputMultiplier: 0.05,
|
|
81017
81123
|
tier: "ultra",
|
|
81018
81124
|
available: true
|
|
81125
|
+
},
|
|
81126
|
+
{
|
|
81127
|
+
id: "gemini-2.5-pro",
|
|
81128
|
+
displayName: "Gemini 2.5 Pro",
|
|
81129
|
+
premiumMultiplier: 1,
|
|
81130
|
+
tokenInputMultiplier: 0.125,
|
|
81131
|
+
tokenOutputMultiplier: 1,
|
|
81132
|
+
cachedInputMultiplier: 0.0125,
|
|
81133
|
+
tier: "fast",
|
|
81134
|
+
available: true
|
|
81135
|
+
},
|
|
81136
|
+
{
|
|
81137
|
+
id: "gemini-3-flash",
|
|
81138
|
+
displayName: "Gemini 3 Flash",
|
|
81139
|
+
premiumMultiplier: 0.33,
|
|
81140
|
+
tokenInputMultiplier: 0.05,
|
|
81141
|
+
tokenOutputMultiplier: 0.3,
|
|
81142
|
+
cachedInputMultiplier: 5e-3,
|
|
81143
|
+
tier: "trivial",
|
|
81144
|
+
available: true
|
|
81145
|
+
},
|
|
81146
|
+
{
|
|
81147
|
+
id: "gemini-3.1-pro",
|
|
81148
|
+
displayName: "Gemini 3.1 Pro",
|
|
81149
|
+
premiumMultiplier: 6,
|
|
81150
|
+
tokenInputMultiplier: 0.2,
|
|
81151
|
+
tokenOutputMultiplier: 1.2,
|
|
81152
|
+
cachedInputMultiplier: 0.02,
|
|
81153
|
+
tier: "premium",
|
|
81154
|
+
available: true
|
|
81155
|
+
},
|
|
81156
|
+
{
|
|
81157
|
+
id: "gemini-3.5-flash",
|
|
81158
|
+
displayName: "Gemini 3.5 Flash",
|
|
81159
|
+
premiumMultiplier: 14,
|
|
81160
|
+
tokenInputMultiplier: 0.15,
|
|
81161
|
+
tokenOutputMultiplier: 0.9,
|
|
81162
|
+
cachedInputMultiplier: 0.015,
|
|
81163
|
+
tier: "premium",
|
|
81164
|
+
available: true
|
|
81165
|
+
},
|
|
81166
|
+
{
|
|
81167
|
+
id: "raptor-mini",
|
|
81168
|
+
displayName: "Raptor mini",
|
|
81169
|
+
premiumMultiplier: 0.33,
|
|
81170
|
+
tokenInputMultiplier: 0.025,
|
|
81171
|
+
tokenOutputMultiplier: 0.2,
|
|
81172
|
+
cachedInputMultiplier: 25e-4,
|
|
81173
|
+
tier: "trivial",
|
|
81174
|
+
available: true
|
|
81175
|
+
},
|
|
81176
|
+
{
|
|
81177
|
+
id: "mai-code-1-flash",
|
|
81178
|
+
displayName: "MAI-Code-1-Flash",
|
|
81179
|
+
premiumMultiplier: 0.33,
|
|
81180
|
+
tokenInputMultiplier: 0.075,
|
|
81181
|
+
tokenOutputMultiplier: 0.45,
|
|
81182
|
+
cachedInputMultiplier: 75e-4,
|
|
81183
|
+
tier: "trivial",
|
|
81184
|
+
available: true
|
|
81019
81185
|
}
|
|
81020
81186
|
];
|
|
81021
81187
|
|
|
@@ -81110,19 +81276,51 @@ async function scrapePremiumPricingIntoMap(modelMap, result, logger2) {
|
|
|
81110
81276
|
logger2?.warn(`Premium pricing scrape failed: ${msg}`);
|
|
81111
81277
|
}
|
|
81112
81278
|
}
|
|
81279
|
+
async function scrapeCopilotPricingIntoMap(modelMap, result, logger2) {
|
|
81280
|
+
try {
|
|
81281
|
+
const copilotPricing = await scrapeCopilotPricing();
|
|
81282
|
+
result.copilotPricingScraped = true;
|
|
81283
|
+
for (const cp of copilotPricing) {
|
|
81284
|
+
const key = normalizeModelName(cp.modelName);
|
|
81285
|
+
const existing = modelMap.get(key) ?? findClosestKey(modelMap, key);
|
|
81286
|
+
if (existing) {
|
|
81287
|
+
existing.tokenInputMultiplier = cp.inputMultiplier;
|
|
81288
|
+
existing.tokenOutputMultiplier = cp.outputMultiplier;
|
|
81289
|
+
if (cp.cachedInputMultiplier !== null) {
|
|
81290
|
+
existing.cachedInputMultiplier = cp.cachedInputMultiplier;
|
|
81291
|
+
}
|
|
81292
|
+
} else {
|
|
81293
|
+
modelMap.set(key, {
|
|
81294
|
+
id: key,
|
|
81295
|
+
displayName: cp.modelName,
|
|
81296
|
+
tokenInputMultiplier: cp.inputMultiplier,
|
|
81297
|
+
tokenOutputMultiplier: cp.outputMultiplier,
|
|
81298
|
+
cachedInputMultiplier: cp.cachedInputMultiplier,
|
|
81299
|
+
available: true
|
|
81300
|
+
});
|
|
81301
|
+
}
|
|
81302
|
+
}
|
|
81303
|
+
} catch (error51) {
|
|
81304
|
+
const msg = error51 instanceof Error ? error51.message : String(error51);
|
|
81305
|
+
result.errors.push(`Copilot pricing scrape failed: ${msg}`);
|
|
81306
|
+
logger2?.warn(`Copilot pricing scrape failed: ${msg}`);
|
|
81307
|
+
}
|
|
81308
|
+
}
|
|
81113
81309
|
async function refreshModelPricing(logger2) {
|
|
81114
81310
|
const result = {
|
|
81115
81311
|
modelsUpdated: 0,
|
|
81116
81312
|
catalogFetched: false,
|
|
81117
81313
|
tokenPricingScraped: false,
|
|
81118
81314
|
premiumPricingScraped: false,
|
|
81315
|
+
copilotPricingScraped: false,
|
|
81119
81316
|
errors: []
|
|
81120
81317
|
};
|
|
81121
81318
|
const modelMap = /* @__PURE__ */ new Map();
|
|
81122
81319
|
await fetchCatalogIntoMap(modelMap, result, logger2);
|
|
81123
81320
|
await scrapeTokenPricingIntoMap(modelMap, result, logger2);
|
|
81321
|
+
await scrapeCopilotPricingIntoMap(modelMap, result, logger2);
|
|
81124
81322
|
await scrapePremiumPricingIntoMap(modelMap, result, logger2);
|
|
81125
|
-
if (!result.catalogFetched && !result.tokenPricingScraped && !result.premiumPricingScraped) {
|
|
81323
|
+
if (!result.catalogFetched && !result.tokenPricingScraped && !result.premiumPricingScraped && !result.copilotPricingScraped) {
|
|
81126
81324
|
logger2?.warn("All pricing sources failed, using seed data");
|
|
81127
81325
|
await seedFromFallback();
|
|
81128
81326
|
result.modelsUpdated = SEED_MODELS.length;
|
|
@@ -81247,9 +81445,10 @@ function buildSystemPrompt(options2) {
|
|
|
81247
81445
|
const sections = [
|
|
81248
81446
|
"You are Io, a helpful AI orchestrator companion for IO v4.",
|
|
81249
81447
|
"You coordinate work across squads, wiki knowledge, installed skills, and direct execution tools.",
|
|
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.",
|
|
81448
|
+
"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.",
|
|
81251
81449
|
"Be practical, concise, and execution-oriented. Use tools when they help you produce a more accurate or durable result.",
|
|
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-
|
|
81450
|
+
"## 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.",
|
|
81451
|
+
"## 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.",
|
|
81253
81452
|
`## Squad Roster
|
|
81254
81453
|
${formatSquadRoster(options2.squads)}`,
|
|
81255
81454
|
formatOptionalSection("## Conversation Summary", options2.conversationSummary ?? ""),
|
|
@@ -82612,44 +82811,6 @@ Review rules:
|
|
|
82612
82811
|
- If approving, summarize why the work is acceptable.
|
|
82613
82812
|
- If rejecting, include concrete fixes or follow-up actions.
|
|
82614
82813
|
- Do not approve work that lacks evidence for important behavior changes.`;
|
|
82615
|
-
var ROLE_GENERATION_PROMPT = `You are staffing an autonomous software squad for a GitHub repository.
|
|
82616
|
-
|
|
82617
|
-
Given repository analysis describing languages, frameworks, file structure, architectural patterns, and operational concerns, propose the smallest effective team that can deliver objectives safely.
|
|
82618
|
-
|
|
82619
|
-
Requirements:
|
|
82620
|
-
- Mandatory roles Team Lead and QA must always exist \u2014 do NOT include them in your response.
|
|
82621
|
-
- Suggest only additional roles that are clearly justified by the repository analysis.
|
|
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.
|
|
82624
|
-
- Each role must have a short human-readable name and a concise description of responsibilities.
|
|
82625
|
-
- Avoid duplicate or overlapping roles.
|
|
82626
|
-
- Optimize for implementation, verification, and maintainability.
|
|
82627
|
-
|
|
82628
|
-
Return strict JSON in this shape:
|
|
82629
|
-
{
|
|
82630
|
-
"roles": [
|
|
82631
|
-
{
|
|
82632
|
-
"role": "principal-react-engineer",
|
|
82633
|
-
"name": "Principal React Engineer",
|
|
82634
|
-
"description": "Owns UI implementation, client state management, and browser-facing integration tests."
|
|
82635
|
-
}
|
|
82636
|
-
]
|
|
82637
|
-
}`;
|
|
82638
|
-
function generateRolePrompt(role, repoContext) {
|
|
82639
|
-
const normalizedRole = role.trim() || "specialist";
|
|
82640
|
-
return `You are the ${normalizedRole} for the IO v4 daemon squad system.
|
|
82641
|
-
|
|
82642
|
-
${ROLE_GUIDELINES}
|
|
82643
|
-
|
|
82644
|
-
Role expectations:
|
|
82645
|
-
- Own work that naturally fits the ${normalizedRole} discipline.
|
|
82646
|
-
- Make changes that align with the repository's existing architecture, conventions, and tooling.
|
|
82647
|
-
- Collaborate with the Team Lead and QA by leaving behind clear implementation notes and validation evidence.
|
|
82648
|
-
- Escalate blockers or missing context early.
|
|
82649
|
-
|
|
82650
|
-
Repository context:
|
|
82651
|
-
${repoContext}`;
|
|
82652
|
-
}
|
|
82653
82814
|
|
|
82654
82815
|
// packages/daemon/src/execution/planning.ts
|
|
82655
82816
|
var execAsync4 = promisify4(exec4);
|
|
@@ -83389,236 +83550,6 @@ ${reviewOutcome.issues?.join("\n") ?? "Resolve the review feedback and re-valida
|
|
|
83389
83550
|
}
|
|
83390
83551
|
}
|
|
83391
83552
|
|
|
83392
|
-
// packages/daemon/src/squad/hiring.ts
|
|
83393
|
-
init_dist();
|
|
83394
|
-
import { CopilotClient as CopilotClient6, approveAll as approveAll6 } from "@github/copilot-sdk";
|
|
83395
|
-
function parseRepoUrl(repoUrl) {
|
|
83396
|
-
const trimmed = repoUrl.trim();
|
|
83397
|
-
const sshMatch = trimmed.match(/^git@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
83398
|
-
if (sshMatch) {
|
|
83399
|
-
return { owner: sshMatch[1], name: sshMatch[2] };
|
|
83400
|
-
}
|
|
83401
|
-
const normalized = trimmed.replace(/\.git$/i, "");
|
|
83402
|
-
const url2 = new URL(normalized);
|
|
83403
|
-
const segments = url2.pathname.split("/").filter(Boolean);
|
|
83404
|
-
if (segments.length < 2) {
|
|
83405
|
-
throw new Error(`Unable to parse owner/name from repo URL: ${repoUrl}`);
|
|
83406
|
-
}
|
|
83407
|
-
return { owner: segments[0], name: segments[1] };
|
|
83408
|
-
}
|
|
83409
|
-
function slugifyRole(role) {
|
|
83410
|
-
return role.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
83411
|
-
}
|
|
83412
|
-
function titleCase(value) {
|
|
83413
|
-
return value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
83414
|
-
}
|
|
83415
|
-
function extractJsonObject4(content) {
|
|
83416
|
-
const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
83417
|
-
if (fenced?.[1]) {
|
|
83418
|
-
return fenced[1].trim();
|
|
83419
|
-
}
|
|
83420
|
-
const start = content.indexOf("{");
|
|
83421
|
-
const end = content.lastIndexOf("}");
|
|
83422
|
-
return start >= 0 && end > start ? content.slice(start, end + 1) : null;
|
|
83423
|
-
}
|
|
83424
|
-
function detectRolesFromAnalysis(repoAnalysis) {
|
|
83425
|
-
const analysis = repoAnalysis.toLowerCase();
|
|
83426
|
-
const roles = [];
|
|
83427
|
-
if (/(react|next|vue|angular|frontend|ui|xaml)/.test(analysis)) {
|
|
83428
|
-
roles.push({
|
|
83429
|
-
role: "senior-frontend-engineer",
|
|
83430
|
-
name: "Senior Frontend Engineer",
|
|
83431
|
-
description: "Implements user-facing experiences, UI state, and presentation-layer changes."
|
|
83432
|
-
});
|
|
83433
|
-
}
|
|
83434
|
-
if (/(node|express|api|backend|server|daemon|database|sql|postgres|sqlite)/.test(analysis)) {
|
|
83435
|
-
roles.push({
|
|
83436
|
-
role: "senior-backend-engineer",
|
|
83437
|
-
name: "Senior Backend Engineer",
|
|
83438
|
-
description: "Implements server-side logic, data access, integrations, and execution flow changes."
|
|
83439
|
-
});
|
|
83440
|
-
}
|
|
83441
|
-
if (/(infra|docker|kubernetes|deploy|ci|cd|workflow|github actions)/.test(analysis)) {
|
|
83442
|
-
roles.push({
|
|
83443
|
-
role: "senior-platform-engineer",
|
|
83444
|
-
name: "Senior Platform Engineer",
|
|
83445
|
-
description: "Owns automation, pipelines, environments, and operational tooling."
|
|
83446
|
-
});
|
|
83447
|
-
}
|
|
83448
|
-
if (/(security|auth|oauth|secret|policy)/.test(analysis)) {
|
|
83449
|
-
roles.push({
|
|
83450
|
-
role: "senior-security-engineer",
|
|
83451
|
-
name: "Senior Security Engineer",
|
|
83452
|
-
description: "Reviews authentication, authorization, secrets handling, and security-sensitive changes."
|
|
83453
|
-
});
|
|
83454
|
-
}
|
|
83455
|
-
return roles;
|
|
83456
|
-
}
|
|
83457
|
-
function dedupeRoles(roles) {
|
|
83458
|
-
const seen = /* @__PURE__ */ new Set();
|
|
83459
|
-
const output = [];
|
|
83460
|
-
for (const role of roles) {
|
|
83461
|
-
const normalizedRole = slugifyRole(role.role);
|
|
83462
|
-
if (!normalizedRole || seen.has(normalizedRole) || MANDATORY_ROLES.includes(normalizedRole)) {
|
|
83463
|
-
continue;
|
|
83464
|
-
}
|
|
83465
|
-
seen.add(normalizedRole);
|
|
83466
|
-
output.push({
|
|
83467
|
-
role: normalizedRole,
|
|
83468
|
-
name: role.name?.trim() || titleCase(normalizedRole),
|
|
83469
|
-
description: role.description?.trim() || `${titleCase(normalizedRole)} responsibilities`
|
|
83470
|
-
});
|
|
83471
|
-
}
|
|
83472
|
-
return output;
|
|
83473
|
-
}
|
|
83474
|
-
function buildMandatoryRoles() {
|
|
83475
|
-
return [
|
|
83476
|
-
{
|
|
83477
|
-
role: "team-lead",
|
|
83478
|
-
name: "Team Lead",
|
|
83479
|
-
description: "Coordinates planning, task assignment, sequencing, and final technical review."
|
|
83480
|
-
},
|
|
83481
|
-
{
|
|
83482
|
-
role: "qa",
|
|
83483
|
-
name: "QA Reviewer",
|
|
83484
|
-
description: "Validates diffs, test evidence, and objective completion before delivery."
|
|
83485
|
-
}
|
|
83486
|
-
];
|
|
83487
|
-
}
|
|
83488
|
-
function modelForRole(role) {
|
|
83489
|
-
const normalized = slugifyRole(role);
|
|
83490
|
-
if (normalized === "team-lead") {
|
|
83491
|
-
return DEFAULT_MODEL;
|
|
83492
|
-
}
|
|
83493
|
-
return DEFAULT_MODEL;
|
|
83494
|
-
}
|
|
83495
|
-
function systemPromptForRole(role, repoContext) {
|
|
83496
|
-
const normalized = slugifyRole(role);
|
|
83497
|
-
if (normalized === "team-lead") {
|
|
83498
|
-
return `${TEAM_LEAD_PROMPT}
|
|
83499
|
-
|
|
83500
|
-
Repository context:
|
|
83501
|
-
${repoContext}`;
|
|
83502
|
-
}
|
|
83503
|
-
if (normalized === "qa") {
|
|
83504
|
-
return `${QA_PROMPT}
|
|
83505
|
-
|
|
83506
|
-
Repository context:
|
|
83507
|
-
${repoContext}`;
|
|
83508
|
-
}
|
|
83509
|
-
return generateRolePrompt(normalized, repoContext);
|
|
83510
|
-
}
|
|
83511
|
-
async function proposeSquadComposition(repoUrl, repoAnalysis, context) {
|
|
83512
|
-
const mandatory = buildMandatoryRoles();
|
|
83513
|
-
const fallbackAdditional = dedupeRoles(detectRolesFromAnalysis(repoAnalysis));
|
|
83514
|
-
const contextSection = context ? `
|
|
83515
|
-
|
|
83516
|
-
Additional context from user:
|
|
83517
|
-
${context}` : "";
|
|
83518
|
-
const prompt = `Repository URL: ${repoUrl}
|
|
83519
|
-
|
|
83520
|
-
Repository analysis:
|
|
83521
|
-
${repoAnalysis}${contextSection}
|
|
83522
|
-
|
|
83523
|
-
${ROLE_GENERATION_PROMPT}`;
|
|
83524
|
-
let client2 = null;
|
|
83525
|
-
try {
|
|
83526
|
-
client2 = new CopilotClient6();
|
|
83527
|
-
await client2.start();
|
|
83528
|
-
const session = await client2.createSession({
|
|
83529
|
-
model: DEFAULT_MODEL,
|
|
83530
|
-
onPermissionRequest: approveAll6,
|
|
83531
|
-
systemMessage: {
|
|
83532
|
-
content: ROLE_GENERATION_PROMPT
|
|
83533
|
-
}
|
|
83534
|
-
});
|
|
83535
|
-
try {
|
|
83536
|
-
const response = await session.sendAndWait({ prompt }, 6e4);
|
|
83537
|
-
const content = response?.data.content?.trim() ?? "";
|
|
83538
|
-
const json2 = extractJsonObject4(content);
|
|
83539
|
-
if (!json2) {
|
|
83540
|
-
return { roles: [...mandatory, ...fallbackAdditional] };
|
|
83541
|
-
}
|
|
83542
|
-
const parsed = JSON.parse(json2);
|
|
83543
|
-
return {
|
|
83544
|
-
roles: [...mandatory, ...dedupeRoles(parsed.roles ?? fallbackAdditional)]
|
|
83545
|
-
};
|
|
83546
|
-
} finally {
|
|
83547
|
-
await session.disconnect().catch(() => void 0);
|
|
83548
|
-
}
|
|
83549
|
-
} catch {
|
|
83550
|
-
return { roles: [...mandatory, ...fallbackAdditional] };
|
|
83551
|
-
} finally {
|
|
83552
|
-
if (client2) {
|
|
83553
|
-
await client2.stop().catch(() => []);
|
|
83554
|
-
}
|
|
83555
|
-
}
|
|
83556
|
-
}
|
|
83557
|
-
async function hireSquad(repoUrl, composition, config2) {
|
|
83558
|
-
const parsedRepo = parseRepoUrl(repoUrl);
|
|
83559
|
-
const repoContext = `Repository: ${parsedRepo.owner}/${parsedRepo.name}
|
|
83560
|
-
Configured MCP servers: ${config2.mcpServers.join(", ") || "none"}
|
|
83561
|
-
QA max revisions: ${config2.maxRevisions || QA_MAX_REVISIONS}`;
|
|
83562
|
-
const existingSquad = await getSquadByRepo(parsedRepo.owner, parsedRepo.name);
|
|
83563
|
-
const normalizedConfig = {
|
|
83564
|
-
prMode: config2.prMode,
|
|
83565
|
-
mcpServers: [...config2.mcpServers],
|
|
83566
|
-
maxRevisions: config2.maxRevisions || QA_MAX_REVISIONS
|
|
83567
|
-
};
|
|
83568
|
-
const desiredRoles = composition.roles.length > 0 ? composition.roles : buildMandatoryRoles();
|
|
83569
|
-
const membersToCreate = desiredRoles.map((role) => ({
|
|
83570
|
-
role: slugifyRole(role.role),
|
|
83571
|
-
name: role.name?.trim() || titleCase(role.role),
|
|
83572
|
-
description: role.description?.trim() || `${titleCase(role.role)} responsibilities`
|
|
83573
|
-
}));
|
|
83574
|
-
let squadId;
|
|
83575
|
-
if (existingSquad) {
|
|
83576
|
-
const updatedSquad = await updateSquad(existingSquad.id, {
|
|
83577
|
-
name: `${titleCase(parsedRepo.name)} Squad`,
|
|
83578
|
-
config: normalizedConfig,
|
|
83579
|
-
status: existingSquad.status
|
|
83580
|
-
});
|
|
83581
|
-
if (!updatedSquad) {
|
|
83582
|
-
throw new Error(`Unable to update existing squad for ${repoUrl}`);
|
|
83583
|
-
}
|
|
83584
|
-
squadId = updatedSquad.id;
|
|
83585
|
-
const existingMembers = await getMembers(updatedSquad.id);
|
|
83586
|
-
await Promise.all(existingMembers.map((member) => removeMember(member.id)));
|
|
83587
|
-
} else {
|
|
83588
|
-
const squad = await createSquad({
|
|
83589
|
-
name: `${titleCase(parsedRepo.name)} Squad`,
|
|
83590
|
-
repoUrl,
|
|
83591
|
-
repoOwner: parsedRepo.owner,
|
|
83592
|
-
repoName: parsedRepo.name,
|
|
83593
|
-
status: "active",
|
|
83594
|
-
config: normalizedConfig
|
|
83595
|
-
});
|
|
83596
|
-
squadId = squad.id;
|
|
83597
|
-
}
|
|
83598
|
-
const members = [];
|
|
83599
|
-
for (const memberDefinition of membersToCreate) {
|
|
83600
|
-
const member = await addMember(squadId, {
|
|
83601
|
-
role: memberDefinition.role,
|
|
83602
|
-
name: memberDefinition.name,
|
|
83603
|
-
systemPrompt: systemPromptForRole(
|
|
83604
|
-
memberDefinition.role,
|
|
83605
|
-
`${repoContext}
|
|
83606
|
-
Role: ${memberDefinition.description}`
|
|
83607
|
-
),
|
|
83608
|
-
model: modelForRole(memberDefinition.role)
|
|
83609
|
-
});
|
|
83610
|
-
members.push(member);
|
|
83611
|
-
}
|
|
83612
|
-
const fullSquad = await getSquad(squadId);
|
|
83613
|
-
if (!fullSquad) {
|
|
83614
|
-
throw new Error(`Unable to load squad ${squadId} after hiring`);
|
|
83615
|
-
}
|
|
83616
|
-
eventBus.emit(existingSquad ? EVENT_NAMES.SQUAD_UPDATED : EVENT_NAMES.SQUAD_CREATED, {
|
|
83617
|
-
squad: fullSquad
|
|
83618
|
-
});
|
|
83619
|
-
return { squad: fullSquad, members: fullSquad.members };
|
|
83620
|
-
}
|
|
83621
|
-
|
|
83622
83553
|
// packages/daemon/src/orchestrator/tools/squad.ts
|
|
83623
83554
|
var execAsync8 = promisify8(exec8);
|
|
83624
83555
|
async function pathExists2(path) {
|
|
@@ -83629,13 +83560,40 @@ async function pathExists2(path) {
|
|
|
83629
83560
|
return false;
|
|
83630
83561
|
}
|
|
83631
83562
|
}
|
|
83632
|
-
var
|
|
83633
|
-
repoUrl: external_exports.string().trim().min(1).describe("The repository URL to
|
|
83634
|
-
|
|
83635
|
-
|
|
83563
|
+
var createSquadSchema = external_exports.object({
|
|
83564
|
+
repoUrl: external_exports.string().trim().min(1).describe("The repository URL to create a squad for"),
|
|
83565
|
+
name: external_exports.string().trim().optional().describe("Squad name. Defaults to '<RepoName> Squad' if not provided."),
|
|
83566
|
+
config: external_exports.object({
|
|
83567
|
+
prMode: external_exports.enum(["draft-pr", "branch-only", "ready-pr", "auto-merge"]).optional().describe("PR creation mode. Defaults to 'draft-pr'."),
|
|
83568
|
+
mcpServers: external_exports.array(external_exports.string()).optional().describe("MCP server URLs to configure for the squad."),
|
|
83569
|
+
maxRevisions: external_exports.number().optional().describe("Max QA revision cycles. Defaults to system default.")
|
|
83570
|
+
}).optional().describe("Squad configuration. All fields are optional with sensible defaults.")
|
|
83571
|
+
});
|
|
83572
|
+
var addSquadMemberSchema = external_exports.object({
|
|
83573
|
+
squadId: external_exports.string().trim().min(1),
|
|
83574
|
+
role: external_exports.string().trim().min(1).describe("Slug-style role identifier (e.g. 'senior-frontend-engineer', 'team-lead')"),
|
|
83575
|
+
name: external_exports.string().trim().min(1).describe("Display name for the member (e.g. 'Senior Frontend Engineer')"),
|
|
83576
|
+
systemPrompt: external_exports.string().min(1).describe(
|
|
83577
|
+
"The system prompt defining this member's expertise, responsibilities, and behavior."
|
|
83636
83578
|
),
|
|
83579
|
+
model: external_exports.string().optional().describe("Model override for this member. Uses squad default if not provided.")
|
|
83580
|
+
});
|
|
83581
|
+
var removeSquadMemberSchema = external_exports.object({
|
|
83582
|
+
squadId: external_exports.string().trim().min(1),
|
|
83583
|
+
memberId: external_exports.string().trim().min(1)
|
|
83584
|
+
});
|
|
83585
|
+
var updateSquadConfigSchema = external_exports.object({
|
|
83586
|
+
squadId: external_exports.string().trim().min(1),
|
|
83587
|
+
config: external_exports.object({
|
|
83588
|
+
prMode: external_exports.enum(["draft-pr", "branch-only", "ready-pr", "auto-merge"]).optional(),
|
|
83589
|
+
mcpServers: external_exports.array(external_exports.string()).optional(),
|
|
83590
|
+
maxRevisions: external_exports.number().optional()
|
|
83591
|
+
}).describe("Partial config fields to update. Only provided fields are changed.")
|
|
83592
|
+
});
|
|
83593
|
+
var analyzeRepoSchema = external_exports.object({
|
|
83594
|
+
repoUrl: external_exports.string().trim().min(1).describe("The repository URL to analyze"),
|
|
83637
83595
|
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
|
|
83596
|
+
"Relative paths within the repository to focus the analysis on. When provided, only these directories are scanned instead of the entire repo."
|
|
83639
83597
|
)
|
|
83640
83598
|
});
|
|
83641
83599
|
var squadIdSchema = external_exports.object({
|
|
@@ -83658,9 +83616,33 @@ var updateSquadMemberSchema = external_exports.object({
|
|
|
83658
83616
|
});
|
|
83659
83617
|
var squadToolDefinitions = [
|
|
83660
83618
|
{
|
|
83661
|
-
name: "
|
|
83662
|
-
description: "
|
|
83663
|
-
parameters:
|
|
83619
|
+
name: "create_squad",
|
|
83620
|
+
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.",
|
|
83621
|
+
parameters: createSquadSchema,
|
|
83622
|
+
skipPermission: true
|
|
83623
|
+
},
|
|
83624
|
+
{
|
|
83625
|
+
name: "add_squad_member",
|
|
83626
|
+
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.",
|
|
83627
|
+
parameters: addSquadMemberSchema,
|
|
83628
|
+
skipPermission: true
|
|
83629
|
+
},
|
|
83630
|
+
{
|
|
83631
|
+
name: "remove_squad_member",
|
|
83632
|
+
description: "Remove a member from a squad by their ID.",
|
|
83633
|
+
parameters: removeSquadMemberSchema,
|
|
83634
|
+
skipPermission: true
|
|
83635
|
+
},
|
|
83636
|
+
{
|
|
83637
|
+
name: "update_squad_config",
|
|
83638
|
+
description: "Update a squad's configuration (prMode, mcpServers, maxRevisions).",
|
|
83639
|
+
parameters: updateSquadConfigSchema,
|
|
83640
|
+
skipPermission: true
|
|
83641
|
+
},
|
|
83642
|
+
{
|
|
83643
|
+
name: "analyze_repo",
|
|
83644
|
+
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.",
|
|
83645
|
+
parameters: analyzeRepoSchema,
|
|
83664
83646
|
skipPermission: true
|
|
83665
83647
|
},
|
|
83666
83648
|
{
|
|
@@ -83700,12 +83682,22 @@ var squadToolDefinitions = [
|
|
|
83700
83682
|
skipPermission: true
|
|
83701
83683
|
}
|
|
83702
83684
|
];
|
|
83703
|
-
function
|
|
83704
|
-
|
|
83705
|
-
|
|
83706
|
-
|
|
83707
|
-
|
|
83708
|
-
}
|
|
83685
|
+
function parseRepoUrl(repoUrl) {
|
|
83686
|
+
const trimmed = repoUrl.trim();
|
|
83687
|
+
const sshMatch = trimmed.match(/^git@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
83688
|
+
if (sshMatch) {
|
|
83689
|
+
return { owner: sshMatch[1], name: sshMatch[2] };
|
|
83690
|
+
}
|
|
83691
|
+
const normalized = trimmed.replace(/\.git$/i, "");
|
|
83692
|
+
const url2 = new URL(normalized);
|
|
83693
|
+
const segments = url2.pathname.split("/").filter(Boolean);
|
|
83694
|
+
if (segments.length < 2) {
|
|
83695
|
+
throw new Error(`Unable to parse owner/name from repo URL: ${repoUrl}`);
|
|
83696
|
+
}
|
|
83697
|
+
return { owner: segments[0], name: segments[1] };
|
|
83698
|
+
}
|
|
83699
|
+
function titleCase(value) {
|
|
83700
|
+
return value.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
83709
83701
|
}
|
|
83710
83702
|
var MANIFEST_FILES = [
|
|
83711
83703
|
"package.json",
|
|
@@ -83889,18 +83881,98 @@ async function startAndExecuteInstance(instanceId, squad, objectiveId) {
|
|
|
83889
83881
|
await processQueue2(squad.id);
|
|
83890
83882
|
}
|
|
83891
83883
|
}
|
|
83892
|
-
async function
|
|
83893
|
-
const { repoUrl,
|
|
83894
|
-
const
|
|
83884
|
+
async function handleCreateSquad(rawArgs) {
|
|
83885
|
+
const { repoUrl, name, config: config2 } = createSquadSchema.parse(rawArgs);
|
|
83886
|
+
const parsedRepo = parseRepoUrl(repoUrl);
|
|
83887
|
+
const existingSquad = await getSquadByRepo(parsedRepo.owner, parsedRepo.name);
|
|
83888
|
+
if (existingSquad) {
|
|
83889
|
+
return {
|
|
83890
|
+
message: `A squad already exists for ${parsedRepo.owner}/${parsedRepo.name}.`,
|
|
83891
|
+
squad: existingSquad
|
|
83892
|
+
};
|
|
83893
|
+
}
|
|
83894
|
+
const squadName = name || `${titleCase(parsedRepo.name)} Squad`;
|
|
83895
|
+
const squadConfig = {
|
|
83896
|
+
prMode: config2?.prMode ?? "draft-pr",
|
|
83897
|
+
mcpServers: config2?.mcpServers ?? [],
|
|
83898
|
+
maxRevisions: config2?.maxRevisions ?? QA_MAX_REVISIONS
|
|
83899
|
+
};
|
|
83900
|
+
const squad = await createSquad({
|
|
83901
|
+
name: squadName,
|
|
83895
83902
|
repoUrl,
|
|
83896
|
-
|
|
83897
|
-
|
|
83898
|
-
|
|
83899
|
-
|
|
83903
|
+
repoOwner: parsedRepo.owner,
|
|
83904
|
+
repoName: parsedRepo.name,
|
|
83905
|
+
status: "active",
|
|
83906
|
+
config: squadConfig
|
|
83907
|
+
});
|
|
83908
|
+
eventBus.emit(EVENT_NAMES.SQUAD_CREATED, { squad });
|
|
83909
|
+
return {
|
|
83910
|
+
message: `Squad "${squad.name}" created for ${parsedRepo.owner}/${parsedRepo.name}. Add members with add_squad_member.`,
|
|
83911
|
+
squad
|
|
83912
|
+
};
|
|
83913
|
+
}
|
|
83914
|
+
async function handleAddSquadMember(rawArgs) {
|
|
83915
|
+
const { squadId, role, name, systemPrompt, model } = addSquadMemberSchema.parse(rawArgs);
|
|
83916
|
+
const squad = await getSquad(squadId);
|
|
83917
|
+
if (!squad) {
|
|
83918
|
+
throw new Error(`Squad ${squadId} was not found.`);
|
|
83919
|
+
}
|
|
83920
|
+
const member = await addMember(squadId, {
|
|
83921
|
+
role,
|
|
83922
|
+
name,
|
|
83923
|
+
systemPrompt,
|
|
83924
|
+
model: model ?? null
|
|
83925
|
+
});
|
|
83926
|
+
eventBus.emit(EVENT_NAMES.SQUAD_MEMBER_UPDATED, { squadId, member });
|
|
83927
|
+
return {
|
|
83928
|
+
message: `Added member "${member.name}" (${member.role}) to squad "${squad.name}".`,
|
|
83929
|
+
member
|
|
83930
|
+
};
|
|
83931
|
+
}
|
|
83932
|
+
async function handleRemoveSquadMember(rawArgs) {
|
|
83933
|
+
const { squadId, memberId } = removeSquadMemberSchema.parse(rawArgs);
|
|
83934
|
+
const squad = await getSquad(squadId);
|
|
83935
|
+
if (!squad) {
|
|
83936
|
+
throw new Error(`Squad ${squadId} was not found.`);
|
|
83937
|
+
}
|
|
83938
|
+
const member = await getMember(memberId);
|
|
83939
|
+
if (!member || member.squadId !== squadId) {
|
|
83940
|
+
throw new Error(`Member ${memberId} was not found in squad ${squadId}.`);
|
|
83941
|
+
}
|
|
83942
|
+
await removeMember(memberId);
|
|
83943
|
+
eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad });
|
|
83944
|
+
return {
|
|
83945
|
+
message: `Removed member "${member.name}" (${member.role}) from squad "${squad.name}".`,
|
|
83946
|
+
memberId
|
|
83947
|
+
};
|
|
83948
|
+
}
|
|
83949
|
+
async function handleUpdateSquadConfig(rawArgs) {
|
|
83950
|
+
const { squadId, config: config2 } = updateSquadConfigSchema.parse(rawArgs);
|
|
83951
|
+
const squad = await getSquad(squadId);
|
|
83952
|
+
if (!squad) {
|
|
83953
|
+
throw new Error(`Squad ${squadId} was not found.`);
|
|
83954
|
+
}
|
|
83955
|
+
const mergedConfig = {
|
|
83956
|
+
prMode: config2.prMode ?? squad.config.prMode,
|
|
83957
|
+
mcpServers: config2.mcpServers ?? squad.config.mcpServers,
|
|
83958
|
+
maxRevisions: config2.maxRevisions ?? squad.config.maxRevisions
|
|
83959
|
+
};
|
|
83960
|
+
const updated = await updateSquad(squadId, { config: mergedConfig });
|
|
83961
|
+
if (!updated) {
|
|
83962
|
+
throw new Error(`Failed to update squad ${squadId}.`);
|
|
83963
|
+
}
|
|
83964
|
+
eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad: updated });
|
|
83965
|
+
return {
|
|
83966
|
+
message: `Updated config for squad "${updated.name}".`,
|
|
83967
|
+
squad: updated
|
|
83968
|
+
};
|
|
83969
|
+
}
|
|
83970
|
+
async function handleAnalyzeRepo(rawArgs) {
|
|
83971
|
+
const { repoUrl, scanPaths } = analyzeRepoSchema.parse(rawArgs);
|
|
83972
|
+
const analysis = await buildRepoAnalysis(repoUrl, scanPaths);
|
|
83900
83973
|
return {
|
|
83901
|
-
message:
|
|
83902
|
-
|
|
83903
|
-
members: result.members
|
|
83974
|
+
message: "Repository analysis complete.",
|
|
83975
|
+
analysis
|
|
83904
83976
|
};
|
|
83905
83977
|
}
|
|
83906
83978
|
async function handleFireSquad(rawArgs) {
|
|
@@ -83969,11 +84041,19 @@ async function handleUpdateSquadMember(rawArgs) {
|
|
|
83969
84041
|
member: updated
|
|
83970
84042
|
};
|
|
83971
84043
|
}
|
|
83972
|
-
function createSquadToolExecutor(
|
|
84044
|
+
function createSquadToolExecutor(_config) {
|
|
83973
84045
|
return async (toolName, rawArgs) => {
|
|
83974
84046
|
switch (toolName) {
|
|
83975
|
-
case "
|
|
83976
|
-
return
|
|
84047
|
+
case "create_squad":
|
|
84048
|
+
return handleCreateSquad(rawArgs);
|
|
84049
|
+
case "add_squad_member":
|
|
84050
|
+
return handleAddSquadMember(rawArgs);
|
|
84051
|
+
case "remove_squad_member":
|
|
84052
|
+
return handleRemoveSquadMember(rawArgs);
|
|
84053
|
+
case "update_squad_config":
|
|
84054
|
+
return handleUpdateSquadConfig(rawArgs);
|
|
84055
|
+
case "analyze_repo":
|
|
84056
|
+
return handleAnalyzeRepo(rawArgs);
|
|
83977
84057
|
case "fire_squad":
|
|
83978
84058
|
return handleFireSquad(rawArgs);
|
|
83979
84059
|
case "list_squads": {
|