bazilion 0.10.0 → 0.11.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 (63) hide show
  1. package/dist/cli.js +1249 -136
  2. package/dist/cli.js.map +1 -1
  3. package/dist/daemon.js +3027 -396
  4. package/dist/daemon.js.map +1 -1
  5. package/dist/migrations/0001_init.sql +20 -0
  6. package/dist/web/client/assets/{ProductionTeamPolicyEditor-CiQ-VVDo.js → ProductionTeamPolicyEditor-B6dbpuYy.js} +1 -1
  7. package/dist/web/client/assets/{ProfileCommunicationEditor-D84F5yfr.js → ProfileCommunicationEditor-Bf8UvmZT.js} +1 -1
  8. package/dist/web/client/assets/{TeamTabs-Btoe_FAE.js → TeamTabs-sQBHChtd.js} +1 -1
  9. package/dist/web/client/assets/{TemplatesTabs-CAYY03F5.js → TemplatesTabs-DPsuwsWB.js} +1 -1
  10. package/dist/web/client/assets/{_id-BqU2BodK.js → _id-C9rkCclz.js} +1 -1
  11. package/dist/web/client/assets/{_id-Q98Of6k9.js → _id-D65IzC8H.js} +1 -1
  12. package/dist/web/client/assets/{_id-BeauECmk.js → _id-DE4IY5bL.js} +1 -1
  13. package/dist/web/client/assets/{_id-CKtoio3t.js → _id-DYASsGQ8.js} +1 -1
  14. package/dist/web/client/assets/{activity-DXN_N85w.js → activity-C7o6-xHg.js} +1 -1
  15. package/dist/web/client/assets/{agents-C5Xsdge1.js → agents-UroO4d25.js} +1 -1
  16. package/dist/web/client/assets/{agents-Cgxo4H5G.js → agents-h8yWXQ2H.js} +1 -1
  17. package/dist/web/client/assets/{approvals-BnG7fbw-.js → approvals-DKhF0YLC.js} +1 -1
  18. package/dist/web/client/assets/chevron-down-BK6NPMMP.js +1 -0
  19. package/dist/web/client/assets/{config-xw4NyrXF.js → config-q8JDresK.js} +1 -1
  20. package/dist/web/client/assets/{context-D2V1Oi4z.js → context-CBucAoi6.js} +1 -1
  21. package/dist/web/client/assets/{inbox-DK6hWCMp.js → inbox-BwoRikj1.js} +1 -1
  22. package/dist/web/client/assets/{index-VdURSoM4.js → index-DUXAevpZ.js} +2 -2
  23. package/dist/web/client/assets/{login-CvptUoMR.js → login-CNAV9Wer.js} +1 -1
  24. package/dist/web/client/assets/{mcp-BDFM_sW-.js → mcp-BKdEFwHO.js} +1 -1
  25. package/dist/web/client/assets/{members-BJvph_TR.js → members-BDqZQh26.js} +1 -1
  26. package/dist/web/client/assets/{memory-DRytqZCD.js → memory-DVdEVr6L.js} +1 -1
  27. package/dist/web/client/assets/plus-b4yFXP0O.js +1 -0
  28. package/dist/web/client/assets/{policy-CGP50OtQ.js → policy-DdDctqvg.js} +1 -1
  29. package/dist/web/client/assets/{routes-DJnWYvC9.js → routes-BEfALSZv.js} +1 -1
  30. package/dist/web/client/assets/{services-DHheiv7L.js → services-DZP2ybzb.js} +1 -1
  31. package/dist/web/client/assets/{skills-DmsitluU.js → skills-DoeIjITs.js} +1 -1
  32. package/dist/web/client/assets/styles-BYM0UGdF.css +2 -0
  33. package/dist/web/client/assets/{teams-CbxSaorm.js → teams-DlHRnrQw.js} +1 -1
  34. package/dist/web/client/assets/{teams-BdC4gthq.js → teams-W3j-UoJ9.js} +1 -1
  35. package/dist/web/client/assets/telegram-2OdeGv4E.js +1 -0
  36. package/dist/web/client/assets/{tokens-CjRplDrJ.js → tokens-MuBfmgue.js} +1 -1
  37. package/dist/web/client/assets/triggers-CX7RMQFQ.js +1 -0
  38. package/dist/web/client/assets/{welcome-wBfX9dAd.js → welcome-CvNr5IMi.js} +1 -1
  39. package/dist/web/client/assets/wire-constants-BhVKOCQa.js +74 -0
  40. package/dist/web/server/assets/{ChatPane-eUY_dga3.js → ChatPane-Ba5wTsVk.js} +325 -36
  41. package/dist/web/server/assets/{_id-ClN9hY2x.js → _id-Cg7x6q3g.js} +2 -2
  42. package/dist/web/server/assets/{_id-DkYodSkG.js → _id-DqON-59C.js} +1 -1
  43. package/dist/web/server/assets/{_tanstack-start-manifest_v-D09Xu_6u.js → _tanstack-start-manifest_v-BXauUXg6.js} +51 -51
  44. package/dist/web/server/assets/{router-DIdp8ZNt.js → router-C5_NjTsP.js} +5 -5
  45. package/dist/web/server/assets/{routes-Jb0zjKav.js → routes-B6cMLIPk.js} +3 -3
  46. package/dist/web/server/assets/{routes-CXz4hTAr.js → routes-BrA-U_Hm.js} +1 -1
  47. package/dist/web/server/assets/{routes-lIclH9Br.js → routes-C19CMLZt.js} +1 -1
  48. package/dist/web/server/assets/{telegram-7ub--6xz.js → telegram-CBF8SJzB.js} +1 -24
  49. package/dist/web/server/assets/{telegram-Bazhmpr4.js → telegram-XwyKSEtj.js} +1 -1
  50. package/dist/web/server/assets/{triggers-Ca57_qym.js → triggers-CBl4yMyw.js} +1 -1
  51. package/dist/web/server/assets/{triggers-C_0y6QV9.js → triggers-D79NoSXk.js} +3 -2
  52. package/dist/web/server/assets/{triggers-BMpHsGIr.js → triggers-_3rdhwSN.js} +27 -3
  53. package/dist/web/server/server.js +4 -4
  54. package/dist/worker.js +1609 -135
  55. package/dist/worker.js.map +1 -1
  56. package/package.json +4 -3
  57. package/dist/web/client/assets/chevron-down-BhcBspw3.js +0 -1
  58. package/dist/web/client/assets/plus-C9ee3SIO.js +0 -1
  59. package/dist/web/client/assets/styles-B5eQAwLP.css +0 -2
  60. package/dist/web/client/assets/telegram-D61G_alO.js +0 -1
  61. package/dist/web/client/assets/triggers-MLzTm_DM.js +0 -1
  62. package/dist/web/client/assets/wire-constants-BxfyUMXB.js +0 -74
  63. package/dist/web/server/assets/{Sidebar-O_CH1-JD.js → Sidebar-DsRVCszF.js} +1 -1
package/dist/daemon.js CHANGED
@@ -62,15 +62,15 @@ var require_windows = __commonJS({
62
62
  }
63
63
  return false;
64
64
  }
