heyio 4.2.4 → 4.2.7

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.
@@ -80,7 +80,7 @@ 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.4";
83
+ APP_VERSION = "4.2.7";
84
84
  API_PORT = 7777;
85
85
  API_HOST = "0.0.0.0";
86
86
  DEFAULT_MODEL = "gpt-4o";
@@ -2121,6 +2121,11 @@ var init_db = __esm({
2121
2121
  agent_name = (SELECT sm.name FROM squad_members sm WHERE sm.id = token_usage.agent_id)
2122
2122
  WHERE squad_id IS NOT NULL OR agent_id IS NOT NULL`
2123
2123
  ]
2124
+ },
2125
+ {
2126
+ version: 5,
2127
+ name: "add-squad-soft-delete",
2128
+ statements: ["ALTER TABLE squads ADD COLUMN deleted_at TEXT"]
2124
2129
  }
2125
2130
  ];
2126
2131
  client = null;
@@ -2144,7 +2149,8 @@ async function createSquad(data, db) {
2144
2149
  status: data.status ?? "active",
2145
2150
  config: data.config,
2146
2151
  createdAt: timestamp,
2147
- updatedAt: timestamp
2152
+ updatedAt: timestamp,
2153
+ deletedAt: null
2148
2154
  };
2149
2155
  await database.execute({
2150
2156
  sql: `INSERT INTO squads (id, name, repo_url, repo_owner, repo_name, status, config, created_at, updated_at)
@@ -2166,7 +2172,7 @@ async function createSquad(data, db) {
2166
2172
  async function getSquad(id, db) {
2167
2173
  const database = db ?? await getDatabase();
2168
2174
  const squadResult = await database.execute({
2169
- sql: "SELECT * FROM squads WHERE id = ? LIMIT 1",
2175
+ sql: "SELECT * FROM squads WHERE id = ? AND deleted_at IS NULL LIMIT 1",
2170
2176
  args: [id]
2171
2177
  });
2172
2178
  const row = squadResult.rows[0];
@@ -2182,7 +2188,7 @@ async function getSquad(id, db) {
2182
2188
  async function getSquadByName(name, db) {
2183
2189
  const database = db ?? await getDatabase();
2184
2190
  const result = await database.execute({
2185
- sql: "SELECT * FROM squads WHERE name = ? LIMIT 1",
2191
+ sql: "SELECT * FROM squads WHERE name = ? AND deleted_at IS NULL LIMIT 1",
2186
2192
  args: [name]
2187
2193
  });
2188
2194
  const row = result.rows[0];
@@ -2195,7 +2201,9 @@ async function getSquadByName(name, db) {
2195
2201
  }
2196
2202
  async function listSquads(db) {
2197
2203
  const database = db ?? await getDatabase();
2198
- const result = await database.execute("SELECT * FROM squads ORDER BY created_at DESC, id DESC");
2204
+ const result = await database.execute(
2205
+ "SELECT * FROM squads WHERE deleted_at IS NULL ORDER BY created_at DESC, id DESC"
2206
+ );
2199
2207
  return result.rows.map((row) => mapSquad(row));
2200
2208
  }
2201
2209
  async function updateSquad(id, data, db) {
@@ -2214,7 +2222,8 @@ async function updateSquad(id, data, db) {
2214
2222
  status: data.status ?? existing.status,
2215
2223
  config: data.config ?? existing.config,
2216
2224
  createdAt: existing.createdAt,
2217
- updatedAt
2225
+ updatedAt,
2226
+ deletedAt: existing.deletedAt
2218
2227
  };
2219
2228
  await database.execute({
2220
2229
  sql: `UPDATE squads
@@ -2235,16 +2244,34 @@ async function updateSquad(id, data, db) {
2235
2244
  }
2236
2245
  async function deleteSquad(id, db) {
2237
2246
  const database = db ?? await getDatabase();
2247
+ const now = nowIso();
2238
2248
  const result = await database.execute({
2239
- sql: "DELETE FROM squads WHERE id = ?",
2240
- args: [id]
2249
+ sql: "UPDATE squads SET status = 'deleted', deleted_at = ?, updated_at = ? WHERE id = ? AND deleted_at IS NULL",
2250
+ args: [now, now, id]
2241
2251
  });
2242
2252
  return result.rowsAffected > 0;
2243
2253
  }
2254
+ async function restoreSquad(id, db) {
2255
+ const database = db ?? await getDatabase();
2256
+ const now = nowIso();
2257
+ const result = await database.execute({
2258
+ sql: "UPDATE squads SET status = 'active', deleted_at = NULL, updated_at = ? WHERE id = ? AND deleted_at IS NOT NULL",
2259
+ args: [now, id]
2260
+ });
2261
+ if (result.rowsAffected === 0) return null;
2262
+ return getSquadRow(id, database);
2263
+ }
2264
+ async function listDeletedSquads(db) {
2265
+ const database = db ?? await getDatabase();
2266
+ const result = await database.execute(
2267
+ "SELECT * FROM squads WHERE deleted_at IS NOT NULL ORDER BY deleted_at DESC, id DESC"
2268
+ );
2269
+ return result.rows.map((row) => mapSquad(row));
2270
+ }
2244
2271
  async function getSquadByRepo(repoOwner, repoName, db) {
2245
2272
  const database = db ?? await getDatabase();
2246
2273
  const result = await database.execute({
2247
- sql: "SELECT * FROM squads WHERE repo_owner = ? AND repo_name = ? LIMIT 1",
2274
+ sql: "SELECT * FROM squads WHERE repo_owner = ? AND repo_name = ? AND deleted_at IS NULL LIMIT 1",
2248
2275
  args: [repoOwner, repoName]
2249
2276
  });
2250
2277
  const row = result.rows[0];
@@ -2348,7 +2375,8 @@ function mapSquad(row) {
2348
2375
  status: asString(row.status),
2349
2376
  config: parseJson(asString(row.config)),
2350
2377
  createdAt: asString(row.created_at),
2351
- updatedAt: asString(row.updated_at)
2378
+ updatedAt: asString(row.updated_at),
2379
+ deletedAt: asNullableString(row.deleted_at)
2352
2380
  };
2353
2381
  }
2354
2382
  function mapMember(row) {
@@ -52850,6 +52878,39 @@ var init_squads2 = __esm({
52850
52878
  });
52851
52879
  }
52852
52880
  });
52881
+ router7.get("/api/squads/deleted", async (_req, res) => {
52882
+ try {
52883
+ const squads = await listDeletedSquads();
52884
+ res.status(200).json({ squads });
52885
+ } catch (error51) {
52886
+ res.status(500).json({
52887
+ error: "Failed to list deleted squads",
52888
+ details: error51 instanceof Error ? error51.message : "Unknown error"
52889
+ });
52890
+ }
52891
+ });
52892
+ router7.post("/api/squads/:id/restore", async (req, res) => {
52893
+ try {
52894
+ const restored = await restoreSquad(req.params.id);
52895
+ if (!restored) {
52896
+ res.status(404).json({ error: "Squad not found or not deleted" });
52897
+ return;
52898
+ }
52899
+ await logActivity({
52900
+ squadId: restored.id,
52901
+ event: EVENT_NAMES.SQUAD_UPDATED,
52902
+ description: `Restored squad ${restored.name}`,
52903
+ metadata: { repoUrl: restored.repoUrl }
52904
+ });
52905
+ eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad: restored });
52906
+ res.status(200).json({ squad: restored });
52907
+ } catch (error51) {
52908
+ res.status(500).json({
52909
+ error: "Failed to restore squad",
52910
+ details: error51 instanceof Error ? error51.message : "Unknown error"
52911
+ });
52912
+ }
52913
+ });
52853
52914
  router7.get("/api/squads/:id/members", async (req, res) => {
52854
52915
  try {
52855
52916
  const squad = await resolveSquad(req.params.id);
@@ -67985,6 +68046,10 @@ async function scrapeTokenUnitPricing() {
67985
68046
  const html = await fetchPage(TOKEN_UNIT_COSTS_URL);
67986
68047
  return parseTokenUnitTable(html);
67987
68048
  }
68049
+ async function scrapeCopilotPricing() {
68050
+ const markdown = await fetchMarkdown(COPILOT_PRICING_URL);
68051
+ return parseCopilotPricingMarkdown(markdown);
68052
+ }
67988
68053
  async function scrapePremiumRequestPricing() {
67989
68054
  const html = await fetchPage(PREMIUM_MULTIPLIERS_URL);
67990
68055
  return parsePremiumMultiplierTable(html);
@@ -68002,6 +68067,19 @@ async function fetchPage(url2) {
68002
68067
  }
68003
68068
  return response.text();
68004
68069
  }
68070
+ async function fetchMarkdown(url2) {
68071
+ const response = await fetch(url2, {
68072
+ headers: {
68073
+ Accept: "text/markdown, text/plain, */*",
68074
+ "User-Agent": "IO-Daemon/4.0 (pricing-refresh)"
68075
+ },
68076
+ redirect: "follow"
68077
+ });
68078
+ if (!response.ok) {
68079
+ throw new Error(`Failed to fetch ${url2}: ${response.status} ${response.statusText}`);
68080
+ }
68081
+ return response.text();
68082
+ }
68005
68083
  function parseTokenUnitTable(html) {
68006
68084
  const results = [];
68007
68085
  const tableRowPattern = /<tr[^>]*>\s*<td[^>]*>([^<]+)<\/td>\s*<td[^>]*>([^<]+)<\/td>\s*<td[^>]*>([^<]*)<\/td>\s*<td[^>]*>([^<]+)<\/td>/gi;
@@ -68036,152 +68114,82 @@ function parsePremiumMultiplierTable(html) {
68036
68114
  function cleanCellText(text) {
68037
68115
  return text.replace(/<[^>]*>/g, "").replace(/&[^;]+;/g, " ").trim();
68038
68116
  }
68039
- var TOKEN_UNIT_COSTS_URL, PREMIUM_MULTIPLIERS_URL;
68117
+ function parseCopilotPricingMarkdown(markdown) {
68118
+ const results = [];
68119
+ const seenModels = /* @__PURE__ */ new Set();
68120
+ const lines = markdown.split("\n");
68121
+ let columnIndices = null;
68122
+ for (const line of lines) {
68123
+ const trimmed = line.trim();
68124
+ if (!trimmed.startsWith("|")) continue;
68125
+ if (/\|\s*Model\s/i.test(trimmed)) {
68126
+ columnIndices = parseHeaderColumns(trimmed);
68127
+ continue;
68128
+ }
68129
+ if (/^[\s|:-]+$/.test(trimmed) || !columnIndices) continue;
68130
+ const entry = parseDataRow(trimmed, columnIndices);
68131
+ if (entry && !seenModels.has(entry.modelName.toLowerCase())) {
68132
+ seenModels.add(entry.modelName.toLowerCase());
68133
+ results.push(entry);
68134
+ }
68135
+ }
68136
+ return results;
68137
+ }
68138
+ function parseDataRow(line, columnIndices) {
68139
+ const cells = splitMarkdownRow(line);
68140
+ if (cells.length <= columnIndices.output) return null;
68141
+ const modelName = cells[columnIndices.model].trim();
68142
+ const inputPrice = parseDollarValue(cells[columnIndices.input]);
68143
+ const cachedPrice = columnIndices.cached >= 0 ? parseDollarValue(cells[columnIndices.cached]) : null;
68144
+ const outputPrice = parseDollarValue(cells[columnIndices.output]);
68145
+ if (!modelName || inputPrice === null || outputPrice === null) return null;
68146
+ return {
68147
+ modelName,
68148
+ inputMultiplier: inputPrice * PRICE_TO_MULTIPLIER,
68149
+ cachedInputMultiplier: cachedPrice !== null ? cachedPrice * PRICE_TO_MULTIPLIER : null,
68150
+ outputMultiplier: outputPrice * PRICE_TO_MULTIPLIER
68151
+ };
68152
+ }
68153
+ function parseHeaderColumns(headerLine) {
68154
+ const cells = splitMarkdownRow(headerLine).map((c) => c.trim().toLowerCase());
68155
+ const model = cells.findIndex((c) => c === "model" || c === "model name");
68156
+ const input2 = cells.findIndex(
68157
+ (c) => (c === "input" || c === "input multiplier") && !c.includes("cached")
68158
+ );
68159
+ const cached2 = cells.findIndex(
68160
+ (c) => c.includes("cached input") || c === "cached input multiplier"
68161
+ );
68162
+ const output2 = cells.findIndex(
68163
+ (c) => (c === "output" || c === "output multiplier") && !c.includes("cached")
68164
+ );
68165
+ if (model < 0 || input2 < 0 || output2 < 0) {
68166
+ return null;
68167
+ }
68168
+ return { model, input: input2, cached: cached2 >= 0 ? cached2 : -1, output: output2 };
68169
+ }
68170
+ function splitMarkdownRow(line) {
68171
+ const parts = line.split("|");
68172
+ if (parts[0].trim() === "") parts.shift();
68173
+ if (parts[parts.length - 1]?.trim() === "") parts.pop();
68174
+ return parts;
68175
+ }
68176
+ function parseDollarValue(cell) {
68177
+ if (!cell) return null;
68178
+ const cleaned = cell.trim().replace(/[$,]/g, "");
68179
+ if (cleaned.toLowerCase() === "n/a" || cleaned === "" || cleaned === "-") {
68180
+ return null;
68181
+ }
68182
+ const value = Number.parseFloat(cleaned);
68183
+ return Number.isNaN(value) ? null : value;
68184
+ }
68185
+ var TOKEN_UNIT_COSTS_URL, PREMIUM_MULTIPLIERS_URL, COPILOT_PRICING_URL, PRICE_TO_MULTIPLIER;
68040
68186
  var init_pricing_scraper = __esm({
68041
68187
  "packages/daemon/src/models/pricing-scraper.ts"() {
68042
68188
  "use strict";
68043
68189
  TOKEN_UNIT_COSTS_URL = "https://docs.github.com/en/billing/reference/costs-for-github-models";
68044
68190
  PREMIUM_MULTIPLIERS_URL = "https://docs.github.com/en/copilot/reference/copilot-billing/request-based-billing-legacy/model-multipliers-for-annual-plans";
68045
- }
68046
- });
68047
-
68048
- // packages/daemon/src/models/seed.ts
68049
- var SEED_MODELS;
68050
- var init_seed = __esm({
68051
- "packages/daemon/src/models/seed.ts"() {
68052
- "use strict";
68053
- SEED_MODELS = [
68054
- {
68055
- id: "gpt-4o-mini",
68056
- displayName: "OpenAI GPT-4o mini",
68057
- premiumMultiplier: 0.33,
68058
- tokenInputMultiplier: 0.015,
68059
- tokenOutputMultiplier: 0.06,
68060
- cachedInputMultiplier: 75e-4,
68061
- tier: "trivial",
68062
- available: true
68063
- },
68064
- {
68065
- id: "gpt-4o",
68066
- displayName: "OpenAI GPT-4o",
68067
- premiumMultiplier: 0.33,
68068
- tokenInputMultiplier: 0.25,
68069
- tokenOutputMultiplier: 1,
68070
- cachedInputMultiplier: 0.125,
68071
- tier: "trivial",
68072
- available: true
68073
- },
68074
- {
68075
- id: "gpt-5-mini",
68076
- displayName: "GPT-5 mini",
68077
- premiumMultiplier: 0.33,
68078
- tokenInputMultiplier: null,
68079
- tokenOutputMultiplier: null,
68080
- cachedInputMultiplier: null,
68081
- tier: "trivial",
68082
- available: true
68083
- },
68084
- {
68085
- id: "claude-sonnet-4",
68086
- displayName: "Claude Sonnet 4",
68087
- premiumMultiplier: 6,
68088
- tokenInputMultiplier: 0.6,
68089
- tokenOutputMultiplier: 2.4,
68090
- cachedInputMultiplier: null,
68091
- tier: "premium",
68092
- available: true
68093
- },
68094
- {
68095
- id: "claude-haiku-4.5",
68096
- displayName: "Claude Haiku 4.5",
68097
- premiumMultiplier: 0.33,
68098
- tokenInputMultiplier: null,
68099
- tokenOutputMultiplier: null,
68100
- cachedInputMultiplier: null,
68101
- tier: "trivial",
68102
- available: true
68103
- },
68104
- {
68105
- id: "gemini-2.5-pro",
68106
- displayName: "Gemini 2.5 Pro",
68107
- premiumMultiplier: 1,
68108
- tokenInputMultiplier: null,
68109
- tokenOutputMultiplier: null,
68110
- cachedInputMultiplier: null,
68111
- tier: "fast",
68112
- available: true
68113
- },
68114
- {
68115
- id: "gpt-5.1",
68116
- displayName: "GPT-5.1",
68117
- premiumMultiplier: 3,
68118
- tokenInputMultiplier: null,
68119
- tokenOutputMultiplier: null,
68120
- cachedInputMultiplier: null,
68121
- tier: "standard",
68122
- available: true
68123
- },
68124
- {
68125
- id: "gpt-5.2",
68126
- displayName: "GPT-5.2",
68127
- premiumMultiplier: 3,
68128
- tokenInputMultiplier: null,
68129
- tokenOutputMultiplier: null,
68130
- cachedInputMultiplier: null,
68131
- tier: "standard",
68132
- available: true
68133
- },
68134
- {
68135
- id: "gpt-5.4",
68136
- displayName: "GPT-5.4",
68137
- premiumMultiplier: 6,
68138
- tokenInputMultiplier: null,
68139
- tokenOutputMultiplier: null,
68140
- cachedInputMultiplier: null,
68141
- tier: "premium",
68142
- available: true
68143
- },
68144
- {
68145
- id: "claude-opus-4.5",
68146
- displayName: "Claude Opus 4.5",
68147
- premiumMultiplier: 15,
68148
- tokenInputMultiplier: null,
68149
- tokenOutputMultiplier: null,
68150
- cachedInputMultiplier: null,
68151
- tier: "premium",
68152
- available: true
68153
- },
68154
- {
68155
- id: "claude-opus-4.6",
68156
- displayName: "Claude Opus 4.6",
68157
- premiumMultiplier: 27,
68158
- tokenInputMultiplier: null,
68159
- tokenOutputMultiplier: null,
68160
- cachedInputMultiplier: null,
68161
- tier: "ultra",
68162
- available: true
68163
- },
68164
- {
68165
- id: "claude-opus-4.7",
68166
- displayName: "Claude Opus 4.7",
68167
- premiumMultiplier: 27,
68168
- tokenInputMultiplier: null,
68169
- tokenOutputMultiplier: null,
68170
- cachedInputMultiplier: null,
68171
- tier: "ultra",
68172
- available: true
68173
- },
68174
- {
68175
- id: "gpt-5.5",
68176
- displayName: "GPT-5.5",
68177
- premiumMultiplier: 57,
68178
- tokenInputMultiplier: null,
68179
- tokenOutputMultiplier: null,
68180
- cachedInputMultiplier: null,
68181
- tier: "ultra",
68182
- available: true
68183
- }
68184
- ];
68191
+ COPILOT_PRICING_URL = "https://docs.github.com/api/article/body?pathname=/en/copilot/reference/copilot-billing/models-and-pricing";
68192
+ PRICE_TO_MULTIPLIER = 0.1;
68185
68193
  }
68186
68194
  });
68187
68195
 
@@ -68282,22 +68290,52 @@ async function scrapePremiumPricingIntoMap(modelMap, result, logger2) {
68282
68290
  logger2?.warn(`Premium pricing scrape failed: ${msg}`);
68283
68291
  }
68284
68292
  }
68293
+ async function scrapeCopilotPricingIntoMap(modelMap, result, logger2) {
68294
+ try {
68295
+ const copilotPricing = await scrapeCopilotPricing();
68296
+ result.copilotPricingScraped = true;
68297
+ for (const cp of copilotPricing) {
68298
+ const key = normalizeModelName(cp.modelName);
68299
+ const existing = modelMap.get(key) ?? findClosestKey(modelMap, key);
68300
+ if (existing) {
68301
+ existing.tokenInputMultiplier = cp.inputMultiplier;
68302
+ existing.tokenOutputMultiplier = cp.outputMultiplier;
68303
+ if (cp.cachedInputMultiplier !== null) {
68304
+ existing.cachedInputMultiplier = cp.cachedInputMultiplier;
68305
+ }
68306
+ } else {
68307
+ modelMap.set(key, {
68308
+ id: key,
68309
+ displayName: cp.modelName,
68310
+ tokenInputMultiplier: cp.inputMultiplier,
68311
+ tokenOutputMultiplier: cp.outputMultiplier,
68312
+ cachedInputMultiplier: cp.cachedInputMultiplier,
68313
+ available: true
68314
+ });
68315
+ }
68316
+ }
68317
+ } catch (error51) {
68318
+ const msg = error51 instanceof Error ? error51.message : String(error51);
68319
+ result.errors.push(`Copilot pricing scrape failed: ${msg}`);
68320
+ logger2?.warn(`Copilot pricing scrape failed: ${msg}`);
68321
+ }
68322
+ }
68285
68323
  async function refreshModelPricing(logger2) {
68286
68324
  const result = {
68287
68325
  modelsUpdated: 0,
68288
68326
  catalogFetched: false,
68289
68327
  tokenPricingScraped: false,
68290
68328
  premiumPricingScraped: false,
68329
+ copilotPricingScraped: false,
68291
68330
  errors: []
68292
68331
  };
68293
68332
  const modelMap = /* @__PURE__ */ new Map();
68294
68333
  await fetchCatalogIntoMap(modelMap, result, logger2);
68295
68334
  await scrapeTokenPricingIntoMap(modelMap, result, logger2);
68335
+ await scrapeCopilotPricingIntoMap(modelMap, result, logger2);
68296
68336
  await scrapePremiumPricingIntoMap(modelMap, result, logger2);
68297
- if (!result.catalogFetched && !result.tokenPricingScraped && !result.premiumPricingScraped) {
68298
- logger2?.warn("All pricing sources failed, using seed data");
68299
- await seedFromFallback();
68300
- result.modelsUpdated = SEED_MODELS.length;
68337
+ if (!result.catalogFetched && !result.tokenPricingScraped && !result.premiumPricingScraped && !result.copilotPricingScraped) {
68338
+ logger2?.warn("All pricing sources failed, no models available");
68301
68339
  return result;
68302
68340
  }
68303
68341
  const db = await getDatabase();
@@ -68319,13 +68357,6 @@ async function refreshModelPricing(logger2) {
68319
68357
  }
68320
68358
  return result;
68321
68359
  }
68322
- async function seedFromFallback() {
68323
- const db = await getDatabase();
68324
- const now = nowIso();
68325
- for (const model of SEED_MODELS) {
68326
- await upsertModel(db, { ...model, updatedAt: now });
68327
- }
68328
- }
68329
68360
  async function getModelPricing(modelId) {
68330
68361
  const db = await getDatabase();
68331
68362
  const result = await db.execute({
@@ -68406,7 +68437,6 @@ var init_registry = __esm({
68406
68437
  init_db();
68407
68438
  init_catalog();
68408
68439
  init_pricing_scraper();
68409
- init_seed();
68410
68440
  init_types();
68411
68441
  }
68412
68442
  });
@@ -68417,7 +68447,6 @@ var init_models = __esm({
68417
68447
  "use strict";
68418
68448
  init_catalog();
68419
68449
  init_registry();
68420
- init_seed();
68421
68450
  init_types();
68422
68451
  }
68423
68452
  });
@@ -70857,7 +70886,7 @@ async function processQueue2(squadId) {
70857
70886
  const next = await getNextQueued(squadId);
70858
70887
  if (next) {
70859
70888
  const freshSquad = await getSquad(squadId);
70860
- if (freshSquad) {
70889
+ if (freshSquad && next.objectiveId) {
70861
70890
  void startAndExecuteInstance(next.id, freshSquad, next.objectiveId);
70862
70891
  }
70863
70892
  }
@@ -70992,13 +71021,28 @@ async function handleFireSquad(rawArgs) {
70992
71021
  if (activeObjectives.length > 0) {
70993
71022
  const updated = await updateSquad(squadId, { status: "inactive" });
70994
71023
  return {
70995
- message: `Squad ${squadId} was deactivated because it still has active objectives.`,
71024
+ message: `Squad ${squadId} was deactivated because it still has active objectives. Use fire_squad again after objectives complete to delete it.`,
70996
71025
  squad: updated,
70997
71026
  activeObjectives
70998
71027
  };
70999
71028
  }
71000
71029
  await deleteSquad(squadId);
71001
- return { message: `Squad ${squadId} was deleted.`, squadId };
71030
+ return {
71031
+ message: `Squad "${squad.name}" has been deleted. It can be restored with restore_squad if needed.`,
71032
+ squadId
71033
+ };
71034
+ }
71035
+ async function handleRestoreSquad(rawArgs) {
71036
+ const { squadId } = squadIdSchema.parse(rawArgs);
71037
+ const restored = await restoreSquad(squadId);
71038
+ if (!restored) {
71039
+ throw new Error(`Squad ${squadId} was not found or is not deleted.`);
71040
+ }
71041
+ eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad: restored });
71042
+ return {
71043
+ message: `Squad "${restored.name}" has been restored.`,
71044
+ squad: restored
71045
+ };
71002
71046
  }
71003
71047
  async function handleDelegateToSquad(rawArgs) {
71004
71048
  const { squadId, objective } = delegateToSquadSchema.parse(rawArgs);
@@ -71048,7 +71092,7 @@ async function handleUpdateSquadMember(rawArgs) {
71048
71092
  member: updated
71049
71093
  };
71050
71094
  }
71051
- function createSquadToolExecutor(config2) {
71095
+ function createSquadToolExecutor(_config) {
71052
71096
  return async (toolName, rawArgs) => {
71053
71097
  switch (toolName) {
71054
71098
  case "create_squad":
@@ -71063,11 +71107,26 @@ function createSquadToolExecutor(config2) {
71063
71107
  return handleAnalyzeRepo(rawArgs);
71064
71108
  case "fire_squad":
71065
71109
  return handleFireSquad(rawArgs);
71110
+ case "restore_squad":
71111
+ return handleRestoreSquad(rawArgs);
71112
+ case "list_deleted_squads": {
71113
+ const deletedSquads = await listDeletedSquads();
71114
+ if (deletedSquads.length === 0) {
71115
+ return { message: "No deleted squads.", squads: [] };
71116
+ }
71117
+ const list = deletedSquads.map((s) => `${s.id}: ${s.name} (${s.repoOwner}/${s.repoName}) [deleted ${s.deletedAt}]`).join("\n");
71118
+ return { message: list, squads: deletedSquads };
71119
+ }
71066
71120
  case "list_squads": {
71067
71121
  const squads = await listSquads();
71122
+ const deletedCount = (await listDeletedSquads()).length;
71123
+ const suffix = deletedCount > 0 ? `
71124
+
71125
+ ${deletedCount} deleted squad(s) available for restore (use list_deleted_squads to view).` : "";
71068
71126
  return {
71069
- message: formatSquadList(squads),
71070
- squads
71127
+ message: formatSquadList(squads) + suffix,
71128
+ squads,
71129
+ deletedCount
71071
71130
  };
71072
71131
  }
71073
71132
  case "get_squad_status": {
@@ -71125,7 +71184,9 @@ var init_squad2 = __esm({
71125
71184
  squadId: external_exports.string().trim().min(1),
71126
71185
  role: external_exports.string().trim().min(1).describe("Slug-style role identifier (e.g. 'senior-frontend-engineer', 'team-lead')"),
71127
71186
  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."),
71187
+ systemPrompt: external_exports.string().min(1).describe(
71188
+ "The system prompt defining this member's expertise, responsibilities, and behavior."
71189
+ ),
71129
71190
  model: external_exports.string().optional().describe("Model override for this member. Uses squad default if not provided.")
71130
71191
  });
71131
71192
  removeSquadMemberSchema = external_exports.object({
@@ -71197,10 +71258,22 @@ var init_squad2 = __esm({
71197
71258
  },
71198
71259
  {
71199
71260
  name: "fire_squad",
71200
- description: "Deactivate or delete a squad.",
71261
+ description: "Soft-delete a squad. The squad can be restored later with restore_squad.",
71201
71262
  parameters: squadIdSchema,
71202
71263
  skipPermission: true
71203
71264
  },
71265
+ {
71266
+ name: "restore_squad",
71267
+ description: "Restore a previously deleted squad and all its members.",
71268
+ parameters: squadIdSchema,
71269
+ skipPermission: true
71270
+ },
71271
+ {
71272
+ name: "list_deleted_squads",
71273
+ description: "List all soft-deleted squads that can be restored.",
71274
+ parameters: external_exports.object({}),
71275
+ skipPermission: true
71276
+ },
71204
71277
  {
71205
71278
  name: "list_squads",
71206
71279
  description: "List all squads and their status.",
@@ -85313,8 +85386,7 @@ async function main() {
85313
85386
  logger2.info("Database initialized");
85314
85387
  const pricingResult = await refreshModelPricing(logger2);
85315
85388
  if (pricingResult.modelsUpdated === 0) {
85316
- logger2.warn("Model pricing refresh returned 0 models, seeding with fallback");
85317
- await seedFromFallback();
85389
+ logger2.warn("Model pricing refresh returned 0 models");
85318
85390
  }
85319
85391
  logger2.info({ modelsUpdated: pricingResult.modelsUpdated }, "Model pricing initialized");
85320
85392
  await scanSkills();