@buildautomaton/cli 0.1.69 → 0.1.71

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
@@ -22367,14 +22367,14 @@ var init_access = __esm({
22367
22367
 
22368
22368
  // src/sqlite/read-migration-sql.ts
22369
22369
  import { existsSync, readFileSync as readFileSync2 } from "node:fs";
22370
- import { dirname as dirname2, join } from "node:path";
22370
+ import { dirname as dirname2, join as join2 } from "node:path";
22371
22371
  import { fileURLToPath as fileURLToPath2 } from "node:url";
22372
22372
  function readMigrationSql(filename, ...searchSubdirs) {
22373
22373
  const moduleDir = dirname2(fileURLToPath2(import.meta.url));
22374
22374
  const candidates = searchSubdirs.flatMap((sub) => [
22375
- join(moduleDir, sub, filename),
22376
- join(moduleDir, "..", sub, filename),
22377
- join(moduleDir, "..", "..", "dist", sub, filename)
22375
+ join2(moduleDir, sub, filename),
22376
+ join2(moduleDir, "..", sub, filename),
22377
+ join2(moduleDir, "..", "..", "dist", sub, filename)
22378
22378
  ]);
22379
22379
  const resolved = candidates.find((path82) => existsSync(path82));
22380
22380
  if (!resolved) {
@@ -25255,26 +25255,26 @@ var init_member_access = __esm({
25255
25255
 
25256
25256
  // src/code-nav/tree-sitter-assets-dir.ts
25257
25257
  import { existsSync as existsSync2 } from "node:fs";
25258
- import { dirname as dirname3, join as join2 } from "node:path";
25258
+ import { dirname as dirname3, join as join3 } from "node:path";
25259
25259
  import { fileURLToPath as fileURLToPath3 } from "node:url";
25260
25260
  function repoNodeModulesDir(fromDir) {
25261
- return join2(fromDir, "..", "..", "..", "node_modules");
25261
+ return join3(fromDir, "..", "..", "..", "node_modules");
25262
25262
  }
25263
25263
  function firstDirWithWebTreeSitterWasm(candidates) {
25264
25264
  for (const dir of candidates) {
25265
- if (existsSync2(join2(dir, "web-tree-sitter.wasm"))) return dir;
25265
+ if (existsSync2(join3(dir, "web-tree-sitter.wasm"))) return dir;
25266
25266
  }
25267
25267
  return null;
25268
25268
  }
25269
25269
  function getTreeSitterAssetsDir() {
25270
25270
  const moduleDir = dirname3(fileURLToPath3(import.meta.url));
25271
25271
  const fromModule = firstDirWithWebTreeSitterWasm([
25272
- join2(moduleDir, "tree-sitter"),
25273
- join2(moduleDir, "..", "tree-sitter"),
25274
- join2(moduleDir, "..", "..", "dist", "tree-sitter")
25272
+ join3(moduleDir, "tree-sitter"),
25273
+ join3(moduleDir, "..", "tree-sitter"),
25274
+ join3(moduleDir, "..", "..", "dist", "tree-sitter")
25275
25275
  ]);
25276
25276
  if (fromModule) return fromModule;
25277
- const fromNodeModules = firstDirWithWebTreeSitterWasm([join2(repoNodeModulesDir(moduleDir), "web-tree-sitter")]);
25277
+ const fromNodeModules = firstDirWithWebTreeSitterWasm([join3(repoNodeModulesDir(moduleDir), "web-tree-sitter")]);
25278
25278
  if (fromNodeModules) return fromNodeModules;
25279
25279
  throw new Error(
25280
25280
  "Missing web-tree-sitter.wasm. Run `pnpm build` in packages/cli to copy tree-sitter assets into dist/tree-sitter."
@@ -25288,7 +25288,7 @@ var init_tree_sitter_assets_dir = __esm({
25288
25288
 
25289
25289
  // src/code-nav/parser/tree-sitter-runtime.ts
25290
25290
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
25291
- import { dirname as dirname4, join as join3 } from "node:path";
25291
+ import { dirname as dirname4, join as join4 } from "node:path";
25292
25292
  import { fileURLToPath as fileURLToPath4 } from "node:url";
25293
25293
  import { Parser, Language, Query, LANGUAGE_VERSION, MIN_COMPATIBLE_VERSION } from "web-tree-sitter";
25294
25294
  function withTreeSitterRuntimeLock(fn) {
@@ -25303,7 +25303,7 @@ async function ensureParserInit() {
25303
25303
  if (!initPromise2) {
25304
25304
  const assetsDir = getTreeSitterAssetsDir();
25305
25305
  initPromise2 = Parser.init({
25306
- locateFile: (scriptName) => join3(assetsDir, scriptName)
25306
+ locateFile: (scriptName) => join4(assetsDir, scriptName)
25307
25307
  }).catch((e) => {
25308
25308
  initPromise2 = null;
25309
25309
  throw e;
@@ -25313,9 +25313,9 @@ async function ensureParserInit() {
25313
25313
  }
25314
25314
  function resolveWasmPath(assetsDir, wasmFile) {
25315
25315
  const candidates = [
25316
- join3(assetsDir, wasmFile),
25317
- join3(assetsDir, "..", "..", "dist", "tree-sitter", wasmFile),
25318
- join3(assetsDir, "..", "..", "..", "node_modules", wasmFile.replace(".wasm", ""), wasmFile)
25316
+ join4(assetsDir, wasmFile),
25317
+ join4(assetsDir, "..", "..", "dist", "tree-sitter", wasmFile),
25318
+ join4(assetsDir, "..", "..", "..", "node_modules", wasmFile.replace(".wasm", ""), wasmFile)
25319
25319
  ];
25320
25320
  const hit = candidates.find((p) => existsSync3(p));
25321
25321
  if (!hit) throw new Error(`Missing tree-sitter wasm: ${wasmFile}`);
@@ -25324,10 +25324,10 @@ function resolveWasmPath(assetsDir, wasmFile) {
25324
25324
  function resolveTagsPath(assetsDir, tagsQueryFile) {
25325
25325
  const pkgName = tagsQueryFile.split("/")[0] ?? "";
25326
25326
  const candidates = [
25327
- join3(CODE_NAV_QUERIES_ROOT, tagsQueryFile),
25328
- join3(assetsDir, tagsQueryFile),
25329
- join3(assetsDir, "..", "..", "dist", "tree-sitter", tagsQueryFile),
25330
- join3(assetsDir, "..", "..", "..", "node_modules", pkgName, "queries", "tags.scm")
25327
+ join4(CODE_NAV_QUERIES_ROOT, tagsQueryFile),
25328
+ join4(assetsDir, tagsQueryFile),
25329
+ join4(assetsDir, "..", "..", "dist", "tree-sitter", tagsQueryFile),
25330
+ join4(assetsDir, "..", "..", "..", "node_modules", pkgName, "queries", "tags.scm")
25331
25331
  ];
25332
25332
  const hit = candidates.find((p) => existsSync3(p));
25333
25333
  if (!hit) throw new Error(`Missing tags query: ${tagsQueryFile}`);
@@ -25365,7 +25365,7 @@ var init_tree_sitter_runtime = __esm({
25365
25365
  loadedByKey = /* @__PURE__ */ new Map();
25366
25366
  failedLanguageKeys = /* @__PURE__ */ new Set();
25367
25367
  runtimeChain = Promise.resolve();
25368
- CODE_NAV_QUERIES_ROOT = join3(dirname4(fileURLToPath4(import.meta.url)), "..", "queries");
25368
+ CODE_NAV_QUERIES_ROOT = join4(dirname4(fileURLToPath4(import.meta.url)), "..", "queries");
25369
25369
  }
25370
25370
  });
25371
25371
 
@@ -26936,7 +26936,7 @@ var init_in_process_bridge = __esm({
26936
26936
  });
26937
26937
 
26938
26938
  // src/code-nav/symbol-index/worker/lifecycle/state.ts
26939
- import { dirname as dirname5, join as join4 } from "node:path";
26939
+ import { dirname as dirname5, join as join5 } from "node:path";
26940
26940
  import { fileURLToPath as fileURLToPath5 } from "node:url";
26941
26941
  function getWorker() {
26942
26942
  return worker;
@@ -26955,7 +26955,7 @@ function shouldUseInProcessWorker() {
26955
26955
  }
26956
26956
  function resolveWorkerScriptPath() {
26957
26957
  const moduleDir = dirname5(fileURLToPath5(import.meta.url));
26958
- return join4(moduleDir, "..", "worker.js");
26958
+ return join5(moduleDir, "..", "worker.js");
26959
26959
  }
26960
26960
  function resetLifecycleStateForTests() {
26961
26961
  terminatePromise = null;
@@ -29341,6 +29341,18 @@ function getAgentModelFromAgentConfig(config2) {
29341
29341
  return t !== "" ? t : null;
29342
29342
  }
29343
29343
 
29344
+ // ../types/src/overview/heatmap/constants.ts
29345
+ var HOUR_MS = 36e5;
29346
+ var DAY_MS = 24 * HOUR_MS;
29347
+
29348
+ // ../types/src/bridges/agents/types.ts
29349
+ var INSTALLABLE_BRIDGE_AGENTS = [
29350
+ { value: "claude-code", label: "Anthropic Claude Code", tokenLabel: "Anthropic API key", tokenEnvVar: "ANTHROPIC_API_KEY" },
29351
+ { value: "codex-acp", label: "Codex", tokenLabel: "OpenAI API key", tokenEnvVar: "OPENAI_API_KEY" },
29352
+ { value: "cursor-cli", label: "Cursor CLI agent", tokenLabel: "Cursor API key", tokenEnvVar: "CURSOR_API_KEY" },
29353
+ { value: "opencode", label: "OpenCode agent", tokenLabel: "Provider API key", tokenEnvVar: "OPENCODE_API_KEY" }
29354
+ ];
29355
+
29344
29356
  // src/agents/acp/safe-fs-path.ts
29345
29357
  import * as path2 from "node:path";
29346
29358
  function resolveSafePathUnderCwd(cwd, filePath) {
@@ -30574,7 +30586,7 @@ function installBridgeProcessResilience() {
30574
30586
  }
30575
30587
 
30576
30588
  // src/cli-version.ts
30577
- var CLI_VERSION = "0.1.69".length > 0 ? "0.1.69" : "0.0.0-dev";
30589
+ var CLI_VERSION = "0.1.71".length > 0 ? "0.1.71" : "0.0.0-dev";
30578
30590
 
30579
30591
  // src/connection/heartbeat/constants.ts
30580
30592
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -31696,6 +31708,46 @@ function runPendingAuth(options) {
31696
31708
  // src/run-bridge-connected.ts
31697
31709
  init_log();
31698
31710
 
31711
+ // src/agents/acp/clients/bridge-agent-path.ts
31712
+ import { execFileSync as execFileSync2 } from "node:child_process";
31713
+ import { homedir } from "node:os";
31714
+ import { join } from "node:path";
31715
+ function getBridgeAgentPathEntries(home = process.env.HOME ?? homedir()) {
31716
+ const entries = [
31717
+ join(home, ".local", "bin"),
31718
+ join(home, ".npm-global", "bin"),
31719
+ "/usr/local/bin"
31720
+ ];
31721
+ const prefix = process.env.NPM_CONFIG_PREFIX;
31722
+ if (prefix) entries.push(join(prefix, "bin"));
31723
+ try {
31724
+ const npmBin = execFileSync2("npm", ["bin", "-g"], { encoding: "utf8", timeout: 2e3 }).trim();
31725
+ if (npmBin) entries.push(npmBin);
31726
+ } catch {
31727
+ }
31728
+ return entries;
31729
+ }
31730
+ function augmentPath(pathValue, extra) {
31731
+ const current = pathValue ?? "";
31732
+ const parts = current.split(":").filter(Boolean);
31733
+ const seen = new Set(parts);
31734
+ const prefix = [];
31735
+ for (const entry of extra) {
31736
+ if (!entry || seen.has(entry)) continue;
31737
+ seen.add(entry);
31738
+ prefix.push(entry);
31739
+ }
31740
+ if (prefix.length === 0) return current;
31741
+ return current ? `${prefix.join(":")}:${current}` : prefix.join(":");
31742
+ }
31743
+ function ensureBridgeAgentPathInProcessEnv() {
31744
+ const extra = getBridgeAgentPathEntries();
31745
+ process.env.PATH = augmentPath(process.env.PATH, extra);
31746
+ }
31747
+ function bridgeAgentPathEnv(base = process.env) {
31748
+ return { ...base, PATH: augmentPath(base.PATH, getBridgeAgentPathEntries()) };
31749
+ }
31750
+
31699
31751
  // src/files/watch-file-index.ts
31700
31752
  import path25 from "node:path";
31701
31753
 
@@ -33000,7 +33052,10 @@ async function execFileShutdownAware(file2, args, timeoutMs) {
33000
33052
  async function isCommandOnPath(command, timeoutMs = COMMAND_ON_PATH_PROBE_TIMEOUT_MS) {
33001
33053
  if (isCliImmediateShutdownRequested()) return false;
33002
33054
  try {
33003
- await execFileShutdownAware("which", [command], timeoutMs);
33055
+ await execFileAsync5("which", [command], {
33056
+ timeout: timeoutMs,
33057
+ env: bridgeAgentPathEnv()
33058
+ });
33004
33059
  return true;
33005
33060
  } catch {
33006
33061
  return false;
@@ -33692,15 +33747,15 @@ function resolveAgentCommand(preferredAgentType) {
33692
33747
  }
33693
33748
 
33694
33749
  // src/agents/acp/session-file-change-path-kind.ts
33695
- import { execFileSync as execFileSync4 } from "node:child_process";
33750
+ import { execFileSync as execFileSync5 } from "node:child_process";
33696
33751
  import { existsSync as existsSync4, statSync } from "node:fs";
33697
33752
 
33698
33753
  // src/git/get-git-repo-root-sync.ts
33699
- import { execFileSync as execFileSync2 } from "node:child_process";
33754
+ import { execFileSync as execFileSync3 } from "node:child_process";
33700
33755
  import * as path27 from "node:path";
33701
33756
  function getGitRepoRootSync(startDir) {
33702
33757
  try {
33703
- const out = execFileSync2("git", ["rev-parse", "--show-toplevel"], {
33758
+ const out = execFileSync3("git", ["rev-parse", "--show-toplevel"], {
33704
33759
  cwd: path27.resolve(startDir),
33705
33760
  encoding: "utf8",
33706
33761
  stdio: ["ignore", "pipe", "ignore"],
@@ -33713,7 +33768,7 @@ function getGitRepoRootSync(startDir) {
33713
33768
  }
33714
33769
 
33715
33770
  // src/agents/acp/workspace-files.ts
33716
- import { execFileSync as execFileSync3 } from "node:child_process";
33771
+ import { execFileSync as execFileSync4 } from "node:child_process";
33717
33772
  import { readFileSync as readFileSync4 } from "node:fs";
33718
33773
  import * as path28 from "node:path";
33719
33774
  function resolveWorkspaceFilePath(sessionParentPath, rawPath) {
@@ -33774,7 +33829,7 @@ function readGitHeadBlob(sessionParentPath, displayPath) {
33774
33829
  const gitRoot = getGitRepoRootSync(sessionParentPath);
33775
33830
  const execCwd = gitRoot ?? sessionParentPath;
33776
33831
  try {
33777
- return execFileSync3("git", ["show", `HEAD:${displayPath}`], {
33832
+ return execFileSync4("git", ["show", `HEAD:${displayPath}`], {
33778
33833
  cwd: execCwd,
33779
33834
  encoding: "utf8",
33780
33835
  maxBuffer: 50 * 1024 * 1024
@@ -33790,7 +33845,7 @@ function gitHeadPathObjectType(sessionParentPath, displayPath) {
33790
33845
  const gitRoot = getGitRepoRootSync(sessionParentPath);
33791
33846
  if (!gitRoot) return null;
33792
33847
  try {
33793
- return execFileSync4("git", ["cat-file", "-t", `HEAD:${displayPath}`], {
33848
+ return execFileSync5("git", ["cat-file", "-t", `HEAD:${displayPath}`], {
33794
33849
  cwd: gitRoot,
33795
33850
  encoding: "utf8"
33796
33851
  }).trim();
@@ -34645,11 +34700,11 @@ init_cli_process_interrupt();
34645
34700
  var BRIDGE_MCP_SERVER_NAME2 = "buildautomaton-bridge";
34646
34701
 
34647
34702
  // src/mcp/resolve-bridge-mcp-server-path.ts
34648
- import { dirname as dirname7, join as join5 } from "node:path";
34703
+ import { dirname as dirname7, join as join6 } from "node:path";
34649
34704
  import { fileURLToPath as fileURLToPath7 } from "node:url";
34650
34705
  function resolveBridgeMcpServerScriptPath() {
34651
34706
  const cliDir = dirname7(fileURLToPath7(import.meta.url));
34652
- return join5(cliDir, "bridge-mcp-server.js");
34707
+ return join6(cliDir, "bridge-mcp-server.js");
34653
34708
  }
34654
34709
 
34655
34710
  // src/mcp/build-acp-mcp-servers.ts
@@ -48443,6 +48498,7 @@ var API_TO_BRIDGE_MESSAGE_TYPES = [
48443
48498
  "code_nav",
48444
48499
  "skill_layout_request",
48445
48500
  "install_skills",
48501
+ "install_agent",
48446
48502
  "refresh_local_skills",
48447
48503
  "bridge_git_context_request",
48448
48504
  "list_repo_branches_request"
@@ -48518,6 +48574,100 @@ var handleBridgeHeartbeatAck = (msg, deps) => {
48518
48574
  deps.onBridgeHeartbeatAck?.(Math.trunc(raw));
48519
48575
  };
48520
48576
 
48577
+ // src/routing/dispatch/connection.ts
48578
+ function dispatchBridgeConnectionMessage(msg, deps) {
48579
+ switch (msg.type) {
48580
+ case "auth_token":
48581
+ handleAuthToken(msg, deps);
48582
+ break;
48583
+ case "bridge_identified":
48584
+ handleBridgeIdentified(msg, deps);
48585
+ break;
48586
+ case "ha":
48587
+ handleBridgeHeartbeatAck(msg, deps);
48588
+ break;
48589
+ }
48590
+ }
48591
+
48592
+ // src/routing/handlers/preview-environment-control.ts
48593
+ var handlePreviewEnvironmentControl = (msg, deps) => {
48594
+ let wire;
48595
+ try {
48596
+ wire = deps.e2ee ? deps.e2ee.decryptMessage(msg) : msg;
48597
+ } catch (e) {
48598
+ deps.log(`[E2EE] Could not decrypt preview environment command: ${e instanceof Error ? e.message : String(e)}`);
48599
+ return;
48600
+ }
48601
+ const environmentId = typeof wire.environmentId === "string" ? wire.environmentId : "";
48602
+ const action = wire.action === "start" || wire.action === "stop" ? wire.action : null;
48603
+ if (!environmentId || !action) return;
48604
+ deps.previewEnvironmentManager?.handleControl(environmentId, action);
48605
+ };
48606
+
48607
+ // src/routing/handlers/preview-environments-config.ts
48608
+ var VALID_PORT = (p) => typeof p === "number" && Number.isInteger(p) && p > 0 && p < 65536;
48609
+ var handlePreviewEnvironmentsConfig = (msg, deps) => {
48610
+ const previewEnvironments = msg.previewEnvironments;
48611
+ const proxyPorts = Array.isArray(msg.proxyPorts) ? msg.proxyPorts.filter(VALID_PORT) : void 0;
48612
+ setImmediate(() => {
48613
+ deps.previewEnvironmentManager?.applyConfig(previewEnvironments ?? []);
48614
+ const environmentIds = parsePreviewEnvironmentDefs(previewEnvironments ?? []).map((d) => d.environmentId);
48615
+ void deps.previewWorktreeManager?.syncConfiguredEnvironments(environmentIds);
48616
+ if (proxyPorts !== void 0) {
48617
+ deps.updateFirehoseProxyPorts?.(proxyPorts);
48618
+ }
48619
+ });
48620
+ };
48621
+
48622
+ // src/routing/handlers/send-deploy-session-to-preview-result.ts
48623
+ function sendDeploySessionToPreviewResult(ws, id, payload) {
48624
+ if (!ws) return;
48625
+ sendWsMessage(ws, { type: "deploy_session_to_preview_result", id, ...payload });
48626
+ }
48627
+
48628
+ // src/routing/handlers/deploy-session-to-preview.ts
48629
+ var handleDeploySessionToPreviewMessage = (msg, deps) => {
48630
+ if (typeof msg.id !== "string") return;
48631
+ const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
48632
+ const environmentId = typeof msg.environmentId === "string" ? msg.environmentId : "";
48633
+ if (!sessionId || !environmentId) return;
48634
+ void (async () => {
48635
+ const ws = deps.getWs();
48636
+ const reply = (payload) => sendDeploySessionToPreviewResult(ws, msg.id, payload);
48637
+ try {
48638
+ if (!deps.previewWorktreeManager) {
48639
+ reply({ ok: false, error: "Preview worktree manager unavailable" });
48640
+ return;
48641
+ }
48642
+ const result = await deploySessionToPreviewEnvironment({
48643
+ sessionWorktreeManager: deps.sessionWorktreeManager,
48644
+ previewWorktreeManager: deps.previewWorktreeManager,
48645
+ sessionId,
48646
+ environmentId,
48647
+ log: deps.log
48648
+ });
48649
+ reply(result);
48650
+ } catch (e) {
48651
+ reply({ ok: false, error: e instanceof Error ? e.message : String(e) });
48652
+ }
48653
+ })();
48654
+ };
48655
+
48656
+ // src/routing/dispatch/preview.ts
48657
+ function dispatchBridgePreviewMessage(msg, deps) {
48658
+ switch (msg.type) {
48659
+ case "preview_environments_config":
48660
+ handlePreviewEnvironmentsConfig(msg, deps);
48661
+ break;
48662
+ case "preview_environment_control":
48663
+ handlePreviewEnvironmentControl(msg, deps);
48664
+ break;
48665
+ case "deploy_session_to_preview":
48666
+ handleDeploySessionToPreviewMessage(msg, deps);
48667
+ break;
48668
+ }
48669
+ }
48670
+
48521
48671
  // src/agents/acp/from-bridge/handle-bridge-agent-config.ts
48522
48672
  function handleBridgeAgentConfig(msg, { acpManager }) {
48523
48673
  if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
@@ -49156,193 +49306,331 @@ var handleSessionRequestResponseMessage = (msg, deps) => {
49156
49306
  handleBridgeSessionRequestResponse(msg, deps);
49157
49307
  };
49158
49308
 
49159
- // src/skills/handle-skill-call.ts
49160
- function handleSkillCall(msg, socket, log2) {
49161
- callSkill(msg.skillId, msg.operationId, msg.params ?? {}).then((result) => {
49162
- sendWsMessage(socket, { type: "skill_result", id: msg.id, result });
49163
- }).catch((err) => {
49164
- sendWsMessage(socket, { type: "skill_result", id: msg.id, error: String(err) });
49165
- log2(`[Bridge service] Skill invocation failed (${msg.skillId}/${msg.operationId}): ${err}`);
49309
+ // src/routing/handlers/git/handle-session-git-commit.ts
49310
+ async function handleSessionGitCommitAction(deps, sessionId, msg, reply) {
49311
+ const branch = typeof msg.branch === "string" ? msg.branch : "";
49312
+ const message = typeof msg.message === "string" ? msg.message : "";
49313
+ const pushAfterCommit = msg.pushAfterCommit === true;
49314
+ if (!branch.trim() || !message.trim()) {
49315
+ reply({ ok: false, error: "branch and message are required for commit" });
49316
+ return;
49317
+ }
49318
+ const commitRes = await deps.sessionWorktreeManager.commitSession({
49319
+ sessionId,
49320
+ branch: branch.trim(),
49321
+ message: message.trim(),
49322
+ push: pushAfterCommit
49323
+ });
49324
+ if (!commitRes.ok) {
49325
+ reply({ ok: false, error: commitRes.error ?? "Commit failed" });
49326
+ return;
49327
+ }
49328
+ const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
49329
+ reply({
49330
+ ok: true,
49331
+ hasUncommittedChanges: st.hasUncommittedChanges,
49332
+ hasUnpushedCommits: st.hasUnpushedCommits,
49333
+ uncommittedFileCount: st.uncommittedFileCount
49166
49334
  });
49167
49335
  }
49168
49336
 
49169
- // src/routing/handlers/skill-call.ts
49170
- var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
49171
- const skillId = typeof msg.skillId === "string" ? msg.skillId : "";
49172
- const operationId = typeof msg.operationId === "string" ? msg.operationId : "";
49173
- if (!skillId || !operationId) return;
49174
- const socket = getWs();
49175
- if (!socket) return;
49176
- handleSkillCall(
49177
- { id: msg.id, skillId, operationId, params: msg.params },
49178
- socket,
49179
- log2
49180
- );
49181
- };
49182
-
49183
- // src/files/browser/send-file-browser-message.ts
49184
- function sendFileBrowserMessage(socket, e2ee, payload) {
49185
- sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
49337
+ // src/routing/handlers/git/session-git-changes-params.ts
49338
+ function readString(value) {
49339
+ return typeof value === "string" ? value.trim() : "";
49340
+ }
49341
+ function readChangesObject(msg) {
49342
+ const changes = msg.changes;
49343
+ if (!changes || typeof changes !== "object" || Array.isArray(changes)) return null;
49344
+ return changes;
49345
+ }
49346
+ function parseSessionGitChangesParams(msg) {
49347
+ const nested = readChangesObject(msg);
49348
+ const file2 = nested?.file && typeof nested.file === "object" && !Array.isArray(nested.file) ? nested.file : null;
49349
+ const repoRelPath = readString(nested?.repoRelPath) || readString(msg.changesRepoRelPath);
49350
+ const viewRaw = nested?.view ?? msg.changesView;
49351
+ const view = viewRaw === "commit" ? "commit" : "working";
49352
+ const commitSha = readString(nested?.commitSha) || readString(msg.changesCommitSha);
49353
+ const recentCommitsLimit = nested?.recentCommitsLimit ?? msg.changesRecentCommitsLimit;
49354
+ return {
49355
+ repoRelPath,
49356
+ view,
49357
+ commitSha,
49358
+ recentCommitsLimit: typeof recentCommitsLimit === "number" || typeof recentCommitsLimit === "string" ? recentCommitsLimit : void 0,
49359
+ fileWorkspaceRelPath: readString(file2?.workspaceRelPath),
49360
+ fileChange: readString(file2?.change),
49361
+ fileMovedFromWorkspaceRelPath: readString(file2?.movedFromWorkspaceRelPath)
49362
+ };
49186
49363
  }
49187
49364
 
49188
- // src/files/browser/handle-file-browser-list.ts
49189
- async function handleFileBrowserList(socket, e2ee, id, reqPath, sessionParentPath, gitScope) {
49190
- const result = await executeFileBrowserList({ reqPath, sessionParentPath, gitScope });
49191
- if ("error" in result) {
49192
- sendWsMessage(socket, { type: "file_browser_response", id, error: result.error });
49365
+ // src/routing/handlers/git/handle-session-git-get-change-file-patch.ts
49366
+ async function handleSessionGitGetChangeFilePatchAction(deps, msg, reply) {
49367
+ const changes = parseSessionGitChangesParams(msg);
49368
+ const { repoRelPath: repoRel, view, commitSha, fileWorkspaceRelPath, fileChange, fileMovedFromWorkspaceRelPath } = changes;
49369
+ const change = parseWorkingTreeChangeKind(fileChange);
49370
+ if (!repoRel || !fileWorkspaceRelPath || !change) {
49371
+ reply({
49372
+ ok: false,
49373
+ error: "changes.repoRelPath, changes.file.workspaceRelPath, and changes.file.change are required"
49374
+ });
49193
49375
  return;
49194
49376
  }
49195
- sendFileBrowserMessage(socket, e2ee, { type: "file_browser_response", id, entries: result.entries });
49196
- }
49197
-
49198
- // src/files/browser/handle-file-browser-read.ts
49199
- async function handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPath, gitScope) {
49200
- const result = await executeFileBrowserRead({ msg, reqPath, sessionParentPath, gitScope });
49201
- if ("error" in result) {
49202
- sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
49377
+ if (view === "commit" && !commitSha) {
49378
+ reply({ ok: false, error: "changes.commitSha is required for commit file patch" });
49203
49379
  return;
49204
49380
  }
49205
- const payload = {
49206
- type: "file_browser_response",
49207
- id: msg.id,
49208
- content: result.content,
49209
- totalLines: result.totalLines,
49210
- size: result.size
49211
- };
49212
- if (result.lineOffset != null) payload.lineOffset = result.lineOffset;
49213
- if (result.mimeType != null) payload.mimeType = result.mimeType;
49214
- if (result.resolvedPath != null) payload.resolvedPath = result.resolvedPath;
49215
- sendFileBrowserMessage(socket, e2ee, payload);
49381
+ const patch = await deps.sessionWorktreeManager.getSessionWorkingTreeChangeFilePatch(msg.sessionId, {
49382
+ repoRelPath: repoRel,
49383
+ workspaceRelPath: fileWorkspaceRelPath,
49384
+ change,
49385
+ movedFromWorkspaceRelPath: fileMovedFromWorkspaceRelPath || null,
49386
+ basis: view === "commit" ? { kind: "commit", sha: commitSha } : { kind: "working" }
49387
+ });
49388
+ reply(
49389
+ {
49390
+ ok: true,
49391
+ patchContent: patch.patchContent ?? null,
49392
+ totalLines: patch.totalLines
49393
+ },
49394
+ ["patchContent"]
49395
+ );
49216
49396
  }
49217
49397
 
49218
- // src/files/browser/index.ts
49219
- init_in_flight();
49220
- function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
49221
- beginFileBrowserRequest();
49222
- void (async () => {
49223
- try {
49224
- const reqPath = msg.path.replace(/^\/+/, "") || ".";
49225
- const sessionParentPath = sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
49226
- const gitScope = resolveGitBranchScope(msg);
49227
- if (msg.source === "git_branch" && typeof msg.repoRelPath === "string" && typeof msg.branch === "string" && msg.branch.trim().length > 0 && !gitScope) {
49228
- sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: "Invalid repository path" });
49229
- return;
49230
- }
49231
- if (msg.op === "read") {
49232
- await handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPath, gitScope);
49233
- } else {
49234
- await handleFileBrowserList(socket, e2ee, msg.id, reqPath, sessionParentPath, gitScope);
49235
- }
49236
- } finally {
49237
- endFileBrowserRequest();
49238
- }
49239
- })();
49398
+ // src/routing/handlers/git/coalesce-list-changes.ts
49399
+ var listChangesInflight = /* @__PURE__ */ new Map();
49400
+ function normalizeListChangesRepoPathRelativeToWorkspaceRoot(repoRelPath) {
49401
+ if (!repoRelPath?.trim()) return "";
49402
+ return normalizeRepoPathRelativeToWorkspaceRoot(repoRelPath.trim());
49403
+ }
49404
+ function listChangesInflightKey(sessionId, opts) {
49405
+ const basis = opts?.basis;
49406
+ return [
49407
+ sessionId,
49408
+ normalizeListChangesRepoPathRelativeToWorkspaceRoot(opts?.repoRelPath),
49409
+ basis?.kind === "commit" ? `commit:${basis.sha}` : "working",
49410
+ String(opts?.recentCommitsLimit ?? "")
49411
+ ].join("|");
49412
+ }
49413
+ function getSessionWorkingTreeChangeDetailsCoalesced(sessionWorktreeManager, sessionId, opts) {
49414
+ const key = listChangesInflightKey(sessionId, opts);
49415
+ const existing = listChangesInflight.get(key);
49416
+ if (existing) return existing;
49417
+ const promise2 = sessionWorktreeManager.getSessionWorkingTreeChangeDetails(sessionId, opts).finally(() => {
49418
+ if (listChangesInflight.get(key) === promise2) listChangesInflight.delete(key);
49419
+ });
49420
+ listChangesInflight.set(key, promise2);
49421
+ return promise2;
49240
49422
  }
49241
49423
 
49242
- // src/files/handle-file-browser-search.ts
49243
- init_yield_to_event_loop();
49244
- import path79 from "node:path";
49245
- var SEARCH_LIMIT = 100;
49246
- function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
49247
- void (async () => {
49248
- await yieldToEventLoop();
49249
- const q = typeof msg.q === "string" ? msg.q : "";
49250
- const sessionParentPath = path79.resolve(
49251
- sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
49252
- );
49253
- if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
49254
- triggerFileIndexBuild(sessionParentPath);
49255
- const payload2 = {
49256
- type: "file_browser_search_response",
49257
- id: msg.id,
49258
- paths: [],
49259
- indexReady: false
49260
- };
49261
- sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
49424
+ // src/routing/handlers/git/handle-session-git-list-changes.ts
49425
+ async function handleSessionGitListChangesAction(deps, msg, reply) {
49426
+ const changes = parseSessionGitChangesParams(msg);
49427
+ const { repoRelPath: repoRel, view, commitSha, recentCommitsLimit } = changes;
49428
+ if (view === "commit") {
49429
+ if (!repoRel || !commitSha) {
49430
+ reply({
49431
+ ok: false,
49432
+ error: "changes.repoRelPath and changes.commitSha are required for commit view"
49433
+ });
49262
49434
  return;
49263
49435
  }
49264
- const results = await searchBridgeFilePathsAsync(sessionParentPath, q, SEARCH_LIMIT);
49265
- const payload = {
49266
- type: "file_browser_search_response",
49267
- id: msg.id,
49268
- paths: results,
49269
- indexReady: true
49270
- };
49271
- sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["paths"]) : payload);
49272
- })();
49436
+ }
49437
+ const opts = repoRel && view === "commit" && commitSha ? {
49438
+ repoRelPath: repoRel,
49439
+ basis: { kind: "commit", sha: commitSha },
49440
+ recentCommitsLimit
49441
+ } : repoRel ? { repoRelPath: repoRel, basis: { kind: "working" }, recentCommitsLimit } : recentCommitsLimit !== void 0 ? { recentCommitsLimit } : void 0;
49442
+ const repos = await getSessionWorkingTreeChangeDetailsCoalesced(
49443
+ deps.sessionWorktreeManager,
49444
+ msg.sessionId,
49445
+ opts
49446
+ );
49447
+ reply({ ok: true, repos }, ["repos"]);
49273
49448
  }
49274
- function triggerFileIndexBuild(sessionParentPath = getBridgeRoot()) {
49275
- setImmediate(() => {
49276
- void ensureFileIndexAsync(sessionParentPath).catch((e) => {
49277
- console.error("[file-index] Background build failed:", e);
49278
- });
49449
+
49450
+ // src/routing/handlers/git/handle-session-git-push.ts
49451
+ async function handleSessionGitPushAction(deps, sessionId, reply) {
49452
+ const pushRes = await deps.sessionWorktreeManager.pushSessionUpstream(sessionId);
49453
+ if (!pushRes.ok) {
49454
+ reply({ ok: false, error: pushRes.error ?? "Push failed" });
49455
+ return;
49456
+ }
49457
+ const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
49458
+ reply({
49459
+ ok: true,
49460
+ hasUncommittedChanges: st.hasUncommittedChanges,
49461
+ hasUnpushedCommits: st.hasUnpushedCommits,
49462
+ uncommittedFileCount: st.uncommittedFileCount
49279
49463
  });
49280
49464
  }
49281
49465
 
49282
- // src/routing/handlers/file-browser-messages.ts
49283
- function handleFileBrowserRequestMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
49284
- if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
49285
- const socket = getWs();
49286
- if (!socket) return;
49287
- handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager);
49288
- }
49289
- function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
49290
- if (typeof msg.id !== "string") return;
49291
- const socket = getWs();
49292
- if (!socket) return;
49293
- handleFileBrowserSearch(
49294
- msg,
49295
- socket,
49296
- e2ee,
49297
- sessionWorktreeManager
49298
- );
49466
+ // src/routing/handlers/git/handle-session-git-status.ts
49467
+ async function handleSessionGitStatusAction(deps, sessionId, reply) {
49468
+ const r = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
49469
+ reply({
49470
+ ok: true,
49471
+ hasUncommittedChanges: r.hasUncommittedChanges,
49472
+ hasUnpushedCommits: r.hasUnpushedCommits,
49473
+ uncommittedFileCount: r.uncommittedFileCount
49474
+ });
49299
49475
  }
49300
49476
 
49301
- // src/code-nav/handlers/send-code-nav-response.ts
49302
- var ENCRYPTED_FIELDS = ["definition", "definitions", "references", "symbols", "confidentTarget"];
49303
- function sendCodeNavResponse(socket, e2ee, payload) {
49304
- const fieldsToEncrypt = ENCRYPTED_FIELDS.filter((field) => field in payload && payload[field] !== void 0);
49305
- sendWsMessage(
49306
- socket,
49307
- e2ee && fieldsToEncrypt.length > 0 ? e2ee.encryptFields(payload, [...fieldsToEncrypt]) : payload
49308
- );
49477
+ // src/routing/handlers/git/send-session-git-result.ts
49478
+ function sendSessionGitResult(ws, id, payload, e2ee, encryptedFields = []) {
49479
+ if (!ws) return;
49480
+ const message = { type: "session_git_result", id, ...payload };
49481
+ let wire = message;
49482
+ if (e2ee && encryptedFields.length > 0) {
49483
+ wire = e2ee.encryptFields(message, encryptedFields);
49484
+ }
49485
+ sendWsMessage(ws, wire);
49309
49486
  }
49310
49487
 
49311
- // src/code-nav/handlers/handle-code-nav-request.ts
49312
- function handleCodeNavRequest(msg, socket, e2ee, sessionWorktreeManager) {
49488
+ // src/routing/handlers/git/session-git-request.ts
49489
+ var handleSessionGitRequestMessage = (msg, deps) => {
49490
+ if (typeof msg.id !== "string") return;
49491
+ const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
49492
+ const action = msg.action;
49493
+ if (!sessionId || action !== "status" && action !== "push" && action !== "commit" && action !== "list_changes" && action !== "file_diff")
49494
+ return;
49313
49495
  void (async () => {
49314
- const body = await executeCodeNavRequest(msg, sessionWorktreeManager);
49315
- sendCodeNavResponse(socket, e2ee, {
49316
- type: "code_nav_response",
49317
- id: msg.id,
49318
- ...body
49496
+ const ws = deps.getWs();
49497
+ const reply = (payload, encryptedFields = []) => sendSessionGitResult(ws, msg.id, payload, deps.e2ee, encryptedFields);
49498
+ try {
49499
+ if (action === "status") {
49500
+ await handleSessionGitStatusAction(deps, sessionId, reply);
49501
+ return;
49502
+ }
49503
+ if (action === "list_changes") {
49504
+ await handleSessionGitListChangesAction(deps, { ...msg, sessionId }, reply);
49505
+ return;
49506
+ }
49507
+ if (action === "file_diff") {
49508
+ await handleSessionGitGetChangeFilePatchAction(deps, { ...msg, sessionId }, reply);
49509
+ return;
49510
+ }
49511
+ if (action === "push") {
49512
+ await handleSessionGitPushAction(deps, sessionId, reply);
49513
+ return;
49514
+ }
49515
+ await handleSessionGitCommitAction(deps, sessionId, msg, reply);
49516
+ } catch (e) {
49517
+ reply({ ok: false, error: e instanceof Error ? e.message : String(e) });
49518
+ }
49519
+ })();
49520
+ };
49521
+
49522
+ // src/routing/handlers/rename-session-branch.ts
49523
+ var handleRenameSessionBranchMessage = (msg, deps) => {
49524
+ const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
49525
+ const newBranch = typeof msg.newBranch === "string" ? msg.newBranch : "";
49526
+ if (!sessionId || !newBranch) return;
49527
+ void deps.sessionWorktreeManager.renameSessionBranch(sessionId, newBranch);
49528
+ };
49529
+
49530
+ // src/routing/handlers/session-archived.ts
49531
+ var handleSessionArchivedMessage = (msg, deps) => {
49532
+ const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
49533
+ if (!sessionId) return;
49534
+ void deps.sessionWorktreeManager.removeSessionWorktrees(sessionId);
49535
+ };
49536
+
49537
+ // src/routing/handlers/session-discarded.ts
49538
+ var handleSessionDiscardedMessage = (msg, deps) => {
49539
+ const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
49540
+ if (!sessionId) return;
49541
+ void deps.sessionWorktreeManager.removeSessionWorktrees(sessionId);
49542
+ };
49543
+
49544
+ // src/routing/handlers/revert-turn-snapshot.ts
49545
+ import * as fs59 from "node:fs";
49546
+ var handleRevertTurnSnapshotMessage = (msg, deps) => {
49547
+ const id = typeof msg.id === "string" ? msg.id : "";
49548
+ const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
49549
+ const turnId = typeof msg.turnId === "string" ? msg.turnId : "";
49550
+ if (!id || !sessionId || !turnId) return;
49551
+ const { getWs, log: log2, sessionWorktreeManager } = deps;
49552
+ void (async () => {
49553
+ const s = getWs();
49554
+ if (!s) return;
49555
+ const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
49556
+ const file2 = snapshotFilePath(agentBase, turnId);
49557
+ try {
49558
+ await fs59.promises.access(file2, fs59.constants.F_OK);
49559
+ } catch {
49560
+ sendWsMessage(s, {
49561
+ type: "revert_turn_snapshot_result",
49562
+ id,
49563
+ ok: false,
49564
+ error: "No snapshot found for this turn (git state may be unavailable)."
49565
+ });
49566
+ return;
49567
+ }
49568
+ const res = await applyPreTurnSnapshot(file2, log2);
49569
+ sendWsMessage(s, {
49570
+ type: "revert_turn_snapshot_result",
49571
+ id,
49572
+ ok: res.ok,
49573
+ ...res.error ? { error: res.error } : {}
49319
49574
  });
49320
49575
  })();
49576
+ };
49577
+
49578
+ // src/routing/dispatch/session.ts
49579
+ function dispatchBridgeSessionMessage(msg, deps) {
49580
+ switch (msg.type) {
49581
+ case "agent_config":
49582
+ handleAgentConfigMessage(msg, deps);
49583
+ break;
49584
+ case "prompt_queue_state":
49585
+ handlePromptQueueStateMessage(msg, deps);
49586
+ break;
49587
+ case "prompt":
49588
+ handlePromptMessage(msg, deps);
49589
+ break;
49590
+ case "session_git_request":
49591
+ handleSessionGitRequestMessage(msg, deps);
49592
+ break;
49593
+ case "rename_session_branch":
49594
+ handleRenameSessionBranchMessage(msg, deps);
49595
+ break;
49596
+ case "session_archived":
49597
+ handleSessionArchivedMessage(msg, deps);
49598
+ break;
49599
+ case "session_discarded":
49600
+ handleSessionDiscardedMessage(msg, deps);
49601
+ break;
49602
+ case "revert_turn_snapshot":
49603
+ handleRevertTurnSnapshotMessage(msg, deps);
49604
+ break;
49605
+ case "cursor_request_response":
49606
+ handleSessionRequestResponseMessage(msg, deps);
49607
+ break;
49608
+ }
49321
49609
  }
49322
49610
 
49323
- // src/code-nav/handlers/trigger-symbol-index-build.ts
49324
- init_normalize_resolved_path();
49611
+ // src/skills/handle-skill-call.ts
49612
+ function handleSkillCall(msg, socket, log2) {
49613
+ callSkill(msg.skillId, msg.operationId, msg.params ?? {}).then((result) => {
49614
+ sendWsMessage(socket, { type: "skill_result", id: msg.id, result });
49615
+ }).catch((err) => {
49616
+ sendWsMessage(socket, { type: "skill_result", id: msg.id, error: String(err) });
49617
+ log2(`[Bridge service] Skill invocation failed (${msg.skillId}/${msg.operationId}): ${err}`);
49618
+ });
49619
+ }
49325
49620
 
49326
- // src/routing/handlers/code-nav-messages.ts
49327
- function handleFileBrowserCodeNavMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
49328
- if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
49621
+ // src/routing/handlers/skill-call.ts
49622
+ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
49623
+ const skillId = typeof msg.skillId === "string" ? msg.skillId : "";
49624
+ const operationId = typeof msg.operationId === "string" ? msg.operationId : "";
49625
+ if (!skillId || !operationId) return;
49329
49626
  const socket = getWs();
49330
49627
  if (!socket) return;
49331
- handleCodeNavRequest(
49332
- {
49333
- id: msg.id,
49334
- type: "code_nav",
49335
- op: msg.op === "references" ? "references" : msg.op === "symbols" ? "symbols" : msg.op === "definitions" ? "definitions" : "definition",
49336
- path: msg.path,
49337
- line: typeof msg.line === "number" ? msg.line : 1,
49338
- column: typeof msg.column === "number" ? msg.column : 0,
49339
- sessionId: msg.sessionId
49340
- },
49628
+ handleSkillCall(
49629
+ { id: msg.id, skillId, operationId, params: msg.params },
49341
49630
  socket,
49342
- e2ee,
49343
- sessionWorktreeManager
49631
+ log2
49344
49632
  );
49345
- }
49633
+ };
49346
49634
 
49347
49635
  // src/routing/handlers/skill-layout-request.ts
49348
49636
  function handleSkillLayoutRequest(msg, deps) {
@@ -49365,8 +49653,8 @@ function isValidRemoteSkillInstallItem(item) {
49365
49653
 
49366
49654
  // src/skills/install/install-remote-skills-async.ts
49367
49655
  init_yield_to_event_loop();
49368
- import fs59 from "node:fs";
49369
- import path80 from "node:path";
49656
+ import fs60 from "node:fs";
49657
+ import path79 from "node:path";
49370
49658
 
49371
49659
  // src/skills/install/constants.ts
49372
49660
  var INSTALL_SKILLS_YIELD_EVERY = 16;
@@ -49374,404 +49662,404 @@ var INSTALL_SKILLS_YIELD_EVERY = 16;
49374
49662
  // src/skills/install/install-remote-skills-async.ts
49375
49663
  async function writeInstallFileAsync(skillDir, f, filesWritten) {
49376
49664
  if (typeof f.path !== "string" || !f.text && !f.base64) return;
49377
- if (++filesWritten.count % INSTALL_SKILLS_YIELD_EVERY === 0) {
49378
- await yieldToEventLoop();
49379
- }
49380
- const dest = path80.join(skillDir, f.path);
49381
- await fs59.promises.mkdir(path80.dirname(dest), { recursive: true });
49382
- if (f.text !== void 0) {
49383
- await fs59.promises.writeFile(dest, f.text, "utf8");
49384
- } else if (f.base64) {
49385
- await fs59.promises.writeFile(dest, Buffer.from(f.base64, "base64"));
49386
- }
49387
- }
49388
- async function installRemoteSkillsAsync(cwd, targetDir, items) {
49389
- const installed2 = [];
49390
- if (!Array.isArray(items)) {
49391
- return { success: false, error: "Invalid items" };
49392
- }
49393
- const filesWritten = { count: 0 };
49394
- try {
49395
- for (const item of items) {
49396
- if (!isValidRemoteSkillInstallItem(item)) continue;
49397
- const skillDir = path80.join(cwd, targetDir, item.skillName);
49398
- for (const f of item.files) {
49399
- await writeInstallFileAsync(skillDir, f, filesWritten);
49400
- }
49401
- installed2.push({
49402
- sourceId: item.sourceId,
49403
- skillName: item.skillName,
49404
- versionHash: item.versionHash
49405
- });
49406
- }
49407
- return { success: true, installed: installed2 };
49408
- } catch (e) {
49409
- return { success: false, error: e instanceof Error ? e.message : String(e) };
49410
- }
49411
- }
49412
-
49413
- // src/routing/handlers/install-skills.ts
49414
- var handleInstallSkillsMessage = (msg, deps) => {
49415
- const id = typeof msg.id === "string" ? msg.id : "";
49416
- const targetDir = typeof msg.targetDir === "string" && msg.targetDir.trim() ? msg.targetDir.trim() : ".agents/skills";
49417
- const rawItems = msg.items;
49418
- const cwd = getBridgeRoot();
49419
- void (async () => {
49420
- const socket = deps.getWs();
49421
- const result = await installRemoteSkillsAsync(cwd, targetDir, rawItems);
49422
- if (!result.success) {
49423
- const err = result.error ?? "Invalid items";
49424
- deps.log(`[Bridge service] Install skills failed: ${err}`);
49425
- if (socket) {
49426
- sendWsMessage(socket, { type: "install_skills_result", id, success: false, error: err });
49427
- }
49428
- return;
49429
- }
49430
- if (socket) {
49431
- sendWsMessage(socket, { type: "install_skills_result", id, success: true, installed: result.installed });
49432
- }
49433
- })();
49434
- };
49435
-
49436
- // src/routing/handlers/refresh-local-skills.ts
49437
- var handleRefreshLocalSkills = (_msg, deps) => {
49438
- setImmediate(() => {
49439
- deps.sendLocalSkillsReport?.();
49440
- });
49441
- };
49442
-
49443
- // src/routing/handlers/git/handle-session-git-commit.ts
49444
- async function handleSessionGitCommitAction(deps, sessionId, msg, reply) {
49445
- const branch = typeof msg.branch === "string" ? msg.branch : "";
49446
- const message = typeof msg.message === "string" ? msg.message : "";
49447
- const pushAfterCommit = msg.pushAfterCommit === true;
49448
- if (!branch.trim() || !message.trim()) {
49449
- reply({ ok: false, error: "branch and message are required for commit" });
49450
- return;
49451
- }
49452
- const commitRes = await deps.sessionWorktreeManager.commitSession({
49453
- sessionId,
49454
- branch: branch.trim(),
49455
- message: message.trim(),
49456
- push: pushAfterCommit
49457
- });
49458
- if (!commitRes.ok) {
49459
- reply({ ok: false, error: commitRes.error ?? "Commit failed" });
49460
- return;
49461
- }
49462
- const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
49463
- reply({
49464
- ok: true,
49465
- hasUncommittedChanges: st.hasUncommittedChanges,
49466
- hasUnpushedCommits: st.hasUnpushedCommits,
49467
- uncommittedFileCount: st.uncommittedFileCount
49468
- });
49469
- }
49470
-
49471
- // src/routing/handlers/git/session-git-changes-params.ts
49472
- function readString(value) {
49473
- return typeof value === "string" ? value.trim() : "";
49474
- }
49475
- function readChangesObject(msg) {
49476
- const changes = msg.changes;
49477
- if (!changes || typeof changes !== "object" || Array.isArray(changes)) return null;
49478
- return changes;
49479
- }
49480
- function parseSessionGitChangesParams(msg) {
49481
- const nested = readChangesObject(msg);
49482
- const file2 = nested?.file && typeof nested.file === "object" && !Array.isArray(nested.file) ? nested.file : null;
49483
- const repoRelPath = readString(nested?.repoRelPath) || readString(msg.changesRepoRelPath);
49484
- const viewRaw = nested?.view ?? msg.changesView;
49485
- const view = viewRaw === "commit" ? "commit" : "working";
49486
- const commitSha = readString(nested?.commitSha) || readString(msg.changesCommitSha);
49487
- const recentCommitsLimit = nested?.recentCommitsLimit ?? msg.changesRecentCommitsLimit;
49488
- return {
49489
- repoRelPath,
49490
- view,
49491
- commitSha,
49492
- recentCommitsLimit: typeof recentCommitsLimit === "number" || typeof recentCommitsLimit === "string" ? recentCommitsLimit : void 0,
49493
- fileWorkspaceRelPath: readString(file2?.workspaceRelPath),
49494
- fileChange: readString(file2?.change),
49495
- fileMovedFromWorkspaceRelPath: readString(file2?.movedFromWorkspaceRelPath)
49496
- };
49497
- }
49498
-
49499
- // src/routing/handlers/git/handle-session-git-get-change-file-patch.ts
49500
- async function handleSessionGitGetChangeFilePatchAction(deps, msg, reply) {
49501
- const changes = parseSessionGitChangesParams(msg);
49502
- const { repoRelPath: repoRel, view, commitSha, fileWorkspaceRelPath, fileChange, fileMovedFromWorkspaceRelPath } = changes;
49503
- const change = parseWorkingTreeChangeKind(fileChange);
49504
- if (!repoRel || !fileWorkspaceRelPath || !change) {
49505
- reply({
49506
- ok: false,
49507
- error: "changes.repoRelPath, changes.file.workspaceRelPath, and changes.file.change are required"
49508
- });
49509
- return;
49510
- }
49511
- if (view === "commit" && !commitSha) {
49512
- reply({ ok: false, error: "changes.commitSha is required for commit file patch" });
49513
- return;
49514
- }
49515
- const patch = await deps.sessionWorktreeManager.getSessionWorkingTreeChangeFilePatch(msg.sessionId, {
49516
- repoRelPath: repoRel,
49517
- workspaceRelPath: fileWorkspaceRelPath,
49518
- change,
49519
- movedFromWorkspaceRelPath: fileMovedFromWorkspaceRelPath || null,
49520
- basis: view === "commit" ? { kind: "commit", sha: commitSha } : { kind: "working" }
49521
- });
49522
- reply(
49523
- {
49524
- ok: true,
49525
- patchContent: patch.patchContent ?? null,
49526
- totalLines: patch.totalLines
49527
- },
49528
- ["patchContent"]
49529
- );
49530
- }
49531
-
49532
- // src/routing/handlers/git/coalesce-list-changes.ts
49533
- var listChangesInflight = /* @__PURE__ */ new Map();
49534
- function normalizeListChangesRepoPathRelativeToWorkspaceRoot(repoRelPath) {
49535
- if (!repoRelPath?.trim()) return "";
49536
- return normalizeRepoPathRelativeToWorkspaceRoot(repoRelPath.trim());
49537
- }
49538
- function listChangesInflightKey(sessionId, opts) {
49539
- const basis = opts?.basis;
49540
- return [
49541
- sessionId,
49542
- normalizeListChangesRepoPathRelativeToWorkspaceRoot(opts?.repoRelPath),
49543
- basis?.kind === "commit" ? `commit:${basis.sha}` : "working",
49544
- String(opts?.recentCommitsLimit ?? "")
49545
- ].join("|");
49665
+ if (++filesWritten.count % INSTALL_SKILLS_YIELD_EVERY === 0) {
49666
+ await yieldToEventLoop();
49667
+ }
49668
+ const dest = path79.join(skillDir, f.path);
49669
+ await fs60.promises.mkdir(path79.dirname(dest), { recursive: true });
49670
+ if (f.text !== void 0) {
49671
+ await fs60.promises.writeFile(dest, f.text, "utf8");
49672
+ } else if (f.base64) {
49673
+ await fs60.promises.writeFile(dest, Buffer.from(f.base64, "base64"));
49674
+ }
49546
49675
  }
49547
- function getSessionWorkingTreeChangeDetailsCoalesced(sessionWorktreeManager, sessionId, opts) {
49548
- const key = listChangesInflightKey(sessionId, opts);
49549
- const existing = listChangesInflight.get(key);
49550
- if (existing) return existing;
49551
- const promise2 = sessionWorktreeManager.getSessionWorkingTreeChangeDetails(sessionId, opts).finally(() => {
49552
- if (listChangesInflight.get(key) === promise2) listChangesInflight.delete(key);
49553
- });
49554
- listChangesInflight.set(key, promise2);
49555
- return promise2;
49676
+ async function installRemoteSkillsAsync(cwd, targetDir, items) {
49677
+ const installed2 = [];
49678
+ if (!Array.isArray(items)) {
49679
+ return { success: false, error: "Invalid items" };
49680
+ }
49681
+ const filesWritten = { count: 0 };
49682
+ try {
49683
+ for (const item of items) {
49684
+ if (!isValidRemoteSkillInstallItem(item)) continue;
49685
+ const skillDir = path79.join(cwd, targetDir, item.skillName);
49686
+ for (const f of item.files) {
49687
+ await writeInstallFileAsync(skillDir, f, filesWritten);
49688
+ }
49689
+ installed2.push({
49690
+ sourceId: item.sourceId,
49691
+ skillName: item.skillName,
49692
+ versionHash: item.versionHash
49693
+ });
49694
+ }
49695
+ return { success: true, installed: installed2 };
49696
+ } catch (e) {
49697
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
49698
+ }
49556
49699
  }
49557
49700
 
49558
- // src/routing/handlers/git/handle-session-git-list-changes.ts
49559
- async function handleSessionGitListChangesAction(deps, msg, reply) {
49560
- const changes = parseSessionGitChangesParams(msg);
49561
- const { repoRelPath: repoRel, view, commitSha, recentCommitsLimit } = changes;
49562
- if (view === "commit") {
49563
- if (!repoRel || !commitSha) {
49564
- reply({
49565
- ok: false,
49566
- error: "changes.repoRelPath and changes.commitSha are required for commit view"
49567
- });
49701
+ // src/routing/handlers/install-skills.ts
49702
+ var handleInstallSkillsMessage = (msg, deps) => {
49703
+ const id = typeof msg.id === "string" ? msg.id : "";
49704
+ const targetDir = typeof msg.targetDir === "string" && msg.targetDir.trim() ? msg.targetDir.trim() : ".agents/skills";
49705
+ const rawItems = msg.items;
49706
+ const cwd = getBridgeRoot();
49707
+ void (async () => {
49708
+ const socket = deps.getWs();
49709
+ const result = await installRemoteSkillsAsync(cwd, targetDir, rawItems);
49710
+ if (!result.success) {
49711
+ const err = result.error ?? "Invalid items";
49712
+ deps.log(`[Bridge service] Install skills failed: ${err}`);
49713
+ if (socket) {
49714
+ sendWsMessage(socket, { type: "install_skills_result", id, success: false, error: err });
49715
+ }
49568
49716
  return;
49569
49717
  }
49570
- }
49571
- const opts = repoRel && view === "commit" && commitSha ? {
49572
- repoRelPath: repoRel,
49573
- basis: { kind: "commit", sha: commitSha },
49574
- recentCommitsLimit
49575
- } : repoRel ? { repoRelPath: repoRel, basis: { kind: "working" }, recentCommitsLimit } : recentCommitsLimit !== void 0 ? { recentCommitsLimit } : void 0;
49576
- const repos = await getSessionWorkingTreeChangeDetailsCoalesced(
49577
- deps.sessionWorktreeManager,
49578
- msg.sessionId,
49579
- opts
49580
- );
49581
- reply({ ok: true, repos }, ["repos"]);
49718
+ if (socket) {
49719
+ sendWsMessage(socket, { type: "install_skills_result", id, success: true, installed: result.installed });
49720
+ }
49721
+ })();
49722
+ };
49723
+
49724
+ // src/agents/install/commands/run-npm-global-install.ts
49725
+ import { execFile as execFile9 } from "node:child_process";
49726
+ import { promisify as promisify10 } from "node:util";
49727
+ var execFileAsync8 = promisify10(execFile9);
49728
+ async function runNpmGlobalInstall(packageName, env, timeoutMs = 3e5) {
49729
+ await execFileAsync8("npm", ["install", "-g", packageName], { timeout: timeoutMs, env: bridgeAgentPathEnv(env) });
49582
49730
  }
49583
49731
 
49584
- // src/routing/handlers/git/handle-session-git-push.ts
49585
- async function handleSessionGitPushAction(deps, sessionId, reply) {
49586
- const pushRes = await deps.sessionWorktreeManager.pushSessionUpstream(sessionId);
49587
- if (!pushRes.ok) {
49588
- reply({ ok: false, error: pushRes.error ?? "Push failed" });
49589
- return;
49732
+ // src/agents/install/commands/claude-code.ts
49733
+ var claudeCodeInstallCommand = {
49734
+ agentType: "claude-code",
49735
+ detectCommand: "claude",
49736
+ async install(ctx) {
49737
+ ctx.onProgress?.("Installing Anthropic Claude Code");
49738
+ await runNpmGlobalInstall("@anthropic-ai/claude-code", {
49739
+ ...ctx.env,
49740
+ ANTHROPIC_API_KEY: ctx.authToken
49741
+ });
49590
49742
  }
49591
- const st = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
49592
- reply({
49593
- ok: true,
49594
- hasUncommittedChanges: st.hasUncommittedChanges,
49595
- hasUnpushedCommits: st.hasUnpushedCommits,
49596
- uncommittedFileCount: st.uncommittedFileCount
49597
- });
49598
- }
49743
+ };
49599
49744
 
49600
- // src/routing/handlers/git/handle-session-git-status.ts
49601
- async function handleSessionGitStatusAction(deps, sessionId, reply) {
49602
- const r = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
49603
- reply({
49604
- ok: true,
49605
- hasUncommittedChanges: r.hasUncommittedChanges,
49606
- hasUnpushedCommits: r.hasUnpushedCommits,
49607
- uncommittedFileCount: r.uncommittedFileCount
49608
- });
49609
- }
49745
+ // src/agents/install/commands/codex-acp.ts
49746
+ var codexAcpInstallCommand = {
49747
+ agentType: "codex-acp",
49748
+ detectCommand: "codex",
49749
+ async install(ctx) {
49750
+ ctx.onProgress?.("Installing Codex");
49751
+ await runNpmGlobalInstall("@openai/codex", {
49752
+ ...ctx.env,
49753
+ OPENAI_API_KEY: ctx.authToken
49754
+ });
49755
+ }
49756
+ };
49610
49757
 
49611
- // src/routing/handlers/git/send-session-git-result.ts
49612
- function sendSessionGitResult(ws, id, payload, e2ee, encryptedFields = []) {
49613
- if (!ws) return;
49614
- const message = { type: "session_git_result", id, ...payload };
49615
- let wire = message;
49616
- if (e2ee && encryptedFields.length > 0) {
49617
- wire = e2ee.encryptFields(message, encryptedFields);
49758
+ // src/agents/install/commands/cursor-cli.ts
49759
+ import { execFile as execFile10 } from "node:child_process";
49760
+ import { promisify as promisify11 } from "node:util";
49761
+ var execFileAsync9 = promisify11(execFile10);
49762
+ var cursorCliInstallCommand = {
49763
+ agentType: "cursor-cli",
49764
+ detectCommand: "agent",
49765
+ async install(ctx) {
49766
+ ctx.onProgress?.("Installing Cursor CLI");
49767
+ await execFileAsync9("bash", ["-lc", "curl -fsSL https://cursor.com/install | bash"], {
49768
+ timeout: 3e5,
49769
+ env: { ...bridgeAgentPathEnv(ctx.env), CURSOR_API_KEY: ctx.authToken }
49770
+ });
49618
49771
  }
49619
- sendWsMessage(ws, wire);
49772
+ };
49773
+
49774
+ // src/agents/install/commands/opencode.ts
49775
+ var opencodeInstallCommand = {
49776
+ agentType: "opencode",
49777
+ detectCommand: "opencode",
49778
+ async install(ctx) {
49779
+ ctx.onProgress?.("Installing OpenCode");
49780
+ await runNpmGlobalInstall("opencode-ai", {
49781
+ ...ctx.env,
49782
+ OPENCODE_API_KEY: ctx.authToken
49783
+ });
49784
+ }
49785
+ };
49786
+
49787
+ // src/agents/install/commands/index.ts
49788
+ var COMMANDS = [
49789
+ claudeCodeInstallCommand,
49790
+ codexAcpInstallCommand,
49791
+ cursorCliInstallCommand,
49792
+ opencodeInstallCommand
49793
+ ];
49794
+ var byType = new Map(COMMANDS.map((c) => [c.agentType, c]));
49795
+ function getAgentInstallCommand(agentType) {
49796
+ return byType.get(agentType);
49797
+ }
49798
+
49799
+ // src/agents/install/install-local-agent.ts
49800
+ async function installLocalAgentOnBridge(params) {
49801
+ const spec = INSTALLABLE_BRIDGE_AGENTS.find((a) => a.value === params.agentType);
49802
+ if (!spec) return { success: false, error: `Unsupported agent type: ${params.agentType}` };
49803
+ const command = getAgentInstallCommand(params.agentType);
49804
+ if (!command) return { success: false, error: `No install command for ${params.agentType}` };
49805
+ params.onProgress?.(`Configuring ${spec.label} credentials`);
49806
+ try {
49807
+ await command.install({
49808
+ authToken: params.authToken,
49809
+ onProgress: params.onProgress,
49810
+ env: { ...process.env, [spec.tokenEnvVar]: params.authToken }
49811
+ });
49812
+ } catch (e) {
49813
+ const msg = e instanceof Error ? e.message : String(e);
49814
+ return { success: false, error: msg };
49815
+ }
49816
+ ensureBridgeAgentPathInProcessEnv();
49817
+ const found = await isCommandOnPath(command.detectCommand);
49818
+ if (!found) {
49819
+ return { success: false, error: `${command.detectCommand} not found on PATH after install` };
49820
+ }
49821
+ return { success: true };
49620
49822
  }
49621
49823
 
49622
- // src/routing/handlers/git/session-git-request.ts
49623
- var handleSessionGitRequestMessage = (msg, deps) => {
49624
- if (typeof msg.id !== "string") return;
49625
- const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
49626
- const action = msg.action;
49627
- if (!sessionId || action !== "status" && action !== "push" && action !== "commit" && action !== "list_changes" && action !== "file_diff")
49628
- return;
49824
+ // src/routing/handlers/install-agent.ts
49825
+ var handleInstallAgentMessage = (msg, deps) => {
49826
+ const processId = typeof msg.processId === "string" ? msg.processId : "";
49827
+ const agentId = typeof msg.agentId === "string" ? msg.agentId : "";
49828
+ const tokenId = typeof msg.tokenId === "string" ? msg.tokenId : "";
49829
+ const agentType = typeof msg.agentType === "string" ? msg.agentType : "";
49830
+ const authToken = typeof msg.authToken === "string" ? msg.authToken : "";
49831
+ if (!processId || !agentId || !tokenId || !agentType || !authToken) return;
49629
49832
  void (async () => {
49630
- const ws = deps.getWs();
49631
- const reply = (payload, encryptedFields = []) => sendSessionGitResult(ws, msg.id, payload, deps.e2ee, encryptedFields);
49632
- try {
49633
- if (action === "status") {
49634
- await handleSessionGitStatusAction(deps, sessionId, reply);
49635
- return;
49636
- }
49637
- if (action === "list_changes") {
49638
- await handleSessionGitListChangesAction(deps, { ...msg, sessionId }, reply);
49639
- return;
49640
- }
49641
- if (action === "file_diff") {
49642
- await handleSessionGitGetChangeFilePatchAction(deps, { ...msg, sessionId }, reply);
49643
- return;
49644
- }
49645
- if (action === "push") {
49646
- await handleSessionGitPushAction(deps, sessionId, reply);
49647
- return;
49833
+ const socket = deps.getWs();
49834
+ const report = (step, message) => {
49835
+ if (socket) {
49836
+ sendWsMessage(socket, {
49837
+ type: "install_agent_progress",
49838
+ processId,
49839
+ agentId,
49840
+ tokenId,
49841
+ agentType,
49842
+ step,
49843
+ message
49844
+ });
49648
49845
  }
49649
- await handleSessionGitCommitAction(deps, sessionId, msg, reply);
49650
- } catch (e) {
49651
- reply({ ok: false, error: e instanceof Error ? e.message : String(e) });
49846
+ };
49847
+ const result = await installLocalAgentOnBridge({
49848
+ agentType,
49849
+ authToken,
49850
+ onProgress: (message) => report("agent_install_package", message)
49851
+ });
49852
+ if (socket) {
49853
+ sendWsMessage(socket, {
49854
+ type: "install_agent_result",
49855
+ processId,
49856
+ agentId,
49857
+ tokenId,
49858
+ agentType,
49859
+ success: result.success,
49860
+ error: result.error
49861
+ });
49862
+ }
49863
+ if (result.success) {
49864
+ await deps.reportAutoDetectedAgents?.();
49865
+ }
49866
+ if (!result.success) {
49867
+ deps.log(`[Bridge service] Install agent failed: ${result.error ?? "unknown"}`);
49652
49868
  }
49653
49869
  })();
49654
49870
  };
49655
49871
 
49656
- // src/routing/handlers/rename-session-branch.ts
49657
- var handleRenameSessionBranchMessage = (msg, deps) => {
49658
- const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
49659
- const newBranch = typeof msg.newBranch === "string" ? msg.newBranch : "";
49660
- if (!sessionId || !newBranch) return;
49661
- void deps.sessionWorktreeManager.renameSessionBranch(sessionId, newBranch);
49872
+ // src/routing/handlers/refresh-local-skills.ts
49873
+ var handleRefreshLocalSkills = (_msg, deps) => {
49874
+ setImmediate(() => {
49875
+ deps.sendLocalSkillsReport?.();
49876
+ });
49662
49877
  };
49663
49878
 
49664
- // src/routing/handlers/session-archived.ts
49665
- var handleSessionArchivedMessage = (msg, deps) => {
49666
- const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
49667
- if (!sessionId) return;
49668
- void deps.sessionWorktreeManager.removeSessionWorktrees(sessionId);
49669
- };
49879
+ // src/routing/dispatch/skills.ts
49880
+ function dispatchBridgeSkillsMessage(msg, deps) {
49881
+ switch (msg.type) {
49882
+ case "skill_call":
49883
+ handleSkillCallMessage(msg, deps);
49884
+ break;
49885
+ case "skill_layout_request":
49886
+ handleSkillLayoutRequest(msg, deps);
49887
+ break;
49888
+ case "install_skills":
49889
+ handleInstallSkillsMessage(msg, deps);
49890
+ break;
49891
+ case "install_agent":
49892
+ handleInstallAgentMessage(msg, deps);
49893
+ break;
49894
+ case "refresh_local_skills":
49895
+ handleRefreshLocalSkills(msg, deps);
49896
+ break;
49897
+ }
49898
+ }
49670
49899
 
49671
- // src/routing/handlers/session-discarded.ts
49672
- var handleSessionDiscardedMessage = (msg, deps) => {
49673
- const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
49674
- if (!sessionId) return;
49675
- void deps.sessionWorktreeManager.removeSessionWorktrees(sessionId);
49676
- };
49900
+ // src/files/browser/send-file-browser-message.ts
49901
+ function sendFileBrowserMessage(socket, e2ee, payload) {
49902
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
49903
+ }
49677
49904
 
49678
- // src/routing/handlers/revert-turn-snapshot.ts
49679
- import * as fs60 from "node:fs";
49680
- var handleRevertTurnSnapshotMessage = (msg, deps) => {
49681
- const id = typeof msg.id === "string" ? msg.id : "";
49682
- const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
49683
- const turnId = typeof msg.turnId === "string" ? msg.turnId : "";
49684
- if (!id || !sessionId || !turnId) return;
49685
- const { getWs, log: log2, sessionWorktreeManager } = deps;
49905
+ // src/files/browser/handle-file-browser-list.ts
49906
+ async function handleFileBrowserList(socket, e2ee, id, reqPath, sessionParentPath, gitScope) {
49907
+ const result = await executeFileBrowserList({ reqPath, sessionParentPath, gitScope });
49908
+ if ("error" in result) {
49909
+ sendWsMessage(socket, { type: "file_browser_response", id, error: result.error });
49910
+ return;
49911
+ }
49912
+ sendFileBrowserMessage(socket, e2ee, { type: "file_browser_response", id, entries: result.entries });
49913
+ }
49914
+
49915
+ // src/files/browser/handle-file-browser-read.ts
49916
+ async function handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPath, gitScope) {
49917
+ const result = await executeFileBrowserRead({ msg, reqPath, sessionParentPath, gitScope });
49918
+ if ("error" in result) {
49919
+ sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
49920
+ return;
49921
+ }
49922
+ const payload = {
49923
+ type: "file_browser_response",
49924
+ id: msg.id,
49925
+ content: result.content,
49926
+ totalLines: result.totalLines,
49927
+ size: result.size
49928
+ };
49929
+ if (result.lineOffset != null) payload.lineOffset = result.lineOffset;
49930
+ if (result.mimeType != null) payload.mimeType = result.mimeType;
49931
+ if (result.resolvedPath != null) payload.resolvedPath = result.resolvedPath;
49932
+ sendFileBrowserMessage(socket, e2ee, payload);
49933
+ }
49934
+
49935
+ // src/files/browser/index.ts
49936
+ init_in_flight();
49937
+ function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
49938
+ beginFileBrowserRequest();
49686
49939
  void (async () => {
49687
- const s = getWs();
49688
- if (!s) return;
49689
- const agentBase = sessionWorktreeManager.getSessionWorktreeRootForSession(sessionId) ?? getBridgeRoot();
49690
- const file2 = snapshotFilePath(agentBase, turnId);
49691
49940
  try {
49692
- await fs60.promises.access(file2, fs60.constants.F_OK);
49693
- } catch {
49694
- sendWsMessage(s, {
49695
- type: "revert_turn_snapshot_result",
49696
- id,
49697
- ok: false,
49698
- error: "No snapshot found for this turn (git state may be unavailable)."
49699
- });
49700
- return;
49941
+ const reqPath = msg.path.replace(/^\/+/, "") || ".";
49942
+ const sessionParentPath = sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
49943
+ const gitScope = resolveGitBranchScope(msg);
49944
+ if (msg.source === "git_branch" && typeof msg.repoRelPath === "string" && typeof msg.branch === "string" && msg.branch.trim().length > 0 && !gitScope) {
49945
+ sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: "Invalid repository path" });
49946
+ return;
49947
+ }
49948
+ if (msg.op === "read") {
49949
+ await handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPath, gitScope);
49950
+ } else {
49951
+ await handleFileBrowserList(socket, e2ee, msg.id, reqPath, sessionParentPath, gitScope);
49952
+ }
49953
+ } finally {
49954
+ endFileBrowserRequest();
49701
49955
  }
49702
- const res = await applyPreTurnSnapshot(file2, log2);
49703
- sendWsMessage(s, {
49704
- type: "revert_turn_snapshot_result",
49705
- id,
49706
- ok: res.ok,
49707
- ...res.error ? { error: res.error } : {}
49708
- });
49709
49956
  })();
49710
- };
49711
-
49712
- // src/routing/handlers/preview-environment-control.ts
49713
- var handlePreviewEnvironmentControl = (msg, deps) => {
49714
- let wire;
49715
- try {
49716
- wire = deps.e2ee ? deps.e2ee.decryptMessage(msg) : msg;
49717
- } catch (e) {
49718
- deps.log(`[E2EE] Could not decrypt preview environment command: ${e instanceof Error ? e.message : String(e)}`);
49719
- return;
49720
- }
49721
- const environmentId = typeof wire.environmentId === "string" ? wire.environmentId : "";
49722
- const action = wire.action === "start" || wire.action === "stop" ? wire.action : null;
49723
- if (!environmentId || !action) return;
49724
- deps.previewEnvironmentManager?.handleControl(environmentId, action);
49725
- };
49957
+ }
49726
49958
 
49727
- // src/routing/handlers/preview-environments-config.ts
49728
- var VALID_PORT = (p) => typeof p === "number" && Number.isInteger(p) && p > 0 && p < 65536;
49729
- var handlePreviewEnvironmentsConfig = (msg, deps) => {
49730
- const previewEnvironments = msg.previewEnvironments;
49731
- const proxyPorts = Array.isArray(msg.proxyPorts) ? msg.proxyPorts.filter(VALID_PORT) : void 0;
49732
- setImmediate(() => {
49733
- deps.previewEnvironmentManager?.applyConfig(previewEnvironments ?? []);
49734
- const environmentIds = parsePreviewEnvironmentDefs(previewEnvironments ?? []).map((d) => d.environmentId);
49735
- void deps.previewWorktreeManager?.syncConfiguredEnvironments(environmentIds);
49736
- if (proxyPorts !== void 0) {
49737
- deps.updateFirehoseProxyPorts?.(proxyPorts);
49959
+ // src/files/handle-file-browser-search.ts
49960
+ init_yield_to_event_loop();
49961
+ import path80 from "node:path";
49962
+ var SEARCH_LIMIT = 100;
49963
+ function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
49964
+ void (async () => {
49965
+ await yieldToEventLoop();
49966
+ const q = typeof msg.q === "string" ? msg.q : "";
49967
+ const sessionParentPath = path80.resolve(
49968
+ sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
49969
+ );
49970
+ if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
49971
+ triggerFileIndexBuild(sessionParentPath);
49972
+ const payload2 = {
49973
+ type: "file_browser_search_response",
49974
+ id: msg.id,
49975
+ paths: [],
49976
+ indexReady: false
49977
+ };
49978
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
49979
+ return;
49738
49980
  }
49981
+ const results = await searchBridgeFilePathsAsync(sessionParentPath, q, SEARCH_LIMIT);
49982
+ const payload = {
49983
+ type: "file_browser_search_response",
49984
+ id: msg.id,
49985
+ paths: results,
49986
+ indexReady: true
49987
+ };
49988
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["paths"]) : payload);
49989
+ })();
49990
+ }
49991
+ function triggerFileIndexBuild(sessionParentPath = getBridgeRoot()) {
49992
+ setImmediate(() => {
49993
+ void ensureFileIndexAsync(sessionParentPath).catch((e) => {
49994
+ console.error("[file-index] Background build failed:", e);
49995
+ });
49739
49996
  });
49740
- };
49741
-
49742
- // src/routing/handlers/send-deploy-session-to-preview-result.ts
49743
- function sendDeploySessionToPreviewResult(ws, id, payload) {
49744
- if (!ws) return;
49745
- sendWsMessage(ws, { type: "deploy_session_to_preview_result", id, ...payload });
49746
49997
  }
49747
49998
 
49748
- // src/routing/handlers/deploy-session-to-preview.ts
49749
- var handleDeploySessionToPreviewMessage = (msg, deps) => {
49999
+ // src/routing/handlers/file-browser-messages.ts
50000
+ function handleFileBrowserRequestMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
50001
+ if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
50002
+ const socket = getWs();
50003
+ if (!socket) return;
50004
+ handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager);
50005
+ }
50006
+ function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
49750
50007
  if (typeof msg.id !== "string") return;
49751
- const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
49752
- const environmentId = typeof msg.environmentId === "string" ? msg.environmentId : "";
49753
- if (!sessionId || !environmentId) return;
50008
+ const socket = getWs();
50009
+ if (!socket) return;
50010
+ handleFileBrowserSearch(
50011
+ msg,
50012
+ socket,
50013
+ e2ee,
50014
+ sessionWorktreeManager
50015
+ );
50016
+ }
50017
+
50018
+ // src/code-nav/handlers/send-code-nav-response.ts
50019
+ var ENCRYPTED_FIELDS = ["definition", "definitions", "references", "symbols", "confidentTarget"];
50020
+ function sendCodeNavResponse(socket, e2ee, payload) {
50021
+ const fieldsToEncrypt = ENCRYPTED_FIELDS.filter((field) => field in payload && payload[field] !== void 0);
50022
+ sendWsMessage(
50023
+ socket,
50024
+ e2ee && fieldsToEncrypt.length > 0 ? e2ee.encryptFields(payload, [...fieldsToEncrypt]) : payload
50025
+ );
50026
+ }
50027
+
50028
+ // src/code-nav/handlers/handle-code-nav-request.ts
50029
+ function handleCodeNavRequest(msg, socket, e2ee, sessionWorktreeManager) {
49754
50030
  void (async () => {
49755
- const ws = deps.getWs();
49756
- const reply = (payload) => sendDeploySessionToPreviewResult(ws, msg.id, payload);
49757
- try {
49758
- if (!deps.previewWorktreeManager) {
49759
- reply({ ok: false, error: "Preview worktree manager unavailable" });
49760
- return;
49761
- }
49762
- const result = await deploySessionToPreviewEnvironment({
49763
- sessionWorktreeManager: deps.sessionWorktreeManager,
49764
- previewWorktreeManager: deps.previewWorktreeManager,
49765
- sessionId,
49766
- environmentId,
49767
- log: deps.log
49768
- });
49769
- reply(result);
49770
- } catch (e) {
49771
- reply({ ok: false, error: e instanceof Error ? e.message : String(e) });
49772
- }
50031
+ const body = await executeCodeNavRequest(msg, sessionWorktreeManager);
50032
+ sendCodeNavResponse(socket, e2ee, {
50033
+ type: "code_nav_response",
50034
+ id: msg.id,
50035
+ ...body
50036
+ });
49773
50037
  })();
49774
- };
50038
+ }
50039
+
50040
+ // src/code-nav/handlers/trigger-symbol-index-build.ts
50041
+ init_normalize_resolved_path();
50042
+
50043
+ // src/routing/handlers/code-nav-messages.ts
50044
+ function handleFileBrowserCodeNavMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
50045
+ if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
50046
+ const socket = getWs();
50047
+ if (!socket) return;
50048
+ handleCodeNavRequest(
50049
+ {
50050
+ id: msg.id,
50051
+ type: "code_nav",
50052
+ op: msg.op === "references" ? "references" : msg.op === "symbols" ? "symbols" : msg.op === "definitions" ? "definitions" : "definition",
50053
+ path: msg.path,
50054
+ line: typeof msg.line === "number" ? msg.line : 1,
50055
+ column: typeof msg.column === "number" ? msg.column : 0,
50056
+ sessionId: msg.sessionId
50057
+ },
50058
+ socket,
50059
+ e2ee,
50060
+ sessionWorktreeManager
50061
+ );
50062
+ }
49775
50063
 
49776
50064
  // src/git/bridge-git-context.ts
49777
50065
  import * as path81 from "node:path";
@@ -49918,80 +50206,64 @@ function handleListRepoBranchesRequestMessage(msg, getWs) {
49918
50206
  })();
49919
50207
  }
49920
50208
 
49921
- // src/routing/dispatch-bridge-message.ts
50209
+ // src/routing/dispatch/file-browser.ts
50210
+ function dispatchBridgeFileBrowserMessage(msg, deps) {
50211
+ switch (msg.type) {
50212
+ case "file_browser_request":
50213
+ handleFileBrowserRequestMessage(msg, deps);
50214
+ break;
50215
+ case "file_browser_search":
50216
+ handleFileBrowserSearchMessage(msg, deps);
50217
+ break;
50218
+ case "code_nav":
50219
+ handleFileBrowserCodeNavMessage(msg, deps);
50220
+ break;
50221
+ case "bridge_git_context_request":
50222
+ handleBridgeGitContextRequestMessage(msg, deps.getWs);
50223
+ break;
50224
+ case "list_repo_branches_request":
50225
+ handleListRepoBranchesRequestMessage(msg, deps.getWs);
50226
+ break;
50227
+ }
50228
+ }
50229
+
50230
+ // src/routing/dispatch/index.ts
49922
50231
  function dispatchBridgeMessage(msg, deps) {
49923
50232
  switch (msg.type) {
49924
50233
  case "auth_token":
49925
- handleAuthToken(msg, deps);
49926
- break;
49927
50234
  case "bridge_identified":
49928
- handleBridgeIdentified(msg, deps);
49929
- break;
49930
50235
  case "ha":
49931
- handleBridgeHeartbeatAck(msg, deps);
50236
+ dispatchBridgeConnectionMessage(msg, deps);
49932
50237
  break;
49933
50238
  case "preview_environments_config":
49934
- handlePreviewEnvironmentsConfig(msg, deps);
49935
- break;
49936
50239
  case "preview_environment_control":
49937
- handlePreviewEnvironmentControl(msg, deps);
49938
- break;
49939
50240
  case "deploy_session_to_preview":
49940
- handleDeploySessionToPreviewMessage(msg, deps);
50241
+ dispatchBridgePreviewMessage(msg, deps);
49941
50242
  break;
49942
50243
  case "agent_config":
49943
- handleAgentConfigMessage(msg, deps);
49944
- break;
49945
50244
  case "prompt_queue_state":
49946
- handlePromptQueueStateMessage(msg, deps);
49947
- break;
49948
50245
  case "prompt":
49949
- handlePromptMessage(msg, deps);
49950
- break;
49951
50246
  case "session_git_request":
49952
- handleSessionGitRequestMessage(msg, deps);
49953
- break;
49954
50247
  case "rename_session_branch":
49955
- handleRenameSessionBranchMessage(msg, deps);
49956
- break;
49957
50248
  case "session_archived":
49958
- handleSessionArchivedMessage(msg, deps);
49959
- break;
49960
50249
  case "session_discarded":
49961
- handleSessionDiscardedMessage(msg, deps);
49962
- break;
49963
50250
  case "revert_turn_snapshot":
49964
- handleRevertTurnSnapshotMessage(msg, deps);
49965
- break;
49966
50251
  case "cursor_request_response":
49967
- handleSessionRequestResponseMessage(msg, deps);
50252
+ dispatchBridgeSessionMessage(msg, deps);
49968
50253
  break;
49969
50254
  case "skill_call":
49970
- handleSkillCallMessage(msg, deps);
49971
- break;
49972
- case "file_browser_request":
49973
- handleFileBrowserRequestMessage(msg, deps);
49974
- break;
49975
- case "file_browser_search":
49976
- handleFileBrowserSearchMessage(msg, deps);
49977
- break;
49978
- case "code_nav":
49979
- handleFileBrowserCodeNavMessage(msg, deps);
49980
- break;
49981
50255
  case "skill_layout_request":
49982
- handleSkillLayoutRequest(msg, deps);
49983
- break;
49984
50256
  case "install_skills":
49985
- handleInstallSkillsMessage(msg, deps);
49986
- break;
50257
+ case "install_agent":
49987
50258
  case "refresh_local_skills":
49988
- handleRefreshLocalSkills(msg, deps);
50259
+ dispatchBridgeSkillsMessage(msg, deps);
49989
50260
  break;
50261
+ case "file_browser_request":
50262
+ case "file_browser_search":
50263
+ case "code_nav":
49990
50264
  case "bridge_git_context_request":
49991
- handleBridgeGitContextRequestMessage(msg, deps.getWs);
49992
- break;
49993
50265
  case "list_repo_branches_request":
49994
- handleListRepoBranchesRequestMessage(msg, deps.getWs);
50266
+ dispatchBridgeFileBrowserMessage(msg, deps);
49995
50267
  break;
49996
50268
  }
49997
50269
  }
@@ -50276,6 +50548,7 @@ init_log();
50276
50548
  async function createBridgeConnection(options) {
50277
50549
  const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
50278
50550
  const logFn = options.log ?? log;
50551
+ ensureBridgeAgentPathInProcessEnv();
50279
50552
  await ensureBridgeConnectionDatabases(logFn);
50280
50553
  const { state, getWs } = createMainBridgeReconnectState();
50281
50554
  const runtime = await createBridgeConnectionRuntime(options, { state, getWs, logFn });