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.
- package/dist/cli.js +1249 -136
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +3027 -396
- package/dist/daemon.js.map +1 -1
- package/dist/migrations/0001_init.sql +20 -0
- package/dist/web/client/assets/{ProductionTeamPolicyEditor-CiQ-VVDo.js → ProductionTeamPolicyEditor-B6dbpuYy.js} +1 -1
- package/dist/web/client/assets/{ProfileCommunicationEditor-D84F5yfr.js → ProfileCommunicationEditor-Bf8UvmZT.js} +1 -1
- package/dist/web/client/assets/{TeamTabs-Btoe_FAE.js → TeamTabs-sQBHChtd.js} +1 -1
- package/dist/web/client/assets/{TemplatesTabs-CAYY03F5.js → TemplatesTabs-DPsuwsWB.js} +1 -1
- package/dist/web/client/assets/{_id-BqU2BodK.js → _id-C9rkCclz.js} +1 -1
- package/dist/web/client/assets/{_id-Q98Of6k9.js → _id-D65IzC8H.js} +1 -1
- package/dist/web/client/assets/{_id-BeauECmk.js → _id-DE4IY5bL.js} +1 -1
- package/dist/web/client/assets/{_id-CKtoio3t.js → _id-DYASsGQ8.js} +1 -1
- package/dist/web/client/assets/{activity-DXN_N85w.js → activity-C7o6-xHg.js} +1 -1
- package/dist/web/client/assets/{agents-C5Xsdge1.js → agents-UroO4d25.js} +1 -1
- package/dist/web/client/assets/{agents-Cgxo4H5G.js → agents-h8yWXQ2H.js} +1 -1
- package/dist/web/client/assets/{approvals-BnG7fbw-.js → approvals-DKhF0YLC.js} +1 -1
- package/dist/web/client/assets/chevron-down-BK6NPMMP.js +1 -0
- package/dist/web/client/assets/{config-xw4NyrXF.js → config-q8JDresK.js} +1 -1
- package/dist/web/client/assets/{context-D2V1Oi4z.js → context-CBucAoi6.js} +1 -1
- package/dist/web/client/assets/{inbox-DK6hWCMp.js → inbox-BwoRikj1.js} +1 -1
- package/dist/web/client/assets/{index-VdURSoM4.js → index-DUXAevpZ.js} +2 -2
- package/dist/web/client/assets/{login-CvptUoMR.js → login-CNAV9Wer.js} +1 -1
- package/dist/web/client/assets/{mcp-BDFM_sW-.js → mcp-BKdEFwHO.js} +1 -1
- package/dist/web/client/assets/{members-BJvph_TR.js → members-BDqZQh26.js} +1 -1
- package/dist/web/client/assets/{memory-DRytqZCD.js → memory-DVdEVr6L.js} +1 -1
- package/dist/web/client/assets/plus-b4yFXP0O.js +1 -0
- package/dist/web/client/assets/{policy-CGP50OtQ.js → policy-DdDctqvg.js} +1 -1
- package/dist/web/client/assets/{routes-DJnWYvC9.js → routes-BEfALSZv.js} +1 -1
- package/dist/web/client/assets/{services-DHheiv7L.js → services-DZP2ybzb.js} +1 -1
- package/dist/web/client/assets/{skills-DmsitluU.js → skills-DoeIjITs.js} +1 -1
- package/dist/web/client/assets/styles-BYM0UGdF.css +2 -0
- package/dist/web/client/assets/{teams-CbxSaorm.js → teams-DlHRnrQw.js} +1 -1
- package/dist/web/client/assets/{teams-BdC4gthq.js → teams-W3j-UoJ9.js} +1 -1
- package/dist/web/client/assets/telegram-2OdeGv4E.js +1 -0
- package/dist/web/client/assets/{tokens-CjRplDrJ.js → tokens-MuBfmgue.js} +1 -1
- package/dist/web/client/assets/triggers-CX7RMQFQ.js +1 -0
- package/dist/web/client/assets/{welcome-wBfX9dAd.js → welcome-CvNr5IMi.js} +1 -1
- package/dist/web/client/assets/wire-constants-BhVKOCQa.js +74 -0
- package/dist/web/server/assets/{ChatPane-eUY_dga3.js → ChatPane-Ba5wTsVk.js} +325 -36
- package/dist/web/server/assets/{_id-ClN9hY2x.js → _id-Cg7x6q3g.js} +2 -2
- package/dist/web/server/assets/{_id-DkYodSkG.js → _id-DqON-59C.js} +1 -1
- package/dist/web/server/assets/{_tanstack-start-manifest_v-D09Xu_6u.js → _tanstack-start-manifest_v-BXauUXg6.js} +51 -51
- package/dist/web/server/assets/{router-DIdp8ZNt.js → router-C5_NjTsP.js} +5 -5
- package/dist/web/server/assets/{routes-Jb0zjKav.js → routes-B6cMLIPk.js} +3 -3
- package/dist/web/server/assets/{routes-CXz4hTAr.js → routes-BrA-U_Hm.js} +1 -1
- package/dist/web/server/assets/{routes-lIclH9Br.js → routes-C19CMLZt.js} +1 -1
- package/dist/web/server/assets/{telegram-7ub--6xz.js → telegram-CBF8SJzB.js} +1 -24
- package/dist/web/server/assets/{telegram-Bazhmpr4.js → telegram-XwyKSEtj.js} +1 -1
- package/dist/web/server/assets/{triggers-Ca57_qym.js → triggers-CBl4yMyw.js} +1 -1
- package/dist/web/server/assets/{triggers-C_0y6QV9.js → triggers-D79NoSXk.js} +3 -2
- package/dist/web/server/assets/{triggers-BMpHsGIr.js → triggers-_3rdhwSN.js} +27 -3
- package/dist/web/server/server.js +4 -4
- package/dist/worker.js +1609 -135
- package/dist/worker.js.map +1 -1
- package/package.json +4 -3
- package/dist/web/client/assets/chevron-down-BhcBspw3.js +0 -1
- package/dist/web/client/assets/plus-C9ee3SIO.js +0 -1
- package/dist/web/client/assets/styles-B5eQAwLP.css +0 -2
- package/dist/web/client/assets/telegram-D61G_alO.js +0 -1
- package/dist/web/client/assets/triggers-MLzTm_DM.js +0 -1
- package/dist/web/client/assets/wire-constants-BxfyUMXB.js +0 -74
- 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(
|
|
66
|
-
if (!
|
|
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,
|
|
73
|
-
cb(er, er ? false : checkStat(
|
|
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,
|
|
91
|
-
cb(er, er ? false : checkStat(
|
|
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(
|
|
98
|
-
return
|
|
97
|
+
function checkStat(stat2, options) {
|
|
98
|
+
return stat2.isFile() && checkMode(stat2, options);
|
|
99
99
|
}
|
|
100
|
-
function checkMode(
|
|
101
|
-
var mod =
|
|
102
|
-
var uid =
|
|
103
|
-
var 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(
|
|
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
|
-
|
|
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((
|
|
210
|
+
const step = (i) => new Promise((resolve7, reject) => {
|
|
211
211
|
if (i === pathEnv.length)
|
|
212
|
-
return opt.all && found.length ?
|
|
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
|
-
|
|
217
|
+
resolve7(subStep(p, i, 0));
|
|
218
218
|
});
|
|
219
|
-
const subStep = (p, i, ii) => new Promise((
|
|
219
|
+
const subStep = (p, i, ii) => new Promise((resolve7, reject) => {
|
|
220
220
|
if (ii === pathExt.length)
|
|
221
|
-
return
|
|
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
|
|
228
|
+
return resolve7(p + ext);
|
|
229
229
|
}
|
|
230
|
-
return
|
|
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((
|
|
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
|
-
|
|
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((
|
|
7875
|
+
const closePromise = new Promise((resolve7) => {
|
|
7876
7876
|
processToClose.once("close", () => {
|
|
7877
|
-
|
|
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((
|
|
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((
|
|
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((
|
|
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
|
-
|
|
7908
|
+
resolve7();
|
|
7909
7909
|
} else {
|
|
7910
|
-
this._process.stdin.once("drain",
|
|
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((
|
|
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(
|
|
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
|
-
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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(
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
11919
|
-
this.code = optimizeExpr(this.code, names,
|
|
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,
|
|
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,
|
|
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,
|
|
12006
|
+
optimizeNames(names, constants4) {
|
|
12007
12007
|
var _a3;
|
|
12008
|
-
this.else = (_a3 = this.else) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names,
|
|
12009
|
-
if (!(super.optimizeNames(names,
|
|
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,
|
|
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,
|
|
12035
|
-
if (!super.optimizeNames(names,
|
|
12034
|
+
optimizeNames(names, constants4) {
|
|
12035
|
+
if (!super.optimizeNames(names, constants4))
|
|
12036
12036
|
return;
|
|
12037
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
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,
|
|
12074
|
-
if (!super.optimizeNames(names,
|
|
12073
|
+
optimizeNames(names, constants4) {
|
|
12074
|
+
if (!super.optimizeNames(names, constants4))
|
|
12075
12075
|
return;
|
|
12076
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
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,
|
|
12118
|
+
optimizeNames(names, constants4) {
|
|
12119
12119
|
var _a3, _b;
|
|
12120
|
-
super.optimizeNames(names,
|
|
12121
|
-
(_a3 = this.catch) === null || _a3 === void 0 ? void 0 : _a3.optimizeNames(names,
|
|
12122
|
-
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names,
|
|
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,
|
|
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 =
|
|
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 &&
|
|
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 =
|
|
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
|
|
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
|
|
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,
|
|
15071
|
+
function resolveComponent(base, relative5, options, skipNormalization) {
|
|
15072
15072
|
const target = {};
|
|
15073
15073
|
if (!skipNormalization) {
|
|
15074
15074
|
base = parse3(serialize(base, options), options);
|
|
15075
|
-
|
|
15075
|
+
relative5 = parse3(serialize(relative5, options), options);
|
|
15076
15076
|
}
|
|
15077
15077
|
options = options || {};
|
|
15078
|
-
if (!options.tolerant &&
|
|
15079
|
-
target.scheme =
|
|
15080
|
-
target.userinfo =
|
|
15081
|
-
target.host =
|
|
15082
|
-
target.port =
|
|
15083
|
-
target.path = removeDotSegments(
|
|
15084
|
-
target.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 (
|
|
15087
|
-
target.userinfo =
|
|
15088
|
-
target.host =
|
|
15089
|
-
target.port =
|
|
15090
|
-
target.path = removeDotSegments(
|
|
15091
|
-
target.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 (!
|
|
15093
|
+
if (!relative5.path) {
|
|
15094
15094
|
target.path = base.path;
|
|
15095
|
-
if (
|
|
15096
|
-
target.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 (
|
|
15102
|
-
target.path = removeDotSegments(
|
|
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 = "/" +
|
|
15105
|
+
target.path = "/" + relative5.path;
|
|
15106
15106
|
} else if (!base.path) {
|
|
15107
|
-
target.path =
|
|
15107
|
+
target.path = relative5.path;
|
|
15108
15108
|
} else {
|
|
15109
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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: () =>
|
|
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
|
|
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 =
|
|
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
|
|
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.
|
|
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: () =>
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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-${
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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 =
|
|
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
|
|
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((
|
|
24082
|
+
return new Promise((resolve7) => {
|
|
23820
24083
|
const queue = leases.waiters.get(agentId) ?? [];
|
|
23821
|
-
queue.push({ resolve:
|
|
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
|
|
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
|
|
23905
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23974
|
-
}
|
|
23975
|
-
|
|
23976
|
-
|
|
23977
|
-
|
|
23978
|
-
|
|
23979
|
-
|
|
23980
|
-
|
|
23981
|
-
|
|
23982
|
-
|
|
23983
|
-
|
|
23984
|
-
|
|
23985
|
-
|
|
23986
|
-
|
|
23987
|
-
|
|
23988
|
-
|
|
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
|
-
|
|
23997
|
-
|
|
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
|
|
24002
|
-
|
|
24003
|
-
|
|
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
|
|
24466
|
+
content,
|
|
24009
24467
|
updatedAt: stats.mtimeMs
|
|
24010
24468
|
};
|
|
24011
24469
|
},
|
|
24012
24470
|
async write(key2, content) {
|
|
24013
|
-
const
|
|
24014
|
-
|
|
24015
|
-
|
|
24016
|
-
const stats =
|
|
24017
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
24052
|
-
|
|
24053
|
-
const
|
|
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
|
|
24143
|
-
import { basename as basename3, join as
|
|
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((
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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 (!
|
|
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 (
|
|
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
|
|
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
|
-
|
|
25060
|
-
|
|
25061
|
-
|
|
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.
|
|
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
|
|
25098
|
-
import { join as
|
|
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 =
|
|
26649
|
+
const path = join17(agentDir, "BOOTSTRAP.md");
|
|
25108
26650
|
if (existsSync13(path)) {
|
|
25109
|
-
|
|
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 {
|
|
25248
|
-
|
|
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
|
|
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
|
-
|
|
25285
|
-
|
|
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
|
-
|
|
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 (
|
|
25292
|
-
throw new Error(
|
|
25293
|
-
|
|
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
|
|
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
|
|
25306
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
25367
|
-
|
|
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 =
|
|
26982
|
+
const path = join18(agentDir, file);
|
|
25381
26983
|
try {
|
|
25382
|
-
const s =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
28156
|
+
for (const entry of readdirSync9(sessionDir)) {
|
|
26541
28157
|
if (!entry.endsWith(".jsonl")) continue;
|
|
26542
|
-
const path =
|
|
28158
|
+
const path = join19(sessionDir, entry);
|
|
26543
28159
|
try {
|
|
26544
|
-
const s =
|
|
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
|
-
|
|
26682
|
-
|
|
26683
|
-
if (!
|
|
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",
|
|
28358
|
+
return ["--experimental-strip-types", "--no-warnings", workerEntryPath];
|
|
26687
28359
|
}
|
|
26688
|
-
return ["--import", tsxImportSpecifier(),
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
-
(
|
|
28424
|
+
(resolve7) => {
|
|
26734
28425
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
26735
|
-
|
|
28426
|
+
resolve7({ code: child.exitCode, signal: child.signalCode });
|
|
26736
28427
|
return;
|
|
26737
28428
|
}
|
|
26738
|
-
child.once("close", (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
|
-
|
|
26746
|
-
|
|
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
|
|
26898
|
-
import { mkdirSync as mkdirSync7, writeFileSync as
|
|
26899
|
-
import { join as
|
|
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 =
|
|
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
|
|
26923
|
-
|
|
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 ${
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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(
|
|
32188
|
+
async function fireTrigger(dispatch2) {
|
|
30185
32189
|
const s = state2();
|
|
30186
|
-
|
|
30187
|
-
s.firing.
|
|
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(
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}:${
|
|
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
|
-
|
|
30236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
30477
|
-
import { existsSync as existsSync17, readdirSync as
|
|
30478
|
-
import { join as
|
|
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(
|
|
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
|
-
|
|
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") ??
|
|
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 =
|
|
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 =
|
|
33163
|
-
|
|
33164
|
-
|
|
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.
|
|
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(
|
|
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 =
|
|
35374
|
+
const sessionsDir = join21(paths.agentDir(agent.id), "sessions");
|
|
33290
35375
|
let deleted = 0;
|
|
33291
35376
|
if (existsSync17(sessionsDir)) {
|
|
33292
|
-
for (const file of
|
|
35377
|
+
for (const file of readdirSync10(sessionsDir)) {
|
|
33293
35378
|
if (!file.endsWith(".jsonl")) continue;
|
|
33294
35379
|
try {
|
|
33295
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
34177
|
-
|
|
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
|
-
|
|
34181
|
-
|
|
34182
|
-
|
|
34183
|
-
|
|
34184
|
-
controller.
|
|
34185
|
-
|
|
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
|
-
|
|
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
|
|
34251
|
-
import { join as
|
|
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:
|
|
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
|
-
|
|
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
|
|
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/
|
|
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
|
|
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
|
-
|
|
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)
|
|
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 =
|
|
34529
|
-
const zipPath =
|
|
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
|
-
|
|
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" ?
|
|
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
|
|
34555
|
-
import { Hono as
|
|
34556
|
-
var teamTemplatesRouter = new
|
|
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 =
|
|
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
|
|
34887
|
-
import { Hono as
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
35492
|
-
var triggersRouter = new
|
|
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
|
|
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
|
});
|