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,372 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for src/copilot/session-timeout.ts — sendWithIdleTimeout
|
|
3
|
-
*
|
|
4
|
-
* Strategy: build a FakeSession class whose sendAndWait() is controlled by
|
|
5
|
-
* a deferred Promise. Event handlers registered via .on() are stored in a
|
|
6
|
-
* subscriber list and can be triggered manually with .emit(). Timer
|
|
7
|
-
* behaviour is driven by node:test's mock.timers so no real setTimeout fires.
|
|
8
|
-
*/
|
|
9
|
-
import { describe, it, before, after, beforeEach, mock } from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import { sendWithIdleTimeout, } from "./session-timeout.js";
|
|
12
|
-
/** Minimal fake implementing only what sendWithIdleTimeout needs. */
|
|
13
|
-
function makeFakeSession() {
|
|
14
|
-
const broadcastHandlers = [];
|
|
15
|
-
const deltaHandlers = [];
|
|
16
|
-
let resolveWait;
|
|
17
|
-
let rejectWait;
|
|
18
|
-
let aborted = false;
|
|
19
|
-
const session = {
|
|
20
|
-
// Typed overload: session.on("assistant.message_delta", handler)
|
|
21
|
-
// Generic overload: session.on(handler)
|
|
22
|
-
on(typeOrHandler, handler) {
|
|
23
|
-
if (typeof typeOrHandler === "function") {
|
|
24
|
-
broadcastHandlers.push(typeOrHandler);
|
|
25
|
-
return () => {
|
|
26
|
-
const idx = broadcastHandlers.indexOf(typeOrHandler);
|
|
27
|
-
if (idx !== -1)
|
|
28
|
-
broadcastHandlers.splice(idx, 1);
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
// typed: only care about delta for accumulation
|
|
32
|
-
if (typeOrHandler === "assistant.message_delta" && handler) {
|
|
33
|
-
deltaHandlers.push(handler);
|
|
34
|
-
return () => {
|
|
35
|
-
const idx = deltaHandlers.indexOf(handler);
|
|
36
|
-
if (idx !== -1)
|
|
37
|
-
deltaHandlers.splice(idx, 1);
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
return () => { };
|
|
41
|
-
},
|
|
42
|
-
sendAndWait(_opts, _timeout) {
|
|
43
|
-
return new Promise((resolve, reject) => {
|
|
44
|
-
resolveWait = resolve;
|
|
45
|
-
rejectWait = reject;
|
|
46
|
-
});
|
|
47
|
-
},
|
|
48
|
-
abort() {
|
|
49
|
-
aborted = true;
|
|
50
|
-
return Promise.resolve();
|
|
51
|
-
},
|
|
52
|
-
// ── Test-only helpers ──────────────────────────────────────────────────
|
|
53
|
-
/** Emit an event to all broad-listener handlers. */
|
|
54
|
-
emit(event) {
|
|
55
|
-
for (const h of broadcastHandlers)
|
|
56
|
-
h(event);
|
|
57
|
-
},
|
|
58
|
-
/** Emit an assistant.message_delta event with the given text chunk. */
|
|
59
|
-
emitDelta(deltaContent) {
|
|
60
|
-
const deltaEvent = {
|
|
61
|
-
type: "assistant.message_delta",
|
|
62
|
-
id: "evt-1",
|
|
63
|
-
timestamp: new Date().toISOString(),
|
|
64
|
-
parentId: null,
|
|
65
|
-
ephemeral: true,
|
|
66
|
-
data: { messageId: "msg-1", deltaContent },
|
|
67
|
-
};
|
|
68
|
-
// Notify delta-specific handlers
|
|
69
|
-
for (const h of deltaHandlers) {
|
|
70
|
-
h(deltaEvent);
|
|
71
|
-
}
|
|
72
|
-
// Also broadcast to all-event handlers so idle timer resets
|
|
73
|
-
this.emit(deltaEvent);
|
|
74
|
-
},
|
|
75
|
-
/** Emit a generic progress event (e.g. tool.execution_complete). */
|
|
76
|
-
emitProgress(type) {
|
|
77
|
-
const event = {
|
|
78
|
-
type,
|
|
79
|
-
id: "evt-2",
|
|
80
|
-
timestamp: new Date().toISOString(),
|
|
81
|
-
parentId: null,
|
|
82
|
-
ephemeral: false,
|
|
83
|
-
data: {},
|
|
84
|
-
};
|
|
85
|
-
this.emit(event);
|
|
86
|
-
},
|
|
87
|
-
/** Resolve the sendAndWait promise with a final content string. */
|
|
88
|
-
resolve(content) {
|
|
89
|
-
resolveWait?.({ data: { content } });
|
|
90
|
-
},
|
|
91
|
-
/** Resolve the sendAndWait promise with undefined (triggers accumulated fallback). */
|
|
92
|
-
resolveUndefined() {
|
|
93
|
-
resolveWait?.(undefined);
|
|
94
|
-
},
|
|
95
|
-
/** Reject the sendAndWait promise with a timeout-style error. */
|
|
96
|
-
rejectWithTimeout() {
|
|
97
|
-
rejectWait?.(new Error("Timeout after 600000ms waiting for session.idle"));
|
|
98
|
-
},
|
|
99
|
-
/** Reject the sendAndWait promise with a non-timeout error. */
|
|
100
|
-
rejectWithError(msg) {
|
|
101
|
-
rejectWait?.(new Error(msg));
|
|
102
|
-
},
|
|
103
|
-
get wasAborted() {
|
|
104
|
-
return aborted;
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
return session;
|
|
108
|
-
}
|
|
109
|
-
// Cast FakeSession to CopilotSession for type-compatibility with sendWithIdleTimeout.
|
|
110
|
-
// The fake satisfies the structural subset used by the function.
|
|
111
|
-
function asSession(fake) {
|
|
112
|
-
return fake;
|
|
113
|
-
}
|
|
114
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
115
|
-
const DEFAULT_OPTS = {
|
|
116
|
-
idleMs: 5_000,
|
|
117
|
-
hardCapMs: 30_000,
|
|
118
|
-
};
|
|
119
|
-
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
120
|
-
describe("sendWithIdleTimeout", () => {
|
|
121
|
-
before(() => {
|
|
122
|
-
mock.timers.enable({ apis: ["setTimeout"] });
|
|
123
|
-
});
|
|
124
|
-
after(() => {
|
|
125
|
-
mock.timers.reset();
|
|
126
|
-
});
|
|
127
|
-
beforeEach(() => {
|
|
128
|
-
// Reset the fake timer state between tests without disabling
|
|
129
|
-
mock.timers.reset();
|
|
130
|
-
mock.timers.enable({ apis: ["setTimeout"] });
|
|
131
|
-
});
|
|
132
|
-
// ── happy path ──────────────────────────────────────────────────────────
|
|
133
|
-
it("returns content and timedOut=false when session resolves normally", async () => {
|
|
134
|
-
const fake = makeFakeSession();
|
|
135
|
-
const promise = sendWithIdleTimeout(asSession(fake), "do something", DEFAULT_OPTS);
|
|
136
|
-
// Let the initial idle timer be registered, then resolve immediately
|
|
137
|
-
fake.resolve("task done successfully");
|
|
138
|
-
const result = await promise;
|
|
139
|
-
assert.equal(result.timedOut, false);
|
|
140
|
-
assert.equal(result.content, "task done successfully");
|
|
141
|
-
assert.equal(result.timeoutReason, undefined);
|
|
142
|
-
});
|
|
143
|
-
it("accumulates delta content during streaming (verified via resolveUndefined)", async () => {
|
|
144
|
-
const fake = makeFakeSession();
|
|
145
|
-
const promise = sendWithIdleTimeout(asSession(fake), "stream this", DEFAULT_OPTS);
|
|
146
|
-
fake.emitDelta("Hello ");
|
|
147
|
-
fake.emitDelta("world");
|
|
148
|
-
fake.emitDelta("!");
|
|
149
|
-
// resolveUndefined triggers `response?.data?.content ?? accumulated` → uses accumulated
|
|
150
|
-
fake.resolveUndefined();
|
|
151
|
-
const result = await promise;
|
|
152
|
-
assert.equal(result.timedOut, false);
|
|
153
|
-
assert.equal(result.content, "Hello world!");
|
|
154
|
-
});
|
|
155
|
-
it("falls back to accumulated delta when sendAndWait resolves with undefined", async () => {
|
|
156
|
-
const fake = makeFakeSession();
|
|
157
|
-
const promise = sendWithIdleTimeout(asSession(fake), "stream this", DEFAULT_OPTS);
|
|
158
|
-
fake.emitDelta("partial ");
|
|
159
|
-
fake.emitDelta("output");
|
|
160
|
-
// Resolve with undefined to trigger the `response?.data?.content ?? accumulated` fallback
|
|
161
|
-
fake.resolveUndefined();
|
|
162
|
-
const result = await promise;
|
|
163
|
-
assert.equal(result.timedOut, false);
|
|
164
|
-
assert.equal(result.content, "partial output");
|
|
165
|
-
});
|
|
166
|
-
// ── idle timer reset ────────────────────────────────────────────────────
|
|
167
|
-
it("resets idle timer on assistant.message_delta", async () => {
|
|
168
|
-
const fake = makeFakeSession();
|
|
169
|
-
const idleTimeoutFired = { value: false };
|
|
170
|
-
const opts = {
|
|
171
|
-
...DEFAULT_OPTS,
|
|
172
|
-
idleMs: 1_000,
|
|
173
|
-
onIdleTimeout: () => { idleTimeoutFired.value = true; },
|
|
174
|
-
};
|
|
175
|
-
const promise = sendWithIdleTimeout(asSession(fake), "do work", opts);
|
|
176
|
-
// Advance to just before idle timeout — timer should NOT fire yet
|
|
177
|
-
mock.timers.tick(800);
|
|
178
|
-
assert.equal(idleTimeoutFired.value, false);
|
|
179
|
-
// Emit a progress event — should reset the idle timer
|
|
180
|
-
fake.emitProgress("tool.execution_complete");
|
|
181
|
-
// Advance another 800ms — if reset worked, timer still hasn't fired (800 < 1000)
|
|
182
|
-
mock.timers.tick(800);
|
|
183
|
-
assert.equal(idleTimeoutFired.value, false, "idle timer should have been reset by progress event");
|
|
184
|
-
// Resolve and clean up
|
|
185
|
-
fake.resolve("done");
|
|
186
|
-
await promise;
|
|
187
|
-
});
|
|
188
|
-
it("fires idle timeout after idleMs of silence", async () => {
|
|
189
|
-
const fake = makeFakeSession();
|
|
190
|
-
let idleFired = false;
|
|
191
|
-
const opts = {
|
|
192
|
-
...DEFAULT_OPTS,
|
|
193
|
-
idleMs: 2_000,
|
|
194
|
-
onIdleTimeout: () => { idleFired = true; },
|
|
195
|
-
};
|
|
196
|
-
const promise = sendWithIdleTimeout(asSession(fake), "long task", opts);
|
|
197
|
-
// Advance past idle timeout — no events emitted
|
|
198
|
-
mock.timers.tick(2_001);
|
|
199
|
-
// Abort fires async; wait a tick then check
|
|
200
|
-
await Promise.resolve();
|
|
201
|
-
assert.equal(idleFired, true, "onIdleTimeout should have fired");
|
|
202
|
-
assert.equal(fake.wasAborted, true, "session.abort() should have been called");
|
|
203
|
-
// Simulate the sendAndWait timing out after abort
|
|
204
|
-
fake.rejectWithTimeout();
|
|
205
|
-
const result = await promise;
|
|
206
|
-
assert.equal(result.timedOut, true);
|
|
207
|
-
});
|
|
208
|
-
it("resets idle timer on tool.execution_complete", async () => {
|
|
209
|
-
const fake = makeFakeSession();
|
|
210
|
-
let idleFired = false;
|
|
211
|
-
const opts = {
|
|
212
|
-
...DEFAULT_OPTS,
|
|
213
|
-
idleMs: 1_000,
|
|
214
|
-
onIdleTimeout: () => { idleFired = true; },
|
|
215
|
-
};
|
|
216
|
-
const promise = sendWithIdleTimeout(asSession(fake), "tool task", opts);
|
|
217
|
-
mock.timers.tick(700);
|
|
218
|
-
fake.emitProgress("tool.execution_complete");
|
|
219
|
-
mock.timers.tick(700); // 700ms since last reset — still under 1000ms
|
|
220
|
-
assert.equal(idleFired, false, "idle timer should have been reset by tool event");
|
|
221
|
-
fake.resolve("done");
|
|
222
|
-
await promise;
|
|
223
|
-
assert.equal(idleFired, false);
|
|
224
|
-
});
|
|
225
|
-
it("resets idle timer on assistant.turn_start", async () => {
|
|
226
|
-
const fake = makeFakeSession();
|
|
227
|
-
let idleFired = false;
|
|
228
|
-
const opts = {
|
|
229
|
-
...DEFAULT_OPTS,
|
|
230
|
-
idleMs: 1_000,
|
|
231
|
-
onIdleTimeout: () => { idleFired = true; },
|
|
232
|
-
};
|
|
233
|
-
const promise = sendWithIdleTimeout(asSession(fake), "turn task", opts);
|
|
234
|
-
mock.timers.tick(700);
|
|
235
|
-
fake.emitProgress("assistant.turn_start");
|
|
236
|
-
mock.timers.tick(700);
|
|
237
|
-
assert.equal(idleFired, false, "idle timer should reset on assistant.turn_start");
|
|
238
|
-
fake.resolve("done");
|
|
239
|
-
await promise;
|
|
240
|
-
});
|
|
241
|
-
it("does NOT reset idle timer for unrecognised event types", async () => {
|
|
242
|
-
const fake = makeFakeSession();
|
|
243
|
-
let idleFired = false;
|
|
244
|
-
const opts = {
|
|
245
|
-
...DEFAULT_OPTS,
|
|
246
|
-
idleMs: 1_000,
|
|
247
|
-
onIdleTimeout: () => { idleFired = true; },
|
|
248
|
-
};
|
|
249
|
-
const promise = sendWithIdleTimeout(asSession(fake), "noisy task", opts);
|
|
250
|
-
mock.timers.tick(700);
|
|
251
|
-
// This event type is not in PROGRESS_EVENT_TYPES
|
|
252
|
-
fake.emitProgress("some.unknown.event");
|
|
253
|
-
mock.timers.tick(400); // 700 + 400 = 1100ms since last real reset
|
|
254
|
-
await Promise.resolve();
|
|
255
|
-
assert.equal(idleFired, true, "idle timer should fire — unknown event should not reset it");
|
|
256
|
-
fake.rejectWithTimeout();
|
|
257
|
-
const result = await promise;
|
|
258
|
-
assert.equal(result.timedOut, true);
|
|
259
|
-
});
|
|
260
|
-
// ── graceful timeout capture ─────────────────────────────────────────────
|
|
261
|
-
it("captures partial content when idle timeout fires mid-stream", async () => {
|
|
262
|
-
const fake = makeFakeSession();
|
|
263
|
-
const opts = {
|
|
264
|
-
...DEFAULT_OPTS,
|
|
265
|
-
idleMs: 1_000,
|
|
266
|
-
};
|
|
267
|
-
const promise = sendWithIdleTimeout(asSession(fake), "long output", opts);
|
|
268
|
-
// Agent emits some content then goes silent
|
|
269
|
-
fake.emitDelta("Step 1 done. ");
|
|
270
|
-
fake.emitDelta("Step 2 in progress...");
|
|
271
|
-
// Advance past idle timeout
|
|
272
|
-
mock.timers.tick(1_001);
|
|
273
|
-
await Promise.resolve();
|
|
274
|
-
// sendAndWait throws a timeout error after abort
|
|
275
|
-
fake.rejectWithTimeout();
|
|
276
|
-
const result = await promise;
|
|
277
|
-
assert.equal(result.timedOut, true);
|
|
278
|
-
// idle timer fires first → abortReason="idle"; catch block sees aborted=true and preserves it
|
|
279
|
-
assert.equal(result.timeoutReason, "idle", "idle fired before sendAndWait rejection → reason stays idle");
|
|
280
|
-
assert.ok(result.content.includes("Step 1 done.") && result.content.includes("Step 2 in progress..."), `partial content should be captured; got: ${result.content}`);
|
|
281
|
-
});
|
|
282
|
-
it("returns fallback message when no content was accumulated before timeout", async () => {
|
|
283
|
-
const fake = makeFakeSession();
|
|
284
|
-
const opts = { ...DEFAULT_OPTS, idleMs: 500 };
|
|
285
|
-
const promise = sendWithIdleTimeout(asSession(fake), "silent agent", opts);
|
|
286
|
-
mock.timers.tick(501);
|
|
287
|
-
await Promise.resolve();
|
|
288
|
-
fake.rejectWithTimeout();
|
|
289
|
-
const result = await promise;
|
|
290
|
-
assert.equal(result.timedOut, true);
|
|
291
|
-
assert.ok(result.content.includes("no output captured"), `should report no output captured; got: ${result.content}`);
|
|
292
|
-
});
|
|
293
|
-
it("sets timeoutReason=idle when abort fires before sendAndWait throws", async () => {
|
|
294
|
-
const fake = makeFakeSession();
|
|
295
|
-
let idleCallbackInfo;
|
|
296
|
-
const opts = {
|
|
297
|
-
...DEFAULT_OPTS,
|
|
298
|
-
idleMs: 500,
|
|
299
|
-
onIdleTimeout: (info) => { idleCallbackInfo = info; },
|
|
300
|
-
};
|
|
301
|
-
const promise = sendWithIdleTimeout(asSession(fake), "silent", opts);
|
|
302
|
-
mock.timers.tick(501);
|
|
303
|
-
await Promise.resolve(); // let abort() fire
|
|
304
|
-
// When aborted=true, the resolve branch returns timedOut:true with timeoutReason from abortReason
|
|
305
|
-
fake.resolve("some final content");
|
|
306
|
-
const result = await promise;
|
|
307
|
-
assert.equal(result.timedOut, true);
|
|
308
|
-
assert.equal(result.timeoutReason, "idle");
|
|
309
|
-
assert.ok(idleCallbackInfo, "onIdleTimeout callback should have been called");
|
|
310
|
-
assert.equal(idleCallbackInfo?.idleMs, 500);
|
|
311
|
-
});
|
|
312
|
-
it("calls onProgress for each recognised event type", async () => {
|
|
313
|
-
const fake = makeFakeSession();
|
|
314
|
-
const progressEvents = [];
|
|
315
|
-
const opts = {
|
|
316
|
-
...DEFAULT_OPTS,
|
|
317
|
-
onProgress: (type) => progressEvents.push(type),
|
|
318
|
-
};
|
|
319
|
-
const promise = sendWithIdleTimeout(asSession(fake), "track progress", opts);
|
|
320
|
-
fake.emitProgress("tool.execution_start");
|
|
321
|
-
fake.emitProgress("tool.execution_complete");
|
|
322
|
-
fake.emitDelta("some output");
|
|
323
|
-
fake.resolve("done");
|
|
324
|
-
await promise;
|
|
325
|
-
assert.ok(progressEvents.includes("tool.execution_start"));
|
|
326
|
-
assert.ok(progressEvents.includes("tool.execution_complete"));
|
|
327
|
-
assert.ok(progressEvents.includes("assistant.message_delta"));
|
|
328
|
-
});
|
|
329
|
-
it("tracks lastEventType in result", async () => {
|
|
330
|
-
const fake = makeFakeSession();
|
|
331
|
-
const promise = sendWithIdleTimeout(asSession(fake), "track last", DEFAULT_OPTS);
|
|
332
|
-
fake.emitProgress("tool.execution_start");
|
|
333
|
-
fake.emitProgress("tool.execution_complete");
|
|
334
|
-
fake.resolve("done");
|
|
335
|
-
const result = await promise;
|
|
336
|
-
assert.equal(result.lastEventType, "tool.execution_complete");
|
|
337
|
-
});
|
|
338
|
-
// ── error handling ───────────────────────────────────────────────────────
|
|
339
|
-
it("re-throws non-timeout errors", async () => {
|
|
340
|
-
const fake = makeFakeSession();
|
|
341
|
-
const promise = sendWithIdleTimeout(asSession(fake), "bad prompt", DEFAULT_OPTS);
|
|
342
|
-
fake.rejectWithError("unexpected authentication failure");
|
|
343
|
-
await assert.rejects(() => promise, (err) => {
|
|
344
|
-
assert.ok(err.message.includes("unexpected authentication failure"));
|
|
345
|
-
return true;
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
it("calls onHardCap when sendAndWait throws timeout and aborted=false", async () => {
|
|
349
|
-
const fake = makeFakeSession();
|
|
350
|
-
let hardCapFired = false;
|
|
351
|
-
const opts = {
|
|
352
|
-
...DEFAULT_OPTS,
|
|
353
|
-
onHardCap: () => { hardCapFired = true; },
|
|
354
|
-
};
|
|
355
|
-
const promise = sendWithIdleTimeout(asSession(fake), "hard task", opts);
|
|
356
|
-
// Don't advance past idle timer — just let the hard cap throw
|
|
357
|
-
fake.rejectWithTimeout();
|
|
358
|
-
const result = await promise;
|
|
359
|
-
assert.equal(result.timedOut, true);
|
|
360
|
-
assert.equal(result.timeoutReason, "hard_cap");
|
|
361
|
-
assert.equal(hardCapFired, true);
|
|
362
|
-
});
|
|
363
|
-
it("cleans up subscriptions after normal completion (no memory leak)", async () => {
|
|
364
|
-
const fake = makeFakeSession();
|
|
365
|
-
const promise = sendWithIdleTimeout(asSession(fake), "cleanup test", DEFAULT_OPTS);
|
|
366
|
-
fake.resolve("done");
|
|
367
|
-
const result = await promise;
|
|
368
|
-
assert.equal(result.timedOut, false);
|
|
369
|
-
// If unsubscribe threw, the test would fail — we're asserting no throw
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
//# sourceMappingURL=session-timeout.test.js.map
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for src/copilot/skills.ts — skill installation, listing, and management.
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it } from "node:test";
|
|
5
|
-
import assert from "node:assert/strict";
|
|
6
|
-
import { parseSkillUrl, removeSkill, updateSkill, deleteSkill, } from "./skills.js";
|
|
7
|
-
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
8
|
-
describe("parseSkillUrl", () => {
|
|
9
|
-
it("parses GitHub blob URLs for SKILL.md", () => {
|
|
10
|
-
const url = "https://github.com/owner/repo/blob/main/SKILL.md";
|
|
11
|
-
const result = parseSkillUrl(url);
|
|
12
|
-
assert.equal(result.type, "file");
|
|
13
|
-
assert.ok("rawUrl" in result);
|
|
14
|
-
assert.ok(result.rawUrl.includes("raw.githubusercontent.com"));
|
|
15
|
-
});
|
|
16
|
-
it("parses GitHub raw content URLs", () => {
|
|
17
|
-
const url = "https://raw.githubusercontent.com/owner/repo/main/SKILL.md";
|
|
18
|
-
const result = parseSkillUrl(url);
|
|
19
|
-
assert.equal(result.type, "file");
|
|
20
|
-
assert.ok("rawUrl" in result);
|
|
21
|
-
});
|
|
22
|
-
it("parses GitHub repo URLs as repo type", () => {
|
|
23
|
-
const url = "https://github.com/owner/repo";
|
|
24
|
-
const result = parseSkillUrl(url);
|
|
25
|
-
assert.equal(result.type, "repo");
|
|
26
|
-
assert.equal(result.url, url);
|
|
27
|
-
});
|
|
28
|
-
it("rejects non-https SKILL.md URLs", () => {
|
|
29
|
-
const url = "http://example.com/SKILL.md";
|
|
30
|
-
assert.throws(() => parseSkillUrl(url));
|
|
31
|
-
});
|
|
32
|
-
it("derives slug from repo name with path prefix", () => {
|
|
33
|
-
const url = "https://github.com/owner/repo/blob/main/skills/ai-chat/SKILL.md";
|
|
34
|
-
const result = parseSkillUrl(url);
|
|
35
|
-
assert.equal(result.type, "file");
|
|
36
|
-
assert.ok(result.slug.includes("repo"));
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
describe("skill store functions", () => {
|
|
40
|
-
it("exports updateSkill function", () => {
|
|
41
|
-
assert.ok(typeof updateSkill === "function");
|
|
42
|
-
});
|
|
43
|
-
it("exports deleteSkill function", () => {
|
|
44
|
-
assert.ok(typeof deleteSkill === "function");
|
|
45
|
-
});
|
|
46
|
-
it("exports removeSkill function", () => {
|
|
47
|
-
assert.ok(typeof removeSkill === "function");
|
|
48
|
-
});
|
|
49
|
-
it("deleteSkill is callable (alias)", () => {
|
|
50
|
-
// Just verify these are functions and importable
|
|
51
|
-
assert.ok(deleteSkill !== undefined);
|
|
52
|
-
assert.ok(removeSkill !== undefined);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
//# sourceMappingURL=skills.test.js.map
|