aismemory 0.6.1 → 0.7.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.
Files changed (49) hide show
  1. package/dist/__tests__/agent-load-persistence.test.js +194 -56
  2. package/dist/__tests__/agent-load-persistence.test.js.map +1 -1
  3. package/dist/__tests__/agent-load-session-override-e2e.test.d.ts +1 -0
  4. package/dist/__tests__/agent-load-session-override-e2e.test.js +212 -0
  5. package/dist/__tests__/agent-load-session-override-e2e.test.js.map +1 -0
  6. package/dist/__tests__/agent-preference.test.d.ts +1 -0
  7. package/dist/__tests__/agent-preference.test.js +84 -0
  8. package/dist/__tests__/agent-preference.test.js.map +1 -0
  9. package/dist/__tests__/device-flow-gate-e2e.test.d.ts +1 -0
  10. package/dist/__tests__/device-flow-gate-e2e.test.js +244 -0
  11. package/dist/__tests__/device-flow-gate-e2e.test.js.map +1 -0
  12. package/dist/__tests__/device-flow-pending-400.test.js +1 -1
  13. package/dist/__tests__/device-flow-pending-400.test.js.map +1 -1
  14. package/dist/__tests__/keyauth-error-surface-e2e.test.d.ts +1 -0
  15. package/dist/__tests__/keyauth-error-surface-e2e.test.js +209 -0
  16. package/dist/__tests__/keyauth-error-surface-e2e.test.js.map +1 -0
  17. package/dist/__tests__/list-all-agents.test.d.ts +1 -0
  18. package/dist/__tests__/list-all-agents.test.js +45 -0
  19. package/dist/__tests__/list-all-agents.test.js.map +1 -0
  20. package/dist/__tests__/memory-write-failure.test.js +30 -0
  21. package/dist/__tests__/memory-write-failure.test.js.map +1 -1
  22. package/dist/__tests__/resolve-agent-preference-e2e.test.js +10 -10
  23. package/dist/__tests__/resolve-agent-preference-e2e.test.js.map +1 -1
  24. package/dist/__tests__/silent-renewal-e2e.test.d.ts +1 -0
  25. package/dist/__tests__/silent-renewal-e2e.test.js +246 -0
  26. package/dist/__tests__/silent-renewal-e2e.test.js.map +1 -0
  27. package/dist/__tests__/token-expiry-reauth.test.js +1 -1
  28. package/dist/__tests__/token-expiry-reauth.test.js.map +1 -1
  29. package/dist/__tests__/token-manager.test.d.ts +1 -0
  30. package/dist/__tests__/token-manager.test.js +253 -0
  31. package/dist/__tests__/token-manager.test.js.map +1 -0
  32. package/dist/__tests__/token-rejection-reauth-e2e.test.d.ts +1 -0
  33. package/dist/__tests__/token-rejection-reauth-e2e.test.js +219 -0
  34. package/dist/__tests__/token-rejection-reauth-e2e.test.js.map +1 -0
  35. package/dist/agent-preference.d.ts +24 -0
  36. package/dist/agent-preference.js +51 -0
  37. package/dist/agent-preference.js.map +1 -0
  38. package/dist/index.d.ts +11 -0
  39. package/dist/index.js +110 -59
  40. package/dist/index.js.map +1 -1
  41. package/dist/key-auth.d.ts +8 -0
  42. package/dist/key-auth.js +13 -0
  43. package/dist/key-auth.js.map +1 -1
  44. package/dist/telemetry.d.ts +1 -1
  45. package/dist/telemetry.js.map +1 -1
  46. package/dist/token-manager.d.ts +61 -0
  47. package/dist/token-manager.js +179 -0
  48. package/dist/token-manager.js.map +1 -0
  49. package/package.json +1 -1
