@herdctl/core 4.1.0 → 4.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/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +3 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/fleet-manager/__tests__/coverage.test.js +8 -0
- package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/discord-manager.test.js +2 -2
- package/dist/fleet-manager/__tests__/discord-manager.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/slack-manager.test.d.ts +2 -2
- package/dist/fleet-manager/__tests__/slack-manager.test.js +125 -164
- package/dist/fleet-manager/__tests__/slack-manager.test.js.map +1 -1
- package/dist/fleet-manager/discord-manager.js +7 -7
- package/dist/fleet-manager/discord-manager.js.map +1 -1
- package/dist/fleet-manager/event-types.d.ts +10 -4
- package/dist/fleet-manager/event-types.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.js +5 -9
- package/dist/fleet-manager/fleet-manager.js.map +1 -1
- package/dist/fleet-manager/job-control.js +1 -1
- package/dist/fleet-manager/job-control.js.map +1 -1
- package/dist/fleet-manager/job-queue.d.ts.map +1 -1
- package/dist/fleet-manager/job-queue.js +2 -6
- package/dist/fleet-manager/job-queue.js.map +1 -1
- package/dist/fleet-manager/slack-manager.d.ts +72 -29
- package/dist/fleet-manager/slack-manager.d.ts.map +1 -1
- package/dist/fleet-manager/slack-manager.js +153 -147
- package/dist/fleet-manager/slack-manager.js.map +1 -1
- package/dist/fleet-manager/status-queries.js +1 -1
- package/dist/fleet-manager/status-queries.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/runner/job-executor.d.ts +1 -0
- package/dist/runner/job-executor.d.ts.map +1 -1
- package/dist/runner/job-executor.js +4 -7
- package/dist/runner/job-executor.js.map +1 -1
- package/dist/runner/runtime/cli-runtime.d.ts.map +1 -1
- package/dist/runner/runtime/cli-runtime.js +29 -27
- package/dist/runner/runtime/cli-runtime.js.map +1 -1
- package/dist/runner/runtime/cli-session-watcher.d.ts.map +1 -1
- package/dist/runner/runtime/cli-session-watcher.js +20 -18
- package/dist/runner/runtime/cli-session-watcher.js.map +1 -1
- package/dist/runner/runtime/container-manager.d.ts.map +1 -1
- package/dist/runner/runtime/container-manager.js +7 -5
- package/dist/runner/runtime/container-manager.js.map +1 -1
- package/dist/runner/runtime/container-runner.d.ts.map +1 -1
- package/dist/runner/runtime/container-runner.js +12 -10
- package/dist/runner/runtime/container-runner.js.map +1 -1
- package/dist/scheduler/__tests__/scheduler.test.js +2 -2
- package/dist/scheduler/__tests__/scheduler.test.js.map +1 -1
- package/dist/scheduler/schedule-runner.d.ts.map +1 -1
- package/dist/scheduler/schedule-runner.js +2 -6
- package/dist/scheduler/schedule-runner.js.map +1 -1
- package/dist/scheduler/schedule-state.d.ts.map +1 -1
- package/dist/scheduler/schedule-state.js +2 -3
- package/dist/scheduler/schedule-state.js.map +1 -1
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/scheduler/scheduler.js +3 -7
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/state/fleet-state.d.ts.map +1 -1
- package/dist/state/fleet-state.js +2 -3
- package/dist/state/fleet-state.js.map +1 -1
- package/dist/state/schemas/job-output.d.ts +2 -2
- package/dist/utils/__tests__/logger.test.d.ts +2 -0
- package/dist/utils/__tests__/logger.test.d.ts.map +1 -0
- package/dist/utils/__tests__/logger.test.js +360 -0
- package/dist/utils/__tests__/logger.test.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +72 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +117 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for SlackManager
|
|
3
3
|
*
|
|
4
|
-
* Tests the SlackManager class which manages
|
|
5
|
-
*
|
|
4
|
+
* Tests the SlackManager class which manages Slack connectors for agents
|
|
5
|
+
* with chat.slack configured (one connector per agent).
|
|
6
6
|
*
|
|
7
7
|
* Since @herdctl/slack is not a dependency of @herdctl/core, we mock the
|
|
8
8
|
* dynamic import to test the full initialization and lifecycle paths.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for SlackManager
|
|
3
3
|
*
|
|
4
|
-
* Tests the SlackManager class which manages
|
|
5
|
-
*
|
|
4
|
+
* Tests the SlackManager class which manages Slack connectors for agents
|
|
5
|
+
* with chat.slack configured (one connector per agent).
|
|
6
6
|
*
|
|
7
7
|
* Since @herdctl/slack is not a dependency of @herdctl/core, we mock the
|
|
8
8
|
* dynamic import to test the full initialization and lifecycle paths.
|
|
@@ -76,8 +76,10 @@ function createConfigWithAgents(...agents) {
|
|
|
76
76
|
// ---------------------------------------------------------------------------
|
|
77
77
|
// Mock SlackConnector and SessionManager
|
|
78
78
|
// ---------------------------------------------------------------------------
|
|
79
|
-
function createMockConnector() {
|
|
79
|
+
function createMockConnector(agentName, sessionManager) {
|
|
80
80
|
const connector = new EventEmitter();
|
|
81
|
+
connector.agentName = agentName;
|
|
82
|
+
connector.sessionManager = sessionManager;
|
|
81
83
|
connector.connect = vi.fn().mockResolvedValue(undefined);
|
|
82
84
|
connector.disconnect = vi.fn().mockResolvedValue(undefined);
|
|
83
85
|
connector.isConnected = vi.fn().mockReturnValue(false);
|
|
@@ -90,6 +92,7 @@ function createMockConnector() {
|
|
|
90
92
|
botUser: null,
|
|
91
93
|
messageStats: { received: 0, sent: 0, ignored: 0 },
|
|
92
94
|
});
|
|
95
|
+
connector.uploadFile = vi.fn().mockResolvedValue({ fileId: "file-123" });
|
|
93
96
|
return connector;
|
|
94
97
|
}
|
|
95
98
|
function createMockSessionManager(agentName) {
|
|
@@ -110,6 +113,11 @@ function createMockSessionManager(agentName) {
|
|
|
110
113
|
describe("SlackManager (no @herdctl/slack)", () => {
|
|
111
114
|
beforeEach(() => {
|
|
112
115
|
vi.clearAllMocks();
|
|
116
|
+
vi.resetModules();
|
|
117
|
+
// Mock @herdctl/slack to simulate it not being installed
|
|
118
|
+
vi.doMock("@herdctl/slack", () => {
|
|
119
|
+
throw new Error("Cannot find package '@herdctl/slack'");
|
|
120
|
+
});
|
|
113
121
|
});
|
|
114
122
|
afterEach(() => {
|
|
115
123
|
vi.restoreAllMocks();
|
|
@@ -141,7 +149,7 @@ describe("SlackManager (no @herdctl/slack)", () => {
|
|
|
141
149
|
const ctx = createMockContext(config);
|
|
142
150
|
const manager = new SlackManager(ctx);
|
|
143
151
|
await manager.initialize();
|
|
144
|
-
expect(mockLogger.debug).toHaveBeenCalledWith("@herdctl/slack not installed, skipping Slack
|
|
152
|
+
expect(mockLogger.debug).toHaveBeenCalledWith("@herdctl/slack not installed, skipping Slack connectors");
|
|
145
153
|
});
|
|
146
154
|
it("skips when @herdctl/slack is not installed (with slack agents)", async () => {
|
|
147
155
|
const SlackManager = await getSlackManager();
|
|
@@ -149,7 +157,7 @@ describe("SlackManager (no @herdctl/slack)", () => {
|
|
|
149
157
|
const ctx = createMockContext(config);
|
|
150
158
|
const manager = new SlackManager(ctx);
|
|
151
159
|
await manager.initialize();
|
|
152
|
-
expect(mockLogger.debug).toHaveBeenCalledWith("@herdctl/slack not installed, skipping Slack
|
|
160
|
+
expect(mockLogger.debug).toHaveBeenCalledWith("@herdctl/slack not installed, skipping Slack connectors");
|
|
153
161
|
});
|
|
154
162
|
it("allows retry when no config (initialized not set)", async () => {
|
|
155
163
|
const SlackManager = await getSlackManager();
|
|
@@ -168,7 +176,7 @@ describe("SlackManager (no @herdctl/slack)", () => {
|
|
|
168
176
|
const manager = new SlackManager(ctx);
|
|
169
177
|
await manager.initialize();
|
|
170
178
|
await manager.start();
|
|
171
|
-
expect(mockLogger.debug).toHaveBeenCalledWith("No Slack
|
|
179
|
+
expect(mockLogger.debug).toHaveBeenCalledWith("No Slack connectors to start");
|
|
172
180
|
});
|
|
173
181
|
});
|
|
174
182
|
describe("stop", () => {
|
|
@@ -178,7 +186,7 @@ describe("SlackManager (no @herdctl/slack)", () => {
|
|
|
178
186
|
const manager = new SlackManager(ctx);
|
|
179
187
|
await manager.initialize();
|
|
180
188
|
await manager.stop();
|
|
181
|
-
expect(mockLogger.debug).toHaveBeenCalledWith("No Slack
|
|
189
|
+
expect(mockLogger.debug).toHaveBeenCalledWith("No Slack connectors to stop");
|
|
182
190
|
});
|
|
183
191
|
});
|
|
184
192
|
describe("hasAgent", () => {
|
|
@@ -190,35 +198,35 @@ describe("SlackManager (no @herdctl/slack)", () => {
|
|
|
190
198
|
});
|
|
191
199
|
});
|
|
192
200
|
describe("getState", () => {
|
|
193
|
-
it("returns null when no connector", async () => {
|
|
201
|
+
it("returns null when no connector for agent", async () => {
|
|
194
202
|
const SlackManager = await getSlackManager();
|
|
195
203
|
const ctx = createMockContext(null);
|
|
196
204
|
const manager = new SlackManager(ctx);
|
|
197
|
-
expect(manager.getState()).toBeNull();
|
|
205
|
+
expect(manager.getState("test-agent")).toBeNull();
|
|
198
206
|
});
|
|
199
207
|
});
|
|
200
|
-
describe("
|
|
201
|
-
it("returns
|
|
208
|
+
describe("getConnectedCount", () => {
|
|
209
|
+
it("returns 0 when no connectors", async () => {
|
|
202
210
|
const SlackManager = await getSlackManager();
|
|
203
211
|
const ctx = createMockContext(null);
|
|
204
212
|
const manager = new SlackManager(ctx);
|
|
205
|
-
expect(manager.
|
|
213
|
+
expect(manager.getConnectedCount()).toBe(0);
|
|
206
214
|
});
|
|
207
215
|
});
|
|
208
216
|
describe("getConnector", () => {
|
|
209
|
-
it("returns
|
|
217
|
+
it("returns undefined when no connector for agent", async () => {
|
|
210
218
|
const SlackManager = await getSlackManager();
|
|
211
219
|
const ctx = createMockContext(null);
|
|
212
220
|
const manager = new SlackManager(ctx);
|
|
213
|
-
expect(manager.getConnector()).
|
|
221
|
+
expect(manager.getConnector("test-agent")).toBeUndefined();
|
|
214
222
|
});
|
|
215
223
|
});
|
|
216
|
-
describe("
|
|
217
|
-
it("returns empty
|
|
224
|
+
describe("getConnectorNames", () => {
|
|
225
|
+
it("returns empty array when not initialized", async () => {
|
|
218
226
|
const SlackManager = await getSlackManager();
|
|
219
227
|
const ctx = createMockContext(null);
|
|
220
228
|
const manager = new SlackManager(ctx);
|
|
221
|
-
expect(manager.
|
|
229
|
+
expect(manager.getConnectorNames()).toEqual([]);
|
|
222
230
|
});
|
|
223
231
|
});
|
|
224
232
|
describe("splitResponse", () => {
|
|
@@ -293,7 +301,8 @@ describe("SlackManager (no @herdctl/slack)", () => {
|
|
|
293
301
|
// Tests – With Mocked @herdctl/slack (full initialization paths)
|
|
294
302
|
// ---------------------------------------------------------------------------
|
|
295
303
|
describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
296
|
-
let
|
|
304
|
+
let mockConnectors;
|
|
305
|
+
let mockSessionManagers;
|
|
297
306
|
let MockSlackConnector;
|
|
298
307
|
let MockSessionManager;
|
|
299
308
|
let originalEnv;
|
|
@@ -304,14 +313,19 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
304
313
|
// Set required env vars
|
|
305
314
|
process.env.SLACK_BOT_TOKEN = "xoxb-test-bot-token";
|
|
306
315
|
process.env.SLACK_APP_TOKEN = "xapp-test-app-token";
|
|
307
|
-
// Create mock implementations
|
|
308
|
-
|
|
316
|
+
// Create mock implementations - per-agent connectors
|
|
317
|
+
mockConnectors = new Map();
|
|
318
|
+
mockSessionManagers = new Map();
|
|
309
319
|
// Must use function expressions (not arrows) so they work with `new`
|
|
310
|
-
MockSlackConnector = vi.fn().mockImplementation(function () {
|
|
311
|
-
return mockConnector;
|
|
312
|
-
});
|
|
313
320
|
MockSessionManager = vi.fn().mockImplementation(function (opts) {
|
|
314
|
-
|
|
321
|
+
const sessionMgr = createMockSessionManager(opts.agentName);
|
|
322
|
+
mockSessionManagers.set(opts.agentName, sessionMgr);
|
|
323
|
+
return sessionMgr;
|
|
324
|
+
});
|
|
325
|
+
MockSlackConnector = vi.fn().mockImplementation(function (opts) {
|
|
326
|
+
const connector = createMockConnector(opts.agentName, opts.sessionManager);
|
|
327
|
+
mockConnectors.set(opts.agentName, connector);
|
|
328
|
+
return connector;
|
|
315
329
|
});
|
|
316
330
|
});
|
|
317
331
|
afterEach(() => {
|
|
@@ -342,14 +356,15 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
342
356
|
sessionExpiryHours: 24,
|
|
343
357
|
}));
|
|
344
358
|
expect(MockSlackConnector).toHaveBeenCalledWith(expect.objectContaining({
|
|
359
|
+
agentName: "agent1",
|
|
345
360
|
botToken: "xoxb-test-bot-token",
|
|
346
361
|
appToken: "xapp-test-app-token",
|
|
347
|
-
|
|
362
|
+
channels: [{ id: "C0123456789", mode: "mention" }],
|
|
348
363
|
}));
|
|
349
364
|
expect(manager.hasAgent("agent1")).toBe(true);
|
|
350
|
-
expect(manager.getConnector()).toBe(
|
|
365
|
+
expect(manager.getConnector("agent1")).toBe(mockConnectors.get("agent1"));
|
|
351
366
|
});
|
|
352
|
-
it("
|
|
367
|
+
it("creates separate connectors for multiple agents", async () => {
|
|
353
368
|
const SlackManager = await getSlackManagerWithMock();
|
|
354
369
|
const config = createConfigWithAgents(createSlackAgent("agent1", {
|
|
355
370
|
...defaultSlackConfig,
|
|
@@ -361,27 +376,10 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
361
376
|
const ctx = createMockContext(config);
|
|
362
377
|
const manager = new SlackManager(ctx);
|
|
363
378
|
await manager.initialize();
|
|
364
|
-
|
|
365
|
-
expect(
|
|
366
|
-
expect(
|
|
367
|
-
expect(
|
|
368
|
-
expect(channelMap.size).toBe(3);
|
|
369
|
-
});
|
|
370
|
-
it("warns about overlapping channel mappings", async () => {
|
|
371
|
-
const SlackManager = await getSlackManagerWithMock();
|
|
372
|
-
const config = createConfigWithAgents(createSlackAgent("agent1", {
|
|
373
|
-
...defaultSlackConfig,
|
|
374
|
-
channels: [{ id: "C001", mode: "mention", context_messages: 10 }],
|
|
375
|
-
}), createSlackAgent("agent2", {
|
|
376
|
-
...defaultSlackConfig,
|
|
377
|
-
channels: [{ id: "C001", mode: "mention", context_messages: 10 }], // Same channel as agent1
|
|
378
|
-
}));
|
|
379
|
-
const ctx = createMockContext(config);
|
|
380
|
-
const manager = new SlackManager(ctx);
|
|
381
|
-
await manager.initialize();
|
|
382
|
-
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining("Channel C001 is already mapped"));
|
|
383
|
-
// Second agent wins
|
|
384
|
-
expect(manager.getChannelAgentMap().get("C001")).toBe("agent2");
|
|
379
|
+
expect(manager.getConnectorNames()).toEqual(["agent1", "agent2"]);
|
|
380
|
+
expect(MockSlackConnector).toHaveBeenCalledTimes(2);
|
|
381
|
+
expect(manager.getConnector("agent1")).toBeDefined();
|
|
382
|
+
expect(manager.getConnector("agent2")).toBeDefined();
|
|
385
383
|
});
|
|
386
384
|
it("skips when no agents have Slack configured", async () => {
|
|
387
385
|
const SlackManager = await getSlackManagerWithMock();
|
|
@@ -392,7 +390,7 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
392
390
|
expect(mockLogger.debug).toHaveBeenCalledWith("No agents with Slack configured");
|
|
393
391
|
expect(MockSlackConnector).not.toHaveBeenCalled();
|
|
394
392
|
});
|
|
395
|
-
it("warns and skips when bot token env var is missing", async () => {
|
|
393
|
+
it("warns and skips agent when bot token env var is missing", async () => {
|
|
396
394
|
delete process.env.SLACK_BOT_TOKEN;
|
|
397
395
|
const SlackManager = await getSlackManagerWithMock();
|
|
398
396
|
const config = createConfigWithAgents(createSlackAgent("agent1", defaultSlackConfig));
|
|
@@ -402,7 +400,7 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
402
400
|
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining("Slack bot token not found"));
|
|
403
401
|
expect(MockSlackConnector).not.toHaveBeenCalled();
|
|
404
402
|
});
|
|
405
|
-
it("warns and skips when app token env var is missing", async () => {
|
|
403
|
+
it("warns and skips agent when app token env var is missing", async () => {
|
|
406
404
|
delete process.env.SLACK_APP_TOKEN;
|
|
407
405
|
const SlackManager = await getSlackManagerWithMock();
|
|
408
406
|
const config = createConfigWithAgents(createSlackAgent("agent1", defaultSlackConfig));
|
|
@@ -438,10 +436,10 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
438
436
|
const ctx = createMockContext(config);
|
|
439
437
|
const manager = new SlackManager(ctx);
|
|
440
438
|
await manager.initialize();
|
|
441
|
-
expect(mockLogger.
|
|
439
|
+
expect(mockLogger.debug).toHaveBeenCalledWith(expect.stringContaining("Slack manager initialized with 1 connector(s)"));
|
|
442
440
|
});
|
|
443
|
-
it("handles connector creation failure", async () => {
|
|
444
|
-
MockSlackConnector.
|
|
441
|
+
it("handles connector creation failure for one agent", async () => {
|
|
442
|
+
MockSlackConnector.mockImplementationOnce(() => {
|
|
445
443
|
throw new Error("Failed to create Bolt app");
|
|
446
444
|
});
|
|
447
445
|
const SlackManager = await getSlackManagerWithMock();
|
|
@@ -449,10 +447,10 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
449
447
|
const ctx = createMockContext(config);
|
|
450
448
|
const manager = new SlackManager(ctx);
|
|
451
449
|
await manager.initialize();
|
|
452
|
-
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to create Slack connector"));
|
|
453
|
-
expect(manager.getConnector()).
|
|
450
|
+
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to create Slack connector for agent 'agent1'"));
|
|
451
|
+
expect(manager.getConnector("agent1")).toBeUndefined();
|
|
454
452
|
});
|
|
455
|
-
it("creates
|
|
453
|
+
it("creates connectors for multiple agents", async () => {
|
|
456
454
|
const SlackManager = await getSlackManagerWithMock();
|
|
457
455
|
const config = createConfigWithAgents(createSlackAgent("agent1", {
|
|
458
456
|
...defaultSlackConfig,
|
|
@@ -465,6 +463,7 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
465
463
|
const manager = new SlackManager(ctx);
|
|
466
464
|
await manager.initialize();
|
|
467
465
|
expect(MockSessionManager).toHaveBeenCalledTimes(2);
|
|
466
|
+
expect(MockSlackConnector).toHaveBeenCalledTimes(2);
|
|
468
467
|
expect(manager.hasAgent("agent1")).toBe(true);
|
|
469
468
|
expect(manager.hasAgent("agent2")).toBe(true);
|
|
470
469
|
expect(manager.hasAgent("agent3")).toBe(false);
|
|
@@ -478,90 +477,92 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
478
477
|
const manager = new SlackManager(ctx);
|
|
479
478
|
await manager.initialize();
|
|
480
479
|
await manager.start();
|
|
481
|
-
|
|
482
|
-
expect(
|
|
480
|
+
const connector = mockConnectors.get("agent1");
|
|
481
|
+
expect(connector?.connect).toHaveBeenCalledTimes(1);
|
|
482
|
+
expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining("Slack connectors started"));
|
|
483
483
|
});
|
|
484
|
-
it("handles connection failure", async () => {
|
|
485
|
-
mockConnector.connect.mockRejectedValue(new Error("Connection refused"));
|
|
484
|
+
it("handles connection failure for one agent", async () => {
|
|
486
485
|
const SlackManager = await getSlackManagerWithMock();
|
|
487
486
|
const config = createConfigWithAgents(createSlackAgent("agent1", defaultSlackConfig));
|
|
488
487
|
const ctx = createMockContext(config);
|
|
489
488
|
const manager = new SlackManager(ctx);
|
|
490
489
|
await manager.initialize();
|
|
490
|
+
// Make the connector fail to connect
|
|
491
|
+
const connector = mockConnectors.get("agent1");
|
|
492
|
+
connector?.connect.mockRejectedValue(new Error("Connection refused"));
|
|
491
493
|
await manager.start();
|
|
492
|
-
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to connect Slack"));
|
|
494
|
+
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to connect Slack for agent 'agent1'"));
|
|
493
495
|
});
|
|
494
|
-
it("logs debug message when no
|
|
496
|
+
it("logs debug message when no connectors to start", async () => {
|
|
495
497
|
const SlackManager = await getSlackManagerWithMock();
|
|
496
498
|
const ctx = createMockContext(null);
|
|
497
499
|
const manager = new SlackManager(ctx);
|
|
498
500
|
await manager.start();
|
|
499
|
-
expect(mockLogger.debug).toHaveBeenCalledWith("No Slack
|
|
501
|
+
expect(mockLogger.debug).toHaveBeenCalledWith("No Slack connectors to start");
|
|
500
502
|
});
|
|
501
503
|
});
|
|
502
504
|
describe("stop", () => {
|
|
503
|
-
it("disconnects
|
|
505
|
+
it("disconnects all connectors", async () => {
|
|
504
506
|
const SlackManager = await getSlackManagerWithMock();
|
|
505
507
|
const config = createConfigWithAgents(createSlackAgent("agent1", defaultSlackConfig));
|
|
506
508
|
const ctx = createMockContext(config);
|
|
507
509
|
const manager = new SlackManager(ctx);
|
|
508
510
|
await manager.initialize();
|
|
509
511
|
await manager.stop();
|
|
510
|
-
|
|
511
|
-
expect(
|
|
512
|
+
const connector = mockConnectors.get("agent1");
|
|
513
|
+
expect(connector?.disconnect).toHaveBeenCalledTimes(1);
|
|
514
|
+
expect(mockLogger.debug).toHaveBeenCalledWith("All Slack connectors stopped");
|
|
512
515
|
});
|
|
513
|
-
it("handles disconnect failure", async () => {
|
|
514
|
-
mockConnector.disconnect.mockRejectedValue(new Error("Disconnect timeout"));
|
|
516
|
+
it("handles disconnect failure for one agent", async () => {
|
|
515
517
|
const SlackManager = await getSlackManagerWithMock();
|
|
516
518
|
const config = createConfigWithAgents(createSlackAgent("agent1", defaultSlackConfig));
|
|
517
519
|
const ctx = createMockContext(config);
|
|
518
520
|
const manager = new SlackManager(ctx);
|
|
519
521
|
await manager.initialize();
|
|
522
|
+
const connector = mockConnectors.get("agent1");
|
|
523
|
+
connector?.disconnect.mockRejectedValue(new Error("Disconnect timeout"));
|
|
520
524
|
await manager.stop();
|
|
521
|
-
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Error disconnecting Slack"));
|
|
525
|
+
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Error disconnecting Slack for agent 'agent1'"));
|
|
522
526
|
});
|
|
523
527
|
it("logs active session counts before stopping", async () => {
|
|
524
|
-
const mockSessionMgr = createMockSessionManager("agent1");
|
|
525
|
-
mockSessionMgr.getActiveSessionCount.mockResolvedValue(3);
|
|
526
|
-
MockSessionManager.mockImplementation(function () {
|
|
527
|
-
return mockSessionMgr;
|
|
528
|
-
});
|
|
529
528
|
const SlackManager = await getSlackManagerWithMock();
|
|
530
529
|
const config = createConfigWithAgents(createSlackAgent("agent1", defaultSlackConfig));
|
|
531
530
|
const ctx = createMockContext(config);
|
|
532
531
|
const manager = new SlackManager(ctx);
|
|
533
532
|
await manager.initialize();
|
|
533
|
+
// Update the session manager to return 3 active sessions
|
|
534
|
+
const connector = mockConnectors.get("agent1");
|
|
535
|
+
connector?.sessionManager.getActiveSessionCount.mockResolvedValue(3);
|
|
534
536
|
await manager.stop();
|
|
535
|
-
expect(mockLogger.
|
|
537
|
+
expect(mockLogger.debug).toHaveBeenCalledWith(expect.stringContaining("Preserving 3 active Slack session(s)"));
|
|
536
538
|
});
|
|
537
539
|
it("handles session count query failure gracefully", async () => {
|
|
538
|
-
const mockSessionMgr = createMockSessionManager("agent1");
|
|
539
|
-
mockSessionMgr.getActiveSessionCount.mockRejectedValue(new Error("File read error"));
|
|
540
|
-
MockSessionManager.mockImplementation(function () {
|
|
541
|
-
return mockSessionMgr;
|
|
542
|
-
});
|
|
543
540
|
const SlackManager = await getSlackManagerWithMock();
|
|
544
541
|
const config = createConfigWithAgents(createSlackAgent("agent1", defaultSlackConfig));
|
|
545
542
|
const ctx = createMockContext(config);
|
|
546
543
|
const manager = new SlackManager(ctx);
|
|
547
544
|
await manager.initialize();
|
|
545
|
+
// Make the session manager fail
|
|
546
|
+
const connector = mockConnectors.get("agent1");
|
|
547
|
+
connector?.sessionManager.getActiveSessionCount.mockRejectedValue(new Error("File read error"));
|
|
548
548
|
await manager.stop();
|
|
549
549
|
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining("Failed to get Slack session count"));
|
|
550
550
|
});
|
|
551
551
|
});
|
|
552
|
-
describe("
|
|
553
|
-
it("
|
|
554
|
-
mockConnector.isConnected.mockReturnValue(true);
|
|
552
|
+
describe("getConnectedCount", () => {
|
|
553
|
+
it("returns count of connected connectors", async () => {
|
|
555
554
|
const SlackManager = await getSlackManagerWithMock();
|
|
556
555
|
const config = createConfigWithAgents(createSlackAgent("agent1", defaultSlackConfig));
|
|
557
556
|
const ctx = createMockContext(config);
|
|
558
557
|
const manager = new SlackManager(ctx);
|
|
559
558
|
await manager.initialize();
|
|
560
|
-
|
|
559
|
+
const connector = mockConnectors.get("agent1");
|
|
560
|
+
connector?.isConnected.mockReturnValue(true);
|
|
561
|
+
expect(manager.getConnectedCount()).toBe(1);
|
|
561
562
|
});
|
|
562
563
|
});
|
|
563
564
|
describe("getState", () => {
|
|
564
|
-
it("delegates to connector.getState()", async () => {
|
|
565
|
+
it("delegates to connector.getState() for specific agent", async () => {
|
|
565
566
|
const state = {
|
|
566
567
|
status: "connected",
|
|
567
568
|
connectedAt: "2026-01-01T00:00:00Z",
|
|
@@ -571,13 +572,14 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
571
572
|
botUser: { id: "U123", username: "testbot" },
|
|
572
573
|
messageStats: { received: 5, sent: 3, ignored: 1 },
|
|
573
574
|
};
|
|
574
|
-
mockConnector.getState.mockReturnValue(state);
|
|
575
575
|
const SlackManager = await getSlackManagerWithMock();
|
|
576
576
|
const config = createConfigWithAgents(createSlackAgent("agent1", defaultSlackConfig));
|
|
577
577
|
const ctx = createMockContext(config);
|
|
578
578
|
const manager = new SlackManager(ctx);
|
|
579
579
|
await manager.initialize();
|
|
580
|
-
|
|
580
|
+
const connector = mockConnectors.get("agent1");
|
|
581
|
+
connector?.getState.mockReturnValue(state);
|
|
582
|
+
expect(manager.getState("agent1")).toBe(state);
|
|
581
583
|
});
|
|
582
584
|
});
|
|
583
585
|
describe("message handling (via connector events)", () => {
|
|
@@ -589,43 +591,18 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
589
591
|
const manager = new SlackManager(ctx);
|
|
590
592
|
await manager.initialize();
|
|
591
593
|
await manager.start();
|
|
592
|
-
// Simulate error from connector (
|
|
593
|
-
|
|
594
|
+
// Simulate error from connector (with agentName)
|
|
595
|
+
const connector = mockConnectors.get("agent1");
|
|
596
|
+
connector?.emit("error", {
|
|
597
|
+
agentName: "agent1",
|
|
594
598
|
error: new Error("Socket closed"),
|
|
595
599
|
});
|
|
596
|
-
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Slack connector error for agent '
|
|
600
|
+
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Slack connector error for agent 'agent1'"));
|
|
597
601
|
expect(emitter.emit).toHaveBeenCalledWith("slack:error", expect.objectContaining({
|
|
598
|
-
agentName: "
|
|
602
|
+
agentName: "agent1",
|
|
599
603
|
error: "Socket closed",
|
|
600
604
|
}));
|
|
601
605
|
});
|
|
602
|
-
it("handles message for unknown agent", async () => {
|
|
603
|
-
const SlackManager = await getSlackManagerWithMock();
|
|
604
|
-
const emitter = createMockEmitter();
|
|
605
|
-
const config = createConfigWithAgents(createSlackAgent("agent1", defaultSlackConfig));
|
|
606
|
-
const ctx = createMockContext(config, emitter);
|
|
607
|
-
const manager = new SlackManager(ctx);
|
|
608
|
-
await manager.initialize();
|
|
609
|
-
await manager.start();
|
|
610
|
-
const replyFn = vi.fn().mockResolvedValue(undefined);
|
|
611
|
-
// Simulate message for an agent not in config
|
|
612
|
-
mockConnector.emit("message", {
|
|
613
|
-
agentName: "unknown-agent",
|
|
614
|
-
prompt: "Hello there",
|
|
615
|
-
metadata: {
|
|
616
|
-
channelId: "C0123456789",
|
|
617
|
-
messageTs: "1707930001.000000",
|
|
618
|
-
userId: "U0123456789",
|
|
619
|
-
wasMentioned: true,
|
|
620
|
-
},
|
|
621
|
-
reply: replyFn,
|
|
622
|
-
startProcessingIndicator: () => () => { },
|
|
623
|
-
});
|
|
624
|
-
// Give time for the async handler to run
|
|
625
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
626
|
-
expect(mockLogger.error).toHaveBeenCalledWith("Agent 'unknown-agent' not found in configuration");
|
|
627
|
-
expect(replyFn).toHaveBeenCalledWith(expect.stringContaining("not properly configured"));
|
|
628
|
-
});
|
|
629
606
|
it("handles message with successful trigger", async () => {
|
|
630
607
|
const SlackManager = await getSlackManagerWithMock();
|
|
631
608
|
const emitter = createMockEmitter();
|
|
@@ -643,7 +620,8 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
643
620
|
await manager.start();
|
|
644
621
|
const replyFn = vi.fn().mockResolvedValue(undefined);
|
|
645
622
|
const stopIndicator = vi.fn();
|
|
646
|
-
|
|
623
|
+
const connector = mockConnectors.get("agent1");
|
|
624
|
+
connector?.emit("message", {
|
|
647
625
|
agentName: "agent1",
|
|
648
626
|
prompt: "Help me with coding",
|
|
649
627
|
metadata: {
|
|
@@ -680,7 +658,8 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
680
658
|
await manager.initialize();
|
|
681
659
|
await manager.start();
|
|
682
660
|
const replyFn = vi.fn().mockResolvedValue(undefined);
|
|
683
|
-
|
|
661
|
+
const connector = mockConnectors.get("agent1");
|
|
662
|
+
connector?.emit("message", {
|
|
684
663
|
agentName: "agent1",
|
|
685
664
|
prompt: "Do something",
|
|
686
665
|
metadata: {
|
|
@@ -712,7 +691,8 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
712
691
|
await manager.initialize();
|
|
713
692
|
await manager.start();
|
|
714
693
|
const replyFn = vi.fn().mockResolvedValue(undefined);
|
|
715
|
-
|
|
694
|
+
const connector = mockConnectors.get("agent1");
|
|
695
|
+
connector?.emit("message", {
|
|
716
696
|
agentName: "agent1",
|
|
717
697
|
prompt: "Do something",
|
|
718
698
|
metadata: {
|
|
@@ -738,7 +718,8 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
738
718
|
await manager.initialize();
|
|
739
719
|
await manager.start();
|
|
740
720
|
const replyFn = vi.fn().mockResolvedValue(undefined);
|
|
741
|
-
|
|
721
|
+
const connector = mockConnectors.get("agent1");
|
|
722
|
+
connector?.emit("message", {
|
|
742
723
|
agentName: "agent1",
|
|
743
724
|
prompt: "Do something",
|
|
744
725
|
metadata: {
|
|
@@ -759,14 +740,6 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
759
740
|
}));
|
|
760
741
|
});
|
|
761
742
|
it("resumes existing session when one exists", async () => {
|
|
762
|
-
const mockSessionMgr = createMockSessionManager("agent1");
|
|
763
|
-
mockSessionMgr.getSession.mockResolvedValue({
|
|
764
|
-
sessionId: "existing-session-456",
|
|
765
|
-
lastMessageAt: "2026-02-15T10:00:00Z",
|
|
766
|
-
});
|
|
767
|
-
MockSessionManager.mockImplementation(function () {
|
|
768
|
-
return mockSessionMgr;
|
|
769
|
-
});
|
|
770
743
|
const SlackManager = await getSlackManagerWithMock();
|
|
771
744
|
const emitter = createMockEmitter();
|
|
772
745
|
const config = createConfigWithAgents(createSlackAgent("agent1", defaultSlackConfig));
|
|
@@ -779,9 +752,15 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
779
752
|
const ctx = createMockContext(config, emitter);
|
|
780
753
|
const manager = new SlackManager(ctx);
|
|
781
754
|
await manager.initialize();
|
|
755
|
+
// Set up the session manager to return an existing session
|
|
756
|
+
const connector = mockConnectors.get("agent1");
|
|
757
|
+
connector?.sessionManager.getSession.mockResolvedValue({
|
|
758
|
+
sessionId: "existing-session-456",
|
|
759
|
+
lastMessageAt: "2026-02-15T10:00:00Z",
|
|
760
|
+
});
|
|
782
761
|
await manager.start();
|
|
783
762
|
const replyFn = vi.fn().mockResolvedValue(undefined);
|
|
784
|
-
|
|
763
|
+
connector?.emit("message", {
|
|
785
764
|
agentName: "agent1",
|
|
786
765
|
prompt: "Continue our conversation",
|
|
787
766
|
metadata: {
|
|
@@ -799,7 +778,7 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
799
778
|
resume: "existing-session-456",
|
|
800
779
|
}));
|
|
801
780
|
// Should store the new session
|
|
802
|
-
expect(
|
|
781
|
+
expect(connector?.sessionManager.setSession).toHaveBeenCalledWith("C0123456789", "new-session-789");
|
|
803
782
|
});
|
|
804
783
|
it("streams assistant messages via onMessage callback", async () => {
|
|
805
784
|
const SlackManager = await getSlackManagerWithMock();
|
|
@@ -820,7 +799,8 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
820
799
|
await manager.initialize();
|
|
821
800
|
await manager.start();
|
|
822
801
|
const replyFn = vi.fn().mockResolvedValue(undefined);
|
|
823
|
-
|
|
802
|
+
const connector = mockConnectors.get("agent1");
|
|
803
|
+
connector?.emit("message", {
|
|
824
804
|
agentName: "agent1",
|
|
825
805
|
prompt: "Say hello",
|
|
826
806
|
metadata: {
|
|
@@ -845,10 +825,12 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
845
825
|
await manager.initialize();
|
|
846
826
|
await manager.start();
|
|
847
827
|
// Simulate non-Error (string) error from connector
|
|
848
|
-
|
|
828
|
+
const connector = mockConnectors.get("agent1");
|
|
829
|
+
connector?.emit("error", {
|
|
830
|
+
agentName: "agent1",
|
|
849
831
|
error: "string error",
|
|
850
832
|
});
|
|
851
|
-
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Slack connector error for agent '
|
|
833
|
+
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Slack connector error for agent 'agent1': string error"));
|
|
852
834
|
});
|
|
853
835
|
it("handles reply failure during error handling gracefully", async () => {
|
|
854
836
|
const SlackManager = await getSlackManagerWithMock();
|
|
@@ -861,7 +843,8 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
861
843
|
await manager.initialize();
|
|
862
844
|
await manager.start();
|
|
863
845
|
const replyFn = vi.fn().mockRejectedValue(new Error("Reply failed too"));
|
|
864
|
-
|
|
846
|
+
const connector = mockConnectors.get("agent1");
|
|
847
|
+
connector?.emit("message", {
|
|
865
848
|
agentName: "agent1",
|
|
866
849
|
prompt: "Do something",
|
|
867
850
|
metadata: {
|
|
@@ -878,31 +861,6 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
878
861
|
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Slack message handling failed"));
|
|
879
862
|
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to send error reply"));
|
|
880
863
|
});
|
|
881
|
-
it("handles error from reply during agent-not-found", async () => {
|
|
882
|
-
const SlackManager = await getSlackManagerWithMock();
|
|
883
|
-
const emitter = createMockEmitter();
|
|
884
|
-
const config = createConfigWithAgents(createSlackAgent("agent1", defaultSlackConfig));
|
|
885
|
-
const ctx = createMockContext(config, emitter);
|
|
886
|
-
const manager = new SlackManager(ctx);
|
|
887
|
-
await manager.initialize();
|
|
888
|
-
await manager.start();
|
|
889
|
-
const replyFn = vi.fn().mockRejectedValue(new Error("Reply error"));
|
|
890
|
-
mockConnector.emit("message", {
|
|
891
|
-
agentName: "nonexistent",
|
|
892
|
-
prompt: "Hello",
|
|
893
|
-
metadata: {
|
|
894
|
-
channelId: "C0123456789",
|
|
895
|
-
messageTs: "1707930001.000000",
|
|
896
|
-
userId: "U0123456789",
|
|
897
|
-
wasMentioned: true,
|
|
898
|
-
},
|
|
899
|
-
reply: replyFn,
|
|
900
|
-
startProcessingIndicator: () => () => { },
|
|
901
|
-
});
|
|
902
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
903
|
-
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Agent 'nonexistent' not found"));
|
|
904
|
-
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("Failed to send error reply"));
|
|
905
|
-
});
|
|
906
864
|
it("extracts text from message.message.content array", async () => {
|
|
907
865
|
const SlackManager = await getSlackManagerWithMock();
|
|
908
866
|
const emitter = createMockEmitter();
|
|
@@ -930,7 +888,8 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
930
888
|
await manager.initialize();
|
|
931
889
|
await manager.start();
|
|
932
890
|
const replyFn = vi.fn().mockResolvedValue(undefined);
|
|
933
|
-
|
|
891
|
+
const connector = mockConnectors.get("agent1");
|
|
892
|
+
connector?.emit("message", {
|
|
934
893
|
agentName: "agent1",
|
|
935
894
|
prompt: "Test",
|
|
936
895
|
metadata: {
|
|
@@ -966,7 +925,8 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
966
925
|
await manager.initialize();
|
|
967
926
|
await manager.start();
|
|
968
927
|
const replyFn = vi.fn().mockResolvedValue(undefined);
|
|
969
|
-
|
|
928
|
+
const connector = mockConnectors.get("agent1");
|
|
929
|
+
connector?.emit("message", {
|
|
970
930
|
agentName: "agent1",
|
|
971
931
|
prompt: "Test",
|
|
972
932
|
metadata: {
|
|
@@ -1000,7 +960,8 @@ describe("SlackManager (mocked @herdctl/slack)", () => {
|
|
|
1000
960
|
await manager.initialize();
|
|
1001
961
|
await manager.start();
|
|
1002
962
|
const replyFn = vi.fn().mockResolvedValue(undefined);
|
|
1003
|
-
|
|
963
|
+
const connector = mockConnectors.get("agent1");
|
|
964
|
+
connector?.emit("message", {
|
|
1004
965
|
agentName: "agent1",
|
|
1005
966
|
prompt: "Test",
|
|
1006
967
|
metadata: {
|