bazilion 0.1.0 → 0.2.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/daemon.js CHANGED
@@ -9,7 +9,7 @@ var __export = (target, all) => {
9
9
  import { serve } from "@hono/node-server";
10
10
 
11
11
  // ../daemon/src/app.ts
12
- import { Hono as Hono10 } from "hono";
12
+ import { Hono as Hono11 } from "hono";
13
13
 
14
14
  // ../daemon/src/lib/middleware-auth.ts
15
15
  import { getCookie } from "hono/cookie";
@@ -541,12 +541,32 @@ Fill this in during your first conversation. Make it yours.
541
541
  `;
542
542
  var DEFAULT_BOOTSTRAP = `# BOOTSTRAP.md \u2014 First Run
543
543
 
544
- You just woke up. There is no memory yet \u2014 that's normal.
544
+ You just woke up. There is no memory yet \u2014 that's normal. This is a multi-turn
545
+ ritual: ask ONE question per turn and wait for the human's reply before moving
546
+ on. Do not race through it. Do not call any tool until the ritual is finished.
547
+
548
+ ## The ritual
549
+
550
+ **Turn 1 (right now):** Greet the human warmly and ask a single opening
551
+ question \u2014 what should they call you, or what should you focus on for them.
552
+ Do NOT call any tool yet. Just reply with greeting + one question.
553
+
554
+ **Turn 2+:** Continue with one more question per turn to fill in the rest of
555
+ your identity \u2014 vibe (warm / sharp / playful / calm / \u2026), an emoji that
556
+ feels right. Each turn is acknowledging the previous answer + at most one
557
+ new question. Skip a turn when you already have enough.
545
558
 
546
- ## What to do
547
- 1. Ask your human who they are and what they want to call you.
548
- 2. Call the \`home_write\` tool with \`file: "IDENTITY.md"\` and new content that captures your name, vibe, and emoji. Do NOT use the generic \`edit\` / \`write\` tools for this \u2014 those land in your workspace, not your private home, and would collide with other agents.
549
- 3. Call \`bootstrap_done\` when finished \u2014 it removes this file so it does not appear in future sessions.
559
+ **Final turn:** Once you have everything (Name, Vibe, Emoji), call \`home_write\`
560
+ with \`file: "IDENTITY.md"\` and the populated content. Do NOT use the generic
561
+ \`edit\` / \`write\` tools \u2014 those land in the shared workspace.
562
+
563
+ Then call \`bootstrap_done\` to retire this ritual file. After that, future
564
+ sessions skip the bootstrap and start from IDENTITY.md directly.
565
+
566
+ ## Hard rules
567
+ - Do not invent a name on your own. Ask the human and use what they say.
568
+ - Do not call \`home_write\` or \`bootstrap_done\` on your very first reply.
569
+ - One question per turn. Wait for the human to answer.
550
570
  `;
551
571
 
552
572
  // ../daemon/src/core/profile/create.ts
@@ -774,6 +794,9 @@ function openDb(path) {
774
794
  }
775
795
  };
776
796
  }
797
+ function inTx(db, fn) {
798
+ return db.raw.transaction(fn)();
799
+ }
777
800
 
778
801
  // ../daemon/src/core/db/migrate.ts
