aismemory 0.6.0 → 0.6.2
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/README.md +16 -0
- package/dist/__tests__/agent-load-persistence.test.d.ts +1 -0
- package/dist/__tests__/agent-load-persistence.test.js +69 -0
- package/dist/__tests__/agent-load-persistence.test.js.map +1 -0
- package/dist/__tests__/device-flow-pending-400.test.js +1 -1
- package/dist/__tests__/device-flow-pending-400.test.js.map +1 -1
- package/dist/__tests__/doctor.test.d.ts +1 -0
- package/dist/__tests__/doctor.test.js +248 -0
- package/dist/__tests__/doctor.test.js.map +1 -0
- package/dist/__tests__/keyauth-error-surface-e2e.test.d.ts +1 -0
- package/dist/__tests__/keyauth-error-surface-e2e.test.js +209 -0
- package/dist/__tests__/keyauth-error-surface-e2e.test.js.map +1 -0
- package/dist/__tests__/list-all-agents.test.d.ts +1 -0
- package/dist/__tests__/list-all-agents.test.js +45 -0
- package/dist/__tests__/list-all-agents.test.js.map +1 -0
- package/dist/__tests__/pipeline-ingestion.test.js +18 -4
- package/dist/__tests__/pipeline-ingestion.test.js.map +1 -1
- package/dist/__tests__/provisional-token-reauth.test.d.ts +1 -0
- package/dist/__tests__/provisional-token-reauth.test.js +156 -0
- package/dist/__tests__/provisional-token-reauth.test.js.map +1 -0
- package/dist/__tests__/resolve-agent-preference-e2e.test.js +10 -10
- package/dist/__tests__/resolve-agent-preference-e2e.test.js.map +1 -1
- package/dist/__tests__/token-expiry-reauth.test.js +1 -1
- package/dist/__tests__/token-expiry-reauth.test.js.map +1 -1
- package/dist/cli/doctor.d.ts +57 -0
- package/dist/cli/doctor.js +332 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +81 -56
- package/dist/index.js.map +1 -1
- package/dist/prompt-resource.js +1 -1
- package/dist/prompt-resource.js.map +1 -1
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -94,6 +94,22 @@ an explicit "not available with agent API key auth" error rather than
|
|
|
94
94
|
failing silently or faking success. `whoami` answers from `AIS_AGENT_ID`
|
|
95
95
|
directly instead of calling the (unscoped) identity route.
|
|
96
96
|
|
|
97
|
+
## Troubleshooting
|
|
98
|
+
|
|
99
|
+
If aismemory "doesn't work," run the built-in self-diagnosis before filing a
|
|
100
|
+
support ticket:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
npx aismemory doctor
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
It checks your Node version, DNS/TLS reachability of the AIS host, REST API
|
|
107
|
+
and MCP endpoint health, local credential state (not bonded / expired /
|
|
108
|
+
rejected / accepted), proxy environment variables, and clock skew — each
|
|
109
|
+
with a PASS/WARN/FAIL line and a remediation hint. Nothing is written to the
|
|
110
|
+
API. Pass `--json` for machine-readable output. Exits `0` unless a check
|
|
111
|
+
FAILs.
|
|
112
|
+
|
|
97
113
|
## License
|
|
98
114
|
|
|
99
115
|
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for CORBOT-497C7FA3:
|
|
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).
|
|
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.
|
|
10
|
+
*
|
|
11
|
+
* Fix: saveCredentials() is now called immediately after the in-memory
|
|
12
|
+
* update inside the agent_load handler.
|
|
13
|
+
*/
|
|
14
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
15
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
16
|
+
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
|
+
}
|
|
28
|
+
describe('agent_load credential persistence (CORBOT-497C7FA3)', () => {
|
|
29
|
+
let tmpHome;
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
tmpHome = mkdtempSync(join(tmpdir(), 'aismem-agent-load-'));
|
|
32
|
+
});
|
|
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);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
//# sourceMappingURL=agent-load-persistence.test.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -69,7 +69,7 @@ describe('device flow survives HTTP 400 authorization_pending polls', () => {
|
|
|
69
69
|
res.end(JSON.stringify({ access_token: ACCESS_TOKEN, expires_in: 604800 }));
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
|
-
if (req.url
|
|
72
|
+
if (req.url?.startsWith('/v1/agents') && !req.url?.includes('/v1/agents/agent-') && req.method === 'GET') {
|
|
73
73
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
74
74
|
res.end(JSON.stringify({
|
|
75
75
|
success: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-flow-pending-400.test.js","sourceRoot":"","sources":["../../src/__tests__/device-flow-pending-400.test.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"device-flow-pending-400.test.js","sourceRoot":"","sources":["../../src/__tests__/device-flow-pending-400.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for `aismemory doctor` (CORBOT-A74B4C8C).
|
|
3
|
+
*
|
|
4
|
+
* Every check function takes its network/fs/clock dependency as a parameter,
|
|
5
|
+
* so tests inject fakes instead of hitting the real network or filesystem.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import { checkNodeVersion, checkDnsResolution, checkTlsReachability, checkRestHealth, checkMcpEndpoint, checkCredentials, checkProxyEnv, checkClockSkew, runDoctor, } from '../cli/doctor.js';
|
|
9
|
+
function fakeResponse(status, headers = {}) {
|
|
10
|
+
const headerMap = new Headers(headers);
|
|
11
|
+
return {
|
|
12
|
+
ok: status >= 200 && status < 300,
|
|
13
|
+
status,
|
|
14
|
+
headers: headerMap,
|
|
15
|
+
json: async () => ({}),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
describe('checkNodeVersion', () => {
|
|
19
|
+
it('passes when node major >= minimum', () => {
|
|
20
|
+
const result = checkNodeVersion('v20.10.0', 18);
|
|
21
|
+
expect(result.status).toBe('PASS');
|
|
22
|
+
});
|
|
23
|
+
it('fails when node major < minimum', () => {
|
|
24
|
+
const result = checkNodeVersion('v16.20.0', 18);
|
|
25
|
+
expect(result.status).toBe('FAIL');
|
|
26
|
+
expect(result.hint).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
it('warns on an unparseable version string', () => {
|
|
29
|
+
const result = checkNodeVersion('not-a-version', 18);
|
|
30
|
+
expect(result.status).toBe('WARN');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('checkDnsResolution', () => {
|
|
34
|
+
it('passes when lookup resolves', async () => {
|
|
35
|
+
const result = await checkDnsResolution('ais.agentsandswarms.ai', async () => ({
|
|
36
|
+
address: '1.2.3.4',
|
|
37
|
+
family: 4,
|
|
38
|
+
}));
|
|
39
|
+
expect(result.status).toBe('PASS');
|
|
40
|
+
expect(result.message).toContain('1.2.3.4');
|
|
41
|
+
});
|
|
42
|
+
it('fails when lookup rejects', async () => {
|
|
43
|
+
const result = await checkDnsResolution('bad.invalid', async () => {
|
|
44
|
+
throw new Error('ENOTFOUND bad.invalid');
|
|
45
|
+
});
|
|
46
|
+
expect(result.status).toBe('FAIL');
|
|
47
|
+
expect(result.hint).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('checkTlsReachability', () => {
|
|
51
|
+
it('passes when the probe resolves', async () => {
|
|
52
|
+
const result = await checkTlsReachability('ais.agentsandswarms.ai', async () => undefined);
|
|
53
|
+
expect(result.status).toBe('PASS');
|
|
54
|
+
});
|
|
55
|
+
it('fails when the probe rejects', async () => {
|
|
56
|
+
const result = await checkTlsReachability('ais.agentsandswarms.ai', async () => {
|
|
57
|
+
throw new Error('connect ECONNREFUSED');
|
|
58
|
+
});
|
|
59
|
+
expect(result.status).toBe('FAIL');
|
|
60
|
+
expect(result.hint).toContain('proxy');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('checkRestHealth', () => {
|
|
64
|
+
it('passes and captures the Date header on 200', async () => {
|
|
65
|
+
const fetchImpl = async () => fakeResponse(200, { date: 'Tue, 07 Jul 2026 06:00:00 GMT' });
|
|
66
|
+
const result = await checkRestHealth('https://ais.agentsandswarms.ai', fetchImpl);
|
|
67
|
+
expect(result.status).toBe('PASS');
|
|
68
|
+
expect(result.dateHeader).toBe('Tue, 07 Jul 2026 06:00:00 GMT');
|
|
69
|
+
});
|
|
70
|
+
it('warns on a reachable but non-2xx response', async () => {
|
|
71
|
+
const fetchImpl = async () => fakeResponse(503);
|
|
72
|
+
const result = await checkRestHealth('https://ais.agentsandswarms.ai', fetchImpl);
|
|
73
|
+
expect(result.status).toBe('WARN');
|
|
74
|
+
});
|
|
75
|
+
it('fails when fetch throws (unreachable)', async () => {
|
|
76
|
+
const fetchImpl = async () => {
|
|
77
|
+
throw new TypeError('fetch failed');
|
|
78
|
+
};
|
|
79
|
+
const result = await checkRestHealth('https://ais.agentsandswarms.ai', fetchImpl);
|
|
80
|
+
expect(result.status).toBe('FAIL');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('checkMcpEndpoint', () => {
|
|
84
|
+
it('passes (reachable) on a 401 — auth required is not the same as down', async () => {
|
|
85
|
+
const fetchImpl = async () => fakeResponse(401);
|
|
86
|
+
const result = await checkMcpEndpoint('https://ais.agentsandswarms.ai', fetchImpl);
|
|
87
|
+
expect(result.status).toBe('PASS');
|
|
88
|
+
expect(result.message).toContain('auth required');
|
|
89
|
+
});
|
|
90
|
+
it('fails when the connection cannot be established', async () => {
|
|
91
|
+
const fetchImpl = async () => {
|
|
92
|
+
throw new Error('getaddrinfo ENOTFOUND');
|
|
93
|
+
};
|
|
94
|
+
const result = await checkMcpEndpoint('https://ais.agentsandswarms.ai', fetchImpl);
|
|
95
|
+
expect(result.status).toBe('FAIL');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe('checkCredentials', () => {
|
|
99
|
+
const baseCreds = {
|
|
100
|
+
token: 'tok',
|
|
101
|
+
agentId: 'agent-1',
|
|
102
|
+
tenantId: 'tenant-1',
|
|
103
|
+
expiresAt: new Date(Date.now() + 3_600_000).toISOString(),
|
|
104
|
+
};
|
|
105
|
+
it('warns when no credentials file exists', async () => {
|
|
106
|
+
const result = await checkCredentials('https://ais.agentsandswarms.ai', {
|
|
107
|
+
load: () => null,
|
|
108
|
+
});
|
|
109
|
+
expect(result.status).toBe('WARN');
|
|
110
|
+
expect(result.hint).toContain('bond');
|
|
111
|
+
});
|
|
112
|
+
it('warns when credentials are expired', async () => {
|
|
113
|
+
const expired = { ...baseCreds, expiresAt: new Date(Date.now() - 1000).toISOString() };
|
|
114
|
+
const result = await checkCredentials('https://ais.agentsandswarms.ai', {
|
|
115
|
+
load: () => expired,
|
|
116
|
+
});
|
|
117
|
+
expect(result.status).toBe('WARN');
|
|
118
|
+
expect(result.message).toContain('expired');
|
|
119
|
+
});
|
|
120
|
+
it('passes when AIS accepts the token', async () => {
|
|
121
|
+
const fetchImpl = async () => fakeResponse(200);
|
|
122
|
+
const result = await checkCredentials('https://ais.agentsandswarms.ai', {
|
|
123
|
+
load: () => baseCreds,
|
|
124
|
+
fetchImpl,
|
|
125
|
+
});
|
|
126
|
+
expect(result.status).toBe('PASS');
|
|
127
|
+
});
|
|
128
|
+
it('warns when AIS rejects the token', async () => {
|
|
129
|
+
const fetchImpl = async () => fakeResponse(401);
|
|
130
|
+
const result = await checkCredentials('https://ais.agentsandswarms.ai', {
|
|
131
|
+
load: () => baseCreds,
|
|
132
|
+
fetchImpl,
|
|
133
|
+
});
|
|
134
|
+
expect(result.status).toBe('WARN');
|
|
135
|
+
});
|
|
136
|
+
it('fails when the authed call cannot reach the network', async () => {
|
|
137
|
+
const fetchImpl = async () => {
|
|
138
|
+
throw new Error('network down');
|
|
139
|
+
};
|
|
140
|
+
const result = await checkCredentials('https://ais.agentsandswarms.ai', {
|
|
141
|
+
load: () => baseCreds,
|
|
142
|
+
fetchImpl,
|
|
143
|
+
});
|
|
144
|
+
expect(result.status).toBe('FAIL');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe('checkProxyEnv', () => {
|
|
148
|
+
it('passes when no proxy vars are set', () => {
|
|
149
|
+
const result = checkProxyEnv({});
|
|
150
|
+
expect(result.status).toBe('PASS');
|
|
151
|
+
});
|
|
152
|
+
it('warns when HTTPS_PROXY is set', () => {
|
|
153
|
+
const result = checkProxyEnv({ HTTPS_PROXY: 'http://proxy.corp:8080' });
|
|
154
|
+
expect(result.status).toBe('WARN');
|
|
155
|
+
expect(result.message).toContain('HTTPS_PROXY');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('checkClockSkew', () => {
|
|
159
|
+
it('warns when there is no Date header to compare', () => {
|
|
160
|
+
const result = checkClockSkew(undefined);
|
|
161
|
+
expect(result.status).toBe('WARN');
|
|
162
|
+
});
|
|
163
|
+
it('passes when clocks are close', () => {
|
|
164
|
+
const now = Date.now();
|
|
165
|
+
const result = checkClockSkew(new Date(now).toUTCString(), () => now);
|
|
166
|
+
expect(result.status).toBe('PASS');
|
|
167
|
+
});
|
|
168
|
+
it('warns when clocks differ by more than the threshold', () => {
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
const skewed = new Date(now - 10 * 60 * 1000).toUTCString();
|
|
171
|
+
const result = checkClockSkew(skewed, () => now);
|
|
172
|
+
expect(result.status).toBe('WARN');
|
|
173
|
+
expect(result.hint).toContain('NTP');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
describe('runDoctor', () => {
|
|
177
|
+
it('produces a report with one entry per check, exit code 0 when all pass', async () => {
|
|
178
|
+
const now = Date.now();
|
|
179
|
+
const fetchImpl = async (input) => {
|
|
180
|
+
const url = String(input);
|
|
181
|
+
if (url.endsWith('/livez'))
|
|
182
|
+
return fakeResponse(200, { date: new Date(now).toUTCString() });
|
|
183
|
+
if (url.endsWith('/mcp'))
|
|
184
|
+
return fakeResponse(401);
|
|
185
|
+
return fakeResponse(200);
|
|
186
|
+
};
|
|
187
|
+
const report = await runDoctor({
|
|
188
|
+
aisUrl: 'https://ais.agentsandswarms.ai',
|
|
189
|
+
env: {},
|
|
190
|
+
nodeVersion: 'v20.10.0',
|
|
191
|
+
now: () => now,
|
|
192
|
+
fetchImpl,
|
|
193
|
+
dnsLookup: async () => ({ address: '1.2.3.4', family: 4 }),
|
|
194
|
+
tlsProbe: async () => undefined,
|
|
195
|
+
loadCreds: () => null,
|
|
196
|
+
});
|
|
197
|
+
expect(report.checks).toHaveLength(8);
|
|
198
|
+
expect(report.checks.every((c) => c.status !== 'FAIL')).toBe(true);
|
|
199
|
+
expect(report.verdict).toBe('problems'); // credentials WARN (not bonded) keeps this non-zero
|
|
200
|
+
expect(report.exitCode).toBe(0);
|
|
201
|
+
});
|
|
202
|
+
it('sets exit code 1 when any check fails', async () => {
|
|
203
|
+
const report = await runDoctor({
|
|
204
|
+
aisUrl: 'https://ais.agentsandswarms.ai',
|
|
205
|
+
env: {},
|
|
206
|
+
nodeVersion: 'v14.0.0', // below minimum -> FAIL
|
|
207
|
+
now: () => Date.now(),
|
|
208
|
+
fetchImpl: async () => fakeResponse(200),
|
|
209
|
+
dnsLookup: async () => ({ address: '1.2.3.4', family: 4 }),
|
|
210
|
+
tlsProbe: async () => undefined,
|
|
211
|
+
loadCreds: () => null,
|
|
212
|
+
});
|
|
213
|
+
expect(report.exitCode).toBe(1);
|
|
214
|
+
expect(report.verdict).toBe('problems');
|
|
215
|
+
expect(report.problemCount).toBeGreaterThan(0);
|
|
216
|
+
});
|
|
217
|
+
it('reports a clean pass when every check succeeds including credentials', async () => {
|
|
218
|
+
const now = Date.now();
|
|
219
|
+
const creds = {
|
|
220
|
+
token: 'tok',
|
|
221
|
+
agentId: 'agent-1',
|
|
222
|
+
tenantId: 'tenant-1',
|
|
223
|
+
expiresAt: new Date(now + 3_600_000).toISOString(),
|
|
224
|
+
};
|
|
225
|
+
const fetchImpl = async (input) => {
|
|
226
|
+
const url = String(input);
|
|
227
|
+
if (url.endsWith('/livez'))
|
|
228
|
+
return fakeResponse(200, { date: new Date(now).toUTCString() });
|
|
229
|
+
if (url.endsWith('/mcp'))
|
|
230
|
+
return fakeResponse(401);
|
|
231
|
+
return fakeResponse(200); // credentials whoami call
|
|
232
|
+
};
|
|
233
|
+
const report = await runDoctor({
|
|
234
|
+
aisUrl: 'https://ais.agentsandswarms.ai',
|
|
235
|
+
env: {},
|
|
236
|
+
nodeVersion: 'v20.10.0',
|
|
237
|
+
now: () => now,
|
|
238
|
+
fetchImpl,
|
|
239
|
+
dnsLookup: async () => ({ address: '1.2.3.4', family: 4 }),
|
|
240
|
+
tlsProbe: async () => undefined,
|
|
241
|
+
loadCreds: () => creds,
|
|
242
|
+
});
|
|
243
|
+
expect(report.problemCount).toBe(0);
|
|
244
|
+
expect(report.verdict).toBe('pass');
|
|
245
|
+
expect(report.exitCode).toBe(0);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
//# sourceMappingURL=doctor.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.test.js","sourceRoot":"","sources":["../../src/__tests__/doctor.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ticket 27FA5CDD: a real agent-resolution error after successful key auth
|
|
3
|
+
* must reach the MCP caller verbatim — it must NEVER be mislabeled as a
|
|
4
|
+
* key-auth failure and routed to the browser device flow.
|
|
5
|
+
*
|
|
6
|
+
* Before the fix, `ensureCredentials`'s try/catch wrapped both `tryKeyAuth`
|
|
7
|
+
* AND the post-auth steps (decodeJwtClaims, resolveAgent). A thrown
|
|
8
|
+
* resolution error (e.g. AIS_AGENT_ID pointing at an agent outside the
|
|
9
|
+
* tenant) was caught, logged as "Key auth failed: ... Falling back to
|
|
10
|
+
* device flow.", and silently routed into the device flow — minting a
|
|
11
|
+
* ghost provisional agent and hiding the real error from the caller.
|
|
12
|
+
*
|
|
13
|
+
* This drives the built binary against a fake AIS with a valid, enrolled
|
|
14
|
+
* key file (so key auth itself succeeds) but `AIS_AGENT_ID` set to an id
|
|
15
|
+
* that is not in the tenant's agent list. The fix must surface the real
|
|
16
|
+
* "not a valid agent in this tenant" error and must NEVER touch the device
|
|
17
|
+
* authorize endpoint.
|
|
18
|
+
*/
|
|
19
|
+
import { createServer } from 'node:http';
|
|
20
|
+
import { spawn } from 'node:child_process';
|
|
21
|
+
import { mkdtempSync, mkdirSync, rmSync, existsSync } from 'node:fs';
|
|
22
|
+
import { tmpdir } from 'node:os';
|
|
23
|
+
import { join, resolve } from 'path';
|
|
24
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
25
|
+
import { generateAndSaveKeypair } from '../key-auth.js';
|
|
26
|
+
const MISSING_AGENT_ID = 'missing-agent-id';
|
|
27
|
+
function mintJwt() {
|
|
28
|
+
const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
|
|
29
|
+
// decodeJwtClaims reads `tenant_id` (snake_case) off the JWT payload —
|
|
30
|
+
// matches the convention already established in auto-enroll-key-e2e.test.ts
|
|
31
|
+
// and resolve-agent-preference-e2e.test.ts.
|
|
32
|
+
const payload = Buffer.from(JSON.stringify({ sub: 'u1', user_id: 'u1', tenant_id: 't1' })).toString('base64url');
|
|
33
|
+
return `${header}.${payload}.sig`;
|
|
34
|
+
}
|
|
35
|
+
describe('key-auth resolution errors surface verbatim instead of falling back to device flow', () => {
|
|
36
|
+
let fakeAis;
|
|
37
|
+
let port = 0;
|
|
38
|
+
let deviceAuthorizeCalls = 0;
|
|
39
|
+
beforeAll(async () => {
|
|
40
|
+
fakeAis = createServer((req, res) => {
|
|
41
|
+
req.on('data', () => { });
|
|
42
|
+
req.on('end', () => {
|
|
43
|
+
if (req.url === '/v1/auth/challenge' && req.method === 'POST') {
|
|
44
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
45
|
+
res.end(JSON.stringify({
|
|
46
|
+
success: true,
|
|
47
|
+
data: { nonce: 'test-nonce', exp: Math.floor(Date.now() / 1000) + 60, hmac: 'test-hmac' },
|
|
48
|
+
}));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (req.url === '/v1/auth/did-prove' && req.method === 'POST') {
|
|
52
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
53
|
+
res.end(JSON.stringify({
|
|
54
|
+
success: true,
|
|
55
|
+
data: {
|
|
56
|
+
bearerToken: mintJwt(),
|
|
57
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString(),
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (req.url === '/v1/oauth/device/authorize' && req.method === 'POST') {
|
|
63
|
+
deviceAuthorizeCalls += 1;
|
|
64
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
65
|
+
res.end(JSON.stringify({
|
|
66
|
+
success: true,
|
|
67
|
+
data: {
|
|
68
|
+
device_code: 'device-1',
|
|
69
|
+
user_code: 'TEST-0001',
|
|
70
|
+
verification_uri: `http://127.0.0.1:${port}/activate`,
|
|
71
|
+
verification_uri_complete: `http://127.0.0.1:${port}/activate?user_code=TEST-0001`,
|
|
72
|
+
expires_in: 1800,
|
|
73
|
+
interval: 5,
|
|
74
|
+
agent_id: 'agent-provisional',
|
|
75
|
+
provisional_tenant_id: 'tenant-provisional',
|
|
76
|
+
provisional_token: 'provisional-token-1',
|
|
77
|
+
},
|
|
78
|
+
}));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (req.method === 'GET') {
|
|
82
|
+
const pathname = new URL(req.url ?? '', 'http://127.0.0.1').pathname;
|
|
83
|
+
if (pathname === '/v1/agents') {
|
|
84
|
+
// Deliberately does NOT include MISSING_AGENT_ID — this is the
|
|
85
|
+
// real resolution failure the fix must surface.
|
|
86
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
87
|
+
res.end(JSON.stringify({
|
|
88
|
+
success: true,
|
|
89
|
+
data: {
|
|
90
|
+
agents: [{ id: 'agent-real', name: 'RealAgent', status: 'active' }],
|
|
91
|
+
pagination: { total: 1, page: 1, limit: 100, hasMore: false },
|
|
92
|
+
},
|
|
93
|
+
}));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
res.writeHead(404, { 'content-type': 'application/json' });
|
|
98
|
+
res.end(JSON.stringify({ success: false, error: { code: 'NOT_FOUND' } }));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
await new Promise((resolveServer) => {
|
|
102
|
+
fakeAis.listen(0, '127.0.0.1', () => {
|
|
103
|
+
const addr = fakeAis.address();
|
|
104
|
+
if (addr && typeof addr === 'object')
|
|
105
|
+
port = addr.port;
|
|
106
|
+
resolveServer();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
afterAll(async () => {
|
|
111
|
+
await new Promise((r) => fakeAis?.close(() => r()));
|
|
112
|
+
});
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
deviceAuthorizeCalls = 0;
|
|
115
|
+
});
|
|
116
|
+
it('surfaces the real resolution error and never calls device-authorize', async () => {
|
|
117
|
+
const distPath = resolve(__dirname, '..', '..', 'dist', 'index.js');
|
|
118
|
+
if (!existsSync(distPath)) {
|
|
119
|
+
throw new Error(`dist/index.js missing — run \`pnpm build\` first (looked at ${distPath})`);
|
|
120
|
+
}
|
|
121
|
+
const fakeHome = mkdtempSync(join(tmpdir(), 'aismem-keyauth-error-surface-'));
|
|
122
|
+
const aismemDir = join(fakeHome, '.aismemory');
|
|
123
|
+
mkdirSync(aismemDir, { recursive: true });
|
|
124
|
+
await generateAndSaveKeypair({
|
|
125
|
+
userId: 'u1',
|
|
126
|
+
userDid: 'did:web:test:users:u1',
|
|
127
|
+
keysDir: join(aismemDir, 'keys'),
|
|
128
|
+
});
|
|
129
|
+
let proc = null;
|
|
130
|
+
try {
|
|
131
|
+
proc = spawn('node', [distPath], {
|
|
132
|
+
env: {
|
|
133
|
+
...process.env,
|
|
134
|
+
HOME: fakeHome,
|
|
135
|
+
AIS_URL: `http://127.0.0.1:${port}`,
|
|
136
|
+
AIS_AGENT_ID: MISSING_AGENT_ID,
|
|
137
|
+
},
|
|
138
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
139
|
+
});
|
|
140
|
+
let stdoutBuf = '';
|
|
141
|
+
let stderrBuf = '';
|
|
142
|
+
proc.stdout.on('data', (d) => { stdoutBuf += d.toString(); });
|
|
143
|
+
proc.stderr.on('data', (d) => { stderrBuf += d.toString(); });
|
|
144
|
+
proc.stdin.write(JSON.stringify({
|
|
145
|
+
jsonrpc: '2.0',
|
|
146
|
+
id: 1,
|
|
147
|
+
method: 'initialize',
|
|
148
|
+
params: {
|
|
149
|
+
protocolVersion: '2024-11-05',
|
|
150
|
+
capabilities: {},
|
|
151
|
+
clientInfo: { name: 'keyauth-error-surface-test', version: '0.0.0' },
|
|
152
|
+
},
|
|
153
|
+
}) + '\n');
|
|
154
|
+
await waitFor(() => stdoutBuf.includes('"id":1'), 5000, 'initialize response');
|
|
155
|
+
proc.stdin.write(JSON.stringify({
|
|
156
|
+
jsonrpc: '2.0',
|
|
157
|
+
id: 2,
|
|
158
|
+
method: 'tools/call',
|
|
159
|
+
params: { name: 'whoami', arguments: {} },
|
|
160
|
+
}) + '\n');
|
|
161
|
+
await waitFor(() => stdoutBuf.includes('"id":2'), 15000, 'whoami response');
|
|
162
|
+
const whoamiText = extractResponseText(stdoutBuf, 2);
|
|
163
|
+
expect(whoamiText).toContain(MISSING_AGENT_ID);
|
|
164
|
+
expect(whoamiText).toContain('not');
|
|
165
|
+
expect(whoamiText).toContain('tenant');
|
|
166
|
+
expect(deviceAuthorizeCalls).toBe(0);
|
|
167
|
+
expect(stderrBuf).not.toContain('Falling back to device flow');
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
proc?.kill();
|
|
171
|
+
try {
|
|
172
|
+
rmSync(fakeHome, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
catch { /* ignore */ }
|
|
175
|
+
}
|
|
176
|
+
}, 30000);
|
|
177
|
+
});
|
|
178
|
+
function waitFor(predicate, ms, label) {
|
|
179
|
+
return new Promise((resolveOuter, reject) => {
|
|
180
|
+
const start = Date.now();
|
|
181
|
+
const tick = () => {
|
|
182
|
+
if (predicate()) {
|
|
183
|
+
resolveOuter();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (Date.now() - start > ms) {
|
|
187
|
+
reject(new Error(`timeout waiting for ${label}`));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
setTimeout(tick, 50);
|
|
191
|
+
};
|
|
192
|
+
tick();
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function extractResponseText(buf, id) {
|
|
196
|
+
for (const line of buf.split('\n')) {
|
|
197
|
+
if (!line.includes(`"id":${id}`))
|
|
198
|
+
continue;
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(line);
|
|
201
|
+
const text = parsed.result?.content?.[0]?.text;
|
|
202
|
+
if (typeof text === 'string')
|
|
203
|
+
return text;
|
|
204
|
+
}
|
|
205
|
+
catch { /* malformed line; skip */ }
|
|
206
|
+
}
|
|
207
|
+
return '';
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=keyauth-error-surface-e2e.test.js.map
|