bazilion 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1257 -150
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +3049 -471
- package/dist/daemon.js.map +1 -1
- package/dist/migrations/0001_init.sql +20 -0
- package/dist/web/client/assets/AgentTabs-B3hq5a3e.js +1 -0
- package/dist/web/client/assets/ConfigPage-BCwfgyjD.js +1 -0
- package/dist/web/client/assets/FieldRow-avAFLgYW.js +1 -0
- package/dist/web/client/assets/Page-CwNuDhWz.js +1 -0
- package/dist/web/client/assets/ProductionTeamPolicyEditor-B6dbpuYy.js +7 -0
- package/dist/web/client/assets/{ProfileCommunicationEditor-D1OZ8WE5.js → ProfileCommunicationEditor-Bf8UvmZT.js} +1 -1
- package/dist/web/client/assets/SectionTabs-DZzy01az.js +1 -0
- package/dist/web/client/assets/TeamTabs-sQBHChtd.js +1 -0
- package/dist/web/client/assets/TemplatesTabs-DPsuwsWB.js +1 -0
- package/dist/web/client/assets/_id-C9rkCclz.js +1 -0
- package/dist/web/client/assets/_id-D65IzC8H.js +1 -0
- package/dist/web/client/assets/_id-DE4IY5bL.js +1 -0
- package/dist/web/client/assets/_id-DYASsGQ8.js +1 -0
- package/dist/web/client/assets/activity-C7o6-xHg.js +1 -0
- package/dist/web/client/assets/agents-UroO4d25.js +1 -0
- package/dist/web/client/assets/agents-h8yWXQ2H.js +1 -0
- package/dist/web/client/assets/approvals-DKhF0YLC.js +1 -0
- package/dist/web/client/assets/chevron-down-BK6NPMMP.js +1 -0
- package/dist/web/client/assets/config-q8JDresK.js +3 -0
- package/dist/web/client/assets/context-CBucAoi6.js +1 -0
- package/dist/web/client/assets/inbox-BwoRikj1.js +1 -0
- package/dist/web/client/assets/index-DUXAevpZ.js +12 -0
- package/dist/web/client/assets/login-CNAV9Wer.js +1 -0
- package/dist/web/client/assets/mcp-BKdEFwHO.js +1 -0
- package/dist/web/client/assets/members-BDqZQh26.js +1 -0
- package/dist/web/client/assets/memory-DVdEVr6L.js +1 -0
- package/dist/web/client/assets/plus-b4yFXP0O.js +1 -0
- package/dist/web/client/assets/policy-DdDctqvg.js +1 -0
- package/dist/web/client/assets/preload-helper-mNuBjNXU.js +1 -0
- package/dist/web/client/assets/react-3W8R1pOO.js +1 -0
- package/dist/web/client/assets/routes-BEfALSZv.js +1 -0
- package/dist/web/client/assets/services-DZP2ybzb.js +1 -0
- package/dist/web/client/assets/skills-DoeIjITs.js +1 -0
- package/dist/web/client/assets/styles-BYM0UGdF.css +2 -0
- package/dist/web/client/assets/team-policy-D0jpnn2b.js +1 -0
- package/dist/web/client/assets/teams-DlHRnrQw.js +1 -0
- package/dist/web/client/assets/teams-W3j-UoJ9.js +1 -0
- package/dist/web/client/assets/telegram-2OdeGv4E.js +1 -0
- package/dist/web/client/assets/tokens-MuBfmgue.js +1 -0
- package/dist/web/client/assets/triggers-CX7RMQFQ.js +1 -0
- package/dist/web/client/assets/welcome-CvNr5IMi.js +1 -0
- package/dist/web/client/assets/wire-constants-BhVKOCQa.js +74 -0
- package/dist/web/server/assets/{AgentTabs-BOjTN7QF.js → AgentTabs-DoMDJBXH.js} +10 -7
- package/dist/web/server/assets/{ChatPane-Dh3qxzdb.js → ChatPane-Ba5wTsVk.js} +340 -44
- package/dist/web/server/assets/ConfigPage-BKlh5vOU.js +68 -0
- package/dist/web/server/assets/{FieldRow-DwyqycWI.js → FieldRow-B4eTegH9.js} +5 -5
- package/dist/web/server/assets/Page-LENulSXY.js +111 -0
- package/dist/web/server/assets/{ProductionTeamPolicyEditor-Bke5doyl.js → ProductionTeamPolicyEditor-Y4mzFFvR.js} +4 -4
- package/dist/web/server/assets/SectionTabs-BJTPr5nU.js +8 -0
- package/dist/web/server/assets/{Sidebar-CGmtCnYU.js → Sidebar-DsRVCszF.js} +229 -118
- package/dist/web/server/assets/{TeamTabs-BPxDBfqy.js → TeamTabs-zZzbP1t_.js} +9 -8
- package/dist/web/server/assets/{TemplatesTabs-zf4YhStd.js → TemplatesTabs-CcRxZhJe.js} +5 -4
- package/dist/web/server/assets/{_id-fpFQ9_Ya.js → _id-BhOB8Umi.js} +1 -1
- package/dist/web/server/assets/{_id-CCHA0gdR.js → _id-C3HY4lWg.js} +7 -10
- package/dist/web/server/assets/{_id-B6g9wmPR.js → _id-Cg7x6q3g.js} +22 -28
- package/dist/web/server/assets/{_id-B04VaQlh.js → _id-CwyGjJeP.js} +2 -2
- package/dist/web/server/assets/{_id-_nFwi9gD.js → _id-D27T85mU.js} +1 -1
- package/dist/web/server/assets/{_id-5lLzLwcv.js → _id-D3Ap7zZA.js} +142 -144
- package/dist/web/server/assets/{_id-ButXHnRH.js → _id-DinTp_7X.js} +2 -2
- package/dist/web/server/assets/{_id-DdC9wpNN.js → _id-Dj1yZ3zJ.js} +2 -2
- package/dist/web/server/assets/{_id-DkFgCIb-.js → _id-DqON-59C.js} +2 -2
- package/dist/web/server/assets/{_id-CUWgTqMS.js → _id-IzadFZV9.js} +1 -1
- package/dist/web/server/assets/{_id-C4AOhstq.js → _id-LrvXU4Nr.js} +6 -5
- package/dist/web/server/assets/{_id-DJkzwHjM.js → _id-tB47Vsfp.js} +1 -1
- package/dist/web/server/assets/{_tanstack-start-manifest_v-Div-TukY.js → _tanstack-start-manifest_v-BXauUXg6.js} +96 -51
- package/dist/web/server/assets/{activity-C2ZKg1Q9.js → activity-BCI4mfNS.js} +28 -30
- package/dist/web/server/assets/{activity-DedraoFj.js → activity-D9SMqBhy.js} +2 -2
- package/dist/web/server/assets/{activity-GPx8NCeB.js → activity-DohitgwW.js} +1 -1
- package/dist/web/server/assets/{agents-BR2qXlXF.js → agents-BPKOshR5.js} +1 -1
- package/dist/web/server/assets/{agents-LHowEI6S.js → agents-BY74v7fM.js} +1 -1
- package/dist/web/server/assets/{agents-B8kinIPW.js → agents-BvttypqB.js} +2 -2
- package/dist/web/server/assets/{agents-Bp1nsVs2.js → agents-C9xlWK0Z.js} +2 -2
- package/dist/web/server/assets/agents-CgZMHj80.js +265 -0
- package/dist/web/server/assets/{agents-CUQmWhTp.js → agents-DZuFEj0D.js} +59 -77
- package/dist/web/server/assets/{approvals-VPc0YpLN.js → approvals-BGU-XGIO.js} +1 -1
- package/dist/web/server/assets/{approvals-Dw2LJoSA.js → approvals-CWcFHlor.js} +2 -2
- package/dist/web/server/assets/{approvals-DHgARzd8.js → approvals-DCaVw1Mz.js} +116 -78
- package/dist/web/server/assets/{auth-l-Vf8wlu.js → auth-byfSx6Ja.js} +1 -1
- package/dist/web/server/assets/{config-CHaCv89D.js → config-BDCJdtj2.js} +1 -1
- package/dist/web/server/assets/{config-BvKctDOX.js → config-D2LAsud2.js} +28 -49
- package/dist/web/server/assets/{config-QvHaxGlP.js → config-TeIoyYYc.js} +2 -2
- package/dist/web/server/assets/{context-5lrf2ame.js → context-B-XDf6Ym.js} +1 -1
- package/dist/web/server/assets/{context-D3Egm-vY.js → context-BXGo5Vfi.js} +2 -2
- package/dist/web/server/assets/context-DrkrIWun.js +47 -0
- package/dist/web/server/assets/{createServerRpc-CeKECg65.js → createServerRpc-pU-IQV2Y.js} +1 -1
- package/dist/web/server/assets/{createSsrRpc-ZBGxhl8t.js → createSsrRpc-DFsIAfFd.js} +1 -1
- package/dist/web/server/assets/{empty-plugin-adapters-DKUlnwwl.js → empty-plugin-adapters-Cq4--YW-.js} +1 -1
- package/dist/web/server/assets/{inbox-CkiSvD14.js → inbox-B3-TRsNI.js} +1 -1
- package/dist/web/server/assets/{inbox-Bxb3W04M.js → inbox-BardsGyy.js} +2 -2
- package/dist/web/server/assets/inbox-CUIyf9MT.js +131 -0
- package/dist/web/server/assets/login-Bx5LEyGP.js +118 -0
- package/dist/web/server/assets/login-CmWvxfTK.js +136 -0
- package/dist/web/server/assets/{mcp-BsFKEoXt.js → mcp-2pwT9HU8.js} +1 -1
- package/dist/web/server/assets/{mcp-BU_kKENi.js → mcp-BJjLIdP0.js} +105 -112
- package/dist/web/server/assets/{mcp-CKqwG4mt.js → mcp-DIWTogHM.js} +2 -2
- package/dist/web/server/assets/members-CCv17cjQ.js +46 -0
- package/dist/web/server/assets/{members-DlGBKZNl.js → members-CW5aBe81.js} +1 -1
- package/dist/web/server/assets/{members-ksELRsyk.js → members-VdPwD6CE.js} +2 -2
- package/dist/web/server/assets/{memory-CO0jKGzJ.js → memory-Dngc13Nw.js} +1 -1
- package/dist/web/server/assets/memory-a8Kz57Kh.js +287 -0
- package/dist/web/server/assets/{memory-B5moRnnp.js → memory-pZ4pZT7p.js} +2 -2
- package/dist/web/server/assets/{policy-Dxs78_GO.js → policy-Bb-vEgqg.js} +1 -1
- package/dist/web/server/assets/{policy-Dr55y-Mh.js → policy-BcGELcc9.js} +16 -22
- package/dist/web/server/assets/{policy-DNcGZLOB.js → policy-C30AeFIb.js} +2 -2
- package/dist/web/server/assets/{router-_izWQUeG.js → router-C5_NjTsP.js} +100 -111
- package/dist/web/server/assets/{routes-BSSKp3Rv.js → routes-B6cMLIPk.js} +16 -13
- package/dist/web/server/assets/{routes-BrTOoeth.js → routes-BrA-U_Hm.js} +2 -2
- package/dist/web/server/assets/{routes-DOj6ljcm.js → routes-C19CMLZt.js} +2 -2
- package/dist/web/server/assets/services-B9yBWDX0.js +79 -0
- package/dist/web/server/assets/{services-Pv_V88VA.js → services-C4szdhPx.js} +2 -2
- package/dist/web/server/assets/{services-DhRiNeq0.js → services-wG8I06oS.js} +1 -1
- package/dist/web/server/assets/skills-BZdqiDig.js +358 -0
- package/dist/web/server/assets/{skills-l2cyQ3zS.js → skills-C-UgkuCW.js} +2 -2
- package/dist/web/server/assets/{skills-CFIWVg32.js → skills-a9VSWHWq.js} +1 -1
- package/dist/web/server/assets/{start-DlHq0RaQ.js → start-WH8WzTL_.js} +1 -1
- package/dist/web/server/assets/{team-policy-Csvc5EDd.js → team-policy-vPG01onj.js} +3 -3
- package/dist/web/server/assets/{teams-B9O-WDH5.js → teams-CpQ0YgWf.js} +38 -34
- package/dist/web/server/assets/{teams-35Oprf6e.js → teams-DEpQR2QA.js} +2 -2
- package/dist/web/server/assets/{teams-DFkN7ic7.js → teams-DqfrEoH6.js} +1 -1
- package/dist/web/server/assets/{teams-B5IF65p2.js → teams-MjqgsmC3.js} +1 -1
- package/dist/web/server/assets/{teams-DH1CvWmT.js → teams-OIkHuxy1.js} +2 -2
- package/dist/web/server/assets/teams-jBtvnk59.js +193 -0
- package/dist/web/server/assets/{telegram-C9sQFwWj.js → telegram-CBF8SJzB.js} +36 -71
- package/dist/web/server/assets/{telegram-BdaUIjvs.js → telegram-DNgCWebD.js} +1 -1
- package/dist/web/server/assets/{telegram-mTnJCOmn.js → telegram-XwyKSEtj.js} +2 -2
- package/dist/web/server/assets/{tokens-DdpnfV_Y.js → tokens-BnwFbV7T.js} +2 -2
- package/dist/web/server/assets/{tokens-BCz3PSoT.js → tokens-DPDMSN5O.js} +1 -1
- package/dist/web/server/assets/tokens-yyTSfBSN.js +186 -0
- package/dist/web/server/assets/{triggers-BcTTwMfy.js → triggers-CBl4yMyw.js} +2 -2
- package/dist/web/server/assets/{triggers-BcH_6PW1.js → triggers-D79NoSXk.js} +4 -3
- package/dist/web/server/assets/{triggers-BzJG1nN5.js → triggers-_3rdhwSN.js} +31 -6
- package/dist/web/server/assets/{welcome-CDGE2Sdk.js → welcome-IYHGurWx.js} +21 -13
- package/dist/web/server/server.js +82 -73
- package/dist/worker.js +1614 -153
- package/dist/worker.js.map +1 -1
- package/package.json +5 -3
- package/dist/web/client/assets/AgentTabs-DioGqUVR.js +0 -1
- package/dist/web/client/assets/ConfigTabs-pY-V0Rie.js +0 -1
- package/dist/web/client/assets/FieldRow-DmwAFfbF.js +0 -1
- package/dist/web/client/assets/ProductionTeamPolicyEditor-1a5jQqUB.js +0 -7
- package/dist/web/client/assets/TeamTabs-BViZ4sti.js +0 -1
- package/dist/web/client/assets/TemplatesTabs-DVcY9pMD.js +0 -1
- package/dist/web/client/assets/_id-8P7brhGy.js +0 -1
- package/dist/web/client/assets/_id-C3o3MHq8.js +0 -1
- package/dist/web/client/assets/_id-DGRrSF1e.js +0 -1
- package/dist/web/client/assets/_id-KnJe-aW9.js +0 -1
- package/dist/web/client/assets/activity-WWvJSo4t.js +0 -1
- package/dist/web/client/assets/agents-C32T1voV.js +0 -1
- package/dist/web/client/assets/agents-nH4m4gZ1.js +0 -1
- package/dist/web/client/assets/approvals-l8jIZjqz.js +0 -1
- package/dist/web/client/assets/config-C7wvi1nR.js +0 -3
- package/dist/web/client/assets/context-BEGWE3RC.js +0 -1
- package/dist/web/client/assets/inbox--4aBaAgG.js +0 -1
- package/dist/web/client/assets/index-BCXoh1c8.js +0 -12
- package/dist/web/client/assets/login-Bk2wwIHQ.js +0 -1
- package/dist/web/client/assets/mcp-vM7xgh9e.js +0 -1
- package/dist/web/client/assets/members-cPKfnrSL.js +0 -1
- package/dist/web/client/assets/memory-BOcsaemD.js +0 -1
- package/dist/web/client/assets/policy-BGLCJYvS.js +0 -1
- package/dist/web/client/assets/preload-helper-eOOCz1Fu.js +0 -1
- package/dist/web/client/assets/routes-DnrP3dwv.js +0 -1
- package/dist/web/client/assets/services-C2_O3ifw.js +0 -1
- package/dist/web/client/assets/skills-B__fLZjx.js +0 -1
- package/dist/web/client/assets/styles-Dn24-ZvP.css +0 -2
- package/dist/web/client/assets/team-policy-BdX340m4.js +0 -1
- package/dist/web/client/assets/teams-DZMB4_iw.js +0 -1
- package/dist/web/client/assets/teams-rpaYKNIb.js +0 -1
- package/dist/web/client/assets/telegram-B1O4Ko0V.js +0 -1
- package/dist/web/client/assets/tokens-D7FH9VgA.js +0 -1
- package/dist/web/client/assets/triggers-3PLy2_Fo.js +0 -1
- package/dist/web/client/assets/welcome-B-QQobzq.js +0 -1
- package/dist/web/client/assets/wire-constants-BY9Q9Z7d.js +0 -74
- package/dist/web/server/assets/ConfigTabs-CyuxzesF.js +0 -41
- package/dist/web/server/assets/agents-DU9Hqs58.js +0 -249
- package/dist/web/server/assets/context-CEr4-xiA.js +0 -49
- package/dist/web/server/assets/inbox-DU27iHv_.js +0 -133
- package/dist/web/server/assets/login-CNznncgA.js +0 -58
- package/dist/web/server/assets/login-CQybKk7Y.js +0 -74
- package/dist/web/server/assets/members-40DxRdVj.js +0 -48
- package/dist/web/server/assets/memory-CiTl4cN9.js +0 -289
- package/dist/web/server/assets/services-CHe4ihJk.js +0 -94
- package/dist/web/server/assets/skills-D89ND2kR.js +0 -321
- package/dist/web/server/assets/teams-zcm9XaQU.js +0 -174
- package/dist/web/server/assets/tokens-CkKqrtk-.js +0 -193
package/dist/worker.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// ../daemon/src/runtime/worker/entry.ts
|
|
4
|
-
import { randomUUID as
|
|
5
|
-
import { join as
|
|
4
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
5
|
+
import { join as join19 } from "path";
|
|
6
6
|
|
|
7
7
|
// ../daemon/src/core/agent/delete.ts
|
|
8
8
|
import { randomUUID } from "crypto";
|
|
@@ -44,7 +44,7 @@ import { existsSync as existsSync5, readdirSync } from "fs";
|
|
|
44
44
|
import { join as join6 } from "path";
|
|
45
45
|
|
|
46
46
|
// ../daemon/src/core/db/client.ts
|
|
47
|
-
import { DatabaseSync } from "node:sqlite";
|
|
47
|
+
import { backup, DatabaseSync } from "node:sqlite";
|
|
48
48
|
|
|
49
49
|
// ../daemon/src/core/db/migrate.ts
|
|
50
50
|
import { readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
@@ -495,12 +495,49 @@ var SERVICES = [
|
|
|
495
495
|
}
|
|
496
496
|
]
|
|
497
497
|
},
|
|
498
|
+
{
|
|
499
|
+
id: "shell-security",
|
|
500
|
+
displayName: "Agent Shell Security",
|
|
501
|
+
category: "service",
|
|
502
|
+
team: "Agent Runtime",
|
|
503
|
+
hint: "Optional dangerous-command approval and Docker isolation. Disabled by default.",
|
|
504
|
+
fields: [
|
|
505
|
+
{
|
|
506
|
+
envVar: "BAZILION_BASH_APPROVAL",
|
|
507
|
+
kind: "config",
|
|
508
|
+
label: "Dangerous-command approval",
|
|
509
|
+
placeholder: "off",
|
|
510
|
+
description: "Use off (current behavior) or dangerous (classified bash commands require per-call approval before execution)."
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
envVar: "BAZILION_BASH_SANDBOX",
|
|
514
|
+
kind: "config",
|
|
515
|
+
label: "Sandbox mode",
|
|
516
|
+
placeholder: "off",
|
|
517
|
+
description: "Use off (current host tools) or docker (containerized bash only; host-backed coding tools are hidden)."
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
envVar: "BAZILION_BASH_SANDBOX_IMAGE",
|
|
521
|
+
kind: "config",
|
|
522
|
+
label: "Docker image",
|
|
523
|
+
placeholder: "debian:bookworm-slim",
|
|
524
|
+
description: "Local image with /bin/bash and /usr/bin/env and no VOLUME declarations. Bazilion never falls back to host execution if it is unavailable or incompatible."
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
envVar: "BAZILION_BASH_SANDBOX_ENV_ALLOWLIST",
|
|
528
|
+
kind: "config",
|
|
529
|
+
label: "Extra environment allowlist",
|
|
530
|
+
placeholder: "MY_SAFE_VARIABLE,ANOTHER_SAFE_VARIABLE",
|
|
531
|
+
description: "Optional comma-separated variable names copied into the container. Provider credentials remain excluded unless explicitly named."
|
|
532
|
+
}
|
|
533
|
+
]
|
|
534
|
+
},
|
|
498
535
|
{
|
|
499
536
|
id: "browser",
|
|
500
537
|
displayName: "Browser Automation",
|
|
501
538
|
category: "service",
|
|
502
539
|
team: "Browser",
|
|
503
|
-
hint: "Playwright-driven browser tools (navigate, snapshot, click, screenshot).
|
|
540
|
+
hint: "Playwright-driven browser tools (navigate, snapshot, click, screenshot). Chromium is installed with Bazilion.",
|
|
504
541
|
fields: [
|
|
505
542
|
{
|
|
506
543
|
envVar: "BROWSER_ENABLED",
|
|
@@ -605,11 +642,14 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
605
642
|
// ../daemon/src/core/repos/secrets.ts
|
|
606
643
|
import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
|
|
607
644
|
|
|
608
|
-
// ../daemon/src/core/repos/
|
|
645
|
+
// ../daemon/src/core/repos/triggerDispatches.ts
|
|
609
646
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
610
647
|
|
|
648
|
+
// ../daemon/src/core/repos/triggers.ts
|
|
649
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
650
|
+
|
|
611
651
|
// ../daemon/src/core/repos/webTokens.ts
|
|
612
|
-
import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as
|
|
652
|
+
import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID9 } from "crypto";
|
|
613
653
|
|
|
614
654
|
// ../daemon/src/core/secrets.ts
|
|
615
655
|
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
@@ -623,22 +663,52 @@ import AdmZip from "adm-zip";
|
|
|
623
663
|
// ../daemon/src/core/skills/parse.ts
|
|
624
664
|
import { readFileSync as readFileSync7 } from "fs";
|
|
625
665
|
import { parse as parseYaml } from "yaml";
|
|
666
|
+
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
667
|
+
function parseSkillContent(raw) {
|
|
668
|
+
const m = raw.match(FRONTMATTER_RE);
|
|
669
|
+
if (!m) {
|
|
670
|
+
throw new Error('SKILL.md missing YAML frontmatter (expected leading "---")');
|
|
671
|
+
}
|
|
672
|
+
const yamlBlock = m[1] ?? "";
|
|
673
|
+
const body = m[2] ?? "";
|
|
674
|
+
let fm;
|
|
675
|
+
try {
|
|
676
|
+
fm = parseYaml(yamlBlock);
|
|
677
|
+
} catch (err) {
|
|
678
|
+
throw new Error(`SKILL.md frontmatter is not valid YAML: ${err.message}`);
|
|
679
|
+
}
|
|
680
|
+
if (!fm || typeof fm !== "object" || Array.isArray(fm)) {
|
|
681
|
+
throw new Error("SKILL.md frontmatter must be a YAML object");
|
|
682
|
+
}
|
|
683
|
+
const fmObj = fm;
|
|
684
|
+
if (typeof fmObj.name !== "string" || fmObj.name.length === 0) {
|
|
685
|
+
throw new Error('SKILL.md frontmatter missing required "name"');
|
|
686
|
+
}
|
|
687
|
+
if (typeof fmObj.description !== "string" || fmObj.description.length === 0) {
|
|
688
|
+
throw new Error('SKILL.md frontmatter missing required "description"');
|
|
689
|
+
}
|
|
690
|
+
return { frontmatter: fmObj, body, raw };
|
|
691
|
+
}
|
|
692
|
+
function parseSkillFile(path) {
|
|
693
|
+
const raw = readFileSync7(path, "utf8");
|
|
694
|
+
return parseSkillContent(raw);
|
|
695
|
+
}
|
|
626
696
|
|
|
627
697
|
// ../daemon/src/core/team/delete.ts
|
|
628
|
-
import { randomUUID as
|
|
698
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
629
699
|
import { existsSync as existsSync9, renameSync as renameSync2, rmSync as rmSync6 } from "fs";
|
|
630
700
|
|
|
631
701
|
// ../daemon/src/core/team-policy/adopt.ts
|
|
632
|
-
import { randomUUID as
|
|
702
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
633
703
|
|
|
634
704
|
// ../daemon/src/core/team-policy/authorization.ts
|
|
635
|
-
import { createHash as createHash3, randomUUID as
|
|
705
|
+
import { createHash as createHash3, randomUUID as randomUUID12 } from "crypto";
|
|
636
706
|
|
|
637
707
|
// ../daemon/src/core/team-policy/source.ts
|
|
638
|
-
import { randomUUID as
|
|
708
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
639
709
|
|
|
640
710
|
// ../daemon/src/core/team-policy/spawn.ts
|
|
641
|
-
import { randomUUID as
|
|
711
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
642
712
|
import { readdirSync as readdirSync4, rmSync as rmSync8 } from "fs";
|
|
643
713
|
|
|
644
714
|
// ../daemon/src/core/team-policy/rm-with-retry.ts
|
|
@@ -646,15 +716,19 @@ import { rmSync as rmSync7 } from "fs";
|
|
|
646
716
|
|
|
647
717
|
// ../daemon/src/runtime/memory/qmd.ts
|
|
648
718
|
import {
|
|
649
|
-
|
|
719
|
+
closeSync,
|
|
720
|
+
constants,
|
|
721
|
+
fstatSync,
|
|
722
|
+
lstatSync,
|
|
650
723
|
mkdirSync as mkdirSync4,
|
|
724
|
+
openSync,
|
|
651
725
|
readdirSync as readdirSync5,
|
|
652
726
|
readFileSync as readFileSync8,
|
|
727
|
+
realpathSync,
|
|
653
728
|
rmSync as rmSync9,
|
|
654
|
-
statSync as statSync3,
|
|
655
729
|
writeFileSync as writeFileSync6
|
|
656
730
|
} from "fs";
|
|
657
|
-
import { dirname as dirname2, join as join12 } from "path";
|
|
731
|
+
import { dirname as dirname2, isAbsolute, join as join12, relative, resolve as resolve3, sep as sep2 } from "path";
|
|
658
732
|
import { createStore, extractSnippet } from "@tobilu/qmd";
|
|
659
733
|
var INDEX_FILENAME = ".qmd-index.sqlite";
|
|
660
734
|
var COLLECTION_NAME = "memory";
|
|
@@ -675,61 +749,222 @@ function getStore(dir) {
|
|
|
675
749
|
}
|
|
676
750
|
return p;
|
|
677
751
|
}
|
|
752
|
+
function unsafeMemoryPath(path, reason) {
|
|
753
|
+
return new Error(`unsafe memory path "${path}": ${reason}`);
|
|
754
|
+
}
|
|
755
|
+
function lstatIfPresent(path) {
|
|
756
|
+
try {
|
|
757
|
+
return lstatSync(path);
|
|
758
|
+
} catch (error) {
|
|
759
|
+
if (error.code === "ENOENT") return void 0;
|
|
760
|
+
throw error;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function isContained(root, candidate) {
|
|
764
|
+
const rel = relative(root, candidate);
|
|
765
|
+
return rel === "" || !isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep2}`);
|
|
766
|
+
}
|
|
767
|
+
function assertContained(root, candidate) {
|
|
768
|
+
if (!isContained(root, candidate)) {
|
|
769
|
+
throw unsafeMemoryPath(candidate, `resolved outside memory root "${root}"`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function initializeSafeRoot(root) {
|
|
773
|
+
mkdirSync4(root, { recursive: true });
|
|
774
|
+
const info = lstatSync(root);
|
|
775
|
+
if (info.isSymbolicLink()) {
|
|
776
|
+
throw unsafeMemoryPath(root, "memory root must not be a symbolic link");
|
|
777
|
+
}
|
|
778
|
+
if (!info.isDirectory()) {
|
|
779
|
+
throw unsafeMemoryPath(root, "memory root is not a directory");
|
|
780
|
+
}
|
|
781
|
+
return realpathSync(root);
|
|
782
|
+
}
|
|
783
|
+
function assertRootUnchanged(root, canonicalRoot) {
|
|
784
|
+
const info = lstatIfPresent(root);
|
|
785
|
+
if (!info) throw unsafeMemoryPath(root, "memory root disappeared");
|
|
786
|
+
if (info.isSymbolicLink()) {
|
|
787
|
+
throw unsafeMemoryPath(root, "memory root must not be a symbolic link");
|
|
788
|
+
}
|
|
789
|
+
if (!info.isDirectory()) {
|
|
790
|
+
throw unsafeMemoryPath(root, "memory root is not a directory");
|
|
791
|
+
}
|
|
792
|
+
const current = realpathSync(root);
|
|
793
|
+
if (relative(canonicalRoot, current) !== "" || relative(current, canonicalRoot) !== "") {
|
|
794
|
+
throw unsafeMemoryPath(root, "memory root changed after initialization");
|
|
795
|
+
}
|
|
796
|
+
}
|
|
678
797
|
function safeKey(root, key) {
|
|
679
|
-
|
|
798
|
+
const segments = key.split(/[\\/]/);
|
|
799
|
+
if (key.length === 0 || key.includes("\0") || isAbsolute(key) || /^[A-Za-z]:[\\/]/.test(key) || key.startsWith("\\\\") || segments.includes("..")) {
|
|
680
800
|
throw new Error(`unsafe memory key: ${key}`);
|
|
681
801
|
}
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
802
|
+
const candidate = resolve3(root, key);
|
|
803
|
+
if (candidate === root) throw new Error(`unsafe memory key: ${key}`);
|
|
804
|
+
assertContained(root, candidate);
|
|
805
|
+
return candidate;
|
|
806
|
+
}
|
|
807
|
+
function inspectSafePath(root, path) {
|
|
808
|
+
assertContained(root, path);
|
|
809
|
+
const rel = relative(root, path);
|
|
810
|
+
let current = root;
|
|
811
|
+
const components = rel.split(sep2).filter(Boolean);
|
|
812
|
+
for (let index = 0; index < components.length; index += 1) {
|
|
813
|
+
current = join12(current, components[index]);
|
|
814
|
+
const info = lstatIfPresent(current);
|
|
815
|
+
if (!info) return void 0;
|
|
816
|
+
if (info.isSymbolicLink()) {
|
|
817
|
+
throw unsafeMemoryPath(current, "symbolic links are not allowed");
|
|
818
|
+
}
|
|
819
|
+
if (index < components.length - 1 && !info.isDirectory()) {
|
|
820
|
+
throw unsafeMemoryPath(current, "path ancestor is not a directory");
|
|
821
|
+
}
|
|
822
|
+
assertContained(root, realpathSync(current));
|
|
823
|
+
}
|
|
824
|
+
return lstatIfPresent(path);
|
|
825
|
+
}
|
|
826
|
+
function ensureSafeParentDirectories(root, path) {
|
|
827
|
+
const parent = dirname2(path);
|
|
828
|
+
assertContained(root, parent);
|
|
829
|
+
const rel = relative(root, parent);
|
|
830
|
+
let current = root;
|
|
831
|
+
for (const component of rel.split(sep2).filter(Boolean)) {
|
|
832
|
+
current = join12(current, component);
|
|
833
|
+
let info = lstatIfPresent(current);
|
|
834
|
+
if (!info) {
|
|
835
|
+
mkdirSync4(current);
|
|
836
|
+
info = lstatSync(current);
|
|
837
|
+
}
|
|
838
|
+
if (info.isSymbolicLink()) {
|
|
839
|
+
throw unsafeMemoryPath(current, "symbolic-link ancestors are not allowed");
|
|
840
|
+
}
|
|
841
|
+
if (!info.isDirectory()) {
|
|
842
|
+
throw unsafeMemoryPath(current, "path ancestor is not a directory");
|
|
843
|
+
}
|
|
844
|
+
assertContained(root, realpathSync(current));
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
function readSafeFile(root, path) {
|
|
848
|
+
const info = inspectSafePath(root, path);
|
|
849
|
+
if (!info) throw new Error(`memory entry not found: ${relative(root, path)}`);
|
|
850
|
+
if (!info.isFile()) throw unsafeMemoryPath(path, "memory entry is not a regular file");
|
|
851
|
+
let fd;
|
|
852
|
+
try {
|
|
853
|
+
fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
854
|
+
const stats = fstatSync(fd);
|
|
855
|
+
if (!stats.isFile()) throw unsafeMemoryPath(path, "memory entry is not a regular file");
|
|
856
|
+
return { content: readFileSync8(fd, "utf8"), stats };
|
|
857
|
+
} catch (error) {
|
|
858
|
+
if (error.code === "ELOOP") {
|
|
859
|
+
throw unsafeMemoryPath(path, "symbolic links are not allowed");
|
|
860
|
+
}
|
|
861
|
+
throw error;
|
|
862
|
+
} finally {
|
|
863
|
+
if (fd !== void 0) closeSync(fd);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function writeSafeFile(root, path, content) {
|
|
867
|
+
ensureSafeParentDirectories(root, path);
|
|
868
|
+
const existing = inspectSafePath(root, path);
|
|
869
|
+
if (existing?.isSymbolicLink()) {
|
|
870
|
+
throw unsafeMemoryPath(path, "symbolic links are not allowed");
|
|
871
|
+
}
|
|
872
|
+
if (existing && !existing.isFile()) {
|
|
873
|
+
throw unsafeMemoryPath(path, "memory entry is not a regular file");
|
|
874
|
+
}
|
|
875
|
+
let fd;
|
|
876
|
+
try {
|
|
877
|
+
fd = openSync(
|
|
878
|
+
path,
|
|
879
|
+
constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW,
|
|
880
|
+
438
|
|
881
|
+
);
|
|
882
|
+
const stats = fstatSync(fd);
|
|
883
|
+
if (!stats.isFile()) throw unsafeMemoryPath(path, "memory entry is not a regular file");
|
|
884
|
+
writeFileSync6(fd, content);
|
|
885
|
+
return fstatSync(fd);
|
|
886
|
+
} catch (error) {
|
|
887
|
+
if (error.code === "ELOOP") {
|
|
888
|
+
throw unsafeMemoryPath(path, "symbolic links are not allowed");
|
|
889
|
+
}
|
|
890
|
+
throw error;
|
|
891
|
+
} finally {
|
|
892
|
+
if (fd !== void 0) closeSync(fd);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
function assertSafeTree(root, dir = root) {
|
|
896
|
+
assertContained(root, realpathSync(dir));
|
|
897
|
+
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
898
|
+
const full = join12(dir, entry.name);
|
|
899
|
+
const info = lstatSync(full);
|
|
900
|
+
if (info.isSymbolicLink()) {
|
|
901
|
+
throw unsafeMemoryPath(full, "symbolic links are not allowed in memory storage");
|
|
902
|
+
}
|
|
903
|
+
if (info.isDirectory()) {
|
|
904
|
+
assertSafeTree(root, full);
|
|
905
|
+
} else if (!info.isFile()) {
|
|
906
|
+
throw unsafeMemoryPath(full, "special files are not allowed in memory storage");
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
function walkMd(root, dir, prefix, out) {
|
|
911
|
+
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
912
|
+
const full = join12(dir, entry.name);
|
|
913
|
+
const info = lstatSync(full);
|
|
914
|
+
if (info.isSymbolicLink()) {
|
|
915
|
+
throw unsafeMemoryPath(full, "symbolic links are not allowed in memory storage");
|
|
916
|
+
}
|
|
917
|
+
if (entry.name.startsWith(".")) continue;
|
|
918
|
+
const key = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
919
|
+
if (info.isDirectory()) {
|
|
920
|
+
walkMd(root, full, key, out);
|
|
921
|
+
} else if (info.isFile() && entry.name.endsWith(".md")) {
|
|
922
|
+
const { content, stats } = readSafeFile(root, full);
|
|
923
|
+
out.push({ key, content, updatedAt: stats.mtimeMs });
|
|
924
|
+
} else if (!info.isFile()) {
|
|
925
|
+
throw unsafeMemoryPath(full, "special files are not allowed in memory storage");
|
|
699
926
|
}
|
|
700
927
|
}
|
|
701
928
|
}
|
|
702
929
|
function qmdBackend(root) {
|
|
930
|
+
let canonicalRoot;
|
|
931
|
+
const safeRoot = () => {
|
|
932
|
+
if (!canonicalRoot) canonicalRoot = initializeSafeRoot(root);
|
|
933
|
+
else assertRootUnchanged(root, canonicalRoot);
|
|
934
|
+
return canonicalRoot;
|
|
935
|
+
};
|
|
703
936
|
return {
|
|
704
937
|
async init() {
|
|
705
|
-
|
|
706
|
-
|
|
938
|
+
const safe = safeRoot();
|
|
939
|
+
assertSafeTree(safe);
|
|
940
|
+
const store = await getStore(safe);
|
|
707
941
|
await store.update();
|
|
708
942
|
},
|
|
709
943
|
async read(key) {
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
const stats = statSync3(path);
|
|
944
|
+
const safe = safeRoot();
|
|
945
|
+
const path = safeKey(safe, key);
|
|
946
|
+
const { content, stats } = readSafeFile(safe, path);
|
|
715
947
|
return {
|
|
716
948
|
key,
|
|
717
|
-
content
|
|
949
|
+
content,
|
|
718
950
|
updatedAt: stats.mtimeMs
|
|
719
951
|
};
|
|
720
952
|
},
|
|
721
953
|
async write(key, content) {
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const stats =
|
|
726
|
-
|
|
954
|
+
const safe = safeRoot();
|
|
955
|
+
assertSafeTree(safe);
|
|
956
|
+
const path = safeKey(safe, key);
|
|
957
|
+
const stats = writeSafeFile(safe, path, content);
|
|
958
|
+
assertSafeTree(safe);
|
|
959
|
+
const store = await getStore(safe);
|
|
727
960
|
await store.update();
|
|
728
961
|
return { key, content, updatedAt: stats.mtimeMs };
|
|
729
962
|
},
|
|
730
963
|
async search(query, opts) {
|
|
731
964
|
const limit = opts?.limit ?? 10;
|
|
732
|
-
const
|
|
965
|
+
const safe = safeRoot();
|
|
966
|
+
assertSafeTree(safe);
|
|
967
|
+
const store = await getStore(safe);
|
|
733
968
|
const results = await store.searchLex(query, {
|
|
734
969
|
limit,
|
|
735
970
|
collection: COLLECTION_NAME
|
|
@@ -738,13 +973,13 @@ function qmdBackend(root) {
|
|
|
738
973
|
for (const r of results) {
|
|
739
974
|
const prefix = `${COLLECTION_NAME}/`;
|
|
740
975
|
const key = r.displayPath.startsWith(prefix) ? r.displayPath.slice(prefix.length) : r.displayPath;
|
|
976
|
+
const path = safeKey(safe, key);
|
|
977
|
+
const info = inspectSafePath(safe, path);
|
|
978
|
+
if (!info) continue;
|
|
979
|
+
if (!info.isFile()) throw unsafeMemoryPath(path, "search result is not a regular file");
|
|
741
980
|
let content = r.body ?? "";
|
|
742
981
|
if (!content) {
|
|
743
|
-
|
|
744
|
-
content = readFileSync8(join12(root, key), "utf8");
|
|
745
|
-
} catch {
|
|
746
|
-
content = "";
|
|
747
|
-
}
|
|
982
|
+
content = readSafeFile(safe, path).content;
|
|
748
983
|
}
|
|
749
984
|
const snippet = extractSnippet(content, query).snippet;
|
|
750
985
|
hits.push({ key, snippet, score: r.score });
|
|
@@ -752,14 +987,23 @@ function qmdBackend(root) {
|
|
|
752
987
|
return hits;
|
|
753
988
|
},
|
|
754
989
|
async list() {
|
|
990
|
+
const safe = safeRoot();
|
|
991
|
+
assertSafeTree(safe);
|
|
755
992
|
const out = [];
|
|
756
|
-
walkMd(
|
|
993
|
+
walkMd(safe, safe, "", out);
|
|
757
994
|
return out.sort((a, b) => a.key.localeCompare(b.key));
|
|
758
995
|
},
|
|
759
996
|
async remove(key) {
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
const
|
|
997
|
+
const safe = safeRoot();
|
|
998
|
+
assertSafeTree(safe);
|
|
999
|
+
const path = safeKey(safe, key);
|
|
1000
|
+
const info = inspectSafePath(safe, path);
|
|
1001
|
+
if (info) {
|
|
1002
|
+
if (!info.isFile()) throw unsafeMemoryPath(path, "memory entry is not a regular file");
|
|
1003
|
+
rmSync9(path);
|
|
1004
|
+
}
|
|
1005
|
+
assertSafeTree(safe);
|
|
1006
|
+
const store = await getStore(safe);
|
|
763
1007
|
await store.update();
|
|
764
1008
|
}
|
|
765
1009
|
};
|
|
@@ -915,8 +1159,8 @@ function piMessagesToProviderView(messages) {
|
|
|
915
1159
|
}
|
|
916
1160
|
|
|
917
1161
|
// ../daemon/src/runtime/pi/session.ts
|
|
918
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as
|
|
919
|
-
import { basename as basename3, join as
|
|
1162
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync8, statSync as statSync5 } from "fs";
|
|
1163
|
+
import { basename as basename3, join as join18 } from "path";
|
|
920
1164
|
import {
|
|
921
1165
|
AuthStorage,
|
|
922
1166
|
createAgentSession,
|
|
@@ -976,21 +1220,38 @@ function resolveModel(cfg, modelId) {
|
|
|
976
1220
|
import { loginOpenAICodex, refreshOpenAICodexToken } from "@earendil-works/pi-ai/oauth";
|
|
977
1221
|
|
|
978
1222
|
// ../daemon/src/runtime/session/prompt.ts
|
|
979
|
-
import { existsSync as
|
|
1223
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
|
|
980
1224
|
import { join as join13 } from "path";
|
|
981
|
-
var
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1225
|
+
var SANDBOX_SKILLS_DIR = "/skills";
|
|
1226
|
+
function loadPromptSkills(skillsDir, attachedNames) {
|
|
1227
|
+
if (!existsSync10(skillsDir) || attachedNames.length === 0) return [];
|
|
1228
|
+
const attached = new Set(attachedNames);
|
|
1229
|
+
const discovered = readdirSync6(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && attached.has(entry.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
1230
|
+
const loaded = [];
|
|
1231
|
+
for (const entry of discovered) {
|
|
1232
|
+
const hostDir = join13(skillsDir, entry.name);
|
|
1233
|
+
try {
|
|
1234
|
+
const parsed = parseSkillFile(join13(hostDir, "SKILL.md"));
|
|
1235
|
+
const safeLabel = entry.name.replace(/[^A-Za-z0-9._-]/g, "_") || "skill";
|
|
1236
|
+
loaded.push({
|
|
1237
|
+
name: entry.name,
|
|
1238
|
+
description: parsed.frontmatter.description,
|
|
1239
|
+
body: parsed.body.trim(),
|
|
1240
|
+
hostDir,
|
|
1241
|
+
sandboxDir: `${SANDBOX_SKILLS_DIR}/${loaded.length}-${safeLabel}`
|
|
1242
|
+
});
|
|
1243
|
+
} catch {
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
return loaded;
|
|
1247
|
+
}
|
|
1248
|
+
var CONTEXT_FILE_ORDER = ["AGENTS.md", "SOUL.md", "TOOLS.md", "IDENTITY.md"];
|
|
1249
|
+
function buildSystemPrompt(agent, options = {}) {
|
|
989
1250
|
const parts = [];
|
|
990
1251
|
const contextBlocks = [];
|
|
991
1252
|
for (const file of CONTEXT_FILE_ORDER) {
|
|
992
1253
|
const path = join13(agent.agent.dir, file);
|
|
993
|
-
if (!
|
|
1254
|
+
if (!existsSync10(path)) continue;
|
|
994
1255
|
const content = readFileSync9(path, "utf8").trimEnd();
|
|
995
1256
|
if (!content) continue;
|
|
996
1257
|
contextBlocks.push(`## ${file}
|
|
@@ -1003,7 +1264,7 @@ ${content}`);
|
|
|
1003
1264
|
${contextBlocks.join("\n\n")}`);
|
|
1004
1265
|
}
|
|
1005
1266
|
const bootstrapPath = join13(agent.agent.dir, "BOOTSTRAP.md");
|
|
1006
|
-
if (
|
|
1267
|
+
if (existsSync10(bootstrapPath)) {
|
|
1007
1268
|
const bootstrap = readFileSync9(bootstrapPath, "utf8").trimEnd();
|
|
1008
1269
|
if (bootstrap) {
|
|
1009
1270
|
parts.push(
|
|
@@ -1028,19 +1289,40 @@ ${contextBlocks.join("\n\n")}`);
|
|
|
1028
1289
|
[
|
|
1029
1290
|
"# Agent Home",
|
|
1030
1291
|
"",
|
|
1031
|
-
"Your private home holds who you are \u2014 identity, soul, behaviour rules
|
|
1292
|
+
"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.",
|
|
1032
1293
|
"",
|
|
1033
1294
|
"- To change who you are (name, vibe, personality, how you behave): use `home_write`.",
|
|
1034
1295
|
"- To inspect exact wording of your own files: use `home_read` or `home_list`.",
|
|
1035
1296
|
"- To remember facts the user told you or things you learned: use `memory_write` \u2014 NOT `home_write`.",
|
|
1036
|
-
"- To produce work output (code, docs, artefacts): use
|
|
1297
|
+
"- 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."
|
|
1037
1298
|
].join("\n")
|
|
1038
1299
|
);
|
|
1039
1300
|
if (agent.skills.length > 0) {
|
|
1301
|
+
const loaded = options.skills ?? [];
|
|
1302
|
+
const loadedNames = new Set(loaded.map((skill) => skill.name));
|
|
1303
|
+
const unavailable = agent.skills.filter((name) => !loadedNames.has(name));
|
|
1304
|
+
const skillBlocks = loaded.map((skill) => {
|
|
1305
|
+
const runtimeDir = options.sandboxMode === "docker" ? skill.sandboxDir : skill.hostDir;
|
|
1306
|
+
return [
|
|
1307
|
+
`## ${skill.name}`,
|
|
1308
|
+
"",
|
|
1309
|
+
skill.description,
|
|
1310
|
+
"",
|
|
1311
|
+
`Runtime directory: \`${runtimeDir}\`. Resolve any relative scripts or assets mentioned by this skill from that directory.`,
|
|
1312
|
+
"",
|
|
1313
|
+
skill.body
|
|
1314
|
+
].filter((line, index, lines) => line !== "" || lines[index - 1] !== "").join("\n").trimEnd();
|
|
1315
|
+
});
|
|
1316
|
+
const unavailableNote = unavailable.length > 0 ? `Attached but unavailable or invalid: ${unavailable.join(", ")}.` : "";
|
|
1040
1317
|
parts.push(
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1318
|
+
[
|
|
1319
|
+
"# Available Skills",
|
|
1320
|
+
"",
|
|
1321
|
+
"Use these attached skill instructions when they are relevant to the task.",
|
|
1322
|
+
"",
|
|
1323
|
+
...skillBlocks,
|
|
1324
|
+
...unavailableNote ? ["", unavailableNote] : []
|
|
1325
|
+
].join("\n")
|
|
1044
1326
|
);
|
|
1045
1327
|
}
|
|
1046
1328
|
const teamLines = [
|
|
@@ -1048,7 +1330,7 @@ You have access to the following skills: ${agent.skills.join(", ")}.`
|
|
|
1048
1330
|
"",
|
|
1049
1331
|
`- ${agent.team.id} (${agent.team.name}): ${agent.team.path}`,
|
|
1050
1332
|
"",
|
|
1051
|
-
"Your team is where work product lives \u2014 code, docs, artefacts, shared scratch. It may be shared with other agents in the same team.
|
|
1333
|
+
"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`."
|
|
1052
1334
|
];
|
|
1053
1335
|
parts.push(teamLines.join("\n"));
|
|
1054
1336
|
if (agent.team.userMd.trim()) {
|
|
@@ -1072,12 +1354,1040 @@ This team's USER.md is empty. As you learn STABLE facts about the human (prefere
|
|
|
1072
1354
|
return parts.join("\n\n---\n\n");
|
|
1073
1355
|
}
|
|
1074
1356
|
|
|
1357
|
+
// ../daemon/src/runtime/shell/tooling.ts
|
|
1358
|
+
import { existsSync as existsSync11, lstatSync as lstatSync2, realpathSync as realpathSync2 } from "fs";
|
|
1359
|
+
import { dirname as dirname3, isAbsolute as isAbsolute3, join as join15, relative as relative3, sep as sep4 } from "path";
|
|
1360
|
+
import {
|
|
1361
|
+
createBashToolDefinition,
|
|
1362
|
+
createLocalBashOperations,
|
|
1363
|
+
defineTool as defineTool2
|
|
1364
|
+
} from "@earendil-works/pi-coding-agent";
|
|
1365
|
+
|
|
1366
|
+
// ../daemon/src/runtime/shell/approval.ts
|
|
1367
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
1368
|
+
|
|
1369
|
+
// ../daemon/src/runtime/shell/security.ts
|
|
1370
|
+
var DEFAULT_BASH_SANDBOX_IMAGE = "debian:bookworm-slim";
|
|
1371
|
+
var SAFE_SHELL_ENV_KEYS = [
|
|
1372
|
+
"PATH",
|
|
1373
|
+
"HOME",
|
|
1374
|
+
"LANG",
|
|
1375
|
+
"LANGUAGE",
|
|
1376
|
+
"LC_ALL",
|
|
1377
|
+
"LC_CTYPE",
|
|
1378
|
+
"TERM",
|
|
1379
|
+
"SHELL",
|
|
1380
|
+
"TMPDIR",
|
|
1381
|
+
"TZ"
|
|
1382
|
+
];
|
|
1383
|
+
var ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1384
|
+
var RESERVED_OBJECT_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
1385
|
+
var DOCKER_IMAGE_RE = /^[A-Za-z0-9][A-Za-z0-9._/:@-]*$/;
|
|
1386
|
+
var DOCKER_CLIENT_CONTROL_ENV = /* @__PURE__ */ new Set([
|
|
1387
|
+
"DOCKER_HOST",
|
|
1388
|
+
"DOCKER_CONTEXT",
|
|
1389
|
+
"DOCKER_CONFIG",
|
|
1390
|
+
"DOCKER_CERT_PATH",
|
|
1391
|
+
"DOCKER_TLS_VERIFY",
|
|
1392
|
+
"DOCKER_API_VERSION",
|
|
1393
|
+
"XDG_CONFIG_HOME",
|
|
1394
|
+
"XDG_RUNTIME_DIR"
|
|
1395
|
+
]);
|
|
1396
|
+
var SANDBOX_CONTROL_ENV = /* @__PURE__ */ new Set(["BASH_ENV"]);
|
|
1397
|
+
var SENSITIVE_HOME_PATH_RE = /(?:~(?:[A-Za-z0-9_-]+)?|\$(?:HOME|\{HOME\})|\/(?:home|Users)\/[^\s'"`;|&/]+|\/root)\/\.(?:ssh|aws|gnupg)(?:\/[^\s'"`;|&]*)?/gi;
|
|
1398
|
+
var SENSITIVE_FILE_RE = /(?:\bauth\.json\b|\bbazilion\.db\b|(?:^|[\s/'"=])\.env(?:\.[A-Za-z0-9_-]+)?(?=$|[\s/'"`;|&])|\/etc\/(?:shadow|sudoers)(?:\.d\/[^\s'"`;|&]+)?)/gim;
|
|
1399
|
+
var SENSITIVE_ENV_RE = /\b(?:OPENAI_CODEX_OAUTH|AWS_ACCESS_KEY_ID|AWS_PROFILE|GOOGLE_APPLICATION_CREDENTIALS|BAZILION_TOKEN|(?:[A-Z][A-Z0-9_]*_)?(?:API_KEY|ACCESS_TOKEN|AUTH_TOKEN|OAUTH_TOKEN|CLIENT_SECRET|SECRET_ACCESS_KEY|PRIVATE_KEY))\b/g;
|
|
1400
|
+
var REMOTE_PIPE_RE = /(?:^|[;&|(\n])\s*(?:command\s+)?(?:curl|wget)\b[^;&\n]*(?:\|\s*(?:(?:sudo|env)\s+)*(?:[^\s;&|()]+\/)?(?:sh|bash|dash|zsh|fish|node|python(?:3)?|perl|ruby)\b)/gim;
|
|
1401
|
+
var COMMAND_BOUNDARY_RE = /[;&|)\n]/;
|
|
1402
|
+
var ShellSecurityConfigError = class extends Error {
|
|
1403
|
+
constructor(message) {
|
|
1404
|
+
super(message);
|
|
1405
|
+
this.name = "ShellSecurityConfigError";
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
function isValidEnvName(name) {
|
|
1409
|
+
return ENV_NAME_RE.test(name) && !RESERVED_OBJECT_KEYS.has(name);
|
|
1410
|
+
}
|
|
1411
|
+
function addRisk(command, risks, seen, input) {
|
|
1412
|
+
const start = Math.max(0, input.start);
|
|
1413
|
+
const end = Math.min(command.length, Math.max(start, input.end));
|
|
1414
|
+
const key = `${input.code}:${start}:${end}`;
|
|
1415
|
+
if (seen.has(key)) return;
|
|
1416
|
+
seen.add(key);
|
|
1417
|
+
risks.push({
|
|
1418
|
+
code: input.code,
|
|
1419
|
+
severity: input.severity ?? "danger",
|
|
1420
|
+
message: input.message,
|
|
1421
|
+
matchedText: command.slice(start, end),
|
|
1422
|
+
span: { start, end }
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
function addRegexRisks(command, risks, seen, pattern, code, message) {
|
|
1426
|
+
pattern.lastIndex = 0;
|
|
1427
|
+
for (const match of command.matchAll(pattern)) {
|
|
1428
|
+
if (match.index === void 0) continue;
|
|
1429
|
+
let start = match.index;
|
|
1430
|
+
let matchedText = match[0];
|
|
1431
|
+
const leading = matchedText.match(/^[;&|(\n\s]+/);
|
|
1432
|
+
if (leading) {
|
|
1433
|
+
start += leading[0].length;
|
|
1434
|
+
matchedText = matchedText.slice(leading[0].length);
|
|
1435
|
+
}
|
|
1436
|
+
addRisk(command, risks, seen, {
|
|
1437
|
+
code,
|
|
1438
|
+
message,
|
|
1439
|
+
start,
|
|
1440
|
+
end: start + matchedText.length
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
function shellTokens(fragment) {
|
|
1445
|
+
return Array.from(
|
|
1446
|
+
fragment.matchAll(/"(?:\\.|[^"\\])*"|'[^']*'|[^\s]+/g),
|
|
1447
|
+
(match) => match[0].replace(/^(?:"|')|(?:"|')$/g, "")
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
function isBroadFilesystemTarget(rawTarget) {
|
|
1451
|
+
const target = rawTarget.replace(/[;,]+$/, "");
|
|
1452
|
+
if (target.startsWith("/")) return true;
|
|
1453
|
+
return /^(?:\.|\.\/\*+|\*+|~(?:\/.*)?|\$(?:HOME|\{HOME\})(?:\/.*)?|\.\.(?:\/.*)?)$/.test(target);
|
|
1454
|
+
}
|
|
1455
|
+
function classifyBroadFilesystemOperations(command, risks, seen) {
|
|
1456
|
+
const commandRe = /(?:^|[;&|(\n])\s*(?:command\s+)?(rm|find|chmod|chown)\b[^;&|)\n]*/gim;
|
|
1457
|
+
for (const match of command.matchAll(commandRe)) {
|
|
1458
|
+
if (match.index === void 0 || !match[1]) continue;
|
|
1459
|
+
const nameOffset = match[0].toLowerCase().indexOf(match[1].toLowerCase());
|
|
1460
|
+
const start = match.index + nameOffset;
|
|
1461
|
+
const text = command.slice(start, match.index + match[0].length).trimEnd();
|
|
1462
|
+
const tokens = shellTokens(text);
|
|
1463
|
+
const name = tokens[0]?.toLowerCase();
|
|
1464
|
+
const args = tokens.slice(1);
|
|
1465
|
+
let risky = false;
|
|
1466
|
+
if (name === "rm") {
|
|
1467
|
+
const recursive = args.some(
|
|
1468
|
+
(arg) => arg === "--recursive" || /^-[^-]+$/.test(arg) && /[rR]/.test(arg)
|
|
1469
|
+
);
|
|
1470
|
+
risky = recursive && args.some((arg) => !arg.startsWith("-") && isBroadFilesystemTarget(arg));
|
|
1471
|
+
} else if (name === "find") {
|
|
1472
|
+
const target = args.find((arg) => !arg.startsWith("-"));
|
|
1473
|
+
risky = args.includes("-delete") && target !== void 0 && isBroadFilesystemTarget(target);
|
|
1474
|
+
} else if (name === "chmod" || name === "chown") {
|
|
1475
|
+
const recursive = args.some(
|
|
1476
|
+
(arg) => arg === "--recursive" || /^-[^-]+$/.test(arg) && /R/.test(arg)
|
|
1477
|
+
);
|
|
1478
|
+
risky = recursive && args.some((arg) => !arg.startsWith("-") && isBroadFilesystemTarget(arg));
|
|
1479
|
+
}
|
|
1480
|
+
if (!risky) continue;
|
|
1481
|
+
addRisk(command, risks, seen, {
|
|
1482
|
+
code: "broad-destructive-operation",
|
|
1483
|
+
message: "Recursively changes or deletes a broad filesystem target.",
|
|
1484
|
+
start,
|
|
1485
|
+
end: start + text.length
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
function isLocalHostname(rawHostname) {
|
|
1490
|
+
const hostname = rawHostname.trim().replace(/^.*@/, "").replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
|
|
1491
|
+
return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "::1" || hostname === "0.0.0.0" || /^127(?:\.\d{1,3}){3}$/.test(hostname);
|
|
1492
|
+
}
|
|
1493
|
+
function hasExternalHttpDestination(segment) {
|
|
1494
|
+
const urls = Array.from(segment.matchAll(/https?:\/\/[^\s'"`]+/gi), (match) => match[0]);
|
|
1495
|
+
if (urls.length === 0) return true;
|
|
1496
|
+
return urls.some((rawUrl) => {
|
|
1497
|
+
try {
|
|
1498
|
+
return !isLocalHostname(new URL(rawUrl).hostname);
|
|
1499
|
+
} catch {
|
|
1500
|
+
return true;
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
function remoteTransferHosts(segment) {
|
|
1505
|
+
const hosts = [];
|
|
1506
|
+
const uriRe = /(?:sftp|ssh|rsync):\/\/(?:[^@\s/]+@)?(\[[^\]]+\]|[^\s/:]+)(?::\d+)?/gi;
|
|
1507
|
+
for (const match of segment.matchAll(uriRe)) {
|
|
1508
|
+
if (match[1]) hosts.push(match[1]);
|
|
1509
|
+
}
|
|
1510
|
+
const scpRe = /(?:^|\s)(?:[^@\s:]+@)?(\[[^\]]+\]|[A-Za-z0-9._-]+):[^\s]+/g;
|
|
1511
|
+
for (const match of segment.matchAll(scpRe)) {
|
|
1512
|
+
if (match[1]) hosts.push(match[1]);
|
|
1513
|
+
}
|
|
1514
|
+
return hosts;
|
|
1515
|
+
}
|
|
1516
|
+
function invocationSegment(command, match, commandName) {
|
|
1517
|
+
if (match.index === void 0) return null;
|
|
1518
|
+
const nameOffset = match[0].toLowerCase().indexOf(commandName.toLowerCase());
|
|
1519
|
+
if (nameOffset < 0) return null;
|
|
1520
|
+
const start = match.index + nameOffset;
|
|
1521
|
+
const boundaryOffset = command.slice(start).search(COMMAND_BOUNDARY_RE);
|
|
1522
|
+
const end = boundaryOffset < 0 ? command.length : start + boundaryOffset;
|
|
1523
|
+
return { start, text: command.slice(start, end).trimEnd() };
|
|
1524
|
+
}
|
|
1525
|
+
function classifyOutboundExfiltration(command, risks, seen) {
|
|
1526
|
+
const invocationRe = /(?:^|[;&|(\n])\s*(?:command\s+)?(curl|wget|scp|rsync|nc|netcat|sftp)\b/gim;
|
|
1527
|
+
for (const match of command.matchAll(invocationRe)) {
|
|
1528
|
+
const name = match[1]?.toLowerCase();
|
|
1529
|
+
if (!name) continue;
|
|
1530
|
+
const segment = invocationSegment(command, match, name);
|
|
1531
|
+
if (!segment) continue;
|
|
1532
|
+
let risky = false;
|
|
1533
|
+
if (name === "curl") {
|
|
1534
|
+
const uploads = /(?:^|\s)(?:-X\s*POST\b|-[dFT](?:\s|[^\s]))/.test(segment.text) || /(?:^|\s)(?:--request(?:=|\s+)POST\b|--data(?:-[a-z-]+)?(?:=|\s)|--form(?:-string)?(?:=|\s)|--upload-file(?:=|\s))/i.test(
|
|
1535
|
+
segment.text
|
|
1536
|
+
);
|
|
1537
|
+
risky = uploads && hasExternalHttpDestination(segment.text);
|
|
1538
|
+
} else if (name === "wget") {
|
|
1539
|
+
const uploads = /(?:^|\s)(?:--method(?:=|\s+)POST\b|--post-data(?:=|\s)|--post-file(?:=|\s)|--body-data(?:=|\s)|--body-file(?:=|\s))/i.test(
|
|
1540
|
+
segment.text
|
|
1541
|
+
);
|
|
1542
|
+
risky = uploads && hasExternalHttpDestination(segment.text);
|
|
1543
|
+
} else if (name === "scp" || name === "rsync") {
|
|
1544
|
+
const hosts = remoteTransferHosts(segment.text);
|
|
1545
|
+
risky = hosts.some((host) => !isLocalHostname(host)) || hosts.length === 0 && /\$/.test(segment.text);
|
|
1546
|
+
} else if (name === "sftp") {
|
|
1547
|
+
const hosts = remoteTransferHosts(segment.text);
|
|
1548
|
+
if (hosts.length > 0) {
|
|
1549
|
+
risky = hosts.some((host) => !isLocalHostname(host));
|
|
1550
|
+
} else {
|
|
1551
|
+
const destination = shellTokens(segment.text).at(-1);
|
|
1552
|
+
risky = destination !== void 0 && destination !== name && !isLocalHostname(destination);
|
|
1553
|
+
}
|
|
1554
|
+
} else {
|
|
1555
|
+
const tokens = shellTokens(segment.text);
|
|
1556
|
+
const host = tokens.find(
|
|
1557
|
+
(token, index) => index > 0 && !token.startsWith("-") && !/^\d+$/.test(token) && !["tcp", "udp"].includes(token.toLowerCase())
|
|
1558
|
+
);
|
|
1559
|
+
risky = host !== void 0 && !isLocalHostname(host);
|
|
1560
|
+
}
|
|
1561
|
+
if (!risky) continue;
|
|
1562
|
+
addRisk(command, risks, seen, {
|
|
1563
|
+
code: "outbound-exfiltration",
|
|
1564
|
+
message: "Uploads data or opens a transfer connection to a non-local destination.",
|
|
1565
|
+
start: segment.start,
|
|
1566
|
+
end: segment.start + segment.text.length
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
function classifyPrivilegeAndCredentialAccess(command, risks, seen) {
|
|
1571
|
+
const patterns = [
|
|
1572
|
+
/(?:^|[;&|(\n])\s*(?:sudo|su|doas|pkexec)\b[^;&|)\n]*/gim,
|
|
1573
|
+
/(?:^|[;&|(\n])\s*security\s+(?:find-(?:generic|internet)-password|dump-keychain)\b[^;&|)\n]*/gim,
|
|
1574
|
+
/(?:^|[;&|(\n])\s*gcloud\s+auth\b[^;&|)\n]*/gim,
|
|
1575
|
+
/(?:^|[;&|(\n])\s*aws\s+(?:configure|sso\s+login)\b[^;&|)\n]*/gim,
|
|
1576
|
+
/(?:^|[;&|(\n])\s*git\s+credential(?:-[A-Za-z0-9_-]+|\s+(?:fill|approve|reject))?\b[^;&|)\n]*/gim,
|
|
1577
|
+
/(?:^|[;&|(\n])\s*(?:docker|npm|gh)\s+(?:login|auth\s+token)\b[^;&|)\n]*/gim,
|
|
1578
|
+
/(?:^|[;&|(\n])\s*(?:pass\s+(?:show|ls)|secret-tool\s+lookup|ssh-add\s+-[Ll])\b[^;&|)\n]*/gim,
|
|
1579
|
+
/(?:^|[;&|(\n])\s*(?:env|printenv|set|export\s+-p)\s*(?=$|[;&|)\n])/gim
|
|
1580
|
+
];
|
|
1581
|
+
for (const pattern of patterns) {
|
|
1582
|
+
addRegexRisks(
|
|
1583
|
+
command,
|
|
1584
|
+
risks,
|
|
1585
|
+
seen,
|
|
1586
|
+
pattern,
|
|
1587
|
+
"privilege-or-credential-access",
|
|
1588
|
+
"Requests elevated privileges or accesses a credential store."
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
function classifyBashCommand(command) {
|
|
1593
|
+
const risks = [];
|
|
1594
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1595
|
+
addRegexRisks(
|
|
1596
|
+
command,
|
|
1597
|
+
risks,
|
|
1598
|
+
seen,
|
|
1599
|
+
SENSITIVE_HOME_PATH_RE,
|
|
1600
|
+
"sensitive-path-read",
|
|
1601
|
+
"Accesses a path that commonly contains credentials."
|
|
1602
|
+
);
|
|
1603
|
+
addRegexRisks(
|
|
1604
|
+
command,
|
|
1605
|
+
risks,
|
|
1606
|
+
seen,
|
|
1607
|
+
SENSITIVE_FILE_RE,
|
|
1608
|
+
"sensitive-path-read",
|
|
1609
|
+
"Accesses a sensitive environment, authentication, or database file."
|
|
1610
|
+
);
|
|
1611
|
+
addRegexRisks(
|
|
1612
|
+
command,
|
|
1613
|
+
risks,
|
|
1614
|
+
seen,
|
|
1615
|
+
SENSITIVE_ENV_RE,
|
|
1616
|
+
"sensitive-path-read",
|
|
1617
|
+
"References an environment variable name commonly used for credentials."
|
|
1618
|
+
);
|
|
1619
|
+
classifyBroadFilesystemOperations(command, risks, seen);
|
|
1620
|
+
addRegexRisks(
|
|
1621
|
+
command,
|
|
1622
|
+
risks,
|
|
1623
|
+
seen,
|
|
1624
|
+
REMOTE_PIPE_RE,
|
|
1625
|
+
"remote-pipe-execution",
|
|
1626
|
+
"Pipes downloaded content directly into an interpreter."
|
|
1627
|
+
);
|
|
1628
|
+
classifyOutboundExfiltration(command, risks, seen);
|
|
1629
|
+
classifyPrivilegeAndCredentialAccess(command, risks, seen);
|
|
1630
|
+
return risks.sort((a, b) => a.span.start - b.span.start || a.code.localeCompare(b.code));
|
|
1631
|
+
}
|
|
1632
|
+
function parseEnvAllowlist(raw) {
|
|
1633
|
+
if (raw === void 0 || raw.trim() === "") return [];
|
|
1634
|
+
const values = raw.split(",").map((value) => value.trim());
|
|
1635
|
+
if (values.some((value) => value === "")) {
|
|
1636
|
+
throw new ShellSecurityConfigError(
|
|
1637
|
+
"BAZILION_BASH_SANDBOX_ENV_ALLOWLIST contains an empty environment variable name"
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
const unique = [];
|
|
1641
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1642
|
+
for (const value of values) {
|
|
1643
|
+
if (!isValidEnvName(value)) {
|
|
1644
|
+
throw new ShellSecurityConfigError(
|
|
1645
|
+
`BAZILION_BASH_SANDBOX_ENV_ALLOWLIST contains invalid environment variable name: ${value}`
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
if (DOCKER_CLIENT_CONTROL_ENV.has(value)) {
|
|
1649
|
+
throw new ShellSecurityConfigError(
|
|
1650
|
+
`BAZILION_BASH_SANDBOX_ENV_ALLOWLIST cannot include Docker client control variable: ${value}`
|
|
1651
|
+
);
|
|
1652
|
+
}
|
|
1653
|
+
if (SANDBOX_CONTROL_ENV.has(value)) {
|
|
1654
|
+
throw new ShellSecurityConfigError(
|
|
1655
|
+
`BAZILION_BASH_SANDBOX_ENV_ALLOWLIST cannot include shell startup control variable: ${value}`
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
if (!seen.has(value)) {
|
|
1659
|
+
seen.add(value);
|
|
1660
|
+
unique.push(value);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return unique;
|
|
1664
|
+
}
|
|
1665
|
+
function resolveShellSecurityConfig(env = process.env) {
|
|
1666
|
+
const rawMode = env.BAZILION_BASH_SANDBOX?.trim() ?? "";
|
|
1667
|
+
if (rawMode !== "" && rawMode !== "off" && rawMode !== "docker") {
|
|
1668
|
+
throw new ShellSecurityConfigError(
|
|
1669
|
+
`BAZILION_BASH_SANDBOX must be "off" or "docker", received: ${rawMode}`
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
const rawApproval = env.BAZILION_BASH_APPROVAL?.trim() ?? "";
|
|
1673
|
+
if (rawApproval !== "" && rawApproval !== "off" && rawApproval !== "dangerous") {
|
|
1674
|
+
throw new ShellSecurityConfigError(
|
|
1675
|
+
`BAZILION_BASH_APPROVAL must be "off" or "dangerous", received: ${rawApproval}`
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
const rawImage = env.BAZILION_BASH_SANDBOX_IMAGE?.trim() ?? "";
|
|
1679
|
+
if (rawImage !== "" && !DOCKER_IMAGE_RE.test(rawImage)) {
|
|
1680
|
+
throw new ShellSecurityConfigError(
|
|
1681
|
+
`BAZILION_BASH_SANDBOX_IMAGE is not a valid Docker image reference: ${rawImage}`
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
return {
|
|
1685
|
+
sandboxMode: rawMode === "docker" ? "docker" : "off",
|
|
1686
|
+
approvalMode: rawApproval === "dangerous" ? "dangerous" : "off",
|
|
1687
|
+
sandboxImage: rawImage || DEFAULT_BASH_SANDBOX_IMAGE,
|
|
1688
|
+
envAllowlist: parseEnvAllowlist(env.BAZILION_BASH_SANDBOX_ENV_ALLOWLIST)
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
function assertEnvName(name) {
|
|
1692
|
+
if (!isValidEnvName(name)) {
|
|
1693
|
+
throw new ShellSecurityConfigError(`invalid shell environment variable name: ${name}`);
|
|
1694
|
+
}
|
|
1695
|
+
if (DOCKER_CLIENT_CONTROL_ENV.has(name) || SANDBOX_CONTROL_ENV.has(name)) {
|
|
1696
|
+
throw new ShellSecurityConfigError(`unsafe shell environment variable name: ${name}`);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
function buildScrubbedShellEnv(env, allowedKeys = []) {
|
|
1700
|
+
const scrubbed = {};
|
|
1701
|
+
const keys = new Set(SAFE_SHELL_ENV_KEYS);
|
|
1702
|
+
for (const key of allowedKeys) {
|
|
1703
|
+
assertEnvName(key);
|
|
1704
|
+
keys.add(key);
|
|
1705
|
+
}
|
|
1706
|
+
for (const key of keys) {
|
|
1707
|
+
const value = env[key];
|
|
1708
|
+
if (value !== void 0) scrubbed[key] = value;
|
|
1709
|
+
}
|
|
1710
|
+
return scrubbed;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// ../daemon/src/runtime/shell/approval.ts
|
|
1714
|
+
var BashApprovalDeniedError = class extends Error {
|
|
1715
|
+
risks;
|
|
1716
|
+
constructor(risks) {
|
|
1717
|
+
const codes = [...new Set(risks.map((risk) => risk.code))];
|
|
1718
|
+
super(`Dangerous bash command denied (${codes.join(", ")})`);
|
|
1719
|
+
this.name = "BashApprovalDeniedError";
|
|
1720
|
+
this.risks = risks;
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
function createApprovalGatedBashTool(base, host) {
|
|
1724
|
+
return defineTool({
|
|
1725
|
+
...base,
|
|
1726
|
+
description: `${base.description} Commands classified as dangerous require operator approval before execution.`,
|
|
1727
|
+
executionMode: "sequential",
|
|
1728
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
1729
|
+
const risks = classifyBashCommand(params.command);
|
|
1730
|
+
if (risks.length > 0) {
|
|
1731
|
+
const decision = host ? await host.requestApproval({
|
|
1732
|
+
toolCallId,
|
|
1733
|
+
command: params.command,
|
|
1734
|
+
risks,
|
|
1735
|
+
signal
|
|
1736
|
+
}) : "denied";
|
|
1737
|
+
if (decision !== "approved") throw new BashApprovalDeniedError(risks);
|
|
1738
|
+
}
|
|
1739
|
+
return base.execute(toolCallId, params, signal, onUpdate, ctx);
|
|
1740
|
+
}
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// ../daemon/src/runtime/shell/docker.ts
|
|
1745
|
+
import { spawn } from "child_process";
|
|
1746
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
1747
|
+
import { closeSync as closeSync2, constants as constants2, mkdtempSync as mkdtempSync2, openSync as openSync2, rmSync as rmSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
1748
|
+
import { access, realpath, stat } from "fs/promises";
|
|
1749
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1750
|
+
import { isAbsolute as isAbsolute2, join as join14, relative as relative2, sep as sep3 } from "path";
|
|
1751
|
+
var CONTAINER_WORKDIR = "/workspace";
|
|
1752
|
+
var MAX_TIMEOUT_MS = 2147483647;
|
|
1753
|
+
var MAX_STDERR_CHARS = 16 * 1024;
|
|
1754
|
+
var CLEANUP_TIMEOUT_MS = 650;
|
|
1755
|
+
var CLEANUP_RETRY_DELAY_MS = 100;
|
|
1756
|
+
var CLEANUP_ATTEMPTS = 3;
|
|
1757
|
+
var DOCKER_CONTEXT_TIMEOUT_MS = 2e3;
|
|
1758
|
+
var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1759
|
+
var CONTAINER_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]+$/;
|
|
1760
|
+
var DOCKER_CLIENT_ENV_KEYS = [
|
|
1761
|
+
"PATH",
|
|
1762
|
+
"HOME",
|
|
1763
|
+
"DOCKER_HOST",
|
|
1764
|
+
"DOCKER_CONTEXT",
|
|
1765
|
+
"DOCKER_CONFIG",
|
|
1766
|
+
"DOCKER_CERT_PATH",
|
|
1767
|
+
"DOCKER_TLS_VERIFY",
|
|
1768
|
+
"DOCKER_API_VERSION",
|
|
1769
|
+
"XDG_CONFIG_HOME",
|
|
1770
|
+
"XDG_RUNTIME_DIR"
|
|
1771
|
+
];
|
|
1772
|
+
var DOCKER_CLIENT_CONTROL_ENV2 = new Set(DOCKER_CLIENT_ENV_KEYS);
|
|
1773
|
+
var SANDBOX_CONTROL_ENV2 = /* @__PURE__ */ new Set(["BASH_ENV"]);
|
|
1774
|
+
var CONTAINER_LITERAL_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
1775
|
+
"PATH",
|
|
1776
|
+
"HOME",
|
|
1777
|
+
"LANG",
|
|
1778
|
+
"LANGUAGE",
|
|
1779
|
+
"LC_ALL",
|
|
1780
|
+
"LC_CTYPE",
|
|
1781
|
+
"TERM",
|
|
1782
|
+
"SHELL",
|
|
1783
|
+
"TMPDIR",
|
|
1784
|
+
"TZ"
|
|
1785
|
+
]);
|
|
1786
|
+
function buildDockerRunSpec(input) {
|
|
1787
|
+
const executable = input.dockerPath ?? "docker";
|
|
1788
|
+
validateDockerPath(executable);
|
|
1789
|
+
validateImage(input.image);
|
|
1790
|
+
if (input.resolvedImage !== void 0) validateImage(input.resolvedImage);
|
|
1791
|
+
validateContainerName(input.containerName);
|
|
1792
|
+
validateMountArgument(input.mountSource);
|
|
1793
|
+
const readOnlyMounts = input.readOnlyMounts ?? [];
|
|
1794
|
+
validateReadOnlyMounts(readOnlyMounts, input.mountSource);
|
|
1795
|
+
validateHostIdentity(input.uid, input.gid);
|
|
1796
|
+
if (input.command.includes("\0")) {
|
|
1797
|
+
throw new Error("Docker sandbox command cannot contain a NUL byte");
|
|
1798
|
+
}
|
|
1799
|
+
const args = [
|
|
1800
|
+
"run",
|
|
1801
|
+
"--rm",
|
|
1802
|
+
"--pull",
|
|
1803
|
+
"never",
|
|
1804
|
+
"--name",
|
|
1805
|
+
input.containerName,
|
|
1806
|
+
"--network",
|
|
1807
|
+
"none",
|
|
1808
|
+
"--read-only",
|
|
1809
|
+
"--tmpfs",
|
|
1810
|
+
"/tmp:rw,nosuid,nodev,size=64m,mode=1777",
|
|
1811
|
+
"--user",
|
|
1812
|
+
`${input.uid}:${input.gid}`,
|
|
1813
|
+
"--cap-drop",
|
|
1814
|
+
"ALL",
|
|
1815
|
+
"--security-opt",
|
|
1816
|
+
"no-new-privileges=true",
|
|
1817
|
+
"--pids-limit",
|
|
1818
|
+
"256",
|
|
1819
|
+
"--entrypoint",
|
|
1820
|
+
"/bin/bash",
|
|
1821
|
+
"--workdir",
|
|
1822
|
+
CONTAINER_WORKDIR,
|
|
1823
|
+
// A bind mount is read/write unless `readonly` is present. No other mount is added.
|
|
1824
|
+
"--mount",
|
|
1825
|
+
`type=bind,source=${input.mountSource},target=${CONTAINER_WORKDIR},bind-recursive=disabled`
|
|
1826
|
+
];
|
|
1827
|
+
for (const mount of readOnlyMounts) {
|
|
1828
|
+
args.push(
|
|
1829
|
+
"--mount",
|
|
1830
|
+
`type=bind,source=${mount.source},target=${mount.target},readonly,bind-recursive=disabled`
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
const processEnv = buildDockerClientEnv(input.hostEnv ?? process.env);
|
|
1834
|
+
const envFileLines = [];
|
|
1835
|
+
const containerEnvNames = [];
|
|
1836
|
+
for (const [name, value] of sortedEnvironment(input.env)) {
|
|
1837
|
+
containerEnvNames.push(name);
|
|
1838
|
+
if (CONTAINER_LITERAL_ENV_KEYS.has(name)) {
|
|
1839
|
+
args.push("--env", `${name}=${value}`);
|
|
1840
|
+
} else {
|
|
1841
|
+
envFileLines.push(`${name}=${value}`);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
if (envFileLines.length > 0) args.push("--env-file", "/dev/fd/3");
|
|
1845
|
+
args.push("--env", "BASH_ENV=");
|
|
1846
|
+
const cleanAssignments = containerEnvNames.map((name) => `${name}="$${name}"`).join(" ");
|
|
1847
|
+
const cleanEnvironmentCommand = `exec /usr/bin/env -i${cleanAssignments ? ` ${cleanAssignments}` : ""} /bin/bash --noprofile --norc -c "$1"`;
|
|
1848
|
+
args.push(
|
|
1849
|
+
input.resolvedImage ?? input.image,
|
|
1850
|
+
"--noprofile",
|
|
1851
|
+
"--norc",
|
|
1852
|
+
"-c",
|
|
1853
|
+
cleanEnvironmentCommand,
|
|
1854
|
+
"bazilion-sandbox",
|
|
1855
|
+
input.command
|
|
1856
|
+
);
|
|
1857
|
+
return {
|
|
1858
|
+
executable,
|
|
1859
|
+
args,
|
|
1860
|
+
containerName: input.containerName,
|
|
1861
|
+
image: input.image,
|
|
1862
|
+
mountSource: input.mountSource,
|
|
1863
|
+
processEnv,
|
|
1864
|
+
...envFileLines.length > 0 ? { envFileContent: `${envFileLines.join("\n")}
|
|
1865
|
+
` } : {}
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
function createDockerBashOperations(options) {
|
|
1869
|
+
const image = options.image;
|
|
1870
|
+
const env = { ...options.env };
|
|
1871
|
+
const dockerPath = options.dockerPath ?? "docker";
|
|
1872
|
+
const configuredReadOnlyMounts = options.readOnlyMounts ?? [];
|
|
1873
|
+
const hostEnv = { ...options.hostEnv ?? process.env };
|
|
1874
|
+
validateDockerPath(dockerPath);
|
|
1875
|
+
validateImage(image);
|
|
1876
|
+
return {
|
|
1877
|
+
async exec(command, cwd, { onData, signal, timeout }) {
|
|
1878
|
+
const timeoutMs = resolveTimeoutMs(timeout);
|
|
1879
|
+
if (signal?.aborted) throw new Error("aborted");
|
|
1880
|
+
const mountSource = await resolveMountSource(cwd);
|
|
1881
|
+
const readOnlyMounts = await Promise.all(
|
|
1882
|
+
configuredReadOnlyMounts.map(async (mount) => {
|
|
1883
|
+
const source = await resolveMountSource(mount.source);
|
|
1884
|
+
if (mount.sourceRoot && !isWithin(mount.sourceRoot, source)) {
|
|
1885
|
+
throw new Error(
|
|
1886
|
+
`Docker sandbox read-only mount escaped its configured root: ${mount.source}`
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
return { source, target: mount.target };
|
|
1890
|
+
})
|
|
1891
|
+
);
|
|
1892
|
+
if (signal?.aborted) throw new Error("aborted");
|
|
1893
|
+
const { uid, gid } = hostIdentity();
|
|
1894
|
+
const dockerClientEnv = await resolveLocalDockerClientEnv(dockerPath, hostEnv);
|
|
1895
|
+
const resolvedImage = await inspectSandboxImage(dockerPath, image, dockerClientEnv);
|
|
1896
|
+
if (signal?.aborted) throw new Error("aborted");
|
|
1897
|
+
const containerName = `bazilion-bash-${process.pid}-${randomUUID15().replaceAll("-", "")}`;
|
|
1898
|
+
const spec = buildDockerRunSpec({
|
|
1899
|
+
image,
|
|
1900
|
+
env,
|
|
1901
|
+
dockerPath,
|
|
1902
|
+
command,
|
|
1903
|
+
mountSource,
|
|
1904
|
+
containerName,
|
|
1905
|
+
uid,
|
|
1906
|
+
gid,
|
|
1907
|
+
resolvedImage,
|
|
1908
|
+
readOnlyMounts,
|
|
1909
|
+
hostEnv: dockerClientEnv
|
|
1910
|
+
});
|
|
1911
|
+
return executeDockerRun(spec, { onData, signal, timeout, timeoutMs });
|
|
1912
|
+
}
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
function executeDockerRun(spec, options) {
|
|
1916
|
+
let envFileFd;
|
|
1917
|
+
try {
|
|
1918
|
+
if (spec.envFileContent !== void 0) {
|
|
1919
|
+
envFileFd = openAnonymousEnvFile(spec.envFileContent);
|
|
1920
|
+
}
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
return Promise.reject(
|
|
1923
|
+
new Error(
|
|
1924
|
+
`Docker sandbox could not prepare its environment: ${error instanceof Error ? error.message : String(error)}`
|
|
1925
|
+
)
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
return new Promise((resolve5, reject) => {
|
|
1929
|
+
const child = spawn(spec.executable, spec.args, {
|
|
1930
|
+
stdio: ["ignore", "pipe", "pipe", envFileFd ?? "ignore"],
|
|
1931
|
+
windowsHide: true,
|
|
1932
|
+
env: spec.processEnv
|
|
1933
|
+
});
|
|
1934
|
+
if (envFileFd !== void 0) closeSync2(envFileFd);
|
|
1935
|
+
let settled = false;
|
|
1936
|
+
let termination;
|
|
1937
|
+
let timeoutHandle;
|
|
1938
|
+
let stderr = "";
|
|
1939
|
+
const requestTermination = (reason) => {
|
|
1940
|
+
termination ??= reason;
|
|
1941
|
+
child.kill("SIGKILL");
|
|
1942
|
+
};
|
|
1943
|
+
const onAbort = () => requestTermination("aborted");
|
|
1944
|
+
const finish = async (exitCode, spawnError) => {
|
|
1945
|
+
if (settled) return;
|
|
1946
|
+
settled = true;
|
|
1947
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
1948
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
1949
|
+
if (!termination && options.signal?.aborted) termination = "aborted";
|
|
1950
|
+
if (termination) {
|
|
1951
|
+
await removeContainer(spec.executable, spec.containerName, spec.processEnv);
|
|
1952
|
+
reject(
|
|
1953
|
+
termination === "aborted" ? new Error("aborted") : new Error(`timeout:${options.timeout}`)
|
|
1954
|
+
);
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
if (spawnError) {
|
|
1958
|
+
reject(formatSpawnError(spec.executable, spawnError));
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
if (exitCode === 125) {
|
|
1962
|
+
await removeContainer(spec.executable, spec.containerName, spec.processEnv);
|
|
1963
|
+
reject(formatDockerRunError(spec, stderr));
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
if (exitCode === null) {
|
|
1967
|
+
await removeContainer(spec.executable, spec.containerName, spec.processEnv);
|
|
1968
|
+
reject(new Error(`Docker sandbox CLI for container "${spec.containerName}" was killed`));
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1971
|
+
resolve5({ exitCode });
|
|
1972
|
+
};
|
|
1973
|
+
child.stdout?.on("data", options.onData);
|
|
1974
|
+
child.stderr?.on("data", (data) => {
|
|
1975
|
+
stderr = `${stderr}${data.toString("utf8")}`.slice(-MAX_STDERR_CHARS);
|
|
1976
|
+
options.onData(data);
|
|
1977
|
+
});
|
|
1978
|
+
child.once("error", (error) => void finish(null, error));
|
|
1979
|
+
child.once("close", (exitCode) => void finish(exitCode));
|
|
1980
|
+
if (options.timeoutMs !== void 0) {
|
|
1981
|
+
timeoutHandle = setTimeout(() => requestTermination("timeout"), options.timeoutMs);
|
|
1982
|
+
}
|
|
1983
|
+
if (options.signal) {
|
|
1984
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
1985
|
+
if (options.signal.aborted) onAbort();
|
|
1986
|
+
}
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
function openAnonymousEnvFile(content) {
|
|
1990
|
+
const dir = mkdtempSync2(join14(tmpdir2(), "bazilion-docker-env-"));
|
|
1991
|
+
const path = join14(dir, "environment");
|
|
1992
|
+
let fd;
|
|
1993
|
+
try {
|
|
1994
|
+
writeFileSync7(path, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
1995
|
+
fd = openSync2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
1996
|
+
rmSync10(dir, { recursive: true, force: true });
|
|
1997
|
+
return fd;
|
|
1998
|
+
} catch (error) {
|
|
1999
|
+
if (fd !== void 0) closeSync2(fd);
|
|
2000
|
+
rmSync10(dir, { recursive: true, force: true });
|
|
2001
|
+
throw error;
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
function resolveTimeoutMs(timeout) {
|
|
2005
|
+
if (timeout === void 0) return void 0;
|
|
2006
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
2007
|
+
throw new Error("Invalid timeout: must be a finite number of seconds");
|
|
2008
|
+
}
|
|
2009
|
+
const timeoutMs = timeout * 1e3;
|
|
2010
|
+
if (timeoutMs > MAX_TIMEOUT_MS) {
|
|
2011
|
+
throw new Error(`Invalid timeout: maximum is ${MAX_TIMEOUT_MS / 1e3} seconds`);
|
|
2012
|
+
}
|
|
2013
|
+
return timeoutMs;
|
|
2014
|
+
}
|
|
2015
|
+
async function resolveMountSource(cwd) {
|
|
2016
|
+
try {
|
|
2017
|
+
await access(cwd, constants2.F_OK);
|
|
2018
|
+
const info = await stat(cwd);
|
|
2019
|
+
if (!info.isDirectory()) {
|
|
2020
|
+
throw new Error(`Docker sandbox mount is not a directory: ${cwd}`);
|
|
2021
|
+
}
|
|
2022
|
+
return await realpath(cwd);
|
|
2023
|
+
} catch (error) {
|
|
2024
|
+
if (error instanceof Error && error.message.startsWith("Docker sandbox mount is not")) {
|
|
2025
|
+
throw error;
|
|
2026
|
+
}
|
|
2027
|
+
const code = error.code;
|
|
2028
|
+
if (code === "ENOENT") {
|
|
2029
|
+
throw new Error(`Docker sandbox mount does not exist: ${cwd}`);
|
|
2030
|
+
}
|
|
2031
|
+
throw new Error(
|
|
2032
|
+
`Docker sandbox cannot access mount "${cwd}": ${error instanceof Error ? error.message : String(error)}`
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
function hostIdentity() {
|
|
2037
|
+
if (typeof process.getuid !== "function" || typeof process.getgid !== "function") {
|
|
2038
|
+
throw new Error("Docker sandbox requires a host platform with numeric uid/gid support");
|
|
2039
|
+
}
|
|
2040
|
+
return { uid: process.getuid(), gid: process.getgid() };
|
|
2041
|
+
}
|
|
2042
|
+
function sortedEnvironment(env) {
|
|
2043
|
+
const entries = [];
|
|
2044
|
+
for (const [name, value] of Object.entries(env)) {
|
|
2045
|
+
if (value === void 0) continue;
|
|
2046
|
+
if (!ENV_NAME.test(name)) {
|
|
2047
|
+
throw new Error(
|
|
2048
|
+
`Docker sandbox environment variable name is invalid: ${JSON.stringify(name)}`
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
if (DOCKER_CLIENT_CONTROL_ENV2.has(name) && !CONTAINER_LITERAL_ENV_KEYS.has(name)) {
|
|
2052
|
+
throw new Error(`Docker sandbox cannot pass Docker client control variable: ${name}`);
|
|
2053
|
+
}
|
|
2054
|
+
if (SANDBOX_CONTROL_ENV2.has(name)) {
|
|
2055
|
+
throw new Error(`Docker sandbox cannot pass shell startup control variable: ${name}`);
|
|
2056
|
+
}
|
|
2057
|
+
if (value.includes("\0")) {
|
|
2058
|
+
throw new Error(`Docker sandbox environment variable ${name} contains a NUL byte`);
|
|
2059
|
+
}
|
|
2060
|
+
if (value.includes("\n") || value.includes("\r")) {
|
|
2061
|
+
throw new Error(`Docker sandbox environment variable ${name} cannot contain a line break`);
|
|
2062
|
+
}
|
|
2063
|
+
entries.push([name, value]);
|
|
2064
|
+
}
|
|
2065
|
+
return entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
2066
|
+
}
|
|
2067
|
+
async function resolveLocalDockerClientEnv(dockerPath, hostEnv) {
|
|
2068
|
+
const discoveryEnv = buildDockerClientEnv(hostEnv);
|
|
2069
|
+
const endpoint = await inspectDockerEndpoint(dockerPath, discoveryEnv);
|
|
2070
|
+
const prefix = "unix://";
|
|
2071
|
+
if (!endpoint.startsWith(prefix) || !isAbsolute2(endpoint.slice(prefix.length))) {
|
|
2072
|
+
throw new Error(
|
|
2073
|
+
`Docker sandbox requires a local Unix-socket context; active endpoint is ${JSON.stringify(endpoint)}`
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
const pinned = { DOCKER_HOST: endpoint };
|
|
2077
|
+
if (hostEnv.PATH !== void 0) pinned.PATH = hostEnv.PATH;
|
|
2078
|
+
if (hostEnv.HOME !== void 0) pinned.HOME = hostEnv.HOME;
|
|
2079
|
+
return pinned;
|
|
2080
|
+
}
|
|
2081
|
+
async function inspectDockerEndpoint(dockerPath, processEnv) {
|
|
2082
|
+
const raw = await runDockerInspection(
|
|
2083
|
+
dockerPath,
|
|
2084
|
+
["context", "inspect", "--format", "{{json .Endpoints.docker.Host}}"],
|
|
2085
|
+
processEnv,
|
|
2086
|
+
"the active context"
|
|
2087
|
+
);
|
|
2088
|
+
try {
|
|
2089
|
+
const parsed = JSON.parse(raw);
|
|
2090
|
+
if (typeof parsed !== "string" || parsed.length === 0) throw new Error("empty endpoint");
|
|
2091
|
+
return parsed;
|
|
2092
|
+
} catch {
|
|
2093
|
+
throw new Error(
|
|
2094
|
+
`Docker sandbox could not resolve the active Docker endpoint: ${raw || "empty context response"}`
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
async function inspectSandboxImage(dockerPath, image, processEnv) {
|
|
2099
|
+
let raw;
|
|
2100
|
+
try {
|
|
2101
|
+
raw = await runDockerInspection(
|
|
2102
|
+
dockerPath,
|
|
2103
|
+
["image", "inspect", "--format", '{{json .Id}} {{json (index .Config "Volumes")}}', image],
|
|
2104
|
+
processEnv,
|
|
2105
|
+
`local image ${JSON.stringify(image)}`
|
|
2106
|
+
);
|
|
2107
|
+
} catch (error) {
|
|
2108
|
+
const detail3 = error instanceof Error ? error.message : String(error);
|
|
2109
|
+
if (/no such image|unable to find image|manifest unknown/i.test(detail3)) {
|
|
2110
|
+
throw new Error(`Docker sandbox image "${image}" is unavailable: ${detail3}`);
|
|
2111
|
+
}
|
|
2112
|
+
throw error;
|
|
2113
|
+
}
|
|
2114
|
+
const separator = raw.indexOf(" ");
|
|
2115
|
+
if (separator < 0) {
|
|
2116
|
+
throw new Error(`Docker sandbox received an invalid inspection result for image "${image}"`);
|
|
2117
|
+
}
|
|
2118
|
+
let id;
|
|
2119
|
+
let volumes;
|
|
2120
|
+
try {
|
|
2121
|
+
id = JSON.parse(raw.slice(0, separator));
|
|
2122
|
+
volumes = JSON.parse(raw.slice(separator + 1));
|
|
2123
|
+
} catch {
|
|
2124
|
+
throw new Error(`Docker sandbox received an invalid inspection result for image "${image}"`);
|
|
2125
|
+
}
|
|
2126
|
+
if (typeof id !== "string" || !id.startsWith("sha256:")) {
|
|
2127
|
+
throw new Error(`Docker sandbox received an invalid image id for "${image}"`);
|
|
2128
|
+
}
|
|
2129
|
+
if (volumes !== null && typeof volumes === "object" && Object.keys(volumes).length > 0) {
|
|
2130
|
+
throw new Error(
|
|
2131
|
+
`Docker sandbox image "${image}" declares writable volumes (${Object.keys(volumes).sort().join(", ")}); use an image without VOLUME declarations`
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2134
|
+
if (volumes !== null && (typeof volumes !== "object" || Array.isArray(volumes))) {
|
|
2135
|
+
throw new Error(`Docker sandbox received invalid volume metadata for image "${image}"`);
|
|
2136
|
+
}
|
|
2137
|
+
return id;
|
|
2138
|
+
}
|
|
2139
|
+
function runDockerInspection(dockerPath, args, processEnv, subject) {
|
|
2140
|
+
return new Promise((resolve5, reject) => {
|
|
2141
|
+
const child = spawn(dockerPath, args, {
|
|
2142
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2143
|
+
windowsHide: true,
|
|
2144
|
+
env: processEnv
|
|
2145
|
+
});
|
|
2146
|
+
let settled = false;
|
|
2147
|
+
let stdout = "";
|
|
2148
|
+
let stderr = "";
|
|
2149
|
+
const finish = (error) => {
|
|
2150
|
+
if (settled) return;
|
|
2151
|
+
settled = true;
|
|
2152
|
+
clearTimeout(timeoutHandle);
|
|
2153
|
+
if (error) {
|
|
2154
|
+
reject(error);
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
resolve5(stdout.trim());
|
|
2158
|
+
};
|
|
2159
|
+
const timeoutHandle = setTimeout(() => {
|
|
2160
|
+
child.kill("SIGKILL");
|
|
2161
|
+
finish(
|
|
2162
|
+
new Error(
|
|
2163
|
+
`Docker sandbox could not inspect ${subject} within ${DOCKER_CONTEXT_TIMEOUT_MS}ms`
|
|
2164
|
+
)
|
|
2165
|
+
);
|
|
2166
|
+
}, DOCKER_CONTEXT_TIMEOUT_MS);
|
|
2167
|
+
child.stdout.on("data", (data) => {
|
|
2168
|
+
stdout = `${stdout}${data.toString("utf8")}`.slice(-4096);
|
|
2169
|
+
});
|
|
2170
|
+
child.stderr.on("data", (data) => {
|
|
2171
|
+
stderr = `${stderr}${data.toString("utf8")}`.slice(-4096);
|
|
2172
|
+
});
|
|
2173
|
+
child.once("error", (error) => finish(formatSpawnError(dockerPath, error)));
|
|
2174
|
+
child.once("close", (code) => {
|
|
2175
|
+
if (code === 0) finish();
|
|
2176
|
+
else
|
|
2177
|
+
finish(
|
|
2178
|
+
new Error(
|
|
2179
|
+
`Docker sandbox could not inspect ${subject}: ${stderr.trim() || `Docker exited with status ${code}`}`
|
|
2180
|
+
)
|
|
2181
|
+
);
|
|
2182
|
+
});
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
function buildDockerClientEnv(hostEnv) {
|
|
2186
|
+
const env = {};
|
|
2187
|
+
for (const key of DOCKER_CLIENT_ENV_KEYS) {
|
|
2188
|
+
const value = hostEnv[key];
|
|
2189
|
+
if (value !== void 0) env[key] = value;
|
|
2190
|
+
}
|
|
2191
|
+
return env;
|
|
2192
|
+
}
|
|
2193
|
+
function validateDockerPath(dockerPath) {
|
|
2194
|
+
if (!dockerPath.trim()) throw new Error("Docker sandbox executable path is required");
|
|
2195
|
+
if (dockerPath.includes("\0"))
|
|
2196
|
+
throw new Error("Docker sandbox executable path contains a NUL byte");
|
|
2197
|
+
}
|
|
2198
|
+
function validateImage(image) {
|
|
2199
|
+
if (!image.trim()) throw new Error("Docker sandbox image is required");
|
|
2200
|
+
if (image.startsWith("-")) {
|
|
2201
|
+
throw new Error(`Docker sandbox image cannot start with "-": ${JSON.stringify(image)}`);
|
|
2202
|
+
}
|
|
2203
|
+
if (image.includes("\0")) throw new Error("Docker sandbox image contains a NUL byte");
|
|
2204
|
+
}
|
|
2205
|
+
function validateContainerName(containerName) {
|
|
2206
|
+
if (!CONTAINER_NAME.test(containerName)) {
|
|
2207
|
+
throw new Error(`Docker sandbox container name is invalid: ${JSON.stringify(containerName)}`);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
function validateMountArgument(mountSource) {
|
|
2211
|
+
if (!mountSource) throw new Error("Docker sandbox mount path is required");
|
|
2212
|
+
if (mountSource.includes("\0") || mountSource.includes(",")) {
|
|
2213
|
+
throw new Error(
|
|
2214
|
+
`Docker sandbox mount path cannot be represented safely: ${JSON.stringify(mountSource)}`
|
|
2215
|
+
);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
function validateReadOnlyMounts(mounts, workspaceSource) {
|
|
2219
|
+
const targets = /* @__PURE__ */ new Set([CONTAINER_WORKDIR, "/tmp"]);
|
|
2220
|
+
for (const mount of mounts) {
|
|
2221
|
+
validateMountArgument(mount.source);
|
|
2222
|
+
if (mount.sourceRoot !== void 0 && (!isAbsolute2(mount.sourceRoot) || mount.sourceRoot.includes("\0") || mount.sourceRoot.includes(","))) {
|
|
2223
|
+
throw new Error(
|
|
2224
|
+
`Docker sandbox read-only mount root is invalid: ${JSON.stringify(mount.sourceRoot)}`
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
if (!mount.target.startsWith("/") || mount.target.includes("\0") || mount.target.includes(",") || mount.target.split("/").includes("..")) {
|
|
2228
|
+
throw new Error(
|
|
2229
|
+
`Docker sandbox read-only mount target is invalid: ${JSON.stringify(mount.target)}`
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
if (mount.source === workspaceSource) {
|
|
2233
|
+
throw new Error("Docker sandbox cannot mount the writable workspace again as an input");
|
|
2234
|
+
}
|
|
2235
|
+
if (targets.has(mount.target)) {
|
|
2236
|
+
throw new Error(`Docker sandbox mount target is duplicated or reserved: ${mount.target}`);
|
|
2237
|
+
}
|
|
2238
|
+
targets.add(mount.target);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
function isWithin(root, candidate) {
|
|
2242
|
+
const rel = relative2(root, candidate);
|
|
2243
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute2(rel);
|
|
2244
|
+
}
|
|
2245
|
+
function validateHostIdentity(uid, gid) {
|
|
2246
|
+
if (!Number.isSafeInteger(uid) || uid < 0 || !Number.isSafeInteger(gid) || gid < 0) {
|
|
2247
|
+
throw new Error(`Docker sandbox received an invalid host uid/gid: ${uid}:${gid}`);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
function formatSpawnError(dockerPath, error) {
|
|
2251
|
+
const code = error.code;
|
|
2252
|
+
if (code === "ENOENT") {
|
|
2253
|
+
return new Error(`Docker sandbox unavailable: executable "${dockerPath}" was not found`);
|
|
2254
|
+
}
|
|
2255
|
+
if (code === "EACCES") {
|
|
2256
|
+
return new Error(`Docker sandbox unavailable: executable "${dockerPath}" is not executable`);
|
|
2257
|
+
}
|
|
2258
|
+
return new Error(`Docker sandbox could not start "${dockerPath}": ${error.message}`);
|
|
2259
|
+
}
|
|
2260
|
+
function formatDockerRunError(spec, stderr) {
|
|
2261
|
+
const detail3 = stderr.trim() || "Docker exited with status 125 without an error message";
|
|
2262
|
+
if (/no such image|unable to find image|manifest unknown|pull access denied/i.test(detail3)) {
|
|
2263
|
+
return new Error(`Docker sandbox image "${spec.image}" is unavailable: ${detail3}`);
|
|
2264
|
+
}
|
|
2265
|
+
if (/mounts denied|invalid mount config|bind source path does not exist|mount.*failed/i.test(detail3)) {
|
|
2266
|
+
return new Error(`Docker sandbox mount failed for "${spec.mountSource}": ${detail3}`);
|
|
2267
|
+
}
|
|
2268
|
+
if (/cannot connect to the docker daemon|is the docker daemon running/i.test(detail3)) {
|
|
2269
|
+
return new Error(`Docker sandbox unavailable: ${detail3}`);
|
|
2270
|
+
}
|
|
2271
|
+
return new Error(`Docker sandbox failed to start container "${spec.containerName}": ${detail3}`);
|
|
2272
|
+
}
|
|
2273
|
+
async function removeContainer(dockerPath, containerName, processEnv) {
|
|
2274
|
+
const args = ["container", "rm", "--force", containerName];
|
|
2275
|
+
let last;
|
|
2276
|
+
for (let attempt = 0; attempt < CLEANUP_ATTEMPTS; attempt += 1) {
|
|
2277
|
+
last = await runCleanupCommand(dockerPath, args, processEnv);
|
|
2278
|
+
if (last.ok) return;
|
|
2279
|
+
if (attempt < CLEANUP_ATTEMPTS - 1) {
|
|
2280
|
+
await new Promise((resolve5) => setTimeout(resolve5, CLEANUP_RETRY_DELAY_MS));
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
if (last && !last.missing) {
|
|
2284
|
+
console.warn(
|
|
2285
|
+
`Docker sandbox could not confirm cleanup of container "${containerName}": ${last.error || "cleanup command failed"}`
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
function runCleanupCommand(dockerPath, args, processEnv) {
|
|
2290
|
+
return new Promise((resolve5) => {
|
|
2291
|
+
let done = false;
|
|
2292
|
+
let stderr = "";
|
|
2293
|
+
const child = spawn(dockerPath, args, {
|
|
2294
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
2295
|
+
windowsHide: true,
|
|
2296
|
+
env: processEnv
|
|
2297
|
+
});
|
|
2298
|
+
const finish = (ok, error = "") => {
|
|
2299
|
+
if (done) return;
|
|
2300
|
+
done = true;
|
|
2301
|
+
clearTimeout(timeoutHandle);
|
|
2302
|
+
const detail3 = error || stderr.trim();
|
|
2303
|
+
resolve5({
|
|
2304
|
+
ok,
|
|
2305
|
+
missing: /no such container/i.test(detail3),
|
|
2306
|
+
error: detail3
|
|
2307
|
+
});
|
|
2308
|
+
};
|
|
2309
|
+
const timeoutHandle = setTimeout(() => {
|
|
2310
|
+
child.kill("SIGKILL");
|
|
2311
|
+
finish(false, `cleanup timed out after ${CLEANUP_TIMEOUT_MS}ms`);
|
|
2312
|
+
}, CLEANUP_TIMEOUT_MS);
|
|
2313
|
+
child.stderr.on("data", (data) => {
|
|
2314
|
+
stderr = `${stderr}${data.toString("utf8")}`.slice(-2048);
|
|
2315
|
+
});
|
|
2316
|
+
child.once("error", (error) => finish(false, error.message));
|
|
2317
|
+
child.once("close", (code) => finish(code === 0));
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
// ../daemon/src/runtime/shell/tooling.ts
|
|
2322
|
+
var HOST_CODING_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
|
2323
|
+
var HOST_FILE_TOOL_NAMES = ["read", "edit", "write", "grep", "find", "ls"];
|
|
2324
|
+
var CONTAINER_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
2325
|
+
var SANDBOX_INPUTS_DIR = "/inputs";
|
|
2326
|
+
var SANDBOX_MEMORY_DIR = "/workspace/memory";
|
|
2327
|
+
function buildSandboxContainerEnv(env, allowlist) {
|
|
2328
|
+
return {
|
|
2329
|
+
...buildScrubbedShellEnv(env, allowlist),
|
|
2330
|
+
PATH: CONTAINER_PATH,
|
|
2331
|
+
HOME: "/tmp",
|
|
2332
|
+
LANG: "C.UTF-8",
|
|
2333
|
+
LC_ALL: "C.UTF-8",
|
|
2334
|
+
SHELL: "/bin/bash",
|
|
2335
|
+
TMPDIR: "/tmp"
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
function createSessionShellTools(cwd, env, options = {}) {
|
|
2339
|
+
const config = resolveShellSecurityConfig(env);
|
|
2340
|
+
if (config.sandboxMode === "off" && config.approvalMode === "off") {
|
|
2341
|
+
return { config, hostToolNames: HOST_CODING_TOOL_NAMES };
|
|
2342
|
+
}
|
|
2343
|
+
if (config.sandboxMode === "off") {
|
|
2344
|
+
const base2 = createBashToolDefinition(cwd, {
|
|
2345
|
+
operations: createLocalBashOperations()
|
|
2346
|
+
});
|
|
2347
|
+
const customBash2 = createApprovalGatedBashTool(base2, options.approvalHost);
|
|
2348
|
+
return { config, hostToolNames: HOST_FILE_TOOL_NAMES, customBash: customBash2 };
|
|
2349
|
+
}
|
|
2350
|
+
const operations = createDockerBashOperations({
|
|
2351
|
+
image: config.sandboxImage,
|
|
2352
|
+
env: buildSandboxContainerEnv(env, config.envAllowlist),
|
|
2353
|
+
readOnlyMounts: [
|
|
2354
|
+
...existsSync11(join15(cwd, "memory")) ? [safeReadOnlyMount(join15(cwd, "memory"), SANDBOX_MEMORY_DIR, cwd)] : [],
|
|
2355
|
+
...options.inputsDir ? [safeReadOnlyMount(options.inputsDir, SANDBOX_INPUTS_DIR, dirname3(options.inputsDir))] : [],
|
|
2356
|
+
...(options.skillMounts ?? []).map(
|
|
2357
|
+
(mount) => safeReadOnlyMount(mount.source, mount.target, dirname3(mount.source))
|
|
2358
|
+
)
|
|
2359
|
+
]
|
|
2360
|
+
});
|
|
2361
|
+
const base = createBashToolDefinition(cwd, { operations });
|
|
2362
|
+
const dockerBash = defineTool2({
|
|
2363
|
+
...base,
|
|
2364
|
+
label: "bash (Docker sandbox)",
|
|
2365
|
+
description: `${base.description} Runs in an ephemeral, network-disabled Docker container with the team workspace mounted read/write plus only bounded read-only memory, skill, and attachment mounts. Other host files and host credentials are unavailable.`
|
|
2366
|
+
});
|
|
2367
|
+
const customBash = config.approvalMode === "dangerous" ? createApprovalGatedBashTool(dockerBash, options.approvalHost) : dockerBash;
|
|
2368
|
+
return { config, hostToolNames: [], customBash };
|
|
2369
|
+
}
|
|
2370
|
+
function safeReadOnlyMount(source, target, expectedRoot) {
|
|
2371
|
+
const info = lstatSync2(source);
|
|
2372
|
+
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
2373
|
+
throw new Error(`Docker sandbox read-only mount must be a real directory: ${source}`);
|
|
2374
|
+
}
|
|
2375
|
+
const sourceRoot = realpathSync2(expectedRoot);
|
|
2376
|
+
const canonicalSource = realpathSync2(source);
|
|
2377
|
+
const rel = relative3(sourceRoot, canonicalSource);
|
|
2378
|
+
const contained = rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !isAbsolute3(rel);
|
|
2379
|
+
if (!contained) {
|
|
2380
|
+
throw new Error(`Docker sandbox read-only mount escaped its configured root: ${source}`);
|
|
2381
|
+
}
|
|
2382
|
+
return { source: canonicalSource, target, sourceRoot };
|
|
2383
|
+
}
|
|
2384
|
+
|
|
1075
2385
|
// ../daemon/src/runtime/pi/tools.ts
|
|
1076
2386
|
import { Type as Type2 } from "typebox";
|
|
1077
2387
|
|
|
1078
2388
|
// ../daemon/src/runtime/tools/bootstrap.ts
|
|
1079
|
-
import { existsSync as existsSync12, rmSync as
|
|
1080
|
-
import { join as
|
|
2389
|
+
import { existsSync as existsSync12, rmSync as rmSync11 } from "fs";
|
|
2390
|
+
import { join as join16 } from "path";
|
|
1081
2391
|
function bootstrapTool(agentDir) {
|
|
1082
2392
|
return {
|
|
1083
2393
|
def: {
|
|
@@ -1086,9 +2396,9 @@ function bootstrapTool(agentDir) {
|
|
|
1086
2396
|
parameters: { type: "object", properties: {} }
|
|
1087
2397
|
},
|
|
1088
2398
|
async invoke() {
|
|
1089
|
-
const path =
|
|
2399
|
+
const path = join16(agentDir, "BOOTSTRAP.md");
|
|
1090
2400
|
if (existsSync12(path)) {
|
|
1091
|
-
|
|
2401
|
+
rmSync11(path);
|
|
1092
2402
|
return "BOOTSTRAP.md removed. Bootstrap is complete.";
|
|
1093
2403
|
}
|
|
1094
2404
|
return "BOOTSTRAP.md was already removed.";
|
|
@@ -1226,8 +2536,16 @@ function browserTools(host, agentId) {
|
|
|
1226
2536
|
}
|
|
1227
2537
|
|
|
1228
2538
|
// ../daemon/src/runtime/tools/deliver-file.ts
|
|
1229
|
-
import {
|
|
1230
|
-
|
|
2539
|
+
import {
|
|
2540
|
+
closeSync as closeSync3,
|
|
2541
|
+
constants as constants3,
|
|
2542
|
+
fstatSync as fstatSync2,
|
|
2543
|
+
openSync as openSync3,
|
|
2544
|
+
readFileSync as readFileSync10,
|
|
2545
|
+
realpathSync as realpathSync3,
|
|
2546
|
+
statSync as statSync3
|
|
2547
|
+
} from "fs";
|
|
2548
|
+
import { basename as basename2, extname, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve4, sep as sep5 } from "path";
|
|
1231
2549
|
var MAX_DELIVER_BYTES = 25 * 1024 * 1024;
|
|
1232
2550
|
var MIME = {
|
|
1233
2551
|
".pdf": "application/pdf",
|
|
@@ -1243,7 +2561,17 @@ var MIME = {
|
|
|
1243
2561
|
".gif": "image/gif",
|
|
1244
2562
|
".webp": "image/webp"
|
|
1245
2563
|
};
|
|
2564
|
+
function isWithin2(root, candidate) {
|
|
2565
|
+
const rel = relative4(root, candidate);
|
|
2566
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep5}`) && !isAbsolute4(rel);
|
|
2567
|
+
}
|
|
1246
2568
|
function deliverFileTool(cwd, sink) {
|
|
2569
|
+
const workspacePath = resolve4(cwd);
|
|
2570
|
+
let workspaceRealPath = null;
|
|
2571
|
+
try {
|
|
2572
|
+
workspaceRealPath = realpathSync3(workspacePath);
|
|
2573
|
+
} catch {
|
|
2574
|
+
}
|
|
1247
2575
|
return {
|
|
1248
2576
|
def: {
|
|
1249
2577
|
name: "deliver_file",
|
|
@@ -1253,7 +2581,7 @@ function deliverFileTool(cwd, sink) {
|
|
|
1253
2581
|
properties: {
|
|
1254
2582
|
path: {
|
|
1255
2583
|
type: "string",
|
|
1256
|
-
description: "Path to
|
|
2584
|
+
description: "Path to a file, relative to your workspace."
|
|
1257
2585
|
}
|
|
1258
2586
|
},
|
|
1259
2587
|
required: ["path"],
|
|
@@ -1263,44 +2591,79 @@ function deliverFileTool(cwd, sink) {
|
|
|
1263
2591
|
async invoke(args) {
|
|
1264
2592
|
const p = String(args.path ?? "");
|
|
1265
2593
|
if (!p) throw new Error("deliver_file: path is required");
|
|
1266
|
-
|
|
1267
|
-
|
|
2594
|
+
if (isAbsolute4(p)) {
|
|
2595
|
+
throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
|
|
2596
|
+
}
|
|
2597
|
+
const abs = resolve4(workspacePath, p);
|
|
2598
|
+
if (!isWithin2(workspacePath, abs)) {
|
|
2599
|
+
throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
|
|
2600
|
+
}
|
|
2601
|
+
if (!workspaceRealPath) {
|
|
2602
|
+
throw new Error("deliver_file: workspace is unavailable");
|
|
2603
|
+
}
|
|
2604
|
+
let real;
|
|
2605
|
+
let expected;
|
|
1268
2606
|
try {
|
|
1269
|
-
|
|
2607
|
+
real = realpathSync3(abs);
|
|
2608
|
+
expected = statSync3(real);
|
|
1270
2609
|
} catch {
|
|
1271
2610
|
throw new Error(`deliver_file: no such file: ${p}`);
|
|
1272
2611
|
}
|
|
1273
|
-
if (
|
|
1274
|
-
throw new Error(
|
|
1275
|
-
|
|
1276
|
-
|
|
2612
|
+
if (!isWithin2(workspaceRealPath, real)) {
|
|
2613
|
+
throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
|
|
2614
|
+
}
|
|
2615
|
+
let fd;
|
|
2616
|
+
try {
|
|
2617
|
+
fd = openSync3(real, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
|
|
2618
|
+
} catch {
|
|
2619
|
+
throw new Error(`deliver_file: no such file: ${p}`);
|
|
2620
|
+
}
|
|
2621
|
+
let data;
|
|
2622
|
+
try {
|
|
2623
|
+
const stat2 = fstatSync2(fd);
|
|
2624
|
+
if (!stat2.isFile()) {
|
|
2625
|
+
throw new Error(`deliver_file: not a regular file: ${p}`);
|
|
2626
|
+
}
|
|
2627
|
+
if (stat2.dev !== expected.dev || stat2.ino !== expected.ino) {
|
|
2628
|
+
throw new Error(`deliver_file: path changed during validation: ${p}`);
|
|
2629
|
+
}
|
|
2630
|
+
let confirmedReal;
|
|
2631
|
+
try {
|
|
2632
|
+
confirmedReal = realpathSync3(abs);
|
|
2633
|
+
} catch {
|
|
2634
|
+
throw new Error(`deliver_file: path changed during validation: ${p}`);
|
|
2635
|
+
}
|
|
2636
|
+
if (confirmedReal !== real) {
|
|
2637
|
+
throw new Error(`deliver_file: path changed during validation: ${p}`);
|
|
2638
|
+
}
|
|
2639
|
+
if (stat2.size > MAX_DELIVER_BYTES) {
|
|
2640
|
+
throw new Error(
|
|
2641
|
+
`deliver_file: "${basename2(abs)}" is too large (${(stat2.size / 1024 / 1024).toFixed(1)} MB > 25 MB)`
|
|
2642
|
+
);
|
|
2643
|
+
}
|
|
2644
|
+
data = readFileSync10(fd).toString("base64");
|
|
2645
|
+
} finally {
|
|
2646
|
+
closeSync3(fd);
|
|
1277
2647
|
}
|
|
1278
2648
|
const name = basename2(abs);
|
|
1279
2649
|
const mimeType = MIME[extname(abs).toLowerCase()] ?? "application/octet-stream";
|
|
1280
|
-
sink({ name, mimeType, data
|
|
2650
|
+
sink({ name, mimeType, data });
|
|
1281
2651
|
return `Delivered "${name}" (${mimeType}) to the user.`;
|
|
1282
2652
|
}
|
|
1283
2653
|
};
|
|
1284
2654
|
}
|
|
1285
2655
|
|
|
1286
2656
|
// ../daemon/src/runtime/tools/home.ts
|
|
1287
|
-
import { readdirSync as
|
|
1288
|
-
import { join as
|
|
2657
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync11, statSync as statSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
2658
|
+
import { join as join17 } from "path";
|
|
1289
2659
|
var HOME_FILES_READABLE = [
|
|
1290
2660
|
"IDENTITY.md",
|
|
1291
2661
|
"SOUL.md",
|
|
1292
2662
|
"BOOTSTRAP.md",
|
|
1293
2663
|
"AGENTS.md",
|
|
1294
|
-
"TOOLS.md"
|
|
1295
|
-
"HEARTBEAT.md"
|
|
1296
|
-
];
|
|
1297
|
-
var HOME_FILES_WRITABLE = [
|
|
1298
|
-
"IDENTITY.md",
|
|
1299
|
-
"SOUL.md",
|
|
1300
|
-
"AGENTS.md",
|
|
1301
|
-
"TOOLS.md",
|
|
1302
|
-
"HEARTBEAT.md"
|
|
2664
|
+
"TOOLS.md"
|
|
1303
2665
|
];
|
|
2666
|
+
var HOME_FILES_WRITABLE = ["IDENTITY.md", "SOUL.md", "AGENTS.md", "TOOLS.md"];
|
|
1304
2667
|
function homeTools(agentDir) {
|
|
1305
2668
|
return [
|
|
1306
2669
|
{
|
|
@@ -1322,7 +2685,7 @@ function homeTools(agentDir) {
|
|
|
1322
2685
|
`home_read: "file" must be one of ${HOME_FILES_READABLE.join(", ")}; got "${file}"`
|
|
1323
2686
|
);
|
|
1324
2687
|
}
|
|
1325
|
-
const path =
|
|
2688
|
+
const path = join17(agentDir, file);
|
|
1326
2689
|
try {
|
|
1327
2690
|
return readFileSync11(path, "utf8");
|
|
1328
2691
|
} catch (err) {
|
|
@@ -1352,8 +2715,8 @@ function homeTools(agentDir) {
|
|
|
1352
2715
|
);
|
|
1353
2716
|
}
|
|
1354
2717
|
const content = typeof args.content === "string" ? args.content : "";
|
|
1355
|
-
const path =
|
|
1356
|
-
|
|
2718
|
+
const path = join17(agentDir, file);
|
|
2719
|
+
writeFileSync8(path, content, "utf8");
|
|
1357
2720
|
return `wrote ${file} (${Buffer.byteLength(content, "utf8")} bytes)`;
|
|
1358
2721
|
}
|
|
1359
2722
|
},
|
|
@@ -1366,9 +2729,9 @@ function homeTools(agentDir) {
|
|
|
1366
2729
|
async invoke() {
|
|
1367
2730
|
const entries = [];
|
|
1368
2731
|
for (const file of HOME_FILES_READABLE) {
|
|
1369
|
-
const path =
|
|
2732
|
+
const path = join17(agentDir, file);
|
|
1370
2733
|
try {
|
|
1371
|
-
const s =
|
|
2734
|
+
const s = statSync4(path);
|
|
1372
2735
|
entries.push(`${file} (${s.size}b)`);
|
|
1373
2736
|
} catch {
|
|
1374
2737
|
}
|
|
@@ -1376,7 +2739,7 @@ function homeTools(agentDir) {
|
|
|
1376
2739
|
if (entries.length === 0) {
|
|
1377
2740
|
const dirEntries = (() => {
|
|
1378
2741
|
try {
|
|
1379
|
-
return
|
|
2742
|
+
return readdirSync7(agentDir);
|
|
1380
2743
|
} catch {
|
|
1381
2744
|
return [];
|
|
1382
2745
|
}
|
|
@@ -2291,7 +3654,6 @@ function createBazilionCustomTools(opts) {
|
|
|
2291
3654
|
}
|
|
2292
3655
|
|
|
2293
3656
|
// ../daemon/src/runtime/pi/session.ts
|
|
2294
|
-
var BUILTIN_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
|
2295
3657
|
async function createBazilionSession(opts) {
|
|
2296
3658
|
const {
|
|
2297
3659
|
agent,
|
|
@@ -2305,6 +3667,7 @@ async function createBazilionSession(opts) {
|
|
|
2305
3667
|
mcpHost,
|
|
2306
3668
|
mcpTools,
|
|
2307
3669
|
fileSink,
|
|
3670
|
+
bashApprovalHost,
|
|
2308
3671
|
refreshApiKey
|
|
2309
3672
|
} = opts;
|
|
2310
3673
|
const { providerName, modelId } = splitModelString(agent.model);
|
|
@@ -2337,7 +3700,17 @@ async function createBazilionSession(opts) {
|
|
|
2337
3700
|
}
|
|
2338
3701
|
const cwd = agent.team.path;
|
|
2339
3702
|
if (!existsSync13(cwd)) mkdirSync5(cwd, { recursive: true });
|
|
2340
|
-
const
|
|
3703
|
+
const promptSkills = loadPromptSkills(paths.skillsDir, agent.skills);
|
|
3704
|
+
const uploadsDir = join18(agent.agent.dir, "uploads");
|
|
3705
|
+
const shellTools = createSessionShellTools(cwd, env, {
|
|
3706
|
+
...existsSync13(uploadsDir) ? { inputsDir: uploadsDir } : {},
|
|
3707
|
+
skillMounts: promptSkills.map((skill) => ({
|
|
3708
|
+
source: skill.hostDir,
|
|
3709
|
+
target: skill.sandboxDir
|
|
3710
|
+
})),
|
|
3711
|
+
approvalHost: bashApprovalHost
|
|
3712
|
+
});
|
|
3713
|
+
const sessionDir = join18(paths.agentDir(agent.agent.id), "sessions");
|
|
2341
3714
|
mkdirSync5(sessionDir, { recursive: true });
|
|
2342
3715
|
const existing = findMostRecent(sessionDir);
|
|
2343
3716
|
const sessionManager = existing ? SessionManager.open(existing, sessionDir, cwd) : SessionManager.create(cwd, sessionDir);
|
|
@@ -2350,10 +3723,13 @@ async function createBazilionSession(opts) {
|
|
|
2350
3723
|
provider: { maxRetryDelayMs: 8e3 }
|
|
2351
3724
|
}
|
|
2352
3725
|
});
|
|
2353
|
-
const bazilionPrompt = buildSystemPrompt(agent
|
|
3726
|
+
const bazilionPrompt = buildSystemPrompt(agent, {
|
|
3727
|
+
skills: promptSkills,
|
|
3728
|
+
sandboxMode: shellTools.config.sandboxMode
|
|
3729
|
+
});
|
|
2354
3730
|
const resourceLoader = createBazilionResourceLoader(bazilionPrompt);
|
|
2355
3731
|
await resourceLoader.reload();
|
|
2356
|
-
const
|
|
3732
|
+
const bazilionTools = createBazilionCustomTools({
|
|
2357
3733
|
agent,
|
|
2358
3734
|
memory,
|
|
2359
3735
|
messagingHost,
|
|
@@ -2364,10 +3740,11 @@ async function createBazilionSession(opts) {
|
|
|
2364
3740
|
fileSink,
|
|
2365
3741
|
env
|
|
2366
3742
|
});
|
|
2367
|
-
const
|
|
3743
|
+
const customTools = shellTools.customBash ? [...bazilionTools, shellTools.customBash] : bazilionTools;
|
|
3744
|
+
const allowedTools = [...shellTools.hostToolNames, ...customTools.map((t) => t.name)];
|
|
2368
3745
|
const { session } = await createAgentSession({
|
|
2369
3746
|
cwd,
|
|
2370
|
-
agentDir:
|
|
3747
|
+
agentDir: join18(paths.home, "pi"),
|
|
2371
3748
|
model,
|
|
2372
3749
|
thinkingLevel: toPiThinkingLevel(agent.reasoningLevel),
|
|
2373
3750
|
tools: allowedTools,
|
|
@@ -2495,11 +3872,11 @@ function createBazilionResourceLoader(appendSystemPrompt) {
|
|
|
2495
3872
|
function findMostRecent(sessionDir) {
|
|
2496
3873
|
if (!existsSync13(sessionDir)) return null;
|
|
2497
3874
|
let newest = null;
|
|
2498
|
-
for (const entry of
|
|
3875
|
+
for (const entry of readdirSync8(sessionDir)) {
|
|
2499
3876
|
if (!entry.endsWith(".jsonl")) continue;
|
|
2500
|
-
const path =
|
|
3877
|
+
const path = join18(sessionDir, entry);
|
|
2501
3878
|
try {
|
|
2502
|
-
const s =
|
|
3879
|
+
const s = statSync5(path);
|
|
2503
3880
|
if (!newest || s.mtimeMs > newest.mtimeMs) newest = { path, mtimeMs: s.mtimeMs };
|
|
2504
3881
|
} catch {
|
|
2505
3882
|
}
|
|
@@ -2507,53 +3884,96 @@ function findMostRecent(sessionDir) {
|
|
|
2507
3884
|
return newest?.path ?? null;
|
|
2508
3885
|
}
|
|
2509
3886
|
|
|
2510
|
-
// ../daemon/src/runtime/worker/
|
|
2511
|
-
function
|
|
2512
|
-
|
|
2513
|
-
|
|
3887
|
+
// ../daemon/src/runtime/worker/api-key-refresh.ts
|
|
3888
|
+
function createIpcApiKeyRefresher(call, context) {
|
|
3889
|
+
return async (providerName) => {
|
|
3890
|
+
assertOpenAICodexProvider(providerName, context.providerName);
|
|
3891
|
+
return call("refreshApiKey", {
|
|
3892
|
+
providerName,
|
|
3893
|
+
agentId: context.agentId,
|
|
3894
|
+
turnId: context.turnId
|
|
3895
|
+
});
|
|
3896
|
+
};
|
|
2514
3897
|
}
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
2519
|
-
if (!raw) throw new Error("worker: empty stdin input");
|
|
2520
|
-
const parsed = JSON.parse(raw);
|
|
2521
|
-
if (!parsed.agent || typeof parsed.message !== "string" || !Array.isArray(parsed.enabledProviders)) {
|
|
2522
|
-
throw new Error("worker: stdin must be {agent, message, enabledProviders}");
|
|
3898
|
+
function assertOpenAICodexProvider(requested, expected) {
|
|
3899
|
+
if (expected !== "openai-codex" || requested !== expected) {
|
|
3900
|
+
throw new Error("unexpected API key refresh provider");
|
|
2523
3901
|
}
|
|
2524
|
-
return parsed;
|
|
2525
3902
|
}
|
|
2526
|
-
|
|
3903
|
+
|
|
3904
|
+
// ../daemon/src/runtime/worker/ipc-client.ts
|
|
3905
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
3906
|
+
function createIpcClient(channel) {
|
|
2527
3907
|
const pending = /* @__PURE__ */ new Map();
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
3908
|
+
let disconnected = false;
|
|
3909
|
+
channel.onMessage((message) => {
|
|
3910
|
+
if (!isIpcReply(message)) return;
|
|
3911
|
+
const entry = pending.get(message.id);
|
|
2531
3912
|
if (!entry) return;
|
|
2532
|
-
pending.delete(
|
|
2533
|
-
if (
|
|
2534
|
-
else entry.reject(new Error(
|
|
3913
|
+
pending.delete(message.id);
|
|
3914
|
+
if (message.ok) entry.resolve(message.result);
|
|
3915
|
+
else entry.reject(new Error(message.error));
|
|
2535
3916
|
});
|
|
2536
|
-
|
|
2537
|
-
|
|
3917
|
+
channel.onDisconnect(() => {
|
|
3918
|
+
disconnected = true;
|
|
3919
|
+
for (const entry of pending.values()) {
|
|
3920
|
+
entry.reject(new Error("worker IPC channel disconnected"));
|
|
3921
|
+
}
|
|
2538
3922
|
pending.clear();
|
|
2539
3923
|
});
|
|
2540
3924
|
return (method, args) => {
|
|
2541
|
-
if (
|
|
3925
|
+
if (disconnected) {
|
|
3926
|
+
return Promise.reject(new Error("worker IPC channel disconnected"));
|
|
3927
|
+
}
|
|
3928
|
+
const send = channel.send;
|
|
3929
|
+
if (!send) {
|
|
2542
3930
|
return Promise.reject(new Error("worker: no IPC channel \u2014 daemon must spawn with stdio:ipc"));
|
|
2543
3931
|
}
|
|
2544
|
-
const id =
|
|
3932
|
+
const id = randomUUID16();
|
|
2545
3933
|
const message = { type: "rpc", id, method, args };
|
|
2546
|
-
return new Promise((
|
|
2547
|
-
pending.set(id, { resolve: (
|
|
2548
|
-
|
|
2549
|
-
|
|
3934
|
+
return new Promise((resolve5, reject) => {
|
|
3935
|
+
pending.set(id, { resolve: (value) => resolve5(value), reject });
|
|
3936
|
+
try {
|
|
3937
|
+
send(message, (error) => {
|
|
3938
|
+
if (!error) return;
|
|
2550
3939
|
pending.delete(id);
|
|
2551
|
-
reject(
|
|
2552
|
-
}
|
|
2553
|
-
})
|
|
3940
|
+
reject(error);
|
|
3941
|
+
});
|
|
3942
|
+
} catch (error) {
|
|
3943
|
+
pending.delete(id);
|
|
3944
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
3945
|
+
}
|
|
2554
3946
|
});
|
|
2555
3947
|
};
|
|
2556
3948
|
}
|
|
3949
|
+
function isIpcReply(message) {
|
|
3950
|
+
if (!message || typeof message !== "object") return false;
|
|
3951
|
+
const candidate = message;
|
|
3952
|
+
return candidate.type === "rpc-reply" && typeof candidate.id === "string" && typeof candidate.ok === "boolean";
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
// ../daemon/src/runtime/worker/entry.ts
|
|
3956
|
+
function emit(frame) {
|
|
3957
|
+
process.stdout.write(`${JSON.stringify(frame)}
|
|
3958
|
+
`);
|
|
3959
|
+
}
|
|
3960
|
+
async function readInput() {
|
|
3961
|
+
const chunks = [];
|
|
3962
|
+
for await (const c of process.stdin) chunks.push(c);
|
|
3963
|
+
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
3964
|
+
if (!raw) throw new Error("worker: empty stdin input");
|
|
3965
|
+
const parsed = JSON.parse(raw);
|
|
3966
|
+
if (!parsed.agent || typeof parsed.message !== "string" || !Array.isArray(parsed.enabledProviders) || typeof parsed.turnId !== "string" || parsed.apiKeyRefreshEnabled !== void 0 && typeof parsed.apiKeyRefreshEnabled !== "boolean" || parsed.bashApprovalMode !== "interactive" && parsed.bashApprovalMode !== "auto_deny") {
|
|
3967
|
+
throw new Error(
|
|
3968
|
+
"worker: stdin must include {agent, message, enabledProviders, turnId, bashApprovalMode}"
|
|
3969
|
+
);
|
|
3970
|
+
}
|
|
3971
|
+
const providerName = parsed.agent.model.split(":", 1)[0] ?? "";
|
|
3972
|
+
if (parsed.apiKeyRefreshEnabled && (providerName !== "openai-codex" || typeof parsed.apiKey !== "string")) {
|
|
3973
|
+
throw new Error("worker: API key refresh requires an openai-codex turn with an initial token");
|
|
3974
|
+
}
|
|
3975
|
+
return parsed;
|
|
3976
|
+
}
|
|
2557
3977
|
function createIpcMessagingHost(call) {
|
|
2558
3978
|
return {
|
|
2559
3979
|
agentExists: (agentId) => call("agentExists", { agentId }),
|
|
@@ -2582,10 +4002,22 @@ function createIpcMcpHost(call) {
|
|
|
2582
4002
|
invoke: (serverId, toolName, args) => call("mcpInvoke", { serverId, toolName, args })
|
|
2583
4003
|
};
|
|
2584
4004
|
}
|
|
2585
|
-
function
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
4005
|
+
function createIpcBashApprovalHost(call, input) {
|
|
4006
|
+
return {
|
|
4007
|
+
async requestApproval(request) {
|
|
4008
|
+
const result = await call("bashApproval", {
|
|
4009
|
+
id: randomUUID17(),
|
|
4010
|
+
turnId: input.turnId,
|
|
4011
|
+
toolCallId: request.toolCallId,
|
|
4012
|
+
agentId: input.agent.agent.id,
|
|
4013
|
+
teamId: input.agent.team.id,
|
|
4014
|
+
command: request.command,
|
|
4015
|
+
risks: [...request.risks],
|
|
4016
|
+
mode: input.bashApprovalMode
|
|
4017
|
+
});
|
|
4018
|
+
return result.decision === "allow" ? "approved" : "denied";
|
|
4019
|
+
}
|
|
4020
|
+
};
|
|
2589
4021
|
}
|
|
2590
4022
|
async function main() {
|
|
2591
4023
|
let aborted = false;
|
|
@@ -2604,15 +4036,42 @@ async function main() {
|
|
|
2604
4036
|
};
|
|
2605
4037
|
process.on("SIGTERM", onSignal);
|
|
2606
4038
|
process.on("SIGINT", onSignal);
|
|
2607
|
-
const {
|
|
4039
|
+
const {
|
|
4040
|
+
agent,
|
|
4041
|
+
message,
|
|
4042
|
+
enabledProviders,
|
|
4043
|
+
apiKey,
|
|
4044
|
+
apiKeyRefreshEnabled,
|
|
4045
|
+
browserEnabled,
|
|
4046
|
+
mcpTools,
|
|
4047
|
+
images,
|
|
4048
|
+
turnId,
|
|
4049
|
+
bashApprovalMode
|
|
4050
|
+
} = await readInput();
|
|
2608
4051
|
const paths = resolvePaths();
|
|
2609
|
-
const memory = qmdBackend(
|
|
4052
|
+
const memory = qmdBackend(join19(agent.team.path, "memory"));
|
|
2610
4053
|
await memory.init();
|
|
2611
|
-
const ipcCall = createIpcClient(
|
|
4054
|
+
const ipcCall = createIpcClient({
|
|
4055
|
+
send: process.send ? (ipcMessage, done) => {
|
|
4056
|
+
process.send?.(ipcMessage, void 0, void 0, done);
|
|
4057
|
+
} : void 0,
|
|
4058
|
+
onMessage: (listener) => process.on("message", listener),
|
|
4059
|
+
onDisconnect: (listener) => process.on("disconnect", listener)
|
|
4060
|
+
});
|
|
4061
|
+
const refreshApiKey = apiKeyRefreshEnabled ? createIpcApiKeyRefresher(ipcCall, {
|
|
4062
|
+
providerName: agent.model.split(":", 1)[0] ?? "",
|
|
4063
|
+
agentId: agent.agent.id,
|
|
4064
|
+
turnId
|
|
4065
|
+
}) : void 0;
|
|
2612
4066
|
const messagingHost = createIpcMessagingHost(ipcCall);
|
|
2613
4067
|
const userMdHost = createIpcUserMdHost(ipcCall);
|
|
2614
4068
|
const browserHost = browserEnabled ? createIpcBrowserHost(ipcCall) : void 0;
|
|
2615
4069
|
const mcpHost = mcpTools && mcpTools.length > 0 ? createIpcMcpHost(ipcCall) : void 0;
|
|
4070
|
+
const bashApprovalHost = createIpcBashApprovalHost(ipcCall, {
|
|
4071
|
+
agent,
|
|
4072
|
+
turnId,
|
|
4073
|
+
bashApprovalMode
|
|
4074
|
+
});
|
|
2616
4075
|
const { session, dispose } = await createBazilionSession({
|
|
2617
4076
|
agent,
|
|
2618
4077
|
paths,
|
|
@@ -2622,9 +4081,11 @@ async function main() {
|
|
|
2622
4081
|
messagingHost,
|
|
2623
4082
|
userMdHost,
|
|
2624
4083
|
apiKey,
|
|
4084
|
+
refreshApiKey,
|
|
2625
4085
|
browserHost,
|
|
2626
4086
|
mcpHost,
|
|
2627
4087
|
mcpTools,
|
|
4088
|
+
bashApprovalHost,
|
|
2628
4089
|
// deliver_file emits a `file` event straight onto our stdout frame stream.
|
|
2629
4090
|
fileSink: (f) => emit({ kind: "event", event: { type: "file", ...f } })
|
|
2630
4091
|
});
|