@agentbridge1/cli 0.0.6 → 0.0.8

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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "builtAt": "2026-06-06T12:05:27.957Z",
3
- "gitHead": "ec17ba8",
4
- "sourceLatestMtime": "2026-06-06T11:59:41.649Z",
5
- "sourceLatestFile": "src/commands/watch.ts"
2
+ "builtAt": "2026-06-20T05:37:34.216Z",
3
+ "gitHead": "fb83b92",
4
+ "sourceLatestMtime": "2026-06-20T04:44:28.305Z",
5
+ "sourceLatestFile": "src/commands/setup-mcp.ts"
6
6
  }
@@ -4,6 +4,8 @@ exports.runConnect = runConnect;
4
4
  const config_1 = require("../config");
5
5
  const errors_1 = require("../errors");
6
6
  const http_1 = require("../http");
7
+ const install_rules_1 = require("./install-rules");
8
+ const mcp_config_1 = require("../mcp-config");
7
9
  function resolveBaseUrl(override) {
8
10
  return (override ??
9
11
  process.env.AGENTBRIDGE_BASE_URL ??
@@ -132,64 +134,12 @@ async function resolveExecutionSurfaceFromHello(projectId, apiKey, apiBaseUrl) {
132
134
  };
133
135
  }
134
136
  }
