heyio 0.1.20 → 0.1.22
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/orchestrator.js +53 -26
- package/dist/copilot/system-message.js +13 -0
- package/dist/copilot/tools.js +156 -1
- package/package.json +1 -1
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import { approveAll, } from "@github/copilot-sdk";
|
|
2
3
|
import { config } from "../config.js";
|
|
3
4
|
import { SESSIONS_DIR } from "../paths.js";
|
|
4
5
|
import { getState, setState, deleteState, logConversation } from "../store/db.js";
|
|
5
6
|
import { clearStaleTasks, getTask } 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";
|
|
7
|
+
import { getSquad, listSquads, createSquad, deleteSquad, logDecision, getDecisionsSummary, updateSquadStatus, } from "../store/squads.js";
|
|
8
|
+
import { readPage, writePage, assertPagePath, deletePage, listPages } from "../wiki/fs.js";
|
|
8
9
|
import { resolveModelTiers } from "./model-router.js";
|
|
9
10
|
import { searchWiki, getWikiSummary } from "../wiki/search.js";
|
|
10
11
|
import { getOrchestratorSystemMessage } from "./system-message.js";
|
|
11
12
|
import { createTools } from "./tools.js";
|
|
12
|
-
import { getSkillDirectories } from "./skills.js";
|
|
13
|
+
import { getSkillDirectories, listSkills, installSkill, removeSkill, searchSkillsRegistry } from "./skills.js";
|
|
13
14
|
import { resetClient } from "./client.js";
|
|
14
15
|
import { delegateToAgent, getActiveAgentTasks } from "./agents.js";
|
|
16
|
+
import { saveConfig } from "../config.js";
|
|
17
|
+
import { checkForUpdate } from "../update.js";
|
|
15
18
|
// ---------------------------------------------------------------------------
|
|
16
19
|
// Constants
|
|
17
20
|
// ---------------------------------------------------------------------------
|
|
@@ -19,6 +22,7 @@ const HEALTH_CHECK_INTERVAL_MS = 30_000;
|
|
|
19
22
|
const SEND_TIMEOUT_MS = 600_000;
|
|
20
23
|
const MAX_RETRIES = 3;
|
|
21
24
|
const SESSION_ID_KEY = "orchestrator_session_id";
|
|
25
|
+
const SESSION_TOOLS_KEY = "orchestrator_session_tools";
|
|
22
26
|
// ---------------------------------------------------------------------------
|
|
23
27
|
// Module state
|
|
24
28
|
// ---------------------------------------------------------------------------
|
|
@@ -41,12 +45,15 @@ function getToolDeps() {
|
|
|
41
45
|
wikiWrite: writePage,
|
|
42
46
|
wikiSearch: searchWiki,
|
|
43
47
|
wikiAssertPagePath: assertPagePath,
|
|
48
|
+
wikiDelete: deletePage,
|
|
49
|
+
wikiList: listPages,
|
|
44
50
|
getSquad: (slug) => {
|
|
45
51
|
const s = getSquad(slug);
|
|
46
52
|
return s ? mapSquad(s) : undefined;
|
|
47
53
|
},
|
|
48
54
|
listSquads: () => listSquads().map(mapSquad),
|
|
49
55
|
createSquad,
|
|
56
|
+
deleteSquad,
|
|
50
57
|
logDecision,
|
|
51
58
|
getDecisionsSummary,
|
|
52
59
|
updateSquadStatus,
|
|
@@ -58,12 +65,23 @@ function getToolDeps() {
|
|
|
58
65
|
description: t.description,
|
|
59
66
|
status: t.status,
|
|
60
67
|
})),
|
|
68
|
+
listSkills,
|
|
69
|
+
installSkill,
|
|
70
|
+
removeSkill,
|
|
71
|
+
searchSkillsRegistry,
|
|
72
|
+
saveConfig,
|
|
73
|
+
checkForUpdate,
|
|
61
74
|
};
|
|
62
75
|
}
|
|
63
76
|
function getSessionConfig() {
|
|
64
77
|
const tools = createTools(getToolDeps());
|
|
65
78
|
return { tools, skillDirectories: getSkillDirectories() };
|
|
66
79
|
}
|
|
80
|
+
/** Hash of tool names — used to detect when tools change across updates. */
|
|
81
|
+
function toolFingerprint(tools) {
|
|
82
|
+
const names = (tools ?? []).map((t) => t.name).sort().join(",");
|
|
83
|
+
return crypto.createHash("sha256").update(names).digest("hex").slice(0, 16);
|
|
84
|
+
}
|
|
67
85
|
function buildFullSessionConfig() {
|
|
68
86
|
const { tools, skillDirectories } = getSessionConfig();
|
|
69
87
|
return {
|
|
@@ -86,21 +104,6 @@ function buildFullSessionConfig() {
|
|
|
86
104
|
},
|
|
87
105
|
};
|
|
88
106
|
}
|
|
89
|
-
function buildResumeConfig() {
|
|
90
|
-
const { tools, skillDirectories } = getSessionConfig();
|
|
91
|
-
return {
|
|
92
|
-
configDir: SESSIONS_DIR,
|
|
93
|
-
streaming: true,
|
|
94
|
-
tools,
|
|
95
|
-
skillDirectories,
|
|
96
|
-
onPermissionRequest: approveAll,
|
|
97
|
-
infiniteSessions: {
|
|
98
|
-
enabled: true,
|
|
99
|
-
backgroundCompactionThreshold: 0.80,
|
|
100
|
-
bufferExhaustionThreshold: 0.95,
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
107
|
// ---------------------------------------------------------------------------
|
|
105
108
|
// Error classification
|
|
106
109
|
// ---------------------------------------------------------------------------
|
|
@@ -162,21 +165,45 @@ async function ensureOrchestratorSession() {
|
|
|
162
165
|
try {
|
|
163
166
|
const c = await ensureClient();
|
|
164
167
|
const savedSessionId = getState(SESSION_ID_KEY);
|
|
168
|
+
const savedToolsHash = getState(SESSION_TOOLS_KEY);
|
|
169
|
+
const { tools, skillDirectories } = getSessionConfig();
|
|
170
|
+
const currentToolsHash = toolFingerprint(tools);
|
|
165
171
|
if (savedSessionId) {
|
|
166
|
-
|
|
167
|
-
console.error("[io]
|
|
168
|
-
const session = await c.resumeSession(savedSessionId, buildResumeConfig());
|
|
169
|
-
orchestratorSession = session;
|
|
170
|
-
return session;
|
|
171
|
-
}
|
|
172
|
-
catch (err) {
|
|
173
|
-
console.error("[io] Failed to resume session, creating new one:", err instanceof Error ? err.message : err);
|
|
172
|
+
if (savedToolsHash && savedToolsHash !== currentToolsHash) {
|
|
173
|
+
console.error("[io] Tool set changed since last session — starting fresh");
|
|
174
174
|
deleteState(SESSION_ID_KEY);
|
|
175
|
+
deleteState(SESSION_TOOLS_KEY);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
try {
|
|
179
|
+
console.error("[io] Resuming session:", savedSessionId);
|
|
180
|
+
const session = await c.resumeSession(savedSessionId, {
|
|
181
|
+
configDir: SESSIONS_DIR,
|
|
182
|
+
streaming: true,
|
|
183
|
+
tools,
|
|
184
|
+
skillDirectories,
|
|
185
|
+
onPermissionRequest: approveAll,
|
|
186
|
+
infiniteSessions: {
|
|
187
|
+
enabled: true,
|
|
188
|
+
backgroundCompactionThreshold: 0.80,
|
|
189
|
+
bufferExhaustionThreshold: 0.95,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
orchestratorSession = session;
|
|
193
|
+
setState(SESSION_TOOLS_KEY, currentToolsHash);
|
|
194
|
+
return session;
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
console.error("[io] Failed to resume session, creating new one:", err instanceof Error ? err.message : err);
|
|
198
|
+
deleteState(SESSION_ID_KEY);
|
|
199
|
+
deleteState(SESSION_TOOLS_KEY);
|
|
200
|
+
}
|
|
175
201
|
}
|
|
176
202
|
}
|
|
177
203
|
console.error("[io] Creating new orchestrator session");
|
|
178
204
|
const session = await c.createSession(buildFullSessionConfig());
|
|
179
205
|
setState(SESSION_ID_KEY, session.sessionId);
|
|
206
|
+
setState(SESSION_TOOLS_KEY, currentToolsHash);
|
|
180
207
|
orchestratorSession = session;
|
|
181
208
|
return session;
|
|
182
209
|
}
|
|
@@ -86,6 +86,8 @@ The model is selected automatically. Tell the user which model tier was chosen w
|
|
|
86
86
|
- \`wiki_read\`: Read a page from your knowledge base.
|
|
87
87
|
- \`wiki_write\`: Write or update a page. Use for preferences, project notes, facts.
|
|
88
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.
|
|
89
91
|
|
|
90
92
|
### Squad Management
|
|
91
93
|
- \`squad_create\`: Create a project squad.
|
|
@@ -94,6 +96,17 @@ The model is selected automatically. Tell the user which model tier was chosen w
|
|
|
94
96
|
- \`squad_log_decision\`: Log a decision for a squad.
|
|
95
97
|
- \`squad_delegate\`: **Delegate a task to a squad agent.** The agent works autonomously in the background. Returns a task ID.
|
|
96
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.
|
|
97
110
|
|
|
98
111
|
### System
|
|
99
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
|
@@ -172,6 +172,161 @@ export function createTools(deps) {
|
|
|
172
172
|
.join("\n");
|
|
173
173
|
},
|
|
174
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
|
+
});
|
|
175
330
|
const shell = defineTool("shell", {
|
|
176
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.",
|
|
177
332
|
skipPermission: true,
|
|
@@ -597,7 +752,7 @@ export function createTools(deps) {
|
|
|
597
752
|
}
|
|
598
753
|
},
|
|
599
754
|
});
|
|
600
|
-
return [wikiRead, wikiWrite, wikiSearch, squadCreate, squadRecall, squadStatus, squadLogDecision, squadDelegate, squadTaskStatus, 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];
|
|
601
756
|
}
|
|
602
757
|
function walkDirectory(dir, maxDepth = 3, depth = 0) {
|
|
603
758
|
if (depth >= maxDepth)
|