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
@@ -0,0 +1,253 @@
1
+ import { mkdtempSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { tmpdir } from 'node:os';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { hasEnrolledKey } from '../key-auth.js';
6
+ import { createTokenManager, AisUnreachableError } from '../token-manager.js';
7
+ describe('hasEnrolledKey', () => {
8
+ it('false for empty dir, true for one v1 key, false for two keys', () => {
9
+ const dir = mkdtempSync(join(tmpdir(), 'ais-keys-'));
10
+ expect(hasEnrolledKey({ keysDir: dir })).toBe(false);
11
+ writeFileSync(join(dir, 'a.json'), JSON.stringify({ version: 1, userId: 'u', userDid: 'd', keyType: 'Ed25519', privateKey: 'x', publicKey: 'y', publicKeyJwk: {}, createdAt: 'now' }));
12
+ expect(hasEnrolledKey({ keysDir: dir })).toBe(true);
13
+ writeFileSync(join(dir, 'b.json'), JSON.stringify({ version: 1 }));
14
+ expect(hasEnrolledKey({ keysDir: dir })).toBe(false);
15
+ });
16
+ });
17
+ /**
18
+ * Controllable `authenticate` test double. Starts in 'ok' mode returning
19
+ * sequential tokens (t1, t2, t3, ...), each with a 1h TTL computed from the
20
+ * current (possibly fake) clock at call time. `setMode('fail')` makes every
21
+ * subsequent call reject until switched back.
22
+ */
23
+ function createAuthMock() {
24
+ let mode = 'ok';
25
+ let tokenIndex = 0;
26
+ let callCount = 0;
27
+ const fn = vi.fn(async () => {
28
+ callCount++;
29
+ if (mode === 'fail') {
30
+ throw new Error(`auth-failed-${callCount}`);
31
+ }
32
+ tokenIndex++;
33
+ return { token: `t${tokenIndex}`, expiresAt: new Date(Date.now() + 3_600_000).toISOString() };
34
+ });
35
+ return { fn, calls: () => callCount, setMode: (m) => { mode = m; } };
36
+ }
37
+ function createIdentityMock() {
38
+ let callCount = 0;
39
+ const fn = vi.fn(async () => {
40
+ callCount++;
41
+ return { agentId: 'agent-1', tenantId: 'tenant-1' };
42
+ });
43
+ return { fn, calls: () => callCount };
44
+ }
45
+ /**
46
+ * Advances fake timers in small steps until `predicate()` is true, so
47
+ * jittered backoff delays never need hand-calculated exact totals.
48
+ */
49
+ async function advanceUntil(predicate, stepMs = 50_000, maxSteps = 200) {
50
+ for (let i = 0; i < maxSteps; i++) {
51
+ if (predicate())
52
+ return;
53
+ await vi.advanceTimersByTimeAsync(stepMs);
54
+ }
55
+ if (!predicate()) {
56
+ throw new Error(`advanceUntil: condition not met after ${maxSteps * stepMs}ms`);
57
+ }
58
+ }
59
+ describe('createTokenManager', () => {
60
+ beforeEach(() => {
61
+ vi.useFakeTimers();
62
+ });
63
+ afterEach(() => {
64
+ vi.useRealTimers();
65
+ });
66
+ it('bullet 1: first getCredentials() authenticates + resolves identity; concurrent callers share one in-flight acquire', async () => {
67
+ const auth = createAuthMock();
68
+ const identity = createIdentityMock();
69
+ const mgr = createTokenManager({ authenticate: auth.fn, resolveIdentity: identity.fn });
70
+ const [c1, c2] = await Promise.all([mgr.getCredentials(), mgr.getCredentials()]);
71
+ expect(auth.calls()).toBe(1);
72
+ expect(identity.calls()).toBe(1);
73
+ expect(c1).toEqual(c2);
74
+ expect(c1.token).toBe('t1');
75
+ expect(c1.agentId).toBe('agent-1');
76
+ expect(c1.tenantId).toBe('tenant-1');
77
+ mgr.stop();
78
+ });
79
+ it('bullet 2: renews at 75% of TTL, swaps token/expiresAt in place, does not re-resolve identity', async () => {
80
+ const auth = createAuthMock();
81
+ const identity = createIdentityMock();
82
+ const mgr = createTokenManager({ authenticate: auth.fn, resolveIdentity: identity.fn });
83
+ const first = await mgr.getCredentials();
84
+ expect(first.token).toBe('t1');
85
+ await vi.advanceTimersByTimeAsync(2_700_000); // 75% of the 1h TTL
86
+ expect(auth.calls()).toBe(2);
87
+ const second = await mgr.getCredentials();
88
+ expect(second.token).toBe('t2');
89
+ expect(identity.calls()).toBe(1); // identity resolved only on first acquire
90
+ expect(second.agentId).toBe('agent-1');
91
+ expect(second.tenantId).toBe('tenant-1');
92
+ mgr.stop();
93
+ });
94
+ it('bullet 3: renewal failure retries with backoff, emits renew.retry, and keeps returning the current token', async () => {
95
+ const auth = createAuthMock();
96
+ const identity = createIdentityMock();
97
+ const events = [];
98
+ const mgr = createTokenManager({
99
+ authenticate: auth.fn,
100
+ resolveIdentity: identity.fn,
101
+ onEvent: (event, detail) => events.push({ event, detail }),
102
+ });
103
+ await mgr.getCredentials(); // acquire t1 (call #1)
104
+ auth.setMode('fail');
105
+ await vi.advanceTimersByTimeAsync(2_700_000); // trips the 75%-TTL renewal → call #2 fails
106
+ expect(auth.calls()).toBe(2);
107
+ expect(events.filter((e) => e.event === 'renew.retry')).toHaveLength(1);
108
+ // Current token (t1) is still returned; it has not hit hard expiry yet.
109
+ await expect(mgr.getCredentials()).resolves.toMatchObject({ token: 't1' });
110
+ await vi.advanceTimersByTimeAsync(6_500); // 5s backoff ± 20% jitter → next retry fires
111
+ expect(auth.calls()).toBe(3);
112
+ expect(events.filter((e) => e.event === 'renew.retry')).toHaveLength(2);
113
+ mgr.stop();
114
+ });
115
+ it('bullet 4: after hard expiry getCredentials() rejects with AisUnreachableError (retries continue; first success recovers with no restart)', async () => {
116
+ const auth = createAuthMock();
117
+ const identity = createIdentityMock();
118
+ const events = [];
119
+ const mgr = createTokenManager({
120
+ authenticate: auth.fn,
121
+ resolveIdentity: identity.fn,
122
+ onEvent: (event, detail) => events.push({ event, detail }),
123
+ });
124
+ await mgr.getCredentials(); // acquire t1, expiresAt = start + 1h
125
+ auth.setMode('fail');
126
+ await vi.advanceTimersByTimeAsync(2_700_000); // 75% TTL → first failed renewal (call #2)
127
+ // Advance, in small steps, through repeated backoff retries (capped at
128
+ // 300s ± 20% jitter) until the token hard-expires and the event fires.
129
+ await advanceUntil(() => events.some((e) => e.event === 'renew.hard_expiry'));
130
+ expect(events.filter((e) => e.event === 'renew.hard_expiry')).toHaveLength(1);
131
+ await expect(mgr.getCredentials()).rejects.toBeInstanceOf(AisUnreachableError);
132
+ await expect(mgr.getCredentials()).rejects.toThrow('AIS unreachable');
133
+ await expect(mgr.getCredentials()).rejects.toThrow('no action needed');
134
+ // hard_expiry must fire only once per outage even as failures continue —
135
+ // advance through at least one more capped-backoff retry attempt.
136
+ await vi.advanceTimersByTimeAsync(400_000);
137
+ expect(events.filter((e) => e.event === 'renew.hard_expiry')).toHaveLength(1);
138
+ // First success after the outage recovers getCredentials() with no restart
139
+ // (same manager instance; identity resolved only on the very first acquire).
140
+ const callsBeforeRecovery = auth.calls();
141
+ auth.setMode('ok');
142
+ await advanceUntil(() => auth.calls() > callsBeforeRecovery);
143
+ const recovered = await mgr.getCredentials();
144
+ expect(recovered.token).not.toBe('t1');
145
+ expect(recovered.agentId).toBe('agent-1');
146
+ expect(identity.calls()).toBe(1);
147
+ mgr.stop();
148
+ });
149
+ it('bullet 5: stop() clears all timers so no further renewal attempts occur', async () => {
150
+ const auth = createAuthMock();
151
+ const identity = createIdentityMock();
152
+ const mgr = createTokenManager({ authenticate: auth.fn, resolveIdentity: identity.fn });
153
+ await mgr.getCredentials();
154
+ expect(auth.calls()).toBe(1);
155
+ mgr.stop();
156
+ await vi.advanceTimersByTimeAsync(3_600_000); // well past the 75%-TTL renewal point
157
+ expect(auth.calls()).toBe(1);
158
+ });
159
+ it('reliability fix: sequential getCredentials() calls during an outage share one backoff window instead of each hammering authenticate()', async () => {
160
+ const auth = createAuthMock();
161
+ auth.setMode('fail'); // AIS is down from the very first call — no successful acquire ever happens
162
+ const identity = createIdentityMock();
163
+ const events = [];
164
+ const mgr = createTokenManager({
165
+ authenticate: auth.fn,
166
+ resolveIdentity: identity.fn,
167
+ onEvent: (event, detail) => events.push({ event, detail }),
168
+ });
169
+ // N sequential (not concurrent) calls in quick succession — no time
170
+ // advances between them, mirroring a single MCP chat turn bursting
171
+ // several tool calls while AIS is unreachable.
172
+ const N = 5;
173
+ for (let i = 0; i < N; i++) {
174
+ await expect(mgr.getCredentials()).rejects.toBeInstanceOf(AisUnreachableError);
175
+ }
176
+ // Exactly one authenticate() attempt for the whole burst, not N.
177
+ expect(auth.calls()).toBe(1);
178
+ expect(events.filter((e) => e.event === 'renew.retry')).toHaveLength(1);
179
+ // After the backoff window (5s ± 20% jitter) elapses and the server
180
+ // heals, a getCredentials() call attempts again and succeeds.
181
+ auth.setMode('ok');
182
+ await vi.advanceTimersByTimeAsync(6_500);
183
+ const recovered = await mgr.getCredentials();
184
+ expect(recovered.agentId).toBe('agent-1');
185
+ expect(auth.calls()).toBe(2);
186
+ mgr.stop();
187
+ });
188
+ it('invalidate(): forces a fresh acquire, and the new token reschedules renewal from its own expiry (not the invalidated token\'s old schedule)', async () => {
189
+ const auth = createAuthMock();
190
+ const identity = createIdentityMock();
191
+ const mgr = createTokenManager({ authenticate: auth.fn, resolveIdentity: identity.fn });
192
+ const first = await mgr.getCredentials(); // T=0: acquires t1, expires T=3_600_000
193
+ expect(first.token).toBe('t1');
194
+ expect(auth.calls()).toBe(1);
195
+ // Let time pass before invalidating so t1's original 75%-TTL renewal
196
+ // slot (T=2_700_000) and t2's fresh slot (computed from whenever the
197
+ // re-acquire actually happens) are measurably different.
198
+ await vi.advanceTimersByTimeAsync(100_000); // T=100_000
199
+ mgr.invalidate();
200
+ const second = await mgr.getCredentials(); // re-acquires t2 at T=100_000, expires T=3_700_000
201
+ expect(second.token).toBe('t2');
202
+ expect(auth.calls()).toBe(2);
203
+ expect(identity.calls()).toBe(1); // identity still resolved only once — invalidate() doesn't touch that
204
+ // t1's OLD renewal slot (absolute T=2_700_000) must not fire — it was
205
+ // superseded when the re-acquire rescheduled renewal off t2's expiry.
206
+ await vi.advanceTimersByTimeAsync(2_600_000); // now T=2_700_000
207
+ expect(auth.calls()).toBe(2);
208
+ // t2's OWN 75%-TTL slot (T=100_000 acquire + 2_700_000 = T=2_800_000) fires here.
209
+ await vi.advanceTimersByTimeAsync(100_000); // now T=2_800_000
210
+ expect(auth.calls()).toBe(3);
211
+ mgr.stop();
212
+ });
213
+ it('invalidate() during an active backoff window does not bypass the gate — the next getCredentials() still rejects fast', async () => {
214
+ const auth = createAuthMock();
215
+ const identity = createIdentityMock();
216
+ const mgr = createTokenManager({ authenticate: auth.fn, resolveIdentity: identity.fn });
217
+ await mgr.getCredentials(); // acquire t1 (call #1)
218
+ auth.setMode('fail');
219
+ await vi.advanceTimersByTimeAsync(2_700_000); // 75% TTL -> renewal fails (call #2), backoff window opens
220
+ expect(auth.calls()).toBe(2);
221
+ // t1 is still time-valid (not hard-expired) at this point — without
222
+ // invalidate(), getCredentials() would just return it via the fast path
223
+ // without ever touching the backoff gate. invalidate() drops it so the
224
+ // gate is exercised, but must NOT reset retryDelayMs/lastAttemptAt.
225
+ mgr.invalidate();
226
+ await expect(mgr.getCredentials()).rejects.toBeInstanceOf(AisUnreachableError);
227
+ expect(auth.calls()).toBe(2); // gated — no extra authenticate() attempt inside the window
228
+ mgr.stop();
229
+ });
230
+ it('acquire(): a non-finite expiresAt from authenticate() rejects instead of scheduling a near-zero renewal loop', async () => {
231
+ const identity = createIdentityMock();
232
+ let authCalls = 0;
233
+ const badAuth = vi.fn(async () => {
234
+ authCalls++;
235
+ return { token: 'bad-token', expiresAt: 'garbage' };
236
+ });
237
+ const mgr = createTokenManager({ authenticate: badAuth, resolveIdentity: identity.fn });
238
+ await expect(mgr.getCredentials()).rejects.toBeInstanceOf(AisUnreachableError);
239
+ expect(authCalls).toBe(1);
240
+ // If the bad expiresAt had scheduled a renewal timer, its NaN-derived
241
+ // delay would tight-loop. Instead: the very next call, made with no
242
+ // time advance, must be gated by the normal backoff window rather than
243
+ // firing a second authenticate() attempt immediately.
244
+ await expect(mgr.getCredentials()).rejects.toBeInstanceOf(AisUnreachableError);
245
+ expect(authCalls).toBe(1);
246
+ // Only after the backoff window elapses does a second attempt happen.
247
+ await vi.advanceTimersByTimeAsync(6_500);
248
+ await expect(mgr.getCredentials()).rejects.toBeInstanceOf(AisUnreachableError);
249
+ expect(authCalls).toBe(2);
250
+ mgr.stop();
251
+ });
252
+ });
253
+ //# sourceMappingURL=token-manager.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-manager.test.js","sourceRoot":"","sources":["../../src/__tests__/token-manager.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Final-review wave (2026-08-24) — CRITICAL fix: `TokenManager.invalidate()`
3
+ * wired to the mid-session 401 branch.
4
+ *
5
+ * Reproduces the JWT_SECRET-rotation scenario (CORBOT-A5BBD580): AIS
6
+ * rejects a token that is still time-valid from the client's point of view
7
+ * (e.g. the server rotated its signing secret). Before this fix,
8
+ * `ensureCredentials`'s key-enrolled path cleared the outer `creds` variable
9
+ * on a 401 but re-read the SAME rejected token straight back out of the
10
+ * TokenManager's `current` (still time-valid, so `getCredentials()` returned
11
+ * it via the fast path) — the "retry" was a no-op that re-sent the exact
12
+ * token AIS had just rejected. `tokenManager.invalidate()` drops `current`
13
+ * so the retry's `ensureCredentials()` call actually re-runs challenge/prove.
14
+ *
15
+ * Also covers the companion Fix 3 (restored `auth.key.success` telemetry +
16
+ * the one-line stderr confirmation on the first successful key-enrolled
17
+ * acquire): asserts both appear exactly once across a session that spans
18
+ * multiple tool calls and one forced re-auth.
19
+ *
20
+ * Follows the fake-AIS + spawned-built-binary pattern of
21
+ * silent-renewal-e2e.test.ts / agent-load-session-override-e2e.test.ts.
22
+ * `isAuthErrorResult` (src/auth-staleness.ts) is what detects the 401 body
23
+ * below — it requires `{ success: false, error: { code: 'TOKEN_EXPIRED' |
24
+ * 'INVALID_TOKEN' } }`, so the fake server's rejection body matches that
25
+ * shape exactly.
26
+ */
27
+ import { createServer } from 'node:http';
28
+ import { spawn } from 'node:child_process';
29
+ import { mkdtempSync, mkdirSync, rmSync, existsSync } from 'node:fs';
30
+ import { tmpdir } from 'node:os';
31
+ import { join, resolve } from 'path';
32
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
33
+ import { generateAndSaveKeypair } from '../key-auth.js';
34
+ // Long enough that the TokenManager's 75%-of-TTL background renewal never
35
+ // fires during this short test — the retry the test exercises must be
36
+ // driven purely by the mid-session 401, not by an incidental renewal.
37
+ const LONG_TTL_MS = 60_000;
38
+ let proveCalls = 0;
39
+ /** GET /v1/agents/agent-1 (exact path) call count — the whoami data call. */
40
+ let agentGetCalls = 0;
41
+ function mintJwt() {
42
+ const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
43
+ const payload = Buffer.from(JSON.stringify({ sub: 'user-1', user_id: 'user-1', tenant_id: 'tenant-1', call: proveCalls })).toString('base64url');
44
+ return `${header}.${payload}.fakesig-${proveCalls}`;
45
+ }
46
+ describe('mid-session 401 (server-rejected, still-time-valid token) forces a real re-auth', () => {
47
+ let fakeAis;
48
+ let port = 0;
49
+ beforeAll(async () => {
50
+ fakeAis = createServer((req, res) => {
51
+ req.on('data', () => { });
52
+ req.on('end', () => {
53
+ const url = req.url ?? '';
54
+ if (url === '/v1/auth/challenge' && req.method === 'POST') {
55
+ res.writeHead(200, { 'content-type': 'application/json' });
56
+ res.end(JSON.stringify({
57
+ success: true,
58
+ data: { nonce: 'test-nonce', exp: Math.floor(Date.now() / 1000) + 60, hmac: 'test-hmac' },
59
+ }));
60
+ return;
61
+ }
62
+ if (url === '/v1/auth/did-prove' && req.method === 'POST') {
63
+ proveCalls += 1;
64
+ res.writeHead(200, { 'content-type': 'application/json' });
65
+ res.end(JSON.stringify({
66
+ success: true,
67
+ data: { bearerToken: mintJwt(), expiresAt: new Date(Date.now() + LONG_TTL_MS).toISOString() },
68
+ }));
69
+ return;
70
+ }
71
+ if (url.startsWith('/v1/agents') && !url.includes('/v1/agents/agent-') && req.method === 'GET') {
72
+ res.writeHead(200, { 'content-type': 'application/json' });
73
+ res.end(JSON.stringify({
74
+ success: true,
75
+ data: {
76
+ agents: [{ id: 'agent-1', name: 'Agent1', status: 'active' }],
77
+ pagination: { total: 1, page: 1, limit: 100, hasMore: false },
78
+ },
79
+ }));
80
+ return;
81
+ }
82
+ if (url === '/v1/agents/agent-1' && req.method === 'GET') {
83
+ agentGetCalls += 1;
84
+ // Reject exactly the SECOND data call — simulates AIS rejecting a
85
+ // still-time-valid token mid-session (e.g. JWT_SECRET rotation).
86
+ // The first call (initial whoami) and every call after the forced
87
+ // re-auth succeed normally.
88
+ if (agentGetCalls === 2) {
89
+ res.writeHead(401, { 'content-type': 'application/json' });
90
+ res.end(JSON.stringify({ success: false, error: { code: 'TOKEN_EXPIRED', message: 'token rejected' } }));
91
+ return;
92
+ }
93
+ res.writeHead(200, { 'content-type': 'application/json' });
94
+ res.end(JSON.stringify({ success: true, data: { id: 'agent-1', name: 'Agent1', status: 'active' } }));
95
+ return;
96
+ }
97
+ // Hydration / memory refresh / handoff / resolve / key-enroll — generic success.
98
+ res.writeHead(200, { 'content-type': 'application/json' });
99
+ res.end(JSON.stringify({ success: true, data: {} }));
100
+ });
101
+ });
102
+ await new Promise((r) => {
103
+ fakeAis.listen(0, '127.0.0.1', () => {
104
+ const addr = fakeAis.address();
105
+ if (addr && typeof addr === 'object')
106
+ port = addr.port;
107
+ r();
108
+ });
109
+ });
110
+ });
111
+ afterAll(async () => {
112
+ await new Promise((r) => fakeAis?.close(() => r()));
113
+ });
114
+ it('re-proves and the retried tool call succeeds; auth.key.success + its stderr line fire exactly once', async () => {
115
+ const distPath = resolve(__dirname, '..', '..', 'dist', 'index.js');
116
+ if (!existsSync(distPath)) {
117
+ throw new Error(`dist/index.js missing — run \`pnpm build\` first (looked at ${distPath})`);
118
+ }
119
+ proveCalls = 0;
120
+ agentGetCalls = 0;
121
+ const fakeHome = mkdtempSync(join(tmpdir(), 'aismem-token-rejection-'));
122
+ const aismemDir = join(fakeHome, '.aismemory');
123
+ mkdirSync(aismemDir, { recursive: true });
124
+ await generateAndSaveKeypair({
125
+ userId: 'user-1',
126
+ userDid: 'did:web:test:users:user-1',
127
+ keysDir: join(aismemDir, 'keys'),
128
+ });
129
+ let proc = null;
130
+ try {
131
+ proc = spawn('node', [distPath], {
132
+ env: { ...process.env, HOME: fakeHome, AIS_URL: `http://127.0.0.1:${port}`, AISMEMORY_TELEMETRY: '1' },
133
+ stdio: ['pipe', 'pipe', 'pipe'],
134
+ });
135
+ let stdoutBuf = '';
136
+ let stderrBuf = '';
137
+ proc.stdout.on('data', (d) => { stdoutBuf += d.toString(); });
138
+ proc.stderr.on('data', (d) => { stderrBuf += d.toString(); });
139
+ proc.stdin.write(JSON.stringify({
140
+ jsonrpc: '2.0', id: 1, method: 'initialize',
141
+ params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'token-rejection-test', version: '0.0.0' } },
142
+ }) + '\n');
143
+ await waitFor(() => stdoutBuf.includes('"id":1'), 5000, 'initialize response');
144
+ // ── whoami #1: establishes the manager's first acquire; ok. ──
145
+ proc.stdin.write(JSON.stringify({
146
+ jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'whoami', arguments: {} },
147
+ }) + '\n');
148
+ await waitFor(() => stdoutBuf.includes('"id":2'), 15000, 'first whoami');
149
+ expect(extractResponseText(stdoutBuf, 2)).toContain('agent-1');
150
+ expect(proveCalls).toBe(1);
151
+ expect(agentGetCalls).toBe(1);
152
+ // ── whoami #2: the fake server rejects this data call with a
153
+ // TOKEN_EXPIRED 401 even though the token is still time-valid. The
154
+ // client must invalidate() the manager, re-prove, and retry the tool
155
+ // call once — succeeding transparently in the SAME response. ──
156
+ proc.stdin.write(JSON.stringify({
157
+ jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: 'whoami', arguments: {} },
158
+ }) + '\n');
159
+ await waitFor(() => stdoutBuf.includes('"id":3'), 15000, 'whoami after server-side rejection');
160
+ const retriedText = extractResponseText(stdoutBuf, 3);
161
+ expect(retriedText).toContain('agent-1');
162
+ expect(retriedText).not.toContain('error');
163
+ // The rejection forced a real re-prove — not a no-op retry of the
164
+ // same rejected token — and the underlying data call was attempted a
165
+ // third time (2nd = rejected, 3rd = succeeded with the new token).
166
+ expect(proveCalls).toBe(2);
167
+ expect(agentGetCalls).toBe(3);
168
+ // The existing re-auth-and-retry log line fired exactly once.
169
+ const retryLogHits = stderrBuf.split('AIS rejected the cached token; re-authenticating and retrying once...').length - 1;
170
+ expect(retryLogHits).toBe(1);
171
+ // ── Fix 3: auth.key.success + its stderr confirmation fire on the
172
+ // FIRST successful key-enrolled acquire only — not again on the
173
+ // forced re-auth triggered by the 401 retry. ──
174
+ const stderrConfirmationHits = stderrBuf.split('Authenticated via DID key auth (no browser required)').length - 1;
175
+ expect(stderrConfirmationHits).toBe(1);
176
+ const telemetryKeySuccessHits = stderrBuf
177
+ .split('\n')
178
+ .filter((line) => line.includes('[aismemory-telemetry]') && line.includes('"auth.key.success"'))
179
+ .length;
180
+ expect(telemetryKeySuccessHits).toBe(1);
181
+ }
182
+ finally {
183
+ proc?.kill();
184
+ rmSync(fakeHome, { recursive: true, force: true });
185
+ }
186
+ }, 30000);
187
+ });
188
+ function waitFor(predicate, ms, label) {
189
+ return new Promise((resolveOuter, reject) => {
190
+ const start = Date.now();
191
+ const tick = () => {
192
+ if (predicate()) {
193
+ resolveOuter();
194
+ return;
195
+ }
196
+ if (Date.now() - start > ms) {
197
+ reject(new Error(`timeout waiting for ${label}`));
198
+ return;
199
+ }
200
+ setTimeout(tick, 50);
201
+ };
202
+ tick();
203
+ });
204
+ }
205
+ function extractResponseText(buf, id) {
206
+ for (const line of buf.split('\n')) {
207
+ if (!line.includes(`"id":${id}`))
208
+ continue;
209
+ try {
210
+ const parsed = JSON.parse(line);
211
+ const text = parsed.result?.content?.[0]?.text;
212
+ if (typeof text === 'string')
213
+ return text;
214
+ }
215
+ catch { /* malformed line; skip */ }
216
+ }
217
+ return '';
218
+ }
219
+ //# sourceMappingURL=token-rejection-reauth-e2e.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-rejection-reauth-e2e.test.js","sourceRoot":"","sources":["../../src/__tests__/token-rejection-reauth-e2e.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,YAAY,EAA0D,MAAM,WAAW,CAAC;AACjG,OAAO,EAAE,KAAK,EAAuC,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAEnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAExD,0EAA0E;AAC1E,sEAAsE;AACtE,sEAAsE;AACtE,MAAM,WAAW,GAAG,MAAM,CAAC;AAE3B,IAAI,UAAU,GAAG,CAAC,CAAC;AACnB,6EAA6E;AAC7E,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB,SAAS,OAAO;IACd,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC/F,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CACzB,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAC9F,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACxB,OAAO,GAAG,MAAM,IAAI,OAAO,YAAY,UAAU,EAAE,CAAC;AACtD,CAAC;AAED,QAAQ,CAAC,iFAAiF,EAAE,GAAG,EAAE;IAC/F,IAAI,OAA2B,CAAC;IAChC,IAAI,IAAI,GAAG,CAAC,CAAC;IAEb,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,OAAO,GAAG,YAAY,CAAC,CAAC,GAAoB,EAAE,GAAmB,EAAE,EAAE;YACnE,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,GAAoD,CAAC,CAAC,CAAC;YAC3E,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC;gBAE1B,IAAI,GAAG,KAAK,oBAAoB,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;oBAC1D,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;oBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;wBACrB,OAAO,EAAE,IAAI;wBACb,IAAI,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE;qBAC1F,CAAC,CAAC,CAAC;oBACJ,OAAO;gBACT,CAAC;gBAED,IAAI,GAAG,KAAK,oBAAoB,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;oBAC1D,UAAU,IAAI,CAAC,CAAC;oBAChB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;oBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;wBACrB,OAAO,EAAE,IAAI;wBACb,IAAI,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC,CAAC,WAAW,EAAE,EAAE;qBAC9F,CAAC,CAAC,CAAC;oBACJ,OAAO;gBACT,CAAC;gBAED,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,mBAAmB,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;oBAC/F,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;oBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;wBACrB,OAAO,EAAE,IAAI;wBACb,IAAI,EAAE;4BACJ,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;4BAC7D,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;yBAC9D;qBACF,CAAC,CAAC,CAAC;oBACJ,OAAO;gBACT,CAAC;gBAED,IAAI,GAAG,KAAK,oBAAoB,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;oBACzD,aAAa,IAAI,CAAC,CAAC;oBACnB,kEAAkE;oBAClE,iEAAiE;oBACjE,kEAAkE;oBAClE,4BAA4B;oBAC5B,IAAI,aAAa,KAAK,CAAC,EAAE,CAAC;wBACxB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;wBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,CAAC,CAAC,CAAC;wBACzG,OAAO;oBACT,CAAC;oBACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;oBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;oBACtG,OAAO;gBACT,CAAC;gBAED,iFAAiF;gBACjF,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YACvD,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE;YAC5B,OAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;gBACnC,MAAM,IAAI,GAAG,OAAQ,CAAC,OAAO,EAAE,CAAC;gBAChC,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;oBAAE,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;gBACvD,CAAC,EAAE,CAAC;YACN,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,KAAK,IAAI,EAAE;QAClB,MAAM,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oGAAoG,EAAE,KAAK,IAAI,EAAE;QAClH,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;QACpE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,+DAA+D,QAAQ,GAAG,CAAC,CAAC;QAC9F,CAAC;QAED,UAAU,GAAG,CAAC,CAAC;QACf,aAAa,GAAG,CAAC,CAAC;QAClB,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,yBAAyB,CAAC,CAAC,CAAC;QACxE,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC/C,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,MAAM,sBAAsB,CAAC;YAC3B,MAAM,EAAE,QAAQ;YAChB,OAAO,EAAE,2BAA2B;YACpC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC;SACjC,CAAC,CAAC;QAEH,IAAI,IAAI,GAA0C,IAAI,CAAC;QACvD,IAAI,CAAC;YACH,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE;gBAC/B,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,oBAAoB,IAAI,EAAE,EAAE,mBAAmB,EAAE,GAAG,EAAE;gBACtG,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;aAChC,CAAC,CAAC;YACH,IAAI,SAAS,GAAG,EAAE,CAAC;YACnB,IAAI,SAAS,GAAG,EAAE,CAAC;YACnB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,GAAG,SAAS,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YACtE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,GAAG,SAAS,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAEtE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;gBAC9B,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY;gBAC3C,MAAM,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;aAC5H,CAAC,GAAG,IAAI,CAAC,CAAC;YACX,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,qBAAqB,CAAC,CAAC;YAE/E,gEAAgE;YAChE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;gBAC9B,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE;aACvF,CAAC,GAAG,IAAI,CAAC,CAAC;YACX,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;YACzE,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YAC/D,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC3B,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAE9B,8DAA8D;YAC9D,mEAAmE;YACnE,qEAAqE;YACrE,gEAAgE;YAChE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;gBAC9B,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,EAAE;aACvF,CAAC,GAAG,IAAI,CAAC,CAAC;YACX,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,oCAAoC,CAAC,CAAC;YAC/F,MAAM,WAAW,GAAG,mBAAmB,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;YACtD,MAAM,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YACzC,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAE3C,kEAAkE;YAClE,qEAAqE;YACrE,mEAAmE;YACnE,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC3B,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAE9B,8DAA8D;YAC9D,MAAM,YAAY,GAAG,SAAS,CAAC,KAAK,CAAC,uEAAuE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;YACzH,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAE7B,mEAAmE;YACnE,gEAAgE;YAChE,gDAAgD;YAChD,MAAM,sBAAsB,GAAG,SAAS,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;YAClH,MAAM,CAAC,sBAAsB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAEvC,MAAM,uBAAuB,GAAG,SAAS;iBACtC,KAAK,CAAC,IAAI,CAAC;iBACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,uBAAuB,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;iBAC/F,MAAM,CAAC;YACV,MAAM,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,CAAC;gBAAS,CAAC;YACT,IAAI,EAAE,IAAI,EAAE,CAAC;YACb,MAAM,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,CAAC;IACH,CAAC,EAAE,KAAK,CAAC,CAAC;AACZ,CAAC,CAAC,CAAC;AAEH,SAAS,OAAO,CAAC,SAAwB,EAAE,EAAU,EAAE,KAAa;IAClE,OAAO,IAAI,OAAO,CAAC,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,GAAS,EAAE;YACtB,IAAI,SAAS,EAAE,EAAE,CAAC;gBAAC,YAAY,EAAE,CAAC;gBAAC,OAAO;YAAC,CAAC;YAC5C,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,EAAE,EAAE,CAAC;gBAAC,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,KAAK,EAAE,CAAC,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YAC3F,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvB,CAAC,CAAC;QACF,IAAI,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,mBAAmB,CAAC,GAAW,EAAE,EAAU;IAClD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC;YAAE,SAAS;QAC3C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAwD,CAAC;YACvF,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;YAC/C,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC,CAAC,0BAA0B,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,24 @@
1
+ export interface AgentPreference {
2
+ schemaVersion: 2;
3
+ agentId: string;
4
+ tenantId: string;
5
+ }
6
+ /**
7
+ * Persist agent preference (schemaVersion + ids, no secrets) under
8
+ * ~/.aismemory/credentials.json with restrictive POSIX permissions
9
+ * (dir 0700, file 0600). Replaces legacy token-based persistence.
10
+ */
11
+ export declare function saveAgentPreference(pref: {
12
+ agentId: string;
13
+ tenantId: string;
14
+ }, home?: string): void;
15
+ /**
16
+ * Load agent preference from ~/.aismemory/credentials.json.
17
+ * Handles both v2 files (agentId/tenantId only) and legacy v1 files
18
+ * (token/agentId/tenantId/expiresAt) — returns ids only.
19
+ * Returns null if file is missing or corrupt.
20
+ */
21
+ export declare function loadAgentPreference(home?: string): {
22
+ agentId: string;
23
+ tenantId: string;
24
+ } | null;
@@ -0,0 +1,51 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { credentialsDir, credentialsFilePath } from './oauth-credentials.js';
4
+ /**
5
+ * Persist agent preference (schemaVersion + ids, no secrets) under
6
+ * ~/.aismemory/credentials.json with restrictive POSIX permissions
7
+ * (dir 0700, file 0600). Replaces legacy token-based persistence.
8
+ */
9
+ export function saveAgentPreference(pref, home = homedir()) {
10
+ const dir = credentialsDir(home);
11
+ const file = credentialsFilePath(home);
12
+ // Create directory with restrictive permissions (0700)
13
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
14
+ // Write the file with schemaVersion + ids (no legacy token/expiresAt)
15
+ const data = {
16
+ schemaVersion: 2,
17
+ agentId: pref.agentId,
18
+ tenantId: pref.tenantId,
19
+ };
20
+ writeFileSync(file, JSON.stringify(data, null, 2), { mode: 0o600 });
21
+ // chmod is the reliable path on POSIX
22
+ if (process.platform !== 'win32') {
23
+ chmodSync(file, 0o600);
24
+ }
25
+ }
26
+ /**
27
+ * Load agent preference from ~/.aismemory/credentials.json.
28
+ * Handles both v2 files (agentId/tenantId only) and legacy v1 files
29
+ * (token/agentId/tenantId/expiresAt) — returns ids only.
30
+ * Returns null if file is missing or corrupt.
31
+ */
32
+ export function loadAgentPreference(home = homedir()) {
33
+ const file = credentialsFilePath(home);
34
+ try {
35
+ if (!existsSync(file))
36
+ return null;
37
+ const content = readFileSync(file, 'utf-8');
38
+ const parsed = JSON.parse(content);
39
+ // Extract agentId and tenantId, ignoring any other fields (like token)
40
+ const agentId = parsed.agentId;
41
+ const tenantId = parsed.tenantId;
42
+ if (typeof agentId === 'string' && typeof tenantId === 'string') {
43
+ return { agentId, tenantId };
44
+ }
45
+ return null;
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ //# sourceMappingURL=agent-preference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-preference.js","sourceRoot":"","sources":["../src/agent-preference.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAQ7E;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAA2C,EAC3C,OAAe,OAAO,EAAE;IAExB,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAEvC,uDAAuD;IACvD,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAEjD,sEAAsE;IACtE,MAAM,IAAI,GAAG;QACX,aAAa,EAAE,CAAC;QAChB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC;IAEF,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAEpE,sCAAsC;IACtC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe,OAAO,EAAE;IAI1D,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QAEnC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA4B,CAAC;QAE9D,uEAAuE;QACvE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC/B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAEjC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAChE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;QAC/B,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
package/dist/index.d.ts CHANGED
@@ -7,4 +7,15 @@
7
7
  *
8
8
  * Usage: npx aismemory
9
9
  */
10
+ type AgentSummary = {
11
+ id: string;
12
+ name: string;
13
+ status: string;
14
+ };
15
+ /**
16
+ * Fetch EVERY agent in the tenant, following server pagination.
17
+ * The server defaults to limit=20 and silently truncates (2026-08-24
18
+ * lockout, ticket 72ab7f4e) — never call GET /v1/agents without this.
19
+ */
20
+ export declare function listAllAgents(token: string): Promise<AgentSummary[]>;
10
21
  export {};