heyio 4.2.7 → 4.3.1

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.7";
83
+ APP_VERSION = "4.3.1";
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;
@@ -2600,6 +2600,14 @@ async function markRead(id, db) {
2600
2600
  }
2601
2601
  return getInboxItem(id, database);
2602
2602
  }
2603
+ async function deleteInboxItem(id, db) {
2604
+ const database = db ?? await getDatabase();
2605
+ const result = await database.execute({
2606
+ sql: "DELETE FROM inbox WHERE id = ?",
2607
+ args: [id]
2608
+ });
2609
+ return (result.rowsAffected ?? 0) > 0;
2610
+ }
2603
2611
  function mapInboxItem(row) {
2604
2612
  return {
2605
2613
  id: asString(row.id),
@@ -36540,6 +36548,21 @@ var init_inbox2 = __esm({
36540
36548
  });
36541
36549
  }
36542
36550
  });
36551
+ router3.delete("/api/inbox/:id", async (req, res) => {
36552
+ try {
36553
+ const deleted = await deleteInboxItem(req.params.id);
36554
+ if (!deleted) {
36555
+ res.status(404).json({ error: "Inbox item not found" });
36556
+ return;
36557
+ }
36558
+ res.status(204).end();
36559
+ } catch (error51) {
36560
+ res.status(500).json({
36561
+ error: "Failed to delete inbox item",
36562
+ details: error51 instanceof Error ? error51.message : "Unknown error"
36563
+ });
36564
+ }
36565
+ });
36543
36566
  }
36544
36567
  });
36545
36568
 
@@ -68221,8 +68244,12 @@ var init_types = __esm({
68221
68244
  });
68222
68245
 
68223
68246
  // packages/daemon/src/models/registry.ts
68247
+ function stripVendorPrefix(id) {
68248
+ const slashIndex = id.indexOf("/");
68249
+ return slashIndex >= 0 ? id.slice(slashIndex + 1) : id;
68250
+ }
68224
68251
  function normalizeModelName(name) {
68225
- return name.toLowerCase().replace(/^openai\s+/i, "").replace(/\s+/g, "-").replace(/[^a-z0-9.\-]/g, "").trim();
68252
+ return stripVendorPrefix(name).toLowerCase().replace(/^openai\s+/i, "").replace(/\s+/g, "-").replace(/[^a-z0-9.\-]/g, "").trim();
68226
68253
  }