135
- async function bootstrapDefaultConnection(projectId, apiKey, apiBaseUrl) {
136
- const url = `${apiBaseUrl}/v1/dev/projects/${projectId}/connections/bootstrap-default`;
137
- try {
138
- const res = await fetch(url, {
139
- method: "POST",
140
- headers: {
141
- Authorization: `Bearer ${apiKey}`,
142
- "Content-Type": "application/json",
143
- },
144
- body: JSON.stringify({}),
145
- });
146
- if (!res.ok)
147
- return { ok: false, status: res.status, reason: `http_${res.status}` };
148
- const body = (await res.json());
149
- const nextApiKey = body.api_key?.trim();
150
- const executionSurfaceId = body.execution_surface_id?.trim();
151
- if (!nextApiKey || !executionSurfaceId) {
152
- return { ok: false, status: 200, reason: "invalid_payload" };
153
- }
154
- return { ok: true, apiKey: nextApiKey, executionSurfaceId };
155
- }
156
- catch {
157
- return { ok: false, status: 0, reason: "network_error" };
158
- }
159
- }
160
- async function rotateActiveConnectionKey(projectId, apiKey, apiBaseUrl) {
161
- const listUrl = `${apiBaseUrl}/v1/dev/projects/${projectId}/connections`;
162
- try {
163
- const listRes = await fetch(listUrl, {
164
- headers: { Authorization: `Bearer ${apiKey}` },
165
- });
166
- if (!listRes.ok)
167
- return { ok: false, reason: `list_http_${listRes.status}` };
168
- const listBody = (await listRes.json());
169
- const activeConnection = (listBody.connections ?? []).find((connection) => connection?.id && connection?.status === "active");
170
- if (!activeConnection?.id)
171
- return { ok: false, reason: "no_active_connection" };
172
- const rotateUrl = `${apiBaseUrl}/v1/dev/projects/${projectId}/connections/${activeConnection.id}/rotate-key`;
173
- const rotateRes = await fetch(rotateUrl, {
174
- method: "POST",
175
- headers: {
176
- Authorization: `Bearer ${apiKey}`,
177
- "Content-Type": "application/json",
178
- },
179
- body: JSON.stringify({}),
180
- });
181
- if (!rotateRes.ok)
182
- return { ok: false, reason: `rotate_http_${rotateRes.status}` };
183
- const rotateBody = (await rotateRes.json());
184
- const rotatedApiKey = rotateBody.api_key?.trim();
185
- const executionSurfaceId = rotateBody.connection?.execution_surface?.id?.trim();
186
- if (!rotatedApiKey || !executionSurfaceId)
187
- return { ok: false, reason: "invalid_payload" };
188
- return { ok: true, apiKey: rotatedApiKey, executionSurfaceId };
189
- }
190
- catch {
191
- return { ok: false, reason: "network_error" };
192
- }
137
+ /**
138
+ * Merge the AgentBridge MCP server entry into .cursor/mcp.json (project-level).
139
+ * Preserves any other MCP servers already configured.
140
+ */
141
+ function writeMcpConfig(projectId, apiKey, apiBaseUrl) {
142
+ return (0, mcp_config_1.writeServerMcpConfig)(projectId, apiKey, apiBaseUrl);
193
143
  }
194
144
  async function runConnect(options = {}) {
195
145
  process.stdout.write("AgentBridge connect\n");
@@ -249,86 +199,67 @@ async function runConnect(options = {}) {
249
199
  throw err;
250
200
  }
251
201
  process.stdout.write("OK\n");
252
- let effectiveApiKey = apiKey;
253
202
  const diagnostics = {
254
203
  helloIdentityModel: "unknown",
255
204
  helloStatus: "not_attempted",
256
- bootstrapStatus: "not_attempted",
257
- rotateStatus: "not_attempted",
258
205
  };
259
- const helloResolution = await resolveExecutionSurfaceFromHello(projectId, effectiveApiKey, apiBaseUrl);
206
+ const helloResolution = await resolveExecutionSurfaceFromHello(projectId, apiKey, apiBaseUrl);
260
207
  diagnostics.helloIdentityModel = helloResolution.identityModel;
261
208
  diagnostics.helloStatus = helloResolution.status;
262
- let executionSurfaceId = helloResolution.executionSurfaceId;
263
- let connectionUpgraded = false;
264
- if (!executionSurfaceId) {
265
- diagnostics.bootstrapStatus = "attempted";
266
- const bootstrapAttempt = await bootstrapDefaultConnection(projectId, effectiveApiKey, apiBaseUrl);
267
- if (bootstrapAttempt.ok) {
268
- effectiveApiKey = bootstrapAttempt.apiKey;
269
- executionSurfaceId = bootstrapAttempt.executionSurfaceId;
270
- diagnostics.bootstrapStatus = "ok";
271
- diagnostics.rotateStatus = "skipped";
272
- connectionUpgraded = true;
273
- }
274
- else if (bootstrapAttempt.status === 409) {
275
- diagnostics.bootstrapStatus = "http_409_existing_connection";
276
- const rotated = await rotateActiveConnectionKey(projectId, effectiveApiKey, apiBaseUrl);
277
- if (rotated.ok) {
278
- diagnostics.rotateStatus = "ok";
279
- effectiveApiKey = rotated.apiKey;
280
- executionSurfaceId = rotated.executionSurfaceId;
281
- connectionUpgraded = true;
282
- }
283
- else {
284
- diagnostics.rotateStatus = rotated.reason;
285
- }
286
- }
287
- else {
288
- diagnostics.bootstrapStatus = bootstrapAttempt.reason;
289
- diagnostics.rotateStatus = "skipped";
290
- }
291
- }
292
- else {
293
- diagnostics.bootstrapStatus = "skipped";
294
- diagnostics.rotateStatus = "skipped";
295
- }
209
+ const executionSurfaceId = helloResolution.executionSurfaceId;
296
210
  if (!executionSurfaceId) {
297
211
  (0, config_1.updateConfig)({
298
212
  projectId,
299
- apiKey: effectiveApiKey,
213
+ apiKey,
300
214
  apiBaseUrl,
301
215
  });
302
- throw new errors_1.SafeCliError([
303
- "Connection incomplete.",
304
- "Project access was verified, but AgentBridge could not create or resolve an execution surface.",
305
- "Tracked work cannot start yet.",
306
- "",
307
- `hello identity model: ${diagnostics.helloIdentityModel}`,
308
- `hello status: ${diagnostics.helloStatus}`,
309
- `bootstrap-default status: ${diagnostics.bootstrapStatus}`,
310
- `rotate-key status: ${diagnostics.rotateStatus}`,
311
- `backend URL: ${apiBaseUrl}`,
312
- ].join("\n"));
216
+ const helloWhy = helloResolution.identityModel === "legacy"
217
+ ? "This API key is a legacy project agent key, not a room connection key from room create or rotate."
218
+ : helloResolution.status === "unknown_identity_model"
219
+ ? "The server /hello response did not include identity_model work_identity for this key."
220
+ : helloResolution.status === "missing_execution_surface"
221
+ ? "/hello returned work_identity but no execution_surface.id."
222
+ : helloResolution.status.startsWith("http_")
223
+ ? `/hello failed (${helloResolution.status}).`
224
+ : "AgentBridge could not resolve an execution surface for this key.";
225
+ throw (0, errors_1.catalogCliError)("CONNECT_EXECUTION_SURFACE_MISSING", {
226
+ what: `Project access was verified, but connect could not bind an execution surface (hello: ${diagnostics.helloIdentityModel}, status: ${diagnostics.helloStatus}).`,
227
+ why: helloWhy,
228
+ });
313
229
  }
314
230
  // Persist credentials
315
231
  (0, config_1.updateConfig)({
316
232
  projectId,
317
- apiKey: effectiveApiKey,
233
+ apiKey,
318
234
  apiBaseUrl,
319
235
  ...(executionSurfaceId ? { executionSurfaceId } : {}),
320
236
  });
321
237
  process.stdout.write("Credentials saved to .agentbridge/config.json\n");
322
- if (connectionUpgraded) {
323
- process.stdout.write("Auto-configured an AgentConnection key and execution surface for this project.\n");
238
+ process.stdout.write(`Execution surface: ${executionSurfaceId}\n`);
239
+ // Auto-write .cursor/mcp.json (merge preserves other MCP servers)
240
+ try {
241
+ const mcpPath = writeMcpConfig(projectId, apiKey, apiBaseUrl);
242
+ process.stdout.write(`MCP config written: ${mcpPath}\n`);
324
243
  }
325
- else {
326
- process.stdout.write(`Execution surface: ${executionSurfaceId}\n`);
244
+ catch (err) {
245
+ const msg = err instanceof Error ? err.message : String(err);
246
+ process.stdout.write(`⚠ Could not write MCP config automatically: ${msg}\n`);
247
+ process.stdout.write(" Run `agentbridge setup-mcp` to generate it manually.\n");
248
+ }
249
+ // Auto-install project rules (soft — failure is non-blocking)
250
+ try {
251
+ await (0, install_rules_1.runInstallRules)({ projectId, apiKey, apiBaseUrl });
252
+ process.stdout.write("AgentBridge rules installed.\n");
253
+ }
254
+ catch (err) {
255
+ const msg = err instanceof Error ? err.message : String(err);
256
+ process.stdout.write(`⚠ Could not install project rules: ${msg}\n`);
257
+ process.stdout.write(" Run `agentbridge install-rules` manually after connecting.\n");
327
258
  }
328
259
  // Pick or auto-select an agent identity
329
260
  let activeAgentId = options.agentId ?? cfg.activeAgentId;
330
261
  if (!activeAgentId) {
331
- const agents = await listAgents(projectId, effectiveApiKey, apiBaseUrl);
262
+ const agents = await listAgents(projectId, apiKey, apiBaseUrl);
332
263
  if (agents.length === 1 && agents[0]) {
333
264
  activeAgentId = agents[0].id;
334
265
  (0, config_1.updateConfig)({ activeAgentId });
@@ -357,15 +288,23 @@ async function runConnect(options = {}) {
357
288
  process.stdout.write([
358
289
  "",
359
290
  "Next:",
360
- " agentbridge doctor",
361
- " agentbridge watch",
291
+ " 1. Restart your editor to activate the MCP server.",
292
+ " 2. agentbridge doctor (verify CLI + MCP + rules are aligned)",
293
+ " 3. agentbridge start \"task summary\"",
294
+ " 4. agentbridge watch",
362
295
  "",
363
296
  ].join("\n"));
364
297
  }
365
298
  else {
366
299
  process.stdout.write("✓ Connected.\n");
367
- process.stdout.write("Run `agentbridge use <agent-id>` and then:\n");
368
- process.stdout.write(" agentbridge doctor\n");
369
- process.stdout.write(" agentbridge watch\n");
300
+ process.stdout.write([
301
+ "",
302
+ "Next:",
303
+ " 1. Run `agentbridge use <agent-id>` to select an agent identity.",
304
+ " 2. Restart your editor to activate the MCP server.",
305
+ " 3. agentbridge doctor",
306
+ " 4. agentbridge start \"task summary\"",
307
+ "",
308
+ ].join("\n"));
370
309
  }
371
310
  }
@@ -11,9 +11,15 @@ const dist_freshness_1 = require("./dist-freshness");
11
11
  const session_state_1 = require("../session-state");
12
12
  const server_sync_1 = require("../server-sync");
13
13
  const recovery_reconcile_1 = require("../recovery-reconcile");
14
+ const rules_sync_1 = require("../rules-sync");
14
15
  function cliRootFromCommandDir() {
15
16
  return (0, node_path_1.resolve)(__dirname, "..", "..");
16
17
  }
18
+ function isRecoveryBaselinePendingHello(hello) {
19
+ return (hello.setup_required === true ||
20
+ hello.agentbridge_status === "recovery_baseline_pending" ||
21
+ hello.instruction?.recovery_status === "baseline_required");
22
+ }
17
23
  function normalizeReasonCandidate(value) {
18
24
  if (!value)
19
25
  return undefined;
@@ -87,26 +93,78 @@ function extractHttpReason(error) {
87
93
  }
88
94
  return `http_${error.status}`;
89
95
  }
90
- function detectGovernanceSignals(workspaceRoot) {
91
- const rulesMdcPath = (0, node_path_1.resolve)(workspaceRoot, ".cursor", "rules", "agentbridge.mdc");
92
- const rulesMarkdownPath = (0, node_path_1.resolve)(workspaceRoot, "AGENTBRIDGE.md");
96
+ function detectGovernanceSignals(workspaceRoot, expectedProjectId) {
97
+ const analysis = (0, rules_sync_1.analyzeRulesArtifacts)(workspaceRoot, expectedProjectId);
93
98
  const mcpConfigPath = (0, node_path_1.resolve)(workspaceRoot, ".cursor", "mcp.json");
94
- const rulesInstalled = (0, node_fs_1.existsSync)(rulesMdcPath) && (0, node_fs_1.existsSync)(rulesMarkdownPath);
95
99
  let mcpConfigured = false;
100
+ let mcpProjectId = null;
96
101
  if ((0, node_fs_1.existsSync)(mcpConfigPath)) {
97
102
  try {
98
103
  const parsed = JSON.parse((0, node_fs_1.readFileSync)(mcpConfigPath, "utf8"));
99
- mcpConfigured = Boolean(parsed?.mcpServers &&
100
- (parsed.mcpServers["agentbridge"] || parsed.mcpServers["agentbridge-mcp"]));
104
+ const entry = parsed?.mcpServers?.["agentbridge"] ?? parsed?.mcpServers?.["agentbridge-mcp"];
105
+ mcpConfigured = Boolean(entry);
106
+ if (entry?.env?.AGENTBRIDGE_PROJECT_ID) {
107
+ mcpProjectId = entry.env.AGENTBRIDGE_PROJECT_ID;
108
+ }
101
109
  }
102
110
  catch {
103
111
  mcpConfigured = false;
104
112
  }
105
113
  }
114
+ const mcpCredentialMismatch = mcpConfigured && mcpProjectId !== null && expectedProjectId !== undefined
115
+ ? mcpProjectId !== expectedProjectId
116
+ : false;
106
117
  return {
107
- rulesInstalled,
118
+ rulesFilesPresent: analysis.filesPresent,
119
+ rulesFormat: analysis.format,
120
+ rulesProjectIdInRepo: analysis.projectIdInRepo,
121
+ rulesProjectIdMatches: analysis.projectIdMatchesConfig,
122
+ rulesInstalled: analysis.protocolRulesValid,
123
+ rulesIssues: analysis.issues,
108
124
  mcpConfigured,
109
- governanceStatus: rulesInstalled && mcpConfigured ? "active" : "inactive",
125
+ mcpCredentialMismatch,
126
+ mcpProjectId,
127
+ governanceStatus: analysis.protocolRulesValid && mcpConfigured ? "active" : "inactive",
128
+ };
129
+ }
130
+ function isRouteMissingBody(body) {
131
+ return body.toLowerCase().includes("route not found");
132
+ }
133
+ function mapProjectAccessFailure(error) {
134
+ if (error instanceof http_1.CliHttpError) {
135
+ const reason = extractHttpReason(error);
136
+ if (reason === "not_project_member") {
137
+ return {
138
+ status: "failed",
139
+ reason,
140
+ suggestedNextAction: "use an API key/agent that is a member of this project, or add this agent to the project.",
141
+ };
142
+ }
143
+ if (reason === "unauthorized") {
144
+ return {
145
+ status: "failed",
146
+ reason,
147
+ suggestedNextAction: "verify AGENTBRIDGE_API_KEY is valid for this environment and has project read access.",
148
+ };
149
+ }
150
+ if (reason === "project_not_found") {
151
+ return {
152
+ status: "failed",
153
+ reason,
154
+ suggestedNextAction: "verify AGENTBRIDGE_PROJECT_ID points to an existing project in this API environment.",
155
+ };
156
+ }
157
+ return {
158
+ status: "failed",
159
+ reason,
160
+ suggestedNextAction: "confirm project ID, API key, and base URL are correct and that this key has project read access.",
161
+ };
162
+ }
163
+ const messageReason = error instanceof Error ? normalizeReasonCandidate(error.message) : undefined;
164
+ return {
165
+ status: "failed",
166
+ reason: messageReason ?? "network_error",
167
+ suggestedNextAction: "check network reachability to the API base URL and retry doctor.",
110
168
  };
111
169
  }
112
170
  async function checkProjectAccess(ctx) {
@@ -119,46 +177,33 @@ async function checkProjectAccess(ctx) {
119
177
  const projectId = ctx.projectId;
120
178
  const apiKey = ctx.apiKey;
121
179
  const apiBaseUrl = ctx.apiBaseUrl;
180
+ const syncCtx = { projectId, apiKey, apiBaseUrl, apiKeySource: "config" };
181
+ const summaryPath = `/v1/dev/projects/${encodeURIComponent(projectId)}/summary`;
182
+ const packetPath = `/v1/dev/projects/${encodeURIComponent(projectId)}/packet`;
122
183
  try {
123
- const packet = await (0, http_1.getJson)({ projectId, apiKey, apiBaseUrl, apiKeySource: "config" }, `/v1/dev/projects/${encodeURIComponent(projectId)}/packet`);
124
- return { status: "ok", packet };
184
+ await (0, http_1.getJson)(syncCtx, summaryPath);
185
+ let packet;
186
+ try {
187
+ packet = await (0, http_1.getJson)(syncCtx, packetPath);
188
+ }
189
+ catch {
190
+ // Summary proves access; packet is optional (recovery metadata only).
191
+ }
192
+ return { status: "ok", packet, projectId };
125
193
  }
126
194
  catch (error) {
127
- if (error instanceof http_1.CliHttpError) {
128
- const reason = extractHttpReason(error);
129
- if (reason === "not_project_member") {
130
- return {
131
- status: "failed",
132
- reason,
133
- suggestedNextAction: "use an API key/agent that is a member of this project, or add this agent to the project.",
134
- };
195
+ if (error instanceof http_1.CliHttpError &&
196
+ error.status === 404 &&
197
+ isRouteMissingBody(error.body ?? "")) {
198
+ try {
199
+ const packet = await (0, http_1.getJson)(syncCtx, packetPath);
200
+ return { status: "ok", packet };
135
201
  }
136
- if (reason === "unauthorized") {
137
- return {
138
- status: "failed",
139
- reason,
140
- suggestedNextAction: "verify AGENTBRIDGE_API_KEY is valid for this environment and has project read access.",
141
- };
202
+ catch (packetError) {
203
+ return mapProjectAccessFailure(packetError);
142
204
  }
143
- if (reason === "project_not_found") {
144
- return {
145
- status: "failed",
146
- reason,
147
- suggestedNextAction: "verify AGENTBRIDGE_PROJECT_ID points to an existing project in this API environment.",
148
- };
149
- }
150
- return {
151
- status: "failed",
152
- reason,
153
- suggestedNextAction: "confirm project ID, API key, and base URL are correct and that this key has project read access.",
154
- };
155
205
  }
156
- const messageReason = error instanceof Error ? normalizeReasonCandidate(error.message) : undefined;
157
- return {
158
- status: "failed",
159
- reason: messageReason ?? "network_error",
160
- suggestedNextAction: "check network reachability to the API base URL and retry doctor.",
161
- };
206
+ return mapProjectAccessFailure(error);
162
207
  }
163
208
  }
164
209
  async function checkIdentityAccess(ctx) {
@@ -173,7 +218,8 @@ async function checkIdentityAccess(ctx) {
173
218
  }
174
219
  const helloUrl = `${ctx.apiBaseUrl}/v1/dev/projects/${ctx.projectId}/hello`;
175
220
  let identityModel = "unknown";
176
- let executionSurfaceId = null;
221
+ let helloExecutionSurfaceId = null;
222
+ let baselinePending = false;
177
223
  try {
178
224
  const helloRes = await fetch(helloUrl, {
179
225
  method: "POST",
@@ -185,18 +231,22 @@ async function checkIdentityAccess(ctx) {
185
231
  });
186
232
  if (helloRes.ok) {
187
233
  const hello = (await helloRes.json());
234
+ baselinePending = isRecoveryBaselinePendingHello(hello);
188
235
  if (hello.identity_model === "work_identity") {
189
236
  identityModel = "work_identity";
190
237
  }
191
238
  else if (hello.identity_model === "legacy") {
192
239
  identityModel = "legacy";
193
240
  }
194
- executionSurfaceId = hello.execution_surface?.id?.trim() || null;
241
+ helloExecutionSurfaceId = hello.execution_surface?.id?.trim() || null;
195
242
  }
196
243
  }
197
244
  catch {
198
245
  // Keep default unknown/null and continue with identity resolution checks.
199
246
  }
247
+ const configSurfaceId = ctx.configExecutionSurfaceId?.trim() || null;
248
+ const executionSurfaceId = helloExecutionSurfaceId || configSurfaceId;
249
+ const executionSurfaceSource = helloExecutionSurfaceId ? "hello" : configSurfaceId ? "config" : undefined;
200
250
  try {
201
251
  const syncCtx = {
202
252
  projectId: ctx.projectId,
@@ -209,18 +259,28 @@ async function checkIdentityAccess(ctx) {
209
259
  (0, server_sync_1.listWorkIdentities)(syncCtx),
210
260
  ]);
211
261
  const callerId = callerPacket?.work_identity?.id ?? null;
212
- const startCapable = Boolean(executionSurfaceId);
213
- if (callerId) {
214
- return { status: "ok", identityModel, executionSurfaceId, startCapable };
262
+ const callerResolvable = Boolean(callerId) || identities.length === 1;
263
+ if (identityModel === "unknown" &&
264
+ callerResolvable &&
265
+ (baselinePending || Boolean(configSurfaceId))) {
266
+ identityModel = "work_identity";
215
267
  }
216
- if (identities.length === 1) {
217
- return { status: "ok", identityModel, executionSurfaceId, startCapable };
268
+ const startCapable = Boolean(executionSurfaceId);
269
+ if (callerResolvable) {
270
+ return {
271
+ status: "ok",
272
+ identityModel,
273
+ executionSurfaceId,
274
+ executionSurfaceSource,
275
+ startCapable,
276
+ };
218
277
  }
219
278
  return {
220
279
  status: "failed",
221
280
  reason: "caller_identity_unresolved",
222
281
  identityModel,
223
282
  executionSurfaceId,
283
+ executionSurfaceSource,
224
284
  startCapable,
225
285
  };
226
286
  }
@@ -230,6 +290,7 @@ async function checkIdentityAccess(ctx) {
230
290
  reason: "caller_identity_unresolved",
231
291
  identityModel,
232
292
  executionSurfaceId,
293
+ executionSurfaceSource,
233
294
  startCapable: Boolean(executionSurfaceId),
234
295
  };
235
296
  }
@@ -256,7 +317,7 @@ async function runDoctor(cliRootOverride) {
256
317
  const projectConfigPresent = projectIdPresent && apiKeyPresent && baseUrlPresent;
257
318
  const activeSession = (0, session_state_1.readSessionState)();
258
319
  const hasActiveWork = Boolean(activeSession?.id || activeSession?.serverSessionId);
259
- const governance = detectGovernanceSignals(workspaceRoot);
320
+ const governance = detectGovernanceSignals(workspaceRoot, resolvedProjectId ?? undefined);
260
321
  const envContributed = Boolean(process.env.AGENTBRIDGE_PROJECT_ID) ||
261
322
  Boolean(process.env.AGENTBRIDGE_API_KEY) ||
262
323
  Boolean(process.env.AGENTBRIDGE_BASE_URL) ||
@@ -290,15 +351,64 @@ async function runDoctor(cliRootOverride) {
290
351
  if (projectAccess.reason) {
291
352
  lines.push(`Project access reason: ${projectAccess.reason}`);
292
353
  }
354
+ lines.push(`API key accepted by server: ${projectAccess.status === "ok"
355
+ ? "yes"
356
+ : projectAccess.status === "skipped"
357
+ ? "skipped"
358
+ : "no"}`);
293
359
  const identityAccess = await checkIdentityAccess({
294
360
  projectId: resolvedProjectId,
295
361
  apiKey: resolvedApiKey,
296
362
  apiBaseUrl: resolvedBaseUrl,
297
- configComplete: projectConfigPresent && projectAccess.status === "ok",
363
+ configComplete: projectConfigPresent,
364
+ configExecutionSurfaceId: cfg.executionSurfaceId,
298
365
  });
366
+ lines.push(`Rules files present: ${governance.rulesFilesPresent ? "yes" : "no"}`);
367
+ lines.push(`Rules format: ${governance.rulesFormat}`);
368
+ if (governance.rulesProjectIdInRepo) {
369
+ lines.push(`Rules project ID: ${governance.rulesProjectIdInRepo}`);
370
+ }
371
+ if (governance.rulesProjectIdMatches !== null) {
372
+ lines.push(`Rules project ID match: ${governance.rulesProjectIdMatches ? "yes" : "no"}`);
373
+ }
299
374
  lines.push(`Rules installed: ${governance.rulesInstalled ? "yes" : "no"}`);
300
375
  lines.push(`MCP configured: ${governance.mcpConfigured ? "yes" : "no"}`);
376
+ if (governance.mcpConfigured && governance.mcpProjectId) {
377
+ lines.push(`MCP project id: ${governance.mcpProjectId}`);
378
+ }
301
379
  lines.push(`Agent governance: ${governance.governanceStatus}`);
380
+ const cliConnected = projectAccess.status === "ok";
381
+ const supervisionStatus = !cliConnected
382
+ ? "blind"
383
+ : governance.mcpCredentialMismatch
384
+ ? "broken"
385
+ : !governance.mcpConfigured && !governance.rulesInstalled
386
+ ? "blind"
387
+ : !governance.mcpConfigured || !governance.rulesInstalled
388
+ ? "degraded"
389
+ : "ready";
390
+ lines.push(`Supervision status: ${supervisionStatus}`);
391
+ if (supervisionStatus === "broken") {
392
+ lines.push(` Supervision note: BROKEN — CLI project id (${projectAccess.projectId ?? "?"}) does not match MCP project id (${governance.mcpProjectId ?? "?"}).`);
393
+ lines.push(" Fix: run `agentbridge connect` again to re-write the MCP config with the correct credentials.");
394
+ }
395
+ else if (supervisionStatus === "blind") {
396
+ lines.push(" Supervision note: watch can observe filesystem changes only — MCP and rules both missing.");
397
+ }
398
+ else if (supervisionStatus === "degraded") {
399
+ if (!governance.mcpConfigured) {
400
+ lines.push(" Supervision note: MCP not configured — agent tool usage and implementation packets are invisible to watch.");
401
+ }
402
+ else {
403
+ lines.push(" Supervision note: rules not installed — agent is not operating under AgentBridge protocol.");
404
+ }
405
+ }
406
+ if (governance.rulesIssues.length > 0) {
407
+ for (const issue of governance.rulesIssues) {
408
+ lines.push(`Rules issue: ${issue}`);
409
+ }
410
+ lines.push("Rules fix: run `agentbridge install-rules`");
411
+ }
302
412
  lines.push(`Start capable: ${identityAccess.startCapable ? "yes" : "no"}`);
303
413
  if (projectAccess.status === "failed" && projectAccess.reason) {
304
414
  const view = (0, error_catalog_1.catalogViewForDoctorReason)(projectAccess.reason, projectAccess.suggestedNextAction);
@@ -354,7 +464,7 @@ async function runDoctor(cliRootOverride) {
354
464
  lines.push("Product status:");
355
465
  if (productStatus === "ready") {
356
466
  lines.push("- Connection: ready");
357
- lines.push("- Governance: " + governance.governanceStatus);
467
+ lines.push(`- Supervision: ${supervisionStatus}`);
358
468
  lines.push("- Recovery: ready");
359
469
  lines.push(`- Rules: ${governance.rulesInstalled ? "installed" : "missing"}`);
360
470
  lines.push(`- MCP: ${governance.mcpConfigured ? "configured" : "missing"}`);
@@ -362,17 +472,17 @@ async function runDoctor(cliRootOverride) {
362
472
  }
363
473
  else if (productStatus === "ready_basic_recovery") {
364
474
  lines.push("- Connection: ready");
365
- lines.push("- Governance: " + governance.governanceStatus);
475
+ lines.push(`- Supervision: ${supervisionStatus}`);
366
476
  lines.push("- Recovery: basic");
367
477
  lines.push(`- Rules: ${governance.rulesInstalled ? "installed" : "missing"}`);
368
478
  lines.push(`- MCP: ${governance.mcpConfigured ? "configured" : "missing"}`);
369
479
  lines.push("- Note: Project context exists, but domains are not fully mapped yet.");
370
- lines.push("- Next: run `agent recover --force` for a full domain rebuild, or `agentbridge watch` for live supervision");
480
+ lines.push("- Next: run `agentbridge recover --force` for a full domain rebuild, or `agentbridge watch` for live supervision");
371
481
  }
372
482
  else if (productStatus === "active_work_found") {
373
483
  const recoveryBasic = (0, recovery_reconcile_1.recoveryIsBasicPacket)(projectAccess.packet);
374
484
  lines.push("- Connection: ready");
375
- lines.push("- Governance: " + governance.governanceStatus);
485
+ lines.push(`- Supervision: ${supervisionStatus}`);
376
486
  lines.push("- Recovery: " + (recoveryBasic ? "basic" : "ready"));
377
487
  lines.push(`- Rules: ${governance.rulesInstalled ? "installed" : "missing"}`);
378
488
  lines.push(`- MCP: ${governance.mcpConfigured ? "configured" : "missing"}`);
@@ -384,7 +494,7 @@ async function runDoctor(cliRootOverride) {
384
494
  }
385
495
  else if (productStatus === "needs_recover") {
386
496
  lines.push("- Connection: ready");
387
- lines.push("- Governance: " + governance.governanceStatus);
497
+ lines.push(`- Supervision: ${supervisionStatus}`);
388
498
  lines.push("- Recovery: required");
389
499
  lines.push(`- Rules: ${governance.rulesInstalled ? "installed" : "missing"}`);
390
500
  lines.push(`- MCP: ${governance.mcpConfigured ? "configured" : "missing"}`);
@@ -392,13 +502,23 @@ async function runDoctor(cliRootOverride) {
392
502
  }
393
503
  else {
394
504
  lines.push("- Connection: incomplete");
395
- lines.push("- Governance: " + governance.governanceStatus);
505
+ lines.push(`- Supervision: ${supervisionStatus}`);
396
506
  lines.push("- Recovery: pending");
397
507
  lines.push(`- Rules: ${governance.rulesInstalled ? "installed" : "missing"}`);
398
508
  lines.push(`- MCP: ${governance.mcpConfigured ? "configured" : "missing"}`);
399
509
  if (!projectConfigPresent) {
400
510
  lines.push("- Note: Connection details are missing. Add credentials, then rerun agentbridge doctor.");
401
511
  }
512
+ else if (projectAccess.status === "failed" &&
513
+ (projectAccess.reason === "unauthorized" ||
514
+ projectAccess.reason === "forbidden" ||
515
+ projectAccess.reason === "not_project_member")) {
516
+ lines.push("- Reason: API key rejected by server.");
517
+ lines.push("- Next: run `agentbridge room exit`, get a fresh key from the dashboard, then `agentbridge connect`.");
518
+ if (identityAccess.executionSurfaceId) {
519
+ lines.push("- Note: Config still has a saved execution surface, but the server rejected the stored API key.");
520
+ }
521
+ }
402
522
  else if (!identityAccess.startCapable) {
403
523
  lines.push("- Reason: execution surface missing.");
404
524
  lines.push("- Next: agentbridge connect");
@@ -406,7 +526,11 @@ async function runDoctor(cliRootOverride) {
406
526
  else {
407
527
  lines.push("- Note: Connection check failed. Verify credentials/network, then rerun agentbridge doctor.");
408
528
  }
409
- if (identityAccess.startCapable) {
529
+ if (identityAccess.startCapable &&
530
+ projectAccess.status === "failed" &&
531
+ projectAccess.reason !== "unauthorized" &&
532
+ projectAccess.reason !== "forbidden" &&
533
+ projectAccess.reason !== "not_project_member") {
410
534
  lines.push("- Next: agentbridge doctor");
411
535
  }
412
536
  }
@@ -418,6 +542,9 @@ async function runDoctor(cliRootOverride) {
418
542
  }
419
543
  lines.push(`- Caller identity model: ${identityAccess.identityModel}`);
420
544
  lines.push(`- Execution surface present: ${identityAccess.executionSurfaceId ? "yes" : "no"}`);
545
+ if (identityAccess.executionSurfaceSource) {
546
+ lines.push(`- Execution surface source: ${identityAccess.executionSurfaceSource}`);
547
+ }
421
548
  lines.push(`- Start capable (strict/session modes): ${identityAccess.startCapable ? "yes" : "no"}`);
422
549
  if (identityAccess.status !== "ok") {
423
550
  lines.push("- Note: internal identity mismatch affects strict/resume flows, not room-level inferred watch startup.");