bazilion 0.14.2 → 0.16.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 +1752 -825
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +16624 -9803
- package/dist/daemon.js.map +1 -1
- package/dist/migrations/0001_init.sql +218 -3
- package/dist/web/client/assets/ChatPane-CQZzxOir.js +76 -0
- package/dist/web/client/assets/{ConfirmDialog-_bmG-wVG.js → ConfirmDialog-MicTTwdy.js} +1 -1
- package/dist/web/client/assets/{FieldRow-Do1cBkRc.js → FieldRow-DJ2Gx4Du.js} +1 -1
- package/dist/web/client/assets/{ProductionTeamPolicyEditor-XP-QJul4.js → ProductionTeamPolicyEditor-DqUnN_ce.js} +1 -1
- package/dist/web/client/assets/{ProfileCommunicationEditor-CwdFPdpk.js → ProfileCommunicationEditor-DArXub0b.js} +1 -1
- package/dist/web/client/assets/ResultCard-Cps_lXVN.js +1 -0
- package/dist/web/client/assets/ResultSourceConversation-CtzJ8QA-.js +1 -0
- package/dist/web/client/assets/TeamTabs-DbIk5vvT.js +1 -0
- package/dist/web/client/assets/{TemplatesTabs-D4WHvs6b.js → TemplatesTabs-CatJIjGi.js} +1 -1
- package/dist/web/client/assets/{UnsavedChangesGuard-B1aLqiK5.js → UnsavedChangesGuard-BCsLw-mU.js} +1 -1
- package/dist/web/client/assets/_id-B5Ov7cdO.js +1 -0
- package/dist/web/client/assets/{_id-BH3v_vI_.js → _id-BEDmIXVf.js} +1 -1
- package/dist/web/client/assets/_id-BFM_tn_s.js +1 -0
- package/dist/web/client/assets/_id-CrnEVcw8.js +1 -0
- package/dist/web/client/assets/{_id-3w4PFfma.js → _id-DxopIW-y.js} +1 -1
- package/dist/web/client/assets/{activity-A_HrG1Tv.js → activity-CrG8Bg60.js} +1 -1
- package/dist/web/client/assets/{agents-C8OVKq3N.js → agents-CMPOFcs-.js} +1 -1
- package/dist/web/client/assets/{agents-BeeFCt2c.js → agents-O4soKZND.js} +1 -1
- package/dist/web/client/assets/{approvals-BIdZ9BSn.js → approvals-CwDu_lrA.js} +1 -1
- package/dist/web/client/assets/{attention-B9bUrxiU.js → attention-BY8MMuM_.js} +1 -1
- package/dist/web/client/assets/chevron-right-Cf6vKeLq.js +1 -0
- package/dist/web/client/assets/{config-By86S7k-.js → config-Cn4iBJFz.js} +1 -1
- package/dist/web/client/assets/{inbox-DlXQCJLe.js → inbox-CWXql0K3.js} +1 -1
- package/dist/web/client/assets/{index-CLdmsmiN.js → index-Bq2zrG2_.js} +2 -2
- package/dist/web/client/assets/{learning-BqZIw2nA.js → learning-DAm4dyyc.js} +1 -1
- package/dist/web/client/assets/login-BsABI8Ux.js +1 -0
- package/dist/web/client/assets/{mcp-DKSY2yVa.js → mcp-loLB0wuh.js} +1 -1
- package/dist/web/client/assets/{members-9GuOiq6J.js → members-BJ0ikkTl.js} +1 -1
- package/dist/web/client/assets/{memory-DiGMI6FC.js → memory-Drn9Gv53.js} +1 -1
- package/dist/web/client/assets/{policy-TBGQffw5.js → policy-pQadZyk0.js} +1 -1
- package/dist/web/client/assets/results-9exU6f7m.js +1 -0
- package/dist/web/client/assets/routes-jw1PuqiR.js +1 -0
- package/dist/web/client/assets/{services-1Z398CES.js → services-BLso1BhA.js} +1 -1
- package/dist/web/client/assets/{skills-BP2gciFt.js → skills-BodqX3OI.js} +1 -1
- package/dist/web/client/assets/styles-d1yjBpud.css +2 -0
- package/dist/web/client/assets/{teams-DBIivGT7.js → teams-ByQedXZi.js} +1 -1
- package/dist/web/client/assets/{teams-tNoMDJ_M.js → teams-SYsF1bOR.js} +1 -1
- package/dist/web/client/assets/telegram-DC9PLFbB.js +1 -0
- package/dist/web/client/assets/{tokens-eDWCGQQ2.js → tokens-DGy8Ijpo.js} +1 -1
- package/dist/web/client/assets/{triggers-DZME3IcK.js → triggers-BXpVmHDG.js} +1 -1
- package/dist/web/client/assets/{welcome-B49sh_uf.js → welcome-BKsnsCxJ.js} +1 -1
- package/dist/web/client/assets/wire-constants-CBD-lTaa.js +1 -0
- package/dist/web/client/assets/{x-CHc7_-TH.js → x-BK4xNCzS.js} +1 -1
- package/dist/web/server/assets/ChatPane-CS5oI7ef.js +2666 -0
- package/dist/web/server/assets/ResultCard-B3zD9N0t.js +143 -0
- package/dist/web/server/assets/ResultSourceConversation-BpVWBRYq.js +64 -0
- package/dist/web/server/assets/{TeamTabs-Co3n2exj.js → TeamTabs-TCKjCvMA.js} +4 -0
- package/dist/web/server/assets/{_id-sNu5aXZt.js → _id-5pgKoid8.js} +2 -1
- package/dist/web/server/assets/_id-BDEpe6k0.js +6 -0
- package/dist/web/server/assets/{_id-DxCnEFpm.js → _id-BG2nkGxa.js} +3 -4
- package/dist/web/server/assets/{_id-BqWTB8Wl.js → _id-B_-CDHOi2.js} +1 -1
- package/dist/web/server/assets/{_id-BeKhPKw3.js → _id-BnqodC1c2.js} +7 -3
- package/dist/web/server/assets/{_id-DvlDkk04.js → _id-CeyHEsCZ.js} +1 -1
- package/dist/web/server/assets/{_id-BYKpT2Tu.js → _id-DFHB9JFQ.js} +1 -1
- package/dist/web/server/assets/{_id-DYU6uIBx.js → _id-DOnInzLb.js} +1 -1
- package/dist/web/server/assets/{_id-HMy7jM3P.js → _id-DrB9uR1b.js} +1 -1
- package/dist/web/server/assets/{_id-BAA9KJBU.js → _id-DtIYuJJX.js} +1 -1
- package/dist/web/server/assets/_id-DwZXhLlL.js +779 -0
- package/dist/web/server/assets/_id-wyjrf0JG.js +17 -0
- package/dist/web/server/assets/{_tanstack-start-manifest_v-Duhf0z7H.js → _tanstack-start-manifest_v-D7gKv6m6.js} +97 -70
- package/dist/web/server/assets/{activity-9VP6QDTH.js → activity-0VUkStOu.js} +1 -1
- package/dist/web/server/assets/{activity-BWv1goK6.js → activity-5fWKPDxj.js} +2 -2
- package/dist/web/server/assets/{activity-LjpblbDL.js → activity-C88qrNqv.js} +1 -1
- package/dist/web/server/assets/{agents-DjyQk94u.js → agents-C86K6HvS.js} +1 -1
- package/dist/web/server/assets/{agents-BJAv919x.js → agents-CSXLaM2W.js} +1 -1
- package/dist/web/server/assets/{approvals-BYi84gwl.js → approvals-C3MysI6l.js} +1 -1
- package/dist/web/server/assets/{approvals-7JOoBchn.js → approvals-DTdrltyk.js} +1 -1
- package/dist/web/server/assets/{approvals-Bz123liE.js → approvals-DXoN-T57.js} +15 -1
- package/dist/web/server/assets/{attention-UhqPK_Ro.js → attention-DLeLCKah.js} +1 -1
- package/dist/web/server/assets/{auth-7BHTH-KS.js → auth-CDZp9aEk.js} +1 -1
- package/dist/web/server/assets/{config-BMiajTZP.js → config-BVTHvljR.js} +1 -1
- package/dist/web/server/assets/{daemon-client-CI5NHwNC.js → daemon-client-xWyYKAcx.js} +59 -0
- package/dist/web/server/assets/{inbox-Dv55rGYq.js → inbox-PQTrjtOw.js} +1 -1
- package/dist/web/server/assets/{learning-C2upa1CE.js → learning-K0cmDsZR.js} +1 -1
- package/dist/web/server/assets/{login-BNj3U6ok.js → login-B_-9L8TU.js} +7 -2
- package/dist/web/server/assets/{login-Be8eP4Jp.js → login-rDox7PRD.js} +5 -2
- package/dist/web/server/assets/{mcp-BvnvlVFd.js → mcp-Rtraochg.js} +1 -1
- package/dist/web/server/assets/{members-DiYtr-zm.js → members-CD3GiTfF.js} +1 -1
- package/dist/web/server/assets/{members-DHYTVgwb.js → members-cc5SeEaW.js} +2 -2
- package/dist/web/server/assets/{members-BP3UTqu1.js → members-grAKFWgv.js} +1 -1
- package/dist/web/server/assets/{memory-CkXUgf5s.js → memory-C5i_GxlY.js} +2 -2
- package/dist/web/server/assets/{memory-DTFEmfF3.js → memory-CVqnyUaM.js} +1 -1
- package/dist/web/server/assets/{memory-BqV-Afy1.js → memory-egFpBD1g.js} +1 -1
- package/dist/web/server/assets/{policy-Bvvav8fh.js → policy-Dgjb0_gK.js} +2 -2
- package/dist/web/server/assets/{policy-BddK4xom.js → policy-bLKwvnja.js} +1 -1
- package/dist/web/server/assets/{policy-18fdTFO4.js → policy-ooSBgiDS.js} +1 -1
- package/dist/web/server/assets/results-CfNsFncJ.js +173 -0
- package/dist/web/server/assets/results-DXdYtHaq.js +12 -0
- package/dist/web/server/assets/results-N-kP4iFT.js +12 -0
- package/dist/web/server/assets/{router-CY_pCgP1.js → router-B6g_s7W3.js} +60 -43
- package/dist/web/server/assets/{routes-COtkyZG1.js → routes-BqSaZvlu.js} +3 -3
- package/dist/web/server/assets/{routes-BgHisAxH.js → routes-Dsh8F_7i.js} +2 -2
- package/dist/web/server/assets/{routes-DAU2g7Uz.js → routes-pcRy_Sf8.js} +1 -1
- package/dist/web/server/assets/{services-DozuJtzX.js → services-DlZA8fRL.js} +1 -1
- package/dist/web/server/assets/{skills-DfGSJTCB.js → skills-i2duvgW-.js} +1 -1
- package/dist/web/server/assets/{teams-be05ci17.js → teams-6RYIj1Xm.js} +1 -1
- package/dist/web/server/assets/{teams-DPnAX4wD.js → teams-B1AI3NfN.js} +1 -1
- package/dist/web/server/assets/{telegram-1AWL6lFw.js → telegram-C4q0YTVn.js} +1 -1
- package/dist/web/server/assets/{telegram-Dj4Wwhd6.js → telegram-CoNeOgqZ.js} +465 -1
- package/dist/web/server/assets/{telegram-DbOFpTFm.js → telegram-Zd1rIZxg.js} +1 -1
- package/dist/web/server/assets/{tokens-DjM8GsgS.js → tokens-RFquA2Bx.js} +1 -1
- package/dist/web/server/assets/{triggers-Nezfw-Bd.js → triggers-DVD-iE-N.js} +1 -1
- package/dist/web/server/server.js +31 -27
- package/dist/worker.js +1765 -710
- package/dist/worker.js.map +1 -1
- package/package.json +3 -3
- package/dist/web/client/assets/TeamTabs-DIRiKcqx.js +0 -1
- package/dist/web/client/assets/_id-Cx7o4Zmf.js +0 -1
- package/dist/web/client/assets/_id-DIiVVxBl.js +0 -1
- package/dist/web/client/assets/chevron-right-VWrk_dTX.js +0 -1
- package/dist/web/client/assets/login-CCMPXiny.js +0 -1
- package/dist/web/client/assets/routes-g3yUOhCb.js +0 -1
- package/dist/web/client/assets/styles-B5vcgXln.css +0 -2
- package/dist/web/client/assets/telegram-CJG_DFdI.js +0 -1
- package/dist/web/client/assets/wire-constants-zZ7Wjb9C.js +0 -74
- package/dist/web/server/assets/ChatPane-CgSe7tsy.js +0 -1429
- package/dist/web/server/assets/_id-ndxFHx0t.js +0 -381
package/dist/worker.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// ../daemon/src/runtime/worker/entry.ts
|
|
4
4
|
import { randomUUID as randomUUID20 } from "crypto";
|
|
5
|
-
import { Type as
|
|
5
|
+
import { Type as Type5 } from "typebox";
|
|
6
6
|
|
|
7
7
|
// ../daemon/src/core/agent/delete.ts
|
|
8
8
|
import { randomUUID } from "crypto";
|
|
@@ -1128,15 +1128,15 @@ function translatePiEvent(e) {
|
|
|
1128
1128
|
const m = e.message;
|
|
1129
1129
|
if (m.role !== "assistant") return [];
|
|
1130
1130
|
const out = [];
|
|
1131
|
-
const
|
|
1132
|
-
if (
|
|
1131
|
+
const text2 = extractAssistantText(m);
|
|
1132
|
+
if (text2) out.push({ type: "assistant_message", text: text2 });
|
|
1133
1133
|
for (const block of m.content ?? []) {
|
|
1134
1134
|
if (block.type === "toolCall") {
|
|
1135
1135
|
out.push({
|
|
1136
1136
|
type: "tool_call",
|
|
1137
1137
|
id: block.id,
|
|
1138
1138
|
name: block.name,
|
|
1139
|
-
arguments: JSON.stringify(block.arguments ?? {})
|
|
1139
|
+
arguments: block.name === "ask_user" ? "{}" : JSON.stringify(block.arguments ?? {})
|
|
1140
1140
|
});
|
|
1141
1141
|
}
|
|
1142
1142
|
}
|
|
@@ -1148,9 +1148,18 @@ function translatePiEvent(e) {
|
|
|
1148
1148
|
return out;
|
|
1149
1149
|
}
|
|
1150
1150
|
case "tool_execution_end": {
|
|
1151
|
-
|
|
1151
|
+
if (e.toolName === "ask_user")
|
|
1152
|
+
return [
|
|
1153
|
+
{
|
|
1154
|
+
type: "tool_result",
|
|
1155
|
+
id: e.toolCallId,
|
|
1156
|
+
name: e.toolName,
|
|
1157
|
+
result: "Question outcome is available through the question card or authorized conversation history."
|
|
1158
|
+
}
|
|
1159
|
+
];
|
|
1160
|
+
const text2 = extractToolResultText(e.result);
|
|
1152
1161
|
if (e.isError) {
|
|
1153
|
-
return [{ type: "tool_error", id: e.toolCallId, name: e.toolName, error:
|
|
1162
|
+
return [{ type: "tool_error", id: e.toolCallId, name: e.toolName, error: text2 }];
|
|
1154
1163
|
}
|
|
1155
1164
|
const images = extractToolResultImages(e.result);
|
|
1156
1165
|
return [
|
|
@@ -1158,7 +1167,8 @@ function translatePiEvent(e) {
|
|
|
1158
1167
|
type: "tool_result",
|
|
1159
1168
|
id: e.toolCallId,
|
|
1160
1169
|
name: e.toolName,
|
|
1161
|
-
result:
|
|
1170
|
+
result: text2,
|
|
1171
|
+
resultReference: extractResultReference(e.result),
|
|
1162
1172
|
...images.length > 0 ? { images } : {}
|
|
1163
1173
|
}
|
|
1164
1174
|
];
|
|
@@ -1178,7 +1188,11 @@ function extractAssistantToolCalls(m) {
|
|
|
1178
1188
|
const out = [];
|
|
1179
1189
|
for (const block of m.content ?? []) {
|
|
1180
1190
|
if (block.type === "toolCall") {
|
|
1181
|
-
out.push({
|
|
1191
|
+
out.push({
|
|
1192
|
+
id: block.id,
|
|
1193
|
+
name: block.name,
|
|
1194
|
+
arguments: block.name === "ask_user" ? "{}" : JSON.stringify(block.arguments ?? {})
|
|
1195
|
+
});
|
|
1182
1196
|
}
|
|
1183
1197
|
}
|
|
1184
1198
|
return out;
|
|
@@ -1215,7 +1229,7 @@ function stringifyContent(content) {
|
|
|
1215
1229
|
}
|
|
1216
1230
|
return out;
|
|
1217
1231
|
}
|
|
1218
|
-
function piMessagesToProviderView(messages) {
|
|
1232
|
+
function piMessagesToProviderView(messages, questionResultVisible) {
|
|
1219
1233
|
const out = [];
|
|
1220
1234
|
for (const m of messages) {
|
|
1221
1235
|
switch (m.role) {
|
|
@@ -1231,21 +1245,42 @@ function piMessagesToProviderView(messages) {
|
|
|
1231
1245
|
}
|
|
1232
1246
|
case "assistant": {
|
|
1233
1247
|
const am = m;
|
|
1234
|
-
const
|
|
1248
|
+
const text2 = extractAssistantText(am);
|
|
1235
1249
|
const toolCalls = extractAssistantToolCalls(am);
|
|
1236
|
-
const msg = { role: "assistant", content:
|
|
1250
|
+
const msg = { role: "assistant", content: text2 };
|
|
1237
1251
|
if (toolCalls.length > 0) msg.toolCalls = toolCalls;
|
|
1238
1252
|
out.push(msg);
|
|
1239
1253
|
break;
|
|
1240
1254
|
}
|
|
1241
1255
|
case "toolResult": {
|
|
1242
1256
|
const tr = m;
|
|
1257
|
+
if (["repository_context", "coding_environment", "coding_command", "coding_receipt"].includes(
|
|
1258
|
+
tr.toolName ?? ""
|
|
1259
|
+
)) {
|
|
1260
|
+
out.push({
|
|
1261
|
+
role: "tool",
|
|
1262
|
+
toolCallId: tr.toolCallId,
|
|
1263
|
+
toolName: tr.toolName,
|
|
1264
|
+
content: "Coding context or command evidence retained privately. Captured transport output follows its communication approval; ask the Agent for the relevant result."
|
|
1265
|
+
});
|
|
1266
|
+
break;
|
|
1267
|
+
}
|
|
1268
|
+
if (tr.toolName === "ask_user" && !questionResultVisible?.(m)) {
|
|
1269
|
+
out.push({
|
|
1270
|
+
role: "tool",
|
|
1271
|
+
toolCallId: tr.toolCallId,
|
|
1272
|
+
toolName: "ask_user",
|
|
1273
|
+
content: "Question content has not been released for this view."
|
|
1274
|
+
});
|
|
1275
|
+
break;
|
|
1276
|
+
}
|
|
1243
1277
|
const images = extractToolResultImages({ content: tr.content });
|
|
1244
1278
|
out.push({
|
|
1245
1279
|
role: "tool",
|
|
1246
1280
|
content: stringifyContent(tr.content),
|
|
1247
1281
|
toolCallId: tr.toolCallId,
|
|
1248
1282
|
toolName: tr.toolName,
|
|
1283
|
+
result: extractResultReference(m),
|
|
1249
1284
|
...images.length > 0 ? { images } : {}
|
|
1250
1285
|
});
|
|
1251
1286
|
break;
|
|
@@ -1256,491 +1291,338 @@ function piMessagesToProviderView(messages) {
|
|
|
1256
1291
|
}
|
|
1257
1292
|
return out;
|
|
1258
1293
|
}
|
|
1294
|
+
function extractResultReference(value) {
|
|
1295
|
+
if (!value || typeof value !== "object") return void 0;
|
|
1296
|
+
const details = value.details;
|
|
1297
|
+
const id = details?.result?.resultId;
|
|
1298
|
+
return typeof id === "string" && /^[a-f0-9-]{36}$/.test(id) ? { resultId: id } : void 0;
|
|
1299
|
+
}
|
|
1259
1300
|
|
|
1260
|
-
// ../daemon/src/
|
|
1261
|
-
import {
|
|
1262
|
-
import { basename as
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1301
|
+
// ../daemon/src/lib/result-source.ts
|
|
1302
|
+
import { closeSync as closeSync2, constants as constants2, fstatSync as fstatSync2, openSync as openSync2, readSync, realpathSync as realpathSync2 } from "fs";
|
|
1303
|
+
import { basename as basename2, join as join13 } from "path";
|
|
1304
|
+
function readCanonicalSessionFile(directory, filename, sessionId, minimumEntryOffset = 0) {
|
|
1305
|
+
if (basename2(filename) !== filename) throw new Error("Invalid session filename");
|
|
1306
|
+
if (realpathSync2(directory) !== directory) throw new Error("Result session escaped its owner");
|
|
1307
|
+
const fd = openSync2(
|
|
1308
|
+
join13(directory, filename),
|
|
1309
|
+
constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK
|
|
1310
|
+
);
|
|
1311
|
+
try {
|
|
1312
|
+
const stat2 = fstatSync2(fd);
|
|
1313
|
+
if (!stat2.isFile() || stat2.size > 64 * 1024 * 1024)
|
|
1314
|
+
throw new Error("Result source session is unavailable or too large");
|
|
1315
|
+
const buffer = Buffer.alloc(stat2.size + 1);
|
|
1316
|
+
let count2 = 0;
|
|
1317
|
+
while (count2 < buffer.length) {
|
|
1318
|
+
const n = readSync(fd, buffer, count2, buffer.length - count2, null);
|
|
1319
|
+
if (n === 0) break;
|
|
1320
|
+
count2 += n;
|
|
1321
|
+
}
|
|
1322
|
+
if (count2 !== stat2.size) throw new Error("Result source session changed while reading");
|
|
1323
|
+
const entries = [];
|
|
1324
|
+
let position = 0;
|
|
1325
|
+
for (const line of buffer.subarray(0, count2).toString("utf8").split("\n")) {
|
|
1326
|
+
if (line.trim()) entries.push({ entry: JSON.parse(line), offset: position });
|
|
1327
|
+
position += Buffer.byteLength(line) + 1;
|
|
1328
|
+
}
|
|
1329
|
+
const header = entries[0]?.entry;
|
|
1330
|
+
if (header?.type !== "session" || header.id !== sessionId)
|
|
1331
|
+
throw new Error("Result source session does not match the active Agent session");
|
|
1332
|
+
return entries.filter((row, index) => index === 0 || row.offset >= minimumEntryOffset).map((row) => row.entry);
|
|
1333
|
+
} finally {
|
|
1334
|
+
closeSync2(fd);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// ../daemon/src/runtime/pi/coding.ts
|
|
1266
1339
|
import {
|
|
1267
|
-
|
|
1268
|
-
DefaultResourceLoader,
|
|
1269
|
-
SessionManager,
|
|
1270
|
-
SettingsManager
|
|
1340
|
+
createLocalBashOperations
|
|
1271
1341
|
} from "@earendil-works/pi-coding-agent";
|
|
1342
|
+
import { Type } from "typebox";
|
|
1272
1343
|
|
|
1273
|
-
// ../daemon/src/
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
var PROVIDER_ALIASES = {
|
|
1277
|
-
bedrock: "amazon-bedrock",
|
|
1278
|
-
"azure-openai": "azure-openai-responses"
|
|
1279
|
-
};
|
|
1280
|
-
var PROTECTED_PROVIDER_NAMES = [
|
|
1281
|
-
"anthropic",
|
|
1282
|
-
"openai",
|
|
1283
|
-
"openai-codex",
|
|
1284
|
-
"google",
|
|
1285
|
-
"google-vertex",
|
|
1286
|
-
"azure-openai",
|
|
1287
|
-
"bedrock",
|
|
1288
|
-
"mistral",
|
|
1289
|
-
"groq",
|
|
1290
|
-
"cerebras",
|
|
1291
|
-
"xai",
|
|
1292
|
-
"zai",
|
|
1293
|
-
"huggingface",
|
|
1294
|
-
"openrouter",
|
|
1295
|
-
"vercel-ai-gateway",
|
|
1296
|
-
"deepseek",
|
|
1297
|
-
"fireworks",
|
|
1298
|
-
"together",
|
|
1299
|
-
"baseten",
|
|
1300
|
-
"moonshotai",
|
|
1301
|
-
"moonshotai-cn",
|
|
1302
|
-
"kimi-coding",
|
|
1303
|
-
"minimax",
|
|
1304
|
-
"minimax-cn",
|
|
1305
|
-
"qwen-token-plan",
|
|
1306
|
-
"qwen-token-plan-cn",
|
|
1307
|
-
"qwen-token-plan-individual",
|
|
1308
|
-
"xiaomi",
|
|
1309
|
-
"xiaomi-token-plan-ams",
|
|
1310
|
-
"xiaomi-token-plan-cn",
|
|
1311
|
-
"xiaomi-token-plan-sgp",
|
|
1312
|
-
"ant-ling",
|
|
1313
|
-
"nvidia",
|
|
1314
|
-
"opencode",
|
|
1315
|
-
"opencode-go",
|
|
1316
|
-
"zai-coding-cn",
|
|
1317
|
-
"github-copilot",
|
|
1318
|
-
"cloudflare-ai-gateway",
|
|
1319
|
-
"cloudflare-workers-ai",
|
|
1320
|
-
"lmstudio",
|
|
1321
|
-
"ollama",
|
|
1322
|
-
"llamacpp"
|
|
1323
|
-
];
|
|
1324
|
-
var LOCAL_PROVIDERS = {
|
|
1325
|
-
lmstudio: {
|
|
1326
|
-
baseUrl: "http://127.0.0.1:1234/v1",
|
|
1327
|
-
apiKey: "lm-studio",
|
|
1328
|
-
authHeader: false
|
|
1329
|
-
},
|
|
1330
|
-
ollama: { baseUrl: "http://127.0.0.1:11434/v1", apiKey: "ollama", authHeader: false },
|
|
1331
|
-
llamacpp: { baseUrl: "http://127.0.0.1:8080/v1", apiKey: "no-key", authHeader: true }
|
|
1332
|
-
};
|
|
1333
|
-
var PROVIDER_API_KEY_ENV = {
|
|
1334
|
-
anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
|
|
1335
|
-
openai: ["OPENAI_API_KEY"],
|
|
1336
|
-
google: ["GEMINI_API_KEY"],
|
|
1337
|
-
"azure-openai": ["AZURE_OPENAI_API_KEY"],
|
|
1338
|
-
mistral: ["MISTRAL_API_KEY"],
|
|
1339
|
-
groq: ["GROQ_API_KEY"],
|
|
1340
|
-
cerebras: ["CEREBRAS_API_KEY"],
|
|
1341
|
-
xai: ["XAI_API_KEY"],
|
|
1342
|
-
zai: ["ZAI_API_KEY"],
|
|
1343
|
-
huggingface: ["HF_TOKEN"],
|
|
1344
|
-
openrouter: ["OPENROUTER_API_KEY"],
|
|
1345
|
-
"vercel-ai-gateway": ["AI_GATEWAY_API_KEY"],
|
|
1346
|
-
deepseek: ["DEEPSEEK_API_KEY"],
|
|
1347
|
-
fireworks: ["FIREWORKS_API_KEY"],
|
|
1348
|
-
together: ["TOGETHER_API_KEY"],
|
|
1349
|
-
baseten: ["BASETEN_API_KEY"],
|
|
1350
|
-
moonshotai: ["MOONSHOT_API_KEY"],
|
|
1351
|
-
"moonshotai-cn": ["MOONSHOT_CN_API_KEY"],
|
|
1352
|
-
"kimi-coding": ["KIMI_API_KEY"],
|
|
1353
|
-
minimax: ["MINIMAX_API_KEY"],
|
|
1354
|
-
"minimax-cn": ["MINIMAX_CN_API_KEY"],
|
|
1355
|
-
"qwen-token-plan": ["QWEN_TOKEN_PLAN_API_KEY"],
|
|
1356
|
-
"qwen-token-plan-cn": ["QWEN_TOKEN_PLAN_CN_API_KEY"],
|
|
1357
|
-
"qwen-token-plan-individual": ["QWEN_TOKEN_PLAN_API_KEY"],
|
|
1358
|
-
xiaomi: ["XIAOMI_API_KEY"],
|
|
1359
|
-
"xiaomi-token-plan-ams": ["XIAOMI_TOKEN_PLAN_AMS_API_KEY"],
|
|
1360
|
-
"xiaomi-token-plan-cn": ["XIAOMI_TOKEN_PLAN_CN_API_KEY"],
|
|
1361
|
-
"xiaomi-token-plan-sgp": ["XIAOMI_TOKEN_PLAN_SGP_API_KEY"],
|
|
1362
|
-
"ant-ling": ["ANT_LING_API_KEY"],
|
|
1363
|
-
nvidia: ["NVIDIA_API_KEY"],
|
|
1364
|
-
opencode: ["OPENCODE_API_KEY"],
|
|
1365
|
-
"opencode-go": ["OPENCODE_GO_API_KEY"],
|
|
1366
|
-
"zai-coding-cn": ["ZAI_CODING_CN_API_KEY"],
|
|
1367
|
-
"github-copilot": ["COPILOT_GITHUB_TOKEN"],
|
|
1368
|
-
"cloudflare-ai-gateway": ["CLOUDFLARE_API_KEY"],
|
|
1369
|
-
"cloudflare-workers-ai": ["CLOUDFLARE_API_KEY"],
|
|
1370
|
-
lmstudio: ["LMSTUDIO_API_KEY"],
|
|
1371
|
-
ollama: ["OLLAMA_API_KEY"],
|
|
1372
|
-
llamacpp: ["LLAMACPP_API_KEY"]
|
|
1373
|
-
};
|
|
1374
|
-
var PROVIDER_CREDENTIAL_ENV = {
|
|
1375
|
-
bedrock: [
|
|
1376
|
-
"AWS_PROFILE",
|
|
1377
|
-
"AWS_BEARER_TOKEN_BEDROCK",
|
|
1378
|
-
"AWS_ACCESS_KEY_ID",
|
|
1379
|
-
"AWS_SECRET_ACCESS_KEY",
|
|
1380
|
-
"AWS_SESSION_TOKEN",
|
|
1381
|
-
"AWS_REGION",
|
|
1382
|
-
"AWS_DEFAULT_REGION"
|
|
1383
|
-
],
|
|
1384
|
-
"google-vertex": [
|
|
1385
|
-
"GOOGLE_CLOUD_PROJECT",
|
|
1386
|
-
"GOOGLE_CLOUD_LOCATION",
|
|
1387
|
-
"GOOGLE_APPLICATION_CREDENTIALS"
|
|
1388
|
-
],
|
|
1389
|
-
"cloudflare-ai-gateway": ["CLOUDFLARE_API_KEY", "CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_GATEWAY_ID"],
|
|
1390
|
-
"cloudflare-workers-ai": ["CLOUDFLARE_API_KEY", "CLOUDFLARE_ACCOUNT_ID"]
|
|
1344
|
+
// ../daemon/src/core/coding-environment/config.ts
|
|
1345
|
+
var CodingEnvironmentValidationError = class extends Error {
|
|
1346
|
+
status = 400;
|
|
1391
1347
|
};
|
|
1392
|
-
function
|
|
1393
|
-
|
|
1394
|
-
}
|
|
1395
|
-
function
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
case "llamacpp":
|
|
1409
|
-
return env.LLAMACPP_URL ?? LOCAL_PROVIDERS.llamacpp?.baseUrl;
|
|
1410
|
-
case "vercel-ai-gateway":
|
|
1411
|
-
return env.AI_GATEWAY_BASE_URL;
|
|
1412
|
-
default:
|
|
1413
|
-
return void 0;
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
function providerCredentialEnv(providerName, env) {
|
|
1417
|
-
const result = {};
|
|
1418
|
-
for (const name of PROVIDER_CREDENTIAL_ENV[providerName] ?? []) {
|
|
1419
|
-
const value = env[name];
|
|
1420
|
-
if (value !== void 0) result[name] = value;
|
|
1421
|
-
}
|
|
1422
|
-
return Object.keys(result).length > 0 ? result : void 0;
|
|
1423
|
-
}
|
|
1424
|
-
function fallbackApi(providerName) {
|
|
1425
|
-
switch (providerName) {
|
|
1426
|
-
case "anthropic":
|
|
1427
|
-
return "anthropic-messages";
|
|
1428
|
-
case "google":
|
|
1429
|
-
return "google-generative-ai";
|
|
1430
|
-
case "google-vertex":
|
|
1431
|
-
return "google-vertex";
|
|
1432
|
-
case "azure-openai":
|
|
1433
|
-
return "azure-openai-responses";
|
|
1434
|
-
case "bedrock":
|
|
1435
|
-
return "bedrock-converse-stream";
|
|
1436
|
-
case "openai-codex":
|
|
1437
|
-
return "openai-codex-responses";
|
|
1438
|
-
default:
|
|
1439
|
-
return "openai-completions";
|
|
1440
|
-
}
|
|
1348
|
+
function invalid(field) {
|
|
1349
|
+
throw new CodingEnvironmentValidationError(`Invalid coding environment ${field}`);
|
|
1350
|
+
}
|
|
1351
|
+
function object(value, keys2, field) {
|
|
1352
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) invalid(field);
|
|
1353
|
+
const record = value;
|
|
1354
|
+
if (Object.keys(record).some((key) => !keys2.includes(key))) invalid(field);
|
|
1355
|
+
return record;
|
|
1356
|
+
}
|
|
1357
|
+
function text(value, max, field) {
|
|
1358
|
+
if (typeof value !== "string" || !value.trim() || Buffer.byteLength(value) > max || Array.from(value).some((char) => {
|
|
1359
|
+
const code = char.charCodeAt(0);
|
|
1360
|
+
return code === 127 || code < 32 && code !== 9 && code !== 10;
|
|
1361
|
+
}))
|
|
1362
|
+
invalid(field);
|
|
1363
|
+
return value;
|
|
1441
1364
|
}
|
|
1442
|
-
function
|
|
1365
|
+
function codingRelativePath(value, field = "cwd") {
|
|
1366
|
+
const path = text(value, 4096, field);
|
|
1367
|
+
if (path === ".") return path;
|
|
1368
|
+
const parts = path.split("/");
|
|
1369
|
+
if (parts.length > 16 || path.includes("\\") || Array.from(path).some((char) => char.charCodeAt(0) < 32) || parts.some((part) => !part || part === "." || part === ".." || part === ".git"))
|
|
1370
|
+
invalid(field);
|
|
1371
|
+
return path;
|
|
1372
|
+
}
|
|
1373
|
+
function validateCodingCommand(input) {
|
|
1374
|
+
const value = object(input, ["command", "cwd", "purpose", "timeoutSeconds"], "command");
|
|
1375
|
+
const command = text(value.command, 4096, "command");
|
|
1376
|
+
if (command.includes("\0")) invalid("command");
|
|
1377
|
+
const cwd = codingRelativePath(value.cwd);
|
|
1378
|
+
if (!["runtime", "dependency", "prepare", "build", "test"].includes(String(value.purpose)))
|
|
1379
|
+
invalid("purpose");
|
|
1380
|
+
if (!Number.isInteger(value.timeoutSeconds) || Number(value.timeoutSeconds) < 1 || Number(value.timeoutSeconds) > 300)
|
|
1381
|
+
invalid("timeout");
|
|
1443
1382
|
return {
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
baseUrl: baseUrl ?? "",
|
|
1449
|
-
reasoning: false,
|
|
1450
|
-
input: ["text"],
|
|
1451
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
1452
|
-
contextWindow: 32768,
|
|
1453
|
-
maxTokens: 4096
|
|
1383
|
+
command,
|
|
1384
|
+
cwd,
|
|
1385
|
+
purpose: value.purpose,
|
|
1386
|
+
timeoutSeconds: value.timeoutSeconds
|
|
1454
1387
|
};
|
|
1455
1388
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1389
|
+
|
|
1390
|
+
// ../daemon/src/lib/coding-environment/diagnostics.ts
|
|
1391
|
+
var CODING_OUTPUT_BYTES = 64 * 1024;
|
|
1392
|
+
function diagnosticTail(value, limit) {
|
|
1393
|
+
const bytes = Buffer.from(value);
|
|
1394
|
+
if (bytes.length <= limit) return { text: value, truncated: false };
|
|
1395
|
+
let offset = bytes.length - limit;
|
|
1396
|
+
while (offset < bytes.length && ((bytes[offset] ?? 0) & 192) === 128) offset++;
|
|
1397
|
+
return { text: bytes.subarray(offset).toString("utf8"), truncated: true };
|
|
1398
|
+
}
|
|
1399
|
+
var CodingDiagnostics = class {
|
|
1400
|
+
decoder = new TextDecoder();
|
|
1401
|
+
secrets;
|
|
1402
|
+
lookbehind;
|
|
1403
|
+
pending = "";
|
|
1404
|
+
text = "";
|
|
1405
|
+
truncated = false;
|
|
1406
|
+
finished = false;
|
|
1407
|
+
constructor(secrets) {
|
|
1408
|
+
this.secrets = [...new Set(secrets.filter(Boolean))].sort((a, b) => b.length - a.length);
|
|
1409
|
+
this.lookbehind = Math.max(1, ...this.secrets.map((secret) => secret.length)) - 1;
|
|
1410
|
+
}
|
|
1411
|
+
append(bytes) {
|
|
1412
|
+
if (this.finished) throw new Error("Coding diagnostics already finalized");
|
|
1413
|
+
for (let offset = 0; offset < bytes.length; offset += 8192) {
|
|
1414
|
+
this.pending += this.decoder.decode(bytes.subarray(offset, offset + 8192), { stream: true });
|
|
1415
|
+
this.flush(false);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
finish() {
|
|
1419
|
+
if (!this.finished) {
|
|
1420
|
+
this.pending += this.decoder.decode();
|
|
1421
|
+
this.flush(true);
|
|
1422
|
+
this.finished = true;
|
|
1423
|
+
}
|
|
1424
|
+
return { diagnostic: this.text, truncated: this.truncated };
|
|
1425
|
+
}
|
|
1426
|
+
flush(final) {
|
|
1427
|
+
let end = final ? this.pending.length : Math.max(0, this.pending.length - this.lookbehind);
|
|
1428
|
+
const last = this.pending.charCodeAt(end - 1);
|
|
1429
|
+
if (!final && last >= 55296 && last <= 56319) end--;
|
|
1430
|
+
let offset = 0;
|
|
1431
|
+
let safe = "";
|
|
1432
|
+
while (offset < end) {
|
|
1433
|
+
const secret = this.secrets.find((value) => this.pending.startsWith(value, offset));
|
|
1434
|
+
if (secret) {
|
|
1435
|
+
safe += "[redacted]";
|
|
1436
|
+
offset += secret.length;
|
|
1437
|
+
} else {
|
|
1438
|
+
const code = this.pending.charCodeAt(offset);
|
|
1439
|
+
const char = this.pending[offset] ?? "";
|
|
1440
|
+
safe += code < 32 && code !== 9 && code !== 10 || code >= 127 && code <= 159 ? `\\u${code.toString(16).padStart(4, "0")}` : char;
|
|
1441
|
+
offset++;
|
|
1478
1442
|
}
|
|
1443
|
+
}
|
|
1444
|
+
this.pending = this.pending.slice(offset);
|
|
1445
|
+
const tail = diagnosticTail(this.text + safe, CODING_OUTPUT_BYTES);
|
|
1446
|
+
this.text = tail.text;
|
|
1447
|
+
this.truncated ||= tail.truncated;
|
|
1448
|
+
}
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
// ../daemon/src/runtime/coding-directory.ts
|
|
1452
|
+
import { realpathSync as realpathSync4 } from "fs";
|
|
1453
|
+
import { join as join15 } from "path";
|
|
1454
|
+
|
|
1455
|
+
// ../daemon/src/lib/repository-context/files.ts
|
|
1456
|
+
import { createHash as createHash6 } from "crypto";
|
|
1457
|
+
import {
|
|
1458
|
+
closeSync as closeSync3,
|
|
1459
|
+
constants as constants3,
|
|
1460
|
+
fstatSync as fstatSync3,
|
|
1461
|
+
lstatSync as lstatSync2,
|
|
1462
|
+
opendirSync,
|
|
1463
|
+
openSync as openSync3,
|
|
1464
|
+
readSync as readSync2,
|
|
1465
|
+
realpathSync as realpathSync3
|
|
1466
|
+
} from "fs";
|
|
1467
|
+
import { isAbsolute as isAbsolute2, join as join14 } from "path";
|
|
1468
|
+
var hash = (value) => createHash6("sha256").update(value).digest("hex");
|
|
1469
|
+
var ContextReadError = class extends Error {
|
|
1470
|
+
code;
|
|
1471
|
+
constructor(code) {
|
|
1472
|
+
super(code);
|
|
1473
|
+
this.code = code;
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
function identity(stat2) {
|
|
1477
|
+
return `${stat2.dev}:${stat2.ino}`;
|
|
1478
|
+
}
|
|
1479
|
+
function stamp(stat2) {
|
|
1480
|
+
return `${identity(stat2)}:${stat2.size}:${stat2.mtimeMs}:${stat2.ctimeMs}`;
|
|
1481
|
+
}
|
|
1482
|
+
function relativeParts(target) {
|
|
1483
|
+
if (target.length > 4096 || isAbsolute2(target) || [...target].some(
|
|
1484
|
+
(char) => char === "\\" || char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127
|
|
1485
|
+
) || target.split("/").some((part) => part === ".." || part === ".git"))
|
|
1486
|
+
throw new ContextReadError("unsafe_target");
|
|
1487
|
+
const parts = target.split("/").filter((part) => part && part !== ".");
|
|
1488
|
+
if (parts.length > 17) throw new ContextReadError("depth_limit");
|
|
1489
|
+
return parts;
|
|
1490
|
+
}
|
|
1491
|
+
var ContextDirectory = class _ContextDirectory {
|
|
1492
|
+
fd;
|
|
1493
|
+
identity;
|
|
1494
|
+
originalStamp;
|
|
1495
|
+
children = [];
|
|
1496
|
+
listing;
|
|
1497
|
+
observedFiles = /* @__PURE__ */ new Map();
|
|
1498
|
+
registeredPath;
|
|
1499
|
+
label;
|
|
1500
|
+
parent;
|
|
1501
|
+
constructor(registeredPath, label = ".", parent) {
|
|
1502
|
+
this.registeredPath = registeredPath;
|
|
1503
|
+
this.label = label;
|
|
1504
|
+
this.parent = parent;
|
|
1505
|
+
if (process.platform !== "linux") throw new ContextReadError("safe_reads_unavailable");
|
|
1506
|
+
const path = parent ? join14(parent.path, registeredPath) : realpathSync3(registeredPath);
|
|
1507
|
+
this.fd = openSync3(path, constants3.O_RDONLY | constants3.O_DIRECTORY | constants3.O_NOFOLLOW);
|
|
1508
|
+
const stat2 = fstatSync3(this.fd);
|
|
1509
|
+
this.identity = hash(identity(stat2));
|
|
1510
|
+
this.originalStamp = identity(stat2);
|
|
1511
|
+
}
|
|
1512
|
+
get path() {
|
|
1513
|
+
return `/proc/self/fd/${this.fd}`;
|
|
1514
|
+
}
|
|
1515
|
+
directory(name) {
|
|
1516
|
+
const child = new _ContextDirectory(
|
|
1517
|
+
name,
|
|
1518
|
+
this.label === "." ? name : `${this.label}/${name}`,
|
|
1519
|
+
this
|
|
1479
1520
|
);
|
|
1521
|
+
this.children.push(child);
|
|
1522
|
+
return child;
|
|
1480
1523
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
baseUrl: baseUrl ?? local.baseUrl,
|
|
1489
|
-
api: "openai-completions",
|
|
1490
|
-
authHeader: local.authHeader,
|
|
1491
|
-
apiKey: apiKey ?? local.apiKey,
|
|
1492
|
-
models: [model]
|
|
1493
|
-
});
|
|
1494
|
-
} else if (baseUrl) {
|
|
1495
|
-
runtime.registerProvider(canonicalName, { baseUrl });
|
|
1496
|
-
}
|
|
1497
|
-
return runtime;
|
|
1498
|
-
}
|
|
1499
|
-
async function createProtectedPiRuntime(options) {
|
|
1500
|
-
const env = {};
|
|
1501
|
-
for (const field of options.credentialEnv ?? []) env[field.name] = field.value;
|
|
1502
|
-
return createBazilionPiRuntime({
|
|
1503
|
-
providerName: options.providerName,
|
|
1504
|
-
modelId: options.modelId,
|
|
1505
|
-
env,
|
|
1506
|
-
apiKey: options.apiKey,
|
|
1507
|
-
baseUrl: options.baseUrl
|
|
1508
|
-
});
|
|
1509
|
-
}
|
|
1510
|
-
function resolvePiModel(runtime, providerName, modelId, baseUrl) {
|
|
1511
|
-
const known = runtime.getModel(piProviderName(providerName), modelId);
|
|
1512
|
-
if (known) return baseUrl ? { ...known, baseUrl } : known;
|
|
1513
|
-
return fallbackPiModel(providerName, modelId, baseUrl);
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
// ../daemon/src/runtime/auth/openai-codex.ts
|
|
1517
|
-
import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
1518
|
-
|
|
1519
|
-
// ../daemon/src/runtime/providers/pi-adapter.ts
|
|
1520
|
-
import {
|
|
1521
|
-
Type
|
|
1522
|
-
} from "@earendil-works/pi-ai";
|
|
1523
|
-
|
|
1524
|
-
// ../daemon/src/runtime/session/prompt.ts
|
|
1525
|
-
import { existsSync as existsSync10, readdirSync as readdirSync6 } from "fs";
|
|
1526
|
-
import { join as join13 } from "path";
|
|
1527
|
-
|
|
1528
|
-
// ../daemon/src/runtime/safe-files.ts
|
|
1529
|
-
import {
|
|
1530
|
-
closeSync as closeSync2,
|
|
1531
|
-
constants as constants2,
|
|
1532
|
-
fstatSync as fstatSync2,
|
|
1533
|
-
lstatSync as lstatSync2,
|
|
1534
|
-
mkdirSync as mkdirSync5,
|
|
1535
|
-
openSync as openSync2,
|
|
1536
|
-
readFileSync as readFileSync9,
|
|
1537
|
-
realpathSync as realpathSync2
|
|
1538
|
-
} from "fs";
|
|
1539
|
-
import { dirname as dirname3, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve4 } from "path";
|
|
1540
|
-
function resolveRealDirectory(path) {
|
|
1541
|
-
const requested = resolve4(path);
|
|
1542
|
-
const entry = lstatSync2(requested);
|
|
1543
|
-
if (entry.isSymbolicLink() || !entry.isDirectory()) {
|
|
1544
|
-
throw new Error("expected a real directory");
|
|
1524
|
+
entry(name) {
|
|
1525
|
+
try {
|
|
1526
|
+
return lstatSync2(join14(this.path, name));
|
|
1527
|
+
} catch (error) {
|
|
1528
|
+
if (error.code === "ENOENT") return null;
|
|
1529
|
+
throw new ContextReadError("unreadable");
|
|
1530
|
+
}
|
|
1545
1531
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
if (entry.isSymbolicLink()) throw new Error("symbolic links are not allowed");
|
|
1551
|
-
if (!entry.isFile()) throw new Error("target is not a regular file");
|
|
1552
|
-
let fd;
|
|
1553
|
-
try {
|
|
1554
|
-
fd = openSync2(path, constants2.O_RDONLY | noFollowFlag());
|
|
1555
|
-
if (!fstatSync2(fd).isFile()) throw new Error("target is not a regular file");
|
|
1556
|
-
return readFileSync9(fd, "utf8");
|
|
1557
|
-
} finally {
|
|
1558
|
-
if (fd !== void 0) closeSync2(fd);
|
|
1532
|
+
list() {
|
|
1533
|
+
const names = this.listNames();
|
|
1534
|
+
this.listing = JSON.stringify(names);
|
|
1535
|
+
return names;
|
|
1559
1536
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
// ../daemon/src/runtime/session/prompt.ts
|
|
1566
|
-
var SANDBOX_SKILLS_DIR = "/skills";
|
|
1567
|
-
function loadPromptSkills(skillsDir, attachedNames) {
|
|
1568
|
-
if (!existsSync10(skillsDir) || attachedNames.length === 0) return [];
|
|
1569
|
-
const attached = new Set(attachedNames);
|
|
1570
|
-
const discovered = readdirSync6(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && attached.has(entry.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
1571
|
-
const loaded = [];
|
|
1572
|
-
for (const entry of discovered) {
|
|
1573
|
-
const hostDir = join13(skillsDir, entry.name);
|
|
1537
|
+
listNames() {
|
|
1538
|
+
const handle = opendirSync(this.path);
|
|
1539
|
+
const names = [];
|
|
1574
1540
|
try {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
} catch {
|
|
1541
|
+
let entry = handle.readSync();
|
|
1542
|
+
while (entry) {
|
|
1543
|
+
if (names.length >= 16384) throw new ContextReadError("directory_entry_limit");
|
|
1544
|
+
names.push(entry.name);
|
|
1545
|
+
entry = handle.readSync();
|
|
1546
|
+
}
|
|
1547
|
+
return names.sort();
|
|
1548
|
+
} finally {
|
|
1549
|
+
handle.closeSync();
|
|
1585
1550
|
}
|
|
1586
1551
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1552
|
+
read(name, maxBytes) {
|
|
1553
|
+
const entry = this.entry(name);
|
|
1554
|
+
if (!entry) {
|
|
1555
|
+
this.observedFiles.set(name, null);
|
|
1556
|
+
return null;
|
|
1557
|
+
}
|
|
1558
|
+
if (!entry.isFile() || entry.isSymbolicLink()) throw new ContextReadError("unsafe_file");
|
|
1559
|
+
if (entry.size > maxBytes) throw new ContextReadError("byte_limit");
|
|
1560
|
+
let fd;
|
|
1593
1561
|
try {
|
|
1594
|
-
|
|
1562
|
+
fd = openSync3(
|
|
1563
|
+
join14(this.path, name),
|
|
1564
|
+
constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK
|
|
1565
|
+
);
|
|
1566
|
+
const before = fstatSync3(fd);
|
|
1567
|
+
if (!before.isFile() || stamp(entry) !== stamp(before))
|
|
1568
|
+
throw new ContextReadError("source_changed");
|
|
1569
|
+
const buffer = Buffer.alloc(Math.min(maxBytes + 1, before.size + 1));
|
|
1570
|
+
let size = 0;
|
|
1571
|
+
while (size < buffer.length) {
|
|
1572
|
+
const count2 = readSync2(fd, buffer, size, buffer.length - size, null);
|
|
1573
|
+
if (!count2) break;
|
|
1574
|
+
size += count2;
|
|
1575
|
+
}
|
|
1576
|
+
if (size > maxBytes || size !== before.size || stamp(fstatSync3(fd)) !== stamp(before)) {
|
|
1577
|
+
throw new ContextReadError("source_changed");
|
|
1578
|
+
}
|
|
1579
|
+
this.observedFiles.set(name, stamp(before));
|
|
1580
|
+
return buffer.subarray(0, size);
|
|
1595
1581
|
} catch (error) {
|
|
1596
|
-
if (error
|
|
1597
|
-
throw new
|
|
1582
|
+
if (error instanceof ContextReadError) throw error;
|
|
1583
|
+
throw new ContextReadError("unreadable");
|
|
1584
|
+
} finally {
|
|
1585
|
+
if (fd !== void 0) closeSync3(fd);
|
|
1598
1586
|
}
|
|
1599
|
-
};
|
|
1600
|
-
return {
|
|
1601
|
-
"AGENTS.md": read("AGENTS.md"),
|
|
1602
|
-
"SOUL.md": read("SOUL.md"),
|
|
1603
|
-
"TOOLS.md": read("TOOLS.md"),
|
|
1604
|
-
"IDENTITY.md": read("IDENTITY.md"),
|
|
1605
|
-
"BOOTSTRAP.md": read("BOOTSTRAP.md")
|
|
1606
|
-
};
|
|
1607
|
-
}
|
|
1608
|
-
function buildSystemPrompt(agent, options = {}) {
|
|
1609
|
-
const parts = [];
|
|
1610
|
-
const homeDocuments = options.homeDocuments ?? loadProtectedHomeDocuments(agent.agent.dir);
|
|
1611
|
-
const contextBlocks = [];
|
|
1612
|
-
for (const file of CONTEXT_FILE_ORDER) {
|
|
1613
|
-
const content = homeDocuments[file]?.trimEnd();
|
|
1614
|
-
if (!content) continue;
|
|
1615
|
-
contextBlocks.push(`## ${file}
|
|
1616
|
-
|
|
1617
|
-
${content}`);
|
|
1618
|
-
}
|
|
1619
|
-
if (contextBlocks.length > 0) {
|
|
1620
|
-
parts.push(`# Project Context
|
|
1621
|
-
|
|
1622
|
-
${contextBlocks.join("\n\n")}`);
|
|
1623
|
-
}
|
|
1624
|
-
const bootstrap = homeDocuments["BOOTSTRAP.md"]?.trimEnd();
|
|
1625
|
-
if (bootstrap) {
|
|
1626
|
-
parts.push(
|
|
1627
|
-
[
|
|
1628
|
-
"# First-Run Ritual",
|
|
1629
|
-
"",
|
|
1630
|
-
"This is your first session. The document below is **conversational guidance**, not a checklist to execute in one shot. It describes a multi-turn Q&A you should have with the human, one question per turn.",
|
|
1631
|
-
"",
|
|
1632
|
-
"## Hard rules",
|
|
1633
|
-
"- Your first reply is ONLY a greeting + ONE question. No tool calls. Wait for the human to answer.",
|
|
1634
|
-
"- Each subsequent turn: at most one new question. Wait between turns.",
|
|
1635
|
-
"- Only after the ritual is complete (you have enough to write IDENTITY.md): call `home_write` once, then `bootstrap_done`.",
|
|
1636
|
-
"",
|
|
1637
|
-
"## BOOTSTRAP.md",
|
|
1638
|
-
"",
|
|
1639
|
-
bootstrap
|
|
1640
|
-
].join("\n")
|
|
1641
|
-
);
|
|
1642
1587
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
const
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
const runtimeDir = options.sandboxMode === "docker" ? skill.sandboxDir : skill.hostDir;
|
|
1661
|
-
return [
|
|
1662
|
-
`## ${skill.name}`,
|
|
1663
|
-
"",
|
|
1664
|
-
skill.description,
|
|
1665
|
-
"",
|
|
1666
|
-
`Runtime directory: \`${runtimeDir}\`. Resolve any relative scripts or assets mentioned by this skill from that directory.`,
|
|
1667
|
-
"",
|
|
1668
|
-
skill.body
|
|
1669
|
-
].filter((line, index, lines) => line !== "" || lines[index - 1] !== "").join("\n").trimEnd();
|
|
1670
|
-
});
|
|
1671
|
-
const unavailableNote = unavailable.length > 0 ? `Attached but unavailable or invalid: ${unavailable.join(", ")}.` : "";
|
|
1672
|
-
parts.push(
|
|
1673
|
-
[
|
|
1674
|
-
"# Available Skills",
|
|
1675
|
-
"",
|
|
1676
|
-
"Use these attached skill instructions when they are relevant to the task.",
|
|
1677
|
-
"",
|
|
1678
|
-
...skillBlocks,
|
|
1679
|
-
...unavailableNote ? ["", unavailableNote] : []
|
|
1680
|
-
].join("\n")
|
|
1681
|
-
);
|
|
1588
|
+
validate(skipGit = false, names) {
|
|
1589
|
+
const currentPath = this.parent ? join14(this.parent.path, this.registeredPath) : realpathSync3(this.registeredPath);
|
|
1590
|
+
const stat2 = lstatSync2(currentPath);
|
|
1591
|
+
if (!stat2.isDirectory() || identity(stat2) !== this.originalStamp)
|
|
1592
|
+
throw new ContextReadError("root_changed");
|
|
1593
|
+
if (this.listing !== void 0 && JSON.stringify(this.listNames()) !== this.listing) {
|
|
1594
|
+
throw new ContextReadError("source_changed");
|
|
1595
|
+
}
|
|
1596
|
+
for (const [name, previous] of this.observedFiles) {
|
|
1597
|
+
if (names && !names.has(name)) continue;
|
|
1598
|
+
const current = this.entry(name);
|
|
1599
|
+
if ((current ? stamp(current) : null) !== previous)
|
|
1600
|
+
throw new ContextReadError("source_changed");
|
|
1601
|
+
}
|
|
1602
|
+
for (const child of this.children) {
|
|
1603
|
+
if (!skipGit || !child.label.split("/").includes(".git")) child.validate(skipGit, names);
|
|
1604
|
+
}
|
|
1682
1605
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
`- ${agent.team.id} (${agent.team.name}): ${options.sandboxMode === "docker" ? "/workspace" : agent.team.path}`,
|
|
1687
|
-
"",
|
|
1688
|
-
"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`."
|
|
1689
|
-
];
|
|
1690
|
-
parts.push(teamLines.join("\n"));
|
|
1691
|
-
if (agent.team.userMd.trim()) {
|
|
1692
|
-
parts.push(
|
|
1693
|
-
`# About the User
|
|
1694
|
-
|
|
1695
|
-
Shared context about the human you're working with in this team. Both you and the human curate it. To update: call \`user_md_get\` (returns current content + an etag), merge your change into the full text, then call \`user_md_write\` with the merged content and the etag. Use this for STABLE user-specific facts (preferences, role, working hours, how they like to be addressed) \u2014 and to CORRECT stale entries when the human tells you something different from what's recorded. For project knowledge use \`memory_write\` instead; for personal notes about yourself use \`home_write\` on IDENTITY.md. **Do NOT send peer messages announcing USER.md changes \u2014 every agent in the team sees the new content in their system prompt on their next turn automatically.**
|
|
1696
|
-
|
|
1697
|
-
${agent.team.userMd.trim()}`
|
|
1698
|
-
);
|
|
1699
|
-
} else {
|
|
1700
|
-
parts.push(
|
|
1701
|
-
`# About the User
|
|
1702
|
-
|
|
1703
|
-
This team's USER.md is empty. As you learn STABLE facts about the human (preferences, role, working hours, how they like to be addressed), populate it via \`user_md_get\` then \`user_md_write\` (always get first \u2014 you need the etag). Reserve this for things you're confident are durable \u2014 project knowledge belongs in \`memory_write\`, personal notes about yourself in \`home_write\` on IDENTITY.md. **Do NOT send peer messages announcing USER.md changes \u2014 every agent in the team sees the new content in their system prompt on their next turn automatically.**`
|
|
1704
|
-
);
|
|
1606
|
+
close() {
|
|
1607
|
+
for (const child of this.children) child.close();
|
|
1608
|
+
closeSync3(this.fd);
|
|
1705
1609
|
}
|
|
1706
|
-
|
|
1707
|
-
if (reviewedLessons.length > 0) {
|
|
1708
|
-
parts.push(
|
|
1709
|
-
`# Reviewed lessons
|
|
1710
|
-
|
|
1711
|
-
These private lessons were explicitly approved by the human for your behavior. Follow them when relevant; current user instructions and higher-priority policy still win.
|
|
1610
|
+
};
|
|
1712
1611
|
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
for (const raw of lessons) {
|
|
1725
|
-
const lesson = raw.trim();
|
|
1726
|
-
if (!lesson) continue;
|
|
1727
|
-
const remaining = maxCharacters - used;
|
|
1728
|
-
if (remaining <= 0) break;
|
|
1729
|
-
selected.push(lesson.slice(0, remaining));
|
|
1730
|
-
used += Math.min(lesson.length, remaining);
|
|
1612
|
+
// ../daemon/src/runtime/coding-directory.ts
|
|
1613
|
+
function resolveCodingDirectory(root, cwd) {
|
|
1614
|
+
const directory = new ContextDirectory(root);
|
|
1615
|
+
try {
|
|
1616
|
+
let current = directory;
|
|
1617
|
+
for (const part of relativeParts(cwd)) current = current.directory(part);
|
|
1618
|
+
const path = realpathSync4(join15(root, cwd));
|
|
1619
|
+
directory.validate();
|
|
1620
|
+
return { path, identity: current.identity };
|
|
1621
|
+
} finally {
|
|
1622
|
+
directory.close();
|
|
1731
1623
|
}
|
|
1732
|
-
return selected;
|
|
1733
1624
|
}
|
|
1734
1625
|
|
|
1735
|
-
// ../daemon/src/runtime/shell/tooling.ts
|
|
1736
|
-
import { existsSync as existsSync11, lstatSync as lstatSync3, realpathSync as realpathSync3 } from "fs";
|
|
1737
|
-
import { dirname as dirname4, isAbsolute as isAbsolute4, join as join15, relative as relative4, sep as sep4 } from "path";
|
|
1738
|
-
import {
|
|
1739
|
-
createBashToolDefinition,
|
|
1740
|
-
createLocalBashOperations,
|
|
1741
|
-
defineTool as defineTool2
|
|
1742
|
-
} from "@earendil-works/pi-coding-agent";
|
|
1743
|
-
|
|
1744
1626
|
// ../daemon/src/runtime/shell/approval.ts
|
|
1745
1627
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
1746
1628
|
|
|
@@ -1836,8 +1718,8 @@ function classifyBroadFilesystemOperations(command, risks, seen) {
|
|
|
1836
1718
|
if (match.index === void 0 || !match[1]) continue;
|
|
1837
1719
|
const nameOffset = match[0].toLowerCase().indexOf(match[1].toLowerCase());
|
|
1838
1720
|
const start = match.index + nameOffset;
|
|
1839
|
-
const
|
|
1840
|
-
const tokens = shellTokens(
|
|
1721
|
+
const text2 = command.slice(start, match.index + match[0].length).trimEnd();
|
|
1722
|
+
const tokens = shellTokens(text2);
|
|
1841
1723
|
const name = tokens[0]?.toLowerCase();
|
|
1842
1724
|
const args = tokens.slice(1);
|
|
1843
1725
|
let risky = false;
|
|
@@ -1860,7 +1742,7 @@ function classifyBroadFilesystemOperations(command, risks, seen) {
|
|
|
1860
1742
|
code: "broad-destructive-operation",
|
|
1861
1743
|
message: "Recursively changes or deletes a broad filesystem target.",
|
|
1862
1744
|
start,
|
|
1863
|
-
end: start +
|
|
1745
|
+
end: start + text2.length
|
|
1864
1746
|
});
|
|
1865
1747
|
}
|
|
1866
1748
|
}
|
|
@@ -2076,12 +1958,12 @@ function assertEnvName(name) {
|
|
|
2076
1958
|
}
|
|
2077
1959
|
function buildScrubbedShellEnv(env, allowedKeys = []) {
|
|
2078
1960
|
const scrubbed = {};
|
|
2079
|
-
const
|
|
1961
|
+
const keys2 = new Set(SAFE_SHELL_ENV_KEYS);
|
|
2080
1962
|
for (const key of allowedKeys) {
|
|
2081
1963
|
assertEnvName(key);
|
|
2082
|
-
|
|
1964
|
+
keys2.add(key);
|
|
2083
1965
|
}
|
|
2084
|
-
for (const key of
|
|
1966
|
+
for (const key of keys2) {
|
|
2085
1967
|
const value = env[key];
|
|
2086
1968
|
if (value !== void 0) scrubbed[key] = value;
|
|
2087
1969
|
}
|
|
@@ -2098,22 +1980,19 @@ var BashApprovalDeniedError = class extends Error {
|
|
|
2098
1980
|
this.risks = risks;
|
|
2099
1981
|
}
|
|
2100
1982
|
};
|
|
1983
|
+
async function requireBashApproval(command, toolCallId, signal, host) {
|
|
1984
|
+
const risks = classifyBashCommand(command);
|
|
1985
|
+
if (!risks.length) return;
|
|
1986
|
+
const decision = host ? await host.requestApproval({ toolCallId, command, risks, signal }) : "denied";
|
|
1987
|
+
if (decision !== "approved") throw new BashApprovalDeniedError(risks);
|
|
1988
|
+
}
|
|
2101
1989
|
function createApprovalGatedBashTool(base, host) {
|
|
2102
1990
|
return defineTool({
|
|
2103
1991
|
...base,
|
|
2104
1992
|
description: `${base.description} Commands classified as dangerous require operator approval before execution.`,
|
|
2105
1993
|
executionMode: "sequential",
|
|
2106
1994
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
2107
|
-
|
|
2108
|
-
if (risks.length > 0) {
|
|
2109
|
-
const decision = host ? await host.requestApproval({
|
|
2110
|
-
toolCallId,
|
|
2111
|
-
command: params.command,
|
|
2112
|
-
risks,
|
|
2113
|
-
signal
|
|
2114
|
-
}) : "denied";
|
|
2115
|
-
if (decision !== "approved") throw new BashApprovalDeniedError(risks);
|
|
2116
|
-
}
|
|
1995
|
+
await requireBashApproval(params.command, toolCallId, signal, host);
|
|
2117
1996
|
return base.execute(toolCallId, params, signal, onUpdate, ctx);
|
|
2118
1997
|
}
|
|
2119
1998
|
});
|
|
@@ -2122,10 +2001,35 @@ function createApprovalGatedBashTool(base, host) {
|
|
|
2122
2001
|
// ../daemon/src/runtime/shell/docker.ts
|
|
2123
2002
|
import { spawn } from "child_process";
|
|
2124
2003
|
import { randomUUID as randomUUID18 } from "crypto";
|
|
2125
|
-
import { closeSync as
|
|
2004
|
+
import { closeSync as closeSync4, constants as constants4, mkdtempSync as mkdtempSync2, openSync as openSync4, rmSync as rmSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
2126
2005
|
import { access, lstat, realpath, stat } from "fs/promises";
|
|
2127
2006
|
import { tmpdir as tmpdir2 } from "os";
|
|
2128
|
-
import { delimiter, isAbsolute as isAbsolute3, join as
|
|
2007
|
+
import { delimiter, isAbsolute as isAbsolute3, join as join16, relative as relative2, sep as sep3 } from "path";
|
|
2008
|
+
|
|
2009
|
+
// ../daemon/src/runtime/shell/coding.ts
|
|
2010
|
+
function validateDockerCodingSelection(input) {
|
|
2011
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
2012
|
+
throw new Error("Invalid Docker coding selection");
|
|
2013
|
+
const value = input;
|
|
2014
|
+
if (Object.keys(value).some((key) => !["revision", "cwd", "env"].includes(key)) || !Number.isSafeInteger(value.revision) || value.revision < 0)
|
|
2015
|
+
throw new Error("Invalid Docker coding revision");
|
|
2016
|
+
const cwd = codingRelativePath(value.cwd);
|
|
2017
|
+
if (!value.env || typeof value.env !== "object" || Array.isArray(value.env))
|
|
2018
|
+
throw new Error("Invalid Docker coding environment");
|
|
2019
|
+
const env = {};
|
|
2020
|
+
for (const [key, entry] of Object.entries(value.env)) {
|
|
2021
|
+
if (key === "CI" && (entry === "true" || entry === "false")) env.CI = entry;
|
|
2022
|
+
else if (key === "NO_COLOR" && (entry === "0" || entry === "1")) env.NO_COLOR = entry;
|
|
2023
|
+
else if (key === "TZ" && entry === "UTC") env.TZ = entry;
|
|
2024
|
+
else throw new Error("Invalid Docker coding environment");
|
|
2025
|
+
}
|
|
2026
|
+
return { revision: value.revision, cwd, env };
|
|
2027
|
+
}
|
|
2028
|
+
function codingContainerCwd(cwd) {
|
|
2029
|
+
return cwd === "." ? "/workspace" : `/workspace/${codingRelativePath(cwd)}`;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
// ../daemon/src/runtime/shell/docker.ts
|
|
2129
2033
|
var CONTAINER_WORKDIR = "/workspace";
|
|
2130
2034
|
var MAX_TIMEOUT_MS = 2147483647;
|
|
2131
2035
|
var MAX_STDERR_CHARS = 16 * 1024;
|
|
@@ -2133,6 +2037,7 @@ var CLEANUP_TIMEOUT_MS = 650;
|
|
|
2133
2037
|
var CLEANUP_RETRY_DELAY_MS = 100;
|
|
2134
2038
|
var CLEANUP_ATTEMPTS = 3;
|
|
2135
2039
|
var DOCKER_CONTEXT_TIMEOUT_MS = 2e3;
|
|
2040
|
+
var DOCKER_CREATE_TIMEOUT_MS = 5e3;
|
|
2136
2041
|
var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2137
2042
|
var CONTAINER_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]+$/;
|
|
2138
2043
|
var DOCKER_CLIENT_ENV_KEYS = [
|
|
@@ -2205,7 +2110,7 @@ function buildDockerRunSpec(input) {
|
|
|
2205
2110
|
"--entrypoint",
|
|
2206
2111
|
"/bin/bash",
|
|
2207
2112
|
"--workdir",
|
|
2208
|
-
|
|
2113
|
+
codingContainerCwd(input.codingCwd ?? "."),
|
|
2209
2114
|
// A bind mount is read/write unless `readonly` is present. No other mount is added.
|
|
2210
2115
|
"--mount",
|
|
2211
2116
|
`type=bind,source=${input.mountSource},target=${CONTAINER_WORKDIR},bind-recursive=disabled`
|
|
@@ -2251,7 +2156,7 @@ function buildDockerRunSpec(input) {
|
|
|
2251
2156
|
` } : {}
|
|
2252
2157
|
};
|
|
2253
2158
|
}
|
|
2254
|
-
function createPreparedDockerBashOperations(runtime) {
|
|
2159
|
+
function createPreparedDockerBashOperations(runtime, lifecycle) {
|
|
2255
2160
|
validatePreparedDockerRuntime(runtime);
|
|
2256
2161
|
return {
|
|
2257
2162
|
async exec(command, cwd, { onData, signal, timeout }) {
|
|
@@ -2268,9 +2173,10 @@ function createPreparedDockerBashOperations(runtime) {
|
|
|
2268
2173
|
await assertDockerExecutableIdentity(runtime.dockerPath, runtime.executableIdentity);
|
|
2269
2174
|
await assertLocalDockerSocket(runtime.endpoint);
|
|
2270
2175
|
const dockerClientEnv = { DOCKER_HOST: runtime.endpoint };
|
|
2176
|
+
if (runtime.coding) resolveCodingDirectory(workspace.source, runtime.coding.cwd);
|
|
2271
2177
|
const currentImageId = await inspectSandboxImage(
|
|
2272
2178
|
runtime.dockerPath,
|
|
2273
|
-
runtime.image,
|
|
2179
|
+
runtime.coding ? runtime.imageId : runtime.image,
|
|
2274
2180
|
dockerClientEnv
|
|
2275
2181
|
);
|
|
2276
2182
|
if (currentImageId !== runtime.imageId) {
|
|
@@ -2278,11 +2184,12 @@ function createPreparedDockerBashOperations(runtime) {
|
|
|
2278
2184
|
}
|
|
2279
2185
|
await assertDockerExecutableIdentity(runtime.dockerPath, runtime.executableIdentity);
|
|
2280
2186
|
if (signal?.aborted) throw new Error("aborted");
|
|
2281
|
-
const containerName = `bazilion-bash-${process.pid}-${randomUUID18().replaceAll("-", "")}`;
|
|
2187
|
+
const containerName = lifecycle?.name("bash") ?? `bazilion-bash-${process.pid}-${randomUUID18().replaceAll("-", "")}`;
|
|
2282
2188
|
const spec = buildDockerRunSpec({
|
|
2283
2189
|
image: runtime.image,
|
|
2284
2190
|
resolvedImage: runtime.imageId,
|
|
2285
|
-
env: { ...runtime.containerEnv },
|
|
2191
|
+
env: { ...runtime.containerEnv, ...runtime.coding?.env },
|
|
2192
|
+
codingCwd: runtime.coding?.cwd,
|
|
2286
2193
|
dockerPath: runtime.dockerPath,
|
|
2287
2194
|
hostEnv: dockerClientEnv,
|
|
2288
2195
|
command,
|
|
@@ -2292,29 +2199,129 @@ function createPreparedDockerBashOperations(runtime) {
|
|
|
2292
2199
|
uid: runtime.uid,
|
|
2293
2200
|
gid: runtime.gid
|
|
2294
2201
|
});
|
|
2295
|
-
|
|
2202
|
+
await lifecycle?.beforeCreate({
|
|
2203
|
+
dockerPath: runtime.dockerPath,
|
|
2204
|
+
executableIdentity: runtime.executableIdentity,
|
|
2205
|
+
endpoint: runtime.endpoint,
|
|
2206
|
+
containerName
|
|
2207
|
+
});
|
|
2208
|
+
try {
|
|
2209
|
+
return await executeRegisteredDockerRun(
|
|
2210
|
+
spec,
|
|
2211
|
+
{ onData, signal, timeout, timeoutMs },
|
|
2212
|
+
lifecycle
|
|
2213
|
+
);
|
|
2214
|
+
} finally {
|
|
2215
|
+
await lifecycle?.afterRemove(containerName);
|
|
2216
|
+
}
|
|
2296
2217
|
}
|
|
2297
2218
|
};
|
|
2298
2219
|
}
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2220
|
+
function runProtectedContainerCreate(spec, options) {
|
|
2221
|
+
if (options?.signal?.aborted) return Promise.reject(new Error("aborted"));
|
|
2222
|
+
let envFileFd;
|
|
2223
|
+
try {
|
|
2224
|
+
if (spec.envFileContent !== void 0) {
|
|
2225
|
+
envFileFd = openAnonymousEnvFile(spec.envFileContent);
|
|
2226
|
+
}
|
|
2227
|
+
} catch (error) {
|
|
2228
|
+
return Promise.reject(
|
|
2229
|
+
new Error(
|
|
2230
|
+
`Docker sandbox could not prepare its preflight environment: ${error instanceof Error ? error.message : String(error)}`
|
|
2231
|
+
)
|
|
2232
|
+
);
|
|
2233
|
+
}
|
|
2234
|
+
return new Promise((resolve6, reject) => {
|
|
2235
|
+
let child;
|
|
2236
|
+
try {
|
|
2237
|
+
child = spawn(spec.executable, ["container", "create", ...spec.args.slice(1)], {
|
|
2238
|
+
stdio: ["ignore", "ignore", "pipe", envFileFd ?? "ignore"],
|
|
2239
|
+
windowsHide: true,
|
|
2240
|
+
env: spec.processEnv
|
|
2241
|
+
});
|
|
2242
|
+
} catch (error) {
|
|
2243
|
+
if (envFileFd !== void 0) closeSync4(envFileFd);
|
|
2244
|
+
reject(error instanceof Error ? formatSpawnError(spec.executable, error) : error);
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
if (envFileFd !== void 0) closeSync4(envFileFd);
|
|
2248
|
+
let settled = false;
|
|
2249
|
+
let stderr = "";
|
|
2250
|
+
let timeoutHandle;
|
|
2251
|
+
let killSettleHandle;
|
|
2252
|
+
let termination;
|
|
2253
|
+
const finish = (error) => {
|
|
2254
|
+
if (settled) return;
|
|
2255
|
+
settled = true;
|
|
2256
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
2257
|
+
if (killSettleHandle) clearTimeout(killSettleHandle);
|
|
2258
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
2259
|
+
if (error) reject(error);
|
|
2260
|
+
else resolve6();
|
|
2261
|
+
};
|
|
2262
|
+
const commandTimeout = options?.timeoutMs !== void 0 && options.timeoutMs <= DOCKER_CREATE_TIMEOUT_MS;
|
|
2263
|
+
const timeoutError = () => commandTimeout ? new Error(`timeout:${options?.timeout}`) : new Error(
|
|
2264
|
+
`Docker sandbox could not create its protected preflight container within ${DOCKER_CREATE_TIMEOUT_MS}ms`
|
|
2265
|
+
);
|
|
2266
|
+
child.stderr?.on("data", (data) => {
|
|
2267
|
+
stderr = `${stderr}${data.toString("utf8")}`.slice(-MAX_STDERR_CHARS);
|
|
2268
|
+
});
|
|
2269
|
+
child.once("error", (error) => finish(formatSpawnError(spec.executable, error)));
|
|
2270
|
+
child.once("close", (code) => {
|
|
2271
|
+
if (termination) finish(termination);
|
|
2272
|
+
else if (code === 0) finish();
|
|
2273
|
+
else finish(formatDockerCreateError(spec, stderr, code));
|
|
2274
|
+
});
|
|
2275
|
+
const terminate = (error) => {
|
|
2276
|
+
if (settled || termination) return;
|
|
2277
|
+
termination = error;
|
|
2278
|
+
child.kill("SIGKILL");
|
|
2279
|
+
killSettleHandle = setTimeout(() => finish(error), CLEANUP_TIMEOUT_MS);
|
|
2280
|
+
};
|
|
2281
|
+
const onAbort = () => terminate(new Error("aborted"));
|
|
2282
|
+
timeoutHandle = setTimeout(
|
|
2283
|
+
() => terminate(timeoutError()),
|
|
2284
|
+
Math.min(options?.timeoutMs ?? DOCKER_CREATE_TIMEOUT_MS, DOCKER_CREATE_TIMEOUT_MS)
|
|
2285
|
+
);
|
|
2286
|
+
options?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2287
|
+
if (options?.signal?.aborted) onAbort();
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
async function recheckProtectedMount(mount) {
|
|
2291
|
+
const sourceInfo = await lstat(mount.source);
|
|
2292
|
+
if (sourceInfo.isSymbolicLink() || !sourceInfo.isDirectory()) {
|
|
2293
|
+
throw new Error("Docker sandbox mount no longer matches preflight");
|
|
2294
|
+
}
|
|
2295
|
+
const sourceRoot = await resolveRequiredRoot(mount.sourceRoot);
|
|
2296
|
+
const source = await resolveMountSource(mount.source);
|
|
2297
|
+
if (sourceRoot !== mount.sourceRoot || source !== mount.source || !isWithin(sourceRoot, source)) {
|
|
2298
|
+
throw new Error("Docker sandbox mount no longer matches preflight");
|
|
2299
|
+
}
|
|
2309
2300
|
return { ...mount, source, sourceRoot };
|
|
2310
2301
|
}
|
|
2311
2302
|
async function resolveRequiredRoot(root) {
|
|
2312
2303
|
if (!isAbsolute3(root)) throw new Error("Docker sandbox mount root must be absolute");
|
|
2313
2304
|
return resolveMountSource(root);
|
|
2314
2305
|
}
|
|
2306
|
+
async function resolveAbsoluteDockerPath(requested, hostEnv) {
|
|
2307
|
+
validateDockerPath(requested);
|
|
2308
|
+
const candidates = isAbsolute3(requested) ? [requested] : (hostEnv.PATH ?? "").split(delimiter).filter(Boolean).map((dir) => join16(dir, requested));
|
|
2309
|
+
for (const candidate of candidates) {
|
|
2310
|
+
try {
|
|
2311
|
+
await access(candidate, constants4.X_OK);
|
|
2312
|
+
const info = await stat(candidate);
|
|
2313
|
+
if (!info.isFile()) continue;
|
|
2314
|
+
const canonical = await realpath(candidate);
|
|
2315
|
+
if (!isAbsolute3(canonical)) continue;
|
|
2316
|
+
return canonical;
|
|
2317
|
+
} catch {
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
throw new Error(`Docker sandbox executable is unavailable: ${requested}`);
|
|
2321
|
+
}
|
|
2315
2322
|
async function inspectDockerExecutableIdentity(dockerPath) {
|
|
2316
2323
|
try {
|
|
2317
|
-
await access(dockerPath,
|
|
2324
|
+
await access(dockerPath, constants4.X_OK);
|
|
2318
2325
|
const info = await stat(dockerPath, { bigint: true });
|
|
2319
2326
|
if (!info.isFile()) throw new Error("not a regular file");
|
|
2320
2327
|
return {
|
|
@@ -2349,12 +2356,13 @@ async function assertLocalDockerSocket(endpoint) {
|
|
|
2349
2356
|
try {
|
|
2350
2357
|
const info = await stat(socketPath);
|
|
2351
2358
|
if (!info.isSocket()) throw new Error("not a Unix socket");
|
|
2352
|
-
await access(socketPath,
|
|
2359
|
+
await access(socketPath, constants4.R_OK | constants4.W_OK);
|
|
2353
2360
|
} catch {
|
|
2354
2361
|
throw new Error("Docker sandbox local Unix socket is unavailable");
|
|
2355
2362
|
}
|
|
2356
2363
|
}
|
|
2357
2364
|
function validatePreparedDockerRuntime(runtime) {
|
|
2365
|
+
if (runtime.coding) validateDockerCodingSelection(runtime.coding);
|
|
2358
2366
|
if (!isAbsolute3(runtime.dockerPath)) {
|
|
2359
2367
|
throw new Error("protected Docker executable must be absolute");
|
|
2360
2368
|
}
|
|
@@ -2372,17 +2380,17 @@ function validatePreparedDockerRuntime(runtime) {
|
|
|
2372
2380
|
throw new Error("protected Docker container environment does not match policy");
|
|
2373
2381
|
}
|
|
2374
2382
|
}
|
|
2375
|
-
function validateDockerExecutableIdentity(
|
|
2376
|
-
if (typeof
|
|
2383
|
+
function validateDockerExecutableIdentity(identity2) {
|
|
2384
|
+
if (typeof identity2 !== "object" || identity2 === null) {
|
|
2377
2385
|
throw new Error("protected Docker executable identity is invalid");
|
|
2378
2386
|
}
|
|
2379
2387
|
const fields = [
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2388
|
+
identity2.device,
|
|
2389
|
+
identity2.inode,
|
|
2390
|
+
identity2.mode,
|
|
2391
|
+
identity2.size,
|
|
2392
|
+
identity2.modifiedTimeNs,
|
|
2393
|
+
identity2.changedTimeNs
|
|
2386
2394
|
];
|
|
2387
2395
|
if (fields.some((field) => typeof field !== "string" || !/^\d+$/.test(field))) {
|
|
2388
2396
|
throw new Error("protected Docker executable identity is invalid");
|
|
@@ -2417,7 +2425,7 @@ function createDockerBashOperations(options) {
|
|
|
2417
2425
|
const dockerClientEnv = await resolveLocalDockerClientEnv(dockerPath, hostEnv);
|
|
2418
2426
|
const resolvedImage = await inspectSandboxImage(dockerPath, image, dockerClientEnv);
|
|
2419
2427
|
if (signal?.aborted) throw new Error("aborted");
|
|
2420
|
-
const containerName = `bazilion-bash-${process.pid}-${randomUUID18().replaceAll("-", "")}`;
|
|
2428
|
+
const containerName = options.lifecycle?.name("bash") ?? `bazilion-bash-${process.pid}-${randomUUID18().replaceAll("-", "")}`;
|
|
2421
2429
|
const spec = buildDockerRunSpec({
|
|
2422
2430
|
image,
|
|
2423
2431
|
env,
|
|
@@ -2431,10 +2439,44 @@ function createDockerBashOperations(options) {
|
|
|
2431
2439
|
readOnlyMounts,
|
|
2432
2440
|
hostEnv: dockerClientEnv
|
|
2433
2441
|
});
|
|
2434
|
-
|
|
2442
|
+
if (options.lifecycle) {
|
|
2443
|
+
const absolute = await resolveAbsoluteDockerPath(dockerPath, hostEnv);
|
|
2444
|
+
await options.lifecycle.beforeCreate({
|
|
2445
|
+
dockerPath: absolute,
|
|
2446
|
+
executableIdentity: await inspectDockerExecutableIdentity(absolute),
|
|
2447
|
+
endpoint: dockerClientEnv.DOCKER_HOST,
|
|
2448
|
+
containerName
|
|
2449
|
+
});
|
|
2450
|
+
}
|
|
2451
|
+
try {
|
|
2452
|
+
return await executeRegisteredDockerRun(
|
|
2453
|
+
spec,
|
|
2454
|
+
{ onData, signal, timeout, timeoutMs },
|
|
2455
|
+
options.lifecycle
|
|
2456
|
+
);
|
|
2457
|
+
} finally {
|
|
2458
|
+
await options.lifecycle?.afterRemove(containerName);
|
|
2459
|
+
}
|
|
2435
2460
|
}
|
|
2436
2461
|
};
|
|
2437
2462
|
}
|
|
2463
|
+
async function executeRegisteredDockerRun(spec, options, lifecycle) {
|
|
2464
|
+
if (!lifecycle) return executeDockerRun(spec, options);
|
|
2465
|
+
const deadline = options.timeoutMs === void 0 ? void 0 : Date.now() + options.timeoutMs;
|
|
2466
|
+
await runProtectedContainerCreate(spec, options);
|
|
2467
|
+
await lifecycle.afterCreate(spec.containerName);
|
|
2468
|
+
if (options.signal?.aborted) throw new Error("aborted");
|
|
2469
|
+
const remaining = deadline === void 0 ? void 0 : deadline - Date.now();
|
|
2470
|
+
if (remaining !== void 0 && remaining <= 0) throw new Error(`timeout:${options.timeout}`);
|
|
2471
|
+
return executeDockerRun(
|
|
2472
|
+
{
|
|
2473
|
+
...spec,
|
|
2474
|
+
args: ["container", "start", "--attach", spec.containerName],
|
|
2475
|
+
envFileContent: void 0
|
|
2476
|
+
},
|
|
2477
|
+
{ ...options, timeoutMs: remaining }
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2438
2480
|
function executeDockerRun(spec, options) {
|
|
2439
2481
|
let envFileFd;
|
|
2440
2482
|
try {
|
|
@@ -2454,7 +2496,7 @@ function executeDockerRun(spec, options) {
|
|
|
2454
2496
|
windowsHide: true,
|
|
2455
2497
|
env: spec.processEnv
|
|
2456
2498
|
});
|
|
2457
|
-
if (envFileFd !== void 0)
|
|
2499
|
+
if (envFileFd !== void 0) closeSync4(envFileFd);
|
|
2458
2500
|
let settled = false;
|
|
2459
2501
|
let termination;
|
|
2460
2502
|
let timeoutHandle;
|
|
@@ -2510,16 +2552,16 @@ function executeDockerRun(spec, options) {
|
|
|
2510
2552
|
});
|
|
2511
2553
|
}
|
|
2512
2554
|
function openAnonymousEnvFile(content) {
|
|
2513
|
-
const dir = mkdtempSync2(
|
|
2514
|
-
const path =
|
|
2555
|
+
const dir = mkdtempSync2(join16(tmpdir2(), "bazilion-docker-env-"));
|
|
2556
|
+
const path = join16(dir, "environment");
|
|
2515
2557
|
let fd;
|
|
2516
2558
|
try {
|
|
2517
2559
|
writeFileSync7(path, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
2518
|
-
fd =
|
|
2560
|
+
fd = openSync4(path, constants4.O_RDONLY | constants4.O_NOFOLLOW);
|
|
2519
2561
|
rmSync10(dir, { recursive: true, force: true });
|
|
2520
2562
|
return fd;
|
|
2521
2563
|
} catch (error) {
|
|
2522
|
-
if (fd !== void 0)
|
|
2564
|
+
if (fd !== void 0) closeSync4(fd);
|
|
2523
2565
|
rmSync10(dir, { recursive: true, force: true });
|
|
2524
2566
|
throw error;
|
|
2525
2567
|
}
|
|
@@ -2537,7 +2579,7 @@ function resolveTimeoutMs(timeout) {
|
|
|
2537
2579
|
}
|
|
2538
2580
|
async function resolveMountSource(cwd) {
|
|
2539
2581
|
try {
|
|
2540
|
-
await access(cwd,
|
|
2582
|
+
await access(cwd, constants4.F_OK);
|
|
2541
2583
|
const info = await stat(cwd);
|
|
2542
2584
|
if (!info.isDirectory()) {
|
|
2543
2585
|
throw new Error(`Docker sandbox mount is not a directory: ${cwd}`);
|
|
@@ -2762,7 +2804,7 @@ function validateReadOnlyMounts(mounts, workspaceSource) {
|
|
|
2762
2804
|
}
|
|
2763
2805
|
}
|
|
2764
2806
|
function isWithin(root, candidate) {
|
|
2765
|
-
const rel =
|
|
2807
|
+
const rel = relative2(root, candidate);
|
|
2766
2808
|
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute3(rel);
|
|
2767
2809
|
}
|
|
2768
2810
|
function validateHostIdentity(uid, gid) {
|
|
@@ -2770,91 +2812,865 @@ function validateHostIdentity(uid, gid) {
|
|
|
2770
2812
|
throw new Error(`Docker sandbox received an invalid host uid/gid: ${uid}:${gid}`);
|
|
2771
2813
|
}
|
|
2772
2814
|
}
|
|
2773
|
-
function formatSpawnError(dockerPath, error) {
|
|
2774
|
-
const code = error.code;
|
|
2775
|
-
if (code === "ENOENT") {
|
|
2776
|
-
return new Error(`Docker sandbox unavailable: executable "${dockerPath}" was not found`);
|
|
2777
|
-
}
|
|
2778
|
-
if (code === "EACCES") {
|
|
2779
|
-
return new Error(`Docker sandbox unavailable: executable "${dockerPath}" is not executable`);
|
|
2815
|
+
function formatSpawnError(dockerPath, error) {
|
|
2816
|
+
const code = error.code;
|
|
2817
|
+
if (code === "ENOENT") {
|
|
2818
|
+
return new Error(`Docker sandbox unavailable: executable "${dockerPath}" was not found`);
|
|
2819
|
+
}
|
|
2820
|
+
if (code === "EACCES") {
|
|
2821
|
+
return new Error(`Docker sandbox unavailable: executable "${dockerPath}" is not executable`);
|
|
2822
|
+
}
|
|
2823
|
+
return new Error(`Docker sandbox could not start "${dockerPath}": ${error.message}`);
|
|
2824
|
+
}
|
|
2825
|
+
function formatDockerRunError(spec, stderr) {
|
|
2826
|
+
const detail3 = stderr.trim() || "Docker exited with status 125 without an error message";
|
|
2827
|
+
if (/no such image|unable to find image|manifest unknown|pull access denied/i.test(detail3)) {
|
|
2828
|
+
return new Error(`Docker sandbox image "${spec.image}" is unavailable: ${detail3}`);
|
|
2829
|
+
}
|
|
2830
|
+
if (/mounts denied|invalid mount config|bind source path does not exist|mount.*failed/i.test(detail3)) {
|
|
2831
|
+
return new Error(`Docker sandbox mount failed for "${spec.mountSource}": ${detail3}`);
|
|
2832
|
+
}
|
|
2833
|
+
if (/cannot connect to the docker daemon|is the docker daemon running/i.test(detail3)) {
|
|
2834
|
+
return new Error(`Docker sandbox unavailable: ${detail3}`);
|
|
2835
|
+
}
|
|
2836
|
+
return new Error(`Docker sandbox failed to start container "${spec.containerName}": ${detail3}`);
|
|
2837
|
+
}
|
|
2838
|
+
function formatDockerCreateError(spec, stderr, exitCode) {
|
|
2839
|
+
const detail3 = stderr.trim() || `Docker exited with ${exitCode === null ? "no status" : `status ${exitCode}`} without an error message`;
|
|
2840
|
+
if (/mounts denied|invalid mount config|bind source path does not exist|mount.*failed/i.test(detail3)) {
|
|
2841
|
+
return new Error(`Docker sandbox preflight mount failed for "${spec.mountSource}": ${detail3}`);
|
|
2842
|
+
}
|
|
2843
|
+
if (/cannot connect to the docker daemon|is the docker daemon running/i.test(detail3)) {
|
|
2844
|
+
return new Error(`Docker sandbox preflight could not reach the local Docker daemon: ${detail3}`);
|
|
2845
|
+
}
|
|
2846
|
+
return new Error(`Docker sandbox preflight could not create the required container: ${detail3}`);
|
|
2847
|
+
}
|
|
2848
|
+
async function removeContainer(dockerPath, containerName, processEnv) {
|
|
2849
|
+
const args = ["container", "rm", "--force", containerName];
|
|
2850
|
+
let last;
|
|
2851
|
+
for (let attempt = 0; attempt < CLEANUP_ATTEMPTS; attempt += 1) {
|
|
2852
|
+
last = await runCleanupCommand(dockerPath, args, processEnv);
|
|
2853
|
+
if (last.ok) return;
|
|
2854
|
+
if (attempt < CLEANUP_ATTEMPTS - 1) {
|
|
2855
|
+
await new Promise((resolve6) => setTimeout(resolve6, CLEANUP_RETRY_DELAY_MS));
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
if (last && !last.missing) {
|
|
2859
|
+
console.warn(
|
|
2860
|
+
`Docker sandbox could not confirm cleanup of container "${containerName}": ${last.error || "cleanup command failed"}`
|
|
2861
|
+
);
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
function runCleanupCommand(dockerPath, args, processEnv) {
|
|
2865
|
+
return new Promise((resolve6) => {
|
|
2866
|
+
let done = false;
|
|
2867
|
+
let stderr = "";
|
|
2868
|
+
const child = spawn(dockerPath, args, {
|
|
2869
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
2870
|
+
windowsHide: true,
|
|
2871
|
+
env: processEnv
|
|
2872
|
+
});
|
|
2873
|
+
const finish = (ok, error = "") => {
|
|
2874
|
+
if (done) return;
|
|
2875
|
+
done = true;
|
|
2876
|
+
clearTimeout(timeoutHandle);
|
|
2877
|
+
const detail3 = error || stderr.trim();
|
|
2878
|
+
resolve6({
|
|
2879
|
+
ok,
|
|
2880
|
+
missing: /no such container/i.test(detail3),
|
|
2881
|
+
error: detail3
|
|
2882
|
+
});
|
|
2883
|
+
};
|
|
2884
|
+
const timeoutHandle = setTimeout(() => {
|
|
2885
|
+
child.kill("SIGKILL");
|
|
2886
|
+
finish(false, `cleanup timed out after ${CLEANUP_TIMEOUT_MS}ms`);
|
|
2887
|
+
}, CLEANUP_TIMEOUT_MS);
|
|
2888
|
+
child.stderr.on("data", (data) => {
|
|
2889
|
+
stderr = `${stderr}${data.toString("utf8")}`.slice(-2048);
|
|
2890
|
+
});
|
|
2891
|
+
child.once("error", (error) => finish(false, error.message));
|
|
2892
|
+
child.once("close", (code) => finish(code === 0));
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
// ../daemon/src/runtime/pi/coding.ts
|
|
2897
|
+
function codingTools(input) {
|
|
2898
|
+
const hostOperations = input.docker ? null : createLocalBashOperations();
|
|
2899
|
+
return [
|
|
2900
|
+
{
|
|
2901
|
+
name: "coding_environment",
|
|
2902
|
+
label: "Inspect coding environment",
|
|
2903
|
+
description: "Read the actual environment admitted to this turn for a Team-relative directory. No commands run. Discover prerequisites during the task; no Team checklist is required. Missing network/tooling is a specific blocker, never permission to change posture.",
|
|
2904
|
+
parameters: Type.Object(
|
|
2905
|
+
{ target: Type.String({ default: ".", maxLength: 4096 }) },
|
|
2906
|
+
{ additionalProperties: false }
|
|
2907
|
+
),
|
|
2908
|
+
executionMode: "sequential",
|
|
2909
|
+
async execute(_id, params) {
|
|
2910
|
+
const value = await input.host.invoke({
|
|
2911
|
+
action: "environment",
|
|
2912
|
+
target: params.target
|
|
2913
|
+
});
|
|
2914
|
+
return {
|
|
2915
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
2916
|
+
details: { codingEnvironment: value }
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
},
|
|
2920
|
+
{
|
|
2921
|
+
name: "coding_command",
|
|
2922
|
+
label: "Run coding command",
|
|
2923
|
+
description: "Execute an ad hoc finite runtime/dependency/preparation/build/test command in this turn\u2019s actual environment. Use discovered repository commands, not saved operator checks. Resolve prerequisites and use available offline artifacts within current authority. Share coding-receipt:<id> through send_message if a teammate needs evidence; never wait holding its workspace lease. Success is an observed exit, not proof about later code.",
|
|
2924
|
+
parameters: Type.Object(
|
|
2925
|
+
{
|
|
2926
|
+
command: Type.String({ minLength: 1, maxLength: 4096 }),
|
|
2927
|
+
cwd: Type.String({ maxLength: 4096 }),
|
|
2928
|
+
purpose: Type.Union(
|
|
2929
|
+
["runtime", "dependency", "prepare", "build", "test"].map(
|
|
2930
|
+
(value) => Type.Literal(value)
|
|
2931
|
+
)
|
|
2932
|
+
),
|
|
2933
|
+
timeoutSeconds: Type.Integer({ minimum: 1, maximum: 300 })
|
|
2934
|
+
},
|
|
2935
|
+
{ additionalProperties: false }
|
|
2936
|
+
),
|
|
2937
|
+
executionMode: "sequential",
|
|
2938
|
+
async execute(toolCallId, params, signal) {
|
|
2939
|
+
const command = validateCodingCommand(params);
|
|
2940
|
+
const context = await input.context(command.cwd);
|
|
2941
|
+
if (context.instructions.state !== "complete")
|
|
2942
|
+
throw new Error("Coding scope instructions are incomplete");
|
|
2943
|
+
const started = await input.host.invoke({
|
|
2944
|
+
action: "start",
|
|
2945
|
+
toolCallId,
|
|
2946
|
+
input: command
|
|
2947
|
+
});
|
|
2948
|
+
const output = new CodingDiagnostics(input.secrets ?? []);
|
|
2949
|
+
let outcome;
|
|
2950
|
+
try {
|
|
2951
|
+
if (input.approval)
|
|
2952
|
+
await requireBashApproval(command.command, toolCallId, signal, input.approvalHost);
|
|
2953
|
+
signal?.throwIfAborted();
|
|
2954
|
+
const cwd = resolveCodingDirectory(input.root, command.cwd);
|
|
2955
|
+
const operations = input.docker ? createPreparedDockerBashOperations(
|
|
2956
|
+
{
|
|
2957
|
+
...input.docker,
|
|
2958
|
+
coding: {
|
|
2959
|
+
revision: input.docker.coding?.revision ?? 0,
|
|
2960
|
+
cwd: command.cwd,
|
|
2961
|
+
env: input.docker.coding?.env ?? {}
|
|
2962
|
+
}
|
|
2963
|
+
},
|
|
2964
|
+
input.lifecycle
|
|
2965
|
+
) : hostOperations;
|
|
2966
|
+
const result = await operations.exec(
|
|
2967
|
+
command.command,
|
|
2968
|
+
input.docker ? input.root : cwd.path,
|
|
2969
|
+
{ onData: (bytes) => output.append(bytes), signal, timeout: command.timeoutSeconds }
|
|
2970
|
+
);
|
|
2971
|
+
outcome = {
|
|
2972
|
+
state: result.exitCode === 0 ? "succeeded" : result.exitCode === null ? "interrupted" : "failed",
|
|
2973
|
+
exitCode: result.exitCode,
|
|
2974
|
+
...output.finish(),
|
|
2975
|
+
reason: result.exitCode === null ? "exit_unavailable" : null
|
|
2976
|
+
};
|
|
2977
|
+
} catch (error) {
|
|
2978
|
+
const message = error instanceof Error ? error.message : "Command execution unavailable";
|
|
2979
|
+
const state = signal?.aborted || message === "aborted" ? "cancelled" : message.startsWith("timeout:") ? "timed_out" : error instanceof BashApprovalDeniedError ? "blocked" : /cleanup|teardown/i.test(message) ? "interrupted" : "blocked";
|
|
2980
|
+
outcome = { state, exitCode: null, ...output.finish(), reason: message };
|
|
2981
|
+
}
|
|
2982
|
+
const receipt = await input.host.invoke({
|
|
2983
|
+
action: "finish",
|
|
2984
|
+
id: started.id,
|
|
2985
|
+
outcome
|
|
2986
|
+
});
|
|
2987
|
+
return {
|
|
2988
|
+
content: [{ type: "text", text: JSON.stringify(receipt) }],
|
|
2989
|
+
details: { codingReceipt: receipt }
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2992
|
+
},
|
|
2993
|
+
{
|
|
2994
|
+
name: "coding_receipt",
|
|
2995
|
+
label: "Read coding result",
|
|
2996
|
+
description: "Read a retained command result with current applicability. Your own receipts are private. For a teammate\u2019s receipt, provide the id of its policy-authorized message containing coding-receipt:<id>. Old results are not current verification.",
|
|
2997
|
+
parameters: Type.Object(
|
|
2998
|
+
{
|
|
2999
|
+
id: Type.String({ maxLength: 64 }),
|
|
3000
|
+
messageId: Type.Optional(Type.String({ maxLength: 64 }))
|
|
3001
|
+
},
|
|
3002
|
+
{ additionalProperties: false }
|
|
3003
|
+
),
|
|
3004
|
+
executionMode: "sequential",
|
|
3005
|
+
async execute(_id, params) {
|
|
3006
|
+
const value = await input.host.invoke({
|
|
3007
|
+
action: "read",
|
|
3008
|
+
...params
|
|
3009
|
+
});
|
|
3010
|
+
return {
|
|
3011
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
3012
|
+
details: { codingEvidence: value }
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
];
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
// ../daemon/src/runtime/pi/repository-context.ts
|
|
3020
|
+
import { Type as Type2 } from "typebox";
|
|
3021
|
+
|
|
3022
|
+
// ../daemon/src/runtime/pi/repository-context-contract.ts
|
|
3023
|
+
var object2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3024
|
+
var keys = (value, required, optional = []) => required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
|
|
3025
|
+
var string = (value, limit = 4096) => typeof value === "string" && value.length <= limit;
|
|
3026
|
+
var digest = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
3027
|
+
var count = (value) => Number.isSafeInteger(value) && value >= 0;
|
|
3028
|
+
var list3 = (value, limit, check) => Array.isArray(value) && value.length <= limit && value.every(check);
|
|
3029
|
+
var issue = (value) => object2(value) && keys(value, ["code", "message"], ["path"]) && string(value.code, 100) && string(value.message, 1024) && (value.path === void 0 || string(value.path));
|
|
3030
|
+
function assertRepositoryContext(value, teamId) {
|
|
3031
|
+
if (!object2(value) || !keys(value, [
|
|
3032
|
+
"version",
|
|
3033
|
+
"teamId",
|
|
3034
|
+
"rootIdentity",
|
|
3035
|
+
"target",
|
|
3036
|
+
"capturedAt",
|
|
3037
|
+
"fingerprint",
|
|
3038
|
+
"instructions",
|
|
3039
|
+
"commands",
|
|
3040
|
+
"git"
|
|
3041
|
+
]) || value.version !== 1 || !string(value.teamId, 256) || teamId !== void 0 && value.teamId !== teamId || value.rootIdentity !== null && !digest(value.rootIdentity) || !string(value.target) || !count(value.capturedAt) || !digest(value.fingerprint) || !object2(value.instructions) || !object2(value.commands) || !object2(value.git) || Buffer.byteLength(JSON.stringify(value)) > 256 * 1024)
|
|
3042
|
+
throw new Error("Invalid repository context report");
|
|
3043
|
+
const instructions = value.instructions;
|
|
3044
|
+
const commands = value.commands;
|
|
3045
|
+
const git = value.git;
|
|
3046
|
+
if (!keys(instructions, ["state", "issues", "files"]) || !keys(commands, ["state", "issues", "sources", "candidates"]) || !keys(git, [
|
|
3047
|
+
"state",
|
|
3048
|
+
"root",
|
|
3049
|
+
"branch",
|
|
3050
|
+
"head",
|
|
3051
|
+
"headState",
|
|
3052
|
+
"staged",
|
|
3053
|
+
"unstaged",
|
|
3054
|
+
"untracked",
|
|
3055
|
+
"conflicted",
|
|
3056
|
+
"issues"
|
|
3057
|
+
]) || !["complete", "incomplete"].includes(String(instructions.state)) || !list3(instructions.issues, 128, issue) || !list3(
|
|
3058
|
+
instructions.files,
|
|
3059
|
+
17,
|
|
3060
|
+
(file) => object2(file) && keys(file, ["path", "scope", "precedence", "sha256", "content"]) && string(file.path) && string(file.scope) && count(file.precedence) && digest(file.sha256) && string(file.content, 64 * 1024) && Buffer.byteLength(file.content) <= 64 * 1024
|
|
3061
|
+
) || !["complete", "incomplete"].includes(String(commands.state)) || !list3(commands.issues, 128, issue) || !list3(
|
|
3062
|
+
commands.sources,
|
|
3063
|
+
32,
|
|
3064
|
+
(source) => object2(source) && keys(source, ["path", "sha256"]) && string(source.path) && digest(source.sha256)
|
|
3065
|
+
) || !list3(
|
|
3066
|
+
commands.candidates,
|
|
3067
|
+
32,
|
|
3068
|
+
(candidate) => object2(candidate) && keys(candidate, [
|
|
3069
|
+
"source",
|
|
3070
|
+
"sha256",
|
|
3071
|
+
"location",
|
|
3072
|
+
"cwd",
|
|
3073
|
+
"command",
|
|
3074
|
+
"kind",
|
|
3075
|
+
"packageManager"
|
|
3076
|
+
]) && string(candidate.source) && digest(candidate.sha256) && string(candidate.location) && string(candidate.cwd) && string(candidate.command) && ["script", "excerpt"].includes(String(candidate.kind)) && [null, "npm", "pnpm", "yarn"].includes(candidate.packageManager)
|
|
3077
|
+
) || !["available", "unavailable", "not_repository"].includes(String(git.state)) || ![null, "branch", "detached", "unborn"].includes(git.headState) || ![git.root, git.branch, git.head].every((value2) => value2 === null || string(value2)) || ![git.staged, git.unstaged, git.untracked, git.conflicted].every(count) || !list3(git.issues, 128, issue))
|
|
3078
|
+
throw new Error("Invalid repository context report");
|
|
3079
|
+
const report = value;
|
|
3080
|
+
if (report.instructions.files.reduce((bytes, file) => bytes + Buffer.byteLength(file.content), 0) > 128 * 1024 || report.instructions.state === "complete" && (report.rootIdentity === null || report.instructions.issues.length > 0))
|
|
3081
|
+
throw new Error("Invalid repository instruction completeness");
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
// ../daemon/src/runtime/pi/repository-context.ts
|
|
3085
|
+
function repositoryContextIntegration(initial, host, cwd) {
|
|
3086
|
+
assertRepositoryContext(initial);
|
|
3087
|
+
if (initial.instructions.state !== "complete")
|
|
3088
|
+
throw new Error("Repository instructions incomplete");
|
|
3089
|
+
let current = initial;
|
|
3090
|
+
let refreshPrompt = () => {
|
|
3091
|
+
};
|
|
3092
|
+
const files = () => ({
|
|
3093
|
+
agentsFiles: [
|
|
3094
|
+
{
|
|
3095
|
+
path: "Repository instructions and provenance",
|
|
3096
|
+
content: [
|
|
3097
|
+
"# Repository context",
|
|
3098
|
+
`Captured target: ${current.target}; fingerprint: ${current.fingerprint}; captured at: ${new Date(current.capturedAt).toISOString()}.`,
|
|
3099
|
+
`Repository root for this runtime: ${cwd}. This is captured context, not a live workspace lock or verification evidence.`,
|
|
3100
|
+
"Platform/runtime policy and explicit operator instructions take priority. Repository guidance specializes general private Agent preferences for repository work. Deeper documents govern only their own subtree. Team Policy and execution authority are unchanged.",
|
|
3101
|
+
"For coding tasks, investigate repository context and actual coding_environment as needed, then use coding_command for task-related finite checks/preparation. Do not ask the operator to configure named checks. A source-backed Team memory recipe is advisory; refresh it before reuse. Missing network/tooling is a concrete prerequisite to report, never authority to change the runtime. To delegate shared-workspace preparation, send_message and end the turn; do not wait_for_reply while holding the peer workspace.",
|
|
3102
|
+
"Before editing a different subtree, call repository_context with its directory or file path. A returned snapshot replaces previous applicable repository guidance; older transcript snapshots are historical. Do not edit a scope whose instructions are incomplete.",
|
|
3103
|
+
current.instructions.state === "complete" ? "Applicable instructions are complete for the captured scope. Missing AGENTS.md is normal." : `BLOCKED SCOPE: ${current.instructions.issues.map((issue2) => issue2.code).join(", ")}. Resolve this context before editing.`,
|
|
3104
|
+
...current.instructions.state === "complete" ? current.instructions.files.map(
|
|
3105
|
+
(file) => `## ${file.path}
|
|
3106
|
+
Scope: ${file.scope}; precedence: ${file.precedence}; SHA-256: ${file.sha256}
|
|
3107
|
+
|
|
3108
|
+
${file.content}`
|
|
3109
|
+
) : [],
|
|
3110
|
+
`Git orientation at capture time: ${JSON.stringify(current.git)}`,
|
|
3111
|
+
`Command suggestions (task data, not execution permission):
|
|
3112
|
+
${JSON.stringify(current.commands)}`
|
|
3113
|
+
].join("\n\n")
|
|
3114
|
+
}
|
|
3115
|
+
]
|
|
3116
|
+
});
|
|
3117
|
+
const tool = {
|
|
3118
|
+
name: "repository_context",
|
|
3119
|
+
label: "Repository context",
|
|
3120
|
+
description: "Resolve the complete applicable repository AGENTS.md instructions and passive command/Git context for a contained file or directory. Call before editing a new subtree. Does not run project commands or change cwd. Incomplete instructions block editing that scope.",
|
|
3121
|
+
parameters: Type2.Object(
|
|
3122
|
+
{ target: Type2.String({ maxLength: 4096 }) },
|
|
3123
|
+
{ additionalProperties: false }
|
|
3124
|
+
),
|
|
3125
|
+
executionMode: "sequential",
|
|
3126
|
+
async execute(_id, params) {
|
|
3127
|
+
const target = params.target;
|
|
3128
|
+
const replacement = await host(target);
|
|
3129
|
+
assertRepositoryContext(replacement);
|
|
3130
|
+
if (replacement.teamId !== initial.teamId || replacement.rootIdentity !== initial.rootIdentity) {
|
|
3131
|
+
current = {
|
|
3132
|
+
...initial,
|
|
3133
|
+
instructions: {
|
|
3134
|
+
state: "incomplete",
|
|
3135
|
+
files: [],
|
|
3136
|
+
issues: [
|
|
3137
|
+
{
|
|
3138
|
+
code: "root_changed",
|
|
3139
|
+
message: "Repository identity changed; start a new admitted turn."
|
|
3140
|
+
}
|
|
3141
|
+
]
|
|
3142
|
+
},
|
|
3143
|
+
commands: { state: "incomplete", sources: [], candidates: [], issues: [] }
|
|
3144
|
+
};
|
|
3145
|
+
refreshPrompt();
|
|
3146
|
+
throw new Error("Repository context identity changed; start a new admitted turn");
|
|
3147
|
+
}
|
|
3148
|
+
current = replacement;
|
|
3149
|
+
refreshPrompt();
|
|
3150
|
+
return {
|
|
3151
|
+
content: [{ type: "text", text: JSON.stringify(replacement) }],
|
|
3152
|
+
details: { repositoryContext: replacement }
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
};
|
|
3156
|
+
return {
|
|
3157
|
+
files,
|
|
3158
|
+
tool,
|
|
3159
|
+
async resolve(target) {
|
|
3160
|
+
await tool.execute("coding-context", { target }, void 0, void 0, {});
|
|
3161
|
+
return current;
|
|
3162
|
+
},
|
|
3163
|
+
bind(session, allowedTools) {
|
|
3164
|
+
refreshPrompt = () => session.setActiveToolsByName(allowedTools);
|
|
3165
|
+
}
|
|
3166
|
+
};
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
// ../daemon/src/runtime/pi/session.ts
|
|
3170
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, statSync as statSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
3171
|
+
import { basename as basename4, join as join22 } from "path";
|
|
3172
|
+
import {
|
|
3173
|
+
createAssistantMessageEventStream
|
|
3174
|
+
} from "@earendil-works/pi-ai";
|
|
3175
|
+
import {
|
|
3176
|
+
buildSessionContext,
|
|
3177
|
+
createAgentSession,
|
|
3178
|
+
DefaultResourceLoader,
|
|
3179
|
+
SessionManager,
|
|
3180
|
+
SettingsManager
|
|
3181
|
+
} from "@earendil-works/pi-coding-agent";
|
|
3182
|
+
|
|
3183
|
+
// ../daemon/src/runtime/providers/pi-runtime.ts
|
|
3184
|
+
import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
|
|
3185
|
+
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
3186
|
+
var PROVIDER_ALIASES = {
|
|
3187
|
+
bedrock: "amazon-bedrock",
|
|
3188
|
+
"azure-openai": "azure-openai-responses"
|
|
3189
|
+
};
|
|
3190
|
+
var PROTECTED_PROVIDER_NAMES = [
|
|
3191
|
+
"anthropic",
|
|
3192
|
+
"openai",
|
|
3193
|
+
"openai-codex",
|
|
3194
|
+
"google",
|
|
3195
|
+
"google-vertex",
|
|
3196
|
+
"azure-openai",
|
|
3197
|
+
"bedrock",
|
|
3198
|
+
"mistral",
|
|
3199
|
+
"groq",
|
|
3200
|
+
"cerebras",
|
|
3201
|
+
"xai",
|
|
3202
|
+
"zai",
|
|
3203
|
+
"huggingface",
|
|
3204
|
+
"openrouter",
|
|
3205
|
+
"vercel-ai-gateway",
|
|
3206
|
+
"deepseek",
|
|
3207
|
+
"fireworks",
|
|
3208
|
+
"together",
|
|
3209
|
+
"baseten",
|
|
3210
|
+
"moonshotai",
|
|
3211
|
+
"moonshotai-cn",
|
|
3212
|
+
"kimi-coding",
|
|
3213
|
+
"minimax",
|
|
3214
|
+
"minimax-cn",
|
|
3215
|
+
"qwen-token-plan",
|
|
3216
|
+
"qwen-token-plan-cn",
|
|
3217
|
+
"qwen-token-plan-individual",
|
|
3218
|
+
"xiaomi",
|
|
3219
|
+
"xiaomi-token-plan-ams",
|
|
3220
|
+
"xiaomi-token-plan-cn",
|
|
3221
|
+
"xiaomi-token-plan-sgp",
|
|
3222
|
+
"ant-ling",
|
|
3223
|
+
"nvidia",
|
|
3224
|
+
"opencode",
|
|
3225
|
+
"opencode-go",
|
|
3226
|
+
"zai-coding-cn",
|
|
3227
|
+
"github-copilot",
|
|
3228
|
+
"cloudflare-ai-gateway",
|
|
3229
|
+
"cloudflare-workers-ai",
|
|
3230
|
+
"lmstudio",
|
|
3231
|
+
"ollama",
|
|
3232
|
+
"llamacpp"
|
|
3233
|
+
];
|
|
3234
|
+
var LOCAL_PROVIDERS = {
|
|
3235
|
+
lmstudio: {
|
|
3236
|
+
baseUrl: "http://127.0.0.1:1234/v1",
|
|
3237
|
+
apiKey: "lm-studio",
|
|
3238
|
+
authHeader: false
|
|
3239
|
+
},
|
|
3240
|
+
ollama: { baseUrl: "http://127.0.0.1:11434/v1", apiKey: "ollama", authHeader: false },
|
|
3241
|
+
llamacpp: { baseUrl: "http://127.0.0.1:8080/v1", apiKey: "no-key", authHeader: true }
|
|
3242
|
+
};
|
|
3243
|
+
var PROVIDER_API_KEY_ENV = {
|
|
3244
|
+
anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
|
|
3245
|
+
openai: ["OPENAI_API_KEY"],
|
|
3246
|
+
google: ["GEMINI_API_KEY"],
|
|
3247
|
+
"azure-openai": ["AZURE_OPENAI_API_KEY"],
|
|
3248
|
+
mistral: ["MISTRAL_API_KEY"],
|
|
3249
|
+
groq: ["GROQ_API_KEY"],
|
|
3250
|
+
cerebras: ["CEREBRAS_API_KEY"],
|
|
3251
|
+
xai: ["XAI_API_KEY"],
|
|
3252
|
+
zai: ["ZAI_API_KEY"],
|
|
3253
|
+
huggingface: ["HF_TOKEN"],
|
|
3254
|
+
openrouter: ["OPENROUTER_API_KEY"],
|
|
3255
|
+
"vercel-ai-gateway": ["AI_GATEWAY_API_KEY"],
|
|
3256
|
+
deepseek: ["DEEPSEEK_API_KEY"],
|
|
3257
|
+
fireworks: ["FIREWORKS_API_KEY"],
|
|
3258
|
+
together: ["TOGETHER_API_KEY"],
|
|
3259
|
+
baseten: ["BASETEN_API_KEY"],
|
|
3260
|
+
moonshotai: ["MOONSHOT_API_KEY"],
|
|
3261
|
+
"moonshotai-cn": ["MOONSHOT_CN_API_KEY"],
|
|
3262
|
+
"kimi-coding": ["KIMI_API_KEY"],
|
|
3263
|
+
minimax: ["MINIMAX_API_KEY"],
|
|
3264
|
+
"minimax-cn": ["MINIMAX_CN_API_KEY"],
|
|
3265
|
+
"qwen-token-plan": ["QWEN_TOKEN_PLAN_API_KEY"],
|
|
3266
|
+
"qwen-token-plan-cn": ["QWEN_TOKEN_PLAN_CN_API_KEY"],
|
|
3267
|
+
"qwen-token-plan-individual": ["QWEN_TOKEN_PLAN_API_KEY"],
|
|
3268
|
+
xiaomi: ["XIAOMI_API_KEY"],
|
|
3269
|
+
"xiaomi-token-plan-ams": ["XIAOMI_TOKEN_PLAN_AMS_API_KEY"],
|
|
3270
|
+
"xiaomi-token-plan-cn": ["XIAOMI_TOKEN_PLAN_CN_API_KEY"],
|
|
3271
|
+
"xiaomi-token-plan-sgp": ["XIAOMI_TOKEN_PLAN_SGP_API_KEY"],
|
|
3272
|
+
"ant-ling": ["ANT_LING_API_KEY"],
|
|
3273
|
+
nvidia: ["NVIDIA_API_KEY"],
|
|
3274
|
+
opencode: ["OPENCODE_API_KEY"],
|
|
3275
|
+
"opencode-go": ["OPENCODE_GO_API_KEY"],
|
|
3276
|
+
"zai-coding-cn": ["ZAI_CODING_CN_API_KEY"],
|
|
3277
|
+
"github-copilot": ["COPILOT_GITHUB_TOKEN"],
|
|
3278
|
+
"cloudflare-ai-gateway": ["CLOUDFLARE_API_KEY"],
|
|
3279
|
+
"cloudflare-workers-ai": ["CLOUDFLARE_API_KEY"],
|
|
3280
|
+
lmstudio: ["LMSTUDIO_API_KEY"],
|
|
3281
|
+
ollama: ["OLLAMA_API_KEY"],
|
|
3282
|
+
llamacpp: ["LLAMACPP_API_KEY"]
|
|
3283
|
+
};
|
|
3284
|
+
var PROVIDER_CREDENTIAL_ENV = {
|
|
3285
|
+
bedrock: [
|
|
3286
|
+
"AWS_PROFILE",
|
|
3287
|
+
"AWS_BEARER_TOKEN_BEDROCK",
|
|
3288
|
+
"AWS_ACCESS_KEY_ID",
|
|
3289
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
3290
|
+
"AWS_SESSION_TOKEN",
|
|
3291
|
+
"AWS_REGION",
|
|
3292
|
+
"AWS_DEFAULT_REGION"
|
|
3293
|
+
],
|
|
3294
|
+
"google-vertex": [
|
|
3295
|
+
"GOOGLE_CLOUD_PROJECT",
|
|
3296
|
+
"GOOGLE_CLOUD_LOCATION",
|
|
3297
|
+
"GOOGLE_APPLICATION_CREDENTIALS"
|
|
3298
|
+
],
|
|
3299
|
+
"cloudflare-ai-gateway": ["CLOUDFLARE_API_KEY", "CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_GATEWAY_ID"],
|
|
3300
|
+
"cloudflare-workers-ai": ["CLOUDFLARE_API_KEY", "CLOUDFLARE_ACCOUNT_ID"]
|
|
3301
|
+
};
|
|
3302
|
+
function piProviderName(providerName) {
|
|
3303
|
+
return PROVIDER_ALIASES[providerName] ?? providerName;
|
|
3304
|
+
}
|
|
3305
|
+
function providerApiKey(providerName, env) {
|
|
3306
|
+
for (const name of PROVIDER_API_KEY_ENV[providerName] ?? []) {
|
|
3307
|
+
const value = env[name];
|
|
3308
|
+
if (value) return value;
|
|
3309
|
+
}
|
|
3310
|
+
return LOCAL_PROVIDERS[providerName]?.apiKey;
|
|
3311
|
+
}
|
|
3312
|
+
function providerBaseUrl(providerName, env) {
|
|
3313
|
+
switch (providerName) {
|
|
3314
|
+
case "lmstudio":
|
|
3315
|
+
return env.LMSTUDIO_URL ?? LOCAL_PROVIDERS.lmstudio?.baseUrl;
|
|
3316
|
+
case "ollama":
|
|
3317
|
+
return env.OLLAMA_URL ?? LOCAL_PROVIDERS.ollama?.baseUrl;
|
|
3318
|
+
case "llamacpp":
|
|
3319
|
+
return env.LLAMACPP_URL ?? LOCAL_PROVIDERS.llamacpp?.baseUrl;
|
|
3320
|
+
case "vercel-ai-gateway":
|
|
3321
|
+
return env.AI_GATEWAY_BASE_URL;
|
|
3322
|
+
default:
|
|
3323
|
+
return void 0;
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
function providerCredentialEnv(providerName, env) {
|
|
3327
|
+
const result = {};
|
|
3328
|
+
for (const name of PROVIDER_CREDENTIAL_ENV[providerName] ?? []) {
|
|
3329
|
+
const value = env[name];
|
|
3330
|
+
if (value !== void 0) result[name] = value;
|
|
3331
|
+
}
|
|
3332
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
3333
|
+
}
|
|
3334
|
+
function fallbackApi(providerName) {
|
|
3335
|
+
switch (providerName) {
|
|
3336
|
+
case "anthropic":
|
|
3337
|
+
return "anthropic-messages";
|
|
3338
|
+
case "google":
|
|
3339
|
+
return "google-generative-ai";
|
|
3340
|
+
case "google-vertex":
|
|
3341
|
+
return "google-vertex";
|
|
3342
|
+
case "azure-openai":
|
|
3343
|
+
return "azure-openai-responses";
|
|
3344
|
+
case "bedrock":
|
|
3345
|
+
return "bedrock-converse-stream";
|
|
3346
|
+
case "openai-codex":
|
|
3347
|
+
return "openai-codex-responses";
|
|
3348
|
+
default:
|
|
3349
|
+
return "openai-completions";
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
function fallbackPiModel(providerName, modelId, baseUrl) {
|
|
3353
|
+
return {
|
|
3354
|
+
id: modelId,
|
|
3355
|
+
name: modelId,
|
|
3356
|
+
api: fallbackApi(providerName),
|
|
3357
|
+
provider: piProviderName(providerName),
|
|
3358
|
+
baseUrl: baseUrl ?? "",
|
|
3359
|
+
reasoning: false,
|
|
3360
|
+
input: ["text"],
|
|
3361
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
3362
|
+
contextWindow: 32768,
|
|
3363
|
+
maxTokens: 4096
|
|
3364
|
+
};
|
|
3365
|
+
}
|
|
3366
|
+
async function createBazilionPiRuntime(options) {
|
|
3367
|
+
const providerName = options.providerName;
|
|
3368
|
+
const canonicalName = piProviderName(providerName);
|
|
3369
|
+
const credentials = new InMemoryCredentialStore();
|
|
3370
|
+
const apiKey = options.apiKey ?? providerApiKey(providerName, options.env);
|
|
3371
|
+
const credentialEnv = providerCredentialEnv(providerName, options.env);
|
|
3372
|
+
if (apiKey || credentialEnv) {
|
|
3373
|
+
await credentials.modify(
|
|
3374
|
+
canonicalName,
|
|
3375
|
+
async () => canonicalName === "openai-codex" && apiKey ? {
|
|
3376
|
+
type: "oauth",
|
|
3377
|
+
access: apiKey,
|
|
3378
|
+
// Bazilion owns refresh outside Pi. This deliberately empty value
|
|
3379
|
+
// satisfies Pi's canonical OAuth shape but is never used because
|
|
3380
|
+
// the token is long-lived only inside this one-shot runtime and
|
|
3381
|
+
// mid-turn replacement is supplied through Agent.getApiKey.
|
|
3382
|
+
refresh: "",
|
|
3383
|
+
expires: Number.MAX_SAFE_INTEGER
|
|
3384
|
+
} : {
|
|
3385
|
+
type: "api_key",
|
|
3386
|
+
...apiKey ? { key: apiKey } : {},
|
|
3387
|
+
...credentialEnv ? { env: credentialEnv } : {}
|
|
3388
|
+
}
|
|
3389
|
+
);
|
|
3390
|
+
}
|
|
3391
|
+
const runtime = await ModelRuntime.create({ credentials, modelsPath: null });
|
|
3392
|
+
const local = LOCAL_PROVIDERS[providerName];
|
|
3393
|
+
const baseUrl = options.baseUrl ?? providerBaseUrl(providerName, options.env);
|
|
3394
|
+
if (local) {
|
|
3395
|
+
const modelId = options.modelId ?? "model-name";
|
|
3396
|
+
const model = fallbackPiModel(providerName, modelId, baseUrl ?? local.baseUrl);
|
|
3397
|
+
runtime.registerProvider(canonicalName, {
|
|
3398
|
+
baseUrl: baseUrl ?? local.baseUrl,
|
|
3399
|
+
api: "openai-completions",
|
|
3400
|
+
authHeader: local.authHeader,
|
|
3401
|
+
apiKey: apiKey ?? local.apiKey,
|
|
3402
|
+
models: [model]
|
|
3403
|
+
});
|
|
3404
|
+
} else if (baseUrl) {
|
|
3405
|
+
runtime.registerProvider(canonicalName, { baseUrl });
|
|
3406
|
+
}
|
|
3407
|
+
return runtime;
|
|
3408
|
+
}
|
|
3409
|
+
async function createProtectedPiRuntime(options) {
|
|
3410
|
+
const env = {};
|
|
3411
|
+
for (const field of options.credentialEnv ?? []) env[field.name] = field.value;
|
|
3412
|
+
return createBazilionPiRuntime({
|
|
3413
|
+
providerName: options.providerName,
|
|
3414
|
+
modelId: options.modelId,
|
|
3415
|
+
env,
|
|
3416
|
+
apiKey: options.apiKey,
|
|
3417
|
+
baseUrl: options.baseUrl
|
|
3418
|
+
});
|
|
3419
|
+
}
|
|
3420
|
+
function resolvePiModel(runtime, providerName, modelId, baseUrl) {
|
|
3421
|
+
const known = runtime.getModel(piProviderName(providerName), modelId);
|
|
3422
|
+
if (known) return baseUrl ? { ...known, baseUrl } : known;
|
|
3423
|
+
return fallbackPiModel(providerName, modelId, baseUrl);
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
// ../daemon/src/runtime/auth/openai-codex.ts
|
|
3427
|
+
import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
3428
|
+
|
|
3429
|
+
// ../daemon/src/runtime/providers/pi-adapter.ts
|
|
3430
|
+
import {
|
|
3431
|
+
Type as Type3
|
|
3432
|
+
} from "@earendil-works/pi-ai";
|
|
3433
|
+
|
|
3434
|
+
// ../daemon/src/runtime/session/prompt.ts
|
|
3435
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6 } from "fs";
|
|
3436
|
+
import { join as join17 } from "path";
|
|
3437
|
+
|
|
3438
|
+
// ../daemon/src/runtime/safe-files.ts
|
|
3439
|
+
import {
|
|
3440
|
+
closeSync as closeSync5,
|
|
3441
|
+
constants as constants5,
|
|
3442
|
+
fstatSync as fstatSync4,
|
|
3443
|
+
lstatSync as lstatSync3,
|
|
3444
|
+
mkdirSync as mkdirSync5,
|
|
3445
|
+
openSync as openSync5,
|
|
3446
|
+
readFileSync as readFileSync9,
|
|
3447
|
+
realpathSync as realpathSync5
|
|
3448
|
+
} from "fs";
|
|
3449
|
+
import { dirname as dirname3, isAbsolute as isAbsolute4, relative as relative3, resolve as resolve4 } from "path";
|
|
3450
|
+
function resolveRealDirectory(path) {
|
|
3451
|
+
const requested = resolve4(path);
|
|
3452
|
+
const entry = lstatSync3(requested);
|
|
3453
|
+
if (entry.isSymbolicLink() || !entry.isDirectory()) {
|
|
3454
|
+
throw new Error("expected a real directory");
|
|
3455
|
+
}
|
|
3456
|
+
return realpathSync5(requested);
|
|
3457
|
+
}
|
|
3458
|
+
function readRegularFileNoFollow(path) {
|
|
3459
|
+
const entry = lstatSync3(path);
|
|
3460
|
+
if (entry.isSymbolicLink()) throw new Error("symbolic links are not allowed");
|
|
3461
|
+
if (!entry.isFile()) throw new Error("target is not a regular file");
|
|
3462
|
+
let fd;
|
|
3463
|
+
try {
|
|
3464
|
+
fd = openSync5(path, constants5.O_RDONLY | noFollowFlag());
|
|
3465
|
+
if (!fstatSync4(fd).isFile()) throw new Error("target is not a regular file");
|
|
3466
|
+
return readFileSync9(fd, "utf8");
|
|
3467
|
+
} finally {
|
|
3468
|
+
if (fd !== void 0) closeSync5(fd);
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
function noFollowFlag() {
|
|
3472
|
+
return typeof constants5.O_NOFOLLOW === "number" ? constants5.O_NOFOLLOW : 0;
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
// ../daemon/src/runtime/session/prompt.ts
|
|
3476
|
+
var SANDBOX_SKILLS_DIR = "/skills";
|
|
3477
|
+
function loadPromptSkills(skillsDir, attachedNames) {
|
|
3478
|
+
if (!existsSync10(skillsDir) || attachedNames.length === 0) return [];
|
|
3479
|
+
const attached = new Set(attachedNames);
|
|
3480
|
+
const discovered = readdirSync6(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && attached.has(entry.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
3481
|
+
const loaded = [];
|
|
3482
|
+
for (const entry of discovered) {
|
|
3483
|
+
const hostDir = join17(skillsDir, entry.name);
|
|
3484
|
+
try {
|
|
3485
|
+
const parsed = parseSkillContent(readRegularFileNoFollow(join17(hostDir, "SKILL.md")));
|
|
3486
|
+
const safeLabel = entry.name.replace(/[^A-Za-z0-9._-]/g, "_") || "skill";
|
|
3487
|
+
loaded.push({
|
|
3488
|
+
name: entry.name,
|
|
3489
|
+
description: parsed.frontmatter.description,
|
|
3490
|
+
body: parsed.body.trim(),
|
|
3491
|
+
hostDir,
|
|
3492
|
+
sandboxDir: `${SANDBOX_SKILLS_DIR}/${loaded.length}-${safeLabel}`
|
|
3493
|
+
});
|
|
3494
|
+
} catch {
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
return loaded;
|
|
3498
|
+
}
|
|
3499
|
+
var CONTEXT_FILE_ORDER = ["AGENTS.md", "SOUL.md", "TOOLS.md", "IDENTITY.md"];
|
|
3500
|
+
function loadProtectedHomeDocuments(agentDir) {
|
|
3501
|
+
const root = resolveRealDirectory(agentDir);
|
|
3502
|
+
const read = (file) => {
|
|
3503
|
+
try {
|
|
3504
|
+
return readRegularFileNoFollow(join17(root, file));
|
|
3505
|
+
} catch (error) {
|
|
3506
|
+
if (error.code === "ENOENT") return null;
|
|
3507
|
+
throw new Error(`private Agent home is unsafe at ${file}`);
|
|
3508
|
+
}
|
|
3509
|
+
};
|
|
3510
|
+
return {
|
|
3511
|
+
"AGENTS.md": read("AGENTS.md"),
|
|
3512
|
+
"SOUL.md": read("SOUL.md"),
|
|
3513
|
+
"TOOLS.md": read("TOOLS.md"),
|
|
3514
|
+
"IDENTITY.md": read("IDENTITY.md"),
|
|
3515
|
+
"BOOTSTRAP.md": read("BOOTSTRAP.md")
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3518
|
+
function buildSystemPrompt(agent, options = {}) {
|
|
3519
|
+
const parts = [];
|
|
3520
|
+
const homeDocuments = options.homeDocuments ?? loadProtectedHomeDocuments(agent.agent.dir);
|
|
3521
|
+
const contextBlocks = [];
|
|
3522
|
+
for (const file of CONTEXT_FILE_ORDER) {
|
|
3523
|
+
const content = homeDocuments[file]?.trimEnd();
|
|
3524
|
+
if (!content) continue;
|
|
3525
|
+
contextBlocks.push(`## ${file}
|
|
3526
|
+
|
|
3527
|
+
${content}`);
|
|
2780
3528
|
}
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
if (/no such image|unable to find image|manifest unknown|pull access denied/i.test(detail3)) {
|
|
2786
|
-
return new Error(`Docker sandbox image "${spec.image}" is unavailable: ${detail3}`);
|
|
3529
|
+
if (contextBlocks.length > 0) {
|
|
3530
|
+
parts.push(`# Agent instructions
|
|
3531
|
+
|
|
3532
|
+
${contextBlocks.join("\n\n")}`);
|
|
2787
3533
|
}
|
|
2788
|
-
|
|
2789
|
-
|
|
3534
|
+
const bootstrap = homeDocuments["BOOTSTRAP.md"]?.trimEnd();
|
|
3535
|
+
if (bootstrap) {
|
|
3536
|
+
parts.push(
|
|
3537
|
+
[
|
|
3538
|
+
"# First-Run Ritual",
|
|
3539
|
+
"",
|
|
3540
|
+
"This is your first session. The document below is **conversational guidance**, not a checklist to execute in one shot. It describes a multi-turn Q&A you should have with the human, one question per turn.",
|
|
3541
|
+
"",
|
|
3542
|
+
"## Hard rules",
|
|
3543
|
+
"- Your first reply is ONLY a greeting + ONE question. No tool calls. Wait for the human to answer.",
|
|
3544
|
+
"- Each subsequent turn: at most one new question. Wait between turns.",
|
|
3545
|
+
"- Only after the ritual is complete (you have enough to write IDENTITY.md): call `home_write` once, then `bootstrap_done`.",
|
|
3546
|
+
"",
|
|
3547
|
+
"## BOOTSTRAP.md",
|
|
3548
|
+
"",
|
|
3549
|
+
bootstrap
|
|
3550
|
+
].join("\n")
|
|
3551
|
+
);
|
|
2790
3552
|
}
|
|
2791
|
-
|
|
2792
|
-
|
|
3553
|
+
parts.push(
|
|
3554
|
+
[
|
|
3555
|
+
"# Agent Home",
|
|
3556
|
+
"",
|
|
3557
|
+
"Your private home holds who you are \u2014 identity, soul, and behaviour rules. It is not shared with other agents and cannot be overwritten by them. The files above (IDENTITY.md, SOUL.md, AGENTS.md, TOOLS.md) live in this home, plus BOOTSTRAP.md when you are still in your first-run ritual.",
|
|
3558
|
+
"",
|
|
3559
|
+
"- To change who you are (name, vibe, personality, how you behave): use `home_write`.",
|
|
3560
|
+
"- To inspect exact wording of your own files: use `home_read` or `home_list`.",
|
|
3561
|
+
"- To remember facts the user told you or things you learned: use `memory_write` \u2014 NOT `home_write`.",
|
|
3562
|
+
"- 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."
|
|
3563
|
+
].join("\n")
|
|
3564
|
+
);
|
|
3565
|
+
if (agent.skills.length > 0) {
|
|
3566
|
+
const loaded = options.skills ?? [];
|
|
3567
|
+
const loadedNames = new Set(loaded.map((skill) => skill.name));
|
|
3568
|
+
const unavailable = agent.skills.filter((name) => !loadedNames.has(name));
|
|
3569
|
+
const skillBlocks = loaded.map((skill) => {
|
|
3570
|
+
const runtimeDir = options.sandboxMode === "docker" ? skill.sandboxDir : skill.hostDir;
|
|
3571
|
+
return [
|
|
3572
|
+
`## ${skill.name}`,
|
|
3573
|
+
"",
|
|
3574
|
+
skill.description,
|
|
3575
|
+
"",
|
|
3576
|
+
`Runtime directory: \`${runtimeDir}\`. Resolve any relative scripts or assets mentioned by this skill from that directory.`,
|
|
3577
|
+
"",
|
|
3578
|
+
skill.body
|
|
3579
|
+
].filter((line, index, lines) => line !== "" || lines[index - 1] !== "").join("\n").trimEnd();
|
|
3580
|
+
});
|
|
3581
|
+
const unavailableNote = unavailable.length > 0 ? `Attached but unavailable or invalid: ${unavailable.join(", ")}.` : "";
|
|
3582
|
+
parts.push(
|
|
3583
|
+
[
|
|
3584
|
+
"# Available Skills",
|
|
3585
|
+
"",
|
|
3586
|
+
"Use these attached skill instructions when they are relevant to the task.",
|
|
3587
|
+
"",
|
|
3588
|
+
...skillBlocks,
|
|
3589
|
+
...unavailableNote ? ["", unavailableNote] : []
|
|
3590
|
+
].join("\n")
|
|
3591
|
+
);
|
|
2793
3592
|
}
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
3593
|
+
const teamLines = [
|
|
3594
|
+
"# Team",
|
|
3595
|
+
"",
|
|
3596
|
+
`- ${agent.team.id} (${agent.team.name}): ${options.sandboxMode === "docker" ? "/workspace" : agent.team.path}`,
|
|
3597
|
+
"",
|
|
3598
|
+
"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`."
|
|
3599
|
+
];
|
|
3600
|
+
parts.push(teamLines.join("\n"));
|
|
3601
|
+
if (agent.team.userMd.trim()) {
|
|
3602
|
+
parts.push(
|
|
3603
|
+
`# About the User
|
|
3604
|
+
|
|
3605
|
+
Shared context about the human you're working with in this team. Both you and the human curate it. To update: call \`user_md_get\` (returns current content + an etag), merge your change into the full text, then call \`user_md_write\` with the merged content and the etag. Use this for STABLE user-specific facts (preferences, role, working hours, how they like to be addressed) \u2014 and to CORRECT stale entries when the human tells you something different from what's recorded. For project knowledge use \`memory_write\` instead; for personal notes about yourself use \`home_write\` on IDENTITY.md. **Do NOT send peer messages announcing USER.md changes \u2014 every agent in the team sees the new content in their system prompt on their next turn automatically.**
|
|
3606
|
+
|
|
3607
|
+
${agent.team.userMd.trim()}`
|
|
3608
|
+
);
|
|
3609
|
+
} else {
|
|
3610
|
+
parts.push(
|
|
3611
|
+
`# About the User
|
|
3612
|
+
|
|
3613
|
+
This team's USER.md is empty. As you learn STABLE facts about the human (preferences, role, working hours, how they like to be addressed), populate it via \`user_md_get\` then \`user_md_write\` (always get first \u2014 you need the etag). Reserve this for things you're confident are durable \u2014 project knowledge belongs in \`memory_write\`, personal notes about yourself in \`home_write\` on IDENTITY.md. **Do NOT send peer messages announcing USER.md changes \u2014 every agent in the team sees the new content in their system prompt on their next turn automatically.**`
|
|
3614
|
+
);
|
|
2805
3615
|
}
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
3616
|
+
const reviewedLessons = boundReviewedLessons(agent.privateLessons);
|
|
3617
|
+
if (reviewedLessons.length > 0) {
|
|
3618
|
+
parts.push(
|
|
3619
|
+
`# Reviewed lessons
|
|
3620
|
+
|
|
3621
|
+
These private lessons were explicitly approved by the human for your behavior. Follow them when relevant; current user instructions and higher-priority policy still win.
|
|
3622
|
+
|
|
3623
|
+
${reviewedLessons.map((lesson) => `- ${lesson}`).join("\n")}`
|
|
2809
3624
|
);
|
|
2810
3625
|
}
|
|
3626
|
+
parts.push(
|
|
3627
|
+
"# Memory\n\nYou share a persistent memory backend with every other agent in this team. Use `memory_write` to remember things across sessions, and `memory_search` / `memory_read` / `memory_list` to recall them. This memory is for project knowledge \u2014 codebase notes, decisions, things the user told you about the work. For personal notes about yourself (preferences, persona quirks), use `home_write` on IDENTITY.md instead. Always check memory at the start of a session: another agent in the team may have already learned something useful. **Do NOT send peer messages announcing memory writes \u2014 every agent has access to the same store via `memory_search` and will find your note when they need it.**"
|
|
3628
|
+
);
|
|
3629
|
+
return parts.join("\n\n---\n\n");
|
|
2811
3630
|
}
|
|
2812
|
-
function
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
const
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
clearTimeout(timeoutHandle);
|
|
2825
|
-
const detail3 = error || stderr.trim();
|
|
2826
|
-
resolve6({
|
|
2827
|
-
ok,
|
|
2828
|
-
missing: /no such container/i.test(detail3),
|
|
2829
|
-
error: detail3
|
|
2830
|
-
});
|
|
2831
|
-
};
|
|
2832
|
-
const timeoutHandle = setTimeout(() => {
|
|
2833
|
-
child.kill("SIGKILL");
|
|
2834
|
-
finish(false, `cleanup timed out after ${CLEANUP_TIMEOUT_MS}ms`);
|
|
2835
|
-
}, CLEANUP_TIMEOUT_MS);
|
|
2836
|
-
child.stderr.on("data", (data) => {
|
|
2837
|
-
stderr = `${stderr}${data.toString("utf8")}`.slice(-2048);
|
|
2838
|
-
});
|
|
2839
|
-
child.once("error", (error) => finish(false, error.message));
|
|
2840
|
-
child.once("close", (code) => finish(code === 0));
|
|
2841
|
-
});
|
|
3631
|
+
function boundReviewedLessons(lessons, maxCharacters = 8e3) {
|
|
3632
|
+
const selected = [];
|
|
3633
|
+
let used = 0;
|
|
3634
|
+
for (const raw of lessons) {
|
|
3635
|
+
const lesson = raw.trim();
|
|
3636
|
+
if (!lesson) continue;
|
|
3637
|
+
const remaining = maxCharacters - used;
|
|
3638
|
+
if (remaining <= 0) break;
|
|
3639
|
+
selected.push(lesson.slice(0, remaining));
|
|
3640
|
+
used += Math.min(lesson.length, remaining);
|
|
3641
|
+
}
|
|
3642
|
+
return selected;
|
|
2842
3643
|
}
|
|
2843
3644
|
|
|
2844
3645
|
// ../daemon/src/runtime/shell/tooling.ts
|
|
3646
|
+
import { existsSync as existsSync11, lstatSync as lstatSync4, realpathSync as realpathSync6 } from "fs";
|
|
3647
|
+
import { dirname as dirname4, isAbsolute as isAbsolute5, join as join18, relative as relative4, sep as sep4 } from "path";
|
|
3648
|
+
import {
|
|
3649
|
+
createBashToolDefinition,
|
|
3650
|
+
createLocalBashOperations as createLocalBashOperations2,
|
|
3651
|
+
defineTool as defineTool2
|
|
3652
|
+
} from "@earendil-works/pi-coding-agent";
|
|
2845
3653
|
var HOST_CODING_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
|
2846
3654
|
var HOST_FILE_TOOL_NAMES = ["read", "edit", "write", "grep", "find", "ls"];
|
|
2847
3655
|
var CONTAINER_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
2848
3656
|
var SANDBOX_INPUTS_DIR = "/inputs";
|
|
2849
3657
|
var SANDBOX_MEMORY_DIR = "/workspace/memory";
|
|
2850
|
-
function createProtectedSessionShellTools(cwd, runtime, approvalHost) {
|
|
3658
|
+
function createProtectedSessionShellTools(cwd, runtime, approvalHost, lifecycle) {
|
|
3659
|
+
const operations = createPreparedDockerBashOperations(runtime, lifecycle);
|
|
3660
|
+
const modelCwd = codingContainerCwd(runtime.coding?.cwd ?? ".");
|
|
2851
3661
|
const base = createBashToolDefinition(cwd, {
|
|
2852
|
-
operations:
|
|
3662
|
+
operations: {
|
|
3663
|
+
exec(command, requestedCwd, options) {
|
|
3664
|
+
if (requestedCwd !== modelCwd && requestedCwd !== cwd)
|
|
3665
|
+
throw new Error("Protected command working directory differs from the admitted scope");
|
|
3666
|
+
return operations.exec(command, cwd, options);
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
2853
3669
|
});
|
|
2854
3670
|
const dockerBash = defineTool2({
|
|
2855
3671
|
...base,
|
|
2856
3672
|
label: "bash (protected Docker sandbox)",
|
|
2857
|
-
description: `${base.description} Runs in an ephemeral, network-disabled Docker container with the Team workspace mounted read/write plus only the preflighted read-only memory, skill, and attachment mounts. Host files, host tools, and host credentials are unavailable.`
|
|
3673
|
+
description: `${base.description} Default working directory: ${codingContainerCwd(runtime.coding?.cwd ?? ".")}. Runs in an ephemeral, network-disabled Docker container with the Team workspace mounted read/write plus only the preflighted read-only memory, skill, and attachment mounts. Host files, host tools, and host credentials are unavailable.`
|
|
2858
3674
|
});
|
|
2859
3675
|
return {
|
|
2860
3676
|
config: {
|
|
@@ -2880,21 +3696,40 @@ function buildSandboxContainerEnv(env, allowlist) {
|
|
|
2880
3696
|
}
|
|
2881
3697
|
function createSessionShellTools(cwd, env, options = {}) {
|
|
2882
3698
|
const config = resolveShellSecurityConfig(env);
|
|
3699
|
+
if (options.preparedDocker) {
|
|
3700
|
+
if (config.sandboxMode !== "docker")
|
|
3701
|
+
throw new Error("Prepared coding environment cannot execute on the host");
|
|
3702
|
+
const runtime = options.preparedDocker;
|
|
3703
|
+
const base2 = createBashToolDefinition(cwd, {
|
|
3704
|
+
operations: createPreparedDockerBashOperations(runtime, options.dockerLifecycle)
|
|
3705
|
+
});
|
|
3706
|
+
const tool = defineTool2({
|
|
3707
|
+
...base2,
|
|
3708
|
+
label: "bash (prepared Docker sandbox)",
|
|
3709
|
+
description: `${base2.description} Default working directory: ${codingContainerCwd(runtime.coding?.cwd ?? ".")}. Fresh network-disabled Docker container with the Team workspace and preflighted read-only mounts.`
|
|
3710
|
+
});
|
|
3711
|
+
return {
|
|
3712
|
+
config: { ...config, sandboxImage: runtime.image, envAllowlist: [] },
|
|
3713
|
+
hostToolNames: [],
|
|
3714
|
+
customBash: config.approvalMode === "dangerous" ? createApprovalGatedBashTool(tool, options.approvalHost) : tool
|
|
3715
|
+
};
|
|
3716
|
+
}
|
|
2883
3717
|
if (config.sandboxMode === "off" && config.approvalMode === "off") {
|
|
2884
3718
|
return { config, hostToolNames: HOST_CODING_TOOL_NAMES };
|
|
2885
3719
|
}
|
|
2886
3720
|
if (config.sandboxMode === "off") {
|
|
2887
3721
|
const base2 = createBashToolDefinition(cwd, {
|
|
2888
|
-
operations:
|
|
3722
|
+
operations: createLocalBashOperations2()
|
|
2889
3723
|
});
|
|
2890
3724
|
const customBash2 = createApprovalGatedBashTool(base2, options.approvalHost);
|
|
2891
3725
|
return { config, hostToolNames: HOST_FILE_TOOL_NAMES, customBash: customBash2 };
|
|
2892
3726
|
}
|
|
2893
3727
|
const operations = createDockerBashOperations({
|
|
3728
|
+
lifecycle: options.dockerLifecycle,
|
|
2894
3729
|
image: config.sandboxImage,
|
|
2895
3730
|
env: buildSandboxContainerEnv(env, config.envAllowlist),
|
|
2896
3731
|
readOnlyMounts: [
|
|
2897
|
-
...existsSync11(
|
|
3732
|
+
...existsSync11(join18(cwd, "memory")) ? [safeReadOnlyMount(join18(cwd, "memory"), SANDBOX_MEMORY_DIR, cwd)] : [],
|
|
2898
3733
|
...options.inputsDir ? [safeReadOnlyMount(options.inputsDir, SANDBOX_INPUTS_DIR, dirname4(options.inputsDir))] : [],
|
|
2899
3734
|
...(options.skillMounts ?? []).map(
|
|
2900
3735
|
(mount) => safeReadOnlyMount(mount.source, mount.target, dirname4(mount.source))
|
|
@@ -2911,14 +3746,14 @@ function createSessionShellTools(cwd, env, options = {}) {
|
|
|
2911
3746
|
return { config, hostToolNames: [], customBash };
|
|
2912
3747
|
}
|
|
2913
3748
|
function safeReadOnlyMount(source, target, expectedRoot) {
|
|
2914
|
-
const info =
|
|
3749
|
+
const info = lstatSync4(source);
|
|
2915
3750
|
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
2916
3751
|
throw new Error(`Docker sandbox read-only mount must be a real directory: ${source}`);
|
|
2917
3752
|
}
|
|
2918
|
-
const sourceRoot =
|
|
2919
|
-
const canonicalSource =
|
|
3753
|
+
const sourceRoot = realpathSync6(expectedRoot);
|
|
3754
|
+
const canonicalSource = realpathSync6(source);
|
|
2920
3755
|
const rel = relative4(sourceRoot, canonicalSource);
|
|
2921
|
-
const contained = rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !
|
|
3756
|
+
const contained = rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !isAbsolute5(rel);
|
|
2922
3757
|
if (!contained) {
|
|
2923
3758
|
throw new Error(`Docker sandbox read-only mount escaped its configured root: ${source}`);
|
|
2924
3759
|
}
|
|
@@ -2927,18 +3762,18 @@ function safeReadOnlyMount(source, target, expectedRoot) {
|
|
|
2927
3762
|
|
|
2928
3763
|
// ../daemon/src/runtime/worker/runtime.ts
|
|
2929
3764
|
import {
|
|
2930
|
-
constants as
|
|
3765
|
+
constants as constants6,
|
|
2931
3766
|
existsSync as existsSync12,
|
|
2932
|
-
lstatSync as
|
|
3767
|
+
lstatSync as lstatSync5,
|
|
2933
3768
|
mkdirSync as mkdirSync6,
|
|
2934
3769
|
mkdtempSync as mkdtempSync3,
|
|
2935
|
-
realpathSync as
|
|
3770
|
+
realpathSync as realpathSync7,
|
|
2936
3771
|
rmSync as rmSync11,
|
|
2937
3772
|
statSync as statSync3
|
|
2938
3773
|
} from "fs";
|
|
2939
3774
|
import { access as access2 } from "fs/promises";
|
|
2940
3775
|
import { tmpdir as tmpdir3 } from "os";
|
|
2941
|
-
import { isAbsolute as
|
|
3776
|
+
import { isAbsolute as isAbsolute6, join as join19, relative as relative5, sep as sep5 } from "path";
|
|
2942
3777
|
var REDACTED_VALUE = "[REDACTED]";
|
|
2943
3778
|
var REASONING_LEVELS = /* @__PURE__ */ new Set([
|
|
2944
3779
|
"off",
|
|
@@ -2949,6 +3784,11 @@ var REASONING_LEVELS = /* @__PURE__ */ new Set([
|
|
|
2949
3784
|
"xhigh"
|
|
2950
3785
|
]);
|
|
2951
3786
|
var CONFIGURED_KEYS = /* @__PURE__ */ new Set([
|
|
3787
|
+
"containerNamespace",
|
|
3788
|
+
"configuredDocker",
|
|
3789
|
+
"repositoryContext",
|
|
3790
|
+
"questionEnabled",
|
|
3791
|
+
"conversation",
|
|
2952
3792
|
"kind",
|
|
2953
3793
|
"agent",
|
|
2954
3794
|
"message",
|
|
@@ -2962,6 +3802,8 @@ var CONFIGURED_KEYS = /* @__PURE__ */ new Set([
|
|
|
2962
3802
|
"bashApprovalMode"
|
|
2963
3803
|
]);
|
|
2964
3804
|
var CONFIGURED_REQUIRED_KEYS = /* @__PURE__ */ new Set([
|
|
3805
|
+
"repositoryContext",
|
|
3806
|
+
"conversation",
|
|
2965
3807
|
"kind",
|
|
2966
3808
|
"agent",
|
|
2967
3809
|
"message",
|
|
@@ -2971,6 +3813,10 @@ var CONFIGURED_REQUIRED_KEYS = /* @__PURE__ */ new Set([
|
|
|
2971
3813
|
"bashApprovalMode"
|
|
2972
3814
|
]);
|
|
2973
3815
|
var PROTECTED_KEYS = /* @__PURE__ */ new Set([
|
|
3816
|
+
"containerNamespace",
|
|
3817
|
+
"repositoryContext",
|
|
3818
|
+
"questionEnabled",
|
|
3819
|
+
"conversation",
|
|
2974
3820
|
"kind",
|
|
2975
3821
|
"agent",
|
|
2976
3822
|
"message",
|
|
@@ -2984,7 +3830,11 @@ var PROTECTED_KEYS = /* @__PURE__ */ new Set([
|
|
|
2984
3830
|
"apiKeyRefreshEnabled",
|
|
2985
3831
|
"scratch"
|
|
2986
3832
|
]);
|
|
2987
|
-
var PROTECTED_REQUIRED_KEYS = new Set(
|
|
3833
|
+
var PROTECTED_REQUIRED_KEYS = new Set(
|
|
3834
|
+
[...PROTECTED_KEYS].filter(
|
|
3835
|
+
(key) => key !== "images" && key !== "questionEnabled" && key !== "containerNamespace"
|
|
3836
|
+
)
|
|
3837
|
+
);
|
|
2988
3838
|
var REVIEW_KEYS = /* @__PURE__ */ new Set([
|
|
2989
3839
|
"kind",
|
|
2990
3840
|
"agentId",
|
|
@@ -3044,14 +3894,27 @@ function validateMinimalWorkerProcessEnv(env, scratch, platform = process.platfo
|
|
|
3044
3894
|
function parseWorkerInput(value) {
|
|
3045
3895
|
const input = objectRecord(value, "worker input");
|
|
3046
3896
|
const kind = input.kind;
|
|
3897
|
+
if (input.containerNamespace !== void 0 && (typeof input.containerNamespace !== "string" || !/^[a-f0-9-]{36}$/.test(input.containerNamespace)))
|
|
3898
|
+
throw new Error("Invalid container namespace");
|
|
3899
|
+
if (input.questionEnabled !== void 0 && typeof input.questionEnabled !== "boolean")
|
|
3900
|
+
throw new Error("Invalid worker question capability");
|
|
3047
3901
|
if (kind === "configured_operator_http") {
|
|
3048
3902
|
assertExactKeys(input, CONFIGURED_KEYS, "configured worker input", CONFIGURED_REQUIRED_KEYS);
|
|
3049
3903
|
if (!isResolvedAgent(input.agent)) throw new Error("worker: configured input requires an agent");
|
|
3904
|
+
if (input.configuredDocker !== void 0) {
|
|
3905
|
+
const prepared = objectRecord(input.configuredDocker, "configured Docker inputs");
|
|
3906
|
+
const keys2 = /* @__PURE__ */ new Set(["paths", "docker"]);
|
|
3907
|
+
assertExactKeys(prepared, keys2, "configured Docker inputs", keys2);
|
|
3908
|
+
assertProtectedWorkerPaths(prepared.paths, input.agent);
|
|
3909
|
+
assertProtectedDockerRuntime(prepared.docker, prepared.paths);
|
|
3910
|
+
}
|
|
3911
|
+
assertRepositoryContext(input.repositoryContext, input.agent.team.id);
|
|
3050
3912
|
requireString(input.message, "message");
|
|
3051
3913
|
if (!Array.isArray(input.enabledProviders) || !input.enabledProviders.every(isString)) {
|
|
3052
3914
|
throw new Error("worker: configured input requires enabledProviders");
|
|
3053
3915
|
}
|
|
3054
3916
|
requireString(input.turnId, "turnId");
|
|
3917
|
+
assertConversationTarget(input.conversation);
|
|
3055
3918
|
if (input.bashApprovalMode !== "interactive" && input.bashApprovalMode !== "auto_deny") {
|
|
3056
3919
|
throw new Error("worker: configured input requires a valid bashApprovalMode");
|
|
3057
3920
|
}
|
|
@@ -3071,8 +3934,10 @@ function parseWorkerInput(value) {
|
|
|
3071
3934
|
if (kind === "protected") {
|
|
3072
3935
|
assertExactKeys(input, PROTECTED_KEYS, "protected worker input", PROTECTED_REQUIRED_KEYS);
|
|
3073
3936
|
if (!isResolvedAgent(input.agent)) throw new Error("worker: protected input requires an agent");
|
|
3937
|
+
assertRepositoryContext(input.repositoryContext, input.agent.team.id);
|
|
3074
3938
|
requireString(input.message, "message");
|
|
3075
3939
|
requireString(input.turnId, "turnId");
|
|
3940
|
+
assertConversationTarget(input.conversation);
|
|
3076
3941
|
if (input.bashApprovalMode !== "auto_deny") {
|
|
3077
3942
|
throw new Error("worker: protected input requires auto-deny shell approval");
|
|
3078
3943
|
}
|
|
@@ -3202,8 +4067,8 @@ function protectedRuntimeSecrets(runtime) {
|
|
|
3202
4067
|
runtime.credentialFile?.content
|
|
3203
4068
|
].filter((value) => Boolean(value));
|
|
3204
4069
|
}
|
|
3205
|
-
function redactExactValues(
|
|
3206
|
-
let redacted =
|
|
4070
|
+
function redactExactValues(text2, values) {
|
|
4071
|
+
let redacted = text2;
|
|
3207
4072
|
for (const value of [...new Set(values)].filter(Boolean).sort((a, b) => b.length - a.length)) {
|
|
3208
4073
|
redacted = redacted.replaceAll(value, REDACTED_VALUE);
|
|
3209
4074
|
}
|
|
@@ -3312,7 +4177,8 @@ function assertProtectedDockerRuntime(value, paths) {
|
|
|
3312
4177
|
"gid",
|
|
3313
4178
|
"workspace",
|
|
3314
4179
|
"readOnlyMounts",
|
|
3315
|
-
"containerEnv"
|
|
4180
|
+
"containerEnv",
|
|
4181
|
+
"coding"
|
|
3316
4182
|
]),
|
|
3317
4183
|
"protected Docker runtime",
|
|
3318
4184
|
/* @__PURE__ */ new Set([
|
|
@@ -3352,7 +4218,7 @@ function assertProtectedDockerRuntime(value, paths) {
|
|
|
3352
4218
|
throw new Error(`worker: protected Docker executable identity has invalid ${name}`);
|
|
3353
4219
|
}
|
|
3354
4220
|
}
|
|
3355
|
-
if (typeof runtime.endpoint !== "string" || !runtime.endpoint.startsWith("unix://") || !
|
|
4221
|
+
if (typeof runtime.endpoint !== "string" || !runtime.endpoint.startsWith("unix://") || !isAbsolute6(runtime.endpoint.slice("unix://".length)) || containsUnsafeDockerArgument(runtime.endpoint)) {
|
|
3356
4222
|
throw new Error("worker: protected Docker endpoint must be a local Unix socket");
|
|
3357
4223
|
}
|
|
3358
4224
|
if (!nonEmptyString(runtime.image) || runtime.image.startsWith("-") || runtime.image.includes("\0") || runtime.image.includes("\n") || runtime.image.includes("\r")) {
|
|
@@ -3416,6 +4282,7 @@ function assertProtectedDockerRuntime(value, paths) {
|
|
|
3416
4282
|
runtime.readOnlyMounts,
|
|
3417
4283
|
paths
|
|
3418
4284
|
);
|
|
4285
|
+
if (runtime.coding !== void 0) validateDockerCodingSelection(runtime.coding);
|
|
3419
4286
|
const containerEnv = objectRecord(runtime.containerEnv, "protected Docker container environment");
|
|
3420
4287
|
const expectedContainerEnv = {
|
|
3421
4288
|
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
@@ -3465,11 +4332,11 @@ function containsUnsafeDockerArgument(value) {
|
|
|
3465
4332
|
function requireCanonicalRealDirectory(value, name) {
|
|
3466
4333
|
if (typeof value !== "string") throw new Error(`worker: ${name} must be a path`);
|
|
3467
4334
|
try {
|
|
3468
|
-
const entry =
|
|
4335
|
+
const entry = lstatSync5(value);
|
|
3469
4336
|
if (entry.isSymbolicLink() || !entry.isDirectory()) {
|
|
3470
4337
|
throw new Error("unsafe entry");
|
|
3471
4338
|
}
|
|
3472
|
-
const canonical =
|
|
4339
|
+
const canonical = realpathSync7(value);
|
|
3473
4340
|
if (canonical !== value) throw new Error("non-canonical entry");
|
|
3474
4341
|
return canonical;
|
|
3475
4342
|
} catch {
|
|
@@ -3480,7 +4347,7 @@ function resolveCanonicalDirectory(value, name) {
|
|
|
3480
4347
|
try {
|
|
3481
4348
|
const entry = statSync3(value);
|
|
3482
4349
|
if (!entry.isDirectory()) throw new Error("not a directory");
|
|
3483
|
-
return
|
|
4350
|
+
return realpathSync7(value);
|
|
3484
4351
|
} catch {
|
|
3485
4352
|
throw new Error(`worker: ${name} is unavailable or unsafe`);
|
|
3486
4353
|
}
|
|
@@ -3513,16 +4380,16 @@ function assertMinimalWorkerScratch(value) {
|
|
|
3513
4380
|
}
|
|
3514
4381
|
}
|
|
3515
4382
|
function requireCanonicalWindowsDirectory(value, name) {
|
|
3516
|
-
if (!value || !
|
|
4383
|
+
if (!value || !isAbsolute6(value) || !existsSync12(value) || !statSync3(value).isDirectory()) {
|
|
3517
4384
|
throw new Error(`minimal worker bootstrap requires a valid ${name} directory`);
|
|
3518
4385
|
}
|
|
3519
|
-
return
|
|
4386
|
+
return realpathSync7(value);
|
|
3520
4387
|
}
|
|
3521
4388
|
function requireCanonicalWindowsFile(value, name) {
|
|
3522
|
-
if (!value || !
|
|
4389
|
+
if (!value || !isAbsolute6(value) || !existsSync12(value) || !statSync3(value).isFile()) {
|
|
3523
4390
|
throw new Error(`minimal worker bootstrap requires a valid ${name} executable`);
|
|
3524
4391
|
}
|
|
3525
|
-
return
|
|
4392
|
+
return realpathSync7(value);
|
|
3526
4393
|
}
|
|
3527
4394
|
function objectRecord(value, label) {
|
|
3528
4395
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -3557,21 +4424,69 @@ function isString(value) {
|
|
|
3557
4424
|
return typeof value === "string";
|
|
3558
4425
|
}
|
|
3559
4426
|
function requireAbsolutePath(value, name) {
|
|
3560
|
-
if (typeof value !== "string" || !
|
|
4427
|
+
if (typeof value !== "string" || !isAbsolute6(value) || value.includes("\0")) {
|
|
3561
4428
|
throw new Error(`worker: ${name} must be an absolute path`);
|
|
3562
4429
|
}
|
|
3563
4430
|
}
|
|
3564
4431
|
function lexicallyWithin(root, candidate) {
|
|
3565
4432
|
const rel = relative5(root, candidate);
|
|
3566
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep5}`) && !
|
|
4433
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep5}`) && !isAbsolute6(rel);
|
|
4434
|
+
}
|
|
4435
|
+
function assertConversationTarget(value) {
|
|
4436
|
+
const target = objectRecord(value, "conversation target");
|
|
4437
|
+
assertExactKeys(target, /* @__PURE__ */ new Set(["id", "filename"]), "conversation target");
|
|
4438
|
+
if (typeof target.id !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(target.id) || target.filename !== `${target.id}.jsonl`)
|
|
4439
|
+
throw new Error("Invalid worker conversation target");
|
|
4440
|
+
}
|
|
4441
|
+
|
|
4442
|
+
// ../daemon/src/runtime/tools/ask-user.ts
|
|
4443
|
+
function askUserTool(ask) {
|
|
4444
|
+
return {
|
|
4445
|
+
def: {
|
|
4446
|
+
name: "ask_user",
|
|
4447
|
+
description: "Ask the operator one clarification question and wait for an explicit choice, free-text answer, Skip, or a typed no-answer outcome. An answer is information only: it never grants shell or communication permission. Do not ask for credentials. Do not invent a response when no answer is returned.",
|
|
4448
|
+
parameters: {
|
|
4449
|
+
type: "object",
|
|
4450
|
+
additionalProperties: false,
|
|
4451
|
+
required: ["prompt", "choices"],
|
|
4452
|
+
properties: {
|
|
4453
|
+
prompt: { type: "string", minLength: 1, maxLength: 4096 },
|
|
4454
|
+
choices: {
|
|
4455
|
+
type: "array",
|
|
4456
|
+
minItems: 2,
|
|
4457
|
+
maxItems: 4,
|
|
4458
|
+
items: {
|
|
4459
|
+
type: "object",
|
|
4460
|
+
additionalProperties: false,
|
|
4461
|
+
required: ["label"],
|
|
4462
|
+
properties: {
|
|
4463
|
+
label: { type: "string", minLength: 1, maxLength: 120 },
|
|
4464
|
+
description: { type: "string", minLength: 1, maxLength: 500 }
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
},
|
|
4468
|
+
recommendedIndex: { type: "integer", minimum: 0, maximum: 3 }
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
},
|
|
4472
|
+
async invoke(args, context) {
|
|
4473
|
+
if (!context?.toolCallId) throw new Error("Question requires its actual tool call identity");
|
|
4474
|
+
const { receipt, ...result } = await ask(
|
|
4475
|
+
context.toolCallId,
|
|
4476
|
+
args
|
|
4477
|
+
);
|
|
4478
|
+
const text2 = JSON.stringify(result);
|
|
4479
|
+
return receipt ? { content: [{ type: "text", text: text2 }], questionReceipt: receipt } : text2;
|
|
4480
|
+
}
|
|
4481
|
+
};
|
|
3567
4482
|
}
|
|
3568
4483
|
|
|
3569
4484
|
// ../daemon/src/runtime/pi/tools.ts
|
|
3570
|
-
import { Type as
|
|
4485
|
+
import { Type as Type4 } from "typebox";
|
|
3571
4486
|
|
|
3572
4487
|
// ../daemon/src/runtime/tools/bootstrap.ts
|
|
3573
4488
|
import { existsSync as existsSync13, rmSync as rmSync12 } from "fs";
|
|
3574
|
-
import { join as
|
|
4489
|
+
import { join as join20 } from "path";
|
|
3575
4490
|
function bootstrapTool(agentDir) {
|
|
3576
4491
|
return {
|
|
3577
4492
|
def: {
|
|
@@ -3580,7 +4495,7 @@ function bootstrapTool(agentDir) {
|
|
|
3580
4495
|
parameters: { type: "object", properties: {} }
|
|
3581
4496
|
},
|
|
3582
4497
|
async invoke() {
|
|
3583
|
-
const path =
|
|
4498
|
+
const path = join20(agentDir, "BOOTSTRAP.md");
|
|
3584
4499
|
if (existsSync13(path)) {
|
|
3585
4500
|
rmSync12(path);
|
|
3586
4501
|
return "BOOTSTRAP.md removed. Bootstrap is complete.";
|
|
@@ -3721,15 +4636,15 @@ function browserTools(host, agentId) {
|
|
|
3721
4636
|
|
|
3722
4637
|
// ../daemon/src/runtime/tools/deliver-file.ts
|
|
3723
4638
|
import {
|
|
3724
|
-
closeSync as
|
|
3725
|
-
constants as
|
|
3726
|
-
fstatSync as
|
|
3727
|
-
openSync as
|
|
3728
|
-
|
|
3729
|
-
realpathSync as
|
|
4639
|
+
closeSync as closeSync6,
|
|
4640
|
+
constants as constants7,
|
|
4641
|
+
fstatSync as fstatSync5,
|
|
4642
|
+
openSync as openSync6,
|
|
4643
|
+
readSync as readSync3,
|
|
4644
|
+
realpathSync as realpathSync8,
|
|
3730
4645
|
statSync as statSync4
|
|
3731
4646
|
} from "fs";
|
|
3732
|
-
import { basename as
|
|
4647
|
+
import { basename as basename3, extname, isAbsolute as isAbsolute7, relative as relative6, resolve as resolve5, sep as sep6 } from "path";
|
|
3733
4648
|
var MAX_DELIVER_BYTES = 25 * 1024 * 1024;
|
|
3734
4649
|
var MIME = {
|
|
3735
4650
|
".pdf": "application/pdf",
|
|
@@ -3747,19 +4662,19 @@ var MIME = {
|
|
|
3747
4662
|
};
|
|
3748
4663
|
function isWithin2(root, candidate) {
|
|
3749
4664
|
const rel = relative6(root, candidate);
|
|
3750
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep6}`) && !
|
|
4665
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep6}`) && !isAbsolute7(rel);
|
|
3751
4666
|
}
|
|
3752
|
-
function deliverFileTool(cwd, sink) {
|
|
4667
|
+
function deliverFileTool(cwd, sink, sessionId = "") {
|
|
3753
4668
|
const workspacePath = resolve5(cwd);
|
|
3754
4669
|
let workspaceRealPath = null;
|
|
3755
4670
|
try {
|
|
3756
|
-
workspaceRealPath =
|
|
4671
|
+
workspaceRealPath = realpathSync8(workspacePath);
|
|
3757
4672
|
} catch {
|
|
3758
4673
|
}
|
|
3759
4674
|
return {
|
|
3760
4675
|
def: {
|
|
3761
4676
|
name: "deliver_file",
|
|
3762
|
-
description: "Send a file from your workspace to the user so they can download it (web), receive it as a document (Telegram), or save it (CLI). Use this to deliver reports, exports, or any artifact you produced. Max 25
|
|
4677
|
+
description: "Send a file from your workspace to the user so they can download it (web), receive it as a document (Telegram), or save it (CLI). Use this to deliver reports, exports, or any artifact you produced. Max 25 MiB. Saved delivery remains subject to Team Policy.",
|
|
3763
4678
|
parameters: {
|
|
3764
4679
|
type: "object",
|
|
3765
4680
|
properties: {
|
|
@@ -3772,10 +4687,10 @@ function deliverFileTool(cwd, sink) {
|
|
|
3772
4687
|
additionalProperties: false
|
|
3773
4688
|
}
|
|
3774
4689
|
},
|
|
3775
|
-
async invoke(args) {
|
|
4690
|
+
async invoke(args, context) {
|
|
3776
4691
|
const p = String(args.path ?? "");
|
|
3777
4692
|
if (!p) throw new Error("deliver_file: path is required");
|
|
3778
|
-
if (
|
|
4693
|
+
if (isAbsolute7(p)) {
|
|
3779
4694
|
throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
|
|
3780
4695
|
}
|
|
3781
4696
|
const abs = resolve5(workspacePath, p);
|
|
@@ -3788,7 +4703,7 @@ function deliverFileTool(cwd, sink) {
|
|
|
3788
4703
|
let real;
|
|
3789
4704
|
let expected;
|
|
3790
4705
|
try {
|
|
3791
|
-
real =
|
|
4706
|
+
real = realpathSync8(abs);
|
|
3792
4707
|
expected = statSync4(real);
|
|
3793
4708
|
} catch {
|
|
3794
4709
|
throw new Error(`deliver_file: no such file: ${p}`);
|
|
@@ -3798,13 +4713,13 @@ function deliverFileTool(cwd, sink) {
|
|
|
3798
4713
|
}
|
|
3799
4714
|
let fd;
|
|
3800
4715
|
try {
|
|
3801
|
-
fd =
|
|
4716
|
+
fd = openSync6(real, constants7.O_RDONLY | constants7.O_NOFOLLOW | constants7.O_NONBLOCK);
|
|
3802
4717
|
} catch {
|
|
3803
4718
|
throw new Error(`deliver_file: no such file: ${p}`);
|
|
3804
4719
|
}
|
|
3805
4720
|
let data;
|
|
3806
4721
|
try {
|
|
3807
|
-
const stat2 =
|
|
4722
|
+
const stat2 = fstatSync5(fd);
|
|
3808
4723
|
if (!stat2.isFile()) {
|
|
3809
4724
|
throw new Error(`deliver_file: not a regular file: ${p}`);
|
|
3810
4725
|
}
|
|
@@ -3813,7 +4728,7 @@ function deliverFileTool(cwd, sink) {
|
|
|
3813
4728
|
}
|
|
3814
4729
|
let confirmedReal;
|
|
3815
4730
|
try {
|
|
3816
|
-
confirmedReal =
|
|
4731
|
+
confirmedReal = realpathSync8(abs);
|
|
3817
4732
|
} catch {
|
|
3818
4733
|
throw new Error(`deliver_file: path changed during validation: ${p}`);
|
|
3819
4734
|
}
|
|
@@ -3822,32 +4737,53 @@ function deliverFileTool(cwd, sink) {
|
|
|
3822
4737
|
}
|
|
3823
4738
|
if (stat2.size > MAX_DELIVER_BYTES) {
|
|
3824
4739
|
throw new Error(
|
|
3825
|
-
`deliver_file: "${
|
|
4740
|
+
`deliver_file: "${basename3(abs)}" is too large (${(stat2.size / 1024 / 1024).toFixed(1)} MB > 25 MB)`
|
|
3826
4741
|
);
|
|
3827
4742
|
}
|
|
3828
|
-
|
|
4743
|
+
const buffer = Buffer.alloc(MAX_DELIVER_BYTES + 1);
|
|
4744
|
+
let length = 0;
|
|
4745
|
+
while (length < buffer.length) {
|
|
4746
|
+
const count2 = readSync3(fd, buffer, length, buffer.length - length, null);
|
|
4747
|
+
if (count2 === 0) break;
|
|
4748
|
+
length += count2;
|
|
4749
|
+
}
|
|
4750
|
+
if (length > MAX_DELIVER_BYTES) throw new Error("deliver_file: file exceeds 25 MiB");
|
|
4751
|
+
data = buffer.subarray(0, length).toString("base64");
|
|
3829
4752
|
} finally {
|
|
3830
|
-
|
|
4753
|
+
closeSync6(fd);
|
|
3831
4754
|
}
|
|
3832
|
-
const name =
|
|
4755
|
+
const name = basename3(abs);
|
|
3833
4756
|
const mimeType = MIME[extname(abs).toLowerCase()] ?? "application/octet-stream";
|
|
3834
|
-
|
|
3835
|
-
|
|
4757
|
+
if (!context?.toolCallId || !sessionId)
|
|
4758
|
+
throw new Error("deliver_file: missing source operation");
|
|
4759
|
+
const result = await sink(
|
|
4760
|
+
{ name, mimeType, data },
|
|
4761
|
+
{ sessionId, toolCallId: context.toolCallId }
|
|
4762
|
+
);
|
|
4763
|
+
return {
|
|
4764
|
+
content: [
|
|
4765
|
+
{
|
|
4766
|
+
type: "text",
|
|
4767
|
+
text: `Saved "${name}" (${mimeType}) as result ${result.resultId}. Delivery is subject to Team Policy.`
|
|
4768
|
+
}
|
|
4769
|
+
],
|
|
4770
|
+
result
|
|
4771
|
+
};
|
|
3836
4772
|
}
|
|
3837
4773
|
};
|
|
3838
4774
|
}
|
|
3839
4775
|
|
|
3840
4776
|
// ../daemon/src/runtime/tools/home.ts
|
|
3841
4777
|
import {
|
|
3842
|
-
closeSync as
|
|
3843
|
-
constants as
|
|
3844
|
-
fstatSync as
|
|
3845
|
-
lstatSync as
|
|
3846
|
-
openSync as
|
|
4778
|
+
closeSync as closeSync7,
|
|
4779
|
+
constants as constants8,
|
|
4780
|
+
fstatSync as fstatSync6,
|
|
4781
|
+
lstatSync as lstatSync6,
|
|
4782
|
+
openSync as openSync7,
|
|
3847
4783
|
readdirSync as readdirSync7,
|
|
3848
4784
|
writeFileSync as writeFileSync8
|
|
3849
4785
|
} from "fs";
|
|
3850
|
-
import { join as
|
|
4786
|
+
import { join as join21 } from "path";
|
|
3851
4787
|
var HOME_FILES_READABLE = [
|
|
3852
4788
|
"IDENTITY.md",
|
|
3853
4789
|
"SOUL.md",
|
|
@@ -3912,18 +4848,18 @@ function homeTools(agentDir) {
|
|
|
3912
4848
|
let fd;
|
|
3913
4849
|
try {
|
|
3914
4850
|
rejectExistingSymlinkOrNonFile(path);
|
|
3915
|
-
fd =
|
|
4851
|
+
fd = openSync7(
|
|
3916
4852
|
path,
|
|
3917
|
-
|
|
4853
|
+
constants8.O_WRONLY | constants8.O_CREAT | constants8.O_TRUNC | noFollowFlag(),
|
|
3918
4854
|
384
|
|
3919
4855
|
);
|
|
3920
|
-
if (!
|
|
4856
|
+
if (!fstatSync6(fd).isFile()) throw new Error("target is not a regular file");
|
|
3921
4857
|
writeFileSync8(fd, content, "utf8");
|
|
3922
4858
|
} catch (err) {
|
|
3923
4859
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3924
4860
|
throw new Error(`home_write: could not write ${file}: ${msg}`);
|
|
3925
4861
|
} finally {
|
|
3926
|
-
if (fd !== void 0)
|
|
4862
|
+
if (fd !== void 0) closeSync7(fd);
|
|
3927
4863
|
}
|
|
3928
4864
|
return `wrote ${file} (${Buffer.byteLength(content, "utf8")} bytes)`;
|
|
3929
4865
|
}
|
|
@@ -3939,7 +4875,7 @@ function homeTools(agentDir) {
|
|
|
3939
4875
|
for (const file of HOME_FILES_READABLE) {
|
|
3940
4876
|
const path = privateHomeFile(homeRoot, file);
|
|
3941
4877
|
try {
|
|
3942
|
-
const s =
|
|
4878
|
+
const s = lstatSync6(path);
|
|
3943
4879
|
if (s.isSymbolicLink() || !s.isFile()) continue;
|
|
3944
4880
|
entries.push(`${file} (${s.size}b)`);
|
|
3945
4881
|
} catch {
|
|
@@ -3968,10 +4904,10 @@ function resolvePrivateHomeRoot(agentDir) {
|
|
|
3968
4904
|
}
|
|
3969
4905
|
}
|
|
3970
4906
|
function privateHomeFile(homeRoot, file) {
|
|
3971
|
-
return
|
|
4907
|
+
return join21(homeRoot, file);
|
|
3972
4908
|
}
|
|
3973
4909
|
function rejectSymlinkOrNonFile(path) {
|
|
3974
|
-
const entry =
|
|
4910
|
+
const entry = lstatSync6(path);
|
|
3975
4911
|
if (entry.isSymbolicLink()) throw new Error("symbolic links are not allowed");
|
|
3976
4912
|
if (!entry.isFile()) throw new Error("target is not a regular file");
|
|
3977
4913
|
}
|
|
@@ -4105,9 +5041,9 @@ function messagingTools(host, fromAgentId) {
|
|
|
4105
5041
|
},
|
|
4106
5042
|
async invoke(args) {
|
|
4107
5043
|
const to = String(args.to ?? "");
|
|
4108
|
-
const
|
|
5044
|
+
const text2 = String(args.text ?? "");
|
|
4109
5045
|
if (!to) throw new Error('send_message: "to" is required');
|
|
4110
|
-
if (!
|
|
5046
|
+
if (!text2) throw new Error('send_message: "text" is required');
|
|
4111
5047
|
if (!await host.agentExists(to)) {
|
|
4112
5048
|
throw new Error(`send_message: agent not found: ${to}`);
|
|
4113
5049
|
}
|
|
@@ -4115,7 +5051,7 @@ function messagingTools(host, fromAgentId) {
|
|
|
4115
5051
|
const { messageId } = await host.sendMessage({
|
|
4116
5052
|
from: fromAgentId,
|
|
4117
5053
|
to,
|
|
4118
|
-
payload: JSON.stringify({ text }),
|
|
5054
|
+
payload: JSON.stringify({ text: text2 }),
|
|
4119
5055
|
replyTo
|
|
4120
5056
|
});
|
|
4121
5057
|
return `sent message ${messageId}`;
|
|
@@ -4175,7 +5111,7 @@ function messagingTools(host, fromAgentId) {
|
|
|
4175
5111
|
{
|
|
4176
5112
|
def: {
|
|
4177
5113
|
name: "wait_for_reply",
|
|
4178
|
-
description: "
|
|
5114
|
+
description: "Wait for a reply only when the peer does not need your current workspace. For shared-workspace preparation, send_message and end this turn; the inbox reply resumes work after cleanup releases ownership.",
|
|
4179
5115
|
parameters: {
|
|
4180
5116
|
type: "object",
|
|
4181
5117
|
properties: {
|
|
@@ -4291,25 +5227,25 @@ function normalizeWhitespace(value) {
|
|
|
4291
5227
|
function htmlToMarkdown(html) {
|
|
4292
5228
|
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
4293
5229
|
const title = titleMatch ? normalizeWhitespace(stripTags(titleMatch[1] ?? "")) : void 0;
|
|
4294
|
-
let
|
|
4295
|
-
|
|
5230
|
+
let text2 = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "").replace(/<nav[\s\S]*?<\/nav>/gi, "").replace(/<header[\s\S]*?<\/header>/gi, "").replace(/<footer[\s\S]*?<\/footer>/gi, "");
|
|
5231
|
+
text2 = text2.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, body) => {
|
|
4296
5232
|
const label = normalizeWhitespace(stripTags(body));
|
|
4297
5233
|
return label ? `[${label}](${href})` : href;
|
|
4298
5234
|
});
|
|
4299
|
-
|
|
5235
|
+
text2 = text2.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, body) => {
|
|
4300
5236
|
const n = Math.max(1, Math.min(6, Number.parseInt(level, 10)));
|
|
4301
5237
|
return `
|
|
4302
5238
|
${"#".repeat(n)} ${normalizeWhitespace(stripTags(body))}
|
|
4303
5239
|
`;
|
|
4304
5240
|
});
|
|
4305
|
-
|
|
5241
|
+
text2 = text2.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_, body) => {
|
|
4306
5242
|
const label = normalizeWhitespace(stripTags(body));
|
|
4307
5243
|
return label ? `
|
|
4308
5244
|
- ${label}` : "";
|
|
4309
5245
|
});
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
return { text: normalizeWhitespace(
|
|
5246
|
+
text2 = text2.replace(/<(br|hr)\s*\/?>/gi, "\n").replace(/<\/(p|div|section|article|tr|ul|ol|table|blockquote)>/gi, "\n");
|
|
5247
|
+
text2 = stripTags(text2);
|
|
5248
|
+
return { text: normalizeWhitespace(text2), title };
|
|
4313
5249
|
}
|
|
4314
5250
|
function markdownToPlain(md) {
|
|
4315
5251
|
let t = md;
|
|
@@ -4343,8 +5279,8 @@ function extractReadable(html, url, mode) {
|
|
|
4343
5279
|
if (!parsed?.content) return fallback();
|
|
4344
5280
|
const title = parsed.title || void 0;
|
|
4345
5281
|
if (mode === "text") {
|
|
4346
|
-
const
|
|
4347
|
-
return
|
|
5282
|
+
const text2 = normalizeWhitespace(parsed.textContent ?? "");
|
|
5283
|
+
return text2 ? { text: text2, title } : fallback();
|
|
4348
5284
|
}
|
|
4349
5285
|
const rendered = htmlToMarkdown(parsed.content);
|
|
4350
5286
|
return { text: rendered.text, title: title ?? rendered.title };
|
|
@@ -4655,9 +5591,9 @@ async function readBodyCapped(res, maxBytes) {
|
|
|
4655
5591
|
const decoder = new TextDecoder(charset, { fatal: false });
|
|
4656
5592
|
const reader = res.body?.getReader();
|
|
4657
5593
|
if (!reader) {
|
|
4658
|
-
const
|
|
4659
|
-
if (
|
|
4660
|
-
return { text, truncated: false };
|
|
5594
|
+
const text2 = await res.text();
|
|
5595
|
+
if (text2.length > maxBytes) return { text: text2.slice(0, maxBytes), truncated: true };
|
|
5596
|
+
return { text: text2, truncated: false };
|
|
4661
5597
|
}
|
|
4662
5598
|
const chunks = [];
|
|
4663
5599
|
let total = 0;
|
|
@@ -4714,8 +5650,8 @@ async function firecrawlScrape(url, mode, env, fetchFn, timeoutMs) {
|
|
|
4714
5650
|
if (!body.success || !body.data?.markdown) return null;
|
|
4715
5651
|
const md = body.data.markdown;
|
|
4716
5652
|
const title = body.data.metadata?.title;
|
|
4717
|
-
const
|
|
4718
|
-
return title ? { text, title } : { text };
|
|
5653
|
+
const text2 = mode === "text" ? markdownToPlain(md) : md;
|
|
5654
|
+
return title ? { text: text2, title } : { text: text2 };
|
|
4719
5655
|
} catch {
|
|
4720
5656
|
return null;
|
|
4721
5657
|
} finally {
|
|
@@ -4911,19 +5847,19 @@ function ourToolToPiTool(h) {
|
|
|
4911
5847
|
name: h.def.name,
|
|
4912
5848
|
label: h.def.name,
|
|
4913
5849
|
description: h.def.description,
|
|
4914
|
-
parameters:
|
|
4915
|
-
async execute(
|
|
4916
|
-
const out = await h.invoke(params);
|
|
5850
|
+
parameters: Type4.Unsafe(h.def.parameters),
|
|
5851
|
+
async execute(toolCallId, params) {
|
|
5852
|
+
const out = await h.invoke(params, { toolCallId });
|
|
4917
5853
|
return {
|
|
4918
5854
|
content: toPiContent(out),
|
|
4919
|
-
details: {}
|
|
5855
|
+
details: typeof out === "object" && !Array.isArray(out) ? "result" in out ? { result: out.result } : { questionReceipt: out.questionReceipt } : {}
|
|
4920
5856
|
};
|
|
4921
5857
|
}
|
|
4922
5858
|
};
|
|
4923
5859
|
}
|
|
4924
5860
|
function toPiContent(out) {
|
|
4925
5861
|
if (typeof out === "string") return [{ type: "text", text: out }];
|
|
4926
|
-
return out.map(
|
|
5862
|
+
return (Array.isArray(out) ? out : out.content).map(
|
|
4927
5863
|
(p) => p.type === "text" ? { type: "text", text: p.text } : { type: "image", data: p.data, mimeType: p.mimeType }
|
|
4928
5864
|
);
|
|
4929
5865
|
}
|
|
@@ -4947,8 +5883,9 @@ function createBazilionCustomTools(opts) {
|
|
|
4947
5883
|
handlers.push(...mcpProxyTools(opts.mcpHost, opts.mcpTools));
|
|
4948
5884
|
}
|
|
4949
5885
|
if (opts.fileSink) {
|
|
4950
|
-
handlers.push(deliverFileTool(opts.agent.team.path, opts.fileSink));
|
|
5886
|
+
handlers.push(deliverFileTool(opts.agent.team.path, opts.fileSink, opts.sessionId));
|
|
4951
5887
|
}
|
|
5888
|
+
if (opts.askUser) handlers.push(askUserTool(opts.askUser));
|
|
4952
5889
|
return handlers.map(ourToolToPiTool);
|
|
4953
5890
|
}
|
|
4954
5891
|
function createProtectedBazilionCustomTools(opts) {
|
|
@@ -4959,8 +5896,9 @@ function createProtectedBazilionCustomTools(opts) {
|
|
|
4959
5896
|
protectedWebFetchTool(),
|
|
4960
5897
|
...messagingTools(opts.messagingHost, opts.agent.agent.id),
|
|
4961
5898
|
...userMdTools(opts.userMdHost, opts.agent.team.id),
|
|
4962
|
-
deliverFileTool(opts.agent.team.path, opts.fileSink)
|
|
5899
|
+
deliverFileTool(opts.agent.team.path, opts.fileSink, opts.sessionId)
|
|
4963
5900
|
];
|
|
5901
|
+
if (opts.askUser) handlers.push(askUserTool(opts.askUser));
|
|
4964
5902
|
return handlers.map(ourToolToPiTool);
|
|
4965
5903
|
}
|
|
4966
5904
|
|
|
@@ -4984,6 +5922,7 @@ async function createBazilionSession(opts) {
|
|
|
4984
5922
|
mcpHost,
|
|
4985
5923
|
mcpTools,
|
|
4986
5924
|
fileSink,
|
|
5925
|
+
askUser,
|
|
4987
5926
|
bashApprovalHost,
|
|
4988
5927
|
refreshApiKey,
|
|
4989
5928
|
restricted
|
|
@@ -5005,8 +5944,10 @@ async function createBazilionSession(opts) {
|
|
|
5005
5944
|
const cwd = agent.team.path;
|
|
5006
5945
|
if (!existsSync14(cwd)) mkdirSync7(cwd, { recursive: true });
|
|
5007
5946
|
const promptSkills = restricted ? [] : loadPromptSkills(paths.skillsDir, agent.skills);
|
|
5008
|
-
const uploadsDir =
|
|
5947
|
+
const uploadsDir = join22(agent.agent.dir, "uploads");
|
|
5009
5948
|
const shellTools = restricted ? null : createSessionShellTools(cwd, env, {
|
|
5949
|
+
preparedDocker: opts.preparedDocker,
|
|
5950
|
+
dockerLifecycle: opts.dockerLifecycle,
|
|
5010
5951
|
...existsSync14(uploadsDir) ? { inputsDir: uploadsDir } : {},
|
|
5011
5952
|
skillMounts: promptSkills.map((skill) => ({
|
|
5012
5953
|
source: skill.hostDir,
|
|
@@ -5014,9 +5955,9 @@ async function createBazilionSession(opts) {
|
|
|
5014
5955
|
})),
|
|
5015
5956
|
approvalHost: bashApprovalHost
|
|
5016
5957
|
});
|
|
5017
|
-
const sessionDir = restricted?.sessionDir ??
|
|
5958
|
+
const sessionDir = restricted?.sessionDir ?? join22(paths.agentDir(agent.agent.id), "sessions");
|
|
5018
5959
|
mkdirSync7(sessionDir, { recursive: true });
|
|
5019
|
-
const existing = restricted ? null :
|
|
5960
|
+
const existing = restricted ? null : explicitConversationFile(sessionDir, opts.conversation);
|
|
5020
5961
|
const sessionManager = existing ? SessionManager.open(existing, sessionDir, cwd) : SessionManager.create(cwd, sessionDir);
|
|
5021
5962
|
const settingsManager = SettingsManager.inMemory({
|
|
5022
5963
|
compaction: { enabled: false },
|
|
@@ -5031,9 +5972,14 @@ async function createBazilionSession(opts) {
|
|
|
5031
5972
|
skills: promptSkills,
|
|
5032
5973
|
sandboxMode: shellTools?.config.sandboxMode ?? "off"
|
|
5033
5974
|
});
|
|
5975
|
+
const repository = !restricted && opts.repositoryContext && opts.repositoryContextHost ? repositoryContextIntegration(
|
|
5976
|
+
opts.repositoryContext,
|
|
5977
|
+
opts.repositoryContextHost,
|
|
5978
|
+
shellTools?.config.sandboxMode === "docker" ? "/workspace" : cwd
|
|
5979
|
+
) : void 0;
|
|
5034
5980
|
const resourceLoader = new DefaultResourceLoader({
|
|
5035
5981
|
cwd,
|
|
5036
|
-
agentDir:
|
|
5982
|
+
agentDir: join22(paths.home, "pi"),
|
|
5037
5983
|
settingsManager,
|
|
5038
5984
|
noExtensions: true,
|
|
5039
5985
|
noSkills: true,
|
|
@@ -5042,6 +5988,7 @@ async function createBazilionSession(opts) {
|
|
|
5042
5988
|
noContextFiles: true,
|
|
5043
5989
|
appendSystemPrompt: bazilionPrompt ? [bazilionPrompt] : void 0
|
|
5044
5990
|
});
|
|
5991
|
+
if (repository) resourceLoader.getAgentsFiles = repository.files;
|
|
5045
5992
|
await resourceLoader.reload();
|
|
5046
5993
|
const bazilionTools = restricted ? restricted.tools : createBazilionCustomTools({
|
|
5047
5994
|
agent,
|
|
@@ -5052,13 +5999,31 @@ async function createBazilionSession(opts) {
|
|
|
5052
5999
|
mcpHost,
|
|
5053
6000
|
mcpTools,
|
|
5054
6001
|
fileSink,
|
|
6002
|
+
askUser,
|
|
6003
|
+
sessionId: sessionManager.getSessionId(),
|
|
5055
6004
|
env
|
|
5056
6005
|
});
|
|
6006
|
+
if (repository) bazilionTools.push(repository.tool);
|
|
6007
|
+
if (repository && opts.codingHost && shellTools && shellTools.config.sandboxMode === "docker" && !opts.preparedDocker)
|
|
6008
|
+
throw new Error("Coding commands require the admitted Docker runtime");
|
|
6009
|
+
if (repository && opts.codingHost && shellTools)
|
|
6010
|
+
bazilionTools.push(
|
|
6011
|
+
...codingTools({
|
|
6012
|
+
host: opts.codingHost,
|
|
6013
|
+
root: cwd,
|
|
6014
|
+
context: repository.resolve,
|
|
6015
|
+
docker: opts.preparedDocker,
|
|
6016
|
+
lifecycle: opts.dockerLifecycle,
|
|
6017
|
+
approval: shellTools.config.approvalMode === "dangerous",
|
|
6018
|
+
approvalHost: bashApprovalHost,
|
|
6019
|
+
secrets: Object.entries(env).filter(([key]) => /KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|AUTH/i.test(key)).map(([, value]) => value ?? "")
|
|
6020
|
+
})
|
|
6021
|
+
);
|
|
5057
6022
|
const customTools = shellTools?.customBash ? [...bazilionTools, shellTools.customBash] : bazilionTools;
|
|
5058
6023
|
const allowedTools = [...shellTools?.hostToolNames ?? [], ...customTools.map((t) => t.name)];
|
|
5059
6024
|
const { session } = await createAgentSession({
|
|
5060
6025
|
cwd,
|
|
5061
|
-
agentDir:
|
|
6026
|
+
agentDir: join22(paths.home, "pi"),
|
|
5062
6027
|
model,
|
|
5063
6028
|
thinkingLevel: toPiThinkingLevel(restricted?.reasoningLevel ?? agent.reasoningLevel),
|
|
5064
6029
|
tools: allowedTools,
|
|
@@ -5068,6 +6033,7 @@ async function createBazilionSession(opts) {
|
|
|
5068
6033
|
modelRuntime,
|
|
5069
6034
|
resourceLoader
|
|
5070
6035
|
});
|
|
6036
|
+
repository?.bind(session, allowedTools);
|
|
5071
6037
|
if (refreshApiKey) {
|
|
5072
6038
|
session.agent.getApiKey = async (requestedProvider) => {
|
|
5073
6039
|
if (requestedProvider !== piProviderName(providerName)) return void 0;
|
|
@@ -5112,10 +6078,12 @@ async function createProtectedBazilionSession(opts) {
|
|
|
5112
6078
|
const shellTools = createProtectedSessionShellTools(
|
|
5113
6079
|
opts.paths.teamDir,
|
|
5114
6080
|
opts.docker,
|
|
5115
|
-
opts.bashApprovalHost
|
|
6081
|
+
opts.bashApprovalHost,
|
|
6082
|
+
opts.dockerLifecycle
|
|
5116
6083
|
);
|
|
6084
|
+
const modelCwd = codingContainerCwd(opts.docker.coding?.cwd ?? ".");
|
|
5117
6085
|
mkdirSync7(opts.paths.sessionDir, { recursive: true });
|
|
5118
|
-
const existing =
|
|
6086
|
+
const existing = explicitConversationFile(opts.paths.sessionDir, opts.conversation);
|
|
5119
6087
|
const sessionManager = existing ? SessionManager.open(existing, opts.paths.sessionDir, opts.paths.teamDir) : SessionManager.create(opts.paths.teamDir, opts.paths.sessionDir);
|
|
5120
6088
|
const settingsManager = createInMemorySettingsManager();
|
|
5121
6089
|
const bazilionPrompt = redactJsonValue(
|
|
@@ -5126,8 +6094,13 @@ async function createProtectedBazilionSession(opts) {
|
|
|
5126
6094
|
}),
|
|
5127
6095
|
protectedRuntimeSecrets(opts.runtime)
|
|
5128
6096
|
);
|
|
6097
|
+
const repository = opts.repositoryContext && opts.repositoryContextHost ? repositoryContextIntegration(
|
|
6098
|
+
redactJsonValue(opts.repositoryContext, protectedRuntimeSecrets(opts.runtime)),
|
|
6099
|
+
opts.repositoryContextHost,
|
|
6100
|
+
PROTECTED_MODEL_CWD
|
|
6101
|
+
) : void 0;
|
|
5129
6102
|
const resourceLoader = new DefaultResourceLoader({
|
|
5130
|
-
cwd:
|
|
6103
|
+
cwd: modelCwd,
|
|
5131
6104
|
agentDir: opts.scratch.piAgentDir,
|
|
5132
6105
|
settingsManager,
|
|
5133
6106
|
noExtensions: true,
|
|
@@ -5138,18 +6111,35 @@ async function createProtectedBazilionSession(opts) {
|
|
|
5138
6111
|
systemPrompt: PROTECTED_BASE_SYSTEM_PROMPT,
|
|
5139
6112
|
appendSystemPrompt: bazilionPrompt ? [bazilionPrompt] : void 0
|
|
5140
6113
|
});
|
|
6114
|
+
if (repository) resourceLoader.getAgentsFiles = repository.files;
|
|
5141
6115
|
await resourceLoader.reload();
|
|
5142
6116
|
const bazilionTools = createProtectedBazilionCustomTools({
|
|
5143
6117
|
agent: opts.agent,
|
|
5144
6118
|
memory: opts.memory,
|
|
5145
6119
|
messagingHost: opts.messagingHost,
|
|
5146
6120
|
userMdHost: opts.userMdHost,
|
|
5147
|
-
fileSink: opts.fileSink
|
|
6121
|
+
fileSink: opts.fileSink,
|
|
6122
|
+
askUser: opts.askUser,
|
|
6123
|
+
sessionId: sessionManager.getSessionId()
|
|
5148
6124
|
});
|
|
6125
|
+
if (repository) bazilionTools.push(repository.tool);
|
|
6126
|
+
if (repository && opts.codingHost)
|
|
6127
|
+
bazilionTools.push(
|
|
6128
|
+
...codingTools({
|
|
6129
|
+
host: opts.codingHost,
|
|
6130
|
+
root: opts.paths.teamDir,
|
|
6131
|
+
context: repository.resolve,
|
|
6132
|
+
docker: opts.docker,
|
|
6133
|
+
lifecycle: opts.dockerLifecycle,
|
|
6134
|
+
approval: true,
|
|
6135
|
+
approvalHost: opts.bashApprovalHost,
|
|
6136
|
+
secrets: protectedRuntimeSecrets(opts.runtime)
|
|
6137
|
+
})
|
|
6138
|
+
);
|
|
5149
6139
|
if (!shellTools.customBash) throw new Error("protected Docker bash tool is unavailable");
|
|
5150
6140
|
const customTools = [...bazilionTools, shellTools.customBash];
|
|
5151
6141
|
const { session } = await createAgentSession({
|
|
5152
|
-
cwd:
|
|
6142
|
+
cwd: modelCwd,
|
|
5153
6143
|
agentDir: opts.scratch.piAgentDir,
|
|
5154
6144
|
model,
|
|
5155
6145
|
thinkingLevel: toPiThinkingLevel(opts.runtime.reasoningLevel),
|
|
@@ -5160,6 +6150,10 @@ async function createProtectedBazilionSession(opts) {
|
|
|
5160
6150
|
modelRuntime,
|
|
5161
6151
|
resourceLoader
|
|
5162
6152
|
});
|
|
6153
|
+
repository?.bind(
|
|
6154
|
+
session,
|
|
6155
|
+
customTools.map((tool) => tool.name)
|
|
6156
|
+
);
|
|
5163
6157
|
installProtectedCredentialBoundary(
|
|
5164
6158
|
session,
|
|
5165
6159
|
opts.runtime.providerName,
|
|
@@ -5222,7 +6216,7 @@ async function createRestrictedReviewSession(opts) {
|
|
|
5222
6216
|
function materializeProtectedCredentialFile(runtime, scratch) {
|
|
5223
6217
|
const fields = [...runtime.credentialEnv ?? []];
|
|
5224
6218
|
if (runtime.credentialFile) {
|
|
5225
|
-
const path =
|
|
6219
|
+
const path = join22(scratch.tempDir, "provider-credential.json");
|
|
5226
6220
|
writeFileSync9(path, runtime.credentialFile.content, {
|
|
5227
6221
|
encoding: "utf8",
|
|
5228
6222
|
mode: 384,
|
|
@@ -5357,19 +6351,12 @@ function toPiThinkingLevel(level) {
|
|
|
5357
6351
|
return "medium";
|
|
5358
6352
|
}
|
|
5359
6353
|
}
|
|
5360
|
-
function
|
|
5361
|
-
if (!
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
try {
|
|
5367
|
-
const s = statSync5(path);
|
|
5368
|
-
if (!newest || s.mtimeMs > newest.mtimeMs) newest = { path, mtimeMs: s.mtimeMs };
|
|
5369
|
-
} catch {
|
|
5370
|
-
}
|
|
5371
|
-
}
|
|
5372
|
-
return newest?.path ?? null;
|
|
6354
|
+
function explicitConversationFile(directory, target) {
|
|
6355
|
+
if (!target || !/^[0-9a-f-]{36}$/i.test(target.id) || target.filename !== `${target.id}.jsonl`)
|
|
6356
|
+
throw new Error("Explicit conversation target is required");
|
|
6357
|
+
const file = join22(directory, target.filename);
|
|
6358
|
+
readCanonicalSessionFile(directory, target.filename, target.id);
|
|
6359
|
+
return file;
|
|
5373
6360
|
}
|
|
5374
6361
|
|
|
5375
6362
|
// ../daemon/src/runtime/worker/api-key-refresh.ts
|
|
@@ -5488,6 +6475,21 @@ function createIpcMessagingHost(call) {
|
|
|
5488
6475
|
approvalStatus: (agentId, approvalId) => call("approvalStatus", { agentId, approvalId })
|
|
5489
6476
|
};
|
|
5490
6477
|
}
|
|
6478
|
+
function createIpcDockerLifecycle(namespace, call) {
|
|
6479
|
+
if (!namespace) return void 0;
|
|
6480
|
+
return {
|
|
6481
|
+
name: (kind) => `bazilion-${namespace}-${kind}-${randomUUID20()}`,
|
|
6482
|
+
beforeCreate: async (identity2) => {
|
|
6483
|
+
await call("containerBeforeCreate", { containerName: identity2.containerName });
|
|
6484
|
+
},
|
|
6485
|
+
afterCreate: async (containerName) => {
|
|
6486
|
+
await call("containerAfterCreate", { containerName });
|
|
6487
|
+
},
|
|
6488
|
+
afterRemove: async (containerName) => {
|
|
6489
|
+
await call("containerAfterRemove", { containerName });
|
|
6490
|
+
}
|
|
6491
|
+
};
|
|
6492
|
+
}
|
|
5491
6493
|
function createIpcUserMdHost(call) {
|
|
5492
6494
|
return {
|
|
5493
6495
|
get: (teamId) => call("userMdGet", { teamId }),
|
|
@@ -5533,13 +6535,13 @@ function createReviewState(evidenceItems) {
|
|
|
5533
6535
|
name: "propose_lesson",
|
|
5534
6536
|
label: "propose_lesson",
|
|
5535
6537
|
description: "Submit zero to five evidence-backed lesson proposals in one final batch. This is the only available action.",
|
|
5536
|
-
parameters:
|
|
5537
|
-
proposals:
|
|
5538
|
-
|
|
5539
|
-
scope:
|
|
5540
|
-
text:
|
|
5541
|
-
evidenceEntryIds:
|
|
5542
|
-
|
|
6538
|
+
parameters: Type5.Object({
|
|
6539
|
+
proposals: Type5.Array(
|
|
6540
|
+
Type5.Object({
|
|
6541
|
+
scope: Type5.Union([Type5.Literal("private"), Type5.Literal("shared")]),
|
|
6542
|
+
text: Type5.String({ minLength: 1, maxLength: 500 }),
|
|
6543
|
+
evidenceEntryIds: Type5.Array(
|
|
6544
|
+
Type5.Object({ sessionId: Type5.String(), entryOrdinal: Type5.Integer({ minimum: 0 }) }),
|
|
5543
6545
|
{ minItems: 1 }
|
|
5544
6546
|
)
|
|
5545
6547
|
}),
|
|
@@ -5551,14 +6553,14 @@ function createReviewState(evidenceItems) {
|
|
|
5551
6553
|
const input = params;
|
|
5552
6554
|
const validated = [];
|
|
5553
6555
|
for (const candidate of input.proposals) {
|
|
5554
|
-
const
|
|
5555
|
-
if (!
|
|
6556
|
+
const text2 = candidate.text.trim();
|
|
6557
|
+
if (!text2 || text2.length > 500) throw new Error("proposal text must be 1-500 characters");
|
|
5556
6558
|
for (const evidence of candidate.evidenceEntryIds) {
|
|
5557
6559
|
if (!allowedEvidence.has(`${evidence.sessionId}:${evidence.entryOrdinal}`)) {
|
|
5558
6560
|
throw new Error("proposal cites evidence outside the supplied digest");
|
|
5559
6561
|
}
|
|
5560
6562
|
}
|
|
5561
|
-
validated.push({ ...candidate, text });
|
|
6563
|
+
validated.push({ ...candidate, text: text2 });
|
|
5562
6564
|
}
|
|
5563
6565
|
proposalBatchSubmitted = true;
|
|
5564
6566
|
proposals.push(...validated);
|
|
@@ -5581,6 +6583,8 @@ function createReviewState(evidenceItems) {
|
|
|
5581
6583
|
};
|
|
5582
6584
|
}
|
|
5583
6585
|
async function createSessionForInput(input, ipcCall) {
|
|
6586
|
+
if (input.kind !== "restricted_review" && !input.repositoryContext)
|
|
6587
|
+
throw new Error("Coding turn requires daemon-prepared repository context");
|
|
5584
6588
|
if (input.kind === "configured_operator_http") {
|
|
5585
6589
|
const paths = resolvePaths();
|
|
5586
6590
|
const memory2 = qmdBackend(`${input.agent.team.path}/memory`);
|
|
@@ -5602,6 +6606,12 @@ async function createSessionForInput(input, ipcCall) {
|
|
|
5602
6606
|
}) : void 0;
|
|
5603
6607
|
const handle2 = await createBazilionSession({
|
|
5604
6608
|
agent: input.agent,
|
|
6609
|
+
preparedDocker: input.configuredDocker?.docker,
|
|
6610
|
+
dockerLifecycle: createIpcDockerLifecycle(input.containerNamespace, ipcCall),
|
|
6611
|
+
conversation: input.conversation,
|
|
6612
|
+
repositoryContext: input.repositoryContext,
|
|
6613
|
+
repositoryContextHost: (target) => ipcCall("repositoryContext", { target }),
|
|
6614
|
+
codingHost: { invoke: (request) => ipcCall("coding", request) },
|
|
5605
6615
|
paths,
|
|
5606
6616
|
env: process.env,
|
|
5607
6617
|
memory: memory2,
|
|
@@ -5614,7 +6624,15 @@ async function createSessionForInput(input, ipcCall) {
|
|
|
5614
6624
|
mcpHost,
|
|
5615
6625
|
mcpTools: input.mcpTools,
|
|
5616
6626
|
bashApprovalHost: bashApprovalHost2,
|
|
5617
|
-
|
|
6627
|
+
askUser: input.questionEnabled ? (toolCallId, question) => ipcCall("askUser", { toolCallId, question }) : void 0,
|
|
6628
|
+
fileSink: async (file, source) => {
|
|
6629
|
+
const result = await ipcCall(
|
|
6630
|
+
"publishResult",
|
|
6631
|
+
{ ...file, ...source }
|
|
6632
|
+
);
|
|
6633
|
+
emit({ kind: "event", event: { type: "file", ...file, result } });
|
|
6634
|
+
return result;
|
|
6635
|
+
}
|
|
5618
6636
|
});
|
|
5619
6637
|
return { handle: handle2 };
|
|
5620
6638
|
}
|
|
@@ -5646,16 +6664,29 @@ async function createSessionForInput(input, ipcCall) {
|
|
|
5646
6664
|
});
|
|
5647
6665
|
const handle = await createProtectedBazilionSession({
|
|
5648
6666
|
agent: input.agent,
|
|
6667
|
+
conversation: input.conversation,
|
|
6668
|
+
repositoryContext: input.repositoryContext,
|
|
6669
|
+
repositoryContextHost: (target) => ipcCall("repositoryContext", { target }),
|
|
6670
|
+
codingHost: { invoke: (request) => ipcCall("coding", request) },
|
|
5649
6671
|
runtime: input.runtime,
|
|
5650
6672
|
paths: input.paths,
|
|
5651
6673
|
scratch: input.scratch,
|
|
5652
6674
|
docker: input.docker,
|
|
6675
|
+
dockerLifecycle: createIpcDockerLifecycle(input.containerNamespace, ipcCall),
|
|
5653
6676
|
memory,
|
|
5654
6677
|
messagingHost,
|
|
5655
6678
|
userMdHost,
|
|
5656
6679
|
bashApprovalHost,
|
|
6680
|
+
askUser: input.questionEnabled ? (toolCallId, question) => ipcCall("askUser", { toolCallId, question }) : void 0,
|
|
5657
6681
|
refreshApiKey,
|
|
5658
|
-
fileSink: (file) =>
|
|
6682
|
+
fileSink: async (file, source) => {
|
|
6683
|
+
const result = await ipcCall("publishResult", {
|
|
6684
|
+
...file,
|
|
6685
|
+
...source
|
|
6686
|
+
});
|
|
6687
|
+
emit({ kind: "event", event: { type: "file", ...file, result } });
|
|
6688
|
+
return result;
|
|
6689
|
+
}
|
|
5659
6690
|
});
|
|
5660
6691
|
return { handle };
|
|
5661
6692
|
}
|
|
@@ -5684,7 +6715,30 @@ async function main() {
|
|
|
5684
6715
|
const ipcCall = createWorkerIpcCall();
|
|
5685
6716
|
const { handle, reviewState } = await createSessionForInput(input, ipcCall);
|
|
5686
6717
|
session = handle.session;
|
|
6718
|
+
const consumptionAcks = /* @__PURE__ */ new Set();
|
|
5687
6719
|
const unsubscribe = session.subscribe((piEvent) => {
|
|
6720
|
+
if (input.kind !== "restricted_review" && input.questionEnabled && piEvent.type === "message_end" && piEvent.message.role === "toolResult" && piEvent.message.toolName === "ask_user" && !piEvent.message.isError) {
|
|
6721
|
+
const message = piEvent.message;
|
|
6722
|
+
const content = message.content[0];
|
|
6723
|
+
if (content?.type === "text") {
|
|
6724
|
+
try {
|
|
6725
|
+
const result = JSON.parse(content.text);
|
|
6726
|
+
if (typeof result.questionId === "string") {
|
|
6727
|
+
const ack = new Promise((resolve6) => setImmediate(resolve6)).then(
|
|
6728
|
+
() => ipcCall("questionConsumed", {
|
|
6729
|
+
questionId: result.questionId,
|
|
6730
|
+
toolCallId: message.toolCallId
|
|
6731
|
+
})
|
|
6732
|
+
).catch(() => {
|
|
6733
|
+
}).finally(() => {
|
|
6734
|
+
consumptionAcks.delete(ack);
|
|
6735
|
+
});
|
|
6736
|
+
consumptionAcks.add(ack);
|
|
6737
|
+
}
|
|
6738
|
+
} catch {
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
}
|
|
5688
6742
|
if (reviewState && piEvent.type === "tool_execution_end" && piEvent.toolName === "propose_lesson" && piEvent.isError) {
|
|
5689
6743
|
reviewState.recordValidationFailure();
|
|
5690
6744
|
if (reviewState.validationFailures() >= 2) void session?.abort();
|
|
@@ -5704,6 +6758,7 @@ async function main() {
|
|
|
5704
6758
|
promptImages.length > 0 ? { images: promptImages } : void 0
|
|
5705
6759
|
);
|
|
5706
6760
|
await session.agent.waitForIdle();
|
|
6761
|
+
await Promise.all(consumptionAcks);
|
|
5707
6762
|
if (reviewState) {
|
|
5708
6763
|
if (reviewState.validationFailures() >= 2) {
|
|
5709
6764
|
throw new Error("review output remained invalid after one schema-repair attempt");
|