@herdctl/core 0.0.1 → 0.0.2
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/__tests__/agent.test.js +31 -13
- package/dist/config/__tests__/agent.test.js.map +1 -1
- package/dist/config/__tests__/merge.test.js +9 -2
- package/dist/config/__tests__/merge.test.js.map +1 -1
- package/dist/config/__tests__/schema.test.js +350 -1
- package/dist/config/__tests__/schema.test.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +3 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +828 -24
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +118 -6
- package/dist/config/schema.js.map +1 -1
- package/dist/fleet-manager/__tests__/coverage.test.js +11 -332
- package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/errors.test.js +1 -49
- package/dist/fleet-manager/__tests__/errors.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/integration.test.js +109 -0
- package/dist/fleet-manager/__tests__/integration.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/reload.test.js +1 -1
- package/dist/fleet-manager/__tests__/reload.test.js.map +1 -1
- package/dist/fleet-manager/config-reload.d.ts +164 -0
- package/dist/fleet-manager/config-reload.d.ts.map +1 -0
- package/dist/fleet-manager/config-reload.js +445 -0
- package/dist/fleet-manager/config-reload.js.map +1 -0
- package/dist/fleet-manager/context.d.ts +76 -0
- package/dist/fleet-manager/context.d.ts.map +1 -0
- package/dist/fleet-manager/context.js +11 -0
- package/dist/fleet-manager/context.js.map +1 -0
- package/dist/fleet-manager/errors.d.ts +0 -25
- package/dist/fleet-manager/errors.d.ts.map +1 -1
- package/dist/fleet-manager/errors.js +0 -38
- package/dist/fleet-manager/errors.js.map +1 -1
- package/dist/fleet-manager/event-emitters.d.ts +123 -0
- package/dist/fleet-manager/event-emitters.d.ts.map +1 -0
- package/dist/fleet-manager/event-emitters.js +136 -0
- package/dist/fleet-manager/event-emitters.js.map +1 -0
- package/dist/fleet-manager/event-types.d.ts +0 -15
- package/dist/fleet-manager/event-types.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.d.ts +40 -653
- package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.js +95 -1720
- package/dist/fleet-manager/fleet-manager.js.map +1 -1
- package/dist/fleet-manager/index.d.ts +13 -2
- package/dist/fleet-manager/index.d.ts.map +1 -1
- package/dist/fleet-manager/index.js +19 -6
- package/dist/fleet-manager/index.js.map +1 -1
- package/dist/fleet-manager/job-control.d.ts +64 -0
- package/dist/fleet-manager/job-control.d.ts.map +1 -0
- package/dist/fleet-manager/job-control.js +296 -0
- package/dist/fleet-manager/job-control.js.map +1 -0
- package/dist/fleet-manager/log-streaming.d.ts +171 -0
- package/dist/fleet-manager/log-streaming.d.ts.map +1 -0
- package/dist/fleet-manager/log-streaming.js +503 -0
- package/dist/fleet-manager/log-streaming.js.map +1 -0
- package/dist/fleet-manager/schedule-executor.d.ts +63 -0
- package/dist/fleet-manager/schedule-executor.d.ts.map +1 -0
- package/dist/fleet-manager/schedule-executor.js +209 -0
- package/dist/fleet-manager/schedule-executor.js.map +1 -0
- package/dist/fleet-manager/schedule-management.d.ts +71 -0
- package/dist/fleet-manager/schedule-management.d.ts.map +1 -0
- package/dist/fleet-manager/schedule-management.js +171 -0
- package/dist/fleet-manager/schedule-management.js.map +1 -0
- package/dist/fleet-manager/status-queries.d.ts +105 -0
- package/dist/fleet-manager/status-queries.d.ts.map +1 -0
- package/dist/fleet-manager/status-queries.js +247 -0
- package/dist/fleet-manager/status-queries.js.map +1 -0
- package/dist/fleet-manager/types.d.ts +0 -39
- package/dist/fleet-manager/types.d.ts.map +1 -1
- package/dist/runner/__tests__/job-executor.test.js +206 -1
- package/dist/runner/__tests__/job-executor.test.js.map +1 -1
- package/dist/runner/job-executor.d.ts +9 -0
- package/dist/runner/job-executor.d.ts.map +1 -1
- package/dist/runner/job-executor.js +78 -4
- package/dist/runner/job-executor.js.map +1 -1
- package/dist/runner/types.d.ts +2 -0
- package/dist/runner/types.d.ts.map +1 -1
- package/dist/scheduler/__tests__/cron.test.d.ts +2 -0
- package/dist/scheduler/__tests__/cron.test.d.ts.map +1 -0
- package/dist/scheduler/__tests__/cron.test.js +867 -0
- package/dist/scheduler/__tests__/cron.test.js.map +1 -0
- package/dist/scheduler/__tests__/scheduler.test.js +164 -5
- package/dist/scheduler/__tests__/scheduler.test.js.map +1 -1
- package/dist/scheduler/cron.d.ts +126 -0
- package/dist/scheduler/cron.d.ts.map +1 -0
- package/dist/scheduler/cron.js +390 -0
- package/dist/scheduler/cron.js.map +1 -0
- package/dist/scheduler/errors.d.ts +81 -1
- package/dist/scheduler/errors.d.ts.map +1 -1
- package/dist/scheduler/errors.js +81 -6
- package/dist/scheduler/errors.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -0
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +2 -0
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/schedule-runner.d.ts +2 -2
- package/dist/scheduler/schedule-runner.d.ts.map +1 -1
- package/dist/scheduler/schedule-runner.js +20 -8
- package/dist/scheduler/schedule-runner.js.map +1 -1
- package/dist/scheduler/scheduler.d.ts +4 -4
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/scheduler/scheduler.js +86 -20
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/scheduler/types.d.ts +1 -1
- package/dist/scheduler/types.d.ts.map +1 -1
- package/dist/state/schemas/job-metadata.d.ts +2 -2
- package/package.json +33 -8
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-test.log +0 -219
- package/.turbo/turbo-typecheck.log +0 -4
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/coverage-final.json +0 -51
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -251
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/config/index.html +0 -191
- package/coverage/src/config/index.ts.html +0 -442
- package/coverage/src/config/interpolate.ts.html +0 -652
- package/coverage/src/config/loader.ts.html +0 -1501
- package/coverage/src/config/merge.ts.html +0 -823
- package/coverage/src/config/parser.ts.html +0 -1213
- package/coverage/src/config/schema.ts.html +0 -1123
- package/coverage/src/fleet-manager/errors.ts.html +0 -2326
- package/coverage/src/fleet-manager/event-types.ts.html +0 -1219
- package/coverage/src/fleet-manager/fleet-manager.ts.html +0 -7030
- package/coverage/src/fleet-manager/index.html +0 -206
- package/coverage/src/fleet-manager/index.ts.html +0 -469
- package/coverage/src/fleet-manager/job-manager.ts.html +0 -2074
- package/coverage/src/fleet-manager/job-queue.ts.html +0 -2479
- package/coverage/src/fleet-manager/types.ts.html +0 -2602
- package/coverage/src/index.html +0 -116
- package/coverage/src/index.ts.html +0 -181
- package/coverage/src/runner/errors.ts.html +0 -1006
- package/coverage/src/runner/index.html +0 -191
- package/coverage/src/runner/index.ts.html +0 -256
- package/coverage/src/runner/job-executor.ts.html +0 -1429
- package/coverage/src/runner/message-processor.ts.html +0 -1150
- package/coverage/src/runner/sdk-adapter.ts.html +0 -658
- package/coverage/src/runner/types.ts.html +0 -559
- package/coverage/src/scheduler/errors.ts.html +0 -388
- package/coverage/src/scheduler/index.html +0 -206
- package/coverage/src/scheduler/index.ts.html +0 -244
- package/coverage/src/scheduler/interval.ts.html +0 -652
- package/coverage/src/scheduler/schedule-runner.ts.html +0 -1411
- package/coverage/src/scheduler/schedule-state.ts.html +0 -718
- package/coverage/src/scheduler/scheduler.ts.html +0 -1795
- package/coverage/src/scheduler/types.ts.html +0 -733
- package/coverage/src/state/directory.ts.html +0 -736
- package/coverage/src/state/errors.ts.html +0 -376
- package/coverage/src/state/fleet-state.ts.html +0 -937
- package/coverage/src/state/index.html +0 -221
- package/coverage/src/state/index.ts.html +0 -322
- package/coverage/src/state/job-metadata.ts.html +0 -1420
- package/coverage/src/state/job-output.ts.html +0 -1033
- package/coverage/src/state/schemas/fleet-state.ts.html +0 -445
- package/coverage/src/state/schemas/index.html +0 -176
- package/coverage/src/state/schemas/index.ts.html +0 -286
- package/coverage/src/state/schemas/job-metadata.ts.html +0 -628
- package/coverage/src/state/schemas/job-output.ts.html +0 -616
- package/coverage/src/state/schemas/session-info.ts.html +0 -361
- package/coverage/src/state/session.ts.html +0 -844
- package/coverage/src/state/types.ts.html +0 -262
- package/coverage/src/state/utils/atomic.ts.html +0 -748
- package/coverage/src/state/utils/index.html +0 -146
- package/coverage/src/state/utils/index.ts.html +0 -103
- package/coverage/src/state/utils/reads.ts.html +0 -1621
- package/coverage/src/work-sources/adapters/github.ts.html +0 -3583
- package/coverage/src/work-sources/adapters/index.html +0 -131
- package/coverage/src/work-sources/adapters/index.ts.html +0 -277
- package/coverage/src/work-sources/errors.ts.html +0 -298
- package/coverage/src/work-sources/index.html +0 -176
- package/coverage/src/work-sources/index.ts.html +0 -529
- package/coverage/src/work-sources/manager.ts.html +0 -1324
- package/coverage/src/work-sources/registry.ts.html +0 -619
- package/coverage/src/work-sources/types.ts.html +0 -568
- package/dist/fleet-manager/__tests__/event-helpers.test.d.ts +0 -7
- package/dist/fleet-manager/__tests__/event-helpers.test.d.ts.map +0 -1
- package/dist/fleet-manager/__tests__/event-helpers.test.js +0 -368
- package/dist/fleet-manager/__tests__/event-helpers.test.js.map +0 -1
- package/src/config/__tests__/agent.test.ts +0 -864
- package/src/config/__tests__/interpolate.test.ts +0 -644
- package/src/config/__tests__/loader.test.ts +0 -784
- package/src/config/__tests__/merge.test.ts +0 -751
- package/src/config/__tests__/parser.test.ts +0 -533
- package/src/config/__tests__/schema.test.ts +0 -873
- package/src/config/index.ts +0 -119
- package/src/config/interpolate.ts +0 -189
- package/src/config/loader.ts +0 -472
- package/src/config/merge.ts +0 -246
- package/src/config/parser.ts +0 -376
- package/src/config/schema.ts +0 -346
- package/src/fleet-manager/__tests__/coverage.test.ts +0 -2869
- package/src/fleet-manager/__tests__/errors.test.ts +0 -660
- package/src/fleet-manager/__tests__/event-helpers.test.ts +0 -448
- package/src/fleet-manager/__tests__/integration.test.ts +0 -1209
- package/src/fleet-manager/__tests__/job-control.test.ts +0 -283
- package/src/fleet-manager/__tests__/job-manager.test.ts +0 -869
- package/src/fleet-manager/__tests__/job-queue.test.ts +0 -401
- package/src/fleet-manager/__tests__/reload.test.ts +0 -751
- package/src/fleet-manager/__tests__/status-queries.test.ts +0 -595
- package/src/fleet-manager/__tests__/trigger.test.ts +0 -601
- package/src/fleet-manager/errors.ts +0 -747
- package/src/fleet-manager/event-types.ts +0 -378
- package/src/fleet-manager/fleet-manager.ts +0 -2315
- package/src/fleet-manager/index.ts +0 -128
- package/src/fleet-manager/job-manager.ts +0 -663
- package/src/fleet-manager/job-queue.ts +0 -798
- package/src/fleet-manager/types.ts +0 -839
- package/src/index.ts +0 -32
- package/src/runner/__tests__/errors.test.ts +0 -382
- package/src/runner/__tests__/job-executor.test.ts +0 -1708
- package/src/runner/__tests__/message-processor.test.ts +0 -960
- package/src/runner/__tests__/sdk-adapter.test.ts +0 -626
- package/src/runner/errors.ts +0 -307
- package/src/runner/index.ts +0 -57
- package/src/runner/job-executor.ts +0 -448
- package/src/runner/message-processor.ts +0 -355
- package/src/runner/sdk-adapter.ts +0 -191
- package/src/runner/types.ts +0 -158
- package/src/scheduler/__tests__/errors.test.ts +0 -159
- package/src/scheduler/__tests__/interval.test.ts +0 -515
- package/src/scheduler/__tests__/schedule-runner.test.ts +0 -798
- package/src/scheduler/__tests__/schedule-state.test.ts +0 -671
- package/src/scheduler/__tests__/scheduler.test.ts +0 -1280
- package/src/scheduler/errors.ts +0 -101
- package/src/scheduler/index.ts +0 -53
- package/src/scheduler/interval.ts +0 -189
- package/src/scheduler/schedule-runner.ts +0 -442
- package/src/scheduler/schedule-state.ts +0 -211
- package/src/scheduler/scheduler.ts +0 -570
- package/src/scheduler/types.ts +0 -216
- package/src/state/__tests__/directory.test.ts +0 -595
- package/src/state/__tests__/fleet-state.test.ts +0 -868
- package/src/state/__tests__/job-metadata-schema.test.ts +0 -414
- package/src/state/__tests__/job-metadata.test.ts +0 -831
- package/src/state/__tests__/job-output.test.ts +0 -856
- package/src/state/__tests__/session-schema.test.ts +0 -378
- package/src/state/__tests__/session.test.ts +0 -604
- package/src/state/directory.ts +0 -217
- package/src/state/errors.ts +0 -97
- package/src/state/fleet-state.ts +0 -284
- package/src/state/index.ts +0 -79
- package/src/state/job-metadata.ts +0 -445
- package/src/state/job-output.ts +0 -316
- package/src/state/schemas/__tests__/job-output.test.ts +0 -338
- package/src/state/schemas/fleet-state.ts +0 -120
- package/src/state/schemas/index.ts +0 -67
- package/src/state/schemas/job-metadata.ts +0 -181
- package/src/state/schemas/job-output.ts +0 -177
- package/src/state/schemas/session-info.ts +0 -92
- package/src/state/session.ts +0 -253
- package/src/state/types.ts +0 -59
- package/src/state/utils/__tests__/atomic.test.ts +0 -723
- package/src/state/utils/__tests__/reads.test.ts +0 -1071
- package/src/state/utils/atomic.ts +0 -221
- package/src/state/utils/index.ts +0 -6
- package/src/state/utils/reads.ts +0 -512
- package/src/work-sources/__tests__/github.test.ts +0 -1800
- package/src/work-sources/__tests__/manager.test.ts +0 -529
- package/src/work-sources/__tests__/registry.test.ts +0 -477
- package/src/work-sources/__tests__/types.test.ts +0 -479
- package/src/work-sources/adapters/github.ts +0 -1166
- package/src/work-sources/adapters/index.ts +0 -64
- package/src/work-sources/errors.ts +0 -71
- package/src/work-sources/index.ts +0 -148
- package/src/work-sources/manager.ts +0 -413
- package/src/work-sources/registry.ts +0 -178
- package/src/work-sources/types.ts +0 -161
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -19
|
@@ -1,604 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { mkdir, rm, realpath, writeFile, readFile } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import {
|
|
6
|
-
getSessionInfo,
|
|
7
|
-
updateSessionInfo,
|
|
8
|
-
clearSession,
|
|
9
|
-
type SessionLogger,
|
|
10
|
-
} from "../session.js";
|
|
11
|
-
import { type SessionInfo } from "../schemas/session-info.js";
|
|
12
|
-
import { StateFileError } from "../errors.js";
|
|
13
|
-
|
|
14
|
-
// Helper to create a temp directory
|
|
15
|
-
async function createTempDir(): Promise<string> {
|
|
16
|
-
const baseDir = join(
|
|
17
|
-
tmpdir(),
|
|
18
|
-
`herdctl-session-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
19
|
-
);
|
|
20
|
-
await mkdir(baseDir, { recursive: true });
|
|
21
|
-
// Resolve to real path to handle macOS /var -> /private/var symlink
|
|
22
|
-
return await realpath(baseDir);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Helper to create a mock logger
|
|
26
|
-
function createMockLogger(): SessionLogger & { warnings: string[] } {
|
|
27
|
-
const warnings: string[] = [];
|
|
28
|
-
return {
|
|
29
|
-
warnings,
|
|
30
|
-
warn: (message: string) => warnings.push(message),
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Helper to create a valid session JSON file
|
|
35
|
-
async function writeSessionFile(
|
|
36
|
-
dir: string,
|
|
37
|
-
agentName: string,
|
|
38
|
-
session: SessionInfo
|
|
39
|
-
): Promise<string> {
|
|
40
|
-
const filePath = join(dir, `${agentName}.json`);
|
|
41
|
-
await writeFile(filePath, JSON.stringify(session, null, 2), "utf-8");
|
|
42
|
-
return filePath;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Helper to create a valid session object
|
|
46
|
-
function createValidSession(agentName: string): SessionInfo {
|
|
47
|
-
const now = new Date().toISOString();
|
|
48
|
-
return {
|
|
49
|
-
agent_name: agentName,
|
|
50
|
-
session_id: `session-${Math.random().toString(36).slice(2)}`,
|
|
51
|
-
created_at: now,
|
|
52
|
-
last_used_at: now,
|
|
53
|
-
job_count: 0,
|
|
54
|
-
mode: "autonomous",
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
describe("getSessionInfo", () => {
|
|
59
|
-
let tempDir: string;
|
|
60
|
-
|
|
61
|
-
beforeEach(async () => {
|
|
62
|
-
tempDir = await createTempDir();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
afterEach(async () => {
|
|
66
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("returns session info when file exists", async () => {
|
|
70
|
-
const session = createValidSession("test-agent");
|
|
71
|
-
await writeSessionFile(tempDir, "test-agent", session);
|
|
72
|
-
|
|
73
|
-
const result = await getSessionInfo(tempDir, "test-agent");
|
|
74
|
-
|
|
75
|
-
expect(result).not.toBeNull();
|
|
76
|
-
expect(result!.agent_name).toBe("test-agent");
|
|
77
|
-
expect(result!.session_id).toBe(session.session_id);
|
|
78
|
-
expect(result!.mode).toBe("autonomous");
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("returns null for non-existent session (handles missing file gracefully)", async () => {
|
|
82
|
-
const result = await getSessionInfo(tempDir, "non-existent-agent");
|
|
83
|
-
expect(result).toBeNull();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("returns null for empty sessions directory", async () => {
|
|
87
|
-
const result = await getSessionInfo(tempDir, "any-agent");
|
|
88
|
-
expect(result).toBeNull();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("returns null and logs warning for corrupted session file", async () => {
|
|
92
|
-
const logger = createMockLogger();
|
|
93
|
-
const corruptedPath = join(tempDir, "corrupted-agent.json");
|
|
94
|
-
await writeFile(corruptedPath, "{ invalid json", "utf-8");
|
|
95
|
-
|
|
96
|
-
const result = await getSessionInfo(tempDir, "corrupted-agent", { logger });
|
|
97
|
-
|
|
98
|
-
expect(result).toBeNull();
|
|
99
|
-
expect(logger.warnings.length).toBeGreaterThan(0);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("returns null for session with invalid schema", async () => {
|
|
103
|
-
const logger = createMockLogger();
|
|
104
|
-
const invalidSession = {
|
|
105
|
-
agent_name: "", // Invalid: empty
|
|
106
|
-
session_id: "valid-session",
|
|
107
|
-
created_at: "2024-01-15T10:00:00Z",
|
|
108
|
-
last_used_at: "2024-01-15T10:00:00Z",
|
|
109
|
-
job_count: 0,
|
|
110
|
-
mode: "autonomous",
|
|
111
|
-
};
|
|
112
|
-
await writeSessionFile(tempDir, "invalid-agent", invalidSession as SessionInfo);
|
|
113
|
-
|
|
114
|
-
const result = await getSessionInfo(tempDir, "invalid-agent", { logger });
|
|
115
|
-
|
|
116
|
-
expect(result).toBeNull();
|
|
117
|
-
expect(logger.warnings.length).toBeGreaterThan(0);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("returns null for session with invalid mode", async () => {
|
|
121
|
-
const logger = createMockLogger();
|
|
122
|
-
const invalidSession = {
|
|
123
|
-
agent_name: "test-agent",
|
|
124
|
-
session_id: "valid-session",
|
|
125
|
-
created_at: "2024-01-15T10:00:00Z",
|
|
126
|
-
last_used_at: "2024-01-15T10:00:00Z",
|
|
127
|
-
job_count: 0,
|
|
128
|
-
mode: "invalid-mode",
|
|
129
|
-
};
|
|
130
|
-
await writeSessionFile(tempDir, "test-agent", invalidSession as SessionInfo);
|
|
131
|
-
|
|
132
|
-
const result = await getSessionInfo(tempDir, "test-agent", { logger });
|
|
133
|
-
|
|
134
|
-
expect(result).toBeNull();
|
|
135
|
-
expect(logger.warnings.length).toBeGreaterThan(0);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it("returns null for session with invalid datetime format", async () => {
|
|
139
|
-
const logger = createMockLogger();
|
|
140
|
-
const invalidSession = {
|
|
141
|
-
agent_name: "test-agent",
|
|
142
|
-
session_id: "valid-session",
|
|
143
|
-
created_at: "not-a-date",
|
|
144
|
-
last_used_at: "2024-01-15T10:00:00Z",
|
|
145
|
-
job_count: 0,
|
|
146
|
-
mode: "autonomous",
|
|
147
|
-
};
|
|
148
|
-
await writeSessionFile(tempDir, "test-agent", invalidSession as SessionInfo);
|
|
149
|
-
|
|
150
|
-
const result = await getSessionInfo(tempDir, "test-agent", { logger });
|
|
151
|
-
|
|
152
|
-
expect(result).toBeNull();
|
|
153
|
-
expect(logger.warnings.length).toBeGreaterThan(0);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("validates all session fields correctly", async () => {
|
|
157
|
-
const session: SessionInfo = {
|
|
158
|
-
agent_name: "full-test-agent",
|
|
159
|
-
session_id: "claude-session-abc123",
|
|
160
|
-
created_at: "2024-01-15T10:00:00.000Z",
|
|
161
|
-
last_used_at: "2024-01-15T12:30:00.000Z",
|
|
162
|
-
job_count: 5,
|
|
163
|
-
mode: "interactive",
|
|
164
|
-
};
|
|
165
|
-
await writeSessionFile(tempDir, "full-test-agent", session);
|
|
166
|
-
|
|
167
|
-
const result = await getSessionInfo(tempDir, "full-test-agent");
|
|
168
|
-
|
|
169
|
-
expect(result).not.toBeNull();
|
|
170
|
-
expect(result!.agent_name).toBe("full-test-agent");
|
|
171
|
-
expect(result!.session_id).toBe("claude-session-abc123");
|
|
172
|
-
expect(result!.created_at).toBe("2024-01-15T10:00:00.000Z");
|
|
173
|
-
expect(result!.last_used_at).toBe("2024-01-15T12:30:00.000Z");
|
|
174
|
-
expect(result!.job_count).toBe(5);
|
|
175
|
-
expect(result!.mode).toBe("interactive");
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it("handles all valid session modes", async () => {
|
|
179
|
-
const modes = ["autonomous", "interactive", "review"] as const;
|
|
180
|
-
|
|
181
|
-
for (const mode of modes) {
|
|
182
|
-
const session = createValidSession(`${mode}-agent`);
|
|
183
|
-
session.mode = mode;
|
|
184
|
-
await writeSessionFile(tempDir, `${mode}-agent`, session);
|
|
185
|
-
|
|
186
|
-
const result = await getSessionInfo(tempDir, `${mode}-agent`);
|
|
187
|
-
expect(result).not.toBeNull();
|
|
188
|
-
expect(result!.mode).toBe(mode);
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
describe("updateSessionInfo", () => {
|
|
194
|
-
let tempDir: string;
|
|
195
|
-
|
|
196
|
-
beforeEach(async () => {
|
|
197
|
-
tempDir = await createTempDir();
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
afterEach(async () => {
|
|
201
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it("creates new session when none exists", async () => {
|
|
205
|
-
const result = await updateSessionInfo(tempDir, "new-agent", {
|
|
206
|
-
session_id: "new-session-123",
|
|
207
|
-
mode: "autonomous",
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
expect(result.agent_name).toBe("new-agent");
|
|
211
|
-
expect(result.session_id).toBe("new-session-123");
|
|
212
|
-
expect(result.mode).toBe("autonomous");
|
|
213
|
-
expect(result.job_count).toBe(0);
|
|
214
|
-
|
|
215
|
-
// Verify file was created
|
|
216
|
-
const content = await readFile(join(tempDir, "new-agent.json"), "utf-8");
|
|
217
|
-
const parsed = JSON.parse(content);
|
|
218
|
-
expect(parsed.agent_name).toBe("new-agent");
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it("throws StateFileError when creating session without session_id", async () => {
|
|
222
|
-
await expect(
|
|
223
|
-
updateSessionInfo(tempDir, "new-agent", {
|
|
224
|
-
mode: "autonomous",
|
|
225
|
-
})
|
|
226
|
-
).rejects.toThrow(StateFileError);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it("updates existing session", async () => {
|
|
230
|
-
const existingSession = createValidSession("test-agent");
|
|
231
|
-
existingSession.job_count = 3;
|
|
232
|
-
await writeSessionFile(tempDir, "test-agent", existingSession);
|
|
233
|
-
|
|
234
|
-
const updated = await updateSessionInfo(tempDir, "test-agent", {
|
|
235
|
-
job_count: 5,
|
|
236
|
-
mode: "interactive",
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
expect(updated.job_count).toBe(5);
|
|
240
|
-
expect(updated.mode).toBe("interactive");
|
|
241
|
-
expect(updated.session_id).toBe(existingSession.session_id); // Preserved
|
|
242
|
-
expect(updated.created_at).toBe(existingSession.created_at); // Preserved
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it("automatically updates last_used_at", async () => {
|
|
246
|
-
const existingSession = createValidSession("test-agent");
|
|
247
|
-
const originalLastUsed = existingSession.last_used_at;
|
|
248
|
-
await writeSessionFile(tempDir, "test-agent", existingSession);
|
|
249
|
-
|
|
250
|
-
// Wait a bit to ensure timestamp differs
|
|
251
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
252
|
-
|
|
253
|
-
const updated = await updateSessionInfo(tempDir, "test-agent", {
|
|
254
|
-
job_count: 1,
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
expect(updated.last_used_at).not.toBe(originalLastUsed);
|
|
258
|
-
expect(new Date(updated.last_used_at).getTime()).toBeGreaterThan(
|
|
259
|
-
new Date(originalLastUsed).getTime()
|
|
260
|
-
);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it("preserves agent_name even if update tries to change it", async () => {
|
|
264
|
-
const existingSession = createValidSession("original-agent");
|
|
265
|
-
await writeSessionFile(tempDir, "original-agent", existingSession);
|
|
266
|
-
|
|
267
|
-
const updated = await updateSessionInfo(tempDir, "original-agent", {
|
|
268
|
-
session_id: "new-session",
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
expect(updated.agent_name).toBe("original-agent");
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it("preserves created_at even if update tries to change it", async () => {
|
|
275
|
-
const existingSession = createValidSession("test-agent");
|
|
276
|
-
const originalCreatedAt = existingSession.created_at;
|
|
277
|
-
await writeSessionFile(tempDir, "test-agent", existingSession);
|
|
278
|
-
|
|
279
|
-
const updated = await updateSessionInfo(tempDir, "test-agent", {
|
|
280
|
-
job_count: 1,
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
expect(updated.created_at).toBe(originalCreatedAt);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it("updates session_id when provided", async () => {
|
|
287
|
-
const existingSession = createValidSession("test-agent");
|
|
288
|
-
await writeSessionFile(tempDir, "test-agent", existingSession);
|
|
289
|
-
|
|
290
|
-
const updated = await updateSessionInfo(tempDir, "test-agent", {
|
|
291
|
-
session_id: "new-session-456",
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
expect(updated.session_id).toBe("new-session-456");
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it("handles corrupted existing file by treating as new", async () => {
|
|
298
|
-
const corruptedPath = join(tempDir, "corrupted-agent.json");
|
|
299
|
-
await writeFile(corruptedPath, "{ invalid json", "utf-8");
|
|
300
|
-
|
|
301
|
-
const updated = await updateSessionInfo(tempDir, "corrupted-agent", {
|
|
302
|
-
session_id: "fresh-session",
|
|
303
|
-
mode: "autonomous",
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
expect(updated.agent_name).toBe("corrupted-agent");
|
|
307
|
-
expect(updated.session_id).toBe("fresh-session");
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it("throws StateFileError when directory does not exist", async () => {
|
|
311
|
-
const nonExistentDir = join(tempDir, "does-not-exist");
|
|
312
|
-
|
|
313
|
-
await expect(
|
|
314
|
-
updateSessionInfo(nonExistentDir, "test-agent", {
|
|
315
|
-
session_id: "test-session",
|
|
316
|
-
})
|
|
317
|
-
).rejects.toThrow(StateFileError);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
it("persists all fields correctly", async () => {
|
|
321
|
-
await updateSessionInfo(tempDir, "test-agent", {
|
|
322
|
-
session_id: "session-abc",
|
|
323
|
-
mode: "review",
|
|
324
|
-
job_count: 10,
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
// Read file directly and verify
|
|
328
|
-
const content = await readFile(join(tempDir, "test-agent.json"), "utf-8");
|
|
329
|
-
const parsed = JSON.parse(content);
|
|
330
|
-
|
|
331
|
-
expect(parsed.agent_name).toBe("test-agent");
|
|
332
|
-
expect(parsed.session_id).toBe("session-abc");
|
|
333
|
-
expect(parsed.mode).toBe("review");
|
|
334
|
-
expect(parsed.job_count).toBe(10);
|
|
335
|
-
expect(parsed.created_at).toBeDefined();
|
|
336
|
-
expect(parsed.last_used_at).toBeDefined();
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
it("handles multiple sequential updates", async () => {
|
|
340
|
-
await updateSessionInfo(tempDir, "test-agent", {
|
|
341
|
-
session_id: "session-1",
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
await updateSessionInfo(tempDir, "test-agent", {
|
|
345
|
-
job_count: 1,
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
await updateSessionInfo(tempDir, "test-agent", {
|
|
349
|
-
mode: "interactive",
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
const final = await getSessionInfo(tempDir, "test-agent");
|
|
353
|
-
expect(final!.session_id).toBe("session-1");
|
|
354
|
-
expect(final!.job_count).toBe(1);
|
|
355
|
-
expect(final!.mode).toBe("interactive");
|
|
356
|
-
});
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
describe("clearSession", () => {
|
|
360
|
-
let tempDir: string;
|
|
361
|
-
|
|
362
|
-
beforeEach(async () => {
|
|
363
|
-
tempDir = await createTempDir();
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
afterEach(async () => {
|
|
367
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
it("deletes existing session and returns true", async () => {
|
|
371
|
-
const session = createValidSession("test-agent");
|
|
372
|
-
await writeSessionFile(tempDir, "test-agent", session);
|
|
373
|
-
|
|
374
|
-
const deleted = await clearSession(tempDir, "test-agent");
|
|
375
|
-
|
|
376
|
-
expect(deleted).toBe(true);
|
|
377
|
-
|
|
378
|
-
// Verify file is gone
|
|
379
|
-
const retrieved = await getSessionInfo(tempDir, "test-agent");
|
|
380
|
-
expect(retrieved).toBeNull();
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it("returns false for non-existent session", async () => {
|
|
384
|
-
const deleted = await clearSession(tempDir, "non-existent-agent");
|
|
385
|
-
expect(deleted).toBe(false);
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it("does not affect other sessions", async () => {
|
|
389
|
-
const session1 = createValidSession("agent-1");
|
|
390
|
-
const session2 = createValidSession("agent-2");
|
|
391
|
-
await writeSessionFile(tempDir, "agent-1", session1);
|
|
392
|
-
await writeSessionFile(tempDir, "agent-2", session2);
|
|
393
|
-
|
|
394
|
-
await clearSession(tempDir, "agent-1");
|
|
395
|
-
|
|
396
|
-
const remaining = await getSessionInfo(tempDir, "agent-2");
|
|
397
|
-
expect(remaining).not.toBeNull();
|
|
398
|
-
expect(remaining!.agent_name).toBe("agent-2");
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it("allows creating new session after clearing", async () => {
|
|
402
|
-
const session = createValidSession("test-agent");
|
|
403
|
-
await writeSessionFile(tempDir, "test-agent", session);
|
|
404
|
-
|
|
405
|
-
await clearSession(tempDir, "test-agent");
|
|
406
|
-
|
|
407
|
-
const newSession = await updateSessionInfo(tempDir, "test-agent", {
|
|
408
|
-
session_id: "new-session-after-clear",
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
expect(newSession.session_id).toBe("new-session-after-clear");
|
|
412
|
-
expect(newSession.job_count).toBe(0); // Fresh session
|
|
413
|
-
});
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
describe("SessionInfoSchema validation", () => {
|
|
417
|
-
let tempDir: string;
|
|
418
|
-
|
|
419
|
-
beforeEach(async () => {
|
|
420
|
-
tempDir = await createTempDir();
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
afterEach(async () => {
|
|
424
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
it("rejects negative job_count", async () => {
|
|
428
|
-
const logger = createMockLogger();
|
|
429
|
-
const invalidSession = {
|
|
430
|
-
agent_name: "test-agent",
|
|
431
|
-
session_id: "valid-session",
|
|
432
|
-
created_at: "2024-01-15T10:00:00Z",
|
|
433
|
-
last_used_at: "2024-01-15T10:00:00Z",
|
|
434
|
-
job_count: -1,
|
|
435
|
-
mode: "autonomous",
|
|
436
|
-
};
|
|
437
|
-
await writeSessionFile(tempDir, "test-agent", invalidSession as SessionInfo);
|
|
438
|
-
|
|
439
|
-
const result = await getSessionInfo(tempDir, "test-agent", { logger });
|
|
440
|
-
expect(result).toBeNull();
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
it("rejects non-integer job_count", async () => {
|
|
444
|
-
const logger = createMockLogger();
|
|
445
|
-
const invalidSession = {
|
|
446
|
-
agent_name: "test-agent",
|
|
447
|
-
session_id: "valid-session",
|
|
448
|
-
created_at: "2024-01-15T10:00:00Z",
|
|
449
|
-
last_used_at: "2024-01-15T10:00:00Z",
|
|
450
|
-
job_count: 1.5,
|
|
451
|
-
mode: "autonomous",
|
|
452
|
-
};
|
|
453
|
-
await writeSessionFile(tempDir, "test-agent", invalidSession as SessionInfo);
|
|
454
|
-
|
|
455
|
-
const result = await getSessionInfo(tempDir, "test-agent", { logger });
|
|
456
|
-
expect(result).toBeNull();
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
it("rejects empty session_id", async () => {
|
|
460
|
-
const logger = createMockLogger();
|
|
461
|
-
const invalidSession = {
|
|
462
|
-
agent_name: "test-agent",
|
|
463
|
-
session_id: "",
|
|
464
|
-
created_at: "2024-01-15T10:00:00Z",
|
|
465
|
-
last_used_at: "2024-01-15T10:00:00Z",
|
|
466
|
-
job_count: 0,
|
|
467
|
-
mode: "autonomous",
|
|
468
|
-
};
|
|
469
|
-
await writeSessionFile(tempDir, "test-agent", invalidSession as SessionInfo);
|
|
470
|
-
|
|
471
|
-
const result = await getSessionInfo(tempDir, "test-agent", { logger });
|
|
472
|
-
expect(result).toBeNull();
|
|
473
|
-
});
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
describe("atomic write behavior", () => {
|
|
477
|
-
let tempDir: string;
|
|
478
|
-
|
|
479
|
-
beforeEach(async () => {
|
|
480
|
-
tempDir = await createTempDir();
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
afterEach(async () => {
|
|
484
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
it("does not leave temp files on successful write", async () => {
|
|
488
|
-
await updateSessionInfo(tempDir, "test-agent", {
|
|
489
|
-
session_id: "test-session",
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
const { readdir } = await import("node:fs/promises");
|
|
493
|
-
const files = await readdir(tempDir);
|
|
494
|
-
const tempFiles = files.filter((f) => f.includes(".tmp."));
|
|
495
|
-
expect(tempFiles).toHaveLength(0);
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
it("creates valid JSON file", async () => {
|
|
499
|
-
await updateSessionInfo(tempDir, "test-agent", {
|
|
500
|
-
session_id: "test-session",
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
const content = await readFile(join(tempDir, "test-agent.json"), "utf-8");
|
|
504
|
-
// Should not throw
|
|
505
|
-
const parsed = JSON.parse(content);
|
|
506
|
-
expect(parsed).toBeDefined();
|
|
507
|
-
});
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
describe("concurrent operations", () => {
|
|
511
|
-
let tempDir: string;
|
|
512
|
-
|
|
513
|
-
beforeEach(async () => {
|
|
514
|
-
tempDir = await createTempDir();
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
afterEach(async () => {
|
|
518
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
it("handles multiple concurrent reads", async () => {
|
|
522
|
-
const session = createValidSession("test-agent");
|
|
523
|
-
await writeSessionFile(tempDir, "test-agent", session);
|
|
524
|
-
|
|
525
|
-
const reads = [];
|
|
526
|
-
for (let i = 0; i < 50; i++) {
|
|
527
|
-
reads.push(getSessionInfo(tempDir, "test-agent"));
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const results = await Promise.all(reads);
|
|
531
|
-
|
|
532
|
-
for (const result of results) {
|
|
533
|
-
expect(result).not.toBeNull();
|
|
534
|
-
expect(result!.agent_name).toBe("test-agent");
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
it("handles sequential updates correctly", async () => {
|
|
539
|
-
await updateSessionInfo(tempDir, "test-agent", {
|
|
540
|
-
session_id: "initial-session",
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
for (let i = 0; i < 10; i++) {
|
|
544
|
-
await updateSessionInfo(tempDir, "test-agent", {
|
|
545
|
-
job_count: i + 1,
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
const final = await getSessionInfo(tempDir, "test-agent");
|
|
550
|
-
expect(final!.job_count).toBe(10);
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
it("handles multiple agents concurrently", async () => {
|
|
554
|
-
const updates = [];
|
|
555
|
-
for (let i = 0; i < 10; i++) {
|
|
556
|
-
updates.push(
|
|
557
|
-
updateSessionInfo(tempDir, `agent-${i}`, {
|
|
558
|
-
session_id: `session-${i}`,
|
|
559
|
-
})
|
|
560
|
-
);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
const results = await Promise.all(updates);
|
|
564
|
-
|
|
565
|
-
expect(results).toHaveLength(10);
|
|
566
|
-
for (let i = 0; i < 10; i++) {
|
|
567
|
-
expect(results[i].agent_name).toBe(`agent-${i}`);
|
|
568
|
-
expect(results[i].session_id).toBe(`session-${i}`);
|
|
569
|
-
}
|
|
570
|
-
});
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
describe("file path handling", () => {
|
|
574
|
-
let tempDir: string;
|
|
575
|
-
|
|
576
|
-
beforeEach(async () => {
|
|
577
|
-
tempDir = await createTempDir();
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
afterEach(async () => {
|
|
581
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
it("stores sessions at correct path", async () => {
|
|
585
|
-
await updateSessionInfo(tempDir, "my-agent", {
|
|
586
|
-
session_id: "test-session",
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
const expectedPath = join(tempDir, "my-agent.json");
|
|
590
|
-
const content = await readFile(expectedPath, "utf-8");
|
|
591
|
-
expect(content).toContain("my-agent");
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
it("handles agent names with special characters", async () => {
|
|
595
|
-
// Agent names with dashes and underscores
|
|
596
|
-
await updateSessionInfo(tempDir, "my-special_agent-123", {
|
|
597
|
-
session_id: "test-session",
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
const result = await getSessionInfo(tempDir, "my-special_agent-123");
|
|
601
|
-
expect(result).not.toBeNull();
|
|
602
|
-
expect(result!.agent_name).toBe("my-special_agent-123");
|
|
603
|
-
});
|
|
604
|
-
});
|