@@ -1,69 +1,207 @@
1
1
  /**
2
2
  * Regression test for CORBOT-497C7FA3:
3
3
  * agent_load must persist the switched agentId to disk so that process
4
- * restarts (silent MCP reconnects via StdioServerTransport) restore the
5
- * correct agent instead of silently reverting to the default (Corbot).
4
+ * restarts (silent MCP reconnect) restore the correct agent instead of
5
+ * silently reverting to the default.
6
6
  *
7
- * Root cause: agent_load updated `creds` in memory but did not call
8
- * saveCredentials(), so the credentials.json file still held the original
9
- * agentId after a reconnect.
7
+ * Task 7 review fix (2026-08-24): the previous version of this file only
8
+ * round-tripped `saveOAuthCredentials`/`loadOAuthCredentials` directly — it
9
+ * never called into `index.ts`'s real `agent_load` handler, and its
10
+ * docstring claimed `saveCredentials()` is what the handler calls, which
11
+ * stopped being true once auth was routed through the TokenManager
12
+ * (agent_load now calls `saveAgentPreference`, never a credentials
13
+ * function that writes a token). That left the actual restart-persistence
14
+ * regression uncovered on the live code path.
10
15
  *
11
- * Fix: saveCredentials() is now called immediately after the in-memory
12
- * update inside the agent_load handler.
16
+ * This version spawns the real built binary twice against a fake AIS with
17
+ * an enrolled DID key (the "silent MCP reconnect" case the regression is
18
+ * about — no human interaction, no device flow): once to switch agents via
19
+ * `agent_load`, and once more, fresh, to prove the switch survives the
20
+ * restart. It also pins the persisted file's shape: schemaVersion 2,
21
+ * ids only, no `token` key.
13
22
  */
14
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
15
- import { mkdtempSync, rmSync } from 'node:fs';
23
+ import { createServer } from 'node:http';
24
+ import { spawn } from 'node:child_process';
25
+ import { mkdtempSync, mkdirSync, rmSync, existsSync, readFileSync } from 'node:fs';
16
26
  import { tmpdir } from 'node:os';
