heyio 0.42.1 → 1.0.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.
Files changed (100) hide show
  1. package/README.md +40 -52
  2. package/dist/api/auth.js +35 -38
  3. package/dist/api/server.js +157 -1134
  4. package/dist/config.js +49 -32
  5. package/dist/copilot/agents.js +72 -1055
  6. package/dist/copilot/client.js +6 -17
  7. package/dist/copilot/io-scheduler.js +55 -139
  8. package/dist/copilot/model-router.js +100 -72
  9. package/dist/copilot/orchestrator.js +91 -515
  10. package/dist/copilot/scheduler.js +67 -189
  11. package/dist/copilot/skills.js +41 -366
  12. package/dist/copilot/system-message.js +40 -200
  13. package/dist/copilot/tools.js +191 -2042
  14. package/dist/daemon.js +54 -201
  15. package/dist/index.js +15 -133
  16. package/dist/mcp/config.js +23 -31
  17. package/dist/mcp/index.js +2 -3
  18. package/dist/mcp/registry.js +33 -88
  19. package/dist/notify.js +18 -100
  20. package/dist/paths.js +13 -24
  21. package/dist/setup.js +35 -0
  22. package/dist/store/db.js +111 -297
  23. package/dist/store/feed.js +29 -97
  24. package/dist/store/instances.js +56 -121
  25. package/dist/store/schedules.js +21 -73
  26. package/dist/store/squads.js +35 -186
  27. package/dist/store/tasks.js +25 -168
  28. package/dist/telegram/bot.js +20 -312
  29. package/dist/telegram/handlers.js +39 -3
  30. package/dist/watchdog.js +31 -45
  31. package/dist/wiki/fs.js +38 -155
  32. package/dist/wiki/search.js +31 -44
  33. package/package.json +5 -8
  34. package/web-dist/assets/ChatView-EFFiln1H.js +11 -0
  35. package/web-dist/assets/FeedView-bN4NMOL7.js +6 -0
  36. package/web-dist/assets/LoginView-CNtasq3n.js +1 -0
  37. package/web-dist/assets/McpView-C2CHiwsi.js +1 -0
  38. package/web-dist/assets/SchedulesView-CyilLban.js +1 -0
  39. package/web-dist/assets/SettingsView-1wLXKEF4.js +1 -0
  40. package/web-dist/assets/SkillsView-BLsD-0u0.js +1 -0
  41. package/web-dist/assets/SquadDetailView-CsCw2ZLp.js +21 -0
  42. package/web-dist/assets/SquadsView-DQ3vFlyO.js +6 -0
  43. package/web-dist/assets/WikiView-19M3oqnq.js +21 -0
  44. package/web-dist/assets/api-WGvTsXaE.js +1 -0
  45. package/web-dist/assets/index-D7M5O-_l.css +1 -0
  46. package/web-dist/assets/index-DZOS9syn.js +95 -0
  47. package/web-dist/assets/plus-BOvyX1BC.js +6 -0
  48. package/web-dist/assets/trash-2-DHoetkC4.js +6 -0
  49. package/web-dist/favicon.svg +4 -1
  50. package/web-dist/index.html +7 -10
  51. package/dist/api/logout.test.js +0 -128
  52. package/dist/api/mcp.test.js +0 -285
  53. package/dist/api/wiki.test.js +0 -283
  54. package/dist/auth/session-logic.js +0 -79
  55. package/dist/auth/session-logic.test.js +0 -201
  56. package/dist/copilot/auto-complete-instance.test.js +0 -104
  57. package/dist/copilot/cron.js +0 -136
  58. package/dist/copilot/event-summary.js +0 -286
  59. package/dist/copilot/instance-deactivate.test.js +0 -119
  60. package/dist/copilot/model-router.test.js +0 -71
  61. package/dist/copilot/review-backfill.js +0 -57
  62. package/dist/copilot/session-timeout.js +0 -112
  63. package/dist/copilot/session-timeout.test.js +0 -372
  64. package/dist/copilot/skills.test.js +0 -55
  65. package/dist/copilot/universes.js +0 -469
  66. package/dist/instance-watchdog.js +0 -104
  67. package/dist/instance-watchdog.test.js +0 -183
  68. package/dist/mcp/client.js +0 -109
  69. package/dist/mcp/client.test.js +0 -99
  70. package/dist/mcp/config.test.js +0 -49
  71. package/dist/mcp/registry.test.js +0 -79
  72. package/dist/notify.test.js +0 -232
  73. package/dist/store/feed.test.js +0 -279
  74. package/dist/store/instances.test.js +0 -310
  75. package/dist/store/io-schedules.js +0 -63
  76. package/dist/store/notifications.js +0 -79
  77. package/dist/store/notifications.test.js +0 -197
  78. package/dist/store/schedule-runs.js +0 -46
  79. package/dist/store/squads.test.js +0 -405
  80. package/dist/store/tasks.test.js +0 -150
  81. package/dist/store/worktrees.js +0 -83
  82. package/dist/tui/index.js +0 -286
  83. package/dist/update.js +0 -81
  84. package/dist/watchdog.test.js +0 -83
  85. package/dist/wiki/wiki-squad.test.js +0 -54
  86. package/web-dist/assets/AgentActivityView-B1PaNYy8.js +0 -1
  87. package/web-dist/assets/ChatView-BbpWnrtC.js +0 -4
  88. package/web-dist/assets/FeedView-B5LaMV0I.js +0 -1
  89. package/web-dist/assets/InboxView-Cwqt8rH7.js +0 -1
  90. package/web-dist/assets/LoginView-refmPLKT.js +0 -1
  91. package/web-dist/assets/McpView-B1w0dRFY.js +0 -1
  92. package/web-dist/assets/SchedulesView-D9l2DI7X.js +0 -1
  93. package/web-dist/assets/SettingsTabs.vue_vue_type_script_setup_true_lang-DncOVVEB.js +0 -1
  94. package/web-dist/assets/SkillsView-uFX0q1mV.js +0 -1
  95. package/web-dist/assets/SquadsView-B1nZW4ml.js +0 -1
  96. package/web-dist/assets/StatusIndicator.vue_vue_type_script_setup_true_lang-pTrJJwX1.js +0 -1
  97. package/web-dist/assets/WikiView-B54cCKIK.js +0 -1
  98. package/web-dist/assets/index-C0VEUWQ1.js +0 -81
  99. package/web-dist/assets/index-eluTyieM.css +0 -10
  100. package/web-dist/icons.svg +0 -24