779
802
  import { readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
@@ -810,11 +833,11 @@ function runMigrations(db) {
810
833
  function deleteGroup(db, paths, id) {
811
834
  const g = get2(db, id, paths);
812
835
  if (!g) throw new Error(`group not found: ${id}`);
813
- const members = list(db, { includeArchived: true }).filter((a) => a.groupId === id);
814
- if (members.length > 0) {
815
- const names = members.map((a) => `${a.name} (${a.id.slice(0, 8)})`).join(", ");
836
+ const members2 = list(db, { includeArchived: true }).filter((a) => a.groupId === id);
837
+ if (members2.length > 0) {
838
+ const names = members2.map((a) => `${a.name} (${a.id.slice(0, 8)})`).join(", ");
816
839
  throw new Error(
817
- `cannot delete group "${id}": ${members.length} agent(s) still belong to it: ${names}. Move or archive them first.`
840
+ `cannot delete group "${id}": ${members2.length} agent(s) still belong to it: ${names}. Move or archive them first.`
818
841
  );
819
842
  }
820
843
  remove2(db, id);
@@ -851,6 +874,123 @@ function resolvePaths(home) {
851
874
 
852
875
  // ../daemon/src/core/profile/delete.ts
853
876
  import { existsSync as existsSync5, rmSync as rmSync2 } from "fs";
877
+
878
+ // ../daemon/src/core/repos/profileGroups.ts
879
+ var profileGroups_exports = {};
880
+ __export(profileGroups_exports, {
881
+ findReferencingProfile: () => findReferencingProfile,
882
+ get: () => get4,
883
+ insert: () => insert4,
884
+ list: () => list5,
885
+ members: () => members,
886
+ remove: () => remove5,
887
+ replaceMembers: () => replaceMembers,
888
+ update: () => update2
889
+ });
890
+ function toProfileGroup(r) {
891
+ return {
892
+ id: r.id,
893
+ name: r.name,
894
+ userMd: r.user_md,
895
+ createdAt: r.created_at,
896
+ updatedAt: r.updated_at
897
+ };
898
+ }
899
+ function toMember(r) {
900
+ return {
901
+ profileGroupId: r.profile_group_id,
902
+ position: r.position,
903
+ profileId: r.profile_id,
904
+ agentName: r.agent_name,
905
+ modelOverride: r.model_override,
906
+ reasoningLevel: r.reasoning_level
907
+ };
908
+ }
909
+ function insert4(db, p) {
910
+ const now = Date.now();
911
+ db.raw.run(
912
+ `INSERT INTO profile_groups (id, name, user_md, created_at, updated_at)
913
+ VALUES (?, ?, ?, ?, ?)`,
914
+ [p.id, p.name, p.userMd, now, now]
915
+ );
916
+ return { ...p, createdAt: now, updatedAt: now };
917
+ }
918
+ function get4(db, id) {
919
+ const row = db.raw.query("SELECT * FROM profile_groups WHERE id = ?").get(id);
920
+ return row ? toProfileGroup(row) : null;
921
+ }
922
+ function list5(db) {
923
+ return db.raw.query(
924
+ `SELECT pg.*, COALESCE(m.cnt, 0) AS member_count
925
+ FROM profile_groups pg
926
+ LEFT JOIN (
927
+ SELECT profile_group_id, COUNT(*) AS cnt
928
+ FROM profile_group_members
929
+ GROUP BY profile_group_id
930
+ ) m ON m.profile_group_id = pg.id
931
+ ORDER BY pg.created_at ASC`
932
+ ).all().map((r) => ({ ...toProfileGroup(r), memberCount: r.member_count }));
933
+ }
934
+ function update2(db, id, patch) {
935
+ const sets = [];
936
+ const args = [];
937
+ if (Object.hasOwn(patch, "name")) {
938
+ sets.push("name = ?");
939
+ args.push(patch.name);
940
+ }
941
+ if (Object.hasOwn(patch, "userMd")) {
942
+ sets.push("user_md = ?");
943
+ args.push(patch.userMd ?? null);
944
+ }
945
+ if (sets.length === 0) return;
946
+ sets.push("updated_at = ?");
947
+ args.push(Date.now());
948
+ args.push(id);
949
+ db.raw.run(`UPDATE profile_groups SET ${sets.join(", ")} WHERE id = ?`, args);
950
+ }
951
+ function remove5(db, id) {
952
+ db.raw.run("DELETE FROM profile_groups WHERE id = ?", [id]);
953
+ }
954
+ function members(db, profileGroupId) {
955
+ return db.raw.query(
956
+ `SELECT * FROM profile_group_members
957
+ WHERE profile_group_id = ?
958
+ ORDER BY position ASC`
959
+ ).all(profileGroupId).map(toMember);
960
+ }
961
+ function findReferencingProfile(db, profileId) {
962
+ return db.raw.query(
963
+ `SELECT DISTINCT pg.id AS id, pg.name AS name
964
+ FROM profile_groups pg
965
+ JOIN profile_group_members m ON m.profile_group_id = pg.id
966
+ WHERE m.profile_id = ?`
967
+ ).all(profileId);
968
+ }
969
+ function replaceMembers(db, profileGroupId, newMembers) {
970
+ const tx = db.raw.transaction(() => {
971
+ db.raw.run("DELETE FROM profile_group_members WHERE profile_group_id = ?", [profileGroupId]);
972
+ const stmt = db.raw.query(
973
+ `INSERT INTO profile_group_members
974
+ (profile_group_id, position, profile_id, agent_name, model_override, reasoning_level)
975
+ VALUES (?, ?, ?, ?, ?, ?)`
976
+ );
977
+ for (let i = 0; i < newMembers.length; i++) {
978
+ const m = newMembers[i];
979
+ if (!m) continue;
980
+ stmt.run(
981
+ profileGroupId,
982
+ i,
983
+ m.profileId,
984
+ m.agentName,
985
+ m.modelOverride ?? null,
986
+ m.reasoningLevel ?? null
987
+ );
988
+ }
989
+ });
990
+ tx();
991
+ }
992
+
993
+ // ../daemon/src/core/profile/delete.ts
854
994
  function deleteProfile(db, id) {
855
995
  const profile = get3(db, id);
856
996
  if (!profile) throw new Error(`profile not found: ${id}`);
@@ -861,6 +1001,13 @@ function deleteProfile(db, id) {
861
1001
  `cannot delete profile "${id}": ${agents.length} agent(s) still reference it: ${names}. Delete or re-profile them first.`
862
1002
  );
863
1003
  }
1004
+ const refGroups = findReferencingProfile(db, id);
1005
+ if (refGroups.length > 0) {
1006
+ const names = refGroups.map((g) => `${g.name} (${g.id})`).join(", ");
1007
+ throw new Error(
1008
+ `cannot delete profile "${id}": ${refGroups.length} profile group(s) still reference it: ${names}. Remove the member(s) first.`
1009
+ );
1010
+ }
864
1011
  remove3(db, id);
865
1012
  if (existsSync5(profile.dir)) {
866
1013
  rmSync2(profile.dir, { recursive: true, force: true });
@@ -900,9 +1047,157 @@ function updateProfile(db, paths, id, input) {
900
1047
  return updated;
901
1048
  }
902
1049
 
1050
+ // ../daemon/src/core/profile-group/spawn.ts
1051
+ import { readdirSync as readdirSync3, rmSync as rmSync4 } from "fs";
1052
+
1053
+ // ../daemon/src/core/profile-group/rm-with-retry.ts
1054
+ import { rmSync as rmSync3 } from "fs";
1055
+ var DEFAULT_RM_RETRY_DELAYS_MS = [100, 500, 2e3];
1056
+ async function rmWithRetry(target, opts = {}) {
1057
+ const rm = opts.rm ?? ((p) => rmSync3(p, { recursive: true, force: true }));
1058
+ const delays = opts.delays ?? DEFAULT_RM_RETRY_DELAYS_MS;
1059
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
1060
+ for (let attempt = 0; attempt <= delays.length; attempt++) {
1061
+ try {
1062
+ rm(target);
1063
+ return true;
1064
+ } catch {
1065
+ if (attempt === delays.length) return false;
1066
+ const delay = delays[attempt] ?? 0;
1067
+ await sleep(delay);
1068
+ }
1069
+ }
1070
+ return false;
1071
+ }
1072
+
1073
+ // ../daemon/src/core/profile-group/spawn.ts
1074
+ var SpawnProfileGroupError = class extends Error {
1075
+ name = "SpawnProfileGroupError";
1076
+ orphanAgentIds;
1077
+ cause;
1078
+ constructor(message, orphanAgentIds, cause) {
1079
+ super(message);
1080
+ this.orphanAgentIds = orphanAgentIds;
1081
+ this.cause = cause;
1082
+ }
1083
+ };
1084
+ function resolveMemberNames(existing, members2) {
1085
+ const taken = new Set(existing);
1086
+ const out = [];
1087
+ for (const m of members2) {
1088
+ let candidate = m.agentName;
1089
+ let n = 2;
1090
+ while (taken.has(candidate)) {
1091
+ candidate = `${m.agentName}-${n}`;
1092
+ n++;
1093
+ }
1094
+ taken.add(candidate);
1095
+ out.push(candidate);
1096
+ }
1097
+ return out;
1098
+ }
1099
+ async function spawnProfileGroup(db, paths, input) {
1100
+ const template = get4(db, input.profileGroupId);
1101
+ if (!template) {
1102
+ throw new Error(`profile group not found: ${input.profileGroupId}`);
1103
+ }
1104
+ const members2 = members(db, input.profileGroupId);
1105
+ const missing = [];
1106
+ for (const m of members2) {
1107
+ if (!get3(db, m.profileId)) missing.push(m.profileId);
1108
+ }
1109
+ if (missing.length > 0) {
1110
+ throw new Error(`profile group spawn: missing profiles: ${missing.join(", ")}`);
1111
+ }
1112
+ const targetSlug = input.groupSlug ?? DEFAULT_GROUP_ID;
1113
+ const targetGroupExists = !!get2(db, targetSlug, paths);
1114
+ const existingNames = new Set(
1115
+ targetGroupExists ? db.raw.query("SELECT name FROM agents WHERE group_id = ?").all(targetSlug).map((r) => r.name) : []
1116
+ );
1117
+ const resolvedNames = resolveMemberNames(existingNames, members2);
1118
+ const beforeAgentDirs = new Set(safeReaddir(paths.agentsDir));
1119
+ const beforeGroupDirs = new Set(safeReaddir(paths.groupsDir));
1120
+ let groupCreated = false;
1121
+ const created = [];
1122
+ try {
1123
+ inTx(db, () => {
1124
+ if (!targetGroupExists) {
1125
+ registerGroup(db, { id: targetSlug, name: targetSlug }, paths);
1126
+ groupCreated = true;
1127
+ }
1128
+ const seedUserMd = input.userMd ?? template.userMd ?? null;
1129
+ if (groupCreated && seedUserMd) {
1130
+ setUserMd(db, targetSlug, seedUserMd);
1131
+ }
1132
+ for (let i = 0; i < members2.length; i++) {
1133
+ const member = members2[i];
1134
+ const name = resolvedNames[i];
1135
+ if (!member || !name) continue;
1136
+ const agent = spawnAgent(db, paths, {
1137
+ profileId: member.profileId,
1138
+ name,
1139
+ modelOverride: member.modelOverride,
1140
+ reasoningLevel: member.reasoningLevel ?? "medium",
1141
+ groupId: targetSlug
1142
+ });
1143
+ created.push({ id: agent.id, name: agent.name });
1144
+ }
1145
+ });
1146
+ return { groupSlug: targetSlug, agents: created, orphanAgentIds: [] };
1147
+ } catch (err) {
1148
+ const newAgentDirs = safeReaddir(paths.agentsDir).filter((d) => !beforeAgentDirs.has(d));
1149
+ const orphans = [];
1150
+ for (const dir of newAgentDirs) {
1151
+ if (!await rmWithRetry(paths.agentDir(dir))) {
1152
+ orphans.push(dir);
1153
+ }
1154
+ }
1155
+ const newGroupDirs = safeReaddir(paths.groupsDir).filter((d) => !beforeGroupDirs.has(d));
1156
+ for (const slug of newGroupDirs) {
1157
+ try {
1158
+ rmSync4(paths.groupDir(slug), { recursive: true, force: true });
1159
+ } catch (cleanupErr) {
1160
+ console.error(`spawnProfileGroup: failed to clean up group dir ${slug}`, cleanupErr);
1161
+ }
1162
+ }
1163
+ for (const id of orphans) {
1164
+ console.error(`spawnProfileGroup: orphan agent dir left on disk: ${paths.agentDir(id)}`);
1165
+ }
1166
+ const original = err instanceof Error ? err.message : String(err);
1167
+ const message = orphans.length > 0 ? `${original} (orphan agent dirs: ${orphans.join(", ")})` : original;
1168
+ throw new SpawnProfileGroupError(message, orphans, err);
1169
+ }
1170
+ }
1171
+ function safeReaddir(dir) {
1172
+ try {
1173
+ return readdirSync3(dir);
1174
+ } catch {
1175
+ return [];
1176
+ }
1177
+ }
1178
+
903
1179
  // ../daemon/src/core/services.ts
904
1180
  var SERVICES = [
905
1181
  // --- LLM providers (configured via API keys / URLs) ---
1182
+ // Top 3: openai-codex (ChatGPT OAuth), openai (API key), anthropic.
1183
+ // Everything else in rough popularity order; locals last.
1184
+ {
1185
+ id: "openai-codex",
1186
+ displayName: "OpenAI ChatGPT (OAuth)",
1187
+ category: "provider",
1188
+ hint: "Use your ChatGPT Plus/Pro/Team account (same login as Codex CLI)",
1189
+ // No form fields — credentials come from an OAuth flow. The /config page
1190
+ // renders a Connect/Disconnect card using /api/auth/openai instead of the
1191
+ // standard field inputs.
1192
+ fields: []
1193
+ },
1194
+ {
1195
+ id: "openai",
1196
+ displayName: "OpenAI",
1197
+ category: "provider",
1198
+ hint: "GPT models \xB7 platform.openai.com",
1199
+ fields: [{ envVar: "OPENAI_API_KEY", kind: "secret", label: "API key", placeholder: "sk-..." }]
1200
+ },
906
1201
  {
907
1202
  id: "anthropic",
908
1203
  displayName: "Anthropic",
@@ -918,23 +1213,6 @@ var SERVICES = [
918
1213
  }
919
1214
  ]
920
1215
  },
921
- {
922
- id: "openai",
923
- displayName: "OpenAI",
924
- category: "provider",
925
- hint: "GPT models \xB7 platform.openai.com",
926
- fields: [{ envVar: "OPENAI_API_KEY", kind: "secret", label: "API key", placeholder: "sk-..." }]
927
- },
928
- {
929
- id: "openai-codex",
930
- displayName: "OpenAI ChatGPT (OAuth)",
931
- category: "provider",
932
- hint: "Use your ChatGPT Plus/Pro/Team account (same login as Codex CLI)",
933
- // No form fields — credentials come from an OAuth flow. The /config page
934
- // renders a Connect/Disconnect card using /api/auth/openai instead of the
935
- // standard field inputs.
936
- fields: []
937
- },
938
1216
  {
939
1217
  id: "google",
940
1218
  displayName: "Google (Gemini)",
@@ -977,6 +1255,27 @@ var SERVICES = [
977
1255
  hint: "Authenticates via AWS SDK env (AWS_PROFILE or AWS_ACCESS_KEY_ID/SECRET)",
978
1256
  fields: []
979
1257
  },
1258
+ {
1259
+ id: "github-copilot",
1260
+ displayName: "GitHub Copilot",
1261
+ category: "provider",
1262
+ hint: "Use a GitHub Copilot subscription to call Claude/GPT/Gemini via Copilot",
1263
+ fields: [
1264
+ {
1265
+ envVar: "COPILOT_GITHUB_TOKEN",
1266
+ kind: "secret",
1267
+ label: "GitHub token",
1268
+ description: "Generic GH_TOKEN/GITHUB_TOKEN are ignored \u2014 set this scoped variable explicitly (or run `bazilion auth copilot login` once available)."
1269
+ }
1270
+ ]
1271
+ },
1272
+ {
1273
+ id: "deepseek",
1274
+ displayName: "DeepSeek",
1275
+ category: "provider",
1276
+ hint: "DeepSeek V4 Flash / Pro \xB7 platform.deepseek.com",
1277
+ fields: [{ envVar: "DEEPSEEK_API_KEY", kind: "secret", label: "API key" }]
1278
+ },
980
1279
  {
981
1280
  id: "mistral",
982
1281
  displayName: "Mistral",
@@ -984,6 +1283,13 @@ var SERVICES = [
984
1283
  hint: "mistral.ai",
985
1284
  fields: [{ envVar: "MISTRAL_API_KEY", kind: "secret", label: "API key" }]
986
1285
  },
1286
+ {
1287
+ id: "xai",
1288
+ displayName: "xAI",
1289
+ category: "provider",
1290
+ hint: "Grok \xB7 x.ai",
1291
+ fields: [{ envVar: "XAI_API_KEY", kind: "secret", label: "API key" }]
1292
+ },
987
1293
  {
988
1294
  id: "groq",
989
1295
  displayName: "Groq",
@@ -998,11 +1304,46 @@ var SERVICES = [
998
1304
  fields: [{ envVar: "CEREBRAS_API_KEY", kind: "secret", label: "API key" }]
999
1305
  },
1000
1306
  {
1001
- id: "xai",
1002
- displayName: "xAI",
1307
+ id: "fireworks",
1308
+ displayName: "Fireworks AI",
1003
1309
  category: "provider",
1004
- hint: "Grok \xB7 x.ai",
1005
- fields: [{ envVar: "XAI_API_KEY", kind: "secret", label: "API key" }]
1310
+ hint: "DeepSeek/GLM/Kimi via fireworks.ai",
1311
+ fields: [{ envVar: "FIREWORKS_API_KEY", kind: "secret", label: "API key" }]
1312
+ },
1313
+ {
1314
+ id: "together",
1315
+ displayName: "Together AI",
1316
+ category: "provider",
1317
+ hint: "Open-weight models \xB7 together.ai",
1318
+ fields: [{ envVar: "TOGETHER_API_KEY", kind: "secret", label: "API key" }]
1319
+ },
1320
+ {
1321
+ id: "moonshotai",
1322
+ displayName: "Moonshot AI",
1323
+ category: "provider",
1324
+ hint: "Kimi K2/K2.5/K2.6 \xB7 platform.moonshot.ai",
1325
+ fields: [{ envVar: "MOONSHOT_API_KEY", kind: "secret", label: "API key" }]
1326
+ },
1327
+ {
1328
+ id: "kimi-coding",
1329
+ displayName: "Kimi Coding",
1330
+ category: "provider",
1331
+ hint: "Coding-tuned Kimi endpoint \xB7 platform.moonshot.cn",
1332
+ fields: [{ envVar: "KIMI_API_KEY", kind: "secret", label: "API key" }]
1333
+ },
1334
+ {
1335
+ id: "minimax",
1336
+ displayName: "MiniMax",
1337
+ category: "provider",
1338
+ hint: "MiniMax M2 family \xB7 platform.minimaxi.com",
1339
+ fields: [{ envVar: "MINIMAX_API_KEY", kind: "secret", label: "API key" }]
1340
+ },
1341
+ {
1342
+ id: "xiaomi",
1343
+ displayName: "Xiaomi MiMo",
1344
+ category: "provider",
1345
+ hint: "API billing endpoint \xB7 platform.xiaomimimo.com",
1346
+ fields: [{ envVar: "XIAOMI_API_KEY", kind: "secret", label: "API key" }]
1006
1347
  },
1007
1348
  {
1008
1349
  id: "zai",
@@ -1017,6 +1358,27 @@ var SERVICES = [
1017
1358
  hint: "Inference endpoints \xB7 huggingface.co",
1018
1359
  fields: [{ envVar: "HF_TOKEN", kind: "secret", label: "Access token", placeholder: "hf_..." }]
1019
1360
  },
1361
+ {
1362
+ id: "cloudflare-ai-gateway",
1363
+ displayName: "Cloudflare AI Gateway",
1364
+ category: "provider",
1365
+ hint: "Per-gateway routing to OpenAI/Anthropic/Workers AI",
1366
+ fields: [
1367
+ { envVar: "CLOUDFLARE_API_KEY", kind: "secret", label: "API key" },
1368
+ { envVar: "CLOUDFLARE_ACCOUNT_ID", kind: "config", label: "Account ID" },
1369
+ { envVar: "CLOUDFLARE_GATEWAY_ID", kind: "config", label: "Gateway ID" }
1370
+ ]
1371
+ },
1372
+ {
1373
+ id: "cloudflare-workers-ai",
1374
+ displayName: "Cloudflare Workers AI",
1375
+ category: "provider",
1376
+ hint: "Inference on Cloudflare Workers \xB7 ai.cloudflare.com",
1377
+ fields: [
1378
+ { envVar: "CLOUDFLARE_API_KEY", kind: "secret", label: "API key" },
1379
+ { envVar: "CLOUDFLARE_ACCOUNT_ID", kind: "config", label: "Account ID" }
1380
+ ]
1381
+ },
1020
1382
  {
1021
1383
  id: "openrouter",
1022
1384
  displayName: "OpenRouter",
@@ -1040,6 +1402,13 @@ var SERVICES = [
1040
1402
  }
1041
1403
  ]
1042
1404
  },
1405
+ {
1406
+ id: "opencode",
1407
+ displayName: "OpenCode",
1408
+ category: "provider",
1409
+ hint: "OpenAI-compatible proxy from the OpenCode CLI",
1410
+ fields: [{ envVar: "OPENCODE_API_KEY", kind: "secret", label: "API key" }]
1411
+ },
1043
1412
  {
1044
1413
  id: "lmstudio",
1045
1414
  displayName: "LM Studio",
@@ -1078,11 +1447,54 @@ var SERVICES = [
1078
1447
  }
1079
1448
  ]
1080
1449
  },
1450
+ {
1451
+ id: "llamacpp",
1452
+ displayName: "llama.cpp",
1453
+ category: "provider",
1454
+ hint: "Local inference \xB7 llama.cpp llama-server (OpenAI-compat /v1 endpoint)",
1455
+ fields: [
1456
+ {
1457
+ envVar: "LLAMACPP_URL",
1458
+ kind: "config",
1459
+ label: "Endpoint URL",
1460
+ placeholder: "http://127.0.0.1:8080/v1"
1461
+ },
1462
+ {
1463
+ envVar: "LLAMACPP_API_KEY",
1464
+ kind: "secret",
1465
+ label: "API key (only if started with --api-key)",
1466
+ description: "llama-server runs without auth by default. Set this only if you launched the server with the `--api-key KEY` flag."
1467
+ }
1468
+ ]
1469
+ },
1081
1470
  // --- Ancillary services (web search, etc) ---
1471
+ {
1472
+ id: "firecrawl",
1473
+ displayName: "Firecrawl",
1474
+ category: "service",
1475
+ group: "Web Search",
1476
+ hint: "web_fetch fallback for JS-heavy/blocked pages \xB7 firecrawl.dev (free tier available)",
1477
+ fields: [
1478
+ {
1479
+ envVar: "FIRECRAWL_API_KEY",
1480
+ kind: "secret",
1481
+ label: "API key",
1482
+ placeholder: "fc-...",
1483
+ description: "When set, web_fetch automatically falls back to Firecrawl if the primary Readability extraction yields too little content."
1484
+ },
1485
+ {
1486
+ envVar: "FIRECRAWL_URL",
1487
+ kind: "config",
1488
+ label: "Base URL (optional, for self-hosted)",
1489
+ placeholder: "https://api.firecrawl.dev"
1490
+ }
1491
+ ]
1492
+ },
1082
1493
  {
1083
1494
  id: "brave-search",
1084
1495
  displayName: "Brave Search",
1085
1496
  category: "service",
1497
+ group: "Web Search",
1086
1498
  hint: "Web search tool \xB7 free tier at brave.com/search/api/",
1087
1499
  fields: [{ envVar: "BRAVE_API_KEY", kind: "secret", label: "API key", placeholder: "BSA..." }]
1088
1500
  },
@@ -1090,6 +1502,7 @@ var SERVICES = [
1090
1502
  id: "searxng",
1091
1503
  displayName: "SearXNG",
1092
1504
  category: "service",
1505
+ group: "Web Search",
1093
1506
  hint: "Self-hosted meta-search engine \xB7 searxng.org",
1094
1507
  fields: [
1095
1508
  {
@@ -1164,7 +1577,7 @@ var messages_exports = {};
1164
1577
  __export(messages_exports, {
1165
1578
  drainUnreadForAgent: () => drainUnreadForAgent,
1166
1579
  findReplies: () => findReplies,
1167
- get: () => get4,
1580
+ get: () => get5,
1168
1581
  listInbox: () => listInbox,
1169
1582
  listRecipientsWithUnread: () => listRecipientsWithUnread,
1170
1583
  markRead: () => markRead,
@@ -1200,7 +1613,7 @@ function send(db, input) {
1200
1613
  readAt: null
1201
1614
  };
1202
1615
  }
1203
- function get4(db, id) {
1616
+ function get5(db, id) {
1204
1617
  const row = db.raw.query("SELECT * FROM messages WHERE id = ?").get(id);
1205
1618
  return row ? toMessage(row) : null;
1206
1619
  }
@@ -1338,9 +1751,9 @@ function openSecrets(db, password) {
1338
1751
  // ../daemon/src/core/repos/skillMeta.ts
1339
1752
  var skillMeta_exports = {};
1340
1753
  __export(skillMeta_exports, {
1341
- get: () => get5,
1754
+ get: () => get6,
1342
1755
  listAll: () => listAll2,
1343
- remove: () => remove5,
1756
+ remove: () => remove6,
1344
1757
  upsert: () => upsert
1345
1758
  });
1346
1759
  function toMeta(r) {
@@ -1350,7 +1763,7 @@ function toMeta(r) {
1350
1763
  importedAt: r.imported_at
1351
1764
  };
1352
1765
  }
1353
- function get5(db, name) {
1766
+ function get6(db, name) {
1354
1767
  const row = db.raw.query("SELECT * FROM skill_meta WHERE name = ?").get(name);
1355
1768
  return row ? toMeta(row) : null;
1356
1769
  }
@@ -1358,7 +1771,7 @@ function listAll2(db) {
1358
1771
  return db.raw.query("SELECT * FROM skill_meta ORDER BY name ASC").all().map(toMeta);
1359
1772
  }
1360
1773
  function upsert(db, input) {
1361
- const existing = get5(db, input.name);
1774
+ const existing = get6(db, input.name);
1362
1775
  const source = input.source !== void 0 ? input.source : existing?.source ?? null;
1363
1776
  const importedAt = input.importedAt !== void 0 ? input.importedAt : existing?.importedAt ?? null;
1364
1777
  db.raw.run(
@@ -1369,19 +1782,19 @@ function upsert(db, input) {
1369
1782
  );
1370
1783
  return { name: input.name, source, importedAt };
1371
1784
  }
1372
- function remove5(db, name) {
1785
+ function remove6(db, name) {
1373
1786
  db.raw.run("DELETE FROM skill_meta WHERE name = ?", [name]);
1374
1787
  }
1375
1788
 
1376
1789
  // ../daemon/src/core/repos/triggers.ts
1377
1790
  var triggers_exports = {};
1378
1791
  __export(triggers_exports, {
1379
- get: () => get6,
1380
- insert: () => insert4,
1792
+ get: () => get7,
1793
+ insert: () => insert5,
1381
1794
  listEnabled: () => listEnabled2,
1382
1795
  listForAgent: () => listForAgent,
1383
1796
  markFired: () => markFired,
1384
- remove: () => remove6,
1797
+ remove: () => remove7,
1385
1798
  setEnabled: () => setEnabled2
1386
1799
  });
1387
1800
  import { randomUUID as randomUUID3 } from "crypto";
@@ -1398,7 +1811,7 @@ function toTrigger(r) {
1398
1811
  createdAt: r.created_at
1399
1812
  };
1400
1813
  }
1401
- function insert4(db, input) {
1814
+ function insert5(db, input) {
1402
1815
  const id = randomUUID3();
1403
1816
  const now = Date.now();
1404
1817
  const enabled = input.enabled === false ? 0 : 1;
@@ -1420,7 +1833,7 @@ function insert4(db, input) {
1420
1833
  createdAt: now
1421
1834
  };
1422
1835
  }
1423
- function get6(db, id) {
1836
+ function get7(db, id) {
1424
1837
  const row = db.raw.query("SELECT * FROM agent_triggers WHERE id = ?").get(id);
1425
1838
  return row ? toTrigger(row) : null;
1426
1839
  }
@@ -1443,7 +1856,7 @@ function setEnabled2(db, id, enabled) {
1443
1856
  function markFired(db, id, when = Date.now()) {
1444
1857
  db.raw.run("UPDATE agent_triggers SET last_fired_at = ? WHERE id = ?", [when, id]);
1445
1858
  }
1446
- function remove6(db, id) {
1859
+ function remove7(db, id) {
1447
1860
  db.raw.run("DELETE FROM agent_triggers WHERE id = ?", [id]);
1448
1861
  }
1449
1862
 
@@ -1452,9 +1865,9 @@ var webTokens_exports = {};
1452
1865
  __export(webTokens_exports, {
1453
1866
  create: () => create,
1454
1867
  findActiveByToken: () => findActiveByToken,
1455
- get: () => get7,
1868
+ get: () => get8,
1456
1869
  hashToken: () => hashToken,
1457
- list: () => list5,
1870
+ list: () => list6,
1458
1871
  markUsed: () => markUsed,
1459
1872
  revoke: () => revoke
1460
1873
  });
@@ -1486,11 +1899,11 @@ function create(db, label) {
1486
1899
  meta: { id, label, createdAt: now, lastUsedAt: null, revokedAt: null }
1487
1900
  };
1488
1901
  }
1489
- function list5(db, opts) {
1902
+ function list6(db, opts) {
1490
1903
  const sql = opts?.includeRevoked ? "SELECT * FROM web_tokens ORDER BY created_at ASC" : "SELECT * FROM web_tokens WHERE revoked_at IS NULL ORDER BY created_at ASC";
1491
1904
  return db.raw.query(sql).all().map(toToken);
1492
1905
  }
1493
- function get7(db, id) {
1906
+ function get8(db, id) {
1494
1907
  const row = db.raw.query("SELECT * FROM web_tokens WHERE id = ?").get(id);
1495
1908
  return row ? toToken(row) : null;
1496
1909
  }
@@ -1516,7 +1929,9 @@ function revoke(db, id, when = Date.now()) {
1516
1929
  import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
1517
1930
  function readAuthFile(authFile) {
1518
1931
  if (!existsSync6(authFile)) {
1519
- throw new Error(`${authFile} not found. Start the daemon (\`bazilion serve\`) \u2014 it auto-bootstraps on first run.`);
1932
+ throw new Error(
1933
+ `${authFile} not found. Start the daemon (\`bazilion serve\`) \u2014 it auto-bootstraps on first run.`
1934
+ );
1520
1935
  }
1521
1936
  const raw = readFileSync4(authFile, "utf8");
1522
1937
  const parsed = JSON.parse(raw);
@@ -1543,7 +1958,7 @@ function mergeSecretsIntoEnv(db, password, env = process.env) {
1543
1958
  }
1544
1959
 
1545
1960
  // ../daemon/src/core/skills/import.ts
1546
- import { cpSync, existsSync as existsSync7, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync2 } from "fs";
1961
+ import { cpSync, existsSync as existsSync7, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync5, statSync as statSync2 } from "fs";
1547
1962
  import { tmpdir } from "os";
1548
1963
  import { basename, join as join8, resolve as resolve2, sep } from "path";
1549
1964
  import AdmZip from "adm-zip";
@@ -1599,11 +2014,11 @@ function extractZipSafely(zipPath) {
1599
2014
  }
1600
2015
  zip.extractAllTo(root, true);
1601
2016
  } catch (err) {
1602
- rmSync3(root, { recursive: true, force: true });
2017
+ rmSync5(root, { recursive: true, force: true });
1603
2018
  throw err;
1604
2019
  }
1605
2020
  let effectiveSource = root;
1606
- const topEntries = readdirSync3(root, { withFileTypes: true });
2021
+ const topEntries = readdirSync4(root, { withFileTypes: true });
1607
2022
  if (topEntries.length === 1 && topEntries[0]?.isDirectory()) {
1608
2023
  effectiveSource = join8(root, topEntries[0].name);
1609
2024
  }
@@ -1630,7 +2045,7 @@ function importSkills(paths, input) {
1630
2045
  try {
1631
2046
  return importSkillsFromDir(paths, source, input);
1632
2047
  } finally {
1633
- if (tempRoot) rmSync3(tempRoot, { recursive: true, force: true });
2048
+ if (tempRoot) rmSync5(tempRoot, { recursive: true, force: true });
1634
2049
  }
1635
2050
  }
1636
2051
  function importSkillsFromDir(paths, source, input) {
@@ -1638,7 +2053,7 @@ function importSkillsFromDir(paths, source, input) {
1638
2053
  if (existsSync7(join8(source, "SKILL.md"))) {
1639
2054
  candidates.push({ name: basename(source), dir: source });
1640
2055
  } else {
1641
- const entries = readdirSync3(source, { withFileTypes: true });
2056
+ const entries = readdirSync4(source, { withFileTypes: true });
1642
2057
  for (const e of entries) {
1643
2058
  if (!e.isDirectory()) continue;
1644
2059
  const skillDir = join8(source, e.name);
@@ -1724,7 +2139,7 @@ function isActiveAgent(agentId) {
1724
2139
  }
1725
2140
 
1726
2141
  // ../daemon/src/runtime/auth/openai-codex.ts
1727
- import { loginOpenAICodex, refreshOpenAICodexToken } from "@mariozechner/pi-ai/oauth";
2142
+ import { loginOpenAICodex, refreshOpenAICodexToken } from "@earendil-works/pi-ai/oauth";
1728
2143
  var OPENAI_CODEX_SECRET_KEY = "OPENAI_CODEX_OAUTH";
1729
2144
  var REFRESH_MARGIN_MS = 6e4;
1730
2145
  function readCredentials(db, authToken) {
@@ -1800,9 +2215,9 @@ var DEFAULT_HEARTBEAT_EVERY_SEC = 30 * 60;
1800
2215
  import {
1801
2216
  existsSync as existsSync8,
1802
2217
  mkdirSync as mkdirSync4,
1803
- readdirSync as readdirSync4,
2218
+ readdirSync as readdirSync5,
1804
2219
  readFileSync as readFileSync6,
1805
- rmSync as rmSync4,
2220
+ rmSync as rmSync6,
1806
2221
  statSync as statSync3,
1807
2222
  writeFileSync as writeFileSync4
1808
2223
  } from "fs";
@@ -1812,9 +2227,9 @@ import { dirname as dirname2, join as join9 } from "path";
1812
2227
  import {
1813
2228
  existsSync as existsSync9,
1814
2229
  mkdirSync as mkdirSync5,
1815
- readdirSync as readdirSync5,
2230
+ readdirSync as readdirSync6,
1816
2231
  readFileSync as readFileSync7,
1817
- rmSync as rmSync5,
2232
+ rmSync as rmSync7,
1818
2233
  statSync as statSync4,
1819
2234
  writeFileSync as writeFileSync5
1820
2235
  } from "fs";
@@ -1847,7 +2262,7 @@ function safeKey(root, key) {
1847
2262
  }
1848
2263
  function walkMd(dir, prefix, out) {
1849
2264
  if (!existsSync9(dir)) return;
1850
- for (const e of readdirSync5(dir, { withFileTypes: true })) {
2265
+ for (const e of readdirSync6(dir, { withFileTypes: true })) {
1851
2266
  if (e.name.startsWith(".")) continue;
1852
2267
  const full = join10(dir, e.name);
1853
2268
  const key = prefix ? `${prefix}/${e.name}` : e.name;
@@ -1922,7 +2337,7 @@ function qmdBackend(root) {
1922
2337
  },
1923
2338
  async remove(key) {
1924
2339
  const path = safeKey(root, key);
1925
- if (existsSync9(path)) rmSync5(path);
2340
+ if (existsSync9(path)) rmSync7(path);
1926
2341
  const store = await getStore(root);
1927
2342
  await store.update();
1928
2343
  }
@@ -1990,7 +2405,7 @@ function piMessagesToProviderView(messages) {
1990
2405
  }
1991
2406
 
1992
2407
  // ../daemon/src/runtime/pi/session.ts
1993
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync7, statSync as statSync6 } from "fs";
2408
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync8, statSync as statSync6 } from "fs";
1994
2409
  import { basename as basename2, join as join14 } from "path";
1995
2410
  import {
1996
2411
  AuthStorage,
@@ -1999,14 +2414,14 @@ import {
1999
2414
  ModelRegistry,
2000
2415
  SessionManager,
2001
2416
  SettingsManager
2002
- } from "@mariozechner/pi-coding-agent";
2417
+ } from "@earendil-works/pi-coding-agent";
2003
2418
 
2004
2419
  // ../daemon/src/runtime/providers/pi-adapter.ts
2005
2420
  import {
2006
2421
  getModel,
2007
2422
  streamSimple,
2008
2423
  Type
2009
- } from "@mariozechner/pi-ai";
2424
+ } from "@earendil-works/pi-ai";
2010
2425
  function defaultBaseUrlFor(providerName) {
2011
2426
  switch (providerName) {
2012
2427
  case "lmstudio":
@@ -2343,6 +2758,10 @@ function loadProviderConfigFromEnv(env = process.env, oauth) {
2343
2758
  ollama: {
2344
2759
  ...env.OLLAMA_URL !== void 0 ? { baseURL: env.OLLAMA_URL } : {},
2345
2760
  ...env.OLLAMA_API_KEY !== void 0 ? { apiKey: env.OLLAMA_API_KEY } : {}
2761
+ },
2762
+ llamacpp: {
2763
+ ...env.LLAMACPP_URL !== void 0 ? { baseURL: env.LLAMACPP_URL } : {},
2764
+ ...env.LLAMACPP_API_KEY !== void 0 ? { apiKey: env.LLAMACPP_API_KEY } : {}
2346
2765
  }
2347
2766
  };
2348
2767
  if (env.ANTHROPIC_API_KEY || env.ANTHROPIC_OAUTH_TOKEN) {
@@ -2365,6 +2784,28 @@ function loadProviderConfigFromEnv(env = process.env, oauth) {
2365
2784
  if (env.HF_TOKEN) config.huggingface = { apiKey: env.HF_TOKEN };
2366
2785
  if (env.OPENROUTER_API_KEY) config.openrouter = { apiKey: env.OPENROUTER_API_KEY };
2367
2786
  if (env.AI_GATEWAY_API_KEY) config.vercelAiGateway = { apiKey: env.AI_GATEWAY_API_KEY };
2787
+ if (env.DEEPSEEK_API_KEY) config.deepseek = { apiKey: env.DEEPSEEK_API_KEY };
2788
+ if (env.FIREWORKS_API_KEY) config.fireworks = { apiKey: env.FIREWORKS_API_KEY };
2789
+ if (env.TOGETHER_API_KEY) config.together = { apiKey: env.TOGETHER_API_KEY };
2790
+ if (env.MOONSHOT_API_KEY) config.moonshotai = { apiKey: env.MOONSHOT_API_KEY };
2791
+ if (env.KIMI_API_KEY) config.kimiCoding = { apiKey: env.KIMI_API_KEY };
2792
+ if (env.MINIMAX_API_KEY) config.minimax = { apiKey: env.MINIMAX_API_KEY };
2793
+ if (env.XIAOMI_API_KEY) config.xiaomi = { apiKey: env.XIAOMI_API_KEY };
2794
+ if (env.OPENCODE_API_KEY) config.opencode = { apiKey: env.OPENCODE_API_KEY };
2795
+ if (env.COPILOT_GITHUB_TOKEN) config.githubCopilot = { apiKey: env.COPILOT_GITHUB_TOKEN };
2796
+ if (env.CLOUDFLARE_API_KEY && env.CLOUDFLARE_ACCOUNT_ID) {
2797
+ config.cloudflareWorkersAi = {
2798
+ apiKey: env.CLOUDFLARE_API_KEY,
2799
+ accountId: env.CLOUDFLARE_ACCOUNT_ID
2800
+ };
2801
+ if (env.CLOUDFLARE_GATEWAY_ID) {
2802
+ config.cloudflareAiGateway = {
2803
+ apiKey: env.CLOUDFLARE_API_KEY,
2804
+ accountId: env.CLOUDFLARE_ACCOUNT_ID,
2805
+ gatewayId: env.CLOUDFLARE_GATEWAY_ID
2806
+ };
2807
+ }
2808
+ }
2368
2809
  if (oauth && hasCredentials(oauth.db, oauth.authToken)) {
2369
2810
  config.openaiCodex = oauth;
2370
2811
  }
@@ -2523,6 +2964,113 @@ var PROVIDERS = {
2523
2964
  }),
2524
2965
  hint: "AI_GATEWAY_API_KEY"
2525
2966
  },
2967
+ deepseek: {
2968
+ configured: (c) => !!c.deepseek,
2969
+ build: (c) => piProvider({
2970
+ providerName: "deepseek",
2971
+ fallbackApi: "openai-completions",
2972
+ apiKey: c.deepseek?.apiKey,
2973
+ baseUrl: c.deepseek?.baseURL
2974
+ }),
2975
+ hint: "DEEPSEEK_API_KEY"
2976
+ },
2977
+ fireworks: {
2978
+ configured: (c) => !!c.fireworks,
2979
+ build: (c) => piProvider({
2980
+ providerName: "fireworks",
2981
+ fallbackApi: "anthropic-messages",
2982
+ apiKey: c.fireworks?.apiKey,
2983
+ baseUrl: c.fireworks?.baseURL
2984
+ }),
2985
+ hint: "FIREWORKS_API_KEY"
2986
+ },
2987
+ together: {
2988
+ configured: (c) => !!c.together,
2989
+ build: (c) => piProvider({
2990
+ providerName: "together",
2991
+ fallbackApi: "openai-completions",
2992
+ apiKey: c.together?.apiKey,
2993
+ baseUrl: c.together?.baseURL
2994
+ }),
2995
+ hint: "TOGETHER_API_KEY"
2996
+ },
2997
+ moonshotai: {
2998
+ configured: (c) => !!c.moonshotai,
2999
+ build: (c) => piProvider({
3000
+ providerName: "moonshotai",
3001
+ fallbackApi: "openai-completions",
3002
+ apiKey: c.moonshotai?.apiKey,
3003
+ baseUrl: c.moonshotai?.baseURL
3004
+ }),
3005
+ hint: "MOONSHOT_API_KEY"
3006
+ },
3007
+ "kimi-coding": {
3008
+ configured: (c) => !!c.kimiCoding,
3009
+ build: (c) => piProvider({
3010
+ providerName: "kimi-coding",
3011
+ fallbackApi: "anthropic-messages",
3012
+ apiKey: c.kimiCoding?.apiKey,
3013
+ baseUrl: c.kimiCoding?.baseURL
3014
+ }),
3015
+ hint: "KIMI_API_KEY"
3016
+ },
3017
+ minimax: {
3018
+ configured: (c) => !!c.minimax,
3019
+ build: (c) => piProvider({
3020
+ providerName: "minimax",
3021
+ fallbackApi: "anthropic-messages",
3022
+ apiKey: c.minimax?.apiKey,
3023
+ baseUrl: c.minimax?.baseURL
3024
+ }),
3025
+ hint: "MINIMAX_API_KEY"
3026
+ },
3027
+ xiaomi: {
3028
+ configured: (c) => !!c.xiaomi,
3029
+ build: (c) => piProvider({
3030
+ providerName: "xiaomi",
3031
+ fallbackApi: "openai-completions",
3032
+ apiKey: c.xiaomi?.apiKey,
3033
+ baseUrl: c.xiaomi?.baseURL
3034
+ }),
3035
+ hint: "XIAOMI_API_KEY"
3036
+ },
3037
+ opencode: {
3038
+ configured: (c) => !!c.opencode,
3039
+ build: (c) => piProvider({
3040
+ providerName: "opencode",
3041
+ fallbackApi: "openai-completions",
3042
+ apiKey: c.opencode?.apiKey,
3043
+ baseUrl: c.opencode?.baseURL
3044
+ }),
3045
+ hint: "OPENCODE_API_KEY"
3046
+ },
3047
+ "github-copilot": {
3048
+ configured: (c) => !!c.githubCopilot,
3049
+ build: (c) => piProvider({
3050
+ providerName: "github-copilot",
3051
+ fallbackApi: "anthropic-messages",
3052
+ apiKey: c.githubCopilot?.apiKey
3053
+ }),
3054
+ hint: "COPILOT_GITHUB_TOKEN (generic GH_TOKEN/GITHUB_TOKEN are ignored)"
3055
+ },
3056
+ "cloudflare-ai-gateway": {
3057
+ configured: (c) => !!c.cloudflareAiGateway,
3058
+ build: (c) => piProvider({
3059
+ providerName: "cloudflare-ai-gateway",
3060
+ fallbackApi: "anthropic-messages",
3061
+ apiKey: c.cloudflareAiGateway?.apiKey
3062
+ }),
3063
+ hint: "CLOUDFLARE_API_KEY + CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_GATEWAY_ID"
3064
+ },
3065
+ "cloudflare-workers-ai": {
3066
+ configured: (c) => !!c.cloudflareWorkersAi,
3067
+ build: (c) => piProvider({
3068
+ providerName: "cloudflare-workers-ai",
3069
+ fallbackApi: "openai-completions",
3070
+ apiKey: c.cloudflareWorkersAi?.apiKey
3071
+ }),
3072
+ hint: "CLOUDFLARE_API_KEY + CLOUDFLARE_ACCOUNT_ID"
3073
+ },
2526
3074
  lmstudio: {
2527
3075
  configured: () => true,
2528
3076
  build: (c) => piProvider({
@@ -2542,12 +3090,26 @@ var PROVIDERS = {
2542
3090
  baseUrl: c.ollama?.baseURL ?? "http://127.0.0.1:11434/v1"
2543
3091
  }),
2544
3092
  hint: "OLLAMA_URL (default http://127.0.0.1:11434/v1)"
3093
+ },
3094
+ llamacpp: {
3095
+ // Like lmstudio/ollama, always considered "configured" — the daemon
3096
+ // can't tell if llama-server is actually running until a request hits
3097
+ // it. Falls back to the documented default port + a placeholder
3098
+ // apiKey (llama-server ignores it unless --api-key was passed).
3099
+ configured: () => true,
3100
+ build: (c) => piProvider({
3101
+ providerName: "llamacpp",
3102
+ fallbackApi: "openai-completions",
3103
+ apiKey: c.llamacpp?.apiKey ?? "no-key",
3104
+ baseUrl: c.llamacpp?.baseURL ?? "http://127.0.0.1:8080/v1"
3105
+ }),
3106
+ hint: "LLAMACPP_URL (default http://127.0.0.1:8080/v1)"
2545
3107
  }
2546
3108
  };
2547
3109
  function createProviderRegistry(config, opts = {}) {
2548
3110
  const cache = /* @__PURE__ */ new Map();
2549
3111
  const enabledSet = opts.enabledSet;
2550
- function get8(name) {
3112
+ function get9(name) {
2551
3113
  const cached = cache.get(name);
2552
3114
  if (cached) return cached;
2553
3115
  const entry = PROVIDERS[name];
@@ -2579,7 +3141,7 @@ function createProviderRegistry(config, opts = {}) {
2579
3141
  }
2580
3142
  const providerName = modelString.slice(0, idx);
2581
3143
  const model = modelString.slice(idx + 1);
2582
- return { provider: get8(providerName), model };
3144
+ return { provider: get9(providerName), model };
2583
3145
  },
2584
3146
  list() {
2585
3147
  return Object.entries(PROVIDERS).filter(([name, entry]) => {
@@ -2606,13 +3168,11 @@ var CONTEXT_FILE_ORDER = [
2606
3168
  "SOUL.md",
2607
3169
  "TOOLS.md",
2608
3170
  "IDENTITY.md",
2609
- "HEARTBEAT.md",
2610
- "BOOTSTRAP.md"
3171
+ "HEARTBEAT.md"
2611
3172
  ];
2612
3173
  function buildSystemPrompt(agent) {
2613
3174
  const parts = [];
2614
3175
  const contextBlocks = [];
2615
- let bootstrapPresent = false;
2616
3176
  for (const file of CONTEXT_FILE_ORDER) {
2617
3177
  const path = join11(agent.agent.dir, file);
2618
3178
  if (!existsSync10(path)) continue;
@@ -2621,23 +3181,39 @@ function buildSystemPrompt(agent) {
2621
3181
  contextBlocks.push(`## ${file}
2622
3182
 
2623
3183
  ${content}`);
2624
- if (file === "BOOTSTRAP.md") bootstrapPresent = true;
2625
3184
  }
2626
3185
  if (contextBlocks.length > 0) {
2627
3186
  parts.push(`# Project Context
2628
3187
 
2629
3188
  ${contextBlocks.join("\n\n")}`);
2630
3189
  }
2631
- if (bootstrapPresent) {
2632
- parts.push(
2633
- "NOTE: This is your first session. After you have completed the bootstrap conversation, call the `bootstrap_done` tool to delete BOOTSTRAP.md."
2634
- );
3190
+ const bootstrapPath = join11(agent.agent.dir, "BOOTSTRAP.md");
3191
+ if (existsSync10(bootstrapPath)) {
3192
+ const bootstrap2 = readFileSync8(bootstrapPath, "utf8").trimEnd();
3193
+ if (bootstrap2) {
3194
+ parts.push(
3195
+ [
3196
+ "# First-Run Ritual",
3197
+ "",
3198
+ "This is your first session. The document below is **conversational guidance**, not a checklist to execute in one shot. It describes a multi-turn Q&A you should have with the human, one question per turn.",
3199
+ "",
3200
+ "## Hard rules",
3201
+ "- Your first reply is ONLY a greeting + ONE question. No tool calls. Wait for the human to answer.",
3202
+ "- Each subsequent turn: at most one new question. Wait between turns.",
3203
+ "- Only after the ritual is complete (you have enough to write IDENTITY.md): call `home_write` once, then `bootstrap_done`.",
3204
+ "",
3205
+ "## BOOTSTRAP.md",
3206
+ "",
3207
+ bootstrap2
3208
+ ].join("\n")
3209
+ );
3210
+ }
2635
3211
  }
2636
3212
  parts.push(
2637
3213
  [
2638
3214
  "# Agent Home",
2639
3215
  "",
2640
- "Your private home holds who you are \u2014 identity, soul, behaviour rules, wake-up routine. It is not shared with other agents and cannot be overwritten by them. The files above (IDENTITY.md, SOUL.md, AGENTS.md, TOOLS.md, HEARTBEAT.md, BOOTSTRAP.md) live in this home.",
3216
+ "Your private home holds who you are \u2014 identity, soul, behaviour rules, wake-up routine. It is not shared with other agents and cannot be overwritten by them. The files above (IDENTITY.md, SOUL.md, AGENTS.md, TOOLS.md, HEARTBEAT.md) live in this home, plus BOOTSTRAP.md when you are still in your first-run ritual.",
2641
3217
  "",
2642
3218
  "- To change who you are (name, vibe, personality, how you behave): use `home_write`.",
2643
3219
  "- To inspect exact wording of your own files: use `home_read` or `home_list`.",
@@ -2664,13 +3240,19 @@ You have access to the following skills: ${agent.skills.join(", ")}.`
2664
3240
  parts.push(
2665
3241
  `# About the User
2666
3242
 
2667
- Read-only context about the human you're working with in this group. You cannot edit this \u2014 if it's wrong, say so and they will update it.
3243
+ Shared context about the human you're working with in this group. Both you and the human curate it. To update: call \`user_md_get\` (returns current content + an etag), merge your change into the full text, then call \`user_md_write\` with the merged content and the etag. Use this for STABLE user-specific facts (preferences, role, working hours, how they like to be addressed) \u2014 and to CORRECT stale entries when the human tells you something different from what's recorded. For project knowledge use \`memory_write\` instead; for personal notes about yourself use \`home_write\` on IDENTITY.md. **Do NOT send peer messages announcing USER.md changes \u2014 every agent in the group sees the new content in their system prompt on their next turn automatically.**
2668
3244
 
2669
3245
  ${agent.group.userMd.trim()}`
2670
3246
  );
3247
+ } else {
3248
+ parts.push(
3249
+ `# About the User
3250
+
3251
+ This group's USER.md is empty. As you learn STABLE facts about the human (preferences, role, working hours, how they like to be addressed), populate it via \`user_md_get\` then \`user_md_write\` (always get first \u2014 you need the etag). Reserve this for things you're confident are durable \u2014 project knowledge belongs in \`memory_write\`, personal notes about yourself in \`home_write\` on IDENTITY.md. **Do NOT send peer messages announcing USER.md changes \u2014 every agent in the group sees the new content in their system prompt on their next turn automatically.**`
3252
+ );
2671
3253
  }
2672
3254
  parts.push(
2673
- "# Memory\n\nYou share a persistent memory backend with every other agent in this group. Use `memory_write` to remember things across sessions, and `memory_search` / `memory_read` / `memory_list` to recall them. This memory is for project knowledge \u2014 codebase notes, decisions, things the user told you about the work. For personal notes about yourself (preferences, persona quirks), use `home_write` on IDENTITY.md instead. Always check memory at the start of a session: another agent in the group may have already learned something useful."
3255
+ "# Memory\n\nYou share a persistent memory backend with every other agent in this group. Use `memory_write` to remember things across sessions, and `memory_search` / `memory_read` / `memory_list` to recall them. This memory is for project knowledge \u2014 codebase notes, decisions, things the user told you about the work. For personal notes about yourself (preferences, persona quirks), use `home_write` on IDENTITY.md instead. Always check memory at the start of a session: another agent in the group may have already learned something useful. **Do NOT send peer messages announcing memory writes \u2014 every agent has access to the same store via `memory_search` and will find your note when they need it.**"
2674
3256
  );
2675
3257
  return parts.join("\n\n---\n\n");
2676
3258
  }
@@ -2679,7 +3261,7 @@ ${agent.group.userMd.trim()}`
2679
3261
  import { Type as Type2 } from "typebox";
2680
3262
 
2681
3263
  // ../daemon/src/runtime/tools/bootstrap.ts
2682
- import { existsSync as existsSync11, rmSync as rmSync6 } from "fs";
3264
+ import { existsSync as existsSync11, rmSync as rmSync8 } from "fs";
2683
3265
  import { join as join12 } from "path";
2684
3266
  function bootstrapTool(agentDir) {
2685
3267
  return {
@@ -2691,7 +3273,7 @@ function bootstrapTool(agentDir) {
2691
3273
  async invoke() {
2692
3274
  const path = join12(agentDir, "BOOTSTRAP.md");
2693
3275
  if (existsSync11(path)) {
2694
- rmSync6(path);
3276
+ rmSync8(path);
2695
3277
  return "BOOTSTRAP.md removed. Bootstrap is complete.";
2696
3278
  }
2697
3279
  return "BOOTSTRAP.md was already removed.";
@@ -2700,7 +3282,7 @@ function bootstrapTool(agentDir) {
2700
3282
  }
2701
3283
 
2702
3284
  // ../daemon/src/runtime/tools/home.ts
2703
- import { readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync5, writeFileSync as writeFileSync6 } from "fs";
3285
+ import { readdirSync as readdirSync7, readFileSync as readFileSync9, statSync as statSync5, writeFileSync as writeFileSync6 } from "fs";
2704
3286
  import { join as join13 } from "path";
2705
3287
  var HOME_FILES_READABLE = [
2706
3288
  "IDENTITY.md",
@@ -2792,7 +3374,7 @@ function homeTools(agentDir) {
2792
3374
  if (entries.length === 0) {
2793
3375
  const dirEntries = (() => {
2794
3376
  try {
2795
- return readdirSync6(agentDir);
3377
+ return readdirSync7(agentDir);
2796
3378
  } catch {
2797
3379
  return [];
2798
3380
  }
@@ -2811,7 +3393,7 @@ function memoryTools(memory) {
2811
3393
  {
2812
3394
  def: {
2813
3395
  name: "memory_write",
2814
- description: 'Write or update a memory note in the GROUP-SHARED memory. All agents in this group can read what you write. Use it for project knowledge, codebase notes, decisions, and findings \u2014 anything other agents in the group should benefit from. For personal notes about yourself (preferences, persona), use `home_write` on IDENTITY.md instead. Key is a path-like string with a markdown extension, e.g. "auth-flow.md" or "people/alice.md".',
3396
+ description: 'Write or update a memory note in the GROUP-SHARED memory. All agents in this group can read what you write. Use it for project knowledge, codebase notes, decisions, and findings \u2014 anything other agents in the group should benefit from. For personal notes about yourself (preferences, persona) use `home_write` on IDENTITY.md. For STABLE facts about the human you\'re working with (their preferences, role, working hours, how they like to be addressed) use `user_md_get` then `user_md_write` \u2014 those land in every agent\'s system prompt directly. Key is a path-like string with a markdown extension, e.g. "auth-flow.md" or "decisions/2026-05-migration.md".',
2815
3397
  parameters: {
2816
3398
  type: "object",
2817
3399
  properties: {
@@ -2897,7 +3479,7 @@ function messagingTools(host2, fromAgentId) {
2897
3479
  {
2898
3480
  def: {
2899
3481
  name: "send_message",
2900
- description: "Send a message to another agent. Use the recipient's agent id (UUID).",
3482
+ description: `Send a message to another agent. Use the recipient's agent id (UUID). Use this ONLY for things the recipient needs to ACT on: delegating a task, asking a peer for information you cannot get yourself, escalating a decision. Do NOT use it for status updates or to announce changes to group-shared resources \u2014 USER.md and the group memory backend both propagate to every agent in the group automatically on their next turn, so messages like "I updated USER.md" or "I wrote a new memory note" are pure noise and will trigger an inbox-wake loop on the recipient.`,
2901
3483
  parameters: {
2902
3484
  type: "object",
2903
3485
  properties: {
@@ -3001,6 +3583,64 @@ function messagingTools(host2, fromAgentId) {
3001
3583
  ];
3002
3584
  }
3003
3585
 
3586
+ // ../daemon/src/runtime/tools/user-md.ts
3587
+ function userMdTools(host2, groupId) {
3588
+ return [
3589
+ {
3590
+ def: {
3591
+ name: "user_md_get",
3592
+ description: "Read the group-shared USER.md (facts every agent in the group knows about the human). Returns the current content followed by an `etag:` line \u2014 you MUST pass that etag back as `if_match` on the next `user_md_write` so the daemon can detect concurrent edits by other agents in the group. Always call this immediately before any `user_md_write`.",
3593
+ parameters: {
3594
+ type: "object",
3595
+ properties: {}
3596
+ }
3597
+ },
3598
+ async invoke() {
3599
+ const { content, etag } = await host2.get(groupId);
3600
+ const body = content.length > 0 ? content : "(USER.md is empty)";
3601
+ return `${body}
3602
+
3603
+ ---
3604
+ etag: ${etag}`;
3605
+ }
3606
+ },
3607
+ {
3608
+ def: {
3609
+ name: "user_md_write",
3610
+ description: "Replace the group-shared USER.md with new content. Use this for STABLE user-specific facts (preferences, role, working hours, how the human likes to be addressed). MANDATORY workflow: (1) call `user_md_get` first, (2) integrate your change into the full content preserving everything unrelated, (3) call `user_md_write` with the merged content and the etag you got from `user_md_get`. If another agent in the group wrote to USER.md between your get and write, this returns an etag-mismatch error \u2014 just call `user_md_get` again and retry the merge. The full result must fit under 12 KB. For project knowledge use `memory_write`; for notes about yourself use `home_write` on IDENTITY.md.",
3611
+ parameters: {
3612
+ type: "object",
3613
+ properties: {
3614
+ content: {
3615
+ type: "string",
3616
+ description: "Full new contents of USER.md (this is a complete replacement, NOT an append). Include everything you want to keep."
3617
+ },
3618
+ if_match: {
3619
+ type: "string",
3620
+ description: "The etag returned by your most recent `user_md_get`. The write fails if USER.md changed in the meantime."
3621
+ }
3622
+ },
3623
+ required: ["content", "if_match"]
3624
+ }
3625
+ },
3626
+ async invoke(args) {
3627
+ const content = String(args.content ?? "");
3628
+ const ifMatch = String(args.if_match ?? "");
3629
+ if (!ifMatch) {
3630
+ throw new Error(
3631
+ 'user_md_write: "if_match" is required \u2014 call user_md_get first to obtain the current etag.'
3632
+ );
3633
+ }
3634
+ const { etag, totalBytes } = await host2.write(groupId, content, ifMatch);
3635
+ return `wrote USER.md (${totalBytes} bytes, new etag: ${etag})`;
3636
+ }
3637
+ }
3638
+ ];
3639
+ }
3640
+
3641
+ // ../daemon/src/runtime/tools/web.ts
3642
+ import { fetch as undiciFetch2 } from "undici";
3643
+
3004
3644
  // ../daemon/src/runtime/tools/web-extract.ts
3005
3645
  import { Readability } from "@mozilla/readability";
3006
3646
  import { parseHTML } from "linkedom";
@@ -3081,7 +3721,7 @@ function extractReadable(html, url, mode) {
3081
3721
  // ../daemon/src/runtime/tools/web-ssrf.ts
3082
3722
  import { lookup as dnsLookupCb } from "dns";
3083
3723
  import { lookup as dnsLookup } from "dns/promises";
3084
- import { Agent } from "undici";
3724
+ import { Agent, fetch as undiciFetch } from "undici";
3085
3725
  var SsrFBlockedError = class extends Error {
3086
3726
  constructor(message) {
3087
3727
  super(message);
@@ -3190,7 +3830,7 @@ async function closeDispatcher(d) {
3190
3830
  }
3191
3831
  }
3192
3832
  async function guardedFetch(opts) {
3193
- const fetcher = opts.fetchImpl ?? globalThis.fetch;
3833
+ const fetcher = opts.fetchImpl ?? undiciFetch;
3194
3834
  const maxRedirects = opts.maxRedirects ?? 3;
3195
3835
  const abortController = new AbortController();
3196
3836
  const timeoutId = opts.timeoutMs ? setTimeout(() => abortController.abort(new Error("timeout")), opts.timeoutMs) : null;
@@ -3271,7 +3911,10 @@ var DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWe
3271
3911
  var DEFAULT_CACHE_TTL_MS = 15 * 6e4;
3272
3912
  var DEFAULT_CACHE_MAX = 100;
3273
3913
  var DEFAULT_MAX_LENGTH = 2e4;
3274
- var DEFAULT_TIMEOUT_MS = 2e4;
3914
+ var DEFAULT_TIMEOUT_MS = 3e4;
3915
+ var DEFAULT_MAX_BODY_BYTES = 3 * 1024 * 1024;
3916
+ var FIRECRAWL_FALLBACK_THRESHOLD = 200;
3917
+ var FIRECRAWL_DEFAULT_URL = "https://api.firecrawl.dev";
3275
3918
  function stripHtml(s) {
3276
3919
  return s.replace(/<[^>]*>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").trim();
3277
3920
  }
@@ -3305,6 +3948,92 @@ async function searxngSearch(query, limit, baseURL, fetchFn) {
3305
3948
  snippet: r.content ?? ""
3306
3949
  }));
3307
3950
  }
3951
+ function describeError(err) {
3952
+ if (!(err instanceof Error)) return String(err);
3953
+ const parts = [];
3954
+ const seen = /* @__PURE__ */ new Set();
3955
+ let cur = err;
3956
+ while (cur instanceof Error && !seen.has(cur)) {
3957
+ seen.add(cur);
3958
+ const code = cur.code;
3959
+ parts.push(code ? `[${code}] ${cur.message}` : cur.message);
3960
+ cur = cur.cause;
3961
+ }
3962
+ return parts.join(" \u2014 cause: ");
3963
+ }
3964
+ async function readBodyCapped(res, maxBytes) {
3965
+ const ct = res.headers.get("content-type") ?? "";
3966
+ const charset = /charset=([^;]+)/i.exec(ct)?.[1]?.trim().toLowerCase() || "utf-8";
3967
+ const decoder = new TextDecoder(charset, { fatal: false });
3968
+ const reader = res.body?.getReader();
3969
+ if (!reader) {
3970
+ const text = await res.text();
3971
+ if (text.length > maxBytes) return { text: text.slice(0, maxBytes), truncated: true };
3972
+ return { text, truncated: false };
3973
+ }
3974
+ const chunks = [];
3975
+ let total = 0;
3976
+ let truncated = false;
3977
+ while (true) {
3978
+ const { value, done } = await reader.read();
3979
+ if (done) break;
3980
+ if (!value) continue;
3981
+ if (total + value.byteLength > maxBytes) {
3982
+ const keep = maxBytes - total;
3983
+ if (keep > 0) chunks.push(value.subarray(0, keep));
3984
+ truncated = true;
3985
+ try {
3986
+ await reader.cancel();
3987
+ } catch {
3988
+ }
3989
+ break;
3990
+ }
3991
+ chunks.push(value);
3992
+ total += value.byteLength;
3993
+ }
3994
+ const sum = chunks.reduce((s, c) => s + c.byteLength, 0);
3995
+ const merged = new Uint8Array(sum);
3996
+ let off = 0;
3997
+ for (const c of chunks) {
3998
+ merged.set(c, off);
3999
+ off += c.byteLength;
4000
+ }
4001
+ return { text: decoder.decode(merged), truncated };
4002
+ }
4003
+ async function firecrawlScrape(url, mode, env, fetchFn, timeoutMs) {
4004
+ const apiKey = env.FIRECRAWL_API_KEY;
4005
+ if (!apiKey) return null;
4006
+ const base = (env.FIRECRAWL_URL ?? FIRECRAWL_DEFAULT_URL).replace(/\/$/, "");
4007
+ const ac = new AbortController();
4008
+ const t = setTimeout(() => ac.abort(new Error("firecrawl timeout")), timeoutMs);
4009
+ try {
4010
+ const res = await fetchFn(`${base}/v1/scrape`, {
4011
+ method: "POST",
4012
+ headers: {
4013
+ "content-type": "application/json",
4014
+ authorization: `Bearer ${apiKey}`,
4015
+ accept: "application/json"
4016
+ },
4017
+ body: JSON.stringify({
4018
+ url,
4019
+ formats: ["markdown"],
4020
+ onlyMainContent: true
4021
+ }),
4022
+ signal: ac.signal
4023
+ });
4024
+ if (!res.ok) return null;
4025
+ const body = await res.json();
4026
+ if (!body.success || !body.data?.markdown) return null;
4027
+ const md = body.data.markdown;
4028
+ const title = body.data.metadata?.title;
4029
+ const text = mode === "text" ? markdownToPlain(md) : md;
4030
+ return title ? { text, title } : { text };
4031
+ } catch {
4032
+ return null;
4033
+ } finally {
4034
+ clearTimeout(t);
4035
+ }
4036
+ }
3308
4037
  function cacheGet(cache, key) {
3309
4038
  const entry = cache.get(key);
3310
4039
  if (!entry) return null;
@@ -3325,12 +4054,14 @@ function cacheSet(cache, key, value, ttlMs, maxEntries) {
3325
4054
  }
3326
4055
  }
3327
4056
  function webTools(opts) {
3328
- const fetchFn = opts?.fetchImpl ?? fetch;
4057
+ const fetchFn = opts?.fetchImpl ?? undiciFetch2;
3329
4058
  const env = opts?.env ?? process.env;
3330
4059
  const allowPrivate = opts?.allowPrivate ?? false;
3331
4060
  const cacheTtlMs = opts?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
3332
4061
  const cacheMax = opts?.cacheMax ?? DEFAULT_CACHE_MAX;
3333
4062
  const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
4063
+ const maxBodyBytes = opts?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
4064
+ const firecrawlDisabled = opts?.firecrawlDisabled ?? false;
3334
4065
  const cache = /* @__PURE__ */ new Map();
3335
4066
  return [
3336
4067
  {
@@ -3368,7 +4099,7 @@ function webTools(opts) {
3368
4099
  {
3369
4100
  def: {
3370
4101
  name: "web_fetch",
3371
- description: "Fetch a URL and return its readable content. HTML is extracted via Readability and converted to markdown. Results are cached for 15 minutes.",
4102
+ description: "Fetch a URL and return its readable content. HTML is extracted via Readability and converted to markdown. When the primary extraction returns near-empty content from a 2xx HTML page (typical of JS-only shells), the tool automatically retries via Firecrawl if FIRECRAWL_API_KEY is configured. Results are cached for 15 minutes.",
3372
4103
  parameters: {
3373
4104
  type: "object",
3374
4105
  properties: {
@@ -3394,7 +4125,7 @@ function webTools(opts) {
3394
4125
  const cacheKey = `${mode}|${url}`;
3395
4126
  const cached = cacheGet(cache, cacheKey);
3396
4127
  if (cached) return formatOutput(cached, maxLen);
3397
- let result;
4128
+ let result = null;
3398
4129
  try {
3399
4130
  result = await guardedFetch({
3400
4131
  url,
@@ -3409,18 +4140,14 @@ function webTools(opts) {
3409
4140
  }
3410
4141
  }
3411
4142
  });
3412
- } catch (err) {
3413
- if (err instanceof SsrFBlockedError) throw new Error(`web_fetch: ${err.message}`);
3414
- throw err;
3415
- }
3416
- try {
3417
4143
  if (!result.response.ok) {
3418
- throw new Error(`web_fetch: ${result.response.status} ${result.response.statusText}`);
4144
+ throw new Error(`${result.response.status} ${result.response.statusText}`);
3419
4145
  }
3420
4146
  const ct = result.response.headers.get("content-type") ?? "";
3421
- const body = await result.response.text();
4147
+ const isHtml = ct.includes("text/html") || ct.includes("xhtml");
4148
+ const { text: body, truncated } = await readBodyCapped(result.response, maxBodyBytes);
3422
4149
  let extracted;
3423
- if (ct.includes("text/html") || ct.includes("xhtml")) {
4150
+ if (isHtml) {
3424
4151
  extracted = extractReadable(body, result.finalUrl, mode);
3425
4152
  } else if (ct.includes("application/json")) {
3426
4153
  try {
@@ -3431,10 +4158,32 @@ function webTools(opts) {
3431
4158
  } else {
3432
4159
  extracted = { text: body };
3433
4160
  }
4161
+ if (isHtml && !firecrawlDisabled && extracted.text.length < FIRECRAWL_FALLBACK_THRESHOLD) {
4162
+ const rescued = await firecrawlScrape(result.finalUrl, mode, env, fetchFn, timeoutMs);
4163
+ if (rescued) {
4164
+ extracted = {
4165
+ ...rescued,
4166
+ text: `${rescued.text}
4167
+
4168
+ [content rendered via Firecrawl fallback \u2014 primary extraction returned ${extracted.text.length} chars]`
4169
+ };
4170
+ }
4171
+ }
4172
+ if (truncated) {
4173
+ extracted = {
4174
+ ...extracted,
4175
+ text: `${extracted.text}
4176
+
4177
+ [raw body truncated at ${maxBodyBytes} bytes before extraction \u2014 page exceeded the size cap]`
4178
+ };
4179
+ }
3434
4180
  cacheSet(cache, cacheKey, extracted, cacheTtlMs, cacheMax);
3435
4181
  return formatOutput(extracted, maxLen);
4182
+ } catch (err) {
4183
+ if (err instanceof SsrFBlockedError) throw new Error(`web_fetch: ${err.message}`);
4184
+ throw new Error(`web_fetch: ${describeError(err)}`);
3436
4185
  } finally {
3437
- await result.release();
4186
+ if (result) await result.release();
3438
4187
  }
3439
4188
  }
3440
4189
  }
@@ -3481,13 +4230,16 @@ function createBazilionCustomTools(opts) {
3481
4230
  if (opts.messagingHost) {
3482
4231
  handlers.push(...messagingTools(opts.messagingHost, opts.agent.agent.id));
3483
4232
  }
4233
+ if (opts.userMdHost) {
4234
+ handlers.push(...userMdTools(opts.userMdHost, opts.agent.group.id));
4235
+ }
3484
4236
  return handlers.map(ourToolToPiTool);
3485
4237
  }
3486
4238
 
3487
4239
  // ../daemon/src/runtime/pi/session.ts
3488
4240
  var BUILTIN_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"];
3489
4241
  async function createBazilionSession(opts) {
3490
- const { agent, paths, env, memory, enabledProviders, messagingHost, refreshApiKey } = opts;
4242
+ const { agent, paths, env, memory, enabledProviders, messagingHost, userMdHost, refreshApiKey } = opts;
3491
4243
  const { providerName, modelId } = splitModelString(agent.model);
3492
4244
  if (enabledProviders.size > 0 && !enabledProviders.has(providerName)) {
3493
4245
  throw new Error(`${providerName} provider is disabled \u2014 enable it on the /config page`);
@@ -3524,12 +4276,17 @@ async function createBazilionSession(opts) {
3524
4276
  const sessionManager = existing ? SessionManager.open(existing, sessionDir, cwd) : SessionManager.create(cwd, sessionDir);
3525
4277
  const settingsManager = SettingsManager.inMemory({
3526
4278
  compaction: { enabled: false },
3527
- retry: { enabled: true, maxRetries: 2, baseDelayMs: 500, maxDelayMs: 8e3 }
4279
+ retry: {
4280
+ enabled: true,
4281
+ maxRetries: 2,
4282
+ baseDelayMs: 500,
4283
+ provider: { maxRetryDelayMs: 8e3 }
4284
+ }
3528
4285
  });
3529
4286
  const bazilionPrompt = buildSystemPrompt(agent);
3530
4287
  const resourceLoader = createBazilionResourceLoader(bazilionPrompt);
3531
4288
  await resourceLoader.reload();
3532
- const customTools = createBazilionCustomTools({ agent, memory, messagingHost, env });
4289
+ const customTools = createBazilionCustomTools({ agent, memory, messagingHost, userMdHost, env });
3533
4290
  const allowedTools = [...BUILTIN_TOOL_NAMES, ...customTools.map((t) => t.name)];
3534
4291
  const { session } = await createAgentSession({
3535
4292
  cwd,
@@ -3692,7 +4449,7 @@ function loadSessionHead(agent, paths) {
3692
4449
  function findMostRecent(sessionDir) {
3693
4450
  if (!existsSync12(sessionDir)) return null;
3694
4451
  let newest = null;
3695
- for (const entry of readdirSync7(sessionDir)) {
4452
+ for (const entry of readdirSync8(sessionDir)) {
3696
4453
  if (!entry.endsWith(".jsonl")) continue;
3697
4454
  const path = join14(sessionDir, entry);
3698
4455
  try {
@@ -3705,7 +4462,8 @@ function findMostRecent(sessionDir) {
3705
4462
  }
3706
4463
 
3707
4464
  // ../daemon/src/runtime/providers/catalog.ts
3708
- import { getModels as piGetModels } from "@mariozechner/pi-ai";
4465
+ import { getModels as piGetModels } from "@earendil-works/pi-ai";
4466
+ import { fetch as undiciFetch3 } from "undici";
3709
4467
  var REGISTRY_TO_PI = {
3710
4468
  bedrock: "amazon-bedrock",
3711
4469
  "azure-openai": "azure-openai-responses"
@@ -3716,6 +4474,8 @@ function liveEndpointFor(providerName, env) {
3716
4474
  return env.LMSTUDIO_URL ?? "http://127.0.0.1:1234/v1";
3717
4475
  case "ollama":
3718
4476
  return env.OLLAMA_URL ?? "http://127.0.0.1:11434/v1";
4477
+ case "llamacpp":
4478
+ return env.LLAMACPP_URL ?? "http://127.0.0.1:8080/v1";
3719
4479
  case "openrouter":
3720
4480
  return "https://openrouter.ai/api/v1";
3721
4481
  case "vercel-ai-gateway":
@@ -3746,7 +4506,7 @@ async function fetchModelsFrom(baseURL, apiKey, signal) {
3746
4506
  try {
3747
4507
  const headers = { accept: "application/json" };
3748
4508
  if (apiKey) headers.authorization = `Bearer ${apiKey}`;
3749
- const res = await fetch(`${baseURL.replace(/\/$/, "")}/models`, { headers, signal });
4509
+ const res = await undiciFetch3(`${baseURL.replace(/\/$/, "")}/models`, { headers, signal });
3750
4510
  if (!res.ok) return { models: [], error: `${res.status} ${res.statusText}` };
3751
4511
  const body = await res.json();
3752
4512
  const ids = (body?.data ?? []).map((m) => m.id).filter((id) => !!id);
@@ -3819,7 +4579,9 @@ async function* spawnWorkerTurn(spec, opts = {}) {
3819
4579
  env: opts.env ?? process.env,
3820
4580
  stdio: ["pipe", "pipe", "inherit", "ipc"]
3821
4581
  });
3822
- if (opts.messagingHost) attachIpcHandler(child, opts.messagingHost);
4582
+ if (opts.messagingHost || opts.userMdHost) {
4583
+ attachIpcHandler(child, opts.messagingHost, opts.userMdHost);
4584
+ }
3823
4585
  child.stdin?.write(JSON.stringify(spec));
3824
4586
  child.stdin?.end();
3825
4587
  const grace = opts.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
@@ -3899,10 +4661,10 @@ function parseFrame(line) {
3899
4661
  return { kind: "fatal", error: `worker emitted malformed frame: ${line.slice(0, 200)}` };
3900
4662
  }
3901
4663
  }
3902
- function attachIpcHandler(child, host2) {
4664
+ function attachIpcHandler(child, messagingHost, userMdHost) {
3903
4665
  child.on("message", (msg) => {
3904
4666
  if (!isIpcRequest(msg)) return;
3905
- void dispatch(msg, host2).then((reply) => {
4667
+ void dispatch(msg, messagingHost, userMdHost).then((reply) => {
3906
4668
  try {
3907
4669
  child.send?.(reply);
3908
4670
  } catch {
@@ -3915,25 +4677,48 @@ function isIpcRequest(msg) {
3915
4677
  const m = msg;
3916
4678
  return m.type === "rpc" && typeof m.id === "string" && typeof m.method === "string";
3917
4679
  }
3918
- async function dispatch(req, host2) {
4680
+ function requireMessagingHost(host2, method) {
4681
+ if (!host2) throw new Error(`worker called messaging method "${method}" without a messagingHost`);
4682
+ return host2;
4683
+ }
4684
+ function requireUserMdHost(host2, method) {
4685
+ if (!host2) throw new Error(`worker called user_md method "${method}" without a userMdHost`);
4686
+ return host2;
4687
+ }
4688
+ async function dispatch(req, messagingHost, userMdHost) {
3919
4689
  try {
3920
4690
  let result;
3921
4691
  switch (req.method) {
3922
4692
  case "agentExists":
3923
- result = await host2.agentExists(req.args.agentId);
4693
+ result = await requireMessagingHost(messagingHost, req.method).agentExists(req.args.agentId);
3924
4694
  break;
3925
4695
  case "sendMessage":
3926
- result = await host2.sendMessage(req.args);
4696
+ result = await requireMessagingHost(messagingHost, req.method).sendMessage(req.args);
3927
4697
  break;
3928
4698
  case "listInbox":
3929
- result = await host2.listInbox(req.args.agentId, { unreadOnly: req.args.unreadOnly });
4699
+ result = await requireMessagingHost(messagingHost, req.method).listInbox(req.args.agentId, {
4700
+ unreadOnly: req.args.unreadOnly
4701
+ });
3930
4702
  break;
3931
4703
  case "markRead":
3932
- await host2.markRead(req.args.messageId);
4704
+ await requireMessagingHost(messagingHost, req.method).markRead(req.args.messageId);
3933
4705
  result = null;
3934
4706
  break;
3935
4707
  case "findReplies":
3936
- result = await host2.findReplies(req.args.agentId, req.args.replyTo);
4708
+ result = await requireMessagingHost(messagingHost, req.method).findReplies(
4709
+ req.args.agentId,
4710
+ req.args.replyTo
4711
+ );
4712
+ break;
4713
+ case "userMdGet":
4714
+ result = await requireUserMdHost(userMdHost, req.method).get(req.args.groupId);
4715
+ break;
4716
+ case "userMdWrite":
4717
+ result = await requireUserMdHost(userMdHost, req.method).write(
4718
+ req.args.groupId,
4719
+ req.args.content,
4720
+ req.args.ifMatch
4721
+ );
3937
4722
  break;
3938
4723
  }
3939
4724
  return { type: "rpc-reply", id: req.id, ok: true, result };
@@ -3986,6 +4771,40 @@ function createDbMessagingHost(db) {
3986
4771
  };
3987
4772
  }
3988
4773
 
4774
+ // ../daemon/src/lib/user-md-host.ts
4775
+ import { createHash as createHash2 } from "crypto";
4776
+ var USER_MD_MAX_BYTES = 12e3;
4777
+ function computeEtag(content) {
4778
+ return createHash2("sha256").update(content, "utf8").digest("hex").slice(0, 16);
4779
+ }
4780
+ function createDbUserMdHost(db, paths) {
4781
+ return {
4782
+ get(groupId) {
4783
+ const group = groups_exports.get(db, groupId, paths);
4784
+ if (!group) throw new Error(`group not found: ${groupId}`);
4785
+ return { content: group.userMd, etag: computeEtag(group.userMd) };
4786
+ },
4787
+ write(groupId, content, ifMatch) {
4788
+ const group = groups_exports.get(db, groupId, paths);
4789
+ if (!group) throw new Error(`group not found: ${groupId}`);
4790
+ const currentEtag = computeEtag(group.userMd);
4791
+ if (currentEtag !== ifMatch) {
4792
+ throw new Error(
4793
+ `etag mismatch \u2014 USER.md was updated by another agent. Current etag is ${currentEtag} (you passed ${ifMatch}). Call user_md_get again to re-read, merge your change, and retry.`
4794
+ );
4795
+ }
4796
+ const bytes = Buffer.byteLength(content, "utf8");
4797
+ if (bytes > USER_MD_MAX_BYTES) {
4798
+ throw new Error(
4799
+ `USER.md would exceed the ${USER_MD_MAX_BYTES}-byte cap (you tried to write ${bytes}). Trim your content or ask the human to compact via the web UI.`
4800
+ );
4801
+ }
4802
+ groups_exports.setUserMd(db, groupId, content);
4803
+ return { etag: computeEtag(content), totalBytes: bytes };
4804
+ }
4805
+ };
4806
+ }
4807
+
3989
4808
  // ../daemon/src/lib/agent-turn.ts
3990
4809
  async function* runAgentTurn(agentId, message, opts = {}) {
3991
4810
  const { db, paths, authToken } = getCtx();
@@ -3993,13 +4812,14 @@ async function* runAgentTurn(agentId, message, opts = {}) {
3993
4812
  const enabledProviders = Array.from(providerState_exports.listEnabled(db));
3994
4813
  const env = mergeSecretsIntoEnv(db, authToken);
3995
4814
  const messagingHost = createDbMessagingHost(db);
4815
+ const userMdHost = createDbUserMdHost(db, paths);
3996
4816
  const { apiKey } = await resolveAgentApiKey(db, authToken, agent);
3997
4817
  const controller = opts.controller ?? new AbortController();
3998
4818
  registerAgent(agentId, controller);
3999
4819
  try {
4000
4820
  for await (const frame of spawnWorkerTurn(
4001
4821
  { agent, message, enabledProviders, apiKey },
4002
- { signal: controller.signal, env, messagingHost }
4822
+ { signal: controller.signal, env, messagingHost, userMdHost }
4003
4823
  )) {
4004
4824
  yield frame;
4005
4825
  }
@@ -4345,7 +5165,7 @@ async function authMiddleware(c, next) {
4345
5165
  }
4346
5166
 
4347
5167
  // ../daemon/src/routes/agents.ts
4348
- import { existsSync as existsSync15, readdirSync as readdirSync8, readFileSync as readFileSync10, rmSync as rmSync7 } from "fs";
5168
+ import { existsSync as existsSync15, readdirSync as readdirSync9, readFileSync as readFileSync10, rmSync as rmSync9 } from "fs";
4349
5169
  import { join as join15 } from "path";
4350
5170
 
4351
5171
  // ../../packages/api-types/src/entities.ts
@@ -4892,10 +5712,10 @@ agentsRouter.post("/:id/chat/reset", (c) => {
4892
5712
  const sessionsDir = join15(paths.agentDir(agent.id), "sessions");
4893
5713
  let deleted = 0;
4894
5714
  if (existsSync15(sessionsDir)) {
4895
- for (const file of readdirSync8(sessionsDir)) {
5715
+ for (const file of readdirSync9(sessionsDir)) {
4896
5716
  if (!file.endsWith(".jsonl")) continue;
4897
5717
  try {
4898
- rmSync7(join15(sessionsDir, file));
5718
+ rmSync9(join15(sessionsDir, file));
4899
5719
  deleted++;
4900
5720
  } catch {
4901
5721
  }
@@ -5136,6 +5956,7 @@ configRouter.get("/services", (c) => {
5136
5956
  id: svc.id,
5137
5957
  displayName: svc.displayName,
5138
5958
  ...svc.hint ? { hint: svc.hint } : {},
5959
+ ...svc.group ? { group: svc.group } : {},
5139
5960
  fields: resolveFieldStates(svc, configValues, secretValues)
5140
5961
  }));
5141
5962
  const body = { services };
@@ -5270,7 +6091,7 @@ function knownProviderRegistryNames() {
5270
6091
  // ../daemon/src/routes/groups.ts
5271
6092
  import { join as join16 } from "path";
5272
6093
  import { Hono as Hono4 } from "hono";
5273
- var USER_MD_MAX_BYTES = 12e3;
6094
+ var USER_MD_MAX_BYTES2 = 12e3;
5274
6095
  var groupsRouter = new Hono4();
5275
6096
  groupsRouter.get("/", (c) => {
5276
6097
  const { db, paths } = getCtx();
@@ -5309,8 +6130,8 @@ groupsRouter.put("/:id/user-md", async (c) => {
5309
6130
  if (!body || typeof body.userMd !== "string") {
5310
6131
  return c.json({ error: "userMd (string) is required" }, 400);
5311
6132
  }
5312
- if (Buffer.byteLength(body.userMd, "utf8") > USER_MD_MAX_BYTES) {
5313
- return c.json({ error: `userMd exceeds ${USER_MD_MAX_BYTES}-byte cap` }, 413);
6133
+ if (Buffer.byteLength(body.userMd, "utf8") > USER_MD_MAX_BYTES2) {
6134
+ return c.json({ error: `userMd exceeds ${USER_MD_MAX_BYTES2}-byte cap` }, 413);
5314
6135
  }
5315
6136
  const { db, paths } = getCtx();
5316
6137
  const g = groups_exports.get(db, c.req.param("id"), paths);
@@ -5586,11 +6407,147 @@ miscRouter.delete("/tokens/:id", (c) => {
5586
6407
  return c.body(null, 204);
5587
6408
  });
5588
6409
 
6410
+ // ../daemon/src/routes/profile-groups.ts
6411
+ import { Hono as Hono7 } from "hono";
6412
+ var profileGroupsRouter = new Hono7();
6413
+ profileGroupsRouter.get("/", (c) => {
6414
+ const { db } = getCtx();
6415
+ return c.json(profileGroups_exports.list(db));
6416
+ });
6417
+ profileGroupsRouter.get("/:id", (c) => {
6418
+ const { db } = getCtx();
6419
+ const id = c.req.param("id");
6420
+ const group = profileGroups_exports.get(db, id);
6421
+ if (!group) return c.json({ error: `profile group not found: ${id}` }, 404);
6422
+ const body = {
6423
+ group,
6424
+ members: profileGroups_exports.members(db, id)
6425
+ };
6426
+ return c.json(body);
6427
+ });
6428
+ profileGroupsRouter.post("/", async (c) => {
6429
+ const raw = await c.req.json().catch(() => null);
6430
+ if (!raw) return c.json({ error: "invalid JSON body" }, 400);
6431
+ const id = typeof raw.id === "string" ? raw.id : "";
6432
+ if (!id) return c.json({ error: "id is required" }, 400);
6433
+ try {
6434
+ validateSlug(id);
6435
+ } catch (err) {
6436
+ return c.json({ error: err.message }, 400);
6437
+ }
6438
+ const { db } = getCtx();
6439
+ if (profileGroups_exports.get(db, id)) {
6440
+ return c.json({ error: `profile group already exists: ${id}` }, 409);
6441
+ }
6442
+ const name = typeof raw.name === "string" && raw.name.length > 0 ? raw.name : id;
6443
+ const userMd = typeof raw.userMd === "string" ? raw.userMd : null;
6444
+ try {
6445
+ const inserted = profileGroups_exports.insert(db, { id, name, userMd });
6446
+ return c.json(inserted, 201);
6447
+ } catch (err) {
6448
+ return c.json({ error: err.message }, 400);
6449
+ }
6450
+ });
6451
+ profileGroupsRouter.patch("/:id", async (c) => {
6452
+ const raw = await c.req.json().catch(() => null);
6453
+ if (!raw) return c.json({ error: "invalid JSON body" }, 400);
6454
+ const { db } = getCtx();
6455
+ const id = c.req.param("id");
6456
+ if (!profileGroups_exports.get(db, id)) {
6457
+ return c.json({ error: `profile group not found: ${id}` }, 404);
6458
+ }
6459
+ const patch = {};
6460
+ if (Object.hasOwn(raw, "name") && typeof raw.name === "string") {
6461
+ patch.name = raw.name;
6462
+ }
6463
+ if (Object.hasOwn(raw, "userMd")) {
6464
+ patch.userMd = raw.userMd === null ? null : typeof raw.userMd === "string" ? raw.userMd : null;
6465
+ }
6466
+ profileGroups_exports.update(db, id, patch);
6467
+ return c.json(profileGroups_exports.get(db, id));
6468
+ });
6469
+ profileGroupsRouter.put("/:id/members", async (c) => {
6470
+ const raw = await c.req.json().catch(() => null);
6471
+ if (!raw || !Array.isArray(raw.members)) {
6472
+ return c.json({ error: "members array is required" }, 400);
6473
+ }
6474
+ const { db } = getCtx();
6475
+ const id = c.req.param("id");
6476
+ if (!profileGroups_exports.get(db, id)) {
6477
+ return c.json({ error: `profile group not found: ${id}` }, 404);
6478
+ }
6479
+ const cleaned = [];
6480
+ const missingProfiles = [];
6481
+ for (let i = 0; i < raw.members.length; i++) {
6482
+ const m = raw.members[i];
6483
+ if (!m || typeof m.profileId !== "string" || typeof m.agentName !== "string") {
6484
+ return c.json({ error: `member ${i}: profileId and agentName are required strings` }, 400);
6485
+ }
6486
+ if (!profiles_exports.get(db, m.profileId)) {
6487
+ missingProfiles.push(m.profileId);
6488
+ continue;
6489
+ }
6490
+ const modelOverride = m.modelOverride === null ? null : typeof m.modelOverride === "string" ? m.modelOverride : null;
6491
+ const reasoningLevel = m.reasoningLevel === null ? null : typeof m.reasoningLevel === "string" && REASONING_LEVELS.includes(m.reasoningLevel) ? m.reasoningLevel : null;
6492
+ cleaned.push({
6493
+ profileId: m.profileId,
6494
+ agentName: m.agentName,
6495
+ modelOverride,
6496
+ reasoningLevel
6497
+ });
6498
+ }
6499
+ if (missingProfiles.length > 0) {
6500
+ return c.json({ error: `missing profiles: ${[...new Set(missingProfiles)].join(", ")}` }, 400);
6501
+ }
6502
+ profileGroups_exports.replaceMembers(db, id, cleaned);
6503
+ return c.json({ members: profileGroups_exports.members(db, id) });
6504
+ });
6505
+ profileGroupsRouter.delete("/:id", (c) => {
6506
+ const { db } = getCtx();
6507
+ const id = c.req.param("id");
6508
+ if (!profileGroups_exports.get(db, id)) {
6509
+ return c.json({ error: `profile group not found: ${id}` }, 404);
6510
+ }
6511
+ profileGroups_exports.remove(db, id);
6512
+ return c.body(null, 204);
6513
+ });
6514
+ profileGroupsRouter.post("/:id/spawn", async (c) => {
6515
+ const raw = await c.req.json().catch(() => ({}));
6516
+ const body = raw ?? {};
6517
+ const groupSlug = typeof body.groupSlug === "string" ? body.groupSlug : void 0;
6518
+ const userMd = typeof body.userMd === "string" ? body.userMd : void 0;
6519
+ const { db, paths } = getCtx();
6520
+ const id = c.req.param("id");
6521
+ try {
6522
+ const result = await spawnProfileGroup(db, paths, {
6523
+ profileGroupId: id,
6524
+ groupSlug,
6525
+ userMd
6526
+ });
6527
+ const response = {
6528
+ groupSlug: result.groupSlug,
6529
+ agents: result.agents
6530
+ };
6531
+ if (result.orphanAgentIds.length > 0) response.orphanAgentIds = result.orphanAgentIds;
6532
+ return c.json(response);
6533
+ } catch (err) {
6534
+ if (err instanceof SpawnProfileGroupError) {
6535
+ return c.json({ error: err.message, orphanAgentIds: err.orphanAgentIds }, 500);
6536
+ }
6537
+ const msg = err.message;
6538
+ if (msg.startsWith("profile group not found")) return c.json({ error: msg }, 404);
6539
+ if (msg.startsWith("profile group spawn: missing profiles")) {
6540
+ return c.json({ error: msg }, 400);
6541
+ }
6542
+ return c.json({ error: msg }, 500);
6543
+ }
6544
+ });
6545
+
5589
6546
  // ../daemon/src/routes/profiles.ts
5590
6547
  import { existsSync as existsSync17, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
5591
6548
  import { join as join18 } from "path";
5592
- import { Hono as Hono7 } from "hono";
5593
- var profilesRouter = new Hono7();
6549
+ import { Hono as Hono8 } from "hono";
6550
+ var profilesRouter = new Hono8();
5594
6551
  profilesRouter.get("/", (c) => {
5595
6552
  const { db } = getCtx();
5596
6553
  const profiles = profiles_exports.list(db);
@@ -5721,12 +6678,12 @@ function toSkillsMode(v) {
5721
6678
  }
5722
6679
 
5723
6680
  // ../daemon/src/routes/skills.ts
5724
- import { existsSync as existsSync18, mkdtempSync as mkdtempSync2, rmSync as rmSync8, writeFileSync as writeFileSync9 } from "fs";
6681
+ import { existsSync as existsSync18, mkdtempSync as mkdtempSync2, rmSync as rmSync10, writeFileSync as writeFileSync9 } from "fs";
5725
6682
  import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
5726
6683
  import { join as join19 } from "path";
5727
- import { Hono as Hono8 } from "hono";
6684
+ import { Hono as Hono9 } from "hono";
5728
6685
  var MAX_ZIP_BYTES = 50 * 1024 * 1024;
5729
- var skillsRouter = new Hono8();
6686
+ var skillsRouter = new Hono9();
5730
6687
  skillsRouter.get("/", (c) => {
5731
6688
  const { db, paths } = getCtx();
5732
6689
  const out = [];
@@ -5753,7 +6710,7 @@ skillsRouter.delete("/:name", (c) => {
5753
6710
  const name = c.req.param("name");
5754
6711
  const dir = paths.skillDir(name);
5755
6712
  if (!existsSync18(dir)) return c.json({ error: `skill not found: ${name}` }, 404);
5756
- rmSync8(dir, { recursive: true, force: true });
6713
+ rmSync10(dir, { recursive: true, force: true });
5757
6714
  skillMeta_exports.remove(db, name);
5758
6715
  return c.body(null, 204);
5759
6716
  });
@@ -5776,7 +6733,7 @@ skillsRouter.post("/import", async (c) => {
5776
6733
  } catch (err) {
5777
6734
  return c.json({ error: err.message }, 400);
5778
6735
  } finally {
5779
- if (input.tempZipPath) rmSync8(input.tempZipPath, { recursive: true, force: true });
6736
+ if (input.tempZipPath) rmSync10(input.tempZipPath, { recursive: true, force: true });
5780
6737
  }
5781
6738
  });
5782
6739
  async function parseImportInput(request) {
@@ -5820,8 +6777,8 @@ async function parseImportInput(request) {
5820
6777
  }
5821
6778
 
5822
6779
  // ../daemon/src/routes/triggers.ts
5823
- import { Hono as Hono9 } from "hono";
5824
- var triggersRouter = new Hono9();
6780
+ import { Hono as Hono10 } from "hono";
6781
+ var triggersRouter = new Hono10();
5825
6782
  triggersRouter.delete("/:id", (c) => {
5826
6783
  const { db } = getCtx();
5827
6784
  const id = c.req.param("id");
@@ -5843,10 +6800,11 @@ triggersRouter.patch("/:id", async (c) => {
5843
6800
 
5844
6801
  // ../daemon/src/app.ts
5845
6802
  function createApp() {
5846
- const app2 = new Hono10();
6803
+ const app2 = new Hono11();
5847
6804
  app2.use("*", authMiddleware);
5848
6805
  app2.route("/api/agents", agentsRouter);
5849
6806
  app2.route("/api/groups", groupsRouter);
6807
+ app2.route("/api/profile-groups", profileGroupsRouter);
5850
6808
  app2.route("/api/profiles", profilesRouter);
5851
6809
  app2.route("/api/skills", skillsRouter);
5852
6810
  app2.route("/api/triggers", triggersRouter);