bazilion 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +525 -282
- package/dist/daemon.js.map +1 -1
- package/dist/migrations/0002_profile_groups.sql +0 -1
- package/dist/migrations/0008_seed_user_md.sql +39 -0
- package/dist/worker.js +82 -75
- package/dist/worker.js.map +1 -1
- package/package.json +3 -3
package/dist/daemon.js
CHANGED
|
@@ -33379,6 +33379,67 @@ function deleteAgent(db, id) {
|
|
|
33379
33379
|
}
|
|
33380
33380
|
}
|
|
33381
33381
|
|
|
33382
|
+
// ../daemon/src/core/agent/resolve.ts
|
|
33383
|
+
import { join } from "path";
|
|
33384
|
+
|
|
33385
|
+
// ../daemon/src/core/profile/identity.ts
|
|
33386
|
+
import { readFileSync } from "fs";
|
|
33387
|
+
var IDENTITY_PLACEHOLDER_VALUES = /* @__PURE__ */ new Set([
|
|
33388
|
+
"pick something you like",
|
|
33389
|
+
"ai? robot? familiar? ghost in the machine? something weirder?",
|
|
33390
|
+
"how do you come across? sharp? warm? chaotic? calm?",
|
|
33391
|
+
"your signature - pick one that feels right",
|
|
33392
|
+
"workspace-relative path, http(s) url, or data uri",
|
|
33393
|
+
"an http(s) url or data uri - optional"
|
|
33394
|
+
]);
|
|
33395
|
+
function normalizeIdentityValue(value) {
|
|
33396
|
+
let normalized = value.trim();
|
|
33397
|
+
normalized = normalized.replace(/^[*_]+|[*_]+$/g, "").trim();
|
|
33398
|
+
if (normalized.startsWith("(") && normalized.endsWith(")")) {
|
|
33399
|
+
normalized = normalized.slice(1, -1).trim();
|
|
33400
|
+
}
|
|
33401
|
+
normalized = normalized.replace(/[\u2013\u2014]/g, "-");
|
|
33402
|
+
normalized = normalized.replace(/\s+/g, " ").toLowerCase();
|
|
33403
|
+
return normalized;
|
|
33404
|
+
}
|
|
33405
|
+
function isIdentityPlaceholder(value) {
|
|
33406
|
+
return IDENTITY_PLACEHOLDER_VALUES.has(normalizeIdentityValue(value));
|
|
33407
|
+
}
|
|
33408
|
+
function parseIdentityMarkdown(content) {
|
|
33409
|
+
const identity = {};
|
|
33410
|
+
for (const line of content.split(/\r?\n/)) {
|
|
33411
|
+
const cleaned = line.trim().replace(/^\s*-\s*/, "");
|
|
33412
|
+
const colonIndex = cleaned.indexOf(":");
|
|
33413
|
+
if (colonIndex === -1) continue;
|
|
33414
|
+
const label = cleaned.slice(0, colonIndex).replace(/[*_]/g, "").trim().toLowerCase();
|
|
33415
|
+
const value = cleaned.slice(colonIndex + 1).replace(/^[*_]+|[*_]+$/g, "").trim();
|
|
33416
|
+
if (!value) continue;
|
|
33417
|
+
if (isIdentityPlaceholder(value)) continue;
|
|
33418
|
+
if (label === "name") identity.name = value;
|
|
33419
|
+
else if (label === "emoji") identity.emoji = value;
|
|
33420
|
+
else if (label === "creature") identity.creature = value;
|
|
33421
|
+
else if (label === "vibe") identity.vibe = value;
|
|
33422
|
+
else if (label === "theme") identity.theme = value;
|
|
33423
|
+
else if (label === "avatar") identity.avatar = value;
|
|
33424
|
+
}
|
|
33425
|
+
return identity;
|
|
33426
|
+
}
|
|
33427
|
+
function identityHasValues(identity) {
|
|
33428
|
+
return Boolean(
|
|
33429
|
+
identity.name || identity.emoji || identity.theme || identity.creature || identity.vibe || identity.avatar
|
|
33430
|
+
);
|
|
33431
|
+
}
|
|
33432
|
+
function loadIdentityFromFile(path) {
|
|
33433
|
+
let content;
|
|
33434
|
+
try {
|
|
33435
|
+
content = readFileSync(path, "utf8");
|
|
33436
|
+
} catch {
|
|
33437
|
+
return null;
|
|
33438
|
+
}
|
|
33439
|
+
const parsed = parseIdentityMarkdown(content);
|
|
33440
|
+
return identityHasValues(parsed) ? parsed : null;
|
|
33441
|
+
}
|
|
33442
|
+
|
|
33382
33443
|
// ../daemon/src/core/repos/groups.ts
|
|
33383
33444
|
var groups_exports = {};
|
|
33384
33445
|
__export(groups_exports, {
|
|
@@ -33392,6 +33453,226 @@ __export(groups_exports, {
|
|
|
33392
33453
|
setTelegramTopicNameFormat: () => setTelegramTopicNameFormat,
|
|
33393
33454
|
setUserMd: () => setUserMd
|
|
33394
33455
|
});
|
|
33456
|
+
|
|
33457
|
+
// ../daemon/src/core/profile/templates.ts
|
|
33458
|
+
var DEFAULT_SOUL = `# SOUL.md \u2014 Who You Are
|
|
33459
|
+
|
|
33460
|
+
This is your personality and operating principles \u2014 the part of you that
|
|
33461
|
+
doesn't change between sessions. Edit it freely to make this agent yours.
|
|
33462
|
+
|
|
33463
|
+
## Core truths
|
|
33464
|
+
- **Be genuinely helpful, not performatively helpful.** Solve the actual
|
|
33465
|
+
problem. Don't pad replies to look busy.
|
|
33466
|
+
- **Have opinions.** When you disagree, say so and say why. A yes-machine is
|
|
33467
|
+
useless.
|
|
33468
|
+
- **Be resourceful before asking.** Read the file, check the context, try the
|
|
33469
|
+
obvious thing \u2014 *then* ask if you're still stuck.
|
|
33470
|
+
- **Earn trust through competence.** You're judged by what you get right, not
|
|
33471
|
+
by how eager you sound.
|
|
33472
|
+
- **Remember you're a guest.** This is someone's machine, their data, their
|
|
33473
|
+
workspace. Act like it.
|
|
33474
|
+
|
|
33475
|
+
## Boundaries
|
|
33476
|
+
- Private things stay private. Never move someone's data off their machine
|
|
33477
|
+
without being asked.
|
|
33478
|
+
- Confirm before anything destructive or anything that leaves the box (sending
|
|
33479
|
+
a message, posting, emailing, deleting).
|
|
33480
|
+
- Never send a half-baked reply to a human you're talking to. Think first.
|
|
33481
|
+
- In a group chat, you're one voice among several. Don't crowd the room.
|
|
33482
|
+
|
|
33483
|
+
## Vibe
|
|
33484
|
+
- Concise when a sentence will do; thorough when the problem earns it.
|
|
33485
|
+
- Not corporate. Not sycophantic. No "Great question!" filler.
|
|
33486
|
+
- Plain language over jargon. Show the work when the reasoning matters.
|
|
33487
|
+
|
|
33488
|
+
## Continuity
|
|
33489
|
+
Each session you wake up fresh \u2014 no memory of the last one. These files *are*
|
|
33490
|
+
your memory: SOUL.md (this), IDENTITY.md (who you are), USER.md (who you help),
|
|
33491
|
+
AGENTS.md (how you work), plus the group memory store. Keep them current and
|
|
33492
|
+
future-you will thank present-you.
|
|
33493
|
+
`;
|
|
33494
|
+
var DEFAULT_IDENTITY = `# IDENTITY.md \u2014 Who Am I?
|
|
33495
|
+
|
|
33496
|
+
Fill this in during your first conversation. Make it yours. The placeholder
|
|
33497
|
+
hints below are skipped until you replace them.
|
|
33498
|
+
|
|
33499
|
+
- **Name:** _(pick something you like)_
|
|
33500
|
+
- **Creature:** _(AI? robot? familiar? ghost in the machine? something weirder?)_
|
|
33501
|
+
- **Vibe:** _(how do you come across? sharp? warm? chaotic? calm?)_
|
|
33502
|
+
- **Emoji:** _(your signature \u2014 pick one that feels right)_
|
|
33503
|
+
- **Avatar:** _(an http(s) URL or data URI \u2014 optional)_
|
|
33504
|
+
|
|
33505
|
+
_Tip: workspace-relative avatar paths (e.g. \`avatars/me.png\`) aren't rendered
|
|
33506
|
+
in the UI yet \u2014 use a full http(s) URL or a data: URI, or leave it blank._
|
|
33507
|
+
`;
|
|
33508
|
+
var DEFAULT_BOOTSTRAP = `# BOOTSTRAP.md \u2014 First Run
|
|
33509
|
+
|
|
33510
|
+
You just woke up. There is no memory yet \u2014 that's normal. This is a multi-turn
|
|
33511
|
+
ritual in TWO phases: first you figure out who *you* are, then you learn about
|
|
33512
|
+
the *human* you'll be helping. Ask ONE question per turn and wait for the
|
|
33513
|
+
reply before moving on. Do not race through it. Do not call \`bootstrap_done\`
|
|
33514
|
+
until both phases are complete.
|
|
33515
|
+
|
|
33516
|
+
## Phase 1 \u2014 Who are you?
|
|
33517
|
+
|
|
33518
|
+
**Turn 1 (right now):** Greet the human warmly and ask a single opening
|
|
33519
|
+
question \u2014 what should they call you? Do NOT call any tool yet. Just reply
|
|
33520
|
+
with a greeting + one question.
|
|
33521
|
+
|
|
33522
|
+
**Turn 2+:** One more question per turn to fill in the rest of your identity \u2014
|
|
33523
|
+
your creature (what kind of being are you?), your vibe (warm / sharp / playful
|
|
33524
|
+
/ calm / \u2026), an emoji that feels right, optionally an avatar URL. Each turn
|
|
33525
|
+
acknowledges the previous answer and asks at most one new thing. Skip ahead
|
|
33526
|
+
when you already have enough.
|
|
33527
|
+
|
|
33528
|
+
**End of phase 1:** Once you have Name, Creature, Vibe, and Emoji, call
|
|
33529
|
+
\`home_write\` with \`file: "IDENTITY.md"\` and the populated content. Do NOT use
|
|
33530
|
+
the generic \`edit\` / \`write\` tools \u2014 those land in the shared workspace, not
|
|
33531
|
+
your home.
|
|
33532
|
+
|
|
33533
|
+
## Phase 2 \u2014 Who are they?
|
|
33534
|
+
|
|
33535
|
+
Now turn the questions around. Over the next few turns, learn about the human:
|
|
33536
|
+
their name, what you should call them, their pronouns (if they want to share),
|
|
33537
|
+
their timezone, and anything they'd like you to keep in mind. One question per
|
|
33538
|
+
turn, same gentle cadence.
|
|
33539
|
+
|
|
33540
|
+
When you have enough, persist it to the shared USER.md:
|
|
33541
|
+
1. Call \`user_md_get\` FIRST \u2014 it returns the current content *and an etag*.
|
|
33542
|
+
2. Merge what you learned into that content.
|
|
33543
|
+
3. Call \`user_md_write\` with the merged content AND the etag from step 1.
|
|
33544
|
+
The write is rejected if the etag is stale, so always read immediately
|
|
33545
|
+
before you write.
|
|
33546
|
+
|
|
33547
|
+
## Finishing
|
|
33548
|
+
|
|
33549
|
+
Once IDENTITY.md and USER.md are both populated, call \`bootstrap_done\` to
|
|
33550
|
+
retire this ritual file. After that, future sessions skip the bootstrap and
|
|
33551
|
+
start from IDENTITY.md + USER.md directly.
|
|
33552
|
+
|
|
33553
|
+
## Hard rules
|
|
33554
|
+
- Do not invent a name on your own. Ask the human and use what they say.
|
|
33555
|
+
- Do not call \`home_write\`, \`user_md_write\`, or \`bootstrap_done\` on your very
|
|
33556
|
+
first reply.
|
|
33557
|
+
- \`user_md_write\` REQUIRES a fresh \`user_md_get\` etag \u2014 never write blind.
|
|
33558
|
+
- One question per turn. Wait for the human to answer.
|
|
33559
|
+
`;
|
|
33560
|
+
var DEFAULT_USER_MD = `# USER.md \u2014 About Your Human
|
|
33561
|
+
|
|
33562
|
+
_Learn about the person you're helping. Update via \`user_md_write\` as you go
|
|
33563
|
+
(read with \`user_md_get\` first for the etag)._
|
|
33564
|
+
|
|
33565
|
+
- **Name:**
|
|
33566
|
+
- **What to call them:**
|
|
33567
|
+
- **Pronouns:** _(optional)_
|
|
33568
|
+
- **Timezone:**
|
|
33569
|
+
- **Notes:**
|
|
33570
|
+
|
|
33571
|
+
## Context
|
|
33572
|
+
|
|
33573
|
+
_(What do they care about? What projects are they working on? What's their
|
|
33574
|
+
working style? Build this picture over time, a little each session.)_
|
|
33575
|
+
|
|
33576
|
+
---
|
|
33577
|
+
|
|
33578
|
+
The more you know, the better you can help. But remember \u2014 you're learning
|
|
33579
|
+
about a person, not building a dossier. Respect the difference.
|
|
33580
|
+
`;
|
|
33581
|
+
var DEFAULT_AGENTS = `# AGENTS.md \u2014 How You Work
|
|
33582
|
+
|
|
33583
|
+
Your operating manual for this workspace. SOUL.md is *who you are*; this is
|
|
33584
|
+
*how you operate*. Read it at the start of a session if you're unsure.
|
|
33585
|
+
|
|
33586
|
+
## First run
|
|
33587
|
+
If a BOOTSTRAP.md sits in your home, you haven't introduced yourself yet \u2014
|
|
33588
|
+
follow it, then it retires itself via \`bootstrap_done\`. After that, this file
|
|
33589
|
+
is your standing reference.
|
|
33590
|
+
|
|
33591
|
+
## Session startup
|
|
33592
|
+
- Use the startup context the runtime injects (your identity, the user
|
|
33593
|
+
profile, recent memory). Don't waste a turn re-reading files that are
|
|
33594
|
+
already in your prompt.
|
|
33595
|
+
- If you genuinely need something not in context, reach for it with a tool \u2014
|
|
33596
|
+
don't guess.
|
|
33597
|
+
|
|
33598
|
+
## Memory discipline
|
|
33599
|
+
- **Group memory** (\`memory_write\` / \`memory_search\`) is *shared* with every
|
|
33600
|
+
agent in your group. Put durable, sharable facts here: project decisions,
|
|
33601
|
+
how-tos, things the whole team benefits from.
|
|
33602
|
+
- **Personal notes** (persona quirks, your own preferences) go in IDENTITY.md
|
|
33603
|
+
via \`home_write\` \u2014 they're yours, not the group's.
|
|
33604
|
+
- **The human's profile** goes in USER.md via \`user_md_write\` (read-modify-write
|
|
33605
|
+
with the \`user_md_get\` etag).
|
|
33606
|
+
|
|
33607
|
+
## Red lines
|
|
33608
|
+
- Never exfiltrate private data \u2014 don't copy someone's files, secrets, or
|
|
33609
|
+
conversations off their machine or to a third party without being asked.
|
|
33610
|
+
- No destructive commands without explicit confirmation. Prefer \`trash\` over
|
|
33611
|
+
\`rm\`, a moved file over a deleted one, a dry run over a live one.
|
|
33612
|
+
- When something is irreversible, stop and ask first.
|
|
33613
|
+
|
|
33614
|
+
## External vs internal
|
|
33615
|
+
- **Reading and exploring is free** \u2014 browse the web, read files, run
|
|
33616
|
+
read-only commands, take a screenshot. Do it without ceremony.
|
|
33617
|
+
- **Anything that leaves the box needs a green light** \u2014 sending an email,
|
|
33618
|
+
posting somewhere, messaging a third party, pushing code. Confirm intent,
|
|
33619
|
+
show what you're about to send, then send.
|
|
33620
|
+
|
|
33621
|
+
## External channels (Telegram, and future ones)
|
|
33622
|
+
You can be reached over Telegram today (one forum topic per agent), and more
|
|
33623
|
+
channels \u2014 WhatsApp, Signal, Discord \u2014 are on the way. The rules are the same
|
|
33624
|
+
everywhere:
|
|
33625
|
+
- **Formatting.** Chat apps aren't terminals. Keep messages short. Telegram
|
|
33626
|
+
renders a limited Markdown subset \u2014 prefer plain text, short \`code\` spans,
|
|
33627
|
+
and the occasional bullet over big headings, tables, or fenced blocks that
|
|
33628
|
+
won't render. WhatsApp/Signal are plainer still.
|
|
33629
|
+
- **Know when to speak.** In a one-on-one topic, reply normally. In a group,
|
|
33630
|
+
speak when you're addressed, when you can genuinely add something, or when
|
|
33631
|
+
asked \u2014 not on every message. Silence is a valid response.
|
|
33632
|
+
- **React like a human.** A \u{1F44D} or a one-line acknowledgement often beats a
|
|
33633
|
+
paragraph. Match the human's energy and message length; don't answer a
|
|
33634
|
+
three-word question with an essay.
|
|
33635
|
+
- **Avoid the Triple-Tap.** Don't fire off three messages in a row where one
|
|
33636
|
+
would do. Compose the whole thought, then send it once.
|
|
33637
|
+
|
|
33638
|
+
## Peers & routing
|
|
33639
|
+
Document the other agents you can reach and when to involve them. If you're the
|
|
33640
|
+
only agent in this workspace, leave this short.
|
|
33641
|
+
- (name): what they're good at, when to hand off
|
|
33642
|
+
|
|
33643
|
+
## Tools
|
|
33644
|
+
Tool-specific patterns and gotchas live in TOOLS.md. Generic tool behaviour
|
|
33645
|
+
comes from the tool descriptions themselves \u2014 don't duplicate those here.
|
|
33646
|
+
`;
|
|
33647
|
+
var DEFAULT_TOOLS = `# TOOLS.md \u2014 Tool Playbook
|
|
33648
|
+
|
|
33649
|
+
Local notes on tool usage that are specific to *this* agent and *this*
|
|
33650
|
+
environment. Generic tool docs come from the tool descriptions \u2014 keep those
|
|
33651
|
+
out. This is the place for the small, concrete facts that save you a round-trip
|
|
33652
|
+
every session.
|
|
33653
|
+
|
|
33654
|
+
## Environment notes
|
|
33655
|
+
Fill these in as you learn them \u2014 they're the kind of thing you'd otherwise
|
|
33656
|
+
re-ask every session:
|
|
33657
|
+
- **Devices / nicknames:** _(e.g. "the NAS" = 192.168.1.10, "the pi" = the
|
|
33658
|
+
living-room Raspberry Pi)_
|
|
33659
|
+
- **SSH hosts:** _(host \u2192 what it's for, which key)_
|
|
33660
|
+
- **Voice / TTS prefs:** _(preferred voice, when to speak vs stay quiet)_
|
|
33661
|
+
- **Cameras / feeds:** _(name \u2192 location)_
|
|
33662
|
+
|
|
33663
|
+
## Patterns
|
|
33664
|
+
- (pattern): when to use, what to avoid
|
|
33665
|
+
`;
|
|
33666
|
+
var DEFAULT_HEARTBEAT = `# HEARTBEAT.md \u2014 Scheduled Wake-Ups
|
|
33667
|
+
|
|
33668
|
+
Tasks the agent should check on every heartbeat. Leave empty (or commented)
|
|
33669
|
+
to opt out \u2014 an empty file means "nothing to do right now".
|
|
33670
|
+
|
|
33671
|
+
## Tasks
|
|
33672
|
+
- (task): cadence, exit criteria
|
|
33673
|
+
`;
|
|
33674
|
+
|
|
33675
|
+
// ../daemon/src/core/repos/groups.ts
|
|
33395
33676
|
function toGroup(r, paths) {
|
|
33396
33677
|
return {
|
|
33397
33678
|
id: r.id,
|
|
@@ -33404,16 +33685,18 @@ function toGroup(r, paths) {
|
|
|
33404
33685
|
}
|
|
33405
33686
|
function insert2(db, g, paths) {
|
|
33406
33687
|
const now = Date.now();
|
|
33407
|
-
|
|
33688
|
+
const userMd = g.userMd && g.userMd.length > 0 ? g.userMd : DEFAULT_USER_MD;
|
|
33689
|
+
db.raw.run("INSERT INTO groups (id, name, user_md, created_at) VALUES (?, ?, ?, ?)", [
|
|
33408
33690
|
g.id,
|
|
33409
33691
|
g.name,
|
|
33692
|
+
userMd,
|
|
33410
33693
|
now
|
|
33411
33694
|
]);
|
|
33412
33695
|
return {
|
|
33413
33696
|
id: g.id,
|
|
33414
33697
|
name: g.name,
|
|
33415
33698
|
path: paths.groupDir(g.id),
|
|
33416
|
-
userMd
|
|
33699
|
+
userMd,
|
|
33417
33700
|
telegramTopicNameFormat: null,
|
|
33418
33701
|
createdAt: now
|
|
33419
33702
|
};
|
|
@@ -33518,6 +33801,7 @@ function getDefaultSkills(db, profileId) {
|
|
|
33518
33801
|
function resolveAgent(db, paths, agentId) {
|
|
33519
33802
|
const agent = get(db, agentId);
|
|
33520
33803
|
if (!agent) throw new Error(`agent not found: ${agentId}`);
|
|
33804
|
+
agent.identity = loadIdentityFromFile(join(agent.dir, "IDENTITY.md"));
|
|
33521
33805
|
const profile = get3(db, agent.profileId);
|
|
33522
33806
|
if (!profile) {
|
|
33523
33807
|
throw new Error(`profile not found for agent ${agentId}: ${agent.profileId}`);
|
|
@@ -33538,68 +33822,24 @@ function resolveAgent(db, paths, agentId) {
|
|
|
33538
33822
|
|
|
33539
33823
|
// ../daemon/src/core/agent/spawn.ts
|
|
33540
33824
|
import { randomUUID } from "crypto";
|
|
33541
|
-
import { mkdirSync as mkdirSync3, writeFileSync as
|
|
33542
|
-
import { join as
|
|
33825
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
33826
|
+
import { join as join6 } from "path";
|
|
33543
33827
|
|
|
33544
33828
|
// ../daemon/src/core/profile/load.ts
|
|
33545
33829
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
33546
|
-
import { join } from "path";
|
|
33547
|
-
|
|
33548
|
-
// ../daemon/src/core/profile/identity.ts
|
|
33549
|
-
import { readFileSync } from "fs";
|
|
33550
|
-
var IDENTITY_PLACEHOLDER_VALUES = /* @__PURE__ */ new Set([
|
|
33551
|
-
"pick something you like",
|
|
33552
|
-
"ai? robot? familiar? ghost in the machine? something weirder?",
|
|
33553
|
-
"how do you come across? sharp? warm? chaotic? calm?",
|
|
33554
|
-
"your signature - pick one that feels right",
|
|
33555
|
-
"workspace-relative path, http(s) url, or data uri"
|
|
33556
|
-
]);
|
|
33557
|
-
function normalizeIdentityValue(value) {
|
|
33558
|
-
let normalized = value.trim();
|
|
33559
|
-
normalized = normalized.replace(/^[*_]+|[*_]+$/g, "").trim();
|
|
33560
|
-
if (normalized.startsWith("(") && normalized.endsWith(")")) {
|
|
33561
|
-
normalized = normalized.slice(1, -1).trim();
|
|
33562
|
-
}
|
|
33563
|
-
normalized = normalized.replace(/[\u2013\u2014]/g, "-");
|
|
33564
|
-
normalized = normalized.replace(/\s+/g, " ").toLowerCase();
|
|
33565
|
-
return normalized;
|
|
33566
|
-
}
|
|
33567
|
-
function isIdentityPlaceholder(value) {
|
|
33568
|
-
return IDENTITY_PLACEHOLDER_VALUES.has(normalizeIdentityValue(value));
|
|
33569
|
-
}
|
|
33570
|
-
function parseIdentityMarkdown(content) {
|
|
33571
|
-
const identity = {};
|
|
33572
|
-
for (const line of content.split(/\r?\n/)) {
|
|
33573
|
-
const cleaned = line.trim().replace(/^\s*-\s*/, "");
|
|
33574
|
-
const colonIndex = cleaned.indexOf(":");
|
|
33575
|
-
if (colonIndex === -1) continue;
|
|
33576
|
-
const label = cleaned.slice(0, colonIndex).replace(/[*_]/g, "").trim().toLowerCase();
|
|
33577
|
-
const value = cleaned.slice(colonIndex + 1).replace(/^[*_]+|[*_]+$/g, "").trim();
|
|
33578
|
-
if (!value) continue;
|
|
33579
|
-
if (isIdentityPlaceholder(value)) continue;
|
|
33580
|
-
if (label === "name") identity.name = value;
|
|
33581
|
-
else if (label === "emoji") identity.emoji = value;
|
|
33582
|
-
else if (label === "creature") identity.creature = value;
|
|
33583
|
-
else if (label === "vibe") identity.vibe = value;
|
|
33584
|
-
else if (label === "theme") identity.theme = value;
|
|
33585
|
-
else if (label === "avatar") identity.avatar = value;
|
|
33586
|
-
}
|
|
33587
|
-
return identity;
|
|
33588
|
-
}
|
|
33589
|
-
|
|
33590
|
-
// ../daemon/src/core/profile/load.ts
|
|
33830
|
+
import { join as join2 } from "path";
|
|
33591
33831
|
function readOptional(path) {
|
|
33592
33832
|
return existsSync2(path) ? readFileSync2(path, "utf8") : null;
|
|
33593
33833
|
}
|
|
33594
33834
|
function loadProfile(db, id) {
|
|
33595
33835
|
const profile = get3(db, id);
|
|
33596
33836
|
if (!profile) throw new Error(`profile not found: ${id}`);
|
|
33597
|
-
const soul = readFileSync2(
|
|
33598
|
-
const identityRaw = readFileSync2(
|
|
33599
|
-
const bootstrap2 = readOptional(
|
|
33600
|
-
const agents = readOptional(
|
|
33601
|
-
const tools = readOptional(
|
|
33602
|
-
const heartbeat = readOptional(
|
|
33837
|
+
const soul = readFileSync2(join2(profile.dir, "SOUL.md"), "utf8");
|
|
33838
|
+
const identityRaw = readFileSync2(join2(profile.dir, "IDENTITY.md"), "utf8");
|
|
33839
|
+
const bootstrap2 = readOptional(join2(profile.dir, "BOOTSTRAP.md"));
|
|
33840
|
+
const agents = readOptional(join2(profile.dir, "AGENTS.md"));
|
|
33841
|
+
const tools = readOptional(join2(profile.dir, "TOOLS.md"));
|
|
33842
|
+
const heartbeat = readOptional(join2(profile.dir, "HEARTBEAT.md"));
|
|
33603
33843
|
const parsedIdentity = parseIdentityMarkdown(identityRaw);
|
|
33604
33844
|
const anyIdentity = parsedIdentity.name || parsedIdentity.emoji || parsedIdentity.theme || parsedIdentity.creature || parsedIdentity.vibe || parsedIdentity.avatar;
|
|
33605
33845
|
return {
|
|
@@ -33617,6 +33857,10 @@ function loadProfile(db, id) {
|
|
|
33617
33857
|
};
|
|
33618
33858
|
}
|
|
33619
33859
|
|
|
33860
|
+
// ../daemon/src/core/profile/seed.ts
|
|
33861
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
33862
|
+
import { join as join4 } from "path";
|
|
33863
|
+
|
|
33620
33864
|
// ../daemon/src/core/repos/providerModels.ts
|
|
33621
33865
|
var providerModels_exports = {};
|
|
33622
33866
|
__export(providerModels_exports, {
|
|
@@ -33754,62 +33998,7 @@ function registerGroup(db, input, paths) {
|
|
|
33754
33998
|
|
|
33755
33999
|
// ../daemon/src/core/profile/create.ts
|
|
33756
34000
|
import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
33757
|
-
import { join as
|
|
33758
|
-
|
|
33759
|
-
// ../daemon/src/core/profile/templates.ts
|
|
33760
|
-
var DEFAULT_SOUL = `# SOUL.md \u2014 Who You Are
|
|
33761
|
-
|
|
33762
|
-
This is your personality and operating principles. Edit it freely to make this agent yours.
|
|
33763
|
-
|
|
33764
|
-
## Core
|
|
33765
|
-
- Be genuinely helpful, not performatively helpful.
|
|
33766
|
-
- Have opinions. Push back when you disagree.
|
|
33767
|
-
- Be resourceful before asking \u2014 read the file, check context, then ask if stuck.
|
|
33768
|
-
|
|
33769
|
-
## Boundaries
|
|
33770
|
-
- Private things stay private.
|
|
33771
|
-
- Confirm before destructive or external actions.
|
|
33772
|
-
- You're a guest in someone's environment. Treat it with respect.
|
|
33773
|
-
`;
|
|
33774
|
-
var DEFAULT_IDENTITY = `# IDENTITY.md \u2014 Who Am I?
|
|
33775
|
-
|
|
33776
|
-
Fill this in during your first conversation. Make it yours.
|
|
33777
|
-
|
|
33778
|
-
- **Name:**
|
|
33779
|
-
- **Vibe:**
|
|
33780
|
-
- **Emoji:**
|
|
33781
|
-
`;
|
|
33782
|
-
var DEFAULT_BOOTSTRAP = `# BOOTSTRAP.md \u2014 First Run
|
|
33783
|
-
|
|
33784
|
-
You just woke up. There is no memory yet \u2014 that's normal. This is a multi-turn
|
|
33785
|
-
ritual: ask ONE question per turn and wait for the human's reply before moving
|
|
33786
|
-
on. Do not race through it. Do not call any tool until the ritual is finished.
|
|
33787
|
-
|
|
33788
|
-
## The ritual
|
|
33789
|
-
|
|
33790
|
-
**Turn 1 (right now):** Greet the human warmly and ask a single opening
|
|
33791
|
-
question \u2014 what should they call you, or what should you focus on for them.
|
|
33792
|
-
Do NOT call any tool yet. Just reply with greeting + one question.
|
|
33793
|
-
|
|
33794
|
-
**Turn 2+:** Continue with one more question per turn to fill in the rest of
|
|
33795
|
-
your identity \u2014 vibe (warm / sharp / playful / calm / \u2026), an emoji that
|
|
33796
|
-
feels right. Each turn is acknowledging the previous answer + at most one
|
|
33797
|
-
new question. Skip a turn when you already have enough.
|
|
33798
|
-
|
|
33799
|
-
**Final turn:** Once you have everything (Name, Vibe, Emoji), call \`home_write\`
|
|
33800
|
-
with \`file: "IDENTITY.md"\` and the populated content. Do NOT use the generic
|
|
33801
|
-
\`edit\` / \`write\` tools \u2014 those land in the shared workspace.
|
|
33802
|
-
|
|
33803
|
-
Then call \`bootstrap_done\` to retire this ritual file. After that, future
|
|
33804
|
-
sessions skip the bootstrap and start from IDENTITY.md directly.
|
|
33805
|
-
|
|
33806
|
-
## Hard rules
|
|
33807
|
-
- Do not invent a name on your own. Ask the human and use what they say.
|
|
33808
|
-
- Do not call \`home_write\` or \`bootstrap_done\` on your very first reply.
|
|
33809
|
-
- One question per turn. Wait for the human to answer.
|
|
33810
|
-
`;
|
|
33811
|
-
|
|
33812
|
-
// ../daemon/src/core/profile/create.ts
|
|
34001
|
+
import { join as join3 } from "path";
|
|
33813
34002
|
function createProfile(db, paths, input) {
|
|
33814
34003
|
validateSlug(input.id);
|
|
33815
34004
|
const dir = paths.profileDir(input.id);
|
|
@@ -33817,19 +34006,22 @@ function createProfile(db, paths, input) {
|
|
|
33817
34006
|
const soul = input.templates?.soul ?? DEFAULT_SOUL;
|
|
33818
34007
|
const identity = input.templates?.identity ?? DEFAULT_IDENTITY;
|
|
33819
34008
|
const bootstrap2 = input.templates?.bootstrap === null ? null : input.templates?.bootstrap ?? DEFAULT_BOOTSTRAP;
|
|
33820
|
-
|
|
33821
|
-
|
|
34009
|
+
const agents = input.templates?.agents === null ? null : input.templates?.agents ?? DEFAULT_AGENTS;
|
|
34010
|
+
const tools = input.templates?.tools === null ? null : input.templates?.tools ?? DEFAULT_TOOLS;
|
|
34011
|
+
const heartbeat = typeof input.templates?.heartbeat === "string" ? input.templates.heartbeat : null;
|
|
34012
|
+
writeFileSync(join3(dir, "SOUL.md"), soul);
|
|
34013
|
+
writeFileSync(join3(dir, "IDENTITY.md"), identity);
|
|
33822
34014
|
if (bootstrap2 !== null) {
|
|
33823
|
-
writeFileSync(
|
|
34015
|
+
writeFileSync(join3(dir, "BOOTSTRAP.md"), bootstrap2);
|
|
33824
34016
|
}
|
|
33825
|
-
if (
|
|
33826
|
-
writeFileSync(
|
|
34017
|
+
if (agents !== null) {
|
|
34018
|
+
writeFileSync(join3(dir, "AGENTS.md"), agents);
|
|
33827
34019
|
}
|
|
33828
|
-
if (
|
|
33829
|
-
writeFileSync(
|
|
34020
|
+
if (tools !== null) {
|
|
34021
|
+
writeFileSync(join3(dir, "TOOLS.md"), tools);
|
|
33830
34022
|
}
|
|
33831
|
-
if (
|
|
33832
|
-
writeFileSync(
|
|
34023
|
+
if (heartbeat !== null) {
|
|
34024
|
+
writeFileSync(join3(dir, "HEARTBEAT.md"), heartbeat);
|
|
33833
34025
|
}
|
|
33834
34026
|
const skillsMode = input.skillsMode ?? "selected";
|
|
33835
34027
|
const profileJson = {
|
|
@@ -33838,7 +34030,7 @@ function createProfile(db, paths, input) {
|
|
|
33838
34030
|
skillsMode,
|
|
33839
34031
|
defaultSkills: input.defaultSkills ?? []
|
|
33840
34032
|
};
|
|
33841
|
-
writeFileSync(
|
|
34033
|
+
writeFileSync(join3(dir, "profile.json"), `${JSON.stringify(profileJson, null, 2)}
|
|
33842
34034
|
`);
|
|
33843
34035
|
const profile = insert3(db, {
|
|
33844
34036
|
id: input.id,
|
|
@@ -33856,6 +34048,26 @@ function createProfile(db, paths, input) {
|
|
|
33856
34048
|
// ../daemon/src/core/profile/seed.ts
|
|
33857
34049
|
var DEFAULT_PROFILE_ID = "default";
|
|
33858
34050
|
var DEFAULT_GROUP_ID = "default";
|
|
34051
|
+
var DEFAULT_PROFILE_FILES = [
|
|
34052
|
+
["SOUL.md", DEFAULT_SOUL],
|
|
34053
|
+
["IDENTITY.md", DEFAULT_IDENTITY],
|
|
34054
|
+
["BOOTSTRAP.md", DEFAULT_BOOTSTRAP],
|
|
34055
|
+
["AGENTS.md", DEFAULT_AGENTS],
|
|
34056
|
+
["TOOLS.md", DEFAULT_TOOLS]
|
|
34057
|
+
];
|
|
34058
|
+
function refreshDefaultProfileTemplates(paths) {
|
|
34059
|
+
const dir = paths.profileDir(DEFAULT_PROFILE_ID);
|
|
34060
|
+
if (!existsSync4(dir)) return [];
|
|
34061
|
+
const written = [];
|
|
34062
|
+
for (const [name, content] of DEFAULT_PROFILE_FILES) {
|
|
34063
|
+
const path = join4(dir, name);
|
|
34064
|
+
if (!existsSync4(path) || readFileSync3(path, "utf8") !== content) {
|
|
34065
|
+
writeFileSync2(path, content);
|
|
34066
|
+
written.push(name);
|
|
34067
|
+
}
|
|
34068
|
+
}
|
|
34069
|
+
return written;
|
|
34070
|
+
}
|
|
33859
34071
|
function seedDefaults(db, paths, input) {
|
|
33860
34072
|
let group = get2(db, DEFAULT_GROUP_ID, paths);
|
|
33861
34073
|
let groupCreated = false;
|
|
@@ -33885,17 +34097,17 @@ function ensureSetupSeeded(db, paths) {
|
|
|
33885
34097
|
}
|
|
33886
34098
|
|
|
33887
34099
|
// ../daemon/src/core/skills/discover.ts
|
|
33888
|
-
import { existsSync as
|
|
33889
|
-
import { join as
|
|
34100
|
+
import { existsSync as existsSync5, readdirSync } from "fs";
|
|
34101
|
+
import { join as join5 } from "path";
|
|
33890
34102
|
function discoverSkills(paths) {
|
|
33891
|
-
if (!
|
|
34103
|
+
if (!existsSync5(paths.skillsDir)) return [];
|
|
33892
34104
|
const entries = readdirSync(paths.skillsDir, { withFileTypes: true });
|
|
33893
34105
|
const skills = [];
|
|
33894
34106
|
for (const entry of entries) {
|
|
33895
34107
|
if (!entry.isDirectory()) continue;
|
|
33896
|
-
const dir =
|
|
33897
|
-
const skillFile =
|
|
33898
|
-
if (!
|
|
34108
|
+
const dir = join5(paths.skillsDir, entry.name);
|
|
34109
|
+
const skillFile = join5(dir, "SKILL.md");
|
|
34110
|
+
if (!existsSync5(skillFile)) continue;
|
|
33899
34111
|
skills.push({ name: entry.name, dir, skillFile });
|
|
33900
34112
|
}
|
|
33901
34113
|
return skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -33907,20 +34119,20 @@ function spawnAgent(db, paths, input) {
|
|
|
33907
34119
|
const id = randomUUID();
|
|
33908
34120
|
const dir = paths.agentDir(id);
|
|
33909
34121
|
mkdirSync3(dir, { recursive: true });
|
|
33910
|
-
mkdirSync3(
|
|
33911
|
-
|
|
33912
|
-
|
|
34122
|
+
mkdirSync3(join6(dir, "sessions"), { recursive: true });
|
|
34123
|
+
writeFileSync3(join6(dir, "SOUL.md"), loaded.files.soul);
|
|
34124
|
+
writeFileSync3(join6(dir, "IDENTITY.md"), loaded.files.identity);
|
|
33913
34125
|
if (loaded.files.bootstrap !== null) {
|
|
33914
|
-
|
|
34126
|
+
writeFileSync3(join6(dir, "BOOTSTRAP.md"), loaded.files.bootstrap);
|
|
33915
34127
|
}
|
|
33916
34128
|
if (loaded.files.agents !== null) {
|
|
33917
|
-
|
|
34129
|
+
writeFileSync3(join6(dir, "AGENTS.md"), loaded.files.agents);
|
|
33918
34130
|
}
|
|
33919
34131
|
if (loaded.files.tools !== null) {
|
|
33920
|
-
|
|
34132
|
+
writeFileSync3(join6(dir, "TOOLS.md"), loaded.files.tools);
|
|
33921
34133
|
}
|
|
33922
34134
|
if (loaded.files.heartbeat !== null) {
|
|
33923
|
-
|
|
34135
|
+
writeFileSync3(join6(dir, "HEARTBEAT.md"), loaded.files.heartbeat);
|
|
33924
34136
|
}
|
|
33925
34137
|
const reasoningLevel = input.reasoningLevel ?? "medium";
|
|
33926
34138
|
const groupId = input.groupId ?? DEFAULT_GROUP_ID;
|
|
@@ -33937,7 +34149,7 @@ function spawnAgent(db, paths, input) {
|
|
|
33937
34149
|
reasoningLevel,
|
|
33938
34150
|
groupId: group.id
|
|
33939
34151
|
};
|
|
33940
|
-
|
|
34152
|
+
writeFileSync3(join6(dir, "agent.json"), `${JSON.stringify(agentJson, null, 2)}
|
|
33941
34153
|
`);
|
|
33942
34154
|
const agent = insert(db, {
|
|
33943
34155
|
id,
|
|
@@ -34039,10 +34251,10 @@ function inTx(db, fn) {
|
|
|
34039
34251
|
}
|
|
34040
34252
|
|
|
34041
34253
|
// ../daemon/src/core/db/migrate.ts
|
|
34042
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
34043
|
-
import { dirname, join as
|
|
34254
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
34255
|
+
import { dirname, join as join7 } from "path";
|
|
34044
34256
|
import { fileURLToPath } from "url";
|
|
34045
|
-
var migrationsDir =
|
|
34257
|
+
var migrationsDir = join7(dirname(fileURLToPath(import.meta.url)), "migrations");
|
|
34046
34258
|
function runMigrations(db) {
|
|
34047
34259
|
db.raw.exec(`
|
|
34048
34260
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
@@ -34057,7 +34269,7 @@ function runMigrations(db) {
|
|
|
34057
34269
|
for (const file of files) {
|
|
34058
34270
|
const version2 = file.replace(/\.sql$/, "");
|
|
34059
34271
|
if (applied.has(version2)) continue;
|
|
34060
|
-
const sql =
|
|
34272
|
+
const sql = readFileSync4(join7(migrationsDir, file), "utf8");
|
|
34061
34273
|
const tx = db.raw.transaction(() => {
|
|
34062
34274
|
db.raw.exec(sql);
|
|
34063
34275
|
db.raw.run("INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)", [
|
|
@@ -34085,35 +34297,35 @@ function deleteGroup(db, paths, id) {
|
|
|
34085
34297
|
|
|
34086
34298
|
// ../daemon/src/core/paths.ts
|
|
34087
34299
|
import { homedir } from "os";
|
|
34088
|
-
import { join as
|
|
34300
|
+
import { join as join8 } from "path";
|
|
34089
34301
|
function resolvePaths(home) {
|
|
34090
|
-
const root = home ?? process.env.BAZILION_HOME ??
|
|
34302
|
+
const root = home ?? process.env.BAZILION_HOME ?? join8(homedir(), ".bazilion");
|
|
34091
34303
|
return {
|
|
34092
34304
|
home: root,
|
|
34093
|
-
db:
|
|
34094
|
-
authFile:
|
|
34095
|
-
profilesDir:
|
|
34096
|
-
agentsDir:
|
|
34097
|
-
skillsDir:
|
|
34098
|
-
groupsDir:
|
|
34099
|
-
logsDir:
|
|
34305
|
+
db: join8(root, "bazilion.db"),
|
|
34306
|
+
authFile: join8(root, "auth.json"),
|
|
34307
|
+
profilesDir: join8(root, "profiles"),
|
|
34308
|
+
agentsDir: join8(root, "agents"),
|
|
34309
|
+
skillsDir: join8(root, "skills"),
|
|
34310
|
+
groupsDir: join8(root, "groups"),
|
|
34311
|
+
logsDir: join8(root, "logs"),
|
|
34100
34312
|
profileDir(id) {
|
|
34101
|
-
return
|
|
34313
|
+
return join8(root, "profiles", id);
|
|
34102
34314
|
},
|
|
34103
34315
|
agentDir(id) {
|
|
34104
|
-
return
|
|
34316
|
+
return join8(root, "agents", id);
|
|
34105
34317
|
},
|
|
34106
34318
|
skillDir(name) {
|
|
34107
|
-
return
|
|
34319
|
+
return join8(root, "skills", name);
|
|
34108
34320
|
},
|
|
34109
34321
|
groupDir(slug) {
|
|
34110
|
-
return
|
|
34322
|
+
return join8(root, "groups", slug);
|
|
34111
34323
|
}
|
|
34112
34324
|
};
|
|
34113
34325
|
}
|
|
34114
34326
|
|
|
34115
34327
|
// ../daemon/src/core/profile/delete.ts
|
|
34116
|
-
import { existsSync as
|
|
34328
|
+
import { existsSync as existsSync6, rmSync as rmSync2 } from "fs";
|
|
34117
34329
|
|
|
34118
34330
|
// ../daemon/src/core/repos/profileGroups.ts
|
|
34119
34331
|
var profileGroups_exports = {};
|
|
@@ -34249,14 +34461,14 @@ function deleteProfile(db, id) {
|
|
|
34249
34461
|
);
|
|
34250
34462
|
}
|
|
34251
34463
|
remove3(db, id);
|
|
34252
|
-
if (
|
|
34464
|
+
if (existsSync6(profile.dir)) {
|
|
34253
34465
|
rmSync2(profile.dir, { recursive: true, force: true });
|
|
34254
34466
|
}
|
|
34255
34467
|
}
|
|
34256
34468
|
|
|
34257
34469
|
// ../daemon/src/core/profile/update.ts
|
|
34258
|
-
import { writeFileSync as
|
|
34259
|
-
import { join as
|
|
34470
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
34471
|
+
import { join as join9 } from "path";
|
|
34260
34472
|
function updateProfile(db, paths, id, input) {
|
|
34261
34473
|
const existing = get3(db, id);
|
|
34262
34474
|
if (!existing) throw new Error(`profile not found: ${id}`);
|
|
@@ -34277,8 +34489,8 @@ function updateProfile(db, paths, id, input) {
|
|
|
34277
34489
|
skillsMode: next.skillsMode,
|
|
34278
34490
|
defaultSkills: skills
|
|
34279
34491
|
};
|
|
34280
|
-
|
|
34281
|
-
|
|
34492
|
+
writeFileSync4(
|
|
34493
|
+
join9(paths.profileDir(id), "profile.json"),
|
|
34282
34494
|
`${JSON.stringify(profileJson, null, 2)}
|
|
34283
34495
|
`
|
|
34284
34496
|
);
|
|
@@ -35418,14 +35630,14 @@ function revoke(db, id, when = Date.now()) {
|
|
|
35418
35630
|
}
|
|
35419
35631
|
|
|
35420
35632
|
// ../daemon/src/core/secrets.ts
|
|
35421
|
-
import { existsSync as
|
|
35633
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
35422
35634
|
function readAuthFile(authFile) {
|
|
35423
|
-
if (!
|
|
35635
|
+
if (!existsSync7(authFile)) {
|
|
35424
35636
|
throw new Error(
|
|
35425
35637
|
`${authFile} not found. Start the daemon (\`bazilion serve\`) \u2014 it auto-bootstraps on first run.`
|
|
35426
35638
|
);
|
|
35427
35639
|
}
|
|
35428
|
-
const raw =
|
|
35640
|
+
const raw = readFileSync5(authFile, "utf8");
|
|
35429
35641
|
const parsed = JSON.parse(raw);
|
|
35430
35642
|
if (typeof parsed.token !== "string" || !parsed.token) {
|
|
35431
35643
|
throw new Error(`${authFile} is missing the "token" field`);
|
|
@@ -35450,13 +35662,13 @@ function mergeSecretsIntoEnv(db, password, env = process.env) {
|
|
|
35450
35662
|
}
|
|
35451
35663
|
|
|
35452
35664
|
// ../daemon/src/core/skills/import.ts
|
|
35453
|
-
import { cpSync, existsSync as
|
|
35665
|
+
import { cpSync, existsSync as existsSync8, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync5, statSync as statSync2 } from "fs";
|
|
35454
35666
|
import { tmpdir } from "os";
|
|
35455
|
-
import { basename, join as
|
|
35667
|
+
import { basename, join as join10, resolve as resolve2, sep } from "path";
|
|
35456
35668
|
import AdmZip from "adm-zip";
|
|
35457
35669
|
|
|
35458
35670
|
// ../daemon/src/core/skills/parse.ts
|
|
35459
|
-
import { readFileSync as
|
|
35671
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
35460
35672
|
import { parse as parseYaml } from "yaml";
|
|
35461
35673
|
var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
35462
35674
|
function parseSkillContent(raw) {
|
|
@@ -35485,13 +35697,13 @@ function parseSkillContent(raw) {
|
|
|
35485
35697
|
return { frontmatter: fmObj, body, raw };
|
|
35486
35698
|
}
|
|
35487
35699
|
function parseSkillFile(path) {
|
|
35488
|
-
const raw =
|
|
35700
|
+
const raw = readFileSync6(path, "utf8");
|
|
35489
35701
|
return parseSkillContent(raw);
|
|
35490
35702
|
}
|
|
35491
35703
|
|
|
35492
35704
|
// ../daemon/src/core/skills/import.ts
|
|
35493
35705
|
function extractZipSafely(zipPath) {
|
|
35494
|
-
const root = mkdtempSync(
|
|
35706
|
+
const root = mkdtempSync(join10(tmpdir(), "bazilion-skill-zip-"));
|
|
35495
35707
|
try {
|
|
35496
35708
|
const zip = new AdmZip(zipPath);
|
|
35497
35709
|
for (const entry of zip.getEntries()) {
|
|
@@ -35512,13 +35724,13 @@ function extractZipSafely(zipPath) {
|
|
|
35512
35724
|
let effectiveSource = root;
|
|
35513
35725
|
const topEntries = readdirSync4(root, { withFileTypes: true });
|
|
35514
35726
|
if (topEntries.length === 1 && topEntries[0]?.isDirectory()) {
|
|
35515
|
-
effectiveSource =
|
|
35727
|
+
effectiveSource = join10(root, topEntries[0].name);
|
|
35516
35728
|
}
|
|
35517
35729
|
return { root, effectiveSource };
|
|
35518
35730
|
}
|
|
35519
35731
|
function importSkills(paths, input) {
|
|
35520
35732
|
const rawSource = resolve2(input.source);
|
|
35521
|
-
if (!
|
|
35733
|
+
if (!existsSync8(rawSource)) {
|
|
35522
35734
|
throw new Error(`source does not exist: ${rawSource}`);
|
|
35523
35735
|
}
|
|
35524
35736
|
let source = rawSource;
|
|
@@ -35542,14 +35754,14 @@ function importSkills(paths, input) {
|
|
|
35542
35754
|
}
|
|
35543
35755
|
function importSkillsFromDir(paths, source, input) {
|
|
35544
35756
|
const candidates = [];
|
|
35545
|
-
if (
|
|
35757
|
+
if (existsSync8(join10(source, "SKILL.md"))) {
|
|
35546
35758
|
candidates.push({ name: basename(source), dir: source });
|
|
35547
35759
|
} else {
|
|
35548
35760
|
const entries = readdirSync4(source, { withFileTypes: true });
|
|
35549
35761
|
for (const e of entries) {
|
|
35550
35762
|
if (!e.isDirectory()) continue;
|
|
35551
|
-
const skillDir =
|
|
35552
|
-
if (!
|
|
35763
|
+
const skillDir = join10(source, e.name);
|
|
35764
|
+
if (!existsSync8(join10(skillDir, "SKILL.md"))) continue;
|
|
35553
35765
|
candidates.push({ name: e.name, dir: skillDir });
|
|
35554
35766
|
}
|
|
35555
35767
|
}
|
|
@@ -35557,13 +35769,13 @@ function importSkillsFromDir(paths, source, input) {
|
|
|
35557
35769
|
throw new Error(`no skills found in ${source}`);
|
|
35558
35770
|
}
|
|
35559
35771
|
for (const c of candidates) {
|
|
35560
|
-
parseSkillFile(
|
|
35772
|
+
parseSkillFile(join10(c.dir, "SKILL.md"));
|
|
35561
35773
|
}
|
|
35562
35774
|
const imported = [];
|
|
35563
35775
|
const skipped = [];
|
|
35564
35776
|
for (const c of candidates) {
|
|
35565
|
-
const target =
|
|
35566
|
-
if (
|
|
35777
|
+
const target = join10(paths.skillsDir, c.name);
|
|
35778
|
+
if (existsSync8(target) && !input.force) {
|
|
35567
35779
|
skipped.push({
|
|
35568
35780
|
name: c.name,
|
|
35569
35781
|
reason: "already exists (use --force to overwrite)"
|
|
@@ -35599,7 +35811,7 @@ function resolveAgentSkills(db, paths, agentId) {
|
|
|
35599
35811
|
}
|
|
35600
35812
|
|
|
35601
35813
|
// ../daemon/src/lib/ctx.ts
|
|
35602
|
-
import { chmodSync, existsSync as
|
|
35814
|
+
import { chmodSync, existsSync as existsSync15, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
35603
35815
|
|
|
35604
35816
|
// ../daemon/src/lib/agent-cancel.ts
|
|
35605
35817
|
var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("bazilion.agent-cancel.registry");
|
|
@@ -35705,27 +35917,27 @@ var DEFAULT_HEARTBEAT_EVERY_SEC = 30 * 60;
|
|
|
35705
35917
|
|
|
35706
35918
|
// ../daemon/src/runtime/memory/files.ts
|
|
35707
35919
|
import {
|
|
35708
|
-
existsSync as
|
|
35920
|
+
existsSync as existsSync9,
|
|
35709
35921
|
mkdirSync as mkdirSync4,
|
|
35710
35922
|
readdirSync as readdirSync5,
|
|
35711
|
-
readFileSync as
|
|
35923
|
+
readFileSync as readFileSync7,
|
|
35712
35924
|
rmSync as rmSync6,
|
|
35713
35925
|
statSync as statSync3,
|
|
35714
|
-
writeFileSync as
|
|
35926
|
+
writeFileSync as writeFileSync5
|
|
35715
35927
|
} from "fs";
|
|
35716
|
-
import { dirname as dirname2, join as
|
|
35928
|
+
import { dirname as dirname2, join as join11 } from "path";
|
|
35717
35929
|
|
|
35718
35930
|
// ../daemon/src/runtime/memory/qmd.ts
|
|
35719
35931
|
import {
|
|
35720
|
-
existsSync as
|
|
35932
|
+
existsSync as existsSync10,
|
|
35721
35933
|
mkdirSync as mkdirSync5,
|
|
35722
35934
|
readdirSync as readdirSync6,
|
|
35723
|
-
readFileSync as
|
|
35935
|
+
readFileSync as readFileSync8,
|
|
35724
35936
|
rmSync as rmSync7,
|
|
35725
35937
|
statSync as statSync4,
|
|
35726
|
-
writeFileSync as
|
|
35938
|
+
writeFileSync as writeFileSync6
|
|
35727
35939
|
} from "fs";
|
|
35728
|
-
import { dirname as dirname3, join as
|
|
35940
|
+
import { dirname as dirname3, join as join12 } from "path";
|
|
35729
35941
|
import { createStore, extractSnippet } from "@tobilu/qmd";
|
|
35730
35942
|
var INDEX_FILENAME = ".qmd-index.sqlite";
|
|
35731
35943
|
var COLLECTION_NAME = "memory";
|
|
@@ -35735,7 +35947,7 @@ function getStore(dir) {
|
|
|
35735
35947
|
let p = storeCache.get(dir);
|
|
35736
35948
|
if (!p) {
|
|
35737
35949
|
p = createStore({
|
|
35738
|
-
dbPath:
|
|
35950
|
+
dbPath: join12(dir, INDEX_FILENAME),
|
|
35739
35951
|
config: {
|
|
35740
35952
|
collections: {
|
|
35741
35953
|
[COLLECTION_NAME]: { path: dir, pattern: PATTERN }
|
|
@@ -35750,13 +35962,13 @@ function safeKey(root, key2) {
|
|
|
35750
35962
|
if (key2.includes("..") || key2.startsWith("/") || key2.includes("\0")) {
|
|
35751
35963
|
throw new Error(`unsafe memory key: ${key2}`);
|
|
35752
35964
|
}
|
|
35753
|
-
return
|
|
35965
|
+
return join12(root, key2);
|
|
35754
35966
|
}
|
|
35755
35967
|
function walkMd(dir, prefix, out) {
|
|
35756
|
-
if (!
|
|
35968
|
+
if (!existsSync10(dir)) return;
|
|
35757
35969
|
for (const e of readdirSync6(dir, { withFileTypes: true })) {
|
|
35758
35970
|
if (e.name.startsWith(".")) continue;
|
|
35759
|
-
const full =
|
|
35971
|
+
const full = join12(dir, e.name);
|
|
35760
35972
|
const key2 = prefix ? `${prefix}/${e.name}` : e.name;
|
|
35761
35973
|
if (e.isDirectory()) {
|
|
35762
35974
|
walkMd(full, key2, out);
|
|
@@ -35764,7 +35976,7 @@ function walkMd(dir, prefix, out) {
|
|
|
35764
35976
|
const stats = statSync4(full);
|
|
35765
35977
|
out.push({
|
|
35766
35978
|
key: key2,
|
|
35767
|
-
content:
|
|
35979
|
+
content: readFileSync8(full, "utf8"),
|
|
35768
35980
|
updatedAt: stats.mtimeMs
|
|
35769
35981
|
});
|
|
35770
35982
|
}
|
|
@@ -35779,20 +35991,20 @@ function qmdBackend(root) {
|
|
|
35779
35991
|
},
|
|
35780
35992
|
async read(key2) {
|
|
35781
35993
|
const path = safeKey(root, key2);
|
|
35782
|
-
if (!
|
|
35994
|
+
if (!existsSync10(path)) {
|
|
35783
35995
|
throw new Error(`memory entry not found: ${key2}`);
|
|
35784
35996
|
}
|
|
35785
35997
|
const stats = statSync4(path);
|
|
35786
35998
|
return {
|
|
35787
35999
|
key: key2,
|
|
35788
|
-
content:
|
|
36000
|
+
content: readFileSync8(path, "utf8"),
|
|
35789
36001
|
updatedAt: stats.mtimeMs
|
|
35790
36002
|
};
|
|
35791
36003
|
},
|
|
35792
36004
|
async write(key2, content) {
|
|
35793
36005
|
const path = safeKey(root, key2);
|
|
35794
36006
|
mkdirSync5(dirname3(path), { recursive: true });
|
|
35795
|
-
|
|
36007
|
+
writeFileSync6(path, content);
|
|
35796
36008
|
const stats = statSync4(path);
|
|
35797
36009
|
const store = await getStore(root);
|
|
35798
36010
|
await store.update();
|
|
@@ -35812,7 +36024,7 @@ function qmdBackend(root) {
|
|
|
35812
36024
|
let content = r.body ?? "";
|
|
35813
36025
|
if (!content) {
|
|
35814
36026
|
try {
|
|
35815
|
-
content =
|
|
36027
|
+
content = readFileSync8(join12(root, key2), "utf8");
|
|
35816
36028
|
} catch {
|
|
35817
36029
|
content = "";
|
|
35818
36030
|
}
|
|
@@ -35829,7 +36041,7 @@ function qmdBackend(root) {
|
|
|
35829
36041
|
},
|
|
35830
36042
|
async remove(key2) {
|
|
35831
36043
|
const path = safeKey(root, key2);
|
|
35832
|
-
if (
|
|
36044
|
+
if (existsSync10(path)) rmSync7(path);
|
|
35833
36045
|
const store = await getStore(root);
|
|
35834
36046
|
await store.update();
|
|
35835
36047
|
}
|
|
@@ -35919,8 +36131,8 @@ function piMessagesToProviderView(messages) {
|
|
|
35919
36131
|
}
|
|
35920
36132
|
|
|
35921
36133
|
// ../daemon/src/runtime/pi/session.ts
|
|
35922
|
-
import { existsSync as
|
|
35923
|
-
import { basename as basename3, join as
|
|
36134
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync8, statSync as statSync7 } from "fs";
|
|
36135
|
+
import { basename as basename3, join as join16 } from "path";
|
|
35924
36136
|
import {
|
|
35925
36137
|
AuthStorage,
|
|
35926
36138
|
createAgentSession,
|
|
@@ -36675,8 +36887,8 @@ function listAllProviders(config2) {
|
|
|
36675
36887
|
}
|
|
36676
36888
|
|
|
36677
36889
|
// ../daemon/src/runtime/session/prompt.ts
|
|
36678
|
-
import { existsSync as
|
|
36679
|
-
import { join as
|
|
36890
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
36891
|
+
import { join as join13 } from "path";
|
|
36680
36892
|
var CONTEXT_FILE_ORDER = [
|
|
36681
36893
|
"AGENTS.md",
|
|
36682
36894
|
"SOUL.md",
|
|
@@ -36688,9 +36900,9 @@ function buildSystemPrompt(agent) {
|
|
|
36688
36900
|
const parts = [];
|
|
36689
36901
|
const contextBlocks = [];
|
|
36690
36902
|
for (const file of CONTEXT_FILE_ORDER) {
|
|
36691
|
-
const path =
|
|
36692
|
-
if (!
|
|
36693
|
-
const content =
|
|
36903
|
+
const path = join13(agent.agent.dir, file);
|
|
36904
|
+
if (!existsSync11(path)) continue;
|
|
36905
|
+
const content = readFileSync9(path, "utf8").trimEnd();
|
|
36694
36906
|
if (!content) continue;
|
|
36695
36907
|
contextBlocks.push(`## ${file}
|
|
36696
36908
|
|
|
@@ -36701,9 +36913,9 @@ ${content}`);
|
|
|
36701
36913
|
|
|
36702
36914
|
${contextBlocks.join("\n\n")}`);
|
|
36703
36915
|
}
|
|
36704
|
-
const bootstrapPath =
|
|
36705
|
-
if (
|
|
36706
|
-
const bootstrap2 =
|
|
36916
|
+
const bootstrapPath = join13(agent.agent.dir, "BOOTSTRAP.md");
|
|
36917
|
+
if (existsSync11(bootstrapPath)) {
|
|
36918
|
+
const bootstrap2 = readFileSync9(bootstrapPath, "utf8").trimEnd();
|
|
36707
36919
|
if (bootstrap2) {
|
|
36708
36920
|
parts.push(
|
|
36709
36921
|
[
|
|
@@ -36775,8 +36987,8 @@ This group's USER.md is empty. As you learn STABLE facts about the human (prefer
|
|
|
36775
36987
|
import { Type as Type2 } from "typebox";
|
|
36776
36988
|
|
|
36777
36989
|
// ../daemon/src/runtime/tools/bootstrap.ts
|
|
36778
|
-
import { existsSync as
|
|
36779
|
-
import { join as
|
|
36990
|
+
import { existsSync as existsSync12, rmSync as rmSync8 } from "fs";
|
|
36991
|
+
import { join as join14 } from "path";
|
|
36780
36992
|
function bootstrapTool(agentDir) {
|
|
36781
36993
|
return {
|
|
36782
36994
|
def: {
|
|
@@ -36785,8 +36997,8 @@ function bootstrapTool(agentDir) {
|
|
|
36785
36997
|
parameters: { type: "object", properties: {} }
|
|
36786
36998
|
},
|
|
36787
36999
|
async invoke() {
|
|
36788
|
-
const path =
|
|
36789
|
-
if (
|
|
37000
|
+
const path = join14(agentDir, "BOOTSTRAP.md");
|
|
37001
|
+
if (existsSync12(path)) {
|
|
36790
37002
|
rmSync8(path);
|
|
36791
37003
|
return "BOOTSTRAP.md removed. Bootstrap is complete.";
|
|
36792
37004
|
}
|
|
@@ -36925,7 +37137,7 @@ function browserTools(host2, agentId) {
|
|
|
36925
37137
|
}
|
|
36926
37138
|
|
|
36927
37139
|
// ../daemon/src/runtime/tools/deliver-file.ts
|
|
36928
|
-
import { readFileSync as
|
|
37140
|
+
import { readFileSync as readFileSync10, statSync as statSync5 } from "fs";
|
|
36929
37141
|
import { basename as basename2, extname, isAbsolute, resolve as resolve3 } from "path";
|
|
36930
37142
|
var MAX_DELIVER_BYTES = 25 * 1024 * 1024;
|
|
36931
37143
|
var MIME = {
|
|
@@ -36976,15 +37188,15 @@ function deliverFileTool(cwd, sink) {
|
|
|
36976
37188
|
}
|
|
36977
37189
|
const name = basename2(abs);
|
|
36978
37190
|
const mimeType = MIME[extname(abs).toLowerCase()] ?? "application/octet-stream";
|
|
36979
|
-
sink({ name, mimeType, data:
|
|
37191
|
+
sink({ name, mimeType, data: readFileSync10(abs).toString("base64") });
|
|
36980
37192
|
return `Delivered "${name}" (${mimeType}) to the user.`;
|
|
36981
37193
|
}
|
|
36982
37194
|
};
|
|
36983
37195
|
}
|
|
36984
37196
|
|
|
36985
37197
|
// ../daemon/src/runtime/tools/home.ts
|
|
36986
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
36987
|
-
import { join as
|
|
37198
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync11, statSync as statSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
37199
|
+
import { join as join15 } from "path";
|
|
36988
37200
|
var HOME_FILES_READABLE = [
|
|
36989
37201
|
"IDENTITY.md",
|
|
36990
37202
|
"SOUL.md",
|
|
@@ -37021,9 +37233,9 @@ function homeTools(agentDir) {
|
|
|
37021
37233
|
`home_read: "file" must be one of ${HOME_FILES_READABLE.join(", ")}; got "${file}"`
|
|
37022
37234
|
);
|
|
37023
37235
|
}
|
|
37024
|
-
const path =
|
|
37236
|
+
const path = join15(agentDir, file);
|
|
37025
37237
|
try {
|
|
37026
|
-
return
|
|
37238
|
+
return readFileSync11(path, "utf8");
|
|
37027
37239
|
} catch (err) {
|
|
37028
37240
|
const msg = err instanceof Error ? err.message : String(err);
|
|
37029
37241
|
throw new Error(`home_read: could not read ${file}: ${msg}`);
|
|
@@ -37051,8 +37263,8 @@ function homeTools(agentDir) {
|
|
|
37051
37263
|
);
|
|
37052
37264
|
}
|
|
37053
37265
|
const content = typeof args.content === "string" ? args.content : "";
|
|
37054
|
-
const path =
|
|
37055
|
-
|
|
37266
|
+
const path = join15(agentDir, file);
|
|
37267
|
+
writeFileSync7(path, content, "utf8");
|
|
37056
37268
|
return `wrote ${file} (${Buffer.byteLength(content, "utf8")} bytes)`;
|
|
37057
37269
|
}
|
|
37058
37270
|
},
|
|
@@ -37065,7 +37277,7 @@ function homeTools(agentDir) {
|
|
|
37065
37277
|
async invoke() {
|
|
37066
37278
|
const entries = [];
|
|
37067
37279
|
for (const file of HOME_FILES_READABLE) {
|
|
37068
|
-
const path =
|
|
37280
|
+
const path = join15(agentDir, file);
|
|
37069
37281
|
try {
|
|
37070
37282
|
const s = statSync6(path);
|
|
37071
37283
|
entries.push(`${file} (${s.size}b)`);
|
|
@@ -38010,8 +38222,8 @@ async function createBazilionSession(opts) {
|
|
|
38010
38222
|
});
|
|
38011
38223
|
}
|
|
38012
38224
|
const cwd = agent.group.path;
|
|
38013
|
-
if (!
|
|
38014
|
-
const sessionDir =
|
|
38225
|
+
if (!existsSync13(cwd)) mkdirSync6(cwd, { recursive: true });
|
|
38226
|
+
const sessionDir = join16(paths.agentDir(agent.agent.id), "sessions");
|
|
38015
38227
|
mkdirSync6(sessionDir, { recursive: true });
|
|
38016
38228
|
const existing = findMostRecent(sessionDir);
|
|
38017
38229
|
const sessionManager = existing ? SessionManager.open(existing, sessionDir, cwd) : SessionManager.create(cwd, sessionDir);
|
|
@@ -38041,7 +38253,7 @@ async function createBazilionSession(opts) {
|
|
|
38041
38253
|
const allowedTools = [...BUILTIN_TOOL_NAMES, ...customTools.map((t) => t.name)];
|
|
38042
38254
|
const { session } = await createAgentSession({
|
|
38043
38255
|
cwd,
|
|
38044
|
-
agentDir:
|
|
38256
|
+
agentDir: join16(paths.home, "pi"),
|
|
38045
38257
|
model,
|
|
38046
38258
|
thinkingLevel: toPiThinkingLevel(agent.reasoningLevel),
|
|
38047
38259
|
tools: allowedTools,
|
|
@@ -38167,10 +38379,10 @@ function createBazilionResourceLoader(appendSystemPrompt) {
|
|
|
38167
38379
|
};
|
|
38168
38380
|
}
|
|
38169
38381
|
function loadInitialMessages(agent, paths) {
|
|
38170
|
-
const sessionDir =
|
|
38171
|
-
if (!
|
|
38382
|
+
const sessionDir = join16(paths.agentDir(agent.agent.id), "sessions");
|
|
38383
|
+
if (!existsSync13(sessionDir)) return [];
|
|
38172
38384
|
const cwd = agent.group.path;
|
|
38173
|
-
if (!
|
|
38385
|
+
if (!existsSync13(cwd)) return [];
|
|
38174
38386
|
const recent = findMostRecent(sessionDir);
|
|
38175
38387
|
if (!recent) return [];
|
|
38176
38388
|
try {
|
|
@@ -38186,8 +38398,8 @@ function loadInitialMessages(agent, paths) {
|
|
|
38186
38398
|
}
|
|
38187
38399
|
}
|
|
38188
38400
|
function loadSessionHead(agent, paths) {
|
|
38189
|
-
const sessionDir =
|
|
38190
|
-
if (!
|
|
38401
|
+
const sessionDir = join16(paths.agentDir(agent.agent.id), "sessions");
|
|
38402
|
+
if (!existsSync13(sessionDir)) return { file: null, size: 0 };
|
|
38191
38403
|
const recent = findMostRecent(sessionDir);
|
|
38192
38404
|
if (!recent) return { file: null, size: 0 };
|
|
38193
38405
|
try {
|
|
@@ -38198,11 +38410,11 @@ function loadSessionHead(agent, paths) {
|
|
|
38198
38410
|
}
|
|
38199
38411
|
}
|
|
38200
38412
|
function findMostRecent(sessionDir) {
|
|
38201
|
-
if (!
|
|
38413
|
+
if (!existsSync13(sessionDir)) return null;
|
|
38202
38414
|
let newest = null;
|
|
38203
38415
|
for (const entry of readdirSync8(sessionDir)) {
|
|
38204
38416
|
if (!entry.endsWith(".jsonl")) continue;
|
|
38205
|
-
const path =
|
|
38417
|
+
const path = join16(sessionDir, entry);
|
|
38206
38418
|
try {
|
|
38207
38419
|
const s = statSync7(path);
|
|
38208
38420
|
if (!newest || s.mtimeMs > newest.mtimeMs) newest = { path, mtimeMs: s.mtimeMs };
|
|
@@ -38302,13 +38514,13 @@ function listCatalogModelsSync(providerName) {
|
|
|
38302
38514
|
|
|
38303
38515
|
// ../daemon/src/runtime/worker/spawn.ts
|
|
38304
38516
|
import { spawn } from "child_process";
|
|
38305
|
-
import { existsSync as
|
|
38517
|
+
import { existsSync as existsSync14 } from "fs";
|
|
38306
38518
|
import { createRequire } from "module";
|
|
38307
38519
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
38308
38520
|
var DEFAULT_KILL_GRACE_MS = 3e3;
|
|
38309
38521
|
var sourceEntryPath = fileURLToPath2(new URL("./entry.ts", import.meta.url));
|
|
38310
38522
|
var bundledEntryPath = fileURLToPath2(new URL("./worker.js", import.meta.url));
|
|
38311
|
-
var entryPath =
|
|
38523
|
+
var entryPath = existsSync14(sourceEntryPath) ? sourceEntryPath : bundledEntryPath;
|
|
38312
38524
|
var entryIsTs = entryPath.endsWith(".ts");
|
|
38313
38525
|
function workerSpawnArgs() {
|
|
38314
38526
|
if (!entryIsTs) return [entryPath];
|
|
@@ -38520,8 +38732,8 @@ async function resolveAgentApiKey(db, authToken, agent, opts = {}) {
|
|
|
38520
38732
|
|
|
38521
38733
|
// ../daemon/src/lib/attachments.ts
|
|
38522
38734
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
38523
|
-
import { mkdirSync as mkdirSync7, writeFileSync as
|
|
38524
|
-
import { join as
|
|
38735
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
38736
|
+
import { join as join17 } from "path";
|
|
38525
38737
|
var MAX_FILE_BYTES = 25 * 1024 * 1024;
|
|
38526
38738
|
function safeName(name) {
|
|
38527
38739
|
const base = name.split(/[\\/]/).pop() ?? "file";
|
|
@@ -38534,7 +38746,7 @@ function fmtBytes(n) {
|
|
|
38534
38746
|
}
|
|
38535
38747
|
function saveInputFiles(agentDir, files) {
|
|
38536
38748
|
if (!files || files.length === 0) return "";
|
|
38537
|
-
const dir =
|
|
38749
|
+
const dir = join17(agentDir, "uploads");
|
|
38538
38750
|
mkdirSync7(dir, { recursive: true });
|
|
38539
38751
|
const lines = [];
|
|
38540
38752
|
for (const f of files) {
|
|
@@ -38544,8 +38756,8 @@ function saveInputFiles(agentDir, files) {
|
|
|
38544
38756
|
lines.push(`[attachment "${label}" skipped: too large (${fmtBytes(buf.byteLength)} > 25 MB)]`);
|
|
38545
38757
|
continue;
|
|
38546
38758
|
}
|
|
38547
|
-
const path =
|
|
38548
|
-
|
|
38759
|
+
const path = join17(dir, `${randomUUID6().slice(0, 8)}-${safeName(label)}`);
|
|
38760
|
+
writeFileSync8(path, buf);
|
|
38549
38761
|
lines.push(
|
|
38550
38762
|
`[file saved to ${path} (${f.mimeType || "unknown"}, ${fmtBytes(buf.byteLength)}) \u2014 open it with your tools]`
|
|
38551
38763
|
);
|
|
@@ -39949,9 +40161,13 @@ function bootstrap(paths) {
|
|
|
39949
40161
|
}
|
|
39950
40162
|
const db = openDb(paths.db);
|
|
39951
40163
|
runMigrations(db);
|
|
39952
|
-
|
|
40164
|
+
const refreshed = refreshDefaultProfileTemplates(paths);
|
|
40165
|
+
if (refreshed.length) {
|
|
40166
|
+
console.log(`bazilion: refreshed default profile templates \u2014 [${refreshed.join(", ")}]`);
|
|
40167
|
+
}
|
|
40168
|
+
if (!existsSync15(paths.authFile)) {
|
|
39953
40169
|
const created = webTokens_exports.create(db, "bootstrap");
|
|
39954
|
-
|
|
40170
|
+
writeFileSync9(paths.authFile, `${JSON.stringify({ token: created.token }, null, 2)}
|
|
39955
40171
|
`, {
|
|
39956
40172
|
mode: 384
|
|
39957
40173
|
});
|
|
@@ -40026,8 +40242,8 @@ async function authMiddleware(c, next) {
|
|
|
40026
40242
|
}
|
|
40027
40243
|
|
|
40028
40244
|
// ../daemon/src/routes/agents.ts
|
|
40029
|
-
import { existsSync as
|
|
40030
|
-
import { join as
|
|
40245
|
+
import { existsSync as existsSync16, readdirSync as readdirSync9, readFileSync as readFileSync12, rmSync as rmSync9 } from "fs";
|
|
40246
|
+
import { join as join18 } from "path";
|
|
40031
40247
|
|
|
40032
40248
|
// ../../packages/api-types/src/entities.ts
|
|
40033
40249
|
var REASONING_LEVELS = [
|
|
@@ -41678,17 +41894,31 @@ async function pollLoop(handle12, db, initialOffset) {
|
|
|
41678
41894
|
let offset = initialOffset;
|
|
41679
41895
|
while (!handle12.stopRequested) {
|
|
41680
41896
|
let updates = [];
|
|
41897
|
+
const ac = new AbortController();
|
|
41898
|
+
const abortTimer = setTimeout(() => ac.abort(), (POLL_TIMEOUT_S + 5) * 1e3);
|
|
41681
41899
|
try {
|
|
41682
|
-
updates = await handle12.bot.api.getUpdates(
|
|
41683
|
-
|
|
41684
|
-
|
|
41685
|
-
|
|
41900
|
+
updates = await handle12.bot.api.getUpdates(
|
|
41901
|
+
{
|
|
41902
|
+
offset,
|
|
41903
|
+
timeout: POLL_TIMEOUT_S
|
|
41904
|
+
},
|
|
41905
|
+
// grammY's Node shim types the signal param as the `abort-controller`
|
|
41906
|
+
// package's AbortSignal, not the global one — runtime-compatible, so
|
|
41907
|
+
// cast through the method's own parameter type.
|
|
41908
|
+
ac.signal
|
|
41909
|
+
);
|
|
41686
41910
|
handle12.state.lastSuccessfulPollAt = Date.now();
|
|
41687
41911
|
handle12.state.error = null;
|
|
41688
41912
|
} catch (e) {
|
|
41689
41913
|
if (handle12.stopRequested) break;
|
|
41690
41914
|
const msg = errMsg2(e);
|
|
41691
41915
|
handle12.state.error = msg;
|
|
41916
|
+
if (ac.signal.aborted) {
|
|
41917
|
+
console.warn(
|
|
41918
|
+
`telegram: getUpdates exceeded ${POLL_TIMEOUT_S + 5}s (connection likely dropped) \u2014 retrying`
|
|
41919
|
+
);
|
|
41920
|
+
continue;
|
|
41921
|
+
}
|
|
41692
41922
|
if (e instanceof import_grammy2.GrammyError && e.error_code === 409) {
|
|
41693
41923
|
console.error("telegram: getUpdates 409 conflict (webhook or duplicate poller):", msg);
|
|
41694
41924
|
} else {
|
|
@@ -41696,6 +41926,8 @@ async function pollLoop(handle12, db, initialOffset) {
|
|
|
41696
41926
|
}
|
|
41697
41927
|
await sleep2(GETUPDATES_RETRY_MS);
|
|
41698
41928
|
continue;
|
|
41929
|
+
} finally {
|
|
41930
|
+
clearTimeout(abortTimer);
|
|
41699
41931
|
}
|
|
41700
41932
|
for (const u of updates) {
|
|
41701
41933
|
try {
|
|
@@ -41865,7 +42097,11 @@ function sanitizeAttachments(raw) {
|
|
|
41865
42097
|
agentsRouter.get("/", (c) => {
|
|
41866
42098
|
const includeArchived = c.req.query("includeArchived") === "true";
|
|
41867
42099
|
const { db, paths, authToken } = getCtx();
|
|
41868
|
-
|
|
42100
|
+
const agents = agents_exports.list(db, { includeArchived }).map((agent) => ({
|
|
42101
|
+
...agent,
|
|
42102
|
+
identity: loadIdentityFromFile(join18(agent.dir, "IDENTITY.md"))
|
|
42103
|
+
}));
|
|
42104
|
+
return c.json(agents);
|
|
41869
42105
|
});
|
|
41870
42106
|
agentsRouter.post("/", async (c) => {
|
|
41871
42107
|
const raw = await c.req.json().catch(() => null);
|
|
@@ -42257,7 +42493,7 @@ agentsRouter.post("/:id/chat/compact", async (c) => {
|
|
|
42257
42493
|
const id = resolveAgentIdParam(db, c.req.param("id"));
|
|
42258
42494
|
if (!agents_exports.get(db, id)) return c.json({ error: "agent not found" }, 404);
|
|
42259
42495
|
const resolved = resolveAgent(db, paths, id);
|
|
42260
|
-
const memory = qmdBackend(
|
|
42496
|
+
const memory = qmdBackend(join18(resolved.group.path, "memory"));
|
|
42261
42497
|
await memory.init();
|
|
42262
42498
|
const env = mergeSecretsIntoEnv(db, authToken);
|
|
42263
42499
|
const apiKeyResolution = await resolveAgentApiKey(db, authToken, resolved, {
|
|
@@ -42308,9 +42544,9 @@ agentsRouter.get("/:id/chat/context", async (c) => {
|
|
|
42308
42544
|
const resolved = resolveAgent(db, paths, id);
|
|
42309
42545
|
const files = [];
|
|
42310
42546
|
for (const file of CONTEXT_FILE_ORDER2) {
|
|
42311
|
-
const path =
|
|
42312
|
-
if (!
|
|
42313
|
-
const content =
|
|
42547
|
+
const path = join18(resolved.agent.dir, file);
|
|
42548
|
+
if (!existsSync16(path)) continue;
|
|
42549
|
+
const content = readFileSync12(path, "utf8").trimEnd();
|
|
42314
42550
|
if (!content) continue;
|
|
42315
42551
|
const chars = content.length + file.length + 6;
|
|
42316
42552
|
files.push({ name: file, chars, tokens: estimateTokens(chars) });
|
|
@@ -42334,7 +42570,7 @@ Read-only context about the human you're working with in this group. You cannot
|
|
|
42334
42570
|
|
|
42335
42571
|
${resolved.group.userMd.trim()}`.length : 0;
|
|
42336
42572
|
const memoryHintChars = "# Memory\n\nYou have a persistent memory backend. Use `memory_write` to remember things across sessions, and `memory_search` / `memory_read` / `memory_list` to recall them. Always check memory at the start of a session if the user might have told you something important before.".length;
|
|
42337
|
-
const memory = qmdBackend(
|
|
42573
|
+
const memory = qmdBackend(join18(resolved.group.path, "memory"));
|
|
42338
42574
|
await memory.init();
|
|
42339
42575
|
const env = mergeSecretsIntoEnv(db, authToken);
|
|
42340
42576
|
const apiKeyResolution = await resolveAgentApiKey(db, authToken, resolved, {
|
|
@@ -42375,7 +42611,7 @@ ${resolved.group.userMd.trim()}`.length : 0;
|
|
|
42375
42611
|
let blockChars = name.length + 2;
|
|
42376
42612
|
if (match) {
|
|
42377
42613
|
try {
|
|
42378
|
-
blockChars =
|
|
42614
|
+
blockChars = readFileSync12(match.skillFile, "utf8").length;
|
|
42379
42615
|
} catch {
|
|
42380
42616
|
}
|
|
42381
42617
|
}
|
|
@@ -42444,13 +42680,13 @@ agentsRouter.post("/:id/chat/reset", (c) => {
|
|
|
42444
42680
|
const id = resolveAgentIdParam(db, c.req.param("id"));
|
|
42445
42681
|
const agent = agents_exports.get(db, id);
|
|
42446
42682
|
if (!agent) return c.json({ error: "agent not found" }, 404);
|
|
42447
|
-
const sessionsDir =
|
|
42683
|
+
const sessionsDir = join18(paths.agentDir(agent.id), "sessions");
|
|
42448
42684
|
let deleted = 0;
|
|
42449
|
-
if (
|
|
42685
|
+
if (existsSync16(sessionsDir)) {
|
|
42450
42686
|
for (const file of readdirSync9(sessionsDir)) {
|
|
42451
42687
|
if (!file.endsWith(".jsonl")) continue;
|
|
42452
42688
|
try {
|
|
42453
|
-
rmSync9(
|
|
42689
|
+
rmSync9(join18(sessionsDir, file));
|
|
42454
42690
|
deleted++;
|
|
42455
42691
|
} catch {
|
|
42456
42692
|
}
|
|
@@ -42473,7 +42709,7 @@ agentsRouter.post("/:id/chat/truncate", async (c) => {
|
|
|
42473
42709
|
const id = resolveAgentIdParam(db, c.req.param("id"));
|
|
42474
42710
|
if (!agents_exports.get(db, id)) return c.json({ error: "agent not found" }, 404);
|
|
42475
42711
|
const resolved = resolveAgent(db, paths, id);
|
|
42476
|
-
const memory = qmdBackend(
|
|
42712
|
+
const memory = qmdBackend(join18(resolved.group.path, "memory"));
|
|
42477
42713
|
await memory.init();
|
|
42478
42714
|
const env = mergeSecretsIntoEnv(db, authToken);
|
|
42479
42715
|
const apiKeyResolution = await resolveAgentApiKey(db, authToken, resolved, {
|
|
@@ -42824,7 +43060,7 @@ function knownProviderRegistryNames() {
|
|
|
42824
43060
|
}
|
|
42825
43061
|
|
|
42826
43062
|
// ../daemon/src/routes/groups.ts
|
|
42827
|
-
import { join as
|
|
43063
|
+
import { join as join19 } from "path";
|
|
42828
43064
|
import { Hono as Hono4 } from "hono";
|
|
42829
43065
|
var USER_MD_MAX_BYTES2 = 12e3;
|
|
42830
43066
|
var groupsRouter = new Hono4();
|
|
@@ -42899,7 +43135,7 @@ async function openMemory(rawId) {
|
|
|
42899
43135
|
const { db, paths } = getCtx();
|
|
42900
43136
|
const group = groups_exports.get(db, rawId, paths);
|
|
42901
43137
|
if (!group) throw new Error(`group not found: ${rawId}`);
|
|
42902
|
-
const mem = qmdBackend(
|
|
43138
|
+
const mem = qmdBackend(join19(group.path, "memory"));
|
|
42903
43139
|
await mem.init();
|
|
42904
43140
|
return { mem, group };
|
|
42905
43141
|
}
|
|
@@ -43105,20 +43341,20 @@ messagesRouter.patch("/:id", async (c) => {
|
|
|
43105
43341
|
|
|
43106
43342
|
// ../daemon/src/routes/misc.ts
|
|
43107
43343
|
import { spawn as spawn4 } from "child_process";
|
|
43108
|
-
import { existsSync as
|
|
43344
|
+
import { existsSync as existsSync17 } from "fs";
|
|
43109
43345
|
import { homedir as homedir2 } from "os";
|
|
43110
|
-
import { join as
|
|
43346
|
+
import { join as join20 } from "path";
|
|
43111
43347
|
import { Hono as Hono7 } from "hono";
|
|
43112
43348
|
var miscRouter = new Hono7();
|
|
43113
43349
|
miscRouter.get("/health", (c) => {
|
|
43114
43350
|
const paths = resolvePaths();
|
|
43115
43351
|
const pathChecks = {
|
|
43116
|
-
home:
|
|
43117
|
-
db:
|
|
43118
|
-
auth:
|
|
43119
|
-
profiles:
|
|
43120
|
-
agents:
|
|
43121
|
-
skills:
|
|
43352
|
+
home: existsSync17(paths.home),
|
|
43353
|
+
db: existsSync17(paths.db),
|
|
43354
|
+
auth: existsSync17(paths.authFile),
|
|
43355
|
+
profiles: existsSync17(paths.profilesDir),
|
|
43356
|
+
agents: existsSync17(paths.agentsDir),
|
|
43357
|
+
skills: existsSync17(paths.skillsDir)
|
|
43122
43358
|
};
|
|
43123
43359
|
let database = null;
|
|
43124
43360
|
const triggersSection = { active: 0, disabled: 0 };
|
|
@@ -43166,7 +43402,7 @@ miscRouter.get("/health", (c) => {
|
|
|
43166
43402
|
}
|
|
43167
43403
|
const providerConfig = loadProviderConfigFromEnv(effectiveEnv, oauth);
|
|
43168
43404
|
const braveKey = effectiveEnv.BRAVE_API_KEY;
|
|
43169
|
-
const openclawSkillsDir =
|
|
43405
|
+
const openclawSkillsDir = join20(homedir2(), ".openclaw", "skills");
|
|
43170
43406
|
const CLOUD_KEYS = [
|
|
43171
43407
|
["anthropic", "anthropic"],
|
|
43172
43408
|
["openai", "openai"],
|
|
@@ -43204,7 +43440,7 @@ miscRouter.get("/health", (c) => {
|
|
|
43204
43440
|
},
|
|
43205
43441
|
openclaw: {
|
|
43206
43442
|
path: openclawSkillsDir,
|
|
43207
|
-
exists:
|
|
43443
|
+
exists: existsSync17(openclawSkillsDir)
|
|
43208
43444
|
},
|
|
43209
43445
|
triggers: triggersSection,
|
|
43210
43446
|
tokens: tokensSection,
|
|
@@ -43217,7 +43453,7 @@ miscRouter.get("/health", (c) => {
|
|
|
43217
43453
|
});
|
|
43218
43454
|
miscRouter.get("/backup", (c) => {
|
|
43219
43455
|
const paths = resolvePaths();
|
|
43220
|
-
if (!
|
|
43456
|
+
if (!existsSync17(paths.home)) {
|
|
43221
43457
|
return c.json({ error: `bazilion home not found at ${paths.home}` }, 404);
|
|
43222
43458
|
}
|
|
43223
43459
|
const proc = spawn4("tar", ["-czf", "-", "-C", paths.home, "."], {
|
|
@@ -43431,8 +43667,8 @@ profileGroupsRouter.post("/:id/spawn", async (c) => {
|
|
|
43431
43667
|
});
|
|
43432
43668
|
|
|
43433
43669
|
// ../daemon/src/routes/profiles.ts
|
|
43434
|
-
import { existsSync as
|
|
43435
|
-
import { join as
|
|
43670
|
+
import { existsSync as existsSync18, readFileSync as readFileSync13, writeFileSync as writeFileSync10 } from "fs";
|
|
43671
|
+
import { join as join21 } from "path";
|
|
43436
43672
|
import { Hono as Hono9 } from "hono";
|
|
43437
43673
|
var profilesRouter = new Hono9();
|
|
43438
43674
|
profilesRouter.get("/", (c) => {
|
|
@@ -43449,7 +43685,11 @@ profilesRouter.get("/_/templates", (c) => {
|
|
|
43449
43685
|
return c.json({
|
|
43450
43686
|
soul: DEFAULT_SOUL,
|
|
43451
43687
|
identity: DEFAULT_IDENTITY,
|
|
43452
|
-
bootstrap: DEFAULT_BOOTSTRAP
|
|
43688
|
+
bootstrap: DEFAULT_BOOTSTRAP,
|
|
43689
|
+
agents: DEFAULT_AGENTS,
|
|
43690
|
+
tools: DEFAULT_TOOLS,
|
|
43691
|
+
heartbeat: DEFAULT_HEARTBEAT,
|
|
43692
|
+
userMd: DEFAULT_USER_MD
|
|
43453
43693
|
});
|
|
43454
43694
|
});
|
|
43455
43695
|
profilesRouter.post("/", async (c) => {
|
|
@@ -43467,9 +43707,12 @@ profilesRouter.post("/", async (c) => {
|
|
|
43467
43707
|
if (raw.skipBootstrap === true || raw.bootstrap === null) templates.bootstrap = null;
|
|
43468
43708
|
else if (typeof raw.bootstrap === "string" && raw.bootstrap.length > 0)
|
|
43469
43709
|
templates.bootstrap = raw.bootstrap;
|
|
43470
|
-
if (
|
|
43471
|
-
if (typeof raw.
|
|
43472
|
-
if (
|
|
43710
|
+
if (raw.agents === null) templates.agents = null;
|
|
43711
|
+
else if (typeof raw.agents === "string" && raw.agents.length > 0) templates.agents = raw.agents;
|
|
43712
|
+
if (raw.tools === null) templates.tools = null;
|
|
43713
|
+
else if (typeof raw.tools === "string" && raw.tools.length > 0) templates.tools = raw.tools;
|
|
43714
|
+
if (raw.heartbeat === null) templates.heartbeat = null;
|
|
43715
|
+
else if (typeof raw.heartbeat === "string" && raw.heartbeat.length > 0)
|
|
43473
43716
|
templates.heartbeat = raw.heartbeat;
|
|
43474
43717
|
const { db, paths } = getCtx();
|
|
43475
43718
|
try {
|
|
@@ -43533,8 +43776,8 @@ profilesRouter.get("/:id/files/:file", (c) => {
|
|
|
43533
43776
|
return c.json({ error: `profile not found: ${c.req.param("id")}` }, 404);
|
|
43534
43777
|
const path = resolveFilePath(paths.profilesDir, c.req.param("id"), c.req.param("file"));
|
|
43535
43778
|
if (!path) return c.json({ error: `unsupported file: ${c.req.param("file")}` }, 400);
|
|
43536
|
-
if (!
|
|
43537
|
-
const body = { content:
|
|
43779
|
+
if (!existsSync18(path)) return c.json({ error: `file not present: ${c.req.param("file")}` }, 404);
|
|
43780
|
+
const body = { content: readFileSync13(path, "utf8") };
|
|
43538
43781
|
return c.json(body);
|
|
43539
43782
|
});
|
|
43540
43783
|
profilesRouter.put("/:id/files/:file", async (c) => {
|
|
@@ -43546,12 +43789,12 @@ profilesRouter.put("/:id/files/:file", async (c) => {
|
|
|
43546
43789
|
return c.json({ error: `profile not found: ${c.req.param("id")}` }, 404);
|
|
43547
43790
|
const path = resolveFilePath(paths.profilesDir, c.req.param("id"), c.req.param("file"));
|
|
43548
43791
|
if (!path) return c.json({ error: `unsupported file: ${c.req.param("file")}` }, 400);
|
|
43549
|
-
|
|
43792
|
+
writeFileSync10(path, body.content);
|
|
43550
43793
|
return c.body(null, 204);
|
|
43551
43794
|
});
|
|
43552
43795
|
function resolveFilePath(profilesDir, id, file) {
|
|
43553
43796
|
if (!PROFILE_FILES.includes(file)) return null;
|
|
43554
|
-
return
|
|
43797
|
+
return join21(profilesDir, id, file);
|
|
43555
43798
|
}
|
|
43556
43799
|
function csvToArray(v) {
|
|
43557
43800
|
if (Array.isArray(v)) return v.filter((s) => typeof s === "string");
|
|
@@ -43565,9 +43808,9 @@ function toSkillsMode(v) {
|
|
|
43565
43808
|
}
|
|
43566
43809
|
|
|
43567
43810
|
// ../daemon/src/routes/skills.ts
|
|
43568
|
-
import { existsSync as
|
|
43811
|
+
import { existsSync as existsSync19, mkdtempSync as mkdtempSync2, rmSync as rmSync10, writeFileSync as writeFileSync11 } from "fs";
|
|
43569
43812
|
import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
|
|
43570
|
-
import { join as
|
|
43813
|
+
import { join as join22 } from "path";
|
|
43571
43814
|
import { Hono as Hono10 } from "hono";
|
|
43572
43815
|
var MAX_ZIP_BYTES = 50 * 1024 * 1024;
|
|
43573
43816
|
var skillsRouter = new Hono10();
|
|
@@ -43596,7 +43839,7 @@ skillsRouter.delete("/:name", (c) => {
|
|
|
43596
43839
|
const { db, paths } = getCtx();
|
|
43597
43840
|
const name = c.req.param("name");
|
|
43598
43841
|
const dir = paths.skillDir(name);
|
|
43599
|
-
if (!
|
|
43842
|
+
if (!existsSync19(dir)) return c.json({ error: `skill not found: ${name}` }, 404);
|
|
43600
43843
|
rmSync10(dir, { recursive: true, force: true });
|
|
43601
43844
|
skillMeta_exports.remove(db, name);
|
|
43602
43845
|
return c.body(null, 204);
|
|
@@ -43638,10 +43881,10 @@ async function parseImportInput(request) {
|
|
|
43638
43881
|
if (!filename.toLowerCase().endsWith(".zip")) {
|
|
43639
43882
|
throw new Error("uploaded file must be a .zip archive");
|
|
43640
43883
|
}
|
|
43641
|
-
const tmpDir = mkdtempSync2(
|
|
43642
|
-
const zipPath =
|
|
43884
|
+
const tmpDir = mkdtempSync2(join22(tmpdir2(), "bazilion-skill-upload-"));
|
|
43885
|
+
const zipPath = join22(tmpDir, filename.replace(/[^\w.-]+/g, "_"));
|
|
43643
43886
|
const buf = Buffer.from(await file.arrayBuffer());
|
|
43644
|
-
|
|
43887
|
+
writeFileSync11(zipPath, buf);
|
|
43645
43888
|
const forceField = form.get("force");
|
|
43646
43889
|
return {
|
|
43647
43890
|
source: zipPath,
|
|
@@ -43654,7 +43897,7 @@ async function parseImportInput(request) {
|
|
|
43654
43897
|
if (!body) throw new Error("invalid JSON body");
|
|
43655
43898
|
const from = body.source ?? body.from;
|
|
43656
43899
|
if (typeof from !== "string" || !from) throw new Error("source is required");
|
|
43657
|
-
const source = from === "openclaw" ?
|
|
43900
|
+
const source = from === "openclaw" ? join22(homedir3(), ".openclaw", "skills") : from;
|
|
43658
43901
|
return {
|
|
43659
43902
|
source,
|
|
43660
43903
|
force: Boolean(body.force),
|