aismemory 0.5.2 → 0.5.4
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__/auto-enroll-key-e2e.test.d.ts +1 -0
- package/dist/__tests__/auto-enroll-key-e2e.test.js +116 -0
- package/dist/__tests__/auto-enroll-key-e2e.test.js.map +1 -0
- package/dist/__tests__/auto-enroll-key.test.d.ts +1 -0
- package/dist/__tests__/auto-enroll-key.test.js +165 -0
- package/dist/__tests__/auto-enroll-key.test.js.map +1 -0
- package/dist/__tests__/device-flow-pending-400.test.d.ts +1 -0
- package/dist/__tests__/device-flow-pending-400.test.js +187 -0
- package/dist/__tests__/device-flow-pending-400.test.js.map +1 -0
- package/dist/__tests__/device-flow-recovery.test.js +9 -5
- package/dist/__tests__/device-flow-recovery.test.js.map +1 -1
- package/dist/__tests__/device-poll.test.d.ts +12 -0
- package/dist/__tests__/device-poll.test.js +191 -0
- package/dist/__tests__/device-poll.test.js.map +1 -0
- package/dist/__tests__/mcp-init-no-auth.test.js +14 -1
- package/dist/__tests__/mcp-init-no-auth.test.js.map +1 -1
- package/dist/__tests__/resolve-agent-preference-e2e.test.d.ts +1 -0
- package/dist/__tests__/resolve-agent-preference-e2e.test.js +173 -0
- package/dist/__tests__/resolve-agent-preference-e2e.test.js.map +1 -0
- package/dist/__tests__/resolve-agent-preference.test.d.ts +1 -0
- package/dist/__tests__/resolve-agent-preference.test.js +83 -0
- package/dist/__tests__/resolve-agent-preference.test.js.map +1 -0
- package/dist/device-poll.d.ts +51 -0
- package/dist/device-poll.js +77 -0
- package/dist/device-poll.js.map +1 -0
- package/dist/env-agent.d.ts +27 -0
- package/dist/env-agent.js +33 -0
- package/dist/env-agent.js.map +1 -1
- package/dist/index.js +162 -57
- package/dist/index.js.map +1 -1
- package/dist/key-auth.d.ts +30 -0
- package/dist/key-auth.js +77 -1
- package/dist/key-auth.js.map +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S2 — client-side device-poll loop tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for runPollLoop() in device-poll.ts:
|
|
5
|
+
* - Returns access_token on approval
|
|
6
|
+
* - Emits heartbeat every HEARTBEAT_INTERVAL_MS during pending
|
|
7
|
+
* - When pending_reason==='page_open', heartbeat contains 'click'
|
|
8
|
+
* - On expired_token, throws DeviceFlowRestartedError (auto-restart,
|
|
9
|
+
* never throws a plain expired error)
|
|
10
|
+
* - Throws on access_denied
|
|
11
|
+
*/
|
|
12
|
+
import { describe, it, expect } from 'vitest';
|
|
13
|
+
import { runPollLoop, DeviceFlowRestartedError, HEARTBEAT_INTERVAL_MS, } from '../device-poll.js';
|
|
14
|
+
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
15
|
+
const HANDLE = {
|
|
16
|
+
device_code: 'test-device-code-abc123',
|
|
17
|
+
verification_uri_complete: 'https://agentsandswarms.ai/activate?user_code=BCDF-GHJK',
|
|
18
|
+
user_code: 'BCDF-GHJK',
|
|
19
|
+
};
|
|
20
|
+
const NEW_HANDLE = {
|
|
21
|
+
device_code: 'new-device-code-xyz789',
|
|
22
|
+
verification_uri_complete: 'https://agentsandswarms.ai/activate?user_code=MNPQ-RSTU',
|
|
23
|
+
user_code: 'MNPQ-RSTU',
|
|
24
|
+
};
|
|
25
|
+
function makePollOnce(impl) {
|
|
26
|
+
return impl;
|
|
27
|
+
}
|
|
28
|
+
function makeStartNewFlow(impl) {
|
|
29
|
+
return impl;
|
|
30
|
+
}
|
|
31
|
+
function makeDeps(overrides = {}) {
|
|
32
|
+
const stderrLines = [];
|
|
33
|
+
const deps = {
|
|
34
|
+
pollOnce: makePollOnce(async () => ({ access_token: 'default-token' })),
|
|
35
|
+
startNewFlow: makeStartNewFlow(async () => NEW_HANDLE),
|
|
36
|
+
emitStderr: (msg) => { stderrLines.push(msg); },
|
|
37
|
+
nowMs: () => Date.now(),
|
|
38
|
+
pollIntervalMs: 0,
|
|
39
|
+
...overrides,
|
|
40
|
+
};
|
|
41
|
+
return { deps, stderrLines };
|
|
42
|
+
}
|
|
43
|
+
// ─── tests ───────────────────────────────────────────────────────────────────
|
|
44
|
+
describe('runPollLoop()', () => {
|
|
45
|
+
describe('success path', () => {
|
|
46
|
+
it('returns the token response when access_token is present', async () => {
|
|
47
|
+
const { deps } = makeDeps({
|
|
48
|
+
pollOnce: makePollOnce(async () => ({ access_token: 'jwt-token-abc', expires_in: 604800 })),
|
|
49
|
+
});
|
|
50
|
+
const result = await runPollLoop(HANDLE, deps);
|
|
51
|
+
expect(result.access_token).toBe('jwt-token-abc');
|
|
52
|
+
expect(result.expires_in).toBe(604800);
|
|
53
|
+
});
|
|
54
|
+
it('retries on authorization_pending then succeeds', async () => {
|
|
55
|
+
let count = 0;
|
|
56
|
+
const { deps } = makeDeps({
|
|
57
|
+
pollOnce: makePollOnce(async () => {
|
|
58
|
+
count++;
|
|
59
|
+
if (count < 3)
|
|
60
|
+
return { error: 'authorization_pending', pending_reason: 'page_not_opened' };
|
|
61
|
+
return { access_token: 'approved-jwt', expires_in: 604800 };
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
const result = await runPollLoop(HANDLE, deps);
|
|
65
|
+
expect(result.access_token).toBe('approved-jwt');
|
|
66
|
+
expect(count).toBe(3);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('heartbeat behavior', () => {
|
|
70
|
+
it('emits a heartbeat line after HEARTBEAT_INTERVAL_MS elapses during pending', async () => {
|
|
71
|
+
const startTime = 1_000_000;
|
|
72
|
+
let callCount = 0;
|
|
73
|
+
const times = [
|
|
74
|
+
startTime, // deadline calc
|
|
75
|
+
startTime + HEARTBEAT_INTERVAL_MS + 1, // pending check — fires heartbeat
|
|
76
|
+
startTime + HEARTBEAT_INTERVAL_MS + 1, // deadline check
|
|
77
|
+
startTime + HEARTBEAT_INTERVAL_MS + 2,
|
|
78
|
+
];
|
|
79
|
+
let timeIdx = 0;
|
|
80
|
+
const { deps, stderrLines } = makeDeps({
|
|
81
|
+
nowMs: () => times[Math.min(timeIdx++, times.length - 1)] ?? startTime,
|
|
82
|
+
pollOnce: makePollOnce(async () => {
|
|
83
|
+
callCount++;
|
|
84
|
+
if (callCount === 1)
|
|
85
|
+
return { error: 'authorization_pending', pending_reason: 'page_not_opened' };
|
|
86
|
+
return { access_token: 'jwt-after-heartbeat', expires_in: 604800 };
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
await runPollLoop(HANDLE, deps);
|
|
90
|
+
expect(stderrLines.some((l) => l.includes('Waiting'))).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
it('emits a "click" coaching message when pending_reason is page_open', async () => {
|
|
93
|
+
const startTime = 2_000_000;
|
|
94
|
+
let callCount = 0;
|
|
95
|
+
const times = [
|
|
96
|
+
startTime,
|
|
97
|
+
startTime + HEARTBEAT_INTERVAL_MS + 1,
|
|
98
|
+
startTime + HEARTBEAT_INTERVAL_MS + 1,
|
|
99
|
+
startTime + HEARTBEAT_INTERVAL_MS + 2,
|
|
100
|
+
];
|
|
101
|
+
let timeIdx = 0;
|
|
102
|
+
const { deps, stderrLines } = makeDeps({
|
|
103
|
+
nowMs: () => times[Math.min(timeIdx++, times.length - 1)] ?? startTime,
|
|
104
|
+
pollOnce: makePollOnce(async () => {
|
|
105
|
+
callCount++;
|
|
106
|
+
if (callCount === 1)
|
|
107
|
+
return { error: 'authorization_pending', pending_reason: 'page_open' };
|
|
108
|
+
return { access_token: 'jwt-after-click', expires_in: 604800 };
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
await runPollLoop(HANDLE, deps);
|
|
112
|
+
expect(stderrLines.some((l) => l.toLowerCase().includes('click'))).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
it('does NOT emit a heartbeat on every poll (throttled)', async () => {
|
|
115
|
+
// Time frozen at 1ms: gap from lastHeartbeatAt=0 is 1ms < 30_000ms
|
|
116
|
+
let callCount = 0;
|
|
117
|
+
const { deps, stderrLines } = makeDeps({
|
|
118
|
+
nowMs: () => 1,
|
|
119
|
+
pollOnce: makePollOnce(async () => {
|
|
120
|
+
callCount++;
|
|
121
|
+
if (callCount < 5)
|
|
122
|
+
return { error: 'authorization_pending', pending_reason: 'page_not_opened' };
|
|
123
|
+
return { access_token: 'jwt-final', expires_in: 604800 };
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
await runPollLoop(HANDLE, deps);
|
|
127
|
+
// 4 pending responses but 0 heartbeats (time never passed the interval)
|
|
128
|
+
expect(stderrLines.filter((l) => l.includes('Waiting')).length).toBe(0);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe('auto-restart on expired_token', () => {
|
|
132
|
+
it('throws DeviceFlowRestartedError (not a plain Error) on expired_token', async () => {
|
|
133
|
+
const { deps } = makeDeps({
|
|
134
|
+
pollOnce: makePollOnce(async () => ({ error: 'expired_token' })),
|
|
135
|
+
});
|
|
136
|
+
await expect(runPollLoop(HANDLE, deps)).rejects.toBeInstanceOf(DeviceFlowRestartedError);
|
|
137
|
+
});
|
|
138
|
+
it('starts a new flow on expired_token', async () => {
|
|
139
|
+
let startCalled = 0;
|
|
140
|
+
const { deps } = makeDeps({
|
|
141
|
+
pollOnce: makePollOnce(async () => ({ error: 'expired_token' })),
|
|
142
|
+
startNewFlow: makeStartNewFlow(async () => { startCalled++; return NEW_HANDLE; }),
|
|
143
|
+
});
|
|
144
|
+
await expect(runPollLoop(HANDLE, deps)).rejects.toThrow();
|
|
145
|
+
expect(startCalled).toBe(1);
|
|
146
|
+
});
|
|
147
|
+
it('DeviceFlowRestartedError carries the new verification URI', async () => {
|
|
148
|
+
const { deps } = makeDeps({
|
|
149
|
+
pollOnce: makePollOnce(async () => ({ error: 'expired_token' })),
|
|
150
|
+
});
|
|
151
|
+
let caught;
|
|
152
|
+
try {
|
|
153
|
+
await runPollLoop(HANDLE, deps);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
caught = err;
|
|
157
|
+
}
|
|
158
|
+
expect(caught).toBeInstanceOf(DeviceFlowRestartedError);
|
|
159
|
+
const restarted = caught;
|
|
160
|
+
expect(restarted.newVerificationUri).toBe(NEW_HANDLE.verification_uri_complete);
|
|
161
|
+
expect(restarted.newUserCode).toBe(NEW_HANDLE.user_code);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
describe('access_denied', () => {
|
|
165
|
+
it('throws a plain Error on access_denied (not DeviceFlowRestartedError)', async () => {
|
|
166
|
+
let startCalled = 0;
|
|
167
|
+
const { deps } = makeDeps({
|
|
168
|
+
pollOnce: makePollOnce(async () => ({ error: 'access_denied' })),
|
|
169
|
+
startNewFlow: makeStartNewFlow(async () => { startCalled++; return NEW_HANDLE; }),
|
|
170
|
+
});
|
|
171
|
+
await expect(runPollLoop(HANDLE, deps)).rejects.toThrow('denied');
|
|
172
|
+
expect(startCalled).toBe(0);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
describe('slow_down', () => {
|
|
176
|
+
it('retries after slow_down and eventually succeeds', async () => {
|
|
177
|
+
let count = 0;
|
|
178
|
+
const { deps } = makeDeps({
|
|
179
|
+
pollOnce: makePollOnce(async () => {
|
|
180
|
+
count++;
|
|
181
|
+
if (count === 1)
|
|
182
|
+
return { error: 'slow_down' };
|
|
183
|
+
return { access_token: 'jwt-after-slowdown', expires_in: 604800 };
|
|
184
|
+
}),
|
|
185
|
+
});
|
|
186
|
+
const result = await runPollLoop(HANDLE, deps);
|
|
187
|
+
expect(result.access_token).toBe('jwt-after-slowdown');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
//# sourceMappingURL=device-poll.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-poll.test.js","sourceRoot":"","sources":["../../src/__tests__/device-poll.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { mkdtempSync, rmSync, existsSync } from 'node:fs';
|
|
2
|
+
import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join, resolve } from 'path';
|
|
5
5
|
import { describe, it, expect } from 'vitest';
|
|
6
|
+
function readPackageVersion() {
|
|
7
|
+
const packageJsonPath = resolve(__dirname, '..', '..', 'package.json');
|
|
8
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
9
|
+
if (typeof packageJson !== 'object' ||
|
|
10
|
+
packageJson === null ||
|
|
11
|
+
!('version' in packageJson) ||
|
|
12
|
+
typeof packageJson.version !== 'string' ||
|
|
13
|
+
packageJson.version.length === 0) {
|
|
14
|
+
throw new Error('Unable to load aismemory package version from package.json');
|
|
15
|
+
}
|
|
16
|
+
return packageJson.version;
|
|
17
|
+
}
|
|
6
18
|
/**
|
|
7
19
|
* Regression test for the v0.2.0/v0.3.0 bug where `npx aismemory` blocked the
|
|
8
20
|
* MCP transport handshake on user device-flow approval, exceeding Claude
|
|
@@ -64,6 +76,7 @@ describe('MCP transport boots without blocking on auth', () => {
|
|
|
64
76
|
const parsed = JSON.parse(responseLine);
|
|
65
77
|
expect(elapsed).toBeLessThan(3000);
|
|
66
78
|
expect(parsed.result?.serverInfo?.name).toBe('aismemory');
|
|
79
|
+
expect(parsed.result?.serverInfo?.version).toBe(readPackageVersion());
|
|
67
80
|
}
|
|
68
81
|
finally {
|
|
69
82
|
if (proc && !proc.killed)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-init-no-auth.test.js","sourceRoot":"","sources":["../../src/__tests__/mcp-init-no-auth.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAuC,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"mcp-init-no-auth.test.js","sourceRoot":"","sources":["../../src/__tests__/mcp-init-no-auth.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CORBOT-B0FA4A79 e2e: after a silent key re-auth, the MCP reconnects to the
|
|
3
|
+
* agent recorded in credentials.json — not the first active agent in the
|
|
4
|
+
* tenant.
|
|
5
|
+
*
|
|
6
|
+
* Spawns the built binary with near-expiry credentials bonded to agent-a and
|
|
7
|
+
* a key file present (silent re-auth path), against a fake AIS whose
|
|
8
|
+
* /v1/agents list returns agent-b FIRST. Before the fix the session
|
|
9
|
+
* reconnected to agent-b (apparent memory loss); after the fix it must come
|
|
10
|
+
* back as agent-a.
|
|
11
|
+
*/
|
|
12
|
+
import { createServer } from 'node:http';
|
|
13
|
+
import { spawn } from 'node:child_process';
|
|
14
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, existsSync, readFileSync } from 'node:fs';
|
|
15
|
+
import { tmpdir } from 'node:os';
|
|
16
|
+
import { join, resolve } from 'path';
|
|
17
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
18
|
+
import { generateAndSaveKeypair } from '../key-auth.js';
|
|
19
|
+
const NEW_TOKEN = mintJwt('fresh-after-reauth');
|
|
20
|
+
function mintJwt(marker) {
|
|
21
|
+
const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
|
|
22
|
+
const payload = Buffer.from(JSON.stringify({ sub: 'user-1', user_id: 'user-1', tenant_id: 'tenant-1', marker })).toString('base64url');
|
|
23
|
+
return `${header}.${payload}.fakesig-${marker}`;
|
|
24
|
+
}
|
|
25
|
+
describe('re-auth reconnects to the previously bonded agent', () => {
|
|
26
|
+
let fakeAis;
|
|
27
|
+
let port = 0;
|
|
28
|
+
beforeAll(async () => {
|
|
29
|
+
fakeAis = createServer((req, res) => {
|
|
30
|
+
req.on('data', () => { });
|
|
31
|
+
req.on('end', () => {
|
|
32
|
+
if (req.url === '/v1/auth/challenge' && req.method === 'POST') {
|
|
33
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
34
|
+
res.end(JSON.stringify({
|
|
35
|
+
success: true,
|
|
36
|
+
data: { nonce: 'test-nonce', exp: Math.floor(Date.now() / 1000) + 60, hmac: 'test-hmac' },
|
|
37
|
+
}));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (req.url === '/v1/auth/did-prove' && req.method === 'POST') {
|
|
41
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
42
|
+
res.end(JSON.stringify({
|
|
43
|
+
success: true,
|
|
44
|
+
data: {
|
|
45
|
+
bearerToken: NEW_TOKEN,
|
|
46
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString(),
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (req.url === '/v1/agents' && req.method === 'GET') {
|
|
52
|
+
// The previously bonded agent is deliberately NOT first.
|
|
53
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
54
|
+
res.end(JSON.stringify({
|
|
55
|
+
success: true,
|
|
56
|
+
data: {
|
|
57
|
+
agents: [
|
|
58
|
+
{ id: 'agent-b', name: 'OtherAgent', status: 'active' },
|
|
59
|
+
{ id: 'agent-a', name: 'MyAgent', status: 'active' },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
}));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (req.url?.startsWith('/v1/agents/agent-') && req.method === 'GET') {
|
|
66
|
+
const id = req.url.split('/')[3];
|
|
67
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
68
|
+
res.end(JSON.stringify({
|
|
69
|
+
success: true,
|
|
70
|
+
data: { id, name: id === 'agent-a' ? 'MyAgent' : 'OtherAgent', status: 'active' },
|
|
71
|
+
}));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
res.writeHead(404, { 'content-type': 'application/json' });
|
|
75
|
+
res.end(JSON.stringify({ success: false, error: { code: 'NOT_FOUND' } }));
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
await new Promise((r) => {
|
|
79
|
+
fakeAis.listen(0, '127.0.0.1', () => {
|
|
80
|
+
const addr = fakeAis.address();
|
|
81
|
+
if (addr && typeof addr === 'object')
|
|
82
|
+
port = addr.port;
|
|
83
|
+
r();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
afterAll(async () => {
|
|
88
|
+
await new Promise((r) => fakeAis?.close(() => r()));
|
|
89
|
+
});
|
|
90
|
+
it('whoami answers as the bonded agent, and credentials keep its id', async () => {
|
|
91
|
+
const distPath = resolve(__dirname, '..', '..', 'dist', 'index.js');
|
|
92
|
+
if (!existsSync(distPath)) {
|
|
93
|
+
throw new Error(`dist/index.js missing — run \`pnpm build\` first (looked at ${distPath})`);
|
|
94
|
+
}
|
|
95
|
+
const fakeHome = mkdtempSync(join(tmpdir(), 'aismem-agent-pref-'));
|
|
96
|
+
const aismemDir = join(fakeHome, '.aismemory');
|
|
97
|
+
mkdirSync(aismemDir, { recursive: true });
|
|
98
|
+
const credsPath = join(aismemDir, 'credentials.json');
|
|
99
|
+
writeFileSync(credsPath, JSON.stringify({
|
|
100
|
+
token: mintJwt('old-session-token'),
|
|
101
|
+
agentId: 'agent-a',
|
|
102
|
+
tenantId: 'tenant-1',
|
|
103
|
+
// Inside the staleness safety margin → forces silent key re-auth.
|
|
104
|
+
expiresAt: new Date(Date.now() + 60 * 1000).toISOString(),
|
|
105
|
+
}));
|
|
106
|
+
await generateAndSaveKeypair({
|
|
107
|
+
userId: 'user-1',
|
|
108
|
+
userDid: 'did:web:test:users:user-1',
|
|
109
|
+
keysDir: join(aismemDir, 'keys'),
|
|
110
|
+
});
|
|
111
|
+
let proc = null;
|
|
112
|
+
try {
|
|
113
|
+
proc = spawn('node', [distPath], {
|
|
114
|
+
env: { ...process.env, HOME: fakeHome, AIS_URL: `http://127.0.0.1:${port}` },
|
|
115
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
116
|
+
});
|
|
117
|
+
let stdoutBuf = '';
|
|
118
|
+
proc.stdout.on('data', (d) => { stdoutBuf += d.toString(); });
|
|
119
|
+
proc.stderr.on('data', () => { });
|
|
120
|
+
proc.stdin.write(JSON.stringify({
|
|
121
|
+
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
122
|
+
params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'agent-pref-test', version: '0.0.0' } },
|
|
123
|
+
}) + '\n');
|
|
124
|
+
await waitFor(() => stdoutBuf.includes('"id":1'), 5000, 'initialize response');
|
|
125
|
+
proc.stdin.write(JSON.stringify({
|
|
126
|
+
jsonrpc: '2.0', id: 2, method: 'tools/call',
|
|
127
|
+
params: { name: 'whoami', arguments: {} },
|
|
128
|
+
}) + '\n');
|
|
129
|
+
await waitFor(() => stdoutBuf.includes('"id":2'), 15000, 'whoami response');
|
|
130
|
+
const whoami = extractResponseText(stdoutBuf, 2);
|
|
131
|
+
expect(whoami).toContain('agent-a');
|
|
132
|
+
expect(whoami).not.toContain('agent-b');
|
|
133
|
+
const savedCreds = JSON.parse(readFileSync(credsPath, 'utf-8'));
|
|
134
|
+
expect(savedCreds.agentId).toBe('agent-a');
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
proc?.kill();
|
|
138
|
+
rmSync(fakeHome, { recursive: true, force: true });
|
|
139
|
+
}
|
|
140
|
+
}, 30000);
|
|
141
|
+
});
|
|
142
|
+
function waitFor(predicate, ms, label) {
|
|
143
|
+
return new Promise((resolveOuter, reject) => {
|
|
144
|
+
const start = Date.now();
|
|
145
|
+
const tick = () => {
|
|
146
|
+
if (predicate()) {
|
|
147
|
+
resolveOuter();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (Date.now() - start > ms) {
|
|
151
|
+
reject(new Error(`timeout waiting for ${label}`));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
setTimeout(tick, 50);
|
|
155
|
+
};
|
|
156
|
+
tick();
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function extractResponseText(buf, id) {
|
|
160
|
+
for (const line of buf.split('\n')) {
|
|
161
|
+
if (!line.includes(`"id":${id}`))
|
|
162
|
+
continue;
|
|
163
|
+
try {
|
|
164
|
+
const parsed = JSON.parse(line);
|
|
165
|
+
const text = parsed.result?.content?.[0]?.text;
|
|
166
|
+
if (typeof text === 'string')
|
|
167
|
+
return text;
|
|
168
|
+
}
|
|
169
|
+
catch { /* malformed line; skip */ }
|
|
170
|
+
}
|
|
171
|
+
return '';
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=resolve-agent-preference-e2e.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-agent-preference-e2e.test.js","sourceRoot":"","sources":["../../src/__tests__/resolve-agent-preference-e2e.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CORBOT-B0FA4A79: resolveAgent() silently reconnects to a different agent
|
|
3
|
+
* after re-auth.
|
|
4
|
+
*
|
|
5
|
+
* After a silent re-auth (or a re-bond), the client picked the FIRST active
|
|
6
|
+
* agent in the tenant instead of the agent the user was previously bonded
|
|
7
|
+
* to. A multi-agent user could be reconnected to a different agent with
|
|
8
|
+
* different memories — to the user this looks like their agent forgot
|
|
9
|
+
* everything.
|
|
10
|
+
*
|
|
11
|
+
* Fix under test: selectAgentForSession() prefers the previously bonded
|
|
12
|
+
* agentId (recovered from the saved credentials file) over first-active,
|
|
13
|
+
* and reports an explicit switch when that agent is gone. Env overrides
|
|
14
|
+
* keep their existing priority.
|
|
15
|
+
*/
|
|
16
|
+
import { describe, it, expect } from 'vitest';
|
|
17
|
+
import { selectAgentForSession } from '../env-agent.js';
|
|
18
|
+
const AGENT_A = { id: 'agent-a', name: 'MyAgent', status: 'active' };
|
|
19
|
+
const AGENT_B = { id: 'agent-b', name: 'OtherAgent', status: 'active' };
|
|
20
|
+
describe('selectAgentForSession', () => {
|
|
21
|
+
it('prefers the previously bonded agent over the first active agent', () => {
|
|
22
|
+
const selection = selectAgentForSession({
|
|
23
|
+
agents: [AGENT_B, AGENT_A], // previously bonded agent listed second
|
|
24
|
+
preferredAgentId: 'agent-a',
|
|
25
|
+
fallbackAgentId: 'agent-new',
|
|
26
|
+
});
|
|
27
|
+
expect(selection.agentId).toBe('agent-a');
|
|
28
|
+
expect(selection.reason).toBe('preferred');
|
|
29
|
+
});
|
|
30
|
+
it('falls back to first-active and reports the switch when the preferred agent is gone', () => {
|
|
31
|
+
const selection = selectAgentForSession({
|
|
32
|
+
agents: [AGENT_B],
|
|
33
|
+
preferredAgentId: 'agent-a',
|
|
34
|
+
fallbackAgentId: 'agent-new',
|
|
35
|
+
});
|
|
36
|
+
expect(selection.agentId).toBe('agent-b');
|
|
37
|
+
expect(selection.reason).toBe('first-active');
|
|
38
|
+
// The switch must be loud enough to log: it explains apparent memory loss.
|
|
39
|
+
expect(selection.notes.join(' ')).toMatch(/agent-a/);
|
|
40
|
+
expect(selection.notes.join(' ')).toMatch(/switch/i);
|
|
41
|
+
});
|
|
42
|
+
it('lets AIS_AGENT_NAME win over the previously bonded agent', () => {
|
|
43
|
+
const selection = selectAgentForSession({
|
|
44
|
+
agents: [AGENT_B, AGENT_A],
|
|
45
|
+
envAgentName: 'otheragent',
|
|
46
|
+
preferredAgentId: 'agent-a',
|
|
47
|
+
fallbackAgentId: 'agent-new',
|
|
48
|
+
});
|
|
49
|
+
expect(selection.agentId).toBe('agent-b');
|
|
50
|
+
expect(selection.reason).toBe('env-name');
|
|
51
|
+
});
|
|
52
|
+
it('keeps existing behavior with no preference: first active agent', () => {
|
|
53
|
+
const selection = selectAgentForSession({
|
|
54
|
+
agents: [AGENT_B, AGENT_A],
|
|
55
|
+
fallbackAgentId: 'agent-new',
|
|
56
|
+
});
|
|
57
|
+
expect(selection.agentId).toBe('agent-b');
|
|
58
|
+
expect(selection.reason).toBe('first-active');
|
|
59
|
+
expect(selection.notes).toHaveLength(0);
|
|
60
|
+
});
|
|
61
|
+
it('uses the fallback (fresh provisional) agent when the tenant has no active agents', () => {
|
|
62
|
+
const selection = selectAgentForSession({
|
|
63
|
+
agents: [],
|
|
64
|
+
preferredAgentId: 'agent-a',
|
|
65
|
+
fallbackAgentId: 'agent-new',
|
|
66
|
+
});
|
|
67
|
+
expect(selection.agentId).toBe('agent-new');
|
|
68
|
+
expect(selection.reason).toBe('fallback');
|
|
69
|
+
});
|
|
70
|
+
it('warns but continues when AIS_AGENT_NAME matches nothing', () => {
|
|
71
|
+
const selection = selectAgentForSession({
|
|
72
|
+
agents: [AGENT_B, AGENT_A],
|
|
73
|
+
envAgentName: 'nosuchagent',
|
|
74
|
+
preferredAgentId: 'agent-a',
|
|
75
|
+
fallbackAgentId: 'agent-new',
|
|
76
|
+
});
|
|
77
|
+
// Name miss falls through to the bonded-agent preference.
|
|
78
|
+
expect(selection.agentId).toBe('agent-a');
|
|
79
|
+
expect(selection.reason).toBe('preferred');
|
|
80
|
+
expect(selection.notes.join(' ')).toMatch(/nosuchagent/);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
//# sourceMappingURL=resolve-agent-preference.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-agent-preference.test.js","sourceRoot":"","sources":["../../src/__tests__/resolve-agent-preference.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device-flow poll loop — extracted for testability.
|
|
3
|
+
*
|
|
4
|
+
* S2 improvements over the original inline loop:
|
|
5
|
+
* - Throttled heartbeat so the human knows polling is active (one line per
|
|
6
|
+
* HEARTBEAT_INTERVAL_MS, not every 5-second poll).
|
|
7
|
+
* - Coaching message when pending_reason === 'page_open' ("you're on the
|
|
8
|
+
* bond page — click Complete bonding").
|
|
9
|
+
* - Auto-restart on expired_token: re-invokes startDeviceFlow and throws
|
|
10
|
+
* DeviceFlowRestartedError so the caller can update the surfaced URL.
|
|
11
|
+
*/
|
|
12
|
+
/** Minimum milliseconds between heartbeat stderr lines during pending polls. */
|
|
13
|
+
export declare const HEARTBEAT_INTERVAL_MS = 30000;
|
|
14
|
+
/** Thrown when the device code expires and a fresh flow has been initiated. */
|
|
15
|
+
export declare class DeviceFlowRestartedError extends Error {
|
|
16
|
+
readonly newVerificationUri: string;
|
|
17
|
+
readonly newUserCode: string;
|
|
18
|
+
readonly isDeviceFlowRestarted = true;
|
|
19
|
+
constructor(newVerificationUri: string, newUserCode: string);
|
|
20
|
+
}
|
|
21
|
+
export interface PollTokenResponse {
|
|
22
|
+
access_token?: string;
|
|
23
|
+
expires_in?: number;
|
|
24
|
+
error?: string;
|
|
25
|
+
pending_reason?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface DeviceFlowHandle {
|
|
28
|
+
device_code: string;
|
|
29
|
+
verification_uri_complete: string;
|
|
30
|
+
user_code: string;
|
|
31
|
+
}
|
|
32
|
+
export interface PollLoopDeps {
|
|
33
|
+
/** Calls the AIS token endpoint and returns the parsed response. */
|
|
34
|
+
pollOnce: (deviceCode: string) => Promise<PollTokenResponse>;
|
|
35
|
+
/** Initiates a fresh device flow and returns the new handle. */
|
|
36
|
+
startNewFlow: () => Promise<DeviceFlowHandle>;
|
|
37
|
+
/** Writes a line to stderr (the existing `stderr` helper in index.ts). */
|
|
38
|
+
emitStderr: (msg: string) => void;
|
|
39
|
+
/** Returns the current wall-clock time in milliseconds (injectable for tests). */
|
|
40
|
+
nowMs?: () => number;
|
|
41
|
+
/** Overrides the poll sleep duration (injectable for tests). */
|
|
42
|
+
pollIntervalMs?: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Poll the AIS token endpoint until the device is approved, expired, or denied.
|
|
46
|
+
*
|
|
47
|
+
* Returns the raw token response on success (access_token present).
|
|
48
|
+
* Throws DeviceFlowRestartedError on expired_token (after auto-restart).
|
|
49
|
+
* Throws Error on access_denied or hard deadline.
|
|
50
|
+
*/
|
|
51
|
+
export declare function runPollLoop(handle: DeviceFlowHandle, deps: PollLoopDeps): Promise<PollTokenResponse>;
|