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
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `aismemory doctor` — read-only self-diagnosis for "it doesn't work" reports.
|
|
4
|
+
*
|
|
5
|
+
* A new user whose MCP client can't reach AIS has no way to tell whether the
|
|
6
|
+
* problem is DNS, a corporate proxy, an unbonded agent, an expired token, or
|
|
7
|
+
* clock skew. This command runs a fixed set of read-only checks against the
|
|
8
|
+
* local environment and the AIS host and prints a PASS/WARN/FAIL report with
|
|
9
|
+
* a one-line remediation hint per problem — no writes to the API, no agent
|
|
10
|
+
* creation.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* npx aismemory doctor
|
|
14
|
+
* npx aismemory doctor --json
|
|
15
|
+
* AIS_URL=https://other.example.com npx aismemory doctor
|
|
16
|
+
*
|
|
17
|
+
* Exit codes:
|
|
18
|
+
* 0 — no FAIL checks (WARNs are still reported but don't fail the command)
|
|
19
|
+
* 1 — at least one FAIL check
|
|
20
|
+
*/
|
|
21
|
+
import { lookup as dnsLookupDefault } from 'node:dns/promises';
|
|
22
|
+
import { connect as tlsConnectDefault } from 'node:tls';
|
|
23
|
+
import { loadOAuthCredentials, credentialsFilePath } from '../oauth-credentials.js';
|
|
24
|
+
import { isCredsStale } from '../auth-staleness.js';
|
|
25
|
+
/** Minimum Node major version this package requires (per @modelcontextprotocol/sdk engines). */
|
|
26
|
+
const MIN_NODE_MAJOR = 18;
|
|
27
|
+
const PROXY_ENV_KEYS = ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'NO_PROXY', 'no_proxy'];
|
|
28
|
+
const CLOCK_SKEW_WARN_MS = 5 * 60 * 1000;
|
|
29
|
+
function errMsg(err) {
|
|
30
|
+
return err instanceof Error ? err.message : String(err);
|
|
31
|
+
}
|
|
32
|
+
// ── Individual checks ────────────────────────────────────
|
|
33
|
+
export function checkNodeVersion(nodeVersion = process.version, minMajor = MIN_NODE_MAJOR) {
|
|
34
|
+
const match = /^v?(\d+)\./.exec(nodeVersion);
|
|
35
|
+
const major = match ? Number(match[1]) : NaN;
|
|
36
|
+
if (Number.isNaN(major)) {
|
|
37
|
+
return {
|
|
38
|
+
name: 'Node version',
|
|
39
|
+
status: 'WARN',
|
|
40
|
+
message: `Could not parse Node version string "${nodeVersion}".`,
|
|
41
|
+
hint: `aismemory requires Node >= ${minMajor}.`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (major < minMajor) {
|
|
45
|
+
return {
|
|
46
|
+
name: 'Node version',
|
|
47
|
+
status: 'FAIL',
|
|
48
|
+
message: `Node ${nodeVersion} is below the required minimum (>= ${minMajor}).`,
|
|
49
|
+
hint: `Upgrade Node (e.g. via nvm: "nvm install ${minMajor}") and retry.`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
name: 'Node version',
|
|
54
|
+
status: 'PASS',
|
|
55
|
+
message: `Node ${nodeVersion} meets the minimum requirement (>= ${minMajor}).`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export async function checkDnsResolution(host, lookup = dnsLookupDefault) {
|
|
59
|
+
try {
|
|
60
|
+
const { address } = await lookup(host);
|
|
61
|
+
return {
|
|
62
|
+
name: 'DNS resolution',
|
|
63
|
+
status: 'PASS',
|
|
64
|
+
message: `${host} resolves to ${address}.`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
return {
|
|
69
|
+
name: 'DNS resolution',
|
|
70
|
+
status: 'FAIL',
|
|
71
|
+
message: `Could not resolve DNS for ${host}: ${errMsg(err)}.`,
|
|
72
|
+
hint: 'Check your internet connection and DNS settings (or /etc/resolv.conf).',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function defaultTlsProbe(host, port, timeoutMs) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
let settled = false;
|
|
79
|
+
const socket = tlsConnectDefault({ host, port, servername: host, timeout: timeoutMs }, () => {
|
|
80
|
+
if (settled)
|
|
81
|
+
return;
|
|
82
|
+
settled = true;
|
|
83
|
+
socket.end();
|
|
84
|
+
resolve();
|
|
85
|
+
});
|
|
86
|
+
socket.on('error', (err) => {
|
|
87
|
+
if (settled)
|
|
88
|
+
return;
|
|
89
|
+
settled = true;
|
|
90
|
+
reject(err);
|
|
91
|
+
});
|
|
92
|
+
socket.on('timeout', () => {
|
|
93
|
+
if (settled)
|
|
94
|
+
return;
|
|
95
|
+
settled = true;
|
|
96
|
+
socket.destroy();
|
|
97
|
+
reject(new Error(`connection to ${host}:${port} timed out after ${timeoutMs}ms`));
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
export async function checkTlsReachability(host, probe = defaultTlsProbe, port = 443, timeoutMs = 5000) {
|
|
102
|
+
try {
|
|
103
|
+
await probe(host, port, timeoutMs);
|
|
104
|
+
return {
|
|
105
|
+
name: 'TLS reachability',
|
|
106
|
+
status: 'PASS',
|
|
107
|
+
message: `${host}:${port} accepts TLS connections.`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
return {
|
|
112
|
+
name: 'TLS reachability',
|
|
113
|
+
status: 'FAIL',
|
|
114
|
+
message: `Could not open a TLS connection to ${host}:${port}: ${errMsg(err)}.`,
|
|
115
|
+
hint: 'A firewall, VPN, or corporate proxy may be blocking outbound HTTPS. See the proxy check below.',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export async function checkRestHealth(baseUrl, fetchImpl = fetch) {
|
|
120
|
+
const url = `${baseUrl}/livez`;
|
|
121
|
+
try {
|
|
122
|
+
const res = await fetchImpl(url);
|
|
123
|
+
const dateHeader = res.headers.get('date') ?? undefined;
|
|
124
|
+
if (res.ok) {
|
|
125
|
+
return {
|
|
126
|
+
name: 'REST API health',
|
|
127
|
+
status: 'PASS',
|
|
128
|
+
message: `${url} responded HTTP ${res.status} (alive).`,
|
|
129
|
+
dateHeader,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
name: 'REST API health',
|
|
134
|
+
status: 'WARN',
|
|
135
|
+
message: `${url} responded HTTP ${res.status} — server reachable but reporting a problem.`,
|
|
136
|
+
hint: 'The AIS backend may be degraded. Check again shortly.',
|
|
137
|
+
dateHeader,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
return {
|
|
142
|
+
name: 'REST API health',
|
|
143
|
+
status: 'FAIL',
|
|
144
|
+
message: `Could not reach ${url}: ${errMsg(err)}.`,
|
|
145
|
+
hint: 'Check network connectivity — see the DNS/TLS checks above.',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export async function checkMcpEndpoint(baseUrl, fetchImpl = fetch) {
|
|
150
|
+
const url = `${baseUrl}/mcp`;
|
|
151
|
+
try {
|
|
152
|
+
const res = await fetchImpl(url);
|
|
153
|
+
// Any resolved HTTP response (even 401 "auth required") proves the MCP
|
|
154
|
+
// endpoint is reachable — only a network-level failure means "down".
|
|
155
|
+
const authNote = res.status === 401 || res.status === 403 ? ' (auth required, as expected without a key)' : '';
|
|
156
|
+
return {
|
|
157
|
+
name: 'MCP HTTP endpoint',
|
|
158
|
+
status: 'PASS',
|
|
159
|
+
message: `${url} responded HTTP ${res.status}${authNote}.`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
return {
|
|
164
|
+
name: 'MCP HTTP endpoint',
|
|
165
|
+
status: 'FAIL',
|
|
166
|
+
message: `Could not reach ${url}: ${errMsg(err)}.`,
|
|
167
|
+
hint: 'Check network connectivity — see the DNS/TLS checks above.',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
export async function checkCredentials(baseUrl, deps = {}) {
|
|
172
|
+
const load = deps.load ?? loadOAuthCredentials;
|
|
173
|
+
const now = deps.now ?? Date.now;
|
|
174
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
175
|
+
const home = deps.home;
|
|
176
|
+
const path = credentialsFilePath(home);
|
|
177
|
+
const creds = load(home);
|
|
178
|
+
if (!creds) {
|
|
179
|
+
return {
|
|
180
|
+
name: 'Credentials',
|
|
181
|
+
status: 'WARN',
|
|
182
|
+
message: `No credentials found at ${path} — this device has not bonded to an agent yet.`,
|
|
183
|
+
hint: 'Run any aismemory tool from your MCP client once and follow the printed bond URL/code (takes ~30s).',
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (isCredsStale(creds.expiresAt, now())) {
|
|
187
|
+
return {
|
|
188
|
+
name: 'Credentials',
|
|
189
|
+
status: 'WARN',
|
|
190
|
+
message: `Credentials at ${path} are expired or expiring within 5 minutes (expiresAt: ${creds.expiresAt}).`,
|
|
191
|
+
hint: 'No action needed — the next tool call re-authenticates automatically (silently if a device key is enrolled).',
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const res = await fetchImpl(`${baseUrl}/v1/agents/${creds.agentId}`, {
|
|
196
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
197
|
+
});
|
|
198
|
+
if (res.ok) {
|
|
199
|
+
return {
|
|
200
|
+
name: 'Credentials',
|
|
201
|
+
status: 'PASS',
|
|
202
|
+
message: `Bonded as agent ${creds.agentId}; AIS accepted the saved token.`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
name: 'Credentials',
|
|
207
|
+
status: 'WARN',
|
|
208
|
+
message: `Saved credentials found, but AIS returned HTTP ${res.status} for agent ${creds.agentId}.`,
|
|
209
|
+
hint: 'The token may be revoked or the agent archived. Re-run any aismemory tool to trigger re-authentication.',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
return {
|
|
214
|
+
name: 'Credentials',
|
|
215
|
+
status: 'FAIL',
|
|
216
|
+
message: `Could not verify saved credentials with AIS: ${errMsg(err)}.`,
|
|
217
|
+
hint: 'Check network connectivity — see the REST API health check above.',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
export function checkProxyEnv(env = process.env) {
|
|
222
|
+
const set = PROXY_ENV_KEYS.filter((key) => Boolean(env[key]));
|
|
223
|
+
if (set.length === 0) {
|
|
224
|
+
return {
|
|
225
|
+
name: 'Proxy environment',
|
|
226
|
+
status: 'PASS',
|
|
227
|
+
message: 'No HTTP(S)_PROXY / NO_PROXY environment variables set.',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
name: 'Proxy environment',
|
|
232
|
+
status: 'WARN',
|
|
233
|
+
message: `Proxy environment variable(s) detected: ${set.join(', ')}.`,
|
|
234
|
+
hint: 'A corporate proxy can block or MITM TLS to ais.agentsandswarms.ai. If aismemory only fails on this network, ask IT to allowlist the host or trust the proxy CA.',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
export function checkClockSkew(dateHeader, now = Date.now, warnThresholdMs = CLOCK_SKEW_WARN_MS) {
|
|
238
|
+
if (!dateHeader) {
|
|
239
|
+
return {
|
|
240
|
+
name: 'Clock skew',
|
|
241
|
+
status: 'WARN',
|
|
242
|
+
message: 'No server Date header available to compare (REST API health check did not return one).',
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const remoteMs = Date.parse(dateHeader);
|
|
246
|
+
if (Number.isNaN(remoteMs)) {
|
|
247
|
+
return {
|
|
248
|
+
name: 'Clock skew',
|
|
249
|
+
status: 'WARN',
|
|
250
|
+
message: `Could not parse the server Date header: "${dateHeader}".`,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const skewMs = Math.abs(now() - remoteMs);
|
|
254
|
+
const skewMinutes = (skewMs / 60000).toFixed(1);
|
|
255
|
+
if (skewMs > warnThresholdMs) {
|
|
256
|
+
return {
|
|
257
|
+
name: 'Clock skew',
|
|
258
|
+
status: 'WARN',
|
|
259
|
+
message: `Local clock differs from the AIS server by ~${skewMinutes} minutes.`,
|
|
260
|
+
hint: 'Large clock skew can invalidate OAuth tokens. Sync your system clock (e.g. enable automatic time/NTP).',
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
name: 'Clock skew',
|
|
265
|
+
status: 'PASS',
|
|
266
|
+
message: `Local clock is within ${skewMinutes} min of the AIS server.`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
export async function runDoctor(options = {}) {
|
|
270
|
+
const env = options.env ?? process.env;
|
|
271
|
+
const aisUrl = options.aisUrl ?? env['AIS_URL'] ?? 'https://ais.agentsandswarms.ai';
|
|
272
|
+
const host = new URL(aisUrl).hostname;
|
|
273
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
274
|
+
const now = options.now ?? Date.now;
|
|
275
|
+
const checks = [];
|
|
276
|
+
checks.push(checkNodeVersion(options.nodeVersion));
|
|
277
|
+
checks.push(await checkDnsResolution(host, options.dnsLookup));
|
|
278
|
+
checks.push(await checkTlsReachability(host, options.tlsProbe));
|
|
279
|
+
const health = await checkRestHealth(aisUrl, fetchImpl);
|
|
280
|
+
checks.push(health);
|
|
281
|
+
checks.push(await checkMcpEndpoint(aisUrl, fetchImpl));
|
|
282
|
+
checks.push(await checkCredentials(aisUrl, {
|
|
283
|
+
fetchImpl,
|
|
284
|
+
load: options.loadCreds,
|
|
285
|
+
home: options.home,
|
|
286
|
+
now,
|
|
287
|
+
}));
|
|
288
|
+
checks.push(checkProxyEnv(env));
|
|
289
|
+
checks.push(checkClockSkew(health.dateHeader, now));
|
|
290
|
+
const problemCount = checks.filter((c) => c.status !== 'PASS').length;
|
|
291
|
+
const hasFail = checks.some((c) => c.status === 'FAIL');
|
|
292
|
+
return {
|
|
293
|
+
aisUrl,
|
|
294
|
+
checks,
|
|
295
|
+
problemCount,
|
|
296
|
+
verdict: problemCount === 0 ? 'pass' : 'problems',
|
|
297
|
+
exitCode: hasFail ? 1 : 0,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
// ── CLI formatting + entrypoint ──────────────────────────
|
|
301
|
+
function formatText(report) {
|
|
302
|
+
const lines = [];
|
|
303
|
+
lines.push(`aismemory doctor — ${report.aisUrl}`);
|
|
304
|
+
lines.push('');
|
|
305
|
+
for (const c of report.checks) {
|
|
306
|
+
lines.push(`[${c.status}] ${c.name}: ${c.message}`);
|
|
307
|
+
if (c.hint && c.status !== 'PASS') {
|
|
308
|
+
lines.push(` hint: ${c.hint}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
lines.push('');
|
|
312
|
+
lines.push(report.problemCount === 0
|
|
313
|
+
? 'doctor: all checks passed'
|
|
314
|
+
: `doctor: ${report.problemCount} problem(s) found`);
|
|
315
|
+
return lines.join('\n');
|
|
316
|
+
}
|
|
317
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
318
|
+
const json = argv.includes('--json');
|
|
319
|
+
const report = await runDoctor();
|
|
320
|
+
if (json) {
|
|
321
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
process.stdout.write(`${formatText(report)}\n`);
|
|
325
|
+
}
|
|
326
|
+
process.exit(report.exitCode);
|
|
327
|
+
}
|
|
328
|
+
main().catch((err) => {
|
|
329
|
+
process.stderr.write(`[aismemory doctor] Fatal: ${errMsg(err)}\n`);
|
|
330
|
+
process.exit(1);
|
|
331
|
+
});
|
|
332
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"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"}
|
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 {};
|
package/dist/index.js
CHANGED
|
@@ -124,6 +124,25 @@ async function aisGet(path, token) {
|
|
|
124
124
|
}
|
|
125
125
|
return data;
|
|
126
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Fetch EVERY agent in the tenant, following server pagination.
|
|
129
|
+
* The server defaults to limit=20 and silently truncates (2026-08-24
|
|
130
|
+
* lockout, ticket 72ab7f4e) — never call GET /v1/agents without this.
|
|
131
|
+
*/
|
|
132
|
+
export async function listAllAgents(token) {
|
|
133
|
+
const all = [];
|
|
134
|
+
let page = 1;
|
|
135
|
+
// hard stop at 50 pages (1000 agents) as a runaway guard
|
|
136
|
+
for (; page <= 50; page += 1) {
|
|
137
|
+
const res = (await aisGet(`/v1/agents?limit=100&page=${page}`, token));
|
|
138
|
+
const agents = res.data?.agents ?? [];
|
|
139
|
+
all.push(...agents);
|
|
140
|
+
const hasMore = res.data?.pagination?.hasMore ?? false;
|
|
141
|
+
if (!hasMore)
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
return all;
|
|
145
|
+
}
|
|
127
146
|
async function resolveAgentForLoad(token, query) {
|
|
128
147
|
const resolveRes = (await aisGet(`/v1/resolve?query=${encodeURIComponent(query)}`, token));
|
|
129
148
|
if (resolveRes.data?.status === 'resolved' && resolveRes.data.agent) {
|
|
@@ -137,8 +156,7 @@ async function resolveAgentForLoad(token, query) {
|
|
|
137
156
|
const available = resolveRes.data.available?.map((ag) => ag.name).join(', ');
|
|
138
157
|
throw new Error(resolveRes.data.message ?? `agent_load: no agent named "${query}" in this tenant. Available: ${available || '(none)'}`);
|
|
139
158
|
}
|
|
140
|
-
const
|
|
141
|
-
const agents = listRes.data?.agents ?? [];
|
|
159
|
+
const agents = await listAllAgents(token);
|
|
142
160
|
const matches = agents.filter((ag) => ag.name.toLowerCase() === query.toLowerCase() || ag.id === query);
|
|
143
161
|
if (matches.length === 0) {
|
|
144
162
|
const available = agents.map((ag) => ag.name).join(', ');
|
|
@@ -160,8 +178,7 @@ async function resolveAgent(token, fallbackAgentId, fallbackTenantId, preferredA
|
|
|
160
178
|
// Explicit env var overrides everything — must belong to this tenant.
|
|
161
179
|
const envAgentId = process.env['AIS_AGENT_ID'];
|
|
162
180
|
if (envAgentId) {
|
|
163
|
-
const
|
|
164
|
-
const agents = listRes.data?.agents ?? [];
|
|
181
|
+
const agents = await listAllAgents(token);
|
|
165
182
|
try {
|
|
166
183
|
const match = assertEnvAgentInTenant(envAgentId, agents);
|
|
167
184
|
stderr(`Using agent from AIS_AGENT_ID: ${match.name} (${match.id})`);
|
|
@@ -174,8 +191,7 @@ async function resolveAgent(token, fallbackAgentId, fallbackTenantId, preferredA
|
|
|
174
191
|
}
|
|
175
192
|
}
|
|
176
193
|
try {
|
|
177
|
-
const
|
|
178
|
-
const agents = res.data?.agents?.filter((a) => a.status === 'active') ?? [];
|
|
194
|
+
const agents = (await listAllAgents(token)).filter((a) => a.status === 'active');
|
|
179
195
|
const selection = selectAgentForSession({
|
|
180
196
|
agents,
|
|
181
197
|
envAgentName: process.env['AIS_AGENT_NAME'],
|
|
@@ -224,31 +240,31 @@ class BondPendingError extends Error {
|
|
|
224
240
|
userCode;
|
|
225
241
|
bondPending = true;
|
|
226
242
|
constructor(verificationUri, userCode) {
|
|
227
|
-
super(`Memory bond required.\n\n` +
|
|
243
|
+
super(`Memory bond required — ask your human to approve now.\n\n` +
|
|
228
244
|
`Visit: ${verificationUri}\n` +
|
|
229
245
|
`Code: ${userCode}\n\n` +
|
|
230
|
-
`Enter your email and tap Approve, then retry this tool. Takes about 30 seconds
|
|
246
|
+
`Enter your email and tap Approve, then retry this tool. Takes about 30 seconds.\n` +
|
|
247
|
+
`Heads up: this code expires in 30 minutes, and unclaimed agents (and their memories) are archived after 7 days.`);
|
|
231
248
|
this.verificationUri = verificationUri;
|
|
232
249
|
this.userCode = userCode;
|
|
233
250
|
this.name = 'BondPendingError';
|
|
234
251
|
}
|
|
235
252
|
}
|
|
236
|
-
async function startDeviceFlow() {
|
|
253
|
+
async function startDeviceFlow(priorProvisionalToken) {
|
|
237
254
|
emitTelemetry({ event: 'auth.device_flow.start' });
|
|
238
255
|
stderr('Starting authentication...');
|
|
239
|
-
const authRes = (await aisPost('/v1/oauth/device/authorize', {
|
|
240
|
-
name: `agent-${homedir().split('/').pop() ?? 'user'}`,
|
|
241
|
-
}));
|
|
256
|
+
const authRes = (await aisPost('/v1/oauth/device/authorize', { name: `agent-${homedir().split('/').pop() ?? 'user'}` }, priorProvisionalToken));
|
|
242
257
|
if (!authRes.success || !authRes.data) {
|
|
243
258
|
throw new Error(`Authentication failed: ${authRes.error?.message ?? 'Unknown error'}`);
|
|
244
259
|
}
|
|
245
|
-
const { device_code, user_code, verification_uri_complete, interval, agent_id, provisional_tenant_id } = authRes.data;
|
|
260
|
+
const { device_code, user_code, verification_uri_complete, interval, agent_id, provisional_tenant_id, provisional_token } = authRes.data;
|
|
246
261
|
stderr('');
|
|
247
262
|
stderr('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
248
263
|
stderr(' aismemory — memory bond required');
|
|
249
264
|
stderr('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
250
265
|
stderr(` Bond URL : ${verification_uri_complete}`);
|
|
251
266
|
stderr(` Code : ${user_code}`);
|
|
267
|
+
stderr(' Code expires in 30 min; unclaimed agents are archived after 7 days.');
|
|
252
268
|
stderr('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
253
269
|
const flow = {
|
|
254
270
|
device_code,
|
|
@@ -256,6 +272,7 @@ async function startDeviceFlow() {
|
|
|
256
272
|
verification_uri_complete,
|
|
257
273
|
agent_id,
|
|
258
274
|
provisional_tenant_id,
|
|
275
|
+
provisional_token,
|
|
259
276
|
// poll is assigned below after flowRef is captured so pollForToken holds
|
|
260
277
|
// a reference to the live flow object for URL updates on auto-restart.
|
|
261
278
|
dead: false,
|
|
@@ -276,7 +293,7 @@ async function startDeviceFlow() {
|
|
|
276
293
|
return flow;
|
|
277
294
|
}
|
|
278
295
|
async function pollForToken(device_code, interval, agent_id, provisional_tenant_id,
|
|
279
|
-
/** Mutable ref so auto-restart can update the surfaced URL in ActiveDeviceFlow */
|
|
296
|
+
/** Mutable ref so auto-restart can update the surfaced URL and provisional token in ActiveDeviceFlow */
|
|
280
297
|
flowRef) {
|
|
281
298
|
const pollIntervalMs = Math.max(interval, 5) * 1000;
|
|
282
299
|
const handle = {
|
|
@@ -307,9 +324,7 @@ flowRef) {
|
|
|
307
324
|
}
|
|
308
325
|
},
|
|
309
326
|
startNewFlow: async () => {
|
|
310
|
-
const authRes = (await aisPost('/v1/oauth/device/authorize', {
|
|
311
|
-
name: `agent-${homedir().split('/').pop() ?? 'user'}`,
|
|
312
|
-
}));
|
|
327
|
+
const authRes = (await aisPost('/v1/oauth/device/authorize', { name: `agent-${homedir().split('/').pop() ?? 'user'}` }, flowRef?.provisional_token));
|
|
313
328
|
if (!authRes.success || !authRes.data) {
|
|
314
329
|
throw new Error(`Re-authorization failed: ${authRes.error?.message ?? 'Unknown error'}`);
|
|
315
330
|
}
|
|
@@ -319,10 +334,13 @@ flowRef) {
|
|
|
319
334
|
user_code: authRes.data.user_code,
|
|
320
335
|
};
|
|
321
336
|
// Update the mutable ref so the caller (ensureCredentials) can surface
|
|
322
|
-
// the new URL to the human without restarting the MCP process
|
|
337
|
+
// the new URL to the human without restarting the MCP process, and so
|
|
338
|
+
// a THIRD expiry in the same flow re-sends the latest provisional
|
|
339
|
+
// token rather than a stale, already-superseded one.
|
|
323
340
|
if (flowRef) {
|
|
324
341
|
flowRef.verification_uri_complete = newHandle.verification_uri_complete;
|
|
325
342
|
flowRef.user_code = newHandle.user_code;
|
|
343
|
+
flowRef.provisional_token = authRes.data.provisional_token;
|
|
326
344
|
}
|
|
327
345
|
stderr('');
|
|
328
346
|
stderr('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
@@ -404,7 +422,7 @@ const tools = [
|
|
|
404
422
|
},
|
|
405
423
|
{
|
|
406
424
|
name: 'dream',
|
|
407
|
-
description: 'Trigger a memory consolidation dream cycle — promotes important memories and archives old ones.',
|
|
425
|
+
description: 'Trigger a memory consolidation dream cycle — promotes important memories and archives old ones. Promotion only scans the session tier; active-tier memories move via context-clear or handoff, not dream.',
|
|
408
426
|
inputSchema: { type: 'object', properties: {} },
|
|
409
427
|
},
|
|
410
428
|
{
|
|
@@ -523,6 +541,10 @@ async function main() {
|
|
|
523
541
|
await import('./cli/sync-memory.js');
|
|
524
542
|
return; // sync-memory.ts calls process.exit() on failure
|
|
525
543
|
}
|
|
544
|
+
if (process.argv[2] === 'doctor') {
|
|
545
|
+
await import('./cli/doctor.js');
|
|
546
|
+
return; // doctor.ts calls process.exit() itself
|
|
547
|
+
}
|
|
526
548
|
// Agent API key auth mode (CORBOT-B1D3244C) — unattended machines. Checked
|
|
527
549
|
// before any device-flow / OAuth-credential / DID-key-auth setup below so
|
|
528
550
|
// this mode NEVER falls back to those paths (that fallback is what used to
|
|
@@ -565,51 +587,50 @@ async function main() {
|
|
|
565
587
|
if (!isCredsStale(creds.expiresAt, Date.now()))
|
|
566
588
|
return creds;
|
|
567
589
|
// CORBOT-A5BBD580: tokens live 7 days but the process can outlive
|
|
568
|
-
// them.
|
|
590
|
+
// them. Before falling through to key/device auth, re-read the file —
|
|
591
|
+
// an external repair may have already refreshed it. loadCredentials()
|
|
592
|
+
// returns null for a still-stale file, so this simplifies to a
|
|
593
|
+
// re-read + early return (27FA5CDD).
|
|
594
|
+
creds = loadCredentials();
|
|
595
|
+
if (creds)
|
|
596
|
+
return creds;
|
|
569
597
|
stderr('Cached credentials are expired or expiring soon — re-authenticating...');
|
|
570
|
-
creds = null;
|
|
571
598
|
}
|
|
572
599
|
// ── Key auth (Phase 1 DID auth) ───────────────────────────────────────
|
|
573
|
-
|
|
574
|
-
// server-issued challenge and skip the device-flow ceremony entirely.
|
|
575
|
-
// tryKeyAuth() returns null if no key file is present (clean fallback
|
|
576
|
-
// to device flow) and throws only on real failures (e.g. server says
|
|
577
|
-
// INVALID_SIGNATURE). On throw we log and continue to device flow —
|
|
578
|
-
// device flow is the recovery path for "user wiped their key file".
|
|
600
|
+
let keyResult = null;
|
|
579
601
|
try {
|
|
580
|
-
|
|
581
|
-
if (keyResult) {
|
|
582
|
-
const claims = decodeJwtClaims(keyResult.token);
|
|
583
|
-
const tenantId = claims.tenantId ?? '';
|
|
584
|
-
const resolved = await resolveAgent(keyResult.token, '', tenantId, loadOAuthCredentials()?.agentId);
|
|
585
|
-
creds = {
|
|
586
|
-
token: keyResult.token,
|
|
587
|
-
agentId: resolved.agentId,
|
|
588
|
-
tenantId: resolved.tenantId || tenantId,
|
|
589
|
-
expiresAt: keyResult.expiresAt,
|
|
590
|
-
};
|
|
591
|
-
saveCredentials(creds);
|
|
592
|
-
emitTelemetry({
|
|
593
|
-
event: 'auth.key.success',
|
|
594
|
-
agentIdHash: hashAgentId(creds.agentId),
|
|
595
|
-
});
|
|
596
|
-
stderr('Authenticated via DID key auth (no browser required)');
|
|
597
|
-
if (!postAuthDone) {
|
|
598
|
-
postAuthDone = true;
|
|
599
|
-
void runPostAuth(creds);
|
|
600
|
-
}
|
|
601
|
-
return creds;
|
|
602
|
-
}
|
|
602
|
+
keyResult = await tryKeyAuth({ aisUrl: AIS_URL });
|
|
603
603
|
}
|
|
604
604
|
catch (err) {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
});
|
|
605
|
+
// Real key-auth failure (e.g. INVALID_SIGNATURE / server unreachable).
|
|
606
|
+
// Device flow remains the recovery path ONLY for this case.
|
|
607
|
+
emitTelemetry({ event: 'auth.key.fallback', error: err instanceof Error ? err.message : String(err) });
|
|
609
608
|
stderr(`Key auth failed: ${err instanceof Error ? err.message : String(err)}. Falling back to device flow.`);
|
|
610
609
|
}
|
|
610
|
+
if (keyResult) {
|
|
611
|
+
// Post-auth failures (agent resolution, claim decode) are REAL errors —
|
|
612
|
+
// surface them to the caller. They must never route to device flow
|
|
613
|
+
// (2026-08-24 lockout: a pagination bug here minted ghost agents).
|
|
614
|
+
const claims = decodeJwtClaims(keyResult.token);
|
|
615
|
+
const tenantId = claims.tenantId ?? '';
|
|
616
|
+
const resolved = await resolveAgent(keyResult.token, '', tenantId, loadOAuthCredentials()?.agentId);
|
|
617
|
+
creds = {
|
|
618
|
+
token: keyResult.token,
|
|
619
|
+
agentId: resolved.agentId,
|
|
620
|
+
tenantId: resolved.tenantId || tenantId,
|
|
621
|
+
expiresAt: keyResult.expiresAt,
|
|
622
|
+
};
|
|
623
|
+
saveCredentials(creds);
|
|
624
|
+
emitTelemetry({ event: 'auth.key.success', agentIdHash: hashAgentId(creds.agentId) });
|
|
625
|
+
stderr('Authenticated via DID key auth (no browser required)');
|
|
626
|
+
if (!postAuthDone) {
|
|
627
|
+
postAuthDone = true;
|
|
628
|
+
void runPostAuth(creds);
|
|
629
|
+
}
|
|
630
|
+
return creds;
|
|
631
|
+
}
|
|
611
632
|
if (!activeFlow || activeFlow.dead) {
|
|
612
|
-
activeFlow = await startDeviceFlow();
|
|
633
|
+
activeFlow = await startDeviceFlow(activeFlow?.provisional_token);
|
|
613
634
|
}
|
|
614
635
|
const sentinel = Symbol('bond-pending');
|
|
615
636
|
// poll is always set by startDeviceFlow before it returns; this guard
|
|
@@ -630,7 +651,7 @@ async function main() {
|
|
|
630
651
|
// Always say WHY the old flow died: a silent discard here hid three
|
|
631
652
|
// weeks of broken cold-start bonding (CORBOT-9CE03C6E).
|
|
632
653
|
stderr(`Device flow discarded (${err instanceof Error ? err.message : String(err)}); starting a fresh one.`);
|
|
633
|
-
activeFlow = await startDeviceFlow();
|
|
654
|
+
activeFlow = await startDeviceFlow(activeFlow.provisional_token);
|
|
634
655
|
throw new BondPendingError(activeFlow.verification_uri_complete, activeFlow.user_code);
|
|
635
656
|
}
|
|
636
657
|
if (got === sentinel) {
|
|
@@ -766,8 +787,12 @@ async function main() {
|
|
|
766
787
|
const target = await resolveAgentForLoad(token, query);
|
|
767
788
|
agentId = target.id;
|
|
768
789
|
// Persist the switch so subsequent tool calls use the new agent.
|
|
790
|
+
// saveCredentials() MUST be called here so that a process restart
|
|
791
|
+
// (silent MCP reconnect) restores the switched agent from disk
|
|
792
|
+
// instead of silently reverting to the default (CORBOT-497C7FA3).
|
|
769
793
|
if (creds) {
|
|
770
794
|
creds = { ...creds, agentId };
|
|
795
|
+
saveCredentials(creds);
|
|
771
796
|
}
|
|
772
797
|
agentContext = await hydrateIdentity(AIS_URL, agentId, token);
|
|
773
798
|
applyMemoryRefresh(agentId, token);
|