heyio 4.2.7 → 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.7";
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;
@@ -68341,12 +68341,15 @@ async function refreshModelPricing(logger2) {
68341
68341
  const db = await getDatabase();
68342
68342
  const now = nowIso();
68343
68343
  for (const model of modelMap.values()) {
68344
+ if (model.tokenInputMultiplier == null) {
68345
+ continue;
68346
+ }
68344
68347
  const tier = computeTierFromMultiplier(model.premiumMultiplier ?? null);
68345
68348
  await upsertModel(db, {
68346
68349
  id: model.id,
68347
68350
  displayName: model.displayName,
68348
68351
  premiumMultiplier: model.premiumMultiplier ?? null,
68349
- tokenInputMultiplier: model.tokenInputMultiplier ?? null,
68352
+ tokenInputMultiplier: model.tokenInputMultiplier,
68350
68353
  tokenOutputMultiplier: model.tokenOutputMultiplier ?? null,
68351
68354
  cachedInputMultiplier: model.cachedInputMultiplier ?? null,
68352
68355
  tier,
@@ -68357,6 +68360,18 @@ async function refreshModelPricing(logger2) {
68357
68360
  }
68358
68361
  return result;
68359
68362
  }
68363
+ async function getModelsForTier(tier) {
68364
+ const db = await getDatabase();
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;
68374
+ }
68360
68375
  async function getModelPricing(modelId) {
68361
68376
  const db = await getDatabase();
68362
68377
  const result = await db.execute({
@@ -68377,6 +68392,16 @@ async function getModelPricing(modelId) {
68377
68392
  }
68378
68393
  return null;
68379
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
+ }
68380
68405
  function calculateTokenUnitCost(inputTokens, outputTokens, inputMultiplier, outputMultiplier) {
68381
68406
  if (inputMultiplier === null || outputMultiplier === null) {
68382
68407
  return 0;
@@ -68384,6 +68409,14 @@ function calculateTokenUnitCost(inputTokens, outputTokens, inputMultiplier, outp
68384
68409
  const tokenUnits = inputTokens * inputMultiplier + outputTokens * outputMultiplier;
68385
68410
  return tokenUnits * TOKEN_UNIT_PRICE;
68386
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
+ }
68387
68420
  async function upsertModel(db, model) {
68388
68421
  await db.execute({
68389
68422
  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 +69503,75 @@ var init_manager2 = __esm({
69470
69503
  }
69471
69504
  });
69472
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
+
69473
69575
  // packages/daemon/src/execution/history.ts
69474
69576
  function summarizeTaskResult(taskResult) {
69475
69577
  const condensed = taskResult.replace(/\s+/g, " ").trim();
@@ -69507,8 +69609,8 @@ import { mkdir as mkdir9, readFile as readFile7, readdir as readdir5, stat as st
69507
69609
  import { dirname as dirname8, extname as extname3, isAbsolute, join as join11, relative as relative3, resolve as resolve4 } from "node:path";
69508
69610
  import { promisify as promisify3 } from "node:util";
69509
69611
  import {
69510
- CopilotClient as CopilotClient2,
69511
- approveAll as approveAll2,
69612
+ CopilotClient as CopilotClient3,
69613
+ approveAll as approveAll3,
69512
69614
  defineTool
69513
69615
  } from "@github/copilot-sdk";
69514
69616
  function createEmptyUsage() {
@@ -69767,14 +69869,15 @@ async function executeAgentTask(member, task, worktreePath, options2) {
69767
69869
  const mcpServerNote = options2?.mcpServers?.length ? `Available MCP server labels: ${options2.mcpServers.join(", ")}.` : "No additional MCP servers were configured for this run.";
69768
69870
  let client2 = null;
69769
69871
  try {
69770
- client2 = new CopilotClient2({ workingDirectory: worktreePath });
69872
+ client2 = new CopilotClient3({ workingDirectory: worktreePath });
69771
69873
  await client2.start();
69874
+ const model = member.model ?? await selectModelForTask(task.description);
69772
69875
  const session = await client2.createSession({
69773
- model: member.model ?? DEFAULT_MODEL,
69876
+ model,
69774
69877
  workingDirectory: worktreePath,
69775
69878
  tools,
69776
69879
  availableTools: ["custom:*"],
69777
- onPermissionRequest: approveAll2,
69880
+ onPermissionRequest: approveAll3,
69778
69881
  systemMessage: {
69779
69882
  content: `${member.systemPrompt}
69780
69883
 
@@ -69826,8 +69929,8 @@ var execAsync3, MAX_FILE_SIZE, MAX_LIST_RESULTS, MAX_SEARCH_RESULTS;
69826
69929
  var init_agent = __esm({
69827
69930
  "packages/daemon/src/execution/agent.ts"() {
69828
69931
  "use strict";
69829
- init_dist();
69830
69932
  init_registry();
69933
+ init_model_selector();
69831
69934
  init_store2();
69832
69935
  init_history();
69833
69936
  execAsync3 = promisify3(exec3);
@@ -69925,7 +70028,7 @@ import { exec as exec4 } from "node:child_process";
69925
70028
  import { access, readFile as readFile8 } from "node:fs/promises";
69926
70029
  import { join as join12 } from "node:path";
69927
70030
  import { promisify as promisify4 } from "node:util";
69928
- import { CopilotClient as CopilotClient3, approveAll as approveAll3 } from "@github/copilot-sdk";
70031
+ import { CopilotClient as CopilotClient4, approveAll as approveAll4 } from "@github/copilot-sdk";
69929
70032
  async function fileExists(path) {
69930
70033
  try {
69931
70034
  await access(path);
@@ -70019,12 +70122,13 @@ Return strict JSON in this shape:
70019
70122
  }`;
70020
70123
  let client2 = null;
70021
70124
  try {
70022
- client2 = new CopilotClient3({ workingDirectory: repoPath });
70125
+ client2 = new CopilotClient4({ workingDirectory: repoPath });
70023
70126
  await client2.start();
70127
+ const model = await selectModelForTask(`Create implementation plan: ${objective.description}`);
70024
70128
  const session = await client2.createSession({
70025
- model: DEFAULT_MODEL,
70129
+ model,
70026
70130
  workingDirectory: repoPath,
70027
- onPermissionRequest: approveAll3,
70131
+ onPermissionRequest: approveAll4,
70028
70132
  systemMessage: {
70029
70133
  content: `${TEAM_LEAD_PROMPT}
70030
70134
 
@@ -70059,7 +70163,7 @@ var execAsync4, README_CANDIDATES, MAX_REPO_CONTEXT_LENGTH;
70059
70163
  var init_planning = __esm({
70060
70164
  "packages/daemon/src/execution/planning.ts"() {
70061
70165
  "use strict";
70062
- init_dist();
70166
+ init_model_selector();
70063
70167
  init_roles();
70064
70168
  execAsync4 = promisify4(exec4);
70065
70169
  README_CANDIDATES = ["README.md", "readme.md"];
@@ -70144,7 +70248,7 @@ var init_pr = __esm({
70144
70248
  // packages/daemon/src/execution/qa.ts
70145
70249
  import { exec as exec6 } from "node:child_process";
70146
70250
  import { promisify as promisify6 } from "node:util";
70147
- import { CopilotClient as CopilotClient4, approveAll as approveAll4 } from "@github/copilot-sdk";
70251
+ import { CopilotClient as CopilotClient5, approveAll as approveAll5 } from "@github/copilot-sdk";
70148
70252
  function extractJsonObject2(content) {
70149
70253
  const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
70150
70254
  if (fenced?.[1]) {
@@ -70182,12 +70286,13 @@ Return strict JSON:
70182
70286
  }`;
70183
70287
  let client2 = null;
70184
70288
  try {
70185
- client2 = new CopilotClient4({ workingDirectory: worktreePath });
70289
+ client2 = new CopilotClient5({ workingDirectory: worktreePath });
70186
70290
  await client2.start();
70291
+ const model = qaMember.model ?? await selectModelForTask(`QA review: ${objective.description}`);
70187
70292
  const session = await client2.createSession({
70188
- model: qaMember.model ?? DEFAULT_MODEL,
70293
+ model,
70189
70294
  workingDirectory: worktreePath,
70190
- onPermissionRequest: approveAll4,
70295
+ onPermissionRequest: approveAll5,
70191
70296
  systemMessage: {
70192
70297
  content: QA_PROMPT
70193
70298
  }
@@ -70261,6 +70366,7 @@ var init_qa = __esm({
70261
70366
  "use strict";
70262
70367
  init_dist();
70263
70368
  init_event_bus();
70369
+ init_model_selector();
70264
70370
  init_roles();
70265
70371
  init_store2();
70266
70372
  execAsync6 = promisify6(exec6);
@@ -70269,7 +70375,7 @@ var init_qa = __esm({
70269
70375
  });
70270
70376
 
70271
70377
  // packages/daemon/src/execution/review.ts
70272
- import { CopilotClient as CopilotClient5, approveAll as approveAll5 } from "@github/copilot-sdk";
70378
+ import { CopilotClient as CopilotClient6, approveAll as approveAll6 } from "@github/copilot-sdk";
70273
70379
  function extractJsonObject3(content) {
70274
70380
  const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
70275
70381
  if (fenced?.[1]) {
@@ -70314,11 +70420,12 @@ Return strict JSON:
70314
70420
  }`;
70315
70421
  let client2 = null;
70316
70422
  try {
70317
- client2 = new CopilotClient5();
70423
+ client2 = new CopilotClient6();
70318
70424
  await client2.start();
70425
+ const model = teamLead.model ?? await selectModelForTask(`Code review: ${objective.description}`);
70319
70426
  const session = await client2.createSession({
70320
- model: teamLead.model ?? DEFAULT_MODEL,
70321
- onPermissionRequest: approveAll5,
70427
+ model,
70428
+ onPermissionRequest: approveAll6,
70322
70429
  systemMessage: {
70323
70430
  content: `${TEAM_LEAD_PROMPT}
70324
70431
 
@@ -70352,7 +70459,7 @@ You are conducting a final coordination review before QA.`
70352
70459
  var init_review = __esm({
70353
70460
  "packages/daemon/src/execution/review.ts"() {
70354
70461
  "use strict";
70355
- init_dist();
70462
+ init_model_selector();
70356
70463
  init_roles();
70357
70464
  }
70358
70465
  });
@@ -71725,13 +71832,13 @@ var init_orchestrator = __esm({
71725
71832
  });
71726
71833
  this.activeModel = model;
71727
71834
  } catch (error51) {
71728
- if (model !== DEFAULT_MODEL) {
71835
+ if (model !== this.config.defaultModel) {
71729
71836
  this.activeSession = await createSession({
71730
- model: DEFAULT_MODEL,
71837
+ model: this.config.defaultModel,
71731
71838
  systemPrompt,
71732
71839
  tools: createBoundOrchestratorTools(this.config)
71733
71840
  });
71734
- this.activeModel = DEFAULT_MODEL;
71841
+ this.activeModel = this.config.defaultModel;
71735
71842
  } else {
71736
71843
  throw error51;
71737
71844
  }
@@ -85183,9 +85290,16 @@ var init_bot = __esm({
85183
85290
  if (this.started) {
85184
85291
  return;
85185
85292
  }
85293
+ this.bot.catch((err) => {
85294
+ this.logger.error({ err: err.error }, "Telegram bot error: %s", err.message);
85295
+ });
85186
85296
  this.registerHandlers();
85187
- this.bot.start().catch((error51) => {
85188
- 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");
85189
85303
  });
85190
85304
  this.started = true;
85191
85305
  }
@@ -85195,11 +85309,26 @@ var init_bot = __esm({
85195
85309
  }
85196
85310
  this.bot.stop();
85197
85311
  this.started = false;
85312
+ this.logger.info("Telegram bot stopped");
85198
85313
  }
85199
85314
  async sendText(chatId, text) {
85200
85315
  await this.bot.api.sendMessage(chatId, text);
85201
85316
  }
85317
+ isAuthorized(userId) {
85318
+ if (!this.config.telegramUserId) {
85319
+ return false;
85320
+ }
85321
+ return String(userId) === this.config.telegramUserId;
85322
+ }
85202
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
+ });
85203
85332
  this.bot.command("start", async (ctx) => {
85204
85333
  await ctx.reply(
85205
85334
  "Hello from Io. Send me a message and I will route it through the daemon orchestrator."
@@ -85406,8 +85535,13 @@ async function main() {
85406
85535
  setChatOrchestrator(orchestrator2);
85407
85536
  const apiServer = createApiServer(config2);
85408
85537
  const telegramBot = createTelegramBot(config2, orchestrator2);
85409
- telegramBot?.start();
85410
- 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
+ }
85411
85545
  registerShutdownHandlers(logger2, async () => {
85412
85546
  if (pricingRefreshTimer) {
85413
85547
  clearInterval(pricingRefreshTimer);