aismemory 0.6.0 → 0.6.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.
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import { type OAuthCredentials } from '../oauth-credentials.js';
3
+ export type CheckStatus = 'PASS' | 'WARN' | 'FAIL';
4
+ export interface CheckResult {
5
+ name: string;
6
+ status: CheckStatus;
7
+ message: string;
8
+ hint?: string;
9
+ }
10
+ export interface DoctorReport {
11
+ aisUrl: string;
12
+ checks: CheckResult[];
13
+ problemCount: number;
14
+ verdict: 'pass' | 'problems';
15
+ exitCode: 0 | 1;
16
+ }
17
+ export declare function checkNodeVersion(nodeVersion?: string, minMajor?: number): CheckResult;
18
+ export interface DnsLookupFn {
19
+ (hostname: string): Promise<{
20
+ address: string;
21
+ family: number;
22
+ }>;
23
+ }
24
+ export declare function checkDnsResolution(host: string, lookup?: DnsLookupFn): Promise<CheckResult>;
25
+ export interface TlsProbeFn {
26
+ (host: string, port: number, timeoutMs: number): Promise<void>;
27
+ }
28
+ export declare function checkTlsReachability(host: string, probe?: TlsProbeFn, port?: number, timeoutMs?: number): Promise<CheckResult>;
29
+ export type FetchFn = typeof fetch;
30
+ export interface RestHealthResult extends CheckResult {
31
+ /** The `Date` response header, if present — reused by the clock-skew check. */
32
+ dateHeader?: string;
33
+ }
34
+ export declare function checkRestHealth(baseUrl: string, fetchImpl?: FetchFn): Promise<RestHealthResult>;
35
+ export declare function checkMcpEndpoint(baseUrl: string, fetchImpl?: FetchFn): Promise<CheckResult>;
36
+ export interface CredentialsCheckDeps {
37
+ load?: (home?: string) => OAuthCredentials | null;
38
+ now?: () => number;
39
+ fetchImpl?: FetchFn;
40
+ home?: string;
41
+ }
42
+ export declare function checkCredentials(baseUrl: string, deps?: CredentialsCheckDeps): Promise<CheckResult>;
43
+ export declare function checkProxyEnv(env?: NodeJS.ProcessEnv): CheckResult;
44
+ export declare function checkClockSkew(dateHeader: string | undefined, now?: () => number, warnThresholdMs?: number): CheckResult;
45
+ export interface DoctorOptions {
46
+ aisUrl?: string;
47
+ env?: NodeJS.ProcessEnv;
48
+ nodeVersion?: string;
49
+ now?: () => number;
50
+ fetchImpl?: FetchFn;
51
+ dnsLookup?: DnsLookupFn;
52
+ tlsProbe?: TlsProbeFn;
53
+ loadCreds?: (home?: string) => OAuthCredentials | null;
54
+ home?: string;
55
+ }
56
+ export declare function runDoctor(options?: DoctorOptions): Promise<DoctorReport>;
57
+ export declare function main(argv?: string[]): Promise<void>;
@@ -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.js CHANGED
@@ -224,31 +224,31 @@ class BondPendingError extends Error {
224
224
  userCode;
225
225
  bondPending = true;
226
226
  constructor(verificationUri, userCode) {
227
- super(`Memory bond required.\n\n` +
227
+ super(`Memory bond required — ask your human to approve now.\n\n` +
228
228
  `Visit: ${verificationUri}\n` +
229
229
  `Code: ${userCode}\n\n` +
230
- `Enter your email and tap Approve, then retry this tool. Takes about 30 seconds.`);
230
+ `Enter your email and tap Approve, then retry this tool. Takes about 30 seconds.\n` +
231
+ `Heads up: this code expires in 30 minutes, and unclaimed agents (and their memories) are archived after 7 days.`);
231
232
  this.verificationUri = verificationUri;
232
233
  this.userCode = userCode;
233
234
  this.name = 'BondPendingError';
234
235
  }
235
236
  }
