@buildautomaton/cli 0.1.26 → 0.1.28
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/cli.js +340 -23
- package/dist/cli.js.map +4 -4
- package/dist/index.js +366 -49
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -25064,7 +25064,7 @@ var {
|
|
|
25064
25064
|
} = import_index.default;
|
|
25065
25065
|
|
|
25066
25066
|
// src/cli-version.ts
|
|
25067
|
-
var CLI_VERSION = "0.1.
|
|
25067
|
+
var CLI_VERSION = "0.1.28".length > 0 ? "0.1.28" : "0.0.0-dev";
|
|
25068
25068
|
|
|
25069
25069
|
// src/cli/defaults.ts
|
|
25070
25070
|
var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
|
|
@@ -25352,6 +25352,17 @@ function createCliE2eeRuntime(key) {
|
|
|
25352
25352
|
const merged = { ...message, ...parsed };
|
|
25353
25353
|
delete merged.ee;
|
|
25354
25354
|
return merged;
|
|
25355
|
+
},
|
|
25356
|
+
decryptEnvelopeToBuffer(envelope) {
|
|
25357
|
+
if (envelope.k !== key.id) throw new Error(`E2EE key mismatch: ${envelope.k}`);
|
|
25358
|
+
const sealed = Buffer.from(base64UrlDecode(envelope.c));
|
|
25359
|
+
if (sealed.length < 16) throw new Error("Invalid E2EE payload.");
|
|
25360
|
+
const ciphertext = sealed.subarray(0, sealed.length - 16);
|
|
25361
|
+
const tag = sealed.subarray(sealed.length - 16);
|
|
25362
|
+
const nonce = Buffer.from(base64UrlDecode(envelope.n));
|
|
25363
|
+
const decipher = createDecipheriv("aes-256-gcm", rawKey, nonce);
|
|
25364
|
+
decipher.setAuthTag(tag);
|
|
25365
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
25355
25366
|
}
|
|
25356
25367
|
};
|
|
25357
25368
|
}
|
|
@@ -25627,6 +25638,12 @@ function sendWsMessage(ws, payload) {
|
|
|
25627
25638
|
}
|
|
25628
25639
|
}
|
|
25629
25640
|
|
|
25641
|
+
// src/connection/heartbeat/constants.ts
|
|
25642
|
+
var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
|
|
25643
|
+
var BRIDGE_HEARTBEAT_SEQ_MAX = 2147483646;
|
|
25644
|
+
var BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT = 4;
|
|
25645
|
+
var BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX = 5;
|
|
25646
|
+
|
|
25630
25647
|
// ../../node_modules/.pnpm/open@10.2.0/node_modules/open/index.js
|
|
25631
25648
|
import process7 from "node:process";
|
|
25632
25649
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
@@ -26605,14 +26622,18 @@ function runPendingAuth(options) {
|
|
|
26605
26622
|
}
|
|
26606
26623
|
function connect() {
|
|
26607
26624
|
const url2 = buildPendingBridgeUrl(apiUrl, connectionId);
|
|
26625
|
+
let pendingHbSeq = -1;
|
|
26608
26626
|
ws = createWsBridge({
|
|
26609
26627
|
url: url2,
|
|
26610
26628
|
onOpen: () => {
|
|
26611
26629
|
clearQuietOnOpen();
|
|
26630
|
+
pendingHbSeq = -1;
|
|
26612
26631
|
sendWsMessage(ws, { type: "identify", role: "cli", cliVersion: CLI_VERSION });
|
|
26613
26632
|
keepaliveInterval = setInterval(() => {
|
|
26614
26633
|
if (resolved || !ws || ws.readyState !== 1) return;
|
|
26615
|
-
|
|
26634
|
+
pendingHbSeq = pendingHbSeq >= BRIDGE_HEARTBEAT_SEQ_MAX ? 0 : pendingHbSeq + 1;
|
|
26635
|
+
const hb = { t: "h", s: pendingHbSeq };
|
|
26636
|
+
sendWsMessage(ws, hb);
|
|
26616
26637
|
}, PENDING_KEEPALIVE_MS);
|
|
26617
26638
|
if (browserFallback) {
|
|
26618
26639
|
clearTimeout(browserFallback);
|
|
@@ -32252,6 +32273,96 @@ function augmentPromptResultAuthFields(agentType, errorText) {
|
|
|
32252
32273
|
return { agentAuthRequired: true, agentType };
|
|
32253
32274
|
}
|
|
32254
32275
|
|
|
32276
|
+
// src/agents/acp/fetch-session-attachments.ts
|
|
32277
|
+
function metaSaysEncrypted(meta) {
|
|
32278
|
+
if (!meta) return false;
|
|
32279
|
+
const e = meta.encrypted;
|
|
32280
|
+
return e === true || e === "true" || e === 1;
|
|
32281
|
+
}
|
|
32282
|
+
function warnIfDecodedImageMagicUnexpected(buf, mimeType, idShort) {
|
|
32283
|
+
const m = mimeType.toLowerCase();
|
|
32284
|
+
if (!m.startsWith("image/") || buf.length < 4) return;
|
|
32285
|
+
const b0 = buf[0];
|
|
32286
|
+
const b1 = buf[1];
|
|
32287
|
+
const b2 = buf[2];
|
|
32288
|
+
const b3 = buf[3];
|
|
32289
|
+
let looksOk = false;
|
|
32290
|
+
if (m.includes("png") && b0 === 137 && b1 === 80 && b2 === 78 && b3 === 71) looksOk = true;
|
|
32291
|
+
else if ((m.includes("jpeg") || m.includes("jpg")) && b0 === 255 && b1 === 216 && b2 === 255) looksOk = true;
|
|
32292
|
+
else if (m.includes("gif") && b0 === 71 && b1 === 73 && b2 === 70) looksOk = true;
|
|
32293
|
+
else if (m.includes("webp") && buf.length >= 12 && buf.subarray(0, 4).toString("ascii") === "RIFF" && buf.subarray(8, 12).toString("ascii") === "WEBP")
|
|
32294
|
+
looksOk = true;
|
|
32295
|
+
else if (!/(png|jpe?g|gif|webp)/i.test(m)) looksOk = true;
|
|
32296
|
+
if (!looksOk) {
|
|
32297
|
+
logDebug(
|
|
32298
|
+
`[Agent] Attachment ${idShort} (${mimeType}): decoded bytes do not match common image signatures \u2014 wrong E2EE key, corrupt blob, or metadata mismatch. First 12 bytes (hex): ${buf.subarray(0, Math.min(12, buf.length)).toString("hex")}`
|
|
32299
|
+
);
|
|
32300
|
+
}
|
|
32301
|
+
}
|
|
32302
|
+
async function fetchSessionAttachmentPayloadsForAgent(params) {
|
|
32303
|
+
const { attachments, sessionId, cloudApiBaseUrl, getCloudAccessToken, e2ee, log: log2 } = params;
|
|
32304
|
+
const token = getCloudAccessToken();
|
|
32305
|
+
if (!token) {
|
|
32306
|
+
return { ok: false, error: "Missing cloud access token; cannot download attachments." };
|
|
32307
|
+
}
|
|
32308
|
+
const wantedCount = attachments.filter((a) => typeof a.attachmentId === "string" && a.attachmentId.trim() !== "").length;
|
|
32309
|
+
if (wantedCount === 0) {
|
|
32310
|
+
return { ok: false, error: "No valid attachment ids in prompt." };
|
|
32311
|
+
}
|
|
32312
|
+
const base = cloudApiBaseUrl.replace(/\/+$/, "");
|
|
32313
|
+
const out = [];
|
|
32314
|
+
for (const a of attachments) {
|
|
32315
|
+
const id = typeof a.attachmentId === "string" ? a.attachmentId.trim() : "";
|
|
32316
|
+
if (!id) continue;
|
|
32317
|
+
const metaUrl = `${base}/api/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/meta`;
|
|
32318
|
+
const blobUrl = `${base}/api/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/blob`;
|
|
32319
|
+
const metaRes = await fetch(metaUrl, { headers: { Authorization: `Bearer ${token}` } });
|
|
32320
|
+
if (!metaRes.ok) {
|
|
32321
|
+
const t = await metaRes.text().catch(() => "");
|
|
32322
|
+
log2(`[Agent] Attachment meta fetch failed ${metaRes.status}: ${t.slice(0, 200)}`);
|
|
32323
|
+
return { ok: false, error: `Could not load attachment (${id.slice(0, 8)}\u2026): ${metaRes.status}` };
|
|
32324
|
+
}
|
|
32325
|
+
const meta = await metaRes.json().catch(() => null);
|
|
32326
|
+
const mimeType = typeof meta?.mimeType === "string" && meta.mimeType.trim() ? meta.mimeType.trim() : a.mimeType;
|
|
32327
|
+
const blobRes = await fetch(blobUrl, { headers: { Authorization: `Bearer ${token}` } });
|
|
32328
|
+
if (!blobRes.ok) {
|
|
32329
|
+
const t = await blobRes.text().catch(() => "");
|
|
32330
|
+
log2(`[Agent] Attachment blob fetch failed ${blobRes.status}: ${t.slice(0, 200)}`);
|
|
32331
|
+
return { ok: false, error: `Could not load attachment data (${id.slice(0, 8)}\u2026): ${blobRes.status}` };
|
|
32332
|
+
}
|
|
32333
|
+
const buf = Buffer.from(await blobRes.arrayBuffer());
|
|
32334
|
+
let imageBytes;
|
|
32335
|
+
const encrypted = metaSaysEncrypted(meta);
|
|
32336
|
+
if (encrypted) {
|
|
32337
|
+
if (!e2ee) {
|
|
32338
|
+
return { ok: false, error: "Encrypted attachments require E2EE keys on this bridge." };
|
|
32339
|
+
}
|
|
32340
|
+
const k = typeof meta?.k === "string" ? meta.k : "";
|
|
32341
|
+
const n = typeof meta?.n === "string" ? meta.n : "";
|
|
32342
|
+
if (!k || !n) {
|
|
32343
|
+
return { ok: false, error: "Invalid encrypted attachment metadata (missing key id or nonce)." };
|
|
32344
|
+
}
|
|
32345
|
+
const c = base64UrlEncode(buf);
|
|
32346
|
+
imageBytes = e2ee.decryptEnvelopeToBuffer({ k, n, c });
|
|
32347
|
+
} else {
|
|
32348
|
+
imageBytes = buf;
|
|
32349
|
+
}
|
|
32350
|
+
warnIfDecodedImageMagicUnexpected(imageBytes, mimeType, id.slice(0, 8));
|
|
32351
|
+
logDebug(
|
|
32352
|
+
`[Agent] Loaded prompt image ${id.slice(0, 8)}\u2026: ${imageBytes.length} bytes, mime=${mimeType}, ${encrypted ? "E2EE decrypted" : "plaintext from storage"}`
|
|
32353
|
+
);
|
|
32354
|
+
const dataBase64 = imageBytes.toString("base64");
|
|
32355
|
+
out.push({ mimeType, dataBase64 });
|
|
32356
|
+
}
|
|
32357
|
+
if (out.length !== wantedCount) {
|
|
32358
|
+
return {
|
|
32359
|
+
ok: false,
|
|
32360
|
+
error: `Expected ${wantedCount} image attachment(s) but only loaded ${out.length} (check attachment ids and empty rows).`
|
|
32361
|
+
};
|
|
32362
|
+
}
|
|
32363
|
+
return { ok: true, images: out };
|
|
32364
|
+
}
|
|
32365
|
+
|
|
32255
32366
|
// src/agents/acp/send-prompt-to-agent.ts
|
|
32256
32367
|
async function sendPromptToAgent(options) {
|
|
32257
32368
|
const {
|
|
@@ -32269,10 +32380,49 @@ async function sendPromptToAgent(options) {
|
|
|
32269
32380
|
sessionChangeSummaryFilePaths,
|
|
32270
32381
|
cloudApiBaseUrl,
|
|
32271
32382
|
getCloudAccessToken,
|
|
32272
|
-
e2ee
|
|
32383
|
+
e2ee,
|
|
32384
|
+
attachments
|
|
32273
32385
|
} = options;
|
|
32274
32386
|
try {
|
|
32275
|
-
|
|
32387
|
+
let sendOpts = {};
|
|
32388
|
+
if (attachments && attachments.length > 0) {
|
|
32389
|
+
if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
|
|
32390
|
+
sendResult2({
|
|
32391
|
+
type: "prompt_result",
|
|
32392
|
+
id: promptId,
|
|
32393
|
+
...sessionId ? { sessionId } : {},
|
|
32394
|
+
...runId ? { runId } : {},
|
|
32395
|
+
success: false,
|
|
32396
|
+
error: "Prompt includes images but the bridge is not configured with a cloud API URL and token to fetch them.",
|
|
32397
|
+
...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
|
|
32398
|
+
...augmentPromptResultAuthFields(agentType, "missing cloud for images download")
|
|
32399
|
+
});
|
|
32400
|
+
return;
|
|
32401
|
+
}
|
|
32402
|
+
const resolved = await fetchSessionAttachmentPayloadsForAgent({
|
|
32403
|
+
attachments,
|
|
32404
|
+
sessionId,
|
|
32405
|
+
cloudApiBaseUrl,
|
|
32406
|
+
getCloudAccessToken,
|
|
32407
|
+
e2ee,
|
|
32408
|
+
log: log2
|
|
32409
|
+
});
|
|
32410
|
+
if (!resolved.ok) {
|
|
32411
|
+
sendResult2({
|
|
32412
|
+
type: "prompt_result",
|
|
32413
|
+
id: promptId,
|
|
32414
|
+
...sessionId ? { sessionId } : {},
|
|
32415
|
+
...runId ? { runId } : {},
|
|
32416
|
+
success: false,
|
|
32417
|
+
error: resolved.error,
|
|
32418
|
+
...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
|
|
32419
|
+
...augmentPromptResultAuthFields(agentType, resolved.error)
|
|
32420
|
+
});
|
|
32421
|
+
return;
|
|
32422
|
+
}
|
|
32423
|
+
sendOpts = { images: resolved.images };
|
|
32424
|
+
}
|
|
32425
|
+
const result = await handle.sendPrompt(promptText, sendOpts);
|
|
32276
32426
|
if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
|
|
32277
32427
|
await collectTurnGitDiffFromPreTurnSnapshot({
|
|
32278
32428
|
sessionId,
|
|
@@ -32714,13 +32864,16 @@ function parseAcpInitAgentCapabilities(initResult) {
|
|
|
32714
32864
|
const canLoad = agentCapabilities?.loadSession === true;
|
|
32715
32865
|
const sessionCaps = agentCapabilities?.sessionCapabilities;
|
|
32716
32866
|
const canResume = Boolean(sessionCaps?.resume);
|
|
32717
|
-
|
|
32867
|
+
const promptCaps = agentCapabilities?.promptCapabilities;
|
|
32868
|
+
const promptSupportsImage = promptCaps?.image === true;
|
|
32869
|
+
return { canResume, canLoad, promptSupportsImage };
|
|
32718
32870
|
}
|
|
32719
32871
|
|
|
32720
32872
|
// src/agents/acp/clients/shared/bootstrap-acp-wire-session.ts
|
|
32721
32873
|
async function bootstrapAcpWireSession(transport, ctx, initializeRequest) {
|
|
32722
32874
|
const initResult = await transport.initialize(initializeRequest);
|
|
32723
|
-
const { canResume, canLoad } = parseAcpInitAgentCapabilities(initResult);
|
|
32875
|
+
const { canResume, canLoad, promptSupportsImage } = parseAcpInitAgentCapabilities(initResult);
|
|
32876
|
+
ctx.agentPromptImageSupported = promptSupportsImage;
|
|
32724
32877
|
await transport.afterInitialize?.();
|
|
32725
32878
|
const established = await establishAcpSessionWithTransport(transport, ctx, canResume, canLoad);
|
|
32726
32879
|
const sessionId = established.sessionId;
|
|
@@ -32811,11 +32964,27 @@ function normalizeAcpPromptTurnFailure(err, stderrCaptureText) {
|
|
|
32811
32964
|
}
|
|
32812
32965
|
|
|
32813
32966
|
// src/agents/acp/clients/shared/send-acp-prompt-via-transport.ts
|
|
32814
|
-
async function sendAcpPromptViaTransport(transport, ctx, sessionId, promptText) {
|
|
32967
|
+
async function sendAcpPromptViaTransport(transport, ctx, sessionId, promptText, images) {
|
|
32968
|
+
if (images && images.length > 0 && !ctx.agentPromptImageSupported) {
|
|
32969
|
+
await new Promise((r) => setImmediate(r));
|
|
32970
|
+
return normalizeAcpPromptTurnFailure(
|
|
32971
|
+
new Error(
|
|
32972
|
+
"This agent does not advertise image support in ACP (missing agentCapabilities.promptCapabilities.image). Remove images or use an agent that supports prompt images."
|
|
32973
|
+
),
|
|
32974
|
+
ctx.getStderrText()
|
|
32975
|
+
);
|
|
32976
|
+
}
|
|
32977
|
+
const textBlock = promptText.trim() !== "" ? promptText : images?.length ? " " : "";
|
|
32978
|
+
const prompt = [{ type: "text", text: textBlock }];
|
|
32979
|
+
if (images && images.length > 0) {
|
|
32980
|
+
for (const im of images) {
|
|
32981
|
+
prompt.push({ type: "image", mimeType: im.mimeType, data: im.data });
|
|
32982
|
+
}
|
|
32983
|
+
}
|
|
32815
32984
|
try {
|
|
32816
32985
|
const response = await transport.prompt({
|
|
32817
32986
|
sessionId,
|
|
32818
|
-
prompt
|
|
32987
|
+
prompt
|
|
32819
32988
|
});
|
|
32820
32989
|
await new Promise((r2) => setImmediate(r2));
|
|
32821
32990
|
const r = response;
|
|
@@ -32991,15 +33160,17 @@ async function createSdkStdioAcpClient(options) {
|
|
|
32991
33160
|
const established = await bootstrapAcpWireSession(transport, sessionCtx, {
|
|
32992
33161
|
protocolVersion: PROTOCOL_VERSION2,
|
|
32993
33162
|
clientCapabilities: {
|
|
32994
|
-
fs: { readTextFile: true, writeTextFile: true }
|
|
33163
|
+
fs: { readTextFile: true, writeTextFile: true },
|
|
33164
|
+
prompt: { image: true }
|
|
32995
33165
|
},
|
|
32996
33166
|
clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
|
|
32997
33167
|
});
|
|
32998
33168
|
const sessionId = established.sessionId;
|
|
32999
33169
|
settleResolve({
|
|
33000
33170
|
sessionId,
|
|
33001
|
-
async sendPrompt(prompt,
|
|
33002
|
-
|
|
33171
|
+
async sendPrompt(prompt, options2) {
|
|
33172
|
+
const imgs = options2?.images?.map((im) => ({ type: "image", mimeType: im.mimeType, data: im.dataBase64 }));
|
|
33173
|
+
return sendAcpPromptViaTransport(transport, sessionCtx, sessionId, prompt, imgs);
|
|
33003
33174
|
},
|
|
33004
33175
|
async cancel() {
|
|
33005
33176
|
for (const [id, entry] of [...pendingPermissionReplies.entries()]) {
|
|
@@ -33367,14 +33538,19 @@ async function createCursorAcpClient(options) {
|
|
|
33367
33538
|
});
|
|
33368
33539
|
const established = await bootstrapAcpWireSession(transport, sessionCtx, {
|
|
33369
33540
|
protocolVersion: 1,
|
|
33370
|
-
clientCapabilities: {
|
|
33541
|
+
clientCapabilities: {
|
|
33542
|
+
fs: { readTextFile: true, writeTextFile: true },
|
|
33543
|
+
terminal: false,
|
|
33544
|
+
prompt: { image: true }
|
|
33545
|
+
},
|
|
33371
33546
|
clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
|
|
33372
33547
|
});
|
|
33373
33548
|
const sessionId = established.sessionId;
|
|
33374
33549
|
resolve18({
|
|
33375
33550
|
sessionId,
|
|
33376
|
-
async sendPrompt(prompt,
|
|
33377
|
-
|
|
33551
|
+
async sendPrompt(prompt, options2) {
|
|
33552
|
+
const imgs = options2?.images?.map((im) => ({ type: "image", mimeType: im.mimeType, data: im.dataBase64 }));
|
|
33553
|
+
return sendAcpPromptViaTransport(transport, sessionCtx, sessionId, prompt, imgs);
|
|
33378
33554
|
},
|
|
33379
33555
|
async cancel() {
|
|
33380
33556
|
cancelPendingPermissionRequests2();
|
|
@@ -34405,7 +34581,8 @@ async function createAcpManager(options) {
|
|
|
34405
34581
|
sessionChangeSummaryFilePaths,
|
|
34406
34582
|
cloudApiBaseUrl,
|
|
34407
34583
|
getCloudAccessToken,
|
|
34408
|
-
e2ee
|
|
34584
|
+
e2ee,
|
|
34585
|
+
attachments
|
|
34409
34586
|
} = opts;
|
|
34410
34587
|
const preferredForPrompt = agentType ?? backendFallbackAgentType ?? null;
|
|
34411
34588
|
pendingCancelRunId = void 0;
|
|
@@ -34476,7 +34653,8 @@ async function createAcpManager(options) {
|
|
|
34476
34653
|
sessionChangeSummaryFilePaths,
|
|
34477
34654
|
cloudApiBaseUrl,
|
|
34478
34655
|
getCloudAccessToken,
|
|
34479
|
-
e2ee
|
|
34656
|
+
e2ee,
|
|
34657
|
+
attachments
|
|
34480
34658
|
});
|
|
34481
34659
|
}
|
|
34482
34660
|
void run().finally(() => {
|
|
@@ -37614,6 +37792,7 @@ function reportGitRepos(getWs, log2) {
|
|
|
37614
37792
|
var API_TO_BRIDGE_MESSAGE_TYPES = [
|
|
37615
37793
|
"auth_token",
|
|
37616
37794
|
"bridge_identified",
|
|
37795
|
+
"ha",
|
|
37617
37796
|
"dev_servers_config",
|
|
37618
37797
|
"server_control",
|
|
37619
37798
|
"agent_config",
|
|
@@ -37642,6 +37821,10 @@ function parseApiToBridgeMessage(data, log2) {
|
|
|
37642
37821
|
}
|
|
37643
37822
|
return null;
|
|
37644
37823
|
}
|
|
37824
|
+
if (t === "ha") {
|
|
37825
|
+
const s = data.s;
|
|
37826
|
+
if (typeof s !== "number" || !Number.isFinite(s)) return null;
|
|
37827
|
+
}
|
|
37645
37828
|
return data;
|
|
37646
37829
|
}
|
|
37647
37830
|
|
|
@@ -37682,6 +37865,13 @@ var handleBridgeIdentified = (msg, deps) => {
|
|
|
37682
37865
|
});
|
|
37683
37866
|
};
|
|
37684
37867
|
|
|
37868
|
+
// src/connection/heartbeat/ack.ts
|
|
37869
|
+
var handleBridgeHeartbeatAck = (msg, deps) => {
|
|
37870
|
+
const raw = msg.s;
|
|
37871
|
+
if (typeof raw !== "number" || !Number.isFinite(raw)) return;
|
|
37872
|
+
deps.onBridgeHeartbeatAck?.(Math.trunc(raw));
|
|
37873
|
+
};
|
|
37874
|
+
|
|
37685
37875
|
// src/agents/acp/from-bridge/handle-bridge-agent-config.ts
|
|
37686
37876
|
function handleBridgeAgentConfig(msg, { acpManager }) {
|
|
37687
37877
|
if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
|
|
@@ -37848,7 +38038,8 @@ function dispatchLocalPrompt(next, deps) {
|
|
|
37848
38038
|
...Array.isArray(pl.sessionChangeSummaryFilePaths) ? { sessionChangeSummaryFilePaths: pl.sessionChangeSummaryFilePaths } : {},
|
|
37849
38039
|
...Array.isArray(pl.sessionChangeSummaryFileSnapshots) ? { sessionChangeSummaryFileSnapshots: pl.sessionChangeSummaryFileSnapshots } : {},
|
|
37850
38040
|
...typeof pl.agentType === "string" && pl.agentType.trim() ? { agentType: pl.agentType.trim() } : {},
|
|
37851
|
-
...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {}
|
|
38041
|
+
...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {},
|
|
38042
|
+
...Array.isArray(pl.attachments) && pl.attachments.length > 0 ? { attachments: pl.attachments } : {}
|
|
37852
38043
|
};
|
|
37853
38044
|
handleBridgePrompt(msg, deps);
|
|
37854
38045
|
}
|
|
@@ -38115,15 +38306,30 @@ function resolveChangeSummaryPromptForAgent(params) {
|
|
|
38115
38306
|
}
|
|
38116
38307
|
|
|
38117
38308
|
// src/agents/acp/from-bridge/handle-bridge-prompt.ts
|
|
38309
|
+
function parseBridgeAttachments(msg) {
|
|
38310
|
+
const raw = msg.attachments;
|
|
38311
|
+
if (!Array.isArray(raw)) return [];
|
|
38312
|
+
const out = [];
|
|
38313
|
+
for (const x of raw) {
|
|
38314
|
+
if (x === null || typeof x !== "object" || Array.isArray(x)) continue;
|
|
38315
|
+
const o = x;
|
|
38316
|
+
const id = typeof o.attachmentId === "string" ? o.attachmentId.trim() : "";
|
|
38317
|
+
if (!id) continue;
|
|
38318
|
+
const mt = typeof o.mimeType === "string" && o.mimeType.trim() ? o.mimeType.trim() : "application/octet-stream";
|
|
38319
|
+
out.push({ attachmentId: id, mimeType: mt });
|
|
38320
|
+
}
|
|
38321
|
+
return out;
|
|
38322
|
+
}
|
|
38118
38323
|
function handleBridgePrompt(msg, deps) {
|
|
38119
38324
|
const { getWs, log: log2, acpManager, sessionWorktreeManager } = deps;
|
|
38120
38325
|
const rawPrompt = msg.prompt;
|
|
38121
38326
|
const promptText = typeof rawPrompt === "string" ? rawPrompt : rawPrompt != null ? String(rawPrompt) : "";
|
|
38327
|
+
const attachments = parseBridgeAttachments(msg);
|
|
38122
38328
|
const sessionId = msg.sessionId;
|
|
38123
38329
|
const runId = typeof msg.runId === "string" ? msg.runId : void 0;
|
|
38124
38330
|
const promptId = typeof msg.id === "string" ? msg.id : void 0;
|
|
38125
38331
|
const { sendBridgeMessage, sendResult: sendResult2, sendSessionUpdate } = createBridgePromptSenders(deps, getWs);
|
|
38126
|
-
if (!promptText.trim()) {
|
|
38332
|
+
if (!promptText.trim() && attachments.length === 0) {
|
|
38127
38333
|
log2(
|
|
38128
38334
|
`[Bridge service] Prompt ignored: empty or missing prompt text (session ${typeof msg.sessionId === "string" ? msg.sessionId.slice(0, 8) : "\u2014"}\u2026, run ${typeof msg.runId === "string" ? msg.runId.slice(0, 8) : "\u2014"}\u2026).`
|
|
38129
38335
|
);
|
|
@@ -38190,7 +38396,8 @@ function handleBridgePrompt(msg, deps) {
|
|
|
38190
38396
|
sessionChangeSummaryFilePaths: pathsForUpload,
|
|
38191
38397
|
cloudApiBaseUrl: deps.cloudApiBaseUrl,
|
|
38192
38398
|
getCloudAccessToken: deps.getCloudAccessToken,
|
|
38193
|
-
e2ee: deps.e2ee
|
|
38399
|
+
e2ee: deps.e2ee,
|
|
38400
|
+
...attachments.length > 0 ? { attachments } : {}
|
|
38194
38401
|
});
|
|
38195
38402
|
}
|
|
38196
38403
|
void sessionWorktreeManager.resolveSessionParentPathForPrompt(sessionId, { isNewSession, sessionParent, sessionParentPath }).then((cwd) => preambleAndPrompt(cwd)).catch((err) => {
|
|
@@ -39008,6 +39215,9 @@ function dispatchBridgeMessage(msg, deps) {
|
|
|
39008
39215
|
case "bridge_identified":
|
|
39009
39216
|
handleBridgeIdentified(msg, deps);
|
|
39010
39217
|
break;
|
|
39218
|
+
case "ha":
|
|
39219
|
+
handleBridgeHeartbeatAck(msg, deps);
|
|
39220
|
+
break;
|
|
39011
39221
|
case "dev_servers_config":
|
|
39012
39222
|
handleDevServersConfig(msg, deps);
|
|
39013
39223
|
break;
|
|
@@ -39063,9 +39273,17 @@ function dispatchBridgeMessage(msg, deps) {
|
|
|
39063
39273
|
}
|
|
39064
39274
|
|
|
39065
39275
|
// src/routing/handle-bridge-message.ts
|
|
39276
|
+
function normalizeInboundBridgeWebSocketJson(data) {
|
|
39277
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) return data;
|
|
39278
|
+
const o = data;
|
|
39279
|
+
if (o.t === "ha" && typeof o.s === "number" && Number.isFinite(o.s)) {
|
|
39280
|
+
return { type: "ha", s: Math.trunc(o.s) };
|
|
39281
|
+
}
|
|
39282
|
+
return data;
|
|
39283
|
+
}
|
|
39066
39284
|
function handleBridgeMessage(data, deps) {
|
|
39067
39285
|
if (!deps.getWs()) return;
|
|
39068
|
-
const msg = parseApiToBridgeMessage(data, deps.log);
|
|
39286
|
+
const msg = parseApiToBridgeMessage(normalizeInboundBridgeWebSocketJson(data), deps.log);
|
|
39069
39287
|
if (!msg) return;
|
|
39070
39288
|
setImmediate(() => {
|
|
39071
39289
|
dispatchBridgeMessage(msg, deps);
|
|
@@ -39113,7 +39331,8 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
39113
39331
|
persistTokens,
|
|
39114
39332
|
onAuthInvalid,
|
|
39115
39333
|
e2ee,
|
|
39116
|
-
identifyReportedPaths
|
|
39334
|
+
identifyReportedPaths,
|
|
39335
|
+
bridgeHeartbeat
|
|
39117
39336
|
} = params;
|
|
39118
39337
|
let authRefreshInFlight = false;
|
|
39119
39338
|
function handleOpen() {
|
|
@@ -39145,6 +39364,7 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
39145
39364
|
}
|
|
39146
39365
|
}
|
|
39147
39366
|
function handleClose(code, reason) {
|
|
39367
|
+
bridgeHeartbeat?.stop();
|
|
39148
39368
|
try {
|
|
39149
39369
|
const was = state.currentWs;
|
|
39150
39370
|
state.currentWs = null;
|
|
@@ -39179,6 +39399,7 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
39179
39399
|
} catch {
|
|
39180
39400
|
}
|
|
39181
39401
|
}
|
|
39402
|
+
bridgeHeartbeat?.stop();
|
|
39182
39403
|
const prev = state.currentWs;
|
|
39183
39404
|
if (prev) {
|
|
39184
39405
|
prev.removeAllListeners();
|
|
@@ -39270,6 +39491,92 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
39270
39491
|
return { connect };
|
|
39271
39492
|
}
|
|
39272
39493
|
|
|
39494
|
+
// src/connection/heartbeat/controller.ts
|
|
39495
|
+
function meanRttMs(samples) {
|
|
39496
|
+
if (samples.length === 0) return void 0;
|
|
39497
|
+
let sum = 0;
|
|
39498
|
+
for (const x of samples) sum += x;
|
|
39499
|
+
return sum / samples.length;
|
|
39500
|
+
}
|
|
39501
|
+
function createBridgeHeartbeatController(params) {
|
|
39502
|
+
const { getWs, log: log2 } = params;
|
|
39503
|
+
let interval = null;
|
|
39504
|
+
let seqCursor = -1;
|
|
39505
|
+
let awaitingSeq = null;
|
|
39506
|
+
let sentAtMs = 0;
|
|
39507
|
+
let missed = 0;
|
|
39508
|
+
const rttSamples = [];
|
|
39509
|
+
function clearTimer() {
|
|
39510
|
+
if (interval != null) {
|
|
39511
|
+
clearInterval(interval);
|
|
39512
|
+
interval = null;
|
|
39513
|
+
}
|
|
39514
|
+
}
|
|
39515
|
+
function nextSeq() {
|
|
39516
|
+
seqCursor = seqCursor >= BRIDGE_HEARTBEAT_SEQ_MAX ? 0 : seqCursor + 1;
|
|
39517
|
+
return seqCursor;
|
|
39518
|
+
}
|
|
39519
|
+
function tick() {
|
|
39520
|
+
const ws = getWs();
|
|
39521
|
+
if (!ws || ws.readyState !== wrapper_default.OPEN) return;
|
|
39522
|
+
if (awaitingSeq !== null) {
|
|
39523
|
+
missed++;
|
|
39524
|
+
if (missed >= BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT) {
|
|
39525
|
+
try {
|
|
39526
|
+
log2("[Bridge service] Heartbeat missed repeatedly; reconnecting\u2026");
|
|
39527
|
+
} catch {
|
|
39528
|
+
}
|
|
39529
|
+
clearTimer();
|
|
39530
|
+
awaitingSeq = null;
|
|
39531
|
+
missed = 0;
|
|
39532
|
+
rttSamples.length = 0;
|
|
39533
|
+
safeCloseWebSocket(ws);
|
|
39534
|
+
return;
|
|
39535
|
+
}
|
|
39536
|
+
}
|
|
39537
|
+
const seq = nextSeq();
|
|
39538
|
+
const mean = meanRttMs(rttSamples);
|
|
39539
|
+
const payload = mean !== void 0 && Number.isFinite(mean) ? { t: "h", s: seq, m: Math.round(mean) } : { t: "h", s: seq };
|
|
39540
|
+
sendWsMessage(ws, payload);
|
|
39541
|
+
awaitingSeq = seq;
|
|
39542
|
+
sentAtMs = Date.now();
|
|
39543
|
+
}
|
|
39544
|
+
return {
|
|
39545
|
+
start() {
|
|
39546
|
+
clearTimer();
|
|
39547
|
+
awaitingSeq = null;
|
|
39548
|
+
missed = 0;
|
|
39549
|
+
seqCursor = -1;
|
|
39550
|
+
rttSamples.length = 0;
|
|
39551
|
+
interval = setInterval(tick, BRIDGE_APP_HEARTBEAT_INTERVAL_MS);
|
|
39552
|
+
},
|
|
39553
|
+
stop() {
|
|
39554
|
+
clearTimer();
|
|
39555
|
+
awaitingSeq = null;
|
|
39556
|
+
missed = 0;
|
|
39557
|
+
rttSamples.length = 0;
|
|
39558
|
+
},
|
|
39559
|
+
onAck(seq) {
|
|
39560
|
+
if (awaitingSeq === null) return;
|
|
39561
|
+
if (!Number.isFinite(seq)) return;
|
|
39562
|
+
const ack = Math.trunc(seq);
|
|
39563
|
+
const pending = awaitingSeq;
|
|
39564
|
+
if (ack < pending) return;
|
|
39565
|
+
if (ack === pending) {
|
|
39566
|
+
const rtt = Date.now() - sentAtMs;
|
|
39567
|
+
if (Number.isFinite(rtt) && rtt >= 0 && rtt < 6e5) {
|
|
39568
|
+
rttSamples.push(rtt);
|
|
39569
|
+
if (rttSamples.length > BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX) {
|
|
39570
|
+
rttSamples.splice(0, rttSamples.length - BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX);
|
|
39571
|
+
}
|
|
39572
|
+
}
|
|
39573
|
+
}
|
|
39574
|
+
awaitingSeq = null;
|
|
39575
|
+
missed = 0;
|
|
39576
|
+
}
|
|
39577
|
+
};
|
|
39578
|
+
}
|
|
39579
|
+
|
|
39273
39580
|
// src/connection/create-bridge-connection.ts
|
|
39274
39581
|
async function createBridgeConnection(options) {
|
|
39275
39582
|
const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
|
|
@@ -39309,13 +39616,18 @@ async function createBridgeConnection(options) {
|
|
|
39309
39616
|
}
|
|
39310
39617
|
const e2ee = options.e2eCertificate ? createCliE2eeRuntime(options.e2eCertificate) : void 0;
|
|
39311
39618
|
const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeRoot, e2ee });
|
|
39312
|
-
const
|
|
39619
|
+
const bridgeHeartbeat = createBridgeHeartbeatController({ getWs, log: logFn });
|
|
39620
|
+
const baseOnBridgeIdentified = createOnBridgeIdentified({
|
|
39313
39621
|
devServerManager,
|
|
39314
39622
|
firehoseServerUrl,
|
|
39315
39623
|
workspaceId,
|
|
39316
39624
|
state,
|
|
39317
39625
|
logFn
|
|
39318
39626
|
});
|
|
39627
|
+
const onBridgeIdentified = (msg) => {
|
|
39628
|
+
baseOnBridgeIdentified(msg);
|
|
39629
|
+
bridgeHeartbeat.start();
|
|
39630
|
+
};
|
|
39319
39631
|
const sendLocalSkillsReport = createSendLocalSkillsReport(getWs, logFn);
|
|
39320
39632
|
const reportAutoDetectedAgents = createReportAutoDetectedAgents(getWs, logFn);
|
|
39321
39633
|
const messageDeps = {
|
|
@@ -39324,6 +39636,9 @@ async function createBridgeConnection(options) {
|
|
|
39324
39636
|
acpManager,
|
|
39325
39637
|
sessionWorktreeManager,
|
|
39326
39638
|
onBridgeIdentified,
|
|
39639
|
+
onBridgeHeartbeatAck: (seq) => {
|
|
39640
|
+
bridgeHeartbeat.onAck(seq);
|
|
39641
|
+
},
|
|
39327
39642
|
sendLocalSkillsReport,
|
|
39328
39643
|
reportAutoDetectedAgents,
|
|
39329
39644
|
devServerManager,
|
|
@@ -39347,13 +39662,15 @@ async function createBridgeConnection(options) {
|
|
|
39347
39662
|
persistTokens,
|
|
39348
39663
|
onAuthInvalid,
|
|
39349
39664
|
e2ee,
|
|
39350
|
-
identifyReportedPaths
|
|
39665
|
+
identifyReportedPaths,
|
|
39666
|
+
bridgeHeartbeat
|
|
39351
39667
|
});
|
|
39352
39668
|
connect();
|
|
39353
39669
|
const stopFileIndexWatcher = startFileIndexWatcher(getBridgeRoot());
|
|
39354
39670
|
return {
|
|
39355
39671
|
close: async () => {
|
|
39356
39672
|
stopFileIndexWatcher();
|
|
39673
|
+
bridgeHeartbeat.stop();
|
|
39357
39674
|
await closeBridgeConnection(state, acpManager, devServerManager, logFn);
|
|
39358
39675
|
}
|
|
39359
39676
|
};
|