heyio 0.1.32 → 0.2.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/dist/api/server.js +120 -6
- package/dist/copilot/agents.js +134 -7
- package/dist/copilot/orchestrator.js +17 -0
- package/dist/store/squads.js +1 -1
- package/dist/store/tasks.js +10 -0
- package/package.json +1 -1
- package/web-dist/assets/index-B6FXWKsy.js +74 -0
- package/web-dist/assets/index-CXTrW8OO.css +1 -0
- package/web-dist/index.html +2 -2
- package/web-dist/assets/index-B4Vnperk.css +0 -1
- package/web-dist/assets/index-BLuohum9.js +0 -74
package/dist/api/server.js
CHANGED
|
@@ -5,7 +5,9 @@ import express from "express";
|
|
|
5
5
|
import { config } from "../config.js";
|
|
6
6
|
import { listSkills } from "../copilot/skills.js";
|
|
7
7
|
import { listSquads, createSquad, listSquadAgents } from "../store/squads.js";
|
|
8
|
-
import { getAgentInfo } from "../copilot/agents.js";
|
|
8
|
+
import { getAgentInfo, cancelAgentTask, getTaskEvents, subscribeToTaskEvents } from "../copilot/agents.js";
|
|
9
|
+
import { abortOrchestrator } from "../copilot/orchestrator.js";
|
|
10
|
+
import { getActiveTasks, getTask, listRecentTasks } from "../store/tasks.js";
|
|
9
11
|
import { IO_VERSION } from "../paths.js";
|
|
10
12
|
import { requireAuth } from "./auth.js";
|
|
11
13
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -90,7 +92,21 @@ export async function startApiServer() {
|
|
|
90
92
|
try {
|
|
91
93
|
const slug = Array.isArray(req.params.slug) ? req.params.slug[0] : req.params.slug;
|
|
92
94
|
const agents = listSquadAgents(slug);
|
|
93
|
-
|
|
95
|
+
const activeTasks = getActiveTasks();
|
|
96
|
+
const taskByKey = new Map();
|
|
97
|
+
for (const t of activeTasks) {
|
|
98
|
+
taskByKey.set(t.agent_slug, { task_id: t.task_id, description: t.description });
|
|
99
|
+
}
|
|
100
|
+
const enriched = agents.map((a) => {
|
|
101
|
+
const key = `${slug}:${a.character_name}`;
|
|
102
|
+
const task = taskByKey.get(key) ?? taskByKey.get(slug);
|
|
103
|
+
return {
|
|
104
|
+
...a,
|
|
105
|
+
currentTaskId: task?.task_id ?? null,
|
|
106
|
+
currentTask: task?.description ?? null,
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
res.json({ agents: enriched });
|
|
94
110
|
}
|
|
95
111
|
catch (e) {
|
|
96
112
|
console.error("Error listing squad agents:", e);
|
|
@@ -108,6 +124,93 @@ export async function startApiServer() {
|
|
|
108
124
|
res.status(500).json({ error: "Failed to list agents" });
|
|
109
125
|
}
|
|
110
126
|
});
|
|
127
|
+
// Task history endpoints
|
|
128
|
+
api.get("/tasks", (req, res) => {
|
|
129
|
+
try {
|
|
130
|
+
const limitRaw = req.query.limit;
|
|
131
|
+
const parsed = typeof limitRaw === "string" ? parseInt(limitRaw, 10) : NaN;
|
|
132
|
+
const limit = Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, 200) : 50;
|
|
133
|
+
const tasks = listRecentTasks(limit);
|
|
134
|
+
res.json({ tasks });
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
console.error("Error listing tasks:", e);
|
|
138
|
+
res.status(500).json({ error: "Failed to list tasks" });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
api.get("/tasks/:taskId", (req, res) => {
|
|
142
|
+
try {
|
|
143
|
+
const taskId = Array.isArray(req.params.taskId) ? req.params.taskId[0] : req.params.taskId;
|
|
144
|
+
const task = getTask(taskId);
|
|
145
|
+
if (!task) {
|
|
146
|
+
res.status(404).json({ error: "Task not found" });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
res.json({ task });
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
console.error("Error fetching task:", e);
|
|
153
|
+
res.status(500).json({ error: "Failed to fetch task" });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
api.get("/tasks/:taskId/events", (req, res) => {
|
|
157
|
+
const taskId = Array.isArray(req.params.taskId) ? req.params.taskId[0] : req.params.taskId;
|
|
158
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
159
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
160
|
+
res.setHeader("Connection", "keep-alive");
|
|
161
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
162
|
+
res.flushHeaders();
|
|
163
|
+
const send = (ev) => {
|
|
164
|
+
try {
|
|
165
|
+
res.write(`data: ${JSON.stringify(ev)}\n\n`);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// client likely disconnected; cleanup happens on req.close
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
// Replay buffered events first so a late subscriber sees the full thread
|
|
172
|
+
for (const ev of getTaskEvents(taskId))
|
|
173
|
+
send(ev);
|
|
174
|
+
// Subscribe to live events
|
|
175
|
+
const unsubscribe = subscribeToTaskEvents(taskId, send);
|
|
176
|
+
// Heartbeat to keep proxies / browsers from closing the connection
|
|
177
|
+
const heartbeat = setInterval(() => {
|
|
178
|
+
try {
|
|
179
|
+
res.write(": ping\n\n");
|
|
180
|
+
}
|
|
181
|
+
catch { /* ignore */ }
|
|
182
|
+
}, 15000);
|
|
183
|
+
req.on("close", () => {
|
|
184
|
+
clearInterval(heartbeat);
|
|
185
|
+
unsubscribe();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
// Stop / cancel endpoints
|
|
189
|
+
api.post("/orchestrator/abort", async (_req, res) => {
|
|
190
|
+
try {
|
|
191
|
+
const aborted = await abortOrchestrator();
|
|
192
|
+
res.json({ aborted });
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
console.error("Error aborting orchestrator:", e);
|
|
196
|
+
res.status(500).json({ error: "Failed to abort orchestrator" });
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
api.post("/tasks/:taskId/cancel", async (req, res) => {
|
|
200
|
+
try {
|
|
201
|
+
const taskId = Array.isArray(req.params.taskId) ? req.params.taskId[0] : req.params.taskId;
|
|
202
|
+
const cancelled = await cancelAgentTask(taskId);
|
|
203
|
+
if (!cancelled) {
|
|
204
|
+
res.status(404).json({ error: "Task not found or not running" });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
res.json({ cancelled: true });
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
console.error("Error cancelling task:", e);
|
|
211
|
+
res.status(500).json({ error: "Failed to cancel task" });
|
|
212
|
+
}
|
|
213
|
+
});
|
|
111
214
|
// Chat endpoints
|
|
112
215
|
api.post("/message", async (req, res) => {
|
|
113
216
|
const { text } = req.body;
|
|
@@ -150,14 +253,25 @@ export async function startApiServer() {
|
|
|
150
253
|
app.use(express.static(WEB_DIST));
|
|
151
254
|
console.log("[io] Web frontend enabled");
|
|
152
255
|
}
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
//
|
|
256
|
+
// SPA fallback for browser navigation: when the web frontend is built,
|
|
257
|
+
// serve index.html for any GET request that accepts HTML and isn't an API
|
|
258
|
+
// call. This lets vue-router handle client-side routes like /chat, /skills,
|
|
259
|
+
// /squads, etc. on direct URL access and page refresh. Programmatic clients
|
|
260
|
+
// (curl, fetch without Accept: text/html) fall through to the backward-compat
|
|
261
|
+
// API mount below.
|
|
156
262
|
if (existsSync(WEB_DIST)) {
|
|
157
|
-
app.get(
|
|
263
|
+
app.get(/.*/, (req, res, next) => {
|
|
264
|
+
if (req.path.startsWith("/api/"))
|
|
265
|
+
return next();
|
|
266
|
+
const accept = req.headers.accept ?? "";
|
|
267
|
+
if (!accept.includes("text/html"))
|
|
268
|
+
return next();
|
|
158
269
|
res.sendFile(path.join(WEB_DIST, "index.html"));
|
|
159
270
|
});
|
|
160
271
|
}
|
|
272
|
+
// Backward-compat: mount API at / for non-browser clients (after static files
|
|
273
|
+
// and SPA fallback so frontend routes are not intercepted).
|
|
274
|
+
app.use("/", api);
|
|
161
275
|
return new Promise((resolve) => {
|
|
162
276
|
app.listen(config.port, () => {
|
|
163
277
|
console.log(`[io] Server listening on port ${config.port}`);
|
package/dist/copilot/agents.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
|
+
import { EventEmitter } from "events";
|
|
2
3
|
import { execSync } from "child_process";
|
|
3
4
|
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, mkdirSync, } from "fs";
|
|
4
5
|
import { join, dirname, resolve } from "path";
|
|
@@ -6,21 +7,24 @@ import { homedir } from "os";
|
|
|
6
7
|
import { defineTool, approveAll } from "@github/copilot-sdk";
|
|
7
8
|
import { z } from "zod";
|
|
8
9
|
import { getClient } from "./client.js";
|
|
9
|
-
import { getModelForTask, getModelForTier } from "./model-router.js";
|
|
10
|
+
import { getModelForTask, getModelForTier, classifyComplexity } from "./model-router.js";
|
|
10
11
|
import { getSquad, updateSquadSession, updateSquadStatus, getDecisionsSummary, logDecision, listSquadAgents, getSquadAgent, updateAgentSession, updateAgentStatus, } from "../store/squads.js";
|
|
11
|
-
import { createTask, completeTask, failTask, getActiveTasks, } from "../store/tasks.js";
|
|
12
|
+
import { createTask, completeTask, failTask, getActiveTasks, getTask, cancelTask, } from "../store/tasks.js";
|
|
12
13
|
import { SESSIONS_DIR } from "../paths.js";
|
|
13
14
|
import { getUniverse } from "./universes.js";
|
|
14
15
|
// Key format: "squadSlug:characterName" for per-agent sessions, "squadSlug" for legacy
|
|
15
16
|
const agentSessions = new Map();
|
|
17
|
+
const agentSessionModels = new Map();
|
|
16
18
|
function agentSessionKey(squadSlug, characterName) {
|
|
17
19
|
return characterName ? `${squadSlug}:${characterName}` : squadSlug;
|
|
18
20
|
}
|
|
19
21
|
export function getAgentInfo() {
|
|
20
22
|
const activeTasks = getActiveTasks();
|
|
21
23
|
const tasksByAgent = new Map();
|
|
24
|
+
const taskIdsByAgent = new Map();
|
|
22
25
|
for (const task of activeTasks) {
|
|
23
26
|
tasksByAgent.set(task.agent_slug, task.description);
|
|
27
|
+
taskIdsByAgent.set(task.agent_slug, task.task_id);
|
|
24
28
|
}
|
|
25
29
|
const agents = [];
|
|
26
30
|
const seenSquads = new Set();
|
|
@@ -34,6 +38,7 @@ export function getAgentInfo() {
|
|
|
34
38
|
if (characterName) {
|
|
35
39
|
const agent = getSquadAgent(squadSlug, characterName);
|
|
36
40
|
const currentTask = tasksByAgent.get(key) ?? tasksByAgent.get(squadSlug);
|
|
41
|
+
const currentTaskId = taskIdsByAgent.get(key) ?? taskIdsByAgent.get(squadSlug);
|
|
37
42
|
agents.push({
|
|
38
43
|
slug: squadSlug,
|
|
39
44
|
name: agent ? `${agent.character_name} (${agent.role_title})` : characterName,
|
|
@@ -42,21 +47,61 @@ export function getAgentInfo() {
|
|
|
42
47
|
universe: squad?.universe ?? undefined,
|
|
43
48
|
status: agent?.status === "working" ? "working" : currentTask ? "working" : "idle",
|
|
44
49
|
currentTask,
|
|
50
|
+
currentTaskId,
|
|
45
51
|
});
|
|
46
52
|
}
|
|
47
53
|
else {
|
|
48
54
|
// Legacy generic agent
|
|
49
55
|
const currentTask = tasksByAgent.get(squadSlug);
|
|
56
|
+
const currentTaskId = taskIdsByAgent.get(squadSlug);
|
|
50
57
|
agents.push({
|
|
51
58
|
slug: squadSlug,
|
|
52
59
|
name: squad?.name ?? squadSlug,
|
|
53
60
|
status: currentTask ? "working" : squad?.status === "error" ? "error" : "idle",
|
|
54
61
|
currentTask,
|
|
62
|
+
currentTaskId,
|
|
55
63
|
});
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
66
|
return agents;
|
|
59
67
|
}
|
|
68
|
+
const STREAM_EVENT_TYPES = new Set([
|
|
69
|
+
"assistant.turn_start",
|
|
70
|
+
"assistant.intent",
|
|
71
|
+
"assistant.reasoning",
|
|
72
|
+
"assistant.reasoning_delta",
|
|
73
|
+
"assistant.message_delta",
|
|
74
|
+
"assistant.message",
|
|
75
|
+
"assistant.turn_end",
|
|
76
|
+
"tool.execution_start",
|
|
77
|
+
"tool.execution_progress",
|
|
78
|
+
"tool.execution_partial_result",
|
|
79
|
+
"tool.execution_complete",
|
|
80
|
+
"session.error",
|
|
81
|
+
"session.warning",
|
|
82
|
+
]);
|
|
83
|
+
const MAX_TASK_EVENTS = 1000;
|
|
84
|
+
const taskEventBuffers = new Map();
|
|
85
|
+
const taskEventEmitter = new EventEmitter();
|
|
86
|
+
taskEventEmitter.setMaxListeners(0);
|
|
87
|
+
function recordTaskEvent(taskId, ev) {
|
|
88
|
+
let buf = taskEventBuffers.get(taskId);
|
|
89
|
+
if (!buf) {
|
|
90
|
+
buf = [];
|
|
91
|
+
taskEventBuffers.set(taskId, buf);
|
|
92
|
+
}
|
|
93
|
+
buf.push(ev);
|
|
94
|
+
if (buf.length > MAX_TASK_EVENTS)
|
|
95
|
+
buf.splice(0, buf.length - MAX_TASK_EVENTS);
|
|
96
|
+
taskEventEmitter.emit(taskId, ev);
|
|
97
|
+
}
|
|
98
|
+
export function getTaskEvents(taskId) {
|
|
99
|
+
return taskEventBuffers.get(taskId) ?? [];
|
|
100
|
+
}
|
|
101
|
+
export function subscribeToTaskEvents(taskId, listener) {
|
|
102
|
+
taskEventEmitter.on(taskId, listener);
|
|
103
|
+
return () => taskEventEmitter.off(taskId, listener);
|
|
104
|
+
}
|
|
60
105
|
export async function delegateToAgent(squadSlug, task, onComplete, targetAgent) {
|
|
61
106
|
const squad = getSquad(squadSlug);
|
|
62
107
|
if (!squad) {
|
|
@@ -88,6 +133,22 @@ export async function delegateToAgent(squadSlug, task, onComplete, targetAgent)
|
|
|
88
133
|
updateSquadStatus(squadSlug, "working");
|
|
89
134
|
if (agent)
|
|
90
135
|
updateAgentStatus(squadSlug, agent.character_name, "working");
|
|
136
|
+
// Subscribe to the agent session's events for the duration of this task so
|
|
137
|
+
// the web UI can preview the agent's "thread of consciousness" live.
|
|
138
|
+
recordTaskEvent(taskId, {
|
|
139
|
+
ts: Date.now(),
|
|
140
|
+
type: "task.start",
|
|
141
|
+
data: { taskId, agentKey, description: task },
|
|
142
|
+
});
|
|
143
|
+
const unsubscribe = session.on((event) => {
|
|
144
|
+
if (!STREAM_EVENT_TYPES.has(event.type))
|
|
145
|
+
return;
|
|
146
|
+
recordTaskEvent(taskId, {
|
|
147
|
+
ts: Date.now(),
|
|
148
|
+
type: event.type,
|
|
149
|
+
data: event.data ?? null,
|
|
150
|
+
});
|
|
151
|
+
});
|
|
91
152
|
// Run the task in the background — return taskId immediately
|
|
92
153
|
void (async () => {
|
|
93
154
|
try {
|
|
@@ -97,6 +158,7 @@ export async function delegateToAgent(squadSlug, task, onComplete, targetAgent)
|
|
|
97
158
|
updateSquadStatus(squadSlug, "idle");
|
|
98
159
|
if (agent)
|
|
99
160
|
updateAgentStatus(squadSlug, agent.character_name, "idle");
|
|
161
|
+
recordTaskEvent(taskId, { ts: Date.now(), type: "task.done", data: { result } });
|
|
100
162
|
onComplete(taskId, result);
|
|
101
163
|
}
|
|
102
164
|
catch (err) {
|
|
@@ -105,6 +167,13 @@ export async function delegateToAgent(squadSlug, task, onComplete, targetAgent)
|
|
|
105
167
|
updateSquadStatus(squadSlug, "error");
|
|
106
168
|
if (agent)
|
|
107
169
|
updateAgentStatus(squadSlug, agent.character_name, "error");
|
|
170
|
+
recordTaskEvent(taskId, { ts: Date.now(), type: "task.failed", data: { error: message } });
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
try {
|
|
174
|
+
unsubscribe();
|
|
175
|
+
}
|
|
176
|
+
catch { /* ignore */ }
|
|
108
177
|
}
|
|
109
178
|
})();
|
|
110
179
|
const agentLabel = agent
|
|
@@ -121,6 +190,7 @@ export async function shutdownAgents() {
|
|
|
121
190
|
// best-effort cleanup
|
|
122
191
|
}
|
|
123
192
|
agentSessions.delete(key);
|
|
193
|
+
agentSessionModels.delete(key);
|
|
124
194
|
}
|
|
125
195
|
}
|
|
126
196
|
export function getActiveAgentTasks() {
|
|
@@ -136,18 +206,38 @@ export function getActiveAgentTasks() {
|
|
|
136
206
|
// ---------------------------------------------------------------------------
|
|
137
207
|
/**
|
|
138
208
|
* Create or resume a Copilot session for a specific named agent.
|
|
139
|
-
*
|
|
209
|
+
* Model is selected per-task: uses the higher of the agent's default tier
|
|
210
|
+
* and the task's classified complexity. This means an agent never gets a
|
|
211
|
+
* model worse than their baseline, but can be upgraded for complex tasks.
|
|
140
212
|
*/
|
|
141
213
|
async function getOrCreateAgentSession(squadSlug, agent, taskDescription) {
|
|
142
214
|
const key = agentSessionKey(squadSlug, agent.character_name);
|
|
215
|
+
// Determine model based on task complexity vs agent's default tier
|
|
216
|
+
const agentTier = agent.model_tier;
|
|
217
|
+
const taskTier = taskDescription ? classifyComplexity(taskDescription) : agentTier;
|
|
218
|
+
const tierRank = { high: 3, medium: 2, low: 1 };
|
|
219
|
+
const effectiveTier = tierRank[taskTier] >= tierRank[agentTier] ? taskTier : agentTier;
|
|
220
|
+
const model = getModelForTier(effectiveTier);
|
|
221
|
+
// If we have a cached session, check if the model matches; if not, destroy and recreate
|
|
143
222
|
const existing = agentSessions.get(key);
|
|
144
|
-
if (existing)
|
|
145
|
-
|
|
223
|
+
if (existing) {
|
|
224
|
+
// Sessions don't expose their model, so track it separately
|
|
225
|
+
const cachedModel = agentSessionModels.get(key);
|
|
226
|
+
if (cachedModel === model)
|
|
227
|
+
return existing;
|
|
228
|
+
// Model changed — destroy old session for the upgraded model
|
|
229
|
+
console.error(`[io] Agent ${agent.character_name}: upgrading model ${cachedModel} → ${model} for task complexity`);
|
|
230
|
+
try {
|
|
231
|
+
await existing.destroy();
|
|
232
|
+
}
|
|
233
|
+
catch { /* best-effort */ }
|
|
234
|
+
agentSessions.delete(key);
|
|
235
|
+
agentSessionModels.delete(key);
|
|
236
|
+
}
|
|
146
237
|
const squad = getSquad(squadSlug);
|
|
147
238
|
const client = await getClient();
|
|
148
239
|
const decisions = getDecisionsSummary(squadSlug);
|
|
149
|
-
|
|
150
|
-
const model = getModelForTier(agent.model_tier);
|
|
240
|
+
console.error(`[io] Agent ${agent.character_name}: using model "${model}" (agent tier: ${agentTier}, task tier: ${taskTier}, effective: ${effectiveTier})`);
|
|
151
241
|
const universeName = squad.universe
|
|
152
242
|
? getUniverse(squad.universe)?.name ?? squad.universe
|
|
153
243
|
: "Unknown";
|
|
@@ -199,6 +289,7 @@ Stay in character — let your personality color your work style and communicati
|
|
|
199
289
|
}
|
|
200
290
|
updateAgentSession(squadSlug, agent.character_name, session.sessionId);
|
|
201
291
|
agentSessions.set(key, session);
|
|
292
|
+
agentSessionModels.set(key, model);
|
|
202
293
|
return session;
|
|
203
294
|
}
|
|
204
295
|
/**
|
|
@@ -390,4 +481,40 @@ function walkDirectory(dir, maxDepth = 3, depth = 0) {
|
|
|
390
481
|
}
|
|
391
482
|
return results;
|
|
392
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* Cancel a running agent task by aborting its session and marking the task
|
|
486
|
+
* cancelled. Returns true if the task existed and was running.
|
|
487
|
+
*/
|
|
488
|
+
export async function cancelAgentTask(taskId) {
|
|
489
|
+
const task = getTask(taskId);
|
|
490
|
+
if (!task || task.status !== "running")
|
|
491
|
+
return false;
|
|
492
|
+
const sessionKey = task.agent_slug;
|
|
493
|
+
const session = agentSessions.get(sessionKey);
|
|
494
|
+
if (session) {
|
|
495
|
+
try {
|
|
496
|
+
await session.abort();
|
|
497
|
+
}
|
|
498
|
+
catch (err) {
|
|
499
|
+
console.error("[io] Error aborting agent session:", err instanceof Error ? err.message : err);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
cancelTask(taskId);
|
|
503
|
+
recordTaskEvent(taskId, { ts: Date.now(), type: "task.cancelled", data: { reason: "Cancelled by user" } });
|
|
504
|
+
// sessionKey is "squadSlug" or "squadSlug:characterName"
|
|
505
|
+
const [squadSlug, characterName] = sessionKey.split(":");
|
|
506
|
+
if (squadSlug) {
|
|
507
|
+
try {
|
|
508
|
+
updateSquadStatus(squadSlug, "idle");
|
|
509
|
+
}
|
|
510
|
+
catch { /* ignore */ }
|
|
511
|
+
}
|
|
512
|
+
if (squadSlug && characterName) {
|
|
513
|
+
try {
|
|
514
|
+
updateAgentStatus(squadSlug, characterName, "idle");
|
|
515
|
+
}
|
|
516
|
+
catch { /* ignore */ }
|
|
517
|
+
}
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
393
520
|
//# sourceMappingURL=agents.js.map
|
|
@@ -407,4 +407,21 @@ export async function shutdownOrchestrator() {
|
|
|
407
407
|
clientResetPromise = undefined;
|
|
408
408
|
client = undefined;
|
|
409
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* Abort the orchestrator's current in-flight request. The session remains valid
|
|
412
|
+
* for subsequent prompts. Returns true if a session existed and abort was
|
|
413
|
+
* attempted, false otherwise.
|
|
414
|
+
*/
|
|
415
|
+
export async function abortOrchestrator() {
|
|
416
|
+
if (!orchestratorSession)
|
|
417
|
+
return false;
|
|
418
|
+
try {
|
|
419
|
+
await orchestratorSession.abort();
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
console.error("[io] Error aborting orchestrator session:", err instanceof Error ? err.message : err);
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
410
427
|
//# sourceMappingURL=orchestrator.js.map
|
package/dist/store/squads.js
CHANGED
|
@@ -74,7 +74,7 @@ export function getSquadAgent(squadSlug, characterName) {
|
|
|
74
74
|
}
|
|
75
75
|
export function listSquadAgents(squadSlug) {
|
|
76
76
|
return getDb()
|
|
77
|
-
.prepare("SELECT * FROM squad_agents WHERE squad_slug = ? ORDER BY
|
|
77
|
+
.prepare("SELECT * FROM squad_agents WHERE squad_slug = ? ORDER BY id ASC")
|
|
78
78
|
.all(squadSlug);
|
|
79
79
|
}
|
|
80
80
|
export function removeSquadAgent(squadSlug, characterName) {
|
package/dist/store/tasks.js
CHANGED
|
@@ -29,4 +29,14 @@ export function clearStaleTasks() {
|
|
|
29
29
|
.prepare("UPDATE agent_tasks SET status = 'failed', result = 'Marked stale on startup', completed_at = CURRENT_TIMESTAMP WHERE status = 'running'")
|
|
30
30
|
.run();
|
|
31
31
|
}
|
|
32
|
+
export function cancelTask(taskId, reason = "Cancelled by user") {
|
|
33
|
+
getDb()
|
|
34
|
+
.prepare("UPDATE agent_tasks SET status = 'cancelled', result = ?, completed_at = CURRENT_TIMESTAMP WHERE task_id = ? AND status = 'running'")
|
|
35
|
+
.run(reason, taskId);
|
|
36
|
+
}
|
|
37
|
+
export function listRecentTasks(limit = 50) {
|
|
38
|
+
return getDb()
|
|
39
|
+
.prepare("SELECT * FROM agent_tasks ORDER BY datetime(started_at) DESC, task_id DESC LIMIT ?")
|
|
40
|
+
.all(limit);
|
|
41
|
+
}
|
|
32
42
|
//# sourceMappingURL=tasks.js.map
|