heyio 4.2.5 → 4.3.0

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,10 +80,10 @@ 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.5";
83
+ APP_VERSION = "4.3.0";
84
84
  API_PORT = 7777;
85
85
  API_HOST = "0.0.0.0";
86
- DEFAULT_MODEL = "gpt-4o";
86
+ DEFAULT_MODEL = "gpt-4.1-mini";
87
87
  SESSION_RESET_THRESHOLD = 50;
88
88
  SCHEDULER_INTERVAL_MS = 6e4;
89
89
  QA_MAX_REVISIONS = 3;
@@ -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);
@@ -68132,226 +68193,6 @@ var init_pricing_scraper = __esm({
68132
68193
  }
68133
68194
  });
68134
68195
 
68135
- // packages/daemon/src/models/seed.ts
68136
- var SEED_MODELS;
68137
- var init_seed = __esm({
68138
- "packages/daemon/src/models/seed.ts"() {
68139
- "use strict";
68140
- SEED_MODELS = [
68141
- {
68142
- id: "gpt-4o-mini",
68143
- displayName: "OpenAI GPT-4o mini",
68144
- premiumMultiplier: 0.33,
68145
- tokenInputMultiplier: 0.015,
68146
- tokenOutputMultiplier: 0.06,
68147
- cachedInputMultiplier: 75e-4,
68148
- tier: "trivial",
68149
- available: true
68150
- },
68151
- {
68152
- id: "gpt-4o",
68153
- displayName: "OpenAI GPT-4o",
68154
- premiumMultiplier: 0.33,
68155
- tokenInputMultiplier: 0.25,
68156
- tokenOutputMultiplier: 1,
68157
- cachedInputMultiplier: 0.125,
68158
- tier: "trivial",
68159
- available: true
68160
- },
68161
- {
68162
- id: "gpt-5-mini",
68163
- displayName: "GPT-5 mini",
68164
- premiumMultiplier: 0.33,
68165
- tokenInputMultiplier: 0.025,
68166
- tokenOutputMultiplier: 0.2,
68167
- cachedInputMultiplier: 25e-4,
68168
- tier: "trivial",
68169
- available: true
68170
- },
68171
- {
68172
- id: "gpt-5.3-codex",
68173
- displayName: "GPT-5.3-Codex",
68174
- premiumMultiplier: 6,
68175
- tokenInputMultiplier: 0.175,
68176
- tokenOutputMultiplier: 1.4,
68177
- cachedInputMultiplier: 0.0175,
68178
- tier: "premium",
68179
- available: true
68180
- },
68181
- {
68182
- id: "gpt-5.4",
68183
- displayName: "GPT-5.4",
68184
- premiumMultiplier: 6,
68185
- tokenInputMultiplier: 0.25,
68186
- tokenOutputMultiplier: 1.5,
68187
- cachedInputMultiplier: 0.025,
68188
- tier: "premium",
68189
- available: true
68190
- },
68191
- {
68192
- id: "gpt-5.4-mini",
68193
- displayName: "GPT-5.4 mini",
68194
- premiumMultiplier: 6,
68195
- tokenInputMultiplier: 0.075,
68196
- tokenOutputMultiplier: 0.45,
68197
- cachedInputMultiplier: 75e-4,
68198
- tier: "premium",
68199
- available: true
68200
- },
68201
- {
68202
- id: "gpt-5.5",
68203
- displayName: "GPT-5.5",
68204
- premiumMultiplier: 57,
68205
- tokenInputMultiplier: 0.5,
68206
- tokenOutputMultiplier: 3,
68207
- cachedInputMultiplier: 0.05,
68208
- tier: "ultra",
68209
- available: true
68210
- },
68211
- {
68212
- id: "claude-sonnet-4",
68213
- displayName: "Claude Sonnet 4",
68214
- premiumMultiplier: 6,
68215
- tokenInputMultiplier: 0.3,
68216
- tokenOutputMultiplier: 1.5,
68217
- cachedInputMultiplier: 0.03,
68218
- tier: "premium",
68219
- available: true
68220
- },
68221
- {
68222
- id: "claude-sonnet-4.5",
68223
- displayName: "Claude Sonnet 4.5",
68224
- premiumMultiplier: 6,
68225
- tokenInputMultiplier: 0.3,
68226
- tokenOutputMultiplier: 1.5,
68227
- cachedInputMultiplier: 0.03,
68228
- tier: "premium",
68229
- available: true
68230
- },
68231
- {
68232
- id: "claude-sonnet-4.6",
68233
- displayName: "Claude Sonnet 4.6",
68234
- premiumMultiplier: 9,
68235
- tokenInputMultiplier: 0.3,
68236
- tokenOutputMultiplier: 1.5,
68237
- cachedInputMultiplier: 0.03,
68238
- tier: "premium",
68239
- available: true
68240
- },
68241
- {
68242
- id: "claude-haiku-4.5",
68243
- displayName: "Claude Haiku 4.5",
68244
- premiumMultiplier: 0.33,
68245
- tokenInputMultiplier: 0.1,
68246
- tokenOutputMultiplier: 0.5,
68247
- cachedInputMultiplier: 0.01,
68248
- tier: "trivial",
68249
- available: true
68250
- },
68251
- {
68252
- id: "claude-opus-4.5",
68253
- displayName: "Claude Opus 4.5",
68254
- premiumMultiplier: 15,
68255
- tokenInputMultiplier: 0.5,
68256
- tokenOutputMultiplier: 2.5,
68257
- cachedInputMultiplier: 0.05,
68258
- tier: "premium",
68259
- available: true
68260
- },
68261
- {
68262
- id: "claude-opus-4.6",
68263
- displayName: "Claude Opus 4.6",
68264
- premiumMultiplier: 27,
68265
- tokenInputMultiplier: 0.5,
68266
- tokenOutputMultiplier: 2.5,
68267
- cachedInputMultiplier: 0.05,
68268
- tier: "ultra",
68269
- available: true
68270
- },
68271
- {
68272
- id: "claude-opus-4.7",
68273
- displayName: "Claude Opus 4.7",
68274
- premiumMultiplier: 27,
68275
- tokenInputMultiplier: 0.5,
68276
- tokenOutputMultiplier: 2.5,
68277
- cachedInputMultiplier: 0.05,
68278
- tier: "ultra",
68279
- available: true
68280
- },
68281
- {
68282
- id: "claude-opus-4.8",
68283
- displayName: "Claude Opus 4.8",
68284
- premiumMultiplier: 27,
68285
- tokenInputMultiplier: 0.5,
68286
- tokenOutputMultiplier: 2.5,
68287
- cachedInputMultiplier: 0.05,
68288
- tier: "ultra",
68289
- available: true
68290
- },
68291
- {
68292
- id: "gemini-2.5-pro",
68293
- displayName: "Gemini 2.5 Pro",
68294
- premiumMultiplier: 1,
68295
- tokenInputMultiplier: 0.125,
68296
- tokenOutputMultiplier: 1,
68297
- cachedInputMultiplier: 0.0125,
68298
- tier: "fast",
68299
- available: true
68300
- },
68301
- {
68302
- id: "gemini-3-flash",
68303
- displayName: "Gemini 3 Flash",
68304
- premiumMultiplier: 0.33,
68305
- tokenInputMultiplier: 0.05,
68306
- tokenOutputMultiplier: 0.3,
68307
- cachedInputMultiplier: 5e-3,
68308
- tier: "trivial",
68309
- available: true
68310
- },
68311
- {
68312
- id: "gemini-3.1-pro",
68313
- displayName: "Gemini 3.1 Pro",
68314
- premiumMultiplier: 6,
68315
- tokenInputMultiplier: 0.2,
68316
- tokenOutputMultiplier: 1.2,
68317
- cachedInputMultiplier: 0.02,
68318
- tier: "premium",
68319
- available: true
68320
- },
68321
- {
68322
- id: "gemini-3.5-flash",
68323
- displayName: "Gemini 3.5 Flash",
68324
- premiumMultiplier: 14,
68325
- tokenInputMultiplier: 0.15,
68326
- tokenOutputMultiplier: 0.9,
68327
- cachedInputMultiplier: 0.015,
68328
- tier: "premium",
68329
- available: true
68330
- },
68331
- {
68332
- id: "raptor-mini",
68333
- displayName: "Raptor mini",
68334
- premiumMultiplier: 0.33,
68335
- tokenInputMultiplier: 0.025,
68336
- tokenOutputMultiplier: 0.2,
68337
- cachedInputMultiplier: 25e-4,
68338
- tier: "trivial",
68339
- available: true
68340
- },
68341
- {
68342
- id: "mai-code-1-flash",
68343
- displayName: "MAI-Code-1-Flash",
68344
- premiumMultiplier: 0.33,
68345
- tokenInputMultiplier: 0.075,
68346
- tokenOutputMultiplier: 0.45,
68347
- cachedInputMultiplier: 75e-4,
68348
- tier: "trivial",
68349
- available: true
68350
- }
68351
- ];
68352
- }
68353
- });
68354
-
68355
68196
  // packages/daemon/src/models/types.ts
