aismemory 0.4.0 → 0.5.1
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__/auth-staleness.test.d.ts +9 -0
- package/dist/__tests__/auth-staleness.test.js +46 -0
- package/dist/__tests__/auth-staleness.test.js.map +1 -0
- package/dist/__tests__/auto-handoff.test.js +108 -2
- package/dist/__tests__/auto-handoff.test.js.map +1 -1
- package/dist/__tests__/config.test.js +6 -3
- package/dist/__tests__/config.test.js.map +1 -1
- package/dist/__tests__/device-flow-recovery.test.d.ts +1 -0
- package/dist/__tests__/device-flow-recovery.test.js +206 -0
- package/dist/__tests__/device-flow-recovery.test.js.map +1 -0
- package/dist/__tests__/env-agent.test.d.ts +1 -0
- package/dist/__tests__/env-agent.test.js +19 -0
- package/dist/__tests__/env-agent.test.js.map +1 -0
- package/dist/__tests__/existing-hashes.test.d.ts +1 -0
- package/dist/__tests__/existing-hashes.test.js +122 -0
- package/dist/__tests__/existing-hashes.test.js.map +1 -0
- package/dist/__tests__/hydration.test.js +38 -0
- package/dist/__tests__/hydration.test.js.map +1 -1
- package/dist/__tests__/key-auth.test.d.ts +1 -0
- package/dist/__tests__/key-auth.test.js +284 -0
- package/dist/__tests__/key-auth.test.js.map +1 -0
- package/dist/__tests__/local-mirror.test.js +4 -0
- package/dist/__tests__/local-mirror.test.js.map +1 -1
- package/dist/__tests__/oauth-credentials.test.d.ts +1 -0
- package/dist/__tests__/oauth-credentials.test.js +29 -0
- package/dist/__tests__/oauth-credentials.test.js.map +1 -0
- package/dist/__tests__/pipeline-ingestion.test.js +112 -1
- package/dist/__tests__/pipeline-ingestion.test.js.map +1 -1
- package/dist/__tests__/refresh.test.js +24 -0
- package/dist/__tests__/refresh.test.js.map +1 -1
- package/dist/__tests__/sync-memory-cli.test.d.ts +1 -0
- package/dist/__tests__/sync-memory-cli.test.js +200 -0
- package/dist/__tests__/sync-memory-cli.test.js.map +1 -0
- package/dist/__tests__/telemetry.test.d.ts +1 -0
- package/dist/__tests__/telemetry.test.js +67 -0
- package/dist/__tests__/telemetry.test.js.map +1 -0
- package/dist/__tests__/token-expiry-reauth.test.d.ts +1 -0
- package/dist/__tests__/token-expiry-reauth.test.js +201 -0
- package/dist/__tests__/token-expiry-reauth.test.js.map +1 -0
- package/dist/__tests__/tool-args.test.d.ts +1 -0
- package/dist/__tests__/tool-args.test.js +78 -0
- package/dist/__tests__/tool-args.test.js.map +1 -0
- package/dist/auth-staleness.d.ts +27 -0
- package/dist/auth-staleness.js +41 -0
- package/dist/auth-staleness.js.map +1 -0
- package/dist/auto-handoff.d.ts +2 -0
- package/dist/auto-handoff.js +40 -16
- package/dist/auto-handoff.js.map +1 -1
- package/dist/cli/enable-key-auth.d.ts +1 -0
- package/dist/cli/enable-key-auth.js +131 -0
- package/dist/cli/enable-key-auth.js.map +1 -0
- package/dist/cli/sync-memory.js +31 -36
- package/dist/cli/sync-memory.js.map +1 -1
- package/dist/config.js +4 -1
- package/dist/config.js.map +1 -1
- package/dist/env-agent.d.ts +10 -0
- package/dist/env-agent.js +14 -0
- package/dist/env-agent.js.map +1 -0
- package/dist/hydration.js +54 -3
- package/dist/hydration.js.map +1 -1
- package/dist/index.js +314 -118
- package/dist/index.js.map +1 -1
- package/dist/key-auth.d.ts +66 -0
- package/dist/key-auth.js +179 -0
- package/dist/key-auth.js.map +1 -0
- package/dist/local-mirror.d.ts +5 -0
- package/dist/local-mirror.js +72 -14
- package/dist/local-mirror.js.map +1 -1
- package/dist/oauth-credentials.d.ts +14 -0
- package/dist/oauth-credentials.js +35 -0
- package/dist/oauth-credentials.js.map +1 -0
- package/dist/pipeline/bulk-store.d.ts +46 -0
- package/dist/pipeline/bulk-store.js +165 -0
- package/dist/pipeline/bulk-store.js.map +1 -0
- package/dist/pipeline/existing-hashes.d.ts +20 -0
- package/dist/pipeline/existing-hashes.js +111 -0
- package/dist/pipeline/existing-hashes.js.map +1 -0
- package/dist/pipeline/ingestion.d.ts +8 -4
- package/dist/pipeline/ingestion.js +36 -8
- package/dist/pipeline/ingestion.js.map +1 -1
- package/dist/telemetry.d.ts +22 -0
- package/dist/telemetry.js +28 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/tool-args.d.ts +52 -0
- package/dist/tool-args.js +78 -0
- package/dist/tool-args.js.map +1 -0
- package/dist/trust-ledger.js +2 -2
- package/dist/trust-ledger.js.map +1 -1
- package/package.json +9 -4
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential staleness + auth-error detection for the MCP re-auth path.
|
|
3
|
+
*
|
|
4
|
+
* Root cause (CORBOT-A5BBD580): ensureCredentials() returned cached creds
|
|
5
|
+
* without re-checking expiresAt, and no TOKEN_EXPIRED response ever cleared
|
|
6
|
+
* them — so a session starting near the end of the 7-day token life failed
|
|
7
|
+
* every memory write until the process was restarted.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential staleness + auth-error detection for the MCP re-auth path.
|
|
3
|
+
*
|
|
4
|
+
* Root cause (CORBOT-A5BBD580): ensureCredentials() returned cached creds
|
|
5
|
+
* without re-checking expiresAt, and no TOKEN_EXPIRED response ever cleared
|
|
6
|
+
* them — so a session starting near the end of the 7-day token life failed
|
|
7
|
+
* every memory write until the process was restarted.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, it } from 'vitest';
|
|
10
|
+
import { CREDS_STALENESS_MARGIN_MS, isCredsStale, isAuthErrorResult, } from '../auth-staleness.js';
|
|
11
|
+
describe('isCredsStale', () => {
|
|
12
|
+
const now = Date.parse('2026-06-10T00:00:00Z');
|
|
13
|
+
it('is fresh when expiry is beyond the safety margin', () => {
|
|
14
|
+
const expiresAt = new Date(now + CREDS_STALENESS_MARGIN_MS + 60_000).toISOString();
|
|
15
|
+
expect(isCredsStale(expiresAt, now)).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
it('is stale when expiry is inside the safety margin', () => {
|
|
18
|
+
const expiresAt = new Date(now + CREDS_STALENESS_MARGIN_MS - 1_000).toISOString();
|
|
19
|
+
expect(isCredsStale(expiresAt, now)).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
it('is stale when already expired', () => {
|
|
22
|
+
const expiresAt = new Date(now - 1_000).toISOString();
|
|
23
|
+
expect(isCredsStale(expiresAt, now)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it('is stale when expiresAt is unparseable', () => {
|
|
26
|
+
expect(isCredsStale('not-a-date', now)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('isAuthErrorResult', () => {
|
|
30
|
+
it('detects TOKEN_EXPIRED error responses', () => {
|
|
31
|
+
expect(isAuthErrorResult({ success: false, error: { code: 'TOKEN_EXPIRED' } })).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
it('detects INVALID_TOKEN error responses', () => {
|
|
34
|
+
expect(isAuthErrorResult({ success: false, error: { code: 'INVALID_TOKEN' } })).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
it('ignores success results', () => {
|
|
37
|
+
expect(isAuthErrorResult({ success: true, data: {} })).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
it('ignores non-auth errors and non-objects', () => {
|
|
40
|
+
expect(isAuthErrorResult({ success: false, error: { code: 'NOT_FOUND' } })).toBe(false);
|
|
41
|
+
expect(isAuthErrorResult(null)).toBe(false);
|
|
42
|
+
expect(isAuthErrorResult('TOKEN_EXPIRED')).toBe(false);
|
|
43
|
+
expect(isAuthErrorResult({ success: false })).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=auth-staleness.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-staleness.test.js","sourceRoot":"","sources":["../../src/__tests__/auth-staleness.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { ActivityTracker, buildHandoffPayload } from '../auto-handoff.js';
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { ActivityTracker, buildHandoffPayload, setupAutoHandoff } from '../auto-handoff.js';
|
|
3
|
+
const MOCK_AIS_URL = 'https://ais.example.com';
|
|
4
|
+
const MOCK_AGENT_ID = 'agent-123';
|
|
5
|
+
const MOCK_TOKEN = 'test-token';
|
|
3
6
|
describe('ActivityTracker', () => {
|
|
4
7
|
it('tracks tool usage counts', () => {
|
|
5
8
|
const tracker = new ActivityTracker();
|
|
@@ -68,5 +71,108 @@ describe('buildHandoffPayload', () => {
|
|
|
68
71
|
expect(payload.summary).not.toContain('memories.');
|
|
69
72
|
expect(payload.keyLearnings).toEqual([]);
|
|
70
73
|
});
|
|
74
|
+
it('caps keyLearnings to the last N snippets while summary keeps total count', () => {
|
|
75
|
+
const tracker = new ActivityTracker();
|
|
76
|
+
tracker.recordToolUse('store_memory');
|
|
77
|
+
for (let i = 1; i <= 12; i++) {
|
|
78
|
+
tracker.recordMemoryStored(`memory-${i}`);
|
|
79
|
+
}
|
|
80
|
+
const payload = buildHandoffPayload(tracker);
|
|
81
|
+
expect(payload.summary).toContain('Stored 12 memories.');
|
|
82
|
+
expect(payload.keyLearnings).toHaveLength(10);
|
|
83
|
+
expect(payload.keyLearnings[0]).toBe('memory-3');
|
|
84
|
+
expect(payload.keyLearnings[9]).toBe('memory-12');
|
|
85
|
+
});
|
|
86
|
+
it('truncates oversized memory snippets', () => {
|
|
87
|
+
const tracker = new ActivityTracker();
|
|
88
|
+
tracker.recordToolUse('store_memory');
|
|
89
|
+
const longContent = 'x'.repeat(600);
|
|
90
|
+
tracker.recordMemoryStored(longContent);
|
|
91
|
+
const payload = buildHandoffPayload(tracker);
|
|
92
|
+
expect(payload.keyLearnings).toHaveLength(1);
|
|
93
|
+
expect(payload.keyLearnings[0]).toHaveLength(513);
|
|
94
|
+
expect(payload.keyLearnings[0]?.endsWith('…')).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('setupAutoHandoff', () => {
|
|
98
|
+
let sigtermHandler;
|
|
99
|
+
let sigintHandler;
|
|
100
|
+
let mockExit;
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
sigtermHandler = undefined;
|
|
103
|
+
sigintHandler = undefined;
|
|
104
|
+
vi.spyOn(process, 'on').mockImplementation((event, handler) => {
|
|
105
|
+
if (event === 'SIGTERM')
|
|
106
|
+
sigtermHandler = handler;
|
|
107
|
+
if (event === 'SIGINT')
|
|
108
|
+
sigintHandler = handler;
|
|
109
|
+
return process;
|
|
110
|
+
});
|
|
111
|
+
mockExit = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
|
|
112
|
+
});
|
|
113
|
+
afterEach(() => {
|
|
114
|
+
vi.restoreAllMocks();
|
|
115
|
+
});
|
|
116
|
+
it('registers SIGTERM and SIGINT handlers', () => {
|
|
117
|
+
const tracker = new ActivityTracker();
|
|
118
|
+
setupAutoHandoff(MOCK_AIS_URL, MOCK_AGENT_ID, MOCK_TOKEN, tracker);
|
|
119
|
+
expect(sigtermHandler).toBeTypeOf('function');
|
|
120
|
+
expect(sigintHandler).toBeTypeOf('function');
|
|
121
|
+
});
|
|
122
|
+
it('awaits handoff fetch before process.exit on SIGTERM', async () => {
|
|
123
|
+
let fetchResolved = false;
|
|
124
|
+
const mockFetch = vi.fn().mockImplementation(() => new Promise((resolve) => {
|
|
125
|
+
setTimeout(() => {
|
|
126
|
+
fetchResolved = true;
|
|
127
|
+
resolve(new Response(null, { status: 200 }));
|
|
128
|
+
}, 10);
|
|
129
|
+
}));
|
|
130
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
131
|
+
const tracker = new ActivityTracker();
|
|
132
|
+
tracker.recordToolUse('store_memory');
|
|
133
|
+
const onBeforeExit = vi.fn();
|
|
134
|
+
setupAutoHandoff(MOCK_AIS_URL, MOCK_AGENT_ID, MOCK_TOKEN, tracker, onBeforeExit);
|
|
135
|
+
sigtermHandler?.();
|
|
136
|
+
await vi.waitFor(() => expect(mockExit).toHaveBeenCalledWith(0));
|
|
137
|
+
expect(fetchResolved).toBe(true);
|
|
138
|
+
expect(mockFetch).toHaveBeenCalledOnce();
|
|
139
|
+
expect(onBeforeExit).toHaveBeenCalledOnce();
|
|
140
|
+
expect(mockExit.mock.invocationCallOrder[0]).toBeGreaterThan(mockFetch.mock.invocationCallOrder[0]);
|
|
141
|
+
});
|
|
142
|
+
it('skips fetch and onBeforeExit when there is no activity', async () => {
|
|
143
|
+
const mockFetch = vi.fn();
|
|
144
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
145
|
+
const onBeforeExit = vi.fn();
|
|
146
|
+
setupAutoHandoff(MOCK_AIS_URL, MOCK_AGENT_ID, MOCK_TOKEN, new ActivityTracker(), onBeforeExit);
|
|
147
|
+
sigintHandler?.();
|
|
148
|
+
await vi.waitFor(() => expect(mockExit).toHaveBeenCalledWith(0));
|
|
149
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
150
|
+
expect(onBeforeExit).not.toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
it('exits after fetch failure without throwing', async () => {
|
|
153
|
+
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network error')));
|
|
154
|
+
const tracker = new ActivityTracker();
|
|
155
|
+
tracker.recordToolUse('recall_memories');
|
|
156
|
+
const onBeforeExit = vi.fn();
|
|
157
|
+
setupAutoHandoff(MOCK_AIS_URL, MOCK_AGENT_ID, MOCK_TOKEN, tracker, onBeforeExit);
|
|
158
|
+
sigtermHandler?.();
|
|
159
|
+
await vi.waitFor(() => expect(mockExit).toHaveBeenCalledWith(0));
|
|
160
|
+
expect(onBeforeExit).toHaveBeenCalledOnce();
|
|
161
|
+
});
|
|
162
|
+
it('ignores duplicate signal while handoff is in flight', async () => {
|
|
163
|
+
let resolveFetch;
|
|
164
|
+
const mockFetch = vi.fn().mockImplementation(() => new Promise((resolve) => {
|
|
165
|
+
resolveFetch = () => resolve(new Response(null, { status: 200 }));
|
|
166
|
+
}));
|
|
167
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
168
|
+
const tracker = new ActivityTracker();
|
|
169
|
+
tracker.recordToolUse('store_memory');
|
|
170
|
+
setupAutoHandoff(MOCK_AIS_URL, MOCK_AGENT_ID, MOCK_TOKEN, tracker);
|
|
171
|
+
sigtermHandler?.();
|
|
172
|
+
sigintHandler?.();
|
|
173
|
+
resolveFetch?.();
|
|
174
|
+
await vi.waitFor(() => expect(mockExit).toHaveBeenCalledWith(0));
|
|
175
|
+
expect(mockFetch).toHaveBeenCalledOnce();
|
|
176
|
+
});
|
|
71
177
|
});
|
|
72
178
|
//# sourceMappingURL=auto-handoff.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-handoff.test.js","sourceRoot":"","sources":["../../src/__tests__/auto-handoff.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"auto-handoff.test.js","sourceRoot":"","sources":["../../src/__tests__/auto-handoff.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,16 +38,19 @@ describe('SyncConfig', () => {
|
|
|
38
38
|
it('recovers from corrupt config file by backing it up', () => {
|
|
39
39
|
mkdirSync(join(tmpHome, '.aismemory'), { recursive: true });
|
|
40
40
|
writeFileSync(join(tmpHome, '.aismemory', 'config.json'), '{not valid json');
|
|
41
|
-
|
|
42
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
41
|
+
const stderrWrite = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
43
42
|
try {
|
|
44
43
|
const cfg = new SyncConfig(tmpHome);
|
|
45
44
|
expect(cfg.retentionDays).toBe(30); // falls back to default
|
|
46
45
|
const files = readdirSync(join(tmpHome, '.aismemory'));
|
|
47
46
|
expect(files.some((f) => f.startsWith('config.json.corrupt-'))).toBe(true);
|
|
47
|
+
expect(stderrWrite).toHaveBeenCalled();
|
|
48
|
+
const written = stderrWrite.mock.calls.map((c) => String(c[0])).join('');
|
|
49
|
+
expect(written).toContain('corrupt config.json backed up to');
|
|
50
|
+
expect(written).toContain('[aismemory]');
|
|
48
51
|
}
|
|
49
52
|
finally {
|
|
50
|
-
|
|
53
|
+
stderrWrite.mockRestore();
|
|
51
54
|
}
|
|
52
55
|
});
|
|
53
56
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { mkdtempSync, rmSync, existsSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join, resolve } from 'path';
|
|
6
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
7
|
+
/**
|
|
8
|
+
* Regression: when the OAuth device-flow poll rejects (expired_token,
|
|
9
|
+
* access_denied, or 30-min deadline) the prior aismemory MCP would store the
|
|
10
|
+
* dead poll on `activeFlow` and every subsequent tool call would re-race
|
|
11
|
+
* against the rejected promise, propagating the same stale error. The only
|
|
12
|
+
* recovery was for the user to restart their AI tool — too friction-heavy for
|
|
13
|
+
* first-time users (they get distracted, the activation drops on the floor).
|
|
14
|
+
*
|
|
15
|
+
* After the fix, the MCP marks the flow `dead` on rejection and discards it,
|
|
16
|
+
* so the next tool call starts a fresh device flow inline and surfaces a new
|
|
17
|
+
* bond URL — no restart required.
|
|
18
|
+
*
|
|
19
|
+
* This test drives the binary against a fake AIS HTTP server that:
|
|
20
|
+
* - returns a unique user_code per `/v1/oauth/device/authorize` call
|
|
21
|
+
* - returns `expired_token` on the first `/v1/oauth/token` poll
|
|
22
|
+
* Two consecutive tool calls should produce two DIFFERENT user_codes in the
|
|
23
|
+
* surfaced bond URLs, proving the dead flow was discarded and replaced.
|
|
24
|
+
*/
|
|
25
|
+
describe('aismemory MCP device-flow recovery', () => {
|
|
26
|
+
let fakeAisServer;
|
|
27
|
+
let fakeAisPort = 0;
|
|
28
|
+
let authorizeCalls = 0;
|
|
29
|
+
const issuedCodes = [];
|
|
30
|
+
beforeAll(async () => {
|
|
31
|
+
fakeAisServer = createServer((req, res) => {
|
|
32
|
+
req.on('data', () => { });
|
|
33
|
+
req.on('end', () => {
|
|
34
|
+
if (req.url === '/v1/oauth/device/authorize' && req.method === 'POST') {
|
|
35
|
+
authorizeCalls += 1;
|
|
36
|
+
const userCode = `TEST-${String(authorizeCalls).padStart(4, '0')}`;
|
|
37
|
+
issuedCodes.push(userCode);
|
|
38
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
39
|
+
res.end(JSON.stringify({
|
|
40
|
+
success: true,
|
|
41
|
+
data: {
|
|
42
|
+
device_code: `device-${authorizeCalls}`,
|
|
43
|
+
user_code: userCode,
|
|
44
|
+
verification_uri: `http://127.0.0.1:${fakeAisPort}/activate`,
|
|
45
|
+
verification_uri_complete: `http://127.0.0.1:${fakeAisPort}/activate?user_code=${userCode}`,
|
|
46
|
+
expires_in: 1800,
|
|
47
|
+
interval: 1, // 1s poll → bounded to 5s min by the client
|
|
48
|
+
agent_id: `agent-${authorizeCalls}`,
|
|
49
|
+
provisional_tenant_id: `tenant-${authorizeCalls}`,
|
|
50
|
+
provisional_token: 'provisional',
|
|
51
|
+
},
|
|
52
|
+
}));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (req.url === '/v1/oauth/token' && req.method === 'POST') {
|
|
56
|
+
// Always reject the poll so the flow dies and recovery kicks in.
|
|
57
|
+
res.writeHead(400, { 'content-type': 'application/json' });
|
|
58
|
+
res.end(JSON.stringify({ error: 'expired_token' }));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
res.writeHead(404);
|
|
62
|
+
res.end();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
await new Promise((resolveServer) => {
|
|
66
|
+
fakeAisServer.listen(0, '127.0.0.1', () => {
|
|
67
|
+
const addr = fakeAisServer.address();
|
|
68
|
+
if (addr && typeof addr === 'object') {
|
|
69
|
+
fakeAisPort = addr.port;
|
|
70
|
+
}
|
|
71
|
+
resolveServer();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
afterAll(async () => {
|
|
76
|
+
await new Promise((r) => fakeAisServer?.close(() => r()));
|
|
77
|
+
});
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
authorizeCalls = 0;
|
|
80
|
+
issuedCodes.length = 0;
|
|
81
|
+
});
|
|
82
|
+
it('starts a fresh device flow after the previous one dies', async () => {
|
|
83
|
+
const distPath = resolve(__dirname, '..', '..', 'dist', 'index.js');
|
|
84
|
+
if (!existsSync(distPath)) {
|
|
85
|
+
throw new Error(`dist/index.js missing — run \`pnpm build\` first (looked at ${distPath})`);
|
|
86
|
+
}
|
|
87
|
+
const fakeHome = mkdtempSync(join(tmpdir(), 'aismem-recover-'));
|
|
88
|
+
let proc = null;
|
|
89
|
+
try {
|
|
90
|
+
proc = spawn('node', [distPath], {
|
|
91
|
+
env: {
|
|
92
|
+
...process.env,
|
|
93
|
+
HOME: fakeHome,
|
|
94
|
+
AIS_URL: `http://127.0.0.1:${fakeAisPort}`,
|
|
95
|
+
},
|
|
96
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
97
|
+
});
|
|
98
|
+
let stdoutBuf = '';
|
|
99
|
+
proc.stdout.on('data', (d) => { stdoutBuf += d.toString(); });
|
|
100
|
+
proc.stderr.on('data', () => { });
|
|
101
|
+
// 1. Handshake: initialize the MCP transport.
|
|
102
|
+
proc.stdin.write(JSON.stringify({
|
|
103
|
+
jsonrpc: '2.0',
|
|
104
|
+
id: 1,
|
|
105
|
+
method: 'initialize',
|
|
106
|
+
params: {
|
|
107
|
+
protocolVersion: '2024-11-05',
|
|
108
|
+
capabilities: {},
|
|
109
|
+
clientInfo: { name: 'recovery-test', version: '0.0.0' },
|
|
110
|
+
},
|
|
111
|
+
}) + '\n');
|
|
112
|
+
await waitFor(() => stdoutBuf.includes('"id":1'), 5000, 'initialize response');
|
|
113
|
+
// 2. First tool call triggers device flow #1. The poll will reject
|
|
114
|
+
// against the fake AIS (expired_token), marking the flow dead.
|
|
115
|
+
// The call surfaces BondPendingError with the first user_code.
|
|
116
|
+
proc.stdin.write(JSON.stringify({
|
|
117
|
+
jsonrpc: '2.0',
|
|
118
|
+
id: 2,
|
|
119
|
+
method: 'tools/call',
|
|
120
|
+
params: { name: 'whoami', arguments: {} },
|
|
121
|
+
}) + '\n');
|
|
122
|
+
await waitFor(() => stdoutBuf.includes('"id":2'), 15000, 'first whoami response');
|
|
123
|
+
const firstResponse = extractResponseText(stdoutBuf, 2);
|
|
124
|
+
// Give the poll time to actually reject — soft timeout is 7s, poll
|
|
125
|
+
// interval is min 5s, so the rejection lands ~5s after the call. We
|
|
126
|
+
// need that rejection observed before the second call so the `dead`
|
|
127
|
+
// flag is set when ensureCredentials runs again.
|
|
128
|
+
await new Promise((r) => setTimeout(r, 8000));
|
|
129
|
+
// 3. Second tool call — should detect the dead flow, start a fresh
|
|
130
|
+
// device flow, and surface a DIFFERENT user_code.
|
|
131
|
+
proc.stdin.write(JSON.stringify({
|
|
132
|
+
jsonrpc: '2.0',
|
|
133
|
+
id: 3,
|
|
134
|
+
method: 'tools/call',
|
|
135
|
+
params: { name: 'whoami', arguments: {} },
|
|
136
|
+
}) + '\n');
|
|
137
|
+
await waitFor(() => stdoutBuf.includes('"id":3'), 15000, 'second whoami response');
|
|
138
|
+
const secondResponse = extractResponseText(stdoutBuf, 3);
|
|
139
|
+
// Recovery proof: more than one device-flow authorize was made. Without
|
|
140
|
+
// the fix the MCP would re-race the dead poll forever and never call
|
|
141
|
+
// authorize a second time.
|
|
142
|
+
expect(authorizeCalls).toBeGreaterThanOrEqual(2);
|
|
143
|
+
expect(issuedCodes.length).toBeGreaterThanOrEqual(2);
|
|
144
|
+
// Both tool calls return a bond URL (no misleading "Authentication
|
|
145
|
+
// timed out" leak from the dead poll's stored rejection).
|
|
146
|
+
expect(firstResponse).toMatch(/Memory bond required/);
|
|
147
|
+
expect(secondResponse).toMatch(/Memory bond required/);
|
|
148
|
+
// The two responses must surface DIFFERENT codes — proving the second
|
|
149
|
+
// call doesn't keep showing the original (now-dead) code.
|
|
150
|
+
const firstCode = extractUserCode(firstResponse);
|
|
151
|
+
const secondCode = extractUserCode(secondResponse);
|
|
152
|
+
expect(firstCode).toBeTruthy();
|
|
153
|
+
expect(secondCode).toBeTruthy();
|
|
154
|
+
expect(firstCode).not.toEqual(secondCode);
|
|
155
|
+
expect(issuedCodes).toContain(firstCode);
|
|
156
|
+
expect(issuedCodes).toContain(secondCode);
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
proc?.kill();
|
|
160
|
+
try {
|
|
161
|
+
rmSync(fakeHome, { recursive: true, force: true });
|
|
162
|
+
}
|
|
163
|
+
catch { /* ignore */ }
|
|
164
|
+
}
|
|
165
|
+
}, 45000);
|
|
166
|
+
});
|
|
167
|
+
function waitFor(predicate, ms, label) {
|
|
168
|
+
return new Promise((resolveOuter, reject) => {
|
|
169
|
+
const start = Date.now();
|
|
170
|
+
const tick = () => {
|
|
171
|
+
if (predicate()) {
|
|
172
|
+
resolveOuter();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (Date.now() - start > ms) {
|
|
176
|
+
reject(new Error(`timeout waiting for ${label}`));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
setTimeout(tick, 50);
|
|
180
|
+
};
|
|
181
|
+
tick();
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function extractUserCode(responseText) {
|
|
185
|
+
const match = /TEST-\d{4}/.exec(responseText);
|
|
186
|
+
return match ? match[0] : null;
|
|
187
|
+
}
|
|
188
|
+
function extractResponseText(buf, id) {
|
|
189
|
+
// MCP responses are newline-delimited JSON. Find the line with our id and
|
|
190
|
+
// pull the content[0].text payload out.
|
|
191
|
+
for (const line of buf.split('\n')) {
|
|
192
|
+
if (!line.includes(`"id":${id}`))
|
|
193
|
+
continue;
|
|
194
|
+
try {
|
|
195
|
+
const parsed = JSON.parse(line);
|
|
196
|
+
const text = parsed.result?.content?.[0]?.text;
|
|
197
|
+
if (typeof text === 'string')
|
|
198
|
+
return text;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
/* malformed line; skip */
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return '';
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=device-flow-recovery.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-flow-recovery.test.js","sourceRoot":"","sources":["../../src/__tests__/device-flow-recovery.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { assertEnvAgentInTenant } from '../env-agent.js';
|
|
3
|
+
describe('assertEnvAgentInTenant', () => {
|
|
4
|
+
const agents = [
|
|
5
|
+
{ id: 'agent-1', name: 'Corbot', status: 'active' },
|
|
6
|
+
{ id: 'agent-2', name: 'Data', status: 'active' },
|
|
7
|
+
];
|
|
8
|
+
it('returns the matching agent when AIS_AGENT_ID exists in the tenant', () => {
|
|
9
|
+
expect(assertEnvAgentInTenant('agent-1', agents)).toEqual(agents[0]);
|
|
10
|
+
});
|
|
11
|
+
it('throws with available agents when AIS_AGENT_ID is unknown', () => {
|
|
12
|
+
expect(() => assertEnvAgentInTenant('agent-typo', agents)).toThrow('AIS_AGENT_ID="agent-typo" is not a valid agent in this tenant');
|
|
13
|
+
expect(() => assertEnvAgentInTenant('agent-typo', agents)).toThrow('Corbot (agent-1)');
|
|
14
|
+
});
|
|
15
|
+
it('lists none when the tenant has no agents', () => {
|
|
16
|
+
expect(() => assertEnvAgentInTenant('agent-1', [])).toThrow('Available: (none)');
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
//# sourceMappingURL=env-agent.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-agent.test.js","sourceRoot":"","sources":["../../src/__tests__/env-agent.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,MAAM,MAAM,GAAG;QACb,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE;QACnD,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE;KAClD,CAAC;IAEF,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,CAAC,sBAAsB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,CAAC,GAAG,EAAE,CAAC,sBAAsB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAChE,+DAA+D,CAChE,CAAC;QACF,MAAM,CAAC,GAAG,EAAE,CAAC,sBAAsB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,GAAG,EAAE,CAAC,sBAAsB,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { LocalMirror } from '../local-mirror.js';
|
|
6
|
+
import { fetchAisSourceHashes, loadExistingHashes } from '../pipeline/existing-hashes.js';
|
|
7
|
+
function draft(sourceHash) {
|
|
8
|
+
const prov = {
|
|
9
|
+
source: 'claude-local',
|
|
10
|
+
sourceHash,
|
|
11
|
+
importedAt: new Date().toISOString(),
|
|
12
|
+
importedBy: 'agent1',
|
|
13
|
+
sourceScope: { kind: 'project', label: 'test' },
|
|
14
|
+
sourcePrompt: '',
|
|
15
|
+
};
|
|
16
|
+
return { content: 'body', type: 'fact', importance: 0.5, trustScore: 0.5, provenance: prov };
|
|
17
|
+
}
|
|
18
|
+
describe('existing-hashes', () => {
|
|
19
|
+
let tmpHome;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
tmpHome = mkdtempSync(join(tmpdir(), 'aismem-hashes-'));
|
|
22
|
+
});
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
25
|
+
vi.unstubAllGlobals();
|
|
26
|
+
});
|
|
27
|
+
it('collectSourceHashes reads hashes from mirror JSONL', () => {
|
|
28
|
+
const mirror = new LocalMirror(tmpHome, 30);
|
|
29
|
+
mirror.append('agent1', [
|
|
30
|
+
{ aisMemoryId: 'm1', memory: draft('hash-a') },
|
|
31
|
+
{ aisMemoryId: 'm2', memory: draft('hash-b') },
|
|
32
|
+
]);
|
|
33
|
+
expect(mirror.collectSourceHashes('agent1')).toEqual(new Set(['hash-a', 'hash-b']));
|
|
34
|
+
});
|
|
35
|
+
it('loadExistingHashes merges mirror and AIS provenance hashes', async () => {
|
|
36
|
+
const mirror = new LocalMirror(tmpHome, 30);
|
|
37
|
+
mirror.append('agent1', [{ aisMemoryId: 'm1', memory: draft('mirror-hash') }]);
|
|
38
|
+
const fetchMock = vi.fn(async (url) => {
|
|
39
|
+
if (url.includes('/memory/mem-ais')) {
|
|
40
|
+
return new Response(JSON.stringify({
|
|
41
|
+
success: true,
|
|
42
|
+
data: {
|
|
43
|
+
metadata: { provenance: { sourceHash: 'ais-hash' } },
|
|
44
|
+
},
|
|
45
|
+
}), { status: 200 });
|
|
46
|
+
}
|
|
47
|
+
if (url.includes('/memory?')) {
|
|
48
|
+
return new Response(JSON.stringify({
|
|
49
|
+
success: true,
|
|
50
|
+
data: { memories: [{ id: 'mem-ais' }] },
|
|
51
|
+
}), { status: 200 });
|
|
52
|
+
}
|
|
53
|
+
return new Response('not found', { status: 404 });
|
|
54
|
+
});
|
|
55
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
56
|
+
const merged = await loadExistingHashes('agent1', mirror, {
|
|
57
|
+
apiBase: 'https://ais.test',
|
|
58
|
+
apiKey: 'key',
|
|
59
|
+
tenantId: 'tenant',
|
|
60
|
+
});
|
|
61
|
+
expect(merged).toEqual(new Set(['mirror-hash', 'ais-hash']));
|
|
62
|
+
expect(fetchMock).toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
it('fetchAisSourceHashes warns and continues when a detail fetch returns 500', async () => {
|
|
65
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
|
66
|
+
const fetchMock = vi.fn(async (url) => {
|
|
67
|
+
if (url.includes('/memory/mem-ok')) {
|
|
68
|
+
return new Response(JSON.stringify({
|
|
69
|
+
success: true,
|
|
70
|
+
data: { metadata: { provenance: { sourceHash: 'ok-hash' } } },
|
|
71
|
+
}), { status: 200 });
|
|
72
|
+
}
|
|
73
|
+
if (url.includes('/memory/mem-bad')) {
|
|
74
|
+
return new Response(JSON.stringify({ success: false, error: 'INTERNAL_ERROR', message: 'Failed to get memory' }), { status: 500 });
|
|
75
|
+
}
|
|
76
|
+
if (url.includes('/memory?')) {
|
|
77
|
+
return new Response(JSON.stringify({
|
|
78
|
+
success: true,
|
|
79
|
+
data: { memories: [{ id: 'mem-ok' }, { id: 'mem-bad' }] },
|
|
80
|
+
}), { status: 200 });
|
|
81
|
+
}
|
|
82
|
+
return new Response('not found', { status: 404 });
|
|
83
|
+
});
|
|
84
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
85
|
+
const hashes = await fetchAisSourceHashes('agent1', {
|
|
86
|
+
apiBase: 'https://ais.test',
|
|
87
|
+
apiKey: 'key',
|
|
88
|
+
tenantId: 'tenant',
|
|
89
|
+
});
|
|
90
|
+
// 'ok-hash' is collected; the 500 does not abort the sync
|
|
91
|
+
expect(hashes).toContain('ok-hash');
|
|
92
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('[aismemory] skipping memory hash lookup'));
|
|
93
|
+
warnSpy.mockRestore();
|
|
94
|
+
});
|
|
95
|
+
it('fetchAisSourceHashes skips duplicate memory ids across types', async () => {
|
|
96
|
+
const fetchMock = vi.fn(async (url) => {
|
|
97
|
+
if (url.includes('/memory/mem-dup')) {
|
|
98
|
+
return new Response(JSON.stringify({
|
|
99
|
+
success: true,
|
|
100
|
+
data: { metadata: { provenance: { sourceHash: 'dup-hash' } } },
|
|
101
|
+
}), { status: 200 });
|
|
102
|
+
}
|
|
103
|
+
if (url.includes('/memory?')) {
|
|
104
|
+
return new Response(JSON.stringify({
|
|
105
|
+
success: true,
|
|
106
|
+
data: { memories: [{ id: 'mem-dup' }] },
|
|
107
|
+
}), { status: 200 });
|
|
108
|
+
}
|
|
109
|
+
return new Response('not found', { status: 404 });
|
|
110
|
+
});
|
|
111
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
112
|
+
const hashes = await fetchAisSourceHashes('agent1', {
|
|
113
|
+
apiBase: 'https://ais.test',
|
|
114
|
+
apiKey: 'key',
|
|
115
|
+
tenantId: 'tenant',
|
|
116
|
+
});
|
|
117
|
+
expect(hashes).toEqual(new Set(['dup-hash']));
|
|
118
|
+
const detailCalls = fetchMock.mock.calls.filter((c) => String(c[0]).includes('/memory/mem-dup'));
|
|
119
|
+
expect(detailCalls).toHaveLength(1);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=existing-hashes.test.js.map
|