@buildautomaton/cli 0.1.15 → 0.1.17

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/index.js CHANGED
@@ -2240,7 +2240,7 @@ var require_websocket = __commonJS({
2240
2240
  var http = __require("http");
2241
2241
  var net = __require("net");
2242
2242
  var tls = __require("tls");
2243
- var { randomBytes, createHash: createHash2 } = __require("crypto");
2243
+ var { randomBytes: randomBytes2, createHash: createHash2 } = __require("crypto");
2244
2244
  var { Duplex, Readable: Readable2 } = __require("stream");
2245
2245
  var { URL: URL2 } = __require("url");
2246
2246
  var PerMessageDeflate = require_permessage_deflate();
@@ -2770,7 +2770,7 @@ var require_websocket = __commonJS({
2770
2770
  }
2771
2771
  }
2772
2772
  const defaultPort = isSecure ? 443 : 80;
2773
- const key = randomBytes(16).toString("base64");
2773
+ const key = randomBytes2(16).toString("base64");
2774
2774
  const request = isSecure ? https2.request : http.request;
2775
2775
  const protocolSet = /* @__PURE__ */ new Set();
2776
2776
  let perMessageDeflate;
@@ -22787,8 +22787,8 @@ var PREVIEW_API_BASE_PATH = "/__preview";
22787
22787
  var PREVIEW_SECRET_HEADER = "X-Preview-Secret";
22788
22788
  var DEFAULT_PORT = 3e3;
22789
22789
  var DEFAULT_COMMAND = "npm run preview";
22790
- var PREVIEW_COMMAND_ENV = "BUILDAMATON_PREVIEW_COMMAND";
22791
- var PREVIEW_PORT_ENV = "BUILDAMATON_PREVIEW_PORT";
22790
+ var PREVIEW_COMMAND_ENV = "BUILDAUTOMATON_PREVIEW_COMMAND";
22791
+ var PREVIEW_PORT_ENV = "BUILDAUTOMATON_PREVIEW_PORT";
22792
22792
  var previewProcess = null;
22793
22793
  var previewPort = DEFAULT_PORT;
22794
22794
  var previewSecret = "";
@@ -22841,7 +22841,7 @@ var OPERATIONS = [
22841
22841
  var previewSkill = {
22842
22842
  id: "preview",
22843
22843
  name: "Preview",
22844
- description: "Start and manage a local preview server that implements the BuildAutomaton Preview Server API. Configure the command with BUILDAMATON_PREVIEW_COMMAND (default: npm run preview). The server receives PORT and PREVIEW_SECRET and must expose /__preview/status and /__preview/stop.",
22844
+ description: "Start and manage a local preview server that implements the BuildAutomaton Preview Server API. Configure the command with BUILDAUTOMATON_PREVIEW_COMMAND (default: npm run preview). The server receives PORT and PREVIEW_SECRET and must expose /__preview/status and /__preview/stop.",
22845
22845
  operations: OPERATIONS,
22846
22846
  async execute(operationId, params) {
22847
22847
  const command = getPreviewCommand();
@@ -23564,8 +23564,11 @@ function isLocalApiUrl(apiUrl) {
23564
23564
  return false;
23565
23565
  }
23566
23566
  }
23567
+ function appUrlForApiUrl(apiUrl) {
23568
+ return apiUrl && isLocalApiUrl(apiUrl) ? process.env.BUILDAUTOMATON_APP_URL ?? "http://localhost:3000" : process.env.BUILDAUTOMATON_APP_URL ?? "https://app.buildautomaton.com";
23569
+ }
23567
23570
  async function openBrowser(connectionId, initialWorkspaceId, preferredBridgeName, apiUrl, logFn = log) {
23568
- const appUrl = apiUrl && isLocalApiUrl(apiUrl) ? process.env.BUILDAUTOMATON_APP_URL ?? "http://localhost:3000" : process.env.BUILDAUTOMATON_APP_URL ?? "https://app.buildautomaton.com";
23571
+ const appUrl = appUrlForApiUrl(apiUrl);
23569
23572
  let connectCliUrl = `${appUrl.replace(/\/$/, "")}/bridges/connect?connectionId=${connectionId}`;
23570
23573
  if (initialWorkspaceId) {
23571
23574
  try {
@@ -28773,6 +28776,498 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
28773
28776
  }
28774
28777
  }
28775
28778
 
28779
+ // ../types/dist/index.js
28780
+ init_zod();
28781
+ init_zod();
28782
+ init_zod();
28783
+ init_zod();
28784
+ init_zod();
28785
+ init_zod();
28786
+ init_zod();
28787
+ init_zod();
28788
+ init_zod();
28789
+ init_zod();
28790
+ init_zod();
28791
+ init_zod();
28792
+ var WorkItemStatusSchema = external_exports.enum(["backlog", "in-progress", "completed"]);
28793
+ var WorkItemProgressSchema = external_exports.object({
28794
+ remainingCriteria: external_exports.array(external_exports.string()).default([]),
28795
+ openQuestions: external_exports.array(external_exports.string()).default([]),
28796
+ assignedTo: external_exports.enum(["agent", "human-product", "human-expert"]).optional()
28797
+ });
28798
+ var ChangeSchema = external_exports.object({
28799
+ id: external_exports.string(),
28800
+ description: external_exports.string(),
28801
+ buildingBlockId: external_exports.string(),
28802
+ buildingBlockType: external_exports.enum(["function", "workflow", "connector", "ui-component", "app-fragment", "application", "project"]),
28803
+ action: external_exports.enum(["create", "update", "split", "combine"])
28804
+ });
28805
+ var CompletionCriterionSchema = external_exports.object({
28806
+ id: external_exports.string(),
28807
+ description: external_exports.string(),
28808
+ type: external_exports.enum(["write-code", "write-tests", "verify-tests", "other"]),
28809
+ verified: external_exports.boolean().default(false)
28810
+ });
28811
+ var WorkItemPrioritySchema = external_exports.enum(["low", "medium", "high", "critical"]);
28812
+ var IterationPhaseSchema = external_exports.enum(["analysis", "implementation", "verify", "reprioritize", "completed"]);
28813
+ var WorkItemDependencySchema = external_exports.object({
28814
+ type: external_exports.enum(["work-item"]),
28815
+ id: external_exports.string()
28816
+ });
28817
+ var WorkItemSchema = external_exports.object({
28818
+ id: external_exports.string(),
28819
+ sessionId: external_exports.string().optional(),
28820
+ summary: external_exports.string().optional(),
28821
+ description: external_exports.string(),
28822
+ status: WorkItemStatusSchema,
28823
+ buildingBlockId: external_exports.string().optional(),
28824
+ buildingBlockType: external_exports.enum(["function", "workflow", "connector", "ui-component", "app-fragment", "application", "project"]),
28825
+ changes: external_exports.array(ChangeSchema).default([]),
28826
+ completionCriteria: external_exports.array(CompletionCriterionSchema).default([]),
28827
+ priority: WorkItemPrioritySchema.default("medium"),
28828
+ dependencies: external_exports.array(WorkItemDependencySchema).default([]),
28829
+ assignedToUserId: external_exports.string().optional()
28830
+ });
28831
+ var UserWorkspaceProfileSchema = external_exports.object({
28832
+ id: external_exports.string(),
28833
+ workspaceId: external_exports.string(),
28834
+ userId: external_exports.string(),
28835
+ roleDescription: external_exports.string().optional(),
28836
+ expertiseAreas: external_exports.array(external_exports.string()),
28837
+ preferences: external_exports.record(external_exports.unknown()).optional(),
28838
+ learnings: external_exports.array(external_exports.string())
28839
+ });
28840
+ var WorkspaceOwnerInfoSchema = external_exports.object({
28841
+ ownerId: external_exports.string(),
28842
+ ownerName: external_exports.string().optional(),
28843
+ ownerEmail: external_exports.string().optional(),
28844
+ ownerProfilePictureUrl: external_exports.string().optional()
28845
+ });
28846
+ var WorkspaceRuntimeEntrySchema = external_exports.object({
28847
+ workspaceId: external_exports.string(),
28848
+ path: external_exports.string(),
28849
+ name: external_exports.string().optional(),
28850
+ owner: WorkspaceOwnerInfoSchema.optional(),
28851
+ isOwner: external_exports.boolean().optional()
28852
+ });
28853
+ var ProjectContextSchema = external_exports.object({
28854
+ projectId: external_exports.string(),
28855
+ context: external_exports.record(external_exports.unknown()).default({}),
28856
+ updatedAt: external_exports.string()
28857
+ });
28858
+ var WebSocketMessageTypeSchema = external_exports.enum([
28859
+ "plan-update",
28860
+ "work-item-update",
28861
+ "work-item-added",
28862
+ "work-item-removed",
28863
+ "project-processing-start",
28864
+ "project-processing-update",
28865
+ "project-processing-complete",
28866
+ "project-processing-error",
28867
+ "file-tool-request",
28868
+ "file-tool-response",
28869
+ "file-generated"
28870
+ ]);
28871
+ var WebSocketMessageSchema = external_exports.object({
28872
+ type: WebSocketMessageTypeSchema,
28873
+ contextId: external_exports.string().optional(),
28874
+ data: external_exports.any().optional(),
28875
+ error: external_exports.string().optional()
28876
+ });
28877
+ var CheckpointKindSchema = external_exports.enum(["daily", "weekly", "overall"]);
28878
+ var CheckpointSummarySchema = external_exports.object({
28879
+ id: external_exports.string(),
28880
+ kind: CheckpointKindSchema,
28881
+ /** ISO date for daily (YYYY-MM-DD), ISO week for weekly, null for overall */
28882
+ periodKey: external_exports.string().nullable(),
28883
+ summary: external_exports.string(),
28884
+ createdAt: external_exports.string(),
28885
+ updatedAt: external_exports.string()
28886
+ });
28887
+ var ThreadMetaSchema = external_exports.object({
28888
+ threadId: external_exports.string(),
28889
+ workspaceId: external_exports.string(),
28890
+ /** External source (e.g. slack, discord); null if internal-only */
28891
+ externalSource: external_exports.string().nullable(),
28892
+ /** Id in the external system (e.g. channel_id + thread_ts) */
28893
+ externalId: external_exports.string().nullable(),
28894
+ title: external_exports.string().optional(),
28895
+ createdAt: external_exports.string(),
28896
+ updatedAt: external_exports.string()
28897
+ });
28898
+ var ThreadMessageSchema = external_exports.object({
28899
+ messageId: external_exports.string(),
28900
+ threadId: external_exports.string(),
28901
+ /** Role: user, assistant, system */
28902
+ role: external_exports.enum(["user", "assistant", "system"]),
28903
+ content: external_exports.string(),
28904
+ /** Optional reference to a ContentItem (e.g. doc, Notion page) */
28905
+ contentItemId: external_exports.string().nullable(),
28906
+ /** External message id if synced from external chat */
28907
+ externalId: external_exports.string().nullable(),
28908
+ createdAt: external_exports.string(),
28909
+ updatedAt: external_exports.string()
28910
+ });
28911
+ var ThreadCheckpointSummarySchema = CheckpointSummarySchema.extend({
28912
+ threadId: external_exports.string()
28913
+ });
28914
+ var ContentSourceSchema = external_exports.enum(["notion", "doc", "slack_thread", "other"]);
28915
+ var ContentItemMetaSchema = external_exports.object({
28916
+ contentId: external_exports.string(),
28917
+ workspaceId: external_exports.string(),
28918
+ source: ContentSourceSchema,
28919
+ /** Id in the external system (e.g. Notion page id, doc url) */
28920
+ externalId: external_exports.string(),
28921
+ /** If source is slack_thread, points to Thread DO id */
28922
+ threadId: external_exports.string().nullable(),
28923
+ title: external_exports.string().optional(),
28924
+ createdAt: external_exports.string(),
28925
+ updatedAt: external_exports.string()
28926
+ });
28927
+ var ContentStorageRefSchema = external_exports.object({
28928
+ storageKey: external_exports.string(),
28929
+ /** Optional: mime type or format hint */
28930
+ contentType: external_exports.string().optional()
28931
+ });
28932
+ var ContentCheckpointSummarySchema = CheckpointSummarySchema.extend({
28933
+ contentId: external_exports.string()
28934
+ });
28935
+ var StoryMetaSchema = external_exports.object({
28936
+ storyId: external_exports.string(),
28937
+ workspaceId: external_exports.string(),
28938
+ title: external_exports.string(),
28939
+ /** feature | bug | epic */
28940
+ kind: external_exports.enum(["feature", "bug", "epic"]).default("feature"),
28941
+ createdAt: external_exports.string(),
28942
+ updatedAt: external_exports.string()
28943
+ });
28944
+ var StoryContentItemRefSchema = external_exports.object({
28945
+ id: external_exports.string(),
28946
+ storyId: external_exports.string(),
28947
+ contentItemId: external_exports.string(),
28948
+ /** Snapshot summary when added to story (or updated) */
28949
+ summary: external_exports.string(),
28950
+ orderIndex: external_exports.number().default(0),
28951
+ createdAt: external_exports.string(),
28952
+ updatedAt: external_exports.string()
28953
+ });
28954
+ var StoryCheckpointSummarySchema = CheckpointSummarySchema.extend({
28955
+ storyId: external_exports.string()
28956
+ });
28957
+ var BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID = "__builtin_change_summary__";
28958
+ var SessionMetaSchema = external_exports.object({
28959
+ sessionId: external_exports.string(),
28960
+ workspaceId: external_exports.string(),
28961
+ title: external_exports.string().optional(),
28962
+ createdAt: external_exports.string(),
28963
+ updatedAt: external_exports.string()
28964
+ });
28965
+ var SessionPromptSchema = external_exports.object({
28966
+ id: external_exports.string(),
28967
+ sessionId: external_exports.string(),
28968
+ /** text | resource */
28969
+ type: external_exports.enum(["text", "resource"]).default("text"),
28970
+ text: external_exports.string().optional(),
28971
+ resourceUri: external_exports.string().optional(),
28972
+ createdAt: external_exports.string()
28973
+ });
28974
+ var SessionResponseSchema = external_exports.object({
28975
+ id: external_exports.string(),
28976
+ sessionId: external_exports.string(),
28977
+ promptId: external_exports.string(),
28978
+ /** message | completion */
28979
+ kind: external_exports.enum(["message", "completion"]),
28980
+ content: external_exports.string().optional(),
28981
+ /** For completion: stopReason etc. */
28982
+ stopReason: external_exports.string().optional(),
28983
+ createdAt: external_exports.string()
28984
+ });
28985
+ var SessionToolCallSchema = external_exports.object({
28986
+ id: external_exports.string(),
28987
+ sessionId: external_exports.string(),
28988
+ promptId: external_exports.string(),
28989
+ name: external_exports.string(),
28990
+ params: external_exports.record(external_exports.unknown()).optional(),
28991
+ result: external_exports.record(external_exports.unknown()).optional(),
28992
+ createdAt: external_exports.string()
28993
+ });
28994
+ var SessionThreadRefSchema = external_exports.object({
28995
+ sessionId: external_exports.string(),
28996
+ threadId: external_exports.string(),
28997
+ addedAt: external_exports.string()
28998
+ });
28999
+ function normalizeRepoRelativePath(p) {
29000
+ let t = p.trim().replace(/\\/g, "/");
29001
+ while (t.startsWith("./")) t = t.slice(2);
29002
+ return t.replace(/\/+/g, "/");
29003
+ }
29004
+ function resolveChangeSummaryPathAgainstAllowed(rawPath, allowed) {
29005
+ const trimmed2 = rawPath.trim();
29006
+ if (!trimmed2) return null;
29007
+ if (allowed.has(trimmed2)) return trimmed2;
29008
+ const n = normalizeRepoRelativePath(trimmed2);
29009
+ if (allowed.has(n)) return n;
29010
+ for (const a of allowed) {
29011
+ if (normalizeRepoRelativePath(a) === n) return a;
29012
+ }
29013
+ return null;
29014
+ }
29015
+ function clampSummaryToAtMostTwoLines(summary) {
29016
+ const lines = summary.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
29017
+ return lines.slice(0, 2).join("\n");
29018
+ }
29019
+ function parseChangeSummaryJson(raw, allowedPaths, options) {
29020
+ if (raw == null || raw.trim() === "") return [];
29021
+ let text = raw.trim();
29022
+ const fence = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
29023
+ if (fence?.[1]) text = fence[1].trim();
29024
+ let parsed;
29025
+ try {
29026
+ parsed = JSON.parse(text);
29027
+ } catch {
29028
+ const start = text.indexOf("[");
29029
+ const end = text.lastIndexOf("]");
29030
+ if (start < 0 || end <= start) return [];
29031
+ try {
29032
+ parsed = JSON.parse(text.slice(start, end + 1));
29033
+ } catch {
29034
+ return [];
29035
+ }
29036
+ }
29037
+ const rows = [];
29038
+ let arr = [];
29039
+ if (Array.isArray(parsed)) {
29040
+ arr = parsed;
29041
+ } else if (parsed && typeof parsed === "object" && Array.isArray(parsed.files)) {
29042
+ arr = parsed.files;
29043
+ }
29044
+ const skip = options?.skipPathAllowlist === true;
29045
+ for (const item of arr) {
29046
+ if (!item || typeof item !== "object") continue;
29047
+ const o = item;
29048
+ const rawPath = typeof o.path === "string" ? o.path.trim() : "";
29049
+ const summary = typeof o.summary === "string" ? o.summary.trim() : "";
29050
+ if (!rawPath || !summary) continue;
29051
+ const path32 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
29052
+ if (!path32) continue;
29053
+ rows.push({ path: path32, summary: clampSummaryToAtMostTwoLines(summary) });
29054
+ }
29055
+ return rows;
29056
+ }
29057
+ var PATCH_PREVIEW_MAX = 12e3;
29058
+ function clip(s, max) {
29059
+ if (s.length <= max) return s;
29060
+ return `${s.slice(0, max)}
29061
+
29062
+ \u2026(truncated, ${s.length - max} more characters)`;
29063
+ }
29064
+ function buildSessionChangeSummaryPrompt(files) {
29065
+ const lines = [
29066
+ "You are the same agent that produced the changes below. Summarize **your own** edits so a reader can scan them quickly.",
29067
+ "",
29068
+ "Write in second person (you / your): what you changed in each path and why it matters.",
29069
+ "",
29070
+ "Each summary must be **very concise**: **one line** of plain text, or **at most two short lines** (use a single line break between the two if needed). No bullets, no paragraphs.",
29071
+ "",
29072
+ "## How to format your reply (machine parsing)",
29073
+ "",
29074
+ "- Put the machine-readable part **only** inside a **markdown fenced code block** whose opening fence is exactly ```json on its own line. Close the block with ``` on its own line after the JSON.",
29075
+ "- Inside that fence: **nothing except** one valid JSON value \u2014 the array described below. No trailing commentary inside the fence.",
29076
+ "- **Do not** attach prose to the JSON on the same line (wrong: `Only one file\u2026page.tsx.[{\u2026}]`). Wrong: any sentence that ends with `.` immediately before `[`. Put a blank line before the ```json line.",
29077
+ "- If you add optional plain English before the fence (e.g. one short sentence), keep it **separate**: end that sentence, blank line, then ```json.",
29078
+ '- In each `"summary"` string, avoid raw double-quote characters, or escape them as `\\"` so the JSON parses.',
29079
+ "",
29080
+ "JSON shape **inside the fence** (array only):",
29081
+ '[{"path":"<file path exactly as given>","summary":"<one line, or two short lines separated by \\n>"}]',
29082
+ "",
29083
+ "Rules:",
29084
+ "- Include **exactly one** object per file path listed below (same path strings).",
29085
+ "- If a path is a removed directory, state briefly what you removed.",
29086
+ "- Do not invent paths; use only the paths provided.",
29087
+ "",
29088
+ "## Files you changed",
29089
+ ""
29090
+ ];
29091
+ for (const f of files) {
29092
+ lines.push(`### ${f.path}`);
29093
+ if (f.directoryRemoved) {
29094
+ lines.push("(directory removed)");
29095
+ lines.push("");
29096
+ continue;
29097
+ }
29098
+ if (f.patchContent && f.patchContent.trim() !== "") {
29099
+ lines.push("```diff");
29100
+ lines.push(clip(f.patchContent.trim(), PATCH_PREVIEW_MAX));
29101
+ lines.push("```");
29102
+ } else if (f.oldText != null || f.newText != null) {
29103
+ const oldT = (f.oldText ?? "").trim();
29104
+ const newT = (f.newText ?? "").trim();
29105
+ lines.push("Previous snippet:", clip(oldT, 6e3));
29106
+ lines.push("New snippet:", clip(newT, 6e3));
29107
+ } else {
29108
+ lines.push("(no diff body stored for this path)");
29109
+ }
29110
+ lines.push("");
29111
+ }
29112
+ return lines.join("\n");
29113
+ }
29114
+ function defaultRichness(c) {
29115
+ const patch = typeof c.patchContent === "string" ? c.patchContent.length : 0;
29116
+ const nt = typeof c.newText === "string" ? c.newText.length : 0;
29117
+ const ot = typeof c.oldText === "string" ? c.oldText.length : 0;
29118
+ const dir = c.directoryRemoved === true ? 8 : 0;
29119
+ return (patch > 0 ? 4 : 0) + (nt > 0 ? 2 : 0) + (ot > 0 ? 1 : 0) + dir;
29120
+ }
29121
+ function dedupeSessionFileChangesByPath(items, richness = (item) => defaultRichness(item)) {
29122
+ const byPath = /* @__PURE__ */ new Map();
29123
+ for (const item of items) {
29124
+ const p = typeof item.path === "string" ? item.path.trim() : "";
29125
+ if (!p) continue;
29126
+ const prev = byPath.get(p);
29127
+ if (!prev || richness(item) >= richness(prev)) byPath.set(p, item);
29128
+ }
29129
+ return Array.from(byPath.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v);
29130
+ }
29131
+ var ArtifactMetaSchema = external_exports.object({
29132
+ artifactId: external_exports.string(),
29133
+ workspaceId: external_exports.string(),
29134
+ /** Slug for permalink: /workspaces/:wid/artifacts/:slug */
29135
+ permalinkSlug: external_exports.string(),
29136
+ title: external_exports.string(),
29137
+ /** e.g. summary_report, build_log */
29138
+ type: external_exports.string().default("report"),
29139
+ /** Optional session that produced this artifact */
29140
+ sessionId: external_exports.string().nullable(),
29141
+ createdAt: external_exports.string(),
29142
+ updatedAt: external_exports.string()
29143
+ });
29144
+ var TemplateMetaSchema = external_exports.object({
29145
+ templateId: external_exports.string(),
29146
+ workspaceId: external_exports.string(),
29147
+ name: external_exports.string(),
29148
+ /** e.g. summary_report, build_log */
29149
+ artifactType: external_exports.string().optional(),
29150
+ createdAt: external_exports.string(),
29151
+ updatedAt: external_exports.string()
29152
+ });
29153
+ var GitRepoMetaSchema = external_exports.object({
29154
+ /** Stable id for the repo (e.g. hash of normalized canonical URL). Used for DO idFromName. */
29155
+ repoId: external_exports.string(),
29156
+ /** Canonical external URL (e.g. https://github.com/org/repo). Normalize before storing. */
29157
+ canonicalUrl: external_exports.string().url(),
29158
+ /** Optional workspace this repo was first linked in. */
29159
+ workspaceId: external_exports.string().nullable(),
29160
+ displayName: external_exports.string().optional(),
29161
+ createdAt: external_exports.string(),
29162
+ updatedAt: external_exports.string()
29163
+ });
29164
+
29165
+ // src/agents/acp/put-summarize-change-summaries.ts
29166
+ async function putEncryptedChangeSummaryRows(params) {
29167
+ const base = params.apiBaseUrl.replace(/\/+$/, "");
29168
+ const entries = params.rows.map(({ path: path32, summary }) => {
29169
+ const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
29170
+ return { path: path32, summary: JSON.stringify(enc) };
29171
+ });
29172
+ const res = await fetch(
29173
+ `${base}/api/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
29174
+ {
29175
+ method: "PUT",
29176
+ headers: {
29177
+ Authorization: `Bearer ${params.authToken}`,
29178
+ "Content-Type": "application/json"
29179
+ },
29180
+ body: JSON.stringify({ entries })
29181
+ }
29182
+ );
29183
+ if (!res.ok) {
29184
+ const t = await res.text();
29185
+ throw new Error(`PUT summarize-changes summaries failed ${res.status}: ${t.slice(0, 500)}`);
29186
+ }
29187
+ }
29188
+
29189
+ // src/agents/acp/maybe-upload-e2ee-session-change-summaries.ts
29190
+ async function maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess(params) {
29191
+ const {
29192
+ sessionId,
29193
+ runId,
29194
+ resultSuccess,
29195
+ output,
29196
+ e2ee,
29197
+ cloudApiBaseUrl,
29198
+ getCloudAccessToken,
29199
+ followUpCatalogPromptId,
29200
+ sessionChangeSummaryFilePaths,
29201
+ log: log2
29202
+ } = params;
29203
+ const outputStr = typeof output === "string" ? output : "";
29204
+ if (!sessionId) {
29205
+ return;
29206
+ }
29207
+ if (!runId) {
29208
+ return;
29209
+ }
29210
+ if (!resultSuccess) {
29211
+ return;
29212
+ }
29213
+ if (!e2ee) {
29214
+ return;
29215
+ }
29216
+ if (!cloudApiBaseUrl) {
29217
+ return;
29218
+ }
29219
+ if (!getCloudAccessToken) {
29220
+ return;
29221
+ }
29222
+ if (followUpCatalogPromptId !== BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID) {
29223
+ return;
29224
+ }
29225
+ if (!sessionChangeSummaryFilePaths || sessionChangeSummaryFilePaths.length === 0) {
29226
+ return;
29227
+ }
29228
+ if (outputStr.trim() === "") {
29229
+ return;
29230
+ }
29231
+ const allowed = /* @__PURE__ */ new Set();
29232
+ for (const p of sessionChangeSummaryFilePaths) {
29233
+ const t = p.trim();
29234
+ if (!t) continue;
29235
+ allowed.add(t);
29236
+ allowed.add(normalizeRepoRelativePath(t));
29237
+ }
29238
+ const rows = parseChangeSummaryJson(outputStr, allowed);
29239
+ if (rows.length === 0) {
29240
+ return;
29241
+ }
29242
+ const token = getCloudAccessToken();
29243
+ if (!token) {
29244
+ return;
29245
+ }
29246
+ try {
29247
+ await putEncryptedChangeSummaryRows({
29248
+ apiBaseUrl: cloudApiBaseUrl,
29249
+ authToken: token,
29250
+ sessionId,
29251
+ e2ee,
29252
+ rows
29253
+ });
29254
+ } catch (uploadErr) {
29255
+ log2(
29256
+ `[Agent] Encrypted change summary upload failed: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
29257
+ );
29258
+ }
29259
+ }
29260
+
29261
+ // src/agents/acp/send-prompt-result-augment.ts
29262
+ function augmentPromptResultAuthFields(agentType, errorText) {
29263
+ const err = errorText ?? "";
29264
+ const at = agentType ?? null;
29265
+ const evaluated = Boolean(at && err.trim());
29266
+ const suggestsAuth = evaluated && at ? localAgentErrorSuggestsAuth(at, err) : false;
29267
+ if (!suggestsAuth || !agentType) return {};
29268
+ return { agentAuthRequired: true, agentType };
29269
+ }
29270
+
28776
29271
  // src/agents/acp/send-prompt-to-agent.ts
28777
29272
  async function sendPromptToAgent(options) {
28778
29273
  const {
@@ -28785,16 +29280,13 @@ async function sendPromptToAgent(options) {
28785
29280
  agentCwd,
28786
29281
  sendResult: sendResult2,
28787
29282
  sendSessionUpdate,
28788
- log: log2
29283
+ log: log2,
29284
+ followUpCatalogPromptId,
29285
+ sessionChangeSummaryFilePaths,
29286
+ cloudApiBaseUrl,
29287
+ getCloudAccessToken,
29288
+ e2ee
28789
29289
  } = options;
28790
- function augmentAuthFields(errorText) {
28791
- const err = errorText ?? "";
28792
- const at = agentType ?? null;
28793
- const evaluated = Boolean(at && err.trim());
28794
- const suggestsAuth = evaluated && at ? localAgentErrorSuggestsAuth(at, err) : false;
28795
- if (!suggestsAuth || !agentType) return {};
28796
- return { agentAuthRequired: true, agentType };
28797
- }
28798
29290
  try {
28799
29291
  const result = await handle.sendPrompt(promptText, {});
28800
29292
  if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
@@ -28806,6 +29298,18 @@ async function sendPromptToAgent(options) {
28806
29298
  log: log2
28807
29299
  });
28808
29300
  }
29301
+ await maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess({
29302
+ sessionId,
29303
+ runId,
29304
+ resultSuccess: result.success === true,
29305
+ output: result.output,
29306
+ e2ee,
29307
+ cloudApiBaseUrl,
29308
+ getCloudAccessToken,
29309
+ followUpCatalogPromptId,
29310
+ sessionChangeSummaryFilePaths,
29311
+ log: log2
29312
+ });
28809
29313
  const errStr = typeof result.error === "string" ? result.error : void 0;
28810
29314
  sendResult2({
28811
29315
  type: "prompt_result",
@@ -28813,7 +29317,8 @@ async function sendPromptToAgent(options) {
28813
29317
  ...sessionId ? { sessionId } : {},
28814
29318
  ...runId ? { runId } : {},
28815
29319
  ...result,
28816
- ...augmentAuthFields(errStr)
29320
+ ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
29321
+ ...augmentPromptResultAuthFields(agentType, errStr)
28817
29322
  });
28818
29323
  if (!result.success) {
28819
29324
  log2(
@@ -28830,7 +29335,8 @@ async function sendPromptToAgent(options) {
28830
29335
  ...runId ? { runId } : {},
28831
29336
  success: false,
28832
29337
  error: errMsg,
28833
- ...augmentAuthFields(errMsg)
29338
+ ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
29339
+ ...augmentPromptResultAuthFields(agentType, errMsg)
28834
29340
  });
28835
29341
  }
28836
29342
  }
@@ -28994,7 +29500,7 @@ async function createCursorAcpClient(options) {
28994
29500
  onRequest,
28995
29501
  onFileChange
28996
29502
  } = options;
28997
- const dbgFs = process.env.BUILDAMATON_DEBUG_ACP_FS === "1";
29503
+ const dbgFs = process.env.BUILDAUTOMATON_DEBUG_ACP_FS === "1";
28998
29504
  const isWindows = process.platform === "win32";
28999
29505
  const child = spawn4(command[0], command.slice(1), {
29000
29506
  cwd,
@@ -30138,7 +30644,12 @@ async function createAcpManager(options) {
30138
30644
  agentType,
30139
30645
  cwd,
30140
30646
  sendResult: sendResult2,
30141
- sendSessionUpdate
30647
+ sendSessionUpdate,
30648
+ followUpCatalogPromptId,
30649
+ sessionChangeSummaryFilePaths,
30650
+ cloudApiBaseUrl,
30651
+ getCloudAccessToken,
30652
+ e2ee
30142
30653
  } = opts;
30143
30654
  const preferredForPrompt = agentType ?? backendFallbackAgentType ?? null;
30144
30655
  pendingCancelRunId = void 0;
@@ -30202,7 +30713,12 @@ async function createAcpManager(options) {
30202
30713
  agentCwd: cwd,
30203
30714
  sendResult: sendResult2,
30204
30715
  sendSessionUpdate,
30205
- log: log2
30716
+ log: log2,
30717
+ followUpCatalogPromptId,
30718
+ sessionChangeSummaryFilePaths,
30719
+ cloudApiBaseUrl,
30720
+ getCloudAccessToken,
30721
+ e2ee
30206
30722
  });
30207
30723
  }
30208
30724
  void run().finally(() => {
@@ -31541,7 +32057,7 @@ function startFileIndexWatcher(cwd = getBridgeWorkspaceDirectory()) {
31541
32057
  }
31542
32058
 
31543
32059
  // src/dev-servers/manager/dev-server-manager.ts
31544
- import { rm } from "node:fs/promises";
32060
+ import { rm as rm2 } from "node:fs/promises";
31545
32061
 
31546
32062
  // src/dev-servers/process/send-server-status.ts
31547
32063
  function sendDevServerStatus(getWs, serverId, status, options) {
@@ -32050,8 +32566,90 @@ var StreamTail = class {
32050
32566
  }
32051
32567
  };
32052
32568
 
32053
- // src/dev-servers/manager/dev-server-manager.ts
32569
+ // src/dev-servers/manager/dev-server-constants.ts
32054
32570
  var BRIDGE_SHUTDOWN_GRACE_MS = 8e3;
32571
+
32572
+ // src/dev-servers/manager/dev-server-firehose-messages.ts
32573
+ function buildFirehoseSnapshotMessage(params) {
32574
+ const payload = {
32575
+ type: "log_snapshot",
32576
+ serverId: params.serverId,
32577
+ viewerId: params.viewerId,
32578
+ stdoutTail: params.tails.stdout,
32579
+ stderrTail: params.tails.stderr
32580
+ };
32581
+ return params.e2ee ? params.e2ee.encryptFields(payload, ["stdoutTail", "stderrTail"]) : payload;
32582
+ }
32583
+ function buildFirehoseLogChunkMessage(params) {
32584
+ const payload = {
32585
+ type: "log_chunk",
32586
+ serverId: params.serverId,
32587
+ stream: params.stream,
32588
+ text: params.text
32589
+ };
32590
+ return params.e2ee ? params.e2ee.encryptFields(payload, ["text"]) : payload;
32591
+ }
32592
+
32593
+ // src/dev-servers/manager/dev-server-firehose-sink.ts
32594
+ var DevServerFirehoseSink = class {
32595
+ constructor(options) {
32596
+ this.options = options;
32597
+ }
32598
+ logViewerRefCountByServerId = /* @__PURE__ */ new Map();
32599
+ firehoseSend = null;
32600
+ attach(send) {
32601
+ this.firehoseSend = send;
32602
+ }
32603
+ detach() {
32604
+ this.firehoseSend = null;
32605
+ this.logViewerRefCountByServerId.clear();
32606
+ }
32607
+ openLogViewer(serverId, viewerId) {
32608
+ const next = (this.logViewerRefCountByServerId.get(serverId) ?? 0) + 1;
32609
+ this.logViewerRefCountByServerId.set(serverId, next);
32610
+ this.sendSnapshot(serverId, viewerId);
32611
+ }
32612
+ closeLogViewer(serverId) {
32613
+ const n = (this.logViewerRefCountByServerId.get(serverId) ?? 0) - 1;
32614
+ if (n <= 0) this.logViewerRefCountByServerId.delete(serverId);
32615
+ else this.logViewerRefCountByServerId.set(serverId, n);
32616
+ }
32617
+ pushLogChunk(serverId, stream, chunk) {
32618
+ if ((this.logViewerRefCountByServerId.get(serverId) ?? 0) <= 0) return;
32619
+ if (!this.options.isPipedCaptureEnabled(serverId)) return;
32620
+ if (!this.firehoseSend) return;
32621
+ const text = chunk.toString("utf8");
32622
+ setImmediate(() => {
32623
+ if (!this.firehoseSend) return;
32624
+ this.firehoseSend(buildFirehoseLogChunkMessage({ serverId, stream, text, e2ee: this.options.e2ee }));
32625
+ });
32626
+ }
32627
+ sendSnapshot(serverId, viewerId) {
32628
+ const payload = buildFirehoseSnapshotMessage({
32629
+ serverId,
32630
+ viewerId,
32631
+ tails: this.options.getTails(serverId),
32632
+ e2ee: this.options.e2ee
32633
+ });
32634
+ setImmediate(() => {
32635
+ const send = this.firehoseSend;
32636
+ if (!send) return;
32637
+ send(payload);
32638
+ });
32639
+ }
32640
+ };
32641
+
32642
+ // src/dev-servers/manager/cleanup-merged-log-dir.ts
32643
+ import { rm } from "node:fs/promises";
32644
+ function cleanupMergedLogDirForServer(map2, serverId) {
32645
+ const mergedDir = map2.get(serverId);
32646
+ if (!mergedDir) return;
32647
+ map2.delete(serverId);
32648
+ void rm(mergedDir, { recursive: true, force: true }).catch(() => {
32649
+ });
32650
+ }
32651
+
32652
+ // src/dev-servers/manager/dev-server-manager.ts
32055
32653
  var emptyTails = () => ({ stdout: [], stderr: [] });
32056
32654
  var DevServerManager = class {
32057
32655
  defsById = /* @__PURE__ */ new Map();
@@ -32059,66 +32657,36 @@ var DevServerManager = class {
32059
32657
  streamTailsByServerId = /* @__PURE__ */ new Map();
32060
32658
  spawnGenerationByServerId = /* @__PURE__ */ new Map();
32061
32659
  pipedCaptureByServerId = /* @__PURE__ */ new Map();
32062
- logViewerRefCountByServerId = /* @__PURE__ */ new Map();
32063
- firehoseSend = null;
32064
32660
  mergedLogPollByServerId = /* @__PURE__ */ new Map();
32065
32661
  mergedLogCleanupDirByServerId = /* @__PURE__ */ new Map();
32066
32662
  abortControllersByServerId = /* @__PURE__ */ new Map();
32067
32663
  getWs;
32068
32664
  log;
32069
32665
  getBridgeCwd;
32666
+ e2ee;
32667
+ firehoseSink;
32070
32668
  constructor(options) {
32071
32669
  this.getWs = options.getWs;
32072
32670
  this.log = options.log;
32073
32671
  this.getBridgeCwd = options.getBridgeCwd ?? (() => process.cwd());
32672
+ this.e2ee = options.e2ee;
32673
+ this.firehoseSink = new DevServerFirehoseSink({
32674
+ getTails: (serverId) => this.snapshotTails(serverId),
32675
+ isPipedCaptureEnabled: (serverId) => this.pipedCaptureByServerId.get(serverId) === true,
32676
+ e2ee: this.e2ee
32677
+ });
32074
32678
  }
32075
32679
  attachFirehose(send) {
32076
- this.firehoseSend = send;
32680
+ this.firehoseSink.attach(send);
32077
32681
  }
32078
32682
  detachFirehose() {
32079
- this.firehoseSend = null;
32080
- this.logViewerRefCountByServerId.clear();
32683
+ this.firehoseSink.detach();
32081
32684
  }
32082
32685
  handleFirehoseLogViewerOpen(serverId, _viewerId) {
32083
- const next = (this.logViewerRefCountByServerId.get(serverId) ?? 0) + 1;
32084
- this.logViewerRefCountByServerId.set(serverId, next);
32085
- this.sendSnapshotToFirehose(serverId, _viewerId);
32686
+ this.firehoseSink.openLogViewer(serverId, _viewerId);
32086
32687
  }
32087
32688
  handleFirehoseLogViewerClose(serverId, _viewerId) {
32088
- const n = (this.logViewerRefCountByServerId.get(serverId) ?? 0) - 1;
32089
- if (n <= 0) this.logViewerRefCountByServerId.delete(serverId);
32090
- else this.logViewerRefCountByServerId.set(serverId, n);
32091
- }
32092
- sendSnapshotToFirehose(serverId, viewerId) {
32093
- const tails = this.streamTailsByServerId.get(serverId);
32094
- const payload = {
32095
- type: "log_snapshot",
32096
- serverId,
32097
- viewerId,
32098
- stdoutTail: tails?.stdout.getTail() ?? [],
32099
- stderrTail: tails?.stderr.getTail() ?? []
32100
- };
32101
- setImmediate(() => {
32102
- const send = this.firehoseSend;
32103
- if (!send) return;
32104
- send(payload);
32105
- });
32106
- }
32107
- pushRemoteLogChunk(serverId, stream, chunk) {
32108
- if ((this.logViewerRefCountByServerId.get(serverId) ?? 0) <= 0) return;
32109
- if (!this.pipedCaptureByServerId.get(serverId)) return;
32110
- const send = this.firehoseSend;
32111
- if (!send) return;
32112
- const text = chunk.toString("utf8");
32113
- setImmediate(() => {
32114
- if (!this.firehoseSend) return;
32115
- this.firehoseSend({
32116
- type: "log_chunk",
32117
- serverId,
32118
- stream,
32119
- text
32120
- });
32121
- });
32689
+ this.firehoseSink.closeLogViewer(serverId);
32122
32690
  }
32123
32691
  applyConfig(servers) {
32124
32692
  this.defsById.clear();
@@ -32171,12 +32739,7 @@ var DevServerManager = class {
32171
32739
  }
32172
32740
  this.clearTails(serverId);
32173
32741
  this.pipedCaptureByServerId.delete(serverId);
32174
- const mergedDir = this.mergedLogCleanupDirByServerId.get(serverId);
32175
- if (mergedDir) {
32176
- this.mergedLogCleanupDirByServerId.delete(serverId);
32177
- void rm(mergedDir, { recursive: true, force: true }).catch(() => {
32178
- });
32179
- }
32742
+ cleanupMergedLogDirForServer(this.mergedLogCleanupDirByServerId, serverId);
32180
32743
  this.sendStatus(serverId, "stopped", void 0, tails);
32181
32744
  }
32182
32745
  start(serverId) {
@@ -32259,7 +32822,7 @@ var DevServerManager = class {
32259
32822
  log: this.log,
32260
32823
  stdoutTail,
32261
32824
  stderrTail,
32262
- pushRemoteLogChunk: (sid, stream, chunk) => this.pushRemoteLogChunk(sid, stream, chunk),
32825
+ pushRemoteLogChunk: (sid, stream, chunk) => this.firehoseSink.pushLogChunk(sid, stream, chunk),
32263
32826
  sendStatus: (status, detail, tails) => this.sendStatus(serverId, status, detail, tails),
32264
32827
  setPollInterval: (iv) => {
32265
32828
  if (iv) this.mergedLogPollByServerId.set(serverId, iv);
@@ -32273,7 +32836,7 @@ var DevServerManager = class {
32273
32836
  this.mergedLogCleanupDirByServerId.delete(serverId);
32274
32837
  },
32275
32838
  rmMergedCleanupDir: (dir) => {
32276
- void rm(dir, { recursive: true, force: true }).catch(() => {
32839
+ void rm2(dir, { recursive: true, force: true }).catch(() => {
32277
32840
  });
32278
32841
  },
32279
32842
  clearTailBuffers: () => this.clearTails(serverId)
@@ -32310,12 +32873,7 @@ var DevServerManager = class {
32310
32873
  this.processes.delete(serverId);
32311
32874
  this.clearPoll(serverId);
32312
32875
  this.pipedCaptureByServerId.delete(serverId);
32313
- const mergedDir = this.mergedLogCleanupDirByServerId.get(serverId);
32314
- if (mergedDir) {
32315
- this.mergedLogCleanupDirByServerId.delete(serverId);
32316
- void rm(mergedDir, { recursive: true, force: true }).catch(() => {
32317
- });
32318
- }
32876
+ cleanupMergedLogDirForServer(this.mergedLogCleanupDirByServerId, serverId);
32319
32877
  const tails = this.snapshotTails(serverId);
32320
32878
  this.clearTails(serverId);
32321
32879
  this.sendStatus(serverId, "unknown", "Bridge closed before process exited", tails);
@@ -32775,7 +33333,7 @@ function reportGitRepos(getWs, log2) {
32775
33333
  var handleAuthToken = (msg, { log: log2 }) => {
32776
33334
  if (typeof msg.token !== "string") return;
32777
33335
  log2("Received auth token. Save it for future runs:");
32778
- log2(` export BUILDAMATON_AUTH_TOKEN="${msg.token}"`);
33336
+ log2(` export BUILDAUTOMATON_AUTH_TOKEN="${msg.token}"`);
32779
33337
  };
32780
33338
 
32781
33339
  // src/bridge/routing/handlers/bridge-identified.ts
@@ -32819,6 +33377,42 @@ var handleAgentConfigMessage = (msg, deps) => {
32819
33377
 
32820
33378
  // src/agents/acp/from-bridge/handle-bridge-prompt.ts
32821
33379
  import * as path28 from "node:path";
33380
+
33381
+ // src/agents/acp/from-bridge/bridge-prompt-wiring.ts
33382
+ function createBridgePromptSenders(deps, getWs) {
33383
+ const sendBridgeMessage = (message, encryptedFields = []) => {
33384
+ const s = getWs();
33385
+ if (!s) return false;
33386
+ const wire = deps.e2ee && encryptedFields.length > 0 ? deps.e2ee.encryptFields(message, encryptedFields) : message;
33387
+ sendWsMessage(s, wire);
33388
+ return true;
33389
+ };
33390
+ const sendResult2 = (result) => {
33391
+ const skipEncryptForChangeSummaryFollowUp = result.type === "prompt_result" && result.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
33392
+ const encryptedFields = result.type === "prompt_result" && !skipEncryptForChangeSummaryFollowUp ? ["output", "error"] : [];
33393
+ sendBridgeMessage(result, encryptedFields);
33394
+ };
33395
+ const sendSessionUpdate = (payload) => {
33396
+ const s = getWs();
33397
+ if (!s) {
33398
+ deps.log("[Bridge service] Session update not sent: not connected to the bridge.");
33399
+ return;
33400
+ }
33401
+ const p = payload;
33402
+ const wire = p.type === "session_update" && deps.e2ee ? deps.e2ee.encryptFields(payload, ["payload"]) : p.type === "session_file_change" && deps.e2ee ? deps.e2ee.encryptFields(payload, [
33403
+ "path",
33404
+ "oldText",
33405
+ "newText",
33406
+ "patchContent",
33407
+ "isDirectory",
33408
+ "directoryRemoved"
33409
+ ]) : payload;
33410
+ sendWsMessage(s, wire);
33411
+ };
33412
+ return { sendBridgeMessage, sendResult: sendResult2, sendSessionUpdate };
33413
+ }
33414
+
33415
+ // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
32822
33416
  import { execFile as execFile10 } from "node:child_process";
32823
33417
  import { promisify as promisify10 } from "node:util";
32824
33418
 
@@ -32877,7 +33471,7 @@ async function resolveBridgeQueueBindFields(options) {
32877
33471
  return { canonicalQueueKey, repoId, cwdAbs };
32878
33472
  }
32879
33473
 
32880
- // src/agents/acp/from-bridge/handle-bridge-prompt.ts
33474
+ // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
32881
33475
  var execFileAsync9 = promisify10(execFile10);
32882
33476
  async function readGitBranch(cwd) {
32883
33477
  try {
@@ -32888,6 +33482,158 @@ async function readGitBranch(cwd) {
32888
33482
  return null;
32889
33483
  }
32890
33484
  }
33485
+ async function runBridgePromptPreamble(params) {
33486
+ const { getWs, log: log2, sessionWorktreeManager, sessionId, runId, effectiveCwd } = params;
33487
+ const s = getWs();
33488
+ const worktreePaths = sessionWorktreeManager.getWorktreePathsForSession(sessionId) ?? [];
33489
+ const repoRoots = await resolveSnapshotRepoRoots({
33490
+ worktreePaths,
33491
+ fallbackCwd: effectiveCwd,
33492
+ log: log2
33493
+ });
33494
+ if (s && sessionId) {
33495
+ const bind = await resolveBridgeQueueBindFields({
33496
+ effectiveCwd,
33497
+ worktreePaths,
33498
+ primaryRepoRoots: repoRoots,
33499
+ log: log2
33500
+ });
33501
+ if (bind) {
33502
+ sendWsMessage(s, {
33503
+ type: "bridge_queue_bind",
33504
+ sessionId,
33505
+ canonicalQueueKey: bind.canonicalQueueKey,
33506
+ repoId: bind.repoId,
33507
+ cwdAbs: bind.cwdAbs
33508
+ });
33509
+ }
33510
+ }
33511
+ if (s && sessionId) {
33512
+ const cliGitBranch = await readGitBranch(effectiveCwd);
33513
+ sendWsMessage(s, {
33514
+ type: "session_git_context_report",
33515
+ sessionId,
33516
+ cliGitBranch,
33517
+ agentUsesWorktree: sessionWorktreeManager.usesWorktreeSession(sessionId)
33518
+ });
33519
+ }
33520
+ if (s && sessionId && runId) {
33521
+ const cap = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false, error: "No git repos" };
33522
+ sendWsMessage(s, {
33523
+ type: "pre_turn_snapshot_report",
33524
+ sessionId,
33525
+ turnId: runId,
33526
+ captured: cap.ok
33527
+ });
33528
+ }
33529
+ }
33530
+ function parseChangeSummarySnapshots(raw) {
33531
+ if (!Array.isArray(raw) || raw.length === 0) return void 0;
33532
+ const out = [];
33533
+ for (const item of raw) {
33534
+ if (!item || typeof item !== "object") continue;
33535
+ const o = item;
33536
+ const path32 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
33537
+ if (!path32) continue;
33538
+ const row = { path: path32 };
33539
+ if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
33540
+ if (typeof o.oldText === "string") row.oldText = o.oldText;
33541
+ if (typeof o.newText === "string") row.newText = o.newText;
33542
+ if (o.directoryRemoved === true) row.directoryRemoved = true;
33543
+ out.push(row);
33544
+ }
33545
+ return out.length > 0 ? out : void 0;
33546
+ }
33547
+ function parseFollowUpFieldsFromPromptMessage(msg) {
33548
+ const followUpCatalogPromptId = typeof msg.followUpCatalogPromptId === "string" && msg.followUpCatalogPromptId.trim() !== "" ? msg.followUpCatalogPromptId.trim() : null;
33549
+ const rawPaths = msg.sessionChangeSummaryFilePaths;
33550
+ const sessionChangeSummaryFilePaths = Array.isArray(rawPaths) ? rawPaths.filter((p) => typeof p === "string" && p.trim() !== "").map((p) => p.trim()) : void 0;
33551
+ const sessionChangeSummaryFileSnapshots = parseChangeSummarySnapshots(msg.sessionChangeSummaryFileSnapshots);
33552
+ return { followUpCatalogPromptId, sessionChangeSummaryFilePaths, sessionChangeSummaryFileSnapshots };
33553
+ }
33554
+
33555
+ // ../e2ee/src/constants.ts
33556
+ var E2EE_NONCE_BYTES = 12;
33557
+
33558
+ // ../e2ee/src/types.ts
33559
+ function isE2eeEnvelope(value) {
33560
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
33561
+ const o = value;
33562
+ return typeof o.k === "string" && typeof o.n === "string" && typeof o.c === "string";
33563
+ }
33564
+
33565
+ // ../e2ee/src/encoding.ts
33566
+ function base64UrlEncode(bytes) {
33567
+ let binary = "";
33568
+ for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
33569
+ const b64 = btoa(binary);
33570
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
33571
+ }
33572
+ function base64UrlDecode(value) {
33573
+ const b64 = value.replace(/-/g, "+").replace(/_/g, "/");
33574
+ const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
33575
+ const binary = atob(padded);
33576
+ const out = new Uint8Array(binary.length);
33577
+ for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
33578
+ return out;
33579
+ }
33580
+
33581
+ // src/agents/acp/change-summary/decrypt-change-summary-file-input.ts
33582
+ function decryptChangeSummaryFileInput(row, e2ee) {
33583
+ if (!e2ee) return row;
33584
+ for (const field of ["path", "patchContent", "oldText", "newText"]) {
33585
+ const raw = row[field];
33586
+ if (typeof raw !== "string" || raw.trim() === "") continue;
33587
+ let o;
33588
+ try {
33589
+ o = JSON.parse(raw);
33590
+ } catch {
33591
+ continue;
33592
+ }
33593
+ if (!isE2eeEnvelope(o.ee)) continue;
33594
+ try {
33595
+ const d = e2ee.decryptMessage(o);
33596
+ const out = {
33597
+ path: typeof d.path === "string" ? d.path : row.path
33598
+ };
33599
+ if (d.directoryRemoved === true) out.directoryRemoved = true;
33600
+ else if (row.directoryRemoved === true) out.directoryRemoved = true;
33601
+ if (typeof d.patchContent === "string") out.patchContent = d.patchContent;
33602
+ else if (typeof row.patchContent === "string" && row.patchContent !== raw) out.patchContent = row.patchContent;
33603
+ if (typeof d.oldText === "string") out.oldText = d.oldText;
33604
+ else if (typeof row.oldText === "string") out.oldText = row.oldText;
33605
+ if (typeof d.newText === "string") out.newText = d.newText;
33606
+ else if (typeof row.newText === "string") out.newText = row.newText;
33607
+ return out;
33608
+ } catch {
33609
+ return row;
33610
+ }
33611
+ }
33612
+ return row;
33613
+ }
33614
+
33615
+ // src/agents/acp/change-summary/resolve-change-summary-prompt-for-agent.ts
33616
+ function hasSummarizePayload(f) {
33617
+ return f.directoryRemoved === true || f.patchContent != null && f.patchContent.trim() !== "" || f.oldText != null && f.oldText.trim() !== "" || f.newText != null && f.newText.trim() !== "";
33618
+ }
33619
+ function resolveChangeSummaryPromptForAgent(params) {
33620
+ const isBuiltin = params.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
33621
+ const snaps = params.sessionChangeSummaryFileSnapshots;
33622
+ if (!isBuiltin || !snaps || snaps.length === 0) {
33623
+ return { promptText: params.bridgePromptText, sessionChangeSummaryFilePaths: void 0 };
33624
+ }
33625
+ const decrypted = dedupeSessionFileChangesByPath(snaps.map((row) => decryptChangeSummaryFileInput(row, params.e2ee)));
33626
+ const withPayload = decrypted.filter(hasSummarizePayload);
33627
+ if (withPayload.length === 0) {
33628
+ return { promptText: params.bridgePromptText, sessionChangeSummaryFilePaths: void 0 };
33629
+ }
33630
+ return {
33631
+ promptText: buildSessionChangeSummaryPrompt(withPayload),
33632
+ sessionChangeSummaryFilePaths: withPayload.map((f) => f.path)
33633
+ };
33634
+ }
33635
+
33636
+ // src/agents/acp/from-bridge/handle-bridge-prompt.ts
32891
33637
  function handleBridgePrompt(msg, deps) {
32892
33638
  const { getWs, log: log2, acpManager, sessionWorktreeManager } = deps;
32893
33639
  const rawPrompt = msg.prompt;
@@ -32895,21 +33641,22 @@ function handleBridgePrompt(msg, deps) {
32895
33641
  const sessionId = msg.sessionId;
32896
33642
  const runId = typeof msg.runId === "string" ? msg.runId : void 0;
32897
33643
  const promptId = typeof msg.id === "string" ? msg.id : void 0;
33644
+ const { sendBridgeMessage, sendResult: sendResult2, sendSessionUpdate } = createBridgePromptSenders(deps, getWs);
32898
33645
  if (!promptText.trim()) {
32899
33646
  log2(
32900
33647
  `[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).`
32901
33648
  );
32902
- const s = getWs();
32903
- if (s) {
32904
- sendWsMessage(s, {
33649
+ sendBridgeMessage(
33650
+ {
32905
33651
  type: "prompt_result",
32906
33652
  ...promptId ? { id: promptId } : {},
32907
33653
  ...sessionId ? { sessionId } : {},
32908
33654
  ...runId ? { runId } : {},
32909
33655
  success: false,
32910
33656
  error: "Empty or missing prompt text from the bridge; this turn was not sent to the agent."
32911
- });
32912
- }
33657
+ },
33658
+ ["error"]
33659
+ );
32913
33660
  return;
32914
33661
  }
32915
33662
  const isNewSession = msg.isNewSession === true;
@@ -32917,65 +33664,35 @@ function handleBridgePrompt(msg, deps) {
32917
33664
  const agentType = typeof msg.agentType === "string" && msg.agentType.trim() ? msg.agentType.trim() : void 0;
32918
33665
  const mode = typeof msg.mode === "string" && msg.mode.trim() ? msg.mode.trim() : void 0;
32919
33666
  acpManager.logPromptReceivedFromBridge({ agentType, mode });
32920
- const sendResult2 = (result) => {
32921
- const s = getWs();
32922
- if (s) sendWsMessage(s, result);
32923
- };
32924
- const sendSessionUpdate = (payload) => {
32925
- const s = getWs();
32926
- if (!s) {
32927
- log2("[Bridge service] Session update not sent: not connected to the bridge.");
32928
- return;
32929
- }
32930
- const p = payload;
32931
- sendWsMessage(s, payload);
32932
- };
32933
33667
  async function preambleAndPrompt(resolvedCwd) {
32934
- const s = getWs();
32935
33668
  const effectiveCwd = path28.resolve(resolvedCwd ?? getBridgeWorkspaceDirectory());
32936
- const worktreePaths = sessionWorktreeManager.getWorktreePathsForSession(sessionId) ?? [];
32937
- const repoRoots = await resolveSnapshotRepoRoots({
32938
- worktreePaths,
32939
- fallbackCwd: effectiveCwd,
32940
- log: log2
33669
+ await runBridgePromptPreamble({
33670
+ getWs,
33671
+ log: log2,
33672
+ sessionWorktreeManager,
33673
+ sessionId,
33674
+ runId,
33675
+ effectiveCwd
32941
33676
  });
32942
- if (s && sessionId) {
32943
- const bind = await resolveBridgeQueueBindFields({
32944
- effectiveCwd,
32945
- worktreePaths,
32946
- primaryRepoRoots: repoRoots,
32947
- log: log2
32948
- });
32949
- if (bind) {
32950
- sendWsMessage(s, {
32951
- type: "bridge_queue_bind",
32952
- sessionId,
32953
- canonicalQueueKey: bind.canonicalQueueKey,
32954
- repoId: bind.repoId,
32955
- cwdAbs: bind.cwdAbs
32956
- });
32957
- }
32958
- }
32959
- if (s && sessionId) {
32960
- const cliGitBranch = await readGitBranch(effectiveCwd);
32961
- sendWsMessage(s, {
32962
- type: "session_git_context_report",
32963
- sessionId,
32964
- cliGitBranch,
32965
- agentUsesWorktree: sessionWorktreeManager.usesWorktreeSession(sessionId)
32966
- });
32967
- }
32968
- if (s && sessionId && runId) {
32969
- const cap = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false, error: "No git repos" };
32970
- sendWsMessage(s, {
32971
- type: "pre_turn_snapshot_report",
32972
- sessionId,
32973
- turnId: runId,
32974
- captured: cap.ok
32975
- });
33677
+ const {
33678
+ followUpCatalogPromptId,
33679
+ sessionChangeSummaryFilePaths: pathsFromBridge,
33680
+ sessionChangeSummaryFileSnapshots
33681
+ } = parseFollowUpFieldsFromPromptMessage(msg);
33682
+ const { promptText: resolvedPromptText, sessionChangeSummaryFilePaths } = resolveChangeSummaryPromptForAgent({
33683
+ followUpCatalogPromptId,
33684
+ sessionChangeSummaryFileSnapshots,
33685
+ bridgePromptText: promptText,
33686
+ e2ee: deps.e2ee
33687
+ });
33688
+ if (sessionChangeSummaryFileSnapshots && sessionChangeSummaryFileSnapshots.length > 0 && resolvedPromptText === promptText) {
33689
+ deps.log(
33690
+ "[Agent] Change-summary snapshots were present but the prompt was not rebuilt (decrypt failed or empty payloads); sending the bridge prompt as-is."
33691
+ );
32976
33692
  }
33693
+ const pathsForUpload = sessionChangeSummaryFilePaths ?? pathsFromBridge;
32977
33694
  acpManager.handlePrompt({
32978
- promptText,
33695
+ promptText: resolvedPromptText,
32979
33696
  promptId: msg.id,
32980
33697
  sessionId,
32981
33698
  runId,
@@ -32983,7 +33700,12 @@ function handleBridgePrompt(msg, deps) {
32983
33700
  agentType,
32984
33701
  cwd: effectiveCwd,
32985
33702
  sendResult: sendResult2,
32986
- sendSessionUpdate
33703
+ sendSessionUpdate,
33704
+ followUpCatalogPromptId,
33705
+ sessionChangeSummaryFilePaths: pathsForUpload,
33706
+ cloudApiBaseUrl: deps.cloudApiBaseUrl,
33707
+ getCloudAccessToken: deps.getCloudAccessToken,
33708
+ e2ee: deps.e2ee
32987
33709
  });
32988
33710
  }
32989
33711
  void sessionWorktreeManager.resolveCwdForPrompt(sessionId, { isNewSession, sessionWorktreesEnabled }).then((cwd) => preambleAndPrompt(cwd)).catch((err) => {
@@ -33309,28 +34031,30 @@ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineC
33309
34031
 
33310
34032
  // src/files/handle-file-browser-search.ts
33311
34033
  var SEARCH_LIMIT = 100;
33312
- function handleFileBrowserSearch(msg, socket) {
34034
+ function handleFileBrowserSearch(msg, socket, e2ee) {
33313
34035
  void (async () => {
33314
34036
  await yieldToEventLoop();
33315
34037
  const q = typeof msg.q === "string" ? msg.q : "";
33316
34038
  const cwd = getBridgeWorkspaceDirectory();
33317
34039
  const index = loadFileIndex(cwd);
33318
34040
  if (index === null) {
33319
- sendWsMessage(socket, {
34041
+ const payload2 = {
33320
34042
  type: "file_browser_search_response",
33321
34043
  id: msg.id,
33322
34044
  paths: [],
33323
34045
  indexReady: false
33324
- });
34046
+ };
34047
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
33325
34048
  return;
33326
34049
  }
33327
34050
  const results = await searchFileIndexAsync(index, q, SEARCH_LIMIT);
33328
- sendWsMessage(socket, {
34051
+ const payload = {
33329
34052
  type: "file_browser_search_response",
33330
34053
  id: msg.id,
33331
34054
  paths: results,
33332
34055
  indexReady: true
33333
- });
34056
+ };
34057
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["paths"]) : payload);
33334
34058
  })();
33335
34059
  }
33336
34060
  function triggerFileIndexBuild() {
@@ -33342,7 +34066,10 @@ function triggerFileIndexBuild() {
33342
34066
  }
33343
34067
 
33344
34068
  // src/files/handle-file-browser-request.ts
33345
- function handleFileBrowserRequest(msg, socket) {
34069
+ function sendFileBrowserMessage(socket, e2ee, payload) {
34070
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
34071
+ }
34072
+ function handleFileBrowserRequest(msg, socket, e2ee) {
33346
34073
  void (async () => {
33347
34074
  const reqPath = msg.path.replace(/^\/+/, "") || ".";
33348
34075
  const op = msg.op === "read" ? "read" : "list";
@@ -33351,7 +34078,7 @@ function handleFileBrowserRequest(msg, socket) {
33351
34078
  if ("error" in result) {
33352
34079
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
33353
34080
  } else {
33354
- sendWsMessage(socket, { type: "file_browser_response", id: msg.id, entries: result.entries });
34081
+ sendFileBrowserMessage(socket, e2ee, { type: "file_browser_response", id: msg.id, entries: result.entries });
33355
34082
  if (reqPath === "." || reqPath === "") {
33356
34083
  triggerFileIndexBuild();
33357
34084
  }
@@ -33373,27 +34100,28 @@ function handleFileBrowserRequest(msg, socket) {
33373
34100
  size: result.size
33374
34101
  };
33375
34102
  if (result.lineOffset != null) payload.lineOffset = result.lineOffset;
33376
- sendWsMessage(socket, payload);
34103
+ sendFileBrowserMessage(socket, e2ee, payload);
33377
34104
  }
33378
34105
  }
33379
34106
  })();
33380
34107
  }
33381
34108
 
33382
34109
  // src/bridge/routing/handlers/file-browser-messages.ts
33383
- function handleFileBrowserRequestMessage(msg, { getWs }) {
34110
+ function handleFileBrowserRequestMessage(msg, { getWs, e2ee }) {
33384
34111
  if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
33385
34112
  const socket = getWs();
33386
34113
  if (!socket) return;
33387
34114
  handleFileBrowserRequest(
33388
34115
  msg,
33389
- socket
34116
+ socket,
34117
+ e2ee
33390
34118
  );
33391
34119
  }
33392
- function handleFileBrowserSearchMessage(msg, { getWs }) {
34120
+ function handleFileBrowserSearchMessage(msg, { getWs, e2ee }) {
33393
34121
  if (typeof msg.id !== "string") return;
33394
34122
  const socket = getWs();
33395
34123
  if (!socket) return;
33396
- handleFileBrowserSearch(msg, socket);
34124
+ handleFileBrowserSearch(msg, socket, e2ee);
33397
34125
  }
33398
34126
 
33399
34127
  // src/bridge/routing/handlers/skill-layout-request.ts
@@ -33463,9 +34191,10 @@ var handleRefreshLocalSkills = (_msg, deps) => {
33463
34191
  };
33464
34192
 
33465
34193
  // src/bridge/routing/handlers/session-git-request.ts
33466
- function sendResult(ws, id, payload) {
34194
+ function sendResult(ws, id, payload, e2ee, encryptedFields = []) {
33467
34195
  if (!ws) return;
33468
- sendWsMessage(ws, { type: "session_git_result", id, ...payload });
34196
+ const message = { type: "session_git_result", id, ...payload };
34197
+ sendWsMessage(ws, e2ee && encryptedFields.length > 0 ? e2ee.encryptFields(message, encryptedFields) : message);
33469
34198
  }
33470
34199
  var handleSessionGitRequestMessage = (msg, deps) => {
33471
34200
  if (typeof msg.id !== "string") return;
@@ -33475,7 +34204,7 @@ var handleSessionGitRequestMessage = (msg, deps) => {
33475
34204
  return;
33476
34205
  void (async () => {
33477
34206
  const ws = deps.getWs();
33478
- const reply = (payload) => sendResult(ws, msg.id, payload);
34207
+ const reply = (payload, encryptedFields = []) => sendResult(ws, msg.id, payload, deps.e2ee, encryptedFields);
33479
34208
  try {
33480
34209
  if (action === "status") {
33481
34210
  const r = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
@@ -33501,7 +34230,7 @@ var handleSessionGitRequestMessage = (msg, deps) => {
33501
34230
  reply({
33502
34231
  ok: true,
33503
34232
  repos
33504
- });
34233
+ }, ["repos"]);
33505
34234
  return;
33506
34235
  }
33507
34236
  if (action === "push") {
@@ -33603,8 +34332,15 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
33603
34332
 
33604
34333
  // src/bridge/routing/handlers/dev-server-control.ts
33605
34334
  var handleDevServerControl = (msg, deps) => {
33606
- const serverId = typeof msg.serverId === "string" ? msg.serverId : "";
33607
- const action = msg.action === "start" || msg.action === "stop" ? msg.action : null;
34335
+ let wire = msg;
34336
+ try {
34337
+ wire = deps.e2ee ? deps.e2ee.decryptMessage(msg) : msg;
34338
+ } catch (e) {
34339
+ deps.log(`[E2EE] Could not decrypt dev server command: ${e instanceof Error ? e.message : String(e)}`);
34340
+ return;
34341
+ }
34342
+ const serverId = typeof wire.serverId === "string" ? wire.serverId : "";
34343
+ const action = wire.action === "start" || wire.action === "stop" ? wire.action : null;
33608
34344
  if (!serverId || !action) return;
33609
34345
  deps.devServerManager?.handleControl(serverId, action);
33610
34346
  };
@@ -33730,7 +34466,8 @@ function createMainBridgeWebSocketLifecycle(params) {
33730
34466
  messageDeps,
33731
34467
  tokens,
33732
34468
  persistTokens,
33733
- onAuthInvalid
34469
+ onAuthInvalid,
34470
+ e2ee
33734
34471
  } = params;
33735
34472
  let authRefreshInFlight = false;
33736
34473
  function handleOpen() {
@@ -33743,15 +34480,15 @@ function createMainBridgeWebSocketLifecycle(params) {
33743
34480
  }
33744
34481
  const socket = getWs();
33745
34482
  if (socket) {
33746
- sendWsMessage(socket, { type: "identify", role: "cli" });
34483
+ sendWsMessage(socket, { type: "identify", role: "cli", ...e2ee ? { e: e2ee.handshake } : {} });
33747
34484
  reportGitRepos(getWs, logFn);
33748
34485
  }
33749
34486
  if (justAuthenticated && socket) {
33750
34487
  logFn(
33751
34488
  "Save these for future runs (access token may rotate; refresh token is stored in ~/.buildautomaton/config.json when you use browser auth):"
33752
34489
  );
33753
- logFn(` export BUILDAMATON_AUTH_TOKEN="${tokens.accessToken}"`);
33754
- logFn(` export BUILDAMATON_WORKSPACE_ID="${workspaceId}"`);
34490
+ logFn(` export BUILDAUTOMATON_AUTH_TOKEN="${tokens.accessToken}"`);
34491
+ logFn(` export BUILDAUTOMATON_WORKSPACE_ID="${workspaceId}"`);
33755
34492
  }
33756
34493
  }
33757
34494
  function handleClose(code, reason) {
@@ -33833,6 +34570,74 @@ function createMainBridgeWebSocketLifecycle(params) {
33833
34570
  return { connect };
33834
34571
  }
33835
34572
 
34573
+ // src/lib/e2ee/cli-e2ee-runtime.ts
34574
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
34575
+ function nonceFromCounter(prefix, counter) {
34576
+ const nonce = Buffer.alloc(E2EE_NONCE_BYTES);
34577
+ prefix.copy(nonce, 0);
34578
+ nonce.writeBigUInt64BE(counter, 4);
34579
+ return nonce;
34580
+ }
34581
+ function createCliE2eeRuntime(key) {
34582
+ const rawKey = Buffer.from(base64UrlDecode(key.k));
34583
+ const prefix = randomBytes(4);
34584
+ let counter = 0n;
34585
+ function nextNonce() {
34586
+ counter += 1n;
34587
+ return nonceFromCounter(prefix, counter);
34588
+ }
34589
+ function encryptObject(messageWithoutSensitiveFields, plaintext) {
34590
+ const nonce = nextNonce();
34591
+ const cipher = createCipheriv("aes-256-gcm", rawKey, nonce);
34592
+ const ciphertext = Buffer.concat([cipher.update(JSON.stringify(plaintext), "utf8"), cipher.final()]);
34593
+ const tag = cipher.getAuthTag();
34594
+ return {
34595
+ k: key.id,
34596
+ n: base64UrlEncode(nonce),
34597
+ c: base64UrlEncode(Buffer.concat([ciphertext, tag]))
34598
+ };
34599
+ }
34600
+ return {
34601
+ keyId: key.id,
34602
+ handshake: { k: key.id, a: key.a },
34603
+ encryptFields(message, fields) {
34604
+ const plaintext = {};
34605
+ const stripped = { ...message };
34606
+ let hasPlaintext = false;
34607
+ for (const field of fields) {
34608
+ if (Object.prototype.hasOwnProperty.call(stripped, field) && stripped[field] !== void 0) {
34609
+ plaintext[field] = stripped[field];
34610
+ delete stripped[field];
34611
+ hasPlaintext = true;
34612
+ }
34613
+ }
34614
+ if (!hasPlaintext) return message;
34615
+ stripped.ee = encryptObject(stripped, plaintext);
34616
+ return stripped;
34617
+ },
34618
+ decryptMessage(message) {
34619
+ const envelope = message.ee;
34620
+ if (!isE2eeEnvelope(envelope)) return message;
34621
+ if (envelope.k !== key.id) throw new Error(`E2EE key mismatch: ${envelope.k}`);
34622
+ const sealed = Buffer.from(base64UrlDecode(envelope.c));
34623
+ if (sealed.length < 16) throw new Error("Invalid E2EE payload.");
34624
+ const ciphertext = sealed.subarray(0, sealed.length - 16);
34625
+ const tag = sealed.subarray(sealed.length - 16);
34626
+ const nonce = Buffer.from(base64UrlDecode(envelope.n));
34627
+ const decipher = createDecipheriv("aes-256-gcm", rawKey, nonce);
34628
+ decipher.setAuthTag(tag);
34629
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
34630
+ const parsed = JSON.parse(decrypted);
34631
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
34632
+ throw new Error("E2EE payload did not decode to an object.");
34633
+ }
34634
+ const merged = { ...message, ...parsed };
34635
+ delete merged.ee;
34636
+ return merged;
34637
+ }
34638
+ };
34639
+ }
34640
+
33836
34641
  // src/bridge/connection/create-bridge-connection.ts
33837
34642
  async function createBridgeConnection(options) {
33838
34643
  const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
@@ -33866,7 +34671,8 @@ async function createBridgeConnection(options) {
33866
34671
  function getWs() {
33867
34672
  return state.currentWs;
33868
34673
  }
33869
- const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeCwd: getBridgeWorkspaceDirectory });
34674
+ const e2ee = options.e2eCertificate ? createCliE2eeRuntime(options.e2eCertificate) : void 0;
34675
+ const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeCwd: getBridgeWorkspaceDirectory, e2ee });
33870
34676
  const onBridgeIdentified = createOnBridgeIdentified({
33871
34677
  sessionWorktreeManager,
33872
34678
  devServerManager,
@@ -33885,7 +34691,10 @@ async function createBridgeConnection(options) {
33885
34691
  onBridgeIdentified,
33886
34692
  sendLocalSkillsReport,
33887
34693
  reportAutoDetectedAgents,
33888
- devServerManager
34694
+ devServerManager,
34695
+ e2ee,
34696
+ cloudApiBaseUrl: apiUrl,
34697
+ getCloudAccessToken: () => tokens.accessToken
33889
34698
  };
33890
34699
  const { connect } = createMainBridgeWebSocketLifecycle({
33891
34700
  state,
@@ -33897,7 +34706,8 @@ async function createBridgeConnection(options) {
33897
34706
  messageDeps,
33898
34707
  tokens,
33899
34708
  persistTokens,
33900
- onAuthInvalid
34709
+ onAuthInvalid,
34710
+ e2ee
33901
34711
  });
33902
34712
  connect();
33903
34713
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeWorkspaceDirectory());
@@ -33909,56 +34719,72 @@ async function createBridgeConnection(options) {
33909
34719
  };
33910
34720
  }
33911
34721
 
33912
- // src/run-bridge.ts
33913
- async function runBridge(options) {
33914
- installBridgeProcessResilience();
33915
- const { apiUrl, workspaceId, authToken, refreshToken, bridgeName, justAuthenticated, worktreesRootAbs } = options;
33916
- const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
33917
- const hasAuth = workspaceId && authToken;
33918
- if (!hasAuth) {
33919
- const handle2 = runPendingAuth({
33920
- apiUrl,
33921
- initialWorkspaceId: workspaceId,
33922
- preferredBridgeName: bridgeName,
33923
- log,
33924
- onAuth: (_auth) => {
33925
- }
33926
- });
33927
- const onSignal2 = (kind) => {
33928
- logImmediate(
33929
- kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
33930
- );
33931
- setImmediate(() => {
33932
- handle2.close();
33933
- process.exit(0);
33934
- });
34722
+ // src/e2e-certificates/key-command.ts
34723
+ import * as readline3 from "node:readline";
34724
+ function installE2eCertificateKeyCommand({
34725
+ log: log2,
34726
+ onOpenCertificate,
34727
+ onInterrupt
34728
+ }) {
34729
+ if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") {
34730
+ log2("[E2EE] Press c to import the E2EE key in a browser when running in an interactive terminal.");
34731
+ return () => {
33935
34732
  };
33936
- const onSigInt2 = () => onSignal2("interrupt");
33937
- const onSigTerm2 = () => onSignal2("stop");
33938
- process.on("SIGINT", onSigInt2);
33939
- process.on("SIGTERM", onSigTerm2);
33940
- const auth = await handle2.authPromise;
33941
- process.off("SIGINT", onSigInt2);
33942
- process.off("SIGTERM", onSigTerm2);
33943
- handle2.close();
33944
- if (!auth) return;
33945
- writeConfigForApi(apiUrl, {
33946
- workspaceId: auth.workspaceId,
33947
- token: auth.token,
33948
- refreshToken: auth.refreshToken
33949
- });
33950
- await runBridge({
33951
- apiUrl,
33952
- workspaceId: auth.workspaceId,
33953
- authToken: auth.token,
33954
- refreshToken: auth.refreshToken,
33955
- firehoseServerUrl,
33956
- bridgeName,
33957
- justAuthenticated: true,
33958
- worktreesRootAbs
33959
- });
33960
- return;
33961
34733
  }
34734
+ readline3.emitKeypressEvents(process.stdin);
34735
+ process.stdin.setRawMode(true);
34736
+ process.stdin.resume();
34737
+ const onKeypress = (str, key) => {
34738
+ if (key?.ctrl && key.name === "c") {
34739
+ onInterrupt();
34740
+ return;
34741
+ }
34742
+ if (!key?.ctrl && !key?.meta && (key?.name === "c" || str === "c")) {
34743
+ onOpenCertificate();
34744
+ }
34745
+ };
34746
+ process.stdin.on("keypress", onKeypress);
34747
+ log2("[E2EE] Press c to import the active E2EE key into the browser.");
34748
+ return () => {
34749
+ process.stdin.off("keypress", onKeypress);
34750
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
34751
+ process.stdin.setRawMode(false);
34752
+ }
34753
+ };
34754
+ }
34755
+
34756
+ // src/e2e-certificates/open-import-url.ts
34757
+ async function openE2eCertificateImportUrl({
34758
+ apiUrl,
34759
+ workspaceId,
34760
+ certificate,
34761
+ log: log2
34762
+ }) {
34763
+ const appUrl = appUrlForApiUrl(apiUrl);
34764
+ const payload = encodeURIComponent(certificate.pemBundle);
34765
+ const url2 = `${appUrl.replace(/\/$/, "")}/w/${encodeURIComponent(workspaceId)}/settings/e2e-encryption?certificate=${payload}`;
34766
+ log2(`[E2EE] Opening browser to import key "${certificate.name}" (${certificate.id})...`);
34767
+ try {
34768
+ await open_default(url2, { wait: false });
34769
+ } catch {
34770
+ log2("[E2EE] Could not open browser. Open this URL manually:");
34771
+ log2(url2);
34772
+ }
34773
+ }
34774
+
34775
+ // src/run-bridge-connected.ts
34776
+ async function runConnectedBridge(options, restartWithoutAuth) {
34777
+ const {
34778
+ apiUrl,
34779
+ workspaceId,
34780
+ authToken,
34781
+ refreshToken,
34782
+ justAuthenticated,
34783
+ worktreesRootAbs,
34784
+ e2eCertificate
34785
+ } = options;
34786
+ const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
34787
+ let cleanupKeyCommand;
33962
34788
  const handle = await createBridgeConnection({
33963
34789
  apiUrl,
33964
34790
  workspaceId,
@@ -33967,6 +34793,7 @@ async function runBridge(options) {
33967
34793
  firehoseServerUrl,
33968
34794
  justAuthenticated,
33969
34795
  worktreesRootAbs,
34796
+ e2eCertificate,
33970
34797
  log,
33971
34798
  persistTokens: (t) => {
33972
34799
  writeConfigForApi(apiUrl, {
@@ -33976,14 +34803,16 @@ async function runBridge(options) {
33976
34803
  });
33977
34804
  },
33978
34805
  onAuthInvalid: () => {
34806
+ cleanupKeyCommand?.();
33979
34807
  log("[Bridge service] Access token invalid or revoked; re-authenticating\u2026");
33980
34808
  clearConfigForApi(apiUrl);
33981
34809
  void handle.close().then(() => {
33982
- void runBridge({ apiUrl, firehoseServerUrl, worktreesRootAbs });
34810
+ void restartWithoutAuth({ apiUrl, firehoseServerUrl, worktreesRootAbs, e2eCertificate });
33983
34811
  });
33984
34812
  }
33985
34813
  });
33986
34814
  const onSignal = (kind) => {
34815
+ cleanupKeyCommand?.();
33987
34816
  logImmediate(
33988
34817
  kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
33989
34818
  );
@@ -33997,6 +34826,86 @@ async function runBridge(options) {
33997
34826
  const onSigTerm = () => onSignal("stop");
33998
34827
  process.on("SIGINT", onSigInt);
33999
34828
  process.on("SIGTERM", onSigTerm);
34829
+ if (e2eCertificate) {
34830
+ let openingCertificate = false;
34831
+ cleanupKeyCommand = installE2eCertificateKeyCommand({
34832
+ log,
34833
+ onInterrupt: onSigInt,
34834
+ onOpenCertificate: () => {
34835
+ if (openingCertificate) return;
34836
+ openingCertificate = true;
34837
+ void openE2eCertificateImportUrl({
34838
+ apiUrl,
34839
+ workspaceId,
34840
+ certificate: e2eCertificate,
34841
+ log
34842
+ }).finally(() => {
34843
+ openingCertificate = false;
34844
+ });
34845
+ }
34846
+ });
34847
+ }
34848
+ }
34849
+
34850
+ // src/run-bridge.ts
34851
+ async function runBridge(options) {
34852
+ installBridgeProcessResilience();
34853
+ const {
34854
+ apiUrl,
34855
+ workspaceId,
34856
+ authToken,
34857
+ bridgeName,
34858
+ worktreesRootAbs,
34859
+ e2eCertificate
34860
+ } = options;
34861
+ const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
34862
+ const hasAuth = workspaceId && authToken;
34863
+ if (!hasAuth) {
34864
+ const handle = runPendingAuth({
34865
+ apiUrl,
34866
+ initialWorkspaceId: workspaceId,
34867
+ preferredBridgeName: bridgeName,
34868
+ log,
34869
+ onAuth: (_auth) => {
34870
+ }
34871
+ });
34872
+ const onSignal = (kind) => {
34873
+ logImmediate(
34874
+ kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
34875
+ );
34876
+ setImmediate(() => {
34877
+ handle.close();
34878
+ process.exit(0);
34879
+ });
34880
+ };
34881
+ const onSigInt = () => onSignal("interrupt");
34882
+ const onSigTerm = () => onSignal("stop");
34883
+ process.on("SIGINT", onSigInt);
34884
+ process.on("SIGTERM", onSigTerm);
34885
+ const auth = await handle.authPromise;
34886
+ process.off("SIGINT", onSigInt);
34887
+ process.off("SIGTERM", onSigTerm);
34888
+ handle.close();
34889
+ if (!auth) return;
34890
+ writeConfigForApi(apiUrl, {
34891
+ workspaceId: auth.workspaceId,
34892
+ token: auth.token,
34893
+ refreshToken: auth.refreshToken
34894
+ });
34895
+ await runBridge({
34896
+ apiUrl,
34897
+ workspaceId: auth.workspaceId,
34898
+ authToken: auth.token,
34899
+ refreshToken: auth.refreshToken,
34900
+ firehoseServerUrl,
34901
+ bridgeName,
34902
+ justAuthenticated: true,
34903
+ worktreesRootAbs,
34904
+ e2eCertificate
34905
+ });
34906
+ return;
34907
+ }
34908
+ await runConnectedBridge(options, runBridge);
34000
34909
  }
34001
34910
  export {
34002
34911
  callSkill,