@@ -1,555 +1,131 @@
1
- import crypto from "node:crypto";
2
- import { approveAll, } from "@github/copilot-sdk";
3
- import { config } from "../config.js";
4
- import { SESSIONS_DIR, IO_VERSION } from "../paths.js";
5
- import { getState, setState, deleteState, logConversation } from "../store/db.js";
6
- import { clearStaleTasks, getAgentTaskStats, getSquadWorkDistribution, getStalestSpecialist, getTask, getTaskReviews } from "../store/tasks.js";
7
- import { getSquad, listSquads, createSquad, deleteSquad, logDecision, getDecisions, getDecisionsSummary, updateSquadStatus, addSquadAgent, listSquadAgents, removeSquadAgent, updateAgentStatus, clearAgentSession, setSquadLead, getSquadLead, setSquadQA, } from "../store/squads.js";
8
- import { readPage, writePage, assertPagePath, deletePage, listPages } from "../wiki/fs.js";
9
- import { resolveModelTiers } from "./model-router.js";
10
- import { searchWiki, getWikiSummary } from "../wiki/search.js";
11
- import { getOrchestratorSystemMessage } from "./system-message.js";
1
+ import { approveAll } from "@github/copilot-sdk";
2
+ import { getDb } from "../store/db.js";
3
+ import { loadConfig } from "../config.js";
4
+ import { buildSystemMessage } from "./system-message.js";
12
5
  import { createTools } from "./tools.js";
13
- import { getSkillDirectories, listSkills, installSkill, removeSkill, searchSkillsRegistry } from "./skills.js";
6
+ import { loadSkillDirectories } from "./skills.js";
14
7
  import { resetClient } from "./client.js";
