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/README.md +1 -1
- package/dist/cli.js +293 -67
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +1088 -130
- package/dist/daemon.js.map +1 -1
- package/dist/worker.js +420 -74
- package/dist/worker.js.map +1 -1
- package/package.json +13 -13
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
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
|
814
|
-
if (
|
|
815
|
-
const names =
|
|
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}": ${
|
|
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: "
|
|
1002
|
-
displayName: "
|
|
1307
|
+
id: "fireworks",
|
|
1308
|
+
displayName: "Fireworks AI",
|
|
1003
1309
|
category: "provider",
|
|
1004
|
-
hint: "
|
|
1005
|
-
fields: [{ envVar: "
|
|
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: () =>
|
|
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
|
|
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: () =>
|
|
1754
|
+
get: () => get6,
|
|
1342
1755
|
listAll: () => listAll2,
|
|
1343
|
-
remove: () =>
|
|
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
|
|
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 =
|
|
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
|
|
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: () =>
|
|
1380
|
-
insert: () =>
|
|
1792
|
+
get: () => get7,
|
|
1793
|
+
insert: () => insert5,
|
|
1381
1794
|
listEnabled: () => listEnabled2,
|
|
1382
1795
|
listForAgent: () => listForAgent,
|
|
1383
1796
|
markFired: () => markFired,
|
|
1384
|
-
remove: () =>
|
|
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
|
|
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
|
|
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
|
|
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: () =>
|
|
1868
|
+
get: () => get8,
|
|
1456
1869
|
hashToken: () => hashToken,
|
|
1457
|
-
list: () =>
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
2017
|
+
rmSync5(root, { recursive: true, force: true });
|
|
1603
2018
|
throw err;
|
|
1604
2019
|
}
|
|
1605
2020
|
let effectiveSource = root;
|
|
1606
|
-
const topEntries =
|
|
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)
|
|
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 =
|
|
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 "@
|
|
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
|
|
2218
|
+
readdirSync as readdirSync5,
|
|
1804
2219
|
readFileSync as readFileSync6,
|
|
1805
|
-
rmSync as
|
|
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
|
|
2230
|
+
readdirSync as readdirSync6,
|
|
1816
2231
|
readFileSync as readFileSync7,
|
|
1817
|
-
rmSync as
|
|
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
|
|
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))
|
|
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
|
|
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 "@
|
|
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 "@
|
|
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
|
|
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:
|
|
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
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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)
|
|
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:
|
|
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 ??
|
|
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 =
|
|
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(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/ /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 ??
|
|
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(
|
|
4144
|
+
throw new Error(`${result.response.status} ${result.response.statusText}`);
|
|
3419
4145
|
}
|
|
3420
4146
|
const ct = result.response.headers.get("content-type") ?? "";
|
|
3421
|
-
const
|
|
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 (
|
|
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: {
|
|
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
|
|
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 "@
|
|
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
|
|
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
|
|
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,
|
|
4664
|
+
function attachIpcHandler(child, messagingHost, userMdHost) {
|
|
3903
4665
|
child.on("message", (msg) => {
|
|
3904
4666
|
if (!isIpcRequest(msg)) return;
|
|
3905
|
-
void dispatch(msg,
|
|
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
|
-
|
|
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
|
|
4693
|
+
result = await requireMessagingHost(messagingHost, req.method).agentExists(req.args.agentId);
|
|
3924
4694
|
break;
|
|
3925
4695
|
case "sendMessage":
|
|
3926
|
-
result = await
|
|
4696
|
+
result = await requireMessagingHost(messagingHost, req.method).sendMessage(req.args);
|
|
3927
4697
|
break;
|
|
3928
4698
|
case "listInbox":
|
|
3929
|
-
result = await
|
|
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
|
|
4704
|
+
await requireMessagingHost(messagingHost, req.method).markRead(req.args.messageId);
|
|
3933
4705
|
result = null;
|
|
3934
4706
|
break;
|
|
3935
4707
|
case "findReplies":
|
|
3936
|
-
result = await
|
|
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
|
|
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
|
|
5715
|
+
for (const file of readdirSync9(sessionsDir)) {
|
|
4896
5716
|
if (!file.endsWith(".jsonl")) continue;
|
|
4897
5717
|
try {
|
|
4898
|
-
|
|
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
|
|
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") >
|
|
5313
|
-
return c.json({ error: `userMd exceeds ${
|
|
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
|
|
5593
|
-
var profilesRouter = new
|
|
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
|
|
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
|
|
6684
|
+
import { Hono as Hono9 } from "hono";
|
|
5728
6685
|
var MAX_ZIP_BYTES = 50 * 1024 * 1024;
|
|
5729
|
-
var skillsRouter = new
|
|
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
|
-
|
|
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)
|
|
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
|
|
5824
|
-
var triggersRouter = new
|
|
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
|
|
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);
|