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/worker.js
CHANGED
|
@@ -76,9 +76,34 @@ import { existsSync as existsSync5, rmSync as rmSync2 } from "fs";
|
|
|
76
76
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
77
77
|
import { join as join7 } from "path";
|
|
78
78
|
|
|
79
|
+
// ../daemon/src/core/profile-group/spawn.ts
|
|
80
|
+
import { readdirSync as readdirSync3, rmSync as rmSync4 } from "fs";
|
|
81
|
+
|
|
82
|
+
// ../daemon/src/core/profile-group/rm-with-retry.ts
|
|
83
|
+
import { rmSync as rmSync3 } from "fs";
|
|
84
|
+
|
|
79
85
|
// ../daemon/src/core/services.ts
|
|
80
86
|
var SERVICES = [
|
|
81
87
|
// --- LLM providers (configured via API keys / URLs) ---
|
|
88
|
+
// Top 3: openai-codex (ChatGPT OAuth), openai (API key), anthropic.
|
|
89
|
+
// Everything else in rough popularity order; locals last.
|
|
90
|
+
{
|
|
91
|
+
id: "openai-codex",
|
|
92
|
+
displayName: "OpenAI ChatGPT (OAuth)",
|
|
93
|
+
category: "provider",
|
|
94
|
+
hint: "Use your ChatGPT Plus/Pro/Team account (same login as Codex CLI)",
|
|
95
|
+
// No form fields — credentials come from an OAuth flow. The /config page
|
|
96
|
+
// renders a Connect/Disconnect card using /api/auth/openai instead of the
|
|
97
|
+
// standard field inputs.
|
|
98
|
+
fields: []
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "openai",
|
|
102
|
+
displayName: "OpenAI",
|
|
103
|
+
category: "provider",
|
|
104
|
+
hint: "GPT models \xB7 platform.openai.com",
|
|
105
|
+
fields: [{ envVar: "OPENAI_API_KEY", kind: "secret", label: "API key", placeholder: "sk-..." }]
|
|
106
|
+
},
|
|
82
107
|
{
|
|
83
108
|
id: "anthropic",
|
|
84
109
|
displayName: "Anthropic",
|
|
@@ -94,23 +119,6 @@ var SERVICES = [
|
|
|
94
119
|
}
|
|
95
120
|
]
|
|
96
121
|
},
|
|
97
|
-
{
|
|
98
|
-
id: "openai",
|
|
99
|
-
displayName: "OpenAI",
|
|
100
|
-
category: "provider",
|
|
101
|
-
hint: "GPT models \xB7 platform.openai.com",
|
|
102
|
-
fields: [{ envVar: "OPENAI_API_KEY", kind: "secret", label: "API key", placeholder: "sk-..." }]
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
id: "openai-codex",
|
|
106
|
-
displayName: "OpenAI ChatGPT (OAuth)",
|
|
107
|
-
category: "provider",
|
|
108
|
-
hint: "Use your ChatGPT Plus/Pro/Team account (same login as Codex CLI)",
|
|
109
|
-
// No form fields — credentials come from an OAuth flow. The /config page
|
|
110
|
-
// renders a Connect/Disconnect card using /api/auth/openai instead of the
|
|
111
|
-
// standard field inputs.
|
|
112
|
-
fields: []
|
|
113
|
-
},
|
|
114
122
|
{
|
|
115
123
|
id: "google",
|
|
116
124
|
displayName: "Google (Gemini)",
|
|
@@ -153,6 +161,27 @@ var SERVICES = [
|
|
|
153
161
|
hint: "Authenticates via AWS SDK env (AWS_PROFILE or AWS_ACCESS_KEY_ID/SECRET)",
|
|
154
162
|
fields: []
|
|
155
163
|
},
|
|
164
|
+
{
|
|
165
|
+
id: "github-copilot",
|
|
166
|
+
displayName: "GitHub Copilot",
|
|
167
|
+
category: "provider",
|
|
168
|
+
hint: "Use a GitHub Copilot subscription to call Claude/GPT/Gemini via Copilot",
|
|
169
|
+
fields: [
|
|
170
|
+
{
|
|
171
|
+
envVar: "COPILOT_GITHUB_TOKEN",
|
|
172
|
+
kind: "secret",
|
|
173
|
+
label: "GitHub token",
|
|
174
|
+
description: "Generic GH_TOKEN/GITHUB_TOKEN are ignored \u2014 set this scoped variable explicitly (or run `bazilion auth copilot login` once available)."
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: "deepseek",
|
|
180
|
+
displayName: "DeepSeek",
|
|
181
|
+
category: "provider",
|
|
182
|
+
hint: "DeepSeek V4 Flash / Pro \xB7 platform.deepseek.com",
|
|
183
|
+
fields: [{ envVar: "DEEPSEEK_API_KEY", kind: "secret", label: "API key" }]
|
|
184
|
+
},
|
|
156
185
|
{
|
|
157
186
|
id: "mistral",
|
|
158
187
|
displayName: "Mistral",
|
|
@@ -160,6 +189,13 @@ var SERVICES = [
|
|
|
160
189
|
hint: "mistral.ai",
|
|
161
190
|
fields: [{ envVar: "MISTRAL_API_KEY", kind: "secret", label: "API key" }]
|
|
162
191
|
},
|
|
192
|
+
{
|
|
193
|
+
id: "xai",
|
|
194
|
+
displayName: "xAI",
|
|
195
|
+
category: "provider",
|
|
196
|
+
hint: "Grok \xB7 x.ai",
|
|
197
|
+
fields: [{ envVar: "XAI_API_KEY", kind: "secret", label: "API key" }]
|
|
198
|
+
},
|
|
163
199
|
{
|
|
164
200
|
id: "groq",
|
|
165
201
|
displayName: "Groq",
|
|
@@ -174,11 +210,46 @@ var SERVICES = [
|
|
|
174
210
|
fields: [{ envVar: "CEREBRAS_API_KEY", kind: "secret", label: "API key" }]
|
|
175
211
|
},
|
|
176
212
|
{
|
|
177
|
-
id: "
|
|
178
|
-
displayName: "
|
|
213
|
+
id: "fireworks",
|
|
214
|
+
displayName: "Fireworks AI",
|
|
179
215
|
category: "provider",
|
|
180
|
-
hint: "
|
|
181
|
-
fields: [{ envVar: "
|
|
216
|
+
hint: "DeepSeek/GLM/Kimi via fireworks.ai",
|
|
217
|
+
fields: [{ envVar: "FIREWORKS_API_KEY", kind: "secret", label: "API key" }]
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "together",
|
|
221
|
+
displayName: "Together AI",
|
|
222
|
+
category: "provider",
|
|
223
|
+
hint: "Open-weight models \xB7 together.ai",
|
|
224
|
+
fields: [{ envVar: "TOGETHER_API_KEY", kind: "secret", label: "API key" }]
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: "moonshotai",
|
|
228
|
+
displayName: "Moonshot AI",
|
|
229
|
+
category: "provider",
|
|
230
|
+
hint: "Kimi K2/K2.5/K2.6 \xB7 platform.moonshot.ai",
|
|
231
|
+
fields: [{ envVar: "MOONSHOT_API_KEY", kind: "secret", label: "API key" }]
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: "kimi-coding",
|
|
235
|
+
displayName: "Kimi Coding",
|
|
236
|
+
category: "provider",
|
|
237
|
+
hint: "Coding-tuned Kimi endpoint \xB7 platform.moonshot.cn",
|
|
238
|
+
fields: [{ envVar: "KIMI_API_KEY", kind: "secret", label: "API key" }]
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: "minimax",
|
|
242
|
+
displayName: "MiniMax",
|
|
243
|
+
category: "provider",
|
|
244
|
+
hint: "MiniMax M2 family \xB7 platform.minimaxi.com",
|
|
245
|
+
fields: [{ envVar: "MINIMAX_API_KEY", kind: "secret", label: "API key" }]
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: "xiaomi",
|
|
249
|
+
displayName: "Xiaomi MiMo",
|
|
250
|
+
category: "provider",
|
|
251
|
+
hint: "API billing endpoint \xB7 platform.xiaomimimo.com",
|
|
252
|
+
fields: [{ envVar: "XIAOMI_API_KEY", kind: "secret", label: "API key" }]
|
|
182
253
|
},
|
|
183
254
|
{
|
|
184
255
|
id: "zai",
|
|
@@ -193,6 +264,27 @@ var SERVICES = [
|
|
|
193
264
|
hint: "Inference endpoints \xB7 huggingface.co",
|
|
194
265
|
fields: [{ envVar: "HF_TOKEN", kind: "secret", label: "Access token", placeholder: "hf_..." }]
|
|
195
266
|
},
|
|
267
|
+
{
|
|
268
|
+
id: "cloudflare-ai-gateway",
|
|
269
|
+
displayName: "Cloudflare AI Gateway",
|
|
270
|
+
category: "provider",
|
|
271
|
+
hint: "Per-gateway routing to OpenAI/Anthropic/Workers AI",
|
|
272
|
+
fields: [
|
|
273
|
+
{ envVar: "CLOUDFLARE_API_KEY", kind: "secret", label: "API key" },
|
|
274
|
+
{ envVar: "CLOUDFLARE_ACCOUNT_ID", kind: "config", label: "Account ID" },
|
|
275
|
+
{ envVar: "CLOUDFLARE_GATEWAY_ID", kind: "config", label: "Gateway ID" }
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: "cloudflare-workers-ai",
|
|
280
|
+
displayName: "Cloudflare Workers AI",
|
|
281
|
+
category: "provider",
|
|
282
|
+
hint: "Inference on Cloudflare Workers \xB7 ai.cloudflare.com",
|
|
283
|
+
fields: [
|
|
284
|
+
{ envVar: "CLOUDFLARE_API_KEY", kind: "secret", label: "API key" },
|
|
285
|
+
{ envVar: "CLOUDFLARE_ACCOUNT_ID", kind: "config", label: "Account ID" }
|
|
286
|
+
]
|
|
287
|
+
},
|
|
196
288
|
{
|
|
197
289
|
id: "openrouter",
|
|
198
290
|
displayName: "OpenRouter",
|
|
@@ -216,6 +308,13 @@ var SERVICES = [
|
|
|
216
308
|
}
|
|
217
309
|
]
|
|
218
310
|
},
|
|
311
|
+
{
|
|
312
|
+
id: "opencode",
|
|
313
|
+
displayName: "OpenCode",
|
|
314
|
+
category: "provider",
|
|
315
|
+
hint: "OpenAI-compatible proxy from the OpenCode CLI",
|
|
316
|
+
fields: [{ envVar: "OPENCODE_API_KEY", kind: "secret", label: "API key" }]
|
|
317
|
+
},
|
|
219
318
|
{
|
|
220
319
|
id: "lmstudio",
|
|
221
320
|
displayName: "LM Studio",
|
|
@@ -254,11 +353,54 @@ var SERVICES = [
|
|
|
254
353
|
}
|
|
255
354
|
]
|
|
256
355
|
},
|
|
356
|
+
{
|
|
357
|
+
id: "llamacpp",
|
|
358
|
+
displayName: "llama.cpp",
|
|
359
|
+
category: "provider",
|
|
360
|
+
hint: "Local inference \xB7 llama.cpp llama-server (OpenAI-compat /v1 endpoint)",
|
|
361
|
+
fields: [
|
|
362
|
+
{
|
|
363
|
+
envVar: "LLAMACPP_URL",
|
|
364
|
+
kind: "config",
|
|
365
|
+
label: "Endpoint URL",
|
|
366
|
+
placeholder: "http://127.0.0.1:8080/v1"
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
envVar: "LLAMACPP_API_KEY",
|
|
370
|
+
kind: "secret",
|
|
371
|
+
label: "API key (only if started with --api-key)",
|
|
372
|
+
description: "llama-server runs without auth by default. Set this only if you launched the server with the `--api-key KEY` flag."
|
|
373
|
+
}
|
|
374
|
+
]
|
|
375
|
+
},
|
|
257
376
|
// --- Ancillary services (web search, etc) ---
|
|
377
|
+
{
|
|
378
|
+
id: "firecrawl",
|
|
379
|
+
displayName: "Firecrawl",
|
|
380
|
+
category: "service",
|
|
381
|
+
group: "Web Search",
|
|
382
|
+
hint: "web_fetch fallback for JS-heavy/blocked pages \xB7 firecrawl.dev (free tier available)",
|
|
383
|
+
fields: [
|
|
384
|
+
{
|
|
385
|
+
envVar: "FIRECRAWL_API_KEY",
|
|
386
|
+
kind: "secret",
|
|
387
|
+
label: "API key",
|
|
388
|
+
placeholder: "fc-...",
|
|
389
|
+
description: "When set, web_fetch automatically falls back to Firecrawl if the primary Readability extraction yields too little content."
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
envVar: "FIRECRAWL_URL",
|
|
393
|
+
kind: "config",
|
|
394
|
+
label: "Base URL (optional, for self-hosted)",
|
|
395
|
+
placeholder: "https://api.firecrawl.dev"
|
|
396
|
+
}
|
|
397
|
+
]
|
|
398
|
+
},
|
|
258
399
|
{
|
|
259
400
|
id: "brave-search",
|
|
260
401
|
displayName: "Brave Search",
|
|
261
402
|
category: "service",
|
|
403
|
+
group: "Web Search",
|
|
262
404
|
hint: "Web search tool \xB7 free tier at brave.com/search/api/",
|
|
263
405
|
fields: [{ envVar: "BRAVE_API_KEY", kind: "secret", label: "API key", placeholder: "BSA..." }]
|
|
264
406
|
},
|
|
@@ -266,6 +408,7 @@ var SERVICES = [
|
|
|
266
408
|
id: "searxng",
|
|
267
409
|
displayName: "SearXNG",
|
|
268
410
|
category: "service",
|
|
411
|
+
group: "Web Search",
|
|
269
412
|
hint: "Self-hosted meta-search engine \xB7 searxng.org",
|
|
270
413
|
fields: [
|
|
271
414
|
{
|
|
@@ -309,7 +452,7 @@ import { createHash, randomBytes as randomBytes2, randomUUID as randomUUID4 } fr
|
|
|
309
452
|
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
310
453
|
|
|
311
454
|
// ../daemon/src/core/skills/import.ts
|
|
312
|
-
import { cpSync, existsSync as existsSync7, mkdtempSync, readdirSync as
|
|
455
|
+
import { cpSync, existsSync as existsSync7, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync5, statSync as statSync2 } from "fs";
|
|
313
456
|
import { tmpdir } from "os";
|
|
314
457
|
import { basename, join as join8, resolve as resolve2, sep } from "path";
|
|
315
458
|
import AdmZip from "adm-zip";
|
|
@@ -322,9 +465,9 @@ import { parse as parseYaml } from "yaml";
|
|
|
322
465
|
import {
|
|
323
466
|
existsSync as existsSync8,
|
|
324
467
|
mkdirSync as mkdirSync4,
|
|
325
|
-
readdirSync as
|
|
468
|
+
readdirSync as readdirSync5,
|
|
326
469
|
readFileSync as readFileSync6,
|
|
327
|
-
rmSync as
|
|
470
|
+
rmSync as rmSync6,
|
|
328
471
|
statSync as statSync3,
|
|
329
472
|
writeFileSync as writeFileSync4
|
|
330
473
|
} from "fs";
|
|
@@ -357,7 +500,7 @@ function safeKey(root, key) {
|
|
|
357
500
|
}
|
|
358
501
|
function walkMd(dir, prefix, out) {
|
|
359
502
|
if (!existsSync8(dir)) return;
|
|
360
|
-
for (const e of
|
|
503
|
+
for (const e of readdirSync5(dir, { withFileTypes: true })) {
|
|
361
504
|
if (e.name.startsWith(".")) continue;
|
|
362
505
|
const full = join9(dir, e.name);
|
|
363
506
|
const key = prefix ? `${prefix}/${e.name}` : e.name;
|
|
@@ -432,7 +575,7 @@ function qmdBackend(root) {
|
|
|
432
575
|
},
|
|
433
576
|
async remove(key) {
|
|
434
577
|
const path = safeKey(root, key);
|
|
435
|
-
if (existsSync8(path))
|
|
578
|
+
if (existsSync8(path)) rmSync6(path);
|
|
436
579
|
const store = await getStore(root);
|
|
437
580
|
await store.update();
|
|
438
581
|
}
|
|
@@ -558,7 +701,7 @@ function piMessagesToProviderView(messages) {
|
|
|
558
701
|
}
|
|
559
702
|
|
|
560
703
|
// ../daemon/src/runtime/pi/session.ts
|
|
561
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as
|
|
704
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
|
|
562
705
|
import { basename as basename2, join as join13 } from "path";
|
|
563
706
|
import {
|
|
564
707
|
AuthStorage,
|
|
@@ -567,14 +710,14 @@ import {
|
|
|
567
710
|
ModelRegistry,
|
|
568
711
|
SessionManager,
|
|
569
712
|
SettingsManager
|
|
570
|
-
} from "@
|
|
713
|
+
} from "@earendil-works/pi-coding-agent";
|
|
571
714
|
|
|
572
715
|
// ../daemon/src/runtime/providers/pi-adapter.ts
|
|
573
716
|
import {
|
|
574
717
|
getModel,
|
|
575
718
|
streamSimple,
|
|
576
719
|
Type
|
|
577
|
-
} from "@
|
|
720
|
+
} from "@earendil-works/pi-ai";
|
|
578
721
|
function defaultBaseUrlFor(providerName) {
|
|
579
722
|
switch (providerName) {
|
|
580
723
|
case "lmstudio":
|
|
@@ -616,7 +759,7 @@ function resolveModel(cfg, modelId) {
|
|
|
616
759
|
}
|
|
617
760
|
|
|
618
761
|
// ../daemon/src/runtime/auth/openai-codex.ts
|
|
619
|
-
import { loginOpenAICodex, refreshOpenAICodexToken } from "@
|
|
762
|
+
import { loginOpenAICodex, refreshOpenAICodexToken } from "@earendil-works/pi-ai/oauth";
|
|
620
763
|
|
|
621
764
|
// ../daemon/src/runtime/session/prompt.ts
|
|
622
765
|
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
@@ -626,13 +769,11 @@ var CONTEXT_FILE_ORDER = [
|
|
|
626
769
|
"SOUL.md",
|
|
627
770
|
"TOOLS.md",
|
|
628
771
|
"IDENTITY.md",
|
|
629
|
-
"HEARTBEAT.md"
|
|
630
|
-
"BOOTSTRAP.md"
|
|
772
|
+
"HEARTBEAT.md"
|
|
631
773
|
];
|
|
632
774
|
function buildSystemPrompt(agent) {
|
|
633
775
|
const parts = [];
|
|
634
776
|
const contextBlocks = [];
|
|
635
|
-
let bootstrapPresent = false;
|
|
636
777
|
for (const file of CONTEXT_FILE_ORDER) {
|
|
637
778
|
const path = join10(agent.agent.dir, file);
|
|
638
779
|
if (!existsSync9(path)) continue;
|
|
@@ -641,23 +782,39 @@ function buildSystemPrompt(agent) {
|
|
|
641
782
|
contextBlocks.push(`## ${file}
|
|
642
783
|
|
|
643
784
|
${content}`);
|
|
644
|
-
if (file === "BOOTSTRAP.md") bootstrapPresent = true;
|
|
645
785
|
}
|
|
646
786
|
if (contextBlocks.length > 0) {
|
|
647
787
|
parts.push(`# Project Context
|
|
648
788
|
|
|
649
789
|
${contextBlocks.join("\n\n")}`);
|
|
650
790
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
)
|
|
791
|
+
const bootstrapPath = join10(agent.agent.dir, "BOOTSTRAP.md");
|
|
792
|
+
if (existsSync9(bootstrapPath)) {
|
|
793
|
+
const bootstrap = readFileSync7(bootstrapPath, "utf8").trimEnd();
|
|
794
|
+
if (bootstrap) {
|
|
795
|
+
parts.push(
|
|
796
|
+
[
|
|
797
|
+
"# First-Run Ritual",
|
|
798
|
+
"",
|
|
799
|
+
"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.",
|
|
800
|
+
"",
|
|
801
|
+
"## Hard rules",
|
|
802
|
+
"- Your first reply is ONLY a greeting + ONE question. No tool calls. Wait for the human to answer.",
|
|
803
|
+
"- Each subsequent turn: at most one new question. Wait between turns.",
|
|
804
|
+
"- Only after the ritual is complete (you have enough to write IDENTITY.md): call `home_write` once, then `bootstrap_done`.",
|
|
805
|
+
"",
|
|
806
|
+
"## BOOTSTRAP.md",
|
|
807
|
+
"",
|
|
808
|
+
bootstrap
|
|
809
|
+
].join("\n")
|
|
810
|
+
);
|
|
811
|
+
}
|
|
655
812
|
}
|
|
656
813
|
parts.push(
|
|
657
814
|
[
|
|
658
815
|
"# Agent Home",
|
|
659
816
|
"",
|
|
660
|
-
"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
|
|
817
|
+
"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.",
|
|
661
818
|
"",
|
|
662
819
|
"- To change who you are (name, vibe, personality, how you behave): use `home_write`.",
|
|
663
820
|
"- To inspect exact wording of your own files: use `home_read` or `home_list`.",
|
|
@@ -684,13 +841,19 @@ You have access to the following skills: ${agent.skills.join(", ")}.`
|
|
|
684
841
|
parts.push(
|
|
685
842
|
`# About the User
|
|
686
843
|
|
|
687
|
-
|
|
844
|
+
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.**
|
|
688
845
|
|
|
689
846
|
${agent.group.userMd.trim()}`
|
|
690
847
|
);
|
|
848
|
+
} else {
|
|
849
|
+
parts.push(
|
|
850
|
+
`# About the User
|
|
851
|
+
|
|
852
|
+
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.**`
|
|
853
|
+
);
|
|
691
854
|
}
|
|
692
855
|
parts.push(
|
|
693
|
-
"# 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."
|
|
856
|
+
"# 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.**"
|
|
694
857
|
);
|
|
695
858
|
return parts.join("\n\n---\n\n");
|
|
696
859
|
}
|
|
@@ -699,7 +862,7 @@ ${agent.group.userMd.trim()}`
|
|
|
699
862
|
import { Type as Type2 } from "typebox";
|
|
700
863
|
|
|
701
864
|
// ../daemon/src/runtime/tools/bootstrap.ts
|
|
702
|
-
import { existsSync as existsSync10, rmSync as
|
|
865
|
+
import { existsSync as existsSync10, rmSync as rmSync7 } from "fs";
|
|
703
866
|
import { join as join11 } from "path";
|
|
704
867
|
function bootstrapTool(agentDir) {
|
|
705
868
|
return {
|
|
@@ -711,7 +874,7 @@ function bootstrapTool(agentDir) {
|
|
|
711
874
|
async invoke() {
|
|
712
875
|
const path = join11(agentDir, "BOOTSTRAP.md");
|
|
713
876
|
if (existsSync10(path)) {
|
|
714
|
-
|
|
877
|
+
rmSync7(path);
|
|
715
878
|
return "BOOTSTRAP.md removed. Bootstrap is complete.";
|
|
716
879
|
}
|
|
717
880
|
return "BOOTSTRAP.md was already removed.";
|
|
@@ -720,7 +883,7 @@ function bootstrapTool(agentDir) {
|
|
|
720
883
|
}
|
|
721
884
|
|
|
722
885
|
// ../daemon/src/runtime/tools/home.ts
|
|
723
|
-
import { readdirSync as
|
|
886
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
724
887
|
import { join as join12 } from "path";
|
|
725
888
|
var HOME_FILES_READABLE = [
|
|
726
889
|
"IDENTITY.md",
|
|
@@ -812,7 +975,7 @@ function homeTools(agentDir) {
|
|
|
812
975
|
if (entries.length === 0) {
|
|
813
976
|
const dirEntries = (() => {
|
|
814
977
|
try {
|
|
815
|
-
return
|
|
978
|
+
return readdirSync6(agentDir);
|
|
816
979
|
} catch {
|
|
817
980
|
return [];
|
|
818
981
|
}
|
|
@@ -831,7 +994,7 @@ function memoryTools(memory) {
|
|
|
831
994
|
{
|
|
832
995
|
def: {
|
|
833
996
|
name: "memory_write",
|
|
834
|
-
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)
|
|
997
|
+
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".',
|
|
835
998
|
parameters: {
|
|
836
999
|
type: "object",
|
|
837
1000
|
properties: {
|
|
@@ -917,7 +1080,7 @@ function messagingTools(host, fromAgentId) {
|
|
|
917
1080
|
{
|
|
918
1081
|
def: {
|
|
919
1082
|
name: "send_message",
|
|
920
|
-
description:
|
|
1083
|
+
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.`,
|
|
921
1084
|
parameters: {
|
|
922
1085
|
type: "object",
|
|
923
1086
|
properties: {
|
|
@@ -1021,6 +1184,64 @@ function messagingTools(host, fromAgentId) {
|
|
|
1021
1184
|
];
|
|
1022
1185
|
}
|
|
1023
1186
|
|
|
1187
|
+
// ../daemon/src/runtime/tools/user-md.ts
|
|
1188
|
+
function userMdTools(host, groupId) {
|
|
1189
|
+
return [
|
|
1190
|
+
{
|
|
1191
|
+
def: {
|
|
1192
|
+
name: "user_md_get",
|
|
1193
|
+
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`.",
|
|
1194
|
+
parameters: {
|
|
1195
|
+
type: "object",
|
|
1196
|
+
properties: {}
|
|
1197
|
+
}
|
|
1198
|
+
},
|
|
1199
|
+
async invoke() {
|
|
1200
|
+
const { content, etag } = await host.get(groupId);
|
|
1201
|
+
const body = content.length > 0 ? content : "(USER.md is empty)";
|
|
1202
|
+
return `${body}
|
|
1203
|
+
|
|
1204
|
+
---
|
|
1205
|
+
etag: ${etag}`;
|
|
1206
|
+
}
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
def: {
|
|
1210
|
+
name: "user_md_write",
|
|
1211
|
+
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.",
|
|
1212
|
+
parameters: {
|
|
1213
|
+
type: "object",
|
|
1214
|
+
properties: {
|
|
1215
|
+
content: {
|
|
1216
|
+
type: "string",
|
|
1217
|
+
description: "Full new contents of USER.md (this is a complete replacement, NOT an append). Include everything you want to keep."
|
|
1218
|
+
},
|
|
1219
|
+
if_match: {
|
|
1220
|
+
type: "string",
|
|
1221
|
+
description: "The etag returned by your most recent `user_md_get`. The write fails if USER.md changed in the meantime."
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
required: ["content", "if_match"]
|
|
1225
|
+
}
|
|
1226
|
+
},
|
|
1227
|
+
async invoke(args) {
|
|
1228
|
+
const content = String(args.content ?? "");
|
|
1229
|
+
const ifMatch = String(args.if_match ?? "");
|
|
1230
|
+
if (!ifMatch) {
|
|
1231
|
+
throw new Error(
|
|
1232
|
+
'user_md_write: "if_match" is required \u2014 call user_md_get first to obtain the current etag.'
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
const { etag, totalBytes } = await host.write(groupId, content, ifMatch);
|
|
1236
|
+
return `wrote USER.md (${totalBytes} bytes, new etag: ${etag})`;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
];
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// ../daemon/src/runtime/tools/web.ts
|
|
1243
|
+
import { fetch as undiciFetch2 } from "undici";
|
|
1244
|
+
|
|
1024
1245
|
// ../daemon/src/runtime/tools/web-extract.ts
|
|
1025
1246
|
import { Readability } from "@mozilla/readability";
|
|
1026
1247
|
import { parseHTML } from "linkedom";
|
|
@@ -1101,7 +1322,7 @@ function extractReadable(html, url, mode) {
|
|
|
1101
1322
|
// ../daemon/src/runtime/tools/web-ssrf.ts
|
|
1102
1323
|
import { lookup as dnsLookupCb } from "dns";
|
|
1103
1324
|
import { lookup as dnsLookup } from "dns/promises";
|
|
1104
|
-
import { Agent } from "undici";
|
|
1325
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
1105
1326
|
var SsrFBlockedError = class extends Error {
|
|
1106
1327
|
constructor(message) {
|
|
1107
1328
|
super(message);
|
|
@@ -1210,7 +1431,7 @@ async function closeDispatcher(d) {
|
|
|
1210
1431
|
}
|
|
1211
1432
|
}
|
|
1212
1433
|
async function guardedFetch(opts) {
|
|
1213
|
-
const fetcher = opts.fetchImpl ??
|
|
1434
|
+
const fetcher = opts.fetchImpl ?? undiciFetch;
|
|
1214
1435
|
const maxRedirects = opts.maxRedirects ?? 3;
|
|
1215
1436
|
const abortController = new AbortController();
|
|
1216
1437
|
const timeoutId = opts.timeoutMs ? setTimeout(() => abortController.abort(new Error("timeout")), opts.timeoutMs) : null;
|
|
@@ -1291,7 +1512,10 @@ var DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWe
|
|
|
1291
1512
|
var DEFAULT_CACHE_TTL_MS = 15 * 6e4;
|
|
1292
1513
|
var DEFAULT_CACHE_MAX = 100;
|
|
1293
1514
|
var DEFAULT_MAX_LENGTH = 2e4;
|
|
1294
|
-
var DEFAULT_TIMEOUT_MS =
|
|
1515
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
1516
|
+
var DEFAULT_MAX_BODY_BYTES = 3 * 1024 * 1024;
|
|
1517
|
+
var FIRECRAWL_FALLBACK_THRESHOLD = 200;
|
|
1518
|
+
var FIRECRAWL_DEFAULT_URL = "https://api.firecrawl.dev";
|
|
1295
1519
|
function stripHtml(s) {
|
|
1296
1520
|
return s.replace(/<[^>]*>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, " ").trim();
|
|
1297
1521
|
}
|
|
@@ -1325,6 +1549,92 @@ async function searxngSearch(query, limit, baseURL, fetchFn) {
|
|
|
1325
1549
|
snippet: r.content ?? ""
|
|
1326
1550
|
}));
|
|
1327
1551
|
}
|
|
1552
|
+
function describeError(err) {
|
|
1553
|
+
if (!(err instanceof Error)) return String(err);
|
|
1554
|
+
const parts = [];
|
|
1555
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1556
|
+
let cur = err;
|
|
1557
|
+
while (cur instanceof Error && !seen.has(cur)) {
|
|
1558
|
+
seen.add(cur);
|
|
1559
|
+
const code = cur.code;
|
|
1560
|
+
parts.push(code ? `[${code}] ${cur.message}` : cur.message);
|
|
1561
|
+
cur = cur.cause;
|
|
1562
|
+
}
|
|
1563
|
+
return parts.join(" \u2014 cause: ");
|
|
1564
|
+
}
|
|
1565
|
+
async function readBodyCapped(res, maxBytes) {
|
|
1566
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
1567
|
+
const charset = /charset=([^;]+)/i.exec(ct)?.[1]?.trim().toLowerCase() || "utf-8";
|
|
1568
|
+
const decoder = new TextDecoder(charset, { fatal: false });
|
|
1569
|
+
const reader = res.body?.getReader();
|
|
1570
|
+
if (!reader) {
|
|
1571
|
+
const text = await res.text();
|
|
1572
|
+
if (text.length > maxBytes) return { text: text.slice(0, maxBytes), truncated: true };
|
|
1573
|
+
return { text, truncated: false };
|
|
1574
|
+
}
|
|
1575
|
+
const chunks = [];
|
|
1576
|
+
let total = 0;
|
|
1577
|
+
let truncated = false;
|
|
1578
|
+
while (true) {
|
|
1579
|
+
const { value, done } = await reader.read();
|
|
1580
|
+
if (done) break;
|
|
1581
|
+
if (!value) continue;
|
|
1582
|
+
if (total + value.byteLength > maxBytes) {
|
|
1583
|
+
const keep = maxBytes - total;
|
|
1584
|
+
if (keep > 0) chunks.push(value.subarray(0, keep));
|
|
1585
|
+
truncated = true;
|
|
1586
|
+
try {
|
|
1587
|
+
await reader.cancel();
|
|
1588
|
+
} catch {
|
|
1589
|
+
}
|
|
1590
|
+
break;
|
|
1591
|
+
}
|
|
1592
|
+
chunks.push(value);
|
|
1593
|
+
total += value.byteLength;
|
|
1594
|
+
}
|
|
1595
|
+
const sum = chunks.reduce((s, c) => s + c.byteLength, 0);
|
|
1596
|
+
const merged = new Uint8Array(sum);
|
|
1597
|
+
let off = 0;
|
|
1598
|
+
for (const c of chunks) {
|
|
1599
|
+
merged.set(c, off);
|
|
1600
|
+
off += c.byteLength;
|
|
1601
|
+
}
|
|
1602
|
+
return { text: decoder.decode(merged), truncated };
|
|
1603
|
+
}
|
|
1604
|
+
async function firecrawlScrape(url, mode, env, fetchFn, timeoutMs) {
|
|
1605
|
+
const apiKey = env.FIRECRAWL_API_KEY;
|
|
1606
|
+
if (!apiKey) return null;
|
|
1607
|
+
const base = (env.FIRECRAWL_URL ?? FIRECRAWL_DEFAULT_URL).replace(/\/$/, "");
|
|
1608
|
+
const ac = new AbortController();
|
|
1609
|
+
const t = setTimeout(() => ac.abort(new Error("firecrawl timeout")), timeoutMs);
|
|
1610
|
+
try {
|
|
1611
|
+
const res = await fetchFn(`${base}/v1/scrape`, {
|
|
1612
|
+
method: "POST",
|
|
1613
|
+
headers: {
|
|
1614
|
+
"content-type": "application/json",
|
|
1615
|
+
authorization: `Bearer ${apiKey}`,
|
|
1616
|
+
accept: "application/json"
|
|
1617
|
+
},
|
|
1618
|
+
body: JSON.stringify({
|
|
1619
|
+
url,
|
|
1620
|
+
formats: ["markdown"],
|
|
1621
|
+
onlyMainContent: true
|
|
1622
|
+
}),
|
|
1623
|
+
signal: ac.signal
|
|
1624
|
+
});
|
|
1625
|
+
if (!res.ok) return null;
|
|
1626
|
+
const body = await res.json();
|
|
1627
|
+
if (!body.success || !body.data?.markdown) return null;
|
|
1628
|
+
const md = body.data.markdown;
|
|
1629
|
+
const title = body.data.metadata?.title;
|
|
1630
|
+
const text = mode === "text" ? markdownToPlain(md) : md;
|
|
1631
|
+
return title ? { text, title } : { text };
|
|
1632
|
+
} catch {
|
|
1633
|
+
return null;
|
|
1634
|
+
} finally {
|
|
1635
|
+
clearTimeout(t);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1328
1638
|
function cacheGet(cache, key) {
|
|
1329
1639
|
const entry = cache.get(key);
|
|
1330
1640
|
if (!entry) return null;
|
|
@@ -1345,12 +1655,14 @@ function cacheSet(cache, key, value, ttlMs, maxEntries) {
|
|
|
1345
1655
|
}
|
|
1346
1656
|
}
|
|
1347
1657
|
function webTools(opts) {
|
|
1348
|
-
const fetchFn = opts?.fetchImpl ??
|
|
1658
|
+
const fetchFn = opts?.fetchImpl ?? undiciFetch2;
|
|
1349
1659
|
const env = opts?.env ?? process.env;
|
|
1350
1660
|
const allowPrivate = opts?.allowPrivate ?? false;
|
|
1351
1661
|
const cacheTtlMs = opts?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
1352
1662
|
const cacheMax = opts?.cacheMax ?? DEFAULT_CACHE_MAX;
|
|
1353
1663
|
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1664
|
+
const maxBodyBytes = opts?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
1665
|
+
const firecrawlDisabled = opts?.firecrawlDisabled ?? false;
|
|
1354
1666
|
const cache = /* @__PURE__ */ new Map();
|
|
1355
1667
|
return [
|
|
1356
1668
|
{
|
|
@@ -1388,7 +1700,7 @@ function webTools(opts) {
|
|
|
1388
1700
|
{
|
|
1389
1701
|
def: {
|
|
1390
1702
|
name: "web_fetch",
|
|
1391
|
-
description: "Fetch a URL and return its readable content. HTML is extracted via Readability and converted to markdown. Results are cached for 15 minutes.",
|
|
1703
|
+
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.",
|
|
1392
1704
|
parameters: {
|
|
1393
1705
|
type: "object",
|
|
1394
1706
|
properties: {
|
|
@@ -1414,7 +1726,7 @@ function webTools(opts) {
|
|
|
1414
1726
|
const cacheKey = `${mode}|${url}`;
|
|
1415
1727
|
const cached = cacheGet(cache, cacheKey);
|
|
1416
1728
|
if (cached) return formatOutput(cached, maxLen);
|
|
1417
|
-
let result;
|
|
1729
|
+
let result = null;
|
|
1418
1730
|
try {
|
|
1419
1731
|
result = await guardedFetch({
|
|
1420
1732
|
url,
|
|
@@ -1429,18 +1741,14 @@ function webTools(opts) {
|
|
|
1429
1741
|
}
|
|
1430
1742
|
}
|
|
1431
1743
|
});
|
|
1432
|
-
} catch (err) {
|
|
1433
|
-
if (err instanceof SsrFBlockedError) throw new Error(`web_fetch: ${err.message}`);
|
|
1434
|
-
throw err;
|
|
1435
|
-
}
|
|
1436
|
-
try {
|
|
1437
1744
|
if (!result.response.ok) {
|
|
1438
|
-
throw new Error(
|
|
1745
|
+
throw new Error(`${result.response.status} ${result.response.statusText}`);
|
|
1439
1746
|
}
|
|
1440
1747
|
const ct = result.response.headers.get("content-type") ?? "";
|
|
1441
|
-
const
|
|
1748
|
+
const isHtml = ct.includes("text/html") || ct.includes("xhtml");
|
|
1749
|
+
const { text: body, truncated } = await readBodyCapped(result.response, maxBodyBytes);
|
|
1442
1750
|
let extracted;
|
|
1443
|
-
if (
|
|
1751
|
+
if (isHtml) {
|
|
1444
1752
|
extracted = extractReadable(body, result.finalUrl, mode);
|
|
1445
1753
|
} else if (ct.includes("application/json")) {
|
|
1446
1754
|
try {
|
|
@@ -1451,10 +1759,32 @@ function webTools(opts) {
|
|
|
1451
1759
|
} else {
|
|
1452
1760
|
extracted = { text: body };
|
|
1453
1761
|
}
|
|
1762
|
+
if (isHtml && !firecrawlDisabled && extracted.text.length < FIRECRAWL_FALLBACK_THRESHOLD) {
|
|
1763
|
+
const rescued = await firecrawlScrape(result.finalUrl, mode, env, fetchFn, timeoutMs);
|
|
1764
|
+
if (rescued) {
|
|
1765
|
+
extracted = {
|
|
1766
|
+
...rescued,
|
|
1767
|
+
text: `${rescued.text}
|
|
1768
|
+
|
|
1769
|
+
[content rendered via Firecrawl fallback \u2014 primary extraction returned ${extracted.text.length} chars]`
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
if (truncated) {
|
|
1774
|
+
extracted = {
|
|
1775
|
+
...extracted,
|
|
1776
|
+
text: `${extracted.text}
|
|
1777
|
+
|
|
1778
|
+
[raw body truncated at ${maxBodyBytes} bytes before extraction \u2014 page exceeded the size cap]`
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1454
1781
|
cacheSet(cache, cacheKey, extracted, cacheTtlMs, cacheMax);
|
|
1455
1782
|
return formatOutput(extracted, maxLen);
|
|
1783
|
+
} catch (err) {
|
|
1784
|
+
if (err instanceof SsrFBlockedError) throw new Error(`web_fetch: ${err.message}`);
|
|
1785
|
+
throw new Error(`web_fetch: ${describeError(err)}`);
|
|
1456
1786
|
} finally {
|
|
1457
|
-
await result.release();
|
|
1787
|
+
if (result) await result.release();
|
|
1458
1788
|
}
|
|
1459
1789
|
}
|
|
1460
1790
|
}
|
|
@@ -1501,13 +1831,16 @@ function createBazilionCustomTools(opts) {
|
|
|
1501
1831
|
if (opts.messagingHost) {
|
|
1502
1832
|
handlers.push(...messagingTools(opts.messagingHost, opts.agent.agent.id));
|
|
1503
1833
|
}
|
|
1834
|
+
if (opts.userMdHost) {
|
|
1835
|
+
handlers.push(...userMdTools(opts.userMdHost, opts.agent.group.id));
|
|
1836
|
+
}
|
|
1504
1837
|
return handlers.map(ourToolToPiTool);
|
|
1505
1838
|
}
|
|
1506
1839
|
|
|
1507
1840
|
// ../daemon/src/runtime/pi/session.ts
|
|
1508
1841
|
var BUILTIN_TOOL_NAMES = ["read", "bash", "edit", "write", "grep", "find", "ls"];
|
|
1509
1842
|
async function createBazilionSession(opts) {
|
|
1510
|
-
const { agent, paths, env, memory, enabledProviders, messagingHost, refreshApiKey } = opts;
|
|
1843
|
+
const { agent, paths, env, memory, enabledProviders, messagingHost, userMdHost, refreshApiKey } = opts;
|
|
1511
1844
|
const { providerName, modelId } = splitModelString(agent.model);
|
|
1512
1845
|
if (enabledProviders.size > 0 && !enabledProviders.has(providerName)) {
|
|
1513
1846
|
throw new Error(`${providerName} provider is disabled \u2014 enable it on the /config page`);
|
|
@@ -1544,12 +1877,17 @@ async function createBazilionSession(opts) {
|
|
|
1544
1877
|
const sessionManager = existing ? SessionManager.open(existing, sessionDir, cwd) : SessionManager.create(cwd, sessionDir);
|
|
1545
1878
|
const settingsManager = SettingsManager.inMemory({
|
|
1546
1879
|
compaction: { enabled: false },
|
|
1547
|
-
retry: {
|
|
1880
|
+
retry: {
|
|
1881
|
+
enabled: true,
|
|
1882
|
+
maxRetries: 2,
|
|
1883
|
+
baseDelayMs: 500,
|
|
1884
|
+
provider: { maxRetryDelayMs: 8e3 }
|
|
1885
|
+
}
|
|
1548
1886
|
});
|
|
1549
1887
|
const bazilionPrompt = buildSystemPrompt(agent);
|
|
1550
1888
|
const resourceLoader = createBazilionResourceLoader(bazilionPrompt);
|
|
1551
1889
|
await resourceLoader.reload();
|
|
1552
|
-
const customTools = createBazilionCustomTools({ agent, memory, messagingHost, env });
|
|
1890
|
+
const customTools = createBazilionCustomTools({ agent, memory, messagingHost, userMdHost, env });
|
|
1553
1891
|
const allowedTools = [...BUILTIN_TOOL_NAMES, ...customTools.map((t) => t.name)];
|
|
1554
1892
|
const { session } = await createAgentSession({
|
|
1555
1893
|
cwd,
|
|
@@ -1681,7 +2019,7 @@ function createBazilionResourceLoader(appendSystemPrompt) {
|
|
|
1681
2019
|
function findMostRecent(sessionDir) {
|
|
1682
2020
|
if (!existsSync11(sessionDir)) return null;
|
|
1683
2021
|
let newest = null;
|
|
1684
|
-
for (const entry of
|
|
2022
|
+
for (const entry of readdirSync7(sessionDir)) {
|
|
1685
2023
|
if (!entry.endsWith(".jsonl")) continue;
|
|
1686
2024
|
const path = join13(sessionDir, entry);
|
|
1687
2025
|
try {
|
|
@@ -1709,7 +2047,7 @@ async function readInput() {
|
|
|
1709
2047
|
}
|
|
1710
2048
|
return parsed;
|
|
1711
2049
|
}
|
|
1712
|
-
function
|
|
2050
|
+
function createIpcClient() {
|
|
1713
2051
|
const pending = /* @__PURE__ */ new Map();
|
|
1714
2052
|
process.on("message", (msg) => {
|
|
1715
2053
|
if (!isIpcReply(msg)) return;
|
|
@@ -1723,17 +2061,14 @@ function createIpcMessagingHost() {
|
|
|
1723
2061
|
for (const [, entry] of pending) entry.reject(new Error("worker IPC channel disconnected"));
|
|
1724
2062
|
pending.clear();
|
|
1725
2063
|
});
|
|
1726
|
-
|
|
2064
|
+
return (method, args) => {
|
|
1727
2065
|
if (!process.send) {
|
|
1728
2066
|
return Promise.reject(new Error("worker: no IPC channel \u2014 daemon must spawn with stdio:ipc"));
|
|
1729
2067
|
}
|
|
1730
2068
|
const id = randomUUID5();
|
|
1731
2069
|
const message = { type: "rpc", id, method, args };
|
|
1732
2070
|
return new Promise((resolve3, reject) => {
|
|
1733
|
-
pending.set(id, {
|
|
1734
|
-
resolve: (v) => resolve3(v),
|
|
1735
|
-
reject
|
|
1736
|
-
});
|
|
2071
|
+
pending.set(id, { resolve: (v) => resolve3(v), reject });
|
|
1737
2072
|
process.send?.(message, void 0, void 0, (err) => {
|
|
1738
2073
|
if (err) {
|
|
1739
2074
|
pending.delete(id);
|
|
@@ -1741,7 +2076,9 @@ function createIpcMessagingHost() {
|
|
|
1741
2076
|
}
|
|
1742
2077
|
});
|
|
1743
2078
|
});
|
|
1744
|
-
}
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
function createIpcMessagingHost(call) {
|
|
1745
2082
|
return {
|
|
1746
2083
|
agentExists: (agentId) => call("agentExists", { agentId }),
|
|
1747
2084
|
sendMessage: (input) => call("sendMessage", input),
|
|
@@ -1752,6 +2089,12 @@ function createIpcMessagingHost() {
|
|
|
1752
2089
|
findReplies: (agentId, replyTo) => call("findReplies", { agentId, replyTo })
|
|
1753
2090
|
};
|
|
1754
2091
|
}
|
|
2092
|
+
function createIpcUserMdHost(call) {
|
|
2093
|
+
return {
|
|
2094
|
+
get: (groupId) => call("userMdGet", { groupId }),
|
|
2095
|
+
write: (groupId, content, ifMatch) => call("userMdWrite", { groupId, content, ifMatch })
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
1755
2098
|
function isIpcReply(msg) {
|
|
1756
2099
|
if (!msg || typeof msg !== "object") return false;
|
|
1757
2100
|
const m = msg;
|
|
@@ -1778,7 +2121,9 @@ async function main() {
|
|
|
1778
2121
|
const paths = resolvePaths();
|
|
1779
2122
|
const memory = qmdBackend(join14(agent.group.path, "memory"));
|
|
1780
2123
|
await memory.init();
|
|
1781
|
-
const
|
|
2124
|
+
const ipcCall = createIpcClient();
|
|
2125
|
+
const messagingHost = createIpcMessagingHost(ipcCall);
|
|
2126
|
+
const userMdHost = createIpcUserMdHost(ipcCall);
|
|
1782
2127
|
const { session, dispose } = await createBazilionSession({
|
|
1783
2128
|
agent,
|
|
1784
2129
|
paths,
|
|
@@ -1786,6 +2131,7 @@ async function main() {
|
|
|
1786
2131
|
memory,
|
|
1787
2132
|
enabledProviders: new Set(enabledProviders),
|
|
1788
2133
|
messagingHost,
|
|
2134
|
+
userMdHost,
|
|
1789
2135
|
apiKey
|
|
1790
2136
|
});
|
|
1791
2137
|
abortSession = () => {
|