236
- async function startDeviceFlow() {
237
+ async function startDeviceFlow(priorProvisionalToken) {
237
238
  emitTelemetry({ event: 'auth.device_flow.start' });
238
239
  stderr('Starting authentication...');
239
- const authRes = (await aisPost('/v1/oauth/device/authorize', {
240
- name: `agent-${homedir().split('/').pop() ?? 'user'}`,
241
- }));
240
+ const authRes = (await aisPost('/v1/oauth/device/authorize', { name: `agent-${homedir().split('/').pop() ?? 'user'}` }, priorProvisionalToken));
242
241
  if (!authRes.success || !authRes.data) {
243
242
  throw new Error(`Authentication failed: ${authRes.error?.message ?? 'Unknown error'}`);
244
243
  }
245
- const { device_code, user_code, verification_uri_complete, interval, agent_id, provisional_tenant_id } = authRes.data;
244
+ const { device_code, user_code, verification_uri_complete, interval, agent_id, provisional_tenant_id, provisional_token } = authRes.data;
246
245
  stderr('');
247
246
  stderr('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
248
247
  stderr(' aismemory — memory bond required');
249
248
  stderr('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
250
249
  stderr(` Bond URL : ${verification_uri_complete}`);
251
250
  stderr(` Code : ${user_code}`);
251
+ stderr(' Code expires in 30 min; unclaimed agents are archived after 7 days.');
252
252
  stderr('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
253
253
  const flow = {
254
254
  device_code,
@@ -256,6 +256,7 @@ async function startDeviceFlow() {
256
256
  verification_uri_complete,
257
257
  agent_id,
258
258
  provisional_tenant_id,
259
+ provisional_token,
259
260
  // poll is assigned below after flowRef is captured so pollForToken holds
260
261
  // a reference to the live flow object for URL updates on auto-restart.
261
262
  dead: false,
@@ -276,7 +277,7 @@ async function startDeviceFlow() {
276
277
  return flow;
277
278
  }
278
279
  async function pollForToken(device_code, interval, agent_id, provisional_tenant_id,
279
- /** Mutable ref so auto-restart can update the surfaced URL in ActiveDeviceFlow */
280
+ /** Mutable ref so auto-restart can update the surfaced URL and provisional token in ActiveDeviceFlow */
280
281
  flowRef) {
281
282
  const pollIntervalMs = Math.max(interval, 5) * 1000;
282
283
  const handle = {
@@ -307,9 +308,7 @@ flowRef) {
307
308
  }
308
309
  },
309
310
  startNewFlow: async () => {
310
- const authRes = (await aisPost('/v1/oauth/device/authorize', {
311
- name: `agent-${homedir().split('/').pop() ?? 'user'}`,
312
- }));
311
+ const authRes = (await aisPost('/v1/oauth/device/authorize', { name: `agent-${homedir().split('/').pop() ?? 'user'}` }, flowRef?.provisional_token));
313
312
  if (!authRes.success || !authRes.data) {
314
313
  throw new Error(`Re-authorization failed: ${authRes.error?.message ?? 'Unknown error'}`);
315
314
  }
@@ -319,10 +318,13 @@ flowRef) {
319
318
  user_code: authRes.data.user_code,
320
319
  };
321
320
  // Update the mutable ref so the caller (ensureCredentials) can surface
322
- // the new URL to the human without restarting the MCP process.
321
+ // the new URL to the human without restarting the MCP process, and so
322
+ // a THIRD expiry in the same flow re-sends the latest provisional
323
+ // token rather than a stale, already-superseded one.
323
324
  if (flowRef) {
324
325
  flowRef.verification_uri_complete = newHandle.verification_uri_complete;
325
326
  flowRef.user_code = newHandle.user_code;
327
+ flowRef.provisional_token = authRes.data.provisional_token;
326
328
  }
327
329
  stderr('');
328
330
  stderr('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
@@ -404,7 +406,7 @@ const tools = [
404
406
  },
405
407
  {
406
408
  name: 'dream',
407
- description: 'Trigger a memory consolidation dream cycle — promotes important memories and archives old ones.',
409
+ 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
410
  inputSchema: { type: 'object', properties: {} },
409
411
  },
410
412
  {
@@ -523,6 +525,10 @@ async function main() {
523
525
  await import('./cli/sync-memory.js');
524
526
  return; // sync-memory.ts calls process.exit() on failure
525
527
  }
528
+ if (process.argv[2] === 'doctor') {
529
+ await import('./cli/doctor.js');
530
+ return; // doctor.ts calls process.exit() itself
531
+ }
526
532
  // Agent API key auth mode (CORBOT-B1D3244C) — unattended machines. Checked
527
533
  // before any device-flow / OAuth-credential / DID-key-auth setup below so
528
534
  // this mode NEVER falls back to those paths (that fallback is what used to
@@ -609,7 +615,7 @@ async function main() {
609
615
  stderr(`Key auth failed: ${err instanceof Error ? err.message : String(err)}. Falling back to device flow.`);
610
616
  }
611
617
  if (!activeFlow || activeFlow.dead) {
612
- activeFlow = await startDeviceFlow();
618
+ activeFlow = await startDeviceFlow(activeFlow?.provisional_token);
613
619
  }
614
620
  const sentinel = Symbol('bond-pending');
615
621
  // poll is always set by startDeviceFlow before it returns; this guard
@@ -630,7 +636,7 @@ async function main() {
630
636
  // Always say WHY the old flow died: a silent discard here hid three
631
637
  // weeks of broken cold-start bonding (CORBOT-9CE03C6E).
632
638
  stderr(`Device flow discarded (${err instanceof Error ? err.message : String(err)}); starting a fresh one.`);
633
- activeFlow = await startDeviceFlow();
639
+ activeFlow = await startDeviceFlow(activeFlow.provisional_token);
634
640
  throw new BondPendingError(activeFlow.verification_uri_complete, activeFlow.user_code);
635
641
  }
636
642
  if (got === sentinel) {
@@ -766,8 +772,12 @@ async function main() {
766
772
  const target = await resolveAgentForLoad(token, query);
767
773
  agentId = target.id;
768
774
  // Persist the switch so subsequent tool calls use the new agent.
775
+ // saveCredentials() MUST be called here so that a process restart
776
+ // (silent MCP reconnect) restores the switched agent from disk
777
+ // instead of silently reverting to the default (CORBOT-497C7FA3).
769
778
  if (creds) {
770
779
  creds = { ...creds, agentId };
780
+ saveCredentials(creds);
771
781
  }
772
782
  agentContext = await hydrateIdentity(AIS_URL, agentId, token);
773
783
  applyMemoryRefresh(agentId, token);