@agentbridge1/cli 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +4 -4
- package/dist/commands/connect.js +192 -13
- package/dist/commands/doctor.js +68 -10
- package/dist/commands/recover.js +64 -2
- package/dist/commands/start.js +27 -3
- package/dist/error-catalog.js +7 -1
- package/dist/index.js +2 -1
- package/dist/init.js +80 -12
- package/dist/server-sync.js +12 -0
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"builtAt": "2026-06-
|
|
3
|
-
"gitHead": "
|
|
4
|
-
"sourceLatestMtime": "2026-06-
|
|
5
|
-
"sourceLatestFile": "src/commands/
|
|
2
|
+
"builtAt": "2026-06-03T09:58:38.231Z",
|
|
3
|
+
"gitHead": "6fcc74c",
|
|
4
|
+
"sourceLatestMtime": "2026-06-03T05:08:44.109Z",
|
|
5
|
+
"sourceLatestFile": "src/commands/doctor.ts"
|
|
6
6
|
}
|
package/dist/commands/connect.js
CHANGED
|
@@ -84,6 +84,113 @@ async function listAgents(projectId, apiKey, apiBaseUrl) {
|
|
|
84
84
|
const body = (await res.json());
|
|
85
85
|
return Array.isArray(body.agents) ? body.agents : [];
|
|
86
86
|
}
|
|
87
|
+
async function resolveExecutionSurfaceFromHello(projectId, apiKey, apiBaseUrl) {
|
|
88
|
+
const url = `${apiBaseUrl}/v1/dev/projects/${projectId}/hello`;
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch(url, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: {
|
|
93
|
+
Authorization: `Bearer ${apiKey}`,
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
},
|
|
96
|
+
body: JSON.stringify({ tool_type: "cli" }),
|
|
97
|
+
});
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
return {
|
|
100
|
+
executionSurfaceId: null,
|
|
101
|
+
identityModel: "unknown",
|
|
102
|
+
status: `http_${res.status}`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const body = (await res.json());
|
|
106
|
+
if (body.identity_model !== "work_identity") {
|
|
107
|
+
if (body.identity_model === "legacy") {
|
|
108
|
+
return {
|
|
109
|
+
executionSurfaceId: null,
|
|
110
|
+
identityModel: "legacy",
|
|
111
|
+
status: "legacy_identity",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
executionSurfaceId: null,
|
|
116
|
+
identityModel: "unknown",
|
|
117
|
+
status: "unknown_identity_model",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const surfaceId = body.execution_surface?.id?.trim();
|
|
121
|
+
return {
|
|
122
|
+
executionSurfaceId: surfaceId || null,
|
|
123
|
+
identityModel: "work_identity",
|
|
124
|
+
status: surfaceId ? "ok" : "missing_execution_surface",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return {
|
|
129
|
+
executionSurfaceId: null,
|
|
130
|
+
identityModel: "unknown",
|
|
131
|
+
status: "network_error",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
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
|
+
}
|
|
193
|
+
}
|
|
87
194
|
async function runConnect(options = {}) {
|
|
88
195
|
process.stdout.write("AgentBridge connect\n");
|
|
89
196
|
process.stdout.write("─────────────────────────────────────────\n");
|
|
@@ -142,13 +249,86 @@ async function runConnect(options = {}) {
|
|
|
142
249
|
throw err;
|
|
143
250
|
}
|
|
144
251
|
process.stdout.write("OK\n");
|
|
252
|
+
let effectiveApiKey = apiKey;
|
|
253
|
+
const diagnostics = {
|
|
254
|
+
helloIdentityModel: "unknown",
|
|
255
|
+
helloStatus: "not_attempted",
|
|
256
|
+
bootstrapStatus: "not_attempted",
|
|
257
|
+
rotateStatus: "not_attempted",
|
|
258
|
+
};
|
|
259
|
+
const helloResolution = await resolveExecutionSurfaceFromHello(projectId, effectiveApiKey, apiBaseUrl);
|
|
260
|
+
diagnostics.helloIdentityModel = helloResolution.identityModel;
|
|
261
|
+
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
|
+
}
|
|
296
|
+
if (!executionSurfaceId) {
|
|
297
|
+
(0, config_1.updateConfig)({
|
|
298
|
+
projectId,
|
|
299
|
+
apiKey: effectiveApiKey,
|
|
300
|
+
apiBaseUrl,
|
|
301
|
+
});
|
|
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"));
|
|
313
|
+
}
|
|
145
314
|
// Persist credentials
|
|
146
|
-
(0, config_1.updateConfig)({
|
|
315
|
+
(0, config_1.updateConfig)({
|
|
316
|
+
projectId,
|
|
317
|
+
apiKey: effectiveApiKey,
|
|
318
|
+
apiBaseUrl,
|
|
319
|
+
...(executionSurfaceId ? { executionSurfaceId } : {}),
|
|
320
|
+
});
|
|
147
321
|
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");
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
process.stdout.write(`Execution surface: ${executionSurfaceId}\n`);
|
|
327
|
+
}
|
|
148
328
|
// Pick or auto-select an agent identity
|
|
149
329
|
let activeAgentId = options.agentId ?? cfg.activeAgentId;
|
|
150
330
|
if (!activeAgentId) {
|
|
151
|
-
const agents = await listAgents(projectId,
|
|
331
|
+
const agents = await listAgents(projectId, effectiveApiKey, apiBaseUrl);
|
|
152
332
|
if (agents.length === 1 && agents[0]) {
|
|
153
333
|
activeAgentId = agents[0].id;
|
|
154
334
|
(0, config_1.updateConfig)({ activeAgentId });
|
|
@@ -159,7 +339,7 @@ async function runConnect(options = {}) {
|
|
|
159
339
|
for (const a of agents) {
|
|
160
340
|
process.stdout.write(` ${a.id} ${a.name ?? ""} ${a.role ?? ""}\n`.trimEnd() + "\n");
|
|
161
341
|
}
|
|
162
|
-
process.stdout.write('\nRun `agentbridge use <agent-id>` to select one
|
|
342
|
+
process.stdout.write('\nRun `agentbridge use <agent-id>` to select one.\n');
|
|
163
343
|
}
|
|
164
344
|
}
|
|
165
345
|
else {
|
|
@@ -169,24 +349,23 @@ async function runConnect(options = {}) {
|
|
|
169
349
|
process.stdout.write("\n");
|
|
170
350
|
process.stdout.write(`Project: ${projectName}\n`);
|
|
171
351
|
process.stdout.write(`Agents: ${agentCount}\n`);
|
|
352
|
+
process.stdout.write(`Execution surface: ${executionSurfaceId}\n`);
|
|
172
353
|
process.stdout.write(`Server: ${apiBaseUrl}\n`);
|
|
173
354
|
process.stdout.write("\n");
|
|
174
355
|
if (activeAgentId) {
|
|
175
|
-
process.stdout.write("✓ Connected
|
|
356
|
+
process.stdout.write("✓ Connected and ready.\n");
|
|
176
357
|
process.stdout.write([
|
|
177
358
|
"",
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"",
|
|
181
|
-
" 2. Run the watcher (keep it running in a terminal):",
|
|
182
|
-
" agentbridge watch",
|
|
183
|
-
"",
|
|
184
|
-
" 3. Code normally — AgentBridge watches in the background.",
|
|
185
|
-
" Approvals surface when you cross domain boundaries.",
|
|
359
|
+
"Next:",
|
|
360
|
+
" agentbridge doctor",
|
|
361
|
+
" agentbridge start",
|
|
186
362
|
"",
|
|
187
363
|
].join("\n"));
|
|
188
364
|
}
|
|
189
365
|
else {
|
|
190
|
-
process.stdout.write("✓ Connected
|
|
366
|
+
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 start\n");
|
|
191
370
|
}
|
|
192
371
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -146,7 +146,39 @@ async function checkProjectAccess(ctx) {
|
|
|
146
146
|
}
|
|
147
147
|
async function checkIdentityAccess(ctx) {
|
|
148
148
|
if (!ctx.configComplete) {
|
|
149
|
-
return {
|
|
149
|
+
return {
|
|
150
|
+
status: "skipped",
|
|
151
|
+
reason: "config_incomplete",
|
|
152
|
+
identityModel: "unknown",
|
|
153
|
+
executionSurfaceId: null,
|
|
154
|
+
startCapable: false,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const helloUrl = `${ctx.apiBaseUrl}/v1/dev/projects/${ctx.projectId}/hello`;
|
|
158
|
+
let identityModel = "unknown";
|
|
159
|
+
let executionSurfaceId = null;
|
|
160
|
+
try {
|
|
161
|
+
const helloRes = await fetch(helloUrl, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: {
|
|
164
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
165
|
+
"Content-Type": "application/json",
|
|
166
|
+
},
|
|
167
|
+
body: JSON.stringify({ tool_type: "cli" }),
|
|
168
|
+
});
|
|
169
|
+
if (helloRes.ok) {
|
|
170
|
+
const hello = (await helloRes.json());
|
|
171
|
+
if (hello.identity_model === "work_identity") {
|
|
172
|
+
identityModel = "work_identity";
|
|
173
|
+
}
|
|
174
|
+
else if (hello.identity_model === "legacy") {
|
|
175
|
+
identityModel = "legacy";
|
|
176
|
+
}
|
|
177
|
+
executionSurfaceId = hello.execution_surface?.id?.trim() || null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// Keep default unknown/null and continue with identity resolution checks.
|
|
150
182
|
}
|
|
151
183
|
try {
|
|
152
184
|
const syncCtx = {
|
|
@@ -160,14 +192,29 @@ async function checkIdentityAccess(ctx) {
|
|
|
160
192
|
(0, server_sync_1.listWorkIdentities)(syncCtx),
|
|
161
193
|
]);
|
|
162
194
|
const callerId = callerPacket?.work_identity?.id ?? null;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
195
|
+
const startCapable = Boolean(executionSurfaceId);
|
|
196
|
+
if (callerId) {
|
|
197
|
+
return { status: "ok", identityModel, executionSurfaceId, startCapable };
|
|
198
|
+
}
|
|
199
|
+
if (identities.length === 1) {
|
|
200
|
+
return { status: "ok", identityModel, executionSurfaceId, startCapable };
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
status: "failed",
|
|
204
|
+
reason: "caller_identity_unresolved",
|
|
205
|
+
identityModel,
|
|
206
|
+
executionSurfaceId,
|
|
207
|
+
startCapable,
|
|
208
|
+
};
|
|
168
209
|
}
|
|
169
210
|
catch {
|
|
170
|
-
return {
|
|
211
|
+
return {
|
|
212
|
+
status: "failed",
|
|
213
|
+
reason: "caller_identity_unresolved",
|
|
214
|
+
identityModel,
|
|
215
|
+
executionSurfaceId,
|
|
216
|
+
startCapable: Boolean(executionSurfaceId),
|
|
217
|
+
};
|
|
171
218
|
}
|
|
172
219
|
}
|
|
173
220
|
async function runDoctor(cliRootOverride) {
|
|
@@ -234,6 +281,9 @@ async function runDoctor(cliRootOverride) {
|
|
|
234
281
|
if (identityAccess.reason) {
|
|
235
282
|
lines.push(`Caller identity reason: ${identityAccess.reason}`);
|
|
236
283
|
}
|
|
284
|
+
lines.push(`Caller identity model: ${identityAccess.identityModel}`);
|
|
285
|
+
lines.push(`Execution surface present: ${identityAccess.executionSurfaceId ? "yes" : "no"}`);
|
|
286
|
+
lines.push(`Start capable: ${identityAccess.startCapable ? "yes" : "no"}`);
|
|
237
287
|
if (projectAccess.status === "failed" && projectAccess.reason) {
|
|
238
288
|
const view = (0, error_catalog_1.catalogViewForDoctorReason)(projectAccess.reason, projectAccess.suggestedNextAction);
|
|
239
289
|
lines.push("");
|
|
@@ -252,7 +302,9 @@ async function runDoctor(cliRootOverride) {
|
|
|
252
302
|
lines.push("Suggested next action: run npm run dogfood:check before external dogfood.");
|
|
253
303
|
}
|
|
254
304
|
let productStatus = "not_ready";
|
|
255
|
-
if (projectAccess.status === "ok" &&
|
|
305
|
+
if (projectAccess.status === "ok" &&
|
|
306
|
+
identityAccess.status === "ok" &&
|
|
307
|
+
identityAccess.startCapable) {
|
|
256
308
|
if (hasActiveWork) {
|
|
257
309
|
productStatus = "active_work_found";
|
|
258
310
|
}
|
|
@@ -281,18 +333,24 @@ async function runDoctor(cliRootOverride) {
|
|
|
281
333
|
lines.push("- Next: agentbridge recover");
|
|
282
334
|
}
|
|
283
335
|
else {
|
|
284
|
-
lines.push("- Connection:
|
|
336
|
+
lines.push("- Connection: incomplete");
|
|
285
337
|
lines.push("- Recovery: pending");
|
|
286
338
|
if (!projectConfigPresent) {
|
|
287
339
|
lines.push("- Note: Connection details are missing. Add credentials, then rerun agentbridge doctor.");
|
|
288
340
|
}
|
|
341
|
+
else if (!identityAccess.startCapable) {
|
|
342
|
+
lines.push("- Reason: execution surface missing.");
|
|
343
|
+
lines.push("- Next: agentbridge connect");
|
|
344
|
+
}
|
|
289
345
|
else if (identityAccess.status !== "ok") {
|
|
290
346
|
lines.push("- Note: Caller identity is unresolved. Run agentbridge identity list and set a valid active agent.");
|
|
291
347
|
}
|
|
292
348
|
else {
|
|
293
349
|
lines.push("- Note: Connection check failed. Verify credentials/network, then rerun agentbridge doctor.");
|
|
294
350
|
}
|
|
295
|
-
|
|
351
|
+
if (identityAccess.startCapable) {
|
|
352
|
+
lines.push("- Next: agentbridge doctor");
|
|
353
|
+
}
|
|
296
354
|
}
|
|
297
355
|
process.stdout.write(`${lines.join("\n")}\n`);
|
|
298
356
|
if (freshness.state === "stale")
|
package/dist/commands/recover.js
CHANGED
|
@@ -7,6 +7,7 @@ const server_sync_1 = require("../server-sync");
|
|
|
7
7
|
const node_process_1 = require("node:process");
|
|
8
8
|
const gates_1 = require("../gates");
|
|
9
9
|
const init_1 = require("../init");
|
|
10
|
+
const http_1 = require("../http");
|
|
10
11
|
function resolveNetworkContext() {
|
|
11
12
|
const cfg = (0, config_1.readConfig)();
|
|
12
13
|
const projectId = process.env.AGENTBRIDGE_PROJECT_ID ?? cfg.projectId ?? "";
|
|
@@ -78,6 +79,60 @@ function renderRecoveryStatusBlock(input) {
|
|
|
78
79
|
lines.push("");
|
|
79
80
|
return lines.join("\n");
|
|
80
81
|
}
|
|
82
|
+
function normalizeBootstrapError(error) {
|
|
83
|
+
if ((0, http_1.isCliHttpError)(error) && error.status === 422) {
|
|
84
|
+
const parsed = (0, http_1.parseCliHttpErrorBody)(error);
|
|
85
|
+
const details = [];
|
|
86
|
+
const nestedError = parsed && typeof parsed.error === "object" && parsed.error !== null
|
|
87
|
+
? parsed.error
|
|
88
|
+
: null;
|
|
89
|
+
const fieldErrors = nestedError && typeof nestedError.fieldErrors === "object" && nestedError.fieldErrors !== null
|
|
90
|
+
? nestedError.fieldErrors
|
|
91
|
+
: null;
|
|
92
|
+
if (fieldErrors) {
|
|
93
|
+
for (const [field, value] of Object.entries(fieldErrors)) {
|
|
94
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
95
|
+
details.push(`${field}: ${String(value[0])}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const detailText = details.length > 0 ? ` (${details.join("; ")})` : "";
|
|
100
|
+
return (0, errors_1.catalogCliError)("CONFIG_INCOMPLETE", {
|
|
101
|
+
what: `Recovery bootstrap payload was rejected by server validation${detailText}.`,
|
|
102
|
+
why: "The server cannot activate recovery until required bootstrap fields pass validation.",
|
|
103
|
+
next: "Retry `agentbridge recover`. If it fails again, run with AGENTBRIDGE_DEBUG=1 and share output.",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (error instanceof Error) {
|
|
107
|
+
const message = error.message;
|
|
108
|
+
if (/does not have any commits yet/i.test(message) ||
|
|
109
|
+
/bad revision ['"]head['"]/i.test(message) ||
|
|
110
|
+
/not a git repository/i.test(message)) {
|
|
111
|
+
return new errors_1.SafeCliError({
|
|
112
|
+
code: "CONFIG_INCOMPLETE",
|
|
113
|
+
category: "CONFIG_ERROR",
|
|
114
|
+
what: "Repository has no usable git history for recovery mapping.",
|
|
115
|
+
why: "Recover infers baseline context from commit history and cannot proceed without it.",
|
|
116
|
+
next: [
|
|
117
|
+
"Create an initial commit, then rerun recover:",
|
|
118
|
+
"1) git add .",
|
|
119
|
+
"2) git commit -m \"Initial commit\"",
|
|
120
|
+
"3) agentbridge recover",
|
|
121
|
+
].join("\n"),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (/payload too large after compaction/i.test(message)) {
|
|
125
|
+
return new errors_1.SafeCliError({
|
|
126
|
+
code: "CONFIG_INCOMPLETE",
|
|
127
|
+
category: "CONFIG_ERROR",
|
|
128
|
+
what: "Repository evidence exceeded bootstrap payload limits.",
|
|
129
|
+
why: "The recovery evidence payload could not be compacted enough for server ingestion.",
|
|
130
|
+
next: "Run `agentbridge init` for an interactive baseline, or retry recover after reducing repository noise.",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return error;
|
|
135
|
+
}
|
|
81
136
|
async function runRecover(options = {}) {
|
|
82
137
|
process.exitCode = 0;
|
|
83
138
|
process.stdout.write([
|
|
@@ -127,8 +182,15 @@ async function runRecover(options = {}) {
|
|
|
127
182
|
? "build recovery baseline from repository evidence"
|
|
128
183
|
: "force-refresh recovery baseline from repository evidence";
|
|
129
184
|
process.stdout.write("Building recovery baseline now...\n");
|
|
130
|
-
|
|
131
|
-
|
|
185
|
+
try {
|
|
186
|
+
await (0, init_1.runBootstrapRecovery)(ctx);
|
|
187
|
+
packet = await (0, server_sync_1.fetchProjectPacket)(ctx);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
process.stderr.write(`${(0, errors_1.renderCliError)(normalizeBootstrapError(error))}\n`);
|
|
191
|
+
process.exitCode = 1;
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
132
194
|
}
|
|
133
195
|
const afterPacket = packet;
|
|
134
196
|
if (baselineRequired(afterPacket)) {
|
package/dist/commands/start.js
CHANGED
|
@@ -395,6 +395,29 @@ function renderOtherSessions(lines, sessionIds) {
|
|
|
395
395
|
...sessionIds.map((id) => `- ${id}`),
|
|
396
396
|
];
|
|
397
397
|
}
|
|
398
|
+
async function resolveExecutionSurfaceIdForStart(ctx, opts, cfgExecutionSurfaceId) {
|
|
399
|
+
const explicitExecutionSurfaceId = opts.executionSurfaceId?.trim();
|
|
400
|
+
if (explicitExecutionSurfaceId) {
|
|
401
|
+
(0, config_1.updateConfig)({ executionSurfaceId: explicitExecutionSurfaceId });
|
|
402
|
+
return explicitExecutionSurfaceId;
|
|
403
|
+
}
|
|
404
|
+
const configuredExecutionSurfaceId = cfgExecutionSurfaceId?.trim();
|
|
405
|
+
if (configuredExecutionSurfaceId) {
|
|
406
|
+
return configuredExecutionSurfaceId;
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
const callerPacket = await (0, server_sync_1.fetchCallerIdentityPacket)(ctx);
|
|
410
|
+
const discoveredExecutionSurfaceId = callerPacket?.execution_surface?.id?.trim() || null;
|
|
411
|
+
if (discoveredExecutionSurfaceId) {
|
|
412
|
+
(0, config_1.updateConfig)({ executionSurfaceId: discoveredExecutionSurfaceId });
|
|
413
|
+
return discoveredExecutionSurfaceId;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
// Keep the existing catalog error path if caller identity discovery fails.
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
398
421
|
async function executeStartWorkSession(opts) {
|
|
399
422
|
const summary = trimRequired(opts.summary ?? "", "--summary");
|
|
400
423
|
const scope = trimRequired(opts.scope ?? "", "--scope");
|
|
@@ -406,7 +429,7 @@ async function executeStartWorkSession(opts) {
|
|
|
406
429
|
if (!explicitAgentId && !configuredActiveAgentId) {
|
|
407
430
|
throw (0, errors_1.catalogCliError)("START_MISSING_ACTIVE_AGENT");
|
|
408
431
|
}
|
|
409
|
-
const executionSurfaceId = cfg.executionSurfaceId
|
|
432
|
+
const executionSurfaceId = await resolveExecutionSurfaceIdForStart(ctx, opts, cfg.executionSurfaceId);
|
|
410
433
|
if (!executionSurfaceId) {
|
|
411
434
|
throw (0, errors_1.catalogCliError)("START_EXECUTION_SURFACE_REQUIRED");
|
|
412
435
|
}
|
|
@@ -547,15 +570,16 @@ async function executeStartWorkSession(opts) {
|
|
|
547
570
|
async function runSmartStart(opts = {}) {
|
|
548
571
|
const cfg = (0, config_1.readConfig)();
|
|
549
572
|
await ensureProjectAccess();
|
|
573
|
+
const ctx = (0, config_1.contextFromConfig)();
|
|
550
574
|
const explicitAgentId = opts.agentId?.trim();
|
|
551
575
|
const configuredActiveAgentId = cfg.activeAgentId?.trim();
|
|
552
576
|
if (!explicitAgentId && !configuredActiveAgentId) {
|
|
553
577
|
throw (0, errors_1.catalogCliError)("START_MISSING_ACTIVE_AGENT");
|
|
554
578
|
}
|
|
555
|
-
|
|
579
|
+
const executionSurfaceId = await resolveExecutionSurfaceIdForStart(ctx, opts, cfg.executionSurfaceId);
|
|
580
|
+
if (!executionSurfaceId) {
|
|
556
581
|
throw (0, errors_1.catalogCliError)("START_EXECUTION_SURFACE_REQUIRED");
|
|
557
582
|
}
|
|
558
|
-
const ctx = (0, config_1.contextFromConfig)();
|
|
559
583
|
const resolution = await (0, work_context_resolver_1.resolveWorkContext)(ctx, {
|
|
560
584
|
allowServerCurrentForCr: true,
|
|
561
585
|
includeOtherActiveSessions: true,
|
package/dist/error-catalog.js
CHANGED
|
@@ -186,7 +186,13 @@ const CATALOG = {
|
|
|
186
186
|
title: "Execution surface missing.",
|
|
187
187
|
what: "No executionSurfaceId is saved in .agentbridge/config.json.",
|
|
188
188
|
why: "The server needs a stable execution surface to open a tracked session.",
|
|
189
|
-
next:
|
|
189
|
+
next: [
|
|
190
|
+
"Run:",
|
|
191
|
+
" agentbridge connect --project <project-id> --api-key <key> --api-base-url <url>",
|
|
192
|
+
"Then:",
|
|
193
|
+
" agentbridge doctor",
|
|
194
|
+
"If this continues, AgentBridge could not create an AgentConnection/execution surface for this project.",
|
|
195
|
+
].join("\n"),
|
|
190
196
|
},
|
|
191
197
|
START_AGENT_INVALID: {
|
|
192
198
|
code: "START_AGENT_INVALID",
|
package/dist/index.js
CHANGED
|
@@ -49,7 +49,7 @@ const COMMAND_HELP = {
|
|
|
49
49
|
use: " agentbridge use <agent-id>\n" +
|
|
50
50
|
" Set the active agent identity used by `watch` and `start`.\n",
|
|
51
51
|
start: " agentbridge start [\"intent\"] [\"scope\"]\n" +
|
|
52
|
-
" Advanced: agentbridge start --summary \"<summary>\" --scope \"<path-or-glob>\" [--domain <name>] [--agent <id>] [--change-request <cr_id>] [--resume]\n" +
|
|
52
|
+
" Advanced: agentbridge start --summary \"<summary>\" --scope \"<path-or-glob>\" [--domain <name>] [--agent <id>] [--execution-surface <es_id>] [--change-request <cr_id>] [--resume]\n" +
|
|
53
53
|
" Start supervised work. Without flags, proposes a smart work contract and asks for confirmation.\n" +
|
|
54
54
|
" --resume Continue the active change request/session instead of creating a new one.\n",
|
|
55
55
|
watch: " agentbridge watch [--change-request <cr_id>] [--execution-surface <es_id>] [--allow-dirty] [--daemon] [--once]\n" +
|
|
@@ -350,6 +350,7 @@ async function main() {
|
|
|
350
350
|
scopeFlagProvided,
|
|
351
351
|
domain: flags["--domain"],
|
|
352
352
|
agentId: flags["--agent"],
|
|
353
|
+
executionSurfaceId: flags["--execution-surface"],
|
|
353
354
|
changeRequestId: flags["--change-request"],
|
|
354
355
|
resume: flags["--resume"] === "true",
|
|
355
356
|
});
|
package/dist/init.js
CHANGED
|
@@ -104,16 +104,24 @@ function assertInsideGitRepo() {
|
|
|
104
104
|
function normalizeToken(value) {
|
|
105
105
|
return value.trim().toLowerCase().replace(/[_\s]+/g, "-");
|
|
106
106
|
}
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
function normalizePath(path) {
|
|
108
|
+
return path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
109
|
+
}
|
|
110
|
+
function deriveDomainKeyFromPath(path) {
|
|
111
|
+
const normalized = normalizePath(path);
|
|
112
|
+
const packageSourcesMatch = normalized.match(/^([^/]+)\/Sources\/([^/]+)\//);
|
|
113
|
+
if (packageSourcesMatch) {
|
|
114
|
+
const folder = normalizeToken(packageSourcesMatch[2] ?? "");
|
|
112
115
|
if (folder === "supabase")
|
|
113
116
|
return "database";
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
return folder || null;
|
|
118
|
+
}
|
|
119
|
+
const srcLikeMatch = normalized.match(/^(src|Sources|modules|Modules|feature|features)\/([^/]+)\//);
|
|
120
|
+
if (srcLikeMatch) {
|
|
121
|
+
const folder = normalizeToken(srcLikeMatch[2] ?? "");
|
|
122
|
+
if (folder === "supabase")
|
|
123
|
+
return "database";
|
|
124
|
+
return folder || null;
|
|
117
125
|
}
|
|
118
126
|
if (normalized.startsWith("db/") || normalized.startsWith("prisma/") || normalized.startsWith("supabase/")) {
|
|
119
127
|
return "database";
|
|
@@ -123,6 +131,29 @@ function classifyDomainKey(path) {
|
|
|
123
131
|
}
|
|
124
132
|
return null;
|
|
125
133
|
}
|
|
134
|
+
function extractDomainPrefix(path) {
|
|
135
|
+
const normalized = normalizePath(path);
|
|
136
|
+
const packageSourcesMatch = normalized.match(/^([^/]+)\/Sources\/([^/]+)\//);
|
|
137
|
+
if (packageSourcesMatch) {
|
|
138
|
+
return `${packageSourcesMatch[1]}/Sources/${packageSourcesMatch[2]}/`;
|
|
139
|
+
}
|
|
140
|
+
const srcLikeMatch = normalized.match(/^(src|Sources|modules|Modules|feature|features)\/([^/]+)\//);
|
|
141
|
+
if (srcLikeMatch) {
|
|
142
|
+
return `${srcLikeMatch[1]}/${srcLikeMatch[2]}/`;
|
|
143
|
+
}
|
|
144
|
+
if (normalized.startsWith("db/"))
|
|
145
|
+
return "db/";
|
|
146
|
+
if (normalized.startsWith("prisma/"))
|
|
147
|
+
return "prisma/";
|
|
148
|
+
if (normalized.startsWith("supabase/"))
|
|
149
|
+
return "supabase/";
|
|
150
|
+
if (/^supabase_.*\.sql$/i.test(normalized))
|
|
151
|
+
return "supabase_*.sql";
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
function classifyDomainKey(path) {
|
|
155
|
+
return deriveDomainKeyFromPath(path);
|
|
156
|
+
}
|
|
126
157
|
function tierForDomainKey(domainKey, files) {
|
|
127
158
|
if (domainKey === "sessions") {
|
|
128
159
|
const highRisk = files.some((file) => /(token|auth|state|orchestrator|repository|session)/i.test(file));
|
|
@@ -148,9 +179,11 @@ function confidenceForDomain(fileCount) {
|
|
|
148
179
|
}
|
|
149
180
|
function collectWorkspaceDomainSeeds() {
|
|
150
181
|
const seeds = new Set();
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
182
|
+
for (const root of ["src", "Sources", "modules", "Modules", "feature", "features"]) {
|
|
183
|
+
const rootPath = (0, node_path_1.resolve)(process.cwd(), root);
|
|
184
|
+
if (!(0, node_fs_1.existsSync)(rootPath))
|
|
185
|
+
continue;
|
|
186
|
+
for (const entry of (0, node_fs_1.readdirSync)(rootPath, { withFileTypes: true })) {
|
|
154
187
|
if (!entry.isDirectory())
|
|
155
188
|
continue;
|
|
156
189
|
const folder = normalizeToken(entry.name);
|
|
@@ -162,6 +195,24 @@ function collectWorkspaceDomainSeeds() {
|
|
|
162
195
|
}
|
|
163
196
|
}
|
|
164
197
|
}
|
|
198
|
+
for (const topLevelEntry of (0, node_fs_1.readdirSync)(process.cwd(), { withFileTypes: true })) {
|
|
199
|
+
if (!topLevelEntry.isDirectory())
|
|
200
|
+
continue;
|
|
201
|
+
const packageSourcesPath = (0, node_path_1.resolve)(process.cwd(), topLevelEntry.name, "Sources");
|
|
202
|
+
if (!(0, node_fs_1.existsSync)(packageSourcesPath))
|
|
203
|
+
continue;
|
|
204
|
+
for (const sourceEntry of (0, node_fs_1.readdirSync)(packageSourcesPath, { withFileTypes: true })) {
|
|
205
|
+
if (!sourceEntry.isDirectory())
|
|
206
|
+
continue;
|
|
207
|
+
const folder = normalizeToken(sourceEntry.name);
|
|
208
|
+
if (folder === "supabase") {
|
|
209
|
+
seeds.add("database");
|
|
210
|
+
}
|
|
211
|
+
else if (folder) {
|
|
212
|
+
seeds.add(folder);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
165
216
|
for (const dbFolder of ["db", "prisma", "supabase"]) {
|
|
166
217
|
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(process.cwd(), dbFolder))) {
|
|
167
218
|
seeds.add("database");
|
|
@@ -171,10 +222,13 @@ function collectWorkspaceDomainSeeds() {
|
|
|
171
222
|
}
|
|
172
223
|
function refineRecoveredDomains(inferredClusters, evidence, architectureDomains) {
|
|
173
224
|
const domainFiles = new Map();
|
|
225
|
+
const domainPrefixes = new Map();
|
|
174
226
|
const workspaceSeeds = collectWorkspaceDomainSeeds();
|
|
175
227
|
for (const seed of workspaceSeeds) {
|
|
176
228
|
if (!domainFiles.has(seed))
|
|
177
229
|
domainFiles.set(seed, new Set());
|
|
230
|
+
if (!domainPrefixes.has(seed))
|
|
231
|
+
domainPrefixes.set(seed, new Set());
|
|
178
232
|
}
|
|
179
233
|
for (const file of evidence.files) {
|
|
180
234
|
const key = classifyDomainKey(file);
|
|
@@ -183,6 +237,12 @@ function refineRecoveredDomains(inferredClusters, evidence, architectureDomains)
|
|
|
183
237
|
if (!domainFiles.has(key))
|
|
184
238
|
domainFiles.set(key, new Set());
|
|
185
239
|
domainFiles.get(key)?.add(file);
|
|
240
|
+
const prefix = extractDomainPrefix(file);
|
|
241
|
+
if (prefix) {
|
|
242
|
+
if (!domainPrefixes.has(key))
|
|
243
|
+
domainPrefixes.set(key, new Set());
|
|
244
|
+
domainPrefixes.get(key)?.add(prefix);
|
|
245
|
+
}
|
|
186
246
|
}
|
|
187
247
|
for (const cluster of inferredClusters) {
|
|
188
248
|
const clusterPaths = [...cluster.files, ...cluster.path_prefixes];
|
|
@@ -207,6 +267,12 @@ function refineRecoveredDomains(inferredClusters, evidence, architectureDomains)
|
|
|
207
267
|
for (const file of cluster.files) {
|
|
208
268
|
if (classifyDomainKey(file) === chosenKey) {
|
|
209
269
|
domainFiles.get(chosenKey)?.add(file);
|
|
270
|
+
const prefix = extractDomainPrefix(file);
|
|
271
|
+
if (prefix) {
|
|
272
|
+
if (!domainPrefixes.has(chosenKey))
|
|
273
|
+
domainPrefixes.set(chosenKey, new Set());
|
|
274
|
+
domainPrefixes.get(chosenKey)?.add(prefix);
|
|
275
|
+
}
|
|
210
276
|
}
|
|
211
277
|
}
|
|
212
278
|
}
|
|
@@ -237,12 +303,14 @@ function refineRecoveredDomains(inferredClusters, evidence, architectureDomains)
|
|
|
237
303
|
return Array.from(domainFiles.entries())
|
|
238
304
|
.map(([key, filesSet]) => {
|
|
239
305
|
const files = Array.from(filesSet).sort((a, b) => a.localeCompare(b));
|
|
306
|
+
const inferredPrefixes = Array.from(domainPrefixes.get(key) ?? []);
|
|
240
307
|
const pathPatterns = DOMAIN_PREFIX_BY_KEY[key] ??
|
|
308
|
+
(inferredPrefixes.length > 0 ? inferredPrefixes : undefined) ??
|
|
241
309
|
(key === "database"
|
|
242
310
|
? ["db/", "prisma/", "supabase/", "supabase_*.sql"]
|
|
243
311
|
: key === "shared"
|
|
244
312
|
? ["src/shared/"]
|
|
245
|
-
: [
|
|
313
|
+
: [`${key}/`]);
|
|
246
314
|
const primaryPrefix = pathPatterns[0] ? pathPatterns[0].replace(/\/$/, "") : key;
|
|
247
315
|
const protectionTier = tierForDomainKey(key, files);
|
|
248
316
|
return {
|
package/dist/server-sync.js
CHANGED
|
@@ -132,6 +132,17 @@ async function fetchCallerIdentityPacket(ctx) {
|
|
|
132
132
|
typeof domainCandidate.name === "string"
|
|
133
133
|
? { id: domainCandidate.id, name: domainCandidate.name }
|
|
134
134
|
: null;
|
|
135
|
+
const executionSurfaceCandidate = typeof response.execution_surface === "object" && response.execution_surface !== null
|
|
136
|
+
? response.execution_surface
|
|
137
|
+
: null;
|
|
138
|
+
const executionSurface = executionSurfaceCandidate && typeof executionSurfaceCandidate.id === "string"
|
|
139
|
+
? {
|
|
140
|
+
id: executionSurfaceCandidate.id,
|
|
141
|
+
...(typeof executionSurfaceCandidate.display_name === "string"
|
|
142
|
+
? { display_name: executionSurfaceCandidate.display_name }
|
|
143
|
+
: {}),
|
|
144
|
+
}
|
|
145
|
+
: null;
|
|
135
146
|
return {
|
|
136
147
|
identity_model: "work_identity",
|
|
137
148
|
work_identity: {
|
|
@@ -139,6 +150,7 @@ async function fetchCallerIdentityPacket(ctx) {
|
|
|
139
150
|
name: workIdentity.name,
|
|
140
151
|
domain,
|
|
141
152
|
},
|
|
153
|
+
execution_surface: executionSurface,
|
|
142
154
|
};
|
|
143
155
|
}
|
|
144
156
|
async function postObservedDiff(ctx, binding, payload) {
|