65
- function checkStat(stat, path, options) {
66
- if (!stat.isSymbolicLink() && !stat.isFile()) {
65
+ function checkStat(stat2, path, options) {
66
+ if (!stat2.isSymbolicLink() && !stat2.isFile()) {
67
67
  return false;
68
68
  }
69
69
  return checkPathExt(path, options);
70
70
  }
71
71
  function isexe(path, options, cb) {
72
- fs.stat(path, function(er, stat) {
73
- cb(er, er ? false : checkStat(stat, path, options));
72
+ fs.stat(path, function(er, stat2) {
73
+ cb(er, er ? false : checkStat(stat2, path, options));
74
74
  });
75
75
  }
76
76
  function sync(path, options) {
@@ -87,20 +87,20 @@ var require_mode = __commonJS({
87
87
  isexe.sync = sync;
88
88
  var fs = __require("fs");
89
89
  function isexe(path, options, cb) {
90
- fs.stat(path, function(er, stat) {
91
- cb(er, er ? false : checkStat(stat, options));
90
+ fs.stat(path, function(er, stat2) {
91
+ cb(er, er ? false : checkStat(stat2, options));
92
92
  });
93
93
  }
94
94
  function sync(path, options) {
95
95
  return checkStat(fs.statSync(path), options);
96
96
  }
97
- function checkStat(stat, options) {
98
- return stat.isFile() && checkMode(stat, options);
97
+ function checkStat(stat2, options) {
98
+ return stat2.isFile() && checkMode(stat2, options);
99
99
  }
100
- function checkMode(stat, options) {
101
- var mod = stat.mode;
102
- var uid = stat.uid;
103
- var gid = stat.gid;
100
+ function checkMode(stat2, options) {
101
+ var mod = stat2.mode;
102
+ var uid = stat2.uid;
103
+ var gid = stat2.gid;
104
104
  var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
105
105
  var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
106
106
  var u = parseInt("100", 8);
@@ -135,12 +135,12 @@ var require_isexe = __commonJS({
135
135
  if (typeof Promise !== "function") {
136
136
  throw new TypeError("callback not provided");
137
137
  }
138
- return new Promise(function(resolve4, reject) {
138
+ return new Promise(function(resolve7, reject) {
139
139
  isexe(path, options || {}, function(er, is) {
140
140
  if (er) {
141
141
  reject(er);
142
142
  } else {
143
- resolve4(is);
143
+ resolve7(is);
144
144
  }
145
145
  });
146
146
  });
@@ -207,27 +207,27 @@ var require_which = __commonJS({
207
207
  opt = {};
208
208
  const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
209
209
  const found = [];
210
- const step = (i) => new Promise((resolve4, reject) => {
210
+ const step = (i) => new Promise((resolve7, reject) => {
211
211
  if (i === pathEnv.length)
212
- return opt.all && found.length ? resolve4(found) : reject(getNotFoundError(cmd));
212
+ return opt.all && found.length ? resolve7(found) : reject(getNotFoundError(cmd));
213
213
  const ppRaw = pathEnv[i];
214
214
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
215
215
  const pCmd = path.join(pathPart, cmd);
216
216
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
217
- resolve4(subStep(p, i, 0));
217
+ resolve7(subStep(p, i, 0));
218
218
  });
219
- const subStep = (p, i, ii) => new Promise((resolve4, reject) => {
219
+ const subStep = (p, i, ii) => new Promise((resolve7, reject) => {
220
220
  if (ii === pathExt.length)
221
- return resolve4(step(i + 1));
221
+ return resolve7(step(i + 1));
222
222
  const ext = pathExt[ii];
223
223
  isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
224
224
  if (!er && is) {
225
225
  if (opt.all)
226
226
  found.push(p + ext);
227
227
  else
228
- return resolve4(p + ext);
228
+ return resolve7(p + ext);
229
229
  }
230
- return resolve4(subStep(p, i, ii + 1));
230
+ return resolve7(subStep(p, i, ii + 1));
231
231
  });
232
232
  });
233
233
  return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
@@ -7796,7 +7796,7 @@ var init_stdio2 = __esm({
7796
7796
  if (this._process) {
7797
7797
  throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
7798
7798
  }
7799
- return new Promise((resolve4, reject) => {
7799
+ return new Promise((resolve7, reject) => {
7800
7800
  this._process = (0, import_cross_spawn.default)(this._serverParams.command, this._serverParams.args ?? [], {
7801
7801
  // merge default env with server env because mcp server needs some env vars
7802
7802
  env: {
@@ -7813,7 +7813,7 @@ var init_stdio2 = __esm({
7813
7813
  this.onerror?.(error2);
7814
7814
  });
7815
7815
  this._process.on("spawn", () => {
7816
- resolve4();
7816
+ resolve7();
7817
7817
  });
7818
7818
  this._process.on("close", (_code) => {
7819
7819
  this._process = void 0;
@@ -7872,22 +7872,22 @@ var init_stdio2 = __esm({
7872
7872
  if (this._process) {
7873
7873
  const processToClose = this._process;
7874
7874
  this._process = void 0;
7875
- const closePromise = new Promise((resolve4) => {
7875
+ const closePromise = new Promise((resolve7) => {
7876
7876
  processToClose.once("close", () => {
7877
- resolve4();
7877
+ resolve7();
7878
7878
  });
7879
7879
  });
7880
7880
  try {
7881
7881
  processToClose.stdin?.end();
7882
7882
  } catch {
7883
7883
  }
7884
- await Promise.race([closePromise, new Promise((resolve4) => setTimeout(resolve4, 2e3).unref())]);
7884
+ await Promise.race([closePromise, new Promise((resolve7) => setTimeout(resolve7, 2e3).unref())]);
7885
7885
  if (processToClose.exitCode === null) {
7886
7886
  try {
7887
7887
  processToClose.kill("SIGTERM");
7888
7888
  } catch {
7889
7889
  }
7890
- await Promise.race([closePromise, new Promise((resolve4) => setTimeout(resolve4, 2e3).unref())]);
7890
+ await Promise.race([closePromise, new Promise((resolve7) => setTimeout(resolve7, 2e3).unref())]);
7891
7891
  }
7892
7892
  if (processToClose.exitCode === null) {
7893
7893
  try {
@@ -7899,15 +7899,15 @@ var init_stdio2 = __esm({
7899
7899
  this._readBuffer.clear();
7900
7900
  }
7901
7901
  send(message) {
7902
- return new Promise((resolve4) => {
7902
+ return new Promise((resolve7) => {
7903
7903
  if (!this._process?.stdin) {
7904
7904
  throw new Error("Not connected");
7905
7905
  }
7906
7906
  const json = serializeMessage(message);
7907
7907
  if (this._process.stdin.write(json)) {
7908
- resolve4();
7908
+ resolve7();
7909
7909
  } else {
7910
- this._process.stdin.once("drain", resolve4);
7910
+ this._process.stdin.once("drain", resolve7);
7911
7911
  }
7912
7912
  });
7913
7913
  }
@@ -9803,7 +9803,7 @@ var init_sse = __esm({
9803
9803
  }
9804
9804
  _startOrAuth() {
9805
9805
  const fetchImpl = this?._eventSourceInit?.fetch ?? this._fetch ?? fetch;
9806
- return new Promise((resolve4, reject) => {
9806
+ return new Promise((resolve7, reject) => {
9807
9807
  this._eventSource = new EventSource(this._url.href, {
9808
9808
  ...this._eventSourceInit,
9809
9809
  fetch: async (url2, init) => {
@@ -9824,7 +9824,7 @@ var init_sse = __esm({
9824
9824
  this._abortController = new AbortController();
9825
9825
  this._eventSource.onerror = (event2) => {
9826
9826
  if (event2.code === 401 && this._authProvider) {
9827
- this._authThenStart().then(resolve4, reject);
9827
+ this._authThenStart().then(resolve7, reject);
9828
9828
  return;
9829
9829
  }
9830
9830
  const error2 = new SseError(event2.code, event2.message, event2);
@@ -9846,7 +9846,7 @@ var init_sse = __esm({
9846
9846
  void this.close();
9847
9847
  return;
9848
9848
  }
9849
- resolve4();
9849
+ resolve7();
9850
9850
  });
9851
9851
  this._eventSource.onmessage = (event2) => {
9852
9852
  const messageEvent = event2;
@@ -11016,7 +11016,7 @@ var init_protocol = __esm({
11016
11016
  return;
11017
11017
  }
11018
11018
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
11019
- await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
11019
+ await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
11020
11020
  options?.signal?.throwIfAborted();
11021
11021
  }
11022
11022
  } catch (error2) {
@@ -11033,7 +11033,7 @@ var init_protocol = __esm({
11033
11033
  */
11034
11034
  request(request2, resultSchema, options) {
11035
11035
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
11036
- return new Promise((resolve4, reject) => {
11036
+ return new Promise((resolve7, reject) => {
11037
11037
  const earlyReject = (error2) => {
11038
11038
  reject(error2);
11039
11039
  };
@@ -11111,7 +11111,7 @@ var init_protocol = __esm({
11111
11111
  if (!parseResult.success) {
11112
11112
  reject(parseResult.error);
11113
11113
  } else {
11114
- resolve4(parseResult.data);
11114
+ resolve7(parseResult.data);
11115
11115
  }
11116
11116
  } catch (error2) {
11117
11117
  reject(error2);
@@ -11372,12 +11372,12 @@ var init_protocol = __esm({
11372
11372
  }
11373
11373
  } catch {
11374
11374
  }
11375
- return new Promise((resolve4, reject) => {
11375
+ return new Promise((resolve7, reject) => {
11376
11376
  if (signal.aborted) {
11377
11377
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
11378
11378
  return;
11379
11379
  }
11380
- const timeoutId = setTimeout(resolve4, interval);
11380
+ const timeoutId = setTimeout(resolve7, interval);
11381
11381
  signal.addEventListener("abort", () => {
11382
11382
  clearTimeout(timeoutId);
11383
11383
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -11830,11 +11830,11 @@ var require_codegen = __commonJS({
11830
11830
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
11831
11831
  return `${varKind} ${this.name}${rhs};` + _n;
11832
11832
  }
11833
- optimizeNames(names, constants) {
11833
+ optimizeNames(names, constants4) {
11834
11834
  if (!names[this.name.str])
11835
11835
  return;
11836
11836
  if (this.rhs)
11837
- this.rhs = optimizeExpr(this.rhs, names, constants);
11837
+ this.rhs = optimizeExpr(this.rhs, names, constants4);
11838
11838
  return this;
11839
11839
  }
11840
11840
  get names() {
@@ -11851,10 +11851,10 @@ var require_codegen = __commonJS({
11851
11851
  render({ _n }) {
11852
11852
  return `${this.lhs} = ${this.rhs};` + _n;
11853
11853
  }
11854
- optimizeNames(names, constants) {
11854
+ optimizeNames(names, constants4) {
11855
11855
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
11856
11856
  return;
11857
- this.rhs = optimizeExpr(this.rhs, names, constants);
11857
+ this.rhs = optimizeExpr(this.rhs, names, constants4);
11858
11858
  return this;
11859
11859
  }
11860
11860
  get names() {
@@ -11915,8 +11915,8 @@ var require_codegen = __commonJS({
11915
11915
  optimizeNodes() {
11916
11916
  return `${this.code}` ? this : void 0;
11917
11917
  }
11918
- optimizeNames(names, constants) {
11919
- this.code = optimizeExpr(this.code, names, constants);
11918
+ optimizeNames(names, constants4) {
11919
+ this.code = optimizeExpr(this.code, names, constants4);
11920
11920
  return this;
11921
11921
  }
11922
11922
  get names() {
@@ -11945,12 +11945,12 @@ var require_codegen = __commonJS({
11945
11945
  }
11946
11946
  return nodes.length > 0 ? this : void 0;
11947
11947
  }
11948
- optimizeNames(names, constants) {
11948
+ optimizeNames(names, constants4) {
11949
11949
  const { nodes } = this;
11950
11950
  let i = nodes.length;
11951
11951
  while (i--) {
11952
11952
  const n = nodes[i];
11953
- if (n.optimizeNames(names, constants))
11953
+ if (n.optimizeNames(names, constants4))
11954
11954
  continue;
11955
11955
  subtractNames(names, n.names);
11956
11956
  nodes.splice(i, 1);
@@ -12003,12 +12003,12 @@ var require_codegen = __commonJS({
12003
12003
  return void 0;
12004
12004
  return this;
12005
12005
  }
12006
- optimizeNames(names, constants) {
12006
+ optimizeNames(names, constants4) {
12007
12007
  var _a3;
12008
- this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants);
12009
- if (!(super.optimizeNames(names, constants) || this.else))
12008
+ this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants4);
12009
+ if (!(super.optimizeNames(names, constants4) || this.else))
12010
12010
  return;
12011
- this.condition = optimizeExpr(this.condition, names, constants);
12011
+ this.condition = optimizeExpr(this.condition, names, constants4);
12012
12012
  return this;
12013
12013
  }
12014
12014
  get names() {
@@ -12031,10 +12031,10 @@ var require_codegen = __commonJS({
12031
12031
  render(opts) {
12032
12032
  return `for(${this.iteration})` + super.render(opts);
12033
12033
  }
12034
- optimizeNames(names, constants) {
12035
- if (!super.optimizeNames(names, constants))
12034
+ optimizeNames(names, constants4) {
12035
+ if (!super.optimizeNames(names, constants4))
12036
12036
  return;
12037
- this.iteration = optimizeExpr(this.iteration, names, constants);
12037
+ this.iteration = optimizeExpr(this.iteration, names, constants4);
12038
12038
  return this;
12039
12039
  }
12040
12040
  get names() {
@@ -12070,10 +12070,10 @@ var require_codegen = __commonJS({
12070
12070
  render(opts) {
12071
12071
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
12072
12072
  }
12073
- optimizeNames(names, constants) {
12074
- if (!super.optimizeNames(names, constants))
12073
+ optimizeNames(names, constants4) {
12074
+ if (!super.optimizeNames(names, constants4))
12075
12075
  return;
12076
- this.iterable = optimizeExpr(this.iterable, names, constants);
12076
+ this.iterable = optimizeExpr(this.iterable, names, constants4);
12077
12077
  return this;
12078
12078
  }
12079
12079
  get names() {
@@ -12115,11 +12115,11 @@ var require_codegen = __commonJS({
12115
12115
  (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
12116
12116
  return this;
12117
12117
  }
12118
- optimizeNames(names, constants) {
12118
+ optimizeNames(names, constants4) {
12119
12119
  var _a3, _b;
12120
- super.optimizeNames(names, constants);
12121
- (_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants);
12122
- (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants);
12120
+ super.optimizeNames(names, constants4);
12121
+ (_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names, constants4);
12122
+ (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants4);
12123
12123
  return this;
12124
12124
  }
12125
12125
  get names() {
@@ -12420,7 +12420,7 @@ var require_codegen = __commonJS({
12420
12420
  function addExprNames(names, from) {
12421
12421
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
12422
12422
  }
12423
- function optimizeExpr(expr, names, constants) {
12423
+ function optimizeExpr(expr, names, constants4) {
12424
12424
  if (expr instanceof code_1.Name)
12425
12425
  return replaceName(expr);
12426
12426
  if (!canOptimize(expr))
@@ -12435,14 +12435,14 @@ var require_codegen = __commonJS({
12435
12435
  return items;
12436
12436
  }, []));
12437
12437
  function replaceName(n) {
12438
- const c = constants[n.str];
12438
+ const c = constants4[n.str];
12439
12439
  if (c === void 0 || names[n.str] !== 1)
12440
12440
  return n;
12441
12441
  delete names[n.str];
12442
12442
  return c;
12443
12443
  }
12444
12444
  function canOptimize(e) {
12445
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== void 0);
12445
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants4[c.str] !== void 0);
12446
12446
  }
12447
12447
  }
12448
12448
  function subtractNames(names, from) {
@@ -14404,7 +14404,7 @@ var require_compile = __commonJS({
14404
14404
  const schOrFunc = root.refs[ref];
14405
14405
  if (schOrFunc)
14406
14406
  return schOrFunc;
14407
- let _sch = resolve4.call(this, root, ref);
14407
+ let _sch = resolve7.call(this, root, ref);
14408
14408
  if (_sch === void 0) {
14409
14409
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
14410
14410
  const { schemaId } = this.opts;
@@ -14431,7 +14431,7 @@ var require_compile = __commonJS({
14431
14431
  function sameSchemaEnv(s1, s2) {
14432
14432
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
14433
14433
  }
14434
- function resolve4(root, ref) {
14434
+ function resolve7(root, ref) {
14435
14435
  let sch;
14436
14436
  while (typeof (sch = this.refs[ref]) == "string")
14437
14437
  ref = sch;
@@ -15062,55 +15062,55 @@ var require_fast_uri = __commonJS({
15062
15062
  }
15063
15063
  return uri;
15064
15064
  }
15065
- function resolve4(baseURI, relativeURI, options) {
15065
+ function resolve7(baseURI, relativeURI, options) {
15066
15066
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
15067
15067
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
15068
15068
  schemelessOptions.skipEscape = true;
15069
15069
  return serialize(resolved, schemelessOptions);
15070
15070
  }
15071
- function resolveComponent(base, relative, options, skipNormalization) {
15071
+ function resolveComponent(base, relative5, options, skipNormalization) {
15072
15072
  const target = {};
15073
15073
  if (!skipNormalization) {
15074
15074
  base = parse3(serialize(base, options), options);
15075
- relative = parse3(serialize(relative, options), options);
15075
+ relative5 = parse3(serialize(relative5, options), options);
15076
15076
  }
15077
15077
  options = options || {};
15078
- if (!options.tolerant && relative.scheme) {
15079
- target.scheme = relative.scheme;
15080
- target.userinfo = relative.userinfo;
15081
- target.host = relative.host;
15082
- target.port = relative.port;
15083
- target.path = removeDotSegments(relative.path || "");
15084
- target.query = relative.query;
15078
+ if (!options.tolerant && relative5.scheme) {
15079
+ target.scheme = relative5.scheme;
15080
+ target.userinfo = relative5.userinfo;
15081
+ target.host = relative5.host;
15082
+ target.port = relative5.port;
15083
+ target.path = removeDotSegments(relative5.path || "");
15084
+ target.query = relative5.query;
15085
15085
  } else {
15086
- if (relative.userinfo !== void 0 || relative.host !== void 0 || relative.port !== void 0) {
15087
- target.userinfo = relative.userinfo;
15088
- target.host = relative.host;
15089
- target.port = relative.port;
15090
- target.path = removeDotSegments(relative.path || "");
15091
- target.query = relative.query;
15086
+ if (relative5.userinfo !== void 0 || relative5.host !== void 0 || relative5.port !== void 0) {
15087
+ target.userinfo = relative5.userinfo;
15088
+ target.host = relative5.host;
15089
+ target.port = relative5.port;
15090
+ target.path = removeDotSegments(relative5.path || "");
15091
+ target.query = relative5.query;
15092
15092
  } else {
15093
- if (!relative.path) {
15093
+ if (!relative5.path) {
15094
15094
  target.path = base.path;
15095
- if (relative.query !== void 0) {
15096
- target.query = relative.query;
15095
+ if (relative5.query !== void 0) {
15096
+ target.query = relative5.query;
15097
15097
  } else {
15098
15098
  target.query = base.query;
15099
15099
  }
15100
15100
  } else {
15101
- if (relative.path[0] === "/") {
15102
- target.path = removeDotSegments(relative.path);
15101
+ if (relative5.path[0] === "/") {
15102
+ target.path = removeDotSegments(relative5.path);
15103
15103
  } else {
15104
15104
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
15105
- target.path = "/" + relative.path;
15105
+ target.path = "/" + relative5.path;
15106
15106
  } else if (!base.path) {
15107
- target.path = relative.path;
15107
+ target.path = relative5.path;
15108
15108
  } else {
15109
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
15109
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative5.path;
15110
15110
  }
15111
15111
  target.path = removeDotSegments(target.path);
15112
15112
  }
15113
- target.query = relative.query;
15113
+ target.query = relative5.query;
15114
15114
  }
15115
15115
  target.userinfo = base.userinfo;
15116
15116
  target.host = base.host;
@@ -15118,7 +15118,7 @@ var require_fast_uri = __commonJS({
15118
15118
  }
15119
15119
  target.scheme = base.scheme;
15120
15120
  }
15121
- target.fragment = relative.fragment;
15121
+ target.fragment = relative5.fragment;
15122
15122
  return target;
15123
15123
  }
15124
15124
  function equal(uriA, uriB, options) {
@@ -15320,7 +15320,7 @@ var require_fast_uri = __commonJS({
15320
15320
  var fastUri = {
15321
15321
  SCHEMES,
15322
15322
  normalize,
15323
- resolve: resolve4,
15323
+ resolve: resolve7,
15324
15324
  resolveComponent,
15325
15325
  equal,
15326
15326
  serialize,
@@ -19125,7 +19125,7 @@ var init_client2 = __esm({
19125
19125
  import { serve } from "@hono/node-server";
19126
19126
 
19127
19127
  // ../daemon/src/app.ts
19128
- import { Hono as Hono15 } from "hono";
19128
+ import { Hono as Hono16 } from "hono";
19129
19129
 
19130
19130
  // ../daemon/src/lib/middleware-auth.ts
19131
19131
  import { getCookie } from "hono/cookie";
@@ -20558,7 +20558,7 @@ function unarchiveAgent(db, id) {
20558
20558
  }
20559
20559
 
20560
20560
  // ../daemon/src/core/db/client.ts
20561
- import { DatabaseSync } from "node:sqlite";
20561
+ import { backup, DatabaseSync } from "node:sqlite";
20562
20562
  function wrap(rawDb) {
20563
20563
  const cache = /* @__PURE__ */ new Map();
20564
20564
  let transactionDepth = 0;
@@ -20631,10 +20631,13 @@ function applyPragmas(rawDb, includeWal) {
20631
20631
  rawDb.exec("PRAGMA foreign_keys = ON");
20632
20632
  }
20633
20633
  function openDb(path) {
20634
- const raw2 = new DatabaseSync(path);
20634
+ const raw2 = new DatabaseSync(path, { timeout: 5e3 });
20635
20635
  applyPragmas(raw2, true);
20636
20636
  return {
20637
20637
  raw: wrap(raw2),
20638
+ backupTo(path2) {
20639
+ return backup(raw2, path2);
20640
+ },
20638
20641
  close() {
20639
20642
  raw2.close();
20640
20643
  }
@@ -21236,6 +21239,8 @@ __export(communicationApprovals_exports, {
21236
21239
  expirePending: () => expirePending,
21237
21240
  finishDelivery: () => finishDelivery,
21238
21241
  get: () => get7,
21242
+ getByAttempt: () => getByAttempt,
21243
+ grantSchedulerTrigger: () => grantSchedulerTrigger,
21239
21244
  list: () => list6,
21240
21245
  request: () => request
21241
21246
  });
@@ -21396,6 +21401,12 @@ function get7(db, id, includePayload = false) {
21396
21401
  if (!includePayload) return approval;
21397
21402
  return detailFromRow(db, row);
21398
21403
  }
21404
+ function getByAttempt(db, attemptKind, attemptId, includePayload = false) {
21405
+ expirePending(db);
21406
+ const row = rawByAttempt(db, attemptKind, attemptId);
21407
+ if (!row) return null;
21408
+ return includePayload ? detailFromRow(db, row) : toApproval(row);
21409
+ }
21399
21410
  function detailFromRow(db, row) {
21400
21411
  const events = db.raw.query(
21401
21412
  "SELECT * FROM communication_approval_events WHERE approval_id = ? ORDER BY created_at, rowid"
@@ -21484,6 +21495,74 @@ function claimDelivery(db, id, actor, revalidate, now = Date.now()) {
21484
21495
  if (!outcome.detail) throw new Error("approval_state_conflict: delivery claim failed");
21485
21496
  return outcome.detail;
21486
21497
  }
21498
+ function grantSchedulerTrigger(db, id, actor, revalidate, validateDelivery, now = Date.now()) {
21499
+ expirePending(db, now);
21500
+ return db.raw.transaction(() => {
21501
+ const current = raw(db, id);
21502
+ if (!current) throw new Error(`approval_not_found: ${id}`);
21503
+ const detail3 = detailFromRow(db, current);
21504
+ if (detail3.status !== "pending")
21505
+ throw new Error(`approval_state_conflict: current ${detail3.status}`);
21506
+ if (detail3.operation !== "scheduler_trigger" || detail3.payloadKind !== "scheduler_trigger") {
21507
+ throw new Error("approval_payload_conflict: not a scheduler trigger approval");
21508
+ }
21509
+ const authorization = revalidate(detail3);
21510
+ const refsMatch = JSON.stringify(authorization.policyRefs) === JSON.stringify(detail3.policyRefs) && JSON.stringify(authorization.requiredEdgeIds) === JSON.stringify(detail3.requiredEdgeIds);
21511
+ if (authorization.decision !== "approval_required" || !refsMatch) {
21512
+ const error2 = "policy or membership changed";
21513
+ const changed2 = db.raw.run(
21514
+ `UPDATE communication_approvals
21515
+ SET status = 'denied', decided_at = ?, decided_by = ?, decision_reason = ?,
21516
+ updated_at = ? WHERE id = ? AND status = 'pending'`,
21517
+ [now, actor, error2, now, id]
21518
+ ).changes;
21519
+ if (changed2 !== 1) throw new Error("approval_state_conflict: concurrent approval");
21520
+ appendEvent(db, id, "denied", actor, error2, now);
21521
+ return {
21522
+ approval: toApproval(raw(db, id)),
21523
+ granted: false,
21524
+ failureKind: "revalidation",
21525
+ error: error2
21526
+ };
21527
+ }
21528
+ let deliveryError = null;
21529
+ try {
21530
+ deliveryError = validateDelivery(detail3);
21531
+ } catch (error2) {
21532
+ deliveryError = error2 instanceof Error ? error2.message : String(error2);
21533
+ }
21534
+ if (deliveryError) {
21535
+ const changed2 = db.raw.run(
21536
+ `UPDATE communication_approvals
21537
+ SET status = 'delivery_failed', decided_at = ?, decided_by = ?,
21538
+ delivery_error = ?, updated_at = ?
21539
+ WHERE id = ? AND status = 'pending'`,
21540
+ [now, actor, deliveryError, now, id]
21541
+ ).changes;
21542
+ if (changed2 !== 1) throw new Error("approval_state_conflict: concurrent approval");
21543
+ appendEvent(db, id, "approved", actor, null, now);
21544
+ appendEvent(db, id, "delivery_started", actor, null, now);
21545
+ appendEvent(db, id, "delivery_failed", actor, deliveryError, now);
21546
+ return {
21547
+ approval: toApproval(raw(db, id)),
21548
+ granted: false,
21549
+ failureKind: "delivery",
21550
+ error: deliveryError
21551
+ };
21552
+ }
21553
+ const changed = db.raw.run(
21554
+ `UPDATE communication_approvals
21555
+ SET status = 'delivered', decided_at = ?, decided_by = ?, delivery_error = NULL,
21556
+ updated_at = ? WHERE id = ? AND status = 'pending'`,
21557
+ [now, actor, now, id]
21558
+ ).changes;
21559
+ if (changed !== 1) throw new Error("approval_state_conflict: concurrent approval");
21560
+ appendEvent(db, id, "approved", actor, null, now);
21561
+ appendEvent(db, id, "delivery_started", actor, null, now);
21562
+ appendEvent(db, id, "delivered", actor, null, now);
21563
+ return { approval: toApproval(raw(db, id)), granted: true };
21564
+ })();
21565
+ }
21487
21566
  function finishDelivery(db, id, ok, actor, error2, now = Date.now()) {
21488
21567
  return db.raw.transaction(() => {
21489
21568
  const status = ok ? "delivered" : "delivery_failed";
@@ -21906,6 +21985,43 @@ var SERVICES = [
21906
21985
  }
21907
21986
  ]
21908
21987
  },
21988
+ {
21989
+ id: "shell-security",
21990
+ displayName: "Agent Shell Security",
21991
+ category: "service",
21992
+ team: "Agent Runtime",
21993
+ hint: "Optional dangerous-command approval and Docker isolation. Disabled by default.",
21994
+ fields: [
21995
+ {
21996
+ envVar: "BAZILION_BASH_APPROVAL",
21997
+ kind: "config",
21998
+ label: "Dangerous-command approval",
21999
+ placeholder: "off",
22000
+ description: "Use off (current behavior) or dangerous (classified bash commands require per-call approval before execution)."
22001
+ },
22002
+ {
22003
+ envVar: "BAZILION_BASH_SANDBOX",
22004
+ kind: "config",
22005
+ label: "Sandbox mode",
22006
+ placeholder: "off",
22007
+ description: "Use off (current host tools) or docker (containerized bash only; host-backed coding tools are hidden)."
22008
+ },
22009
+ {
22010
+ envVar: "BAZILION_BASH_SANDBOX_IMAGE",
22011
+ kind: "config",
22012
+ label: "Docker image",
22013
+ placeholder: "debian:bookworm-slim",
22014
+ description: "Local image with /bin/bash and /usr/bin/env and no VOLUME declarations. Bazilion never falls back to host execution if it is unavailable or incompatible."
22015
+ },
22016
+ {
22017
+ envVar: "BAZILION_BASH_SANDBOX_ENV_ALLOWLIST",
22018
+ kind: "config",
22019
+ label: "Extra environment allowlist",
22020
+ placeholder: "MY_SAFE_VARIABLE,ANOTHER_SAFE_VARIABLE",
22021
+ description: "Optional comma-separated variable names copied into the container. Provider credentials remain excluded unless explicitly named."
22022
+ }
22023
+ ]
22024
+ },
21909
22025
  {
21910
22026
  id: "browser",
21911
22027
  displayName: "Browser Automation",
@@ -22457,10 +22573,154 @@ function remove7(db, userId) {
22457
22573
  return true;
22458
22574
  }
22459
22575
 
22576
+ // ../daemon/src/core/repos/triggerDispatches.ts
22577
+ var triggerDispatches_exports = {};
22578
+ __export(triggerDispatches_exports, {
22579
+ cancelPendingForTrigger: () => cancelPendingForTrigger,
22580
+ cancelRunning: () => cancelRunning,
22581
+ claim: () => claim,
22582
+ defer: () => defer,
22583
+ fail: () => fail,
22584
+ get: () => get12,
22585
+ hasOpenForTrigger: () => hasOpenForTrigger,
22586
+ listClaimable: () => listClaimable,
22587
+ listForTrigger: () => listForTrigger,
22588
+ materialize: () => materialize,
22589
+ succeed: () => succeed
22590
+ });
22591
+ import { randomUUID as randomUUID7 } from "crypto";
22592
+ function toDispatch(row) {
22593
+ return {
22594
+ id: row.id,
22595
+ triggerId: row.trigger_id,
22596
+ agentId: row.agent_id,
22597
+ scheduledAt: row.scheduled_at,
22598
+ status: row.status,
22599
+ attemptCount: row.attempt_count,
22600
+ nextAttemptAt: row.next_attempt_at,
22601
+ leaseExpiresAt: row.lease_expires_at,
22602
+ startedAt: row.started_at,
22603
+ finishedAt: row.finished_at,
22604
+ lastError: row.last_error,
22605
+ createdAt: row.created_at,
22606
+ updatedAt: row.updated_at
22607
+ };
22608
+ }
22609
+ function materialize(db, input) {
22610
+ const now = input.now ?? Date.now();
22611
+ db.raw.run(
22612
+ `INSERT INTO trigger_dispatches
22613
+ (id, trigger_id, agent_id, scheduled_at, status, attempt_count, next_attempt_at,
22614
+ lease_expires_at, started_at, finished_at, last_error, created_at, updated_at)
22615
+ VALUES (?, ?, ?, ?, 'pending', 0, ?, NULL, NULL, NULL, NULL, ?, ?)
22616
+ ON CONFLICT(trigger_id, scheduled_at) DO NOTHING`,
22617
+ [randomUUID7(), input.triggerId, input.agentId, input.scheduledAt, now, now, now]
22618
+ );
22619
+ const row = db.raw.query(
22620
+ "SELECT * FROM trigger_dispatches WHERE trigger_id = ? AND scheduled_at = ?"
22621
+ ).get(input.triggerId, input.scheduledAt);
22622
+ if (!row) throw new Error("trigger dispatch materialization failed");
22623
+ return toDispatch(row);
22624
+ }
22625
+ function get12(db, id) {
22626
+ const row = db.raw.query("SELECT * FROM trigger_dispatches WHERE id = ?").get(id);
22627
+ return row ? toDispatch(row) : null;
22628
+ }
22629
+ function listClaimable(db, now = Date.now()) {
22630
+ return db.raw.query(
22631
+ `SELECT * FROM trigger_dispatches
22632
+ WHERE (status IN ('pending','retrying') AND next_attempt_at <= ?)
22633
+ OR (status = 'running' AND lease_expires_at <= ?)
22634
+ ORDER BY scheduled_at ASC, created_at ASC`
22635
+ ).all(now, now).map(toDispatch);
22636
+ }
22637
+ function hasOpenForTrigger(db, triggerId) {
22638
+ return db.raw.query(
22639
+ `SELECT 1 found FROM trigger_dispatches
22640
+ WHERE trigger_id = ? AND status IN ('pending','running','retrying') LIMIT 1`
22641
+ ).get(triggerId) !== null;
22642
+ }
22643
+ function claim(db, id, options = {}) {
22644
+ const now = options.now ?? Date.now();
22645
+ const leaseExpiresAt = now + (options.leaseMs ?? 5 * 6e4);
22646
+ return db.raw.transaction(() => {
22647
+ const result = db.raw.run(
22648
+ `UPDATE trigger_dispatches
22649
+ SET status = 'running', attempt_count = attempt_count + 1,
22650
+ lease_expires_at = ?, started_at = ?, finished_at = NULL, updated_at = ?
22651
+ WHERE id = ? AND (
22652
+ (status IN ('pending','retrying') AND next_attempt_at <= ?)
22653
+ OR (status = 'running' AND lease_expires_at <= ?)
22654
+ )`,
22655
+ [leaseExpiresAt, now, now, id, now, now]
22656
+ );
22657
+ if (Number(result.changes) !== 1) return null;
22658
+ const row = db.raw.query("SELECT * FROM trigger_dispatches WHERE id = ?").get(id);
22659
+ return row ? toDispatch(row) : null;
22660
+ })();
22661
+ }
22662
+ function succeed(db, id, now = Date.now()) {
22663
+ db.raw.run(
22664
+ `UPDATE trigger_dispatches SET status = 'succeeded', lease_expires_at = NULL,
22665
+ finished_at = ?, last_error = NULL, updated_at = ? WHERE id = ? AND status = 'running'`,
22666
+ [now, now, id]
22667
+ );
22668
+ }
22669
+ function defer(db, id, nextAttemptAt = Date.now() + 1e3) {
22670
+ db.raw.run(
22671
+ `UPDATE trigger_dispatches SET status = 'pending',
22672
+ attempt_count = CASE WHEN attempt_count > 0 THEN attempt_count - 1 ELSE 0 END,
22673
+ next_attempt_at = ?, lease_expires_at = NULL, started_at = NULL, updated_at = ?
22674
+ WHERE id = ? AND status = 'running'`,
22675
+ [nextAttemptAt, Date.now(), id]
22676
+ );
22677
+ }
22678
+ function cancelRunning(db, id, reason, now = Date.now()) {
22679
+ db.raw.run(
22680
+ `UPDATE trigger_dispatches SET status = 'cancelled', lease_expires_at = NULL,
22681
+ finished_at = ?, last_error = ?, updated_at = ? WHERE id = ? AND status = 'running'`,
22682
+ [now, reason.slice(0, 2e3), now, id]
22683
+ );
22684
+ }
22685
+ function fail(db, id, error2, options = {}) {
22686
+ const now = options.now ?? Date.now();
22687
+ const maxAttempts = options.maxAttempts ?? 3;
22688
+ const retryDelayMs = options.retryDelayMs ?? 5e3;
22689
+ const current = db.raw.query(
22690
+ "SELECT attempt_count FROM trigger_dispatches WHERE id = ?"
22691
+ ).get(id);
22692
+ if (!current) return;
22693
+ const terminal = current.attempt_count >= maxAttempts;
22694
+ db.raw.run(
22695
+ `UPDATE trigger_dispatches SET status = ?, next_attempt_at = ?, lease_expires_at = NULL,
22696
+ finished_at = ?, last_error = ?, updated_at = ? WHERE id = ? AND status = 'running'`,
22697
+ [
22698
+ terminal ? "failed" : "retrying",
22699
+ terminal ? now : now + retryDelayMs * 2 ** Math.max(0, current.attempt_count - 1),
22700
+ terminal ? now : null,
22701
+ error2.slice(0, 2e3),
22702
+ now,
22703
+ id
22704
+ ]
22705
+ );
22706
+ }
22707
+ function cancelPendingForTrigger(db, triggerId, now = Date.now()) {
22708
+ db.raw.run(
22709
+ `UPDATE trigger_dispatches SET status = 'cancelled', finished_at = ?, updated_at = ?
22710
+ WHERE trigger_id = ? AND status IN ('pending','retrying')`,
22711
+ [now, now, triggerId]
22712
+ );
22713
+ }
22714
+ function listForTrigger(db, triggerId, limit = 20) {
22715
+ return db.raw.query(
22716
+ "SELECT * FROM trigger_dispatches WHERE trigger_id = ? ORDER BY scheduled_at DESC LIMIT ?"
22717
+ ).all(triggerId, limit).map(toDispatch);
22718
+ }
22719
+
22460
22720
  // ../daemon/src/core/repos/triggers.ts
22461
22721
  var triggers_exports = {};
22462
22722
  __export(triggers_exports, {
22463
- get: () => get12,
22723
+ get: () => get13,
22464
22724
  insert: () => insert5,
22465
22725
  listEnabled: () => listEnabled3,
22466
22726
  listForAgent: () => listForAgent,
@@ -22468,7 +22728,7 @@ __export(triggers_exports, {
22468
22728
  remove: () => remove8,
22469
22729
  setEnabled: () => setEnabled3
22470
22730
  });
22471
- import { randomUUID as randomUUID7 } from "crypto";
22731
+ import { randomUUID as randomUUID8 } from "crypto";
22472
22732
  function toTrigger(r) {
22473
22733
  return {
22474
22734
  id: r.id,
@@ -22483,7 +22743,7 @@ function toTrigger(r) {
22483
22743
  };
22484
22744
  }
22485
22745
  function insert5(db, input) {
22486
- const id = randomUUID7();
22746
+ const id = randomUUID8();
22487
22747
  const now = Date.now();
22488
22748
  const enabled = input.enabled === false ? 0 : 1;
22489
22749
  db.raw.run(
@@ -22504,7 +22764,7 @@ function insert5(db, input) {
22504
22764
  createdAt: now
22505
22765
  };
22506
22766
  }
22507
- function get12(db, id) {
22767
+ function get13(db, id) {
22508
22768
  const row = db.raw.query("SELECT * FROM agent_triggers WHERE id = ?").get(id);
22509
22769
  return row ? toTrigger(row) : null;
22510
22770
  }
@@ -22522,7 +22782,10 @@ function listEnabled3(db) {
22522
22782
  ).all().map(toTrigger);
22523
22783
  }
22524
22784
  function setEnabled3(db, id, enabled) {
22525
- db.raw.run("UPDATE agent_triggers SET enabled = ? WHERE id = ?", [enabled ? 1 : 0, id]);
22785
+ db.raw.transaction(() => {
22786
+ db.raw.run("UPDATE agent_triggers SET enabled = ? WHERE id = ?", [enabled ? 1 : 0, id]);
22787
+ if (!enabled) cancelPendingForTrigger(db, id);
22788
+ })();
22526
22789
  }
22527
22790
  function markFired(db, id, when = Date.now()) {
22528
22791
  db.raw.run("UPDATE agent_triggers SET last_fired_at = ? WHERE id = ?", [when, id]);
@@ -22536,13 +22799,13 @@ var webTokens_exports = {};
22536
22799
  __export(webTokens_exports, {
22537
22800
  create: () => create,
22538
22801
  findActiveByToken: () => findActiveByToken,
22539
- get: () => get13,
22802
+ get: () => get14,
22540
22803
  hashToken: () => hashToken,
22541
22804
  list: () => list9,
22542
22805
  markUsed: () => markUsed,
22543
22806
  revoke: () => revoke
22544
22807
  });
22545
- import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID8 } from "crypto";
22808
+ import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID9 } from "crypto";
22546
22809
  function toToken(r) {
22547
22810
  return {
22548
22811
  id: r.id,
@@ -22556,7 +22819,7 @@ function hashToken(token) {
22556
22819
  return createHash2("sha256").update(token).digest("hex");
22557
22820
  }
22558
22821
  function create(db, label) {
22559
- const id = randomUUID8();
22822
+ const id = randomUUID9();
22560
22823
  const token = randomBytes2(24).toString("hex");
22561
22824
  const tokenHash = hashToken(token);
22562
22825
  const now = Date.now();
@@ -22574,7 +22837,7 @@ function list9(db, opts) {
22574
22837
  const sql = opts?.includeRevoked ? "SELECT * FROM web_tokens ORDER BY created_at ASC" : "SELECT * FROM web_tokens WHERE revoked_at IS NULL ORDER BY created_at ASC";
22575
22838
  return db.raw.query(sql).all().map(toToken);
22576
22839
  }
22577
- function get13(db, id) {
22840
+ function get14(db, id) {
22578
22841
  const row = db.raw.query("SELECT * FROM web_tokens WHERE id = ?").get(id);
22579
22842
  return row ? toToken(row) : null;
22580
22843
  }
@@ -22856,7 +23119,7 @@ function resolveAgentSkills(db, paths, agentId) {
22856
23119
  }
22857
23120
 
22858
23121
  // ../daemon/src/core/team/delete.ts
22859
- import { randomUUID as randomUUID9 } from "crypto";
23122
+ import { randomUUID as randomUUID10 } from "crypto";
22860
23123
  import { existsSync as existsSync9, renameSync as renameSync2, rmSync as rmSync6 } from "fs";
22861
23124
  function deleteTeam(db, paths, id, expectedTeamPolicyRevision) {
22862
23125
  const g2 = get3(db, id, paths);
@@ -22875,7 +23138,7 @@ function deleteTeam(db, paths, id, expectedTeamPolicyRevision) {
22875
23138
  `cannot delete team "${id}": ${members.length} agent(s) still belong to it: ${names}. Move or delete them first.`
22876
23139
  );
22877
23140
  }
22878
- const stagedPath = `${g2.path}.deleting-${randomUUID9()}`;
23141
+ const stagedPath = `${g2.path}.deleting-${randomUUID10()}`;
22879
23142
  const hadSlot = existsSync9(g2.path);
22880
23143
  if (hadSlot) renameSync2(g2.path, stagedPath);
22881
23144
  try {
@@ -22899,7 +23162,7 @@ function deleteTeam(db, paths, id, expectedTeamPolicyRevision) {
22899
23162
  }
22900
23163
 
22901
23164
  // ../daemon/src/core/team-policy/adopt.ts
22902
- import { randomUUID as randomUUID10 } from "crypto";
23165
+ import { randomUUID as randomUUID11 } from "crypto";
22903
23166
  function previewTeamPolicyAdoption(db, teamId, input) {
22904
23167
  return resolveAdoptionPlan(db, teamId, { ...input, previewEdges: [] }).resolved;
22905
23168
  }
@@ -22972,7 +23235,7 @@ function adoptTeamTemplate(db, teamId, input) {
22972
23235
  );
22973
23236
  db.raw.run("DELETE FROM template_instantiations WHERE team_id = ?", [teamId]);
22974
23237
  db.raw.run("DELETE FROM team_policy_edges WHERE team_id = ?", [teamId]);
22975
- const instantiationId = randomUUID10();
23238
+ const instantiationId = randomUUID11();
22976
23239
  db.raw.run(
22977
23240
  `INSERT INTO template_instantiations
22978
23241
  (id, team_id, template_id, template_revision, created_at)
@@ -23070,7 +23333,7 @@ function edgeKey(edge) {
23070
23333
  }
23071
23334
 
23072
23335
  // ../daemon/src/core/team-policy/authorization.ts
23073
- import { createHash as createHash3, randomUUID as randomUUID11 } from "crypto";
23336
+ import { createHash as createHash3, randomUUID as randomUUID12 } from "crypto";
23074
23337
  function denied(channel, code, reason) {
23075
23338
  return {
23076
23339
  decision: "deny",
@@ -23250,7 +23513,7 @@ function recordDenial(db, input, operation, result) {
23250
23513
  const sourceGroup = agentGroup(input.source) ?? agentGroup(input.target) ?? ("teamId" in input.source ? input.source.teamId : null);
23251
23514
  const targetGroup = agentGroup(input.target) ?? agentGroup(input.source) ?? ("teamId" in input.target ? input.target.teamId : null);
23252
23515
  const values = [
23253
- randomUUID11(),
23516
+ randomUUID12(),
23254
23517
  input.attemptKind,
23255
23518
  input.attemptId,
23256
23519
  operation,
@@ -23300,7 +23563,7 @@ function recordDenial(db, input, operation, result) {
23300
23563
  }
23301
23564
 
23302
23565
  // ../daemon/src/core/team-policy/source.ts
23303
- import { randomUUID as randomUUID12 } from "crypto";
23566
+ import { randomUUID as randomUUID13 } from "crypto";
23304
23567
  function diffTeamPolicy(db, teamId) {
23305
23568
  const detail3 = detail(db, teamId);
23306
23569
  if (!detail3) throw new Error(`team_policy_missing: ${teamId}`);
@@ -23368,7 +23631,7 @@ function saveTeamPolicyAsTemplate(db, paths, teamId, input) {
23368
23631
  const team = get3(db, teamId, paths);
23369
23632
  if (!team) throw new Error(`team not found: ${teamId}`);
23370
23633
  const members = list(db, { includeArchived: true }).filter((agent) => agent.teamId === teamId);
23371
- const slotByAgent = new Map(members.map((agent) => [agent.id, randomUUID12()]));
23634
+ const slotByAgent = new Map(members.map((agent) => [agent.id, randomUUID13()]));
23372
23635
  const stateByAgent = new Map(
23373
23636
  agentState(db, teamId).map((state3) => [state3.agentId, state3])
23374
23637
  );
@@ -23444,7 +23707,7 @@ function updateTeamPolicySource(db, teamId, input) {
23444
23707
  baselineBindings.map((binding) => [binding.agentId, binding.sourceSlotId])
23445
23708
  );
23446
23709
  for (const id of selectedIds) {
23447
- if (!slotByAgent.has(id)) slotByAgent.set(id, randomUUID12());
23710
+ if (!slotByAgent.has(id)) slotByAgent.set(id, randomUUID13());
23448
23711
  }
23449
23712
  const previousSlotById = new Map(previous.slots.map((slot) => [slot.slotId, slot]));
23450
23713
  const selectedMembers = [...selectedIds].map((id) => {
@@ -23512,7 +23775,7 @@ function pruneEmptyNonbaselineInstantiation(db, instantiationId, baselineId) {
23512
23775
  }
23513
23776
 
23514
23777
  // ../daemon/src/core/team-policy/spawn.ts
23515
- import { randomUUID as randomUUID13 } from "crypto";
23778
+ import { randomUUID as randomUUID14 } from "crypto";
23516
23779
  import { readdirSync as readdirSync4, rmSync as rmSync8 } from "fs";
23517
23780
 
23518
23781
  // ../daemon/src/core/team-policy/rm-with-retry.ts
@@ -23679,7 +23942,7 @@ async function spawnTeamTemplate(db, paths, input) {
23679
23942
  })
23680
23943
  );
23681
23944
  }
23682
- const instantiationId = randomUUID13();
23945
+ const instantiationId = randomUUID14();
23683
23946
  db.raw.run(
23684
23947
  `INSERT INTO template_instantiations
23685
23948
  (id, team_id, template_id, template_revision, created_at)
@@ -23766,7 +24029,7 @@ function safeReaddir(path) {
23766
24029
  }
23767
24030
 
23768
24031
  // ../daemon/src/lib/ctx.ts
23769
- import { chmodSync, existsSync as existsSync16, mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "fs";
24032
+ import { chmodSync, existsSync as existsSync16, mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "fs";
23770
24033
 
23771
24034
  // ../daemon/src/lib/agent-cancel.ts
23772
24035
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("bazilion.agent-cancel.registry");
@@ -23816,9 +24079,9 @@ function acquireAgentLifecycleLease(agentId) {
23816
24079
  leases.held.add(agentId);
23817
24080
  return Promise.resolve(makeRelease(agentId));
23818
24081
  }
23819
- return new Promise((resolve4) => {
24082
+ return new Promise((resolve7) => {
23820
24083
  const queue = leases.waiters.get(agentId) ?? [];
23821
- queue.push({ resolve: resolve4 });
24084
+ queue.push({ resolve: resolve7 });
23822
24085
  leases.waiters.set(agentId, queue);
23823
24086
  });
23824
24087
  }
@@ -23851,12 +24114,13 @@ async function runAgentLifecycleMutation(agentId, mutation) {
23851
24114
  }
23852
24115
 
23853
24116
  // ../daemon/src/lib/agent-turn.ts
23854
- import { randomUUID as randomUUID16 } from "crypto";
24117
+ import { randomUUID as randomUUID18 } from "crypto";
23855
24118
 
23856
24119
  // ../daemon/src/runtime/auth/openai-codex.ts
23857
24120
  import { loginOpenAICodex, refreshOpenAICodexToken } from "@earendil-works/pi-ai/oauth";
23858
24121
  var OPENAI_CODEX_SECRET_KEY = "OPENAI_CODEX_OAUTH";
23859
24122
  var REFRESH_MARGIN_MS = 6e4;
24123
+ var refreshFlights = /* @__PURE__ */ new WeakMap();
23860
24124
  function readCredentials(db, authToken) {
23861
24125
  const raw2 = openSecrets(db, authToken).get(OPENAI_CODEX_SECRET_KEY);
23862
24126
  if (!raw2) return null;
@@ -23901,10 +24165,28 @@ function getStatus(db, authToken) {
23901
24165
  async function loadAccessToken(db, authToken) {
23902
24166
  const creds = readCredentials(db, authToken);
23903
24167
  if (!creds) {
23904
- throw new Error(
23905
- "OpenAI ChatGPT OAuth not configured \u2014 run `bazilion auth openai login` (or use the Connect button on /config)"
23906
- );
24168
+ throw credentialsMissingError();
24169
+ }
24170
+ if (creds.expires > Date.now() + REFRESH_MARGIN_MS) return creds.access;
24171
+ let dbFlights = refreshFlights.get(db);
24172
+ if (!dbFlights) {
24173
+ dbFlights = /* @__PURE__ */ new Map();
24174
+ refreshFlights.set(db, dbFlights);
24175
+ }
24176
+ const existing = dbFlights.get(authToken);
24177
+ if (existing) return existing;
24178
+ const pending = Promise.resolve().then(() => refreshExpiredAccessToken(db, authToken));
24179
+ dbFlights.set(authToken, pending);
24180
+ try {
24181
+ return await pending;
24182
+ } finally {
24183
+ if (dbFlights.get(authToken) === pending) dbFlights.delete(authToken);
24184
+ if (dbFlights.size === 0) refreshFlights.delete(db);
23907
24185
  }
24186
+ }
24187
+ async function refreshExpiredAccessToken(db, authToken) {
24188
+ const creds = readCredentials(db, authToken);
24189
+ if (!creds) throw credentialsMissingError();
23908
24190
  if (creds.expires > Date.now() + REFRESH_MARGIN_MS) return creds.access;
23909
24191
  const refreshed = await refreshOpenAICodexToken(creds.refresh);
23910
24192
  const next = {
@@ -23912,9 +24194,23 @@ async function loadAccessToken(db, authToken) {
23912
24194
  access: refreshed.access,
23913
24195
  expires: refreshed.expires
23914
24196
  };
24197
+ const current = readCredentials(db, authToken);
24198
+ if (!current) throw credentialsMissingError();
24199
+ if (!sameCredentials(current, creds)) {
24200
+ if (current.expires > Date.now() + REFRESH_MARGIN_MS) return current.access;
24201
+ throw new Error("OpenAI ChatGPT OAuth credentials changed while refresh was in progress");
24202
+ }
23915
24203
  writeCredentials(db, authToken, next);
23916
24204
  return next.access;
23917
24205
  }
24206
+ function credentialsMissingError() {
24207
+ return new Error(
24208
+ "OpenAI ChatGPT OAuth not configured \u2014 run `bazilion auth openai login` (or use the Connect button on /config)"
24209
+ );
24210
+ }
24211
+ function sameCredentials(left, right) {
24212
+ return left.refresh === right.refresh && left.access === right.access && left.expires === right.expires;
24213
+ }
23918
24214
  function saveLoginCredentials(db, authToken, creds) {
23919
24215
  writeCredentials(db, authToken, {
23920
24216
  refresh: creds.refresh,
@@ -23937,15 +24233,19 @@ import { dirname as dirname2, join as join12 } from "path";
23937
24233
 
23938
24234
  // ../daemon/src/runtime/memory/qmd.ts
23939
24235
  import {
23940
- existsSync as existsSync11,
24236
+ closeSync,
24237
+ constants,
24238
+ fstatSync,
24239
+ lstatSync,
23941
24240
  mkdirSync as mkdirSync5,
24241
+ openSync,
23942
24242
  readdirSync as readdirSync6,
23943
24243
  readFileSync as readFileSync9,
24244
+ realpathSync,
23944
24245
  rmSync as rmSync10,
23945
- statSync as statSync4,
23946
24246
  writeFileSync as writeFileSync7
23947
24247
  } from "fs";
23948
- import { dirname as dirname3, join as join13 } from "path";
24248
+ import { dirname as dirname3, isAbsolute, join as join13, relative, resolve as resolve3, sep as sep2 } from "path";
23949
24249
  import { createStore, extractSnippet } from "@tobilu/qmd";
23950
24250
  var INDEX_FILENAME = ".qmd-index.sqlite";
23951
24251
  var COLLECTION_NAME = "memory";
@@ -23966,61 +24266,222 @@ function getStore(dir) {
23966
24266
  }
23967
24267
  return p;
23968
24268
  }
24269
+ function unsafeMemoryPath(path, reason) {
24270
+ return new Error(`unsafe memory path "${path}": ${reason}`);
24271
+ }
24272
+ function lstatIfPresent(path) {
24273
+ try {
24274
+ return lstatSync(path);
24275
+ } catch (error2) {
24276
+ if (error2.code === "ENOENT") return void 0;
24277
+ throw error2;
24278
+ }
24279
+ }
24280
+ function isContained(root, candidate) {
24281
+ const rel = relative(root, candidate);
24282
+ return rel === "" || !isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep2}`);
24283
+ }
24284
+ function assertContained(root, candidate) {
24285
+ if (!isContained(root, candidate)) {
24286
+ throw unsafeMemoryPath(candidate, `resolved outside memory root "${root}"`);
24287
+ }
24288
+ }
24289
+ function initializeSafeRoot(root) {
24290
+ mkdirSync5(root, { recursive: true });
24291
+ const info = lstatSync(root);
24292
+ if (info.isSymbolicLink()) {
24293
+ throw unsafeMemoryPath(root, "memory root must not be a symbolic link");
24294
+ }
24295
+ if (!info.isDirectory()) {
24296
+ throw unsafeMemoryPath(root, "memory root is not a directory");
24297
+ }
24298
+ return realpathSync(root);
24299
+ }
24300
+ function assertRootUnchanged(root, canonicalRoot) {
24301
+ const info = lstatIfPresent(root);
24302
+ if (!info) throw unsafeMemoryPath(root, "memory root disappeared");
24303
+ if (info.isSymbolicLink()) {
24304
+ throw unsafeMemoryPath(root, "memory root must not be a symbolic link");
24305
+ }
24306
+ if (!info.isDirectory()) {
24307
+ throw unsafeMemoryPath(root, "memory root is not a directory");
24308
+ }
24309
+ const current = realpathSync(root);
24310
+ if (relative(canonicalRoot, current) !== "" || relative(current, canonicalRoot) !== "") {
24311
+ throw unsafeMemoryPath(root, "memory root changed after initialization");
24312
+ }
24313
+ }
23969
24314
  function safeKey(root, key2) {
23970
- if (key2.includes("..") || key2.startsWith("/") || key2.includes("\0")) {
24315
+ const segments = key2.split(/[\\/]/);
24316
+ if (key2.length === 0 || key2.includes("\0") || isAbsolute(key2) || /^[A-Za-z]:[\\/]/.test(key2) || key2.startsWith("\\\\") || segments.includes("..")) {
23971
24317
  throw new Error(`unsafe memory key: ${key2}`);
23972
24318
  }
23973
- return join13(root, key2);
23974
- }
23975
- function walkMd(dir, prefix, out) {
23976
- if (!existsSync11(dir)) return;
23977
- for (const e of readdirSync6(dir, { withFileTypes: true })) {
23978
- if (e.name.startsWith(".")) continue;
23979
- const full = join13(dir, e.name);
23980
- const key2 = prefix ? `${prefix}/${e.name}` : e.name;
23981
- if (e.isDirectory()) {
23982
- walkMd(full, key2, out);
23983
- } else if (e.isFile() && e.name.endsWith(".md")) {
23984
- const stats = statSync4(full);
23985
- out.push({
23986
- key: key2,
23987
- content: readFileSync9(full, "utf8"),
23988
- updatedAt: stats.mtimeMs
23989
- });
24319
+ const candidate = resolve3(root, key2);
24320
+ if (candidate === root) throw new Error(`unsafe memory key: ${key2}`);
24321
+ assertContained(root, candidate);
24322
+ return candidate;
24323
+ }
24324
+ function inspectSafePath(root, path) {
24325
+ assertContained(root, path);
24326
+ const rel = relative(root, path);
24327
+ let current = root;
24328
+ const components = rel.split(sep2).filter(Boolean);
24329
+ for (let index = 0; index < components.length; index += 1) {
24330
+ current = join13(current, components[index]);
24331
+ const info = lstatIfPresent(current);
24332
+ if (!info) return void 0;
24333
+ if (info.isSymbolicLink()) {
24334
+ throw unsafeMemoryPath(current, "symbolic links are not allowed");
24335
+ }
24336
+ if (index < components.length - 1 && !info.isDirectory()) {
24337
+ throw unsafeMemoryPath(current, "path ancestor is not a directory");
24338
+ }
24339
+ assertContained(root, realpathSync(current));
24340
+ }
24341
+ return lstatIfPresent(path);
24342
+ }
24343
+ function ensureSafeParentDirectories(root, path) {
24344
+ const parent = dirname3(path);
24345
+ assertContained(root, parent);
24346
+ const rel = relative(root, parent);
24347
+ let current = root;
24348
+ for (const component of rel.split(sep2).filter(Boolean)) {
24349
+ current = join13(current, component);
24350
+ let info = lstatIfPresent(current);
24351
+ if (!info) {
24352
+ mkdirSync5(current);
24353
+ info = lstatSync(current);
24354
+ }
24355
+ if (info.isSymbolicLink()) {
24356
+ throw unsafeMemoryPath(current, "symbolic-link ancestors are not allowed");
24357
+ }
24358
+ if (!info.isDirectory()) {
24359
+ throw unsafeMemoryPath(current, "path ancestor is not a directory");
24360
+ }
24361
+ assertContained(root, realpathSync(current));
24362
+ }
24363
+ }
24364
+ function readSafeFile(root, path) {
24365
+ const info = inspectSafePath(root, path);
24366
+ if (!info) throw new Error(`memory entry not found: ${relative(root, path)}`);
24367
+ if (!info.isFile()) throw unsafeMemoryPath(path, "memory entry is not a regular file");
24368
+ let fd;
24369
+ try {
24370
+ fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
24371
+ const stats = fstatSync(fd);
24372
+ if (!stats.isFile()) throw unsafeMemoryPath(path, "memory entry is not a regular file");
24373
+ return { content: readFileSync9(fd, "utf8"), stats };
24374
+ } catch (error2) {
24375
+ if (error2.code === "ELOOP") {
24376
+ throw unsafeMemoryPath(path, "symbolic links are not allowed");
24377
+ }
24378
+ throw error2;
24379
+ } finally {
24380
+ if (fd !== void 0) closeSync(fd);
24381
+ }
24382
+ }
24383
+ function writeSafeFile(root, path, content) {
24384
+ ensureSafeParentDirectories(root, path);
24385
+ const existing = inspectSafePath(root, path);
24386
+ if (existing?.isSymbolicLink()) {
24387
+ throw unsafeMemoryPath(path, "symbolic links are not allowed");
24388
+ }
24389
+ if (existing && !existing.isFile()) {
24390
+ throw unsafeMemoryPath(path, "memory entry is not a regular file");
24391
+ }
24392
+ let fd;
24393
+ try {
24394
+ fd = openSync(
24395
+ path,
24396
+ constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW,
24397
+ 438
24398
+ );
24399
+ const stats = fstatSync(fd);
24400
+ if (!stats.isFile()) throw unsafeMemoryPath(path, "memory entry is not a regular file");
24401
+ writeFileSync7(fd, content);
24402
+ return fstatSync(fd);
24403
+ } catch (error2) {
24404
+ if (error2.code === "ELOOP") {
24405
+ throw unsafeMemoryPath(path, "symbolic links are not allowed");
24406
+ }
24407
+ throw error2;
24408
+ } finally {
24409
+ if (fd !== void 0) closeSync(fd);
24410
+ }
24411
+ }
24412
+ function assertSafeTree(root, dir = root) {
24413
+ assertContained(root, realpathSync(dir));
24414
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
24415
+ const full = join13(dir, entry.name);
24416
+ const info = lstatSync(full);
24417
+ if (info.isSymbolicLink()) {
24418
+ throw unsafeMemoryPath(full, "symbolic links are not allowed in memory storage");
24419
+ }
24420
+ if (info.isDirectory()) {
24421
+ assertSafeTree(root, full);
24422
+ } else if (!info.isFile()) {
24423
+ throw unsafeMemoryPath(full, "special files are not allowed in memory storage");
24424
+ }
24425
+ }
24426
+ }
24427
+ function walkMd(root, dir, prefix, out) {
24428
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
24429
+ const full = join13(dir, entry.name);
24430
+ const info = lstatSync(full);
24431
+ if (info.isSymbolicLink()) {
24432
+ throw unsafeMemoryPath(full, "symbolic links are not allowed in memory storage");
24433
+ }
24434
+ if (entry.name.startsWith(".")) continue;
24435
+ const key2 = prefix ? `${prefix}/${entry.name}` : entry.name;
24436
+ if (info.isDirectory()) {
24437
+ walkMd(root, full, key2, out);
24438
+ } else if (info.isFile() && entry.name.endsWith(".md")) {
24439
+ const { content, stats } = readSafeFile(root, full);
24440
+ out.push({ key: key2, content, updatedAt: stats.mtimeMs });
24441
+ } else if (!info.isFile()) {
24442
+ throw unsafeMemoryPath(full, "special files are not allowed in memory storage");
23990
24443
  }
23991
24444
  }
23992
24445
  }
23993
24446
  function qmdBackend(root) {
24447
+ let canonicalRoot;
24448
+ const safeRoot = () => {
24449
+ if (!canonicalRoot) canonicalRoot = initializeSafeRoot(root);
24450
+ else assertRootUnchanged(root, canonicalRoot);
24451
+ return canonicalRoot;
24452
+ };
23994
24453
  return {
23995
24454
  async init() {
23996
- mkdirSync5(root, { recursive: true });
23997
- const store = await getStore(root);
24455
+ const safe = safeRoot();
24456
+ assertSafeTree(safe);
24457
+ const store = await getStore(safe);
23998
24458
  await store.update();
23999
24459
  },
24000
24460
  async read(key2) {
24001
- const path = safeKey(root, key2);
24002
- if (!existsSync11(path)) {
24003
- throw new Error(`memory entry not found: ${key2}`);
24004
- }
24005
- const stats = statSync4(path);
24461
+ const safe = safeRoot();
24462
+ const path = safeKey(safe, key2);
24463
+ const { content, stats } = readSafeFile(safe, path);
24006
24464
  return {
24007
24465
  key: key2,
24008
- content: readFileSync9(path, "utf8"),
24466
+ content,
24009
24467
  updatedAt: stats.mtimeMs
24010
24468
  };
24011
24469
  },
24012
24470
  async write(key2, content) {
24013
- const path = safeKey(root, key2);
24014
- mkdirSync5(dirname3(path), { recursive: true });
24015
- writeFileSync7(path, content);
24016
- const stats = statSync4(path);
24017
- const store = await getStore(root);
24471
+ const safe = safeRoot();
24472
+ assertSafeTree(safe);
24473
+ const path = safeKey(safe, key2);
24474
+ const stats = writeSafeFile(safe, path, content);
24475
+ assertSafeTree(safe);
24476
+ const store = await getStore(safe);
24018
24477
  await store.update();
24019
24478
  return { key: key2, content, updatedAt: stats.mtimeMs };
24020
24479
  },
24021
24480
  async search(query, opts) {
24022
24481
  const limit = opts?.limit ?? 10;
24023
- const store = await getStore(root);
24482
+ const safe = safeRoot();
24483
+ assertSafeTree(safe);
24484
+ const store = await getStore(safe);
24024
24485
  const results = await store.searchLex(query, {
24025
24486
  limit,
24026
24487
  collection: COLLECTION_NAME
@@ -24029,13 +24490,13 @@ function qmdBackend(root) {
24029
24490
  for (const r of results) {
24030
24491
  const prefix = `${COLLECTION_NAME}/`;
24031
24492
  const key2 = r.displayPath.startsWith(prefix) ? r.displayPath.slice(prefix.length) : r.displayPath;
24493
+ const path = safeKey(safe, key2);
24494
+ const info = inspectSafePath(safe, path);
24495
+ if (!info) continue;
24496
+ if (!info.isFile()) throw unsafeMemoryPath(path, "search result is not a regular file");
24032
24497
  let content = r.body ?? "";
24033
24498
  if (!content) {
24034
- try {
24035
- content = readFileSync9(join13(root, key2), "utf8");
24036
- } catch {
24037
- content = "";
24038
- }
24499
+ content = readSafeFile(safe, path).content;
24039
24500
  }
24040
24501
  const snippet = extractSnippet(content, query).snippet;
24041
24502
  hits.push({ key: key2, snippet, score: r.score });
@@ -24043,14 +24504,23 @@ function qmdBackend(root) {
24043
24504
  return hits;
24044
24505
  },
24045
24506
  async list() {
24507
+ const safe = safeRoot();
24508
+ assertSafeTree(safe);
24046
24509
  const out = [];
24047
- walkMd(root, "", out);
24510
+ walkMd(safe, safe, "", out);
24048
24511
  return out.sort((a, b2) => a.key.localeCompare(b2.key));
24049
24512
  },
24050
24513
  async remove(key2) {
24051
- const path = safeKey(root, key2);
24052
- if (existsSync11(path)) rmSync10(path);
24053
- const store = await getStore(root);
24514
+ const safe = safeRoot();
24515
+ assertSafeTree(safe);
24516
+ const path = safeKey(safe, key2);
24517
+ const info = inspectSafePath(safe, path);
24518
+ if (info) {
24519
+ if (!info.isFile()) throw unsafeMemoryPath(path, "memory entry is not a regular file");
24520
+ rmSync10(path);
24521
+ }
24522
+ assertSafeTree(safe);
24523
+ const store = await getStore(safe);
24054
24524
  await store.update();
24055
24525
  }
24056
24526
  };
@@ -24139,8 +24609,8 @@ function piMessagesToProviderView(messages) {
24139
24609
  }
24140
24610
 
24141
24611
  // ../daemon/src/runtime/pi/session.ts
24142
- import { existsSync as existsSync14, mkdirSync as mkdirSync6, readdirSync as readdirSync8, statSync as statSync7 } from "fs";
24143
- import { basename as basename3, join as join17 } from "path";
24612
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, readdirSync as readdirSync9, statSync as statSync6 } from "fs";
24613
+ import { basename as basename3, join as join19 } from "path";
24144
24614
  import {
24145
24615
  AuthStorage,
24146
24616
  createAgentSession,
@@ -24426,14 +24896,14 @@ function isRetryableError(err) {
24426
24896
  return false;
24427
24897
  }
24428
24898
  function sleepWithAbort(ms, signal) {
24429
- return new Promise((resolve4, reject) => {
24899
+ return new Promise((resolve7, reject) => {
24430
24900
  if (signal?.aborted) {
24431
24901
  reject(new Error("aborted"));
24432
24902
  return;
24433
24903
  }
24434
24904
  const t = setTimeout(() => {
24435
24905
  if (signal) signal.removeEventListener("abort", onAbort);
24436
- resolve4();
24906
+ resolve7();
24437
24907
  }, ms);
24438
24908
  const onAbort = () => {
24439
24909
  clearTimeout(t);
@@ -24948,7 +25418,7 @@ var PROVIDERS = {
24948
25418
  function createProviderRegistry(config2, opts = {}) {
24949
25419
  const cache = /* @__PURE__ */ new Map();
24950
25420
  const enabledSet = opts.enabledSet;
24951
- function get14(name) {
25421
+ function get15(name) {
24952
25422
  const cached2 = cache.get(name);
24953
25423
  if (cached2) return cached2;
24954
25424
  const entry = PROVIDERS[name];
@@ -24980,7 +25450,7 @@ function createProviderRegistry(config2, opts = {}) {
24980
25450
  }
24981
25451
  const providerName = modelString.slice(0, idx);
24982
25452
  const model = modelString.slice(idx + 1);
24983
- return { provider: get14(providerName), model };
25453
+ return { provider: get15(providerName), model };
24984
25454
  },
24985
25455
  list() {
24986
25456
  return Object.entries(PROVIDERS).filter(([name, entry]) => {
@@ -25000,15 +25470,38 @@ function listAllProviders(config2) {
25000
25470
  }
25001
25471
 
25002
25472
  // ../daemon/src/runtime/session/prompt.ts
25003
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
25473
+ import { existsSync as existsSync11, readdirSync as readdirSync7, readFileSync as readFileSync10 } from "fs";
25004
25474
  import { join as join14 } from "path";
25475
+ var SANDBOX_SKILLS_DIR = "/skills";
25476
+ function loadPromptSkills(skillsDir, attachedNames) {
25477
+ if (!existsSync11(skillsDir) || attachedNames.length === 0) return [];
25478
+ const attached = new Set(attachedNames);
25479
+ const discovered = readdirSync7(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && attached.has(entry.name)).sort((a, b2) => a.name.localeCompare(b2.name));
25480
+ const loaded = [];
25481
+ for (const entry of discovered) {
25482
+ const hostDir = join14(skillsDir, entry.name);
25483
+ try {
25484
+ const parsed = parseSkillFile(join14(hostDir, "SKILL.md"));
25485
+ const safeLabel = entry.name.replace(/[^A-Za-z0-9._-]/g, "_") || "skill";
25486
+ loaded.push({
25487
+ name: entry.name,
25488
+ description: parsed.frontmatter.description,
25489
+ body: parsed.body.trim(),
25490
+ hostDir,
25491
+ sandboxDir: `${SANDBOX_SKILLS_DIR}/${loaded.length}-${safeLabel}`
25492
+ });
25493
+ } catch {
25494
+ }
25495
+ }
25496
+ return loaded;
25497
+ }
25005
25498
  var CONTEXT_FILE_ORDER = ["AGENTS.md", "SOUL.md", "TOOLS.md", "IDENTITY.md"];
25006
- function buildSystemPrompt(agent) {
25499
+ function buildSystemPrompt(agent, options = {}) {
25007
25500
  const parts = [];
25008
25501
  const contextBlocks = [];
25009
25502
  for (const file of CONTEXT_FILE_ORDER) {
25010
25503
  const path = join14(agent.agent.dir, file);
25011
- if (!existsSync12(path)) continue;
25504
+ if (!existsSync11(path)) continue;
25012
25505
  const content = readFileSync10(path, "utf8").trimEnd();
25013
25506
  if (!content) continue;
25014
25507
  contextBlocks.push(`## ${file}
@@ -25021,7 +25514,7 @@ ${content}`);
25021
25514
  ${contextBlocks.join("\n\n")}`);
25022
25515
  }
25023
25516
  const bootstrapPath = join14(agent.agent.dir, "BOOTSTRAP.md");
25024
- if (existsSync12(bootstrapPath)) {
25517
+ if (existsSync11(bootstrapPath)) {
25025
25518
  const bootstrap2 = readFileSync10(bootstrapPath, "utf8").trimEnd();
25026
25519
  if (bootstrap2) {
25027
25520
  parts.push(
@@ -25051,14 +25544,35 @@ ${contextBlocks.join("\n\n")}`);
25051
25544
  "- To change who you are (name, vibe, personality, how you behave): use `home_write`.",
25052
25545
  "- To inspect exact wording of your own files: use `home_read` or `home_list`.",
25053
25546
  "- To remember facts the user told you or things you learned: use `memory_write` \u2014 NOT `home_write`.",
25054
- "- To produce work output (code, docs, artefacts): use `write` / `edit` \u2014 those land in your workspace, not your home."
25547
+ "- To produce work output (code, docs, artefacts): use the workspace tools currently exposed to you \u2014 those operate on your team workspace, not your private home."
25055
25548
  ].join("\n")
25056
25549
  );
25057
25550
  if (agent.skills.length > 0) {
25551
+ const loaded = options.skills ?? [];
25552
+ const loadedNames = new Set(loaded.map((skill) => skill.name));
25553
+ const unavailable = agent.skills.filter((name) => !loadedNames.has(name));
25554
+ const skillBlocks = loaded.map((skill) => {
25555
+ const runtimeDir = options.sandboxMode === "docker" ? skill.sandboxDir : skill.hostDir;
25556
+ return [
25557
+ `## ${skill.name}`,
25558
+ "",
25559
+ skill.description,
25560
+ "",
25561
+ `Runtime directory: \`${runtimeDir}\`. Resolve any relative scripts or assets mentioned by this skill from that directory.`,
25562
+ "",
25563
+ skill.body
25564
+ ].filter((line, index, lines) => line !== "" || lines[index - 1] !== "").join("\n").trimEnd();
25565
+ });
25566
+ const unavailableNote = unavailable.length > 0 ? `Attached but unavailable or invalid: ${unavailable.join(", ")}.` : "";
25058
25567
  parts.push(
25059
- `# Available Skills
25060
-
25061
- You have access to the following skills: ${agent.skills.join(", ")}.`
25568
+ [
25569
+ "# Available Skills",
25570
+ "",
25571
+ "Use these attached skill instructions when they are relevant to the task.",
25572
+ "",
25573
+ ...skillBlocks,
25574
+ ...unavailableNote ? ["", unavailableNote] : []
25575
+ ].join("\n")
25062
25576
  );
25063
25577
  }
25064
25578
  const teamLines = [
@@ -25066,7 +25580,7 @@ You have access to the following skills: ${agent.skills.join(", ")}.`
25066
25580
  "",
25067
25581
  `- ${agent.team.id} (${agent.team.name}): ${agent.team.path}`,
25068
25582
  "",
25069
- "Your team is where work product lives \u2014 code, docs, artefacts, shared scratch. It may be shared with other agents in the same team. Your coding tools (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`) are rooted at the team directory. Never use these tools to edit your identity/soul/behaviour files \u2014 those live in your home and are reached via `home_write` / `home_read`."
25583
+ "Your team is where work product lives \u2014 code, docs, artefacts, shared scratch. It may be shared with other agents in the same team. Use the workspace tools currently exposed to work there. Never use workspace tools to edit your identity/soul/behaviour files \u2014 those live in your private home and are reached via `home_write` / `home_read`."
25070
25584
  ];
25071
25585
  parts.push(teamLines.join("\n"));
25072
25586
  if (agent.team.userMd.trim()) {
@@ -25090,12 +25604,1040 @@ This team's USER.md is empty. As you learn STABLE facts about the human (prefere
25090
25604
  return parts.join("\n\n---\n\n");
25091
25605
  }
25092
25606
 
25607
+ // ../daemon/src/runtime/shell/tooling.ts
25608
+ import { existsSync as existsSync12, lstatSync as lstatSync2, realpathSync as realpathSync2 } from "fs";
25609
+ import { dirname as dirname4, isAbsolute as isAbsolute3, join as join16, relative as relative3, sep as sep4 } from "path";
25610
+ import {
25611
+ createBashToolDefinition,
25612
+ createLocalBashOperations,
25613
+ defineTool as defineTool2
25614
+ } from "@earendil-works/pi-coding-agent";
25615
+
25616
+ // ../daemon/src/runtime/shell/approval.ts
25617
+ import { defineTool } from "@earendil-works/pi-coding-agent";
25618
+
25619
+ // ../daemon/src/runtime/shell/security.ts
25620
+ var DEFAULT_BASH_SANDBOX_IMAGE = "debian:bookworm-slim";
25621
+ var SAFE_SHELL_ENV_KEYS = [
25622
+ "PATH",
25623
+ "HOME",
25624
+ "LANG",
25625
+ "LANGUAGE",
25626
+ "LC_ALL",
25627
+ "LC_CTYPE",
25628
+ "TERM",
25629
+ "SHELL",
25630
+ "TMPDIR",
25631
+ "TZ"
25632
+ ];
25633
+ var ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
25634
+ var RESERVED_OBJECT_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
25635
+ var DOCKER_IMAGE_RE = /^[A-Za-z0-9][A-Za-z0-9._/:@-]*$/;
25636
+ var DOCKER_CLIENT_CONTROL_ENV = /* @__PURE__ */ new Set([
25637
+ "DOCKER_HOST",
25638
+ "DOCKER_CONTEXT",
25639
+ "DOCKER_CONFIG",
25640
+ "DOCKER_CERT_PATH",
25641
+ "DOCKER_TLS_VERIFY",
25642
+ "DOCKER_API_VERSION",
25643
+ "XDG_CONFIG_HOME",
25644
+ "XDG_RUNTIME_DIR"
25645
+ ]);
25646
+ var SANDBOX_CONTROL_ENV = /* @__PURE__ */ new Set(["BASH_ENV"]);
25647
+ var SENSITIVE_HOME_PATH_RE = /(?:~(?:[A-Za-z0-9_-]+)?|\$(?:HOME|\{HOME\})|\/(?:home|Users)\/[^\s'"`;|&/]+|\/root)\/\.(?:ssh|aws|gnupg)(?:\/[^\s'"`;|&]*)?/gi;
25648
+ var SENSITIVE_FILE_RE = /(?:\bauth\.json\b|\bbazilion\.db\b|(?:^|[\s/'"=])\.env(?:\.[A-Za-z0-9_-]+)?(?=$|[\s/'"`;|&])|\/etc\/(?:shadow|sudoers)(?:\.d\/[^\s'"`;|&]+)?)/gim;
25649
+ var SENSITIVE_ENV_RE = /\b(?:OPENAI_CODEX_OAUTH|AWS_ACCESS_KEY_ID|AWS_PROFILE|GOOGLE_APPLICATION_CREDENTIALS|BAZILION_TOKEN|(?:[A-Z][A-Z0-9_]*_)?(?:API_KEY|ACCESS_TOKEN|AUTH_TOKEN|OAUTH_TOKEN|CLIENT_SECRET|SECRET_ACCESS_KEY|PRIVATE_KEY))\b/g;
25650
+ var REMOTE_PIPE_RE = /(?:^|[;&|(\n])\s*(?:command\s+)?(?:curl|wget)\b[^;&\n]*(?:\|\s*(?:(?:sudo|env)\s+)*(?:[^\s;&|()]+\/)?(?:sh|bash|dash|zsh|fish|node|python(?:3)?|perl|ruby)\b)/gim;
25651
+ var COMMAND_BOUNDARY_RE = /[;&|)\n]/;
25652
+ var ShellSecurityConfigError = class extends Error {
25653
+ constructor(message) {
25654
+ super(message);
25655
+ this.name = "ShellSecurityConfigError";
25656
+ }
25657
+ };
25658
+ function isValidEnvName(name) {
25659
+ return ENV_NAME_RE.test(name) && !RESERVED_OBJECT_KEYS.has(name);
25660
+ }
25661
+ function addRisk(command, risks, seen, input) {
25662
+ const start = Math.max(0, input.start);
25663
+ const end = Math.min(command.length, Math.max(start, input.end));
25664
+ const key2 = `${input.code}:${start}:${end}`;
25665
+ if (seen.has(key2)) return;
25666
+ seen.add(key2);
25667
+ risks.push({
25668
+ code: input.code,
25669
+ severity: input.severity ?? "danger",
25670
+ message: input.message,
25671
+ matchedText: command.slice(start, end),
25672
+ span: { start, end }
25673
+ });
25674
+ }
25675
+ function addRegexRisks(command, risks, seen, pattern, code, message) {
25676
+ pattern.lastIndex = 0;
25677
+ for (const match of command.matchAll(pattern)) {
25678
+ if (match.index === void 0) continue;
25679
+ let start = match.index;
25680
+ let matchedText = match[0];
25681
+ const leading = matchedText.match(/^[;&|(\n\s]+/);
25682
+ if (leading) {
25683
+ start += leading[0].length;
25684
+ matchedText = matchedText.slice(leading[0].length);
25685
+ }
25686
+ addRisk(command, risks, seen, {
25687
+ code,
25688
+ message,
25689
+ start,
25690
+ end: start + matchedText.length
25691
+ });
25692
+ }
25693
+ }
25694
+ function shellTokens(fragment) {
25695
+ return Array.from(
25696
+ fragment.matchAll(/"(?:\\.|[^"\\])*"|'[^']*'|[^\s]+/g),
25697
+ (match) => match[0].replace(/^(?:"|')|(?:"|')$/g, "")
25698
+ );
25699
+ }
25700
+ function isBroadFilesystemTarget(rawTarget) {
25701
+ const target = rawTarget.replace(/[;,]+$/, "");
25702
+ if (target.startsWith("/")) return true;
25703
+ return /^(?:\.|\.\/\*+|\*+|~(?:\/.*)?|\$(?:HOME|\{HOME\})(?:\/.*)?|\.\.(?:\/.*)?)$/.test(target);
25704
+ }
25705
+ function classifyBroadFilesystemOperations(command, risks, seen) {
25706
+ const commandRe = /(?:^|[;&|(\n])\s*(?:command\s+)?(rm|find|chmod|chown)\b[^;&|)\n]*/gim;
25707
+ for (const match of command.matchAll(commandRe)) {
25708
+ if (match.index === void 0 || !match[1]) continue;
25709
+ const nameOffset = match[0].toLowerCase().indexOf(match[1].toLowerCase());
25710
+ const start = match.index + nameOffset;
25711
+ const text2 = command.slice(start, match.index + match[0].length).trimEnd();
25712
+ const tokens = shellTokens(text2);
25713
+ const name = tokens[0]?.toLowerCase();
25714
+ const args = tokens.slice(1);
25715
+ let risky = false;
25716
+ if (name === "rm") {
25717
+ const recursive = args.some(
25718
+ (arg) => arg === "--recursive" || /^-[^-]+$/.test(arg) && /[rR]/.test(arg)
25719
+ );
25720
+ risky = recursive && args.some((arg) => !arg.startsWith("-") && isBroadFilesystemTarget(arg));
25721
+ } else if (name === "find") {
25722
+ const target = args.find((arg) => !arg.startsWith("-"));
25723
+ risky = args.includes("-delete") && target !== void 0 && isBroadFilesystemTarget(target);
25724
+ } else if (name === "chmod" || name === "chown") {
25725
+ const recursive = args.some(
25726
+ (arg) => arg === "--recursive" || /^-[^-]+$/.test(arg) && /R/.test(arg)
25727
+ );
25728
+ risky = recursive && args.some((arg) => !arg.startsWith("-") && isBroadFilesystemTarget(arg));
25729
+ }
25730
+ if (!risky) continue;
25731
+ addRisk(command, risks, seen, {
25732
+ code: "broad-destructive-operation",
25733
+ message: "Recursively changes or deletes a broad filesystem target.",
25734
+ start,
25735
+ end: start + text2.length
25736
+ });
25737
+ }
25738
+ }
25739
+ function isLocalHostname(rawHostname) {
25740
+ const hostname = rawHostname.trim().replace(/^.*@/, "").replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
25741
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "::1" || hostname === "0.0.0.0" || /^127(?:\.\d{1,3}){3}$/.test(hostname);
25742
+ }
25743
+ function hasExternalHttpDestination(segment) {
25744
+ const urls = Array.from(segment.matchAll(/https?:\/\/[^\s'"`]+/gi), (match) => match[0]);
25745
+ if (urls.length === 0) return true;
25746
+ return urls.some((rawUrl) => {
25747
+ try {
25748
+ return !isLocalHostname(new URL(rawUrl).hostname);
25749
+ } catch {
25750
+ return true;
25751
+ }
25752
+ });
25753
+ }
25754
+ function remoteTransferHosts(segment) {
25755
+ const hosts = [];
25756
+ const uriRe = /(?:sftp|ssh|rsync):\/\/(?:[^@\s/]+@)?(\[[^\]]+\]|[^\s/:]+)(?::\d+)?/gi;
25757
+ for (const match of segment.matchAll(uriRe)) {
25758
+ if (match[1]) hosts.push(match[1]);
25759
+ }
25760
+ const scpRe = /(?:^|\s)(?:[^@\s:]+@)?(\[[^\]]+\]|[A-Za-z0-9._-]+):[^\s]+/g;
25761
+ for (const match of segment.matchAll(scpRe)) {
25762
+ if (match[1]) hosts.push(match[1]);
25763
+ }
25764
+ return hosts;
25765
+ }
25766
+ function invocationSegment(command, match, commandName) {
25767
+ if (match.index === void 0) return null;
25768
+ const nameOffset = match[0].toLowerCase().indexOf(commandName.toLowerCase());
25769
+ if (nameOffset < 0) return null;
25770
+ const start = match.index + nameOffset;
25771
+ const boundaryOffset = command.slice(start).search(COMMAND_BOUNDARY_RE);
25772
+ const end = boundaryOffset < 0 ? command.length : start + boundaryOffset;
25773
+ return { start, text: command.slice(start, end).trimEnd() };
25774
+ }
25775
+ function classifyOutboundExfiltration(command, risks, seen) {
25776
+ const invocationRe = /(?:^|[;&|(\n])\s*(?:command\s+)?(curl|wget|scp|rsync|nc|netcat|sftp)\b/gim;
25777
+ for (const match of command.matchAll(invocationRe)) {
25778
+ const name = match[1]?.toLowerCase();
25779
+ if (!name) continue;
25780
+ const segment = invocationSegment(command, match, name);
25781
+ if (!segment) continue;
25782
+ let risky = false;
25783
+ if (name === "curl") {
25784
+ const uploads = /(?:^|\s)(?:-X\s*POST\b|-[dFT](?:\s|[^\s]))/.test(segment.text) || /(?:^|\s)(?:--request(?:=|\s+)POST\b|--data(?:-[a-z-]+)?(?:=|\s)|--form(?:-string)?(?:=|\s)|--upload-file(?:=|\s))/i.test(
25785
+ segment.text
25786
+ );
25787
+ risky = uploads && hasExternalHttpDestination(segment.text);
25788
+ } else if (name === "wget") {
25789
+ const uploads = /(?:^|\s)(?:--method(?:=|\s+)POST\b|--post-data(?:=|\s)|--post-file(?:=|\s)|--body-data(?:=|\s)|--body-file(?:=|\s))/i.test(
25790
+ segment.text
25791
+ );
25792
+ risky = uploads && hasExternalHttpDestination(segment.text);
25793
+ } else if (name === "scp" || name === "rsync") {
25794
+ const hosts = remoteTransferHosts(segment.text);
25795
+ risky = hosts.some((host2) => !isLocalHostname(host2)) || hosts.length === 0 && /\$/.test(segment.text);
25796
+ } else if (name === "sftp") {
25797
+ const hosts = remoteTransferHosts(segment.text);
25798
+ if (hosts.length > 0) {
25799
+ risky = hosts.some((host2) => !isLocalHostname(host2));
25800
+ } else {
25801
+ const destination = shellTokens(segment.text).at(-1);
25802
+ risky = destination !== void 0 && destination !== name && !isLocalHostname(destination);
25803
+ }
25804
+ } else {
25805
+ const tokens = shellTokens(segment.text);
25806
+ const host2 = tokens.find(
25807
+ (token, index) => index > 0 && !token.startsWith("-") && !/^\d+$/.test(token) && !["tcp", "udp"].includes(token.toLowerCase())
25808
+ );
25809
+ risky = host2 !== void 0 && !isLocalHostname(host2);
25810
+ }
25811
+ if (!risky) continue;
25812
+ addRisk(command, risks, seen, {
25813
+ code: "outbound-exfiltration",
25814
+ message: "Uploads data or opens a transfer connection to a non-local destination.",
25815
+ start: segment.start,
25816
+ end: segment.start + segment.text.length
25817
+ });
25818
+ }
25819
+ }
25820
+ function classifyPrivilegeAndCredentialAccess(command, risks, seen) {
25821
+ const patterns = [
25822
+ /(?:^|[;&|(\n])\s*(?:sudo|su|doas|pkexec)\b[^;&|)\n]*/gim,
25823
+ /(?:^|[;&|(\n])\s*security\s+(?:find-(?:generic|internet)-password|dump-keychain)\b[^;&|)\n]*/gim,
25824
+ /(?:^|[;&|(\n])\s*gcloud\s+auth\b[^;&|)\n]*/gim,
25825
+ /(?:^|[;&|(\n])\s*aws\s+(?:configure|sso\s+login)\b[^;&|)\n]*/gim,
25826
+ /(?:^|[;&|(\n])\s*git\s+credential(?:-[A-Za-z0-9_-]+|\s+(?:fill|approve|reject))?\b[^;&|)\n]*/gim,
25827
+ /(?:^|[;&|(\n])\s*(?:docker|npm|gh)\s+(?:login|auth\s+token)\b[^;&|)\n]*/gim,
25828
+ /(?:^|[;&|(\n])\s*(?:pass\s+(?:show|ls)|secret-tool\s+lookup|ssh-add\s+-[Ll])\b[^;&|)\n]*/gim,
25829
+ /(?:^|[;&|(\n])\s*(?:env|printenv|set|export\s+-p)\s*(?=$|[;&|)\n])/gim
25830
+ ];
25831
+ for (const pattern of patterns) {
25832
+ addRegexRisks(
25833
+ command,
25834
+ risks,
25835
+ seen,
25836
+ pattern,
25837
+ "privilege-or-credential-access",
25838
+ "Requests elevated privileges or accesses a credential store."
25839
+ );
25840
+ }
25841
+ }
25842
+ function classifyBashCommand(command) {
25843
+ const risks = [];
25844
+ const seen = /* @__PURE__ */ new Set();
25845
+ addRegexRisks(
25846
+ command,
25847
+ risks,
25848
+ seen,
25849
+ SENSITIVE_HOME_PATH_RE,
25850
+ "sensitive-path-read",
25851
+ "Accesses a path that commonly contains credentials."
25852
+ );
25853
+ addRegexRisks(
25854
+ command,
25855
+ risks,
25856
+ seen,
25857
+ SENSITIVE_FILE_RE,
25858
+ "sensitive-path-read",
25859
+ "Accesses a sensitive environment, authentication, or database file."
25860
+ );
25861
+ addRegexRisks(
25862
+ command,
25863
+ risks,
25864
+ seen,
25865
+ SENSITIVE_ENV_RE,
25866
+ "sensitive-path-read",
25867
+ "References an environment variable name commonly used for credentials."
25868
+ );
25869
+ classifyBroadFilesystemOperations(command, risks, seen);
25870
+ addRegexRisks(
25871
+ command,
25872
+ risks,
25873
+ seen,
25874
+ REMOTE_PIPE_RE,
25875
+ "remote-pipe-execution",
25876
+ "Pipes downloaded content directly into an interpreter."
25877
+ );
25878
+ classifyOutboundExfiltration(command, risks, seen);
25879
+ classifyPrivilegeAndCredentialAccess(command, risks, seen);
25880
+ return risks.sort((a, b2) => a.span.start - b2.span.start || a.code.localeCompare(b2.code));
25881
+ }
25882
+ function parseEnvAllowlist(raw2) {
25883
+ if (raw2 === void 0 || raw2.trim() === "") return [];
25884
+ const values = raw2.split(",").map((value) => value.trim());
25885
+ if (values.some((value) => value === "")) {
25886
+ throw new ShellSecurityConfigError(
25887
+ "BAZILION_BASH_SANDBOX_ENV_ALLOWLIST contains an empty environment variable name"
25888
+ );
25889
+ }
25890
+ const unique = [];
25891
+ const seen = /* @__PURE__ */ new Set();
25892
+ for (const value of values) {
25893
+ if (!isValidEnvName(value)) {
25894
+ throw new ShellSecurityConfigError(
25895
+ `BAZILION_BASH_SANDBOX_ENV_ALLOWLIST contains invalid environment variable name: ${value}`
25896
+ );
25897
+ }
25898
+ if (DOCKER_CLIENT_CONTROL_ENV.has(value)) {
25899
+ throw new ShellSecurityConfigError(
25900
+ `BAZILION_BASH_SANDBOX_ENV_ALLOWLIST cannot include Docker client control variable: ${value}`
25901
+ );
25902
+ }
25903
+ if (SANDBOX_CONTROL_ENV.has(value)) {
25904
+ throw new ShellSecurityConfigError(
25905
+ `BAZILION_BASH_SANDBOX_ENV_ALLOWLIST cannot include shell startup control variable: ${value}`
25906
+ );
25907
+ }
25908
+ if (!seen.has(value)) {
25909
+ seen.add(value);
25910
+ unique.push(value);
25911
+ }
25912
+ }
25913
+ return unique;
25914
+ }
25915
+ function resolveShellSecurityConfig(env = process.env) {
25916
+ const rawMode = env.BAZILION_BASH_SANDBOX?.trim() ?? "";
25917
+ if (rawMode !== "" && rawMode !== "off" && rawMode !== "docker") {
25918
+ throw new ShellSecurityConfigError(
25919
+ `BAZILION_BASH_SANDBOX must be "off" or "docker", received: ${rawMode}`
25920
+ );
25921
+ }
25922
+ const rawApproval = env.BAZILION_BASH_APPROVAL?.trim() ?? "";
25923
+ if (rawApproval !== "" && rawApproval !== "off" && rawApproval !== "dangerous") {
25924
+ throw new ShellSecurityConfigError(
25925
+ `BAZILION_BASH_APPROVAL must be "off" or "dangerous", received: ${rawApproval}`
25926
+ );
25927
+ }
25928
+ const rawImage = env.BAZILION_BASH_SANDBOX_IMAGE?.trim() ?? "";
25929
+ if (rawImage !== "" && !DOCKER_IMAGE_RE.test(rawImage)) {
25930
+ throw new ShellSecurityConfigError(
25931
+ `BAZILION_BASH_SANDBOX_IMAGE is not a valid Docker image reference: ${rawImage}`
25932
+ );
25933
+ }
25934
+ return {
25935
+ sandboxMode: rawMode === "docker" ? "docker" : "off",
25936
+ approvalMode: rawApproval === "dangerous" ? "dangerous" : "off",
25937
+ sandboxImage: rawImage || DEFAULT_BASH_SANDBOX_IMAGE,
25938
+ envAllowlist: parseEnvAllowlist(env.BAZILION_BASH_SANDBOX_ENV_ALLOWLIST)
25939
+ };
25940
+ }
25941
+ function assertEnvName(name) {
25942
+ if (!isValidEnvName(name)) {
25943
+ throw new ShellSecurityConfigError(`invalid shell environment variable name: ${name}`);
25944
+ }
25945
+ if (DOCKER_CLIENT_CONTROL_ENV.has(name) || SANDBOX_CONTROL_ENV.has(name)) {
25946
+ throw new ShellSecurityConfigError(`unsafe shell environment variable name: ${name}`);
25947
+ }
25948
+ }
25949
+ function buildScrubbedShellEnv(env, allowedKeys = []) {
25950
+ const scrubbed = {};
25951
+ const keys = new Set(SAFE_SHELL_ENV_KEYS);
25952
+ for (const key2 of allowedKeys) {
25953
+ assertEnvName(key2);
25954
+ keys.add(key2);
25955
+ }
25956
+ for (const key2 of keys) {
25957
+ const value = env[key2];
25958
+ if (value !== void 0) scrubbed[key2] = value;
25959
+ }
25960
+ return scrubbed;
25961
+ }
25962
+
25963
+ // ../daemon/src/runtime/shell/approval.ts
25964
+ var BashApprovalDeniedError = class extends Error {
25965
+ risks;
25966
+ constructor(risks) {
25967
+ const codes = [...new Set(risks.map((risk) => risk.code))];
25968
+ super(`Dangerous bash command denied (${codes.join(", ")})`);
25969
+ this.name = "BashApprovalDeniedError";
25970
+ this.risks = risks;
25971
+ }
25972
+ };
25973
+ function createApprovalGatedBashTool(base, host2) {
25974
+ return defineTool({
25975
+ ...base,
25976
+ description: `${base.description} Commands classified as dangerous require operator approval before execution.`,
25977
+ executionMode: "sequential",
25978
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
25979
+ const risks = classifyBashCommand(params.command);
25980
+ if (risks.length > 0) {
25981
+ const decision = host2 ? await host2.requestApproval({
25982
+ toolCallId,
25983
+ command: params.command,
25984
+ risks,
25985
+ signal
25986
+ }) : "denied";
25987
+ if (decision !== "approved") throw new BashApprovalDeniedError(risks);
25988
+ }
25989
+ return base.execute(toolCallId, params, signal, onUpdate, ctx);
25990
+ }
25991
+ });
25992
+ }
25993
+
25994
+ // ../daemon/src/runtime/shell/docker.ts
25995
+ import { spawn } from "child_process";
25996
+ import { randomUUID as randomUUID15 } from "crypto";
25997
+ import { closeSync as closeSync2, constants as constants2, mkdtempSync as mkdtempSync2, openSync as openSync2, rmSync as rmSync11, writeFileSync as writeFileSync8 } from "fs";
25998
+ import { access, realpath, stat } from "fs/promises";
25999
+ import { tmpdir as tmpdir2 } from "os";
26000
+ import { isAbsolute as isAbsolute2, join as join15, relative as relative2, sep as sep3 } from "path";
26001
+ var CONTAINER_WORKDIR = "/workspace";
26002
+ var MAX_TIMEOUT_MS = 2147483647;
26003
+ var MAX_STDERR_CHARS = 16 * 1024;
26004
+ var CLEANUP_TIMEOUT_MS = 650;
26005
+ var CLEANUP_RETRY_DELAY_MS = 100;
26006
+ var CLEANUP_ATTEMPTS = 3;
26007
+ var DOCKER_CONTEXT_TIMEOUT_MS = 2e3;
26008
+ var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
26009
+ var CONTAINER_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]+$/;
26010
+ var DOCKER_CLIENT_ENV_KEYS = [
26011
+ "PATH",
26012
+ "HOME",
26013
+ "DOCKER_HOST",
26014
+ "DOCKER_CONTEXT",
26015
+ "DOCKER_CONFIG",
26016
+ "DOCKER_CERT_PATH",
26017
+ "DOCKER_TLS_VERIFY",
26018
+ "DOCKER_API_VERSION",
26019
+ "XDG_CONFIG_HOME",
26020
+ "XDG_RUNTIME_DIR"
26021
+ ];
26022
+ var DOCKER_CLIENT_CONTROL_ENV2 = new Set(DOCKER_CLIENT_ENV_KEYS);
26023
+ var SANDBOX_CONTROL_ENV2 = /* @__PURE__ */ new Set(["BASH_ENV"]);
26024
+ var CONTAINER_LITERAL_ENV_KEYS = /* @__PURE__ */ new Set([
26025
+ "PATH",
26026
+ "HOME",
26027
+ "LANG",
26028
+ "LANGUAGE",
26029
+ "LC_ALL",
26030
+ "LC_CTYPE",
26031
+ "TERM",
26032
+ "SHELL",
26033
+ "TMPDIR",
26034
+ "TZ"
26035
+ ]);
26036
+ function buildDockerRunSpec(input) {
26037
+ const executable = input.dockerPath ?? "docker";
26038
+ validateDockerPath(executable);
26039
+ validateImage(input.image);
26040
+ if (input.resolvedImage !== void 0) validateImage(input.resolvedImage);
26041
+ validateContainerName(input.containerName);
26042
+ validateMountArgument(input.mountSource);
26043
+ const readOnlyMounts = input.readOnlyMounts ?? [];
26044
+ validateReadOnlyMounts(readOnlyMounts, input.mountSource);
26045
+ validateHostIdentity(input.uid, input.gid);
26046
+ if (input.command.includes("\0")) {
26047
+ throw new Error("Docker sandbox command cannot contain a NUL byte");
26048
+ }
26049
+ const args = [
26050
+ "run",
26051
+ "--rm",
26052
+ "--pull",
26053
+ "never",
26054
+ "--name",
26055
+ input.containerName,
26056
+ "--network",
26057
+ "none",
26058
+ "--read-only",
26059
+ "--tmpfs",
26060
+ "/tmp:rw,nosuid,nodev,size=64m,mode=1777",
26061
+ "--user",
26062
+ `${input.uid}:${input.gid}`,
26063
+ "--cap-drop",
26064
+ "ALL",
26065
+ "--security-opt",
26066
+ "no-new-privileges=true",
26067
+ "--pids-limit",
26068
+ "256",
26069
+ "--entrypoint",
26070
+ "/bin/bash",
26071
+ "--workdir",
26072
+ CONTAINER_WORKDIR,
26073
+ // A bind mount is read/write unless `readonly` is present. No other mount is added.
26074
+ "--mount",
26075
+ `type=bind,source=${input.mountSource},target=${CONTAINER_WORKDIR},bind-recursive=disabled`
26076
+ ];
26077
+ for (const mount of readOnlyMounts) {
26078
+ args.push(
26079
+ "--mount",
26080
+ `type=bind,source=${mount.source},target=${mount.target},readonly,bind-recursive=disabled`
26081
+ );
26082
+ }
26083
+ const processEnv = buildDockerClientEnv(input.hostEnv ?? process.env);
26084
+ const envFileLines = [];
26085
+ const containerEnvNames = [];
26086
+ for (const [name, value] of sortedEnvironment(input.env)) {
26087
+ containerEnvNames.push(name);
26088
+ if (CONTAINER_LITERAL_ENV_KEYS.has(name)) {
26089
+ args.push("--env", `${name}=${value}`);
26090
+ } else {
26091
+ envFileLines.push(`${name}=${value}`);
26092
+ }
26093
+ }
26094
+ if (envFileLines.length > 0) args.push("--env-file", "/dev/fd/3");
26095
+ args.push("--env", "BASH_ENV=");
26096
+ const cleanAssignments = containerEnvNames.map((name) => `${name}="$${name}"`).join(" ");
26097
+ const cleanEnvironmentCommand = `exec /usr/bin/env -i${cleanAssignments ? ` ${cleanAssignments}` : ""} /bin/bash --noprofile --norc -c "$1"`;
26098
+ args.push(
26099
+ input.resolvedImage ?? input.image,
26100
+ "--noprofile",
26101
+ "--norc",
26102
+ "-c",
26103
+ cleanEnvironmentCommand,
26104
+ "bazilion-sandbox",
26105
+ input.command
26106
+ );
26107
+ return {
26108
+ executable,
26109
+ args,
26110
+ containerName: input.containerName,
26111
+ image: input.image,
26112
+ mountSource: input.mountSource,
26113
+ processEnv,
26114
+ ...envFileLines.length > 0 ? { envFileContent: `${envFileLines.join("\n")}
26115
+ ` } : {}
26116
+ };
26117
+ }
26118
+ function createDockerBashOperations(options) {
26119
+ const image = options.image;
26120
+ const env = { ...options.env };
26121
+ const dockerPath = options.dockerPath ?? "docker";
26122
+ const configuredReadOnlyMounts = options.readOnlyMounts ?? [];
26123
+ const hostEnv = { ...options.hostEnv ?? process.env };
26124
+ validateDockerPath(dockerPath);
26125
+ validateImage(image);
26126
+ return {
26127
+ async exec(command, cwd, { onData, signal, timeout }) {
26128
+ const timeoutMs = resolveTimeoutMs(timeout);
26129
+ if (signal?.aborted) throw new Error("aborted");
26130
+ const mountSource = await resolveMountSource(cwd);
26131
+ const readOnlyMounts = await Promise.all(
26132
+ configuredReadOnlyMounts.map(async (mount) => {
26133
+ const source = await resolveMountSource(mount.source);
26134
+ if (mount.sourceRoot && !isWithin(mount.sourceRoot, source)) {
26135
+ throw new Error(
26136
+ `Docker sandbox read-only mount escaped its configured root: ${mount.source}`
26137
+ );
26138
+ }
26139
+ return { source, target: mount.target };
26140
+ })
26141
+ );
26142
+ if (signal?.aborted) throw new Error("aborted");
26143
+ const { uid, gid } = hostIdentity();
26144
+ const dockerClientEnv = await resolveLocalDockerClientEnv(dockerPath, hostEnv);
26145
+ const resolvedImage = await inspectSandboxImage(dockerPath, image, dockerClientEnv);
26146
+ if (signal?.aborted) throw new Error("aborted");
26147
+ const containerName = `bazilion-bash-${process.pid}-${randomUUID15().replaceAll("-", "")}`;
26148
+ const spec = buildDockerRunSpec({
26149
+ image,
26150
+ env,
26151
+ dockerPath,
26152
+ command,
26153
+ mountSource,
26154
+ containerName,
26155
+ uid,
26156
+ gid,
26157
+ resolvedImage,
26158
+ readOnlyMounts,
26159
+ hostEnv: dockerClientEnv
26160
+ });
26161
+ return executeDockerRun(spec, { onData, signal, timeout, timeoutMs });
26162
+ }
26163
+ };
26164
+ }
26165
+ function executeDockerRun(spec, options) {
26166
+ let envFileFd;
26167
+ try {
26168
+ if (spec.envFileContent !== void 0) {
26169
+ envFileFd = openAnonymousEnvFile(spec.envFileContent);
26170
+ }
26171
+ } catch (error2) {
26172
+ return Promise.reject(
26173
+ new Error(
26174
+ `Docker sandbox could not prepare its environment: ${error2 instanceof Error ? error2.message : String(error2)}`
26175
+ )
26176
+ );
26177
+ }
26178
+ return new Promise((resolve7, reject) => {
26179
+ const child = spawn(spec.executable, spec.args, {
26180
+ stdio: ["ignore", "pipe", "pipe", envFileFd ?? "ignore"],
26181
+ windowsHide: true,
26182
+ env: spec.processEnv
26183
+ });
26184
+ if (envFileFd !== void 0) closeSync2(envFileFd);
26185
+ let settled = false;
26186
+ let termination;
26187
+ let timeoutHandle;
26188
+ let stderr = "";
26189
+ const requestTermination = (reason) => {
26190
+ termination ??= reason;
26191
+ child.kill("SIGKILL");
26192
+ };
26193
+ const onAbort = () => requestTermination("aborted");
26194
+ const finish = async (exitCode, spawnError) => {
26195
+ if (settled) return;
26196
+ settled = true;
26197
+ if (timeoutHandle) clearTimeout(timeoutHandle);
26198
+ options.signal?.removeEventListener("abort", onAbort);
26199
+ if (!termination && options.signal?.aborted) termination = "aborted";
26200
+ if (termination) {
26201
+ await removeContainer(spec.executable, spec.containerName, spec.processEnv);
26202
+ reject(
26203
+ termination === "aborted" ? new Error("aborted") : new Error(`timeout:${options.timeout}`)
26204
+ );
26205
+ return;
26206
+ }
26207
+ if (spawnError) {
26208
+ reject(formatSpawnError(spec.executable, spawnError));
26209
+ return;
26210
+ }
26211
+ if (exitCode === 125) {
26212
+ await removeContainer(spec.executable, spec.containerName, spec.processEnv);
26213
+ reject(formatDockerRunError(spec, stderr));
26214
+ return;
26215
+ }
26216
+ if (exitCode === null) {
26217
+ await removeContainer(spec.executable, spec.containerName, spec.processEnv);
26218
+ reject(new Error(`Docker sandbox CLI for container "${spec.containerName}" was killed`));
26219
+ return;
26220
+ }
26221
+ resolve7({ exitCode });
26222
+ };
26223
+ child.stdout?.on("data", options.onData);
26224
+ child.stderr?.on("data", (data) => {
26225
+ stderr = `${stderr}${data.toString("utf8")}`.slice(-MAX_STDERR_CHARS);
26226
+ options.onData(data);
26227
+ });
26228
+ child.once("error", (error2) => void finish(null, error2));
26229
+ child.once("close", (exitCode) => void finish(exitCode));
26230
+ if (options.timeoutMs !== void 0) {
26231
+ timeoutHandle = setTimeout(() => requestTermination("timeout"), options.timeoutMs);
26232
+ }
26233
+ if (options.signal) {
26234
+ options.signal.addEventListener("abort", onAbort, { once: true });
26235
+ if (options.signal.aborted) onAbort();
26236
+ }
26237
+ });
26238
+ }
26239
+ function openAnonymousEnvFile(content) {
26240
+ const dir = mkdtempSync2(join15(tmpdir2(), "bazilion-docker-env-"));
26241
+ const path = join15(dir, "environment");
26242
+ let fd;
26243
+ try {
26244
+ writeFileSync8(path, content, { encoding: "utf8", flag: "wx", mode: 384 });
26245
+ fd = openSync2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
26246
+ rmSync11(dir, { recursive: true, force: true });
26247
+ return fd;
26248
+ } catch (error2) {
26249
+ if (fd !== void 0) closeSync2(fd);
26250
+ rmSync11(dir, { recursive: true, force: true });
26251
+ throw error2;
26252
+ }
26253
+ }
26254
+ function resolveTimeoutMs(timeout) {
26255
+ if (timeout === void 0) return void 0;
26256
+ if (!Number.isFinite(timeout) || timeout <= 0) {
26257
+ throw new Error("Invalid timeout: must be a finite number of seconds");
26258
+ }
26259
+ const timeoutMs = timeout * 1e3;
26260
+ if (timeoutMs > MAX_TIMEOUT_MS) {
26261
+ throw new Error(`Invalid timeout: maximum is ${MAX_TIMEOUT_MS / 1e3} seconds`);
26262
+ }
26263
+ return timeoutMs;
26264
+ }
26265
+ async function resolveMountSource(cwd) {
26266
+ try {
26267
+ await access(cwd, constants2.F_OK);
26268
+ const info = await stat(cwd);
26269
+ if (!info.isDirectory()) {
26270
+ throw new Error(`Docker sandbox mount is not a directory: ${cwd}`);
26271
+ }
26272
+ return await realpath(cwd);
26273
+ } catch (error2) {
26274
+ if (error2 instanceof Error && error2.message.startsWith("Docker sandbox mount is not")) {
26275
+ throw error2;
26276
+ }
26277
+ const code = error2.code;
26278
+ if (code === "ENOENT") {
26279
+ throw new Error(`Docker sandbox mount does not exist: ${cwd}`);
26280
+ }
26281
+ throw new Error(
26282
+ `Docker sandbox cannot access mount "${cwd}": ${error2 instanceof Error ? error2.message : String(error2)}`
26283
+ );
26284
+ }
26285
+ }
26286
+ function hostIdentity() {
26287
+ if (typeof process.getuid !== "function" || typeof process.getgid !== "function") {
26288
+ throw new Error("Docker sandbox requires a host platform with numeric uid/gid support");
26289
+ }
26290
+ return { uid: process.getuid(), gid: process.getgid() };
26291
+ }
26292
+ function sortedEnvironment(env) {
26293
+ const entries = [];
26294
+ for (const [name, value] of Object.entries(env)) {
26295
+ if (value === void 0) continue;
26296
+ if (!ENV_NAME.test(name)) {
26297
+ throw new Error(
26298
+ `Docker sandbox environment variable name is invalid: ${JSON.stringify(name)}`
26299
+ );
26300
+ }
26301
+ if (DOCKER_CLIENT_CONTROL_ENV2.has(name) && !CONTAINER_LITERAL_ENV_KEYS.has(name)) {
26302
+ throw new Error(`Docker sandbox cannot pass Docker client control variable: ${name}`);
26303
+ }
26304
+ if (SANDBOX_CONTROL_ENV2.has(name)) {
26305
+ throw new Error(`Docker sandbox cannot pass shell startup control variable: ${name}`);
26306
+ }
26307
+ if (value.includes("\0")) {
26308
+ throw new Error(`Docker sandbox environment variable ${name} contains a NUL byte`);
26309
+ }
26310
+ if (value.includes("\n") || value.includes("\r")) {
26311
+ throw new Error(`Docker sandbox environment variable ${name} cannot contain a line break`);
26312
+ }
26313
+ entries.push([name, value]);
26314
+ }
26315
+ return entries.sort(([a], [b2]) => a < b2 ? -1 : a > b2 ? 1 : 0);
26316
+ }
26317
+ async function resolveLocalDockerClientEnv(dockerPath, hostEnv) {
26318
+ const discoveryEnv = buildDockerClientEnv(hostEnv);
26319
+ const endpoint2 = await inspectDockerEndpoint(dockerPath, discoveryEnv);
26320
+ const prefix = "unix://";
26321
+ if (!endpoint2.startsWith(prefix) || !isAbsolute2(endpoint2.slice(prefix.length))) {
26322
+ throw new Error(
26323
+ `Docker sandbox requires a local Unix-socket context; active endpoint is ${JSON.stringify(endpoint2)}`
26324
+ );
26325
+ }
26326
+ const pinned = { DOCKER_HOST: endpoint2 };
26327
+ if (hostEnv.PATH !== void 0) pinned.PATH = hostEnv.PATH;
26328
+ if (hostEnv.HOME !== void 0) pinned.HOME = hostEnv.HOME;
26329
+ return pinned;
26330
+ }
26331
+ async function inspectDockerEndpoint(dockerPath, processEnv) {
26332
+ const raw2 = await runDockerInspection(
26333
+ dockerPath,
26334
+ ["context", "inspect", "--format", "{{json .Endpoints.docker.Host}}"],
26335
+ processEnv,
26336
+ "the active context"
26337
+ );
26338
+ try {
26339
+ const parsed = JSON.parse(raw2);
26340
+ if (typeof parsed !== "string" || parsed.length === 0) throw new Error("empty endpoint");
26341
+ return parsed;
26342
+ } catch {
26343
+ throw new Error(
26344
+ `Docker sandbox could not resolve the active Docker endpoint: ${raw2 || "empty context response"}`
26345
+ );
26346
+ }
26347
+ }
26348
+ async function inspectSandboxImage(dockerPath, image, processEnv) {
26349
+ let raw2;
26350
+ try {
26351
+ raw2 = await runDockerInspection(
26352
+ dockerPath,
26353
+ ["image", "inspect", "--format", '{{json .Id}} {{json (index .Config "Volumes")}}', image],
26354
+ processEnv,
26355
+ `local image ${JSON.stringify(image)}`
26356
+ );
26357
+ } catch (error2) {
26358
+ const detail3 = error2 instanceof Error ? error2.message : String(error2);
26359
+ if (/no such image|unable to find image|manifest unknown/i.test(detail3)) {
26360
+ throw new Error(`Docker sandbox image "${image}" is unavailable: ${detail3}`);
26361
+ }
26362
+ throw error2;
26363
+ }
26364
+ const separator = raw2.indexOf(" ");
26365
+ if (separator < 0) {
26366
+ throw new Error(`Docker sandbox received an invalid inspection result for image "${image}"`);
26367
+ }
26368
+ let id;
26369
+ let volumes;
26370
+ try {
26371
+ id = JSON.parse(raw2.slice(0, separator));
26372
+ volumes = JSON.parse(raw2.slice(separator + 1));
26373
+ } catch {
26374
+ throw new Error(`Docker sandbox received an invalid inspection result for image "${image}"`);
26375
+ }
26376
+ if (typeof id !== "string" || !id.startsWith("sha256:")) {
26377
+ throw new Error(`Docker sandbox received an invalid image id for "${image}"`);
26378
+ }
26379
+ if (volumes !== null && typeof volumes === "object" && Object.keys(volumes).length > 0) {
26380
+ throw new Error(
26381
+ `Docker sandbox image "${image}" declares writable volumes (${Object.keys(volumes).sort().join(", ")}); use an image without VOLUME declarations`
26382
+ );
26383
+ }
26384
+ if (volumes !== null && (typeof volumes !== "object" || Array.isArray(volumes))) {
26385
+ throw new Error(`Docker sandbox received invalid volume metadata for image "${image}"`);
26386
+ }
26387
+ return id;
26388
+ }
26389
+ function runDockerInspection(dockerPath, args, processEnv, subject) {
26390
+ return new Promise((resolve7, reject) => {
26391
+ const child = spawn(dockerPath, args, {
26392
+ stdio: ["ignore", "pipe", "pipe"],
26393
+ windowsHide: true,
26394
+ env: processEnv
26395
+ });
26396
+ let settled = false;
26397
+ let stdout = "";
26398
+ let stderr = "";
26399
+ const finish = (error2) => {
26400
+ if (settled) return;
26401
+ settled = true;
26402
+ clearTimeout(timeoutHandle);
26403
+ if (error2) {
26404
+ reject(error2);
26405
+ return;
26406
+ }
26407
+ resolve7(stdout.trim());
26408
+ };
26409
+ const timeoutHandle = setTimeout(() => {
26410
+ child.kill("SIGKILL");
26411
+ finish(
26412
+ new Error(
26413
+ `Docker sandbox could not inspect ${subject} within ${DOCKER_CONTEXT_TIMEOUT_MS}ms`
26414
+ )
26415
+ );
26416
+ }, DOCKER_CONTEXT_TIMEOUT_MS);
26417
+ child.stdout.on("data", (data) => {
26418
+ stdout = `${stdout}${data.toString("utf8")}`.slice(-4096);
26419
+ });
26420
+ child.stderr.on("data", (data) => {
26421
+ stderr = `${stderr}${data.toString("utf8")}`.slice(-4096);
26422
+ });
26423
+ child.once("error", (error2) => finish(formatSpawnError(dockerPath, error2)));
26424
+ child.once("close", (code) => {
26425
+ if (code === 0) finish();
26426
+ else
26427
+ finish(
26428
+ new Error(
26429
+ `Docker sandbox could not inspect ${subject}: ${stderr.trim() || `Docker exited with status ${code}`}`
26430
+ )
26431
+ );
26432
+ });
26433
+ });
26434
+ }
26435
+ function buildDockerClientEnv(hostEnv) {
26436
+ const env = {};
26437
+ for (const key2 of DOCKER_CLIENT_ENV_KEYS) {
26438
+ const value = hostEnv[key2];
26439
+ if (value !== void 0) env[key2] = value;
26440
+ }
26441
+ return env;
26442
+ }
26443
+ function validateDockerPath(dockerPath) {
26444
+ if (!dockerPath.trim()) throw new Error("Docker sandbox executable path is required");
26445
+ if (dockerPath.includes("\0"))
26446
+ throw new Error("Docker sandbox executable path contains a NUL byte");
26447
+ }
26448
+ function validateImage(image) {
26449
+ if (!image.trim()) throw new Error("Docker sandbox image is required");
26450
+ if (image.startsWith("-")) {
26451
+ throw new Error(`Docker sandbox image cannot start with "-": ${JSON.stringify(image)}`);
26452
+ }
26453
+ if (image.includes("\0")) throw new Error("Docker sandbox image contains a NUL byte");
26454
+ }
26455
+ function validateContainerName(containerName) {
26456
+ if (!CONTAINER_NAME.test(containerName)) {
26457
+ throw new Error(`Docker sandbox container name is invalid: ${JSON.stringify(containerName)}`);
26458
+ }
26459
+ }
26460
+ function validateMountArgument(mountSource) {
26461
+ if (!mountSource) throw new Error("Docker sandbox mount path is required");
26462
+ if (mountSource.includes("\0") || mountSource.includes(",")) {
26463
+ throw new Error(
26464
+ `Docker sandbox mount path cannot be represented safely: ${JSON.stringify(mountSource)}`
26465
+ );
26466
+ }
26467
+ }
26468
+ function validateReadOnlyMounts(mounts, workspaceSource) {
26469
+ const targets = /* @__PURE__ */ new Set([CONTAINER_WORKDIR, "/tmp"]);
26470
+ for (const mount of mounts) {
26471
+ validateMountArgument(mount.source);
26472
+ if (mount.sourceRoot !== void 0 && (!isAbsolute2(mount.sourceRoot) || mount.sourceRoot.includes("\0") || mount.sourceRoot.includes(","))) {
26473
+ throw new Error(
26474
+ `Docker sandbox read-only mount root is invalid: ${JSON.stringify(mount.sourceRoot)}`
26475
+ );
26476
+ }
26477
+ if (!mount.target.startsWith("/") || mount.target.includes("\0") || mount.target.includes(",") || mount.target.split("/").includes("..")) {
26478
+ throw new Error(
26479
+ `Docker sandbox read-only mount target is invalid: ${JSON.stringify(mount.target)}`
26480
+ );
26481
+ }
26482
+ if (mount.source === workspaceSource) {
26483
+ throw new Error("Docker sandbox cannot mount the writable workspace again as an input");
26484
+ }
26485
+ if (targets.has(mount.target)) {
26486
+ throw new Error(`Docker sandbox mount target is duplicated or reserved: ${mount.target}`);
26487
+ }
26488
+ targets.add(mount.target);
26489
+ }
26490
+ }
26491
+ function isWithin(root, candidate) {
26492
+ const rel = relative2(root, candidate);
26493
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute2(rel);
26494
+ }
26495
+ function validateHostIdentity(uid, gid) {
26496
+ if (!Number.isSafeInteger(uid) || uid < 0 || !Number.isSafeInteger(gid) || gid < 0) {
26497
+ throw new Error(`Docker sandbox received an invalid host uid/gid: ${uid}:${gid}`);
26498
+ }
26499
+ }
26500
+ function formatSpawnError(dockerPath, error2) {
26501
+ const code = error2.code;
26502
+ if (code === "ENOENT") {
26503
+ return new Error(`Docker sandbox unavailable: executable "${dockerPath}" was not found`);
26504
+ }
26505
+ if (code === "EACCES") {
26506
+ return new Error(`Docker sandbox unavailable: executable "${dockerPath}" is not executable`);
26507
+ }
26508
+ return new Error(`Docker sandbox could not start "${dockerPath}": ${error2.message}`);
26509
+ }
26510
+ function formatDockerRunError(spec, stderr) {
26511
+ const detail3 = stderr.trim() || "Docker exited with status 125 without an error message";
26512
+ if (/no such image|unable to find image|manifest unknown|pull access denied/i.test(detail3)) {
26513
+ return new Error(`Docker sandbox image "${spec.image}" is unavailable: ${detail3}`);
26514
+ }
26515
+ if (/mounts denied|invalid mount config|bind source path does not exist|mount.*failed/i.test(detail3)) {
26516
+ return new Error(`Docker sandbox mount failed for "${spec.mountSource}": ${detail3}`);
26517
+ }
26518
+ if (/cannot connect to the docker daemon|is the docker daemon running/i.test(detail3)) {
26519
+ return new Error(`Docker sandbox unavailable: ${detail3}`);
26520
+ }
26521
+ return new Error(`Docker sandbox failed to start container "${spec.containerName}": ${detail3}`);
26522
+ }
26523
+ async function removeContainer(dockerPath, containerName, processEnv) {
26524
+ const args = ["container", "rm", "--force", containerName];
26525
+ let last;
26526
+ for (let attempt = 0; attempt < CLEANUP_ATTEMPTS; attempt += 1) {
26527
+ last = await runCleanupCommand(dockerPath, args, processEnv);
26528
+ if (last.ok) return;
26529
+ if (attempt < CLEANUP_ATTEMPTS - 1) {
26530
+ await new Promise((resolve7) => setTimeout(resolve7, CLEANUP_RETRY_DELAY_MS));
26531
+ }
26532
+ }
26533
+ if (last && !last.missing) {
26534
+ console.warn(
26535
+ `Docker sandbox could not confirm cleanup of container "${containerName}": ${last.error || "cleanup command failed"}`
26536
+ );
26537
+ }
26538
+ }
26539
+ function runCleanupCommand(dockerPath, args, processEnv) {
26540
+ return new Promise((resolve7) => {
26541
+ let done = false;
26542
+ let stderr = "";
26543
+ const child = spawn(dockerPath, args, {
26544
+ stdio: ["ignore", "ignore", "pipe"],
26545
+ windowsHide: true,
26546
+ env: processEnv
26547
+ });
26548
+ const finish = (ok, error2 = "") => {
26549
+ if (done) return;
26550
+ done = true;
26551
+ clearTimeout(timeoutHandle);
26552
+ const detail3 = error2 || stderr.trim();
26553
+ resolve7({
26554
+ ok,
26555
+ missing: /no such container/i.test(detail3),
26556
+ error: detail3
26557
+ });
26558
+ };
26559
+ const timeoutHandle = setTimeout(() => {
26560
+ child.kill("SIGKILL");
26561
+ finish(false, `cleanup timed out after ${CLEANUP_TIMEOUT_MS}ms`);
26562
+ }, CLEANUP_TIMEOUT_MS);
26563
+ child.stderr.on("data", (data) => {
26564
+ stderr = `${stderr}${data.toString("utf8")}`.slice(-2048);
26565
+ });
26566
+ child.once("error", (error2) => finish(false, error2.message));
26567
+ child.once("close", (code) => finish(code === 0));
26568
+ });
26569
+ }
26570
+
26571
+ // ../daemon/src/runtime/shell/tooling.ts
26572
+ var HOST_CODING_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"];
26573
+ var HOST_FILE_TOOL_NAMES = ["read", "edit", "write", "grep", "find", "ls"];
26574
+ var CONTAINER_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
26575
+ var SANDBOX_INPUTS_DIR = "/inputs";
26576
+ var SANDBOX_MEMORY_DIR = "/workspace/memory";
26577
+ function buildSandboxContainerEnv(env, allowlist) {
26578
+ return {
26579
+ ...buildScrubbedShellEnv(env, allowlist),
26580
+ PATH: CONTAINER_PATH,
26581
+ HOME: "/tmp",
26582
+ LANG: "C.UTF-8",
26583
+ LC_ALL: "C.UTF-8",
26584
+ SHELL: "/bin/bash",
26585
+ TMPDIR: "/tmp"
26586
+ };
26587
+ }
26588
+ function createSessionShellTools(cwd, env, options = {}) {
26589
+ const config2 = resolveShellSecurityConfig(env);
26590
+ if (config2.sandboxMode === "off" && config2.approvalMode === "off") {
26591
+ return { config: config2, hostToolNames: HOST_CODING_TOOL_NAMES };
26592
+ }
26593
+ if (config2.sandboxMode === "off") {
26594
+ const base2 = createBashToolDefinition(cwd, {
26595
+ operations: createLocalBashOperations()
26596
+ });
26597
+ const customBash2 = createApprovalGatedBashTool(base2, options.approvalHost);
26598
+ return { config: config2, hostToolNames: HOST_FILE_TOOL_NAMES, customBash: customBash2 };
26599
+ }
26600
+ const operations = createDockerBashOperations({
26601
+ image: config2.sandboxImage,
26602
+ env: buildSandboxContainerEnv(env, config2.envAllowlist),
26603
+ readOnlyMounts: [
26604
+ ...existsSync12(join16(cwd, "memory")) ? [safeReadOnlyMount(join16(cwd, "memory"), SANDBOX_MEMORY_DIR, cwd)] : [],
26605
+ ...options.inputsDir ? [safeReadOnlyMount(options.inputsDir, SANDBOX_INPUTS_DIR, dirname4(options.inputsDir))] : [],
26606
+ ...(options.skillMounts ?? []).map(
26607
+ (mount) => safeReadOnlyMount(mount.source, mount.target, dirname4(mount.source))
26608
+ )
26609
+ ]
26610
+ });
26611
+ const base = createBashToolDefinition(cwd, { operations });
26612
+ const dockerBash = defineTool2({
26613
+ ...base,
26614
+ label: "bash (Docker sandbox)",
26615
+ description: `${base.description} Runs in an ephemeral, network-disabled Docker container with the team workspace mounted read/write plus only bounded read-only memory, skill, and attachment mounts. Other host files and host credentials are unavailable.`
26616
+ });
26617
+ const customBash = config2.approvalMode === "dangerous" ? createApprovalGatedBashTool(dockerBash, options.approvalHost) : dockerBash;
26618
+ return { config: config2, hostToolNames: [], customBash };
26619
+ }
26620
+ function safeReadOnlyMount(source, target, expectedRoot) {
26621
+ const info = lstatSync2(source);
26622
+ if (info.isSymbolicLink() || !info.isDirectory()) {
26623
+ throw new Error(`Docker sandbox read-only mount must be a real directory: ${source}`);
26624
+ }
26625
+ const sourceRoot = realpathSync2(expectedRoot);
26626
+ const canonicalSource = realpathSync2(source);
26627
+ const rel = relative3(sourceRoot, canonicalSource);
26628
+ const contained = rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !isAbsolute3(rel);
26629
+ if (!contained) {
26630
+ throw new Error(`Docker sandbox read-only mount escaped its configured root: ${source}`);
26631
+ }
26632
+ return { source: canonicalSource, target, sourceRoot };
26633
+ }
26634
+
25093
26635
  // ../daemon/src/runtime/pi/tools.ts
25094
26636
  import { Type as Type2 } from "typebox";
25095
26637
 
25096
26638
  // ../daemon/src/runtime/tools/bootstrap.ts
25097
- import { existsSync as existsSync13, rmSync as rmSync11 } from "fs";
25098
- import { join as join15 } from "path";
26639
+ import { existsSync as existsSync13, rmSync as rmSync12 } from "fs";
26640
+ import { join as join17 } from "path";
25099
26641
  function bootstrapTool(agentDir) {
25100
26642
  return {
25101
26643
  def: {
@@ -25104,9 +26646,9 @@ function bootstrapTool(agentDir) {
25104
26646
  parameters: { type: "object", properties: {} }
25105
26647
  },
25106
26648
  async invoke() {
25107
- const path = join15(agentDir, "BOOTSTRAP.md");
26649
+ const path = join17(agentDir, "BOOTSTRAP.md");
25108
26650
  if (existsSync13(path)) {
25109
- rmSync11(path);
26651
+ rmSync12(path);
25110
26652
  return "BOOTSTRAP.md removed. Bootstrap is complete.";
25111
26653
  }
25112
26654
  return "BOOTSTRAP.md was already removed.";
@@ -25244,8 +26786,16 @@ function browserTools(host2, agentId) {
25244
26786
  }
25245
26787
 
25246
26788
  // ../daemon/src/runtime/tools/deliver-file.ts
25247
- import { readFileSync as readFileSync11, statSync as statSync5 } from "fs";
25248
- import { basename as basename2, extname, isAbsolute, resolve as resolve3 } from "path";
26789
+ import {
26790
+ closeSync as closeSync3,
26791
+ constants as constants3,
26792
+ fstatSync as fstatSync2,
26793
+ openSync as openSync3,
26794
+ readFileSync as readFileSync11,
26795
+ realpathSync as realpathSync3,
26796
+ statSync as statSync4
26797
+ } from "fs";
26798
+ import { basename as basename2, extname, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve4, sep as sep5 } from "path";
25249
26799
  var MAX_DELIVER_BYTES = 25 * 1024 * 1024;
25250
26800
  var MIME = {
25251
26801
  ".pdf": "application/pdf",
@@ -25261,7 +26811,17 @@ var MIME = {
25261
26811
  ".gif": "image/gif",
25262
26812
  ".webp": "image/webp"
25263
26813
  };
26814
+ function isWithin2(root, candidate) {
26815
+ const rel = relative4(root, candidate);
26816
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep5}`) && !isAbsolute4(rel);
26817
+ }
25264
26818
  function deliverFileTool(cwd, sink) {
26819
+ const workspacePath = resolve4(cwd);
26820
+ let workspaceRealPath = null;
26821
+ try {
26822
+ workspaceRealPath = realpathSync3(workspacePath);
26823
+ } catch {
26824
+ }
25265
26825
  return {
25266
26826
  def: {
25267
26827
  name: "deliver_file",
@@ -25271,7 +26831,7 @@ function deliverFileTool(cwd, sink) {
25271
26831
  properties: {
25272
26832
  path: {
25273
26833
  type: "string",
25274
- description: "Path to the file, relative to your workspace or absolute."
26834
+ description: "Path to a file, relative to your workspace."
25275
26835
  }
25276
26836
  },
25277
26837
  required: ["path"],
@@ -25281,29 +26841,71 @@ function deliverFileTool(cwd, sink) {
25281
26841
  async invoke(args) {
25282
26842
  const p = String(args.path ?? "");
25283
26843
  if (!p) throw new Error("deliver_file: path is required");
25284
- const abs = isAbsolute(p) ? p : resolve3(cwd, p);
25285
- let size;
26844
+ if (isAbsolute4(p)) {
26845
+ throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
26846
+ }
26847
+ const abs = resolve4(workspacePath, p);
26848
+ if (!isWithin2(workspacePath, abs)) {
26849
+ throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
26850
+ }
26851
+ if (!workspaceRealPath) {
26852
+ throw new Error("deliver_file: workspace is unavailable");
26853
+ }
26854
+ let real;
26855
+ let expected;
25286
26856
  try {
25287
- size = statSync5(abs).size;
26857
+ real = realpathSync3(abs);
26858
+ expected = statSync4(real);
25288
26859
  } catch {
25289
26860
  throw new Error(`deliver_file: no such file: ${p}`);
25290
26861
  }
25291
- if (size > MAX_DELIVER_BYTES) {
25292
- throw new Error(
25293
- `deliver_file: "${basename2(abs)}" is too large (${(size / 1024 / 1024).toFixed(1)} MB > 25 MB)`
25294
- );
26862
+ if (!isWithin2(workspaceRealPath, real)) {
26863
+ throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
26864
+ }
26865
+ let fd;
26866
+ try {
26867
+ fd = openSync3(real, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
26868
+ } catch {
26869
+ throw new Error(`deliver_file: no such file: ${p}`);
26870
+ }
26871
+ let data;
26872
+ try {
26873
+ const stat2 = fstatSync2(fd);
26874
+ if (!stat2.isFile()) {
26875
+ throw new Error(`deliver_file: not a regular file: ${p}`);
26876
+ }
26877
+ if (stat2.dev !== expected.dev || stat2.ino !== expected.ino) {
26878
+ throw new Error(`deliver_file: path changed during validation: ${p}`);
26879
+ }
26880
+ let confirmedReal;
26881
+ try {
26882
+ confirmedReal = realpathSync3(abs);
26883
+ } catch {
26884
+ throw new Error(`deliver_file: path changed during validation: ${p}`);
26885
+ }
26886
+ if (confirmedReal !== real) {
26887
+ throw new Error(`deliver_file: path changed during validation: ${p}`);
26888
+ }
26889
+ if (stat2.size > MAX_DELIVER_BYTES) {
26890
+ throw new Error(
26891
+ `deliver_file: "${basename2(abs)}" is too large (${(stat2.size / 1024 / 1024).toFixed(1)} MB > 25 MB)`
26892
+ );
26893
+ }
26894
+ data = readFileSync11(fd).toString("base64");
26895
+ } finally {
26896
+ closeSync3(fd);
25295
26897
  }
25296
26898
  const name = basename2(abs);
25297
26899
  const mimeType = MIME[extname(abs).toLowerCase()] ?? "application/octet-stream";
25298
- sink({ name, mimeType, data: readFileSync11(abs).toString("base64") });
26900
+ sink({ name, mimeType, data });
25299
26901
  return `Delivered "${name}" (${mimeType}) to the user.`;
25300
26902
  }
25301
26903
  };
25302
26904
  }
25303
26905
 
25304
26906
  // ../daemon/src/runtime/tools/home.ts
25305
- import { readdirSync as readdirSync7, readFileSync as readFileSync12, statSync as statSync6, writeFileSync as writeFileSync8 } from "fs";
25306
- import { join as join16 } from "path";
26907
+ import { readdirSync as readdirSync8, readFileSync as readFileSync12, statSync as statSync5, writeFileSync as writeFileSync9 } from "fs";
26908
+ import { join as join18 } from "path";
25307
26909
  var HOME_FILES_READABLE = [
25308
26910
  "IDENTITY.md",
25309
26911
  "SOUL.md",
@@ -25333,7 +26935,7 @@ function homeTools(agentDir) {
25333
26935
  `home_read: "file" must be one of ${HOME_FILES_READABLE.join(", ")}; got "${file}"`
25334
26936
  );
25335
26937
  }
25336
- const path = join16(agentDir, file);
26938
+ const path = join18(agentDir, file);
25337
26939
  try {
25338
26940
  return readFileSync12(path, "utf8");
25339
26941
  } catch (err) {
@@ -25363,8 +26965,8 @@ function homeTools(agentDir) {
25363
26965
  );
25364
26966
  }
25365
26967
  const content = typeof args.content === "string" ? args.content : "";
25366
- const path = join16(agentDir, file);
25367
- writeFileSync8(path, content, "utf8");
26968
+ const path = join18(agentDir, file);
26969
+ writeFileSync9(path, content, "utf8");
25368
26970
  return `wrote ${file} (${Buffer.byteLength(content, "utf8")} bytes)`;
25369
26971
  }
25370
26972
  },
@@ -25377,9 +26979,9 @@ function homeTools(agentDir) {
25377
26979
  async invoke() {
25378
26980
  const entries = [];
25379
26981
  for (const file of HOME_FILES_READABLE) {
25380
- const path = join16(agentDir, file);
26982
+ const path = join18(agentDir, file);
25381
26983
  try {
25382
- const s = statSync6(path);
26984
+ const s = statSync5(path);
25383
26985
  entries.push(`${file} (${s.size}b)`);
25384
26986
  } catch {
25385
26987
  }
@@ -25387,7 +26989,7 @@ function homeTools(agentDir) {
25387
26989
  if (entries.length === 0) {
25388
26990
  const dirEntries = (() => {
25389
26991
  try {
25390
- return readdirSync7(agentDir);
26992
+ return readdirSync8(agentDir);
25391
26993
  } catch {
25392
26994
  return [];
25393
26995
  }
@@ -26302,7 +27904,6 @@ function createBazilionCustomTools(opts) {
26302
27904
  }
26303
27905
 
26304
27906
  // ../daemon/src/runtime/pi/session.ts
26305
- var BUILTIN_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"];
26306
27907
  async function createBazilionSession(opts) {
26307
27908
  const {
26308
27909
  agent,
@@ -26316,6 +27917,7 @@ async function createBazilionSession(opts) {
26316
27917
  mcpHost,
26317
27918
  mcpTools,
26318
27919
  fileSink,
27920
+ bashApprovalHost,
26319
27921
  refreshApiKey
26320
27922
  } = opts;
26321
27923
  const { providerName, modelId } = splitModelString(agent.model);
@@ -26348,7 +27950,17 @@ async function createBazilionSession(opts) {
26348
27950
  }
26349
27951
  const cwd = agent.team.path;
26350
27952
  if (!existsSync14(cwd)) mkdirSync6(cwd, { recursive: true });
26351
- const sessionDir = join17(paths.agentDir(agent.agent.id), "sessions");
27953
+ const promptSkills = loadPromptSkills(paths.skillsDir, agent.skills);
27954
+ const uploadsDir = join19(agent.agent.dir, "uploads");
27955
+ const shellTools = createSessionShellTools(cwd, env, {
27956
+ ...existsSync14(uploadsDir) ? { inputsDir: uploadsDir } : {},
27957
+ skillMounts: promptSkills.map((skill) => ({
27958
+ source: skill.hostDir,
27959
+ target: skill.sandboxDir
27960
+ })),
27961
+ approvalHost: bashApprovalHost
27962
+ });
27963
+ const sessionDir = join19(paths.agentDir(agent.agent.id), "sessions");
26352
27964
  mkdirSync6(sessionDir, { recursive: true });
26353
27965
  const existing = findMostRecent(sessionDir);
26354
27966
  const sessionManager = existing ? SessionManager.open(existing, sessionDir, cwd) : SessionManager.create(cwd, sessionDir);
@@ -26361,10 +27973,13 @@ async function createBazilionSession(opts) {
26361
27973
  provider: { maxRetryDelayMs: 8e3 }
26362
27974
  }
26363
27975
  });
26364
- const bazilionPrompt = buildSystemPrompt(agent);
27976
+ const bazilionPrompt = buildSystemPrompt(agent, {
27977
+ skills: promptSkills,
27978
+ sandboxMode: shellTools.config.sandboxMode
27979
+ });
26365
27980
  const resourceLoader = createBazilionResourceLoader(bazilionPrompt);
26366
27981
  await resourceLoader.reload();
26367
- const customTools = createBazilionCustomTools({
27982
+ const bazilionTools = createBazilionCustomTools({
26368
27983
  agent,
26369
27984
  memory,
26370
27985
  messagingHost,
@@ -26375,10 +27990,11 @@ async function createBazilionSession(opts) {
26375
27990
  fileSink,
26376
27991
  env
26377
27992
  });
26378
- const allowedTools = [...BUILTIN_TOOL_NAMES, ...customTools.map((t) => t.name)];
27993
+ const customTools = shellTools.customBash ? [...bazilionTools, shellTools.customBash] : bazilionTools;
27994
+ const allowedTools = [...shellTools.hostToolNames, ...customTools.map((t) => t.name)];
26379
27995
  const { session } = await createAgentSession({
26380
27996
  cwd,
26381
- agentDir: join17(paths.home, "pi"),
27997
+ agentDir: join19(paths.home, "pi"),
26382
27998
  model,
26383
27999
  thinkingLevel: toPiThinkingLevel(agent.reasoningLevel),
26384
28000
  tools: allowedTools,
@@ -26504,7 +28120,7 @@ function createBazilionResourceLoader(appendSystemPrompt) {
26504
28120
  };
26505
28121
  }
26506
28122
  function loadInitialMessages(agent, paths) {
26507
- const sessionDir = join17(paths.agentDir(agent.agent.id), "sessions");
28123
+ const sessionDir = join19(paths.agentDir(agent.agent.id), "sessions");
26508
28124
  if (!existsSync14(sessionDir)) return [];
26509
28125
  const cwd = agent.team.path;
26510
28126
  if (!existsSync14(cwd)) return [];
@@ -26523,12 +28139,12 @@ function loadInitialMessages(agent, paths) {
26523
28139
  }
26524
28140
  }
26525
28141
  function loadSessionHead(agent, paths) {
26526
- const sessionDir = join17(paths.agentDir(agent.agent.id), "sessions");
28142
+ const sessionDir = join19(paths.agentDir(agent.agent.id), "sessions");
26527
28143
  if (!existsSync14(sessionDir)) return { file: null, size: 0 };
26528
28144
  const recent = findMostRecent(sessionDir);
26529
28145
  if (!recent) return { file: null, size: 0 };
26530
28146
  try {
26531
- const s = statSync7(recent);
28147
+ const s = statSync6(recent);
26532
28148
  return { file: basename3(recent), size: s.size };
26533
28149
  } catch {
26534
28150
  return { file: null, size: 0 };
@@ -26537,11 +28153,11 @@ function loadSessionHead(agent, paths) {
26537
28153
  function findMostRecent(sessionDir) {
26538
28154
  if (!existsSync14(sessionDir)) return null;
26539
28155
  let newest = null;
26540
- for (const entry of readdirSync8(sessionDir)) {
28156
+ for (const entry of readdirSync9(sessionDir)) {
26541
28157
  if (!entry.endsWith(".jsonl")) continue;
26542
- const path = join17(sessionDir, entry);
28158
+ const path = join19(sessionDir, entry);
26543
28159
  try {
26544
- const s = statSync7(path);
28160
+ const s = statSync6(path);
26545
28161
  if (!newest || s.mtimeMs > newest.mtimeMs) newest = { path, mtimeMs: s.mtimeMs };
26546
28162
  } catch {
26547
28163
  }
@@ -26670,22 +28286,78 @@ function listCatalogModelsSync(providerName) {
26670
28286
  }
26671
28287
 
26672
28288
  // ../daemon/src/runtime/worker/spawn.ts
26673
- import { spawn } from "child_process";
28289
+ import { spawn as spawn2 } from "child_process";
26674
28290
  import { existsSync as existsSync15 } from "fs";
26675
28291
  import { createRequire } from "module";
26676
28292
  import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
28293
+
28294
+ // ../daemon/src/runtime/worker/api-key-refresh.ts
28295
+ async function refreshApiKeyForTurn(args, context, host2, signal) {
28296
+ if (args.agentId !== context.agentId || args.turnId !== context.turnId) {
28297
+ throw new Error("API key refresh request does not belong to this worker turn");
28298
+ }
28299
+ assertOpenAICodexProvider(args.providerName, context.providerName);
28300
+ if (!host2) throw new Error("OpenAI Codex refresh is unavailable for this worker turn");
28301
+ if (signal?.aborted) throw refreshCancelledError();
28302
+ let token;
28303
+ try {
28304
+ const pending = host2.refresh(args.providerName, signal);
28305
+ token = await waitForRefresh(pending, signal);
28306
+ } catch (error2) {
28307
+ if (error2 instanceof ApiKeyRefreshCancelledError) throw error2;
28308
+ throw new Error("OpenAI Codex access token refresh failed \u2014 reconnect on /config and retry");
28309
+ }
28310
+ if (typeof token !== "string" || token.length === 0) {
28311
+ throw new Error("API key refresh returned an invalid token");
28312
+ }
28313
+ return token;
28314
+ }
28315
+ function assertOpenAICodexProvider(requested, expected) {
28316
+ if (expected !== "openai-codex" || requested !== expected) {
28317
+ throw new Error("unexpected API key refresh provider");
28318
+ }
28319
+ }
28320
+ function waitForRefresh(pending, signal) {
28321
+ if (!signal) return pending;
28322
+ return new Promise((resolve7, reject) => {
28323
+ let settled = false;
28324
+ const finish = (callback) => {
28325
+ if (settled) return;
28326
+ settled = true;
28327
+ signal.removeEventListener("abort", onAbort);
28328
+ callback();
28329
+ };
28330
+ const onAbort = () => finish(() => reject(refreshCancelledError()));
28331
+ signal.addEventListener("abort", onAbort, { once: true });
28332
+ if (signal.aborted) {
28333
+ onAbort();
28334
+ return;
28335
+ }
28336
+ pending.then(
28337
+ (value) => finish(() => resolve7(value)),
28338
+ (error2) => finish(() => reject(error2))
28339
+ );
28340
+ });
28341
+ }
28342
+ var ApiKeyRefreshCancelledError = class extends Error {
28343
+ };
28344
+ function refreshCancelledError() {
28345
+ return new ApiKeyRefreshCancelledError("API key refresh cancelled with the worker turn");
28346
+ }
28347
+
28348
+ // ../daemon/src/runtime/worker/spawn.ts
26677
28349
  var DEFAULT_KILL_GRACE_MS = 3e3;
26678
28350
  var sourceEntryPath = fileURLToPath2(new URL("./entry.ts", import.meta.url));
26679
28351
  var bundledEntryPath = fileURLToPath2(new URL("./worker.js", import.meta.url));
26680
28352
  var entryPath = existsSync15(sourceEntryPath) ? sourceEntryPath : bundledEntryPath;
26681
- var entryIsTs = entryPath.endsWith(".ts");
26682
- function workerSpawnArgs() {
26683
- if (!entryIsTs) return [entryPath];
28353
+ function workerSpawnArgs(workerEntryPath = entryPath) {
28354
+ const workerEntryIsTs = workerEntryPath.endsWith(".ts");
28355
+ if (!workerEntryIsTs) return [workerEntryPath];
26684
28356
  const tsFeature = process.features.typescript;
26685
28357
  if (typeof tsFeature === "string" || tsFeature === true) {
26686
- return ["--experimental-strip-types", "--no-warnings", entryPath];
28358
+ return ["--experimental-strip-types", "--no-warnings", workerEntryPath];
26687
28359
  }
26688
- return ["--import", tsxImportSpecifier(), entryPath];
28360
+ return ["--import", tsxImportSpecifier(), workerEntryPath];
26689
28361
  }
26690
28362
  var cachedTsxImport = null;
26691
28363
  function tsxImportSpecifier() {
@@ -26695,23 +28367,42 @@ function tsxImportSpecifier() {
26695
28367
  return cachedTsxImport;
26696
28368
  }
26697
28369
  async function* spawnWorkerTurn(spec, opts = {}) {
26698
- const child = spawn(process.execPath, workerSpawnArgs(), {
28370
+ const child = spawn2(process.execPath, workerSpawnArgs(opts.workerEntryPath), {
26699
28371
  env: opts.env ?? process.env,
26700
28372
  stdio: ["pipe", "pipe", "inherit", "ipc"]
26701
28373
  });
26702
- if (opts.messagingHost || opts.userMdHost || opts.browserHost || opts.mcpHost) {
28374
+ const frames = new AsyncFrameQueue();
28375
+ const ipcLifetime = new AbortController();
28376
+ if (opts.messagingHost || opts.userMdHost || opts.browserHost || opts.mcpHost || opts.bashApprovalHost || opts.apiKeyRefreshHost) {
26703
28377
  attachIpcHandler(child, {
26704
28378
  messagingHost: opts.messagingHost,
26705
28379
  userMdHost: opts.userMdHost,
26706
28380
  browserHost: opts.browserHost,
26707
- mcpHost: opts.mcpHost
28381
+ mcpHost: opts.mcpHost,
28382
+ bashApprovalHost: opts.bashApprovalHost,
28383
+ apiKeyRefreshHost: opts.apiKeyRefreshHost,
28384
+ apiKeyRefreshContext: {
28385
+ providerName: spec.agent.model.split(":", 1)[0] ?? "",
28386
+ agentId: spec.agent.agent.id,
28387
+ turnId: spec.turnId
28388
+ },
28389
+ ipcSignal: ipcLifetime.signal,
28390
+ onBashApproval: (approval) => {
28391
+ frames.push({ kind: "event", event: { type: "command_approval", approval } });
28392
+ }
26708
28393
  });
26709
28394
  }
26710
- child.stdin?.write(JSON.stringify(spec));
28395
+ child.stdin?.write(
28396
+ JSON.stringify({
28397
+ ...spec,
28398
+ apiKeyRefreshEnabled: opts.apiKeyRefreshHost !== void 0
28399
+ })
28400
+ );
26711
28401
  child.stdin?.end();
26712
28402
  const grace = opts.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
26713
28403
  let killTimer = null;
26714
28404
  const onAbort = () => {
28405
+ ipcLifetime.abort();
26715
28406
  if (child.exitCode !== null || child.signalCode !== null) return;
26716
28407
  try {
26717
28408
  child.kill("SIGTERM");
@@ -26730,37 +28421,23 @@ async function* spawnWorkerTurn(spec, opts = {}) {
26730
28421
  if (opts.signal?.aborted) onAbort();
26731
28422
  else opts.signal?.addEventListener("abort", onAbort);
26732
28423
  const waitForExit = new Promise(
26733
- (resolve4) => {
28424
+ (resolve7) => {
26734
28425
  if (child.exitCode !== null || child.signalCode !== null) {
26735
- resolve4({ code: child.exitCode, signal: child.signalCode });
28426
+ resolve7({ code: child.exitCode, signal: child.signalCode });
26736
28427
  return;
26737
28428
  }
26738
- child.once("close", (code, signal) => resolve4({ code, signal }));
28429
+ child.once("close", (code, signal) => resolve7({ code, signal }));
26739
28430
  }
26740
28431
  );
26741
28432
  let emittedFatal = false;
26742
28433
  try {
26743
- let buf = "";
26744
28434
  if (!child.stdout) throw new Error("worker spawn: stdout pipe missing");
26745
- for await (const chunk of child.stdout) {
26746
- buf += chunk.toString("utf8");
26747
- let idx = buf.indexOf("\n");
26748
- while (idx !== -1) {
26749
- const line = buf.slice(0, idx);
26750
- buf = buf.slice(idx + 1);
26751
- if (line.trim()) {
26752
- const frame = parseFrame(line);
26753
- if (frame.kind === "fatal") emittedFatal = true;
26754
- yield frame;
26755
- }
26756
- idx = buf.indexOf("\n");
26757
- }
26758
- }
26759
- if (buf.trim()) {
26760
- const frame = parseFrame(buf);
28435
+ const stdoutTask = pumpWorkerFrames(child.stdout, frames);
28436
+ for await (const frame of frames) {
26761
28437
  if (frame.kind === "fatal") emittedFatal = true;
26762
28438
  yield frame;
26763
28439
  }
28440
+ await stdoutTask;
26764
28441
  const exit = await waitForExit;
26765
28442
  if (!emittedFatal && exit.code !== 0 && exit.code !== null) {
26766
28443
  yield {
@@ -26771,6 +28448,7 @@ async function* spawnWorkerTurn(spec, opts = {}) {
26771
28448
  yield { kind: "fatal", error: `worker killed by ${exit.signal}` };
26772
28449
  }
26773
28450
  } finally {
28451
+ ipcLifetime.abort();
26774
28452
  opts.signal?.removeEventListener("abort", onAbort);
26775
28453
  if (killTimer) clearTimeout(killTimer);
26776
28454
  try {
@@ -26864,12 +28542,88 @@ async function dispatch(req, hosts) {
26864
28542
  req.args.args
26865
28543
  );
26866
28544
  break;
28545
+ case "refreshApiKey":
28546
+ result = await refreshApiKeyForTurn(
28547
+ req.args,
28548
+ require2(hosts.apiKeyRefreshContext, "apiKeyRefreshContext", req.method),
28549
+ hosts.apiKeyRefreshHost,
28550
+ hosts.ipcSignal
28551
+ );
28552
+ break;
28553
+ case "bashApproval": {
28554
+ const handle12 = require2(hosts.bashApprovalHost, "bashApproval", req.method).begin(
28555
+ req.args,
28556
+ hosts.ipcSignal
28557
+ );
28558
+ hosts.onBashApproval?.(handle12.approval);
28559
+ const decision = await handle12.decision;
28560
+ result = decision;
28561
+ if (handle12.approval.status === "pending") {
28562
+ hosts.onBashApproval?.({
28563
+ ...handle12.approval,
28564
+ status: approvalStatusForDecision(decision)
28565
+ });
28566
+ }
28567
+ break;
28568
+ }
26867
28569
  }
26868
28570
  return { type: "rpc-reply", id: req.id, ok: true, result };
26869
28571
  } catch (err) {
26870
28572
  return { type: "rpc-reply", id: req.id, ok: false, error: err.message };
26871
28573
  }
26872
28574
  }
28575
+ function approvalStatusForDecision(result) {
28576
+ if (result.reason === "auto_deny") return "auto_denied";
28577
+ if (result.reason === "timeout") return "expired";
28578
+ if (result.reason === "cancelled") return "cancelled";
28579
+ return result.decision === "allow" ? "allowed" : "denied";
28580
+ }
28581
+ var AsyncFrameQueue = class {
28582
+ #frames = [];
28583
+ #waiters = [];
28584
+ #ended = false;
28585
+ push(frame) {
28586
+ if (this.#ended) return;
28587
+ const waiter = this.#waiters.shift();
28588
+ if (waiter) waiter({ done: false, value: frame });
28589
+ else this.#frames.push(frame);
28590
+ }
28591
+ end() {
28592
+ if (this.#ended) return;
28593
+ this.#ended = true;
28594
+ for (const waiter of this.#waiters.splice(0)) waiter({ done: true, value: void 0 });
28595
+ }
28596
+ [Symbol.asyncIterator]() {
28597
+ return {
28598
+ next: () => {
28599
+ const frame = this.#frames.shift();
28600
+ if (frame) return Promise.resolve({ done: false, value: frame });
28601
+ if (this.#ended) return Promise.resolve({ done: true, value: void 0 });
28602
+ return new Promise((resolve7) => this.#waiters.push(resolve7));
28603
+ }
28604
+ };
28605
+ }
28606
+ };
28607
+ async function pumpWorkerFrames(stdout, frames) {
28608
+ let buf = "";
28609
+ try {
28610
+ for await (const chunk of stdout) {
28611
+ buf += chunk.toString("utf8");
28612
+ let idx = buf.indexOf("\n");
28613
+ while (idx !== -1) {
28614
+ const line = buf.slice(0, idx);
28615
+ buf = buf.slice(idx + 1);
28616
+ if (line.trim()) frames.push(parseFrame(line));
28617
+ idx = buf.indexOf("\n");
28618
+ }
28619
+ }
28620
+ if (buf.trim()) frames.push(parseFrame(buf));
28621
+ } catch (error2) {
28622
+ frames.push({ kind: "fatal", error: `worker stdout failed: ${error2.message}` });
28623
+ } finally {
28624
+ frames.end();
28625
+ }
28626
+ }
26873
28627
 
26874
28628
  // ../daemon/src/lib/api-key.ts
26875
28629
  async function resolveAgentApiKey(db, authToken, agent, opts = {}) {
@@ -26894,9 +28648,9 @@ async function resolveAgentApiKey(db, authToken, agent, opts = {}) {
26894
28648
  }
26895
28649
 
26896
28650
  // ../daemon/src/lib/attachments.ts
26897
- import { randomUUID as randomUUID14 } from "crypto";
26898
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync9 } from "fs";
26899
- import { join as join18 } from "path";
28651
+ import { randomUUID as randomUUID16 } from "crypto";
28652
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "fs";
28653
+ import { join as join20, posix } from "path";
26900
28654
  var MAX_FILE_BYTES = 25 * 1024 * 1024;
26901
28655
  function safeName(name) {
26902
28656
  const base = name.split(/[\\/]/).pop() ?? "file";
@@ -26907,9 +28661,9 @@ function fmtBytes(n) {
26907
28661
  if (n < 1024 * 1024) return `${Math.round(n / 1024)} KB`;
26908
28662
  return `${(n / (1024 * 1024)).toFixed(1)} MB`;
26909
28663
  }
26910
- function saveInputFiles(agentDir, files) {
28664
+ function saveInputFiles(agentDir, files, options = {}) {
26911
28665
  if (!files || files.length === 0) return "";
26912
- const dir = join18(agentDir, "uploads");
28666
+ const dir = join20(agentDir, "uploads");
26913
28667
  mkdirSync7(dir, { recursive: true });
26914
28668
  const lines = [];
26915
28669
  for (const f of files) {
@@ -26919,15 +28673,177 @@ function saveInputFiles(agentDir, files) {
26919
28673
  lines.push(`[attachment "${label}" skipped: too large (${fmtBytes(buf.byteLength)} > 25 MB)]`);
26920
28674
  continue;
26921
28675
  }
26922
- const path = join18(dir, `${randomUUID14().slice(0, 8)}-${safeName(label)}`);
26923
- writeFileSync9(path, buf);
28676
+ const filename = `${randomUUID16().slice(0, 8)}-${safeName(label)}`;
28677
+ const path = join20(dir, filename);
28678
+ writeFileSync10(path, buf);
28679
+ const referencePath = options.referenceDir ? posix.join(options.referenceDir, filename) : path;
26924
28680
  lines.push(
26925
- `[file saved to ${path} (${f.mimeType || "unknown"}, ${fmtBytes(buf.byteLength)}) \u2014 open it with your tools]`
28681
+ `[file saved to ${referencePath} (${f.mimeType || "unknown"}, ${fmtBytes(buf.byteLength)}) \u2014 open it with your tools]`
26926
28682
  );
26927
28683
  }
26928
28684
  return lines.join("\n");
26929
28685
  }
26930
28686
 
28687
+ // ../daemon/src/lib/bash-approval.ts
28688
+ var DEFAULT_BASH_APPROVAL_TIMEOUT_MS = 12e4;
28689
+ var DEFAULT_TOMBSTONE_TTL_MS = 6e4;
28690
+ var CommandApprovalRegistry = class {
28691
+ #pending = /* @__PURE__ */ new Map();
28692
+ /**
28693
+ * Exactly-once claim for one worker turn's tool call. Unlike response
28694
+ * tombstones, this survives settlement and is released only when that
28695
+ * worker's IPC lifetime ends (or the registry is explicitly cleared).
28696
+ */
28697
+ #attemptKeys = /* @__PURE__ */ new Map();
28698
+ #terminal = /* @__PURE__ */ new Map();
28699
+ #timeoutMs;
28700
+ #tombstoneTtlMs;
28701
+ #now;
28702
+ #audit;
28703
+ constructor(options = {}) {
28704
+ this.#timeoutMs = options.timeoutMs ?? DEFAULT_BASH_APPROVAL_TIMEOUT_MS;
28705
+ this.#tombstoneTtlMs = options.tombstoneTtlMs ?? DEFAULT_TOMBSTONE_TTL_MS;
28706
+ this.#now = options.now ?? Date.now;
28707
+ this.#audit = options.audit ?? logBashApprovalDecision;
28708
+ }
28709
+ begin(input, signal) {
28710
+ const key2 = approvalKey(input.turnId, input.toolCallId);
28711
+ if (this.#pending.has(input.id) || this.#terminal.has(input.id)) {
28712
+ throw new Error(`duplicate shell approval id: ${input.id}`);
28713
+ }
28714
+ const existing = this.#attemptKeys.get(key2);
28715
+ if (existing) {
28716
+ const state3 = this.#pending.has(existing.approvalId) ? "already pending" : "already decided";
28717
+ throw new Error(
28718
+ `shell approval ${state3} for turn ${input.turnId} tool call ${input.toolCallId}`
28719
+ );
28720
+ }
28721
+ const expiresAt = this.#now() + this.#timeoutMs;
28722
+ const base = {
28723
+ id: input.id,
28724
+ turnId: input.turnId,
28725
+ toolCallId: input.toolCallId,
28726
+ agentId: input.agentId,
28727
+ teamId: input.teamId,
28728
+ command: input.command,
28729
+ risks: input.risks,
28730
+ status: "pending",
28731
+ expiresAt
28732
+ };
28733
+ if (input.mode === "auto_deny") {
28734
+ this.#claimAttempt(key2, input.id, signal);
28735
+ const approval = { ...base, status: "auto_denied" };
28736
+ const decision2 = { decision: "deny", reason: "auto_deny" };
28737
+ this.#recordTerminal(approval, decision2);
28738
+ return { approval, decision: Promise.resolve(decision2) };
28739
+ }
28740
+ if (signal?.aborted) {
28741
+ const approval = { ...base, status: "cancelled" };
28742
+ const decision2 = { decision: "deny", reason: "cancelled" };
28743
+ this.#recordTerminal(approval, decision2);
28744
+ return { approval, decision: Promise.resolve(decision2) };
28745
+ }
28746
+ let resolveDecision;
28747
+ const decision = new Promise((resolve7) => {
28748
+ resolveDecision = resolve7;
28749
+ });
28750
+ const timer = setTimeout(() => {
28751
+ this.#settle(input.id, "deny", "timeout");
28752
+ }, this.#timeoutMs);
28753
+ timer.unref();
28754
+ const entry = { approval: base, resolve: resolveDecision, timer };
28755
+ this.#pending.set(input.id, entry);
28756
+ this.#claimAttempt(key2, input.id, signal);
28757
+ return { approval: base, decision };
28758
+ }
28759
+ list(agentId) {
28760
+ return Array.from(this.#pending.values(), (entry) => entry.approval).filter((approval) => agentId === void 0 || approval.agentId === agentId).sort((a, b2) => a.expiresAt - b2.expiresAt || a.id.localeCompare(b2.id));
28761
+ }
28762
+ respond(id, decision) {
28763
+ const pending = this.#pending.get(id);
28764
+ if (pending) {
28765
+ const approval = this.#settle(id, decision, "user");
28766
+ if (!approval) return { kind: "not_found" };
28767
+ return { kind: "accepted", approval };
28768
+ }
28769
+ const terminal = this.#terminal.get(id);
28770
+ if (!terminal) return { kind: "not_found" };
28771
+ if (terminal.decision.reason === "user" && terminal.decision.decision === decision) {
28772
+ return { kind: "already_applied", approval: terminal.approval };
28773
+ }
28774
+ return { kind: "conflict", approval: terminal.approval };
28775
+ }
28776
+ /** Test/shutdown helper: fail closed and release every waiter. */
28777
+ cancelAll() {
28778
+ for (const id of [...this.#pending.keys()]) this.#settle(id, "deny", "cancelled");
28779
+ for (const entry of this.#terminal.values()) clearTimeout(entry.timer);
28780
+ this.#terminal.clear();
28781
+ for (const [key2, entry] of [...this.#attemptKeys]) {
28782
+ this.#releaseAttempt(key2, entry.approvalId);
28783
+ }
28784
+ }
28785
+ #settle(id, decision, reason) {
28786
+ const entry = this.#pending.get(id);
28787
+ if (!entry) return void 0;
28788
+ this.#pending.delete(id);
28789
+ clearTimeout(entry.timer);
28790
+ const result = { decision, reason };
28791
+ const approval = {
28792
+ ...entry.approval,
28793
+ status: statusForDecision(result)
28794
+ };
28795
+ this.#recordTerminal(approval, result);
28796
+ entry.resolve(result);
28797
+ return approval;
28798
+ }
28799
+ #claimAttempt(key2, approvalId, signal) {
28800
+ const entry = { approvalId, signal };
28801
+ this.#attemptKeys.set(key2, entry);
28802
+ if (!signal) return;
28803
+ entry.onAbort = () => {
28804
+ this.#settle(approvalId, "deny", "cancelled");
28805
+ this.#releaseAttempt(key2, approvalId);
28806
+ };
28807
+ signal.addEventListener("abort", entry.onAbort, { once: true });
28808
+ if (signal.aborted) entry.onAbort();
28809
+ }
28810
+ #releaseAttempt(key2, approvalId) {
28811
+ const entry = this.#attemptKeys.get(key2);
28812
+ if (!entry || entry.approvalId !== approvalId) return;
28813
+ if (entry.signal && entry.onAbort) {
28814
+ entry.signal.removeEventListener("abort", entry.onAbort);
28815
+ }
28816
+ this.#attemptKeys.delete(key2);
28817
+ }
28818
+ #recordTerminal(approval, decision) {
28819
+ this.#audit({
28820
+ approvalId: approval.id,
28821
+ turnId: approval.turnId,
28822
+ toolCallId: approval.toolCallId,
28823
+ agentId: approval.agentId,
28824
+ teamId: approval.teamId,
28825
+ riskCodes: [...new Set(approval.risks.map((risk) => risk.code))],
28826
+ status: approval.status
28827
+ });
28828
+ const timer = setTimeout(() => this.#terminal.delete(approval.id), this.#tombstoneTtlMs);
28829
+ timer.unref();
28830
+ this.#terminal.set(approval.id, { approval, decision, timer });
28831
+ }
28832
+ };
28833
+ function approvalKey(turnId, toolCallId) {
28834
+ return `${turnId}\0${toolCallId}`;
28835
+ }
28836
+ function statusForDecision(result) {
28837
+ if (result.reason === "auto_deny") return "auto_denied";
28838
+ if (result.reason === "timeout") return "expired";
28839
+ if (result.reason === "cancelled") return "cancelled";
28840
+ return result.decision === "allow" ? "allowed" : "denied";
28841
+ }
28842
+ function logBashApprovalDecision(entry) {
28843
+ console.info("bash approval decision", entry);
28844
+ }
28845
+ var commandApprovalRegistry = new CommandApprovalRegistry();
28846
+
26931
28847
  // ../daemon/src/lib/browser/config.ts
26932
28848
  var FALSEY = /* @__PURE__ */ new Set(["false", "0", "no", "off"]);
26933
28849
  function envBool(v2, def) {
@@ -27043,11 +28959,11 @@ async function getSsrfProxy() {
27043
28959
  if (serverUrl) return serverUrl;
27044
28960
  const s = createServer(onRequest);
27045
28961
  s.on("connect", onConnect);
27046
- await new Promise((resolve4, reject) => {
28962
+ await new Promise((resolve7, reject) => {
27047
28963
  s.once("error", reject);
27048
28964
  s.listen(0, "127.0.0.1", () => {
27049
28965
  s.off("error", reject);
27050
- resolve4();
28966
+ resolve7();
27051
28967
  });
27052
28968
  });
27053
28969
  const addr = s.address();
@@ -27062,7 +28978,7 @@ async function closeSsrfProxy() {
27062
28978
  server = null;
27063
28979
  serverUrl = null;
27064
28980
  if (!s) return;
27065
- await new Promise((resolve4) => s.close(() => resolve4()));
28981
+ await new Promise((resolve7) => s.close(() => resolve7()));
27066
28982
  }
27067
28983
 
27068
28984
  // ../daemon/src/lib/resources.ts
@@ -27442,7 +29358,7 @@ function createBrowserHost(config2) {
27442
29358
  }
27443
29359
 
27444
29360
  // ../daemon/src/lib/communication.ts
27445
- import { randomUUID as randomUUID15 } from "crypto";
29361
+ import { randomUUID as randomUUID17 } from "crypto";
27446
29362
 
27447
29363
  // ../daemon/src/lib/team-policy-contract.ts
27448
29364
  var TEAM_POLICY_MANAGEMENT_CONTRACT_VERSION = 1;
@@ -27589,7 +29505,7 @@ function isUserFacingFrame(frame) {
27589
29505
  function sendAgentMessage(db, input) {
27590
29506
  if (!teamPolicyEnforcementEnabled()) return messages_exports.send(db, input);
27591
29507
  const attemptKind = input.attemptKind ?? "agent_tool";
27592
- const attemptId = input.attemptId ?? randomUUID15();
29508
+ const attemptId = input.attemptId ?? randomUUID17();
27593
29509
  const outcome = db.raw.transaction(
27594
29510
  () => {
27595
29511
  const authorization = {
@@ -27682,28 +29598,55 @@ function deliverableReplies(db, agentId, replyTo) {
27682
29598
  return deliverableInbox(db, agentId, false).filter((message) => message.replyTo === replyTo);
27683
29599
  }
27684
29600
  function claimSchedulerTrigger(db, input) {
29601
+ const attempt = {
29602
+ origin: "scheduler_trigger",
29603
+ attemptKind: "scheduler_trigger",
29604
+ attemptId: `${input.triggerId}:${input.occurrence}`
29605
+ };
29606
+ const existingApproval = communicationApprovals_exports.getByAttempt(
29607
+ db,
29608
+ attempt.attemptKind,
29609
+ attempt.attemptId,
29610
+ true
29611
+ );
29612
+ const existingPayload = existingApproval?.payload;
29613
+ if (existingApproval && (existingApproval.operation !== "scheduler_trigger" || existingApproval.payloadKind !== "scheduler_trigger" || !existingPayload || existingPayload.dispatchId !== input.dispatchId || existingPayload.triggerId !== input.triggerId || existingPayload.occurrence !== input.occurrence || existingPayload.agentId !== input.agentId || typeof existingPayload.message !== "string")) {
29614
+ return {
29615
+ kind: "approval_terminal",
29616
+ approval: existingApproval,
29617
+ reason: "scheduler approval does not match the durable occurrence"
29618
+ };
29619
+ }
29620
+ if (existingApproval && ["pending", "approved", "delivering"].includes(existingApproval.status)) {
29621
+ return { kind: "approval_pending", approval: existingApproval };
29622
+ }
29623
+ if (existingApproval && existingApproval.status !== "delivered") {
29624
+ return {
29625
+ kind: "approval_terminal",
29626
+ approval: existingApproval,
29627
+ reason: existingApproval.deliveryError ?? existingApproval.decisionReason ?? `scheduler approval ${existingApproval.status}`
29628
+ };
29629
+ }
27685
29630
  if (!teamPolicyEnforcementEnabled()) {
27686
29631
  return db.raw.transaction(() => {
27687
29632
  const current = triggers_exports.get(db, input.triggerId);
27688
29633
  if (!current) throw new Error(`trigger not found: ${input.triggerId}`);
27689
- if (current.lastFiredAt !== null && current.lastFiredAt >= input.occurrence) {
27690
- return false;
29634
+ if (!input.materialized && current.lastFiredAt !== null && current.lastFiredAt >= input.occurrence) {
29635
+ return { kind: "already_claimed" };
29636
+ }
29637
+ if (current.lastFiredAt === null || current.lastFiredAt < input.occurrence) {
29638
+ triggers_exports.markFired(db, input.triggerId, input.occurrence);
27691
29639
  }
27692
- triggers_exports.markFired(db, input.triggerId, input.occurrence);
27693
29640
  input.onAllowed?.();
27694
- return true;
29641
+ communicationDecisionMetrics.allowed++;
29642
+ return { kind: "claimed" };
27695
29643
  })();
27696
29644
  }
27697
- const attempt = {
27698
- origin: "scheduler_trigger",
27699
- attemptKind: "scheduler_trigger",
27700
- attemptId: `${input.triggerId}:${input.occurrence}`
27701
- };
27702
29645
  const outcome = db.raw.transaction(
27703
29646
  () => {
27704
29647
  const current = triggers_exports.get(db, input.triggerId);
27705
29648
  if (!current) throw new Error(`trigger not found: ${input.triggerId}`);
27706
- if (current.lastFiredAt !== null && current.lastFiredAt >= input.occurrence) {
29649
+ if (!input.materialized && current.lastFiredAt !== null && current.lastFiredAt >= input.occurrence) {
27707
29650
  return {
27708
29651
  claimed: false,
27709
29652
  result: {
@@ -27725,14 +29668,36 @@ function claimSchedulerTrigger(db, input) {
27725
29668
  ...attempt
27726
29669
  };
27727
29670
  const result = authorizeInSnapshot(db, authorization);
27728
- triggers_exports.markFired(db, input.triggerId, input.occurrence);
29671
+ if (current.lastFiredAt === null || current.lastFiredAt < input.occurrence) {
29672
+ triggers_exports.markFired(db, input.triggerId, input.occurrence);
29673
+ }
27729
29674
  if (result.decision === "deny") {
27730
29675
  return {
27731
29676
  claimed: true,
27732
29677
  result: recordDenial(db, authorization, "scheduler_trigger", result)
27733
29678
  };
27734
29679
  }
29680
+ if (existingApproval?.status === "delivered" && result.decision !== "approval_required") {
29681
+ return {
29682
+ claimed: false,
29683
+ result,
29684
+ invalidApproval: "approved scheduler policy or membership changed"
29685
+ };
29686
+ }
27735
29687
  if (result.decision === "approval_required") {
29688
+ if (existingApproval?.status === "delivered") {
29689
+ const attemptMatches = existingPayload?.message === current.message;
29690
+ const refsMatch = JSON.stringify(result.policyRefs) === JSON.stringify(existingApproval.policyRefs) && JSON.stringify(result.requiredEdgeIds) === JSON.stringify(existingApproval.requiredEdgeIds);
29691
+ if (!attemptMatches || !refsMatch) {
29692
+ return {
29693
+ claimed: false,
29694
+ result,
29695
+ invalidApproval: !attemptMatches ? "approved scheduler occurrence changed" : "approved scheduler policy or membership changed"
29696
+ };
29697
+ }
29698
+ input.onAllowed?.();
29699
+ return { claimed: true, result };
29700
+ }
27736
29701
  return {
27737
29702
  claimed: true,
27738
29703
  result,
@@ -27747,24 +29712,43 @@ function claimSchedulerTrigger(db, input) {
27747
29712
  observeDenial(outcome.result, attempt);
27748
29713
  throw new CommunicationDeniedError(outcome.result, attempt.attemptKind, attempt.attemptId);
27749
29714
  }
29715
+ if (outcome.invalidApproval && existingApproval) {
29716
+ return {
29717
+ kind: "approval_terminal",
29718
+ approval: existingApproval,
29719
+ reason: outcome.invalidApproval
29720
+ };
29721
+ }
27750
29722
  if (outcome.approval) {
27751
- communicationApprovals_exports.request(
29723
+ const approval = communicationApprovals_exports.request(
27752
29724
  db,
27753
29725
  outcome.approval.input,
27754
29726
  "scheduler_trigger",
27755
29727
  outcome.approval.authorization,
27756
- "agent_turn",
29728
+ "scheduler_trigger",
27757
29729
  {
29730
+ dispatchId: input.dispatchId,
29731
+ triggerId: input.triggerId,
29732
+ occurrence: input.occurrence,
27758
29733
  agentId: input.agentId,
27759
- message: outcome.approval.message,
27760
- attachments: []
29734
+ message: outcome.approval.message
27761
29735
  },
27762
29736
  { requester: "scheduler" }
27763
29737
  );
27764
- return false;
29738
+ if (["pending", "approved", "delivering"].includes(approval.status)) {
29739
+ return { kind: "approval_pending", approval };
29740
+ }
29741
+ if (approval.status === "delivered") {
29742
+ return { kind: "approval_pending", approval };
29743
+ }
29744
+ return {
29745
+ kind: "approval_terminal",
29746
+ approval,
29747
+ reason: approval.deliveryError ?? approval.decisionReason ?? `scheduler approval ${approval.status}`
29748
+ };
27765
29749
  }
27766
29750
  if (outcome.claimed) communicationDecisionMetrics.allowed++;
27767
- return outcome.claimed;
29751
+ return outcome.claimed ? { kind: "claimed" } : { kind: "already_claimed" };
27768
29752
  }
27769
29753
  function claimDeliverableInbox(db, agentId, onAllowed) {
27770
29754
  if (!teamPolicyEnforcementEnabled()) {
@@ -29853,6 +31837,8 @@ function renderFrame(frame, mode) {
29853
31837
  return mode === "verbose" ? `\u2715 ${ev.name}: ${ev.error}` : null;
29854
31838
  case "file":
29855
31839
  return null;
31840
+ case "command_approval":
31841
+ return null;
29856
31842
  case "user_message":
29857
31843
  case "assistant_delta":
29858
31844
  return null;
@@ -29979,7 +31965,7 @@ async function* runAgentTurn(agentId, rawMessage, opts = {}) {
29979
31965
  const authorization = opts.authorization ?? {
29980
31966
  origin: "internal_turn",
29981
31967
  attemptKind: "turn",
29982
- attemptId: randomUUID16()
31968
+ attemptId: randomUUID18()
29983
31969
  };
29984
31970
  const releaseLease = opts.acquiredLeaseRelease ?? await acquireAgentLifecycleLease(agentId);
29985
31971
  let agent;
@@ -30009,18 +31995,25 @@ async function* runAgentTurn(agentId, rawMessage, opts = {}) {
30009
31995
  releaseLease();
30010
31996
  }
30011
31997
  try {
31998
+ const env = mergeSecretsIntoEnv(db, authToken);
31999
+ const shellSecurity = resolveShellSecurityConfig(env);
30012
32000
  const attachments = opts.attachments ?? [];
30013
32001
  const images = attachments.filter((a) => a.mimeType.startsWith("image/"));
30014
32002
  const docs = attachments.filter((a) => !a.mimeType.startsWith("image/"));
30015
- const fileNote = saveInputFiles(agent.agent.dir, docs);
32003
+ const fileNote = saveInputFiles(
32004
+ agent.agent.dir,
32005
+ docs,
32006
+ shellSecurity.sandboxMode === "docker" ? { referenceDir: SANDBOX_INPUTS_DIR } : {}
32007
+ );
30016
32008
  const message = fileNote ? rawMessage ? `${rawMessage}
30017
32009
 
30018
32010
  ${fileNote}` : fileNote : rawMessage;
30019
32011
  const enabledProviders = Array.from(providerState_exports.listEnabled(db));
30020
- const env = mergeSecretsIntoEnv(db, authToken);
30021
32012
  const messagingHost = createDbMessagingHost(db);
30022
32013
  const userMdHost = createDbUserMdHost(db, paths);
30023
- const { apiKey } = await resolveAgentApiKey(db, authToken, agent);
32014
+ const { apiKey, refreshApiKey } = await resolveAgentApiKey(db, authToken, agent, {
32015
+ withRefresher: true
32016
+ });
30024
32017
  const browserEnabled = isBrowserEnabled(env);
30025
32018
  const browserHost = browserEnabled ? createBrowserHost(resolveBrowserConfig(env)) : void 0;
30026
32019
  const mcp = await resolveMcpForTurn(db, env, authToken);
@@ -30034,7 +32027,9 @@ ${fileNote}` : fileNote : rawMessage;
30034
32027
  apiKey,
30035
32028
  browserEnabled,
30036
32029
  mcpTools: mcp?.tools,
30037
- images
32030
+ images,
32031
+ turnId: authorization.attemptId,
32032
+ bashApprovalMode: opts.bashApprovalMode ?? "auto_deny"
30038
32033
  },
30039
32034
  {
30040
32035
  signal: controller.signal,
@@ -30042,7 +32037,9 @@ ${fileNote}` : fileNote : rawMessage;
30042
32037
  messagingHost,
30043
32038
  userMdHost,
30044
32039
  browserHost,
30045
- mcpHost: mcp?.host
32040
+ mcpHost: mcp?.host,
32041
+ bashApprovalHost: commandApprovalRegistry,
32042
+ apiKeyRefreshHost: refreshApiKey ? { refresh: refreshApiKey } : void 0
30046
32043
  }
30047
32044
  )) {
30048
32045
  void mirrorAgentTurnFrame(
@@ -30134,6 +32131,13 @@ function validateCron(expr) {
30134
32131
  parseCron(expr);
30135
32132
  }
30136
32133
 
32134
+ // ../daemon/src/lib/turn-outcome.ts
32135
+ function turnFrameFailure(frame) {
32136
+ if (frame.kind === "fatal") return frame.error;
32137
+ if (frame.kind === "event" && frame.event.type === "error") return frame.event.error;
32138
+ return null;
32139
+ }
32140
+
30137
32141
  // ../daemon/src/lib/scheduler.ts
30138
32142
  var SCHEDULER_KEY = /* @__PURE__ */ Symbol.for("bazilion.scheduler");
30139
32143
  var TICK_MS = Number(process.env.BAZILION_SCHEDULER_TICK_MS ?? 5e3);
@@ -30181,65 +32185,108 @@ function scheduledOccurrence(t, now) {
30181
32185
  if (every <= 0) return now;
30182
32186
  return baseline + Math.floor((now - baseline) / every) * every;
30183
32187
  }
30184
- async function fireTrigger(t, occurrence) {
32188
+ async function fireTrigger(dispatch2) {
30185
32189
  const s = state2();
30186
- if (s.firing.has(t.id)) return;
30187
- s.firing.add(t.id);
32190
+ const key2 = `trigger-dispatch:${dispatch2.id}`;
32191
+ if (s.firing.has(key2)) return;
32192
+ s.firing.add(key2);
30188
32193
  const ctx = getCtx();
30189
- const releaseLease = await acquireAgentLifecycleLease(t.agentId);
32194
+ const releaseLease = await acquireAgentLifecycleLease(dispatch2.agentId);
32195
+ const claimedDispatch = triggerDispatches_exports.claim(ctx.db, dispatch2.id);
32196
+ if (!claimedDispatch) {
32197
+ releaseLease();
32198
+ s.firing.delete(key2);
32199
+ return;
32200
+ }
32201
+ const t = triggers_exports.get(ctx.db, claimedDispatch.triggerId);
32202
+ if (!t?.enabled) {
32203
+ triggerDispatches_exports.fail(ctx.db, claimedDispatch.id, "trigger is disabled or deleted", {
32204
+ maxAttempts: 1
32205
+ });
32206
+ releaseLease();
32207
+ s.firing.delete(key2);
32208
+ return;
32209
+ }
30190
32210
  if (isActiveAgent(t.agentId)) {
30191
32211
  releaseLease();
30192
- s.firing.delete(t.id);
32212
+ triggerDispatches_exports.defer(ctx.db, claimedDispatch.id);
32213
+ s.firing.delete(key2);
30193
32214
  return;
30194
32215
  }
30195
32216
  const controller = new AbortController();
30196
32217
  let registered = false;
30197
32218
  try {
30198
- const claimed = claimSchedulerTrigger(ctx.db, {
32219
+ const claim2 = claimSchedulerTrigger(ctx.db, {
32220
+ dispatchId: claimedDispatch.id,
30199
32221
  triggerId: t.id,
30200
32222
  agentId: t.agentId,
30201
- occurrence,
32223
+ occurrence: claimedDispatch.scheduledAt,
32224
+ materialized: true,
30202
32225
  onAllowed: () => {
30203
32226
  registerAgent(t.agentId, controller);
30204
32227
  registered = true;
30205
32228
  }
30206
32229
  });
30207
- if (!claimed) {
32230
+ if (claim2.kind === "already_claimed") {
32231
+ releaseLease();
32232
+ triggerDispatches_exports.succeed(ctx.db, claimedDispatch.id);
32233
+ s.firing.delete(key2);
32234
+ return;
32235
+ }
32236
+ if (claim2.kind === "approval_pending") {
30208
32237
  releaseLease();
30209
- s.firing.delete(t.id);
32238
+ triggerDispatches_exports.defer(ctx.db, claimedDispatch.id, Date.now() + TICK_MS);
32239
+ s.firing.delete(key2);
32240
+ return;
32241
+ }
32242
+ if (claim2.kind === "approval_terminal") {
32243
+ releaseLease();
32244
+ triggerDispatches_exports.fail(ctx.db, claimedDispatch.id, claim2.reason, { maxAttempts: 1 });
32245
+ s.firing.delete(key2);
30210
32246
  return;
30211
32247
  }
30212
32248
  } catch (err) {
30213
32249
  if (err instanceof CommunicationDeniedError) {
30214
32250
  releaseLease();
30215
- s.firing.delete(t.id);
32251
+ triggerDispatches_exports.fail(ctx.db, claimedDispatch.id, err.message, { maxAttempts: 1 });
32252
+ s.firing.delete(key2);
30216
32253
  return;
30217
32254
  }
30218
32255
  releaseLease();
30219
32256
  if (registered) unregisterAgent(t.agentId);
30220
32257
  console.error(`[scheduler] markFired failed for ${t.id}:`, err);
30221
- s.firing.delete(t.id);
32258
+ triggerDispatches_exports.fail(ctx.db, claimedDispatch.id, err.message);
32259
+ s.firing.delete(key2);
30222
32260
  return;
30223
32261
  }
32262
+ let failure = null;
30224
32263
  try {
30225
32264
  for await (const frame of runAgentTurn(t.agentId, t.message, {
30226
32265
  authorization: {
30227
32266
  origin: "scheduler_trigger",
30228
32267
  attemptKind: "scheduler_trigger",
30229
- attemptId: `${t.id}:${occurrence}`
32268
+ attemptId: `${t.id}:${claimedDispatch.scheduledAt}`
30230
32269
  },
30231
32270
  acquiredLeaseRelease: releaseLease,
30232
32271
  controller,
30233
- alreadyRegistered: true
32272
+ alreadyRegistered: true,
32273
+ bashApprovalMode: "auto_deny"
30234
32274
  })) {
30235
- if (frame.kind === "fatal") {
30236
- console.error(`[scheduler] trigger ${t.id} fatal:`, frame.error);
32275
+ const frameFailure = turnFrameFailure(frame);
32276
+ if (frameFailure) {
32277
+ console.error(`[scheduler] trigger ${t.id} failed:`, frameFailure);
32278
+ failure ??= frameFailure;
30237
32279
  }
30238
32280
  }
30239
32281
  } catch (err) {
30240
32282
  console.error(`[scheduler] trigger ${t.id} unexpected throw:`, err);
32283
+ failure = err.message;
30241
32284
  } finally {
30242
- s.firing.delete(t.id);
32285
+ if (controller.signal.aborted) {
32286
+ triggerDispatches_exports.cancelRunning(ctx.db, claimedDispatch.id, "agent turn cancelled");
32287
+ } else if (failure) triggerDispatches_exports.fail(ctx.db, claimedDispatch.id, failure);
32288
+ else triggerDispatches_exports.succeed(ctx.db, claimedDispatch.id);
32289
+ s.firing.delete(key2);
30243
32290
  }
30244
32291
  }
30245
32292
  function decodeText2(payload) {
@@ -30322,7 +32369,8 @@ async function fireInboxWake(agentId) {
30322
32369
  },
30323
32370
  acquiredLeaseRelease: releaseLease,
30324
32371
  controller,
30325
- alreadyRegistered: true
32372
+ alreadyRegistered: true,
32373
+ bashApprovalMode: "auto_deny"
30326
32374
  })) {
30327
32375
  if (frame.kind === "fatal") {
30328
32376
  console.error(`[scheduler] inbox wake ${agentId} fatal:`, frame.error);
@@ -30343,10 +32391,10 @@ async function tick() {
30343
32391
  const s = state2();
30344
32392
  if (s.stopped) return;
30345
32393
  const now = Date.now();
32394
+ const ctx = getCtx();
30346
32395
  let triggers;
30347
32396
  let recipients = [];
30348
32397
  try {
30349
- const ctx = getCtx();
30350
32398
  communicationApprovals_exports.expirePending(ctx.db, now);
30351
32399
  triggers = triggers_exports.listEnabled(ctx.db);
30352
32400
  recipients = messages_exports.listRecipientsWithUnread(ctx.db);
@@ -30356,12 +32404,26 @@ async function tick() {
30356
32404
  }
30357
32405
  for (const t of triggers) {
30358
32406
  if (isDue(t, now, s.cronCache)) {
30359
- void fireTrigger(t, scheduledOccurrence(t, now));
32407
+ const occurrence = scheduledOccurrence(t, now);
32408
+ if (!triggerDispatches_exports.hasOpenForTrigger(ctx.db, t.id)) {
32409
+ triggerDispatches_exports.materialize(ctx.db, {
32410
+ triggerId: t.id,
32411
+ agentId: t.agentId,
32412
+ scheduledAt: occurrence,
32413
+ now
32414
+ });
32415
+ }
32416
+ triggers_exports.markFired(ctx.db, t.id, occurrence);
30360
32417
  }
30361
32418
  }
32419
+ const work = [];
32420
+ for (const dispatch2 of triggerDispatches_exports.listClaimable(ctx.db, now)) {
32421
+ work.push(fireTrigger(dispatch2));
32422
+ }
30362
32423
  for (const agentId of recipients) {
30363
- void fireInboxWake(agentId);
32424
+ work.push(fireInboxWake(agentId));
30364
32425
  }
32426
+ await Promise.allSettled(work);
30365
32427
  }
30366
32428
  function startScheduler() {
30367
32429
  const s = state2();
@@ -30397,7 +32459,7 @@ function bootstrap(paths) {
30397
32459
  }
30398
32460
  if (!existsSync16(paths.authFile)) {
30399
32461
  const created = webTokens_exports.create(db, "bootstrap");
30400
- writeFileSync10(paths.authFile, `${JSON.stringify({ token: created.token }, null, 2)}
32462
+ writeFileSync11(paths.authFile, `${JSON.stringify({ token: created.token }, null, 2)}
30401
32463
  `, {
30402
32464
  mode: 384
30403
32465
  });
@@ -30425,6 +32487,12 @@ function getCtx() {
30425
32487
  }
30426
32488
  return { db: _db, paths: _paths, authToken: _authToken };
30427
32489
  }
32490
+ function closeCtxForShutdown() {
32491
+ if (!_db) return;
32492
+ _db.close();
32493
+ _db = null;
32494
+ _authToken = null;
32495
+ }
30428
32496
 
30429
32497
  // ../daemon/src/lib/auth.ts
30430
32498
  function isValidToken(token) {
@@ -30473,9 +32541,9 @@ async function authMiddleware(c, next) {
30473
32541
  }
30474
32542
 
30475
32543
  // ../daemon/src/routes/agents.ts
30476
- import { randomUUID as randomUUID17 } from "crypto";
30477
- import { existsSync as existsSync17, readdirSync as readdirSync9, readFileSync as readFileSync13, rmSync as rmSync12 } from "fs";
30478
- import { join as join19 } from "path";
32544
+ import { randomUUID as randomUUID19 } from "crypto";
32545
+ import { existsSync as existsSync17, readdirSync as readdirSync10, readFileSync as readFileSync13, rmSync as rmSync13 } from "fs";
32546
+ import { join as join21 } from "path";
30479
32547
 
30480
32548
  // ../../packages/api-types/src/entities.ts
30481
32549
  var REASONING_LEVELS = [
@@ -31610,6 +33678,7 @@ async function drainLoop(agentId) {
31610
33678
  const batchAttempt = q2[0]?.authorization;
31611
33679
  for await (const _frame of runAgentTurn(agentId, message, {
31612
33680
  attachments,
33681
+ bashApprovalMode: "auto_deny",
31613
33682
  ...batchAttempt ? { authorization: batchAttempt } : {}
31614
33683
  })) {
31615
33684
  void _frame;
@@ -32437,7 +34506,7 @@ agentsRouter.get("/", (c) => {
32437
34506
  const { db, paths, authToken } = getCtx();
32438
34507
  const agents = agents_exports.list(db, { includeArchived }).map((agent) => ({
32439
34508
  ...agent,
32440
- identity: loadIdentityFromFile(join19(agent.dir, "IDENTITY.md"))
34509
+ identity: loadIdentityFromFile(join21(agent.dir, "IDENTITY.md"))
32441
34510
  }));
32442
34511
  return c.json(agents);
32443
34512
  });
@@ -32843,7 +34912,13 @@ agentsRouter.get("/:id/triggers", (c) => {
32843
34912
  const { db, paths, authToken } = getCtx();
32844
34913
  const agent = agents_exports.get(db, c.req.param("id"));
32845
34914
  if (!agent) return c.json({ error: `agent not found: ${c.req.param("id")}` }, 404);
32846
- return c.json({ triggers: triggers_exports.listForAgent(db, agent.id) });
34915
+ const triggers = triggers_exports.listForAgent(db, agent.id);
34916
+ return c.json({
34917
+ triggers,
34918
+ dispatches: triggers.flatMap(
34919
+ (trigger) => triggerDispatches_exports.listForTrigger(db, trigger.id, 10)
34920
+ )
34921
+ });
32847
34922
  });
32848
34923
  agentsRouter.post("/:id/triggers", async (c) => {
32849
34924
  const body = await c.req.json().catch(() => null);
@@ -32988,11 +35063,14 @@ agentsRouter.post("/:id/chat", async (c) => {
32988
35063
  if (typeof message !== "string") {
32989
35064
  return c.json({ error: "message is required" }, 400);
32990
35065
  }
35066
+ if (body.bashApprovalMode !== void 0 && body.bashApprovalMode !== "interactive" && body.bashApprovalMode !== "auto_deny") {
35067
+ return c.json({ error: 'bashApprovalMode must be "interactive" or "auto_deny"' }, 400);
35068
+ }
32991
35069
  const attachments = sanitizeAttachments(body.attachments);
32992
35070
  if (!message && attachments.length === 0) {
32993
35071
  return c.json({ error: "message or an attachment is required" }, 400);
32994
35072
  }
32995
- const requestAttemptId = c.req.header("x-request-id") ?? randomUUID17();
35073
+ const requestAttemptId = c.req.header("x-request-id") ?? randomUUID19();
32996
35074
  try {
32997
35075
  authorizeUserIngress(db, id, {
32998
35076
  origin: "http_chat",
@@ -33043,7 +35121,8 @@ agentsRouter.post("/:id/chat", async (c) => {
33043
35121
  origin: "http_chat",
33044
35122
  attemptKind: "http_chat_ingress",
33045
35123
  attemptId: requestAttemptId
33046
- }
35124
+ },
35125
+ bashApprovalMode: body.bashApprovalMode ?? "auto_deny"
33047
35126
  })) {
33048
35127
  authorizeHttpChatFrame(db, id, requestAttemptId, frameIndex, frame);
33049
35128
  frameIndex++;
@@ -33099,9 +35178,9 @@ agentsRouter.post("/:id/chat/compact", async (c) => {
33099
35178
  const id = resolveAgentIdParam(db, c.req.param("id"));
33100
35179
  if (!agents_exports.get(db, id)) return c.json({ error: "agent not found" }, 404);
33101
35180
  const resolved = resolveAgent(db, paths, id);
33102
- const memory = qmdBackend(join19(resolved.team.path, "memory"));
33103
- await memory.init();
33104
35181
  const env = mergeSecretsIntoEnv(db, authToken);
35182
+ const memory = qmdBackend(join21(resolved.team.path, "memory"));
35183
+ await memory.init();
33105
35184
  const apiKeyResolution = await resolveAgentApiKey(db, authToken, resolved, {
33106
35185
  withRefresher: true
33107
35186
  });
@@ -33148,26 +35227,33 @@ agentsRouter.get("/:id/chat/context", async (c) => {
33148
35227
  const id = resolveAgentIdParam(db, c.req.param("id"));
33149
35228
  if (!agents_exports.get(db, id)) return c.json({ error: "agent not found" }, 404);
33150
35229
  const resolved = resolveAgent(db, paths, id);
35230
+ const env = mergeSecretsIntoEnv(db, authToken);
35231
+ const shellSecurity = resolveShellSecurityConfig(env);
35232
+ const promptSkills = loadPromptSkills(paths.skillsDir, resolved.skills);
33151
35233
  const files = [];
33152
35234
  for (const file of CONTEXT_FILE_ORDER2) {
33153
- const path = join19(resolved.agent.dir, file);
35235
+ const path = join21(resolved.agent.dir, file);
33154
35236
  if (!existsSync17(path)) continue;
33155
35237
  const content = readFileSync13(path, "utf8").trimEnd();
33156
35238
  if (!content) continue;
33157
35239
  const chars = content.length + file.length + 6;
33158
35240
  files.push({ name: file, chars, tokens: estimateTokens(chars) });
33159
35241
  }
33160
- const systemPromptText = buildSystemPrompt(resolved);
35242
+ const systemPromptText = buildSystemPrompt(resolved, {
35243
+ skills: promptSkills,
35244
+ sandboxMode: shellSecurity.sandboxMode
35245
+ });
33161
35246
  const systemPromptChars = systemPromptText.length;
33162
- const skillsListChars = resolved.skills.length > 0 ? `# Available Skills
33163
-
33164
- You have access to the following skills: ${resolved.skills.join(", ")}.`.length : 0;
35247
+ const skillsListChars = promptSkills.reduce(
35248
+ (total, skill) => total + skill.name.length + skill.description.length + skill.body.length,
35249
+ 0
35250
+ );
33165
35251
  const teamLines = [
33166
35252
  "# Team",
33167
35253
  "",
33168
35254
  `- ${resolved.team.id} (${resolved.team.name}): ${resolved.team.path}`,
33169
35255
  "",
33170
- "Your team is where work product lives \u2014 code, docs, artefacts, shared scratch. It may be shared with other agents in the same team. Your coding tools (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`) are rooted at the team directory. Never use these tools to edit your identity/soul/behaviour files \u2014 those live in your home and are reached via `home_write` / `home_read`."
35256
+ "Your team is where work product lives \u2014 code, docs, artefacts, shared scratch. It may be shared with other agents in the same team. Use the workspace tools currently exposed to work there. Never use workspace tools to edit your identity/soul/behaviour files \u2014 those live in your private home and are reached via `home_write` / `home_read`."
33171
35257
  ];
33172
35258
  const teamListChars = teamLines.join("\n").length;
33173
35259
  const userMdChars = resolved.team.userMd.trim() ? `# About the User
@@ -33176,9 +35262,8 @@ Read-only context about the human you're working with in this team. You cannot e
33176
35262
 
33177
35263
  ${resolved.team.userMd.trim()}`.length : 0;
33178
35264
  const memoryHintChars = "# Memory\n\nYou have a persistent memory backend. Use `memory_write` to remember things across sessions, and `memory_search` / `memory_read` / `memory_list` to recall them. Always check memory at the start of a session if the user might have told you something important before.".length;
33179
- const memory = qmdBackend(join19(resolved.team.path, "memory"));
35265
+ const memory = qmdBackend(join21(resolved.team.path, "memory"));
33180
35266
  await memory.init();
33181
- const env = mergeSecretsIntoEnv(db, authToken);
33182
35267
  const apiKeyResolution = await resolveAgentApiKey(db, authToken, resolved, {
33183
35268
  withRefresher: true
33184
35269
  });
@@ -33286,13 +35371,13 @@ agentsRouter.post("/:id/chat/reset", (c) => {
33286
35371
  const id = resolveAgentIdParam(db, c.req.param("id"));
33287
35372
  const agent = agents_exports.get(db, id);
33288
35373
  if (!agent) return c.json({ error: "agent not found" }, 404);
33289
- const sessionsDir = join19(paths.agentDir(agent.id), "sessions");
35374
+ const sessionsDir = join21(paths.agentDir(agent.id), "sessions");
33290
35375
  let deleted = 0;
33291
35376
  if (existsSync17(sessionsDir)) {
33292
- for (const file of readdirSync9(sessionsDir)) {
35377
+ for (const file of readdirSync10(sessionsDir)) {
33293
35378
  if (!file.endsWith(".jsonl")) continue;
33294
35379
  try {
33295
- rmSync12(join19(sessionsDir, file));
35380
+ rmSync13(join21(sessionsDir, file));
33296
35381
  deleted++;
33297
35382
  } catch {
33298
35383
  }
@@ -33315,7 +35400,7 @@ agentsRouter.post("/:id/chat/truncate", async (c) => {
33315
35400
  const id = resolveAgentIdParam(db, c.req.param("id"));
33316
35401
  if (!agents_exports.get(db, id)) return c.json({ error: "agent not found" }, 404);
33317
35402
  const resolved = resolveAgent(db, paths, id);
33318
- const memory = qmdBackend(join19(resolved.team.path, "memory"));
35403
+ const memory = qmdBackend(join21(resolved.team.path, "memory"));
33319
35404
  await memory.init();
33320
35405
  const env = mergeSecretsIntoEnv(db, authToken);
33321
35406
  const apiKeyResolution = await resolveAgentApiKey(db, authToken, resolved, {
@@ -33434,6 +35519,37 @@ approvalsRouter.post("/:id/approve", async (c) => {
33434
35519
  const { db } = getCtx();
33435
35520
  const id = c.req.param("id");
33436
35521
  try {
35522
+ const pending = communicationApprovals_exports.get(
35523
+ db,
35524
+ id,
35525
+ true
35526
+ );
35527
+ if (!pending) return c.json({ error: `approval_not_found: ${id}` }, 404);
35528
+ if (pending.operation === "scheduler_trigger" || pending.payloadKind === "scheduler_trigger") {
35529
+ const granted = communicationApprovals_exports.grantSchedulerTrigger(
35530
+ db,
35531
+ id,
35532
+ "authenticated_operator",
35533
+ (approval) => authorizeInSnapshot(db, {
35534
+ source: approval.source,
35535
+ target: approval.target,
35536
+ origin: approval.origin,
35537
+ attemptKind: approval.attemptKind,
35538
+ attemptId: approval.attemptId
35539
+ }),
35540
+ (approval) => validateSchedulerGrant(db, approval)
35541
+ );
35542
+ if (!granted.granted) {
35543
+ return c.json(
35544
+ {
35545
+ error: granted.failureKind === "revalidation" ? "approval revalidation failed" : "approval delivery failed",
35546
+ detail: granted.error
35547
+ },
35548
+ 409
35549
+ );
35550
+ }
35551
+ return c.json(granted.approval);
35552
+ }
33437
35553
  const claimed = communicationApprovals_exports.claimDelivery(
33438
35554
  db,
33439
35555
  id,
@@ -33460,19 +35576,45 @@ approvalsRouter.post("/:id/approve", async (c) => {
33460
35576
  return approvalError(error2);
33461
35577
  }
33462
35578
  });
35579
+ function validateSchedulerGrant(db, approval) {
35580
+ const payload = approval.payload;
35581
+ if (typeof payload.dispatchId !== "string" || typeof payload.triggerId !== "string" || typeof payload.occurrence !== "number" || typeof payload.agentId !== "string" || typeof payload.message !== "string") {
35582
+ return "scheduler approval payload is incomplete";
35583
+ }
35584
+ const trigger = triggers_exports.get(db, payload.triggerId);
35585
+ if (!trigger?.enabled) return "scheduled trigger is disabled or deleted";
35586
+ if (trigger.agentId !== payload.agentId) return "scheduled trigger target changed";
35587
+ if (trigger.message !== payload.message) return "scheduled trigger message changed";
35588
+ const dispatch2 = triggerDispatches_exports.get(db, payload.dispatchId);
35589
+ if (!dispatch2) return "scheduled dispatch no longer exists";
35590
+ if (dispatch2.triggerId !== payload.triggerId || dispatch2.agentId !== payload.agentId || dispatch2.scheduledAt !== payload.occurrence) {
35591
+ return "scheduled dispatch no longer matches the approved occurrence";
35592
+ }
35593
+ if (["succeeded", "failed", "cancelled"].includes(dispatch2.status)) {
35594
+ return `scheduled dispatch is already ${dispatch2.status}`;
35595
+ }
35596
+ return null;
35597
+ }
33463
35598
  async function deliver(approval) {
33464
35599
  if (approval.payloadKind === "agent_message") {
33465
35600
  const payload = approval.payload;
33466
35601
  messages_exports.send(getCtx().db, payload);
33467
35602
  return;
33468
35603
  }
35604
+ if (approval.payloadKind === "scheduler_trigger") {
35605
+ return;
35606
+ }
33469
35607
  if (approval.payloadKind === "agent_turn") {
33470
35608
  const payload = approval.payload;
33471
- for await (const _frame of runAgentTurn(payload.agentId, payload.message, {
35609
+ let failure = null;
35610
+ for await (const frame of runAgentTurn(payload.agentId, payload.message, {
33472
35611
  attachments: payload.attachments ?? [],
33473
- skipUserIngress: true
35612
+ skipUserIngress: true,
35613
+ bashApprovalMode: "auto_deny"
33474
35614
  })) {
35615
+ failure ??= turnFrameFailure(frame);
33475
35616
  }
35617
+ if (failure) throw new Error(failure);
33476
35618
  return;
33477
35619
  }
33478
35620
  if (approval.payloadKind === "http_chat_frame") {
@@ -33514,11 +35656,15 @@ async function deliver(approval) {
33514
35656
  transport.threadId ? { message_thread_id: transport.threadId } : {}
33515
35657
  );
33516
35658
  }
33517
- for await (const _frame of runAgentTurn(payload.agentId, text2, {
35659
+ let failure = null;
35660
+ for await (const frame of runAgentTurn(payload.agentId, text2, {
33518
35661
  attachments,
33519
- skipUserIngress: true
35662
+ skipUserIngress: true,
35663
+ bashApprovalMode: "auto_deny"
33520
35664
  })) {
35665
+ failure ??= turnFrameFailure(frame);
33521
35666
  }
35667
+ if (failure) throw new Error(failure);
33522
35668
  return;
33523
35669
  }
33524
35670
  if (approval.payloadKind.startsWith("telegram_")) {
@@ -33589,7 +35735,7 @@ function approvalError(error2) {
33589
35735
  }
33590
35736
 
33591
35737
  // ../daemon/src/routes/auth-login.ts
33592
- import { spawn as spawn3 } from "child_process";
35738
+ import { spawn as spawn4 } from "child_process";
33593
35739
  import { Hono as Hono3 } from "hono";
33594
35740
  import { setCookie } from "hono/cookie";
33595
35741
  var authRouter = new Hono3();
@@ -33697,7 +35843,7 @@ function openBrowser(url2) {
33697
35843
  const platform = process.platform;
33698
35844
  const [cmd, ...args] = platform === "darwin" ? ["open", url2] : platform === "win32" ? ["cmd", "/c", "start", '""', url2] : ["xdg-open", url2];
33699
35845
  try {
33700
- const child = spawn3(cmd, args, { stdio: "ignore", detached: true });
35846
+ const child = spawn4(cmd, args, { stdio: "ignore", detached: true });
33701
35847
  child.unref();
33702
35848
  child.on("error", () => {
33703
35849
  });
@@ -34049,13 +36195,395 @@ messagesRouter.patch("/:id", async (c) => {
34049
36195
  });
34050
36196
 
34051
36197
  // ../daemon/src/routes/misc.ts
34052
- import { spawn as spawn4 } from "child_process";
34053
36198
  import { existsSync as existsSync18 } from "fs";
34054
36199
  import { homedir as homedir2 } from "os";
34055
- import { join as join20 } from "path";
36200
+ import { join as join24 } from "path";
34056
36201
  import { Hono as Hono8 } from "hono";
36202
+
36203
+ // ../daemon/src/lib/backup.ts
36204
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync14 } from "fs";
36205
+ import { tmpdir as tmpdir3 } from "os";
36206
+ import { join as join22, resolve as resolve5 } from "path";
36207
+ import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
36208
+ import { create as create2 } from "tar";
36209
+ var snapshotTail = Promise.resolve();
36210
+ async function serializeSnapshot(work) {
36211
+ let release = () => {
36212
+ };
36213
+ const previous = snapshotTail;
36214
+ snapshotTail = new Promise((resolve7) => {
36215
+ release = resolve7;
36216
+ });
36217
+ await previous;
36218
+ try {
36219
+ return await work();
36220
+ } finally {
36221
+ release();
36222
+ }
36223
+ }
36224
+ function assertSnapshotIntegrity(path) {
36225
+ const snapshot = new DatabaseSync2(path, { readOnly: true });
36226
+ try {
36227
+ const rows = snapshot.prepare("PRAGMA integrity_check").all();
36228
+ if (rows.length !== 1 || rows[0]?.integrity_check !== "ok") {
36229
+ const detail3 = rows.map((row) => row.integrity_check).join("; ") || "no result";
36230
+ throw new Error(`SQLite snapshot failed integrity_check: ${detail3}`);
36231
+ }
36232
+ const foreignKeyErrors = snapshot.prepare("PRAGMA foreign_key_check").all();
36233
+ if (foreignKeyErrors.length > 0) {
36234
+ throw new Error(
36235
+ `SQLite snapshot failed foreign_key_check with ${foreignKeyErrors.length} violation(s)`
36236
+ );
36237
+ }
36238
+ } finally {
36239
+ snapshot.close();
36240
+ }
36241
+ }
36242
+ async function createBackupSnapshot(db) {
36243
+ return serializeSnapshot(async () => {
36244
+ const directory = mkdtempSync3(join22(tmpdir3(), "bazilion-backup-"));
36245
+ const database = join22(directory, "bazilion.snapshot");
36246
+ let cleaned = false;
36247
+ const cleanup = () => {
36248
+ if (cleaned) return;
36249
+ try {
36250
+ rmSync14(directory, { recursive: true, force: true });
36251
+ cleaned = true;
36252
+ } catch {
36253
+ }
36254
+ };
36255
+ try {
36256
+ await db.backupTo(database);
36257
+ assertSnapshotIntegrity(database);
36258
+ return { directory, database, cleanup };
36259
+ } catch (error2) {
36260
+ cleanup();
36261
+ throw error2;
36262
+ }
36263
+ });
36264
+ }
36265
+ function createBackupArchive(paths, snapshot) {
36266
+ const snapshotPath = resolve5(snapshot.database);
36267
+ return create2(
36268
+ {
36269
+ cwd: paths.home,
36270
+ gzip: true,
36271
+ portable: true,
36272
+ // The snapshot is an absolute, daemon-created path outside `cwd`. Its
36273
+ // entry name is rewritten below before the header is emitted. Every
36274
+ // user-controlled home entry still originates from the relative `.`.
36275
+ preservePaths: true,
36276
+ strict: true,
36277
+ filter(path) {
36278
+ if (resolve5(path) === snapshotPath) return true;
36279
+ const normalized = path.replaceAll("\\", "/").replace(/^\.\//, "");
36280
+ if (normalized === "bazilion.db" || normalized.startsWith("bazilion.db-")) return false;
36281
+ if (normalized === "daemon-runtime.json" || normalized.startsWith("daemon-runtime.json.reclaim-")) {
36282
+ return false;
36283
+ }
36284
+ if (/^teams\/[^/]+\/memory\/\.qmd-index\.sqlite/.test(normalized)) return false;
36285
+ return true;
36286
+ },
36287
+ onWriteEntry(entry) {
36288
+ if (resolve5(entry.absolute) === snapshotPath) entry.path = "./bazilion.db";
36289
+ }
36290
+ },
36291
+ [".", snapshotPath]
36292
+ );
36293
+ }
36294
+
36295
+ // ../daemon/src/lib/daemon-liveness.ts
36296
+ import { createHash as createHash5, randomUUID as randomUUID20 } from "crypto";
36297
+ import {
36298
+ chmodSync as chmodSync2,
36299
+ closeSync as closeSync4,
36300
+ fstatSync as fstatSync3,
36301
+ fsyncSync,
36302
+ ftruncateSync,
36303
+ futimesSync,
36304
+ lstatSync as lstatSync3,
36305
+ mkdirSync as mkdirSync9,
36306
+ openSync as openSync4,
36307
+ readFileSync as readFileSync14,
36308
+ realpathSync as realpathSync4,
36309
+ renameSync as renameSync3,
36310
+ rmSync as rmSync15,
36311
+ writeSync
36312
+ } from "fs";
36313
+ import { basename as basename4, dirname as dirname5, join as join23, resolve as resolve6 } from "path";
36314
+ var HEARTBEAT_MS = 2e3;
36315
+ function canonicalHomeIdentity(home) {
36316
+ const absolute = resolve6(home);
36317
+ try {
36318
+ return realpathSync4.native(absolute);
36319
+ } catch (error2) {
36320
+ if (errorCode(error2) !== "ENOENT") throw error2;
36321
+ const parent = dirname5(absolute);
36322
+ if (parent === absolute) throw error2;
36323
+ return join23(canonicalHomeIdentity(parent), basename4(absolute));
36324
+ }
36325
+ }
36326
+ function daemonLivenessPath(home) {
36327
+ const canonicalHome = canonicalHomeIdentity(home);
36328
+ const identity = createHash5("sha256").update(canonicalHome).digest("hex").slice(0, 24);
36329
+ return join23(dirname5(canonicalHome), `.bazilion-runtime-${identity}.json`);
36330
+ }
36331
+ var activeInstanceId = null;
36332
+ function getDaemonInstanceId() {
36333
+ return activeInstanceId;
36334
+ }
36335
+ function sameFile(left, right) {
36336
+ return left.dev === right.dev && left.ino === right.ino;
36337
+ }
36338
+ function errorCode(error2) {
36339
+ return error2 instanceof Error && "code" in error2 && typeof error2.code === "string" ? error2.code : void 0;
36340
+ }
36341
+ function readExisting(path) {
36342
+ try {
36343
+ const stat2 = lstatSync3(path);
36344
+ const raw2 = stat2.isFile() ? readFileSync14(path, "utf8") : "";
36345
+ let instanceId;
36346
+ let version2;
36347
+ let pid;
36348
+ let owner;
36349
+ let state3;
36350
+ let recoveryPath;
36351
+ let hadPrevious;
36352
+ let host2;
36353
+ let port2;
36354
+ let startedAt;
36355
+ try {
36356
+ const parsed = JSON.parse(raw2);
36357
+ if (parsed.version === 1) version2 = 1;
36358
+ if (typeof parsed.instanceId === "string" && parsed.instanceId) {
36359
+ instanceId = parsed.instanceId;
36360
+ }
36361
+ if (Number.isSafeInteger(parsed.pid) && parsed.pid > 0) {
36362
+ pid = parsed.pid;
36363
+ }
36364
+ if (parsed.owner === "daemon" || parsed.owner === "restore") owner = parsed.owner;
36365
+ if (typeof parsed.state === "string" && [
36366
+ "starting",
36367
+ "listening",
36368
+ "restoring",
36369
+ "swapping",
36370
+ "installed",
36371
+ "recovery-required"
36372
+ ].includes(parsed.state)) {
36373
+ state3 = parsed.state;
36374
+ }
36375
+ if (typeof parsed.recoveryPath === "string" && parsed.recoveryPath) {
36376
+ recoveryPath = parsed.recoveryPath;
36377
+ }
36378
+ if (typeof parsed.hadPrevious === "boolean") hadPrevious = parsed.hadPrevious;
36379
+ if (typeof parsed.host === "string" && parsed.host) host2 = parsed.host;
36380
+ if (Number.isSafeInteger(parsed.port) && parsed.port > 0 && parsed.port <= 65535) {
36381
+ port2 = parsed.port;
36382
+ }
36383
+ if (typeof parsed.startedAt === "number" && Number.isFinite(parsed.startedAt)) {
36384
+ startedAt = parsed.startedAt;
36385
+ }
36386
+ const validOwnerState = owner === "daemon" && state3 !== void 0 && ["starting", "listening"].includes(state3) && (state3 !== "listening" || host2 !== void 0 && port2 !== void 0) || owner === "restore" && state3 !== void 0 && ["restoring", "swapping", "installed", "recovery-required"].includes(state3) && (!["swapping", "recovery-required"].includes(state3) || recoveryPath !== void 0 && hadPrevious !== void 0);
36387
+ if (!validOwnerState) {
36388
+ owner = void 0;
36389
+ state3 = void 0;
36390
+ }
36391
+ } catch {
36392
+ }
36393
+ return {
36394
+ raw: raw2,
36395
+ stat: stat2,
36396
+ version: version2,
36397
+ instanceId,
36398
+ pid,
36399
+ owner,
36400
+ state: state3,
36401
+ recoveryPath,
36402
+ hadPrevious,
36403
+ host: host2,
36404
+ port: port2,
36405
+ startedAt
36406
+ };
36407
+ } catch (error2) {
36408
+ if (errorCode(error2) === "ENOENT") return null;
36409
+ throw error2;
36410
+ }
36411
+ }
36412
+ function pidIsAlive(pid) {
36413
+ try {
36414
+ process.kill(pid, 0);
36415
+ return true;
36416
+ } catch (error2) {
36417
+ return errorCode(error2) !== "ESRCH";
36418
+ }
36419
+ }
36420
+ function reclaimStaleRecord(path, existing) {
36421
+ const identity = createHash5("sha256").update(existing.instanceId ?? existing.raw).update(`\0${existing.stat.dev}\0${existing.stat.ino}`).digest("hex").slice(0, 24);
36422
+ const claim2 = `${path}.reclaim-${identity}`;
36423
+ try {
36424
+ mkdirSync9(claim2, { mode: 448 });
36425
+ } catch (error2) {
36426
+ if (errorCode(error2) === "EEXIST") return null;
36427
+ throw error2;
36428
+ }
36429
+ try {
36430
+ const current = readExisting(path);
36431
+ if (!current || !sameFile(current.stat, existing.stat) || current.raw !== existing.raw) {
36432
+ return null;
36433
+ }
36434
+ renameSync3(path, join23(claim2, "stale-record.json"));
36435
+ return claim2;
36436
+ } catch (error2) {
36437
+ if (!["ENOENT", "EEXIST"].includes(errorCode(error2) ?? "")) throw error2;
36438
+ return null;
36439
+ }
36440
+ }
36441
+ function writeRecord(fd, record2) {
36442
+ const body = `${JSON.stringify(record2, null, 2)}
36443
+ `;
36444
+ ftruncateSync(fd, 0);
36445
+ writeSync(fd, body, 0, "utf8");
36446
+ fsyncSync(fd);
36447
+ }
36448
+ function fsyncParentDirectory(path) {
36449
+ let directoryFd = null;
36450
+ try {
36451
+ directoryFd = openSync4(dirname5(path), "r");
36452
+ fsyncSync(directoryFd);
36453
+ } catch (error2) {
36454
+ if (process.platform !== "win32") throw error2;
36455
+ } finally {
36456
+ if (directoryFd !== null) closeSync4(directoryFd);
36457
+ }
36458
+ }
36459
+ function acquireDaemonLiveness(paths) {
36460
+ mkdirSync9(dirname5(resolve6(paths.home)), { recursive: true });
36461
+ const path = daemonLivenessPath(paths.home);
36462
+ mkdirSync9(dirname5(path), { recursive: true });
36463
+ const instanceId = randomUUID20();
36464
+ const startedAt = Date.now();
36465
+ let fd = null;
36466
+ let reclaimClaim = null;
36467
+ for (let attempt = 0; attempt < 8 && fd === null; attempt++) {
36468
+ try {
36469
+ fd = openSync4(path, "wx", 384);
36470
+ } catch (error2) {
36471
+ if (errorCode(error2) !== "EEXIST") throw error2;
36472
+ const existing = readExisting(path);
36473
+ if (!existing) continue;
36474
+ if (!existing.instanceId || existing.version !== 1 || existing.pid === void 0 || !existing.owner || !existing.state || existing.startedAt === void 0) {
36475
+ throw new Error(
36476
+ `the daemon ownership record at ${path} is invalid; verify no daemon is running before removing it manually`
36477
+ );
36478
+ }
36479
+ const livePid = pidIsAlive(existing.pid);
36480
+ if (existing.owner === "restore" && ["swapping", "recovery-required"].includes(existing.state)) {
36481
+ if (livePid) {
36482
+ throw new Error(
36483
+ `restore process ${existing.pid} is ${existing.state} this home; wait for it to finish`
36484
+ );
36485
+ }
36486
+ const recovery = existing.recoveryPath ? ` Recovery data is at ${existing.recoveryPath}.` : "";
36487
+ throw new Error(
36488
+ `restore recovery is required before this home can start.${recovery} Recover the previous home, then remove ${path} manually.`
36489
+ );
36490
+ }
36491
+ if (livePid) {
36492
+ const owner = ` ${existing.owner} process ${existing.pid} (instance ${existing.instanceId})`;
36493
+ throw new Error(
36494
+ `another Bazilion daemon owns this home${owner}; stop it before starting another`
36495
+ );
36496
+ }
36497
+ const claim2 = reclaimStaleRecord(path, existing);
36498
+ if (claim2) reclaimClaim = claim2;
36499
+ }
36500
+ }
36501
+ if (fd === null) {
36502
+ throw new Error("could not acquire daemon ownership after reclaiming a stale record");
36503
+ }
36504
+ const ownedStat = fstatSync3(fd);
36505
+ const record2 = {
36506
+ version: 1,
36507
+ instanceId,
36508
+ pid: process.pid,
36509
+ owner: "daemon",
36510
+ state: "starting",
36511
+ startedAt
36512
+ };
36513
+ try {
36514
+ writeRecord(fd, record2);
36515
+ fsyncParentDirectory(path);
36516
+ if (reclaimClaim) rmSync15(reclaimClaim, { recursive: true, force: true });
36517
+ try {
36518
+ chmodSync2(path, 384);
36519
+ } catch {
36520
+ }
36521
+ } catch (error2) {
36522
+ closeSync4(fd);
36523
+ fd = null;
36524
+ try {
36525
+ if (sameFile(lstatSync3(path), ownedStat)) rmSync15(path, { force: true });
36526
+ } catch {
36527
+ }
36528
+ throw error2;
36529
+ }
36530
+ activeInstanceId = instanceId;
36531
+ let stopped = false;
36532
+ const stillOwnsPath = () => {
36533
+ try {
36534
+ return sameFile(lstatSync3(path), ownedStat);
36535
+ } catch {
36536
+ return false;
36537
+ }
36538
+ };
36539
+ const heartbeat = setInterval(() => {
36540
+ if (stopped) return;
36541
+ if (!stillOwnsPath()) {
36542
+ console.error("daemon ownership was lost; exiting to protect the Bazilion database");
36543
+ process.exit(1);
36544
+ }
36545
+ try {
36546
+ const now = /* @__PURE__ */ new Date();
36547
+ futimesSync(fd, now, now);
36548
+ } catch (error2) {
36549
+ console.error(
36550
+ "daemon ownership heartbeat failed:",
36551
+ error2 instanceof Error ? error2.message : error2
36552
+ );
36553
+ process.exit(1);
36554
+ }
36555
+ }, HEARTBEAT_MS);
36556
+ heartbeat.unref();
36557
+ return {
36558
+ instanceId,
36559
+ path,
36560
+ publishEndpoint(endpoint2) {
36561
+ if (stopped || !stillOwnsPath()) {
36562
+ throw new Error("daemon ownership was lost before the HTTP endpoint was published");
36563
+ }
36564
+ record2.state = "listening";
36565
+ record2.host = endpoint2.host;
36566
+ record2.port = endpoint2.port;
36567
+ writeRecord(fd, record2);
36568
+ },
36569
+ stop() {
36570
+ if (stopped) return;
36571
+ stopped = true;
36572
+ clearInterval(heartbeat);
36573
+ if (activeInstanceId === instanceId) activeInstanceId = null;
36574
+ const ownsPath = stillOwnsPath();
36575
+ closeSync4(fd);
36576
+ fd = null;
36577
+ if (ownsPath) rmSync15(path, { force: true });
36578
+ }
36579
+ };
36580
+ }
36581
+
36582
+ // ../daemon/src/routes/misc.ts
34057
36583
  var miscRouter = new Hono8();
34058
36584
  miscRouter.get("/health", (c) => {
36585
+ const instanceId = getDaemonInstanceId();
36586
+ if (instanceId) c.header("x-bazilion-daemon-instance", instanceId);
34059
36587
  const paths = resolvePaths();
34060
36588
  const pathChecks = {
34061
36589
  home: existsSync18(paths.home),
@@ -34111,7 +36639,7 @@ miscRouter.get("/health", (c) => {
34111
36639
  }
34112
36640
  const providerConfig = loadProviderConfigFromEnv(effectiveEnv, oauth);
34113
36641
  const braveKey = effectiveEnv.BRAVE_API_KEY;
34114
- const openclawSkillsDir = join20(homedir2(), ".openclaw", "skills");
36642
+ const openclawSkillsDir = join24(homedir2(), ".openclaw", "skills");
34115
36643
  const CLOUD_KEYS = [
34116
36644
  ["anthropic", "anthropic"],
34117
36645
  ["openai", "openai"],
@@ -34136,8 +36664,25 @@ miscRouter.get("/health", (c) => {
34136
36664
  },
34137
36665
  ollama: { baseURL: providerConfig.ollama?.baseURL ?? "http://localhost:11434/v1" }
34138
36666
  };
36667
+ let shellSecurity;
36668
+ try {
36669
+ const shellConfig = resolveShellSecurityConfig(effectiveEnv);
36670
+ shellSecurity = {
36671
+ ok: true,
36672
+ sandboxMode: shellConfig.sandboxMode,
36673
+ approvalMode: shellConfig.approvalMode,
36674
+ sandboxImage: shellConfig.sandboxImage,
36675
+ hostCodingTools: shellConfig.sandboxMode === "off",
36676
+ network: shellConfig.sandboxMode === "docker" ? "none" : "host"
36677
+ };
36678
+ } catch (err) {
36679
+ shellSecurity = {
36680
+ ok: false,
36681
+ error: err instanceof Error ? err.message : String(err)
36682
+ };
36683
+ }
34139
36684
  const report = {
34140
- ok: pathChecks.home && pathChecks.db && pathChecks.auth && pathChecks.profiles && pathChecks.agents && pathChecks.skills && (database === null || database.ok) && parseErrors === 0,
36685
+ ok: pathChecks.home && pathChecks.db && pathChecks.auth && pathChecks.profiles && pathChecks.agents && pathChecks.skills && (database === null || database.ok) && parseErrors === 0 && shellSecurity.ok,
34141
36686
  home: paths.home,
34142
36687
  paths: pathChecks,
34143
36688
  database,
@@ -34157,6 +36702,7 @@ miscRouter.get("/health", (c) => {
34157
36702
  enabled: process.env.BAZILION_SCHEDULER !== "off",
34158
36703
  tickMs: Number(process.env.BAZILION_SCHEDULER_TICK_MS ?? 5e3)
34159
36704
  },
36705
+ shellSecurity,
34160
36706
  teamPolicyManagement: {
34161
36707
  contractVersion: TEAM_POLICY_MANAGEMENT_CONTRACT_VERSION,
34162
36708
  enforcementRequested: teamPolicyEnforcementRequested(),
@@ -34168,47 +36714,54 @@ miscRouter.get("/health", (c) => {
34168
36714
  };
34169
36715
  return c.json(report);
34170
36716
  });
34171
- miscRouter.get("/backup", (c) => {
34172
- const paths = resolvePaths();
36717
+ miscRouter.get("/backup", async (c) => {
36718
+ const { db, paths } = getCtx();
34173
36719
  if (!existsSync18(paths.home)) {
34174
36720
  return c.json({ error: `bazilion home not found at ${paths.home}` }, 404);
34175
36721
  }
34176
- const proc = spawn4("tar", ["-czf", "-", "-C", paths.home, "."], {
34177
- stdio: ["ignore", "pipe", "pipe"]
34178
- });
36722
+ let snapshot;
36723
+ try {
36724
+ snapshot = await createBackupSnapshot(db);
36725
+ } catch (error2) {
36726
+ const message = error2 instanceof Error ? error2.message : String(error2);
36727
+ return c.json({ error: `could not create database snapshot: ${message}` }, 500);
36728
+ }
36729
+ let archive2;
36730
+ try {
36731
+ archive2 = createBackupArchive(paths, snapshot);
36732
+ } catch (error2) {
36733
+ snapshot.cleanup();
36734
+ const message = error2 instanceof Error ? error2.message : String(error2);
36735
+ return c.json({ error: `could not start backup archive: ${message}` }, 500);
36736
+ }
36737
+ const iterator = archive2[Symbol.asyncIterator]();
34179
36738
  const stream = new ReadableStream({
34180
- start(controller) {
34181
- proc.stdout.on("data", (chunk) => controller.enqueue(chunk));
34182
- proc.stdout.on("end", () => {
34183
- try {
34184
- controller.close();
34185
- } catch {
34186
- }
34187
- });
34188
- proc.on("error", (err) => {
34189
- try {
34190
- controller.error(err);
34191
- } catch {
34192
- }
34193
- });
34194
- proc.on("exit", (code) => {
34195
- if (code !== 0) {
34196
- try {
34197
- controller.error(new Error(`tar exited with code ${code}`));
34198
- } catch {
34199
- }
36739
+ async pull(controller) {
36740
+ try {
36741
+ const next = await iterator.next();
36742
+ if (!next.done) {
36743
+ controller.enqueue(Buffer.isBuffer(next.value) ? next.value : Buffer.from(next.value));
36744
+ return;
34200
36745
  }
34201
- });
36746
+ snapshot.cleanup();
36747
+ controller.close();
36748
+ } catch (error2) {
36749
+ snapshot.cleanup();
36750
+ controller.error(error2);
36751
+ }
34202
36752
  },
34203
- cancel() {
34204
- proc.kill("SIGTERM");
36753
+ async cancel() {
36754
+ await iterator.return?.();
36755
+ archive2.destroy();
36756
+ snapshot.cleanup();
34205
36757
  }
34206
36758
  });
34207
36759
  const date4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
34208
36760
  return new Response(stream, {
34209
36761
  headers: {
34210
36762
  "content-type": "application/gzip",
34211
- "content-disposition": `attachment; filename="bazilion-backup-${date4}.tar.gz"`
36763
+ "content-disposition": `attachment; filename="bazilion-backup-${date4}.tar.gz"`,
36764
+ "cache-control": "no-store"
34212
36765
  }
34213
36766
  });
34214
36767
  });
@@ -34247,8 +36800,8 @@ miscRouter.delete("/tokens/:id", (c) => {
34247
36800
  });
34248
36801
 
34249
36802
  // ../daemon/src/routes/profiles.ts
34250
- import { existsSync as existsSync19, readFileSync as readFileSync14, writeFileSync as writeFileSync11 } from "fs";
34251
- import { join as join21 } from "path";
36803
+ import { existsSync as existsSync19, readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "fs";
36804
+ import { join as join25 } from "path";
34252
36805
  import { Hono as Hono9 } from "hono";
34253
36806
  var profilesRouter = new Hono9();
34254
36807
  profilesRouter.get("/", (c) => {
@@ -34383,7 +36936,7 @@ profilesRouter.get("/:id/files/:file", (c) => {
34383
36936
  const path = resolveFilePath(paths.profilesDir, c.req.param("id"), c.req.param("file"));
34384
36937
  if (!path) return c.json({ error: `unsupported file: ${c.req.param("file")}` }, 400);
34385
36938
  if (!existsSync19(path)) return c.json({ error: `file not present: ${c.req.param("file")}` }, 404);
34386
- const body = { content: readFileSync14(path, "utf8") };
36939
+ const body = { content: readFileSync15(path, "utf8") };
34387
36940
  return c.json(body);
34388
36941
  });
34389
36942
  profilesRouter.put("/:id/files/:file", async (c) => {
@@ -34395,12 +36948,12 @@ profilesRouter.put("/:id/files/:file", async (c) => {
34395
36948
  return c.json({ error: `profile not found: ${c.req.param("id")}` }, 404);
34396
36949
  const path = resolveFilePath(paths.profilesDir, c.req.param("id"), c.req.param("file"));
34397
36950
  if (!path) return c.json({ error: `unsupported file: ${c.req.param("file")}` }, 400);
34398
- writeFileSync11(path, body.content);
36951
+ writeFileSync12(path, body.content);
34399
36952
  return c.body(null, 204);
34400
36953
  });
34401
36954
  function resolveFilePath(profilesDir, id, file) {
34402
36955
  if (!PROFILE_FILES.includes(file)) return null;
34403
- return join21(profilesDir, id, file);
36956
+ return join25(profilesDir, id, file);
34404
36957
  }
34405
36958
  function csvToArray(v2) {
34406
36959
  if (Array.isArray(v2)) return v2.filter((s) => typeof s === "string");
@@ -34428,13 +36981,46 @@ function parseCommunicationDefaults(value) {
34428
36981
  };
34429
36982
  }
34430
36983
 
34431
- // ../daemon/src/routes/skills.ts
34432
- import { existsSync as existsSync20, mkdtempSync as mkdtempSync2, rmSync as rmSync13, writeFileSync as writeFileSync12 } from "fs";
34433
- import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
34434
- import { join as join22 } from "path";
36984
+ // ../daemon/src/routes/shell-approvals.ts
34435
36985
  import { Hono as Hono10 } from "hono";
36986
+ var shellApprovalsRouter = new Hono10();
36987
+ shellApprovalsRouter.get("/", (c) => {
36988
+ const agentId = c.req.query("agentId")?.trim();
36989
+ if (!agentId) return c.json({ error: "agentId query parameter is required" }, 400);
36990
+ const body = {
36991
+ approvals: commandApprovalRegistry.list(agentId)
36992
+ };
36993
+ return c.json(body);
36994
+ });
36995
+ shellApprovalsRouter.post("/:id", async (c) => {
36996
+ const body = await c.req.json().catch(() => null);
36997
+ if (!body || body.decision !== "allow" && body.decision !== "deny") {
36998
+ return c.json({ error: 'body must be {"decision":"allow"|"deny"}' }, 400);
36999
+ }
37000
+ const result = commandApprovalRegistry.respond(c.req.param("id"), body.decision);
37001
+ if (result.kind === "not_found") {
37002
+ return c.json({ error: "shell approval not found", code: "shell_approval_not_found" }, 404);
37003
+ }
37004
+ if (result.kind === "conflict") {
37005
+ return c.json(
37006
+ {
37007
+ error: `shell approval already decided: ${result.approval.status}`,
37008
+ code: "shell_approval_already_decided"
37009
+ },
37010
+ 409
37011
+ );
37012
+ }
37013
+ const response = { approval: result.approval };
37014
+ return c.json(response);
37015
+ });
37016
+
37017
+ // ../daemon/src/routes/skills.ts
37018
+ import { existsSync as existsSync20, mkdtempSync as mkdtempSync4, rmSync as rmSync16, writeFileSync as writeFileSync13 } from "fs";
37019
+ import { homedir as homedir3, tmpdir as tmpdir4 } from "os";
37020
+ import { join as join26 } from "path";
37021
+ import { Hono as Hono11 } from "hono";
34436
37022
  var MAX_ZIP_BYTES = 50 * 1024 * 1024;
34437
- var skillsRouter = new Hono10();
37023
+ var skillsRouter = new Hono11();
34438
37024
  skillsRouter.get("/", (c) => {
34439
37025
  const { db, paths } = getCtx();
34440
37026
  const out = [];
@@ -34462,7 +37048,7 @@ skillsRouter.delete("/:name", (c) => {
34462
37048
  const name = c.req.param("name");
34463
37049
  const dir = paths.skillDir(name);
34464
37050
  if (!existsSync20(dir)) return c.json({ error: `skill not found: ${name}` }, 404);
34465
- rmSync13(dir, { recursive: true, force: true });
37051
+ rmSync16(dir, { recursive: true, force: true });
34466
37052
  skillMeta_exports.remove(db, name);
34467
37053
  return c.body(null, 204);
34468
37054
  });
@@ -34499,7 +37085,7 @@ skillsRouter.post("/import", async (c) => {
34499
37085
  }
34500
37086
  return c.json({ error: err.message }, 400);
34501
37087
  } finally {
34502
- if (input.tempZipPath) rmSync13(input.tempZipPath, { recursive: true, force: true });
37088
+ if (input.tempZipPath) rmSync16(input.tempZipPath, { recursive: true, force: true });
34503
37089
  }
34504
37090
  });
34505
37091
  function flattenFindings(findings) {
@@ -34525,10 +37111,10 @@ async function parseImportInput(request2) {
34525
37111
  if (!filename.toLowerCase().endsWith(".zip")) {
34526
37112
  throw new Error("uploaded file must be a .zip archive");
34527
37113
  }
34528
- const tmpDir = mkdtempSync2(join22(tmpdir2(), "bazilion-skill-upload-"));
34529
- const zipPath = join22(tmpDir, filename.replace(/[^\w.-]+/g, "_"));
37114
+ const tmpDir = mkdtempSync4(join26(tmpdir4(), "bazilion-skill-upload-"));
37115
+ const zipPath = join26(tmpDir, filename.replace(/[^\w.-]+/g, "_"));
34530
37116
  const buf = Buffer.from(await file.arrayBuffer());
34531
- writeFileSync12(zipPath, buf);
37117
+ writeFileSync13(zipPath, buf);
34532
37118
  const forceField = form.get("force");
34533
37119
  return {
34534
37120
  source: zipPath,
@@ -34541,7 +37127,7 @@ async function parseImportInput(request2) {
34541
37127
  if (!body) throw new Error("invalid JSON body");
34542
37128
  const from = body.source ?? body.from;
34543
37129
  if (typeof from !== "string" || !from) throw new Error("source is required");
34544
- const source = from === "openclaw" ? join22(homedir3(), ".openclaw", "skills") : from;
37130
+ const source = from === "openclaw" ? join26(homedir3(), ".openclaw", "skills") : from;
34545
37131
  return {
34546
37132
  source,
34547
37133
  force: Boolean(body.force),
@@ -34551,9 +37137,9 @@ async function parseImportInput(request2) {
34551
37137
  }
34552
37138
 
34553
37139
  // ../daemon/src/routes/team-templates.ts
34554
- import { randomUUID as randomUUID18 } from "crypto";
34555
- import { Hono as Hono11 } from "hono";
34556
- var teamTemplatesRouter = new Hono11();
37140
+ import { randomUUID as randomUUID21 } from "crypto";
37141
+ import { Hono as Hono12 } from "hono";
37142
+ var teamTemplatesRouter = new Hono12();
34557
37143
  teamTemplatesRouter.get("/", (c) => {
34558
37144
  const { db } = getCtx();
34559
37145
  return c.json(
@@ -34591,7 +37177,7 @@ teamTemplatesRouter.post("/import", async (c) => {
34591
37177
  if (!profiles_exports.get(db, parsed.profileId)) {
34592
37178
  throw new Error(`profile not found: ${parsed.profileId}`);
34593
37179
  }
34594
- const slotId = randomUUID18();
37180
+ const slotId = randomUUID21();
34595
37181
  keyToSlot.set(clientKey, slotId);
34596
37182
  return { ...parsed, slotId };
34597
37183
  });
@@ -34883,10 +37469,10 @@ function canonicalError(c, error2) {
34883
37469
  }
34884
37470
 
34885
37471
  // ../daemon/src/routes/teams.ts
34886
- import { join as join23 } from "path";
34887
- import { Hono as Hono12 } from "hono";
37472
+ import { join as join27 } from "path";
37473
+ import { Hono as Hono13 } from "hono";
34888
37474
  var USER_MD_MAX_BYTES2 = 12e3;
34889
- var teamsRouter = new Hono12();
37475
+ var teamsRouter = new Hono13();
34890
37476
  teamsRouter.get("/", (c) => {
34891
37477
  const { db, paths } = getCtx();
34892
37478
  return c.json(teams_exports.list(db, paths));
@@ -35178,7 +37764,7 @@ async function openMemory(rawId) {
35178
37764
  const { db, paths } = getCtx();
35179
37765
  const team = teams_exports.get(db, rawId, paths);
35180
37766
  if (!team) throw new Error(`team not found: ${rawId}`);
35181
- const mem = qmdBackend(join23(team.path, "memory"));
37767
+ const mem = qmdBackend(join27(team.path, "memory"));
35182
37768
  await mem.init();
35183
37769
  return { mem, team };
35184
37770
  }
@@ -35255,7 +37841,7 @@ function teamPolicyError(c, error2) {
35255
37841
  }
35256
37842
 
35257
37843
  // ../daemon/src/routes/telegram.ts
35258
- import { Hono as Hono13 } from "hono";
37844
+ import { Hono as Hono14 } from "hono";
35259
37845
 
35260
37846
  // ../daemon/src/lib/telegram/preflight.ts
35261
37847
  var TELEGRAM_API_BASE = "https://api.telegram.org";
@@ -35284,7 +37870,7 @@ async function runPreflight(args) {
35284
37870
  clearTimeout(t);
35285
37871
  }
35286
37872
  };
35287
- const fail = (step, message) => ({
37873
+ const fail2 = (step, message) => ({
35288
37874
  configured: true,
35289
37875
  preflight: null,
35290
37876
  error: { step, message },
@@ -35294,13 +37880,13 @@ async function runPreflight(args) {
35294
37880
  try {
35295
37881
  me2 = await call("getMe", {});
35296
37882
  } catch (e) {
35297
- return fail("getMe", errorMessage(e));
37883
+ return fail2("getMe", errorMessage(e));
35298
37884
  }
35299
37885
  let chat;
35300
37886
  try {
35301
37887
  chat = await call("getChat", { chat_id: chatId(args.chatId) });
35302
37888
  } catch (e) {
35303
- return fail("getChat", errorMessage(e));
37889
+ return fail2("getChat", errorMessage(e));
35304
37890
  }
35305
37891
  let member;
35306
37892
  try {
@@ -35309,7 +37895,7 @@ async function runPreflight(args) {
35309
37895
  user_id: me2.id
35310
37896
  });
35311
37897
  } catch (e) {
35312
- return fail("getChatMember", errorMessage(e));
37898
+ return fail2("getChatMember", errorMessage(e));
35313
37899
  }
35314
37900
  const hasManageTopics = member.status === "creator" || member.can_manage_topics === true;
35315
37901
  const preflight = {
@@ -35349,7 +37935,7 @@ var DERIVED_STATE_KEYS = [
35349
37935
  "TELEGRAM_DIRECTORY_MESSAGE_ID",
35350
37936
  MIGRATED_CHAT_ID_KEY
35351
37937
  ];
35352
- var telegramRouter = new Hono13();
37938
+ var telegramRouter = new Hono14();
35353
37939
  telegramRouter.get("/", (c) => {
35354
37940
  const { db, authToken } = getCtx();
35355
37941
  return c.json(readState(db, authToken));
@@ -35488,8 +38074,19 @@ function maskBotToken(t) {
35488
38074
  }
35489
38075
 
35490
38076
  // ../daemon/src/routes/triggers.ts
35491
- import { Hono as Hono14 } from "hono";
35492
- var triggersRouter = new Hono14();
38077
+ import { Hono as Hono15 } from "hono";
38078
+ var triggersRouter = new Hono15();
38079
+ triggersRouter.get("/:id/dispatches", (c) => {
38080
+ const { db } = getCtx();
38081
+ const id = c.req.param("id");
38082
+ if (!triggers_exports.get(db, id)) return c.json({ error: `trigger not found: ${id}` }, 404);
38083
+ const requestedLimit = Number(c.req.query("limit") ?? 20);
38084
+ if (!Number.isInteger(requestedLimit) || requestedLimit < 1) {
38085
+ return c.json({ error: "limit must be a positive integer" }, 400);
38086
+ }
38087
+ const limit = Math.min(100, requestedLimit);
38088
+ return c.json({ dispatches: triggerDispatches_exports.listForTrigger(db, id, limit) });
38089
+ });
35493
38090
  triggersRouter.delete("/:id", (c) => {
35494
38091
  const { db } = getCtx();
35495
38092
  const id = c.req.param("id");
@@ -35511,10 +38108,11 @@ triggersRouter.patch("/:id", async (c) => {
35511
38108
 
35512
38109
  // ../daemon/src/app.ts
35513
38110
  function createApp() {
35514
- const app2 = new Hono15();
38111
+ const app2 = new Hono16();
35515
38112
  app2.use("*", authMiddleware);
35516
38113
  app2.route("/api/agents", agentsRouter);
35517
38114
  app2.route("/api/approvals", approvalsRouter);
38115
+ app2.route("/api/shell-approvals", shellApprovalsRouter);
35518
38116
  app2.route("/api/teams", teamsRouter);
35519
38117
  app2.route("/api/team-templates", teamTemplatesRouter);
35520
38118
  app2.route("/api/profiles", profilesRouter);
@@ -35533,8 +38131,19 @@ function createApp() {
35533
38131
  // ../daemon/src/index.ts
35534
38132
  var host = process.env.HOST ?? "127.0.0.1";
35535
38133
  var port = Number.parseInt(process.env.PORT ?? "4321", 10);
38134
+ var daemonLiveness;
38135
+ try {
38136
+ daemonLiveness = acquireDaemonLiveness(resolvePaths());
38137
+ } catch (error2) {
38138
+ console.error(
38139
+ "failed to acquire daemon ownership:",
38140
+ error2 instanceof Error ? error2.message : error2
38141
+ );
38142
+ process.exit(1);
38143
+ }
35536
38144
  getCtx();
35537
38145
  var app = createApp();
38146
+ var shuttingDown = false;
35538
38147
  var server2 = serve({ fetch: app.fetch, hostname: host, port }, (info) => {
35539
38148
  console.log(`bazilion daemon listening at http://${info.address}:${info.port}`);
35540
38149
  if (host !== "127.0.0.1" && host !== "localhost" && host !== "::1") {
@@ -35544,6 +38153,16 @@ var server2 = serve({ fetch: app.fetch, hostname: host, port }, (info) => {
35544
38153
  console.error(" put a TLS proxy in front for untrusted networks.");
35545
38154
  console.error("");
35546
38155
  }
38156
+ try {
38157
+ daemonLiveness.publishEndpoint({ host, port: info.port });
38158
+ } catch (error2) {
38159
+ console.error(
38160
+ "failed to publish daemon liveness record:",
38161
+ error2 instanceof Error ? error2.message : error2
38162
+ );
38163
+ server2.close(() => process.exit(1));
38164
+ return;
38165
+ }
35547
38166
  const { db, authToken } = getCtx();
35548
38167
  setTelegramAuthToken(authToken);
35549
38168
  maybeStartTelegramBot(db, authToken).catch((err) => {
@@ -35551,6 +38170,8 @@ var server2 = serve({ fetch: app.fetch, hostname: host, port }, (info) => {
35551
38170
  });
35552
38171
  });
35553
38172
  var shutdown = (signal) => {
38173
+ if (shuttingDown) return;
38174
+ shuttingDown = true;
35554
38175
  console.log(`
35555
38176
  bazilion daemon caught ${signal}, shutting down\u2026`);
35556
38177
  const botStop = isTelegramBotRunning() ? stopTelegramBot().catch(
@@ -35569,6 +38190,16 @@ bazilion daemon caught ${signal}, shutting down\u2026`);
35569
38190
  console.error("shutdown error:", err);
35570
38191
  process.exit(1);
35571
38192
  }
38193
+ try {
38194
+ closeCtxForShutdown();
38195
+ daemonLiveness.stop();
38196
+ } catch (closeError) {
38197
+ console.error(
38198
+ "database shutdown error:",
38199
+ closeError instanceof Error ? closeError.message : closeError
38200
+ );
38201
+ process.exit(1);
38202
+ }
35572
38203
  process.exit(0);
35573
38204
  });
35574
38205
  });