@band-app/server 0.53.0 → 0.54.0

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.
Files changed (71) hide show
  1. package/dist/client/_shell.html +0 -0
  2. package/dist/client/assets/{TerminalPanel-CUrp7Wxk.js → TerminalPanel-RYLpWa8Q.js} +1 -1
  3. package/dist/client/assets/{_basePickBy-DzTpqc54.js → _basePickBy-D-5MXYU_.js} +1 -1
  4. package/dist/client/assets/{_baseUniq-BH8BhmRB.js → _baseUniq-DYuk2xMI.js} +1 -1
  5. package/dist/client/assets/{arc-C3KloRZC.js → arc-dXtOms1t.js} +1 -1
  6. package/dist/client/assets/{architectureDiagram-VXUJARFQ-C0v4c_1F.js → architectureDiagram-VXUJARFQ-CsyRS4YA.js} +1 -1
  7. package/dist/client/assets/{blockDiagram-VD42YOAC-JA1asAF3.js → blockDiagram-VD42YOAC-DKwcsJ3k.js} +1 -1
  8. package/dist/client/assets/{c4Diagram-YG6GDRKO-eDAKz1MH.js → c4Diagram-YG6GDRKO-B-1bWrTm.js} +1 -1
  9. package/dist/client/assets/channel-B8VkZiqn.js +1 -0
  10. package/dist/client/assets/{chunk-4BX2VUAB-GiBdk6wd.js → chunk-4BX2VUAB-Lx4boQMz.js} +1 -1
  11. package/dist/client/assets/{chunk-55IACEB6-CZirdcYj.js → chunk-55IACEB6-C4WK81Kx.js} +1 -1
  12. package/dist/client/assets/{chunk-B4BG7PRW-CRx24wmo.js → chunk-B4BG7PRW-awrixx-i.js} +1 -1
  13. package/dist/client/assets/{chunk-DI55MBZ5-BDlvmWyE.js → chunk-DI55MBZ5-BXvp16cX.js} +1 -1
  14. package/dist/client/assets/{chunk-FMBD7UC4-CFQ4Ns-B.js → chunk-FMBD7UC4-DyY6khot.js} +1 -1
  15. package/dist/client/assets/{chunk-QN33PNHL-O1rjaqXm.js → chunk-QN33PNHL-BMvkmzKw.js} +1 -1
  16. package/dist/client/assets/{chunk-QZHKN3VN-BUex41N2.js → chunk-QZHKN3VN-CWY-fLAo.js} +1 -1
  17. package/dist/client/assets/{chunk-TZMSLE5B-DJjv_do_.js → chunk-TZMSLE5B-BE-ngWv6.js} +1 -1
  18. package/dist/client/assets/classDiagram-2ON5EDUG-CePVU31o.js +1 -0
  19. package/dist/client/assets/classDiagram-v2-WZHVMYZB-CePVU31o.js +1 -0
  20. package/dist/client/assets/clone-BGTwLCsr.js +1 -0
  21. package/dist/client/assets/{cose-bilkent-S5V4N54A-DFpXjakt.js → cose-bilkent-S5V4N54A-DJ8h3kKA.js} +1 -1
  22. package/dist/client/assets/{dagre-6UL2VRFP-CduI6jnM.js → dagre-6UL2VRFP-Bd1qWRGC.js} +1 -1
  23. package/dist/client/assets/{diagram-PSM6KHXK-CzWJmLPu.js → diagram-PSM6KHXK-BR64_p5f.js} +1 -1
  24. package/dist/client/assets/{diagram-QEK2KX5R-CyCNI4-k.js → diagram-QEK2KX5R-DFQt3gYM.js} +1 -1
  25. package/dist/client/assets/{diagram-S2PKOQOG-C5wLA6ra.js → diagram-S2PKOQOG-C8HaYYzZ.js} +1 -1
  26. package/dist/client/assets/{erDiagram-Q2GNP2WA-BNMyokGR.js → erDiagram-Q2GNP2WA-CS64Rcxd.js} +1 -1
  27. package/dist/client/assets/{flowDiagram-NV44I4VS-CDMiz1jN.js → flowDiagram-NV44I4VS-C-lDTK_F.js} +1 -1
  28. package/dist/client/assets/{ganttDiagram-JELNMOA3-BjKbhCuq.js → ganttDiagram-JELNMOA3-mfB20mUi.js} +1 -1
  29. package/dist/client/assets/{gitGraphDiagram-V2S2FVAM-DcKCNdhS.js → gitGraphDiagram-V2S2FVAM-DIT7OOcN.js} +1 -1
  30. package/dist/client/assets/{graph-BCkWMEPy.js → graph-CuDWaZgk.js} +1 -1
  31. package/dist/client/assets/{highlighted-body-B3W2YXNL-ESHG-c2t.js → highlighted-body-B3W2YXNL-CEukDjjL.js} +1 -1
  32. package/dist/client/assets/{index-D1w6ILcm.js → index-B7DmSLZ5.js} +1 -1
  33. package/dist/client/assets/{index-CJAA69-B.js → index-B7yHTHBd.js} +1 -1
  34. package/dist/client/assets/{index-nnOiMGMS.js → index-BAFH952I.js} +1 -1
  35. package/dist/client/assets/{index-C4YFmo8L.js → index-BAJM6NLC.js} +1 -1
  36. package/dist/client/assets/{index-biU1uw37.js → index-BAlhbw5A.js} +1 -1
  37. package/dist/client/assets/{index-MWMi8v82.js → index-BMcFlUOo.js} +1 -1
  38. package/dist/client/assets/{index-ChF20gAc.js → index-CQfVS8uj.js} +1 -1
  39. package/dist/client/assets/{index-CutOvwS7.js → index-CQknVNu4.js} +1 -1
  40. package/dist/client/assets/{index-CklvaSBA.js → index-CiKJmP62.js} +1 -1
  41. package/dist/client/assets/{index-Ong_o86j.js → index-CwJX9TIf.js} +1 -1
  42. package/dist/client/assets/{index-kl_BbR04.js → index-CzkjYgaV.js} +1 -1
  43. package/dist/client/assets/{index-Bp2vWP2Y.js → index-DFdFKost.js} +1 -1
  44. package/dist/client/assets/{index-C3m3P5mw.js → index-DV3p0C-w.js} +1 -1
  45. package/dist/client/assets/{infoDiagram-HS3SLOUP-CoptTTxB.js → infoDiagram-HS3SLOUP-BJFhYv2N.js} +1 -1
  46. package/dist/client/assets/{journeyDiagram-XKPGCS4Q-CMN-HV9u.js → journeyDiagram-XKPGCS4Q-CtemPXmL.js} +1 -1
  47. package/dist/client/assets/{kanban-definition-3W4ZIXB7-DC3laXMu.js → kanban-definition-3W4ZIXB7-C9Xu3EkY.js} +1 -1
  48. package/dist/client/assets/{layout-BNpvAk6E.js → layout-DtMsQ09j.js} +1 -1
  49. package/dist/client/assets/{main-Bgor0kVJ.js → main-DashEqVP.js} +241 -241
  50. package/dist/client/assets/main-sOkcigfk.css +1 -0
  51. package/dist/client/assets/{mindmap-definition-VGOIOE7T-CFfDEH47.js → mindmap-definition-VGOIOE7T-C_FSSv6y.js} +1 -1
  52. package/dist/client/assets/{pieDiagram-ADFJNKIX-CZP5lrkz.js → pieDiagram-ADFJNKIX-BdDAYUXV.js} +1 -1
  53. package/dist/client/assets/{quadrantDiagram-AYHSOK5B-Bc1rWWnR.js → quadrantDiagram-AYHSOK5B-B5g2nuTP.js} +1 -1
  54. package/dist/client/assets/{requirementDiagram-UZGBJVZJ-BtfVzz9o.js → requirementDiagram-UZGBJVZJ-BYa9pNRf.js} +1 -1
  55. package/dist/client/assets/{sankeyDiagram-TZEHDZUN-CTFUjzzh.js → sankeyDiagram-TZEHDZUN-Co2c_zNW.js} +1 -1
  56. package/dist/client/assets/{sequenceDiagram-WL72ISMW-Ceovvbrm.js → sequenceDiagram-WL72ISMW-eShduLQR.js} +1 -1
  57. package/dist/client/assets/{stateDiagram-FKZM4ZOC-BboZL1pg.js → stateDiagram-FKZM4ZOC-53kPEZua.js} +1 -1
  58. package/dist/client/assets/stateDiagram-v2-4FDKWEC3-BOfSj2dL.js +1 -0
  59. package/dist/client/assets/{timeline-definition-IT6M3QCI-7vfajGp6.js → timeline-definition-IT6M3QCI-CZjtXE9X.js} +1 -1
  60. package/dist/client/assets/{treemap-GDKQZRPO-C1ZwadxG.js → treemap-GDKQZRPO-DOvJYPBs.js} +1 -1
  61. package/dist/client/assets/{worktree._worktreeId-BmMetrj_.js → worktree._worktreeId-BUGQ0lrG.js} +1 -1
  62. package/dist/client/assets/{worktree._worktreeId-C4_UcKsk.js → worktree._worktreeId-CvNxzyR7.js} +1 -1
  63. package/dist/client/assets/{xychartDiagram-PRI3JC2R-pTpN8cts.js → xychartDiagram-PRI3JC2R-BwZe0MO_.js} +1 -1
  64. package/dist/start-server.mjs +132 -50
  65. package/package.json +7 -7
  66. package/dist/client/assets/channel-DO7Rw5s3.js +0 -1
  67. package/dist/client/assets/classDiagram-2ON5EDUG-Dydmjn5K.js +0 -1
  68. package/dist/client/assets/classDiagram-v2-WZHVMYZB-Dydmjn5K.js +0 -1
  69. package/dist/client/assets/clone-BBqOdmdW.js +0 -1
  70. package/dist/client/assets/main-oF1qBPU-.css +0 -1
  71. package/dist/client/assets/stateDiagram-v2-4FDKWEC3-BsPSpOyb.js +0 -1
