@buildautomaton/cli 0.1.26 → 0.1.27

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 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.26".length > 0 ? "0.1.26" : "0.0.0-dev";
25067
+ var CLI_VERSION = "0.1.27".length > 0 ? "0.1.27" : "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
  }
@@ -32252,6 +32263,96 @@ function augmentPromptResultAuthFields(agentType, errorText) {
32252
32263
  return { agentAuthRequired: true, agentType };
32253
32264
  }
32254
32265
 
32266
+ // src/agents/acp/fetch-session-attachments.ts
32267
+ function metaSaysEncrypted(meta) {
32268
+ if (!meta) return false;
32269
+ const e = meta.encrypted;
32270
+ return e === true || e === "true" || e === 1;
32271
+ }
32272
+ function warnIfDecodedImageMagicUnexpected(buf, mimeType, idShort) {
32273
+ const m = mimeType.toLowerCase();
32274
+ if (!m.startsWith("image/") || buf.length < 4) return;
32275
+ const b0 = buf[0];
32276
+ const b1 = buf[1];
32277
+ const b2 = buf[2];
32278
+ const b3 = buf[3];
32279
+ let looksOk = false;
32280
+ if (m.includes("png") && b0 === 137 && b1 === 80 && b2 === 78 && b3 === 71) looksOk = true;
32281
+ else if ((m.includes("jpeg") || m.includes("jpg")) && b0 === 255 && b1 === 216 && b2 === 255) looksOk = true;
32282
+ else if (m.includes("gif") && b0 === 71 && b1 === 73 && b2 === 70) looksOk = true;
32283
+ else if (m.includes("webp") && buf.length >= 12 && buf.subarray(0, 4).toString("ascii") === "RIFF" && buf.subarray(8, 12).toString("ascii") === "WEBP")
32284
+ looksOk = true;
32285
+ else if (!/(png|jpe?g|gif|webp)/i.test(m)) looksOk = true;
32286
+ if (!looksOk) {
32287
+ logDebug(
32288
+ `[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")}`
32289
+ );
32290
+ }
32291
+ }
32292
+ async function fetchSessionAttachmentPayloadsForAgent(params) {
32293
+ const { attachments, sessionId, cloudApiBaseUrl, getCloudAccessToken, e2ee, log: log2 } = params;
32294
+ const token = getCloudAccessToken();
32295
+ if (!token) {
32296
+ return { ok: false, error: "Missing cloud access token; cannot download attachments." };
32297
+ }
32298
+ const wantedCount = attachments.filter((a) => typeof a.attachmentId === "string" && a.attachmentId.trim() !== "").length;
32299
+ if (wantedCount === 0) {
32300
+ return { ok: false, error: "No valid attachment ids in prompt." };
32301
+ }
32302
+ const base = cloudApiBaseUrl.replace(/\/+$/, "");
32303
+ const out = [];
32304
+ for (const a of attachments) {
32305
+ const id = typeof a.attachmentId === "string" ? a.attachmentId.trim() : "";
32306
+ if (!id) continue;
32307
+ const metaUrl = `${base}/api/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/meta`;
32308
+ const blobUrl = `${base}/api/sessions/${encodeURIComponent(sessionId)}/attachments/${encodeURIComponent(id)}/blob`;
32309
+ const metaRes = await fetch(metaUrl, { headers: { Authorization: `Bearer ${token}` } });
32310
+ if (!metaRes.ok) {
32311
+ const t = await metaRes.text().catch(() => "");
32312
+ log2(`[Agent] Attachment meta fetch failed ${metaRes.status}: ${t.slice(0, 200)}`);
32313
+ return { ok: false, error: `Could not load attachment (${id.slice(0, 8)}\u2026): ${metaRes.status}` };
32314
+ }
32315
+ const meta = await metaRes.json().catch(() => null);
32316
+ const mimeType = typeof meta?.mimeType === "string" && meta.mimeType.trim() ? meta.mimeType.trim() : a.mimeType;
32317
+ const blobRes = await fetch(blobUrl, { headers: { Authorization: `Bearer ${token}` } });
32318
+ if (!blobRes.ok) {
32319
+ const t = await blobRes.text().catch(() => "");
32320
+ log2(`[Agent] Attachment blob fetch failed ${blobRes.status}: ${t.slice(0, 200)}`);
32321
+ return { ok: false, error: `Could not load attachment data (${id.slice(0, 8)}\u2026): ${blobRes.status}` };
32322
+ }
32323
+ const buf = Buffer.from(await blobRes.arrayBuffer());
32324
+ let imageBytes;
32325
+ const encrypted = metaSaysEncrypted(meta);
32326
+ if (encrypted) {
32327
+ if (!e2ee) {
32328
+ return { ok: false, error: "Encrypted attachments require E2EE keys on this bridge." };
32329
+ }
32330
+ const k = typeof meta?.k === "string" ? meta.k : "";
32331
+ const n = typeof meta?.n === "string" ? meta.n : "";
32332
+ if (!k || !n) {
32333
+ return { ok: false, error: "Invalid encrypted attachment metadata (missing key id or nonce)." };
32334
+ }
32335
+ const c = base64UrlEncode(buf);
32336
+ imageBytes = e2ee.decryptEnvelopeToBuffer({ k, n, c });
32337
+ } else {
32338
+ imageBytes = buf;
32339
+ }
32340
+ warnIfDecodedImageMagicUnexpected(imageBytes, mimeType, id.slice(0, 8));
32341
+ logDebug(
32342
+ `[Agent] Loaded prompt image ${id.slice(0, 8)}\u2026: ${imageBytes.length} bytes, mime=${mimeType}, ${encrypted ? "E2EE decrypted" : "plaintext from storage"}`
32343
+ );
32344
+ const dataBase64 = imageBytes.toString("base64");
32345
+ out.push({ mimeType, dataBase64 });
32346
+ }
32347
+ if (out.length !== wantedCount) {
32348
+ return {
32349
+ ok: false,
32350
+ error: `Expected ${wantedCount} image attachment(s) but only loaded ${out.length} (check attachment ids and empty rows).`
32351
+ };
32352
+ }
32353
+ return { ok: true, images: out };
32354
+ }
32355
+
32255
32356
  // src/agents/acp/send-prompt-to-agent.ts
