@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/README.md +4 -4
- package/dist/cli.js +1718 -607
- package/dist/cli.js.map +4 -4
- package/dist/index.js +1123 -214
- package/dist/index.js.map +4 -4
- package/package.json +4 -2
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 =
|
|
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 = "
|
|
22791
|
-
var PREVIEW_PORT_ENV = "
|
|
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
|
|
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 =
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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.
|
|
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-
|
|
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.
|
|
32680
|
+
this.firehoseSink.attach(send);
|
|
32077
32681
|
}
|
|
32078
32682
|
detachFirehose() {
|
|
32079
|
-
this.
|
|
32080
|
-
this.logViewerRefCountByServerId.clear();
|
|
32683
|
+
this.firehoseSink.detach();
|
|
32081
32684
|
}
|
|
32082
32685
|
handleFirehoseLogViewerOpen(serverId, _viewerId) {
|
|
32083
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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/
|
|
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
|
-
|
|
32903
|
-
|
|
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
|
-
|
|
32937
|
-
|
|
32938
|
-
|
|
32939
|
-
|
|
32940
|
-
|
|
33669
|
+
await runBridgePromptPreamble({
|
|
33670
|
+
getWs,
|
|
33671
|
+
log: log2,
|
|
33672
|
+
sessionWorktreeManager,
|
|
33673
|
+
sessionId,
|
|
33674
|
+
runId,
|
|
33675
|
+
effectiveCwd
|
|
32941
33676
|
});
|
|
32942
|
-
|
|
32943
|
-
|
|
32944
|
-
|
|
32945
|
-
|
|
32946
|
-
|
|
32947
|
-
|
|
32948
|
-
|
|
32949
|
-
|
|
32950
|
-
|
|
32951
|
-
|
|
32952
|
-
|
|
32953
|
-
|
|
32954
|
-
|
|
32955
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33607
|
-
|
|
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
|
|
33754
|
-
logFn(` export
|
|
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
|
|
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/
|
|
33913
|
-
|
|
33914
|
-
|
|
33915
|
-
|
|
33916
|
-
|
|
33917
|
-
|
|
33918
|
-
|
|
33919
|
-
|
|
33920
|
-
|
|
33921
|
-
|
|
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
|
|
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,
|