68356
68197
  function computeTierFromMultiplier(premiumMultiplier) {
68357
68198
  if (premiumMultiplier === null) {
@@ -68494,20 +68335,21 @@ async function refreshModelPricing(logger2) {
68494
68335
  await scrapeCopilotPricingIntoMap(modelMap, result, logger2);
68495
68336
  await scrapePremiumPricingIntoMap(modelMap, result, logger2);
68496
68337
  if (!result.catalogFetched && !result.tokenPricingScraped && !result.premiumPricingScraped && !result.copilotPricingScraped) {
68497
- logger2?.warn("All pricing sources failed, using seed data");
68498
- await seedFromFallback();
68499
- result.modelsUpdated = SEED_MODELS.length;
68338
+ logger2?.warn("All pricing sources failed, no models available");
68500
68339
  return result;
68501
68340
  }
68502
68341
  const db = await getDatabase();
68503
68342
  const now = nowIso();
68504
68343
  for (const model of modelMap.values()) {
68344
+ if (model.tokenInputMultiplier == null) {
68345
+ continue;
68346
+ }
68505
68347
  const tier = computeTierFromMultiplier(model.premiumMultiplier ?? null);
68506
68348
  await upsertModel(db, {
68507
68349
  id: model.id,
68508
68350
  displayName: model.displayName,
68509
68351
  premiumMultiplier: model.premiumMultiplier ?? null,
68510
- tokenInputMultiplier: model.tokenInputMultiplier ?? null,
68352
+ tokenInputMultiplier: model.tokenInputMultiplier,
68511
68353
  tokenOutputMultiplier: model.tokenOutputMultiplier ?? null,
68512
68354
  cachedInputMultiplier: model.cachedInputMultiplier ?? null,
68513
68355
  tier,
@@ -68518,12 +68360,17 @@ async function refreshModelPricing(logger2) {
68518
68360
  }
68519
68361
  return result;
68520
68362
  }
68521
- async function seedFromFallback() {
68363
+ async function getModelsForTier(tier) {
68522
68364
  const db = await getDatabase();
68523
- const now = nowIso();
68524
- for (const model of SEED_MODELS) {
68525
- await upsertModel(db, { ...model, updatedAt: now });
68526
- }
68365
+ const result = await db.execute({
68366
+ sql: "SELECT * FROM model_pricing WHERE tier = ? AND available = 1 ORDER BY premium_multiplier ASC NULLS LAST",
68367
+ args: [tier]
68368
+ });
68369
+ return result.rows.map(rowToModelPricing);
68370
+ }
68371
+ async function getCheapestInTier(tier) {
68372
+ const models = await getModelsForTier(tier);
68373
+ return models[0] ?? null;
68527
68374
  }
68528
68375
  async function getModelPricing(modelId) {
68529
68376
  const db = await getDatabase();
@@ -68545,6 +68392,16 @@ async function getModelPricing(modelId) {
68545
68392
  }
68546
68393
  return null;
68547
68394
  }
68395
+ async function getCheapestAvailableModel() {
68396
+ const tiers = ["trivial", "fast", "standard", "premium", "ultra"];
68397
+ for (const tier of tiers) {
68398
+ const model = await getCheapestInTier(tier);
68399
+ if (model) {
68400
+ return model;
68401
+ }
68402
+ }
68403
+ return null;
68404
+ }
68548
68405
  function calculateTokenUnitCost(inputTokens, outputTokens, inputMultiplier, outputMultiplier) {
68549
68406
  if (inputMultiplier === null || outputMultiplier === null) {
68550
68407
  return 0;
@@ -68552,6 +68409,14 @@ function calculateTokenUnitCost(inputTokens, outputTokens, inputMultiplier, outp
68552
68409
  const tokenUnits = inputTokens * inputMultiplier + outputTokens * outputMultiplier;
68553
68410
  return tokenUnits * TOKEN_UNIT_PRICE;
68554
68411
  }
68412
+ function getNextTierUp(tier) {
68413
+ const order = ["trivial", "fast", "standard", "premium", "ultra"];
68414
+ const idx = order.indexOf(tier);
68415
+ if (idx < 0 || idx >= order.length - 1) {
68416
+ return null;
68417
+ }
68418
+ return order[idx + 1];
68419
+ }
68555
68420
  async function upsertModel(db, model) {
68556
68421
  await db.execute({
68557
68422
  sql: `INSERT INTO model_pricing (id, display_name, premium_multiplier, token_input_multiplier, token_output_multiplier, cached_input_multiplier, tier, available, updated_at)
@@ -68605,7 +68470,6 @@ var init_registry = __esm({
68605
68470
  init_db();
68606
68471
  init_catalog();
68607
68472
  init_pricing_scraper();
68608
- init_seed();
68609
68473
  init_types();
68610
68474
  }
68611
68475
  });
@@ -68616,7 +68480,6 @@ var init_models = __esm({
68616
68480
  "use strict";
68617
68481
  init_catalog();
68618
68482
  init_registry();
68619
- init_seed();
68620
68483
  init_types();
68621
68484
  }
68622
68485
  });
@@ -69640,6 +69503,75 @@ var init_manager2 = __esm({
69640
69503
  }
69641
69504
  });
69642
69505
 
69506
+ // packages/daemon/src/squad/model-selector.ts
69507
+ import { CopilotClient as CopilotClient2, approveAll as approveAll2 } from "@github/copilot-sdk";
69508
+ async function selectModelForTask(taskDescription) {
69509
+ const classifierModel = await getCheapestAvailableModel();
69510
+ if (!classifierModel) {
69511
+ throw new Error("No models available in pricing database");
69512
+ }
69513
+ let tier;
69514
+ try {
69515
+ tier = await classifyTaskComplexity(taskDescription, classifierModel.id);
69516
+ } catch {
69517
+ return classifierModel.id;
69518
+ }
69519
+ const selectedModel = await getCheapestInTier(tier);
69520
+ if (selectedModel) {
69521
+ return selectedModel.id;
69522
+ }
69523
+ const nextTier = getNextTierUp(tier);
69524
+ if (nextTier) {
69525
+ const escalatedModel = await getCheapestInTier(nextTier);
69526
+ if (escalatedModel) {
69527
+ return escalatedModel.id;
69528
+ }
69529
+ }
69530
+ return classifierModel.id;
69531
+ }
69532
+ async function classifyTaskComplexity(taskDescription, modelId) {
69533
+ let client2 = null;
69534
+ try {
69535
+ client2 = new CopilotClient2();
69536
+ await client2.start();
69537
+ const session = await client2.createSession({
69538
+ model: modelId,
69539
+ onPermissionRequest: approveAll2,
69540
+ systemMessage: { content: CLASSIFICATION_PROMPT }
69541
+ });
69542
+ try {
69543
+ const response = await session.sendAndWait({ prompt: `Task: ${taskDescription}` }, 15e3);
69544
+ const raw = (response.text ?? "").trim().toLowerCase();
69545
+ const tier = VALID_TIERS.find((t) => raw.includes(t));
69546
+ return tier ?? "standard";
69547
+ } finally {
69548
+ await session.disconnect().catch(() => void 0);
69549
+ }
69550
+ } finally {
69551
+ if (client2) {
69552
+ await client2.stop().catch(() => void 0);
69553
+ }
69554
+ }
69555
+ }
69556
+ var VALID_TIERS, CLASSIFICATION_PROMPT;
69557
+ var init_model_selector = __esm({
69558
+ "packages/daemon/src/squad/model-selector.ts"() {
69559
+ "use strict";
69560
+ init_registry();
69561
+ VALID_TIERS = ["trivial", "fast", "standard", "premium", "ultra"];
69562
+ CLASSIFICATION_PROMPT = `You are a task complexity classifier. Given a task description, classify its complexity into exactly one tier.
69563
+
69564
+ Tiers (from simplest to most complex):
69565
+ - trivial: Typos, renames, comment changes, config tweaks, formatting
69566
+ - fast: Simple bug fixes, small features, documentation updates, single-file changes
69567
+ - standard: Feature implementation, multi-file changes, moderate refactoring
69568
+ - premium: Architecture changes, complex refactoring, security work, performance optimization
69569
+ - ultra: System-wide redesigns, critical infrastructure, cross-cutting concerns
69570
+
69571
+ Reply with ONLY the tier name (one word, lowercase). Nothing else.`;
69572
+ }
69573
+ });
69574
+
69643
69575
  // packages/daemon/src/execution/history.ts
69644
69576
  function summarizeTaskResult(taskResult) {
69645
69577
  const condensed = taskResult.replace(/\s+/g, " ").trim();
@@ -69677,8 +69609,8 @@ import { mkdir as mkdir9, readFile as readFile7, readdir as readdir5, stat as st
69677
69609
  import { dirname as dirname8, extname as extname3, isAbsolute, join as join11, relative as relative3, resolve as resolve4 } from "node:path";
69678
69610
  import { promisify as promisify3 } from "node:util";
69679
69611
  import {
69680
- CopilotClient as CopilotClient2,
69681
- approveAll as approveAll2,
69612
+ CopilotClient as CopilotClient3,
69613
+ approveAll as approveAll3,
69682
69614
  defineTool
69683
69615
  } from "@github/copilot-sdk";
69684
69616
  function createEmptyUsage() {
@@ -69937,14 +69869,15 @@ async function executeAgentTask(member, task, worktreePath, options2) {
69937
69869
  const mcpServerNote = options2?.mcpServers?.length ? `Available MCP server labels: ${options2.mcpServers.join(", ")}.` : "No additional MCP servers were configured for this run.";
69938
69870
  let client2 = null;
69939
69871
  try {
69940
- client2 = new CopilotClient2({ workingDirectory: worktreePath });
69872
+ client2 = new CopilotClient3({ workingDirectory: worktreePath });
69941
69873
  await client2.start();
69874
+ const model = member.model ?? await selectModelForTask(task.description);
69942
69875
  const session = await client2.createSession({
69943
- model: member.model ?? DEFAULT_MODEL,
69876
+ model,
69944
69877
  workingDirectory: worktreePath,
69945
69878
  tools,
69946
69879
  availableTools: ["custom:*"],
69947
- onPermissionRequest: approveAll2,
69880
+ onPermissionRequest: approveAll3,
69948
69881
  systemMessage: {
69949
69882
  content: `${member.systemPrompt}
69950
69883
 
@@ -69996,8 +69929,8 @@ var execAsync3, MAX_FILE_SIZE, MAX_LIST_RESULTS, MAX_SEARCH_RESULTS;
69996
69929
  var init_agent = __esm({
69997
69930
  "packages/daemon/src/execution/agent.ts"() {
69998
69931
  "use strict";
69999
- init_dist();
70000
69932
  init_registry();
69933
+ init_model_selector();
70001
69934
  init_store2();
70002
69935
  init_history();
70003
69936
  execAsync3 = promisify3(exec3);
@@ -70095,7 +70028,7 @@ import { exec as exec4 } from "node:child_process";
70095
70028
  import { access, readFile as readFile8 } from "node:fs/promises";
70096
70029
  import { join as join12 } from "node:path";
70097
70030
  import { promisify as promisify4 } from "node:util";
70098
- import { CopilotClient as CopilotClient3, approveAll as approveAll3 } from "@github/copilot-sdk";
70031
+ import { CopilotClient as CopilotClient4, approveAll as approveAll4 } from "@github/copilot-sdk";
70099
70032
  async function fileExists(path) {
70100
70033
  try {
70101
70034
  await access(path);
@@ -70189,12 +70122,13 @@ Return strict JSON in this shape:
70189
70122
  }`;
70190
70123
  let client2 = null;
70191
70124
  try {
70192
- client2 = new CopilotClient3({ workingDirectory: repoPath });
70125
+ client2 = new CopilotClient4({ workingDirectory: repoPath });
70193
70126
  await client2.start();
70127
+ const model = await selectModelForTask(`Create implementation plan: ${objective.description}`);
70194
70128
  const session = await client2.createSession({
70195
- model: DEFAULT_MODEL,
70129
+ model,
70196
70130
  workingDirectory: repoPath,
70197
- onPermissionRequest: approveAll3,
70131
+ onPermissionRequest: approveAll4,
70198
70132
  systemMessage: {
70199
70133
  content: `${TEAM_LEAD_PROMPT}
70200
70134
 
@@ -70229,7 +70163,7 @@ var execAsync4, README_CANDIDATES, MAX_REPO_CONTEXT_LENGTH;
70229
70163
  var init_planning = __esm({
70230
70164
  "packages/daemon/src/execution/planning.ts"() {
70231
70165
  "use strict";
70232
- init_dist();
70166
+ init_model_selector();
70233
70167
  init_roles();
70234
70168
  execAsync4 = promisify4(exec4);
70235
70169
  README_CANDIDATES = ["README.md", "readme.md"];
@@ -70314,7 +70248,7 @@ var init_pr = __esm({
70314
70248
  // packages/daemon/src/execution/qa.ts
70315
70249
  import { exec as exec6 } from "node:child_process";
70316
70250
  import { promisify as promisify6 } from "node:util";
70317
- import { CopilotClient as CopilotClient4, approveAll as approveAll4 } from "@github/copilot-sdk";
70251
+ import { CopilotClient as CopilotClient5, approveAll as approveAll5 } from "@github/copilot-sdk";
70318
70252
  function extractJsonObject2(content) {
70319
70253
  const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
70320
70254
  if (fenced?.[1]) {
@@ -70352,12 +70286,13 @@ Return strict JSON:
70352
70286
  }`;
70353
70287
  let client2 = null;
70354
70288
  try {
70355
- client2 = new CopilotClient4({ workingDirectory: worktreePath });
70289
+ client2 = new CopilotClient5({ workingDirectory: worktreePath });
70356
70290
  await client2.start();
70291
+ const model = qaMember.model ?? await selectModelForTask(`QA review: ${objective.description}`);
70357
70292
  const session = await client2.createSession({
70358
- model: qaMember.model ?? DEFAULT_MODEL,
70293
+ model,
70359
70294
  workingDirectory: worktreePath,
70360
- onPermissionRequest: approveAll4,
70295
+ onPermissionRequest: approveAll5,
70361
70296
  systemMessage: {
70362
70297
  content: QA_PROMPT
70363
70298
  }
@@ -70431,6 +70366,7 @@ var init_qa = __esm({
70431
70366
  "use strict";
70432
70367
  init_dist();
70433
70368
  init_event_bus();
70369
+ init_model_selector();
70434
70370
  init_roles();
70435
70371
  init_store2();
70436
70372
  execAsync6 = promisify6(exec6);
@@ -70439,7 +70375,7 @@ var init_qa = __esm({
70439
70375
  });
70440
70376
 
70441
70377
  // packages/daemon/src/execution/review.ts
70442
- import { CopilotClient as CopilotClient5, approveAll as approveAll5 } from "@github/copilot-sdk";
70378
+ import { CopilotClient as CopilotClient6, approveAll as approveAll6 } from "@github/copilot-sdk";
70443
70379
  function extractJsonObject3(content) {
70444
70380
  const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
70445
70381
  if (fenced?.[1]) {
@@ -70484,11 +70420,12 @@ Return strict JSON:
70484
70420
  }`;
70485
70421
  let client2 = null;
70486
70422
  try {
70487
- client2 = new CopilotClient5();
70423
+ client2 = new CopilotClient6();
70488
70424
  await client2.start();
70425
+ const model = teamLead.model ?? await selectModelForTask(`Code review: ${objective.description}`);
70489
70426
  const session = await client2.createSession({
70490
- model: teamLead.model ?? DEFAULT_MODEL,
70491
- onPermissionRequest: approveAll5,
70427
+ model,
70428
+ onPermissionRequest: approveAll6,
70492
70429
  systemMessage: {
70493
70430
  content: `${TEAM_LEAD_PROMPT}
70494
70431
 
@@ -70522,7 +70459,7 @@ You are conducting a final coordination review before QA.`
70522
70459
  var init_review = __esm({
70523
70460
  "packages/daemon/src/execution/review.ts"() {
70524
70461
  "use strict";
70525
- init_dist();
70462
+ init_model_selector();
70526
70463
  init_roles();
70527
70464
  }
70528
70465
  });
@@ -71056,7 +70993,7 @@ async function processQueue2(squadId) {
71056
70993
  const next = await getNextQueued(squadId);
71057
70994
  if (next) {
71058
70995
  const freshSquad = await getSquad(squadId);
71059
- if (freshSquad) {
70996
+ if (freshSquad && next.objectiveId) {
71060
70997
  void startAndExecuteInstance(next.id, freshSquad, next.objectiveId);
71061
70998
  }
71062
70999
  }
@@ -71191,13 +71128,28 @@ async function handleFireSquad(rawArgs) {
71191
71128
  if (activeObjectives.length > 0) {
71192
71129
  const updated = await updateSquad(squadId, { status: "inactive" });
71193
71130
  return {
71194
- message: `Squad ${squadId} was deactivated because it still has active objectives.`,
71131
+ message: `Squad ${squadId} was deactivated because it still has active objectives. Use fire_squad again after objectives complete to delete it.`,
71195
71132
  squad: updated,
71196
71133
  activeObjectives
71197
71134
  };
71198
71135
  }
71199
71136
  await deleteSquad(squadId);
71200
- return { message: `Squad ${squadId} was deleted.`, squadId };
71137
+ return {
71138
+ message: `Squad "${squad.name}" has been deleted. It can be restored with restore_squad if needed.`,
71139
+ squadId
71140
+ };
71141
+ }
71142
+ async function handleRestoreSquad(rawArgs) {
71143
+ const { squadId } = squadIdSchema.parse(rawArgs);
71144
+ const restored = await restoreSquad(squadId);
71145
+ if (!restored) {
71146
+ throw new Error(`Squad ${squadId} was not found or is not deleted.`);
71147
+ }
71148
+ eventBus.emit(EVENT_NAMES.SQUAD_UPDATED, { squad: restored });
71149
+ return {
71150
+ message: `Squad "${restored.name}" has been restored.`,
71151
+ squad: restored
71152
+ };
71201
71153
  }
71202
71154
  async function handleDelegateToSquad(rawArgs) {
71203
71155
  const { squadId, objective } = delegateToSquadSchema.parse(rawArgs);
@@ -71262,11 +71214,26 @@ function createSquadToolExecutor(_config) {
71262
71214
  return handleAnalyzeRepo(rawArgs);
71263
71215
  case "fire_squad":
71264
71216
  return handleFireSquad(rawArgs);
71217
+ case "restore_squad":
71218
+ return handleRestoreSquad(rawArgs);
71219
+ case "list_deleted_squads": {
71220
+ const deletedSquads = await listDeletedSquads();
71221
+ if (deletedSquads.length === 0) {
71222
+ return { message: "No deleted squads.", squads: [] };
71223
+ }
71224
+ const list = deletedSquads.map((s) => `${s.id}: ${s.name} (${s.repoOwner}/${s.repoName}) [deleted ${s.deletedAt}]`).join("\n");
71225
+ return { message: list, squads: deletedSquads };
71226
+ }
71265
71227
  case "list_squads": {
71266
71228
  const squads = await listSquads();
71229
+ const deletedCount = (await listDeletedSquads()).length;
71230
+ const suffix = deletedCount > 0 ? `
71231
+
71232
+ ${deletedCount} deleted squad(s) available for restore (use list_deleted_squads to view).` : "";
71267
71233
  return {
71268
- message: formatSquadList(squads),
71269
- squads
71234
+ message: formatSquadList(squads) + suffix,
71235
+ squads,
71236
+ deletedCount
71270
71237
  };
71271
71238
  }
71272
71239
  case "get_squad_status": {
@@ -71398,10 +71365,22 @@ var init_squad2 = __esm({
71398
71365
  },
71399
71366
  {
71400
71367
  name: "fire_squad",
71401
- description: "Deactivate or delete a squad.",
71368
+ description: "Soft-delete a squad. The squad can be restored later with restore_squad.",
71402
71369
  parameters: squadIdSchema,
71403
71370
  skipPermission: true
71404
71371
  },
71372
+ {
71373
+ name: "restore_squad",
71374
+ description: "Restore a previously deleted squad and all its members.",
71375
+ parameters: squadIdSchema,
71376
+ skipPermission: true
71377
+ },
71378
+ {
71379
+ name: "list_deleted_squads",
71380
+ description: "List all soft-deleted squads that can be restored.",
71381
+ parameters: external_exports.object({}),
71382
+ skipPermission: true
71383
+ },
71405
71384
  {
71406
71385
  name: "list_squads",
71407
71386
  description: "List all squads and their status.",
@@ -71853,13 +71832,13 @@ var init_orchestrator = __esm({
71853
71832
  });
71854
71833
  this.activeModel = model;
71855
71834
  } catch (error51) {
71856
- if (model !== DEFAULT_MODEL) {
71835
+ if (model !== this.config.defaultModel) {
71857
71836
  this.activeSession = await createSession({
71858
- model: DEFAULT_MODEL,
71837
+ model: this.config.defaultModel,
71859
71838
  systemPrompt,
71860
71839
  tools: createBoundOrchestratorTools(this.config)
71861
71840
  });
71862
- this.activeModel = DEFAULT_MODEL;
71841
+ this.activeModel = this.config.defaultModel;
71863
71842
  } else {
71864
71843
  throw error51;
71865
71844
  }
@@ -85311,9 +85290,16 @@ var init_bot = __esm({
85311
85290
  if (this.started) {
85312
85291
  return;
85313
85292
  }
85293
+ this.bot.catch((err) => {
85294
+ this.logger.error({ err: err.error }, "Telegram bot error: %s", err.message);
85295
+ });
85314
85296
  this.registerHandlers();
85315
- this.bot.start().catch((error51) => {
85316
- this.logger.error({ err: error51 }, "Telegram polling failed");
85297
+ this.bot.start({
85298
+ onStart: () => {
85299
+ this.logger.info("Telegram bot connected and polling");
85300
+ }
85301
+ }).catch((error51) => {
85302
+ this.logger.error({ err: error51 }, "Telegram polling failed to start");
85317
85303
  });
85318
85304
  this.started = true;
85319
85305
  }
@@ -85323,11 +85309,26 @@ var init_bot = __esm({
85323
85309
  }
85324
85310
  this.bot.stop();
85325
85311
  this.started = false;
85312
+ this.logger.info("Telegram bot stopped");
85326
85313
  }
85327
85314
  async sendText(chatId, text) {
85328
85315
  await this.bot.api.sendMessage(chatId, text);
85329
85316
  }
85317
+ isAuthorized(userId) {
85318
+ if (!this.config.telegramUserId) {
85319
+ return false;
85320
+ }
85321
+ return String(userId) === this.config.telegramUserId;
85322
+ }
85330
85323
  registerHandlers() {
85324
+ this.bot.use(async (ctx, next) => {
85325
+ const userId = ctx.from?.id;
85326
+ if (!userId || !this.isAuthorized(userId)) {
85327
+ this.logger.warn({ userId }, "Unauthorized Telegram message, ignoring");
85328
+ return;
85329
+ }
85330
+ await next();
85331
+ });
85331
85332
  this.bot.command("start", async (ctx) => {
85332
85333
  await ctx.reply(
85333
85334
  "Hello from Io. Send me a message and I will route it through the daemon orchestrator."
@@ -85514,8 +85515,7 @@ async function main() {
85514
85515
  logger2.info("Database initialized");
85515
85516
  const pricingResult = await refreshModelPricing(logger2);
85516
85517
  if (pricingResult.modelsUpdated === 0) {
85517
- logger2.warn("Model pricing refresh returned 0 models, seeding with fallback");
85518
- await seedFromFallback();
85518
+ logger2.warn("Model pricing refresh returned 0 models");
85519
85519
  }
85520
85520
  logger2.info({ modelsUpdated: pricingResult.modelsUpdated }, "Model pricing initialized");
85521
85521
  await scanSkills();
@@ -85535,8 +85535,13 @@ async function main() {
85535
85535
  setChatOrchestrator(orchestrator2);
85536
85536
  const apiServer = createApiServer(config2);
85537
85537
  const telegramBot = createTelegramBot(config2, orchestrator2);
85538
- telegramBot?.start();
85539
- createTelegramNotifier(telegramBot, config2, eventBus);
85538
+ if (telegramBot) {
85539
+ telegramBot.start();
85540
+ createTelegramNotifier(telegramBot, config2, eventBus);
85541
+ logger2.info("Telegram bot initialized");
85542
+ } else {
85543
+ logger2.info("Telegram bot disabled (no token configured)");
85544
+ }
85540
85545
  registerShutdownHandlers(logger2, async () => {
85541
85546
  if (pricingRefreshTimer) {
85542
85547
  clearInterval(pricingRefreshTimer);