15
- import { delegateToAgent, getActiveAgentTasks, clearAgentInMemorySession } from "./agents.js";
16
- import { saveConfig } from "../config.js";
17
- import { checkForUpdate } from "../update.js";
18
- import { startInstanceWatchdog } from "../instance-watchdog.js";
19
- import { loadMcpConfig } from "../mcp/config.js";
20
- import { McpConnectionManager } from "../mcp/client.js";
21
- import { createMcpTools } from "../mcp/registry.js";
22
- import { createInstance, getInstance, listInstances, updateInstanceStatus, logInstanceDecision, getInstanceDecisions, mergeInstanceDecisions, deleteInstance, buildContextSnapshot, reconcileInstances, ensureInstanceTables, } from "../store/instances.js";
23
- import { createWorktree, removeWorktree } from "../store/worktrees.js";
24
- // ---------------------------------------------------------------------------
25
- // Constants
26
- // ---------------------------------------------------------------------------
27
- const HEALTH_CHECK_INTERVAL_MS = 30_000;
28
- const SEND_TIMEOUT_MS = 600_000;
29
- const MAX_RETRIES = 3;
30
- const SESSION_ID_KEY = "orchestrator_session_id";
31
- const SESSION_TOOLS_KEY = "orchestrator_session_tools";
32
- // ---------------------------------------------------------------------------
33
- // Module state
34
- // ---------------------------------------------------------------------------
35
- let client;
36
8
  let orchestratorSession;
37
- let healthCheckTimer;
38
- let sessionInitPromise;
39
- let clientResetPromise;
9
+ let sessionCreatePromise;
10
+ let healthCheckInterval;
40
11
  const messageQueue = [];
41
12
  let processing = false;