32256
32357
  async function sendPromptToAgent(options) {
32257
32358
  const {
@@ -32269,10 +32370,49 @@ async function sendPromptToAgent(options) {
32269
32370
  sessionChangeSummaryFilePaths,
32270
32371
  cloudApiBaseUrl,
32271
32372
  getCloudAccessToken,
32272
- e2ee
32373
+ e2ee,
32374
+ attachments
32273
32375
  } = options;
32274
32376
  try {
32275
- const result = await handle.sendPrompt(promptText, {});
32377
+ let sendOpts = {};
32378
+ if (attachments && attachments.length > 0) {
32379
+ if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
32380
+ sendResult2({
32381
+ type: "prompt_result",
32382
+ id: promptId,
32383
+ ...sessionId ? { sessionId } : {},
32384
+ ...runId ? { runId } : {},
32385
+ success: false,
32386
+ error: "Prompt includes images but the bridge is not configured with a cloud API URL and token to fetch them.",
32387
+ ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
32388
+ ...augmentPromptResultAuthFields(agentType, "missing cloud for images download")
32389
+ });
32390
+ return;
32391
+ }
32392
+ const resolved = await fetchSessionAttachmentPayloadsForAgent({
32393
+ attachments,
32394
+ sessionId,
32395
+ cloudApiBaseUrl,
32396
+ getCloudAccessToken,
32397
+ e2ee,
32398
+ log: log2
32399
+ });
32400
+ if (!resolved.ok) {
32401
+ sendResult2({
32402
+ type: "prompt_result",
32403
+ id: promptId,
32404
+ ...sessionId ? { sessionId } : {},
32405
+ ...runId ? { runId } : {},
32406
+ success: false,
32407
+ error: resolved.error,
32408
+ ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
32409
+ ...augmentPromptResultAuthFields(agentType, resolved.error)
32410
+ });
32411
+ return;
32412
+ }
32413
+ sendOpts = { images: resolved.images };
32414
+ }
32415
+ const result = await handle.sendPrompt(promptText, sendOpts);
32276
32416
  if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
32277
32417
  await collectTurnGitDiffFromPreTurnSnapshot({
32278
32418
  sessionId,
@@ -32714,13 +32854,16 @@ function parseAcpInitAgentCapabilities(initResult) {
32714
32854
  const canLoad = agentCapabilities?.loadSession === true;
32715
32855
  const sessionCaps = agentCapabilities?.sessionCapabilities;
32716
32856
  const canResume = Boolean(sessionCaps?.resume);
32717
- return { canResume, canLoad };
32857
+ const promptCaps = agentCapabilities?.promptCapabilities;
32858
+ const promptSupportsImage = promptCaps?.image === true;
32859
+ return { canResume, canLoad, promptSupportsImage };
32718
32860
  }
32719
32861
 
32720
32862
  // src/agents/acp/clients/shared/bootstrap-acp-wire-session.ts
32721
32863
  async function bootstrapAcpWireSession(transport, ctx, initializeRequest) {
32722
32864
  const initResult = await transport.initialize(initializeRequest);
32723
- const { canResume, canLoad } = parseAcpInitAgentCapabilities(initResult);
32865
+ const { canResume, canLoad, promptSupportsImage } = parseAcpInitAgentCapabilities(initResult);
32866
+ ctx.agentPromptImageSupported = promptSupportsImage;
32724
32867
  await transport.afterInitialize?.();
32725
32868
  const established = await establishAcpSessionWithTransport(transport, ctx, canResume, canLoad);
32726
32869
  const sessionId = established.sessionId;
@@ -32811,11 +32954,27 @@ function normalizeAcpPromptTurnFailure(err, stderrCaptureText) {
32811
32954
  }
32812
32955
 
32813
32956
  // src/agents/acp/clients/shared/send-acp-prompt-via-transport.ts
32814
- async function sendAcpPromptViaTransport(transport, ctx, sessionId, promptText) {
32957
+ async function sendAcpPromptViaTransport(transport, ctx, sessionId, promptText, images) {
32958
+ if (images && images.length > 0 && !ctx.agentPromptImageSupported) {
32959
+ await new Promise((r) => setImmediate(r));
32960
+ return normalizeAcpPromptTurnFailure(
32961
+ new Error(
32962
+ "This agent does not advertise image support in ACP (missing agentCapabilities.promptCapabilities.image). Remove images or use an agent that supports prompt images."
32963
+ ),
32964
+ ctx.getStderrText()
32965
+ );
32966
+ }
32967
+ const textBlock = promptText.trim() !== "" ? promptText : images?.length ? " " : "";
32968
+ const prompt = [{ type: "text", text: textBlock }];
32969
+ if (images && images.length > 0) {
32970
+ for (const im of images) {
32971
+ prompt.push({ type: "image", mimeType: im.mimeType, data: im.data });
32972
+ }
32973
+ }
32815
32974
  try {
32816
32975
  const response = await transport.prompt({
32817
32976
  sessionId,
32818
- prompt: [{ type: "text", text: promptText }]
32977
+ prompt
32819
32978
  });
32820
32979
  await new Promise((r2) => setImmediate(r2));
32821
32980
  const r = response;
@@ -32991,15 +33150,17 @@ async function createSdkStdioAcpClient(options) {
32991
33150
  const established = await bootstrapAcpWireSession(transport, sessionCtx, {
32992
33151
  protocolVersion: PROTOCOL_VERSION2,
32993
33152
  clientCapabilities: {
32994
- fs: { readTextFile: true, writeTextFile: true }
33153
+ fs: { readTextFile: true, writeTextFile: true },
33154
+ prompt: { image: true }
32995
33155
  },
32996
33156
  clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
32997
33157
  });
32998
33158
  const sessionId = established.sessionId;
32999
33159
  settleResolve({
33000
33160
  sessionId,
33001
- async sendPrompt(prompt, _options) {
33002
- return sendAcpPromptViaTransport(transport, sessionCtx, sessionId, prompt);
33161
+ async sendPrompt(prompt, options2) {
33162
+ const imgs = options2?.images?.map((im) => ({ type: "image", mimeType: im.mimeType, data: im.dataBase64 }));
33163
+ return sendAcpPromptViaTransport(transport, sessionCtx, sessionId, prompt, imgs);
33003
33164
  },
33004
33165
  async cancel() {
33005
33166
  for (const [id, entry] of [...pendingPermissionReplies.entries()]) {
@@ -33367,14 +33528,19 @@ async function createCursorAcpClient(options) {
33367
33528
  });
33368
33529
  const established = await bootstrapAcpWireSession(transport, sessionCtx, {
33369
33530
  protocolVersion: 1,
33370
- clientCapabilities: { fs: { readTextFile: true, writeTextFile: true }, terminal: false },
33531
+ clientCapabilities: {
33532
+ fs: { readTextFile: true, writeTextFile: true },
33533
+ terminal: false,
33534
+ prompt: { image: true }
33535
+ },
33371
33536
  clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
33372
33537
  });
33373
33538
  const sessionId = established.sessionId;
33374
33539
  resolve18({
33375
33540
  sessionId,
33376
- async sendPrompt(prompt, _options) {
33377
- return sendAcpPromptViaTransport(transport, sessionCtx, sessionId, prompt);
33541
+ async sendPrompt(prompt, options2) {
33542
+ const imgs = options2?.images?.map((im) => ({ type: "image", mimeType: im.mimeType, data: im.dataBase64 }));
33543
+ return sendAcpPromptViaTransport(transport, sessionCtx, sessionId, prompt, imgs);
33378
33544
  },
33379
33545
  async cancel() {
33380
33546
  cancelPendingPermissionRequests2();
@@ -34405,7 +34571,8 @@ async function createAcpManager(options) {
34405
34571
  sessionChangeSummaryFilePaths,
34406
34572
  cloudApiBaseUrl,
34407
34573
  getCloudAccessToken,
34408
- e2ee
34574
+ e2ee,
34575
+ attachments
34409
34576
  } = opts;
34410
34577
  const preferredForPrompt = agentType ?? backendFallbackAgentType ?? null;
34411
34578
  pendingCancelRunId = void 0;
@@ -34476,7 +34643,8 @@ async function createAcpManager(options) {
34476
34643
  sessionChangeSummaryFilePaths,
34477
34644
  cloudApiBaseUrl,
34478
34645
  getCloudAccessToken,
34479
- e2ee
34646
+ e2ee,
34647
+ attachments
34480
34648
  });
34481
34649
  }
34482
34650
  void run().finally(() => {
@@ -37848,7 +38016,8 @@ function dispatchLocalPrompt(next, deps) {
37848
38016
  ...Array.isArray(pl.sessionChangeSummaryFilePaths) ? { sessionChangeSummaryFilePaths: pl.sessionChangeSummaryFilePaths } : {},
37849
38017
  ...Array.isArray(pl.sessionChangeSummaryFileSnapshots) ? { sessionChangeSummaryFileSnapshots: pl.sessionChangeSummaryFileSnapshots } : {},
37850
38018
  ...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 } : {}
38019
+ ...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {},
38020
+ ...Array.isArray(pl.attachments) && pl.attachments.length > 0 ? { attachments: pl.attachments } : {}
37852
38021
  };
37853
38022
  handleBridgePrompt(msg, deps);
37854
38023
  }
@@ -38115,15 +38284,30 @@ function resolveChangeSummaryPromptForAgent(params) {
38115
38284
  }
38116
38285
 
38117
38286
  // src/agents/acp/from-bridge/handle-bridge-prompt.ts
38287
+ function parseBridgeAttachments(msg) {
38288
+ const raw = msg.attachments;
38289
+ if (!Array.isArray(raw)) return [];
38290
+ const out = [];
38291
+ for (const x of raw) {
38292
+ if (x === null || typeof x !== "object" || Array.isArray(x)) continue;
38293
+ const o = x;
38294
+ const id = typeof o.attachmentId === "string" ? o.attachmentId.trim() : "";
38295
+ if (!id) continue;
38296
+ const mt = typeof o.mimeType === "string" && o.mimeType.trim() ? o.mimeType.trim() : "application/octet-stream";
38297
+ out.push({ attachmentId: id, mimeType: mt });
38298
+ }
38299
+ return out;
38300
+ }
38118
38301
  function handleBridgePrompt(msg, deps) {
38119
38302
  const { getWs, log: log2, acpManager, sessionWorktreeManager } = deps;
38120
38303
  const rawPrompt = msg.prompt;
38121
38304
  const promptText = typeof rawPrompt === "string" ? rawPrompt : rawPrompt != null ? String(rawPrompt) : "";
38305
+ const attachments = parseBridgeAttachments(msg);
38122
38306
  const sessionId = msg.sessionId;
38123
38307
  const runId = typeof msg.runId === "string" ? msg.runId : void 0;
38124
38308
  const promptId = typeof msg.id === "string" ? msg.id : void 0;
38125
38309
  const { sendBridgeMessage, sendResult: sendResult2, sendSessionUpdate } = createBridgePromptSenders(deps, getWs);
38126
- if (!promptText.trim()) {
38310
+ if (!promptText.trim() && attachments.length === 0) {
38127
38311
  log2(
38128
38312
  `[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
38313
  );
@@ -38190,7 +38374,8 @@ function handleBridgePrompt(msg, deps) {
38190
38374
  sessionChangeSummaryFilePaths: pathsForUpload,
38191
38375
  cloudApiBaseUrl: deps.cloudApiBaseUrl,
38192
38376
  getCloudAccessToken: deps.getCloudAccessToken,
38193
- e2ee: deps.e2ee
38377
+ e2ee: deps.e2ee,
38378
+ ...attachments.length > 0 ? { attachments } : {}
38194
38379
  });
38195
38380
  }
38196
38381
  void sessionWorktreeManager.resolveSessionParentPathForPrompt(sessionId, { isNewSession, sessionParent, sessionParentPath }).then((cwd) => preambleAndPrompt(cwd)).catch((err) => {