17
- import { join } from 'node:path';
18
- import { saveOAuthCredentials, loadOAuthCredentials, } from '../oauth-credentials.js';
19
- const FUTURE_EXPIRY = new Date(Date.now() + 3_600_000).toISOString();
20
- function makeCreds(agentId) {
21
- return {
22
- token: 'tok-test',
23
- agentId,
24
- tenantId: 'tenant-1',
25
- expiresAt: FUTURE_EXPIRY,
26
- };
27
+ import { join, resolve } from 'path';
28
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
29
+ import { generateAndSaveKeypair } from '../key-auth.js';
30
+ function mintJwt() {
31
+ const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
32
+ const payload = Buffer.from(JSON.stringify({ sub: 'user-1', user_id: 'user-1', tenant_id: 'tenant-1' })).toString('base64url');
33
+ return `${header}.${payload}.fakesig`;
27
34
  }
28
35
  describe('agent_load credential persistence (CORBOT-497C7FA3)', () => {
29
- let tmpHome;
30
- beforeEach(() => {
31
- tmpHome = mkdtempSync(join(tmpdir(), 'aismem-agent-load-'));
36
+ let fakeAis;
37
+ let port = 0;
38
+ beforeAll(async () => {
39
+ fakeAis = createServer((req, res) => {
40
+ req.on('data', () => { });
41
+ req.on('end', () => {
42
+ if (req.url === '/v1/auth/challenge' && req.method === 'POST') {
43
+ res.writeHead(200, { 'content-type': 'application/json' });
44
+ res.end(JSON.stringify({
45
+ success: true,
46
+ data: { nonce: 'test-nonce', exp: Math.floor(Date.now() / 1000) + 60, hmac: 'test-hmac' },
47
+ }));
48
+ return;
49
+ }
50
+ if (req.url === '/v1/auth/did-prove' && req.method === 'POST') {
51
+ res.writeHead(200, { 'content-type': 'application/json' });
52
+ res.end(JSON.stringify({
53
+ success: true,
54
+ data: { bearerToken: mintJwt(), expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString() },
55
+ }));
56
+ return;
57
+ }
58
+ if ((req.url === '/v1/agents' || req.url?.startsWith('/v1/agents?')) && req.method === 'GET') {
59
+ // corbot-id deliberately first — the pre-fix bug reverted to
60
+ // "the first active agent" on reconnect, which must NOT be ender-id.
61
+ res.writeHead(200, { 'content-type': 'application/json' });
62
+ res.end(JSON.stringify({
63
+ success: true,
64
+ data: {
65
+ agents: [
66
+ { id: 'corbot-id', name: 'Corbot', status: 'active' },
67
+ { id: 'ender-id', name: 'Ender', status: 'active' },
68
+ ],
69
+ pagination: { total: 2, page: 1, limit: 100, hasMore: false },
70
+ },
71
+ }));
72
+ return;
73
+ }
74
+ if (req.url === '/v1/agents/corbot-id' && req.method === 'GET') {
75
+ res.writeHead(200, { 'content-type': 'application/json' });
76
+ res.end(JSON.stringify({ success: true, data: { id: 'corbot-id', name: 'Corbot', status: 'active' } }));
77
+ return;
78
+ }
79
+ if (req.url === '/v1/agents/ender-id' && req.method === 'GET') {
80
+ res.writeHead(200, { 'content-type': 'application/json' });
81
+ res.end(JSON.stringify({ success: true, data: { id: 'ender-id', name: 'Ender', status: 'active' } }));
82
+ return;
83
+ }
84
+ // /v1/resolve and everything else (hydration / memory refresh /
85
+ // handoff) — generic success.
86
+ res.writeHead(200, { 'content-type': 'application/json' });
87
+ res.end(JSON.stringify({ success: true, data: {} }));
88
+ });
89
+ });
90
+ await new Promise((r) => {
91
+ fakeAis.listen(0, '127.0.0.1', () => {
92
+ const addr = fakeAis.address();
93
+ if (addr && typeof addr === 'object')
94
+ port = addr.port;
95
+ r();
96
+ });
97
+ });
32
98
  });
33
- afterEach(() => {
34
- rmSync(tmpHome, { recursive: true, force: true });
35
- });
36
- it('credentials file reflects the switched agentId after saveOAuthCredentials', () => {
37
- // Start with the default (Corbot) agent on disk, as it would be after initial auth.
38
- saveOAuthCredentials(makeCreds('corbot-id'), tmpHome);
39
- // Simulate what agent_load now does: update agentId AND persist to disk.
40
- const switched = { ...makeCreds('corbot-id'), agentId: 'ender-id' };
41
- saveOAuthCredentials(switched, tmpHome); // the fix ensures this is called
42
- // A new process (reconnect) loads credentials from disk — must get 'ender-id'.
43
- const restored = loadOAuthCredentials(tmpHome);
44
- expect(restored?.agentId).toBe('ender-id');
45
- });
46
- it('without persisting, a reload returns the original agentId — documents the pre-fix bug', () => {
47
- // Start with 'corbot-id' on disk.
48
- saveOAuthCredentials(makeCreds('corbot-id'), tmpHome);
49
- // Pre-fix: agent_load only updated the in-memory variable, never called save.
50
- // Demonstrate what that means: disk still has the old agentId.
51
- const _inMemoryOnly = { ...makeCreds('corbot-id'), agentId: 'ender-id' };
52
- // (intentionally NOT calling saveOAuthCredentials here)
53
- // Reconnect scenario: fresh process reads from disk → wrong agent.
54
- const restored = loadOAuthCredentials(tmpHome);
55
- expect(restored?.agentId).toBe('corbot-id'); // bug: silent revert to default
56
- });
57
- it('token and tenantId are preserved when only agentId is updated', () => {
58
- const initial = makeCreds('corbot-id');
59
- saveOAuthCredentials(initial, tmpHome);
60
- const switched = { ...initial, agentId: 'ender-id' };
61
- saveOAuthCredentials(switched, tmpHome);
62
- const restored = loadOAuthCredentials(tmpHome);
63
- expect(restored?.agentId).toBe('ender-id');
64
- expect(restored?.token).toBe(initial.token);
65
- expect(restored?.tenantId).toBe(initial.tenantId);
66
- expect(restored?.expiresAt).toBe(initial.expiresAt);
99
+ afterAll(async () => {
100
+ await new Promise((r) => fakeAis?.close(() => r()));
67
101
  });
102
+ it('a switch made via agent_load survives a process restart (silent key-auth reconnect)', async () => {
103
+ const distPath = resolve(__dirname, '..', '..', 'dist', 'index.js');
104
+ if (!existsSync(distPath)) {
105
+ throw new Error(`dist/index.js missing — run \`pnpm build\` first (looked at ${distPath})`);
106
+ }
107
+ const fakeHome = mkdtempSync(join(tmpdir(), 'aismem-agent-load-persist-'));
108
+ const aismemDir = join(fakeHome, '.aismemory');
109
+ mkdirSync(aismemDir, { recursive: true });
110
+ await generateAndSaveKeypair({
111
+ userId: 'user-1',
112
+ userDid: 'did:web:test:users:user-1',
113
+ keysDir: join(aismemDir, 'keys'),
114
+ });
115
+ const env = { ...process.env, HOME: fakeHome, AIS_URL: `http://127.0.0.1:${port}` };
116
+ let proc = null;
117
+ try {
118
+ // ── Session 1: cold start, then switch to ender-id via agent_load ──
119
+ proc = spawn('node', [distPath], { env, stdio: ['pipe', 'pipe', 'pipe'] });
120
+ let stdoutBuf = '';
121
+ proc.stdout.on('data', (d) => { stdoutBuf += d.toString(); });
122
+ proc.stderr.on('data', () => { });
123
+ proc.stdin.write(JSON.stringify({
124
+ jsonrpc: '2.0', id: 1, method: 'initialize',
125
+ params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'agent-load-persist-test', version: '0.0.0' } },
126
+ }) + '\n');
127
+ await waitFor(() => stdoutBuf.includes('"id":1'), 5000, 'initialize (session 1)');
128
+ proc.stdin.write(JSON.stringify({
129
+ jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'whoami', arguments: {} },
130
+ }) + '\n');
131
+ await waitFor(() => stdoutBuf.includes('"id":2'), 15000, 'whoami before switch');
132
+ expect(extractResponseText(stdoutBuf, 2)).toContain('corbot-id');
133
+ proc.stdin.write(JSON.stringify({
134
+ jsonrpc: '2.0', id: 3, method: 'tools/call',
135
+ params: { name: 'agent_load', arguments: { query: 'ender-id' } },
136
+ }) + '\n');
137
+ await waitFor(() => stdoutBuf.includes('"id":3'), 15000, 'agent_load');
138
+ expect(extractResponseText(stdoutBuf, 3)).toContain('ender-id');
139
+ proc.kill();
140
+ proc = null;
141
+ // ── The persisted file: v2 shape, ids only, no token ──
142
+ const credsPath = join(aismemDir, 'credentials.json');
143
+ const raw = readFileSync(credsPath, 'utf-8');
144
+ const savedCreds = JSON.parse(raw);
145
+ expect(savedCreds.schemaVersion).toBe(2);
146
+ expect(savedCreds.agentId).toBe('ender-id');
147
+ expect(savedCreds.tenantId).toBeTypeOf('string');
148
+ expect(Object.prototype.hasOwnProperty.call(savedCreds, 'token')).toBe(false);
149
+ expect(Object.prototype.hasOwnProperty.call(savedCreds, 'expiresAt')).toBe(false);
150
+ // ── Session 2: fresh process, same fake HOME — a silent reconnect. ──
151
+ // Must resolve back to ender-id via loadAgentPreference()'s
152
+ // preferredAgentId, NOT revert to corbot-id (the first active agent).
153
+ proc = spawn('node', [distPath], { env, stdio: ['pipe', 'pipe', 'pipe'] });
154
+ let stdoutBuf2 = '';
155
+ proc.stdout.on('data', (d) => { stdoutBuf2 += d.toString(); });
156
+ proc.stderr.on('data', () => { });
157
+ proc.stdin.write(JSON.stringify({
158
+ jsonrpc: '2.0', id: 1, method: 'initialize',
159
+ params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'agent-load-persist-test-2', version: '0.0.0' } },
160
+ }) + '\n');
161
+ await waitFor(() => stdoutBuf2.includes('"id":1'), 5000, 'initialize (session 2)');
162
+ proc.stdin.write(JSON.stringify({
163
+ jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'whoami', arguments: {} },
164
+ }) + '\n');
165
+ await waitFor(() => stdoutBuf2.includes('"id":2'), 15000, 'whoami after restart');
166
+ const restartedWhoami = extractResponseText(stdoutBuf2, 2);
167
+ expect(restartedWhoami).toContain('ender-id');
168
+ expect(restartedWhoami).not.toContain('corbot-id');
169
+ }
170
+ finally {
171
+ proc?.kill();
172
+ rmSync(fakeHome, { recursive: true, force: true });
173
+ }
174
+ }, 30000);
68
175
  });