@@ -54081,7 +54081,7 @@ import { lstatSync as lstatSync3, realpathSync as realpathSync4 } from "node:fs"
54081
54081
  import { platform } from "node:os";
54082
54082
  import { delimiter as delimiter3, join as join19, resolve as resolve4 } from "node:path";
54083
54083
  function findCliBinaryAt(opts) {
54084
- const { cwd, dirname: dirname17 } = opts;
54084
+ const { cwd, dirname: dirname18 } = opts;
54085
54085
  const exe = platform() === "win32" ? "band.exe" : "band";
54086
54086
  const explicit = process.env.BAND_CLI_PATH;
54087
54087
  if (explicit) {
@@ -54097,7 +54097,7 @@ function findCliBinaryAt(opts) {
54097
54097
  // cwd = repo root (fallback)
54098
54098
  resolve4(cwd, "apps"),
54099
54099
  // From this source file in dev (packages/host-local/src/process/ → apps/)
54100
- resolve4(dirname17, "..", "..", "..", "..", "apps"),
54100
+ resolve4(dirname18, "..", "..", "..", "..", "apps"),
54101
54101
  // From bundled `dist/` file (<Resources>/web/dist/ → <Resources>/) only
54102
54102
  // — in dev mode this resolves to `packages/`, which has no
54103
54103
  // `cli/target/<profile>/band` and is harmless; the walk above is the
@@ -54105,7 +54105,7 @@ function findCliBinaryAt(opts) {
54105
54105
  // layout under <Resources>/cli/ would still resolve. Today's Electron
54106
54106
  // bundle ships the binary under `binaries/` (handled by Strategy B),
54107
54107
  // so this strategy never hits in production either.
54108
- resolve4(dirname17, "..", "..", "..")
54108
+ resolve4(dirname18, "..", "..", "..")
54109
54109
  ];
54110
54110
  for (const appsDir of appsStrategies) {
54111
54111
  for (const profile of ["release", "debug"]) {
@@ -54122,7 +54122,7 @@ function findCliBinaryAt(opts) {
54122
54122
  resolve4(cwd, "..", "binaries", exe),
54123
54123
  // From the bundled dist file (<Resources>/web/dist/start-server.mjs)
54124
54124
  // → <Resources>/binaries/band
54125
- resolve4(dirname17, "..", "..", "binaries", exe)
54125
+ resolve4(dirname18, "..", "..", "binaries", exe)
54126
54126
  ];
54127
54127
  for (const p2 of electronCandidates) {
54128
54128
  try {
@@ -63153,10 +63153,10 @@ var init_agent_command = __esm({
63153
63153
  import { readdirSync as readdirSync8 } from "node:fs";
63154
63154
  import { mkdir as mkdir5 } from "node:fs/promises";
63155
63155
  import { join as join38 } from "node:path";
63156
- async function openSharedDir(worktreeId4) {
63156
+ async function openSharedDir(worktreeId4, chatId) {
63157
63157
  const host = hostRegistry.hostFor(worktreeId4);
63158
63158
  if (host.id === hostRegistry.local.id) {
63159
- const path3 = join38(bandHome(), "shared", worktreeId4);
63159
+ const path3 = join38(bandHome(), "shared", worktreeId4, chatId);
63160
63160
  await mkdir5(path3, { recursive: true });
63161
63161
  return {
63162
63162
  path: path3,
@@ -63171,7 +63171,7 @@ async function openSharedDir(worktreeId4) {
63171
63171
  }
63172
63172
  const dirs = (await host.info()).dirs;
63173
63173
  if (!dirs) throw new Error(`Worker ${host.id} has no directory for shared files`);
63174
- const path2 = join38(dirs.shared, worktreeId4);
63174
+ const path2 = join38(dirs.shared, worktreeId4, chatId);
63175
63175
  await host.fs.mkdir(path2, { recursive: true });
63176
63176
  return {
63177
63177
  path: path2,
@@ -63192,6 +63192,25 @@ async function agentExtraDirs(worktreeId4) {
63192
63192
  const dirs = (await host.info()).dirs;
63193
63193
  return dirs ? [dirs.uploads, dirs.shared] : [];
63194
63194
  }
63195
+ function sharedFileUrl(worktreeId4, chatId, filename) {
63196
+ return `/api/shared/${encodeURIComponent(worktreeId4)}/${encodeURIComponent(chatId)}/${encodeURIComponent(filename)}`;
63197
+ }
63198
+ function parseSharedPath(rest) {
63199
+ const raw = rest.split("?")[0].split("/");
63200
+ if (raw.length !== 2 && raw.length !== 3) return null;
63201
+ const parts = [];
63202
+ for (const segment of raw) {
63203
+ let decoded;
63204
+ try {
63205
+ decoded = decodeURIComponent(segment);
63206
+ } catch {
63207
+ return null;
63208
+ }
63209
+ if (!decoded || decoded === "." || decoded === ".." || /[/\\\0]/.test(decoded)) return null;
63210
+ parts.push(decoded);
63211
+ }
63212
+ return parts.length === 3 ? { worktreeId: parts[0], chatId: parts[1], name: parts[2] } : { worktreeId: parts[0], name: parts[1] };
63213
+ }
63195
63214
  var init_shared_dir = __esm({
63196
63215
  "src/server/services/_utils/shared-dir.ts"() {
63197
63216
  "use strict";
@@ -68229,7 +68248,7 @@ function turnUsage(res, ctx) {
68229
68248
  return { ...usage, model, costUsd: (previousCost ?? 0) + cost };
68230
68249
  }
68231
68250
  function sharedDirNote(sharedDir) {
68232
- return `[File sharing: to send a file to the user, write or copy it to ${sharedDir}/ and it will appear as a downloadable file card in the chat.]`;
68251
+ return `[File sharing: to send a file to the user, write or copy it to ${sharedDir}/ and it will appear as a file card in this chat.]`;
68233
68252
  }
68234
68253
  function fileUri(path2) {
68235
68254
  return `file://${path2.split("/").map(encodeURIComponent).join("/")}`;
@@ -68729,6 +68748,8 @@ var init_session3 = __esm({
68729
68748
  this.misses = opts.heartbeatMisses ?? HEARTBEAT_MISSES;
68730
68749
  this.nextChan = opts.role === "client" ? 1 : 2;
68731
68750
  }
68751
+ /** Close code and reason of the last socket that ended. Code 1006 means no close frame arrived. */
68752
+ lastClose = null;
68732
68753
  get attached() {
68733
68754
  return this.ws !== null && this.ws.readyState === 1;
68734
68755
  }
@@ -68823,8 +68844,10 @@ var init_session3 = __esm({
68823
68844
  ws.on("message", (data, isBinary) => {
68824
68845
  if (this.ws === ws) this.onMessage(data, isBinary);
68825
68846
  });
68826
- ws.on("close", () => {
68827
- if (this.ws === ws) this.detach();
68847
+ ws.on("close", (code, reason) => {
68848
+ if (this.ws !== ws) return;
68849
+ this.lastClose = { code, reason: reason.toString().slice(0, 200) };
68850
+ this.detach();
68828
68851
  });
68829
68852
  ws.on("error", () => {
68830
68853
  });
@@ -68913,34 +68936,40 @@ var init_session3 = __esm({
68913
68936
  let frame;
68914
68937
  try {
68915
68938
  frame = decodeFrame(Buffer.isBuffer(data) ? data : Buffer.from(data));
68916
- } catch {
68917
- this.ws?.terminate();
68939
+ } catch (err) {
68940
+ this.cut("undecodable frame", err);
68918
68941
  return;
68919
68942
  }
68920
68943
  try {
68921
68944
  this.channels.get(frame.chan)?.handleFrame(frame);
68922
- } catch {
68923
- this.ws?.terminate();
68945
+ } catch (err) {
68946
+ this.cut(`channel ${frame.chan} failed to handle a frame`, err);
68924
68947
  }
68925
68948
  return;
68926
68949
  }
68927
68950
  let msg;
68928
68951
  try {
68929
68952
  msg = JSON.parse(data.toString());
68930
- } catch {
68931
- this.ws?.terminate();
68953
+ } catch (err) {
68954
+ this.cut("unparsable message", err);
68932
68955
  return;
68933
68956
  }
68934
68957
  if (msg === null || typeof msg !== "object") {
68935
- this.ws?.terminate();
68958
+ this.cut("message is not an object");
68936
68959
  return;
68937
68960
  }
68938
68961
  try {
68939
68962
  this.dispatchText(msg);
68940
- } catch {
68941
- this.ws?.terminate();
68963
+ } catch (err) {
68964
+ this.cut("invalid message", err);
68942
68965
  }
68943
68966
  }
68967
+ /** Drops the socket after a protocol violation, and says why, so the drop is not silent. */
68968
+ cut(reason, err) {
68969
+ const detail = err instanceof Error ? `: ${err.message}` : "";
68970
+ this.emit("fault", `${reason}${detail}`);
68971
+ this.ws?.terminate();
68972
+ }
68944
68973
  dispatchText(msg) {
68945
68974
  if (typeof msg.method === "string") {
68946
68975
  if ("id" in msg && msg.id !== void 0 && msg.id !== null)
@@ -68965,14 +68994,32 @@ var init_session3 = __esm({
68965
68994
  throw new Error("malformed link.open");
68966
68995
  }
68967
68996
  if (this.channels.has(p2.chan) || this.tombstones.has(p2.chan)) return;
68968
- if (this.channels.size >= MAX_CHANNELS) throw new Error("too many channels");
68997
+ if (this.channels.size >= MAX_CHANNELS) {
68998
+ this.emit("fault", `refused channel ${p2.chan}: too many channels`);
68999
+ this.writeBinary(
69000
+ encodeFrame({
69001
+ chan: p2.chan,
69002
+ seq: 0,
69003
+ kind: "reset",
69004
+ payload: Buffer.from("too many channels")
69005
+ })
69006
+ );
69007
+ return;
69008
+ }
68969
69009
  const ch = new Channel(this, p2.chan, p2.name, p2.meta, this.window);
68970
69010
  this.channels.set(p2.chan, ch);
68971
69011
  this.emit("channel", ch);
68972
69012
  return;
68973
69013
  }
68974
69014
  }
68975
- this.notifyHandlers.get(msg.method)?.(msg.params);
69015
+ try {
69016
+ this.notifyHandlers.get(msg.method)?.(msg.params);
69017
+ } catch (err) {
69018
+ this.emit(
69019
+ "fault",
69020
+ `notification ${msg.method} handler threw: ${err instanceof Error ? err.message : String(err)}`
69021
+ );
69022
+ }
68976
69023
  }
68977
69024
  async onRequest(msg) {
68978
69025
  const handler = this.handlers.get(msg.method);
@@ -69324,7 +69371,8 @@ var init_server3 = __esm({
69324
69371
  resumed,
69325
69372
  wsPing: true
69326
69373
  });
69327
- } catch {
69374
+ } catch (err) {
69375
+ this.emit("fault", session, `attach failed: ${err instanceof Error ? err.message : err}`);
69328
69376
  ws.terminate();
69329
69377
  return;
69330
69378
  }
@@ -69341,6 +69389,7 @@ var init_server3 = __esm({
69341
69389
  this.expiry.set(session.workerId, t2);
69342
69390
  });
69343
69391
  session.on("lost", () => this.emit("lost", session));
69392
+ session.on("fault", (reason) => this.emit("fault", session, reason));
69344
69393
  }
69345
69394
  dropSession(session, reason) {
69346
69395
  clearTimeout(this.expiry.get(session.workerId));
@@ -70413,7 +70462,7 @@ async function runTask(task) {
70413
70462
  }
70414
70463
  if (task.model) await applyTurnChoice(chatId, "model", task.model);
70415
70464
  if (task.mode) await applyTurnChoice(chatId, "mode", task.mode);
70416
- const shared2 = await openSharedDir(task.worktreeId);
70465
+ const shared2 = await openSharedDir(task.worktreeId, chatId);
70417
70466
  const seenShared = new Set(await shared2.list());
70418
70467
  const announce = (names) => {
70419
70468
  for (const filename of names) {
@@ -70422,7 +70471,7 @@ async function runTask(task) {
70422
70471
  agentSessionService.record(chatId, {
70423
70472
  type: "file",
70424
70473
  mediaType: mimeTypeFromFilename(filename),
70425
- url: `/api/shared/${encodeURIComponent(task.worktreeId)}/${encodeURIComponent(filename)}`,
70474
+ url: sharedFileUrl(task.worktreeId, chatId, filename),
70426
70475
  filename
70427
70476
  });
70428
70477
  }
@@ -75665,10 +75714,8 @@ var init_client_state_keys = __esm({
75665
75714
  perWorktree("band:dockview-layout-v9:", [jsonPart("device")]),
75666
75715
  // The `at` of the continuation the user has seen, which hides the sidebar clock icon.
75667
75716
  perWorktree("band:continued-seen:", [rawPart("device")]),
75668
- // The repo names the mobile app's worktree list has collapsed.
75717
+ // The repo and group names the mobile app's worktree list has collapsed.
75669
75718
  exact("band.mobile.collapsed-repos", [jsonPart("device")]),
75670
- // How the mobile worktree list is grouped: repo, origin or host.
75671
- exact("band.mobile.group-by", [rawPart("device")]),
75672
75719
  // The group the mobile worktree list is filtered to (a group id; unset means All).
75673
75720
  exact("band.mobile.group-filter", [rawPart("device")])
75674
75721
  ];
@@ -75803,7 +75850,7 @@ var init_client_state_service = __esm({
75803
75850
  }
75804
75851
  const updatedAt = Date.now();
75805
75852
  const version3 = input2.baseVersion + 1;
75806
- const written = input2.baseVersion === 0 ? this.queries.insertIfAbsent({
75853
+ let written = input2.baseVersion === 0 ? this.queries.insertIfAbsent({
75807
75854
  key: input2.key,
75808
75855
  scope: input2.scope,
75809
75856
  worktreeId: worktreeId4,
@@ -75815,6 +75862,16 @@ var init_client_state_service = __esm({
75815
75862
  version: version3,
75816
75863
  updatedAt
75817
75864
  });
75865
+ if (!written && input2.baseVersion === 0) {
75866
+ const current3 = this.queries.find(input2.key, input2.scope);
75867
+ if (current3 && current3.value === null) {
75868
+ written = this.queries.updateIfVersion(input2.key, input2.scope, current3.version, {
75869
+ value,
75870
+ version: current3.version + 1,
75871
+ updatedAt
75872
+ });
75873
+ }
75874
+ }
75818
75875
  const entry = this.queries.find(input2.key, input2.scope) ?? {
75819
75876
  key: input2.key,
75820
75877
  scope: input2.scope,
@@ -83233,7 +83290,7 @@ var init_agent_session_service = __esm({
83233
83290
  ownerGrants.delete(chatId);
83234
83291
  const grant = await worktree.host.relay?.issue({ worktreeId: chat.worktreeId, chatId });
83235
83292
  if (grant) ownerGrants.set(chatId, grant);
83236
- const shared2 = await openSharedDir(chat.worktreeId).catch(() => null);
83293
+ const shared2 = await openSharedDir(chat.worktreeId, chatId).catch(() => null);
83237
83294
  const login = await agentLoginService.resolve({
83238
83295
  agentType: def.type,
83239
83296
  repo: worktree.repo.name,
@@ -90632,6 +90689,7 @@ import {
90632
90689
  existsSync as existsSync18,
90633
90690
  mkdirSync as mkdirSync11,
90634
90691
  readFileSync as readFileSync12,
90692
+ realpathSync as realpathSync7,
90635
90693
  statSync as statSync16
90636
90694
  } from "node:fs";
90637
90695
  import { createServer as createServer3 } from "node:http";
@@ -111646,7 +111704,7 @@ init_registry();
111646
111704
  // package.json
111647
111705
  var package_default = {
111648
111706
  name: "@band-app/server",
111649
- version: "0.53.0",
111707
+ version: "0.54.0",
111650
111708
  type: "module",
111651
111709
  types: "src/index.ts",
111652
111710
  exports: {
@@ -113002,9 +113060,18 @@ var WorkerLinkService = class {
113002
113060
  this.server = new LinkServer({ authenticate: (hello) => this.authenticate(hello) });
113003
113061
  this.server.on("session", (session) => this.onSession(session));
113004
113062
  this.server.on("connected", (session) => void this.onConnected(session));
113063
+ this.server.on("disconnected", (session) => {
113064
+ const close = session.lastClose;
113065
+ if (close) {
113066
+ log67.info(
113067
+ `link of ${session.workerId} closed (code ${close.code}${close.reason ? `, ${close.reason}` : ""})`
113068
+ );
113069
+ }
113070
+ this.setStatus(session.workerId, "offline", this.now());
113071
+ });
113005
113072
  this.server.on(
113006
- "disconnected",
113007
- (session) => this.setStatus(session.workerId, "offline", this.now())
113073
+ "fault",
113074
+ (session, reason) => log67.warn(`link of ${session.workerId}: ${reason}`)
113008
113075
  );
113009
113076
  this.server.on(
113010
113077
  "lost",
@@ -124601,8 +124668,8 @@ init_webhook();
124601
124668
 
124602
124669
  // src/server/services/_utils/hosted-file.ts
124603
124670
  init_registry();
124604
- import { basename as basename6, join as join58 } from "node:path";
124605
- async function openHostedFile(kind, worktreeId4, rawName) {
124671
+ import { basename as basename6, dirname as dirname17, join as join58 } from "node:path";
124672
+ async function openHostedFile(kind, worktreeId4, rawName, chatId) {
124606
124673
  const host = hostRegistry.hostFor(worktreeId4);
124607
124674
  if (host.id === hostRegistry.local.id) return null;
124608
124675
  let name5;
@@ -124615,7 +124682,9 @@ async function openHostedFile(kind, worktreeId4, rawName) {
124615
124682
  try {
124616
124683
  const dirs = (await host.info()).dirs;
124617
124684
  if (!dirs) return null;
124618
- const path2 = join58(dirs[kind], worktreeId4, name5);
124685
+ const path2 = chatId ? join58(dirs[kind], worktreeId4, chatId, name5) : join58(dirs[kind], worktreeId4, name5);
124686
+ const dir = await host.fs.realpath(dirname17(path2));
124687
+ if (!(await host.fs.realpath(path2)).startsWith(`${dir}/`)) return null;
124619
124688
  const stat7 = await host.fs.stat(path2, { followSymlinks: true });
124620
124689
  if (stat7.kind !== "file") return null;
124621
124690
  return { size: stat7.size, stream: host.fs.readStream(path2) };
@@ -132736,6 +132805,7 @@ async function listenWithFallback(server, startPort, attempts = 20) {
132736
132805
  }
132737
132806
 
132738
132807
  // start-server.ts
132808
+ init_shared_dir();
132739
132809
  init_agent_session_registry_service();
132740
132810
  init_branch_status_poller();
132741
132811
  init_browser_service();
@@ -133098,6 +133168,13 @@ function getCachedScalarHtml() {
133098
133168
  _scalarHtml = getScalarHtml("/api/openapi.json");
133099
133169
  return _scalarHtml;
133100
133170
  }
133171
+ function hostedFileHeaders(contentType) {
133172
+ const headers = { "X-Content-Type-Options": "nosniff" };
133173
+ if (/^(text\/html|image\/svg\+xml)\b/.test(contentType)) {
133174
+ headers["Content-Security-Policy"] = "sandbox allow-scripts";
133175
+ }
133176
+ return headers;
133177
+ }
133101
133178
  function serveStaticFile(res, root, subdir, rawFilename) {
133102
133179
  const filename = basename7(decodeURIComponent(rawFilename));
133103
133180
  if (!filename || filename.includes("..")) {
@@ -133107,12 +133184,19 @@ function serveStaticFile(res, root, subdir, rawFilename) {
133107
133184
  }
133108
133185
  const filePath = join59(root, subdir, filename);
133109
133186
  try {
133187
+ const dir = realpathSync7(join59(root, subdir));
133188
+ if (!realpathSync7(filePath).startsWith(dir + sep10)) {
133189
+ res.writeHead(404);
133190
+ res.end("Not found");
133191
+ return;
133192
+ }
133110
133193
  const fileStat = statSync16(filePath);
133111
133194
  const contentType = mimeTypeFromFilename(filename);
133112
133195
  res.writeHead(200, {
133113
133196
  "Content-Type": contentType,
133114
133197
  "Content-Length": fileStat.size.toString(),
133115
- "Cache-Control": "private, max-age=86400"
133198
+ "Cache-Control": "private, max-age=86400",
133199
+ ...hostedFileHeaders(contentType)
133116
133200
  });
133117
133201
  createReadStream6(filePath).pipe(res);
133118
133202
  } catch {
@@ -133120,18 +133204,20 @@ function serveStaticFile(res, root, subdir, rawFilename) {
133120
133204
  res.end("Not found");
133121
133205
  }
133122
133206
  }
133123
- async function serveHostedFile(res, kind, worktreeId4, rawName) {
133207
+ async function serveHostedFile(res, kind, worktreeId4, rawName, chatId) {
133124
133208
  if (hostRegistry.hostFor(worktreeId4).id === hostRegistry.local.id) return false;
133125
- const file3 = await openHostedFile(kind, worktreeId4, rawName);
133209
+ const file3 = await openHostedFile(kind, worktreeId4, rawName, chatId);
133126
133210
  if (!file3) {
133127
133211
  res.writeHead(404);
133128
133212
  res.end("Not found");
133129
133213
  return true;
133130
133214
  }
133215
+ const contentType = mimeTypeFromFilename(basename7(decodeURIComponent(rawName)));
133131
133216
  res.writeHead(200, {
133132
- "Content-Type": mimeTypeFromFilename(basename7(decodeURIComponent(rawName))),
133217
+ "Content-Type": contentType,
133133
133218
  "Content-Length": file3.size.toString(),
133134
- "Cache-Control": "private, max-age=86400"
133219
+ "Cache-Control": "private, max-age=86400",
133220
+ ...hostedFileHeaders(contentType)
133135
133221
  });
133136
133222
  pipeline2(
133137
133223
  Readable2.from(file3.stream, { objectMode: false, highWaterMark: 64 * 1024 }),
@@ -133559,21 +133645,17 @@ async function main() {
133559
133645
  return;
133560
133646
  }
133561
133647
  if (req.url?.startsWith("/api/shared/")) {
133562
- const rest = req.url.slice("/api/shared/".length).split("?")[0];
133563
- const slashIdx = rest.indexOf("/");
133564
- if (slashIdx === -1) {
133648
+ const parsed = parseSharedPath(req.url.slice("/api/shared/".length));
133649
+ if (!parsed) {
133565
133650
  res.writeHead(400);
133566
133651
  res.end("Bad request");
133567
133652
  return;
133568
133653
  }
133569
- const partition = basename7(decodeURIComponent(rest.slice(0, slashIdx)));
133570
- if (!partition || partition === ".." || partition === ".") {
133571
- res.writeHead(400);
133572
- res.end("Bad request");
133654
+ const encodedName = encodeURIComponent(parsed.name);
133655
+ if (await serveHostedFile(res, "shared", parsed.worktreeId, encodedName, parsed.chatId))
133573
133656
  return;
133574
- }
133575
- if (await serveHostedFile(res, "shared", partition, rest.slice(slashIdx + 1))) return;
133576
- serveStaticFile(res, bandHome(), join59("shared", partition), rest.slice(slashIdx + 1));
133657
+ const subdir = parsed.chatId ? join59("shared", parsed.worktreeId, parsed.chatId) : join59("shared", parsed.worktreeId);
133658
+ serveStaticFile(res, bandHome(), subdir, encodedName);
133577
133659
  return;
133578
133660
  }
133579
133661
  if (req.url?.startsWith("/api/worktree-file/")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@band-app/server",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "type": "module",
5
5
  "types": "src/index.ts",
6
6
  "exports": {
@@ -57,18 +57,18 @@
57
57
  "vitest": "^4.0.18",
58
58
  "ws": "^8.18.0",
59
59
  "zod": "^3.25.67",
60
- "@band-app/coding-agent": "^0.53.0",
60
+ "@band-app/coding-agent": "^0.54.0",
61
+ "@band-app/host-api": "^0.1.0",
62
+ "@band-app/chat-runtime": "^0.1.0",
61
63
  "@band-app/environment": "^0.1.0",
62
64
  "@band-app/host-local": "^0.1.0",
63
65
  "@band-app/host-remote": "^0.1.0",
64
- "@band-app/chat-runtime": "^0.1.0",
65
- "@band-app/host-api": "^0.1.0",
66
- "@band-app/logger": "^0.53.0",
67
- "@band-app/plugin-api": "^0.1.0",
66
+ "@band-app/link": "^0.1.0",
68
67
  "@band-app/plugin-github": "^0.1.0",
68
+ "@band-app/plugin-api": "^0.1.0",
69
69
  "@band-app/shared": "^0.1.0",
70
70
  "@band-app/worktree-ops": "^0.1.0",
71
- "@band-app/link": "^0.1.0"
71
+ "@band-app/logger": "^0.54.0"
72
72
  },
73
73
  "scripts": {
74
74
  "dev": "NODE_ENV=development tsx watch --exclude '../web/**' start-server.ts",
@@ -1 +0,0 @@
1
- import{Y as a,Z as n}from"./main-Bgor0kVJ.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW-CRx24wmo.js";import{_ as i}from"./main-Bgor0kVJ.js";import"./chunk-FMBD7UC4-CFQ4Ns-B.js";import"./chunk-55IACEB6-CZirdcYj.js";import"./chunk-QN33PNHL-O1rjaqXm.js";var p={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{p as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW-CRx24wmo.js";import{_ as i}from"./main-Bgor0kVJ.js";import"./chunk-FMBD7UC4-CFQ4Ns-B.js";import"./chunk-55IACEB6-CZirdcYj.js";import"./chunk-QN33PNHL-O1rjaqXm.js";var p={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{p as diagram};
@@ -1 +0,0 @@
1
- import{b as r}from"./_baseUniq-BH8BhmRB.js";var e=4;function a(o){return r(o,e)}export{a as c};