heyio 0.42.0 → 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.
- package/README.md +40 -52
- package/dist/api/auth.js +35 -38
- package/dist/api/server.js +157 -1139
- package/dist/config.js +49 -32
- package/dist/copilot/agents.js +72 -1055
- package/dist/copilot/client.js +6 -17
- package/dist/copilot/io-scheduler.js +55 -139
- package/dist/copilot/model-router.js +100 -72
- package/dist/copilot/orchestrator.js +91 -515
- package/dist/copilot/scheduler.js +67 -189
- package/dist/copilot/skills.js +41 -366
- package/dist/copilot/system-message.js +40 -200
- package/dist/copilot/tools.js +191 -2042
- package/dist/daemon.js +54 -201
- package/dist/index.js +15 -133
- package/dist/mcp/config.js +23 -31
- package/dist/mcp/index.js +2 -3
- package/dist/mcp/registry.js +33 -88
- package/dist/notify.js +18 -100
- package/dist/paths.js +13 -24
- package/dist/setup.js +35 -0
- package/dist/store/db.js +111 -297
- package/dist/store/feed.js +29 -97
- package/dist/store/instances.js +56 -121
- package/dist/store/schedules.js +21 -73
- package/dist/store/squads.js +35 -186
- package/dist/store/tasks.js +25 -168
- package/dist/telegram/bot.js +20 -312
- package/dist/telegram/handlers.js +39 -3
- package/dist/watchdog.js +31 -45
- package/dist/wiki/fs.js +38 -155
- package/dist/wiki/search.js +31 -44
- package/package.json +5 -8
- package/web-dist/assets/ChatView-EFFiln1H.js +11 -0
- package/web-dist/assets/FeedView-bN4NMOL7.js +6 -0
- package/web-dist/assets/LoginView-CNtasq3n.js +1 -0
- package/web-dist/assets/McpView-C2CHiwsi.js +1 -0
- package/web-dist/assets/SchedulesView-CyilLban.js +1 -0
- package/web-dist/assets/SettingsView-1wLXKEF4.js +1 -0
- package/web-dist/assets/SkillsView-BLsD-0u0.js +1 -0
- package/web-dist/assets/SquadDetailView-CsCw2ZLp.js +21 -0
- package/web-dist/assets/SquadsView-DQ3vFlyO.js +6 -0
- package/web-dist/assets/WikiView-19M3oqnq.js +21 -0
- package/web-dist/assets/api-WGvTsXaE.js +1 -0
- package/web-dist/assets/index-D7M5O-_l.css +1 -0
- package/web-dist/assets/index-DZOS9syn.js +95 -0
- package/web-dist/assets/plus-BOvyX1BC.js +6 -0
- package/web-dist/assets/trash-2-DHoetkC4.js +6 -0
- package/web-dist/favicon.svg +4 -1
- package/web-dist/index.html +7 -10
- package/dist/api/logout.test.js +0 -129
- package/dist/api/mcp.test.js +0 -285
- package/dist/api/wiki.test.js +0 -283
- package/dist/auth/session-logic.js +0 -79
- package/dist/auth/session-logic.test.js +0 -201
- package/dist/copilot/auto-complete-instance.test.js +0 -104
- package/dist/copilot/cron.js +0 -136
- package/dist/copilot/event-summary.js +0 -286
- package/dist/copilot/instance-deactivate.test.js +0 -119
- package/dist/copilot/model-router.test.js +0 -71
- package/dist/copilot/review-backfill.js +0 -57
- package/dist/copilot/session-timeout.js +0 -112
- package/dist/copilot/session-timeout.test.js +0 -372
- package/dist/copilot/skills.test.js +0 -55
- package/dist/copilot/universes.js +0 -469
- package/dist/instance-watchdog.js +0 -104
- package/dist/instance-watchdog.test.js +0 -183
- package/dist/mcp/client.js +0 -109
- package/dist/mcp/client.test.js +0 -99
- package/dist/mcp/config.test.js +0 -49
- package/dist/mcp/registry.test.js +0 -79
- package/dist/notify.test.js +0 -232
- package/dist/store/feed.test.js +0 -279
- package/dist/store/instances.test.js +0 -310
- package/dist/store/io-schedules.js +0 -63
- package/dist/store/notifications.js +0 -79
- package/dist/store/notifications.test.js +0 -197
- package/dist/store/schedule-runs.js +0 -46
- package/dist/store/squads.test.js +0 -405
- package/dist/store/tasks.test.js +0 -150
- package/dist/store/worktrees.js +0 -83
- package/dist/tui/index.js +0 -286
- package/dist/update.js +0 -81
- package/dist/watchdog.test.js +0 -83
- package/dist/wiki/wiki-squad.test.js +0 -54
- package/web-dist/assets/AgentActivityView-CedxxE6K.js +0 -1
- package/web-dist/assets/ChatView-DMkYQo_V.js +0 -4
- package/web-dist/assets/FeedView-BH4q-31V.js +0 -1
- package/web-dist/assets/InboxView-BVwVP4EW.js +0 -1
- package/web-dist/assets/LoginView-DRPDhnwu.js +0 -1
- package/web-dist/assets/McpView-D8yWz-lq.js +0 -1
- package/web-dist/assets/SchedulesView-BzzyncGF.js +0 -1
- package/web-dist/assets/SettingsTabs.vue_vue_type_script_setup_true_lang-oW3ySu7Y.js +0 -1
- package/web-dist/assets/SkillsView-oxpYuhx7.js +0 -1
- package/web-dist/assets/SquadsView-CaKUIKlq.js +0 -1
- package/web-dist/assets/StatusIndicator.vue_vue_type_script_setup_true_lang-8U15Qp_Q.js +0 -1
- package/web-dist/assets/WikiView-C5jXUlfW.js +0 -1
- package/web-dist/assets/index-BrWzNw-N.css +0 -10
- package/web-dist/assets/index-f67odrrt.js +0 -81
- package/web-dist/icons.svg +0 -24
|
@@ -1,555 +1,131 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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 {
|
|
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
|
|
38
|
-
let
|
|
39
|
-
let clientResetPromise;
|
|
9
|
+
let sessionCreatePromise;
|
|
10
|
+
let healthCheckInterval;
|
|
40
11
|
const messageQueue = [];
|
|
41
12
|
let processing = false;
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
167
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
function
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
218
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
338
|
-
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
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
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
412
|
-
|
|
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
|
|
94
|
+
await executeOnSession(msg);
|
|
528
95
|
}
|
|
529
96
|
catch (err) {
|
|
530
|
-
|
|
97
|
+
const errMsg = err instanceof Error ? err.message : "Unknown error";
|
|
98
|
+
msg.callback(`Error: ${errMsg}`, true);
|
|
531
99
|
}
|
|
532
|
-
orchestratorSession = undefined;
|
|
533
100
|
}
|
|
534
|
-
|
|
535
|
-
clientResetPromise = undefined;
|
|
536
|
-
client = undefined;
|
|
101
|
+
processing = false;
|
|
537
102
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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.
|
|
548
|
-
|
|
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
|
-
|
|
551
|
-
|
|
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
|