heyio 0.1.19 → 0.1.21
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/README.md +23 -11
- package/dist/copilot/orchestrator.js +24 -4
- package/dist/copilot/system-message.js +25 -0
- package/dist/copilot/tools.js +207 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,21 +100,33 @@ WantedBy=multi-user.target
|
|
|
100
100
|
|
|
101
101
|
IO stores its configuration at `~/.io/config.json`. The setup wizard (`io setup`) handles initial configuration, but you can also edit the file directly.
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
{
|
|
105
|
-
// Telegram bot token from @BotFather
|
|
106
|
-
"telegramBotToken": "123456:ABC-DEF...",
|
|
103
|
+
### Parameters
|
|
107
104
|
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
| Parameter | Type | Default | Description |
|
|
106
|
+
| --- | --- | --- | --- |
|
|
107
|
+
| `telegramBotToken` | `string` | — | Telegram bot token from [@BotFather](https://t.me/BotFather) |
|
|
108
|
+
| `authorizedUserId` | `number` | — | Your Telegram user ID (only this user can interact with the bot) |
|
|
109
|
+
| `telegramEnabled` | `boolean` | `false` | Enable the Telegram bot interface |
|
|
110
|
+
| `selfEditEnabled` | `boolean` | `false` | Allow IO to modify its own source code |
|
|
111
|
+
| `defaultModel` | `string` | `"gpt-4.1"` | LLM model for the main orchestrator session |
|
|
112
|
+
| `modelTiers` | `object` | *(see below)* | Per-complexity model preferences for squad agents |
|
|
113
|
+
| `modelTiers.high` | `string[]` | `["claude-opus-4.7", "claude-opus-4.6"]` | Models for complex tasks (architecture, debugging, design) |
|
|
114
|
+
| `modelTiers.medium` | `string[]` | `["claude-sonnet-4.6", "gpt-5.5", "claude-opus-4.5"]` | Models for standard tasks (features, tests, reviews) |
|
|
115
|
+
| `modelTiers.low` | `string[]` | `["claude-haiku-4.5", "gpt-5.4-mini"]` | Models for simple tasks (reads, formatting, lookups) |
|
|
116
|
+
| `apiPort` | `number` | `3170` | Port for the HTTP API server |
|
|
110
117
|
|
|
111
|
-
|
|
112
|
-
"selfEdit": false,
|
|
118
|
+
Each `modelTiers` list is a ranked preference — IO picks the first available model at startup.
|
|
113
119
|
|
|
114
|
-
|
|
115
|
-
"defaultModel": "claude-sonnet-4.6",
|
|
120
|
+
### Example
|
|
116
121
|
|
|
117
|
-
|
|
122
|
+
```jsonc
|
|
123
|
+
{
|
|
124
|
+
"telegramBotToken": "123456:ABC-DEF...",
|
|
125
|
+
"authorizedUserId": 123456789,
|
|
126
|
+
"telegramEnabled": true,
|
|
127
|
+
"selfEditEnabled": false,
|
|
128
|
+
"defaultModel": "claude-sonnet-4.6",
|
|
129
|
+
"apiPort": 3170,
|
|
118
130
|
"modelTiers": {
|
|
119
131
|
"high": ["claude-opus-4.7", "claude-opus-4.6"],
|
|
120
132
|
"medium": ["claude-sonnet-4.6", "gpt-5.5", "claude-opus-4.5"],
|
|
@@ -2,15 +2,18 @@ import { approveAll, } from "@github/copilot-sdk";
|
|
|
2
2
|
import { config } from "../config.js";
|
|
3
3
|
import { SESSIONS_DIR } from "../paths.js";
|
|
4
4
|
import { getState, setState, deleteState, logConversation } from "../store/db.js";
|
|
5
|
-
import { clearStaleTasks } from "../store/tasks.js";
|
|
6
|
-
import { getSquad, listSquads, createSquad, logDecision, getDecisionsSummary, updateSquadStatus, } from "../store/squads.js";
|
|
7
|
-
import { readPage, writePage, assertPagePath } from "../wiki/fs.js";
|
|
5
|
+
import { clearStaleTasks, getTask } from "../store/tasks.js";
|
|
6
|
+
import { getSquad, listSquads, createSquad, deleteSquad, logDecision, getDecisionsSummary, updateSquadStatus, } from "../store/squads.js";
|
|
7
|
+
import { readPage, writePage, assertPagePath, deletePage, listPages } from "../wiki/fs.js";
|
|
8
8
|
import { resolveModelTiers } from "./model-router.js";
|
|
9
9
|
import { searchWiki, getWikiSummary } from "../wiki/search.js";
|
|
10
10
|
import { getOrchestratorSystemMessage } from "./system-message.js";
|
|
11
11
|
import { createTools } from "./tools.js";
|
|
12
|
-
import { getSkillDirectories } from "./skills.js";
|
|
12
|
+
import { getSkillDirectories, listSkills, installSkill, removeSkill, searchSkillsRegistry } from "./skills.js";
|
|
13
13
|
import { resetClient } from "./client.js";
|
|
14
|
+
import { delegateToAgent, getActiveAgentTasks } from "./agents.js";
|
|
15
|
+
import { saveConfig } from "../config.js";
|
|
16
|
+
import { checkForUpdate } from "../update.js";
|
|
14
17
|
// ---------------------------------------------------------------------------
|
|
15
18
|
// Constants
|
|
16
19
|
// ---------------------------------------------------------------------------
|
|
@@ -40,15 +43,32 @@ function getToolDeps() {
|
|
|
40
43
|
wikiWrite: writePage,
|
|
41
44
|
wikiSearch: searchWiki,
|
|
42
45
|
wikiAssertPagePath: assertPagePath,
|
|
46
|
+
wikiDelete: deletePage,
|
|
47
|
+
wikiList: listPages,
|
|
43
48
|
getSquad: (slug) => {
|
|
44
49
|
const s = getSquad(slug);
|
|
45
50
|
return s ? mapSquad(s) : undefined;
|
|
46
51
|
},
|
|
47
52
|
listSquads: () => listSquads().map(mapSquad),
|
|
48
53
|
createSquad,
|
|
54
|
+
deleteSquad,
|
|
49
55
|
logDecision,
|
|
50
56
|
getDecisionsSummary,
|
|
51
57
|
updateSquadStatus,
|
|
58
|
+
delegateToAgent,
|
|
59
|
+
getTask,
|
|
60
|
+
getActiveAgentTasks: () => getActiveAgentTasks().map((t) => ({
|
|
61
|
+
taskId: t.taskId,
|
|
62
|
+
agentSlug: t.agentSlug,
|
|
63
|
+
description: t.description,
|
|
64
|
+
status: t.status,
|
|
65
|
+
})),
|
|
66
|
+
listSkills,
|
|
67
|
+
installSkill,
|
|
68
|
+
removeSkill,
|
|
69
|
+
searchSkillsRegistry,
|
|
70
|
+
saveConfig,
|
|
71
|
+
checkForUpdate,
|
|
52
72
|
};
|
|
53
73
|
}
|
|
54
74
|
function getSessionConfig() {
|
|
@@ -62,6 +62,16 @@ 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
|
+
### Delegating Work
|
|
66
|
+
After planning tasks with the user, **use \`squad_delegate\` to send each task to the squad agent for implementation**. The workflow is:
|
|
67
|
+
1. Plan the work with the user (break into concrete tasks).
|
|
68
|
+
2. Call \`squad_delegate\` for each task — provide detailed instructions including file paths, expected behavior, and acceptance criteria.
|
|
69
|
+
3. The agent works autonomously in the background. You get a task ID immediately.
|
|
70
|
+
4. Use \`squad_task_status\` to check progress and retrieve results.
|
|
71
|
+
5. Report results back to the user.
|
|
72
|
+
|
|
73
|
+
You can delegate multiple tasks in parallel — each gets its own task ID.
|
|
74
|
+
|
|
65
75
|
### Model Selection
|
|
66
76
|
Squad agents are automatically assigned a model based on task complexity:
|
|
67
77
|
- **High complexity** (architecture, refactoring, debugging, design) → most capable model
|
|
@@ -76,12 +86,27 @@ The model is selected automatically. Tell the user which model tier was chosen w
|
|
|
76
86
|
- \`wiki_read\`: Read a page from your knowledge base.
|
|
77
87
|
- \`wiki_write\`: Write or update a page. Use for preferences, project notes, facts.
|
|
78
88
|
- \`wiki_search\`: Search your knowledge base.
|
|
89
|
+
- \`wiki_delete\`: Delete a page from your knowledge base.
|
|
90
|
+
- \`wiki_list\`: List all pages in your knowledge base.
|
|
79
91
|
|
|
80
92
|
### Squad Management
|
|
81
93
|
- \`squad_create\`: Create a project squad.
|
|
82
94
|
- \`squad_recall\`: Get a squad's context and decisions.
|
|
83
95
|
- \`squad_status\`: Check squad status.
|
|
84
96
|
- \`squad_log_decision\`: Log a decision for a squad.
|
|
97
|
+
- \`squad_delegate\`: **Delegate a task to a squad agent.** The agent works autonomously in the background. Returns a task ID.
|
|
98
|
+
- \`squad_task_status\`: Check the status/result of a delegated task, or list all active tasks.
|
|
99
|
+
- \`squad_delete\`: Delete a squad and all its decisions permanently.
|
|
100
|
+
|
|
101
|
+
### Skills
|
|
102
|
+
- \`skill_list\`: List all installed skills.
|
|
103
|
+
- \`skill_install\`: Install a skill from a git repository URL.
|
|
104
|
+
- \`skill_remove\`: Remove an installed skill by slug.
|
|
105
|
+
- \`skill_search\`: Search the skills.sh registry for available skills.
|
|
106
|
+
|
|
107
|
+
### Configuration
|
|
108
|
+
- \`config_update\`: Update IO's configuration (defaultModel, telegramEnabled, selfEditEnabled, apiPort).
|
|
109
|
+
- \`check_update\`: Check if a newer version of IO is available.
|
|
85
110
|
|
|
86
111
|
### System
|
|
87
112
|
- \`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.**
|
package/dist/copilot/tools.js
CHANGED
|
@@ -121,6 +121,212 @@ export function createTools(deps) {
|
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
123
|
});
|
|
124
|
+
const squadDelegate = defineTool("squad_delegate", {
|
|
125
|
+
description: "Delegate a task to a squad agent for autonomous execution. The agent runs in the background and you get a task ID immediately. Use squad_task_status to check progress. Use this after planning work with the user to send each task to the squad for implementation.",
|
|
126
|
+
skipPermission: true,
|
|
127
|
+
parameters: z.object({
|
|
128
|
+
slug: z.string().describe("Squad slug to delegate to"),
|
|
129
|
+
task: z
|
|
130
|
+
.string()
|
|
131
|
+
.describe("Detailed task description. Be specific — include file paths, expected behavior, acceptance criteria. The agent works autonomously with this as its only instruction."),
|
|
132
|
+
}),
|
|
133
|
+
handler: async ({ slug, task }) => {
|
|
134
|
+
console.error(`[io] squad_delegate called: ${slug} — ${task.slice(0, 100)}…`);
|
|
135
|
+
try {
|
|
136
|
+
const taskId = await deps.delegateToAgent(slug, task, (id, result) => {
|
|
137
|
+
console.error(`[io] Agent task ${id} completed for squad ${slug}`);
|
|
138
|
+
});
|
|
139
|
+
return `Task delegated to squad "${slug}". Task ID: ${taskId}\n\nThe agent is working on this in the background. Use squad_task_status to check progress.`;
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
return `Error delegating task: ${err instanceof Error ? err.message : String(err)}`;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
const squadTaskStatus = defineTool("squad_task_status", {
|
|
147
|
+
description: "Check the status of a delegated squad task, or list all active tasks. Returns status (running/done/failed) and result when complete.",
|
|
148
|
+
skipPermission: true,
|
|
149
|
+
parameters: z.object({
|
|
150
|
+
task_id: z
|
|
151
|
+
.string()
|
|
152
|
+
.optional()
|
|
153
|
+
.describe("Specific task ID to check. If omitted, lists all active tasks."),
|
|
154
|
+
}),
|
|
155
|
+
handler: async ({ task_id }) => {
|
|
156
|
+
if (task_id) {
|
|
157
|
+
const task = deps.getTask(task_id);
|
|
158
|
+
if (!task)
|
|
159
|
+
return `Task not found: ${task_id}`;
|
|
160
|
+
let response = `**Task ${task.task_id}**\nSquad: ${task.agent_slug}\nStatus: ${task.status}\nDescription: ${task.description}`;
|
|
161
|
+
if (task.result) {
|
|
162
|
+
const result = task.result.length > 4000 ? task.result.slice(0, 4000) + "\n[…truncated]" : task.result;
|
|
163
|
+
response += `\n\nResult:\n${result}`;
|
|
164
|
+
}
|
|
165
|
+
return response;
|
|
166
|
+
}
|
|
167
|
+
const tasks = deps.getActiveAgentTasks();
|
|
168
|
+
if (tasks.length === 0)
|
|
169
|
+
return "No active tasks.";
|
|
170
|
+
return tasks
|
|
171
|
+
.map((t) => `- **${t.taskId}** (${t.agentSlug}) — ${t.status} — ${t.description}`)
|
|
172
|
+
.join("\n");
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
// --- Squad delete ---
|
|
176
|
+
const squadDelete = defineTool("squad_delete", {
|
|
177
|
+
description: "Delete a squad and all its decisions. This is permanent.",
|
|
178
|
+
skipPermission: true,
|
|
179
|
+
parameters: z.object({
|
|
180
|
+
slug: z.string().describe("Squad slug to delete"),
|
|
181
|
+
}),
|
|
182
|
+
handler: async ({ slug }) => {
|
|
183
|
+
console.error(`[io] squad_delete called: ${slug}`);
|
|
184
|
+
try {
|
|
185
|
+
const squad = deps.getSquad(slug);
|
|
186
|
+
if (!squad)
|
|
187
|
+
return `Squad not found: ${slug}`;
|
|
188
|
+
deps.deleteSquad(slug);
|
|
189
|
+
return `Squad "${squad.name}" (${slug}) has been deleted.`;
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
// --- Skill management ---
|
|
197
|
+
const skillList = defineTool("skill_list", {
|
|
198
|
+
description: "List all installed skills with their names, slugs, and descriptions.",
|
|
199
|
+
skipPermission: true,
|
|
200
|
+
parameters: z.object({}),
|
|
201
|
+
handler: async () => {
|
|
202
|
+
const skills = deps.listSkills();
|
|
203
|
+
if (skills.length === 0)
|
|
204
|
+
return "No skills installed.";
|
|
205
|
+
return skills
|
|
206
|
+
.map((s) => `- **${s.name}** (\`${s.slug}\`): ${s.description || "(no description)"}`)
|
|
207
|
+
.join("\n");
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
const skillInstall = defineTool("skill_install", {
|
|
211
|
+
description: "Install a skill from a git repository URL. The repo must contain a SKILL.md file.",
|
|
212
|
+
skipPermission: true,
|
|
213
|
+
parameters: z.object({
|
|
214
|
+
repo_url: z.string().describe("Git repository URL (e.g., https://github.com/user/my-skill.git)"),
|
|
215
|
+
}),
|
|
216
|
+
handler: async ({ repo_url }) => {
|
|
217
|
+
console.error(`[io] skill_install called: ${repo_url}`);
|
|
218
|
+
try {
|
|
219
|
+
const skill = await deps.installSkill(repo_url);
|
|
220
|
+
return `Skill "${skill.name}" installed successfully.\nSlug: ${skill.slug}\nDescription: ${skill.description || "(none)"}`;
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
return `Error installing skill: ${err instanceof Error ? err.message : String(err)}`;
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
const skillRemove = defineTool("skill_remove", {
|
|
228
|
+
description: "Remove an installed skill by its slug.",
|
|
229
|
+
skipPermission: true,
|
|
230
|
+
parameters: z.object({
|
|
231
|
+
slug: z.string().describe("Skill slug to remove"),
|
|
232
|
+
}),
|
|
233
|
+
handler: async ({ slug }) => {
|
|
234
|
+
console.error(`[io] skill_remove called: ${slug}`);
|
|
235
|
+
const removed = deps.removeSkill(slug);
|
|
236
|
+
return removed ? `Skill "${slug}" removed.` : `Skill not found: ${slug}`;
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
const skillSearch = defineTool("skill_search", {
|
|
240
|
+
description: "Search the skills.sh registry for skills matching a query.",
|
|
241
|
+
skipPermission: true,
|
|
242
|
+
parameters: z.object({
|
|
243
|
+
query: z.string().describe("Search query"),
|
|
244
|
+
}),
|
|
245
|
+
handler: async ({ query }) => {
|
|
246
|
+
console.error(`[io] skill_search called: ${query}`);
|
|
247
|
+
try {
|
|
248
|
+
const results = await deps.searchSkillsRegistry(query);
|
|
249
|
+
if (results.length === 0)
|
|
250
|
+
return `No skills found for "${query}".`;
|
|
251
|
+
return results
|
|
252
|
+
.map((r) => `- **${r.name}**: ${r.description}\n ${r.repoUrl}`)
|
|
253
|
+
.join("\n");
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
return `Error searching registry: ${err instanceof Error ? err.message : String(err)}`;
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
// --- Wiki extras ---
|
|
261
|
+
const wikiDelete = defineTool("wiki_delete", {
|
|
262
|
+
description: "Delete a page from IO's knowledge base wiki.",
|
|
263
|
+
skipPermission: true,
|
|
264
|
+
parameters: z.object({
|
|
265
|
+
path: z.string().describe("Relative path to the wiki page (e.g., 'pages/preferences/editor.md')"),
|
|
266
|
+
}),
|
|
267
|
+
handler: async ({ path: pagePath }) => {
|
|
268
|
+
console.error(`[io] wiki_delete called: ${pagePath}`);
|
|
269
|
+
try {
|
|
270
|
+
const deleted = deps.wikiDelete(pagePath);
|
|
271
|
+
return deleted ? `Deleted: ${pagePath}` : `Page not found: ${pagePath}`;
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
const wikiList = defineTool("wiki_list", {
|
|
279
|
+
description: "List all pages in IO's knowledge base wiki.",
|
|
280
|
+
skipPermission: true,
|
|
281
|
+
parameters: z.object({}),
|
|
282
|
+
handler: async () => {
|
|
283
|
+
const pages = deps.wikiList();
|
|
284
|
+
if (pages.length === 0)
|
|
285
|
+
return "Wiki is empty — no pages yet.";
|
|
286
|
+
return pages.map((p) => `- ${p}`).join("\n");
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
// --- Config update ---
|
|
290
|
+
const configUpdate = defineTool("config_update", {
|
|
291
|
+
description: "Update IO's configuration. Changes are saved to ~/.io/config.json and take effect on restart.",
|
|
292
|
+
skipPermission: true,
|
|
293
|
+
parameters: z.object({
|
|
294
|
+
key: z
|
|
295
|
+
.enum(["defaultModel", "telegramEnabled", "selfEditEnabled", "apiPort"])
|
|
296
|
+
.describe("Config key to update"),
|
|
297
|
+
value: z
|
|
298
|
+
.union([z.string(), z.number(), z.boolean()])
|
|
299
|
+
.describe("New value for the config key"),
|
|
300
|
+
}),
|
|
301
|
+
handler: async ({ key, value }) => {
|
|
302
|
+
console.error(`[io] config_update called: ${key} = ${JSON.stringify(value)}`);
|
|
303
|
+
try {
|
|
304
|
+
deps.saveConfig({ [key]: value });
|
|
305
|
+
return `Config updated: ${key} = ${JSON.stringify(value)}\nNote: Some changes require a restart to take effect.`;
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
// --- Update checker ---
|
|
313
|
+
const checkUpdate = defineTool("check_update", {
|
|
314
|
+
description: "Check if a newer version of IO is available.",
|
|
315
|
+
skipPermission: true,
|
|
316
|
+
parameters: z.object({}),
|
|
317
|
+
handler: async () => {
|
|
318
|
+
try {
|
|
319
|
+
const info = await deps.checkForUpdate();
|
|
320
|
+
if (info.updateAvailable) {
|
|
321
|
+
return `Update available! Current: v${info.current} → Latest: v${info.latest}\nRun: npm update -g heyio`;
|
|
322
|
+
}
|
|
323
|
+
return `IO is up to date (v${info.current}).`;
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
return `Error checking for updates: ${err instanceof Error ? err.message : String(err)}`;
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
});
|
|
124
330
|
const shell = defineTool("shell", {
|
|
125
331
|
description: "Run a shell command on the host machine. You have FULL ROOT ACCESS. Use for git, apt, npm, build tools, system administration, or anything else. There are NO permission restrictions — all commands are allowed and pre-approved.",
|
|
126
332
|
skipPermission: true,
|
|
@@ -546,7 +752,7 @@ export function createTools(deps) {
|
|
|
546
752
|
}
|
|
547
753
|
},
|
|
548
754
|
});
|
|
549
|
-
return [wikiRead, wikiWrite, wikiSearch, squadCreate, squadRecall, squadStatus, squadLogDecision, shell, fileOps, bash, readFile, viewTool, grepTool, strReplaceEditor, github];
|
|
755
|
+
return [wikiRead, wikiWrite, wikiSearch, wikiDelete, wikiList, squadCreate, squadRecall, squadStatus, squadLogDecision, squadDelegate, squadTaskStatus, squadDelete, skillList, skillInstall, skillRemove, skillSearch, configUpdate, checkUpdate, shell, fileOps, bash, readFile, viewTool, grepTool, strReplaceEditor, github];
|
|
550
756
|
}
|
|
551
757
|
function walkDirectory(dir, maxDepth = 3, depth = 0) {
|
|
552
758
|
if (depth >= maxDepth)
|