176
+ function waitFor(predicate, ms, label) {
177
+ return new Promise((resolveOuter, reject) => {
178
+ const start = Date.now();
179
+ const tick = () => {
180
+ if (predicate()) {
181
+ resolveOuter();
182
+ return;
183
+ }
184
+ if (Date.now() - start > ms) {
185
+ reject(new Error(`timeout waiting for ${label}`));
186
+ return;
187
+ }
188
+ setTimeout(tick, 50);
189
+ };
190
+ tick();
191
+ });
192
+ }
193
+ function extractResponseText(buf, id) {
194
+ for (const line of buf.split('\n')) {
195
+ if (!line.includes(`"id":${id}`))
196
+ continue;
197
+ try {
198
+ const parsed = JSON.parse(line);
199
+ const text = parsed.result?.content?.[0]?.text;
200
+ if (typeof text === 'string')
201
+ return text;
202
+ }
203
+ catch { /* malformed line; skip */ }
204
+ }
205
+ return '';
206
+ }
69
207
  //# sourceMappingURL=agent-load-persistence.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-load-persistence.test.js","sourceRoot":"","sources":["../../src/__tests__/agent-load-persistence.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-load-persistence.test.js","sourceRoot":"","sources":["../../src/__tests__/agent-load-persistence.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * CRITICAL fix, Task 7 review: on the key-enrolled path, `ensureCredentials`
