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.
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 -1139
  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 -129
  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-CedxxE6K.js +0 -1
  87. package/web-dist/assets/ChatView-DMkYQo_V.js +0 -4
  88. package/web-dist/assets/FeedView-BH4q-31V.js +0 -1
  89. package/web-dist/assets/InboxView-BVwVP4EW.js +0 -1
  90. package/web-dist/assets/LoginView-DRPDhnwu.js +0 -1
  91. package/web-dist/assets/McpView-D8yWz-lq.js +0 -1
  92. package/web-dist/assets/SchedulesView-BzzyncGF.js +0 -1
  93. package/web-dist/assets/SettingsTabs.vue_vue_type_script_setup_true_lang-oW3ySu7Y.js +0 -1
  94. package/web-dist/assets/SkillsView-oxpYuhx7.js +0 -1
  95. package/web-dist/assets/SquadsView-CaKUIKlq.js +0 -1
  96. package/web-dist/assets/StatusIndicator.vue_vue_type_script_setup_true_lang-8U15Qp_Q.js +0 -1
  97. package/web-dist/assets/WikiView-C5jXUlfW.js +0 -1
  98. package/web-dist/assets/index-BrWzNw-N.css +0 -10
  99. package/web-dist/assets/index-f67odrrt.js +0 -81
  100. 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