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.
- package/dist/daemon/cli.js +164 -30
- package/dist/daemon/index.js +975 -791
- package/package.json +1 -1
package/dist/daemon/cli.js
CHANGED
|
@@ -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.
|
|
83
|
+
APP_VERSION = "4.3.0";
|
|
84
84
|
API_PORT = 7777;
|
|
85
85
|
API_HOST = "0.0.0.0";
|
|
86
|
-
DEFAULT_MODEL = "gpt-
|
|
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
|
|
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
|
|
69511
|
-
approveAll as
|
|
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
|
|
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
|
|
69876
|
+
model,
|
|
69774
69877
|
workingDirectory: worktreePath,
|
|
69775
69878
|
tools,
|
|
69776
69879
|
availableTools: ["custom:*"],
|
|
69777
|
-
onPermissionRequest:
|
|
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
|
|
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
|
|
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
|
|
70129
|
+
model,
|
|
70026
70130
|
workingDirectory: repoPath,
|
|
70027
|
-
onPermissionRequest:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
70293
|
+
model,
|
|
70189
70294
|
workingDirectory: worktreePath,
|
|
70190
|
-
onPermissionRequest:
|
|
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
|
|
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
|
|
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
|
|
70321
|
-
onPermissionRequest:
|
|
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
|
-
|
|
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 !==
|
|
71835
|
+
if (model !== this.config.defaultModel) {
|
|
71729
71836
|
this.activeSession = await createSession({
|
|
71730
|
-
model:
|
|
71837
|
+
model: this.config.defaultModel,
|
|
71731
71838
|
systemPrompt,
|
|
71732
71839
|
tools: createBoundOrchestratorTools(this.config)
|
|
71733
71840
|
});
|
|
71734
|
-
this.activeModel =
|
|
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(
|
|
85188
|
-
|
|
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
|
|
85410
|
-
|
|
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);
|