68227
68254
  async function fetchCatalogIntoMap(modelMap, result, logger2) {
68228
68255
  try {
@@ -68230,7 +68257,8 @@ async function fetchCatalogIntoMap(modelMap, result, logger2) {
68230
68257
  result.catalogFetched = true;
68231
68258
  for (const m of catalogModels) {
68232
68259
  const key = normalizeModelName(m.id);
68233
- modelMap.set(key, { id: m.id, displayName: m.displayName, available: true });
68260
+ const id = stripVendorPrefix(m.id);
68261
+ modelMap.set(key, { id, displayName: m.displayName, available: true });
68234
68262
  }
68235
68263
  } catch (error51) {
68236
68264
  const msg = error51 instanceof Error ? error51.message : String(error51);
@@ -68341,12 +68369,15 @@ async function refreshModelPricing(logger2) {
68341
68369
  const db = await getDatabase();
68342
68370
  const now = nowIso();
68343
68371
  for (const model of modelMap.values()) {
68372
+ if (model.tokenInputMultiplier == null) {
68373
+ continue;
68374
+ }
68344
68375
  const tier = computeTierFromMultiplier(model.premiumMultiplier ?? null);
68345
68376
  await upsertModel(db, {
68346
68377
  id: model.id,
68347
68378
  displayName: model.displayName,
68348
68379
  premiumMultiplier: model.premiumMultiplier ?? null,
68349
- tokenInputMultiplier: model.tokenInputMultiplier ?? null,
68380
+ tokenInputMultiplier: model.tokenInputMultiplier,
68350
68381
  tokenOutputMultiplier: model.tokenOutputMultiplier ?? null,
68351
68382
  cachedInputMultiplier: model.cachedInputMultiplier ?? null,
68352
68383
  tier,
@@ -68357,6 +68388,18 @@ async function refreshModelPricing(logger2) {
68357
68388
  }
68358
68389
  return result;
68359
68390
  }
68391
+ async function getModelsForTier(tier) {
68392
+ const db = await getDatabase();
68393
+ const result = await db.execute({
68394
+ sql: "SELECT * FROM model_pricing WHERE tier = ? AND available = 1 ORDER BY premium_multiplier ASC NULLS LAST",
68395
+ args: [tier]
68396
+ });
68397
+ return result.rows.map(rowToModelPricing);
68398
+ }
68399
+ async function getCheapestInTier(tier) {
68400
+ const models = await getModelsForTier(tier);
68401
+ return models[0] ?? null;
68402
+ }
68360
68403
  async function getModelPricing(modelId) {
68361
68404
  const db = await getDatabase();
68362
68405
  const result = await db.execute({
@@ -68377,6 +68420,16 @@ async function getModelPricing(modelId) {
68377
68420
  }
68378
68421
  return null;
68379
68422
  }
68423
+ async function getCheapestAvailableModel() {
68424
+ const tiers = ["trivial", "fast", "standard", "premium", "ultra"];
68425
+ for (const tier of tiers) {
68426
+ const model = await getCheapestInTier(tier);
68427
+ if (model) {
68428
+ return model;
68429
+ }
68430
+ }
68431
+ return null;
68432
+ }
68380
68433
  function calculateTokenUnitCost(inputTokens, outputTokens, inputMultiplier, outputMultiplier) {
68381
68434
  if (inputMultiplier === null || outputMultiplier === null) {
68382
68435
  return 0;
@@ -68384,6 +68437,14 @@ function calculateTokenUnitCost(inputTokens, outputTokens, inputMultiplier, outp
68384
68437
  const tokenUnits = inputTokens * inputMultiplier + outputTokens * outputMultiplier;
68385
68438
  return tokenUnits * TOKEN_UNIT_PRICE;
68386
68439
  }
68440
+ function getNextTierUp(tier) {
68441
+ const order = ["trivial", "fast", "standard", "premium", "ultra"];
68442
+ const idx = order.indexOf(tier);
68443
+ if (idx < 0 || idx >= order.length - 1) {
68444
+ return null;
68445
+ }
68446
+ return order[idx + 1];
68447
+ }
68387
68448
  async function upsertModel(db, model) {
68388
68449
  await db.execute({
68389
68450
  sql: `INSERT INTO model_pricing (id, display_name, premium_multiplier, token_input_multiplier, token_output_multiplier, cached_input_multiplier, tier, available, updated_at)
@@ -69470,6 +69531,75 @@ var init_manager2 = __esm({
69470
69531
  }
69471
69532
  });
69472
69533
 
69534
+ // packages/daemon/src/squad/model-selector.ts
69535
+ import { CopilotClient as CopilotClient2, approveAll as approveAll2 } from "@github/copilot-sdk";
69536
+ async function selectModelForTask(taskDescription) {
69537
+ const classifierModel = await getCheapestAvailableModel();
69538
+ if (!classifierModel) {
69539
+ throw new Error("No models available in pricing database");
69540
+ }
69541
+ let tier;
69542
+ try {
69543
+ tier = await classifyTaskComplexity(taskDescription, classifierModel.id);
69544
+ } catch {
69545
+ return classifierModel.id;
69546
+ }
69547
+ const selectedModel = await getCheapestInTier(tier);
69548
+ if (selectedModel) {
69549
+ return selectedModel.id;
69550
+ }
69551
+ const nextTier = getNextTierUp(tier);
69552
+ if (nextTier) {
69553
+ const escalatedModel = await getCheapestInTier(nextTier);
69554
+ if (escalatedModel) {
69555
+ return escalatedModel.id;
69556
+ }
69557
+ }
69558
+ return classifierModel.id;
69559
+ }
69560
+ async function classifyTaskComplexity(taskDescription, modelId) {
69561
+ let client2 = null;
69562
+ try {
69563
+ client2 = new CopilotClient2();
69564
+ await client2.start();
69565
+ const session = await client2.createSession({
69566
+ model: modelId,
69567
+ onPermissionRequest: approveAll2,
69568
+ systemMessage: { content: CLASSIFICATION_PROMPT }
69569
+ });
69570
+ try {
69571
+ const response = await session.sendAndWait({ prompt: `Task: ${taskDescription}` }, 15e3);
69572
+ const raw = (response.text ?? "").trim().toLowerCase();
69573
+ const tier = VALID_TIERS.find((t) => raw.includes(t));
69574
+ return tier ?? "standard";
69575
+ } finally {
69576
+ await session.disconnect().catch(() => void 0);
69577
+ }
69578
+ } finally {
69579
+ if (client2) {
69580
+ await client2.stop().catch(() => void 0);
69581
+ }
69582
+ }
69583
+ }
69584
+ var VALID_TIERS, CLASSIFICATION_PROMPT;
69585
+ var init_model_selector = __esm({
69586
+ "packages/daemon/src/squad/model-selector.ts"() {
69587
+ "use strict";
69588
+ init_registry();
69589
+ VALID_TIERS = ["trivial", "fast", "standard", "premium", "ultra"];
69590
+ CLASSIFICATION_PROMPT = `You are a task complexity classifier. Given a task description, classify its complexity into exactly one tier.
69591
+
69592
+ Tiers (from simplest to most complex):
69593
+ - trivial: Typos, renames, comment changes, config tweaks, formatting
69594
+ - fast: Simple bug fixes, small features, documentation updates, single-file changes
69595
+ - standard: Feature implementation, multi-file changes, moderate refactoring
69596
+ - premium: Architecture changes, complex refactoring, security work, performance optimization
69597
+ - ultra: System-wide redesigns, critical infrastructure, cross-cutting concerns
69598
+
69599
+ Reply with ONLY the tier name (one word, lowercase). Nothing else.`;
69600
+ }
69601
+ });
69602
+
69473
69603
  // packages/daemon/src/execution/history.ts
69474
69604
  function summarizeTaskResult(taskResult) {
69475
69605
  const condensed = taskResult.replace(/\s+/g, " ").trim();
@@ -69507,8 +69637,8 @@ import { mkdir as mkdir9, readFile as readFile7, readdir as readdir5, stat as st
69507
69637
  import { dirname as dirname8, extname as extname3, isAbsolute, join as join11, relative as relative3, resolve as resolve4 } from "node:path";
69508
69638
  import { promisify as promisify3 } from "node:util";
69509
69639
  import {
69510
- CopilotClient as CopilotClient2,
69511
- approveAll as approveAll2,
69640
+ CopilotClient as CopilotClient3,
69641
+ approveAll as approveAll3,
69512
69642
  defineTool
69513
69643
  } from "@github/copilot-sdk";
69514
69644
  function createEmptyUsage() {
@@ -69767,14 +69897,15 @@ async function executeAgentTask(member, task, worktreePath, options2) {
69767
69897
  const mcpServerNote = options2?.mcpServers?.length ? `Available MCP server labels: ${options2.mcpServers.join(", ")}.` : "No additional MCP servers were configured for this run.";
69768
69898
  let client2 = null;
69769
69899
  try {
69770
- client2 = new CopilotClient2({ workingDirectory: worktreePath });
69900
+ client2 = new CopilotClient3({ workingDirectory: worktreePath });
69771
69901
  await client2.start();
69902
+ const model = member.model ?? await selectModelForTask(task.description);
69772
69903
  const session = await client2.createSession({
69773
- model: member.model ?? DEFAULT_MODEL,
69904
+ model,
69774
69905
  workingDirectory: worktreePath,
69775
69906
  tools,
69776
69907
  availableTools: ["custom:*"],
69777
- onPermissionRequest: approveAll2,
69908
+ onPermissionRequest: approveAll3,
69778
69909
  systemMessage: {
69779
69910
  content: `${member.systemPrompt}
69780
69911
 
@@ -69826,8 +69957,8 @@ var execAsync3, MAX_FILE_SIZE, MAX_LIST_RESULTS, MAX_SEARCH_RESULTS;
69826
69957
  var init_agent = __esm({
69827
69958
  "packages/daemon/src/execution/agent.ts"() {
69828
69959
  "use strict";
69829
- init_dist();
69830
69960
  init_registry();
69961
+ init_model_selector();
69831
69962
  init_store2();
69832
69963
  init_history();
69833
69964
  execAsync3 = promisify3(exec3);
@@ -69925,7 +70056,7 @@ import { exec as exec4 } from "node:child_process";
69925
70056
  import { access, readFile as readFile8 } from "node:fs/promises";
69926
70057
  import { join as join12 } from "node:path";
69927
70058
  import { promisify as promisify4 } from "node:util";
69928
- import { CopilotClient as CopilotClient3, approveAll as approveAll3 } from "@github/copilot-sdk";
70059
+ import { CopilotClient as CopilotClient4, approveAll as approveAll4 } from "@github/copilot-sdk";
69929
70060
  async function fileExists(path) {
69930
70061
  try {
69931
70062
  await access(path);
@@ -70019,12 +70150,13 @@ Return strict JSON in this shape:
70019
70150
  }`;
70020
70151
  let client2 = null;
70021
70152
  try {
70022
- client2 = new CopilotClient3({ workingDirectory: repoPath });
70153
+ client2 = new CopilotClient4({ workingDirectory: repoPath });
70023
70154
  await client2.start();
70155
+ const model = await selectModelForTask(`Create implementation plan: ${objective.description}`);
70024
70156
  const session = await client2.createSession({
70025
- model: DEFAULT_MODEL,
70157
+ model,
70026
70158
  workingDirectory: repoPath,
70027
- onPermissionRequest: approveAll3,
70159
+ onPermissionRequest: approveAll4,
70028
70160
  systemMessage: {
70029
70161
  content: `${TEAM_LEAD_PROMPT}
70030
70162
 
@@ -70059,7 +70191,7 @@ var execAsync4, README_CANDIDATES, MAX_REPO_CONTEXT_LENGTH;
70059
70191
  var init_planning = __esm({
70060
70192
  "packages/daemon/src/execution/planning.ts"() {
70061
70193
  "use strict";
70062
- init_dist();
70194
+ init_model_selector();
70063
70195
  init_roles();
70064
70196
  execAsync4 = promisify4(exec4);
70065
70197
  README_CANDIDATES = ["README.md", "readme.md"];
@@ -70144,7 +70276,7 @@ var init_pr = __esm({
70144
70276
  // packages/daemon/src/execution/qa.ts
70145
70277
  import { exec as exec6 } from "node:child_process";
70146
70278
  import { promisify as promisify6 } from "node:util";
70147
- import { CopilotClient as CopilotClient4, approveAll as approveAll4 } from "@github/copilot-sdk";
70279
+ import { CopilotClient as CopilotClient5, approveAll as approveAll5 } from "@github/copilot-sdk";
70148
70280
  function extractJsonObject2(content) {
70149
70281
  const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
70150
70282
  if (fenced?.[1]) {
@@ -70182,12 +70314,13 @@ Return strict JSON:
70182
70314
  }`;
70183
70315
  let client2 = null;
70184
70316
  try {
70185
- client2 = new CopilotClient4({ workingDirectory: worktreePath });
70317
+ client2 = new CopilotClient5({ workingDirectory: worktreePath });
70186
70318
  await client2.start();
70319
+ const model = qaMember.model ?? await selectModelForTask(`QA review: ${objective.description}`);
70187
70320
  const session = await client2.createSession({
70188
- model: qaMember.model ?? DEFAULT_MODEL,
70321
+ model,
70189
70322
  workingDirectory: worktreePath,
70190
- onPermissionRequest: approveAll4,
70323
+ onPermissionRequest: approveAll5,
70191
70324
  systemMessage: {
70192
70325
  content: QA_PROMPT
70193
70326
  }
@@ -70261,6 +70394,7 @@ var init_qa = __esm({
70261
70394
  "use strict";
70262
70395
  init_dist();
70263
70396
  init_event_bus();
70397
+ init_model_selector();
70264
70398
  init_roles();
70265
70399
  init_store2();
70266
70400
  execAsync6 = promisify6(exec6);
@@ -70269,7 +70403,7 @@ var init_qa = __esm({
70269
70403
  });
70270
70404
 
70271
70405
  // packages/daemon/src/execution/review.ts
70272
- import { CopilotClient as CopilotClient5, approveAll as approveAll5 } from "@github/copilot-sdk";
70406
+ import { CopilotClient as CopilotClient6, approveAll as approveAll6 } from "@github/copilot-sdk";
70273
70407
  function extractJsonObject3(content) {
70274
70408
  const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
70275
70409
  if (fenced?.[1]) {
@@ -70314,11 +70448,12 @@ Return strict JSON:
70314
70448
  }`;
70315
70449
  let client2 = null;
70316
70450
  try {
70317
- client2 = new CopilotClient5();
70451
+ client2 = new CopilotClient6();
70318
70452
  await client2.start();
70453
+ const model = teamLead.model ?? await selectModelForTask(`Code review: ${objective.description}`);
70319
70454
  const session = await client2.createSession({
70320
- model: teamLead.model ?? DEFAULT_MODEL,
70321
- onPermissionRequest: approveAll5,
70455
+ model,
70456
+ onPermissionRequest: approveAll6,
70322
70457
  systemMessage: {
70323
70458
  content: `${TEAM_LEAD_PROMPT}
70324
70459
 
@@ -70352,7 +70487,7 @@ You are conducting a final coordination review before QA.`
70352
70487
  var init_review = __esm({
70353
70488
  "packages/daemon/src/execution/review.ts"() {
70354
70489
  "use strict";
70355
- init_dist();
70490
+ init_model_selector();
70356
70491
  init_roles();
70357
70492
  }
70358
70493
  });
@@ -71725,13 +71860,13 @@ var init_orchestrator = __esm({
71725
71860
  });
71726
71861
  this.activeModel = model;
71727
71862
  } catch (error51) {
71728
- if (model !== DEFAULT_MODEL) {
71863
+ if (model !== this.config.defaultModel) {
71729
71864
  this.activeSession = await createSession({
71730
- model: DEFAULT_MODEL,
71865
+ model: this.config.defaultModel,
71731
71866
  systemPrompt,
71732
71867
  tools: createBoundOrchestratorTools(this.config)
71733
71868
  });
71734
- this.activeModel = DEFAULT_MODEL;
71869
+ this.activeModel = this.config.defaultModel;
71735
71870
  } else {
71736
71871
  throw error51;
71737
71872
  }
@@ -85183,9 +85318,16 @@ var init_bot = __esm({
85183
85318
  if (this.started) {
85184
85319
  return;
85185
85320
  }
85321
+ this.bot.catch((err) => {
85322
+ this.logger.error({ err: err.error }, "Telegram bot error: %s", err.message);
85323
+ });
85186
85324
  this.registerHandlers();
85187
- this.bot.start().catch((error51) => {
85188
- this.logger.error({ err: error51 }, "Telegram polling failed");
85325
+ this.bot.start({
85326
+ onStart: () => {
85327
+ this.logger.info("Telegram bot connected and polling");
85328
+ }
85329
+ }).catch((error51) => {
85330
+ this.logger.error({ err: error51 }, "Telegram polling failed to start");
85189
85331
  });
85190
85332
  this.started = true;
85191
85333
  }
@@ -85195,11 +85337,26 @@ var init_bot = __esm({
85195
85337
  }
85196
85338
  this.bot.stop();
85197
85339
  this.started = false;
85340
+ this.logger.info("Telegram bot stopped");
85198
85341
  }
85199
85342
  async sendText(chatId, text) {
85200
85343
  await this.bot.api.sendMessage(chatId, text);
85201
85344
  }
85345
+ isAuthorized(userId) {
85346
+ if (!this.config.telegramUserId) {
85347
+ return false;
85348
+ }
85349
+ return String(userId) === this.config.telegramUserId;
85350
+ }
85202
85351
  registerHandlers() {
85352
+ this.bot.use(async (ctx, next) => {
85353
+ const userId = ctx.from?.id;
85354
+ if (!userId || !this.isAuthorized(userId)) {
85355
+ this.logger.warn({ userId }, "Unauthorized Telegram message, ignoring");
85356
+ return;
85357
+ }
85358
+ await next();
85359
+ });
85203
85360
  this.bot.command("start", async (ctx) => {
85204
85361
  await ctx.reply(
85205
85362
  "Hello from Io. Send me a message and I will route it through the daemon orchestrator."
@@ -85406,8 +85563,13 @@ async function main() {
85406
85563
  setChatOrchestrator(orchestrator2);
85407
85564
  const apiServer = createApiServer(config2);
85408
85565
  const telegramBot = createTelegramBot(config2, orchestrator2);
85409
- telegramBot?.start();
85410
- createTelegramNotifier(telegramBot, config2, eventBus);
85566
+ if (telegramBot) {
85567
+ telegramBot.start();
85568
+ createTelegramNotifier(telegramBot, config2, eventBus);
85569
+ logger2.info("Telegram bot initialized");
85570
+ } else {
85571
+ logger2.info("Telegram bot disabled (no token configured)");
85572
+ }
85411
85573
  registerShutdownHandlers(logger2, async () => {
85412
85574
  if (pricingRefreshTimer) {
85413
85575
  clearInterval(pricingRefreshTimer);