@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/index.js
CHANGED
|
@@ -23128,13 +23128,16 @@ function parseAcpInitAgentCapabilities(initResult) {
|
|
|
23128
23128
|
const canLoad = agentCapabilities?.loadSession === true;
|
|
23129
23129
|
const sessionCaps = agentCapabilities?.sessionCapabilities;
|
|
23130
23130
|
const canResume = Boolean(sessionCaps?.resume);
|
|
23131
|
-
|
|
23131
|
+
const promptCaps = agentCapabilities?.promptCapabilities;
|
|
23132
|
+
const promptSupportsImage = promptCaps?.image === true;
|
|
23133
|
+
return { canResume, canLoad, promptSupportsImage };
|
|
23132
23134
|
}
|
|
23133
23135
|
|
|
23134
23136
|
// src/agents/acp/clients/shared/bootstrap-acp-wire-session.ts
|
|
23135
23137
|
async function bootstrapAcpWireSession(transport, ctx, initializeRequest) {
|
|
23136
23138
|
const initResult = await transport.initialize(initializeRequest);
|
|
23137
|
-
const { canResume, canLoad } = parseAcpInitAgentCapabilities(initResult);
|
|
23139
|
+
const { canResume, canLoad, promptSupportsImage } = parseAcpInitAgentCapabilities(initResult);
|
|
23140
|
+
ctx.agentPromptImageSupported = promptSupportsImage;
|
|
23138
23141
|
await transport.afterInitialize?.();
|
|
23139
23142
|
const established = await establishAcpSessionWithTransport(transport, ctx, canResume, canLoad);
|
|
23140
23143
|
const sessionId = established.sessionId;
|
|
@@ -23225,11 +23228,27 @@ function normalizeAcpPromptTurnFailure(err, stderrCaptureText) {
|
|
|
23225
23228
|
}
|
|
23226
23229
|
|
|
23227
23230
|
// src/agents/acp/clients/shared/send-acp-prompt-via-transport.ts
|
|
23228
|
-
async function sendAcpPromptViaTransport(transport, ctx, sessionId, promptText) {
|
|
23231
|
+
async function sendAcpPromptViaTransport(transport, ctx, sessionId, promptText, images) {
|
|
23232
|
+
if (images && images.length > 0 && !ctx.agentPromptImageSupported) {
|
|
23233
|
+
await new Promise((r) => setImmediate(r));
|
|
23234
|
+
return normalizeAcpPromptTurnFailure(
|
|
23235
|
+
new Error(
|
|
23236
|
+
"This agent does not advertise image support in ACP (missing agentCapabilities.promptCapabilities.image). Remove images or use an agent that supports prompt images."
|
|
23237
|
+
),
|
|
23238
|
+
ctx.getStderrText()
|
|
23239
|
+
);
|
|
23240
|
+
}
|
|
23241
|
+
const textBlock = promptText.trim() !== "" ? promptText : images?.length ? " " : "";
|
|
23242
|
+
const prompt = [{ type: "text", text: textBlock }];
|
|
23243
|
+
if (images && images.length > 0) {
|
|
23244
|
+
for (const im of images) {
|
|
23245
|
+
prompt.push({ type: "image", mimeType: im.mimeType, data: im.data });
|
|
23246
|
+
}
|
|
23247
|
+
}
|
|
23229
23248
|
try {
|
|
23230
23249
|
const response = await transport.prompt({
|
|
23231
23250
|
sessionId,
|
|
23232
|
-
prompt
|
|
23251
|
+
prompt
|
|
23233
23252
|
});
|
|
23234
23253
|
await new Promise((r2) => setImmediate(r2));
|
|
23235
23254
|
const r = response;
|
|
@@ -23405,15 +23424,17 @@ async function createSdkStdioAcpClient(options) {
|
|
|
23405
23424
|
const established = await bootstrapAcpWireSession(transport, sessionCtx, {
|
|
23406
23425
|
protocolVersion: PROTOCOL_VERSION2,
|
|
23407
23426
|
clientCapabilities: {
|
|
23408
|
-
fs: { readTextFile: true, writeTextFile: true }
|
|
23427
|
+
fs: { readTextFile: true, writeTextFile: true },
|
|
23428
|
+
prompt: { image: true }
|
|
23409
23429
|
},
|
|
23410
23430
|
clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
|
|
23411
23431
|
});
|
|
23412
23432
|
const sessionId = established.sessionId;
|
|
23413
23433
|
settleResolve({
|
|
23414
23434
|
sessionId,
|
|
23415
|
-
async sendPrompt(prompt,
|
|
23416
|
-
|
|
23435
|
+
async sendPrompt(prompt, options2) {
|
|
23436
|
+
const imgs = options2?.images?.map((im) => ({ type: "image", mimeType: im.mimeType, data: im.dataBase64 }));
|
|
23437
|
+
return sendAcpPromptViaTransport(transport, sessionCtx, sessionId, prompt, imgs);
|
|
23417
23438
|
},
|
|
23418
23439
|
async cancel() {
|
|
23419
23440
|
for (const [id, entry] of [...pendingPermissionReplies.entries()]) {
|
|
@@ -23875,7 +23896,13 @@ function installBridgeProcessResilience() {
|
|
|
23875
23896
|
}
|
|
23876
23897
|
|
|
23877
23898
|
// src/cli-version.ts
|
|
23878
|
-
var CLI_VERSION = "0.1.
|
|
23899
|
+
var CLI_VERSION = "0.1.28".length > 0 ? "0.1.28" : "0.0.0-dev";
|
|
23900
|
+
|
|
23901
|
+
// src/connection/heartbeat/constants.ts
|
|
23902
|
+
var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
|
|
23903
|
+
var BRIDGE_HEARTBEAT_SEQ_MAX = 2147483646;
|
|
23904
|
+
var BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT = 4;
|
|
23905
|
+
var BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX = 5;
|
|
23879
23906
|
|
|
23880
23907
|
// ../../node_modules/.pnpm/open@10.2.0/node_modules/open/index.js
|
|
23881
23908
|
import process7 from "node:process";
|
|
@@ -24855,14 +24882,18 @@ function runPendingAuth(options) {
|
|
|
24855
24882
|
}
|
|
24856
24883
|
function connect() {
|
|
24857
24884
|
const url2 = buildPendingBridgeUrl(apiUrl, connectionId);
|
|
24885
|
+
let pendingHbSeq = -1;
|
|
24858
24886
|
ws = createWsBridge({
|
|
24859
24887
|
url: url2,
|
|
24860
24888
|
onOpen: () => {
|
|
24861
24889
|
clearQuietOnOpen();
|
|
24890
|
+
pendingHbSeq = -1;
|
|
24862
24891
|
sendWsMessage(ws, { type: "identify", role: "cli", cliVersion: CLI_VERSION });
|
|
24863
24892
|
keepaliveInterval = setInterval(() => {
|
|
24864
24893
|
if (resolved || !ws || ws.readyState !== 1) return;
|
|
24865
|
-
|
|
24894
|
+
pendingHbSeq = pendingHbSeq >= BRIDGE_HEARTBEAT_SEQ_MAX ? 0 : pendingHbSeq + 1;
|
|
24895
|
+
const hb = { t: "h", s: pendingHbSeq };
|
|
24896
|
+
sendWsMessage(ws, hb);
|
|
24866
24897
|
}, PENDING_KEEPALIVE_MS);
|
|
24867
24898
|
if (browserFallback) {
|
|
24868
24899
|
clearTimeout(browserFallback);
|
|
@@ -29951,6 +29982,122 @@ function augmentPromptResultAuthFields(agentType, errorText) {
|
|
|
29951
29982
|
return { agentAuthRequired: true, agentType };
|
|
29952
29983
|
}
|
|
29953
29984
|
|
|
29985
|
+
// ../e2ee/src/constants.ts
|
|
29986
|
+
var E2EE_NONCE_BYTES = 12;
|
|
29987
|
+
|
|
29988
|
+
// ../e2ee/src/types.ts
|
|
29989
|
+
function isE2eeEnvelope(value) {
|
|
29990
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
29991
|
+
const o = value;
|
|
29992
|
+
return typeof o.k === "string" && typeof o.n === "string" && typeof o.c === "string";
|
|
29993
|
+
}
|
|
29994
|
+
|
|
29995
|
+
// ../e2ee/src/encoding.ts
|
|
29996
|
+
function base64UrlEncode(bytes) {
|
|
29997
|
+
let binary = "";
|
|
29998
|
+
for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
|
|
29999
|
+
const b64 = btoa(binary);
|
|
30000
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
30001
|
+
}
|
|
30002
|
+
function base64UrlDecode(value) {
|
|
30003
|
+
const b64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
30004
|
+
const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
|
|
30005
|
+
const binary = atob(padded);
|
|
30006
|
+
const out = new Uint8Array(binary.length);
|
|
30007
|
+
for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
|
|
30008
|
+
return out;
|
|
30009
|
+
}
|
|
30010
|
+
|
|
30011
|
+
// src/agents/acp/fetch-session-attachments.ts
|
|
30012
|
+
function metaSaysEncrypted(meta) {
|
|
30013
|
+
if (!meta) return false;
|
|
30014
|
+
const e = meta.encrypted;
|
|
30015
|
+
return e === true || e === "true" || e === 1;
|
|
30016
|
+
}
|
|
30017
|
+
function warnIfDecodedImageMagicUnexpected(buf, mimeType, idShort) {
|
|
30018
|
+
const m = mimeType.toLowerCase();
|
|
30019
|
+
if (!m.startsWith("image/") || buf.length < 4) return;
|
|
30020
|
+
const b0 = buf[0];
|
|
30021
|
+
const b1 = buf[1];
|
|
30022
|
+
const b2 = buf[2];
|
|
30023
|
+
const b3 = buf[3];
|
|
30024
|
+
let looksOk = false;
|
|
30025
|
+
if (m.includes("png") && b0 === 137 && b1 === 80 && b2 === 78 && b3 === 71) looksOk = true;
|
|
30026
|
+
else if ((m.includes("jpeg") || m.includes("jpg")) && b0 === 255 && b1 === 216 && b2 === 255) looksOk = true;
|
|
30027
|
+
else if (m.includes("gif") && b0 === 71 && b1 === 73 && b2 === 70) looksOk = true;
|
|
30028
|
+
else if (m.includes("webp") && buf.length >= 12 && buf.subarray(0, 4).toString("ascii") === "RIFF" && buf.subarray(8, 12).toString("ascii") === "WEBP")
|
|
30029
|
+
looksOk = true;
|
|
30030
|
+
else if (!/(png|jpe?g|gif|webp)/i.test(m)) looksOk = true;
|
|
30031
|
+
if (!looksOk) {
|
|
30032
|
+
logDebug(
|
|
30033
|
+
`[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")}`
|
|
30034
|
+
);
|
|
30035
|
+
}
|
|
30036
|
+
}
|
|
30037
|
+
async function fetchSessionAttachmentPayloadsForAgent(params) {
|
|
30038
|
+
const { attachments, sessionId, cloudApiBaseUrl, getCloudAccessToken, e2ee, log: log2 } = params;
|
|
30039
|
+
const token = getCloudAccessToken();
|
|
30040
|
+
if (!token) {
|
|
30041
|
+
return { ok: false, error: "Missing cloud access token; cannot download attachments." };
|
|
30042
|
+
}
|
|
30043
|
+
const wantedCount = attachments.filter((a) => typeof a.attachmentId === "string" && a.attachmentId.trim() !== "").length;
|
|
30044
|
+
if (wantedCount === 0) {
|
|
30045
|
+
return { ok: false, error: "No valid attachment ids in prompt." };
|
|
30046
|
+
}
|
|
30047
|
+
const base = cloudApiBaseUrl.replace(/\/+$/, "");
|
|
30048
|
+
const out = [];
|
|
30049
|
+
for (const a of attachments) {
|
|
30050
|
+
const id = typeof a.attachmentId === "string" ? a.attachmentId.trim() : "";
|
|
30051
|
+
if (!id) continue;
|
|
30052
|
+
const metaUrl = `${base}/api/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/meta`;
|
|
30053
|
+
const blobUrl = `${base}/api/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/blob`;
|
|
30054
|
+
const metaRes = await fetch(metaUrl, { headers: { Authorization: `Bearer ${token}` } });
|
|
30055
|
+
if (!metaRes.ok) {
|
|
30056
|
+
const t = await metaRes.text().catch(() => "");
|
|
30057
|
+
log2(`[Agent] Attachment meta fetch failed ${metaRes.status}: ${t.slice(0, 200)}`);
|
|
30058
|
+
return { ok: false, error: `Could not load attachment (${id.slice(0, 8)}\u2026): ${metaRes.status}` };
|
|
30059
|
+
}
|
|
30060
|
+
const meta = await metaRes.json().catch(() => null);
|
|
30061
|
+
const mimeType = typeof meta?.mimeType === "string" && meta.mimeType.trim() ? meta.mimeType.trim() : a.mimeType;
|
|
30062
|
+
const blobRes = await fetch(blobUrl, { headers: { Authorization: `Bearer ${token}` } });
|
|
30063
|
+
if (!blobRes.ok) {
|
|
30064
|
+
const t = await blobRes.text().catch(() => "");
|
|
30065
|
+
log2(`[Agent] Attachment blob fetch failed ${blobRes.status}: ${t.slice(0, 200)}`);
|
|
30066
|
+
return { ok: false, error: `Could not load attachment data (${id.slice(0, 8)}\u2026): ${blobRes.status}` };
|
|
30067
|
+
}
|
|
30068
|
+
const buf = Buffer.from(await blobRes.arrayBuffer());
|
|
30069
|
+
let imageBytes;
|
|
30070
|
+
const encrypted = metaSaysEncrypted(meta);
|
|
30071
|
+
if (encrypted) {
|
|
30072
|
+
if (!e2ee) {
|
|
30073
|
+
return { ok: false, error: "Encrypted attachments require E2EE keys on this bridge." };
|
|
30074
|
+
}
|
|
30075
|
+
const k = typeof meta?.k === "string" ? meta.k : "";
|
|
30076
|
+
const n = typeof meta?.n === "string" ? meta.n : "";
|
|
30077
|
+
if (!k || !n) {
|
|
30078
|
+
return { ok: false, error: "Invalid encrypted attachment metadata (missing key id or nonce)." };
|
|
30079
|
+
}
|
|
30080
|
+
const c = base64UrlEncode(buf);
|
|
30081
|
+
imageBytes = e2ee.decryptEnvelopeToBuffer({ k, n, c });
|
|
30082
|
+
} else {
|
|
30083
|
+
imageBytes = buf;
|
|
30084
|
+
}
|
|
30085
|
+
warnIfDecodedImageMagicUnexpected(imageBytes, mimeType, id.slice(0, 8));
|
|
30086
|
+
logDebug(
|
|
30087
|
+
`[Agent] Loaded prompt image ${id.slice(0, 8)}\u2026: ${imageBytes.length} bytes, mime=${mimeType}, ${encrypted ? "E2EE decrypted" : "plaintext from storage"}`
|
|
30088
|
+
);
|
|
30089
|
+
const dataBase64 = imageBytes.toString("base64");
|
|
30090
|
+
out.push({ mimeType, dataBase64 });
|
|
30091
|
+
}
|
|
30092
|
+
if (out.length !== wantedCount) {
|
|
30093
|
+
return {
|
|
30094
|
+
ok: false,
|
|
30095
|
+
error: `Expected ${wantedCount} image attachment(s) but only loaded ${out.length} (check attachment ids and empty rows).`
|
|
30096
|
+
};
|
|
30097
|
+
}
|
|
30098
|
+
return { ok: true, images: out };
|
|
30099
|
+
}
|
|
30100
|
+
|
|
29954
30101
|
// src/agents/acp/send-prompt-to-agent.ts
|
|
29955
30102
|
async function sendPromptToAgent(options) {
|
|
29956
30103
|
const {
|
|
@@ -29968,10 +30115,49 @@ async function sendPromptToAgent(options) {
|
|
|
29968
30115
|
sessionChangeSummaryFilePaths,
|
|
29969
30116
|
cloudApiBaseUrl,
|
|
29970
30117
|
getCloudAccessToken,
|
|
29971
|
-
e2ee
|
|
30118
|
+
e2ee,
|
|
30119
|
+
attachments
|
|
29972
30120
|
} = options;
|
|
29973
30121
|
try {
|
|
29974
|
-
|
|
30122
|
+
let sendOpts = {};
|
|
30123
|
+
if (attachments && attachments.length > 0) {
|
|
30124
|
+
if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
|
|
30125
|
+
sendResult2({
|
|
30126
|
+
type: "prompt_result",
|
|
30127
|
+
id: promptId,
|
|
30128
|
+
...sessionId ? { sessionId } : {},
|
|
30129
|
+
...runId ? { runId } : {},
|
|
30130
|
+
success: false,
|
|
30131
|
+
error: "Prompt includes images but the bridge is not configured with a cloud API URL and token to fetch them.",
|
|
30132
|
+
...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
|
|
30133
|
+
...augmentPromptResultAuthFields(agentType, "missing cloud for images download")
|
|
30134
|
+
});
|
|
30135
|
+
return;
|
|
30136
|
+
}
|
|
30137
|
+
const resolved = await fetchSessionAttachmentPayloadsForAgent({
|
|
30138
|
+
attachments,
|
|
30139
|
+
sessionId,
|
|
30140
|
+
cloudApiBaseUrl,
|
|
30141
|
+
getCloudAccessToken,
|
|
30142
|
+
e2ee,
|
|
30143
|
+
log: log2
|
|
30144
|
+
});
|
|
30145
|
+
if (!resolved.ok) {
|
|
30146
|
+
sendResult2({
|
|
30147
|
+
type: "prompt_result",
|
|
30148
|
+
id: promptId,
|
|
30149
|
+
...sessionId ? { sessionId } : {},
|
|
30150
|
+
...runId ? { runId } : {},
|
|
30151
|
+
success: false,
|
|
30152
|
+
error: resolved.error,
|
|
30153
|
+
...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
|
|
30154
|
+
...augmentPromptResultAuthFields(agentType, resolved.error)
|
|
30155
|
+
});
|
|
30156
|
+
return;
|
|
30157
|
+
}
|
|
30158
|
+
sendOpts = { images: resolved.images };
|
|
30159
|
+
}
|
|
30160
|
+
const result = await handle.sendPrompt(promptText, sendOpts);
|
|
29975
30161
|
if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
|
|
29976
30162
|
await collectTurnGitDiffFromPreTurnSnapshot({
|
|
29977
30163
|
sessionId,
|
|
@@ -30392,14 +30578,19 @@ async function createCursorAcpClient(options) {
|
|
|
30392
30578
|
});
|
|
30393
30579
|
const established = await bootstrapAcpWireSession(transport, sessionCtx, {
|
|
30394
30580
|
protocolVersion: 1,
|
|
30395
|
-
clientCapabilities: {
|
|
30581
|
+
clientCapabilities: {
|
|
30582
|
+
fs: { readTextFile: true, writeTextFile: true },
|
|
30583
|
+
terminal: false,
|
|
30584
|
+
prompt: { image: true }
|
|
30585
|
+
},
|
|
30396
30586
|
clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
|
|
30397
30587
|
});
|
|
30398
30588
|
const sessionId = established.sessionId;
|
|
30399
30589
|
resolve16({
|
|
30400
30590
|
sessionId,
|
|
30401
|
-
async sendPrompt(prompt,
|
|
30402
|
-
|
|
30591
|
+
async sendPrompt(prompt, options2) {
|
|
30592
|
+
const imgs = options2?.images?.map((im) => ({ type: "image", mimeType: im.mimeType, data: im.dataBase64 }));
|
|
30593
|
+
return sendAcpPromptViaTransport(transport, sessionCtx, sessionId, prompt, imgs);
|
|
30403
30594
|
},
|
|
30404
30595
|
async cancel() {
|
|
30405
30596
|
cancelPendingPermissionRequests2();
|
|
@@ -31430,7 +31621,8 @@ async function createAcpManager(options) {
|
|
|
31430
31621
|
sessionChangeSummaryFilePaths,
|
|
31431
31622
|
cloudApiBaseUrl,
|
|
31432
31623
|
getCloudAccessToken,
|
|
31433
|
-
e2ee
|
|
31624
|
+
e2ee,
|
|
31625
|
+
attachments
|
|
31434
31626
|
} = opts;
|
|
31435
31627
|
const preferredForPrompt = agentType ?? backendFallbackAgentType ?? null;
|
|
31436
31628
|
pendingCancelRunId = void 0;
|
|
@@ -31501,7 +31693,8 @@ async function createAcpManager(options) {
|
|
|
31501
31693
|
sessionChangeSummaryFilePaths,
|
|
31502
31694
|
cloudApiBaseUrl,
|
|
31503
31695
|
getCloudAccessToken,
|
|
31504
|
-
e2ee
|
|
31696
|
+
e2ee,
|
|
31697
|
+
attachments
|
|
31505
31698
|
});
|
|
31506
31699
|
}
|
|
31507
31700
|
void run().finally(() => {
|
|
@@ -34471,6 +34664,7 @@ function reportGitRepos(getWs, log2) {
|
|
|
34471
34664
|
var API_TO_BRIDGE_MESSAGE_TYPES = [
|
|
34472
34665
|
"auth_token",
|
|
34473
34666
|
"bridge_identified",
|
|
34667
|
+
"ha",
|
|
34474
34668
|
"dev_servers_config",
|
|
34475
34669
|
"server_control",
|
|
34476
34670
|
"agent_config",
|
|
@@ -34499,6 +34693,10 @@ function parseApiToBridgeMessage(data, log2) {
|
|
|
34499
34693
|
}
|
|
34500
34694
|
return null;
|
|
34501
34695
|
}
|
|
34696
|
+
if (t === "ha") {
|
|
34697
|
+
const s = data.s;
|
|
34698
|
+
if (typeof s !== "number" || !Number.isFinite(s)) return null;
|
|
34699
|
+
}
|
|
34502
34700
|
return data;
|
|
34503
34701
|
}
|
|
34504
34702
|
|
|
@@ -34539,6 +34737,13 @@ var handleBridgeIdentified = (msg, deps) => {
|
|
|
34539
34737
|
});
|
|
34540
34738
|
};
|
|
34541
34739
|
|
|
34740
|
+
// src/connection/heartbeat/ack.ts
|
|
34741
|
+
var handleBridgeHeartbeatAck = (msg, deps) => {
|
|
34742
|
+
const raw = msg.s;
|
|
34743
|
+
if (typeof raw !== "number" || !Number.isFinite(raw)) return;
|
|
34744
|
+
deps.onBridgeHeartbeatAck?.(Math.trunc(raw));
|
|
34745
|
+
};
|
|
34746
|
+
|
|
34542
34747
|
// src/agents/acp/from-bridge/handle-bridge-agent-config.ts
|
|
34543
34748
|
function handleBridgeAgentConfig(msg, { acpManager }) {
|
|
34544
34749
|
if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
|
|
@@ -34705,7 +34910,8 @@ function dispatchLocalPrompt(next, deps) {
|
|
|
34705
34910
|
...Array.isArray(pl.sessionChangeSummaryFilePaths) ? { sessionChangeSummaryFilePaths: pl.sessionChangeSummaryFilePaths } : {},
|
|
34706
34911
|
...Array.isArray(pl.sessionChangeSummaryFileSnapshots) ? { sessionChangeSummaryFileSnapshots: pl.sessionChangeSummaryFileSnapshots } : {},
|
|
34707
34912
|
...typeof pl.agentType === "string" && pl.agentType.trim() ? { agentType: pl.agentType.trim() } : {},
|
|
34708
|
-
...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {}
|
|
34913
|
+
...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {},
|
|
34914
|
+
...Array.isArray(pl.attachments) && pl.attachments.length > 0 ? { attachments: pl.attachments } : {}
|
|
34709
34915
|
};
|
|
34710
34916
|
handleBridgePrompt(msg, deps);
|
|
34711
34917
|
}
|
|
@@ -34916,32 +35122,6 @@ function parseFollowUpFieldsFromPromptMessage(msg) {
|
|
|
34916
35122
|
return { followUpCatalogPromptId, sessionChangeSummaryFilePaths, sessionChangeSummaryFileSnapshots };
|
|
34917
35123
|
}
|
|
34918
35124
|
|
|
34919
|
-
// ../e2ee/src/constants.ts
|
|
34920
|
-
var E2EE_NONCE_BYTES = 12;
|
|
34921
|
-
|
|
34922
|
-
// ../e2ee/src/types.ts
|
|
34923
|
-
function isE2eeEnvelope(value) {
|
|
34924
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
34925
|
-
const o = value;
|
|
34926
|
-
return typeof o.k === "string" && typeof o.n === "string" && typeof o.c === "string";
|
|
34927
|
-
}
|
|
34928
|
-
|
|
34929
|
-
// ../e2ee/src/encoding.ts
|
|
34930
|
-
function base64UrlEncode(bytes) {
|
|
34931
|
-
let binary = "";
|
|
34932
|
-
for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
|
|
34933
|
-
const b64 = btoa(binary);
|
|
34934
|
-
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
34935
|
-
}
|
|
34936
|
-
function base64UrlDecode(value) {
|
|
34937
|
-
const b64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
34938
|
-
const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
|
|
34939
|
-
const binary = atob(padded);
|
|
34940
|
-
const out = new Uint8Array(binary.length);
|
|
34941
|
-
for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
|
|
34942
|
-
return out;
|
|
34943
|
-
}
|
|
34944
|
-
|
|
34945
35125
|
// src/agents/acp/change-summary/decrypt-change-summary-file-input.ts
|
|
34946
35126
|
function decryptChangeSummaryFileInput(row, e2ee) {
|
|
34947
35127
|
if (!e2ee) return row;
|
|
@@ -34998,15 +35178,30 @@ function resolveChangeSummaryPromptForAgent(params) {
|
|
|
34998
35178
|
}
|
|
34999
35179
|
|
|
35000
35180
|
// src/agents/acp/from-bridge/handle-bridge-prompt.ts
|
|
35181
|
+
function parseBridgeAttachments(msg) {
|
|
35182
|
+
const raw = msg.attachments;
|
|
35183
|
+
if (!Array.isArray(raw)) return [];
|
|
35184
|
+
const out = [];
|
|
35185
|
+
for (const x of raw) {
|
|
35186
|
+
if (x === null || typeof x !== "object" || Array.isArray(x)) continue;
|
|
35187
|
+
const o = x;
|
|
35188
|
+
const id = typeof o.attachmentId === "string" ? o.attachmentId.trim() : "";
|
|
35189
|
+
if (!id) continue;
|
|
35190
|
+
const mt = typeof o.mimeType === "string" && o.mimeType.trim() ? o.mimeType.trim() : "application/octet-stream";
|
|
35191
|
+
out.push({ attachmentId: id, mimeType: mt });
|
|
35192
|
+
}
|
|
35193
|
+
return out;
|
|
35194
|
+
}
|
|
35001
35195
|
function handleBridgePrompt(msg, deps) {
|
|
35002
35196
|
const { getWs, log: log2, acpManager, sessionWorktreeManager } = deps;
|
|
35003
35197
|
const rawPrompt = msg.prompt;
|
|
35004
35198
|
const promptText = typeof rawPrompt === "string" ? rawPrompt : rawPrompt != null ? String(rawPrompt) : "";
|
|
35199
|
+
const attachments = parseBridgeAttachments(msg);
|
|
35005
35200
|
const sessionId = msg.sessionId;
|
|
35006
35201
|
const runId = typeof msg.runId === "string" ? msg.runId : void 0;
|
|
35007
35202
|
const promptId = typeof msg.id === "string" ? msg.id : void 0;
|
|
35008
35203
|
const { sendBridgeMessage, sendResult: sendResult2, sendSessionUpdate } = createBridgePromptSenders(deps, getWs);
|
|
35009
|
-
if (!promptText.trim()) {
|
|
35204
|
+
if (!promptText.trim() && attachments.length === 0) {
|
|
35010
35205
|
log2(
|
|
35011
35206
|
`[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).`
|
|
35012
35207
|
);
|
|
@@ -35073,7 +35268,8 @@ function handleBridgePrompt(msg, deps) {
|
|
|
35073
35268
|
sessionChangeSummaryFilePaths: pathsForUpload,
|
|
35074
35269
|
cloudApiBaseUrl: deps.cloudApiBaseUrl,
|
|
35075
35270
|
getCloudAccessToken: deps.getCloudAccessToken,
|
|
35076
|
-
e2ee: deps.e2ee
|
|
35271
|
+
e2ee: deps.e2ee,
|
|
35272
|
+
...attachments.length > 0 ? { attachments } : {}
|
|
35077
35273
|
});
|
|
35078
35274
|
}
|
|
35079
35275
|
void sessionWorktreeManager.resolveSessionParentPathForPrompt(sessionId, { isNewSession, sessionParent, sessionParentPath }).then((cwd) => preambleAndPrompt(cwd)).catch((err) => {
|
|
@@ -35717,6 +35913,9 @@ function dispatchBridgeMessage(msg, deps) {
|
|
|
35717
35913
|
case "bridge_identified":
|
|
35718
35914
|
handleBridgeIdentified(msg, deps);
|
|
35719
35915
|
break;
|
|
35916
|
+
case "ha":
|
|
35917
|
+
handleBridgeHeartbeatAck(msg, deps);
|
|
35918
|
+
break;
|
|
35720
35919
|
case "dev_servers_config":
|
|
35721
35920
|
handleDevServersConfig(msg, deps);
|
|
35722
35921
|
break;
|
|
@@ -35772,9 +35971,17 @@ function dispatchBridgeMessage(msg, deps) {
|
|
|
35772
35971
|
}
|
|
35773
35972
|
|
|
35774
35973
|
// src/routing/handle-bridge-message.ts
|
|
35974
|
+
function normalizeInboundBridgeWebSocketJson(data) {
|
|
35975
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) return data;
|
|
35976
|
+
const o = data;
|
|
35977
|
+
if (o.t === "ha" && typeof o.s === "number" && Number.isFinite(o.s)) {
|
|
35978
|
+
return { type: "ha", s: Math.trunc(o.s) };
|
|
35979
|
+
}
|
|
35980
|
+
return data;
|
|
35981
|
+
}
|
|
35775
35982
|
function handleBridgeMessage(data, deps) {
|
|
35776
35983
|
if (!deps.getWs()) return;
|
|
35777
|
-
const msg = parseApiToBridgeMessage(data, deps.log);
|
|
35984
|
+
const msg = parseApiToBridgeMessage(normalizeInboundBridgeWebSocketJson(data), deps.log);
|
|
35778
35985
|
if (!msg) return;
|
|
35779
35986
|
setImmediate(() => {
|
|
35780
35987
|
dispatchBridgeMessage(msg, deps);
|
|
@@ -35822,7 +36029,8 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
35822
36029
|
persistTokens,
|
|
35823
36030
|
onAuthInvalid,
|
|
35824
36031
|
e2ee,
|
|
35825
|
-
identifyReportedPaths
|
|
36032
|
+
identifyReportedPaths,
|
|
36033
|
+
bridgeHeartbeat
|
|
35826
36034
|
} = params;
|
|
35827
36035
|
let authRefreshInFlight = false;
|
|
35828
36036
|
function handleOpen() {
|
|
@@ -35854,6 +36062,7 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
35854
36062
|
}
|
|
35855
36063
|
}
|
|
35856
36064
|
function handleClose(code, reason) {
|
|
36065
|
+
bridgeHeartbeat?.stop();
|
|
35857
36066
|
try {
|
|
35858
36067
|
const was = state.currentWs;
|
|
35859
36068
|
state.currentWs = null;
|
|
@@ -35888,6 +36097,7 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
35888
36097
|
} catch {
|
|
35889
36098
|
}
|
|
35890
36099
|
}
|
|
36100
|
+
bridgeHeartbeat?.stop();
|
|
35891
36101
|
const prev = state.currentWs;
|
|
35892
36102
|
if (prev) {
|
|
35893
36103
|
prev.removeAllListeners();
|
|
@@ -36043,6 +36253,103 @@ function createCliE2eeRuntime(key) {
|
|
|
36043
36253
|
const merged = { ...message, ...parsed };
|
|
36044
36254
|
delete merged.ee;
|
|
36045
36255
|
return merged;
|
|
36256
|
+
},
|
|
36257
|
+
decryptEnvelopeToBuffer(envelope) {
|
|
36258
|
+
if (envelope.k !== key.id) throw new Error(`E2EE key mismatch: ${envelope.k}`);
|
|
36259
|
+
const sealed = Buffer.from(base64UrlDecode(envelope.c));
|
|
36260
|
+
if (sealed.length < 16) throw new Error("Invalid E2EE payload.");
|
|
36261
|
+
const ciphertext = sealed.subarray(0, sealed.length - 16);
|
|
36262
|
+
const tag = sealed.subarray(sealed.length - 16);
|
|
36263
|
+
const nonce = Buffer.from(base64UrlDecode(envelope.n));
|
|
36264
|
+
const decipher = createDecipheriv("aes-256-gcm", rawKey, nonce);
|
|
36265
|
+
decipher.setAuthTag(tag);
|
|
36266
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
36267
|
+
}
|
|
36268
|
+
};
|
|
36269
|
+
}
|
|
36270
|
+
|
|
36271
|
+
// src/connection/heartbeat/controller.ts
|
|
36272
|
+
function meanRttMs(samples) {
|
|
36273
|
+
if (samples.length === 0) return void 0;
|
|
36274
|
+
let sum = 0;
|
|
36275
|
+
for (const x of samples) sum += x;
|
|
36276
|
+
return sum / samples.length;
|
|
36277
|
+
}
|
|
36278
|
+
function createBridgeHeartbeatController(params) {
|
|
36279
|
+
const { getWs, log: log2 } = params;
|
|
36280
|
+
let interval = null;
|
|
36281
|
+
let seqCursor = -1;
|
|
36282
|
+
let awaitingSeq = null;
|
|
36283
|
+
let sentAtMs = 0;
|
|
36284
|
+
let missed = 0;
|
|
36285
|
+
const rttSamples = [];
|
|
36286
|
+
function clearTimer() {
|
|
36287
|
+
if (interval != null) {
|
|
36288
|
+
clearInterval(interval);
|
|
36289
|
+
interval = null;
|
|
36290
|
+
}
|
|
36291
|
+
}
|
|
36292
|
+
function nextSeq() {
|
|
36293
|
+
seqCursor = seqCursor >= BRIDGE_HEARTBEAT_SEQ_MAX ? 0 : seqCursor + 1;
|
|
36294
|
+
return seqCursor;
|
|
36295
|
+
}
|
|
36296
|
+
function tick() {
|
|
36297
|
+
const ws = getWs();
|
|
36298
|
+
if (!ws || ws.readyState !== wrapper_default.OPEN) return;
|
|
36299
|
+
if (awaitingSeq !== null) {
|
|
36300
|
+
missed++;
|
|
36301
|
+
if (missed >= BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT) {
|
|
36302
|
+
try {
|
|
36303
|
+
log2("[Bridge service] Heartbeat missed repeatedly; reconnecting\u2026");
|
|
36304
|
+
} catch {
|
|
36305
|
+
}
|
|
36306
|
+
clearTimer();
|
|
36307
|
+
awaitingSeq = null;
|
|
36308
|
+
missed = 0;
|
|
36309
|
+
rttSamples.length = 0;
|
|
36310
|
+
safeCloseWebSocket(ws);
|
|
36311
|
+
return;
|
|
36312
|
+
}
|
|
36313
|
+
}
|
|
36314
|
+
const seq = nextSeq();
|
|
36315
|
+
const mean = meanRttMs(rttSamples);
|
|
36316
|
+
const payload = mean !== void 0 && Number.isFinite(mean) ? { t: "h", s: seq, m: Math.round(mean) } : { t: "h", s: seq };
|
|
36317
|
+
sendWsMessage(ws, payload);
|
|
36318
|
+
awaitingSeq = seq;
|
|
36319
|
+
sentAtMs = Date.now();
|
|
36320
|
+
}
|
|
36321
|
+
return {
|
|
36322
|
+
start() {
|
|
36323
|
+
clearTimer();
|
|
36324
|
+
awaitingSeq = null;
|
|
36325
|
+
missed = 0;
|
|
36326
|
+
seqCursor = -1;
|
|
36327
|
+
rttSamples.length = 0;
|
|
36328
|
+
interval = setInterval(tick, BRIDGE_APP_HEARTBEAT_INTERVAL_MS);
|
|
36329
|
+
},
|
|
36330
|
+
stop() {
|
|
36331
|
+
clearTimer();
|
|
36332
|
+
awaitingSeq = null;
|
|
36333
|
+
missed = 0;
|
|
36334
|
+
rttSamples.length = 0;
|
|
36335
|
+
},
|
|
36336
|
+
onAck(seq) {
|
|
36337
|
+
if (awaitingSeq === null) return;
|
|
36338
|
+
if (!Number.isFinite(seq)) return;
|
|
36339
|
+
const ack = Math.trunc(seq);
|
|
36340
|
+
const pending = awaitingSeq;
|
|
36341
|
+
if (ack < pending) return;
|
|
36342
|
+
if (ack === pending) {
|
|
36343
|
+
const rtt = Date.now() - sentAtMs;
|
|
36344
|
+
if (Number.isFinite(rtt) && rtt >= 0 && rtt < 6e5) {
|
|
36345
|
+
rttSamples.push(rtt);
|
|
36346
|
+
if (rttSamples.length > BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX) {
|
|
36347
|
+
rttSamples.splice(0, rttSamples.length - BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX);
|
|
36348
|
+
}
|
|
36349
|
+
}
|
|
36350
|
+
}
|
|
36351
|
+
awaitingSeq = null;
|
|
36352
|
+
missed = 0;
|
|
36046
36353
|
}
|
|
36047
36354
|
};
|
|
36048
36355
|
}
|
|
@@ -36086,13 +36393,18 @@ async function createBridgeConnection(options) {
|
|
|
36086
36393
|
}
|
|
36087
36394
|
const e2ee = options.e2eCertificate ? createCliE2eeRuntime(options.e2eCertificate) : void 0;
|
|
36088
36395
|
const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeRoot, e2ee });
|
|
36089
|
-
const
|
|
36396
|
+
const bridgeHeartbeat = createBridgeHeartbeatController({ getWs, log: logFn });
|
|
36397
|
+
const baseOnBridgeIdentified = createOnBridgeIdentified({
|
|
36090
36398
|
devServerManager,
|
|
36091
36399
|
firehoseServerUrl,
|
|
36092
36400
|
workspaceId,
|
|
36093
36401
|
state,
|
|
36094
36402
|
logFn
|
|
36095
36403
|
});
|
|
36404
|
+
const onBridgeIdentified = (msg) => {
|
|
36405
|
+
baseOnBridgeIdentified(msg);
|
|
36406
|
+
bridgeHeartbeat.start();
|
|
36407
|
+
};
|
|
36096
36408
|
const sendLocalSkillsReport = createSendLocalSkillsReport(getWs, logFn);
|
|
36097
36409
|
const reportAutoDetectedAgents = createReportAutoDetectedAgents(getWs, logFn);
|
|
36098
36410
|
const messageDeps = {
|
|
@@ -36101,6 +36413,9 @@ async function createBridgeConnection(options) {
|
|
|
36101
36413
|
acpManager,
|
|
36102
36414
|
sessionWorktreeManager,
|
|
36103
36415
|
onBridgeIdentified,
|
|
36416
|
+
onBridgeHeartbeatAck: (seq) => {
|
|
36417
|
+
bridgeHeartbeat.onAck(seq);
|
|
36418
|
+
},
|
|
36104
36419
|
sendLocalSkillsReport,
|
|
36105
36420
|
reportAutoDetectedAgents,
|
|
36106
36421
|
devServerManager,
|
|
@@ -36124,13 +36439,15 @@ async function createBridgeConnection(options) {
|
|
|
36124
36439
|
persistTokens,
|
|
36125
36440
|
onAuthInvalid,
|
|
36126
36441
|
e2ee,
|
|
36127
|
-
identifyReportedPaths
|
|
36442
|
+
identifyReportedPaths,
|
|
36443
|
+
bridgeHeartbeat
|
|
36128
36444
|
});
|
|
36129
36445
|
connect();
|
|
36130
36446
|
const stopFileIndexWatcher = startFileIndexWatcher(getBridgeRoot());
|
|
36131
36447
|
return {
|
|
36132
36448
|
close: async () => {
|
|
36133
36449
|
stopFileIndexWatcher();
|
|
36450
|
+
bridgeHeartbeat.stop();
|
|
36134
36451
|
await closeBridgeConnection(state, acpManager, devServerManager, logFn);
|
|
36135
36452
|
}
|
|
36136
36453
|
};
|