42
- // ---------------------------------------------------------------------------
43
- // Session config helpers
44
- // ---------------------------------------------------------------------------
45
- function mapSquad(s) {
46
- return { slug: s.slug, name: s.name, projectPath: s.project_path, status: s.status, universe: s.universe };
13
+ export async function initOrchestrator(client, opts) {
14
+ await ensureOrchestratorSession(client, opts);
15
+ startHealthCheck(client, opts);
47
16
  }
48
- function getToolDeps() {
49
- return {
50
- wikiRead: readPage,
51
- wikiWrite: writePage,
52
- wikiSearch: searchWiki,
53
- wikiAssertPagePath: assertPagePath,
54
- wikiDelete: deletePage,
55
- wikiList: listPages,
56
- getSquad: (slug) => {
57
- const s = getSquad(slug);
58
- return s ? mapSquad(s) : undefined;
59
- },
60
- listSquads: () => listSquads().map(mapSquad),
61
- createSquad,
62
- deleteSquad,
63
- logDecision,
64
- getDecisionsSummary,
65
- getRecentDecisions: (slug, limit) => getDecisions(slug, limit ?? 5).map((d) => ({
66
- decision: d.decision,
67
- context: d.context,
68
- created_at: d.created_at,
69
- })),
70
- updateSquadStatus,
71
- delegateToAgent: (squadSlug, task, onComplete, targetAgent, instanceId) => delegateToAgent(squadSlug, task, onComplete, targetAgent, instanceId),
72
- getTask,
73
- getActiveAgentTasks: () => getActiveAgentTasks().map((t) => ({
74
- taskId: t.taskId,
75
- agentSlug: t.agentSlug,
76
- description: t.description,
77
- status: t.status,
78
- })),
79
- addSquadAgent,
80
- listSquadAgents: (slug) => listSquadAgents(slug).map((a) => ({
81
- character_name: a.character_name,
82
- role_title: a.role_title,
83
- charter: a.charter,
84
- model_tier: a.model_tier,
85
- personality: a.personality,
86
- status: a.status,
87
- is_lead: a.is_lead,
88
- is_qa: a.is_qa,
89
- })),
90
- removeSquadAgent,
91
- resetSquadAgent: (squadSlug, characterName) => {
92
- const agents = listSquadAgents(squadSlug);
93
- const target = agents.find((a) => a.character_name === characterName);
94
- if (!target) {
95
- return { found: false, previousStatus: "", agent: null };
96
- }
97
- const previousStatus = target.status;
98
- updateAgentStatus(squadSlug, characterName, "idle");
99
- clearAgentSession(squadSlug, characterName);
100
- clearAgentInMemorySession(squadSlug, characterName);
101
- return {
102
- found: true,
103
- previousStatus,
104
- agent: {
105
- character_name: target.character_name,
106
- role_title: target.role_title,
107
- },
108
- };
109
- },
110
- setSquadLead,
111
- getSquadLead: (slug) => {
112
- const lead = getSquadLead(slug);
113
- return lead
114
- ? { character_name: lead.character_name, role_title: lead.role_title }
115
- : undefined;
116
- },
117
- setSquadQA,
118
- getTaskReviews: (taskId) => getTaskReviews(taskId).map((r) => ({
119
- reviewer_character: r.reviewer_character,
120
- approved: r.approved,
121
- comments: r.comments,
122
- squad_slug: r.squad_slug,
123
- })),
124
- getSquadWorkDistribution: (slug, limit) => getSquadWorkDistribution(slug, limit),
125
- getAgentTaskStats: (squadSlug, characterNames) => getAgentTaskStats(squadSlug, characterNames),
126
- getStalestSpecialist: (squadSlug, characterNames, options) => getStalestSpecialist(squadSlug, characterNames, options),
127
- listSkills,
128
- installSkill,
129
- removeSkill,
130
- searchSkillsRegistry,
131
- saveConfig,
132
- checkForUpdate,
133
- // Squad instance deps
134
- createInstance,
135
- getInstance,
136
- listInstances,
137
- updateInstanceStatus,
138
- logInstanceDecision,
139
- getInstanceDecisions,
140
- mergeInstanceDecisions,
141
- deleteInstance,
142
- buildContextSnapshot,
143
- reconcileInstances,
144
- createWorktree,
145
- removeWorktree,
146
- activeInstanceId: undefined,
147
- reloadMcpTools: initMcpTools,
148
- };
149
- }
150
- // MCP state — loaded at startup, refreshed on config change
151
- let mcpToolEntries = [];
152
- let mcpManager = null;
153
- export async function initMcpTools() {
154
- const config = loadMcpConfig();
155
- if (config.servers.length === 0) {
156
- mcpToolEntries = [];
157
- return;
158
- }
159
- if (!mcpManager) {
160
- mcpManager = new McpConnectionManager();
161
- }
162
- else {
163
- await mcpManager.disconnectAll();
164
- }
17
+ async function ensureOrchestratorSession(client, opts) {
18
+ if (orchestratorSession)
19
+ return orchestratorSession;
20
+ if (sessionCreatePromise)
21
+ return sessionCreatePromise;
22
+ sessionCreatePromise = createOrResumeSession(client, opts);
165
23
  try {
166
- mcpToolEntries = await createMcpTools(mcpManager, config);
167
- console.error(`[mcp] Loaded ${mcpToolEntries.length} tool(s) from ${config.servers.filter(s => s.enabled !== false).length} server(s)`);
168
- }
169
- catch (err) {
170
- console.error("[mcp] Error loading MCP tools:", err instanceof Error ? err.message : err);
171
- mcpToolEntries = [];
24
+ orchestratorSession = await sessionCreatePromise;
25
+ return orchestratorSession;
172
26
  }
173
- }
174
- export function getMcpManager() {
175
- return mcpManager;
176
- }
177
- function getSessionConfig() {
178
- const tools = createTools(getToolDeps());
179
- const allTools = [...tools, ...mcpToolEntries.map(e => e.tool)];
180
- return { tools: allTools, skillDirectories: getSkillDirectories() };
181
- }
182
- /** Hash of tool names + version — used to detect when tools change across updates. */
183
- function toolFingerprint(tools) {
184
- const names = (tools ?? []).map((t) => t.name).sort().join(",");
185
- return crypto.createHash("sha256").update(`${IO_VERSION}:${names}`).digest("hex").slice(0, 16);
186
- }
187
- function buildSquadRoster() {
188
- const squads = listSquads();
189
- if (squads.length === 0)
190
- return "";
191
- return squads
192
- .map((s) => {
193
- const agents = listSquadAgents(s.slug);
194
- const lead = agents.find((a) => a.is_lead === 1);
195
- const agentList = agents
196
- .map((a) => {
197
- const badges = [
198
- a.is_lead === 1 ? "⭐ LEAD" : "",
199
- a.is_qa === 1 ? "🛡️ QA" : "",
200
- ]
201
- .filter(Boolean)
202
- .join(", ");
203
- return ` - ${a.character_name} (${a.role_title})${badges ? ` [${badges}]` : ""}`;
204
- })
205
- .join("\n");
206
- const leadLine = lead ? `\nTeam Lead: ${lead.character_name}` : "";
207
- return `**${s.name}** (\`${s.slug}\`) — ${s.status}\n📁 ${s.project_path}${leadLine}\n${agentList || " _(no agents yet)_"}`;
208
- })
209
- .join("\n\n");
210
- }
211
- function buildFullSessionConfig() {
212
- const { tools, skillDirectories } = getSessionConfig();
213
- return {
214
- model: config.defaultModel || "gpt-4.1",
215
- configDir: SESSIONS_DIR,
27
+ finally {
28
+ sessionCreatePromise = undefined;
29
+ }
30
+ }
31
+ async function createOrResumeSession(client, opts) {
32
+ const config = loadConfig();
33
+ const db = getDb();
34
+ const tools = createTools();
35
+ const skillDirs = await loadSkillDirectories();
36
+ const systemMessage = buildSystemMessage(opts.selfEdit);
37
+ const savedId = db
38
+ .prepare("SELECT value FROM session_state WHERE key = 'orchestrator_session_id'")
39
+ .get();
40
+ const sessionConfig = {
41
+ model: config.defaultModel,
216
42
  streaming: true,
217
- systemMessage: {
218
- content: getOrchestratorSystemMessage({
219
- selfEditEnabled: config.selfEditEnabled,
220
- memorySummary: getWikiSummary() || undefined,
221
- squadRoster: buildSquadRoster() || undefined,
222
- }),
223
- },
43
+ workingDirectory: process.cwd(),
44
+ systemMessage: { content: systemMessage },
224
45
  tools,
225
- skillDirectories,
46
+ skillDirectories: skillDirs,
226
47
  onPermissionRequest: approveAll,
227
48
  infiniteSessions: {
228
49
  enabled: true,
229
- backgroundCompactionThreshold: 0.80,
50
+ backgroundCompactionThreshold: 0.8,
230
51
  bufferExhaustionThreshold: 0.95,
231
52
  },
232
53
  };
233
- }
234
- // ---------------------------------------------------------------------------
235
- // Error classification
236
- // ---------------------------------------------------------------------------
237
- function isConnectionError(err) {
238
- if (!(err instanceof Error))
239
- return false;
240
- const msg = err.message.toLowerCase();
241
- return (msg.includes("disconnect") ||
242
- msg.includes("epipe") ||
243
- msg.includes("econnreset") ||
244
- msg.includes("econnrefused") ||
245
- msg.includes("connection") ||
246
- msg.includes("socket"));
247
- }
248
- function isSessionError(err) {
249
- if (!(err instanceof Error))
250
- return false;
251
- const msg = err.message.toLowerCase();
252
- return (msg.includes("session") ||
253
- msg.includes("closed") ||
254
- msg.includes("expired") ||
255
- msg.includes("not found"));
256
- }
257
- // ---------------------------------------------------------------------------
258
- // Client management
259
- // ---------------------------------------------------------------------------
260
- async function ensureClient() {
261
- if (!client) {
262
- throw new Error("Orchestrator not initialized — call initOrchestrator first");
263
- }
264
- if (client.getState() === "connected")
265
- return client;
266
- // Coalesce concurrent reset attempts
267
- if (clientResetPromise)
268
- return clientResetPromise;
269
- clientResetPromise = (async () => {
270
- console.error("[io] Client disconnected, resetting…");
54
+ if (savedId?.value) {
271
55
  try {
272
- const newClient = await resetClient();
273
- client = newClient;
274
- return newClient;
275
- }
276
- finally {
277
- clientResetPromise = undefined;
278
- }
279
- })();
280
- return clientResetPromise;
281
- }
282
- // ---------------------------------------------------------------------------
283
- // Session management
284
- // ---------------------------------------------------------------------------
285
- async function ensureOrchestratorSession() {
286
- if (orchestratorSession)
287
- return orchestratorSession;
288
- // Coalesce concurrent session creation
289
- if (sessionInitPromise)
290
- return sessionInitPromise;
291
- sessionInitPromise = (async () => {
292
- try {
293
- const c = await ensureClient();
294
- const savedSessionId = getState(SESSION_ID_KEY);
295
- const savedToolsHash = getState(SESSION_TOOLS_KEY);
296
- const { tools, skillDirectories } = getSessionConfig();
297
- const currentToolsHash = toolFingerprint(tools);
298
- if (savedSessionId) {
299
- if (!savedToolsHash || savedToolsHash !== currentToolsHash) {
300
- console.error("[io] Tool set changed since last session — starting fresh");
301
- deleteState(SESSION_ID_KEY);
302
- deleteState(SESSION_TOOLS_KEY);
303
- }
304
- else {
305
- try {
306
- console.error("[io] Resuming session:", savedSessionId);
307
- const session = await c.resumeSession(savedSessionId, {
308
- configDir: SESSIONS_DIR,
309
- streaming: true,
310
- tools,
311
- skillDirectories,
312
- onPermissionRequest: approveAll,
313
- infiniteSessions: {
314
- enabled: true,
315
- backgroundCompactionThreshold: 0.80,
316
- bufferExhaustionThreshold: 0.95,
317
- },
318
- });
319
- orchestratorSession = session;
320
- setState(SESSION_TOOLS_KEY, currentToolsHash);
321
- return session;
322
- }
323
- catch (err) {
324
- console.error("[io] Failed to resume session, creating new one:", err instanceof Error ? err.message : err);
325
- deleteState(SESSION_ID_KEY);
326
- deleteState(SESSION_TOOLS_KEY);
327
- }
328
- }
329
- }
330
- console.error("[io] Creating new orchestrator session");
331
- const session = await c.createSession(buildFullSessionConfig());
332
- setState(SESSION_ID_KEY, session.sessionId);
333
- setState(SESSION_TOOLS_KEY, currentToolsHash);
334
- orchestratorSession = session;
56
+ const session = await client.resumeSession(savedId.value, sessionConfig);
335
57
  return session;
336
58
  }
337
- finally {
338
- sessionInitPromise = undefined;
59
+ catch {
60
+ // Resume failed, create new
339
61
  }
340
- })();
341
- return sessionInitPromise;
342
- }
343
- function invalidateSession() {
344
- orchestratorSession = undefined;
345
- sessionInitPromise = undefined;
346
- }
347
- // ---------------------------------------------------------------------------
348
- // Message execution
349
- // ---------------------------------------------------------------------------
350
- async function executeOnSession(prompt, callback, attachments) {
351
- const session = await ensureOrchestratorSession();
352
- let accumulated = "";
353
- const unsubDelta = session.on("assistant.message_delta", (event) => {
354
- const delta = event.data.deltaContent;
355
- accumulated += delta;
356
- callback(delta, false);
357
- });
358
- try {
359
- const sendPayload = { prompt };
360
- if (attachments && attachments.length > 0)
361
- sendPayload.attachments = attachments;
362
- const result = await session.sendAndWait(sendPayload, SEND_TIMEOUT_MS);
363
- unsubDelta();
364
- const finalText = result?.data.content ?? accumulated;
365
- callback("", true);
366
- return finalText;
367
62
  }
368
- catch (err) {
369
- unsubDelta();
370
- // If we accumulated partial text, return it gracefully on timeout
371
- if (accumulated && err instanceof Error && err.message.includes("timeout")) {
372
- console.error("[io] Session sendAndWait timed out, returning partial response");
373
- callback("", true);
374
- return accumulated;
63
+ const session = await client.createSession(sessionConfig);
64
+ // Persist session ID
65
+ db.prepare("INSERT OR REPLACE INTO session_state (key, value) VALUES ('orchestrator_session_id', ?)").run(session.sessionId);
66
+ return session;
67
+ }
68
+ function startHealthCheck(client, opts) {
69
+ healthCheckInterval = setInterval(async () => {
70
+ try {
71
+ await client.ping();
375
72
  }
376
- // Session-level errors: invalidate and let caller retry
377
- if (isSessionError(err)) {
378
- console.error("[io] Session error, invalidating:", err instanceof Error ? err.message : err);
379
- invalidateSession();
73
+ catch {
74
+ console.warn("[io] Health check failed, resetting client...");
75
+ orchestratorSession = undefined;
76
+ const newClient = await resetClient();
77
+ await ensureOrchestratorSession(newClient, opts);
380
78
  }
381
- throw err;
382
- }
383
- }
384
- // ---------------------------------------------------------------------------
385
- // Queue processing
386
- // ---------------------------------------------------------------------------
387
- function sourceTag(source) {
388
- switch (source.type) {
389
- case "telegram":
390
- return "[via telegram]";
391
- case "tui":
392
- return "[via tui]";
393
- case "background":
394
- return "[via background]";
395
- }
79
+ }, 30_000);
80
+ healthCheckInterval.unref();
396
81
  }
397
- function sourceLabel(source) {
398
- switch (source.type) {
399
- case "telegram":
400
- return "telegram";
401
- case "tui":
402
- return "tui";
403
- case "background":
404
- return "background";
405
- }
82
+ export async function sendToOrchestrator(prompt, source, callback) {
83
+ messageQueue.push({ prompt, source, callback });
84
+ if (!processing)
85
+ processQueue();
406
86
  }
407
87
  async function processQueue() {
408
88
  if (processing)
409
89
  return;
410
90
  processing = true;
411
- try {
412
- while (messageQueue.length > 0) {
413
- const msg = messageQueue.shift();
414
- const taggedPrompt = `${sourceTag(msg.source)} ${msg.prompt}`;
415
- let lastError;
416
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
417
- try {
418
- const response = await executeOnSession(taggedPrompt, msg.callback, msg.attachments);
419
- logConversation("assistant", response, sourceLabel(msg.source));
420
- msg.resolve();
421
- lastError = undefined;
422
- break;
423
- }
424
- catch (err) {
425
- lastError = err instanceof Error ? err : new Error(String(err));
426
- console.error(`[io] Attempt ${attempt}/${MAX_RETRIES} failed:`, lastError.message);
427
- if (isConnectionError(err)) {
428
- // Reset client and invalidate session for connection errors
429
- invalidateSession();
430
- try {
431
- await ensureClient();
432
- }
433
- catch (resetErr) {
434
- console.error("[io] Client reset failed:", resetErr instanceof Error ? resetErr.message : resetErr);
435
- }
436
- }
437
- else if (isSessionError(err)) {
438
- // Session already invalidated in executeOnSession
439
- }
440
- else if (attempt === MAX_RETRIES) {
441
- // Non-retryable error on last attempt
442
- break;
443
- }
444
- }
445
- }
446
- if (lastError) {
447
- const errorMsg = `Sorry, I encountered an error: ${lastError.message}`;
448
- msg.callback(errorMsg, true);
449
- msg.reject(lastError);
450
- }
451
- }
452
- }
453
- finally {
454
- processing = false;
455
- }
456
- }
457
- // ---------------------------------------------------------------------------
458
- // Public API
459
- // ---------------------------------------------------------------------------
460
- export async function initOrchestrator(copilotClient) {
461
- client = copilotClient;
462
- ensureInstanceTables();
463
- const reconciledInstances = reconcileInstances();
464
- if (reconciledInstances > 0) {
465
- console.error(`[orchestrator] Reconciled ${reconciledInstances} stale instance(s) on startup`);
466
- }
467
- startInstanceWatchdog();
468
- clearStaleTasks();
469
- // Load MCP server tools
470
- await initMcpTools();
471
- // Validate the configured model and resolve model tiers
472
- try {
473
- const models = await copilotClient.listModels();
474
- const defaultModel = config.defaultModel || "gpt-4.1";
475
- const modelIds = models.map((m) => m.id);
476
- if (!modelIds.includes(defaultModel)) {
477
- console.error(`[io] Configured model "${defaultModel}" not found. Available: ${modelIds.join(", ")}`);
478
- }
479
- else {
480
- console.error(`[io] Model validated: ${defaultModel}`);
481
- }
482
- resolveModelTiers(modelIds);
483
- }
484
- catch (err) {
485
- console.error("[io] Could not validate models:", err instanceof Error ? err.message : err);
486
- }
487
- // Log built-in tools for diagnostics
488
- try {
489
- const toolsList = await copilotClient.rpc.tools.list({});
490
- const toolNames = toolsList.tools.map((t) => t.name);
491
- console.error(`[io] Built-in tools: ${toolNames.join(", ")}`);
492
- }
493
- catch { /* non-fatal */ }
494
- // Start health check timer
495
- healthCheckTimer = setInterval(() => {
496
- if (!client || client.getState() !== "connected") {
497
- console.error("[io] Health check: client disconnected, reconnecting…");
498
- ensureClient().catch((err) => {
499
- console.error("[io] Health check reconnect failed:", err instanceof Error ? err.message : err);
500
- });
501
- invalidateSession();
502
- }
503
- }, HEALTH_CHECK_INTERVAL_MS);
504
- // Eagerly create/resume the session
505
- try {
506
- await ensureOrchestratorSession();
507
- console.error("[io] Orchestrator session ready");
508
- }
509
- catch (err) {
510
- console.error("[io] Eager session creation failed (will retry on first message):", err instanceof Error ? err.message : err);
511
- }
512
- }
513
- export async function sendToOrchestrator(prompt, source, callback, attachments) {
514
- logConversation("user", prompt, sourceLabel(source));
515
- return new Promise((resolve, reject) => {
516
- messageQueue.push({ prompt, source, callback, attachments, resolve, reject });
517
- processQueue();
518
- });
519
- }
520
- export async function shutdownOrchestrator() {
521
- if (healthCheckTimer) {
522
- clearInterval(healthCheckTimer);
523
- healthCheckTimer = undefined;
524
- }
525
- if (orchestratorSession) {
91
+ while (messageQueue.length > 0) {
92
+ const msg = messageQueue.shift();
526
93
  try {
527
- await orchestratorSession.disconnect();
94
+ await executeOnSession(msg);
528
95
  }
529
96
  catch (err) {
530
- console.error("[io] Error disconnecting session:", err instanceof Error ? err.message : err);
97
+ const errMsg = err instanceof Error ? err.message : "Unknown error";
98
+ msg.callback(`Error: ${errMsg}`, true);
531
99
  }
532
- orchestratorSession = undefined;
533
100
  }
534
- sessionInitPromise = undefined;
535
- clientResetPromise = undefined;
536
- client = undefined;
101
+ processing = false;
537
102
  }
538
- /**
539
- * Abort the orchestrator's current in-flight request. The session remains valid
540
- * for subsequent prompts. Returns true if a session existed and abort was
541
- * attempted, false otherwise.
542
- */
543
- export async function abortOrchestrator() {
544
- if (!orchestratorSession)
545
- return false;
103
+ async function executeOnSession(msg) {
104
+ if (!orchestratorSession) {
105
+ msg.callback("Error: Orchestrator session not initialized.", true);
106
+ return;
107
+ }
108
+ const taggedPrompt = `[via ${msg.source}] ${msg.prompt}`;
109
+ let accumulated = "";
110
+ const unsubscribe = orchestratorSession.on("assistant.message_delta", (event) => {
111
+ const delta = event.data?.deltaContent ?? "";
112
+ accumulated += delta;
113
+ msg.callback(accumulated, false);
114
+ });
546
115
  try {
547
- await orchestratorSession.abort();
548
- return true;
116
+ const response = await orchestratorSession.sendAndWait({ prompt: taggedPrompt }, 600_000);
117
+ const finalContent = response?.data?.content ?? accumulated;
118
+ msg.callback(finalContent, true);
549
119
  }
550
- catch (err) {
551
- console.error("[io] Error aborting orchestrator session:", err instanceof Error ? err.message : err);
552
- return false;
120
+ finally {
121
+ unsubscribe();
553
122
  }
554
123
  }
124
+ export function feedAgentResult(taskId, agentName, result, callback) {
125
+ const prompt = `[Agent task completed] @${agentName} finished task ${taskId}:\n\n${result}`;
126
+ sendToOrchestrator(prompt, "background", callback);
127
+ }
128
+ export function getOrchestratorSession() {
129
+ return orchestratorSession;
130
+ }
555
131
  //# sourceMappingURL=orchestrator.js.map