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,286 +0,0 @@
|
|
|
1
|
-
// Tiered activity log normalizer.
|
|
2
|
-
//
|
|
3
|
-
// Converts raw TaskStreamEvent records (forwarded from the Copilot SDK
|
|
4
|
-
// session) into "activity entries" that present a clean human-readable
|
|
5
|
-
// summary. Each entry preserves the underlying raw payload for drill-down so
|
|
6
|
-
// no information is lost.
|
|
7
|
-
//
|
|
8
|
-
// Used by:
|
|
9
|
-
// - GET /tasks/:taskId/activity (full collapsed list)
|
|
10
|
-
// - the SSE per-event payload (single-event summary attached alongside raw)
|
|
11
|
-
// - the TUI /activity command
|
|
12
|
-
const TOOL_VERBS = {
|
|
13
|
-
bash: "Ran shell command",
|
|
14
|
-
shell: "Ran shell command",
|
|
15
|
-
read_file: "Read file",
|
|
16
|
-
view: "Read file",
|
|
17
|
-
create: "Created file",
|
|
18
|
-
edit: "Edited file",
|
|
19
|
-
str_replace_editor: "Edited file",
|
|
20
|
-
grep: "Searched code",
|
|
21
|
-
glob: "Searched filenames",
|
|
22
|
-
web_fetch: "Fetched URL",
|
|
23
|
-
github: "Called GitHub API",
|
|
24
|
-
delegate_to_teammate: "Delegated to teammate",
|
|
25
|
-
squad_delegate: "Delegated to squad",
|
|
26
|
-
squad_status: "Listed squads",
|
|
27
|
-
squad_agents: "Listed squad agents",
|
|
28
|
-
squad_set_lead: "Set squad lead",
|
|
29
|
-
squad_set_qa: "Set squad QA",
|
|
30
|
-
squad_task_status: "Checked task status",
|
|
31
|
-
squad_task_reviews: "Read task reviews",
|
|
32
|
-
squad_log_decision: "Logged squad decision",
|
|
33
|
-
squad_schedule_create: "Created squad schedule",
|
|
34
|
-
squad_schedule_list: "Listed squad schedules",
|
|
35
|
-
squad_schedule_run_now: "Fired squad schedule",
|
|
36
|
-
wiki_read: "Read wiki page",
|
|
37
|
-
wiki_write: "Wrote wiki page",
|
|
38
|
-
wiki_search: "Searched wiki",
|
|
39
|
-
wiki_list: "Listed wiki pages",
|
|
40
|
-
skill_list: "Listed skills",
|
|
41
|
-
skill_install: "Installed skill",
|
|
42
|
-
skill_remove: "Removed skill",
|
|
43
|
-
skill_search: "Searched skills registry",
|
|
44
|
-
config_update: "Updated config",
|
|
45
|
-
check_update: "Checked for IO update",
|
|
46
|
-
file_ops: "File operation",
|
|
47
|
-
};
|
|
48
|
-
function trunc(s, n) {
|
|
49
|
-
if (!s)
|
|
50
|
-
return "";
|
|
51
|
-
const flat = s.replace(/\s+/g, " ").trim();
|
|
52
|
-
return flat.length > n ? flat.slice(0, n - 1) + "…" : flat;
|
|
53
|
-
}
|
|
54
|
-
function pickArgSummary(toolName, args) {
|
|
55
|
-
if (!args)
|
|
56
|
-
return "";
|
|
57
|
-
const a = args;
|
|
58
|
-
switch (toolName) {
|
|
59
|
-
case "bash":
|
|
60
|
-
case "shell": {
|
|
61
|
-
const cmd = typeof a.command === "string" ? a.command : "";
|
|
62
|
-
return trunc(cmd, 80);
|
|
63
|
-
}
|
|
64
|
-
case "read_file":
|
|
65
|
-
case "view":
|
|
66
|
-
case "create":
|
|
67
|
-
case "edit":
|
|
68
|
-
case "str_replace_editor": {
|
|
69
|
-
const p = typeof a.path === "string" ? a.path : (typeof a.file_path === "string" ? a.file_path : "");
|
|
70
|
-
return p;
|
|
71
|
-
}
|
|
72
|
-
case "grep": {
|
|
73
|
-
const pat = typeof a.pattern === "string" ? `"${trunc(a.pattern, 40)}"` : "";
|
|
74
|
-
const paths = Array.isArray(a.paths) ? a.paths.join(", ") : (typeof a.paths === "string" ? a.paths : "");
|
|
75
|
-
return [pat, paths].filter(Boolean).join(" in ");
|
|
76
|
-
}
|
|
77
|
-
case "glob": {
|
|
78
|
-
return typeof a.pattern === "string" ? a.pattern : "";
|
|
79
|
-
}
|
|
80
|
-
case "web_fetch": {
|
|
81
|
-
return typeof a.url === "string" ? a.url : "";
|
|
82
|
-
}
|
|
83
|
-
case "delegate_to_teammate": {
|
|
84
|
-
const teammate = typeof a.teammate === "string" ? a.teammate : "";
|
|
85
|
-
const task = typeof a.task === "string" ? trunc(a.task, 60) : "";
|
|
86
|
-
return [teammate, task].filter(Boolean).join(": ");
|
|
87
|
-
}
|
|
88
|
-
case "squad_delegate": {
|
|
89
|
-
const slug = typeof a.slug === "string" ? a.slug : "";
|
|
90
|
-
const agent = typeof a.agent === "string" ? ` → ${a.agent}` : "";
|
|
91
|
-
const task = typeof a.task === "string" ? trunc(a.task, 50) : "";
|
|
92
|
-
return `${slug}${agent}${task ? `: ${task}` : ""}`;
|
|
93
|
-
}
|
|
94
|
-
case "wiki_read":
|
|
95
|
-
case "wiki_write":
|
|
96
|
-
case "wiki_delete": {
|
|
97
|
-
return typeof a.path === "string" ? a.path : "";
|
|
98
|
-
}
|
|
99
|
-
case "wiki_search":
|
|
100
|
-
case "skill_search": {
|
|
101
|
-
return typeof a.query === "string" ? `"${trunc(a.query, 40)}"` : "";
|
|
102
|
-
}
|
|
103
|
-
case "github": {
|
|
104
|
-
const ep = typeof a.endpoint === "string" ? a.endpoint : "";
|
|
105
|
-
const method = typeof a.method === "string" ? a.method : "";
|
|
106
|
-
return [method, ep].filter(Boolean).join(" ");
|
|
107
|
-
}
|
|
108
|
-
case "file_ops": {
|
|
109
|
-
const op = typeof a.operation === "string" ? a.operation : "";
|
|
110
|
-
const p = typeof a.path === "string" ? a.path : "";
|
|
111
|
-
return [op, p].filter(Boolean).join(" ");
|
|
112
|
-
}
|
|
113
|
-
default: {
|
|
114
|
-
// Best-effort: stringify the first arg value.
|
|
115
|
-
const k = Object.keys(a)[0];
|
|
116
|
-
if (!k)
|
|
117
|
-
return "";
|
|
118
|
-
const v = a[k];
|
|
119
|
-
const s = typeof v === "string" ? v : JSON.stringify(v);
|
|
120
|
-
return `${k}=${trunc(s ?? "", 50)}`;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
/** Summarize a single TaskStreamEvent. */
|
|
125
|
-
export function summarizeEvent(ev) {
|
|
126
|
-
const data = (ev.data ?? {});
|
|
127
|
-
const base = { ts: ev.ts, rawType: ev.type, raw: ev.data };
|
|
128
|
-
switch (ev.type) {
|
|
129
|
-
case "assistant.intent": {
|
|
130
|
-
const intent = typeof data.intent === "string" ? data.intent : "";
|
|
131
|
-
return { ...base, kind: "reasoning", icon: "🧠", summary: intent || "(intent)" };
|
|
132
|
-
}
|
|
133
|
-
case "assistant.reasoning": {
|
|
134
|
-
const content = typeof data.content === "string" ? data.content : "";
|
|
135
|
-
return {
|
|
136
|
-
...base,
|
|
137
|
-
kind: "reasoning",
|
|
138
|
-
icon: "🧠",
|
|
139
|
-
summary: trunc(content, 140) || "(reasoning)",
|
|
140
|
-
detail: content,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
case "assistant.message": {
|
|
144
|
-
const content = typeof data.content === "string" ? data.content : "";
|
|
145
|
-
return {
|
|
146
|
-
...base,
|
|
147
|
-
kind: "message",
|
|
148
|
-
icon: "💬",
|
|
149
|
-
summary: trunc(content, 200) || "(empty message)",
|
|
150
|
-
detail: content,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
case "assistant.turn_start":
|
|
154
|
-
return { ...base, kind: "system", icon: "▶️", summary: "Turn started" };
|
|
155
|
-
case "assistant.turn_end":
|
|
156
|
-
return { ...base, kind: "system", icon: "⏹️", summary: "Turn ended" };
|
|
157
|
-
case "tool.execution_start": {
|
|
158
|
-
const name = typeof data.toolName === "string" ? data.toolName : "tool";
|
|
159
|
-
const verb = TOOL_VERBS[name] ?? `Used ${name}`;
|
|
160
|
-
const args = pickArgSummary(name, data.arguments);
|
|
161
|
-
return {
|
|
162
|
-
...base,
|
|
163
|
-
kind: "tool",
|
|
164
|
-
icon: "🔧",
|
|
165
|
-
summary: args ? `${verb} — ${args}` : verb,
|
|
166
|
-
toolCallId: typeof data.toolCallId === "string" ? data.toolCallId : undefined,
|
|
167
|
-
status: "pending",
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
case "tool.execution_complete": {
|
|
171
|
-
const success = data.success === true;
|
|
172
|
-
const result = (data.result ?? {});
|
|
173
|
-
const content = typeof result.content === "string" ? result.content : "";
|
|
174
|
-
return {
|
|
175
|
-
...base,
|
|
176
|
-
kind: "tool",
|
|
177
|
-
icon: success ? "✅" : "❌",
|
|
178
|
-
summary: success ? "Tool completed" : "Tool failed",
|
|
179
|
-
detail: trunc(content, 300),
|
|
180
|
-
toolCallId: typeof data.toolCallId === "string" ? data.toolCallId : undefined,
|
|
181
|
-
status: success ? "success" : "error",
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
case "tool.execution_progress": {
|
|
185
|
-
const message = typeof data.message === "string" ? data.message : "";
|
|
186
|
-
return {
|
|
187
|
-
...base,
|
|
188
|
-
kind: "tool",
|
|
189
|
-
icon: "⏳",
|
|
190
|
-
summary: trunc(message, 140) || "Tool progress",
|
|
191
|
-
toolCallId: typeof data.toolCallId === "string" ? data.toolCallId : undefined,
|
|
192
|
-
status: "pending",
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
case "session.error": {
|
|
196
|
-
const msg = typeof data.message === "string" ? data.message : (typeof data.error === "string" ? data.error : "");
|
|
197
|
-
return { ...base, kind: "outcome", icon: "❌", summary: `Error: ${trunc(msg, 160) || "session error"}` };
|
|
198
|
-
}
|
|
199
|
-
case "session.warning": {
|
|
200
|
-
const msg = typeof data.message === "string" ? data.message : "";
|
|
201
|
-
return { ...base, kind: "outcome", icon: "⚠️", summary: `Warning: ${trunc(msg, 160) || "session warning"}` };
|
|
202
|
-
}
|
|
203
|
-
case "task.done": {
|
|
204
|
-
const r = typeof data.result === "string" ? data.result : "";
|
|
205
|
-
return { ...base, kind: "outcome", icon: "✅", summary: "Task completed", detail: trunc(r, 300) };
|
|
206
|
-
}
|
|
207
|
-
case "task.failed": {
|
|
208
|
-
const e = typeof data.error === "string" ? data.error : "";
|
|
209
|
-
return { ...base, kind: "outcome", icon: "❌", summary: `Task failed: ${trunc(e, 160)}` };
|
|
210
|
-
}
|
|
211
|
-
case "task.cancelled":
|
|
212
|
-
return { ...base, kind: "outcome", icon: "🛑", summary: "Task cancelled" };
|
|
213
|
-
case "task.review":
|
|
214
|
-
return {
|
|
215
|
-
...base,
|
|
216
|
-
kind: "outcome",
|
|
217
|
-
icon: data.approved ? "👍" : "👎",
|
|
218
|
-
summary: `Review by ${data.reviewer ?? "?"}: ${data.approved ? "APPROVED" : "REJECTED"}${data.is_qa ? " (QA)" : ""}`,
|
|
219
|
-
detail: typeof data.comments === "string" ? data.comments : undefined,
|
|
220
|
-
};
|
|
221
|
-
case "task.review_complete":
|
|
222
|
-
return {
|
|
223
|
-
...base,
|
|
224
|
-
kind: "outcome",
|
|
225
|
-
icon: data.promoted ? "🚀" : "ℹ️",
|
|
226
|
-
summary: data.promoted
|
|
227
|
-
? `Promoted PR: ${data.prUrl ?? ""}`
|
|
228
|
-
: `Review complete: ${typeof data.reason === "string" ? data.reason : ""}`,
|
|
229
|
-
};
|
|
230
|
-
case "task.review_advisory":
|
|
231
|
-
return {
|
|
232
|
-
...base,
|
|
233
|
-
kind: "outcome",
|
|
234
|
-
icon: "ℹ️",
|
|
235
|
-
summary: typeof data.reason === "string" ? data.reason : "Advisory review note",
|
|
236
|
-
};
|
|
237
|
-
case "task.review_error": {
|
|
238
|
-
const e = typeof data.error === "string" ? data.error : "";
|
|
239
|
-
return { ...base, kind: "outcome", icon: "❌", summary: `Review error: ${trunc(e, 160)}` };
|
|
240
|
-
}
|
|
241
|
-
default:
|
|
242
|
-
return { ...base, kind: "system", icon: "•", summary: ev.type };
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Collapse a stream of events into the activity log. Tool start + complete
|
|
247
|
-
* pairs are merged into one entry (success/error and detail attached).
|
|
248
|
-
* Streaming deltas (assistant.message_delta, assistant.reasoning_delta,
|
|
249
|
-
* tool.execution_partial_result) are dropped — the "complete" sibling event
|
|
250
|
-
* is what we summarize.
|
|
251
|
-
*/
|
|
252
|
-
export function summarize(events) {
|
|
253
|
-
const out = [];
|
|
254
|
-
const toolIndex = new Map(); // toolCallId → index into out
|
|
255
|
-
for (const ev of events) {
|
|
256
|
-
if (ev.type === "assistant.message_delta" ||
|
|
257
|
-
ev.type === "assistant.reasoning_delta" ||
|
|
258
|
-
ev.type === "tool.execution_partial_result") {
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
const entry = summarizeEvent(ev);
|
|
262
|
-
if (entry.kind === "tool" && entry.toolCallId) {
|
|
263
|
-
const existing = toolIndex.get(entry.toolCallId);
|
|
264
|
-
if (existing != null && entry.status && entry.status !== "pending") {
|
|
265
|
-
// Merge: keep the verb from the start event, add status/detail/icon from completion.
|
|
266
|
-
const prior = out[existing];
|
|
267
|
-
out[existing] = {
|
|
268
|
-
...prior,
|
|
269
|
-
icon: entry.icon,
|
|
270
|
-
status: entry.status,
|
|
271
|
-
detail: entry.detail ?? prior.detail,
|
|
272
|
-
// Append a brief outcome marker if the start summary is the only descriptor.
|
|
273
|
-
summary: prior.summary,
|
|
274
|
-
// Keep the completion event raw available for drill-down by stashing on detail when no detail present
|
|
275
|
-
raw: { start: prior.raw, complete: entry.raw },
|
|
276
|
-
rawType: `${prior.rawType}+${entry.rawType}`,
|
|
277
|
-
};
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
toolIndex.set(entry.toolCallId, out.length);
|
|
281
|
-
}
|
|
282
|
-
out.push(entry);
|
|
283
|
-
}
|
|
284
|
-
return out;
|
|
285
|
-
}
|
|
286
|
-
//# sourceMappingURL=event-summary.js.map
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for auto-deactivation of activeInstanceId on instance complete/abort.
|
|
3
|
-
* Exercises the squad_instance_complete and squad_instance_abort tool handlers.
|
|
4
|
-
*/
|
|
5
|
-
import { describe, it, beforeEach } from "node:test";
|
|
6
|
-
import assert from "node:assert/strict";
|
|
7
|
-
import { createTools } from "./tools.js";
|
|
8
|
-
// Minimal mock deps sufficient to test the instance tools
|
|
9
|
-
function makeMockDeps(overrides = {}) {
|
|
10
|
-
const instances = {};
|
|
11
|
-
const base = {
|
|
12
|
-
wikiRead: () => undefined,
|
|
13
|
-
wikiWrite: () => { },
|
|
14
|
-
wikiSearch: () => [],
|
|
15
|
-
wikiAssertPagePath: () => { },
|
|
16
|
-
wikiDelete: () => false,
|
|
17
|
-
wikiList: () => [],
|
|
18
|
-
getSquad: () => ({ slug: "test", name: "Test", projectPath: "/tmp/test", status: "idle" }),
|
|
19
|
-
listSquads: () => [],
|
|
20
|
-
createSquad: () => { },
|
|
21
|
-
deleteSquad: () => { },
|
|
22
|
-
logDecision: () => { },
|
|
23
|
-
getDecisionsSummary: () => "",
|
|
24
|
-
getRecentDecisions: () => [],
|
|
25
|
-
updateSquadStatus: () => { },
|
|
26
|
-
delegateToAgent: async () => "task-1",
|
|
27
|
-
getTask: () => undefined,
|
|
28
|
-
getActiveAgentTasks: () => [],
|
|
29
|
-
addSquadAgent: () => ({ character_name: "A", role_title: "R", personality: null, model_tier: "medium" }),
|
|
30
|
-
listSquadAgents: () => [],
|
|
31
|
-
getAgentTaskStats: () => [],
|
|
32
|
-
getStalestSpecialist: () => null,
|
|
33
|
-
removeSquadAgent: () => false,
|
|
34
|
-
resetSquadAgent: () => ({ found: false, previousStatus: "", agent: null }),
|
|
35
|
-
setSquadLead: () => { },
|
|
36
|
-
getSquadLead: () => undefined,
|
|
37
|
-
setSquadQA: () => { },
|
|
38
|
-
getTaskReviews: () => [],
|
|
39
|
-
getSquadWorkDistribution: () => ({ total: 0, perAgent: [] }),
|
|
40
|
-
listSkills: () => [],
|
|
41
|
-
installSkill: async () => ({ name: "", slug: "", description: "", path: "" }),
|
|
42
|
-
removeSkill: () => false,
|
|
43
|
-
searchSkillsRegistry: async () => [],
|
|
44
|
-
saveConfig: () => { },
|
|
45
|
-
checkForUpdate: async () => ({ updateAvailable: false, current: "1.0.0", latest: "1.0.0" }),
|
|
46
|
-
// Instance deps
|
|
47
|
-
createInstance: (input) => {
|
|
48
|
-
const inst = { id: input.id, master_squad_slug: input.masterSquadSlug, status: "pending", worktree_path: input.worktreePath, branch_name: input.branchName, issue_ref: null, context_snapshot: null, created_at: new Date().toISOString(), completed_at: null };
|
|
49
|
-
instances[input.id] = inst;
|
|
50
|
-
return inst;
|
|
51
|
-
},
|
|
52
|
-
getInstance: (id) => instances[id],
|
|
53
|
-
listInstances: () => [],
|
|
54
|
-
updateInstanceStatus: (id, status) => { if (instances[id])
|
|
55
|
-
instances[id].status = status; },
|
|
56
|
-
logInstanceDecision: () => { },
|
|
57
|
-
getInstanceDecisions: () => [],
|
|
58
|
-
mergeInstanceDecisions: () => 0,
|
|
59
|
-
deleteInstance: (id) => { delete instances[id]; },
|
|
60
|
-
buildContextSnapshot: () => "[]",
|
|
61
|
-
reconcileInstances: () => 0,
|
|
62
|
-
createWorktree: () => "/tmp/wt",
|
|
63
|
-
removeWorktree: () => { },
|
|
64
|
-
activeInstanceId: undefined,
|
|
65
|
-
...overrides,
|
|
66
|
-
};
|
|
67
|
-
// Pre-seed an instance for tests
|
|
68
|
-
instances["test-squad--issue-1"] = {
|
|
69
|
-
id: "test-squad--issue-1",
|
|
70
|
-
master_squad_slug: "test",
|
|
71
|
-
issue_ref: "#1",
|
|
72
|
-
worktree_path: "/tmp/wt/test-squad--issue-1",
|
|
73
|
-
branch_name: "test/instance/issue-1",
|
|
74
|
-
status: "active",
|
|
75
|
-
context_snapshot: null,
|
|
76
|
-
created_at: new Date().toISOString(),
|
|
77
|
-
completed_at: null,
|
|
78
|
-
};
|
|
79
|
-
return base;
|
|
80
|
-
}
|
|
81
|
-
function findToolHandler(tools, name) {
|
|
82
|
-
const tool = tools.find((t) => t.name === name);
|
|
83
|
-
if (!tool)
|
|
84
|
-
throw new Error(`Tool not found: ${name}`);
|
|
85
|
-
return tool.handler;
|
|
86
|
-
}
|
|
87
|
-
describe("auto-deactivate activeInstanceId", () => {
|
|
88
|
-
let deps;
|
|
89
|
-
let tools;
|
|
90
|
-
beforeEach(() => {
|
|
91
|
-
deps = makeMockDeps();
|
|
92
|
-
tools = createTools(deps);
|
|
93
|
-
});
|
|
94
|
-
it("completing an active instance auto-deactivates it", async () => {
|
|
95
|
-
deps.activeInstanceId = "test-squad--issue-1";
|
|
96
|
-
const handler = findToolHandler(tools, "squad_instance_complete");
|
|
97
|
-
await handler({ instance_id: "test-squad--issue-1" });
|
|
98
|
-
assert.strictEqual(deps.activeInstanceId, undefined);
|
|
99
|
-
});
|
|
100
|
-
it("aborting an active instance auto-deactivates it", async () => {
|
|
101
|
-
deps.activeInstanceId = "test-squad--issue-1";
|
|
102
|
-
const handler = findToolHandler(tools, "squad_instance_abort");
|
|
103
|
-
await handler({ instance_id: "test-squad--issue-1" });
|
|
104
|
-
assert.strictEqual(deps.activeInstanceId, undefined);
|
|
105
|
-
});
|
|
106
|
-
it("completing a non-active instance does NOT change activeInstanceId", async () => {
|
|
107
|
-
deps.activeInstanceId = "some-other-instance";
|
|
108
|
-
const handler = findToolHandler(tools, "squad_instance_complete");
|
|
109
|
-
await handler({ instance_id: "test-squad--issue-1" });
|
|
110
|
-
assert.strictEqual(deps.activeInstanceId, "some-other-instance");
|
|
111
|
-
});
|
|
112
|
-
it("aborting a non-active instance does NOT change activeInstanceId", async () => {
|
|
113
|
-
deps.activeInstanceId = "some-other-instance";
|
|
114
|
-
const handler = findToolHandler(tools, "squad_instance_abort");
|
|
115
|
-
await handler({ instance_id: "test-squad--issue-1" });
|
|
116
|
-
assert.strictEqual(deps.activeInstanceId, "some-other-instance");
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
//# sourceMappingURL=instance-deactivate.test.js.map
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { classifyComplexity } from "./model-router.js";
|
|
4
|
-
describe("classifyComplexity", () => {
|
|
5
|
-
describe("high complexity", () => {
|
|
6
|
-
it("returns high when multiple high keywords are present", () => {
|
|
7
|
-
assert.equal(classifyComplexity("Refactor and redesign the auth module"), "high");
|
|
8
|
-
});
|
|
9
|
-
it("returns high with architect + security keywords", () => {
|
|
10
|
-
assert.equal(classifyComplexity("Architect a security overhaul"), "high");
|
|
11
|
-
});
|
|
12
|
-
it("returns high with debug + investigate", () => {
|
|
13
|
-
assert.equal(classifyComplexity("Investigate and debug the memory leak"), "high");
|
|
14
|
-
});
|
|
15
|
-
it("returns high for performance + optimize", () => {
|
|
16
|
-
assert.equal(classifyComplexity("Optimize performance of the query engine"), "high");
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
describe("low complexity", () => {
|
|
20
|
-
it("returns low when multiple low keywords are present", () => {
|
|
21
|
-
assert.equal(classifyComplexity("Read the file and check status"), "low");
|
|
22
|
-
});
|
|
23
|
-
it("returns low for simple rename task", () => {
|
|
24
|
-
assert.equal(classifyComplexity("Simple rename of the variable"), "low");
|
|
25
|
-
});
|
|
26
|
-
it("returns low for list + format", () => {
|
|
27
|
-
assert.equal(classifyComplexity("List all entries and format them"), "low");
|
|
28
|
-
});
|
|
29
|
-
it("returns low for delete file + remove file", () => {
|
|
30
|
-
assert.equal(classifyComplexity("Delete file foo.txt and remove file bar.txt"), "low");
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
describe("medium complexity", () => {
|
|
34
|
-
it("returns medium for ambiguous description with no keywords", () => {
|
|
35
|
-
assert.equal(classifyComplexity("Update the user profile page"), "medium");
|
|
36
|
-
});
|
|
37
|
-
it("returns medium for empty string", () => {
|
|
38
|
-
assert.equal(classifyComplexity(""), "medium");
|
|
39
|
-
});
|
|
40
|
-
it("returns medium when one high and one low keyword tie", () => {
|
|
41
|
-
// highScore=1, lowScore=1 — both weak, neither wins, falls to default
|
|
42
|
-
assert.equal(classifyComplexity("Debug and check the output"), "medium");
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
describe("weak signal tiebreaker", () => {
|
|
46
|
-
it("returns high when single high keyword beats zero low keywords", () => {
|
|
47
|
-
assert.equal(classifyComplexity("Refactor the utils module"), "high");
|
|
48
|
-
});
|
|
49
|
-
it("returns low when single low keyword beats zero high keywords", () => {
|
|
50
|
-
assert.equal(classifyComplexity("Check the build output"), "low");
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
describe("case insensitivity", () => {
|
|
54
|
-
it("matches keywords regardless of case", () => {
|
|
55
|
-
assert.equal(classifyComplexity("REFACTOR and REDESIGN everything"), "high");
|
|
56
|
-
});
|
|
57
|
-
it("matches low keywords in mixed case", () => {
|
|
58
|
-
assert.equal(classifyComplexity("FORMAT the List of entries"), "low");
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
describe("multi-word keywords", () => {
|
|
62
|
-
it("matches 'delete file' as a single keyword", () => {
|
|
63
|
-
assert.equal(classifyComplexity("Please delete file and copy file"), "low");
|
|
64
|
-
});
|
|
65
|
-
it("does not match partial multi-word keywords", () => {
|
|
66
|
-
// "delete" alone is not a keyword, only "delete file" is
|
|
67
|
-
assert.equal(classifyComplexity("Delete the branch"), "medium");
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
//# sourceMappingURL=model-router.test.js.map
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { getDb } from "../store/db.js";
|
|
2
|
-
/**
|
|
3
|
-
* Surgically correct historical peer-review rows that were stored as REJECTED
|
|
4
|
-
* (approved=0) but whose comments unambiguously begin with `APPROVED` (issue
|
|
5
|
-
* #50). Earlier daemon builds (pre-#43) inspected only the literal first line,
|
|
6
|
-
* which silently flipped many APPROVED reviews into REJECTED whenever the
|
|
7
|
-
* agent began its response with a markdown rule, blank line, header, or a
|
|
8
|
-
* short prose preamble.
|
|
9
|
-
*
|
|
10
|
-
* We only flip 0 -> 1, and only when the *current* parser sees an explicit
|
|
11
|
-
* line-leading APPROVED token in the prose. We never flip 1 -> 0: doing so
|
|
12
|
-
* would destroy data on legitimate prose-only approvals (e.g. "Excellent
|
|
13
|
-
* work — ships it") that the conservative parser would otherwise downgrade.
|
|
14
|
-
*
|
|
15
|
-
* Returns the number of rows updated.
|
|
16
|
-
*/
|
|
17
|
-
export function backfillReviewVerdicts() {
|
|
18
|
-
const db = getDb();
|
|
19
|
-
const rows = db
|
|
20
|
-
.prepare("SELECT id, approved, comments FROM squad_task_reviews WHERE approved = 0 AND comments IS NOT NULL AND comments != ''")
|
|
21
|
-
.all();
|
|
22
|
-
const update = db.prepare("UPDATE squad_task_reviews SET approved = 1 WHERE id = ?");
|
|
23
|
-
let fixed = 0;
|
|
24
|
-
const tx = db.transaction((batch) => {
|
|
25
|
-
for (const r of batch) {
|
|
26
|
-
if (hasExplicitApproval(r.comments ?? "")) {
|
|
27
|
-
update.run(r.id);
|
|
28
|
-
fixed++;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
tx(rows);
|
|
33
|
-
return fixed;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* True when the comment body unambiguously starts with an APPROVED verdict —
|
|
37
|
-
* either as the first non-empty line (after stripping markdown noise) or as
|
|
38
|
-
* the verdict the current parser extracts before any REJECTED token. Anything
|
|
39
|
-
* shorter than that is left as the daemon originally recorded it.
|
|
40
|
-
*/
|
|
41
|
-
function hasExplicitApproval(content) {
|
|
42
|
-
const stripped = content.replace(/[*_`#>]/g, "");
|
|
43
|
-
const lines = stripped
|
|
44
|
-
.split(/\r?\n/)
|
|
45
|
-
.map((l) => l.trim())
|
|
46
|
-
.filter(Boolean)
|
|
47
|
-
.slice(0, 10);
|
|
48
|
-
for (const line of lines) {
|
|
49
|
-
const lead = line
|
|
50
|
-
.toUpperCase()
|
|
51
|
-
.match(/^[^A-Z]*\b(APPROVED|REJECTED)\b/);
|
|
52
|
-
if (lead)
|
|
53
|
-
return lead[1] === "APPROVED";
|
|
54
|
-
}
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
//# sourceMappingURL=review-backfill.js.map
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Idle timeout helper for agent task execution (issue #53).
|
|
3
|
-
*
|
|
4
|
-
* The Copilot SDK's `sendAndWait(prompt, timeout)` enforces a wall-clock
|
|
5
|
-
* timeout. Long-running squad tasks were silently killed at 600s even when
|
|
6
|
-
* the agent was actively making progress (#42, #45). This helper replaces
|
|
7
|
-
* the wall-clock timeout with an **idle-reset** timeout: every progress
|
|
8
|
-
* event (tool execution, assistant message, turn boundary) resets the
|
|
9
|
-
* timer. The agent is only killed if it stops emitting events for `idleMs`
|
|
10
|
-
* — i.e. it is actually stuck, not just slow.
|
|
11
|
-
*
|
|
12
|
-
* On graceful timeout we capture the partial content emitted so far and
|
|
13
|
-
* surface it to the caller instead of throwing.
|
|
14
|
-
*/
|
|
15
|
-
const PROGRESS_EVENT_TYPES = new Set([
|
|
16
|
-
"assistant.turn_start",
|
|
17
|
-
"assistant.message_delta",
|
|
18
|
-
"assistant.message",
|
|
19
|
-
"assistant.turn_end",
|
|
20
|
-
"assistant.reasoning",
|
|
21
|
-
"assistant.reasoning_delta",
|
|
22
|
-
"tool.execution_start",
|
|
23
|
-
"tool.execution_progress",
|
|
24
|
-
"tool.execution_partial_result",
|
|
25
|
-
"tool.execution_complete",
|
|
26
|
-
]);
|
|
27
|
-
export async function sendWithIdleTimeout(session, prompt, opts) {
|
|
28
|
-
let accumulated = "";
|
|
29
|
-
let lastEventType;
|
|
30
|
-
let idleTimer;
|
|
31
|
-
let aborted = false;
|
|
32
|
-
let abortReason;
|
|
33
|
-
const triggerIdleAbort = () => {
|
|
34
|
-
if (aborted)
|
|
35
|
-
return;
|
|
36
|
-
aborted = true;
|
|
37
|
-
abortReason = "idle";
|
|
38
|
-
opts.onIdleTimeout?.({ lastEventType, idleMs: opts.idleMs });
|
|
39
|
-
void session.abort().catch(() => {
|
|
40
|
-
/* best-effort */
|
|
41
|
-
});
|
|
42
|
-
};
|
|
43
|
-
const resetIdle = () => {
|
|
44
|
-
if (idleTimer)
|
|
45
|
-
clearTimeout(idleTimer);
|
|
46
|
-
idleTimer = setTimeout(triggerIdleAbort, opts.idleMs);
|
|
47
|
-
};
|
|
48
|
-
const unsubDelta = session.on("assistant.message_delta", (event) => {
|
|
49
|
-
const delta = event?.data?.deltaContent;
|
|
50
|
-
if (typeof delta === "string")
|
|
51
|
-
accumulated += delta;
|
|
52
|
-
});
|
|
53
|
-
const unsubAll = session.on((event) => {
|
|
54
|
-
if (PROGRESS_EVENT_TYPES.has(event.type)) {
|
|
55
|
-
lastEventType = event.type;
|
|
56
|
-
opts.onProgress?.(event.type);
|
|
57
|
-
resetIdle();
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
resetIdle();
|
|
61
|
-
try {
|
|
62
|
-
const response = await session.sendAndWait({ prompt }, opts.hardCapMs);
|
|
63
|
-
if (aborted) {
|
|
64
|
-
return {
|
|
65
|
-
content: response?.data?.content ?? accumulated,
|
|
66
|
-
timedOut: true,
|
|
67
|
-
timeoutReason: abortReason,
|
|
68
|
-
lastEventType,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
content: response?.data?.content ?? accumulated,
|
|
73
|
-
timedOut: false,
|
|
74
|
-
lastEventType,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
catch (err) {
|
|
78
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
79
|
-
const looksLikeTimeout = /timeout/i.test(message);
|
|
80
|
-
if (aborted || looksLikeTimeout) {
|
|
81
|
-
if (!aborted && looksLikeTimeout) {
|
|
82
|
-
abortReason = "hard_cap";
|
|
83
|
-
opts.onHardCap?.();
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
content: accumulated ||
|
|
87
|
-
`(no output captured before timeout; last event: ${lastEventType ?? "none"})`,
|
|
88
|
-
timedOut: true,
|
|
89
|
-
timeoutReason: abortReason ?? "hard_cap",
|
|
90
|
-
lastEventType,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
throw err;
|
|
94
|
-
}
|
|
95
|
-
finally {
|
|
96
|
-
if (idleTimer)
|
|
97
|
-
clearTimeout(idleTimer);
|
|
98
|
-
try {
|
|
99
|
-
unsubDelta();
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
/* ignore */
|
|
103
|
-
}
|
|
104
|
-
try {
|
|
105
|
-
unsubAll();
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
/* ignore */
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
//# sourceMappingURL=session-timeout.js.map
|