@ai-devkit/agent-manager 0.28.1 → 0.30.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/__tests__/adapters/ClaudeCodeAdapter.test.js +1 -342
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
- package/dist/__tests__/capacity/codex.test.js +343 -0
- package/dist/__tests__/capacity/codex.test.js.map +1 -0
- package/dist/__tests__/capacity/index.test.js +50 -0
- package/dist/__tests__/capacity/index.test.js.map +1 -0
- package/dist/__tests__/database/DurableAgentsDatabase.test.js +31 -3
- package/dist/__tests__/database/DurableAgentsDatabase.test.js.map +1 -1
- package/dist/__tests__/durable/CodexCliProbe.test.js +129 -0
- package/dist/__tests__/durable/CodexCliProbe.test.js.map +1 -0
- package/dist/__tests__/durable/CodexDurableAgentRepository.test.js +84 -0
- package/dist/__tests__/durable/CodexDurableAgentRepository.test.js.map +1 -0
- package/dist/__tests__/durable/CodexPrintAgent.integration.test.js +126 -0
- package/dist/__tests__/durable/CodexPrintAgent.integration.test.js.map +1 -0
- package/dist/__tests__/durable/CodexPrintAgentService.test.js +271 -0
- package/dist/__tests__/durable/CodexPrintAgentService.test.js.map +1 -0
- package/dist/__tests__/durable/CodexPrintRunner.test.js +393 -0
- package/dist/__tests__/durable/CodexPrintRunner.test.js.map +1 -0
- package/dist/__tests__/print/ClaudeCliProbe.test.js +3 -7
- package/dist/__tests__/print/ClaudeCliProbe.test.js.map +1 -1
- package/dist/__tests__/print/ClaudePrintAgent.integration.test.js +33 -27
- package/dist/__tests__/print/ClaudePrintAgent.integration.test.js.map +1 -1
- package/dist/__tests__/print/ClaudePrintAgentService.test.js +65 -0
- package/dist/__tests__/print/ClaudePrintAgentService.test.js.map +1 -1
- package/dist/__tests__/print/ClaudePrintRunner.test.js +4 -10
- package/dist/__tests__/print/ClaudePrintRunner.test.js.map +1 -1
- package/dist/__tests__/providers/claude/ClaudeAgentMapper.test.js +79 -0
- package/dist/__tests__/providers/claude/ClaudeAgentMapper.test.js.map +1 -0
- package/dist/__tests__/providers/claude/ClaudeSessionLocator.test.js +436 -0
- package/dist/__tests__/providers/claude/ClaudeSessionLocator.test.js.map +1 -0
- package/dist/__tests__/providers/pi/durable/PiCliProbe.test.js +76 -0
- package/dist/__tests__/providers/pi/durable/PiCliProbe.test.js.map +1 -0
- package/dist/__tests__/providers/pi/durable/PiDurableAgentRepository.test.js +43 -0
- package/dist/__tests__/providers/pi/durable/PiDurableAgentRepository.test.js.map +1 -0
- package/dist/__tests__/providers/pi/durable/PiPrintAgentService.test.js +202 -0
- package/dist/__tests__/providers/pi/durable/PiPrintAgentService.test.js.map +1 -0
- package/dist/__tests__/providers/pi/durable/PiPrintProtocol.test.js +119 -0
- package/dist/__tests__/providers/pi/durable/PiPrintProtocol.test.js.map +1 -0
- package/dist/__tests__/providers/pi/durable/PiPrintRunner.test.js +386 -0
- package/dist/__tests__/providers/pi/durable/PiPrintRunner.test.js.map +1 -0
- package/dist/__tests__/providers/pi/durable/PiStreamParser.test.js +34 -0
- package/dist/__tests__/providers/pi/durable/PiStreamParser.test.js.map +1 -0
- package/dist/__tests__/utils/ClaudeSessionParser.test.js +2 -2
- package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/capacity/codex.d.ts +36 -0
- package/dist/capacity/codex.d.ts.map +1 -0
- package/dist/capacity/codex.js +317 -0
- package/dist/capacity/codex.js.map +1 -0
- package/dist/capacity/index.d.ts +11 -0
- package/dist/capacity/index.d.ts.map +1 -0
- package/dist/capacity/index.js +49 -0
- package/dist/capacity/index.js.map +1 -0
- package/dist/capacity/types.d.ts +17 -0
- package/dist/capacity/types.d.ts.map +1 -0
- package/dist/capacity/types.js +3 -0
- package/dist/capacity/types.js.map +1 -0
- package/dist/database/migrations/004_codex_durable_sessions.sql +1 -0
- package/dist/durable/DurableAgent.d.ts +23 -3
- package/dist/durable/DurableAgent.d.ts.map +1 -1
- package/dist/durable/DurableAgent.js +12 -0
- package/dist/durable/DurableAgent.js.map +1 -1
- package/dist/durable/DurableAgentRepository.d.ts +6 -7
- package/dist/durable/DurableAgentRepository.d.ts.map +1 -1
- package/dist/durable/DurableAgentRepository.js +73 -42
- package/dist/durable/DurableAgentRepository.js.map +1 -1
- package/dist/durable/process.d.ts +8 -0
- package/dist/durable/process.d.ts.map +1 -0
- package/dist/durable/process.js +40 -0
- package/dist/durable/process.js.map +1 -0
- package/dist/durable/run.d.ts +32 -0
- package/dist/durable/run.d.ts.map +1 -0
- package/dist/durable/run.js +46 -0
- package/dist/durable/run.js.map +1 -0
- package/dist/durable/utils.d.ts +19 -0
- package/dist/durable/utils.d.ts.map +1 -0
- package/dist/durable/utils.js +52 -0
- package/dist/durable/utils.js.map +1 -0
- package/dist/index.d.ts +19 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -8
- package/dist/index.js.map +1 -1
- package/dist/providers/claude/ClaudeAgentMapper.d.ts +21 -0
- package/dist/providers/claude/ClaudeAgentMapper.d.ts.map +1 -0
- package/dist/providers/claude/ClaudeAgentMapper.js +42 -0
- package/dist/providers/claude/ClaudeAgentMapper.js.map +1 -0
- package/dist/providers/claude/ClaudeCodeAdapter.d.ts +29 -0
- package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -0
- package/dist/providers/claude/ClaudeCodeAdapter.js +129 -0
- package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -0
- package/dist/{adapters/ClaudeCodeAdapter.d.ts → providers/claude/ClaudeSessionLocator.d.ts} +68 -52
- package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -0
- package/dist/{adapters/ClaudeCodeAdapter.js → providers/claude/ClaudeSessionLocator.js} +71 -189
- package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -0
- package/dist/{utils → providers/claude}/ClaudeSessionParser.d.ts +2 -2
- package/dist/providers/claude/ClaudeSessionParser.d.ts.map +1 -0
- package/dist/{utils → providers/claude}/ClaudeSessionParser.js +1 -1
- package/dist/providers/claude/ClaudeSessionParser.js.map +1 -0
- package/dist/providers/claude/durable/ClaudeCliProbe.d.ts.map +1 -0
- package/dist/{durable → providers/claude/durable}/ClaudeCliProbe.js +4 -9
- package/dist/providers/claude/durable/ClaudeCliProbe.js.map +1 -0
- package/dist/{durable → providers/claude/durable}/ClaudePrintAgentService.d.ts +4 -9
- package/dist/providers/claude/durable/ClaudePrintAgentService.d.ts.map +1 -0
- package/dist/providers/claude/durable/ClaudePrintAgentService.js +45 -0
- package/dist/providers/claude/durable/ClaudePrintAgentService.js.map +1 -0
- package/dist/{durable → providers/claude/durable}/ClaudePrintRunner.d.ts +3 -3
- package/dist/providers/claude/durable/ClaudePrintRunner.d.ts.map +1 -0
- package/dist/providers/claude/durable/ClaudePrintRunner.js +75 -0
- package/dist/providers/claude/durable/ClaudePrintRunner.js.map +1 -0
- package/dist/providers/claude/durable/ClaudeStreamParser.d.ts +17 -0
- package/dist/providers/claude/durable/ClaudeStreamParser.d.ts.map +1 -0
- package/dist/providers/claude/durable/ClaudeStreamParser.js +69 -0
- package/dist/providers/claude/durable/ClaudeStreamParser.js.map +1 -0
- package/dist/providers/codex/durable/CodexCliProbe.d.ts +20 -0
- package/dist/providers/codex/durable/CodexCliProbe.d.ts.map +1 -0
- package/dist/providers/codex/durable/CodexCliProbe.js +61 -0
- package/dist/providers/codex/durable/CodexCliProbe.js.map +1 -0
- package/dist/providers/codex/durable/CodexPrintAgentService.d.ts +40 -0
- package/dist/providers/codex/durable/CodexPrintAgentService.d.ts.map +1 -0
- package/dist/providers/codex/durable/CodexPrintAgentService.js +50 -0
- package/dist/providers/codex/durable/CodexPrintAgentService.js.map +1 -0
- package/dist/providers/codex/durable/CodexPrintRunner.d.ts +34 -0
- package/dist/providers/codex/durable/CodexPrintRunner.d.ts.map +1 -0
- package/dist/providers/codex/durable/CodexPrintRunner.js +78 -0
- package/dist/providers/codex/durable/CodexPrintRunner.js.map +1 -0
- package/dist/providers/codex/durable/CodexStreamParser.d.ts +21 -0
- package/dist/providers/codex/durable/CodexStreamParser.d.ts.map +1 -0
- package/dist/providers/codex/durable/CodexStreamParser.js +86 -0
- package/dist/providers/codex/durable/CodexStreamParser.js.map +1 -0
- package/dist/providers/pi/durable/PiCliProbe.d.ts +20 -0
- package/dist/providers/pi/durable/PiCliProbe.d.ts.map +1 -0
- package/dist/providers/pi/durable/PiCliProbe.js +51 -0
- package/dist/providers/pi/durable/PiCliProbe.js.map +1 -0
- package/dist/providers/pi/durable/PiPrintAgentService.d.ts +39 -0
- package/dist/providers/pi/durable/PiPrintAgentService.d.ts.map +1 -0
- package/dist/providers/pi/durable/PiPrintAgentService.js +47 -0
- package/dist/providers/pi/durable/PiPrintAgentService.js.map +1 -0
- package/dist/providers/pi/durable/PiPrintProtocol.d.ts +4 -0
- package/dist/providers/pi/durable/PiPrintProtocol.d.ts.map +1 -0
- package/dist/providers/pi/durable/PiPrintProtocol.js +44 -0
- package/dist/providers/pi/durable/PiPrintProtocol.js.map +1 -0
- package/dist/providers/pi/durable/PiPrintRunner.d.ts +32 -0
- package/dist/providers/pi/durable/PiPrintRunner.d.ts.map +1 -0
- package/dist/providers/pi/durable/PiPrintRunner.js +61 -0
- package/dist/providers/pi/durable/PiPrintRunner.js.map +1 -0
- package/dist/providers/pi/durable/PiStreamParser.d.ts +21 -0
- package/dist/providers/pi/durable/PiStreamParser.d.ts.map +1 -0
- package/dist/providers/pi/durable/PiStreamParser.js +71 -0
- package/dist/providers/pi/durable/PiStreamParser.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +1 -307
- package/src/__tests__/capacity/codex.test.ts +184 -0
- package/src/__tests__/capacity/index.test.ts +38 -0
- package/src/__tests__/database/DurableAgentsDatabase.test.ts +28 -3
- package/src/__tests__/durable/CodexCliProbe.test.ts +63 -0
- package/src/__tests__/durable/CodexDurableAgentRepository.test.ts +54 -0
- package/src/__tests__/durable/CodexPrintAgent.integration.test.ts +79 -0
- package/src/__tests__/durable/CodexPrintAgentService.test.ts +139 -0
- package/src/__tests__/durable/CodexPrintRunner.test.ts +189 -0
- package/src/__tests__/fixtures/fake-codex.cjs +45 -0
- package/src/__tests__/print/ClaudeCliProbe.test.ts +3 -7
- package/src/__tests__/print/ClaudePrintAgent.integration.test.ts +19 -22
- package/src/__tests__/print/ClaudePrintAgentService.test.ts +33 -1
- package/src/__tests__/print/ClaudePrintRunner.test.ts +4 -10
- package/src/__tests__/providers/claude/ClaudeAgentMapper.test.ts +84 -0
- package/src/__tests__/providers/claude/ClaudeSessionLocator.test.ts +411 -0
- package/src/__tests__/providers/pi/durable/PiCliProbe.test.ts +38 -0
- package/src/__tests__/providers/pi/durable/PiDurableAgentRepository.test.ts +32 -0
- package/src/__tests__/providers/pi/durable/PiPrintAgentService.test.ts +69 -0
- package/src/__tests__/providers/pi/durable/PiPrintProtocol.test.ts +33 -0
- package/src/__tests__/providers/pi/durable/PiPrintRunner.test.ts +107 -0
- package/src/__tests__/providers/pi/durable/PiStreamParser.test.ts +24 -0
- package/src/__tests__/utils/ClaudeSessionParser.test.ts +2 -2
- package/src/adapters/index.ts +1 -1
- package/src/capacity/codex.ts +320 -0
- package/src/capacity/index.ts +57 -0
- package/src/capacity/types.ts +18 -0
- package/src/database/migrations/004_codex_durable_sessions.sql +1 -0
- package/src/durable/DurableAgent.ts +51 -3
- package/src/durable/DurableAgentRepository.ts +60 -31
- package/src/durable/process.ts +28 -0
- package/src/durable/run.ts +74 -0
- package/src/durable/utils.ts +61 -0
- package/src/index.ts +37 -15
- package/src/providers/claude/ClaudeAgentMapper.ts +62 -0
- package/src/providers/claude/ClaudeCodeAdapter.ts +160 -0
- package/src/{adapters/ClaudeCodeAdapter.ts → providers/claude/ClaudeSessionLocator.ts} +97 -239
- package/src/{utils → providers/claude}/ClaudeSessionParser.ts +2 -2
- package/src/{durable → providers/claude/durable}/ClaudeCliProbe.ts +4 -10
- package/src/providers/claude/durable/ClaudePrintAgentService.ts +66 -0
- package/src/providers/claude/durable/ClaudePrintRunner.ts +92 -0
- package/src/providers/claude/durable/ClaudeStreamParser.ts +79 -0
- package/src/providers/codex/durable/CodexCliProbe.ts +60 -0
- package/src/providers/codex/durable/CodexPrintAgentService.ts +65 -0
- package/src/providers/codex/durable/CodexPrintRunner.ts +94 -0
- package/src/providers/codex/durable/CodexStreamParser.ts +104 -0
- package/src/providers/pi/durable/PiCliProbe.ts +48 -0
- package/src/providers/pi/durable/PiPrintAgentService.ts +69 -0
- package/src/providers/pi/durable/PiPrintProtocol.ts +39 -0
- package/src/providers/pi/durable/PiPrintRunner.ts +88 -0
- package/src/providers/pi/durable/PiStreamParser.ts +84 -0
- package/dist/adapters/ClaudeCodeAdapter.d.ts.map +0 -1
- package/dist/adapters/ClaudeCodeAdapter.js.map +0 -1
- package/dist/durable/ClaudeCliProbe.d.ts.map +0 -1
- package/dist/durable/ClaudeCliProbe.js.map +0 -1
- package/dist/durable/ClaudePrintAgentService.d.ts.map +0 -1
- package/dist/durable/ClaudePrintAgentService.js +0 -66
- package/dist/durable/ClaudePrintAgentService.js.map +0 -1
- package/dist/durable/ClaudePrintRunner.d.ts.map +0 -1
- package/dist/durable/ClaudePrintRunner.js +0 -128
- package/dist/durable/ClaudePrintRunner.js.map +0 -1
- package/dist/utils/ClaudeSessionParser.d.ts.map +0 -1
- package/dist/utils/ClaudeSessionParser.js.map +0 -1
- package/src/durable/ClaudePrintAgentService.ts +0 -94
- package/src/durable/ClaudePrintRunner.ts +0 -139
- /package/dist/{durable → providers/claude/durable}/ClaudeCliProbe.d.ts +0 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { CODEX_APP_SERVER_ARGS, parseUsage, probeCodexCapacity, resolveCodexAuthPath, toRateWindow } from '../../capacity/codex.js';
|
|
3
|
+
const checkedAt = '2026-08-20T10:00:00.000Z';
|
|
4
|
+
const context = {
|
|
5
|
+
installed: true,
|
|
6
|
+
checkedAt
|
|
7
|
+
};
|
|
8
|
+
function apiUsage(overrides = {}) {
|
|
9
|
+
return {
|
|
10
|
+
rate_limit: {
|
|
11
|
+
primary_window: {
|
|
12
|
+
used_percent: 20,
|
|
13
|
+
limit_window_seconds: 18_000,
|
|
14
|
+
reset_at: 1_787_220_000
|
|
15
|
+
},
|
|
16
|
+
secondary_window: {
|
|
17
|
+
used_percent: 60,
|
|
18
|
+
limit_window_seconds: 604_800,
|
|
19
|
+
reset_at: 1_787_824_800
|
|
20
|
+
},
|
|
21
|
+
...overrides
|
|
22
|
+
},
|
|
23
|
+
credits: {
|
|
24
|
+
balance: 12.5
|
|
25
|
+
},
|
|
26
|
+
additional_rate_limits: [
|
|
27
|
+
{
|
|
28
|
+
limit_name: 'reviews',
|
|
29
|
+
rate_limit: {
|
|
30
|
+
primary_window: {
|
|
31
|
+
used_percent: 10,
|
|
32
|
+
limit_window_seconds: 3_600,
|
|
33
|
+
reset_at: 1_787_220_000
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
describe('Codex auth resolution', ()=>{
|
|
41
|
+
it('uses CODEX_HOME before HOME', ()=>{
|
|
42
|
+
expect(resolveCodexAuthPath({
|
|
43
|
+
CODEX_HOME: '/custom/codex',
|
|
44
|
+
HOME: '/users/test'
|
|
45
|
+
})).toBe('/custom/codex/auth.json');
|
|
46
|
+
});
|
|
47
|
+
it('falls back to ~/.codex/auth.json', ()=>{
|
|
48
|
+
expect(resolveCodexAuthPath({
|
|
49
|
+
HOME: '/users/test'
|
|
50
|
+
})).toBe('/users/test/.codex/auth.json');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe('Codex API usage mapping', ()=>{
|
|
54
|
+
it('converts an API window without treating missing data as zero', ()=>{
|
|
55
|
+
expect(toRateWindow({
|
|
56
|
+
used_percent: 25,
|
|
57
|
+
limit_window_seconds: 18_000,
|
|
58
|
+
reset_at: 1_787_220_000
|
|
59
|
+
}, 'session', 'Session')).toEqual({
|
|
60
|
+
id: 'session',
|
|
61
|
+
label: 'Session',
|
|
62
|
+
durationMinutes: 300,
|
|
63
|
+
usedPercent: 25,
|
|
64
|
+
resetsAt: '2026-08-20T10:00:00.000Z'
|
|
65
|
+
});
|
|
66
|
+
expect(toRateWindow({}, 'session', 'Session')).toMatchObject({
|
|
67
|
+
usedPercent: null
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
it('maps session, weekly, credits, and extra limits', ()=>{
|
|
71
|
+
const snapshot = parseUsage(apiUsage(), 'pat');
|
|
72
|
+
expect(snapshot).toMatchObject({
|
|
73
|
+
source: 'pat',
|
|
74
|
+
creditsRemaining: 12.5
|
|
75
|
+
});
|
|
76
|
+
expect(snapshot.windows).toEqual([
|
|
77
|
+
expect.objectContaining({
|
|
78
|
+
id: 'session',
|
|
79
|
+
durationMinutes: 300
|
|
80
|
+
}),
|
|
81
|
+
expect.objectContaining({
|
|
82
|
+
id: 'weekly',
|
|
83
|
+
durationMinutes: 10080
|
|
84
|
+
}),
|
|
85
|
+
expect.objectContaining({
|
|
86
|
+
id: 'reviews:primary',
|
|
87
|
+
durationMinutes: 60
|
|
88
|
+
})
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
it('represents missing limits as unavailable rather than zero', ()=>{
|
|
92
|
+
const snapshot = parseUsage({
|
|
93
|
+
credits: {}
|
|
94
|
+
}, 'oauth');
|
|
95
|
+
expect(snapshot.windows).toEqual([]);
|
|
96
|
+
expect(snapshot.creditsRemaining).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('tiered Codex probing', ()=>{
|
|
100
|
+
it('selects PAT, calls whoami then usage, and never invokes the CLI', async ()=>{
|
|
101
|
+
const fetch = vi.fn().mockResolvedValueOnce(new Response(JSON.stringify({
|
|
102
|
+
chatgpt_account_id: 'acct-1'
|
|
103
|
+
}), {
|
|
104
|
+
status: 200
|
|
105
|
+
})).mockResolvedValueOnce(new Response(JSON.stringify(apiUsage()), {
|
|
106
|
+
status: 200
|
|
107
|
+
}));
|
|
108
|
+
const rpc = vi.fn();
|
|
109
|
+
const result = await probeCodexCapacity({
|
|
110
|
+
...context,
|
|
111
|
+
readFile: async ()=>JSON.stringify({
|
|
112
|
+
personal_access_token: 'pat-secret',
|
|
113
|
+
tokens: {
|
|
114
|
+
access_token: 'ignored-oauth',
|
|
115
|
+
account_id: 'ignored-account'
|
|
116
|
+
}
|
|
117
|
+
}),
|
|
118
|
+
fetch,
|
|
119
|
+
rpc
|
|
120
|
+
});
|
|
121
|
+
expect(fetch).toHaveBeenCalledTimes(2);
|
|
122
|
+
expect(fetch.mock.calls[0][0]).toBe('https://auth.openai.com/api/accounts/v1/user-auth-credential/whoami');
|
|
123
|
+
expect(fetch.mock.calls[1][0]).toBe('https://chatgpt.com/backend-api/wham/usage');
|
|
124
|
+
expect(fetch.mock.calls[1][1].headers).toMatchObject({
|
|
125
|
+
Authorization: 'Bearer pat-secret',
|
|
126
|
+
'ChatGPT-Account-Id': 'acct-1'
|
|
127
|
+
});
|
|
128
|
+
expect(rpc).not.toHaveBeenCalled();
|
|
129
|
+
expect(result).toMatchObject({
|
|
130
|
+
provider: 'codex',
|
|
131
|
+
available: 'yes',
|
|
132
|
+
creditsRemaining: 12.5,
|
|
133
|
+
authenticated: true
|
|
134
|
+
});
|
|
135
|
+
expect(result.windows.map((window)=>window.id)).toEqual([
|
|
136
|
+
'session',
|
|
137
|
+
'weekly',
|
|
138
|
+
'reviews:primary'
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
it('selects a fresh OAuth token without calling whoami', async ()=>{
|
|
142
|
+
const fetch = vi.fn().mockResolvedValue(new Response(JSON.stringify(apiUsage()), {
|
|
143
|
+
status: 200
|
|
144
|
+
}));
|
|
145
|
+
const result = await probeCodexCapacity({
|
|
146
|
+
...context,
|
|
147
|
+
readFile: async ()=>JSON.stringify({
|
|
148
|
+
tokens: {
|
|
149
|
+
access_token: 'oauth-secret',
|
|
150
|
+
account_id: 'acct-2',
|
|
151
|
+
expires_at: 1_800_000_000
|
|
152
|
+
}
|
|
153
|
+
}),
|
|
154
|
+
fetch,
|
|
155
|
+
now: ()=>new Date('2026-08-20T10:00:00.000Z')
|
|
156
|
+
});
|
|
157
|
+
expect(fetch).toHaveBeenCalledOnce();
|
|
158
|
+
expect(fetch.mock.calls[0][1].headers).toMatchObject({
|
|
159
|
+
Authorization: 'Bearer oauth-secret',
|
|
160
|
+
'ChatGPT-Account-Id': 'acct-2'
|
|
161
|
+
});
|
|
162
|
+
expect(result.available).toBe('yes');
|
|
163
|
+
});
|
|
164
|
+
it.each([
|
|
165
|
+
[
|
|
166
|
+
'missing auth file',
|
|
167
|
+
async ()=>{
|
|
168
|
+
throw Object.assign(new Error('missing'), {
|
|
169
|
+
code: 'ENOENT'
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
],
|
|
173
|
+
[
|
|
174
|
+
'stale OAuth token',
|
|
175
|
+
async ()=>JSON.stringify({
|
|
176
|
+
tokens: {
|
|
177
|
+
access_token: 'stale-secret',
|
|
178
|
+
account_id: 'acct',
|
|
179
|
+
expires_at: 1
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
],
|
|
183
|
+
[
|
|
184
|
+
'OAuth 401',
|
|
185
|
+
async ()=>JSON.stringify({
|
|
186
|
+
tokens: {
|
|
187
|
+
access_token: 'oauth-secret',
|
|
188
|
+
account_id: 'acct',
|
|
189
|
+
expires_at: 1_800_000_000
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
]
|
|
193
|
+
])('falls back to the CLI for %s', async (name, readFile)=>{
|
|
194
|
+
const fetch = vi.fn().mockResolvedValue(new Response('', {
|
|
195
|
+
status: name === 'OAuth 401' ? 401 : 200
|
|
196
|
+
}));
|
|
197
|
+
const rpc = vi.fn(async ()=>({
|
|
198
|
+
rateLimits: {
|
|
199
|
+
rateLimits: {
|
|
200
|
+
primary: {
|
|
201
|
+
usedPercent: 5,
|
|
202
|
+
windowDurationMins: 300,
|
|
203
|
+
resetsAt: null
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
account: {
|
|
208
|
+
account: {
|
|
209
|
+
type: 'chatgpt'
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}));
|
|
213
|
+
const result = await probeCodexCapacity({
|
|
214
|
+
...context,
|
|
215
|
+
readFile,
|
|
216
|
+
fetch,
|
|
217
|
+
rpc,
|
|
218
|
+
now: ()=>new Date(checkedAt)
|
|
219
|
+
});
|
|
220
|
+
expect(rpc).toHaveBeenCalledOnce();
|
|
221
|
+
expect(result.windows).toHaveLength(1);
|
|
222
|
+
});
|
|
223
|
+
it('falls back to CLI if PAT requests fail', async ()=>{
|
|
224
|
+
const rpc = vi.fn(async ()=>({
|
|
225
|
+
rateLimits: {},
|
|
226
|
+
account: {
|
|
227
|
+
account: null
|
|
228
|
+
}
|
|
229
|
+
}));
|
|
230
|
+
const result = await probeCodexCapacity({
|
|
231
|
+
...context,
|
|
232
|
+
readFile: async ()=>JSON.stringify({
|
|
233
|
+
personal_access_token: 'pat-secret'
|
|
234
|
+
}),
|
|
235
|
+
fetch: vi.fn().mockRejectedValue(new Error('network failure pat-secret')),
|
|
236
|
+
rpc
|
|
237
|
+
});
|
|
238
|
+
expect(rpc).toHaveBeenCalledOnce();
|
|
239
|
+
expect(result.available).toBe('unknown');
|
|
240
|
+
});
|
|
241
|
+
it('tries fresh OAuth after a PAT request fails', async ()=>{
|
|
242
|
+
const fetch = vi.fn().mockRejectedValueOnce(new Error('PAT failed')).mockResolvedValueOnce(new Response(JSON.stringify(apiUsage()), {
|
|
243
|
+
status: 200
|
|
244
|
+
}));
|
|
245
|
+
const rpc = vi.fn();
|
|
246
|
+
const result = await probeCodexCapacity({
|
|
247
|
+
...context,
|
|
248
|
+
readFile: async ()=>JSON.stringify({
|
|
249
|
+
personal_access_token: 'pat-secret',
|
|
250
|
+
tokens: {
|
|
251
|
+
access_token: 'oauth-secret',
|
|
252
|
+
account_id: 'acct',
|
|
253
|
+
expires_at: 1_800_000_000
|
|
254
|
+
}
|
|
255
|
+
}),
|
|
256
|
+
fetch,
|
|
257
|
+
rpc,
|
|
258
|
+
now: ()=>new Date(checkedAt)
|
|
259
|
+
});
|
|
260
|
+
expect(fetch).toHaveBeenCalledTimes(2);
|
|
261
|
+
expect(result.available).toBe('yes');
|
|
262
|
+
expect(rpc).not.toHaveBeenCalled();
|
|
263
|
+
});
|
|
264
|
+
it('uses hardened read-only app-server arguments and both account methods', async ()=>{
|
|
265
|
+
const rpc = vi.fn(async ()=>({
|
|
266
|
+
rateLimits: {
|
|
267
|
+
rateLimits: {
|
|
268
|
+
primary: {
|
|
269
|
+
usedPercent: 5,
|
|
270
|
+
windowDurationMins: 300,
|
|
271
|
+
resetsAt: null
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
account: {
|
|
276
|
+
account: {
|
|
277
|
+
type: 'chatgpt'
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}));
|
|
281
|
+
await probeCodexCapacity({
|
|
282
|
+
...context,
|
|
283
|
+
readFile: async ()=>'{}',
|
|
284
|
+
rpc
|
|
285
|
+
});
|
|
286
|
+
const messages = rpc.mock.calls[0][0];
|
|
287
|
+
expect(messages.map((message)=>message.method)).toEqual([
|
|
288
|
+
'initialize',
|
|
289
|
+
'initialized',
|
|
290
|
+
'account/rateLimits/read',
|
|
291
|
+
'account/read'
|
|
292
|
+
]);
|
|
293
|
+
expect(JSON.stringify(messages)).not.toMatch(/prompt|turn\/start/);
|
|
294
|
+
expect(CODEX_APP_SERVER_ARGS).toEqual([
|
|
295
|
+
'-s',
|
|
296
|
+
'read-only',
|
|
297
|
+
'-a',
|
|
298
|
+
'untrusted',
|
|
299
|
+
'app-server'
|
|
300
|
+
]);
|
|
301
|
+
});
|
|
302
|
+
it('uses account/read to distinguish logged-out CLI state', async ()=>{
|
|
303
|
+
const result = await probeCodexCapacity({
|
|
304
|
+
...context,
|
|
305
|
+
readFile: async ()=>'{}',
|
|
306
|
+
rpc: async ()=>({
|
|
307
|
+
rateLimits: {},
|
|
308
|
+
account: {
|
|
309
|
+
account: null
|
|
310
|
+
}
|
|
311
|
+
})
|
|
312
|
+
});
|
|
313
|
+
expect(result).toMatchObject({
|
|
314
|
+
authenticated: false,
|
|
315
|
+
available: 'unknown'
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
it('never exposes tokens or raw auth content through failures', async ()=>{
|
|
319
|
+
const secrets = [
|
|
320
|
+
'pat-secret-value',
|
|
321
|
+
'oauth-secret-value',
|
|
322
|
+
'refresh-secret-value'
|
|
323
|
+
];
|
|
324
|
+
const result = await probeCodexCapacity({
|
|
325
|
+
...context,
|
|
326
|
+
readFile: async ()=>JSON.stringify({
|
|
327
|
+
personal_access_token: secrets[0],
|
|
328
|
+
tokens: {
|
|
329
|
+
access_token: secrets[1],
|
|
330
|
+
refresh_token: secrets[2]
|
|
331
|
+
}
|
|
332
|
+
}),
|
|
333
|
+
fetch: vi.fn().mockRejectedValue(new Error(secrets.join(' '))),
|
|
334
|
+
rpc: async ()=>{
|
|
335
|
+
throw new Error(secrets.join(' '));
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
const output = JSON.stringify(result);
|
|
339
|
+
for (const secret of secrets)expect(output).not.toContain(secret);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
//# sourceMappingURL=codex.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/capacity/codex.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\nimport {\n CODEX_APP_SERVER_ARGS,\n parseUsage,\n probeCodexCapacity,\n resolveCodexAuthPath,\n toRateWindow\n} from '../../capacity/codex.js';\n\nconst checkedAt = '2026-08-20T10:00:00.000Z';\nconst context = { installed: true, checkedAt };\n\nfunction apiUsage(overrides: Record<string, unknown> = {}) {\n return {\n rate_limit: {\n primary_window: { used_percent: 20, limit_window_seconds: 18_000, reset_at: 1_787_220_000 },\n secondary_window: { used_percent: 60, limit_window_seconds: 604_800, reset_at: 1_787_824_800 },\n ...overrides\n },\n credits: { balance: 12.5 },\n additional_rate_limits: [{\n limit_name: 'reviews',\n rate_limit: {\n primary_window: { used_percent: 10, limit_window_seconds: 3_600, reset_at: 1_787_220_000 }\n }\n }]\n };\n}\n\ndescribe('Codex auth resolution', () => {\n it('uses CODEX_HOME before HOME', () => {\n expect(resolveCodexAuthPath({ CODEX_HOME: '/custom/codex', HOME: '/users/test' })).toBe('/custom/codex/auth.json');\n });\n\n it('falls back to ~/.codex/auth.json', () => {\n expect(resolveCodexAuthPath({ HOME: '/users/test' })).toBe('/users/test/.codex/auth.json');\n });\n});\n\ndescribe('Codex API usage mapping', () => {\n it('converts an API window without treating missing data as zero', () => {\n expect(toRateWindow({ used_percent: 25, limit_window_seconds: 18_000, reset_at: 1_787_220_000 }, 'session', 'Session')).toEqual({\n id: 'session', label: 'Session', durationMinutes: 300, usedPercent: 25,\n resetsAt: '2026-08-20T10:00:00.000Z'\n });\n expect(toRateWindow({}, 'session', 'Session')).toMatchObject({ usedPercent: null });\n });\n\n it('maps session, weekly, credits, and extra limits', () => {\n const snapshot = parseUsage(apiUsage(), 'pat');\n expect(snapshot).toMatchObject({ source: 'pat', creditsRemaining: 12.5 });\n expect(snapshot.windows).toEqual([\n expect.objectContaining({ id: 'session', durationMinutes: 300 }),\n expect.objectContaining({ id: 'weekly', durationMinutes: 10080 }),\n expect.objectContaining({ id: 'reviews:primary', durationMinutes: 60 })\n ]);\n });\n\n it('represents missing limits as unavailable rather than zero', () => {\n const snapshot = parseUsage({ credits: {} }, 'oauth');\n expect(snapshot.windows).toEqual([]);\n expect(snapshot.creditsRemaining).toBeNull();\n });\n});\n\ndescribe('tiered Codex probing', () => {\n it('selects PAT, calls whoami then usage, and never invokes the CLI', async () => {\n const fetch = vi.fn()\n .mockResolvedValueOnce(new Response(JSON.stringify({ chatgpt_account_id: 'acct-1' }), { status: 200 }))\n .mockResolvedValueOnce(new Response(JSON.stringify(apiUsage()), { status: 200 }));\n const rpc = vi.fn();\n const result = await probeCodexCapacity({\n ...context, readFile: async () => JSON.stringify({\n personal_access_token: 'pat-secret',\n tokens: { access_token: 'ignored-oauth', account_id: 'ignored-account' }\n }), fetch, rpc\n });\n expect(fetch).toHaveBeenCalledTimes(2);\n expect(fetch.mock.calls[0][0]).toBe('https://auth.openai.com/api/accounts/v1/user-auth-credential/whoami');\n expect(fetch.mock.calls[1][0]).toBe('https://chatgpt.com/backend-api/wham/usage');\n expect(fetch.mock.calls[1][1].headers).toMatchObject({ Authorization: 'Bearer pat-secret', 'ChatGPT-Account-Id': 'acct-1' });\n expect(rpc).not.toHaveBeenCalled();\n expect(result).toMatchObject({ provider: 'codex', available: 'yes', creditsRemaining: 12.5, authenticated: true });\n expect(result.windows.map(window => window.id)).toEqual(['session', 'weekly', 'reviews:primary']);\n });\n\n it('selects a fresh OAuth token without calling whoami', async () => {\n const fetch = vi.fn().mockResolvedValue(new Response(JSON.stringify(apiUsage()), { status: 200 }));\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => JSON.stringify({ tokens: { access_token: 'oauth-secret', account_id: 'acct-2', expires_at: 1_800_000_000 } }),\n fetch,\n now: () => new Date('2026-08-20T10:00:00.000Z')\n });\n expect(fetch).toHaveBeenCalledOnce();\n expect(fetch.mock.calls[0][1].headers).toMatchObject({ Authorization: 'Bearer oauth-secret', 'ChatGPT-Account-Id': 'acct-2' });\n expect(result.available).toBe('yes');\n });\n\n it.each([\n ['missing auth file', async () => { throw Object.assign(new Error('missing'), { code: 'ENOENT' }); }],\n ['stale OAuth token', async () => JSON.stringify({ tokens: { access_token: 'stale-secret', account_id: 'acct', expires_at: 1 } })],\n ['OAuth 401', async () => JSON.stringify({ tokens: { access_token: 'oauth-secret', account_id: 'acct', expires_at: 1_800_000_000 } })]\n ])('falls back to the CLI for %s', async (name, readFile) => {\n const fetch = vi.fn().mockResolvedValue(new Response('', { status: name === 'OAuth 401' ? 401 : 200 }));\n const rpc = vi.fn(async () => ({\n rateLimits: { rateLimits: { primary: { usedPercent: 5, windowDurationMins: 300, resetsAt: null } } },\n account: { account: { type: 'chatgpt' } }\n }));\n const result = await probeCodexCapacity({ ...context, readFile, fetch, rpc, now: () => new Date(checkedAt) });\n expect(rpc).toHaveBeenCalledOnce();\n expect(result.windows).toHaveLength(1);\n });\n\n it('falls back to CLI if PAT requests fail', async () => {\n const rpc = vi.fn(async () => ({ rateLimits: {}, account: { account: null } }));\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => JSON.stringify({ personal_access_token: 'pat-secret' }),\n fetch: vi.fn().mockRejectedValue(new Error('network failure pat-secret')),\n rpc\n });\n expect(rpc).toHaveBeenCalledOnce();\n expect(result.available).toBe('unknown');\n });\n\n it('tries fresh OAuth after a PAT request fails', async () => {\n const fetch = vi.fn()\n .mockRejectedValueOnce(new Error('PAT failed'))\n .mockResolvedValueOnce(new Response(JSON.stringify(apiUsage()), { status: 200 }));\n const rpc = vi.fn();\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => JSON.stringify({\n personal_access_token: 'pat-secret',\n tokens: { access_token: 'oauth-secret', account_id: 'acct', expires_at: 1_800_000_000 }\n }),\n fetch,\n rpc,\n now: () => new Date(checkedAt)\n });\n expect(fetch).toHaveBeenCalledTimes(2);\n expect(result.available).toBe('yes');\n expect(rpc).not.toHaveBeenCalled();\n });\n\n it('uses hardened read-only app-server arguments and both account methods', async () => {\n const rpc = vi.fn(async () => ({\n rateLimits: { rateLimits: { primary: { usedPercent: 5, windowDurationMins: 300, resetsAt: null } } },\n account: { account: { type: 'chatgpt' } }\n }));\n await probeCodexCapacity({ ...context, readFile: async () => '{}', rpc });\n const messages = rpc.mock.calls[0][0];\n expect(messages.map(message => message.method)).toEqual([\n 'initialize', 'initialized', 'account/rateLimits/read', 'account/read'\n ]);\n expect(JSON.stringify(messages)).not.toMatch(/prompt|turn\\/start/);\n expect(CODEX_APP_SERVER_ARGS).toEqual(['-s', 'read-only', '-a', 'untrusted', 'app-server']);\n });\n\n it('uses account/read to distinguish logged-out CLI state', async () => {\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => '{}',\n rpc: async () => ({ rateLimits: {}, account: { account: null } })\n });\n expect(result).toMatchObject({ authenticated: false, available: 'unknown' });\n });\n\n it('never exposes tokens or raw auth content through failures', async () => {\n const secrets = ['pat-secret-value', 'oauth-secret-value', 'refresh-secret-value'];\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => JSON.stringify({\n personal_access_token: secrets[0],\n tokens: { access_token: secrets[1], refresh_token: secrets[2] }\n }),\n fetch: vi.fn().mockRejectedValue(new Error(secrets.join(' '))),\n rpc: async () => { throw new Error(secrets.join(' ')); }\n });\n const output = JSON.stringify(result);\n for (const secret of secrets) expect(output).not.toContain(secret);\n });\n});\n"],"names":["describe","expect","it","vi","CODEX_APP_SERVER_ARGS","parseUsage","probeCodexCapacity","resolveCodexAuthPath","toRateWindow","checkedAt","context","installed","apiUsage","overrides","rate_limit","primary_window","used_percent","limit_window_seconds","reset_at","secondary_window","credits","balance","additional_rate_limits","limit_name","CODEX_HOME","HOME","toBe","toEqual","id","label","durationMinutes","usedPercent","resetsAt","toMatchObject","snapshot","source","creditsRemaining","windows","objectContaining","toBeNull","fetch","fn","mockResolvedValueOnce","Response","JSON","stringify","chatgpt_account_id","status","rpc","result","readFile","personal_access_token","tokens","access_token","account_id","toHaveBeenCalledTimes","mock","calls","headers","Authorization","not","toHaveBeenCalled","provider","available","authenticated","map","window","mockResolvedValue","expires_at","now","Date","toHaveBeenCalledOnce","each","Object","assign","Error","code","name","rateLimits","primary","windowDurationMins","account","type","toHaveLength","mockRejectedValue","mockRejectedValueOnce","messages","message","method","toMatch","secrets","refresh_token","join","output","secret","toContain"],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { getCodexCapacityReport } from '../../capacity/index.js';
|
|
3
|
+
const checkedAt = '2026-08-09T10:00:00.000Z';
|
|
4
|
+
describe('getCodexCapacityReport', ()=>{
|
|
5
|
+
it('checks Codex installation before probing', async ()=>{
|
|
6
|
+
const probe = vi.fn(async (context)=>({
|
|
7
|
+
provider: 'codex',
|
|
8
|
+
generatedAt: context.checkedAt,
|
|
9
|
+
authenticated: true,
|
|
10
|
+
available: 'yes',
|
|
11
|
+
windows: [],
|
|
12
|
+
creditsRemaining: null
|
|
13
|
+
}));
|
|
14
|
+
const report = await getCodexCapacityReport({
|
|
15
|
+
now: ()=>new Date(checkedAt),
|
|
16
|
+
path: '/usr/bin:/opt/bin',
|
|
17
|
+
access: async (target)=>{
|
|
18
|
+
if (target !== '/opt/bin/codex') throw new Error('missing');
|
|
19
|
+
},
|
|
20
|
+
probe
|
|
21
|
+
});
|
|
22
|
+
expect(probe).toHaveBeenCalledWith({
|
|
23
|
+
installed: true,
|
|
24
|
+
checkedAt
|
|
25
|
+
});
|
|
26
|
+
expect(report).toMatchObject({
|
|
27
|
+
provider: 'codex',
|
|
28
|
+
generatedAt: checkedAt,
|
|
29
|
+
available: 'yes'
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
it('redacts unexpected probe failures into a stable unknown result', async ()=>{
|
|
33
|
+
const report = await getCodexCapacityReport({
|
|
34
|
+
now: ()=>new Date(checkedAt),
|
|
35
|
+
path: '',
|
|
36
|
+
probe: async ()=>{
|
|
37
|
+
throw new Error('private provider response');
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
expect(report).toMatchObject({
|
|
41
|
+
provider: 'codex',
|
|
42
|
+
available: 'unknown',
|
|
43
|
+
authenticated: null,
|
|
44
|
+
windows: []
|
|
45
|
+
});
|
|
46
|
+
expect(JSON.stringify(report)).not.toContain('private provider response');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=index.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/capacity/index.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\nimport { getCodexCapacityReport } from '../../capacity/index.js';\n\nconst checkedAt = '2026-08-09T10:00:00.000Z';\n\ndescribe('getCodexCapacityReport', () => {\n it('checks Codex installation before probing', async () => {\n const probe = vi.fn(async context => ({\n provider: 'codex', generatedAt: context.checkedAt,\n authenticated: true, available: 'yes' as const, windows: [], creditsRemaining: null\n }));\n\n const report = await getCodexCapacityReport({\n now: () => new Date(checkedAt),\n path: '/usr/bin:/opt/bin',\n access: async target => {\n if (target !== '/opt/bin/codex') throw new Error('missing');\n },\n probe\n });\n\n expect(probe).toHaveBeenCalledWith({ installed: true, checkedAt });\n expect(report).toMatchObject({ provider: 'codex', generatedAt: checkedAt, available: 'yes' });\n });\n\n it('redacts unexpected probe failures into a stable unknown result', async () => {\n const report = await getCodexCapacityReport({\n now: () => new Date(checkedAt),\n path: '',\n probe: async () => { throw new Error('private provider response'); }\n });\n\n expect(report).toMatchObject({\n provider: 'codex', available: 'unknown', authenticated: null, windows: []\n });\n expect(JSON.stringify(report)).not.toContain('private provider response');\n });\n});\n"],"names":["describe","expect","it","vi","getCodexCapacityReport","checkedAt","probe","fn","context","provider","generatedAt","authenticated","available","windows","creditsRemaining","report","now","Date","path","access","target","Error","toHaveBeenCalledWith","installed","toMatchObject","JSON","stringify","not","toContain"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAS;AAClD,SAASC,sBAAsB,QAAQ,0BAA0B;AAEjE,MAAMC,YAAY;AAElBL,SAAS,0BAA0B;IACjCE,GAAG,4CAA4C;QAC7C,MAAMI,QAAQH,GAAGI,EAAE,CAAC,OAAMC,UAAY,CAAA;gBACpCC,UAAU;gBAASC,aAAaF,QAAQH,SAAS;gBACjDM,eAAe;gBAAMC,WAAW;gBAAgBC,SAAS,EAAE;gBAAEC,kBAAkB;YACjF,CAAA;QAEA,MAAMC,SAAS,MAAMX,uBAAuB;YAC1CY,KAAK,IAAM,IAAIC,KAAKZ;YACpBa,MAAM;YACNC,QAAQ,OAAMC;gBACZ,IAAIA,WAAW,kBAAkB,MAAM,IAAIC,MAAM;YACnD;YACAf;QACF;QAEAL,OAAOK,OAAOgB,oBAAoB,CAAC;YAAEC,WAAW;YAAMlB;QAAU;QAChEJ,OAAOc,QAAQS,aAAa,CAAC;YAAEf,UAAU;YAASC,aAAaL;YAAWO,WAAW;QAAM;IAC7F;IAEAV,GAAG,kEAAkE;QACnE,MAAMa,SAAS,MAAMX,uBAAuB;YAC1CY,KAAK,IAAM,IAAIC,KAAKZ;YACpBa,MAAM;YACNZ,OAAO;gBAAc,MAAM,IAAIe,MAAM;YAA8B;QACrE;QAEApB,OAAOc,QAAQS,aAAa,CAAC;YAC3Bf,UAAU;YAASG,WAAW;YAAWD,eAAe;YAAME,SAAS,EAAE;QAC3E;QACAZ,OAAOwB,KAAKC,SAAS,CAACX,SAASY,GAAG,CAACC,SAAS,CAAC;IAC/C;AACF"}
|
|
@@ -18,11 +18,11 @@ function dbPath() {
|
|
|
18
18
|
return path.join(root, 'state', 'agents.db');
|
|
19
19
|
}
|
|
20
20
|
describe('durable agents schema', ()=>{
|
|
21
|
-
it('migrates to version
|
|
21
|
+
it('migrates to version 4 with durable constraints and indexes', ()=>{
|
|
22
22
|
const connection = new DatabaseConnection({
|
|
23
23
|
dbPath: dbPath()
|
|
24
24
|
});
|
|
25
|
-
expect(getSchemaVersion(connection)).toBe(
|
|
25
|
+
expect(getSchemaVersion(connection)).toBe(4);
|
|
26
26
|
const table = connection.queryOne("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'durable_agents'");
|
|
27
27
|
expect(table?.sql).toContain("DEFAULT 'durable'");
|
|
28
28
|
expect(table?.sql).toContain("state IN ('ready','running','degraded')");
|
|
@@ -32,6 +32,34 @@ describe('durable agents schema', ()=>{
|
|
|
32
32
|
]));
|
|
33
33
|
connection.close();
|
|
34
34
|
});
|
|
35
|
+
it('upgrades a version 3 database to nullable, unique provider sessions', ()=>{
|
|
36
|
+
const file = dbPath();
|
|
37
|
+
fs.mkdirSync(path.dirname(file), {
|
|
38
|
+
recursive: true
|
|
39
|
+
});
|
|
40
|
+
const raw = new Database(file);
|
|
41
|
+
raw.exec(`
|
|
42
|
+
CREATE TABLE durable_agents (
|
|
43
|
+
id TEXT PRIMARY KEY,
|
|
44
|
+
provider_session_id TEXT NOT NULL UNIQUE
|
|
45
|
+
);
|
|
46
|
+
INSERT INTO durable_agents (id, provider_session_id) VALUES ('existing', 'existing-session');
|
|
47
|
+
PRAGMA user_version = 3;
|
|
48
|
+
`);
|
|
49
|
+
raw.close();
|
|
50
|
+
const connection = new DatabaseConnection({
|
|
51
|
+
dbPath: file
|
|
52
|
+
});
|
|
53
|
+
expect(getSchemaVersion(connection)).toBe(4);
|
|
54
|
+
expect(()=>connection.execute('INSERT INTO durable_agents (id, provider_session_id) VALUES (?, NULL)', [
|
|
55
|
+
'codex'
|
|
56
|
+
])).not.toThrow();
|
|
57
|
+
expect(()=>connection.execute('INSERT INTO durable_agents (id, provider_session_id) VALUES (?, ?)', [
|
|
58
|
+
'duplicate',
|
|
59
|
+
'existing-session'
|
|
60
|
+
])).toThrow(/UNIQUE/i);
|
|
61
|
+
connection.close();
|
|
62
|
+
});
|
|
35
63
|
it('enforces case-insensitive names and active-run consistency but permits new providers', ()=>{
|
|
36
64
|
const connection = new DatabaseConnection({
|
|
37
65
|
dbPath: dbPath()
|
|
@@ -68,7 +96,7 @@ describe('readonly DatabaseConnection', ()=>{
|
|
|
68
96
|
dbPath: file,
|
|
69
97
|
readonly: true
|
|
70
98
|
});
|
|
71
|
-
expect(readonly.queryOne('PRAGMA user_version')?.user_version).toBe(
|
|
99
|
+
expect(readonly.queryOne('PRAGMA user_version')?.user_version).toBe(4);
|
|
72
100
|
readonly.close();
|
|
73
101
|
expect(fs.statSync(file).mtimeMs).toBe(before);
|
|
74
102
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/__tests__/database/DurableAgentsDatabase.test.ts"],"sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport Database from 'better-sqlite3';\nimport { afterEach, describe, expect, it } from 'vitest';\nimport { DatabaseConnection } from '../../database/connection.js';\nimport { getSchemaVersion } from '../../database/schema.js';\n\nconst roots: string[] = [];\n\nafterEach(() => {\n for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });\n});\n\nfunction dbPath(): string {\n const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-db-'));\n roots.push(root);\n return path.join(root, 'state', 'agents.db');\n}\n\ndescribe('durable agents schema', () => {\n it('migrates to version
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/database/DurableAgentsDatabase.test.ts"],"sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport Database from 'better-sqlite3';\nimport { afterEach, describe, expect, it } from 'vitest';\nimport { DatabaseConnection } from '../../database/connection.js';\nimport { getSchemaVersion } from '../../database/schema.js';\n\nconst roots: string[] = [];\n\nafterEach(() => {\n for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });\n});\n\nfunction dbPath(): string {\n const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-db-'));\n roots.push(root);\n return path.join(root, 'state', 'agents.db');\n}\n\ndescribe('durable agents schema', () => {\n it('migrates to version 4 with durable constraints and indexes', () => {\n const connection = new DatabaseConnection({ dbPath: dbPath() });\n expect(getSchemaVersion(connection)).toBe(4);\n const table = connection.queryOne<{ sql: string }>(\n \"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'durable_agents'\",\n );\n expect(table?.sql).toContain(\"DEFAULT 'durable'\");\n expect(table?.sql).toContain(\"state IN ('ready','running','degraded')\");\n expect(connection.query<{ name: string }>(\n \"SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'durable_agents'\",\n ).map(({ name }) => name)).toEqual(expect.arrayContaining([\n 'idx_durable_agents_state', 'idx_durable_agents_list',\n ]));\n connection.close();\n });\n\n it('upgrades a version 3 database to nullable, unique provider sessions', () => {\n const file = dbPath();\n fs.mkdirSync(path.dirname(file), { recursive: true });\n const raw = new Database(file);\n raw.exec(`\n CREATE TABLE durable_agents (\n id TEXT PRIMARY KEY,\n provider_session_id TEXT NOT NULL UNIQUE\n );\n INSERT INTO durable_agents (id, provider_session_id) VALUES ('existing', 'existing-session');\n PRAGMA user_version = 3;\n `);\n raw.close();\n\n const connection = new DatabaseConnection({ dbPath: file });\n expect(getSchemaVersion(connection)).toBe(4);\n expect(() => connection.execute(\n 'INSERT INTO durable_agents (id, provider_session_id) VALUES (?, NULL)', ['codex'],\n )).not.toThrow();\n expect(() => connection.execute(\n 'INSERT INTO durable_agents (id, provider_session_id) VALUES (?, ?)', ['duplicate', 'existing-session'],\n )).toThrow(/UNIQUE/i);\n connection.close();\n });\n\n it('enforces case-insensitive names and active-run consistency but permits new providers', () => {\n const connection = new DatabaseConnection({ dbPath: dbPath() });\n const insert = (name: string, provider: string, state = 'ready') => connection.execute(`\n INSERT INTO durable_agents (\n id, name, provider, mode, cwd, provider_session_id, state, session_health,\n created_at, updated_at\n ) VALUES (?, ?, ?, 'durable', '/tmp', ?, ?, 'uninitialized', ?, ?)\n `, [crypto.randomUUID(), name, provider, crypto.randomUUID(), state, new Date().toISOString(), new Date().toISOString()]);\n expect(() => insert('Alpha', 'future-provider')).not.toThrow();\n expect(() => insert('alpha', 'claude')).toThrow(/UNIQUE/i);\n expect(() => insert('Broken', 'claude', 'running')).toThrow(/CHECK/i);\n connection.close();\n });\n});\n\ndescribe('readonly DatabaseConnection', () => {\n it('opens an already migrated database without changing it', () => {\n const file = dbPath();\n const writable = new DatabaseConnection({ dbPath: file });\n writable.close();\n const before = fs.statSync(file).mtimeMs;\n const readonly = new DatabaseConnection({ dbPath: file, readonly: true });\n expect(readonly.queryOne<{ user_version: number }>('PRAGMA user_version')?.user_version).toBe(4);\n readonly.close();\n expect(fs.statSync(file).mtimeMs).toBe(before);\n });\n\n it('does not create a missing database or migrate an old one', () => {\n const missing = dbPath();\n expect(() => new DatabaseConnection({ dbPath: missing, readonly: true })).toThrow(/readonly.*exist/i);\n expect(fs.existsSync(missing)).toBe(false);\n\n fs.mkdirSync(path.dirname(missing), { recursive: true });\n const raw = new Database(missing);\n raw.pragma('user_version = 2');\n raw.close();\n expect(() => new DatabaseConnection({ dbPath: missing, readonly: true })).toThrow(/schema version 3/i);\n expect(new Database(missing, { readonly: true }).pragma('user_version', { simple: true })).toBe(2);\n });\n});\n"],"names":["fs","os","path","Database","afterEach","describe","expect","it","DatabaseConnection","getSchemaVersion","roots","root","splice","rmSync","recursive","force","dbPath","mkdtempSync","join","tmpdir","push","connection","toBe","table","queryOne","sql","toContain","query","map","name","toEqual","arrayContaining","close","file","mkdirSync","dirname","raw","exec","execute","not","toThrow","insert","provider","state","crypto","randomUUID","Date","toISOString","writable","before","statSync","mtimeMs","readonly","user_version","missing","existsSync","pragma","simple"],"mappings":"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"}
|