3
+ * composed its returned Credentials directly from
4
+ * `tokenManager.getCredentials()` on every call. The TokenManager resolves
5
+ * agentId/tenantId ONCE, on its first acquire, and never re-resolves them
6
+ * (by design — see token-manager.ts). That meant an in-session `agent_load`
7
+ * switch — which mutated the outer `creds` variable directly — was
8
+ * clobbered back to the manager's original identity on the very NEXT tool
9
+ * call, not merely after a background renewal: `ensureCredentials` reassigns
10
+ * `creds = mc` unconditionally before returning.
11
+ *
12
+ * Fix: a module-scope `sessionAgentOverride` set by `agent_load`, which
13
+ * `ensureCredentials` composes into the returned Credentials (token/expiresAt
14
+ * from the manager, agentId/tenantId from the override when set) instead of
15
+ * ever letting the manager's cached identity win over a live switch.
16
+ *
17
+ * This test drives the REAL built binary (not a mock of ensureCredentials)
18
+ * against a fake AIS that hands out short-lived tokens, so a background
19
+ * renewal fires mid-test. It proves the switch survives both:
20
+ * 1. the very next tool call after `agent_load`, and
21
+ * 2. a background token renewal that happens after the switch.
22
+ */
23
+ import { createServer } from 'node:http';
24
+ import { spawn } from 'node:child_process';
25
+ import { mkdtempSync, mkdirSync, rmSync, existsSync } from 'node:fs';
26
+ import { tmpdir } from 'node:os';
27
+ import { join, resolve } from 'path';
28
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
29
+ import { generateAndSaveKeypair } from '../key-auth.js';
30
+ // Short enough that the TokenManager's 75%-of-TTL renewal fires well inside
31
+ // the test's timeout, long enough to leave room for the intervening tool
32
+ // calls (agent_load + one whoami) before it does.
33
+ const SHORT_TTL_MS = 4000;
34
+ let didProveCalls = 0;
35
+ function mintJwt() {
36
+ const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
37
+ const payload = Buffer.from(JSON.stringify({ sub: 'user-1', user_id: 'user-1', tenant_id: 'tenant-1', call: didProveCalls })).toString('base64url');
38
+ return `${header}.${payload}.fakesig-${didProveCalls}`;
39
+ }
40
+ describe('agent_load session override survives the next tool call and a background renewal', () => {
41
+ let fakeAis;
42
+ let port = 0;
43
+ beforeAll(async () => {
44
+ fakeAis = createServer((req, res) => {
45
+ req.on('data', () => { });
46
+ req.on('end', () => {
47
+ if (req.url === '/v1/auth/challenge' && req.method === 'POST') {
48
+ res.writeHead(200, { 'content-type': 'application/json' });
49
+ res.end(JSON.stringify({
50
+ success: true,
51
+ data: { nonce: 'test-nonce', exp: Math.floor(Date.now() / 1000) + 60, hmac: 'test-hmac' },
52
+ }));
53
+ return;
54
+ }
55
+ if (req.url === '/v1/auth/did-prove' && req.method === 'POST') {
56
+ didProveCalls += 1;
57
+ res.writeHead(200, { 'content-type': 'application/json' });
58
+ res.end(JSON.stringify({
59
+ success: true,
60
+ data: {
61
+ bearerToken: mintJwt(),
62
+ expiresAt: new Date(Date.now() + SHORT_TTL_MS).toISOString(),
63
+ },
64
+ }));
65
+ return;
66
+ }
67
+ if (req.url?.startsWith('/v1/agents') && !req.url?.includes('/v1/agents/agent-') && req.method === 'GET') {
68
+ res.writeHead(200, { 'content-type': 'application/json' });
69
+ res.end(JSON.stringify({
70
+ success: true,
71
+ data: {
72
+ agents: [
73
+ { id: 'agent-a', name: 'AgentA', status: 'active' },
74
+ { id: 'agent-b', name: 'AgentB', status: 'active' },
75
+ ],
76
+ pagination: { total: 2, page: 1, limit: 100, hasMore: false },
77
+ },
78
+ }));
79
+ return;
80
+ }
81
+ if (req.url === '/v1/agents/agent-a' && req.method === 'GET') {
82
+ res.writeHead(200, { 'content-type': 'application/json' });
83
+ res.end(JSON.stringify({ success: true, data: { id: 'agent-a', name: 'AgentA', status: 'active' } }));
84
+ return;
85
+ }
86
+ if (req.url === '/v1/agents/agent-b' && req.method === 'GET') {
87
+ res.writeHead(200, { 'content-type': 'application/json' });
88
+ res.end(JSON.stringify({ success: true, data: { id: 'agent-b', name: 'AgentB', status: 'active' } }));
89
+ return;
90
+ }
91
+ // /v1/resolve (agent_load's primary lookup) and everything else
92
+ // (hydration / memory refresh / handoff) — generic success. agent_load
93
+ // falls back to its own listAllAgents-based name/id match when
94
+ // /v1/resolve doesn't return a `status`.
95
+ res.writeHead(200, { 'content-type': 'application/json' });
96
+ res.end(JSON.stringify({ success: true, data: {} }));
97
+ });
98
+ });
99
+ await new Promise((r) => {
100
+ fakeAis.listen(0, '127.0.0.1', () => {
101
+ const addr = fakeAis.address();
102
+ if (addr && typeof addr === 'object')
103
+ port = addr.port;
104
+ r();
105
+ });
106
+ });
107
+ });
108
+ afterAll(async () => {
109
+ await new Promise((r) => fakeAis?.close(() => r()));
110
+ });
111
+ it('keeps the switched agent across the next tool call and a renewal', async () => {
112
+ const distPath = resolve(__dirname, '..', '..', 'dist', 'index.js');
113
+ if (!existsSync(distPath)) {
114
+ throw new Error(`dist/index.js missing — run \`pnpm build\` first (looked at ${distPath})`);
115
+ }
116
+ didProveCalls = 0;
117
+ const fakeHome = mkdtempSync(join(tmpdir(), 'aismem-agent-load-override-'));
118
+ const aismemDir = join(fakeHome, '.aismemory');
119
+ mkdirSync(aismemDir, { recursive: true });
120
+ await generateAndSaveKeypair({
121
+ userId: 'user-1',
122
+ userDid: 'did:web:test:users:user-1',
123
+ keysDir: join(aismemDir, 'keys'),
124
+ });
125
+ let proc = null;
126
+ try {
127
+ proc = spawn('node', [distPath], {
128
+ env: { ...process.env, HOME: fakeHome, AIS_URL: `http://127.0.0.1:${port}` },
129
+ stdio: ['pipe', 'pipe', 'pipe'],
130
+ });
131
+ let stdoutBuf = '';
132
+ proc.stdout.on('data', (d) => { stdoutBuf += d.toString(); });
133
+ proc.stderr.on('data', () => { });
134
+ proc.stdin.write(JSON.stringify({
135
+ jsonrpc: '2.0', id: 1, method: 'initialize',
136
+ params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'agent-load-override-test', version: '0.0.0' } },
137
+ }) + '\n');
138
+ await waitFor(() => stdoutBuf.includes('"id":1'), 5000, 'initialize response');
139
+ // First whoami: establishes the TokenManager's first acquire, which
140
+ // resolves identity to the first active agent (agent-a — no
141
+ // preference on disk yet).
142
+ proc.stdin.write(JSON.stringify({
143
+ jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'whoami', arguments: {} },
144
+ }) + '\n');
145
+ await waitFor(() => stdoutBuf.includes('"id":2'), 15000, 'first whoami');
146
+ expect(extractResponseText(stdoutBuf, 2)).toContain('agent-a');
147
+ expect(didProveCalls).toBe(1);
148
+ // Switch to agent-b.
149
+ proc.stdin.write(JSON.stringify({
150
+ jsonrpc: '2.0', id: 3, method: 'tools/call',
151
+ params: { name: 'agent_load', arguments: { query: 'agent-b' } },
152
+ }) + '\n');
153
+ await waitFor(() => stdoutBuf.includes('"id":3'), 15000, 'agent_load response');
154
+ expect(extractResponseText(stdoutBuf, 3)).toContain('agent-b');
155
+ // THE critical assertion: the very next tool call after agent_load
156
+ // must dispatch as agent-b, not revert to the manager's original
157
+ // agent-a. No renewal has happened yet (didProveCalls still 1).
158
+ proc.stdin.write(JSON.stringify({
159
+ jsonrpc: '2.0', id: 4, method: 'tools/call', params: { name: 'whoami', arguments: {} },
160
+ }) + '\n');
161
+ await waitFor(() => stdoutBuf.includes('"id":4'), 15000, 'whoami right after switch');
162
+ expect(extractResponseText(stdoutBuf, 4)).toContain('agent-b');
163
+ expect(didProveCalls).toBe(1);
164
+ // Wait for a background renewal to fire (75% of the short TTL).
165
+ await waitFor(() => didProveCalls >= 2, 10000, 'background renewal');
166
+ // THE second critical assertion: after a renewal — which reuses the
167
+ // manager's cached (agent-a) identity internally — the override must
168
+ // still win. The switch must not have reverted.
169
+ proc.stdin.write(JSON.stringify({
170
+ jsonrpc: '2.0', id: 5, method: 'tools/call', params: { name: 'whoami', arguments: {} },
171
+ }) + '\n');
172
+ await waitFor(() => stdoutBuf.includes('"id":5'), 15000, 'whoami after renewal');
173
+ expect(extractResponseText(stdoutBuf, 5)).toContain('agent-b');
174
+ }
175
+ finally {
176
+ proc?.kill();
177
+ rmSync(fakeHome, { recursive: true, force: true });
178
+ }
179
+ }, 30000);
180
+ });
181
+ function waitFor(predicate, ms, label) {
182
+ return new Promise((resolveOuter, reject) => {
183
+ const start = Date.now();
184
+ const tick = () => {
185
+ if (predicate()) {
186
+ resolveOuter();
187
+ return;
188
+ }
189
+ if (Date.now() - start > ms) {
190
+ reject(new Error(`timeout waiting for ${label}`));
191
+ return;
192
+ }
193
+ setTimeout(tick, 50);
194
+ };
195
+ tick();
196
+ });
197
+ }
198
+ function extractResponseText(buf, id) {
199
+ for (const line of buf.split('\n')) {
200
+ if (!line.includes(`"id":${id}`))
201
+ continue;
202
+ try {
203
+ const parsed = JSON.parse(line);
204
+ const text = parsed.result?.content?.[0]?.text;
205
+ if (typeof text === 'string')
206
+ return text;
207
+ }
208
+ catch { /* malformed line; skip */ }
209
+ }
210
+ return '';
211
+ }
212
+ //# sourceMappingURL=agent-load-session-override-e2e.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-load-session-override-e2e.test.js","sourceRoot":"","sources":["../../src/__tests__/agent-load-session-override-e2e.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,84 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdtempSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { saveAgentPreference, loadAgentPreference, } from '../agent-preference.js';
6
+ describe('agent preference persistence (schemaVersion 2)', () => {
7
+ let tmpHome;
8
+ beforeEach(() => {
9
+ tmpHome = mkdtempSync(join(tmpdir(), 'aismem-agent-pref-'));
10
+ });
11
+ afterEach(() => {
12
+ rmSync(tmpHome, { recursive: true, force: true });
13
+ });
14
+ it('round-trip: save and load agent preference', () => {
15
+ const pref = { agentId: 'agent-a', tenantId: 'tenant-1' };
16
+ saveAgentPreference(pref, tmpHome);
17
+ const loaded = loadAgentPreference(tmpHome);
18
+ expect(loaded).toEqual(pref);
19
+ });
20
+ it('load legacy v1 file: returns ids only, ignoring token and expiresAt', () => {
21
+ const credentialsPath = join(tmpHome, '.aismemory', 'credentials.json');
22
+ const credentialsDir = join(tmpHome, '.aismemory');
23
+ // Create directory and write legacy v1 format
24
+ mkdirSync(credentialsDir, { recursive: true });
25
+ const legacyV1 = {
26
+ token: 'secret-token-value',
27
+ agentId: 'agent-a',
28
+ tenantId: 'tenant-1',
29
+ expiresAt: '2026-08-24T12:00:00Z',
30
+ };
31
+ writeFileSync(credentialsPath, JSON.stringify(legacyV1));
32
+ const loaded = loadAgentPreference(tmpHome);
33
+ expect(loaded).toEqual({ agentId: 'agent-a', tenantId: 'tenant-1' });
34
+ });
35
+ it('after saveAgentPreference over legacy file, raw file contains no "token" key', () => {
36
+ const credentialsPath = join(tmpHome, '.aismemory', 'credentials.json');
37
+ const credentialsDir = join(tmpHome, '.aismemory');
38
+ // Create directory and write legacy v1 format
39
+ mkdirSync(credentialsDir, { recursive: true });
40
+ const legacyV1 = {
41
+ token: 'secret-token-value',
42
+ agentId: 'agent-a',
43
+ tenantId: 'tenant-1',
44
+ expiresAt: '2026-08-24T12:00:00Z',
45
+ };
46
+ writeFileSync(credentialsPath, JSON.stringify(legacyV1));
47
+ // Save new preference
48
+ const newPref = { agentId: 'agent-b', tenantId: 'tenant-2' };
49
+ saveAgentPreference(newPref, tmpHome);
50
+ // Read raw file and verify no "token" key
51
+ const rawContent = readFileSync(credentialsPath, 'utf-8');
52
+ expect(rawContent).not.toContain('"token"');
53
+ expect(rawContent).toContain('"agentId"');
54
+ expect(rawContent).toContain('"tenantId"');
55
+ });
56
+ it('missing file returns null', () => {
57
+ const loaded = loadAgentPreference(tmpHome);
58
+ expect(loaded).toBeNull();
59
+ });
60
+ it('corrupt JSON file returns null', () => {
61
+ const credentialsPath = join(tmpHome, '.aismemory', 'credentials.json');
62
+ const credentialsDir = join(tmpHome, '.aismemory');
63
+ mkdirSync(credentialsDir, { recursive: true });
64
+ writeFileSync(credentialsPath, 'not valid json {]');
65
+ const loaded = loadAgentPreference(tmpHome);
66
+ expect(loaded).toBeNull();
67
+ });
68
+ it('v2 schema persists schemaVersion, agentId, tenantId; no legacy token key', () => {
69
+ const pref = { agentId: 'agent-a', tenantId: 'tenant-1' };
70
+ saveAgentPreference(pref, tmpHome);
71
+ const credentialsPath = join(tmpHome, '.aismemory', 'credentials.json');
72
+ const rawContent = readFileSync(credentialsPath, 'utf-8');
73
+ const parsed = JSON.parse(rawContent);
74
+ // The file should have schemaVersion + ids, no legacy token/expiresAt
75
+ expect(parsed).toEqual({
76
+ schemaVersion: 2,
77
+ agentId: 'agent-a',
78
+ tenantId: 'tenant-1',
79
+ });
80
+ expect(rawContent).not.toContain('"token"');
81
+ expect(parsed.schemaVersion).toBe(2);
82
+ });
83
+ });
84
+ //# sourceMappingURL=agent-preference.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-preference.test.js","sourceRoot":"","sources":["../../src/__tests__/agent-preference.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};