botinabox 0.2.5 → 0.3.1
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/CHANGELOG.md +9 -0
- package/dist/index.js +703 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,15 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [S
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.3.0] — 2026-04-03
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Domain tables** — `defineDomainTables(db, options?)` creates standard multi-agent app tables: org, project, client, invoice, repository, file, channel, rule, event + junction tables. Configurable: disable clients, repos, files, channels, rules, or events.
|
|
14
|
+
- **Domain entity contexts** — `defineDomainEntityContexts(db, options?)` renders per-entity context directories for all domain tables. Projects get REPOS.md + RULES.md. Clients get REPOS.md + AGENTS.md + INVOICES.md.
|
|
15
|
+
- **Claude stream parser** — `parseClaudeStream(stdout)` parses Claude CLI NDJSON output into structured results (session, model, cost, tokens, text, errors). Plus `isMaxTurns()`, `isLoginRequired()`, `deactivateLocalImagePaths()`.
|
|
16
|
+
- **Process env builder** — `buildProcessEnv(allowedKeys?, inject?)` creates a clean subprocess environment with only safe variables. Strips all secrets.
|
|
17
|
+
|
|
9
18
|
## [0.2.0] — 2026-04-03
|
|
10
19
|
|
|
11
20
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -830,14 +830,14 @@ var SessionManager = class {
|
|
|
830
830
|
async save(agentId, channelId, peerId, params) {
|
|
831
831
|
const existing = await this._find(agentId, channelId, peerId);
|
|
832
832
|
if (existing) {
|
|
833
|
-
await this.db.update("
|
|
833
|
+
await this.db.update("sessions", { id: existing["id"] }, {
|
|
834
834
|
context: JSON.stringify(params),
|
|
835
835
|
last_message_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
836
836
|
message_count: (existing["message_count"] ?? 0) + 1
|
|
837
837
|
});
|
|
838
838
|
return existing["id"];
|
|
839
839
|
} else {
|
|
840
|
-
const row = await this.db.insert("
|
|
840
|
+
const row = await this.db.insert("sessions", {
|
|
841
841
|
agent_id: agentId,
|
|
842
842
|
channel: channelId,
|
|
843
843
|
peer_id: peerId,
|
|
@@ -857,7 +857,7 @@ var SessionManager = class {
|
|
|
857
857
|
async clear(agentId, channelId, peerId) {
|
|
858
858
|
const session = await this._find(agentId, channelId, peerId);
|
|
859
859
|
if (session) {
|
|
860
|
-
await this.db.delete("
|
|
860
|
+
await this.db.delete("sessions", { id: session["id"] });
|
|
861
861
|
}
|
|
862
862
|
}
|
|
863
863
|
async shouldClear(session, opts) {
|
|
@@ -876,7 +876,7 @@ var SessionManager = class {
|
|
|
876
876
|
return false;
|
|
877
877
|
}
|
|
878
878
|
async _find(agentId, channelId, peerId) {
|
|
879
|
-
const rows = await this.db.query("
|
|
879
|
+
const rows = await this.db.query("sessions", {
|
|
880
880
|
where: { agent_id: agentId, channel: channelId, peer_id: peerId }
|
|
881
881
|
});
|
|
882
882
|
return rows[0] ?? void 0;
|
|
@@ -1369,20 +1369,41 @@ function defineCoreTables(db) {
|
|
|
1369
1369
|
]
|
|
1370
1370
|
});
|
|
1371
1371
|
db.define("messages", {
|
|
1372
|
+
columns: {
|
|
1373
|
+
id: "TEXT PRIMARY KEY",
|
|
1374
|
+
channel: "TEXT NOT NULL DEFAULT 'slack'",
|
|
1375
|
+
direction: "TEXT NOT NULL DEFAULT 'inbound'",
|
|
1376
|
+
from_user: "TEXT",
|
|
1377
|
+
from_agent: "TEXT",
|
|
1378
|
+
agent_id: "TEXT",
|
|
1379
|
+
user_id: "TEXT",
|
|
1380
|
+
body: "TEXT NOT NULL",
|
|
1381
|
+
thread_id: "TEXT",
|
|
1382
|
+
task_id: "TEXT",
|
|
1383
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1384
|
+
deleted_at: "TEXT"
|
|
1385
|
+
},
|
|
1386
|
+
tableConstraints: [
|
|
1387
|
+
"CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at)",
|
|
1388
|
+
"CREATE INDEX IF NOT EXISTS idx_messages_thread ON messages(thread_id)",
|
|
1389
|
+
"CREATE INDEX IF NOT EXISTS idx_messages_agent ON messages(agent_id)"
|
|
1390
|
+
]
|
|
1391
|
+
});
|
|
1392
|
+
db.define("sessions", {
|
|
1372
1393
|
columns: {
|
|
1373
1394
|
id: "TEXT PRIMARY KEY",
|
|
1374
1395
|
agent_id: "TEXT NOT NULL",
|
|
1375
1396
|
channel: "TEXT NOT NULL",
|
|
1376
1397
|
peer_id: "TEXT NOT NULL",
|
|
1377
1398
|
user_id: "TEXT",
|
|
1378
|
-
last_message_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1379
|
-
message_count: "INTEGER NOT NULL DEFAULT 0",
|
|
1380
1399
|
context: "TEXT NOT NULL DEFAULT '{}'",
|
|
1400
|
+
message_count: "INTEGER NOT NULL DEFAULT 0",
|
|
1401
|
+
last_message_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1381
1402
|
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1382
1403
|
expires_at: "TEXT"
|
|
1383
1404
|
},
|
|
1384
1405
|
tableConstraints: [
|
|
1385
|
-
"CREATE UNIQUE INDEX IF NOT EXISTS
|
|
1406
|
+
"CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_agent_channel_peer ON sessions(agent_id, channel, peer_id)"
|
|
1386
1407
|
]
|
|
1387
1408
|
});
|
|
1388
1409
|
db.define("skills", {
|
|
@@ -1671,6 +1692,550 @@ ${s.definition}` : null,
|
|
|
1671
1692
|
}
|
|
1672
1693
|
}
|
|
1673
1694
|
});
|
|
1695
|
+
db.defineEntityContext("messages", {
|
|
1696
|
+
table: "messages",
|
|
1697
|
+
directory: "messages",
|
|
1698
|
+
slugColumn: "id",
|
|
1699
|
+
indexFile: "messages/MESSAGES.md",
|
|
1700
|
+
indexRender: (rows) => {
|
|
1701
|
+
const active = rows.filter((r) => r.deleted_at == null);
|
|
1702
|
+
if (!active.length) return "# Messages\n\nNo messages.\n";
|
|
1703
|
+
const recent = active.slice(-100);
|
|
1704
|
+
const lines = recent.map((r) => {
|
|
1705
|
+
const dir = r.direction === "outbound" ? "\u2192" : "\u2190";
|
|
1706
|
+
const who = r.from_agent ?? r.from_user ?? "unknown";
|
|
1707
|
+
const time = (r.created_at ?? "").slice(0, 16);
|
|
1708
|
+
const preview = (r.body ?? "").slice(0, 80);
|
|
1709
|
+
return `- ${dir} **${who}** (${time}): ${preview}`;
|
|
1710
|
+
});
|
|
1711
|
+
return `# Messages
|
|
1712
|
+
|
|
1713
|
+
Last ${lines.length} messages:
|
|
1714
|
+
|
|
1715
|
+
${lines.join("\n")}
|
|
1716
|
+
`;
|
|
1717
|
+
},
|
|
1718
|
+
files: {
|
|
1719
|
+
"MESSAGE.md": {
|
|
1720
|
+
source: { type: "self" },
|
|
1721
|
+
render: (rows) => {
|
|
1722
|
+
const m = rows[0];
|
|
1723
|
+
if (!m) return "";
|
|
1724
|
+
return [
|
|
1725
|
+
"# Message",
|
|
1726
|
+
"",
|
|
1727
|
+
`**Direction:** ${m.direction}`,
|
|
1728
|
+
m.from_user ? `**From User:** ${m.from_user}` : null,
|
|
1729
|
+
m.from_agent ? `**From Agent:** ${m.from_agent}` : null,
|
|
1730
|
+
`**Channel:** ${m.channel}`,
|
|
1731
|
+
m.thread_id ? `**Thread:** ${m.thread_id}` : null,
|
|
1732
|
+
m.task_id ? `**Task:** ${m.task_id}` : null,
|
|
1733
|
+
`**Time:** ${m.created_at}`,
|
|
1734
|
+
"",
|
|
1735
|
+
"---",
|
|
1736
|
+
"",
|
|
1737
|
+
m.body,
|
|
1738
|
+
""
|
|
1739
|
+
].filter(Boolean).join("\n");
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// src/core/data/domain-schema.ts
|
|
1747
|
+
function defineDomainTables(db, options = {}) {
|
|
1748
|
+
const opts = {
|
|
1749
|
+
clients: true,
|
|
1750
|
+
repositories: true,
|
|
1751
|
+
files: true,
|
|
1752
|
+
channels: true,
|
|
1753
|
+
rules: true,
|
|
1754
|
+
events: true,
|
|
1755
|
+
...options
|
|
1756
|
+
};
|
|
1757
|
+
db.define("org", {
|
|
1758
|
+
columns: {
|
|
1759
|
+
id: "TEXT PRIMARY KEY",
|
|
1760
|
+
name: "TEXT NOT NULL",
|
|
1761
|
+
type: "TEXT NOT NULL DEFAULT 'company'",
|
|
1762
|
+
description: "TEXT",
|
|
1763
|
+
mission: "TEXT",
|
|
1764
|
+
website: "TEXT",
|
|
1765
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1766
|
+
updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1767
|
+
deleted_at: "TEXT"
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
db.define("project", {
|
|
1771
|
+
columns: {
|
|
1772
|
+
id: "TEXT PRIMARY KEY",
|
|
1773
|
+
org_id: "TEXT NOT NULL",
|
|
1774
|
+
name: "TEXT NOT NULL",
|
|
1775
|
+
status: "TEXT",
|
|
1776
|
+
description: "TEXT",
|
|
1777
|
+
tech_stack: "TEXT",
|
|
1778
|
+
github_repo: "TEXT",
|
|
1779
|
+
deploy_target: "TEXT",
|
|
1780
|
+
production_url: "TEXT",
|
|
1781
|
+
branch_strategy: "TEXT",
|
|
1782
|
+
notes: "TEXT",
|
|
1783
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1784
|
+
updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1785
|
+
deleted_at: "TEXT"
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
db.define("agent_project", {
|
|
1789
|
+
columns: {
|
|
1790
|
+
agent_id: "TEXT NOT NULL",
|
|
1791
|
+
project_id: "TEXT NOT NULL",
|
|
1792
|
+
role: "TEXT",
|
|
1793
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
|
|
1794
|
+
},
|
|
1795
|
+
primaryKey: ["agent_id", "project_id"]
|
|
1796
|
+
});
|
|
1797
|
+
db.define("secret_project", {
|
|
1798
|
+
columns: {
|
|
1799
|
+
secret_id: "TEXT NOT NULL",
|
|
1800
|
+
project_id: "TEXT NOT NULL",
|
|
1801
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
|
|
1802
|
+
},
|
|
1803
|
+
primaryKey: ["secret_id", "project_id"]
|
|
1804
|
+
});
|
|
1805
|
+
db.define("secret_org", {
|
|
1806
|
+
columns: {
|
|
1807
|
+
secret_id: "TEXT NOT NULL",
|
|
1808
|
+
org_id: "TEXT NOT NULL",
|
|
1809
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
|
|
1810
|
+
},
|
|
1811
|
+
primaryKey: ["secret_id", "org_id"]
|
|
1812
|
+
});
|
|
1813
|
+
if (opts.clients) {
|
|
1814
|
+
db.define("client", {
|
|
1815
|
+
columns: {
|
|
1816
|
+
id: "TEXT PRIMARY KEY",
|
|
1817
|
+
org_id: "TEXT NOT NULL",
|
|
1818
|
+
name: "TEXT NOT NULL",
|
|
1819
|
+
contact_name: "TEXT",
|
|
1820
|
+
contact_email: "TEXT",
|
|
1821
|
+
phone: "TEXT",
|
|
1822
|
+
status: "TEXT NOT NULL DEFAULT 'active'",
|
|
1823
|
+
notes: "TEXT",
|
|
1824
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1825
|
+
updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1826
|
+
deleted_at: "TEXT"
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
db.define("invoice", {
|
|
1830
|
+
columns: {
|
|
1831
|
+
id: "TEXT PRIMARY KEY",
|
|
1832
|
+
org_id: "TEXT NOT NULL",
|
|
1833
|
+
client_id: "TEXT NOT NULL",
|
|
1834
|
+
number: "TEXT",
|
|
1835
|
+
amount_cents: "INTEGER",
|
|
1836
|
+
currency: "TEXT DEFAULT 'USD'",
|
|
1837
|
+
status: "TEXT NOT NULL DEFAULT 'draft'",
|
|
1838
|
+
description: "TEXT",
|
|
1839
|
+
hours: "REAL",
|
|
1840
|
+
rate_cents: "INTEGER",
|
|
1841
|
+
items_json: "TEXT",
|
|
1842
|
+
issued_at: "TEXT",
|
|
1843
|
+
due_at: "TEXT",
|
|
1844
|
+
paid_at: "TEXT",
|
|
1845
|
+
notes: "TEXT",
|
|
1846
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1847
|
+
updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1848
|
+
deleted_at: "TEXT"
|
|
1849
|
+
},
|
|
1850
|
+
tableConstraints: [
|
|
1851
|
+
"CREATE INDEX IF NOT EXISTS idx_invoice_client ON invoice(client_id)"
|
|
1852
|
+
]
|
|
1853
|
+
});
|
|
1854
|
+
db.define("agent_client", {
|
|
1855
|
+
columns: {
|
|
1856
|
+
agent_id: "TEXT NOT NULL",
|
|
1857
|
+
client_id: "TEXT NOT NULL",
|
|
1858
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
|
|
1859
|
+
},
|
|
1860
|
+
primaryKey: ["agent_id", "client_id"]
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
if (opts.repositories) {
|
|
1864
|
+
db.define("repository", {
|
|
1865
|
+
columns: {
|
|
1866
|
+
id: "TEXT PRIMARY KEY",
|
|
1867
|
+
org_id: "TEXT NOT NULL",
|
|
1868
|
+
project_id: "TEXT",
|
|
1869
|
+
client_id: "TEXT",
|
|
1870
|
+
name: "TEXT NOT NULL",
|
|
1871
|
+
url: "TEXT",
|
|
1872
|
+
local_path: "TEXT",
|
|
1873
|
+
default_branch: "TEXT DEFAULT 'main'",
|
|
1874
|
+
platform: "TEXT DEFAULT 'github'",
|
|
1875
|
+
status: "TEXT NOT NULL DEFAULT 'active'",
|
|
1876
|
+
notes: "TEXT",
|
|
1877
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1878
|
+
updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1879
|
+
deleted_at: "TEXT"
|
|
1880
|
+
},
|
|
1881
|
+
tableConstraints: [
|
|
1882
|
+
"CREATE INDEX IF NOT EXISTS idx_repository_project ON repository(project_id)",
|
|
1883
|
+
"CREATE INDEX IF NOT EXISTS idx_repository_client ON repository(client_id)"
|
|
1884
|
+
]
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
if (opts.files) {
|
|
1888
|
+
db.define("file", {
|
|
1889
|
+
columns: {
|
|
1890
|
+
id: "TEXT PRIMARY KEY",
|
|
1891
|
+
org_id: "TEXT",
|
|
1892
|
+
name: "TEXT NOT NULL",
|
|
1893
|
+
file_path: "TEXT",
|
|
1894
|
+
mime_type: "TEXT",
|
|
1895
|
+
size_bytes: "INTEGER",
|
|
1896
|
+
project_id: "TEXT",
|
|
1897
|
+
access_level: "TEXT NOT NULL DEFAULT 'org'",
|
|
1898
|
+
description: "TEXT",
|
|
1899
|
+
notes: "TEXT",
|
|
1900
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1901
|
+
updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1902
|
+
deleted_at: "TEXT"
|
|
1903
|
+
}
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
if (opts.channels) {
|
|
1907
|
+
db.define("channel", {
|
|
1908
|
+
columns: {
|
|
1909
|
+
id: "TEXT PRIMARY KEY",
|
|
1910
|
+
org_id: "TEXT",
|
|
1911
|
+
platform: "TEXT NOT NULL",
|
|
1912
|
+
external_id: "TEXT",
|
|
1913
|
+
name: "TEXT NOT NULL",
|
|
1914
|
+
type: "TEXT NOT NULL DEFAULT 'channel'",
|
|
1915
|
+
instructions: "TEXT",
|
|
1916
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1917
|
+
updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1918
|
+
deleted_at: "TEXT"
|
|
1919
|
+
}
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
if (opts.rules) {
|
|
1923
|
+
db.define("rule", {
|
|
1924
|
+
columns: {
|
|
1925
|
+
id: "TEXT PRIMARY KEY",
|
|
1926
|
+
org_id: "TEXT",
|
|
1927
|
+
title: "TEXT NOT NULL",
|
|
1928
|
+
rule_text: "TEXT NOT NULL",
|
|
1929
|
+
scope: "TEXT NOT NULL DEFAULT 'org'",
|
|
1930
|
+
category: "TEXT NOT NULL DEFAULT 'process'",
|
|
1931
|
+
priority: "INTEGER NOT NULL DEFAULT 50",
|
|
1932
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1933
|
+
updated_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1934
|
+
deleted_at: "TEXT"
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
db.define("rule_agent", {
|
|
1938
|
+
columns: {
|
|
1939
|
+
rule_id: "TEXT NOT NULL",
|
|
1940
|
+
agent_id: "TEXT NOT NULL",
|
|
1941
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
|
|
1942
|
+
},
|
|
1943
|
+
primaryKey: ["rule_id", "agent_id"]
|
|
1944
|
+
});
|
|
1945
|
+
db.define("rule_project", {
|
|
1946
|
+
columns: {
|
|
1947
|
+
rule_id: "TEXT NOT NULL",
|
|
1948
|
+
project_id: "TEXT NOT NULL",
|
|
1949
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
|
|
1950
|
+
},
|
|
1951
|
+
primaryKey: ["rule_id", "project_id"]
|
|
1952
|
+
});
|
|
1953
|
+
db.define("rule_org", {
|
|
1954
|
+
columns: {
|
|
1955
|
+
rule_id: "TEXT NOT NULL",
|
|
1956
|
+
org_id: "TEXT NOT NULL",
|
|
1957
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP"
|
|
1958
|
+
},
|
|
1959
|
+
primaryKey: ["rule_id", "org_id"]
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
if (opts.events) {
|
|
1963
|
+
db.define("event", {
|
|
1964
|
+
columns: {
|
|
1965
|
+
id: "TEXT PRIMARY KEY",
|
|
1966
|
+
org_id: "TEXT",
|
|
1967
|
+
type: "TEXT NOT NULL",
|
|
1968
|
+
summary: "TEXT NOT NULL",
|
|
1969
|
+
details: "TEXT",
|
|
1970
|
+
severity: "TEXT NOT NULL DEFAULT 'info'",
|
|
1971
|
+
actor_agent_id: "TEXT",
|
|
1972
|
+
actor_user_id: "TEXT",
|
|
1973
|
+
project_id: "TEXT",
|
|
1974
|
+
channel_id: "TEXT",
|
|
1975
|
+
created_at: "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
1976
|
+
deleted_at: "TEXT"
|
|
1977
|
+
},
|
|
1978
|
+
tableConstraints: [
|
|
1979
|
+
"CREATE INDEX IF NOT EXISTS idx_event_type ON event(type, created_at)",
|
|
1980
|
+
"CREATE INDEX IF NOT EXISTS idx_event_project ON event(project_id)"
|
|
1981
|
+
]
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// src/core/data/domain-entity-contexts.ts
|
|
1987
|
+
function defineDomainEntityContexts(db, options = {}) {
|
|
1988
|
+
const opts = {
|
|
1989
|
+
clients: true,
|
|
1990
|
+
repositories: true,
|
|
1991
|
+
files: true,
|
|
1992
|
+
channels: true,
|
|
1993
|
+
rules: true,
|
|
1994
|
+
...options
|
|
1995
|
+
};
|
|
1996
|
+
db.defineEntityContext("org", {
|
|
1997
|
+
table: "org",
|
|
1998
|
+
directory: "orgs",
|
|
1999
|
+
slugColumn: "name",
|
|
2000
|
+
indexFile: "orgs/ORGS.md",
|
|
2001
|
+
files: {
|
|
2002
|
+
"ORG.md": {
|
|
2003
|
+
source: { type: "self" },
|
|
2004
|
+
render: (rows) => {
|
|
2005
|
+
const o = rows[0];
|
|
2006
|
+
if (!o) return "";
|
|
2007
|
+
return [
|
|
2008
|
+
`# ${o.name}`,
|
|
2009
|
+
"",
|
|
2010
|
+
o.type ? `**Type:** ${o.type}` : null,
|
|
2011
|
+
o.description ? `
|
|
2012
|
+
${o.description}` : null,
|
|
2013
|
+
o.mission ? `
|
|
2014
|
+
**Mission:** ${o.mission}` : null,
|
|
2015
|
+
o.website ? `**Website:** ${o.website}` : null,
|
|
2016
|
+
""
|
|
2017
|
+
].filter(Boolean).join("\n");
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
});
|
|
2022
|
+
db.defineEntityContext("project", {
|
|
2023
|
+
table: "project",
|
|
2024
|
+
directory: "projects",
|
|
2025
|
+
slugColumn: "name",
|
|
2026
|
+
indexFile: "projects/PROJECTS.md",
|
|
2027
|
+
files: {
|
|
2028
|
+
"PROJECT.md": {
|
|
2029
|
+
source: { type: "self" },
|
|
2030
|
+
render: (rows) => {
|
|
2031
|
+
const p = rows[0];
|
|
2032
|
+
if (!p) return "";
|
|
2033
|
+
return [
|
|
2034
|
+
`# ${p.name}`,
|
|
2035
|
+
"",
|
|
2036
|
+
p.status ? `**Status:** ${p.status}` : null,
|
|
2037
|
+
p.description ? `
|
|
2038
|
+
${p.description}` : null,
|
|
2039
|
+
p.tech_stack ? `
|
|
2040
|
+
**Tech Stack:** ${p.tech_stack}` : null,
|
|
2041
|
+
p.production_url ? `**URL:** ${p.production_url}` : null,
|
|
2042
|
+
p.github_repo ? `**GitHub:** ${p.github_repo}` : null,
|
|
2043
|
+
p.deploy_target ? `**Deploy:** ${p.deploy_target}` : null,
|
|
2044
|
+
p.branch_strategy ? `**Branch Strategy:** ${p.branch_strategy}` : null,
|
|
2045
|
+
p.notes ? `
|
|
2046
|
+
**Notes:**
|
|
2047
|
+
${p.notes}` : null,
|
|
2048
|
+
""
|
|
2049
|
+
].filter(Boolean).join("\n");
|
|
2050
|
+
}
|
|
2051
|
+
},
|
|
2052
|
+
...opts.repositories ? {
|
|
2053
|
+
"REPOS.md": {
|
|
2054
|
+
source: {
|
|
2055
|
+
type: "hasMany",
|
|
2056
|
+
table: "repository",
|
|
2057
|
+
foreignKey: "project_id"
|
|
2058
|
+
},
|
|
2059
|
+
render: (rows) => {
|
|
2060
|
+
if (!rows.length) return "";
|
|
2061
|
+
const lines = rows.map(
|
|
2062
|
+
(r) => `- **${r.name}** \u2014 ${r.url ?? ""}`
|
|
2063
|
+
);
|
|
2064
|
+
return `# Repositories
|
|
2065
|
+
|
|
2066
|
+
${lines.join("\n")}
|
|
2067
|
+
`;
|
|
2068
|
+
},
|
|
2069
|
+
omitIfEmpty: true
|
|
2070
|
+
}
|
|
2071
|
+
} : {},
|
|
2072
|
+
...opts.rules ? {
|
|
2073
|
+
"RULES.md": {
|
|
2074
|
+
source: {
|
|
2075
|
+
type: "manyToMany",
|
|
2076
|
+
junctionTable: "rule_project",
|
|
2077
|
+
localKey: "project_id",
|
|
2078
|
+
remoteKey: "rule_id",
|
|
2079
|
+
remoteTable: "rule",
|
|
2080
|
+
softDelete: true,
|
|
2081
|
+
orderBy: "priority"
|
|
2082
|
+
},
|
|
2083
|
+
render: (rows) => {
|
|
2084
|
+
if (!rows.length) return "";
|
|
2085
|
+
const lines = rows.map(
|
|
2086
|
+
(r) => `### ${r.title}
|
|
2087
|
+
${r.rule_text}`
|
|
2088
|
+
);
|
|
2089
|
+
return `# Project Rules
|
|
2090
|
+
|
|
2091
|
+
${lines.join("\n\n")}
|
|
2092
|
+
`;
|
|
2093
|
+
},
|
|
2094
|
+
omitIfEmpty: true
|
|
2095
|
+
}
|
|
2096
|
+
} : {}
|
|
2097
|
+
}
|
|
2098
|
+
});
|
|
2099
|
+
if (opts.clients) {
|
|
2100
|
+
db.defineEntityContext("client", {
|
|
2101
|
+
table: "client",
|
|
2102
|
+
directory: "clients",
|
|
2103
|
+
slugColumn: "name",
|
|
2104
|
+
indexFile: "clients/CLIENTS.md",
|
|
2105
|
+
files: {
|
|
2106
|
+
"CLIENT.md": {
|
|
2107
|
+
source: { type: "self" },
|
|
2108
|
+
render: (rows) => {
|
|
2109
|
+
const c = rows[0];
|
|
2110
|
+
if (!c) return "";
|
|
2111
|
+
return [
|
|
2112
|
+
`# ${c.name}`,
|
|
2113
|
+
"",
|
|
2114
|
+
c.contact_name ? `**Contact:** ${c.contact_name}` : null,
|
|
2115
|
+
c.contact_email ? `**Email:** ${c.contact_email}` : null,
|
|
2116
|
+
c.phone ? `**Phone:** ${c.phone}` : null,
|
|
2117
|
+
c.status ? `**Status:** ${c.status}` : null,
|
|
2118
|
+
c.notes ? `
|
|
2119
|
+
${c.notes}` : null,
|
|
2120
|
+
""
|
|
2121
|
+
].filter(Boolean).join("\n");
|
|
2122
|
+
}
|
|
2123
|
+
},
|
|
2124
|
+
...opts.repositories ? {
|
|
2125
|
+
"REPOS.md": {
|
|
2126
|
+
source: {
|
|
2127
|
+
type: "hasMany",
|
|
2128
|
+
table: "repository",
|
|
2129
|
+
foreignKey: "client_id"
|
|
2130
|
+
},
|
|
2131
|
+
render: (rows) => {
|
|
2132
|
+
if (!rows.length) return "";
|
|
2133
|
+
const lines = rows.map(
|
|
2134
|
+
(r) => `- **${r.name}** \u2014 ${r.url ?? ""}`
|
|
2135
|
+
);
|
|
2136
|
+
return `# Repositories
|
|
2137
|
+
|
|
2138
|
+
${lines.join("\n")}
|
|
2139
|
+
`;
|
|
2140
|
+
},
|
|
2141
|
+
omitIfEmpty: true
|
|
2142
|
+
}
|
|
2143
|
+
} : {},
|
|
2144
|
+
AGENTS: {
|
|
2145
|
+
source: {
|
|
2146
|
+
type: "manyToMany",
|
|
2147
|
+
junctionTable: "agent_client",
|
|
2148
|
+
localKey: "client_id",
|
|
2149
|
+
remoteKey: "agent_id",
|
|
2150
|
+
remoteTable: "agents"
|
|
2151
|
+
},
|
|
2152
|
+
render: (rows) => {
|
|
2153
|
+
if (!rows.length) return "";
|
|
2154
|
+
const lines = rows.map(
|
|
2155
|
+
(r) => `- **${r.name}** (${r.role ?? "agent"})`
|
|
2156
|
+
);
|
|
2157
|
+
return `# Assigned Agents
|
|
2158
|
+
|
|
2159
|
+
${lines.join("\n")}
|
|
2160
|
+
`;
|
|
2161
|
+
},
|
|
2162
|
+
omitIfEmpty: true
|
|
2163
|
+
},
|
|
2164
|
+
"INVOICES.md": {
|
|
2165
|
+
source: {
|
|
2166
|
+
type: "hasMany",
|
|
2167
|
+
table: "invoice",
|
|
2168
|
+
foreignKey: "client_id"
|
|
2169
|
+
},
|
|
2170
|
+
render: (rows) => {
|
|
2171
|
+
if (!rows.length) return "";
|
|
2172
|
+
const lines = rows.map((r) => {
|
|
2173
|
+
const amt = r.amount_cents ? `$${(r.amount_cents / 100).toFixed(2)}` : "TBD";
|
|
2174
|
+
return `- **${r.number ?? "Draft"}** \u2014 ${amt} (${r.status})${r.description ? ": " + r.description : ""}`;
|
|
2175
|
+
});
|
|
2176
|
+
return `# Invoices
|
|
2177
|
+
|
|
2178
|
+
${lines.join("\n")}
|
|
2179
|
+
`;
|
|
2180
|
+
},
|
|
2181
|
+
omitIfEmpty: true
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
2186
|
+
if (opts.files) {
|
|
2187
|
+
db.defineEntityContext("file", {
|
|
2188
|
+
table: "file",
|
|
2189
|
+
directory: "files",
|
|
2190
|
+
slugColumn: "name",
|
|
2191
|
+
indexFile: "files/FILES.md",
|
|
2192
|
+
files: {
|
|
2193
|
+
"FILE.md": {
|
|
2194
|
+
source: { type: "self" },
|
|
2195
|
+
render: (rows) => {
|
|
2196
|
+
const f = rows[0];
|
|
2197
|
+
if (!f) return "";
|
|
2198
|
+
return [
|
|
2199
|
+
`# ${f.name}`,
|
|
2200
|
+
"",
|
|
2201
|
+
f.mime_type ? `**Type:** ${f.mime_type}` : null,
|
|
2202
|
+
f.access_level ? `**Access:** ${f.access_level}` : null,
|
|
2203
|
+
f.file_path ? `**Path:** ${f.file_path}` : null,
|
|
2204
|
+
f.description ? `
|
|
2205
|
+
${f.description}` : null,
|
|
2206
|
+
""
|
|
2207
|
+
].filter(Boolean).join("\n");
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2213
|
+
if (opts.channels) {
|
|
2214
|
+
db.defineEntityContext("channel", {
|
|
2215
|
+
table: "channel",
|
|
2216
|
+
directory: "channels",
|
|
2217
|
+
slugColumn: "name",
|
|
2218
|
+
indexFile: "channels/CHANNELS.md",
|
|
2219
|
+
files: {
|
|
2220
|
+
"CHANNEL.md": {
|
|
2221
|
+
source: { type: "self" },
|
|
2222
|
+
render: (rows) => {
|
|
2223
|
+
const c = rows[0];
|
|
2224
|
+
if (!c) return "";
|
|
2225
|
+
return [
|
|
2226
|
+
`# ${c.name}`,
|
|
2227
|
+
"",
|
|
2228
|
+
c.platform ? `**Platform:** ${c.platform}` : null,
|
|
2229
|
+
c.type ? `**Type:** ${c.type}` : null,
|
|
2230
|
+
c.instructions ? `
|
|
2231
|
+
${c.instructions}` : null,
|
|
2232
|
+
""
|
|
2233
|
+
].filter(Boolean).join("\n");
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
1674
2239
|
}
|
|
1675
2240
|
|
|
1676
2241
|
// src/core/security/sanitizer.ts
|
|
@@ -1756,6 +2321,44 @@ var AuditEmitter = class {
|
|
|
1756
2321
|
}
|
|
1757
2322
|
};
|
|
1758
2323
|
|
|
2324
|
+
// src/core/security/process-env.ts
|
|
2325
|
+
var DEFAULT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
2326
|
+
"PATH",
|
|
2327
|
+
"HOME",
|
|
2328
|
+
"USER",
|
|
2329
|
+
"SHELL",
|
|
2330
|
+
"LANG",
|
|
2331
|
+
"TERM",
|
|
2332
|
+
"TMPDIR",
|
|
2333
|
+
"XDG_RUNTIME_DIR",
|
|
2334
|
+
"NODE_ENV",
|
|
2335
|
+
// Git
|
|
2336
|
+
"GIT_AUTHOR_NAME",
|
|
2337
|
+
"GIT_AUTHOR_EMAIL",
|
|
2338
|
+
"GIT_COMMITTER_NAME",
|
|
2339
|
+
"GIT_COMMITTER_EMAIL",
|
|
2340
|
+
// Homebrew / system
|
|
2341
|
+
"HOMEBREW_PREFIX",
|
|
2342
|
+
"HOMEBREW_CELLAR",
|
|
2343
|
+
"HOMEBREW_REPOSITORY"
|
|
2344
|
+
]);
|
|
2345
|
+
function buildProcessEnv(allowedKeys, inject) {
|
|
2346
|
+
const allowed = new Set(DEFAULT_ALLOWED_KEYS);
|
|
2347
|
+
if (allowedKeys) {
|
|
2348
|
+
for (const k of allowedKeys) allowed.add(k);
|
|
2349
|
+
}
|
|
2350
|
+
const env = {};
|
|
2351
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
2352
|
+
if (allowed.has(key) && value !== void 0) {
|
|
2353
|
+
env[key] = value;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
if (inject) {
|
|
2357
|
+
Object.assign(env, inject);
|
|
2358
|
+
}
|
|
2359
|
+
return env;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
1759
2362
|
// src/core/update/version-utils.ts
|
|
1760
2363
|
function parseVersion(v) {
|
|
1761
2364
|
const cleaned = v.replace(/^v/, "").split("-")[0] ?? v;
|
|
@@ -3233,6 +3836,92 @@ var SecretStore = class {
|
|
|
3233
3836
|
};
|
|
3234
3837
|
}
|
|
3235
3838
|
};
|
|
3839
|
+
|
|
3840
|
+
// src/core/orchestrator/claude-stream-parser.ts
|
|
3841
|
+
function parseClaudeStream(stdout) {
|
|
3842
|
+
let sessionId = null;
|
|
3843
|
+
let model = null;
|
|
3844
|
+
let costUsd = null;
|
|
3845
|
+
let usage = null;
|
|
3846
|
+
let isError = false;
|
|
3847
|
+
let errorMessage = null;
|
|
3848
|
+
let stopReason = null;
|
|
3849
|
+
const textBlocks = [];
|
|
3850
|
+
for (const line of stdout.split("\n")) {
|
|
3851
|
+
if (!line.trim()) continue;
|
|
3852
|
+
let event;
|
|
3853
|
+
try {
|
|
3854
|
+
event = JSON.parse(line);
|
|
3855
|
+
} catch {
|
|
3856
|
+
continue;
|
|
3857
|
+
}
|
|
3858
|
+
const type = event.type;
|
|
3859
|
+
if (type === "system" && event.subtype === "init") {
|
|
3860
|
+
sessionId = event.session_id ?? null;
|
|
3861
|
+
model = event.model ?? null;
|
|
3862
|
+
}
|
|
3863
|
+
if (type === "assistant") {
|
|
3864
|
+
const msg = event.message;
|
|
3865
|
+
const content = msg?.content ?? event.content;
|
|
3866
|
+
if (Array.isArray(content)) {
|
|
3867
|
+
for (const block of content) {
|
|
3868
|
+
if (block.type === "text" && block.text) {
|
|
3869
|
+
textBlocks.push(block.text);
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
if (type === "result") {
|
|
3875
|
+
isError = !!event.is_error;
|
|
3876
|
+
stopReason = event.stop_reason ?? null;
|
|
3877
|
+
costUsd = typeof event.total_cost_usd === "number" ? event.total_cost_usd : null;
|
|
3878
|
+
const u = event.usage;
|
|
3879
|
+
if (u) {
|
|
3880
|
+
usage = {
|
|
3881
|
+
inputTokens: u.input_tokens ?? 0,
|
|
3882
|
+
cachedInputTokens: u.cache_read_input_tokens ?? 0,
|
|
3883
|
+
outputTokens: u.output_tokens ?? 0
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
if (isError) {
|
|
3887
|
+
errorMessage = event.error ?? event.result ?? "Unknown error";
|
|
3888
|
+
}
|
|
3889
|
+
const resultContent = event.result;
|
|
3890
|
+
if (typeof resultContent === "string" && resultContent) {
|
|
3891
|
+
textBlocks.push(resultContent);
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
return {
|
|
3896
|
+
sessionId,
|
|
3897
|
+
model,
|
|
3898
|
+
costUsd,
|
|
3899
|
+
usage,
|
|
3900
|
+
summary: textBlocks.join("\n"),
|
|
3901
|
+
isError,
|
|
3902
|
+
errorMessage,
|
|
3903
|
+
stopReason
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
function isMaxTurns(parsed) {
|
|
3907
|
+
return parsed.stopReason === "max_turns" || parsed.stopReason === "tool_use";
|
|
3908
|
+
}
|
|
3909
|
+
function isLoginRequired(stdout) {
|
|
3910
|
+
const patterns = [
|
|
3911
|
+
"not logged in",
|
|
3912
|
+
"login required",
|
|
3913
|
+
"authentication required",
|
|
3914
|
+
"please log in"
|
|
3915
|
+
];
|
|
3916
|
+
const lower = stdout.toLowerCase();
|
|
3917
|
+
return patterns.some((p) => lower.includes(p));
|
|
3918
|
+
}
|
|
3919
|
+
function deactivateLocalImagePaths(prompt) {
|
|
3920
|
+
return prompt.replace(
|
|
3921
|
+
/(?<=\s|^)(\/[\w./-]+\.(?:png|jpg|jpeg|gif|webp|svg))(?=\s|$)/gi,
|
|
3922
|
+
"[image-path:$1]"
|
|
3923
|
+
);
|
|
3924
|
+
}
|
|
3236
3925
|
export {
|
|
3237
3926
|
AGENT_STATUSES,
|
|
3238
3927
|
AgentRegistry,
|
|
@@ -3275,6 +3964,7 @@ export {
|
|
|
3275
3964
|
areDependenciesMet,
|
|
3276
3965
|
buildAgentBindings,
|
|
3277
3966
|
buildChainOrigin,
|
|
3967
|
+
buildProcessEnv,
|
|
3278
3968
|
checkAllowlist,
|
|
3279
3969
|
checkChainDepth,
|
|
3280
3970
|
checkMentionGate,
|
|
@@ -3282,8 +3972,11 @@ export {
|
|
|
3282
3972
|
classifyUpdate,
|
|
3283
3973
|
compareVersions,
|
|
3284
3974
|
createConfigRevision,
|
|
3975
|
+
deactivateLocalImagePaths,
|
|
3285
3976
|
defineCoreEntityContexts,
|
|
3286
3977
|
defineCoreTables,
|
|
3978
|
+
defineDomainEntityContexts,
|
|
3979
|
+
defineDomainTables,
|
|
3287
3980
|
detectCycle,
|
|
3288
3981
|
discoverChannels,
|
|
3289
3982
|
discoverProviders,
|
|
@@ -3292,7 +3985,10 @@ export {
|
|
|
3292
3985
|
initConfig,
|
|
3293
3986
|
interpolate,
|
|
3294
3987
|
interpolateEnv,
|
|
3988
|
+
isLoginRequired,
|
|
3989
|
+
isMaxTurns,
|
|
3295
3990
|
loadConfig,
|
|
3991
|
+
parseClaudeStream,
|
|
3296
3992
|
parseVersion,
|
|
3297
3993
|
runPackageMigrations,
|
|
3298
3994
|
sanitize,
|