heyio 0.1.16 → 0.1.18
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/copilot/agents.js
CHANGED
|
@@ -5,6 +5,7 @@ import { join, dirname, resolve } from "path";
|
|
|
5
5
|
import { defineTool, approveAll } from "@github/copilot-sdk";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { getClient } from "./client.js";
|
|
8
|
+
import { getModelForTask } from "./model-router.js";
|
|
8
9
|
import { getSquad, updateSquadSession, updateSquadStatus, getDecisionsSummary, logDecision, } from "../store/squads.js";
|
|
9
10
|
import { createTask, completeTask, failTask, getActiveTasks, } from "../store/tasks.js";
|
|
10
11
|
import { SESSIONS_DIR } from "../paths.js";
|
|
@@ -40,7 +41,7 @@ export async function delegateToAgent(squadSlug, task, onComplete) {
|
|
|
40
41
|
if (!squad) {
|
|
41
42
|
throw new Error(`Squad not found: ${squadSlug}`);
|
|
42
43
|
}
|
|
43
|
-
const session = await getOrCreateSession(squadSlug);
|
|
44
|
+
const session = await getOrCreateSession(squadSlug, task);
|
|
44
45
|
const taskId = randomUUID();
|
|
45
46
|
createTask(taskId, squadSlug, task);
|
|
46
47
|
updateSquadStatus(squadSlug, "working");
|
|
@@ -83,7 +84,7 @@ export function getActiveAgentTasks() {
|
|
|
83
84
|
// ---------------------------------------------------------------------------
|
|
84
85
|
// Internal helpers
|
|
85
86
|
// ---------------------------------------------------------------------------
|
|
86
|
-
async function getOrCreateSession(squadSlug) {
|
|
87
|
+
async function getOrCreateSession(squadSlug, taskDescription) {
|
|
87
88
|
const existing = agentSessions.get(squadSlug);
|
|
88
89
|
if (existing)
|
|
89
90
|
return existing;
|
|
@@ -91,8 +92,9 @@ async function getOrCreateSession(squadSlug) {
|
|
|
91
92
|
const client = await getClient();
|
|
92
93
|
const decisions = getDecisionsSummary(squadSlug);
|
|
93
94
|
const agentTools = buildAgentTools(squadSlug);
|
|
95
|
+
const model = getModelForTask(taskDescription ?? "", squad.model);
|
|
94
96
|
const commonConfig = {
|
|
95
|
-
model
|
|
97
|
+
model,
|
|
96
98
|
configDir: SESSIONS_DIR,
|
|
97
99
|
streaming: false,
|
|
98
100
|
systemMessage: {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { config } from "../config.js";
|
|
2
|
+
const DEFAULT_TIERS = {
|
|
3
|
+
high: ["claude-opus-4.7", "claude-opus-4.6"],
|
|
4
|
+
medium: ["claude-sonnet-4.6", "gpt-5.5", "claude-opus-4.5"],
|
|
5
|
+
low: ["claude-haiku-4.5", "gpt-5.4-mini"],
|
|
6
|
+
};
|
|
7
|
+
// Resolved model for each tier (populated at startup)
|
|
8
|
+
let resolvedTiers = null;
|
|
9
|
+
const HIGH_KEYWORDS = [
|
|
10
|
+
"architect", "refactor", "redesign", "debug", "design",
|
|
11
|
+
"complex", "migration", "security", "performance", "optimize",
|
|
12
|
+
"rewrite", "overhaul", "investigate", "diagnose", "plan",
|
|
13
|
+
];
|
|
14
|
+
const LOW_KEYWORDS = [
|
|
15
|
+
"read", "list", "format", "lookup", "check", "status",
|
|
16
|
+
"simple", "rename", "typo", "log", "print", "echo",
|
|
17
|
+
"delete file", "remove file", "copy file", "move file",
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* Resolve each tier to the first available model from its preference list.
|
|
21
|
+
* Call once at startup with the result of `client.listModels()`.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveModelTiers(availableModelIds) {
|
|
24
|
+
const available = new Set(availableModelIds);
|
|
25
|
+
const tiers = config.modelTiers ?? {};
|
|
26
|
+
const result = {};
|
|
27
|
+
for (const tier of ["high", "medium", "low"]) {
|
|
28
|
+
const candidates = tiers[tier] ?? DEFAULT_TIERS[tier];
|
|
29
|
+
const match = candidates.find((m) => available.has(m));
|
|
30
|
+
if (match) {
|
|
31
|
+
result[tier] = match;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// Fallback: use defaultModel, then first candidate regardless of availability
|
|
35
|
+
result[tier] = config.defaultModel || candidates[0];
|
|
36
|
+
}
|
|
37
|
+
console.error(`[io] Model tier "${tier}" resolved to: ${result[tier]}`);
|
|
38
|
+
}
|
|
39
|
+
resolvedTiers = result;
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Classify a task description into a complexity tier.
|
|
44
|
+
*/
|
|
45
|
+
export function classifyComplexity(taskDescription) {
|
|
46
|
+
const lower = taskDescription.toLowerCase();
|
|
47
|
+
const highScore = HIGH_KEYWORDS.filter((kw) => lower.includes(kw)).length;
|
|
48
|
+
const lowScore = LOW_KEYWORDS.filter((kw) => lower.includes(kw)).length;
|
|
49
|
+
// Strong signals
|
|
50
|
+
if (highScore >= 2)
|
|
51
|
+
return "high";
|
|
52
|
+
if (lowScore >= 2)
|
|
53
|
+
return "low";
|
|
54
|
+
// Weak signals
|
|
55
|
+
if (highScore > lowScore)
|
|
56
|
+
return "high";
|
|
57
|
+
if (lowScore > highScore)
|
|
58
|
+
return "low";
|
|
59
|
+
// Default to medium for ambiguous tasks
|
|
60
|
+
return "medium";
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the resolved model for a tier.
|
|
64
|
+
*/
|
|
65
|
+
export function getModelForTier(tier) {
|
|
66
|
+
if (!resolvedTiers) {
|
|
67
|
+
console.error("[io] Warning: model tiers not yet resolved, using defaults");
|
|
68
|
+
return config.defaultModel || DEFAULT_TIERS[tier][0];
|
|
69
|
+
}
|
|
70
|
+
return resolvedTiers[tier];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get the best model for a task based on its description.
|
|
74
|
+
* If squadModel is provided, it always takes priority.
|
|
75
|
+
*/
|
|
76
|
+
export function getModelForTask(taskDescription, squadModel) {
|
|
77
|
+
if (squadModel)
|
|
78
|
+
return squadModel;
|
|
79
|
+
const tier = classifyComplexity(taskDescription);
|
|
80
|
+
const model = getModelForTier(tier);
|
|
81
|
+
console.error(`[io] Task classified as "${tier}" → model: ${model}`);
|
|
82
|
+
return model;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=model-router.js.map
|
|
@@ -5,6 +5,7 @@ import { getState, setState, deleteState, logConversation } from "../store/db.js
|
|
|
5
5
|
import { clearStaleTasks } from "../store/tasks.js";
|
|
6
6
|
import { getSquad, listSquads, createSquad, logDecision, getDecisionsSummary, updateSquadStatus, } from "../store/squads.js";
|
|
7
7
|
import { readPage, writePage, assertPagePath } from "../wiki/fs.js";
|
|
8
|
+
import { resolveModelTiers } from "./model-router.js";
|
|
8
9
|
import { searchWiki, getWikiSummary } from "../wiki/search.js";
|
|
9
10
|
import { getOrchestratorSystemMessage } from "./system-message.js";
|
|
10
11
|
import { createTools } from "./tools.js";
|
|
@@ -293,7 +294,7 @@ async function processQueue() {
|
|
|
293
294
|
export async function initOrchestrator(copilotClient) {
|
|
294
295
|
client = copilotClient;
|
|
295
296
|
clearStaleTasks();
|
|
296
|
-
// Validate the configured model
|
|
297
|
+
// Validate the configured model and resolve model tiers
|
|
297
298
|
try {
|
|
298
299
|
const models = await copilotClient.listModels();
|
|
299
300
|
const defaultModel = config.defaultModel || "gpt-4.1";
|
|
@@ -304,6 +305,7 @@ export async function initOrchestrator(copilotClient) {
|
|
|
304
305
|
else {
|
|
305
306
|
console.error(`[io] Model validated: ${defaultModel}`);
|
|
306
307
|
}
|
|
308
|
+
resolveModelTiers(modelIds);
|
|
307
309
|
}
|
|
308
310
|
catch (err) {
|
|
309
311
|
console.error("[io] Could not validate models:", err instanceof Error ? err.message : err);
|
|
@@ -62,6 +62,14 @@ Squads are persistent project teams. When a user works on a codebase:
|
|
|
62
62
|
3. Recall squad context with \`squad_recall\` before doing project work.
|
|
63
63
|
4. Check squad status with \`squad_status\`.
|
|
64
64
|
|
|
65
|
+
### Model Selection
|
|
66
|
+
Squad agents are automatically assigned a model based on task complexity:
|
|
67
|
+
- **High complexity** (architecture, refactoring, debugging, design) → most capable model
|
|
68
|
+
- **Medium complexity** (implementing features, writing tests, reviews) → balanced model
|
|
69
|
+
- **Low complexity** (file reads, formatting, lookups) → fast/cheap model
|
|
70
|
+
|
|
71
|
+
The model is selected automatically. Tell the user which model tier was chosen when delegating tasks.
|
|
72
|
+
|
|
65
73
|
## Tool Usage
|
|
66
74
|
|
|
67
75
|
### Knowledge Base
|
|
@@ -78,6 +86,7 @@ Squads are persistent project teams. When a user works on a codebase:
|
|
|
78
86
|
### System
|
|
79
87
|
- \`shell\`: Run a shell command. You have full system access — you can create directories, install packages, clone repos, etc. **Always use this instead of the built-in \`bash\` tool.**
|
|
80
88
|
- \`file_ops\`: Read, write, or list files anywhere on the filesystem. Can create directories automatically.
|
|
89
|
+
- \`github\`: Manage GitHub issues and PRs — create, list, view, comment, close issues; create, list, view, comment on PRs.
|
|
81
90
|
- \`web_fetch\`: (built-in) Fetch a URL and return content.
|
|
82
91
|
|
|
83
92
|
## Guidelines
|
package/dist/copilot/tools.js
CHANGED
|
@@ -409,7 +409,132 @@ export function createTools(deps) {
|
|
|
409
409
|
}
|
|
410
410
|
},
|
|
411
411
|
});
|
|
412
|
-
|
|
412
|
+
// GitHub issue/PR management via gh CLI
|
|
413
|
+
const github = defineTool("github", {
|
|
414
|
+
description: "Manage GitHub issues and pull requests using the gh CLI. Supports creating, listing, viewing, and commenting on issues and PRs.",
|
|
415
|
+
skipPermission: true,
|
|
416
|
+
parameters: z.object({
|
|
417
|
+
action: z
|
|
418
|
+
.enum([
|
|
419
|
+
"create_issue",
|
|
420
|
+
"list_issues",
|
|
421
|
+
"view_issue",
|
|
422
|
+
"comment_issue",
|
|
423
|
+
"close_issue",
|
|
424
|
+
"create_pr",
|
|
425
|
+
"list_prs",
|
|
426
|
+
"view_pr",
|
|
427
|
+
"comment_pr",
|
|
428
|
+
])
|
|
429
|
+
.describe("The GitHub action to perform"),
|
|
430
|
+
repo: z.string().describe("Repository in owner/repo format"),
|
|
431
|
+
title: z.string().optional().describe("Title (for create_issue, create_pr)"),
|
|
432
|
+
body: z.string().optional().describe("Body text (for create_issue, create_pr, comment_*)"),
|
|
433
|
+
labels: z.array(z.string()).optional().describe("Labels (for create_issue)"),
|
|
434
|
+
assignees: z.array(z.string()).optional().describe("Assignees (for create_issue)"),
|
|
435
|
+
number: z.number().optional().describe("Issue or PR number (for view, comment, close)"),
|
|
436
|
+
base: z.string().optional().describe("Base branch (for create_pr)"),
|
|
437
|
+
head: z.string().optional().describe("Head branch (for create_pr)"),
|
|
438
|
+
state: z.enum(["open", "closed", "all"]).optional().describe("Filter by state (for list_*)"),
|
|
439
|
+
limit: z.number().optional().describe("Max results (for list_*, default 10)"),
|
|
440
|
+
}),
|
|
441
|
+
handler: async ({ action, repo, title, body, labels, assignees, number, base, head, state, limit }) => {
|
|
442
|
+
console.error(`[io] github tool called: ${action} on ${repo}`);
|
|
443
|
+
try {
|
|
444
|
+
let cmd;
|
|
445
|
+
const r = `--repo ${repo}`;
|
|
446
|
+
switch (action) {
|
|
447
|
+
case "create_issue": {
|
|
448
|
+
if (!title)
|
|
449
|
+
return "Error: title is required for create_issue";
|
|
450
|
+
cmd = `gh issue create ${r} --title "${title.replace(/"/g, '\\"')}"`;
|
|
451
|
+
if (body)
|
|
452
|
+
cmd += ` --body "${body.replace(/"/g, '\\"')}"`;
|
|
453
|
+
if (labels?.length)
|
|
454
|
+
cmd += ` --label "${labels.join(",")}"`;
|
|
455
|
+
if (assignees?.length)
|
|
456
|
+
cmd += ` --assignee "${assignees.join(",")}"`;
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
case "list_issues": {
|
|
460
|
+
cmd = `gh issue list ${r} --limit ${limit ?? 10}`;
|
|
461
|
+
if (state)
|
|
462
|
+
cmd += ` --state ${state}`;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
case "view_issue": {
|
|
466
|
+
if (!number)
|
|
467
|
+
return "Error: number is required for view_issue";
|
|
468
|
+
cmd = `gh issue view ${number} ${r}`;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
case "comment_issue": {
|
|
472
|
+
if (!number)
|
|
473
|
+
return "Error: number is required for comment_issue";
|
|
474
|
+
if (!body)
|
|
475
|
+
return "Error: body is required for comment_issue";
|
|
476
|
+
cmd = `gh issue comment ${number} ${r} --body "${body.replace(/"/g, '\\"')}"`;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case "close_issue": {
|
|
480
|
+
if (!number)
|
|
481
|
+
return "Error: number is required for close_issue";
|
|
482
|
+
cmd = `gh issue close ${number} ${r}`;
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
case "create_pr": {
|
|
486
|
+
if (!title)
|
|
487
|
+
return "Error: title is required for create_pr";
|
|
488
|
+
cmd = `gh pr create ${r} --title "${title.replace(/"/g, '\\"')}"`;
|
|
489
|
+
if (body)
|
|
490
|
+
cmd += ` --body "${body.replace(/"/g, '\\"')}"`;
|
|
491
|
+
if (base)
|
|
492
|
+
cmd += ` --base ${base}`;
|
|
493
|
+
if (head)
|
|
494
|
+
cmd += ` --head ${head}`;
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
case "list_prs": {
|
|
498
|
+
cmd = `gh pr list ${r} --limit ${limit ?? 10}`;
|
|
499
|
+
if (state)
|
|
500
|
+
cmd += ` --state ${state}`;
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
case "view_pr": {
|
|
504
|
+
if (!number)
|
|
505
|
+
return "Error: number is required for view_pr";
|
|
506
|
+
cmd = `gh pr view ${number} ${r}`;
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
case "comment_pr": {
|
|
510
|
+
if (!number)
|
|
511
|
+
return "Error: number is required for comment_pr";
|
|
512
|
+
if (!body)
|
|
513
|
+
return "Error: body is required for comment_pr";
|
|
514
|
+
cmd = `gh pr comment ${number} ${r} --body "${body.replace(/"/g, '\\"')}"`;
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
default:
|
|
518
|
+
return `Unknown action: ${action}`;
|
|
519
|
+
}
|
|
520
|
+
const result = execSync(cmd, {
|
|
521
|
+
encoding: "utf-8",
|
|
522
|
+
timeout: 30_000,
|
|
523
|
+
maxBuffer: 1024 * 1024,
|
|
524
|
+
}).trim();
|
|
525
|
+
if (result.length > 8000) {
|
|
526
|
+
return result.slice(0, 8000) + "\n\n[…truncated]";
|
|
527
|
+
}
|
|
528
|
+
return result || "(success, no output)";
|
|
529
|
+
}
|
|
530
|
+
catch (err) {
|
|
531
|
+
const execErr = err;
|
|
532
|
+
const msg = execErr.stderr?.trim() || execErr.stdout?.trim() || execErr.message || "Command failed";
|
|
533
|
+
return `Error: ${msg.length > 4000 ? msg.slice(0, 4000) + "\n[…truncated]" : msg}`;
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
return [wikiRead, wikiWrite, wikiSearch, squadCreate, squadRecall, squadStatus, squadLogDecision, shell, fileOps, bash, readFile, viewTool, grepTool, strReplaceEditor, github];
|
|
413
538
|
}
|
|
414
539
|
function walkDirectory(dir, maxDepth = 3, depth = 0) {
|
|
415
540
|
if (depth >= maxDepth)
|
package/dist/store/db.js
CHANGED
|
@@ -21,6 +21,7 @@ export function getDb() {
|
|
|
21
21
|
name TEXT NOT NULL,
|
|
22
22
|
project_path TEXT NOT NULL,
|
|
23
23
|
copilot_session_id TEXT,
|
|
24
|
+
model TEXT,
|
|
24
25
|
status TEXT NOT NULL DEFAULT 'idle',
|
|
25
26
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
26
27
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
@@ -53,6 +54,13 @@ export function getDb() {
|
|
|
53
54
|
completed_at DATETIME
|
|
54
55
|
);
|
|
55
56
|
`);
|
|
57
|
+
// Migration: add model column to squads (for existing databases)
|
|
58
|
+
try {
|
|
59
|
+
db.exec(`ALTER TABLE squads ADD COLUMN model TEXT`);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Column already exists — ignore
|
|
63
|
+
}
|
|
56
64
|
return db;
|
|
57
65
|
}
|
|
58
66
|
export function closeDb() {
|
package/dist/store/squads.js
CHANGED
|
@@ -22,6 +22,11 @@ export function updateSquadStatus(slug, status) {
|
|
|
22
22
|
.prepare("UPDATE squads SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE slug = ?")
|
|
23
23
|
.run(status, slug);
|
|
24
24
|
}
|
|
25
|
+
export function updateSquadModel(slug, model) {
|
|
26
|
+
getDb()
|
|
27
|
+
.prepare("UPDATE squads SET model = ?, updated_at = CURRENT_TIMESTAMP WHERE slug = ?")
|
|
28
|
+
.run(model, slug);
|
|
29
|
+
}
|
|
25
30
|
export function deleteSquad(slug) {
|
|
26
31
|
const db = getDb();
|
|
27
32
|
db.prepare("DELETE FROM squad_decisions WHERE squad_slug = ?").run(slug);
|